diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index 827f43333..85aa620d9 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -411,6 +411,8 @@ ADD_ASSIMP_IMPORTER( M3D M3D/M3DMaterials.h M3D/M3DImporter.h M3D/M3DImporter.cpp + M3D/M3DWrapper.h + M3D/M3DWrapper.cpp M3D/m3d.h ) diff --git a/code/M3D/M3DExporter.cpp b/code/M3D/M3DExporter.cpp index b1c7ebdba..0e84f6d4f 100644 --- a/code/M3D/M3DExporter.cpp +++ b/code/M3D/M3DExporter.cpp @@ -55,17 +55,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include -#include // aiGetVersion -#include -#include -#include -#include // StreamWriterLE #include // DeadlyExportError +#include // StreamWriterLE #include // aiTextureType -#include #include +#include +#include // aiGetVersion +#include +#include +#include + #include "M3DExporter.h" #include "M3DMaterials.h" +#include "M3DWrapper.h" // RESOURCES: // https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md @@ -80,341 +82,331 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * - aiAnimation -> m3d_action (frame with timestamp and list of bone id, position, orientation * triplets, instead of per bone timestamp + lists) */ -using namespace Assimp; - -namespace Assimp { - - // --------------------------------------------------------------------- - // Worker function for exporting a scene to binary M3D. - // Prototyped and registered in Exporter.cpp - void ExportSceneM3D ( - const char* pFile, - IOSystem* pIOSystem, - const aiScene* pScene, - const ExportProperties* pProperties - ){ - // initialize the exporter - M3DExporter exporter(pScene, pProperties); - - // perform binary export - exporter.doExport(pFile, pIOSystem, false); - } - - // --------------------------------------------------------------------- - // Worker function for exporting a scene to ASCII A3D. - // Prototyped and registered in Exporter.cpp - void ExportSceneA3D ( - const char* pFile, - IOSystem* pIOSystem, - const aiScene* pScene, - const ExportProperties* pProperties - - ){ - // initialize the exporter - M3DExporter exporter(pScene, pProperties); - - // perform ascii export - exporter.doExport(pFile, pIOSystem, true); - } - -} // end of namespace Assimp // ------------------------------------------------------------------------------------------------ -M3DExporter::M3DExporter ( const aiScene* pScene, const ExportProperties* pProperties ) -: mScene(pScene) -, mProperties(pProperties) -, outfile() -, m3d(nullptr) { } - -// ------------------------------------------------------------------------------------------------ -void M3DExporter::doExport ( - const char* pFile, - IOSystem* pIOSystem, - bool toAscii -){ - // TODO: convert mProperties into M3D_EXP_* flags - (void)mProperties; - - // open the indicated file for writing (in binary / ASCII mode) - outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb")); - if (!outfile) { - throw DeadlyExportError( "could not open output .m3d file: " + std::string(pFile) ); - } - - // use malloc() here because m3d_free() will call free() - m3d = (m3d_t*)calloc(1, sizeof(m3d_t)); - if(!m3d) { - throw DeadlyExportError( "memory allocation error" ); - } - m3d->name = _m3d_safestr((char*)&mScene->mRootNode->mName.data, 2); - - // Create a model from assimp structures - aiMatrix4x4 m; - NodeWalk(mScene->mRootNode, m); - - // serialize the structures - unsigned int size; - unsigned char *output = m3d_save(m3d, M3D_EXP_FLOAT, - M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), &size); - m3d_free(m3d); - if(!output || size < 8) { - throw DeadlyExportError( "unable to serialize into Model 3D" ); - } - - // Write out serialized model - outfile->Write(output, size, 1); - - // explicitly release file pointer, - // so we don't have to rely on class destruction. - outfile.reset(); -} - - +// Conversion functions // ------------------------------------------------------------------------------------------------ // helper to add a vertex (private to NodeWalk) -m3dv_t *M3DExporter::AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx) -{ - if(v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0; - if(v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0; - if(v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0; - if(v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0; - vrtx = (m3dv_t*)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t)); - memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t)); - *idx = *numvrtx; - (*numvrtx)++; - return vrtx; +m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx) { + if (v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0; + if (v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0; + if (v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0; + if (v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0; + vrtx = (m3dv_t *)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t)); + memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t)); + *idx = *numvrtx; + (*numvrtx)++; + return vrtx; } // ------------------------------------------------------------------------------------------------ // helper to add a tmap (private to NodeWalk) -m3dti_t *M3DExporter::AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx) -{ - tmap = (m3dti_t*)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t)); - memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t)); - *idx = *numtmap; - (*numtmap)++; - return tmap; -} - -// ------------------------------------------------------------------------------------------------ -// recursive node walker -void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m) -{ - aiMatrix4x4 nm = m * pNode->mTransformation; - - for(unsigned int i = 0; i < pNode->mNumMeshes; i++) { - const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]]; - unsigned int mi = (M3D_INDEX)-1U; - if(mScene->mMaterials) { - // get the material for this mesh - mi = addMaterial(mScene->mMaterials[mesh->mMaterialIndex]); - } - // iterate through the mesh faces - for(unsigned int j = 0; j < mesh->mNumFaces; j++) { - unsigned int n; - const aiFace* face = &(mesh->mFaces[j]); - // only triangle meshes supported for now - if(face->mNumIndices != 3) { - throw DeadlyExportError( "use aiProcess_Triangulate before export" ); - } - // add triangle to the output - n = m3d->numface++; - m3d->face = (m3df_t*)M3D_REALLOC(m3d->face, - m3d->numface * sizeof(m3df_t)); - if(!m3d->face) { - throw DeadlyExportError( "memory allocation error" ); - } - /* set all index to -1 by default */ - m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] = - m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] = - m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U; - m3d->face[n].materialid = mi; - for(unsigned int k = 0; k < face->mNumIndices; k++) { - // get the vertex's index - unsigned int l = face->mIndices[k]; - unsigned int idx; - m3dv_t vertex; - m3dti_t ti; - // multiply the position vector by the transformation matrix - aiVector3D v = mesh->mVertices[l]; - v *= nm; - vertex.x = v.x; - vertex.y = v.y; - vertex.z = v.z; - vertex.w = 1.0; - vertex.color = 0; - vertex.skinid = -1U; - // add color if defined - if(mesh->HasVertexColors(0)) - vertex.color = mkColor(&mesh->mColors[0][l]); - // save the vertex to the output - m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, - &vertex, &idx); - m3d->face[n].vertex[k] = (M3D_INDEX)idx; - // do we have texture coordinates? - if(mesh->HasTextureCoords(0)) { - ti.u = mesh->mTextureCoords[0][l].x; - ti.v = mesh->mTextureCoords[0][l].y; - m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx); - m3d->face[n].texcoord[k] = (M3D_INDEX)idx; - } - // do we have normal vectors? - if(mesh->HasNormals()) { - vertex.x = mesh->mNormals[l].x; - vertex.y = mesh->mNormals[l].y; - vertex.z = mesh->mNormals[l].z; - vertex.color = 0; - m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx); - m3d->face[n].normal[k] = (M3D_INDEX)idx; - } - } - } - } - // repeat for the children nodes - for (unsigned int i = 0; i < pNode->mNumChildren; i++) { - NodeWalk(pNode->mChildren[i], nm); - } +m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx) { + tmap = (m3dti_t *)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t)); + memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t)); + *idx = *numtmap; + (*numtmap)++; + return tmap; } // ------------------------------------------------------------------------------------------------ // convert aiColor4D into uint32_t -uint32_t M3DExporter::mkColor(aiColor4D* c) -{ - return ((uint8_t)(c->a*255) << 24L) | - ((uint8_t)(c->b*255) << 16L) | - ((uint8_t)(c->g*255) << 8L) | - ((uint8_t)(c->r*255) << 0L); -} - -// ------------------------------------------------------------------------------------------------ -// add a material to the output -M3D_INDEX M3DExporter::addMaterial(const aiMaterial *mat) -{ - unsigned int mi = -1U; - aiColor4D c; - aiString name; - ai_real f; - char *fn; - - if(mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length && - strcmp((char*)&name.data, AI_DEFAULT_MATERIAL_NAME)) { - // check if we have saved a material by this name. This has to be done - // because only the referenced materials should be added to the output - for(unsigned int i = 0; i < m3d->nummaterial; i++) - if(!strcmp((char*)&name.data, m3d->material[i].name)) { - mi = i; - break; - } - // if not found, add the material to the output - if(mi == -1U) { - unsigned int k; - mi = m3d->nummaterial++; - m3d->material = (m3dm_t*)M3D_REALLOC(m3d->material, m3d->nummaterial - * sizeof(m3dm_t)); - if(!m3d->material) { - throw DeadlyExportError( "memory allocation error" ); - } - m3d->material[mi].name = _m3d_safestr((char*)&name.data, 0); - m3d->material[mi].numprop = 0; - m3d->material[mi].prop = NULL; - // iterate through the material property table and see what we got - for(k = 0; k < 15; k++) { - unsigned int j; - if(m3d_propertytypes[k].format == m3dpf_map) - continue; - if(aiProps[k].pKey) { - switch(m3d_propertytypes[k].format) { - case m3dpf_color: - if(mat->Get(aiProps[k].pKey, aiProps[k].type, - aiProps[k].index, c) == AI_SUCCESS) - addProp(&m3d->material[mi], - m3d_propertytypes[k].id, mkColor(&c)); - break; - case m3dpf_float: - if(mat->Get(aiProps[k].pKey, aiProps[k].type, - aiProps[k].index, f) == AI_SUCCESS) - addProp(&m3d->material[mi], - m3d_propertytypes[k].id, - /* not (uint32_t)f, because we don't want to convert - * it, we want to see it as 32 bits of memory */ - *((uint32_t*)&f)); - break; - case m3dpf_uint8: - if(mat->Get(aiProps[k].pKey, aiProps[k].type, - aiProps[k].index, j) == AI_SUCCESS) { - // special conversion for illumination model property - if(m3d_propertytypes[k].id == m3dp_il) { - switch(j) { - case aiShadingMode_NoShading: j = 0; break; - case aiShadingMode_Phong: j = 2; break; - default: j = 1; break; - } - } - addProp(&m3d->material[mi], - m3d_propertytypes[k].id, j); - } - break; - default: - if(mat->Get(aiProps[k].pKey, aiProps[k].type, - aiProps[k].index, j) == AI_SUCCESS) - addProp(&m3d->material[mi], - m3d_propertytypes[k].id, j); - break; - } - } - if(aiTxProps[k].pKey && - mat->GetTexture((aiTextureType)aiTxProps[k].type, - aiTxProps[k].index, &name, NULL, NULL, NULL, - NULL, NULL) == AI_SUCCESS) { - unsigned int i; - for(j = name.length-1; j > 0 && name.data[j]!='.'; j++); - if(j && name.data[j]=='.' && - (name.data[j+1]=='p' || name.data[j+1]=='P') && - (name.data[j+1]=='n' || name.data[j+1]=='N') && - (name.data[j+1]=='g' || name.data[j+1]=='G')) - name.data[j]=0; - // do we have this texture saved already? - fn = _m3d_safestr((char*)&name.data, 0); - for(j = 0, i = -1U; j < m3d->numtexture; j++) - if(!strcmp(fn, m3d->texture[j].name)) { - i = j; - free(fn); - break; - } - if(i == -1U) { - i = m3d->numtexture++; - m3d->texture = (m3dtx_t*)M3D_REALLOC( - m3d->texture, - m3d->numtexture * sizeof(m3dtx_t)); - if(!m3d->texture) { - throw DeadlyExportError( "memory allocation error" ); - } - // we don't need the texture itself, only its name - m3d->texture[i].name = fn; - m3d->texture[i].w = 0; - m3d->texture[i].h = 0; - m3d->texture[i].d = NULL; - } - addProp(&m3d->material[mi], - m3d_propertytypes[k].id + 128, i); - } - } - } - } - return mi; +uint32_t mkColor(aiColor4D *c) { + return ((uint8_t)(c->a * 255) << 24L) | + ((uint8_t)(c->b * 255) << 16L) | + ((uint8_t)(c->g * 255) << 8L) | + ((uint8_t)(c->r * 255) << 0L); } // ------------------------------------------------------------------------------------------------ // add a material property to the output -void M3DExporter::addProp(m3dm_t *m, uint8_t type, uint32_t value) -{ - unsigned int i; - i = m->numprop++; - m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); - if(!m->prop) { throw DeadlyExportError( "memory allocation error" ); } - m->prop[i].type = type; - m->prop[i].value.num = value; +void addProp(m3dm_t *m, uint8_t type, uint32_t value) { + unsigned int i; + i = m->numprop++; + m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if (!m->prop) { + throw DeadlyExportError("memory allocation error"); + } + m->prop[i].type = type; + m->prop[i].value.num = value; } +// ------------------------------------------------------------------------------------------------ +// add a material to the output +M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) { + unsigned int mi = -1U; + aiColor4D c; + aiString name; + ai_real f; + char *fn; + + if (mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length && + strcmp((char *)&name.data, AI_DEFAULT_MATERIAL_NAME)) { + // check if we have saved a material by this name. This has to be done + // because only the referenced materials should be added to the output + for (unsigned int i = 0; i < m3d->nummaterial; i++) + if (!strcmp((char *)&name.data, m3d->material[i].name)) { + mi = i; + break; + } + // if not found, add the material to the output + if (mi == -1U) { + unsigned int k; + mi = m3d->nummaterial++; + m3d->material = (m3dm_t *)M3D_REALLOC(m3d->material, m3d->nummaterial * sizeof(m3dm_t)); + if (!m3d->material) { + throw DeadlyExportError("memory allocation error"); + } + m3d->material[mi].name = _m3d_safestr((char *)&name.data, 0); + m3d->material[mi].numprop = 0; + m3d->material[mi].prop = NULL; + // iterate through the material property table and see what we got + for (k = 0; k < 15; k++) { + unsigned int j; + if (m3d_propertytypes[k].format == m3dpf_map) + continue; + if (aiProps[k].pKey) { + switch (m3d_propertytypes[k].format) { + case m3dpf_color: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, c) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, mkColor(&c)); + break; + case m3dpf_float: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, f) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, + /* not (uint32_t)f, because we don't want to convert + * it, we want to see it as 32 bits of memory */ + *((uint32_t *)&f)); + break; + case m3dpf_uint8: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) { + // special conversion for illumination model property + if (m3d_propertytypes[k].id == m3dp_il) { + switch (j) { + case aiShadingMode_NoShading: j = 0; break; + case aiShadingMode_Phong: j = 2; break; + default: j = 1; break; + } + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + } + break; + default: + if (mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + break; + } + } + if (aiTxProps[k].pKey && + mat->GetTexture((aiTextureType)aiTxProps[k].type, + aiTxProps[k].index, &name, NULL, NULL, NULL, + NULL, NULL) == AI_SUCCESS) { + unsigned int i; + for (j = name.length - 1; j > 0 && name.data[j] != '.'; j++) + ; + if (j && name.data[j] == '.' && + (name.data[j + 1] == 'p' || name.data[j + 1] == 'P') && + (name.data[j + 1] == 'n' || name.data[j + 1] == 'N') && + (name.data[j + 1] == 'g' || name.data[j + 1] == 'G')) + name.data[j] = 0; + // do we have this texture saved already? + fn = _m3d_safestr((char *)&name.data, 0); + for (j = 0, i = -1U; j < m3d->numtexture; j++) + if (!strcmp(fn, m3d->texture[j].name)) { + i = j; + free(fn); + break; + } + if (i == -1U) { + i = m3d->numtexture++; + m3d->texture = (m3dtx_t *)M3D_REALLOC( + m3d->texture, + m3d->numtexture * sizeof(m3dtx_t)); + if (!m3d->texture) { + throw DeadlyExportError("memory allocation error"); + } + // we don't need the texture itself, only its name + m3d->texture[i].name = fn; + m3d->texture[i].w = 0; + m3d->texture[i].h = 0; + m3d->texture[i].d = NULL; + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id + 128, i); + } + } + } + } + return mi; +} + +namespace Assimp { + +// --------------------------------------------------------------------- +// Worker function for exporting a scene to binary M3D. +// Prototyped and registered in Exporter.cpp +void ExportSceneM3D( + const char *pFile, + IOSystem *pIOSystem, + const aiScene *pScene, + const ExportProperties *pProperties) { + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform binary export + exporter.doExport(pFile, pIOSystem, false); +} + +// --------------------------------------------------------------------- +// Worker function for exporting a scene to ASCII A3D. +// Prototyped and registered in Exporter.cpp +void ExportSceneA3D( + const char *pFile, + IOSystem *pIOSystem, + const aiScene *pScene, + const ExportProperties *pProperties + +) { + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform ascii export + exporter.doExport(pFile, pIOSystem, true); +} + +// ------------------------------------------------------------------------------------------------ +M3DExporter::M3DExporter(const aiScene *pScene, const ExportProperties *pProperties) : + mScene(pScene), + mProperties(pProperties), + outfile() {} + +// ------------------------------------------------------------------------------------------------ +void M3DExporter::doExport( + const char *pFile, + IOSystem *pIOSystem, + bool toAscii) { + // TODO: convert mProperties into M3D_EXP_* flags + (void)mProperties; + + // open the indicated file for writing (in binary / ASCII mode) + outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb")); + if (!outfile) { + throw DeadlyExportError("could not open output .m3d file: " + std::string(pFile)); + } + + M3DWrapper m3d; + if (!m3d) { + throw DeadlyExportError("memory allocation error"); + } + m3d->name = _m3d_safestr((char *)&mScene->mRootNode->mName.data, 2); + + // Create a model from assimp structures + aiMatrix4x4 m; + NodeWalk(m3d, mScene->mRootNode, m); + + // serialize the structures + unsigned int size; + unsigned char *output = m3d.Save(M3D_EXP_FLOAT, M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), size); + + if (!output || size < 8) { + throw DeadlyExportError("unable to serialize into Model 3D"); + } + + // Write out serialized model + outfile->Write(output, size, 1); + + // explicitly release file pointer, + // so we don't have to rely on class destruction. + outfile.reset(); +} + +// ------------------------------------------------------------------------------------------------ +// recursive node walker +void M3DExporter::NodeWalk(const M3DWrapper &m3d, const aiNode *pNode, aiMatrix4x4 m) { + aiMatrix4x4 nm = m * pNode->mTransformation; + + for (unsigned int i = 0; i < pNode->mNumMeshes; i++) { + const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]]; + unsigned int mi = (M3D_INDEX)-1U; + if (mScene->mMaterials) { + // get the material for this mesh + mi = addMaterial(m3d, mScene->mMaterials[mesh->mMaterialIndex]); + } + // iterate through the mesh faces + for (unsigned int j = 0; j < mesh->mNumFaces; j++) { + unsigned int n; + const aiFace *face = &(mesh->mFaces[j]); + // only triangle meshes supported for now + if (face->mNumIndices != 3) { + throw DeadlyExportError("use aiProcess_Triangulate before export"); + } + // add triangle to the output + n = m3d->numface++; + m3d->face = (m3df_t *)M3D_REALLOC(m3d->face, + m3d->numface * sizeof(m3df_t)); + if (!m3d->face) { + throw DeadlyExportError("memory allocation error"); + } + /* set all index to -1 by default */ + m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] = + m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] = + m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U; + m3d->face[n].materialid = mi; + for (unsigned int k = 0; k < face->mNumIndices; k++) { + // get the vertex's index + unsigned int l = face->mIndices[k]; + unsigned int idx; + m3dv_t vertex; + m3dti_t ti; + // multiply the position vector by the transformation matrix + aiVector3D v = mesh->mVertices[l]; + v *= nm; + vertex.x = v.x; + vertex.y = v.y; + vertex.z = v.z; + vertex.w = 1.0; + vertex.color = 0; + vertex.skinid = -1U; + // add color if defined + if (mesh->HasVertexColors(0)) + vertex.color = mkColor(&mesh->mColors[0][l]); + // save the vertex to the output + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, + &vertex, &idx); + m3d->face[n].vertex[k] = (M3D_INDEX)idx; + // do we have texture coordinates? + if (mesh->HasTextureCoords(0)) { + ti.u = mesh->mTextureCoords[0][l].x; + ti.v = mesh->mTextureCoords[0][l].y; + m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx); + m3d->face[n].texcoord[k] = (M3D_INDEX)idx; + } + // do we have normal vectors? + if (mesh->HasNormals()) { + vertex.x = mesh->mNormals[l].x; + vertex.y = mesh->mNormals[l].y; + vertex.z = mesh->mNormals[l].z; + vertex.color = 0; + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx); + m3d->face[n].normal[k] = (M3D_INDEX)idx; + } + } + } + } + // repeat for the children nodes + for (unsigned int i = 0; i < pNode->mNumChildren; i++) { + NodeWalk(m3d, pNode->mChildren[i], nm); + } +} +} // namespace Assimp + #endif // ASSIMP_BUILD_NO_M3D_EXPORTER #endif // ASSIMP_BUILD_NO_EXPORT diff --git a/code/M3D/M3DExporter.h b/code/M3D/M3DExporter.h index 58d8d597e..fce89b9de 100644 --- a/code/M3D/M3DExporter.h +++ b/code/M3D/M3DExporter.h @@ -48,8 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_M3D_EXPORTER -#include "m3d.h" - #include //#include #include // StreamWriterLE @@ -68,6 +66,8 @@ namespace Assimp class IOStream; class ExportProperties; + class M3DWrapper; + // --------------------------------------------------------------------- /** Helper class to export a given scene to an M3D file. */ // --------------------------------------------------------------------- @@ -83,15 +83,9 @@ namespace Assimp const aiScene* mScene; // the scene to export const ExportProperties* mProperties; // currently unused std::shared_ptr outfile; // file to write to - m3d_t *m3d; // model for the C library to convert to // helper to do the recursive walking - void NodeWalk(const aiNode* pNode, aiMatrix4x4 m); - m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx); - m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx); - uint32_t mkColor(aiColor4D* c); - M3D_INDEX addMaterial(const aiMaterial *mat); - void addProp(m3dm_t *m, uint8_t type, uint32_t value); + void NodeWalk(const M3DWrapper &m3d, const aiNode* pNode, aiMatrix4x4 m); }; } diff --git a/code/M3D/M3DImporter.cpp b/code/M3D/M3DImporter.cpp index 9371e2228..5218dd9ed 100644 --- a/code/M3D/M3DImporter.cpp +++ b/code/M3D/M3DImporter.cpp @@ -44,20 +44,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define M3D_IMPLEMENTATION #define M3D_ASCII -#define M3D_NONORMALS /* leave the post-processing to Assimp */ +#define M3D_NONORMALS /* leave the post-processing to Assimp */ #define M3D_NOWEIGHTS #define M3D_NOANIMATION -#include -#include #include -#include -#include +#include #include -#include #include +#include +#include +#include +#include + #include "M3DImporter.h" #include "M3DMaterials.h" +#include "M3DWrapper.h" // RESOURCES: // https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md @@ -85,682 +87,656 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ static const aiImporterDesc desc = { - "Model 3D Importer", - "", - "", - "", - aiImporterFlags_SupportBinaryFlavour, - 0, - 0, - 0, - 0, - "m3d a3d" + "Model 3D Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "m3d a3d" }; -// workaround: the SDK expects a C callback, but we want to use Assimp::IOSystem to implement that -extern "C" { - void* m3dimporter_pIOHandler; - - unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) { - ai_assert( nullptr != fn ); - ai_assert( nullptr != size ); - std::string file(fn); - std::unique_ptr pStream( - (reinterpret_cast(m3dimporter_pIOHandler))->Open( file, "rb")); - size_t fileSize = 0; - unsigned char *data = NULL; - // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess) - if(pStream) { - fileSize = pStream->FileSize(); - // should be allocated with malloc(), because the library will call free() to deallocate - data = (unsigned char*)malloc(fileSize); - if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) { - pStream.reset(); - *size = 0; - // don't throw a deadly exception, it's not fatal if we can't read an external asset - return nullptr; - } - pStream.reset(); - } - *size = (int)fileSize; - return data; - } -} - namespace Assimp { using namespace std; // ------------------------------------------------------------------------------------------------ // Default constructor -M3DImporter::M3DImporter() -: mScene(nullptr) -, m3d(nullptr) { } - -// ------------------------------------------------------------------------------------------------ -// Destructor. -M3DImporter::~M3DImporter() {} +M3DImporter::M3DImporter() : + mScene(nullptr) {} // ------------------------------------------------------------------------------------------------ // Returns true, if file is a binary or ASCII Model 3D file. -bool M3DImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler , bool checkSig) const { - const std::string extension = GetExtension(pFile); +bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + const std::string extension = GetExtension(pFile); - if (extension == "m3d" || extension == "a3d") - return true; - else if (!extension.length() || checkSig) { - if (!pIOHandler) { - return true; - } - /* + if (extension == "m3d" || extension == "a3d") + return true; + else if (!extension.length() || checkSig) { + if (!pIOHandler) { + return true; + } + /* * don't use CheckMagicToken because that checks with swapped bytes too, leading to false * positives. This magic is not uint32_t, but char[4], so memcmp is the best way const char* tokens[] = {"3DMO", "3dmo"}; return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4); */ - std::unique_ptr pStream (pIOHandler->Open(pFile, "rb")); - unsigned char data[4]; - if(4 != pStream->Read(data,1,4)) { - return false; - } - return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */; - } - return false; + std::unique_ptr pStream(pIOHandler->Open(pFile, "rb")); + unsigned char data[4]; + if (4 != pStream->Read(data, 1, 4)) { + return false; + } + return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */; + } + return false; } // ------------------------------------------------------------------------------------------------ -const aiImporterDesc* M3DImporter::GetInfo() const { - return &desc; +const aiImporterDesc *M3DImporter::GetInfo() const { + return &desc; } // ------------------------------------------------------------------------------------------------ // Model 3D import implementation -void M3DImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) { - // Read file into memory - std::unique_ptr pStream( pIOHandler->Open( file, "rb")); - if( !pStream.get() ) { - throw DeadlyImportError( "Failed to open file " + file + "." ); - } +void M3DImporter::InternReadFile(const std::string &file, aiScene *pScene, IOSystem *pIOHandler) { + // Read file into memory + std::unique_ptr pStream(pIOHandler->Open(file, "rb")); + if (!pStream.get()) { + throw DeadlyImportError("Failed to open file " + file + "."); + } - // Get the file-size and validate it, throwing an exception when fails - size_t fileSize = pStream->FileSize(); - if( fileSize < 8 ) { - throw DeadlyImportError( "M3D-file " + file + " is too small." ); - } - std::unique_ptr _buffer (new unsigned char[fileSize]); - unsigned char *data( _buffer.get() ); - if(fileSize != pStream->Read(data,1,fileSize)) { - throw DeadlyImportError( "Failed to read the file " + file + "." ); - } + // Get the file-size and validate it, throwing an exception when fails + size_t fileSize = pStream->FileSize(); + if (fileSize < 8) { + throw DeadlyImportError("M3D-file " + file + " is too small."); + } + std::vector buffer(fileSize); + if (fileSize != pStream->Read(buffer.data(), 1, fileSize)) { + throw DeadlyImportError("Failed to read the file " + file + "."); + } - // Get the path for external assets - std::string folderName( "./" ); - std::string::size_type pos = file.find_last_of( "\\/" ); - if ( pos != std::string::npos ) { - folderName = file.substr( 0, pos ); - if ( !folderName.empty() ) { - pIOHandler->PushDirectory( folderName ); - } - } - // pass this IOHandler to the C callback - m3dimporter_pIOHandler = pIOHandler; + // Get the path for external assets + std::string folderName("./"); + std::string::size_type pos = file.find_last_of("\\/"); + if (pos != std::string::npos) { + folderName = file.substr(0, pos); + if (!folderName.empty()) { + pIOHandler->PushDirectory(folderName); + } + } //DefaultLogger::create("/dev/stderr", Logger::VERBOSE); - ASSIMP_LOG_DEBUG_F("M3D: loading ", file); + ASSIMP_LOG_DEBUG_F("M3D: loading ", file); - // let the C SDK do the hard work for us - m3d = m3d_load(&data[0], m3dimporter_readfile, free, nullptr); - m3dimporter_pIOHandler = nullptr; - if( !m3d ) { - throw DeadlyImportError( "Unable to parse " + file + " as M3D." ); - } + // let the C SDK do the hard work for us + M3DWrapper m3d(pIOHandler, buffer); - // create the root node - pScene->mRootNode = new aiNode; - pScene->mRootNode->mName = aiString(std::string(std::string(m3d->name))); - pScene->mRootNode->mTransformation = aiMatrix4x4(); - pScene->mRootNode->mNumChildren = 0; - mScene = pScene; + + if (!m3d) { + throw DeadlyImportError("Unable to parse " + file + " as M3D."); + } - ASSIMP_LOG_DEBUG("M3D: root node " + std::string(m3d->name)); + // create the root node + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName = aiString(m3d.Name()); + pScene->mRootNode->mTransformation = aiMatrix4x4(); + pScene->mRootNode->mNumChildren = 0; + mScene = pScene; - // now we just have to fill up the Assimp structures in pScene - importMaterials(); - importTextures(); - importBones(-1U, pScene->mRootNode); - importMeshes(); - importAnimations(); + ASSIMP_LOG_DEBUG("M3D: root node " + m3d.Name()); - // we don't need the SDK's version any more - m3d_free(m3d); + // now we just have to fill up the Assimp structures in pScene + importMaterials(m3d); + importTextures(m3d); + importBones(m3d, -1U, pScene->mRootNode); + importMeshes(m3d); + importAnimations(m3d); - // Pop directory stack - if ( pIOHandler->StackSize() > 0 ) { - pIOHandler->PopDirectory(); - } + // Pop directory stack + if (pIOHandler->StackSize() > 0) { + pIOHandler->PopDirectory(); + } } // ------------------------------------------------------------------------------------------------ // convert materials. properties are converted using a static table in M3DMaterials.h -void M3DImporter::importMaterials() -{ - unsigned int i, j, k, l, n; - m3dm_t *m; - aiString name = aiString(AI_DEFAULT_MATERIAL_NAME); - aiColor4D c; - ai_real f; +void M3DImporter::importMaterials(const M3DWrapper &m3d_wrap) { + unsigned int i, j, k, l, n; + m3dm_t *m; + aiString name = aiString(AI_DEFAULT_MATERIAL_NAME); + aiColor4D c; + ai_real f; - ai_assert(mScene != nullptr); - ai_assert(m3d != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d_wrap); - mScene->mNumMaterials = m3d->nummaterial + 1; - mScene->mMaterials = new aiMaterial*[ m3d->nummaterial + 1 ]; + mScene->mNumMaterials = m3d_wrap->nummaterial + 1; + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; - ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials); + ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials); - // add a default material as first - aiMaterial* mat = new aiMaterial; - mat->AddProperty( &name, AI_MATKEY_NAME ); - c.a = 1.0; c.b = c.g = c.r = 0.6; - mat->AddProperty( &c, 1, AI_MATKEY_COLOR_DIFFUSE); - mScene->mMaterials[0] = mat; + // add a default material as first + aiMaterial *mat = new aiMaterial; + mat->AddProperty(&name, AI_MATKEY_NAME); + c.a = 1.0f; + c.b = c.g = c.r = 0.6f; + mat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE); + mScene->mMaterials[0] = mat; - for(i = 0; i < m3d->nummaterial; i++) { - m = &m3d->material[i]; - aiMaterial* mat = new aiMaterial; - name.Set(std::string(m->name)); - mat->AddProperty( &name, AI_MATKEY_NAME ); - for(j = 0; j < m->numprop; j++) { - // look up property type - // 0 - 127 scalar values, - // 128 - 255 the same properties but for texture maps - k = 256; - for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) - if(m->prop[j].type == m3d_propertytypes[l].id || - m->prop[j].type == m3d_propertytypes[l].id + 128) { - k = l; - break; - } - // should never happen, but be safe than sorry - if(k == 256) continue; + for (i = 0; i < m3d_wrap->nummaterial; i++) { + m = &m3d_wrap->material[i]; + aiMaterial *mat = new aiMaterial; + name.Set(std::string(m->name)); + mat->AddProperty(&name, AI_MATKEY_NAME); + for (j = 0; j < m->numprop; j++) { + // look up property type + // 0 - 127 scalar values, + // 128 - 255 the same properties but for texture maps + k = 256; + for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) + if (m->prop[j].type == m3d_propertytypes[l].id || + m->prop[j].type == m3d_propertytypes[l].id + 128) { + k = l; + break; + } + // should never happen, but be safe than sorry + if (k == 256) continue; - // scalar properties - if(m->prop[j].type < 128 && aiProps[k].pKey) { - switch(m3d_propertytypes[k].format) { - case m3dpf_color: - c = mkColor(m->prop[j].value.color); - mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); - break; - case m3dpf_float: - f = m->prop[j].value.fnum; - mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); - break; - default: - n = m->prop[j].value.num; - if(m->prop[j].type == m3dp_il) { - switch(n) { - case 0: n = aiShadingMode_NoShading; break; - case 2: n = aiShadingMode_Phong; break; - default: n = aiShadingMode_Gouraud; break; - } - } - mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); - break; - } - } - // texture map properties - if(m->prop[j].type >= 128 && aiTxProps[k].pKey && - // extra check, should never happen, do we have the refered texture? - m->prop[j].value.textureid < m3d->numtexture && - m3d->texture[m->prop[j].value.textureid].name) { - name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png")); - mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); - n = 0; - mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); - } - } - mScene->mMaterials[i + 1] = mat; - } + // scalar properties + if (m->prop[j].type < 128 && aiProps[k].pKey) { + switch (m3d_propertytypes[k].format) { + case m3dpf_color: + c = mkColor(m->prop[j].value.color); + mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + case m3dpf_float: + f = m->prop[j].value.fnum; + mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + default: + n = m->prop[j].value.num; + if (m->prop[j].type == m3dp_il) { + switch (n) { + case 0: n = aiShadingMode_NoShading; break; + case 2: n = aiShadingMode_Phong; break; + default: n = aiShadingMode_Gouraud; break; + } + } + mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + } + } + // texture map properties + if (m->prop[j].type >= 128 && aiTxProps[k].pKey && + // extra check, should never happen, do we have the refered texture? + m->prop[j].value.textureid < m3d_wrap->numtexture && + m3d_wrap->texture[m->prop[j].value.textureid].name) { + name.Set(std::string(std::string(m3d_wrap->texture[m->prop[j].value.textureid].name) + ".png")); + mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); + n = 0; + mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); + } + } + mScene->mMaterials[i + 1] = mat; + } } // ------------------------------------------------------------------------------------------------ // import textures, this is the simplest of all -void M3DImporter::importTextures() -{ - unsigned int i; - const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" }; - m3dtx_t *t; +void M3DImporter::importTextures(const M3DWrapper &m3d) { + unsigned int i; + const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" }; + m3dtx_t *t; - ai_assert(mScene != nullptr); - ai_assert(m3d != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d); - mScene->mNumTextures = m3d->numtexture; - ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures); + mScene->mNumTextures = m3d->numtexture; + ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures); - if(!m3d->numtexture) - return; + if (!m3d->numtexture) + return; - mScene->mTextures = new aiTexture*[m3d->numtexture]; - for(i = 0; i < m3d->numtexture; i++) { - unsigned int j, k; - t = &m3d->texture[i]; - if(!t->w || !t->h || !t->f || !t->d) continue; - aiTexture *tx = new aiTexture; - strcpy(tx->achFormatHint, formatHint[t->f - 1]); - tx->mFilename = aiString(std::string(t->name) + ".png"); - tx->mWidth = t->w; - tx->mHeight = t->h; - tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ]; - for(j = k = 0; j < tx->mWidth*tx->mHeight; j++) { - switch(t->f) { - case 1: tx->pcData[j].g = t->d[k++]; break; - case 2: tx->pcData[j].g = t->d[k++]; tx->pcData[j].a = t->d[k++]; break; - case 3: - tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++]; - tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = 255; - break; - case 4: - tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++]; - tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = t->d[k++]; - break; - } - } - mScene->mTextures[i] = tx; - } + mScene->mTextures = new aiTexture *[m3d->numtexture]; + for (i = 0; i < m3d->numtexture; i++) { + unsigned int j, k; + t = &m3d->texture[i]; + if (!t->w || !t->h || !t->f || !t->d) continue; + aiTexture *tx = new aiTexture; + strcpy(tx->achFormatHint, formatHint[t->f - 1]); + tx->mFilename = aiString(std::string(t->name) + ".png"); + tx->mWidth = t->w; + tx->mHeight = t->h; + tx->pcData = new aiTexel[tx->mWidth * tx->mHeight]; + for (j = k = 0; j < tx->mWidth * tx->mHeight; j++) { + switch (t->f) { + case 1: tx->pcData[j].g = t->d[k++]; break; + case 2: + tx->pcData[j].g = t->d[k++]; + tx->pcData[j].a = t->d[k++]; + break; + case 3: + tx->pcData[j].r = t->d[k++]; + tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; + tx->pcData[j].a = 255; + break; + case 4: + tx->pcData[j].r = t->d[k++]; + tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; + tx->pcData[j].a = t->d[k++]; + break; + } + } + mScene->mTextures[i] = tx; + } } // ------------------------------------------------------------------------------------------------ // this is tricky. M3D has a global vertex and UV list, and faces are indexing them // individually. In assimp there're per mesh vertex and UV lists, and they must be // indexed simultaneously. -void M3DImporter::importMeshes() -{ - unsigned int i, j, k, l, numpoly = 3, lastMat = -2U; - std::vector *meshes = new std::vector(); - std::vector *faces = nullptr; - std::vector *vertices = nullptr; - std::vector *normals = nullptr; - std::vector *texcoords = nullptr; - std::vector *colors = nullptr; - std::vector *vertexids = nullptr; - aiMesh *pMesh = nullptr; +void M3DImporter::importMeshes(const M3DWrapper &m3d) { + unsigned int i, j, k, l, numpoly = 3, lastMat = -2U; + std::vector *meshes = new std::vector(); + std::vector *faces = nullptr; + std::vector *vertices = nullptr; + std::vector *normals = nullptr; + std::vector *texcoords = nullptr; + std::vector *colors = nullptr; + std::vector *vertexids = nullptr; + aiMesh *pMesh = nullptr; - ai_assert(mScene != nullptr); - ai_assert(m3d != nullptr); - ai_assert(mScene->mRootNode != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d); + ai_assert(mScene->mRootNode != nullptr); - ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface); + ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface); - for(i = 0; i < m3d->numface; i++) { - // we must switch mesh if material changes - if(lastMat != m3d->face[i].materialid) { - lastMat = m3d->face[i].materialid; - if(pMesh && vertices && vertices->size() && faces && faces->size()) { - populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids); - meshes->push_back(pMesh); - delete faces; - delete vertices; - delete normals; - delete texcoords; - delete colors; - delete vertexids; // this is not stored in pMesh, just to collect bone vertices - } - pMesh = new aiMesh; - pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; - pMesh->mMaterialIndex = lastMat + 1; - faces = new std::vector(); - vertices = new std::vector(); - normals = new std::vector(); - texcoords = new std::vector(); - colors = new std::vector(); - vertexids = new std::vector(); - } - // add a face to temporary vector - aiFace *pFace = new aiFace; - pFace->mNumIndices = numpoly; - pFace->mIndices = new unsigned int[numpoly]; - for(j = 0; j < numpoly; j++) { - aiVector3D pos, uv, norm; - k = vertices->size(); - pFace->mIndices[j] = k; - l = m3d->face[i].vertex[j]; - pos.x = m3d->vertex[l].x; - pos.y = m3d->vertex[l].y; - pos.z = m3d->vertex[l].z; - vertices->push_back(pos); - colors->push_back(mkColor(m3d->vertex[l].color)); - // add a bone to temporary vector - if(m3d->vertex[l].skinid != -1U &&m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) { - // this is complicated, because M3D stores a list of bone id / weight pairs per - // vertex but assimp uses lists of local vertex id/weight pairs per local bone list - vertexids->push_back(l); - } - l = m3d->face[i].texcoord[j]; - if(l != -1U) { - uv.x = m3d->tmap[l].u; - uv.y = m3d->tmap[l].v; - uv.z = 0.0; - texcoords->push_back(uv); - } - l = m3d->face[i].normal[j]; - if(l != -1U) { - norm.x = m3d->vertex[l].x; - norm.y = m3d->vertex[l].y; - norm.z = m3d->vertex[l].z; - normals->push_back(norm); - } - } - faces->push_back(*pFace); - delete pFace; - } - // if there's data left in the temporary vectors, flush them - if(pMesh && vertices->size() && faces->size()) { - populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids); - meshes->push_back(pMesh); - } + for (i = 0; i < m3d->numface; i++) { + // we must switch mesh if material changes + if (lastMat != m3d->face[i].materialid) { + lastMat = m3d->face[i].materialid; + if (pMesh && vertices && vertices->size() && faces && faces->size()) { + populateMesh(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + delete faces; + delete vertices; + delete normals; + delete texcoords; + delete colors; + delete vertexids; // this is not stored in pMesh, just to collect bone vertices + } + pMesh = new aiMesh; + pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pMesh->mMaterialIndex = lastMat + 1; + faces = new std::vector(); + vertices = new std::vector(); + normals = new std::vector(); + texcoords = new std::vector(); + colors = new std::vector(); + vertexids = new std::vector(); + } + // add a face to temporary vector + aiFace *pFace = new aiFace; + pFace->mNumIndices = numpoly; + pFace->mIndices = new unsigned int[numpoly]; + for (j = 0; j < numpoly; j++) { + aiVector3D pos, uv, norm; + k = static_cast(vertices->size()); + pFace->mIndices[j] = k; + l = m3d->face[i].vertex[j]; + pos.x = m3d->vertex[l].x; + pos.y = m3d->vertex[l].y; + pos.z = m3d->vertex[l].z; + vertices->push_back(pos); + colors->push_back(mkColor(m3d->vertex[l].color)); + // add a bone to temporary vector + if (m3d->vertex[l].skinid != -1U && m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) { + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + vertexids->push_back(l); + } + l = m3d->face[i].texcoord[j]; + if (l != -1U) { + uv.x = m3d->tmap[l].u; + uv.y = m3d->tmap[l].v; + uv.z = 0.0; + texcoords->push_back(uv); + } + l = m3d->face[i].normal[j]; + if (l != -1U) { + norm.x = m3d->vertex[l].x; + norm.y = m3d->vertex[l].y; + norm.z = m3d->vertex[l].z; + normals->push_back(norm); + } + } + faces->push_back(*pFace); + delete pFace; + } + // if there's data left in the temporary vectors, flush them + if (pMesh && vertices->size() && faces->size()) { + populateMesh(m3d, pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + } - // create global mesh list in scene - mScene->mNumMeshes = meshes->size(); - mScene->mMeshes = new aiMesh*[mScene->mNumMeshes]; - std::copy(meshes->begin(), meshes->end(), mScene->mMeshes); + // create global mesh list in scene + mScene->mNumMeshes = static_cast(meshes->size()); + mScene->mMeshes = new aiMesh *[mScene->mNumMeshes]; + std::copy(meshes->begin(), meshes->end(), mScene->mMeshes); - // create mesh indeces in root node - mScene->mRootNode->mNumMeshes = meshes->size(); - mScene->mRootNode->mMeshes = new unsigned int[meshes->size()]; - for(i = 0; i < meshes->size(); i++) { - mScene->mRootNode->mMeshes[i] = i; - } + // create mesh indeces in root node + mScene->mRootNode->mNumMeshes = static_cast(meshes->size()); + mScene->mRootNode->mMeshes = new unsigned int[meshes->size()]; + for (i = 0; i < meshes->size(); i++) { + mScene->mRootNode->mMeshes[i] = i; + } - delete meshes; - if(faces) delete faces; - if(vertices) delete vertices; - if(normals) delete normals; - if(texcoords) delete texcoords; - if(colors) delete colors; - if(vertexids) delete vertexids; + delete meshes; + if (faces) delete faces; + if (vertices) delete vertices; + if (normals) delete normals; + if (texcoords) delete texcoords; + if (colors) delete colors; + if (vertexids) delete vertexids; } // ------------------------------------------------------------------------------------------------ // a reentrant node parser. Otherwise this is simple -void M3DImporter::importBones(unsigned int parentid, aiNode *pParent) -{ - unsigned int i, n; +void M3DImporter::importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent) { + unsigned int i, n; - ai_assert(pParent != nullptr); - ai_assert(mScene != nullptr); - ai_assert(m3d != nullptr); + ai_assert(pParent != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d); - ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid); + ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid); - for(n = 0, i = parentid + 1; i < m3d->numbone; i++) - if(m3d->bone[i].parent == parentid) n++; - pParent->mChildren = new aiNode*[n]; + for (n = 0, i = parentid + 1; i < m3d->numbone; i++) + if (m3d->bone[i].parent == parentid) n++; + pParent->mChildren = new aiNode *[n]; - for(i = parentid + 1; i < m3d->numbone; i++) { - if(m3d->bone[i].parent == parentid) { - aiNode *pChild = new aiNode; - pChild->mParent = pParent; - pChild->mName = aiString(std::string(m3d->bone[i].name)); - convertPose(&pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori); - pChild->mNumChildren = 0; - pParent->mChildren[pParent->mNumChildren] = pChild; - pParent->mNumChildren++; - importBones(i, pChild); - } - } + for (i = parentid + 1; i < m3d->numbone; i++) { + if (m3d->bone[i].parent == parentid) { + aiNode *pChild = new aiNode; + pChild->mParent = pParent; + pChild->mName = aiString(std::string(m3d->bone[i].name)); + convertPose(m3d, &pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori); + pChild->mNumChildren = 0; + pParent->mChildren[pParent->mNumChildren] = pChild; + pParent->mNumChildren++; + importBones(m3d, i, pChild); + } + } } // ------------------------------------------------------------------------------------------------ // this is another headache. M3D stores list of changed bone id/position/orientation triplets and // a timestamp per frame, but assimp needs timestamp and lists of position, orientation lists per // bone, so we have to convert between the two conceptually different representation forms -void M3DImporter::importAnimations() -{ - unsigned int i, j, k, l, pos, ori; - double t; - m3da_t *a; +void M3DImporter::importAnimations(const M3DWrapper &m3d) { + unsigned int i, j, k, l, pos, ori; + double t; + m3da_t *a; - ai_assert(mScene != nullptr); - ai_assert(m3d != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d); - mScene->mNumAnimations = m3d->numaction; + mScene->mNumAnimations = m3d->numaction; - ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations); + ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations); - if(!m3d->numaction || !m3d->numbone) - return; + if (!m3d->numaction || !m3d->numbone) + return; - mScene->mAnimations = new aiAnimation*[m3d->numaction]; - for(i = 0; i < m3d->numaction; i++) { - a = &m3d->action[i]; - aiAnimation *pAnim = new aiAnimation; - pAnim->mName = aiString(std::string(a->name)); - pAnim->mDuration = ((double)a->durationmsec) / 10; - pAnim->mTicksPerSecond = 100; - // now we know how many bones are referenced in this animation - pAnim->mNumChannels = m3d->numbone; - pAnim->mChannels = new aiNodeAnim*[pAnim->mNumChannels]; - for(l = 0; l < m3d->numbone; l++) { - unsigned int n; - pAnim->mChannels[l] = new aiNodeAnim; - pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name)); - // now n is the size of positions / orientations arrays - pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe; - pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe]; - pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe]; - pos = m3d->bone[l].pos; - ori = m3d->bone[l].ori; - for(j = n = 0; j < a->numframe; j++) { - t = ((double)a->frame[j].msec) / 10; - for(k = 0; k < a->frame[j].numtransform; k++) { - if(a->frame[j].transform[k].boneid == l) { - pos = a->frame[j].transform[k].pos; - ori = a->frame[j].transform[k].ori; - } - } - m3dv_t *v = &m3d->vertex[pos]; - m3dv_t *q = &m3d->vertex[ori]; - pAnim->mChannels[l]->mPositionKeys[j].mTime = t; - pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x; - pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y; - pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z; - pAnim->mChannels[l]->mRotationKeys[j].mTime = t; - pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w; - pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x; - pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y; - pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z; - }// foreach frame - }// foreach bones - mScene->mAnimations[i] = pAnim; - } + mScene->mAnimations = new aiAnimation *[m3d->numaction]; + for (i = 0; i < m3d->numaction; i++) { + a = &m3d->action[i]; + aiAnimation *pAnim = new aiAnimation; + pAnim->mName = aiString(std::string(a->name)); + pAnim->mDuration = ((double)a->durationmsec) / 10; + pAnim->mTicksPerSecond = 100; + // now we know how many bones are referenced in this animation + pAnim->mNumChannels = m3d->numbone; + pAnim->mChannels = new aiNodeAnim *[pAnim->mNumChannels]; + for (l = 0; l < m3d->numbone; l++) { + unsigned int n; + pAnim->mChannels[l] = new aiNodeAnim; + pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name)); + // now n is the size of positions / orientations arrays + pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe; + pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe]; + pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe]; + pos = m3d->bone[l].pos; + ori = m3d->bone[l].ori; + for (j = n = 0; j < a->numframe; j++) { + t = ((double)a->frame[j].msec) / 10; + for (k = 0; k < a->frame[j].numtransform; k++) { + if (a->frame[j].transform[k].boneid == l) { + pos = a->frame[j].transform[k].pos; + ori = a->frame[j].transform[k].ori; + } + } + m3dv_t *v = &m3d->vertex[pos]; + m3dv_t *q = &m3d->vertex[ori]; + pAnim->mChannels[l]->mPositionKeys[j].mTime = t; + pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x; + pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y; + pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z; + pAnim->mChannels[l]->mRotationKeys[j].mTime = t; + pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w; + pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x; + pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y; + pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z; + } // foreach frame + } // foreach bones + mScene->mAnimations[i] = pAnim; + } } // ------------------------------------------------------------------------------------------------ // convert uint32_t into aiColor4D aiColor4D M3DImporter::mkColor(uint32_t c) { - aiColor4D color; - color.a = ((float)((c >> 24)&0xff)) / 255; - color.b = ((float)((c >> 16)&0xff)) / 255; - color.g = ((float)((c >> 8)&0xff)) / 255; - color.r = ((float)((c >> 0)&0xff)) / 255; - return color; + aiColor4D color; + color.a = ((float)((c >> 24) & 0xff)) / 255; + color.b = ((float)((c >> 16) & 0xff)) / 255; + color.g = ((float)((c >> 8) & 0xff)) / 255; + color.r = ((float)((c >> 0) & 0xff)) / 255; + return color; } // ------------------------------------------------------------------------------------------------ // convert a position id and orientation id into a 4 x 4 transformation matrix -void M3DImporter::convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) -{ - ai_assert(m != nullptr); - ai_assert(m3d != nullptr); - ai_assert(posid != -1U && posid < m3d->numvertex); - ai_assert(orientid != -1U && orientid < m3d->numvertex); - m3dv_t *p = &m3d->vertex[posid]; - m3dv_t *q = &m3d->vertex[orientid]; +void M3DImporter::convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) { + ai_assert(m != nullptr); + ai_assert(m3d); + ai_assert(posid != -1U && posid < m3d->numvertex); + ai_assert(orientid != -1U && orientid < m3d->numvertex); + m3dv_t *p = &m3d->vertex[posid]; + m3dv_t *q = &m3d->vertex[orientid]; - /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */ - if(q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) { - m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0; - m->a1 = m->b2 = m->c3 = -1.0; - } else { - m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0; - m->a2 = 2 * (q->x * q->y - q->z * q->w); if(m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0; - m->a3 = 2 * (q->x * q->z + q->y * q->w); if(m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0; - m->b1 = 2 * (q->x * q->y + q->z * q->w); if(m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0; - m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0; - m->b3 = 2 * (q->y * q->z - q->x * q->w); if(m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0; - m->c1 = 2 * (q->x * q->z - q->y * q->w); if(m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0; - m->c2 = 2 * (q->y * q->z + q->x * q->w); if(m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0; - m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0; - } + /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */ + if (q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) { + m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0; + m->a1 = m->b2 = m->c3 = -1.0; + } else { + m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); + if (m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0; + m->a2 = 2 * (q->x * q->y - q->z * q->w); + if (m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0; + m->a3 = 2 * (q->x * q->z + q->y * q->w); + if (m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0; + m->b1 = 2 * (q->x * q->y + q->z * q->w); + if (m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0; + m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); + if (m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0; + m->b3 = 2 * (q->y * q->z - q->x * q->w); + if (m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0; + m->c1 = 2 * (q->x * q->z - q->y * q->w); + if (m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0; + m->c2 = 2 * (q->y * q->z + q->x * q->w); + if (m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0; + m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); + if (m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0; + } - /* set translation */ - m->a4 = p->x; m->b4 = p->y; m->c4 = p->z; + /* set translation */ + m->a4 = p->x; + m->b4 = p->y; + m->c4 = p->z; - m->d1 = 0; m->d2 = 0; m->d3 = 0; m->d4 = 1; + m->d1 = 0; + m->d2 = 0; + m->d3 = 0; + m->d4 = 1; } // ------------------------------------------------------------------------------------------------ // find a node by name -aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) -{ - unsigned int i; +aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) { + unsigned int i; - ai_assert(pNode != nullptr); - ai_assert(mScene != nullptr); + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); - if(pNode->mName == name) - return pNode; - for(i = 0; i < pNode->mNumChildren; i++) { - aiNode *pChild = findNode(pNode->mChildren[i], name); - if(pChild) return pChild; - } - return nullptr; + if (pNode->mName == name) + return pNode; + for (i = 0; i < pNode->mNumChildren; i++) { + aiNode *pChild = findNode(pNode->mChildren[i], name); + if (pChild) return pChild; + } + return nullptr; } // ------------------------------------------------------------------------------------------------ // fills up offsetmatrix in mBones -void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m) -{ - ai_assert(pNode != nullptr); - ai_assert(mScene != nullptr); +void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m) { + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); - if(pNode->mParent) { - calculateOffsetMatrix(pNode->mParent, m); - *m *= pNode->mTransformation; - } else { - *m = pNode->mTransformation; - } + if (pNode->mParent) { + calculateOffsetMatrix(pNode->mParent, m); + *m *= pNode->mTransformation; + } else { + *m = pNode->mTransformation; + } } // ------------------------------------------------------------------------------------------------ // because M3D has a global mesh, global vertex ids and stores materialid on the face, we need // temporary lists to collect data for an aiMesh, which requires local arrays and local indeces // this function fills up an aiMesh with those temporary lists -void M3DImporter::populateMesh(aiMesh *pMesh, std::vector *faces, std::vector *vertices, - std::vector *normals, std::vector *texcoords, std::vector *colors, - std::vector *vertexids) { +void M3DImporter::populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector *faces, std::vector *vertices, + std::vector *normals, std::vector *texcoords, std::vector *colors, + std::vector *vertexids) { - ai_assert(pMesh != nullptr); - ai_assert(faces != nullptr); - ai_assert(vertices != nullptr); - ai_assert(normals != nullptr); - ai_assert(texcoords != nullptr); - ai_assert(colors != nullptr); - ai_assert(vertexids != nullptr); - ai_assert(m3d != nullptr); + ai_assert(pMesh != nullptr); + ai_assert(faces != nullptr); + ai_assert(vertices != nullptr); + ai_assert(normals != nullptr); + ai_assert(texcoords != nullptr); + ai_assert(colors != nullptr); + ai_assert(vertexids != nullptr); + ai_assert(m3d); - ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(), - " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone); + ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(), + " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone); - if(vertices->size() && faces->size()) { - pMesh->mNumFaces = faces->size(); - pMesh->mFaces = new aiFace[pMesh->mNumFaces]; - std::copy(faces->begin(), faces->end(), pMesh->mFaces); - pMesh->mNumVertices = vertices->size(); - pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; - std::copy(vertices->begin(), vertices->end(), pMesh->mVertices); - if(normals->size() == vertices->size()) { - pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; - std::copy(normals->begin(), normals->end(), pMesh->mNormals); - } - if(texcoords->size() == vertices->size()) { - pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; - std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]); - pMesh->mNumUVComponents[0] = 2; - } - if(colors->size() == vertices->size()) { - pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; - std::copy(colors->begin(), colors->end(), pMesh->mColors[0]); - } - // this is complicated, because M3D stores a list of bone id / weight pairs per - // vertex but assimp uses lists of local vertex id/weight pairs per local bone list - pMesh->mNumBones = m3d->numbone; - /* we need aiBone with mOffsetMatrix for bones without weights as well */ - if(pMesh->mNumBones) { - pMesh->mBones = new aiBone*[pMesh->mNumBones]; - for(unsigned int i = 0; i < m3d->numbone; i++) { - aiNode *pNode; - pMesh->mBones[i] = new aiBone; - pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name)); - pMesh->mBones[i]->mNumWeights = 0; - pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName); - if(pNode) { - calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix); - pMesh->mBones[i]->mOffsetMatrix.Inverse(); - } else - pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4(); - } - if(vertexids->size()) { - unsigned int i, j; - // first count how many vertices we have per bone - for(i = 0; i < vertexids->size(); i++) { - unsigned int s = m3d->vertex[vertexids->at(i)].skinid; - if(s != -1U && s!= -2U) { - for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { - aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); - for(j = 0; j < pMesh->mNumBones; j++) { - if(pMesh->mBones[j]->mName == name) { - pMesh->mBones[j]->mNumWeights++; - break; - } - } - } - } - } - // allocate mWeights - for(j = 0; j < pMesh->mNumBones; j++) { - aiBone *pBone = pMesh->mBones[j]; - if(pBone->mNumWeights) { - pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; - pBone->mNumWeights = 0; - } - } - // fill up with data - for(i = 0; i < vertexids->size(); i++) { - unsigned int s = m3d->vertex[vertexids->at(i)].skinid; - if(s != -1U && s!= -2U) { - for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { - aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); - for(j = 0; j < pMesh->mNumBones; j++) { - if(pMesh->mBones[j]->mName == name) { - aiBone *pBone = pMesh->mBones[j]; - pBone->mWeights[pBone->mNumWeights].mVertexId = i; - pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k]; - pBone->mNumWeights++; - break; - } - } - } // foreach skin - } - } // foreach vertexids - } - } - } + if (vertices->size() && faces->size()) { + pMesh->mNumFaces = static_cast(faces->size()); + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + std::copy(faces->begin(), faces->end(), pMesh->mFaces); + pMesh->mNumVertices = static_cast(vertices->size()); + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + std::copy(vertices->begin(), vertices->end(), pMesh->mVertices); + if (normals->size() == vertices->size()) { + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + std::copy(normals->begin(), normals->end(), pMesh->mNormals); + } + if (texcoords->size() == vertices->size()) { + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]); + pMesh->mNumUVComponents[0] = 2; + } + if (colors->size() == vertices->size()) { + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + std::copy(colors->begin(), colors->end(), pMesh->mColors[0]); + } + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + pMesh->mNumBones = m3d->numbone; + /* we need aiBone with mOffsetMatrix for bones without weights as well */ + if (pMesh->mNumBones) { + pMesh->mBones = new aiBone *[pMesh->mNumBones]; + for (unsigned int i = 0; i < m3d->numbone; i++) { + aiNode *pNode; + pMesh->mBones[i] = new aiBone; + pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name)); + pMesh->mBones[i]->mNumWeights = 0; + pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName); + if (pNode) { + calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix); + pMesh->mBones[i]->mOffsetMatrix.Inverse(); + } else + pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4(); + } + if (vertexids->size()) { + unsigned int i, j; + // first count how many vertices we have per bone + for (i = 0; i < vertexids->size(); i++) { + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if (s != -1U && s != -2U) { + for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for (j = 0; j < pMesh->mNumBones; j++) { + if (pMesh->mBones[j]->mName == name) { + pMesh->mBones[j]->mNumWeights++; + break; + } + } + } + } + } + // allocate mWeights + for (j = 0; j < pMesh->mNumBones; j++) { + aiBone *pBone = pMesh->mBones[j]; + if (pBone->mNumWeights) { + pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; + pBone->mNumWeights = 0; + } + } + // fill up with data + for (i = 0; i < vertexids->size(); i++) { + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if (s != -1U && s != -2U) { + for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for (j = 0; j < pMesh->mNumBones; j++) { + if (pMesh->mBones[j]->mName == name) { + aiBone *pBone = pMesh->mBones[j]; + pBone->mWeights[pBone->mNumWeights].mVertexId = i; + pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k]; + pBone->mNumWeights++; + break; + } + } + } // foreach skin + } + } // foreach vertexids + } + } + } } // ------------------------------------------------------------------------------------------------ -} // Namespace Assimp +} // Namespace Assimp #endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER diff --git a/code/M3D/M3DImporter.h b/code/M3D/M3DImporter.h index 06cc757b6..e8e3a65ed 100644 --- a/code/M3D/M3DImporter.h +++ b/code/M3D/M3DImporter.h @@ -48,7 +48,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER -#include "m3d.h" #include #include #include @@ -60,43 +59,41 @@ struct aiFace; namespace Assimp { +class M3DWrapper; + class M3DImporter : public BaseImporter { public: - /// \brief Default constructor - M3DImporter(); - - /// \brief Destructor - ~M3DImporter(); + /// \brief Default constructor + M3DImporter(); public: - /// \brief Returns whether the class can handle the format of the given file. - /// \remark See BaseImporter::CanRead() for details. - bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const; private: - aiScene* mScene; // the scene to import to - m3d_t *m3d; // model for the C library to convert from + aiScene *mScene = nullptr; // the scene to import to - //! \brief Appends the supported extension. - const aiImporterDesc* GetInfo () const; + //! \brief Appends the supported extension. + const aiImporterDesc *GetInfo() const; - //! \brief File import implementation. - void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + //! \brief File import implementation. + void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler); - void importMaterials(); - void importTextures(); - void importMeshes(); - void importBones(unsigned int parentid, aiNode *pParent); - void importAnimations(); + void importMaterials(const M3DWrapper &m3d); + void importTextures(const M3DWrapper &m3d); + void importMeshes(const M3DWrapper &m3d); + void importBones(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent); + void importAnimations(const M3DWrapper &m3d); - // helper functions - aiColor4D mkColor(uint32_t c); - void convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); - aiNode *findNode(aiNode *pNode, aiString name); - void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); - void populateMesh(aiMesh *pMesh, std::vector *faces, std::vector *verteces, - std::vector *normals, std::vector *texcoords, std::vector *colors, - std::vector *vertexids); + // helper functions + aiColor4D mkColor(uint32_t c); + void convertPose(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); + aiNode *findNode(aiNode *pNode, aiString name); + void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); + void populateMesh(const M3DWrapper &m3d, aiMesh *pMesh, std::vector *faces, std::vector *verteces, + std::vector *normals, std::vector *texcoords, std::vector *colors, + std::vector *vertexids); }; } // Namespace Assimp diff --git a/code/M3D/M3DWrapper.cpp b/code/M3D/M3DWrapper.cpp new file mode 100644 index 000000000..0060c894e --- /dev/null +++ b/code/M3D/M3DWrapper.cpp @@ -0,0 +1,142 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER + +#include "M3DWrapper.h" + +#include +#include +#include + +#ifndef AI_M3D_USE_STDMUTEX +#if (__cplusplus >= 201103L) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards +#define AI_M3D_USE_STDMUTEX 1 +#else +#define AI_M3D_USE_STDMUTEX 0 +#endif +#endif + +#if AI_M3D_USE_STDMUTEX +#include +std::mutex file_mutex; +#endif + +// workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that +// This makes it non-rentrant so lock a mutex (requires C++11) + +extern "C" { +void *m3dimporter_pIOHandler; + +unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) { + ai_assert(nullptr != fn); + ai_assert(nullptr != size); + std::string file(fn); + std::unique_ptr pStream( + (reinterpret_cast(m3dimporter_pIOHandler))->Open(file, "rb")); + size_t fileSize = 0; + unsigned char *data = NULL; + // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess) + if (pStream) { + fileSize = pStream->FileSize(); + // should be allocated with malloc(), because the library will call free() to deallocate + data = (unsigned char *)malloc(fileSize); + if (!data || !pStream.get() || !fileSize || fileSize != pStream->Read(data, 1, fileSize)) { + pStream.reset(); + *size = 0; + // don't throw a deadly exception, it's not fatal if we can't read an external asset + return nullptr; + } + pStream.reset(); + } + *size = (int)fileSize; + return data; +} +} + +namespace Assimp { +M3DWrapper::M3DWrapper() { + // use malloc() here because m3d_free() will call free() + m3d_ = (m3d_t *)calloc(1, sizeof(m3d_t)); +} + +M3DWrapper::M3DWrapper(IOSystem *pIOHandler, const std::vector &buffer) { +#if AI_M3D_USE_STDMUTEX + // M3D is NOT thread-safe, so lock the global mutex + const std::lock_guard lock(file_mutex); +#endif + // pass this IOHandler to the C callback + m3dimporter_pIOHandler = pIOHandler; + m3d_ = m3d_load(const_cast(buffer.data()), m3dimporter_readfile, free, nullptr); + // Clear the C callback + m3dimporter_pIOHandler = nullptr; +} + +M3DWrapper::~M3DWrapper() { + reset(); +} + +void M3DWrapper::reset() { + ClearSave(); + if (m3d_) + m3d_free(m3d_); + m3d_ = nullptr; +} + +unsigned char *M3DWrapper::Save(int quality, int flags, unsigned int &size) { +#if (!(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER)) + ClearSave(); + saved_output_ = m3d_save(m3d_, quality, flags, &size); + return saved_output_; +#else + return nullptr; +#endif +} + +void M3DWrapper::ClearSave() { + if (saved_output_) + M3D_FREE(saved_output_); + saved_output_ = nullptr; +} +} // namespace Assimp + +#endif diff --git a/code/M3D/M3DWrapper.h b/code/M3D/M3DWrapper.h new file mode 100644 index 000000000..f404d3d4b --- /dev/null +++ b/code/M3D/M3DWrapper.h @@ -0,0 +1,97 @@ +#pragma once +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DWrapper.h +* @brief Declares a class to wrap the C m3d SDK +*/ +#ifndef AI_M3DWRAPPER_H_INC +#define AI_M3DWRAPPER_H_INC +#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER) || !ASSIMP_BUILD_NO_M3D_IMPORTER + +#include +#include +#include + +#include "m3d.h" + +namespace Assimp { +class IOSystem; + +class M3DWrapper { + m3d_t *m3d_ = nullptr; + unsigned char *saved_output_ = nullptr; + +public: + // Construct an empty M3D model + explicit M3DWrapper(); + + // Construct an M3D model from provided buffer + // NOTE: The m3d.h SDK function does not mark the data as const. Have assumed it does not write. + // BUG: SECURITY: The m3d.h SDK cannot be informed of the buffer size. BUFFER OVERFLOW IS CERTAIN + explicit M3DWrapper(IOSystem *pIOHandler, const std::vector &buffer); + + ~M3DWrapper(); + + void reset(); + + // Name + inline std::string Name() const { + if (m3d_) return std::string(m3d_->name); + return std::string(); + } + + // Execute a save + unsigned char *Save(int quality, int flags, unsigned int &size); + void ClearSave(); + + inline explicit operator bool() const { return m3d_ != nullptr; } + + // Allow direct access to M3D API + inline m3d_t *operator->() const { return m3d_; } + inline m3d_t *M3D() const { return m3d_; } +}; +} // namespace Assimp + +#endif + +#endif // AI_M3DWRAPPER_H_INC