QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
qgs3dexportobject.cpp
Go to the documentation of this file.
1/***************************************************************************
2 Qgs3DExportObject.cpp
3 --------------------------------------
4 Date : June 2020
5 Copyright : (C) 2020 by Belgacem Nedjima
6 Email : gb underscore nedjima at esi dot dz
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgs3dexportobject.h"
17
19#include "qgslogger.h"
20
21#include <QDir>
22#include <QImage>
23#include <QMatrix4x4>
24#include <QString>
25#include <QVector3D>
26#include <Qt3DCore/QAttribute>
27#include <Qt3DCore/QBuffer>
28
29using namespace Qt::StringLiterals;
30
31void Qgs3DExportObject::setupPositionCoordinates( const QVector<float> &positionsBuffer, const QMatrix4x4 &transform )
32{
33 mVertexPosition.clear();
34 for ( int i = 0; i < positionsBuffer.size(); i += 3 )
35 {
36 const QVector3D position( positionsBuffer[i], positionsBuffer[i + 1], positionsBuffer[i + 2] );
37 const QVector3D positionFinal = transform.map( position );
38 mVertexPosition << positionFinal.x() << positionFinal.y() << positionFinal.z();
39 }
40}
41
42void Qgs3DExportObject::setupTriangle( const QVector<float> &positionsBuffer, const QVector<uint> &facesIndexes, const QMatrix4x4 &transform )
43{
45 setupPositionCoordinates( positionsBuffer, transform );
46
47 // setup faces
48 mIndexes.clear();
49 for ( int i = 0; i < facesIndexes.size(); i += 3 )
50 {
51 if ( i + 2 >= facesIndexes.size() )
52 continue;
53 // skip invalid triangles
54 if ( facesIndexes[i] == facesIndexes[i + 1] || facesIndexes[i + 1] == facesIndexes[i + 2] || facesIndexes[i] == facesIndexes[i + 2] )
55 continue;
56 for ( int j = 0; j < 3; ++j )
57 mIndexes << facesIndexes[i + j];
58 }
59}
60
61void Qgs3DExportObject::setupLine( const QVector<float> &positionsBuffer )
62{
64 setupPositionCoordinates( positionsBuffer );
65
66 // setup indexes
67 mIndexes.clear();
68 for ( int i = 0; i < mVertexPosition.size(); i += 3 )
69 mIndexes << i / 3 + 1;
70}
71
72void Qgs3DExportObject::setupPoint( const QVector<float> &positionsBuffer )
73{
75 setupPositionCoordinates( positionsBuffer );
76}
77
78void Qgs3DExportObject::setupNormalCoordinates( const QVector<float> &normalsBuffer, const QMatrix4x4 &transform )
79{
80 mNormals.clear();
81
82 // Qt does not provide QMatrix3x3 * QVector3D multiplication so we use QMatrix4x4
83 QMatrix3x3 normal3x3 = transform.normalMatrix();
84 // clang-format off
85 QMatrix4x4 normal4x4( normal3x3( 0, 0 ), normal3x3( 0, 1 ), normal3x3( 0, 2 ), 0,
86 normal3x3( 1, 0 ), normal3x3( 1, 1 ), normal3x3( 1, 2 ), 0,
87 normal3x3( 2, 0 ), normal3x3( 2, 1 ), normal3x3( 2, 2 ), 0,
88 0, 0, 0, 1 );
89 // clang-format on
90
91 for ( int i = 0; i < normalsBuffer.size(); i += 3 )
92 {
93 const QVector3D normalVector( normalsBuffer[i], normalsBuffer[i + 1], normalsBuffer[i + 2] );
94 QVector3D v = normal4x4.mapVector( normalVector );
95 // round numbers very close to zero to avoid tiny numbers like 6e-8 in export
96 if ( qgsFloatNear( v.x(), 0 ) )
97 v.setX( 0 );
98 if ( qgsFloatNear( v.y(), 0 ) )
99 v.setY( 0 );
100 if ( qgsFloatNear( v.z(), 0 ) )
101 v.setZ( 0 );
102 mNormals << v.x() << v.y() << v.z();
103 }
104}
105
106void Qgs3DExportObject::setupTextureCoordinates( const QVector<float> &texturesBuffer )
107{
108 mTexturesUV.clear();
109 mTexturesUV << texturesBuffer;
110}
111
113{
114 mMaterialParameters.clear();
115 QMap<QString, QString> parameters = material->toExportParameters();
116 for ( auto it = parameters.begin(); it != parameters.end(); ++it )
117 {
118 mMaterialParameters[it.key()] = it.value();
119 }
120}
121
122void Qgs3DExportObject::objectBounds( float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ ) const
123{
124 if ( mType != TriangularFaces )
125 return;
126 for ( const unsigned int vertice : qAsConst( mIndexes ) )
127 {
128 const int heightIndex = static_cast<int>( vertice ) * 3 + 1;
129 minX = std::min( minX, mVertexPosition[heightIndex - 1] );
130 maxX = std::max( maxX, mVertexPosition[heightIndex - 1] );
131 minY = std::min( minY, mVertexPosition[heightIndex] );
132 maxY = std::max( maxY, mVertexPosition[heightIndex] );
133 minZ = std::min( minZ, mVertexPosition[heightIndex + 1] );
134 maxZ = std::max( maxZ, mVertexPosition[heightIndex + 1] );
135 }
136}
137
138void Qgs3DExportObject::saveTo( QTextStream &out, float scale, const QVector3D &center, int precision ) const
139{
140 // Set groups
141 // turns out grouping doest work as expected in blender
142 out << qSetRealNumberPrecision( precision );
143
144 // smoothen edges
145 if ( mSmoothEdges )
146 out << "s on\n";
147 else
148 out << "s off\n";
149
150 // Construct vertices
151 // As we can have holes in the face list and we only write vertices from these faces
152 // then the vertex list in the obj is not the whole from mVertexPosition!
153 for ( const unsigned int vertice : qAsConst( mIndexes ) )
154 {
155 const int i = static_cast<int>( vertice * 3 );
156 // for now just ignore wrong vertex positions
157 out << "v ";
158 out << ( mVertexPosition[i] - center.x() ) / scale << " ";
159 out << ( mVertexPosition[i + 1] - center.y() ) / scale << " ";
160 out << ( mVertexPosition[i + 2] - center.z() ) / scale << "\n";
161 if ( i + 3 <= mNormals.size() )
162 {
163 out << "vn " << mNormals[i] << " " << mNormals[i + 1] << " " << mNormals[i + 2] << "\n";
164 }
165 const int u_index = i / 3 * 2;
166 if ( u_index + 1 < mTexturesUV.size() )
167 {
168 // TODO: flip texture in a more appropriate way (for repeated textures)
169 out << "vt " << mTexturesUV[u_index] << " " << 1.0f - mTexturesUV[u_index + 1] << "\n";
170 }
171 }
172
173 bool hasTextures = mTexturesUV.size() == mVertexPosition.size() / 3 * 2;
174 // if the object has normals then the normals and positions buffers should be the same size
175 bool hasNormals = mNormals.size() == mVertexPosition.size();
176
177 if ( !hasNormals && !mNormals.empty() )
178 {
179 QgsDebugError( "Vertex normals count and vertex positions count are different" );
180 }
181 const int verticesCount = mIndexes.size();
182
183 // we use negative indexes as this is the way to use relative values to reference vertex positions
184 // Positive values are absolute vertex position from the beginning of the file.
185 auto getVertexIndex = [&]( unsigned int i ) -> QString {
186 const int negativeIndex = static_cast<int>( i - verticesCount );
187 if ( hasNormals && !hasTextures )
188 return u"%1//%2"_s.arg( negativeIndex ).arg( negativeIndex );
189 if ( !hasNormals && hasTextures )
190 return u"%1/%2"_s.arg( negativeIndex ).arg( negativeIndex );
191 if ( hasNormals && hasTextures )
192 return u"%1/%2/%3"_s.arg( negativeIndex ).arg( negativeIndex ).arg( negativeIndex );
193 return QString::number( negativeIndex );
194 };
195
196 if ( mType == TriangularFaces )
197 {
198 // Construct triangular faces
199 // As we have "compressed" the vertex/normal section above by using only the vertices referenced by the faces
200 // we do not need to the 'mIndexes[i]' value but only the 'i' value.
201 for ( int i = 0; i < mIndexes.size(); i += 3 )
202 {
203 out << "f " << getVertexIndex( i );
204 out << " " << getVertexIndex( i + 1 );
205 out << " " << getVertexIndex( i + 2 );
206 out << "\n";
207 }
208 }
209 else if ( mType == LineStrip )
210 {
211 out << "l";
212 for ( const unsigned int i : qAsConst( mIndexes ) )
213 out << " " << getVertexIndex( i );
214 out << "\n";
215 }
216 else if ( mType == Points )
217 {
218 out << "p";
219 for ( const unsigned int i : qAsConst( mIndexes ) )
220 out << " " << getVertexIndex( i );
221 out << "\n";
222 }
223}
224
225QString Qgs3DExportObject::saveMaterial( QTextStream &mtlOut, const QString &folderPath ) const
226{
227 QString materialName = mName + "_material";
228 if ( mMaterialParameters.size() == 0 && ( mTexturesUV.size() == 0 || mTextureImage.isNull() ) )
229 return QString();
230 mtlOut << "newmtl " << materialName << "\n";
231 if ( mTexturesUV.size() != 0 && !mTextureImage.isNull() )
232 {
233 const QString filePath = QDir( folderPath ).filePath( materialName + ".jpg" );
234 mTextureImage.save( filePath, "JPG" );
235 mtlOut << "\tmap_Kd " << materialName << ".jpg" << "\n";
236 }
237 for ( auto it = mMaterialParameters.constBegin(); it != mMaterialParameters.constEnd(); it++ )
238 {
239 mtlOut << "\t" << it.key() << " " << it.value() << "\n";
240 }
241 mtlOut << "\tillum 2\n";
242 return materialName;
243}
void setupPoint(const QVector< float > &positionsBuffer)
sets point positions coordinates
QString saveMaterial(QTextStream &mtlOut, const QString &folder) const
saves the texture of the object and material information
void setupMaterial(QgsAbstractMaterialSettings *material)
Sets the material parameters (diffuse color, shininess...) to be exported in the ....
void setupTriangle(const QVector< float > &positionsBuffer, const QVector< uint > &facesIndexes, const QMatrix4x4 &transform)
sets triangle indexes and positions coordinates
void setupNormalCoordinates(const QVector< float > &normalsBuffer, const QMatrix4x4 &transform)
Sets normal coordinates for each vertex.
void saveTo(QTextStream &out, float scale, const QVector3D &center, int precision=6) const
Saves the current object to the output stream while scaling the object and centering it to be visible...
void objectBounds(float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ) const
Updates the box bounds explained with the current object bounds This expands the bounding box if the ...
void setupLine(const QVector< float > &positionsBuffer)
sets line indexes and positions coordinates
void setupTextureCoordinates(const QVector< float > &texturesBuffer)
Sets texture coordinates for each vertex.
Abstract base class for material settings.
virtual QMap< QString, QString > toExportParameters() const =0
Returns the parameters to be exported to .mtl file.
bool qgsFloatNear(float a, float b, float epsilon=4 *FLT_EPSILON)
Compare two floats (but allow some difference).
Definition qgis.h:6986
#define QgsDebugError(str)
Definition qgslogger.h:59