/**************************************************************************** * MeshLab o o * * A versatile mesh processing toolbox o o * * _ O _ * * Copyright(C) 2005-2021 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * * All rights reserved. * * * * 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. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * * for more details. * * * ****************************************************************************/ // From tinygltf example: // Define these only in *one* .cc file. #define TINYGLTF_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_WRITE_IMPLEMENTATION #include "gltf_loader.h" #include #include namespace gltf { /** * @brief returns the number of meshes referred by the nodes contained in the * gltf file. * * Note: this number may differ from model.meshes.size(). * This is because some gltf file may duplicate a mesh in the scene, and place * it in different positions using the node hierarchy. * * This function actually returns how many (referenced) nodes contain a mesh. * * @param model: the tinygltf content of the file * @return */ unsigned int getNumberMeshes( const tinygltf::Model& model) { unsigned int nMeshes = 0; for (unsigned int s = 0; s < model.scenes.size(); ++s){ const tinygltf::Scene& scene = model.scenes[s]; for (unsigned int n = 0; n < scene.nodes.size(); ++n){ nMeshes += internal::getNumberMeshes(model, scene.nodes[n]); } } return nMeshes; } /** * @brief Loads all the meshes referred in the scene of the gltf file into * the list of meshes. * * @param meshModelList * @param maskList * @param model */ void loadMeshes( const std::list& meshModelList, std::list& maskList, const tinygltf::Model& model, bool loadInSingleLayer, vcg::CallBackPos* cb) { CallBackProgress progress(100.0/meshModelList.size()); maskList.resize(meshModelList.size(), 0); std::list::const_iterator meshit = meshModelList.begin(); std::list::iterator maskit = maskList.begin(); for (unsigned int s = 0; s < model.scenes.size(); ++s){ const tinygltf::Scene& scene = model.scenes[s]; for (unsigned int n = 0; n < scene.nodes.size(); ++n){ internal::loadMeshesWhileTraversingNodes( model, meshit, maskit, Matrix44m::Identity(), scene.nodes[n], loadInSingleLayer, cb, progress); } } if (cb) cb(100, "GLTF File loaded"); } namespace internal { /** * @brief Recursive function that returns the number of meshes contained * in the current node (0 or 1) plus the number of meshes contained in the * children of the node. * * Call this function from a root node to know how many meshes are referred * in the scene. * * @param model * @param node * @return */ unsigned int getNumberMeshes( const tinygltf::Model& model, unsigned int node) { unsigned int nMeshes = 0; if (model.nodes[node].mesh >= 0){ nMeshes = 1; } for (int c : model.nodes[node].children){ if (c>=0){ nMeshes += getNumberMeshes(model, c); } } return nMeshes; } /** * @brief Recursive function that loads a mesh if the current node contains one, * and then calls itself on the children of the node. * * @param model * @param currentMesh * @param currentMask * @param currentMatrix * @param currentNode */ void loadMeshesWhileTraversingNodes( const tinygltf::Model& model, std::list::const_iterator& currentMesh, std::list::iterator& currentMask, Matrix44m currentMatrix, unsigned int currentNode, bool loadInSingleLayer, vcg::CallBackPos* cb, CallBackProgress& progress) { currentMatrix = currentMatrix * getCurrentNodeTrMatrix(model, currentNode); if (model.nodes[currentNode].mesh >= 0) { int meshid = model.nodes[currentNode].mesh; loadMesh( **currentMesh, *currentMask, model.meshes[meshid], model, loadInSingleLayer, currentMatrix, cb, progress); if (!loadInSingleLayer) { (*currentMesh)->cm.Tr = currentMatrix; ++currentMesh; ++currentMask; } } //for each child for (int c : model.nodes[currentNode].children){ if (c>=0){ //if it is valid //visit child loadMeshesWhileTraversingNodes( model, currentMesh, currentMask, currentMatrix, c, loadInSingleLayer, cb, progress); } } } /** * @brief Gets the 4x4 transformation matrix contained in the node itself, * without taking into account parent transformations of the node. * * @param model * @param currentNode * @return */ Matrix44m getCurrentNodeTrMatrix( const tinygltf::Model& model, unsigned int currentNode) { Matrix44m currentMatrix = Matrix44m::Identity(); //if the current node contains a 4x4 matrix if (model.nodes[currentNode].matrix.size() == 16) { vcg::Matrix44d curr(model.nodes[currentNode].matrix.data()); curr.transposeInPlace(); currentMatrix = Matrix44m::Construct(curr); } //if the current node contains rotation quaternion, scale vector or // translation vector else { //note: if one or more of these are missing, identity is used. //note: final matrix is computed as M = T * R * S, as specified by //gltf docs: https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_004_ScenesNodes.md //4x4 matrices associated to rotation, translation and scale vcg::Matrix44d rot; rot.SetIdentity(); vcg::Matrix44d scale; scale.SetIdentity(); vcg::Matrix44d trans; trans.SetIdentity(); //if node contains rotation quaternion if (model.nodes[currentNode].rotation.size() == 4) { vcg::Quaterniond qr( model.nodes[currentNode].rotation[3], model.nodes[currentNode].rotation[0], model.nodes[currentNode].rotation[1], model.nodes[currentNode].rotation[2]); qr.ToMatrix(rot); //set 4x4 matrix quaternion } //if node contains scale if (model.nodes[currentNode].scale.size() == 3) { //set 4x4 matrix scale scale.ElementAt(0,0) = model.nodes[currentNode].scale[0]; scale.ElementAt(1,1) = model.nodes[currentNode].scale[1]; scale.ElementAt(2,2) = model.nodes[currentNode].scale[2]; } //if node contains translation if (model.nodes[currentNode].translation.size() == 3) { //set 4x4 matrix translation trans.ElementAt(0,3) = model.nodes[currentNode].translation[0]; trans.ElementAt(1,3) = model.nodes[currentNode].translation[1]; trans.ElementAt(2,3) = model.nodes[currentNode].translation[2]; } //M = T * R * S vcg::Matrix44d curr = trans * rot * scale; currentMatrix = Matrix44m::Construct(curr); } return currentMatrix; } /** * @brief loads a mesh from gltf file. * It merges all the primitives in the loaded mesh. * * @param m: the mesh that will contain the loaded mesh * @param tm: tinygltf structure of the mesh to load * @param model: tinygltf file */ void loadMesh( MeshModel& m, int& mask, const tinygltf::Mesh& tm, const tinygltf::Model& model, bool loadInSingleLayer, const Matrix44m& transf, vcg::CallBackPos* cb, CallBackProgress& progress) { if (!tm.name.empty()) m.setLabel(QString::fromStdString(tm.name)); double oldStep = progress.step(); progress.setStep(oldStep / tm.primitives.size()); //for each primitive, load it into the mesh for (const tinygltf::Primitive& p : tm.primitives){ internal::loadMeshPrimitive( m, mask, model, p, loadInSingleLayer, transf, cb, progress); } if (cb) cb(progress.progress(), "Loaded all primitives for current mesh."); progress.setStep(oldStep); } /** * @brief loads the given primitive into the mesh * @param m * @param model * @param p */ void loadMeshPrimitive( MeshModel& m, int& mask, const tinygltf::Model& model, const tinygltf::Primitive& p, bool loadInSingleLayer, const Matrix44m& transf, vcg::CallBackPos* cb, CallBackProgress& progress) { double oldStep = progress.step(); progress.setStep(oldStep/GLTF_ATTR_STR.size()+1); int textureImg = -1; //id of the texture associated to the material bool vCol = false; // used if a material has a base color for all the primitive bool vTex = false; // used if a material has a texture vcg::Color4b col; //the base color, to be set to all the vertices if (p.material >= 0) { //if the primitive has a material const tinygltf::Material& mat = model.materials[p.material]; auto it = mat.values.find("baseColorTexture"); if (it != mat.values.end()){ //the material is a texture auto it2 = it->second.json_double_value.find("index"); if (it2 != it->second.json_double_value.end()){ textureImg = it2->second; //get the id of the texture } } it = mat.values.find("baseColorFactor"); if (it != mat.values.end()) { //vertex base color, the same for a primitive vCol = true; const std::vector& vc = it->second.number_array; for (unsigned int i = 0; i < 4; i++) col[i] = vc[i] * 255.0; } } if (textureImg != -1) { //if we found a texture vTex = true; const tinygltf::Image& img = model.images[model.textures[textureImg].source]; //add the path of the texture to the mesh std::string uri = img.uri; uri = std::regex_replace(uri, std::regex("\\%20"), " "); bool textureAlreadyExists = false; if (img.image.size() > 0) { if (img.bits == 8 || img.component == 4) { QImage qimg(img.image.data(), img.width, img.height, QImage::Format_RGBA8888); if (!qimg.isNull()){ QImage copy = qimg.copy(); if (uri.empty()) { uri = "texture_" + std::to_string(textureImg); } QImage existingTexture = m.getTexture(uri); if (existingTexture.isNull()) { m.addTexture(uri, copy); } else { //use the existing texture index auto it = std::find(m.cm.textures.begin(), m.cm.textures.end(), uri); textureAlreadyExists = it != m.cm.textures.end(); if (textureAlreadyExists) { textureImg = it - m.cm.textures.begin(); } } } else { m.cm.textures.push_back(uri); } } else { m.cm.textures.push_back(uri); } } else { //set to load later (could be format non-supported by tinygltf) m.cm.textures.push_back(uri); } if (!textureAlreadyExists) { //set the id of the texture: we need it when set uv coords textureImg = m.cm.textures.size() - 1; } } //vector of vertex pointers added to the mesh //this vector is modified only when adding vertex position attributes std::vector ivp; //load vertex position attribute, sets also the ivp vector if (cb) cb(progress.progress(), "Loading vertex coordinates"); loadAttribute(m, ivp, model, p, POSITION); progress.increment(); //if all the meshes are loaded in a single layer, I need to apply //the transformation matrix to the loaded coordinates if (loadInSingleLayer){ for (CMeshO::VertexPointer p : ivp){ p->P() = transf * p->P(); } } //if the mesh has a base color, set it to vertex colors if (vCol) { mask |= vcg::tri::io::Mask::IOM_VERTCOLOR; m.updateDataMask(MeshModel::MM_VERTCOLOR); for (auto v : ivp) v->C() = col; } // if the mesh has a texture or texcoords, enable texcoords to the mesh if (vTex || p.attributes.find(GLTF_ATTR_STR[TEXCOORD_0]) != p.attributes.end()) { m.updateDataMask(MeshModel::MM_VERTTEXCOORD); m.updateDataMask(MeshModel::MM_WEDGTEXCOORD); } if (cb) cb(progress.progress(), "Loading vertex normals"); //load all the other vertex attributes (ivp is not modified by these calls) bool res = loadAttribute(m, ivp, model, p, NORMAL); if (res) { mask |= vcg::tri::io::Mask::IOM_VERTNORMAL; //if all the meshes are loaded in a single layer, I need to apply //the transformation matrix to the loaded coordinates if (loadInSingleLayer){ Matrix33m mat33(transf,3); for (CMeshO::VertexPointer p : ivp){ p->N() = mat33 * p->N(); } } } progress.increment(); if (cb) cb(progress.progress(), "Loading vertex colors"); res = loadAttribute(m, ivp, model, p, COLOR_0); if (res) { mask |= vcg::tri::io::Mask::IOM_VERTCOLOR; } progress.increment(); if (cb) cb(progress.progress(), "Loading vertex texcoords"); res = loadAttribute(m, ivp, model, p, TEXCOORD_0, textureImg); if (res) { mask |= vcg::tri::io::Mask::IOM_WEDGTEXCOORD; } progress.increment(); //load triangles if (cb) cb(progress.progress(), "Loading triangle indices"); loadAttribute(m, ivp, model, p, INDICES); progress.increment(); // if vTex was true, it means that we loaded texcoords, that have been transferred from vertex to // wedges when loading triangle indices. Therefore, we can remove vertex texcoords and leave // only wedges, which are the only that can be rendered with multiple textures in meshlab // TODO: remove this mechanism whenever vertex texcoords allow to render multiple textures in // vcg. if (vTex) { m.clearDataMask(MeshModel::MM_VERTTEXCOORD); } if (cb) cb(progress.progress(), "Loaded all attributes of current mesh"); progress.setStep(oldStep); } /** * @brief loads the attribute attr from the primitive p contained in the * gltf model. If the attribute is vertex position, sets also vertex pointers * vector ivp. For all the other parameters, ivp is a const input. * * If the primitive does not contain the attribute attr, nothing is done. * However, if the attribute is POSITION, then a MLException will be thrown. * * * @param m * @param ivp * @param model * @param p * @param attr * @param textID: id of the texture in case of the attr is TEXCOORD_0 * @return true if the attribute has been loaded */ bool loadAttribute( MeshModel& m, std::vector& ivp, const tinygltf::Model& model, const tinygltf::Primitive& p, GLTF_ATTR_TYPE attr, int textID) { bool attrLoaded = false; const tinygltf::Accessor* accessor = nullptr; //get the accessor associated to the attribute if (attr != INDICES) { auto it = p.attributes.find(GLTF_ATTR_STR[attr]); if (it != p.attributes.end()) { //accessor found accessor = &model.accessors[it->second]; } else if (attr == POSITION) { //if we were looking for POSITION and didn't find any throw MLException("File has not 'Position' attribute"); } } else { //if the attribute is triangle indices //if the mode is GL_TRIANGLES and we have triangle indices if (p.mode == 4 && p.indices >= 0 && (unsigned int)p.indices < model.accessors.size()) { accessor = &model.accessors[p.indices]; } } //if we found an accessor of the attribute if (accessor) { //bufferview: contains infos on how to access buffer with the accessor const tinygltf::BufferView& posbw = model.bufferViews[accessor->bufferView]; //data of the whole buffer (vector of bytes); //may contain also other data not associated to our attribute const std::vector& posdata = model.buffers[posbw.buffer].data; //offset where the data of the attribute starts unsigned int posOffset = posbw.byteOffset + accessor->byteOffset; //hack: //if the attribute is a color, textid is used to tell the size of the //color (3 or 4 components) if (attr == COLOR_0){ if (accessor->type == TINYGLTF_TYPE_VEC3) textID = 3; else if (accessor->type == TINYGLTF_TYPE_VEC4) textID = 4; } const unsigned int elementSize = tinygltf::GetNumComponentsInType(accessor->type) * tinygltf::GetComponentSizeInBytes(accessor->componentType); const unsigned int stride = (posbw.byteStride > elementSize) ? posbw.byteStride : elementSize; //if data is float if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { //get the starting point of the data as float pointer const float* posArray = (const float*) (posdata.data() + posOffset); populateAttr(attr, m, ivp, posArray, stride, accessor->count, textID); attrLoaded = true; } //if data is double else if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { //get the starting point of the data as double pointer const double* posArray = (const double*) (posdata.data() + posOffset); populateAttr(attr, m, ivp, posArray, stride, accessor->count, textID); attrLoaded = true; } //if data is ubyte else if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE) { //get the starting point of the data as uchar pointer const unsigned char* triArray = (const unsigned char*) (posdata.data() + posOffset); populateAttr(attr, m, ivp, triArray, stride, accessor->count, textID); attrLoaded = true; } //if data is ushort else if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) { //get the starting point of the data as ushort pointer const unsigned short* triArray = (const unsigned short*) (posdata.data() + posOffset); populateAttr(attr, m, ivp, triArray, stride, accessor->count, textID); attrLoaded = true; } //if data is uint else if (accessor->componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { //get the starting point of the data as uint pointer const unsigned int* triArray = (const unsigned int*) (posdata.data() + posOffset); populateAttr(attr, m, ivp, triArray, stride, accessor->count, textID); attrLoaded = true; } } //if accessor not found and attribute is indices, it means that //the mesh is not indexed, and triplets of contiguous vertices //generate triangles else if (attr == INDICES) { //this case is managed when passing nullptr as data populateAttr(attr, m, ivp, nullptr, 0, 0); attrLoaded = true; } return attrLoaded; } /** * @brief given the attribute and the pointer to the data, * it calls the appropriate functions that put the data into the mesh * appropriately * @param attr * @param m * @param ivp: modified only if attr * @param array: vector containing the data * @param stride: * number of bytes between consecutive elements in the vector * (only applies to vertex attributes; indices are always tightly packed) * @param number: number of elements contained in the data * @param textID: * if attr is texcoord, it is the texture id * if attr is color, tells if color has 3 or 4 components */ template void populateAttr( GLTF_ATTR_TYPE attr, MeshModel&m, std::vector& ivp, const Scalar* array, unsigned int stride, unsigned int number, int textID) { switch (attr) { case POSITION: populateVertices(m, ivp, array, stride, number); break; case NORMAL: populateVNormals(ivp, array, stride, number); break; case COLOR_0: populateVColors(ivp, array, stride, number, textID); break; case TEXCOORD_0: populateVTextCoords(ivp, array, stride, number, textID); break; case INDICES: populateTriangles(m, ivp, array, number/3); break; } } template void populateVertices( MeshModel&m, std::vector& ivp, const Scalar* posArray, unsigned int stride, unsigned int vertNumber) { ivp.clear(); ivp.resize(vertNumber); CMeshO::VertexIterator vi = vcg::tri::Allocator::AddVertices(m.cm, vertNumber); for (unsigned int i = 0; i < vertNumber*3; i+= 3, ++vi){ ivp[i/3] = &*vi; const Scalar* posBase = reinterpret_cast(reinterpret_cast(posArray) + (i/3) * stride); vi->P() = CMeshO::CoordType(posBase[0], posBase[1], posBase[2]); } } template void populateVNormals( const std::vector& ivp, const Scalar* normArray, unsigned int stride, unsigned int vertNumber) { for (unsigned int i = 0; i < vertNumber*3; i+= 3) { const Scalar* normBase = reinterpret_cast(reinterpret_cast(normArray) + (i/3) * stride); ivp[i/3]->N() = CMeshO::CoordType(normBase[0], normBase[1], normBase[2]); } } template void populateVColors( const std::vector& ivp, const Scalar* colorArray, unsigned int stride, unsigned int vertNumber, int nElemns) { for (unsigned int i = 0; i < vertNumber*nElemns; i+= nElemns) { const Scalar* colorBase = reinterpret_cast(reinterpret_cast(colorArray) + (i/nElemns) * stride); if (!std::is_floating_point::value) { int alpha = nElemns == 4 ? colorBase[3] : 255; ivp[i/nElemns]->C() = vcg::Color4b(colorBase[0], colorBase[1], colorBase[2], alpha); } else { int alpha = nElemns == 4 ? colorBase[3] * 255 : 255; ivp[i/nElemns]->C() = vcg::Color4b(colorBase[0] * 255, colorBase[1]*255, colorBase[2]*255, alpha); } } } template void populateVTextCoords( const std::vector& ivp, const Scalar* textCoordArray, unsigned int stride, unsigned int vertNumber, int textID) { for (unsigned int i = 0; i < vertNumber*2; i+= 2) { const Scalar* textCoordBase = reinterpret_cast(reinterpret_cast(textCoordArray) + (i/2) * stride); ivp[i/2]->T() = CMeshO::VertexType::TexCoordType(textCoordBase[0], 1-textCoordBase[1]); ivp[i/2]->T().N() = textID; } } template void populateTriangles( MeshModel&m, const std::vector& ivp, const Scalar* triArray, unsigned int triNumber) { const bool hasTexcoord = m.hasPerVertexTexCoord(); if (triArray != nullptr) { CMeshO::FaceIterator fi = vcg::tri::Allocator::AddFaces(m.cm, triNumber); for (unsigned int i = 0; i < triNumber*3; i+=3, ++fi) { for (int j = 0; j < 3; ++j) { fi->V(j) = ivp[triArray[i+j]]; if (hasTexcoord) { fi->WT(j).u() = fi->V(j)->T().u(); fi->WT(j).v() = fi->V(j)->T().v(); fi->WT(j).n() = fi->V(j)->T().N(); } } } } else { CMeshO::FaceIterator fi = vcg::tri::Allocator::AddFaces(m.cm, ivp.size()/3); for (unsigned int i = 0; i < ivp.size(); i+=3, ++fi) { for (int j = 0; j < 3; ++j) { fi->V(j) = ivp[i+j]; if (hasTexcoord) { fi->WT(j).u() = fi->V(j)->T().u(); fi->WT(j).v() = fi->V(j)->T().v(); fi->WT(j).n() = fi->V(j)->T().N(); } } } } } } //namespace gltf::internal } //namespace gltf