QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 <QVector3D>
19#include <QDir>
20#include <QImage>
21#include <QMatrix4x4>
22
23#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
24#include <Qt3DRender/QAttribute>
25#include <Qt3DRender/QBuffer>
26typedef Qt3DRender::QAttribute Qt3DQAttribute;
27typedef Qt3DRender::QBuffer Qt3DQBuffer;
28#else
29#include <Qt3DCore/QAttribute>
30#include <Qt3DCore/QBuffer>
31typedef Qt3DCore::QAttribute Qt3DQAttribute;
32typedef Qt3DCore::QBuffer Qt3DQBuffer;
33#endif
34
35#include "qgslogger.h"
37
38
39template<typename T>
40void insertIndexData( QVector<uint> &vertexIndex, const QVector<T> &faceIndex )
41{
42 for ( int i = 0; i < faceIndex.size(); i += 3 )
43 {
44 if ( i + 2 >= faceIndex.size() )
45 continue;
46 // skip invalid triangles
47 if ( faceIndex[i] == faceIndex[i + 1] || faceIndex[i + 1] == faceIndex[i + 2] || faceIndex[i] == faceIndex[i + 2] )
48 continue;
49 for ( int j = 0; j < 3; ++j )
50 vertexIndex << faceIndex[i + j];
51 }
52}
53
54void Qgs3DExportObject::setupPositionCoordinates( const QVector<float> &positionsBuffer, const QMatrix4x4 &transform )
55{
56 for ( int i = 0; i < positionsBuffer.size(); i += 3 )
57 {
58 const QVector3D position( positionsBuffer[i], positionsBuffer[i + 1], positionsBuffer[i + 2] );
59 const QVector3D positionFinal = transform.map( position );
60 mVertexPosition << positionFinal.x() << positionFinal.y() << positionFinal.z();
61 }
62}
63
64void Qgs3DExportObject::setupFaces( const QVector<uint> &facesIndexes )
65{
66 insertIndexData<uint>( mIndexes, facesIndexes );
67}
68
69void Qgs3DExportObject::setupLine( const QVector<uint> &lineIndexes )
70{
71 Q_UNUSED( lineIndexes );
72 for ( int i = 0; i < mVertexPosition.size(); i += 3 )
73 mIndexes << i / 3 + 1;
74}
75
76void Qgs3DExportObject::setupNormalCoordinates( const QVector<float> &normalsBuffer, const QMatrix4x4 &transform )
77{
78 // Qt does not provide QMatrix3x3 * QVector3D multiplication so we use QMatrix4x4
79 QMatrix3x3 normal3x3 = transform.normalMatrix();
80 QMatrix4x4 normal4x4( normal3x3( 0, 0 ), normal3x3( 0, 1 ), normal3x3( 0, 2 ), 0, normal3x3( 1, 0 ), normal3x3( 1, 1 ), normal3x3( 1, 2 ), 0, normal3x3( 2, 0 ), normal3x3( 2, 1 ), normal3x3( 2, 2 ), 0, 0, 0, 0, 1 );
81
82 for ( int i = 0; i < normalsBuffer.size(); i += 3 )
83 {
84 const QVector3D normalVector( normalsBuffer[i], normalsBuffer[i + 1], normalsBuffer[i + 2] );
85 QVector3D v = normal4x4.mapVector( normalVector );
86 // round numbers very close to zero to avoid tiny numbers like 6e-8 in export
87 if ( qgsFloatNear( v.x(), 0 ) )
88 v.setX( 0 );
89 if ( qgsFloatNear( v.y(), 0 ) )
90 v.setY( 0 );
91 if ( qgsFloatNear( v.z(), 0 ) )
92 v.setZ( 0 );
93 mNormals << v.x() << v.y() << v.z();
94 }
95}
96
97void Qgs3DExportObject::setupTextureCoordinates( const QVector<float> &texturesBuffer )
98{
99 mTexturesUV << texturesBuffer;
100}
101
103{
104 QMap<QString, QString> parameters = material->toExportParameters();
105 for ( auto it = parameters.begin(); it != parameters.end(); ++it )
106 {
107 setMaterialParameter( it.key(), it.value() );
108 }
109}
110
111void Qgs3DExportObject::objectBounds( float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ )
112{
113 if ( mType != TriangularFaces )
114 return;
115 for ( const unsigned int vertice : qAsConst( mIndexes ) )
116 {
117 const int heightIndex = static_cast<int>( vertice ) * 3 + 1;
118 minX = std::min( minX, mVertexPosition[heightIndex - 1] );
119 maxX = std::max( maxX, mVertexPosition[heightIndex - 1] );
120 minY = std::min( minY, mVertexPosition[heightIndex] );
121 maxY = std::max( maxY, mVertexPosition[heightIndex] );
122 minZ = std::min( minZ, mVertexPosition[heightIndex + 1] );
123 maxZ = std::max( maxZ, mVertexPosition[heightIndex + 1] );
124 }
125}
126
127void Qgs3DExportObject::saveTo( QTextStream &out, float scale, const QVector3D &center, int precision )
128{
129 // Set groups
130 // turns out grouping doest work as expected in blender
131 out << qSetRealNumberPrecision( precision );
132
133 // smoothen edges
134 if ( mSmoothEdges )
135 out << "s on\n";
136 else
137 out << "s off\n";
138
139 // Construct vertices
140 // As we can have holes in the face list and we only write vertices from these faces
141 // then the vertex list in the obj is not the whole from mVertexPosition!
142 for ( const unsigned int vertice : qAsConst( mIndexes ) )
143 {
144 const int i = static_cast<int>( vertice * 3 );
145 // for now just ignore wrong vertex positions
146 out << "v ";
147 out << ( mVertexPosition[i] - center.x() ) / scale << " ";
148 out << ( mVertexPosition[i + 1] - center.y() ) / scale << " ";
149 out << ( mVertexPosition[i + 2] - center.z() ) / scale << "\n";
150 if ( i + 3 <= mNormals.size() )
151 {
152 out << "vn " << mNormals[i] << " " << mNormals[i + 1] << " " << mNormals[i + 2] << "\n";
153 }
154 const int u_index = i / 3 * 2;
155 if ( u_index + 1 < mTexturesUV.size() )
156 {
157 // TODO: flip texture in a more appropriate way (for repeated textures)
158 out << "vt " << mTexturesUV[u_index] << " " << 1.0f - mTexturesUV[u_index + 1] << "\n";
159 }
160 }
161
162 bool hasTextures = mTexturesUV.size() == mVertexPosition.size() / 3 * 2;
163 // if the object has normals then the normals and positions buffers should be the same size
164 bool hasNormals = mNormals.size() == mVertexPosition.size();
165
166 if ( !hasNormals && !mNormals.empty() )
167 {
168 QgsDebugError( "Vertex normals count and vertex positions count are different" );
169 }
170 const int verticesCount = mIndexes.size();
171
172 // we use negative indexes as this is the way to use relative values to reference vertex positions
173 // Positive values are absolute vertex position from the beginning of the file.
174 auto getVertexIndex = [&]( unsigned int i ) -> QString {
175 const int negativeIndex = static_cast<int>( i - verticesCount );
176 if ( hasNormals && !hasTextures )
177 return QStringLiteral( "%1//%2" ).arg( negativeIndex ).arg( negativeIndex );
178 if ( !hasNormals && hasTextures )
179 return QStringLiteral( "%1/%2" ).arg( negativeIndex ).arg( negativeIndex );
180 if ( hasNormals && hasTextures )
181 return QStringLiteral( "%1/%2/%3" ).arg( negativeIndex ).arg( negativeIndex ).arg( negativeIndex );
182 return QString::number( negativeIndex );
183 };
184
185 if ( mType == TriangularFaces )
186 {
187 // Construct triangular faces
188 // As we have "compressed" the vertex/normal section above by using only the vertices referenced by the faces
189 // we do not need to the 'mIndexes[i]' value but only the 'i' value.
190 for ( int i = 0; i < mIndexes.size(); i += 3 )
191 {
192 out << "f " << getVertexIndex( i );
193 out << " " << getVertexIndex( i + 1 );
194 out << " " << getVertexIndex( i + 2 );
195 out << "\n";
196 }
197 }
198 else if ( mType == LineStrip )
199 {
200 out << "l";
201 for ( const unsigned int i : qAsConst( mIndexes ) )
202 out << " " << getVertexIndex( i );
203 out << "\n";
204 }
205 else if ( mType == Points )
206 {
207 out << "p";
208 for ( const unsigned int i : qAsConst( mIndexes ) )
209 out << " " << getVertexIndex( i );
210 out << "\n";
211 }
212}
213
214QString Qgs3DExportObject::saveMaterial( QTextStream &mtlOut, const QString &folderPath )
215{
216 QString materialName = mName + "_material";
217 if ( mMaterialParameters.size() == 0 && ( mTexturesUV.size() == 0 || mTextureImage.isNull() ) )
218 return QString();
219 mtlOut << "newmtl " << materialName << "\n";
220 if ( mTexturesUV.size() != 0 && !mTextureImage.isNull() )
221 {
222 const QString filePath = QDir( folderPath ).filePath( materialName + ".jpg" );
223 mTextureImage.save( filePath, "JPG" );
224 mtlOut << "\tmap_Kd " << materialName << ".jpg" << "\n";
225 }
226 for ( auto it = mMaterialParameters.constBegin(); it != mMaterialParameters.constEnd(); it++ )
227 {
228 mtlOut << "\t" << it.key() << " " << it.value() << "\n";
229 }
230 mtlOut << "\tillum 2\n";
231 return materialName;
232}
void setMaterialParameter(const QString &parameter, const QString &value)
Sets a material parameter to be exported in the .mtl file.
void setupPositionCoordinates(const QVector< float > &positionsBuffer, const QMatrix4x4 &transform)
Sets positions coordinates and does the translation, rotation and scaling.
void objectBounds(float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ)
Updates the box bounds explained with the current object bounds This expands the bounding box if the ...
void saveTo(QTextStream &out, float scale, const QVector3D &center, int precision=6)
Saves the current object to the output stream while scaling the object and centering it to be visible...
void setupMaterial(QgsAbstractMaterialSettings *material)
Sets the material parameters (diffuse color, shininess...) from phong material.
void setupNormalCoordinates(const QVector< float > &normalsBuffer, const QMatrix4x4 &transform)
Sets normal coordinates for each vertex.
QString saveMaterial(QTextStream &mtlOut, const QString &folder)
saves the texture of the object and material information
void setupLine(const QVector< uint > &facesIndexes)
sets line vertex indexes
void setupFaces(const QVector< uint > &facesIndexes)
Sets the faces in facesIndexes to the faces in the object.
void setupTextureCoordinates(const QVector< float > &texturesBuffer)
Sets texture coordinates for each vertex.
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:6035
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
void insertIndexData(QVector< uint > &vertexIndex, const QVector< T > &faceIndex)
#define QgsDebugError(str)
Definition qgslogger.h:38
int precision