QGIS API Documentation 4.1.0-Master (31622b25bb0)
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
18#include "qgs3d.h"
20#include "qgslogger.h"
22
23#include <QDir>
24#include <QImage>
25#include <QMatrix4x4>
26#include <QString>
27#include <QVector3D>
28#include <Qt3DCore/QAttribute>
29#include <Qt3DCore/QBuffer>
30
31using namespace Qt::StringLiterals;
32
33void Qgs3DExportObject::setupPositionCoordinates( const QVector<float> &positionsBuffer, const QMatrix4x4 &transform )
34{
35 mVertexPosition.clear();
36 for ( int i = 0; i < positionsBuffer.size(); i += 3 )
37 {
38 const QVector3D position( positionsBuffer[i], positionsBuffer[i + 1], positionsBuffer[i + 2] );
39 const QVector3D positionFinal = transform.map( position );
40 mVertexPosition << positionFinal.x() << positionFinal.y() << positionFinal.z();
41 }
42}
43
44void Qgs3DExportObject::setupTriangle( const QVector<float> &positionsBuffer, const QVector<uint> &facesIndexes, const QMatrix4x4 &transform )
45{
47 setupPositionCoordinates( positionsBuffer, transform );
48
49 // setup faces
50 mIndexes.clear();
51 for ( int i = 0; i < facesIndexes.size(); i += 3 )
52 {
53 if ( i + 2 >= facesIndexes.size() )
54 continue;
55 // skip invalid triangles
56 if ( facesIndexes[i] == facesIndexes[i + 1] || facesIndexes[i + 1] == facesIndexes[i + 2] || facesIndexes[i] == facesIndexes[i + 2] )
57 continue;
58 for ( int j = 0; j < 3; ++j )
59 mIndexes << facesIndexes[i + j];
60 }
61}
62
63void Qgs3DExportObject::setupLine( const QVector<float> &positionsBuffer )
64{
66 setupPositionCoordinates( positionsBuffer );
67
68 // setup indexes
69 mIndexes.clear();
70 for ( int i = 0; i < mVertexPosition.size(); i += 3 )
71 mIndexes << i / 3 + 1;
72}
73
74void Qgs3DExportObject::setupPoint( const QVector<float> &positionsBuffer )
75{
77 setupPositionCoordinates( positionsBuffer );
78}
79
80void Qgs3DExportObject::setupNormalCoordinates( const QVector<float> &normalsBuffer, const QMatrix4x4 &transform )
81{
82 mNormals.clear();
83
84 // Qt does not provide QMatrix3x3 * QVector3D multiplication so we use QMatrix4x4
85 QMatrix3x3 normal3x3 = transform.normalMatrix();
86 // clang-format off
87 QMatrix4x4 normal4x4( normal3x3( 0, 0 ), normal3x3( 0, 1 ), normal3x3( 0, 2 ), 0,
88 normal3x3( 1, 0 ), normal3x3( 1, 1 ), normal3x3( 1, 2 ), 0,
89 normal3x3( 2, 0 ), normal3x3( 2, 1 ), normal3x3( 2, 2 ), 0,
90 0, 0, 0, 1 );
91 // clang-format on
92
93 for ( int i = 0; i < normalsBuffer.size(); i += 3 )
94 {
95 const QVector3D normalVector( normalsBuffer[i], normalsBuffer[i + 1], normalsBuffer[i + 2] );
96 QVector3D v = normal4x4.mapVector( normalVector );
97 // round numbers very close to zero to avoid tiny numbers like 6e-8 in export
98 if ( qgsFloatNear( v.x(), 0 ) )
99 v.setX( 0 );
100 if ( qgsFloatNear( v.y(), 0 ) )
101 v.setY( 0 );
102 if ( qgsFloatNear( v.z(), 0 ) )
103 v.setZ( 0 );
104 mNormals << v.x() << v.y() << v.z();
105 }
106}
107
108void Qgs3DExportObject::setupTextureCoordinates( const QVector<float> &texturesBuffer )
109{
110 mTexturesUV.clear();
111 mTexturesUV << texturesBuffer;
112}
113
115{
116 mMaterialParameters.clear();
117
118 QMap<QString, QString> parameters;
119 if ( const QgsAbstractMaterial3DHandler *handler = Qgs3D::handlerForMaterialSettings( material ) )
120 {
121 parameters = handler->toExportParameters( material );
122 }
123
124 for ( auto it = parameters.begin(); it != parameters.end(); ++it )
125 {
126 mMaterialParameters[it.key()] = it.value();
127 }
128}
129
130void Qgs3DExportObject::objectBounds( float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ ) const
131{
132 if ( mType != TriangularFaces )
133 return;
134 for ( const unsigned int vertice : std::as_const( mIndexes ) )
135 {
136 const int heightIndex = static_cast<int>( vertice ) * 3 + 1;
137 minX = std::min( minX, mVertexPosition[heightIndex - 1] );
138 maxX = std::max( maxX, mVertexPosition[heightIndex - 1] );
139 minY = std::min( minY, mVertexPosition[heightIndex] );
140 maxY = std::max( maxY, mVertexPosition[heightIndex] );
141 minZ = std::min( minZ, mVertexPosition[heightIndex + 1] );
142 maxZ = std::max( maxZ, mVertexPosition[heightIndex + 1] );
143 }
144}
145
146void Qgs3DExportObject::saveTo( QTextStream &out, float scale, const QVector3D &center, const Qgis::Export3DSceneFormat &exportFormat, int precision, QString materialName ) const
147{
148 switch ( exportFormat )
149 {
151 saveToObj( out, scale, center, precision, materialName );
152 return;
154 saveToStl( out, scale, center, precision );
155 return;
156 }
157
159}
160
161void Qgs3DExportObject::saveToObj( QTextStream &out, float scale, const QVector3D &center, int precision, QString materialName ) const
162{
163 // Set object name
164 out << "o " << mName << "\n";
165
166 // Set material name
167 if ( !materialName.isEmpty() )
168 out << "usemtl " << materialName << "\n";
169
170 // Set groups
171 // turns out grouping doest work as expected in blender
172 out << qSetRealNumberPrecision( precision );
173
174 // smoothen edges
175 if ( mSmoothEdges )
176 out << "s on\n";
177 else
178 out << "s off\n";
179
180 // Construct vertices
181 // As we can have holes in the face list and we only write vertices from these faces
182 // then the vertex list in the obj is not the whole from mVertexPosition!
183 for ( const unsigned int vertice : std::as_const( mIndexes ) )
184 {
185 const int i = static_cast<int>( vertice * 3 );
186 // for now just ignore wrong vertex positions
187 out << "v ";
188 out << ( mVertexPosition[i] - center.x() ) / scale << " ";
189 out << ( mVertexPosition[i + 1] - center.y() ) / scale << " ";
190 out << ( mVertexPosition[i + 2] - center.z() ) / scale << "\n";
191 if ( i + 3 <= mNormals.size() )
192 {
193 out << "vn " << mNormals[i] << " " << mNormals[i + 1] << " " << mNormals[i + 2] << "\n";
194 }
195 const int u_index = i / 3 * 2;
196 if ( u_index + 1 < mTexturesUV.size() )
197 {
198 // TODO: flip texture in a more appropriate way (for repeated textures)
199 out << "vt " << mTexturesUV[u_index] << " " << 1.0f - mTexturesUV[u_index + 1] << "\n";
200 }
201 }
202
203 bool hasTextures = mTexturesUV.size() == mVertexPosition.size() / 3 * 2;
204 // if the object has normals then the normals and positions buffers should be the same size
205 bool hasNormals = mNormals.size() == mVertexPosition.size();
206
207 if ( !hasNormals && !mNormals.empty() )
208 {
209 QgsDebugError( "Vertex normals count and vertex positions count are different" );
210 }
211 const unsigned int verticesCount = mIndexes.size();
212
213 // we use negative indexes as this is the way to use relative values to reference vertex positions
214 // Positive values are absolute vertex position from the beginning of the file.
215 auto getVertexIndex = [&]( unsigned int i ) -> QString {
216 const int negativeIndex = static_cast<int>( i - verticesCount );
217 if ( hasNormals && !hasTextures )
218 return u"%1//%2"_s.arg( negativeIndex ).arg( negativeIndex );
219 if ( !hasNormals && hasTextures )
220 return u"%1/%2"_s.arg( negativeIndex ).arg( negativeIndex );
221 if ( hasNormals && hasTextures )
222 return u"%1/%2/%3"_s.arg( negativeIndex ).arg( negativeIndex ).arg( negativeIndex );
223 return QString::number( negativeIndex );
224 };
225
226 if ( mType == TriangularFaces )
227 {
228 // Construct triangular faces
229 // As we have "compressed" the vertex/normal section above by using only the vertices referenced by the faces
230 // we do not need to the 'mIndexes[i]' value but only the 'i' value.
231 for ( int i = 0; i < mIndexes.size(); i += 3 )
232 {
233 out << "f " << getVertexIndex( i );
234 out << " " << getVertexIndex( i + 1 );
235 out << " " << getVertexIndex( i + 2 );
236 out << "\n";
237 }
238 }
239 else if ( mType == LineStrip )
240 {
241 out << "l";
242 for ( const unsigned int i : std::as_const( mIndexes ) )
243 out << " " << getVertexIndex( i );
244 out << "\n";
245 }
246 else if ( mType == Points )
247 {
248 out << "p";
249 for ( const unsigned int i : std::as_const( mIndexes ) )
250 out << " " << getVertexIndex( i );
251 out << "\n";
252 }
253}
254
255void Qgs3DExportObject::saveToStl( QTextStream &out, float scale, const QVector3D &center, int precision ) const
256{
257 if ( mType == LineStrip || mType == Points )
258 {
259 QgsDebugMsgLevel( u"Cannot export object %s in %s type. Only triangular type is handled by STL export"_s.arg( mName ).arg( mType ), 3 );
260 return;
261 }
262
263 // Set object name
264 out << "solid " << mName << "\n";
265
266 out << qSetRealNumberPrecision( precision );
267
268 for ( int i = 0; i < mIndexes.size(); i += 3 )
269 {
270 // Vertices of the triangle
271 unsigned int i0 = mIndexes[i] * 3;
272 unsigned int i1 = mIndexes[i + 1] * 3;
273 unsigned int i2 = mIndexes[i + 2] * 3;
274
275 QVector3D v0( ( mVertexPosition[i0] - center.x() ) / scale, ( mVertexPosition[i0 + 1] - center.y() ) / scale, ( mVertexPosition[i0 + 2] - center.z() ) / scale );
276 QVector3D v1( ( mVertexPosition[i1] - center.x() ) / scale, ( mVertexPosition[i1 + 1] - center.y() ) / scale, ( mVertexPosition[i1 + 2] - center.z() ) / scale );
277 QVector3D v2( ( mVertexPosition[i2] - center.x() ) / scale, ( mVertexPosition[i2 + 1] - center.y() ) / scale, ( mVertexPosition[i2 + 2] - center.z() ) / scale );
278
279 QVector3D normal = QVector3D::crossProduct( v1 - v0, v2 - v0 ).normalized();
280
281 out << " facet normal " << normal.x() << " " << normal.y() << " " << normal.z() << "\n";
282 out << " outer loop\n";
283 out << " vertex " << v0.x() << " " << v0.y() << " " << v0.z() << "\n";
284 out << " vertex " << v1.x() << " " << v1.y() << " " << v1.z() << "\n";
285 out << " vertex " << v2.x() << " " << v2.y() << " " << v2.z() << "\n";
286 out << " endloop\n";
287 out << " endfacet\n";
288 }
289
290 out << "endsolid " << mName << "\n";
291}
292
293QString Qgs3DExportObject::saveMaterial( QTextStream &mtlOut, const QString &folderPath ) const
294{
295 QString materialName = mName + "_material";
296 if ( mMaterialParameters.size() == 0 && ( mTexturesUV.size() == 0 || mTextureImage.isNull() ) )
297 return QString();
298 mtlOut << "newmtl " << materialName << "\n";
299 if ( mTexturesUV.size() != 0 && !mTextureImage.isNull() )
300 {
301 const QString filePath = QDir( folderPath ).filePath( materialName + ".jpg" );
302 mTextureImage.save( filePath, "JPG" );
303 mtlOut << "\tmap_Kd " << materialName << ".jpg" << "\n";
304 }
305 for ( auto it = mMaterialParameters.constBegin(); it != mMaterialParameters.constEnd(); it++ )
306 {
307 mtlOut << "\t" << it.key() << " " << it.value() << "\n";
308 }
309 mtlOut << "\tillum 2\n";
310 return materialName;
311}
Export3DSceneFormat
The file format used when exporting a 3D scene.
Definition qgis.h:4481
@ StlAscii
STL ascii format.
Definition qgis.h:4483
@ Obj
Wavefront OBJ format.
Definition qgis.h:4482
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 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.
void saveTo(QTextStream &out, float scale, const QVector3D &center, const Qgis::Export3DSceneFormat &exportFormat=Qgis::Export3DSceneFormat::Obj, int precision=6, QString materialName=QString()) const
Exports the 3D object to an OBJ or STL compatible output stream.
static const QgsAbstractMaterial3DHandler * handlerForMaterialSettings(const QgsAbstractMaterialSettings *settings)
Returns the handler to use for a material settings.
Definition qgs3d.cpp:135
Abstract base class for material 3D handlers.
Abstract base class for material settings.
#define BUILTIN_UNREACHABLE
Definition qgis.h:7802
bool qgsFloatNear(float a, float b, float epsilon=4 *FLT_EPSILON)
Compare two floats (but allow some difference).
Definition qgis.h:7233
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59