Browse Source

Added M3D format support

bzt 5 years ago
parent
commit
0baec5f0bd

+ 1 - 0
Readme.md

@@ -67,6 +67,7 @@ __Importers__:
 - [LWO](https://en.wikipedia.org/wiki/LightWave_3D)
 - LWS
 - LXO
+- [M3D](https://gitlab.com/bztsrc/model3d)
 - MD2
 - MD3
 - MD5

+ 12 - 0
code/CMakeLists.txt

@@ -407,6 +407,18 @@ ADD_ASSIMP_IMPORTER( LWS
   LWS/LWSLoader.h
 )
 
+ADD_ASSIMP_IMPORTER( M3D
+  M3D/M3DMaterials.h
+  M3D/M3DImporter.h
+  M3D/M3DImporter.cpp
+  M3D/m3d.h
+)
+
+ADD_ASSIMP_EXPORTER( M3D
+  M3D/M3DExporter.h
+  M3D/M3DExporter.cpp
+)
+
 ADD_ASSIMP_IMPORTER( MD2
   MD2/MD2FileData.h
   MD2/MD2Loader.cpp

+ 7 - 0
code/Common/Exporter.cpp

@@ -102,6 +102,8 @@ void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperti
 void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
+void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneA3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
 void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*);
 
 // ------------------------------------------------------------------------------------------------
@@ -179,6 +181,11 @@ Exporter::ExportFormatEntry gExporters[] =
     Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
 #endif
 
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+    Exporter::ExportFormatEntry( "m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0 ),
+    Exporter::ExportFormatEntry( "a3d", "Model 3D (ascii)",  "m3d", &ExportSceneA3D, 0 ),
+#endif
+
 #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
     Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ),
 #endif

+ 6 - 0
code/Common/ImporterRegistry.cpp

@@ -197,6 +197,9 @@ corresponding preprocessor flag to selectively disable formats.
 #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
 #   include "MMD/MMDImporter.h"
 #endif
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#   include "M3D/M3DImporter.h"
+#endif
 #ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
 #   include "Importer/StepFile/StepFileImporter.h"
 #endif
@@ -223,6 +226,9 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out)
 #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER)
     out.push_back( new Discreet3DSImporter());
 #endif
+#if (!defined ASSIMP_BUILD_NO_M3D_IMPORTER)
+    out.push_back( new M3DImporter());
+#endif
 #if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER)
     out.push_back( new MD3Importer());
 #endif

+ 392 - 0
code/M3D/M3DExporter.cpp

@@ -0,0 +1,392 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#define M3D_IMPLEMENTATION
+#define M3D_NOIMPORTER
+#define M3D_EXPORTER
+#define M3D_ASCII
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#define M3D_NODUP
+#endif
+
+// Header files, standard library.
+#include <memory> // shared_ptr
+#include <string>
+#include <vector>
+
+#include <assimp/version.h> // aiGetVersion
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/material.h> // aiTextureType
+#include <assimp/scene.h>
+#include <assimp/mesh.h>
+#include "M3DExporter.h"
+#include "M3DMaterials.h"
+
+// RESOURCES:
+// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md
+// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md
+
+/*
+ * Currently supports static meshes, vertex colors, materials, textures
+ *
+ * For animation, it would require the following conversions:
+ *  - aiNode (bones) -> m3d_t.bone (with parent id, position vector and oriantation quaternion)
+ *  - aiMesh.aiBone -> m3d_t.skin (per vertex, with bone id, weight pairs)
+ *  - 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*)malloc(sizeof(m3d_t));
+    if(!m3d) {
+        throw DeadlyExportError( "memory allocation error" );
+    }
+    memset(m3d, 0, sizeof(m3d_t));
+    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();
+}
+
+// ------------------------------------------------------------------------------------------------
+// recursive node walker
+void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m)
+{
+    unsigned int i, j, k, l, n, mi, idx;
+    aiMatrix4x4 nm = m * pNode->mTransformation;
+    m3dv_t vertex;
+    m3dti_t ti;
+
+    for(i = 0; i < pNode->mNumMeshes; i++) {
+        const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]];
+
+        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(j = 0; j < mesh->mNumFaces; j++) {
+            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 */
+            memset(&m3d->face[n], 255, sizeof(m3df_t));
+            m3d->face[n].materialid = mi;
+            for(k = 0; k < face->mNumIndices; k++) {
+                // get the vertex's index
+                l = face->mIndices[k];
+                // multiply the position vector by the transformation matrix
+                aiVector3D v = mesh->mVertices[l];
+                v *= nm;
+                memset(&vertex, 0, sizeof(m3dv_t));
+                vertex.x = v.x;
+                vertex.y = v.y;
+                vertex.z = v.z;
+                vertex.w = 1.0;
+                // add color if defined
+                if(mesh->HasVertexColors(0))
+                    vertex.color = mkColor(&mesh->mColors[0][l]);
+                // save the vertex to the output
+                m3d->vertex = _m3d_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 = _m3d_addtmap(m3d->tmap, &m3d->numtmap, &ti,
+                        &idx);
+                    m3d->face[n].texcoord[k] = (M3D_INDEX)idx;
+                }
+                // do we have normal vectors?
+                if(mesh->HasNormals()) {
+                    vertex.color = 0;
+                    vertex.x = mesh->mNormals[l].x;
+                    vertex.y = mesh->mNormals[l].y;
+                    vertex.z = mesh->mNormals[l].z;
+                    m3d->vertex = _m3d_addnorm(m3d->vertex, &m3d->numvertex,
+                        &vertex, &idx);
+                    m3d->face[n].normal[k] = (M3D_INDEX)idx;
+                }
+            }
+        }
+    }
+    // repeat for the children nodes
+    for (i = 0; i < pNode->mNumChildren; i++) {
+        NodeWalk(pNode->mChildren[i], nm);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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 i, j, k, 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(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) {
+            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 < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]);
+                k++) {
+                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) {
+                        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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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;
+}
+
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 98 - 0
code/M3D/M3DExporter.h

@@ -0,0 +1,98 @@
+/*
+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 M3DExporter.h
+*   @brief Declares the exporter class to write a scene to a Model 3D file
+*/
+#ifndef AI_M3DEXPORTER_H_INC
+#define AI_M3DEXPORTER_H_INC
+
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#include "m3d.h"
+
+#include <assimp/types.h>
+//#include <assimp/material.h>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+
+#include <memory> // shared_ptr
+
+struct aiScene;
+struct aiNode;
+struct aiMaterial;
+struct aiFace;
+
+namespace Assimp
+{
+    class IOSystem;
+    class IOStream;
+    class ExportProperties;
+
+    // ---------------------------------------------------------------------
+    /** Helper class to export a given scene to an M3D file. */
+    // ---------------------------------------------------------------------
+    class M3DExporter
+    {
+    public:
+        /// Constructor for a specific scene to export
+        M3DExporter(const aiScene* pScene, const ExportProperties* pProperties);
+        // call this to do the actual export
+        void doExport(const char* pFile, IOSystem* pIOSystem, bool toAscii);
+
+    private:
+        const aiScene* mScene; // the scene to export
+        const ExportProperties* mProperties; // currently unused
+        std::shared_ptr<IOStream> 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);
+        uint32_t mkColor(aiColor4D* c);
+        M3D_INDEX addMaterial(const aiMaterial *mat);
+        void addProp(m3dm_t *m, uint8_t type, uint32_t value);
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#endif // AI_M3DEXPORTER_H_INC

+ 734 - 0
code/M3D/M3DImporter.cpp

@@ -0,0 +1,734 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#define M3D_IMPLEMENTATION
+#define M3D_ASCII
+
+#include <assimp/IOStreamBuffer.h>
+#include <memory>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Importer.hpp>
+#include <assimp/scene.h>
+#include <assimp/ai_assert.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/importerdesc.h>
+#include "M3DImporter.h"
+#include "M3DMaterials.h"
+
+// RESOURCES:
+// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md
+// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md
+
+/*
+ Unfortunately aiNode has bone structures and meshes too, yet we can't assign
+ the mesh to a bone aiNode as a skin may refer to several aiNodes. Therefore
+ I've decided to import into this structure:
+
+   aiScene->mRootNode
+    |        |->mMeshes (all the meshes)
+    |        \->children (empty if there's no skeleton imported, no meshes)
+    |             \->skeleton root aiNode*
+    |                   |->bone aiNode
+    |                   |   \->subbone aiNode
+    |                   |->bone aiNode
+    |                   |   ...
+    |                   \->bone aiNode
+    \->mMeshes[]
+        \->aiBone, referencing mesh-less aiNodes from above
+
+  * - normally one, but if a model has several skeleton roots, then all of them
+      are listed in aiScene->mRootNode->children, but all without meshes
+*/
+
+static const aiImporterDesc desc = {
+    "Model 3D Importer",
+    "",
+    "",
+    "",
+    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" {
+    struct Assimp::IOSystem* 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<Assimp::IOStream> pStream( m3dimporter_pIOHandler->Open( file, "rb"));
+        size_t fileSize = pStream->FileSize();
+        // should be allocated with malloc(), because the library will call free() to deallocate
+        unsigned char *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() {}
+
+// ------------------------------------------------------------------------------------------------
+//  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);
+
+    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<IOStream> 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;
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Model 3D import implementation
+void M3DImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) {
+    // Read file into memory
+    std::unique_ptr<IOStream> 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." );
+    }
+    unsigned char data[fileSize];
+    if(fileSize != pStream->Read(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;
+
+    //DefaultLogger::create("/dev/stderr", Logger::VERBOSE);
+    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." );
+    }
+
+    // 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;
+
+    ASSIMP_LOG_DEBUG("M3D: root node " + std::string(m3d->name));
+
+    // now we just have to fill up the Assimp structures in pScene
+    importMaterials();
+    importTextures();
+    importBones(-1U, pScene->mRootNode);
+    importMeshes();
+    importAnimations();
+
+    // we don't need the SDK's version any more
+    m3d_free(m3d);
+
+    // 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;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    mScene->mNumMaterials = m3d->nummaterial + 1;
+    mScene->mMaterials = new aiMaterial*[ m3d->nummaterial + 1 ];
+
+    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;
+
+    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;
+
+            // 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, aiProps[k].pKey, aiProps[k].type, aiProps[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;
+    m3dtx_t *t;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    mScene->mNumTextures = m3d->numtexture;
+    ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures);
+
+    if(!m3d->numtexture)
+        return;
+
+    mScene->mTextures = new aiTexture*[m3d->numtexture];
+    for(i = 0; i < m3d->numtexture; i++) {
+        t = &m3d->texture[i];
+        aiTexture *tx = new aiTexture;
+        strcpy(tx->achFormatHint, "rgba8888");
+        tx->mFilename = aiString(std::string(t->name) + ".png");
+        tx->mWidth = t->w;
+        tx->mHeight = t->h;
+        tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ];
+        memcpy(tx->pcData, t->d, tx->mWidth*tx->mHeight*4);
+        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<aiMesh*> *meshes = new std::vector<aiMesh*>();
+    std::vector<aiFace> *faces = nullptr;
+    std::vector<aiVector3D> *vertices = nullptr;
+    std::vector<aiVector3D> *normals = nullptr;
+    std::vector<aiVector3D> *texcoords = nullptr;
+    std::vector<aiColor4D> *colors = nullptr;
+    std::vector<unsigned int> *vertexids = nullptr;
+    aiMesh *pMesh = nullptr;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+    ai_assert(mScene->mRootNode != nullptr);
+
+    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->size() && faces->size()) {
+                populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids);
+                meshes->push_back(pMesh);
+                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<aiFace>();
+            vertices = new std::vector<aiVector3D>();
+            normals = new std::vector<aiVector3D>();
+            texcoords = new std::vector<aiVector3D>();
+            colors = new std::vector<aiColor4D>();
+            vertexids = new std::vector<unsigned int>();
+        }
+        // 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);
+    }
+
+    // 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 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;
+    }
+
+    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;
+
+    ai_assert(pParent != nullptr);
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    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(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);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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, n, pos, ori;
+    double t;
+    m3da_t *a;
+
+    ai_assert(mScene != nullptr);
+    ai_assert(m3d != nullptr);
+
+    mScene->mNumAnimations = m3d->numaction;
+
+    ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations);
+
+    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++) {
+            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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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];
+
+    /* 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 > -1e-7 && m->a1 < 1e-7) m->a1 = 0.0;
+        m->a2 = 2 * (q->x * q->y - q->z * q->w);     if(m->a2 > -1e-7 && m->a2 < 1e-7) m->a2 = 0.0;
+        m->a3 = 2 * (q->x * q->z + q->y * q->w);     if(m->a3 > -1e-7 && m->a3 < 1e-7) m->a3 = 0.0;
+        m->b1 = 2 * (q->x * q->y + q->z * q->w);     if(m->b1 > -1e-7 && m->b1 < 1e-7) m->b1 = 0.0;
+        m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -1e-7 && m->b2 < 1e-7) m->b2 = 0.0;
+        m->b3 = 2 * (q->y * q->z - q->x * q->w);     if(m->b3 > -1e-7 && m->b3 < 1e-7) m->b3 = 0.0;
+        m->c1 = 2 * (q->x * q->z - q->y * q->w);     if(m->c1 > -1e-7 && m->c1 < 1e-7) m->c1 = 0.0;
+        m->c2 = 2 * (q->y * q->z + q->x * q->w);     if(m->c2 > -1e-7 && m->c2 < 1e-7) m->c2 = 0.0;
+        m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -1e-7 && m->c3 < 1e-7) m->c3 = 0.0;
+    }
+
+    /* 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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// find a node by name
+aiNode *M3DImporter::findNode(aiNode *pNode, aiString name)
+{
+    unsigned int i;
+
+    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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// fills up offsetmatrix in mBones
+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;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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<aiFace> *faces, std::vector<aiVector3D> *vertices,
+    std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
+    std::vector<unsigned int> *vertexids) {
+    unsigned int i, j, k, s;
+    aiNode *pNode;
+
+    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);
+
+    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(i = 0; i < m3d->numbone; i++) {
+                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()) {
+                // first count how many vertices we have per bone
+                for(i = 0; i < vertexids->size(); i++) {
+                    s = m3d->vertex[vertexids->at(i)].skinid;
+                    if(s != -1U && s!= -2U) {
+                        for(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++) {
+                    s = m3d->vertex[vertexids->at(i)].skinid;
+                    if(s != -1U && s!= -2U) {
+                        for(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
+
+#endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER

+ 106 - 0
code/M3D/M3DImporter.h

@@ -0,0 +1,106 @@
+/*
+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 M3DImporter.h
+*   @brief Declares the importer class to read a scene from a Model 3D file
+*/
+#ifndef AI_M3DIMPORTER_H_INC
+#define AI_M3DIMPORTER_H_INC
+
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#include "m3d.h"
+#include <assimp/BaseImporter.h>
+#include <assimp/material.h>
+#include <vector>
+
+struct aiMesh;
+struct aiNode;
+struct aiMaterial;
+struct aiFace;
+
+namespace Assimp {
+
+class M3DImporter : public BaseImporter {
+public:
+    /// \brief  Default constructor
+    M3DImporter();
+
+    /// \brief  Destructor
+    ~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;
+
+private:
+    aiScene* mScene; // the scene to import to
+    m3d_t *m3d; // model for the C library to convert from
+
+    //! \brief  Appends the supported extension.
+    const aiImporterDesc* GetInfo () const;
+
+    //! \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();
+
+    // 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<aiFace> *faces, std::vector<aiVector3D> *verteces,
+        std::vector<aiVector3D> *normals, std::vector<aiVector3D> *texcoords, std::vector<aiColor4D> *colors,
+        std::vector<unsigned int> *vertexids);
+};
+
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#endif // AI_M3DIMPORTER_H_INC

+ 106 - 0
code/M3D/M3DMaterials.h

@@ -0,0 +1,106 @@
+/*
+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 M3DMaterials.h
+*   @brief Declares the Assimp and Model 3D file material type relations
+*/
+#ifndef AI_M3DMATERIALS_H_INC
+#define AI_M3DMATERIALS_H_INC
+
+/*
+ * In the m3d.h header, there's a static array which defines the material
+ * properties, called m3d_propertytypes. These must have the same size, and
+ * list the matching Assimp materials for those properties. Used by both the
+ * M3DImporter and the M3DExporter, so you have to define these relations
+ * only once. D.R.Y. and K.I.S.S.
+ */
+typedef struct {
+    char *pKey;
+    unsigned int type;
+    unsigned int index;
+} aiMatProp;
+
+/* --- Scalar Properties ---        !!!!! must match m3d_propertytypes !!!!! */
+static aiMatProp aiProps[] = {
+    { AI_MATKEY_COLOR_DIFFUSE },                                /* m3dp_Kd */
+    { AI_MATKEY_COLOR_AMBIENT },                                /* m3dp_Ka */
+    { AI_MATKEY_COLOR_SPECULAR },                               /* m3dp_Ks */
+    { AI_MATKEY_SHININESS },                                    /* m3dp_Ns */
+    { AI_MATKEY_COLOR_EMISSIVE },                               /* m3dp_Ke */
+    { AI_MATKEY_COLOR_REFLECTIVE },                             /* m3dp_Tf */
+    { AI_MATKEY_BUMPSCALING },                                  /* m3dp_Km */
+    { AI_MATKEY_OPACITY },                                      /* m3dp_d */
+    { AI_MATKEY_SHADING_MODEL },                                /* m3dp_il */
+
+    { NULL, 0, 0 },                                             /* m3dp_Pr */
+    { AI_MATKEY_REFLECTIVITY },                                 /* m3dp_Pm */
+    { NULL, 0, 0 },                                             /* m3dp_Ps */
+    { AI_MATKEY_REFRACTI },                                     /* m3dp_Ni */
+    { NULL, 0, 0 },
+    { NULL, 0, 0 },
+    { NULL, 0, 0 },
+    { NULL, 0, 0 }
+};
+
+/* --- Texture Map Properties ---   !!!!! must match m3d_propertytypes !!!!! */
+static aiMatProp aiTxProps[] = {
+    { AI_MATKEY_TEXTURE_DIFFUSE(0) },                        /* m3dp_map_Kd */
+    { AI_MATKEY_TEXTURE_AMBIENT(0) },                        /* m3dp_map_Ka */
+    { AI_MATKEY_TEXTURE_SPECULAR(0) },                       /* m3dp_map_Ks */
+    { AI_MATKEY_TEXTURE_SHININESS(0) },                      /* m3dp_map_Ns */
+    { AI_MATKEY_TEXTURE_EMISSIVE(0) },                       /* m3dp_map_Ke */
+    { NULL, 0, 0 },                                          /* m3dp_map_Tf */
+    { AI_MATKEY_TEXTURE_HEIGHT(0) },                         /* m3dp_bump */
+    { AI_MATKEY_TEXTURE_OPACITY(0) },                        /* m3dp_map_d */
+    { AI_MATKEY_TEXTURE_REFLECTION(0) },                     /* m3dp_refl */
+
+    { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */
+    { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) },        /* m3dp_map_Pm */
+    { NULL, 0, 0 },                                          /* m3dp_map_Ps */
+    { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ni */
+    { NULL, 0, 0 },
+    { NULL, 0, 0 },
+    { NULL, 0, 0 },
+    { NULL, 0, 0 }
+};
+
+#endif // AI_M3DMATERIALS_H_INC

+ 1 - 0
code/M3D/m3d.h

@@ -0,0 +1 @@
+../../../m3d.h

+ 14 - 0
test/models/M3D/README.md

@@ -0,0 +1,14 @@
+Model 3D Samples
+================
+
+ aliveai_character.m3d - from Minetest aliveai mod (textures, animations, original 47k, m3d 2.5k)
+ cube.m3d - smallest possible example, 119 bytes only
+ cube_normals.m3d - cube with normal vectors, 159 bytes
+ cube_usemtl.m3d - converted from Assimp sample OBJ by the same name, cube with materials
+ cube_with_vertexcolors.m3d - converted from Assimp sample OBJ by the same name, cube with vertex colors
+ cube_with_vertexcolors.a3d - same, but saved in ASCII variant with Windows line endings (\r\n)
+ mobs_dwarves_character.m3d - from Minetest mobs_dwarves mod (with Assimp artifacts converted perfectly too...)
+ suzanne.m3d - exported from Blender (monkey face, with normals and texture UVs and materials)
+ WusonBlitz0.m3d - from Assimp sample by the same name (was 87k, triangle mesh) with int8 coordinates, minor quality degradation
+ WusonBlitz1.m3d - same, but uses int16 coordinates (no noticable difference to the original, but just 35k)
+ WusonBlitz2.m3d - same, but with 32 bit floating point numbers (same precision as the original, half the file size, 42k)

BIN
test/models/M3D/WusonBlitz0.m3d


BIN
test/models/M3D/WusonBlitz1.m3d


BIN
test/models/M3D/WusonBlitz2.m3d


BIN
test/models/M3D/aliveai_character.m3d


BIN
test/models/M3D/cube_normals.m3d


BIN
test/models/M3D/cube_usemtl.m3d


+ 33 - 0
test/models/M3D/cube_with_vertexcolors.a3d

@@ -0,0 +1,33 @@
+3dmodel 1
+cube_with_vertexcolors.obj
+MIT
+bzt
+comment
+
+Vertex
+0 0 0 1 #ff786d7b
+0 0 -1 1
+1 1 0 1 #ff320a4d
+1 0 0 1 #ff19c718
+0 1 0 1 #ff2c0004
+-1 0 0 1
+0 1 1 1 #ff0a00df
+0 0 1 1 #ff790018
+1 1 1 1 #ffc70017
+1 0 1 1 #ff380a7b
+0 -1 0 1
+
+Mesh
+0//1 2//1 3//1
+0//1 4//1 2//1
+0//5 6//5 4//5
+0//5 7//5 6//5
+4//4 8//4 2//4
+4//4 6//4 8//4
+3//3 2//3 8//3
+3//3 8//3 9//3
+0//10 3//10 9//10
+0//10 9//10 7//10
+7//7 9//7 8//7
+7//7 8//7 6//7
+

BIN
test/models/M3D/cube_with_vertexcolors.m3d


BIN
test/models/M3D/mobs_dwarves_character.m3d


BIN
test/models/M3D/suzanne.m3d