Skip to content

Commit

Permalink
Merge pull request #37588 from NEDJIMAbelgacem/export-scene-feature
Browse files Browse the repository at this point in the history
[Feature] Export 3D scene feature
  • Loading branch information
wonder-sk committed Jul 30, 2020
2 parents 1ed8e41 + 28d0349 commit 836666e
Show file tree
Hide file tree
Showing 52 changed files with 2,031 additions and 58 deletions.
3 changes: 3 additions & 0 deletions python/3d/auto_generated/qgsphongmaterialsettings.sip.in
Expand Up @@ -62,6 +62,9 @@ Returns specular color component
Returns shininess of the surface
%End

virtual QMap<QString, QString> toExportParameters() const;


bool diffuseTextureEnabled() const;
%Docstring
Returns whether the diffuse texture is used.
Expand Down
1 change: 1 addition & 0 deletions python/3d/auto_generated/symbols/qgsline3dsymbol.sip.in
Expand Up @@ -117,6 +117,7 @@ Sets the ``material`` settings used for shading of the symbol.
Ownership of ``material`` is transferred to the symbol.
%End


};


Expand Down
1 change: 1 addition & 0 deletions python/3d/auto_generated/symbols/qgspolygon3dsymbol.sip.in
Expand Up @@ -188,6 +188,7 @@ Returns which facade of the buildings is rendered (0 for None, 1 for Walls, 2 fo
.. versionadded:: 3.16
%End


};


Expand Down
1 change: 1 addition & 0 deletions python/core/auto_generated/3d/qgsabstract3dsymbol.sip.in
Expand Up @@ -85,6 +85,7 @@ Returns a reference to the symbol layer's property collection, used for data def
Sets the symbol layer's property collection, used for data defined overrides.
%End


protected:

void copyBaseSettings( QgsAbstract3DSymbol *destination ) const;
Expand Down
6 changes: 5 additions & 1 deletion src/3d/CMakeLists.txt
Expand Up @@ -69,7 +69,10 @@ SET(QGIS_3D_SRCS
mesh/qgsmesh3dentity_p.cpp
mesh/qgsmesh3dmaterial_p.cpp
mesh/qgsmeshterraingenerator.cpp

qgs3dsceneexporter.cpp
qgs3dexportobject.cpp
qgs3dmapexportsettings.cpp
qgsimagetexture.cpp
)

SET(QGIS_3D_HDRS
Expand Down Expand Up @@ -113,6 +116,7 @@ SET(QGIS_3D_HDRS
chunks/qgschunkloader_p.h
chunks/qgschunkqueuejob_p.h
mesh/qgsmeshterraingenerator.h
qgs3dsceneexporter.h
)

SET(QGIS_3D_PRIVATE_HDRS
Expand Down
1 change: 1 addition & 0 deletions src/3d/mesh/qgsmeshterraingenerator.h
Expand Up @@ -32,6 +32,7 @@
//! Chunk loader for mesh terrain implementation
class QgsMeshTerrainTileLoader: public QgsTerrainTileLoader
{
Q_OBJECT
public:
//! Construct the loader for a node
QgsMeshTerrainTileLoader( QgsTerrainEntity *terrain,
Expand Down
200 changes: 200 additions & 0 deletions src/3d/qgs3dexportobject.cpp
@@ -0,0 +1,200 @@
/***************************************************************************
Qgs3DExportObject.cpp
--------------------------------------
Date : June 2020
Copyright : (C) 2020 by Belgacem Nedjima
Email : gb underscore nedjima at esi dot dz
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgs3dexportobject.h"

#include <QVector3D>
#include <QDebug>
#include <QDir>
#include <QImage>

#include <Qt3DRender/QAttribute>
#include <Qt3DRender/QBuffer>
#include <Qt3DRender/QBufferDataGenerator>
#include <Qt3DRender/QBufferDataGeneratorPtr>

#include "qgslogger.h"
#include "qgsphongmaterialsettings.h"


template<typename T>
void insertIndexData( QVector<uint> &vertexIndex, const QVector<T> &faceIndex )
{
for ( int i = 0; i < faceIndex.size(); i += 3 )
{
if ( i + 2 >= faceIndex.size() ) continue;
// skip invalid triangles
if ( faceIndex[i] == faceIndex[i + 1] || faceIndex[i + 1] == faceIndex[i + 2] || faceIndex[i] == faceIndex[i + 2] )
continue;
for ( int j = 0; j < 3; ++j )
vertexIndex << faceIndex[i + j] + 1;
}
}

void Qgs3DExportObject::setupPositionCoordinates( const QVector<float> &positionsBuffer, float scale, const QVector3D &translation )
{
for ( int i = 0; i < positionsBuffer.size(); i += 3 )
{
for ( int j = 0; j < 3; ++j )
{
mVertexPosition << positionsBuffer[i + j] * scale + translation[j];
}
}
}

void Qgs3DExportObject::setupFaces( const QVector<uint> &facesIndexes )
{
insertIndexData<uint>( mIndexes, facesIndexes );
}

void Qgs3DExportObject::setupLine( const QVector<uint> &lineIndexes )
{
Q_UNUSED( lineIndexes );
for ( int i = 0; i < mVertexPosition.size(); i += 3 ) mIndexes << i / 3 + 1;
}

void Qgs3DExportObject::setupNormalCoordinates( const QVector<float> &normalsBuffer )
{
mNormals << normalsBuffer;
}

void Qgs3DExportObject::setupTextureCoordinates( const QVector<float> &texturesBuffer )
{
mTexturesUV << texturesBuffer;
}

void Qgs3DExportObject::setupMaterial( QgsAbstractMaterialSettings *material )
{
QMap<QString, QString> parameters = material->toExportParameters();
for ( auto it = parameters.begin(); it != parameters.end(); ++it )
{
setMaterialParameter( it.key(), it.value() );
}
}

void Qgs3DExportObject::objectBounds( float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ )
{
if ( mType != TriangularFaces ) return;
for ( unsigned int vertice : mIndexes )
{
int heightIndex = ( vertice - 1 ) * 3 + 1;
minX = std::min( minX, mVertexPosition[heightIndex - 1] );
maxX = std::max( maxX, mVertexPosition[heightIndex - 1] );
minY = std::min( minY, mVertexPosition[heightIndex] );
maxY = std::max( maxY, mVertexPosition[heightIndex] );
minZ = std::min( minZ, mVertexPosition[heightIndex + 1] );
maxZ = std::max( maxZ, mVertexPosition[heightIndex + 1] );
}
}

void Qgs3DExportObject::saveTo( QTextStream &out, float scale, const QVector3D &center )
{
// Set groups
// turns out grouping doest work as expected in blender

// smoothen edges
if ( mSmoothEdges )
out << "s on\n";
else
out << "s off\n";

// Construct vertices
for ( int i = 0; i < mVertexPosition.size(); i += 3 )
{
// for now just ignore wrong vertex positions
out << "v ";
out << ( mVertexPosition[i] - center.x() ) / scale << " ";
out << ( mVertexPosition[i + 1] - center.y() ) / scale << " ";
out << ( mVertexPosition[i + 2] - center.z() ) / scale << "\n";
if ( i + 3 <= mNormals.size() )
{
out << "vn " << mNormals[i] << " " << mNormals[i + 1] << " " << mNormals[i + 2] << "\n";
}
int u_index = i / 3 * 2;
if ( u_index + 1 < mTexturesUV.size() )
{
// TODO: flip texture in a more appropriate way (for repeated textures)
out << "vt " << mTexturesUV[u_index] << " " << 1.0f - mTexturesUV[u_index + 1] << "\n";
}
}

bool hasTextures = mTexturesUV.size() == mVertexPosition.size() / 3 * 2;
// if the object has normals then the normals and positions buffers should be the same size
bool hasNormals = mNormals.size() == mVertexPosition.size();

if ( !hasNormals && !mNormals.empty() )
{
QgsDebugMsg( "Vertex normals count and vertex positions count are different" );
}
int verticesCount = mVertexPosition.size() / 3;

auto getVertexIndex = [&]( int i ) -> QString
{
int negativeIndex = -1 - ( verticesCount - i );
if ( hasNormals && !hasTextures )
return QStringLiteral( "%1//%2" ).arg( negativeIndex ).arg( negativeIndex );
if ( !hasNormals && hasTextures )
return QStringLiteral( "%1/%2" ).arg( negativeIndex ).arg( negativeIndex );
if ( hasNormals && hasTextures )
return QStringLiteral( "%1/%2/%3" ).arg( negativeIndex ).arg( negativeIndex ).arg( negativeIndex );
return QStringLiteral( "%1" ).arg( negativeIndex );
};

if ( mType == TriangularFaces )
{
// Construct triangular faces
for ( int i = 0; i < mIndexes.size(); i += 3 )
{
if ( mIndexes[i] == mIndexes[i + 1] && mIndexes[i + 1] == mIndexes[i + 2] )
continue;
out << "f " << getVertexIndex( mIndexes[i] );
out << " " << getVertexIndex( mIndexes[i + 1] );
out << " " << getVertexIndex( mIndexes[i + 2] );
out << "\n";
}
}
else if ( mType == LineStrip )
{
out << "l";
for ( int i : mIndexes ) out << " " << getVertexIndex( i );
out << "\n";
}
else if ( mType == Points )
{
out << "p";
for ( int i = 0; i < mVertexPosition.size(); i += 3 )
out << " " << getVertexIndex( i / 3 + 1 );
out << "\n";
}
}

QString Qgs3DExportObject::saveMaterial( QTextStream &mtlOut, const QString &folderPath )
{
QString materialName = mName + "_material";
if ( mMaterialParameters.size() == 0 && ( mTexturesUV.size() == 0 || mTextureImage.isNull() ) ) return QString();
mtlOut << "newmtl " << materialName << "\n";
if ( mTexturesUV.size() != 0 && !mTextureImage.isNull() )
{
QString filePath = QDir( folderPath ).filePath( materialName + ".jpg" );
mTextureImage.save( filePath, "JPG" );
mtlOut << "\tmap_Kd " << materialName << ".jpg" << "\n";
}
for ( QString key : mMaterialParameters.keys() )
{
mtlOut << "\t" << key << " " << mMaterialParameters[key] << "\n";
}
mtlOut << "\tillum 2\n";
return materialName;
}
120 changes: 120 additions & 0 deletions src/3d/qgs3dexportobject.h
@@ -0,0 +1,120 @@
/***************************************************************************
Qgs3DExportObject.h
--------------------------------------
Date : June 2020
Copyright : (C) 2020 by Belgacem Nedjima
Email : gb underscore nedjima at esi dot dz
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef Qgs3DExportObject_H
#define Qgs3DExportObject_H

#include <QObject>
#include <QTextStream>
#include <QVector>
#include <QVector3D>
#include <QImage>

#include <Qt3DRender/QAttribute>

class QgsAbstractMaterialSettings;

/**
* \brief The Qgs3DExportObject class
* Manages the data of each object of the scene (positions, normals, texture coordinates ...) since each object
* \ingroup 3d
* \since QGIS 3.16
*/
class Qgs3DExportObject
{
public:
//! The type of exported geometry
enum ObjectType
{
TriangularFaces,
LineStrip,
Points
};

/**
* \brief Qgs3DExportObject
* Constructs an export object that will be filled with coordinates later
* \param parentName
* The name of the parent (Will be useful to define scene hierarchie)
* \param parent
* The parent QObject (we use this to delete the Qgs3DExportObject instance once the exporter instance is deallocated)
*/
Qgs3DExportObject( const QString &name ) : mName( name ) { }

//! Returns the object name
QString name() const { return mName; }
//! Sets the object name
void setName( const QString &name ) { mName = name; }

//! Returns the object type
ObjectType type() const { return mType; }
//! Sets the object type
void setType( ObjectType type ) { mType = type; }

//! Returns whether object edges will look smooth
bool smoothEdges() { return mSmoothEdges; }
//! Sets whether triangles edges will look smooth
void setSmoothEdges( bool smoothEdges ) { mSmoothEdges = smoothEdges; }

//! Sets positions coordinates and does the translation and scaling
void setupPositionCoordinates( const QVector<float> &positionsBuffer, float scale = 1.0f, const QVector3D &translation = QVector3D( 0, 0, 0 ) );
//! Sets the faces in facesIndexes to the faces in the object
void setupFaces( const QVector<uint> &facesIndexes );
//! sets line vertex indexes
void setupLine( const QVector<uint> &facesIndexes );

//! Sets normal coordinates for each vertex
void setupNormalCoordinates( const QVector<float> &normalsBuffer );
//! Sets texture coordinates for each vertex
void setupTextureCoordinates( const QVector<float> &texturesBuffer );
//! Sets the material parameters (diffuse color, shininess...) from phong material
void setupMaterial( QgsAbstractMaterialSettings *material );

//! Sets the texture image used by the object
void setTextureImage( const QImage &image ) { this->mTextureImage = image; };
//! Returns the texture image used by the object
QImage textureImage() { return mTextureImage; }

/**
*
* Updates the box bounds explained with the current object bounds
* This expands the bounding box if the current object outside the bounds of the already established bounds
*/
void objectBounds( float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ );

//! Sets a material parameter to be exported in the .mtl file
void setMaterialParameter( const QString &parameter, const QString &value ) { mMaterialParameters[parameter] = value; }

//! Saves the current object to the output stream while scaling the object and centering it to be visible in exported scene
void saveTo( QTextStream &out, float scale, const QVector3D &center );
//! saves the texture of the object and material informations
QString saveMaterial( QTextStream &mtlOut, const QString &folder );

private:
QString mName;
ObjectType mType = ObjectType::TriangularFaces;
QString mParentName;
QVector<float> mVertexPosition;
QVector<float> mNormals;
QVector<float> mTexturesUV;
QVector<unsigned int> mIndexes;
QMap<QString, QString> mMaterialParameters;

QImage mTextureImage;

bool mSmoothEdges = false;
};

#endif // Qgs3DExportObject_H

0 comments on commit 836666e

Please sign in to comment.