16#define TINYOBJLOADER_IMPLEMENTATION
17#define TINYOBJLOADER_USE_MAPBOX_EARCUT
20#include <tiny_obj_loader.h>
21#include <unordered_map>
34#include <Qt3DCore/QAttribute>
35#include <Qt3DCore/QBuffer>
36#include <Qt3DCore/QGeometry>
37#include <Qt3DRender/QTexture>
39using namespace Qt::StringLiterals;
43std::vector<QgsMeshNodeData> QgsObj3DUtils::buildObjGeometries(
const QString &filePath,
const QgsMaterialContext &materialContext )
45 tinyobj::ObjReaderConfig config;
46 config.triangulate =
true;
47 config.triangulation_method =
"earcut";
48 config.mtl_search_path = QFileInfo( filePath ).absolutePath().toStdString();
50 tinyobj::ObjReader reader;
51 if ( !reader.ParseFromFile( filePath.toStdString(), config ) )
53 if ( !reader.Error().empty() )
54 QgsDebugMsgLevel( u
"tinyobj error loading '%1': %2"_s.arg( filePath, QString::fromStdString( reader.Error() ) ), 1 );
57 if ( !reader.Warning().empty() )
58 QgsDebugMsgLevel( u
"tinyobj warning loading '%1': %2"_s.arg( filePath, QString::fromStdString( reader.Warning() ) ), 2 );
60 const tinyobj::attrib_t &attrib = reader.GetAttrib();
61 const std::vector<tinyobj::shape_t> &shapes = reader.GetShapes();
62 const std::vector<tinyobj::material_t> &materials = reader.GetMaterials();
64 const QString objDir = QFileInfo( filePath ).absolutePath();
66 const bool hasNormals = !attrib.normals.empty();
67 const bool hasTexCoords = !attrib.texcoords.empty();
69 const size_t floatsPerVertex = 3 + ( hasNormals ? 3 : 0 ) + ( hasTexCoords ? 2 : 0 );
71 std::unordered_map<int, std::vector<float>> matDataMap;
74 for (
size_t shapeIdx = 0; shapeIdx < shapes.size(); shapeIdx++ )
76 size_t index_offset = 0;
77 for (
size_t faceIdx = 0; faceIdx < shapes[shapeIdx].mesh.num_face_vertices.size(); faceIdx++ )
79 const size_t faceVertexCount = size_t( shapes[shapeIdx].mesh.num_face_vertices[faceIdx] );
80 const int matId = shapes[shapeIdx].mesh.material_ids[faceIdx];
81 std::vector<float> &data = matDataMap[matId];
83 data.reserve( data.size() + faceVertexCount * floatsPerVertex );
85 for (
size_t v = 0; v < faceVertexCount; v++ )
87 const tinyobj::index_t idx = shapes[shapeIdx].mesh.indices[index_offset + v];
89 data.push_back( attrib.vertices[3 *
size_t( idx.vertex_index ) + 0] );
90 data.push_back( attrib.vertices[3 *
size_t( idx.vertex_index ) + 1] );
91 data.push_back( attrib.vertices[3 *
size_t( idx.vertex_index ) + 2] );
95 if ( idx.normal_index >= 0 )
97 data.push_back( attrib.normals[3 *
size_t( idx.normal_index ) + 0] );
98 data.push_back( attrib.normals[3 *
size_t( idx.normal_index ) + 1] );
99 data.push_back( attrib.normals[3 *
size_t( idx.normal_index ) + 2] );
103 data.push_back( 0.0f );
104 data.push_back( 0.0f );
105 data.push_back( 0.0f );
111 if ( idx.texcoord_index >= 0 )
113 data.push_back( attrib.texcoords[2 *
size_t( idx.texcoord_index ) + 0] );
114 data.push_back( 1.0f - attrib.texcoords[2 *
size_t( idx.texcoord_index ) + 1] );
118 data.push_back( 0.0f );
119 data.push_back( 0.0f );
123 index_offset += faceVertexCount;
127 std::vector<QgsMeshNodeData> result;
128 result.reserve( matDataMap.size() );
130 for (
auto &[matId, vertices] : matDataMap )
132 if ( vertices.empty() )
135 Qt3DCore::QGeometry *geom =
new Qt3DCore::QGeometry;
137 QByteArray vertexBufferData(
reinterpret_cast<const char *
>( vertices.data() ),
static_cast<qsizetype
>( vertices.size() *
sizeof(
float ) ) );
139 Qt3DCore::QBuffer *vertexBuffer =
new Qt3DCore::QBuffer( geom );
140 vertexBuffer->setData( vertexBufferData );
142 const int vertexFloats = 3 + ( hasNormals ? 3 : 0 ) + ( hasTexCoords ? 2 : 0 );
143 const int stride = vertexFloats *
static_cast<int>(
sizeof( float ) );
144 const uint vertCount =
static_cast<uint
>( vertices.size() / vertexFloats );
146 Qt3DRender::QAbstractTexture *diffuseTex =
nullptr;
147 if ( matId >= 0 && matId <
static_cast<int>( materials.size() ) )
149 const std::string &texName = materials[matId].diffuse_texname;
150 if ( !texName.empty() )
152 const QString texPath = QDir( objDir ).filePath( QString::fromStdString( texName ) );
153 const QImage img( texPath );
156 Qt3DRender::QTexture2D *texture =
new Qt3DRender::QTexture2D;
157 texture->wrapMode()->setX( Qt3DRender::QTextureWrapMode::Repeat );
158 texture->wrapMode()->setY( Qt3DRender::QTextureWrapMode::Repeat );
159 texture->setFormat( Qt3DRender::QAbstractTexture::SRGB8_Alpha8 );
162 diffuseTex = texture;
166 QgsDebugMsgLevel( u
"OBJ texture not found or unreadable: '%1'"_s.arg( texPath ), 2 );
173 Qt3DCore::QAttribute *posAttr =
new Qt3DCore::QAttribute;
174 posAttr->setName( Qt3DCore::QAttribute::defaultPositionAttributeName() );
175 posAttr->setVertexBaseType( Qt3DCore::QAttribute::Float );
176 posAttr->setVertexSize( 3 );
177 posAttr->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
178 posAttr->setBuffer( vertexBuffer );
179 posAttr->setByteStride( stride );
180 posAttr->setByteOffset( byteOffset );
181 posAttr->setCount( vertCount );
182 geom->addAttribute( posAttr );
183 byteOffset += 3 *
static_cast<int>(
sizeof( float ) );
187 Qt3DCore::QAttribute *normalAttr =
new Qt3DCore::QAttribute;
188 normalAttr->setName( Qt3DCore::QAttribute::defaultNormalAttributeName() );
189 normalAttr->setVertexBaseType( Qt3DCore::QAttribute::Float );
190 normalAttr->setVertexSize( 3 );
191 normalAttr->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
192 normalAttr->setBuffer( vertexBuffer );
193 normalAttr->setByteStride( stride );
194 normalAttr->setByteOffset( byteOffset );
195 normalAttr->setCount( vertCount );
196 geom->addAttribute( normalAttr );
197 byteOffset += 3 *
static_cast<int>(
sizeof( float ) );
200 if ( hasTexCoords && diffuseTex )
202 Qt3DCore::QAttribute *texAttr =
new Qt3DCore::QAttribute;
203 texAttr->setName( Qt3DCore::QAttribute::defaultTextureCoordinateAttributeName() );
204 texAttr->setVertexBaseType( Qt3DCore::QAttribute::Float );
205 texAttr->setVertexSize( 2 );
206 texAttr->setAttributeType( Qt3DCore::QAttribute::VertexAttribute );
207 texAttr->setBuffer( vertexBuffer );
208 texAttr->setByteStride( stride );
209 texAttr->setByteOffset( byteOffset );
210 texAttr->setCount( vertCount );
211 geom->addAttribute( texAttr );
217 QgsPhongTexturedMaterial *texMat =
new QgsPhongTexturedMaterial();
218 texMat->setDiffuseTexture( diffuseTex );
221 else if ( matId >= 0 && matId <
static_cast<int>( materials.size() ) )
223 const tinyobj::material_t &m = materials[matId];
224 QgsPhongMaterial *phong =
new QgsPhongMaterial();
227 phong->setDiffuse( QColor::fromRgbF( m.diffuse[0], m.diffuse[1], m.diffuse[2] ) );
228 phong->setSpecular( QColor::fromRgbF( m.specular[0], m.specular[1], m.specular[2] ) );
229 phong->setShininess( m.shininess );
230 phong->setOpacity( m.dissolve );
234 result.push_back(
QgsMeshNodeData { std::unique_ptr<Qt3DCore::QGeometry>( geom ), std::unique_ptr<QgsMaterial>( mat ), QMatrix4x4() } );
static void setTextureFiltering(Qt3DRender::QAbstractTexture *texture, const QgsMaterialContext &context)
Sets the default filtering options for a texture.
Holds an image that can be used as a texture in the 3D view.
Context settings for a material.
Base class for all materials used within QGIS 3D views.
#define QgsDebugMsgLevel(str, level)
Pairs a Qt3D geometry with its material and transform.