summaryrefslogtreecommitdiff
path: root/libs/assimp/code/AssetLib/M3D
diff options
context:
space:
mode:
Diffstat (limited to 'libs/assimp/code/AssetLib/M3D')
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DExporter.cpp442
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DExporter.h93
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DImporter.cpp789
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DImporter.h105
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DMaterials.h106
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp152
-rw-r--r--libs/assimp/code/AssetLib/M3D/M3DWrapper.h134
-rw-r--r--libs/assimp/code/AssetLib/M3D/m3d.h4902
8 files changed, 6723 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/M3D/M3DExporter.cpp b/libs/assimp/code/AssetLib/M3D/M3DExporter.cpp
new file mode 100644
index 0000000..cf87b62
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DExporter.cpp
@@ -0,0 +1,442 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#define M3D_NODUP
+
+
+// Header files, standard library.
+#include <memory> // shared_ptr
+#include <string>
+#include <vector>
+
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/material.h> // aiTextureType
+#include <assimp/mesh.h>
+#include <assimp/scene.h>
+#include <assimp/version.h> // aiGetVersion
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/IOSystem.hpp>
+
+#include "M3DExporter.h"
+#include "M3DMaterials.h"
+#include "M3DWrapper.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 orientation 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)
+ */
+
+// ------------------------------------------------------------------------------------------------
+// Conversion functions
+// ------------------------------------------------------------------------------------------------
+// helper to add a vertex (private to NodeWalk)
+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 *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 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 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;
+}
+
+// ------------------------------------------------------------------------------------------------
+// convert aiString to identifier safe C string. This is a duplication of _m3d_safestr
+char *SafeStr(aiString str, bool isStrict) {
+ char *s = (char *)&str.data;
+ char *d, *ret;
+ int i, len;
+
+ for (len = str.length + 1; *s && (*s == ' ' || *s == '\t'); s++, len--)
+ ;
+ if (len > 255) len = 255;
+ ret = (char *)M3D_MALLOC(len + 1);
+ if (!ret) {
+ throw DeadlyExportError("memory allocation error");
+ }
+ for (i = 0, d = ret; i < len && *s && *s != '\r' && *s != '\n'; s++, d++, i++) {
+ *d = isStrict && (*s == ' ' || *s == '\t' || *s == '/' || *s == '\\') ? '_' : (*s == '\t' ? ' ' : *s);
+ }
+ for (; d > ret && (*(d - 1) == ' ' || *(d - 1) == '\t'); d--)
+ ;
+ *d = 0;
+ return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+// add a material to the output
+M3D_INDEX addMaterial(const Assimp::M3DWrapper &m3d, const aiMaterial *mat) {
+ unsigned int mi = M3D_NOTDEFINED;
+ 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 == M3D_NOTDEFINED) {
+ 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 = SafeStr(name, true);
+ m3d->material[mi].numprop = 0;
+ m3d->material[mi].prop = nullptr;
+ // 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) {
+ uint32_t f_uint32;
+ memcpy(&f_uint32, &f, sizeof(uint32_t));
+ 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 */
+ f_uint32);
+ }
+ 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, nullptr, nullptr, nullptr,
+ nullptr, nullptr) == 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 = SafeStr(name, true);
+ for (j = 0, i = M3D_NOTDEFINED; j < m3d->numtexture; j++)
+ if (!strcmp(fn, m3d->texture[j].name)) {
+ i = j;
+ free(fn);
+ break;
+ }
+ if (i == M3D_NOTDEFINED) {
+ 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 = nullptr;
+ }
+ 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 ExportSceneM3DA(
+ 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() {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+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 = SafeStr(mScene->mRootNode->mName, false);
+
+ // 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();
+
+ M3D_FREE(m3d->name);
+ m3d->name = nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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_NOTDEFINED;
+ 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] = M3D_UNDEF;
+ 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 = M3D_UNDEF;
+ // 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
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT
diff --git a/libs/assimp/code/AssetLib/M3D/M3DExporter.h b/libs/assimp/code/AssetLib/M3D/M3DExporter.h
new file mode 100644
index 0000000..d77743f
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DExporter.h
@@ -0,0 +1,93 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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_IMPORTER
+#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#include <assimp/types.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;
+
+ class M3DWrapper;
+
+ // ---------------------------------------------------------------------
+ /** 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
+
+ // helper to do the recursive walking
+ void NodeWalk(const M3DWrapper &m3d, const aiNode* pNode, aiMatrix4x4 m);
+ };
+}
+
+#endif // #ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#endif // ASSIMP_BUILD_NO_M3D_EXPORTER
+
+#endif // AI_M3DEXPORTER_H_INC
diff --git a/libs/assimp/code/AssetLib/M3D/M3DImporter.cpp b/libs/assimp/code/AssetLib/M3D/M3DImporter.cpp
new file mode 100644
index 0000000..895b2bf
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DImporter.cpp
@@ -0,0 +1,789 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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_NONORMALS /* leave the post-processing to Assimp */
+#define M3D_NOWEIGHTS
+#define M3D_NOANIMATION
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+#include <memory>
+
+#include "M3DImporter.h"
+#include "M3DMaterials.h"
+#include "M3DWrapper.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_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour,
+ 0,
+ 0,
+ 0,
+ 0,
+ "m3d a3d"
+};
+
+namespace Assimp {
+
+using namespace std;
+
+// ------------------------------------------------------------------------------------------------
+// Default constructor
+M3DImporter::M3DImporter() :
+ mScene(nullptr) {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns true, if file is a binary or ASCII Model 3D file.
+bool M3DImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+ // 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
+ 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 */
+#ifdef M3D_ASCII
+ || !memcmp(data, "3dmo", 4) /* ASCII */
+#endif
+ ;
+}
+
+// ------------------------------------------------------------------------------------------------
+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.");
+ }
+ std::vector<unsigned char> buffer(fileSize);
+ if (fileSize != pStream->Read(buffer.data(), 1, fileSize)) {
+ throw DeadlyImportError("Failed to read the file ", file, ".");
+ }
+ // extra check for binary format's first 8 bytes. Not done for the ASCII variant
+ if (!memcmp(buffer.data(), "3DMO", 4) && memcmp(buffer.data() + 4, &fileSize, 4)) {
+ throw DeadlyImportError("Bad binary header in file ", file, ".");
+ }
+ // make sure there's a terminator zero character, as input must be ASCIIZ
+ if (!memcmp(buffer.data(), "3dmo", 4)) {
+ buffer.push_back(0);
+ }
+
+ // 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("M3D: loading ", file);
+
+ // let the C SDK do the hard work for us
+ M3DWrapper m3d(pIOHandler, buffer);
+
+ if (!m3d) {
+ throw DeadlyImportError("Unable to parse ", file, " as M3D.");
+ }
+
+ // create the root node
+ pScene->mRootNode = new aiNode;
+ pScene->mRootNode->mName = aiString(m3d.Name());
+ pScene->mRootNode->mTransformation = aiMatrix4x4();
+ pScene->mRootNode->mNumChildren = 0;
+ mScene = pScene;
+
+ ASSIMP_LOG_DEBUG("M3D: root node ", m3d.Name());
+
+ // now we just have to fill up the Assimp structures in pScene
+ importMaterials(m3d);
+ importTextures(m3d);
+ importBones(m3d, M3D_NOTDEFINED, pScene->mRootNode);
+ importMeshes(m3d);
+ importAnimations(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(const M3DWrapper &m3d) {
+ 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);
+
+ mScene->mNumMaterials = m3d->nummaterial + 1;
+ mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials];
+
+ ASSIMP_LOG_DEBUG("M3D: importMaterials ", mScene->mNumMaterials);
+
+ // add a default material as first
+ aiMaterial *defaultMat = new aiMaterial;
+ defaultMat->AddProperty(&name, AI_MATKEY_NAME);
+ c.a = 1.0f;
+ c.b = c.g = c.r = 0.6f;
+ defaultMat->AddProperty(&c, 1, AI_MATKEY_COLOR_DIFFUSE);
+ mScene->mMaterials[0] = defaultMat;
+
+ if (!m3d->nummaterial || !m3d->material) {
+ return;
+ }
+
+ for (i = 0; i < m3d->nummaterial; i++) {
+ m = &m3d->material[i];
+ aiMaterial *newMat = new aiMaterial;
+ name.Set(std::string(m->name));
+ newMat->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);
+ newMat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index);
+ break;
+ case m3dpf_float:
+ f = m->prop[j].value.fnum;
+ newMat->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;
+ }
+ }
+ newMat->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 referred 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"));
+ newMat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index);
+ n = 0;
+ newMat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index);
+ }
+ }
+ mScene->mMaterials[i + 1] = newMat;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// import textures, this is the simplest of all
+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);
+
+ mScene->mNumTextures = m3d->numtexture;
+ ASSIMP_LOG_DEBUG("M3D: importTextures ", mScene->mNumTextures);
+
+ if (!m3d->numtexture || !m3d->texture) {
+ return;
+ }
+
+ mScene->mTextures = new aiTexture *[m3d->numtexture];
+ for (i = 0; i < m3d->numtexture; i++) {
+ unsigned int j, k;
+ t = &m3d->texture[i];
+ aiTexture *tx = new aiTexture;
+ tx->mFilename = aiString(std::string(t->name) + ".png");
+ if (!t->w || !t->h || !t->f || !t->d) {
+ /* without ASSIMP_USE_M3D_READFILECB, we only have the filename, but no texture data ever */
+ tx->mWidth = 0;
+ tx->mHeight = 0;
+ memcpy(tx->achFormatHint, "png\000", 4);
+ tx->pcData = nullptr;
+ } else {
+ /* if we have the texture loaded, set format hint and pcData too */
+ tx->mWidth = t->w;
+ tx->mHeight = t->h;
+ strcpy(tx->achFormatHint, formatHint[t->f - 1]);
+ 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(const M3DWrapper &m3d) {
+ ASSIMP_LOG_DEBUG("M3D: importMeshes ", m3d->numface);
+
+ if (!m3d->numface || !m3d->face || !m3d->numvertex || !m3d->vertex) {
+ return;
+ }
+
+ unsigned int i, j, k, l, numpoly = 3, lastMat = M3D_INDEXMAX;
+ 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);
+ ai_assert(mScene->mRootNode != nullptr);
+
+ 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<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 = static_cast<unsigned int>(vertices->size());
+ pFace->mIndices[j] = k;
+ l = m3d->face[i].vertex[j];
+ if (l >= m3d->numvertex) continue;
+ 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 != M3D_UNDEF && m3d->vertex[l].skinid != M3D_INDEXMAX && 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 != M3D_UNDEF && l < m3d->numtmap) {
+ 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 != M3D_UNDEF && l < m3d->numvertex) {
+ 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 = static_cast<unsigned int>(meshes->size());
+ mScene->mMeshes = new aiMesh *[mScene->mNumMeshes];
+ std::copy(meshes->begin(), meshes->end(), mScene->mMeshes);
+
+ // create mesh indices in root node
+ mScene->mRootNode->mNumMeshes = static_cast<unsigned int>(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(const M3DWrapper &m3d, unsigned int parentid, aiNode *pParent) {
+ unsigned int i, n;
+
+ ai_assert(pParent != nullptr);
+ ai_assert(mScene != nullptr);
+ ai_assert(m3d);
+
+ ASSIMP_LOG_DEBUG("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid);
+
+ if (!m3d->numbone || !m3d->bone) {
+ return;
+ }
+
+ 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(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(const M3DWrapper &m3d) {
+ unsigned int i, j, k, l, pos, ori;
+ double t;
+ m3da_t *a;
+
+ ai_assert(mScene != nullptr);
+ ai_assert(m3d);
+
+ mScene->mNumAnimations = m3d->numaction;
+
+ ASSIMP_LOG_DEBUG("M3D: importAnimations ", mScene->mNumAnimations);
+
+ if (!m3d->numaction || !m3d->action || !m3d->numbone || !m3d->bone || !m3d->vertex) {
+ 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;
+ }
+ }
+ if (pos >= m3d->numvertex || ori >= m3d->numvertex) continue;
+ 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(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) {
+ ai_assert(m != nullptr);
+ ai_assert(m3d);
+ ai_assert(posid != M3D_UNDEF);
+ ai_assert(posid < m3d->numvertex);
+ ai_assert(orientid != M3D_UNDEF);
+ ai_assert(orientid < m3d->numvertex);
+ if (!m3d->numvertex || !m3d->vertex)
+ return;
+ 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;
+ }
+
+ /* 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, const aiString &name) {
+ ai_assert(pNode != nullptr);
+ ai_assert(mScene != nullptr);
+
+ if (pNode->mName == name) {
+ return pNode;
+ }
+
+ for (unsigned int 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 indices
+// this function fills up an aiMesh with those temporary lists
+void M3DImporter::populateMesh(const M3DWrapper &m3d, 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) {
+
+ 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("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(),
+ " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone);
+
+ if (vertices->size() && faces->size()) {
+ pMesh->mNumFaces = static_cast<unsigned int>(faces->size());
+ pMesh->mFaces = new aiFace[pMesh->mNumFaces];
+ std::copy(faces->begin(), faces->end(), pMesh->mFaces);
+ pMesh->mNumVertices = static_cast<unsigned int>(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 && m3d->numbone && m3d->bone) {
+ 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() && m3d->numvertex && m3d->vertex && m3d->numskin && m3d->skin) {
+ unsigned int i, j;
+ // first count how many vertices we have per bone
+ for (i = 0; i < vertexids->size(); i++) {
+ if (vertexids->at(i) >= m3d->numvertex) {
+ continue;
+ }
+ unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
+ if (s != M3D_UNDEF && s != M3D_INDEXMAX) {
+ 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++) {
+ if (vertexids->at(i) >= m3d->numvertex) continue;
+ unsigned int s = m3d->vertex[vertexids->at(i)].skinid;
+ if (s != M3D_UNDEF && s != M3D_INDEXMAX && s < m3d->numskin) {
+ for (unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) {
+ if (m3d->skin[s].boneid[k] >= m3d->numbone) continue;
+ 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
diff --git a/libs/assimp/code/AssetLib/M3D/M3DImporter.h b/libs/assimp/code/AssetLib/M3D/M3DImporter.h
new file mode 100644
index 0000000..5d3fcaa
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DImporter.h
@@ -0,0 +1,105 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 <assimp/BaseImporter.h>
+#include <assimp/material.h>
+#include <vector>
+
+struct aiMesh;
+struct aiNode;
+struct aiMaterial;
+struct aiFace;
+
+namespace Assimp {
+
+class M3DWrapper;
+
+class M3DImporter : public BaseImporter {
+public:
+ /// \brief Default constructor
+ M3DImporter();
+ ~M3DImporter() override {}
+
+ /// \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 override;
+
+protected:
+ //! \brief Appends the supported extension.
+ const aiImporterDesc *GetInfo() const override;
+
+ //! \brief File import implementation.
+ void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
+
+private:
+ 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(const M3DWrapper &m3d, aiMatrix4x4 *m, unsigned int posid, unsigned int orientid);
+ aiNode *findNode(aiNode *pNode, const aiString &name);
+ void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m);
+ void populateMesh(const M3DWrapper &m3d, 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);
+
+private:
+ aiScene *mScene = nullptr; // the scene to import to
+};
+
+} // Namespace Assimp
+
+#endif // ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#endif // AI_M3DIMPORTER_H_INC
diff --git a/libs/assimp/code/AssetLib/M3D/M3DMaterials.h b/libs/assimp/code/AssetLib/M3D/M3DMaterials.h
new file mode 100644
index 0000000..a1b0fd7
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DMaterials.h
@@ -0,0 +1,106 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 {
+ const char *pKey;
+ unsigned int type;
+ unsigned int index;
+} aiMatProp;
+
+/* --- Scalar Properties --- !!!!! must match m3d_propertytypes !!!!! */
+static const 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 */
+
+ { nullptr, 0, 0 }, /* m3dp_Pr */
+ { AI_MATKEY_REFLECTIVITY }, /* m3dp_Pm */
+ { nullptr, 0, 0 }, /* m3dp_Ps */
+ { AI_MATKEY_REFRACTI }, /* m3dp_Ni */
+ { nullptr, 0, 0 }, /* m3dp_Nt */
+ { nullptr, 0, 0 },
+ { nullptr, 0, 0 },
+ { nullptr, 0, 0 }
+};
+
+/* --- Texture Map Properties --- !!!!! must match m3d_propertytypes !!!!! */
+static const aiMatProp aiTxProps[] = {
+ { AI_MATKEY_TEXTURE_DIFFUSE(0) }, /* m3dp_map_Kd */
+ { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,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 */
+ { nullptr, 0, 0 }, /* m3dp_map_Tf */
+ { AI_MATKEY_TEXTURE_HEIGHT(0) }, /* m3dp_bump */
+ { AI_MATKEY_TEXTURE_OPACITY(0) }, /* m3dp_map_d */
+ { AI_MATKEY_TEXTURE_NORMALS(0) }, /* m3dp_map_N */
+
+ { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */
+ { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) }, /* m3dp_map_Pm */
+ { nullptr, 0, 0 }, /* m3dp_map_Ps */
+ { AI_MATKEY_TEXTURE(aiTextureType_REFLECTION,0) }, /* m3dp_map_Ni */
+ { nullptr, 0, 0 }, /* m3dp_map_Nt */
+ { nullptr, 0, 0 },
+ { nullptr, 0, 0 },
+ { nullptr, 0, 0 }
+};
+
+#endif // AI_M3DMATERIALS_H_INC
diff --git a/libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp b/libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp
new file mode 100644
index 0000000..30452c7
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DWrapper.cpp
@@ -0,0 +1,152 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
+#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER)
+
+#include "M3DWrapper.h"
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOStreamBuffer.h>
+#include <assimp/ai_assert.h>
+
+#ifdef ASSIMP_USE_M3D_READFILECB
+
+#if (__cplusplus >= 201103L) || !defined(_MSC_VER) || (_MSC_VER >= 1900) // C++11 and MSVC 2015 onwards
+#define threadlocal thread_local
+#else
+#if defined(_MSC_VER) && (_MSC_VER >= 1800) // there's an alternative for MSVC 2013
+#define threadlocal __declspec(thread)
+#else
+#define threadlocal
+#endif
+#endif
+
+extern "C" {
+
+// workaround: the M3D SDK expects a C callback, but we want to use Assimp::IOSystem to implement that
+threadlocal 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<Assimp::IOStream> pStream(
+ (reinterpret_cast<Assimp::IOSystem *>(m3dimporter_pIOHandler))->Open(file, "rb"));
+ size_t fileSize = 0;
+ unsigned char *data = nullptr;
+ // sometimes pStream is nullptr in a single-threaded scenario too 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;
+}
+}
+#endif
+
+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<unsigned char> &buffer) {
+ if (nullptr == pIOHandler) {
+ ai_assert(nullptr != pIOHandler);
+ }
+
+#ifdef ASSIMP_USE_M3D_READFILECB
+ // pass this IOHandler to the C callback in a thread-local pointer
+ m3dimporter_pIOHandler = pIOHandler;
+ m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), m3dimporter_readfile, free, nullptr);
+ // Clear the C callback
+ m3dimporter_pIOHandler = nullptr;
+#else
+ m3d_ = m3d_load(const_cast<unsigned char *>(buffer.data()), nullptr, nullptr, nullptr);
+#endif
+}
+
+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
+ (void)quality;
+ (void)flags;
+ (void)size;
+ return nullptr;
+#endif
+}
+
+void M3DWrapper::ClearSave() {
+ if (saved_output_) {
+ M3D_FREE(saved_output_);
+ }
+ saved_output_ = nullptr;
+}
+} // namespace Assimp
+
+#endif
+#endif
diff --git a/libs/assimp/code/AssetLib/M3D/M3DWrapper.h b/libs/assimp/code/AssetLib/M3D/M3DWrapper.h
new file mode 100644
index 0000000..7cc2d0e
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/M3DWrapper.h
@@ -0,0 +1,134 @@
+#pragma once
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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
+
+#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER
+#if !(ASSIMP_BUILD_NO_EXPORT || ASSIMP_BUILD_NO_M3D_EXPORTER)
+
+#include <memory>
+#include <vector>
+#include <string>
+
+// Assimp specific M3D configuration. Comment out these defines to remove functionality
+//#define ASSIMP_USE_M3D_READFILECB
+
+// Share stb_image's PNG loader with other importers/exporters instead of bringing our own copy.
+#define STBI_ONLY_PNG
+#include <stb/stb_image.h>
+
+#include "m3d.h"
+
+namespace Assimp {
+
+class IOSystem;
+
+/// brief The M3D-Wrapper, provudes c++ access to the data.
+class M3DWrapper {
+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<unsigned char> &buffer);
+
+ /// Theclasss destructor.
+ ~M3DWrapper();
+
+ /// Will reset the wrapper, all data will become nullptr.
+ void reset();
+
+ // The Name access, empty string returned when no m3d instance.
+ std::string Name() const;
+
+ /// Executes a save.
+ unsigned char *Save(int quality, int flags, unsigned int &size);
+
+ /// Clearer
+ void ClearSave();
+
+ /// True for m3d instance exists.
+ explicit operator bool() const;
+
+ // Allow direct access to M3D API
+ m3d_t *operator->() const;
+ m3d_t *M3D() const;
+
+private:
+ m3d_t *m3d_ = nullptr;
+ unsigned char *saved_output_ = nullptr;
+};
+
+inline std::string M3DWrapper::Name() const {
+ if (nullptr != m3d_) {
+ if (nullptr != m3d_->name) {
+ return std::string(m3d_->name);
+ }
+ }
+ return std::string();
+}
+
+inline M3DWrapper::operator bool() const {
+ return m3d_ != nullptr;
+}
+
+inline m3d_t *M3DWrapper::operator->() const {
+ return m3d_;
+}
+
+inline m3d_t *M3DWrapper::M3D() const {
+ return m3d_;
+}
+
+} // namespace Assimp
+
+#endif
+#endif // ASSIMP_BUILD_NO_M3D_IMPORTER
+
+#endif // AI_M3DWRAPPER_H_INC
diff --git a/libs/assimp/code/AssetLib/M3D/m3d.h b/libs/assimp/code/AssetLib/M3D/m3d.h
new file mode 100644
index 0000000..875007e
--- /dev/null
+++ b/libs/assimp/code/AssetLib/M3D/m3d.h
@@ -0,0 +1,4902 @@
+/*
+ * m3d.h
+ *
+ * Copyright (C) 2019 bzt (bztsrc@gitlab)
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * @brief ANSI C89 / C++11 single header importer / exporter SDK for the Model 3D (.M3D) format
+ * https://gitlab.com/bztsrc/model3d
+ *
+ * PNG decompressor included from (with minor modifications to make it C89 valid):
+ * stb_image - v2.13 - public domain image loader - http://nothings.org/stb_image.h
+ *
+ * @version: 1.0.0
+ */
+
+#ifndef _M3D_H_
+#define _M3D_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+/*** configuration ***/
+#ifndef M3D_MALLOC
+#define M3D_MALLOC(sz) malloc(sz)
+#endif
+#ifndef M3D_REALLOC
+#define M3D_REALLOC(p, nsz) realloc(p, nsz)
+#endif
+#ifndef M3D_FREE
+#define M3D_FREE(p) free(p)
+#endif
+#ifndef M3D_LOG
+#define M3D_LOG(x)
+#endif
+#ifndef M3D_APIVERSION
+#define M3D_APIVERSION 0x0100
+#ifndef M3D_DOUBLE
+typedef float M3D_FLOAT;
+#ifndef M3D_EPSILON
+/* carefully chosen for IEEE 754 don't change */
+#define M3D_EPSILON ((M3D_FLOAT)1e-7)
+#endif
+#else
+typedef double M3D_FLOAT;
+#ifndef M3D_EPSILON
+#define M3D_EPSILON ((M3D_FLOAT)1e-14)
+#endif
+#endif
+#if !defined(M3D_SMALLINDEX)
+typedef uint32_t M3D_INDEX;
+#define M3D_UNDEF 0xffffffff
+#define M3D_INDEXMAX 0xfffffffe
+#else
+typedef uint16_t M3D_INDEX;
+#define M3D_UNDEF 0xffff
+#define M3D_INDEXMAX 0xfffe
+#endif
+#define M3D_NOTDEFINED 0xffffffff
+#ifndef M3D_NUMBONE
+#define M3D_NUMBONE 4
+#endif
+#ifndef M3D_BONEMAXLEVEL
+#define M3D_BONEMAXLEVEL 8
+#endif
+#if !defined(_MSC_VER) || defined(__clang__)
+#ifndef _inline
+#define _inline __inline__
+#endif
+#define _pack __attribute__((packed))
+#define _unused __attribute__((unused))
+#else
+#define _inline
+#define _pack
+#define _unused __pragma(warning(suppress : 4100))
+#endif
+#ifndef __cplusplus
+#define _register register
+#else
+#define _register
+#endif
+
+#if _MSC_VER > 1920 && !defined(__clang__)
+# pragma warning(push)
+# pragma warning(disable : 4100 4127 4189 4505 4244 4403 4701 4703)
+# if (_MSC_VER > 1800 )
+# pragma warning(disable : 5573 5744)
+# endif
+#endif // _MSC_VER
+
+/*** File format structures ***/
+
+/**
+ * M3D file format structure
+ * 3DMO m3dchunk_t file header chunk, may followed by compressed data
+ * HEAD m3dhdr_t model header chunk
+ * n x m3dchunk_t more chunks follow
+ * PRVW preview chunk (optional)
+ * CMAP color map chunk (optional)
+ * TMAP texture map chunk (optional)
+ * VRTS vertex data chunk (optional if it's a material library)
+ * BONE bind-pose skeleton, bone hierarchy chunk (optional)
+ * n x m3db_t contains probably more, but at least one bone
+ * n x m3ds_t skin group records
+ * MTRL* material chunk(s), can be more (optional)
+ * n x m3dp_t each material contains propapbly more, but at least one property
+ * the properties are configurable with a static array, see m3d_propertytypes
+ * n x m3dchunk_t at least one, but maybe more face chunks
+ * PROC* procedural face, or
+ * MESH* triangle mesh (vertex index list) or
+ * SHPE* mathematical shapes like parameterized surfaces
+ * LBLS* annotation label chunks, can be more (optional)
+ * ACTN* action chunk(s), animation-pose skeletons, can be more (optional)
+ * n x m3dfr_t each action contains probably more, but at least one frame
+ * n x m3dtr_t each frame contains probably more, but at least one transformation
+ * ASET* inlined asset chunk(s), can be more (optional)
+ * OMD3 end chunk
+ *
+ * Typical chunks for a game engine: 3DMO, HEAD, CMAP, TMAP, VRTS, BONE, MTRL, MESH, ACTN, OMD3
+ * Typical chunks for CAD software: 3DMO, HEAD, PRVW, CMAP, TMAP, VRTS, MTRL, SHPE, LBLS, OMD3
+ */
+#ifdef _MSC_VER
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+typedef struct {
+ char magic[4];
+ uint32_t length;
+ float scale; /* deliberately not M3D_FLOAT */
+ uint32_t types;
+} _pack m3dhdr_t;
+
+typedef struct {
+ char magic[4];
+ uint32_t length;
+} _pack m3dchunk_t;
+
+#ifdef _MSC_VER
+#pragma pack(pop)
+#endif
+
+/*** in-memory model structure ***/
+
+/* textmap entry */
+typedef struct {
+ M3D_FLOAT u;
+ M3D_FLOAT v;
+} m3dti_t;
+#define m3d_textureindex_t m3dti_t
+
+/* texture */
+typedef struct {
+ char *name; /* texture name */
+ uint8_t *d; /* pixels data */
+ uint16_t w; /* width */
+ uint16_t h; /* height */
+ uint8_t f; /* format, 1 = grayscale, 2 = grayscale+alpha, 3 = rgb, 4 = rgba */
+} m3dtx_t;
+#define m3d_texturedata_t m3dtx_t
+
+typedef struct {
+ M3D_INDEX vertexid;
+ M3D_FLOAT weight;
+} m3dw_t;
+#define m3d_weight_t m3dw_t
+
+/* bone entry */
+typedef struct {
+ M3D_INDEX parent; /* parent bone index */
+ char *name; /* name for this bone */
+ M3D_INDEX pos; /* vertex index position */
+ M3D_INDEX ori; /* vertex index orientation (quaternion) */
+ M3D_INDEX numweight; /* number of controlled vertices */
+ m3dw_t *weight; /* weights for those vertices */
+ M3D_FLOAT mat4[16]; /* transformation matrix */
+} m3db_t;
+#define m3d_bone_t m3db_t
+
+/* skin: bone per vertex entry */
+typedef struct {
+ M3D_INDEX boneid[M3D_NUMBONE];
+ M3D_FLOAT weight[M3D_NUMBONE];
+} m3ds_t;
+#define m3d_skin_t m3ds_t
+
+/* vertex entry */
+typedef struct {
+ M3D_FLOAT x; /* 3D coordinates and weight */
+ M3D_FLOAT y;
+ M3D_FLOAT z;
+ M3D_FLOAT w;
+ uint32_t color; /* default vertex color */
+ M3D_INDEX skinid; /* skin index */
+#ifdef M3D_VERTEXTYPE
+ uint8_t type;
+#endif
+} m3dv_t;
+#define m3d_vertex_t m3dv_t
+
+/* material property formats */
+enum {
+ m3dpf_color,
+ m3dpf_uint8,
+ m3dpf_uint16,
+ m3dpf_uint32,
+ m3dpf_float,
+ m3dpf_map
+};
+typedef struct {
+ uint8_t format;
+ uint8_t id;
+#define M3D_PROPERTYDEF(f, i, n) \
+ { (f), (i), (char *)(n) }
+ char *key;
+} m3dpd_t;
+
+/* material property types */
+/* You shouldn't change the first 8 display and first 4 physical property. Assign the rest as you like. */
+enum {
+ m3dp_Kd = 0, /* scalar display properties */
+ m3dp_Ka,
+ m3dp_Ks,
+ m3dp_Ns,
+ m3dp_Ke,
+ m3dp_Tf,
+ m3dp_Km,
+ m3dp_d,
+ m3dp_il,
+
+ m3dp_Pr = 64, /* scalar physical properties */
+ m3dp_Pm,
+ m3dp_Ps,
+ m3dp_Ni,
+ m3dp_Nt,
+
+ m3dp_map_Kd = 128, /* textured display map properties */
+ m3dp_map_Ka,
+ m3dp_map_Ks,
+ m3dp_map_Ns,
+ m3dp_map_Ke,
+ m3dp_map_Tf,
+ m3dp_map_Km, /* bump map */
+ m3dp_map_D,
+ m3dp_map_N, /* normal map */
+
+ m3dp_map_Pr = 192, /* textured physical map properties */
+ m3dp_map_Pm,
+ m3dp_map_Ps,
+ m3dp_map_Ni,
+ m3dp_map_Nt
+};
+enum { /* aliases */
+ m3dp_bump = m3dp_map_Km,
+ m3dp_map_il = m3dp_map_N,
+ m3dp_refl = m3dp_map_Pm
+};
+
+/* material property */
+typedef struct {
+ uint8_t type; /* property type, see "m3dp_*" enumeration */
+ union {
+ uint32_t color; /* if value is a color, m3dpf_color */
+ uint32_t num; /* if value is a number, m3dpf_uint8, m3pf_uint16, m3dpf_uint32 */
+ float fnum; /* if value is a floating point number, m3dpf_float */
+ M3D_INDEX textureid; /* if value is a texture, m3dpf_map */
+ } value;
+} m3dp_t;
+#define m3d_property_t m3dp_t
+
+/* material entry */
+typedef struct {
+ char *name; /* name of the material */
+ uint8_t numprop; /* number of properties */
+ m3dp_t *prop; /* properties array */
+} m3dm_t;
+#define m3d_material_t m3dm_t
+
+/* face entry */
+typedef struct {
+ M3D_INDEX materialid; /* material index */
+ M3D_INDEX vertex[3]; /* 3D points of the triangle in CCW order */
+ M3D_INDEX normal[3]; /* normal vectors */
+ M3D_INDEX texcoord[3]; /* UV coordinates */
+} m3df_t;
+#define m3d_face_t m3df_t
+
+/* shape command types. must match the row in m3d_commandtypes */
+enum {
+ /* special commands */
+ m3dc_use = 0, /* use material */
+ m3dc_inc, /* include another shape */
+ m3dc_mesh, /* include part of polygon mesh */
+ /* approximations */
+ m3dc_div, /* subdivision by constant resolution for both u, v */
+ m3dc_sub, /* subdivision by constant, different for u and v */
+ m3dc_len, /* spacial subdivision by maxlength */
+ m3dc_dist, /* subdivision by maxdistance and maxangle */
+ /* modifiers */
+ m3dc_degu, /* degree for both u, v */
+ m3dc_deg, /* separate degree for u and v */
+ m3dc_rangeu, /* range for u */
+ m3dc_range, /* range for u and v */
+ m3dc_paru, /* u parameters (knots) */
+ m3dc_parv, /* v parameters */
+ m3dc_trim, /* outer trimming curve */
+ m3dc_hole, /* inner trimming curve */
+ m3dc_scrv, /* spacial curve */
+ m3dc_sp, /* special points */
+ /* helper curves */
+ m3dc_bez1, /* Bezier 1D */
+ m3dc_bsp1, /* B-spline 1D */
+ m3dc_bez2, /* bezier 2D */
+ m3dc_bsp2, /* B-spline 2D */
+ /* surfaces */
+ m3dc_bezun, /* Bezier 3D with control, UV, normal */
+ m3dc_bezu, /* with control and UV */
+ m3dc_bezn, /* with control and normal */
+ m3dc_bez, /* control points only */
+ m3dc_nurbsun, /* B-spline 3D */
+ m3dc_nurbsu,
+ m3dc_nurbsn,
+ m3dc_nurbs,
+ m3dc_conn, /* connect surfaces */
+ /* geometrical */
+ m3dc_line,
+ m3dc_polygon,
+ m3dc_circle,
+ m3dc_cylinder,
+ m3dc_shpere,
+ m3dc_torus,
+ m3dc_cone,
+ m3dc_cube
+};
+
+/* shape command argument types */
+enum {
+ m3dcp_mi_t = 1, /* material index */
+ m3dcp_hi_t, /* shape index */
+ m3dcp_fi_t, /* face index */
+ m3dcp_ti_t, /* texture map index */
+ m3dcp_vi_t, /* vertex index */
+ m3dcp_qi_t, /* vertex index for quaternions */
+ m3dcp_vc_t, /* coordinate or radius, float scalar */
+ m3dcp_i1_t, /* int8 scalar */
+ m3dcp_i2_t, /* int16 scalar */
+ m3dcp_i4_t, /* int32 scalar */
+ m3dcp_va_t /* variadic arguments */
+};
+
+#define M3D_CMDMAXARG 8 /* if you increase this, add more arguments to the macro below */
+typedef struct {
+#define M3D_CMDDEF(t, n, p, a, b, c, d, e, f, g, h) \
+ { \
+ (char *)(n), (p), { (a), (b), (c), (d), (e), (f), (g), (h) } \
+ }
+ char *key;
+ uint8_t p;
+ uint8_t a[M3D_CMDMAXARG];
+} m3dcd_t;
+
+/* shape command */
+typedef struct {
+ uint16_t type; /* shape type */
+ uint32_t *arg; /* arguments array */
+} m3dc_t;
+#define m3d_shapecommand_t m3dc_t
+
+/* shape entry */
+typedef struct {
+ char *name; /* name of the mathematical shape */
+ M3D_INDEX group; /* group this shape belongs to or -1 */
+ uint32_t numcmd; /* number of commands */
+ m3dc_t *cmd; /* commands array */
+} m3dh_t;
+#define m3d_shape_t m3dh_t
+
+/* label entry */
+typedef struct {
+ char *name; /* name of the annotation layer or NULL */
+ char *lang; /* language code or NULL */
+ char *text; /* the label text */
+ uint32_t color; /* color */
+ M3D_INDEX vertexid; /* the vertex the label refers to */
+} m3dl_t;
+#define m3d_label_t m3dl_t
+
+/* frame transformations / working copy skeleton entry */
+typedef struct {
+ M3D_INDEX boneid; /* selects a node in bone hierarchy */
+ M3D_INDEX pos; /* vertex index new position */
+ M3D_INDEX ori; /* vertex index new orientation (quaternion) */
+} m3dtr_t;
+#define m3d_transform_t m3dtr_t
+
+/* animation frame entry */
+typedef struct {
+ uint32_t msec; /* frame's position on the timeline, timestamp */
+ M3D_INDEX numtransform; /* number of transformations in this frame */
+ m3dtr_t *transform; /* transformations */
+} m3dfr_t;
+#define m3d_frame_t m3dfr_t
+
+/* model action entry */
+typedef struct {
+ char *name; /* name of the action */
+ uint32_t durationmsec; /* duration in millisec (1/1000 sec) */
+ M3D_INDEX numframe; /* number of frames in this animation */
+ m3dfr_t *frame; /* frames array */
+} m3da_t;
+#define m3d_action_t m3da_t
+
+/* inlined asset */
+typedef struct {
+ char *name; /* asset name (same pointer as in texture[].name) */
+ uint8_t *data; /* compressed asset data */
+ uint32_t length; /* compressed data length */
+} m3di_t;
+#define m3d_inlinedasset_t m3di_t
+
+/*** in-memory model structure ***/
+#define M3D_FLG_FREERAW (1 << 0)
+#define M3D_FLG_FREESTR (1 << 1)
+#define M3D_FLG_MTLLIB (1 << 2)
+#define M3D_FLG_GENNORM (1 << 3)
+
+typedef struct {
+ m3dhdr_t *raw; /* pointer to raw data */
+ char flags; /* internal flags */
+ signed char errcode; /* returned error code */
+ char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; /* decoded sizes for types */
+ char *name; /* name of the model, like "Utah teapot" */
+ char *license; /* usage condition or license, like "MIT", "LGPL" or "BSD-3clause" */
+ char *author; /* nickname, email, homepage or github URL etc. */
+ char *desc; /* comments, descriptions. May contain '\n' newline character */
+ M3D_FLOAT scale; /* the model's bounding cube's size in SI meters */
+ M3D_INDEX numcmap;
+ uint32_t *cmap; /* color map */
+ M3D_INDEX numtmap;
+ m3dti_t *tmap; /* texture map indices */
+ M3D_INDEX numtexture;
+ m3dtx_t *texture; /* uncompressed textures */
+ M3D_INDEX numbone;
+ m3db_t *bone; /* bone hierarchy */
+ M3D_INDEX numvertex;
+ m3dv_t *vertex; /* vertex data */
+ M3D_INDEX numskin;
+ m3ds_t *skin; /* skin data */
+ M3D_INDEX nummaterial;
+ m3dm_t *material; /* material list */
+ M3D_INDEX numface;
+ m3df_t *face; /* model face, polygon (triangle) mesh */
+ M3D_INDEX numshape;
+ m3dh_t *shape; /* model face, shape commands */
+ M3D_INDEX numlabel;
+ m3dl_t *label; /* annotation labels */
+ M3D_INDEX numaction;
+ m3da_t *action; /* action animations */
+ M3D_INDEX numinlined;
+ m3di_t *inlined; /* inlined assets */
+ M3D_INDEX numextra;
+ m3dchunk_t **extra; /* unknown chunks, application / engine specific data probably */
+ m3di_t preview; /* preview chunk */
+} m3d_t;
+
+/*** export parameters ***/
+#define M3D_EXP_INT8 0
+#define M3D_EXP_INT16 1
+#define M3D_EXP_FLOAT 2
+#define M3D_EXP_DOUBLE 3
+
+#define M3D_EXP_NOCMAP (1 << 0)
+#define M3D_EXP_NOMATERIAL (1 << 1)
+#define M3D_EXP_NOFACE (1 << 2)
+#define M3D_EXP_NONORMAL (1 << 3)
+#define M3D_EXP_NOTXTCRD (1 << 4)
+#define M3D_EXP_FLIPTXTCRD (1 << 5)
+#define M3D_EXP_NORECALC (1 << 6)
+#define M3D_EXP_IDOSUCK (1 << 7)
+#define M3D_EXP_NOBONE (1 << 8)
+#define M3D_EXP_NOACTION (1 << 9)
+#define M3D_EXP_INLINE (1 << 10)
+#define M3D_EXP_EXTRA (1 << 11)
+#define M3D_EXP_NOZLIB (1 << 14)
+#define M3D_EXP_ASCII (1 << 15)
+
+/*** error codes ***/
+#define M3D_SUCCESS 0
+#define M3D_ERR_ALLOC -1
+#define M3D_ERR_BADFILE -2
+#define M3D_ERR_UNIMPL -65
+#define M3D_ERR_UNKPROP -66
+#define M3D_ERR_UNKMESH -67
+#define M3D_ERR_UNKIMG -68
+#define M3D_ERR_UNKFRAME -69
+#define M3D_ERR_UNKCMD -70
+#define M3D_ERR_TRUNC -71
+#define M3D_ERR_CMAP -72
+#define M3D_ERR_TMAP -73
+#define M3D_ERR_VRTS -74
+#define M3D_ERR_BONE -75
+#define M3D_ERR_MTRL -76
+#define M3D_ERR_SHPE -77
+
+#define M3D_ERR_ISFATAL(x) ((x) < 0 && (x) > -65)
+
+/* callbacks */
+typedef unsigned char *(*m3dread_t)(char *filename, unsigned int *size); /* read file contents into buffer */
+typedef void (*m3dfree_t)(void *buffer); /* free file contents buffer */
+typedef int (*m3dtxsc_t)(const char *name, const void *script, uint32_t len, m3dtx_t *output); /* interpret texture script */
+typedef int (*m3dprsc_t)(const char *name, const void *script, uint32_t len, m3d_t *model); /* interpret surface script */
+#endif /* ifndef M3D_APIVERSION */
+
+/*** C prototypes ***/
+/* import / export */
+m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib);
+unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size);
+void m3d_free(m3d_t *model);
+/* generate animation pose skeleton */
+m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton);
+m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec);
+
+/* private prototypes used by both importer and exporter */
+char *_m3d_safestr(char *in, int morelines);
+
+/*** C implementation ***/
+#ifdef M3D_IMPLEMENTATION
+#if !defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)
+/* material property definitions */
+static m3dpd_t m3d_propertytypes[] = {
+ M3D_PROPERTYDEF(m3dpf_color, m3dp_Kd, "Kd"), /* diffuse color */
+ M3D_PROPERTYDEF(m3dpf_color, m3dp_Ka, "Ka"), /* ambient color */
+ M3D_PROPERTYDEF(m3dpf_color, m3dp_Ks, "Ks"), /* specular color */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Ns, "Ns"), /* specular exponent */
+ M3D_PROPERTYDEF(m3dpf_color, m3dp_Ke, "Ke"), /* emissive (emitting light of this color) */
+ M3D_PROPERTYDEF(m3dpf_color, m3dp_Tf, "Tf"), /* transmission color */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Km, "Km"), /* bump strength */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_d, "d"), /* dissolve (transparency) */
+ M3D_PROPERTYDEF(m3dpf_uint8, m3dp_il, "il"), /* illumination model (informational, ignored by PBR-shaders) */
+
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Pr, "Pr"), /* roughness */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Pm, "Pm"), /* metallic, also reflection */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Ps, "Ps"), /* sheen */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Ni, "Ni"), /* index of refraction (optical density) */
+ M3D_PROPERTYDEF(m3dpf_float, m3dp_Nt, "Nt"), /* thickness of face in millimeter, for printing */
+
+ /* aliases, note that "map_*" aliases are handled automatically */
+ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"),
+ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_N, "map_N"), /* as normal map has no scalar version, it's counterpart is 'il' */
+ M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl")
+};
+/* shape command definitions. if more commands start with the same string, the longer must come first */
+static m3dcd_t m3d_commandtypes[] = {
+ /* technical */
+ M3D_CMDDEF(m3dc_use, "use", 1, m3dcp_mi_t, 0, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_inc, "inc", 3, m3dcp_hi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_mesh, "mesh", 1, m3dcp_fi_t, m3dcp_fi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0),
+ /* approximations */
+ M3D_CMDDEF(m3dc_div, "div", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_sub, "sub", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_len, "len", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_dist, "dist", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+ /* modifiers */
+ M3D_CMDDEF(m3dc_degu, "degu", 1, m3dcp_i1_t, 0, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_deg, "deg", 2, m3dcp_i1_t, m3dcp_i1_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_rangeu, "rangeu", 1, m3dcp_ti_t, 0, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_range, "range", 2, m3dcp_ti_t, m3dcp_ti_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_paru, "paru", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_parv, "parv", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_trim, "trim", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_hole, "hole", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_scrv, "scrv", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_sp, "sp", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ /* helper curves */
+ M3D_CMDDEF(m3dc_bez1, "bez1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_bsp1, "bsp1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_bez2, "bez2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_bsp2, "bsp2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ /* surfaces */
+ M3D_CMDDEF(m3dc_bezun, "bezun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_bezu, "bezu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_bezn, "bezn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_bez, "bez", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_nurbsun, "nurbsun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_nurbsu, "nurbsu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_nurbsn, "nurbsn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_nurbs, "nurbs", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_conn, "conn", 6, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0),
+ /* geometrical */
+ M3D_CMDDEF(m3dc_line, "line", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_polygon, "polygon", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_circle, "circle", 3, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_cylinder, "cylinder", 6, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0),
+ M3D_CMDDEF(m3dc_shpere, "shpere", 2, m3dcp_vi_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_torus, "torus", 4, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_cone, "cone", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0),
+ M3D_CMDDEF(m3dc_cube, "cube", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0)
+};
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H)
+/* zlib_compressor from
+
+ stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h
+*/
+typedef unsigned char _m3dstbiw__uc;
+typedef unsigned short _m3dstbiw__us;
+
+typedef uint16_t _m3dstbiw__uint16;
+typedef int16_t _m3dstbiw__int16;
+typedef uint32_t _m3dstbiw__uint32;
+typedef int32_t _m3dstbiw__int32;
+
+#define STBIW_MALLOC(s) M3D_MALLOC(s)
+#define STBIW_REALLOC(p, ns) M3D_REALLOC(p, ns)
+#define STBIW_REALLOC_SIZED(p, oldsz, newsz) STBIW_REALLOC(p, newsz)
+#define STBIW_FREE M3D_FREE
+#define STBIW_MEMMOVE memmove
+#define STBIW_UCHAR (uint8_t)
+#define STBIW_ASSERT(x)
+#define _m3dstbiw___sbraw(a) ((int *)(a)-2)
+#define _m3dstbiw___sbm(a) _m3dstbiw___sbraw(a)[0]
+#define _m3dstbiw___sbn(a) _m3dstbiw___sbraw(a)[1]
+
+#define _m3dstbiw___sbneedgrow(a, n) ((a) == 0 || _m3dstbiw___sbn(a) + n >= _m3dstbiw___sbm(a))
+#define _m3dstbiw___sbmaybegrow(a, n) (_m3dstbiw___sbneedgrow(a, (n)) ? _m3dstbiw___sbgrow(a, n) : 0)
+#define _m3dstbiw___sbgrow(a, n) _m3dstbiw___sbgrowf((void **)&(a), (n), sizeof(*(a)))
+
+#define _m3dstbiw___sbpush(a, v) (_m3dstbiw___sbmaybegrow(a, 1), (a)[_m3dstbiw___sbn(a)++] = (v))
+#define _m3dstbiw___sbcount(a) ((a) ? _m3dstbiw___sbn(a) : 0)
+#define _m3dstbiw___sbfree(a) ((a) ? STBIW_FREE(_m3dstbiw___sbraw(a)), 0 : 0)
+
+static void *_m3dstbiw___sbgrowf(void **arr, int increment, int itemsize) {
+ int m = *arr ? 2 * _m3dstbiw___sbm(*arr) + increment : increment + 1;
+ void *p = STBIW_REALLOC_SIZED(*arr ? _m3dstbiw___sbraw(*arr) : 0, *arr ? (_m3dstbiw___sbm(*arr) * itemsize + sizeof(int) * 2) : 0, itemsize * m + sizeof(int) * 2);
+ STBIW_ASSERT(p);
+ if (p) {
+ if (!*arr) ((int *)p)[1] = 0;
+ *arr = (void *)((int *)p + 2);
+ _m3dstbiw___sbm(*arr) = m;
+ }
+ return *arr;
+}
+
+static unsigned char *_m3dstbiw___zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) {
+ while (*bitcount >= 8) {
+ _m3dstbiw___sbpush(data, STBIW_UCHAR(*bitbuffer));
+ *bitbuffer >>= 8;
+ *bitcount -= 8;
+ }
+ return data;
+}
+
+static int _m3dstbiw___zlib_bitrev(int code, int codebits) {
+ int res = 0;
+ while (codebits--) {
+ res = (res << 1) | (code & 1);
+ code >>= 1;
+ }
+ return res;
+}
+
+static unsigned int _m3dstbiw___zlib_countm(unsigned char *a, unsigned char *b, int limit) {
+ int i;
+ for (i = 0; i < limit && i < 258; ++i)
+ if (a[i] != b[i]) break;
+ return i;
+}
+
+static unsigned int _m3dstbiw___zhash(unsigned char *data) {
+ _m3dstbiw__uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16);
+ hash ^= hash << 3;
+ hash += hash >> 5;
+ hash ^= hash << 4;
+ hash += hash >> 17;
+ hash ^= hash << 25;
+ hash += hash >> 6;
+ return hash;
+}
+
+#define _m3dstbiw___zlib_flush() (out = _m3dstbiw___zlib_flushf(out, &bitbuf, &bitcount))
+#define _m3dstbiw___zlib_add(code, codebits) \
+ (bitbuf |= (code) << bitcount, bitcount += (codebits), _m3dstbiw___zlib_flush())
+#define _m3dstbiw___zlib_huffa(b, c) _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(b, c), c)
+#define _m3dstbiw___zlib_huff1(n) _m3dstbiw___zlib_huffa(0x30 + (n), 8)
+#define _m3dstbiw___zlib_huff2(n) _m3dstbiw___zlib_huffa(0x190 + (n)-144, 9)
+#define _m3dstbiw___zlib_huff3(n) _m3dstbiw___zlib_huffa(0 + (n)-256, 7)
+#define _m3dstbiw___zlib_huff4(n) _m3dstbiw___zlib_huffa(0xc0 + (n)-280, 8)
+#define _m3dstbiw___zlib_huff(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : (n) <= 255 ? _m3dstbiw___zlib_huff2(n) : (n) <= 279 ? _m3dstbiw___zlib_huff3(n) : _m3dstbiw___zlib_huff4(n))
+#define _m3dstbiw___zlib_huffb(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : _m3dstbiw___zlib_huff2(n))
+
+#define _m3dstbiw___ZHASH 16384
+
+unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) {
+ static unsigned short lengthc[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 259 };
+ static unsigned char lengtheb[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 };
+ static unsigned short distc[] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 32768 };
+ static unsigned char disteb[] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 };
+ unsigned int bitbuf = 0;
+ int i, j, bitcount = 0;
+ unsigned char *out = NULL;
+ unsigned char ***hash_table = (unsigned char ***)STBIW_MALLOC(_m3dstbiw___ZHASH * sizeof(char **));
+ if (hash_table == NULL)
+ return NULL;
+ if (quality < 5) quality = 5;
+
+ _m3dstbiw___sbpush(out, 0x78);
+ _m3dstbiw___sbpush(out, 0x5e);
+ _m3dstbiw___zlib_add(1, 1);
+ _m3dstbiw___zlib_add(1, 2);
+
+ for (i = 0; i < _m3dstbiw___ZHASH; ++i)
+ hash_table[i] = NULL;
+
+ i = 0;
+ while (i < data_len - 3) {
+ int h = _m3dstbiw___zhash(data + i) & (_m3dstbiw___ZHASH - 1), best = 3;
+ unsigned char *bestloc = 0;
+ unsigned char **hlist = hash_table[h];
+ int n = _m3dstbiw___sbcount(hlist);
+ for (j = 0; j < n; ++j) {
+ if (hlist[j] - data > i - 32768) {
+ int d = _m3dstbiw___zlib_countm(hlist[j], data + i, data_len - i);
+ if (d >= best) best = d, bestloc = hlist[j];
+ }
+ }
+ if (hash_table[h] && _m3dstbiw___sbn(hash_table[h]) == 2 * quality) {
+ STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, sizeof(hash_table[h][0]) * quality);
+ _m3dstbiw___sbn(hash_table[h]) = quality;
+ }
+ _m3dstbiw___sbpush(hash_table[h], data + i);
+
+ if (bestloc) {
+ h = _m3dstbiw___zhash(data + i + 1) & (_m3dstbiw___ZHASH - 1);
+ hlist = hash_table[h];
+ n = _m3dstbiw___sbcount(hlist);
+ for (j = 0; j < n; ++j) {
+ if (hlist[j] - data > i - 32767) {
+ int e = _m3dstbiw___zlib_countm(hlist[j], data + i + 1, data_len - i - 1);
+ if (e > best) {
+ bestloc = NULL;
+ break;
+ }
+ }
+ }
+ }
+
+ if (bestloc) {
+ int d = (int)(data + i - bestloc);
+ STBIW_ASSERT(d <= 32767 && best <= 258);
+ for (j = 0; best > lengthc[j + 1] - 1; ++j)
+ ;
+ _m3dstbiw___zlib_huff(j + 257);
+ if (lengtheb[j]) _m3dstbiw___zlib_add(best - lengthc[j], lengtheb[j]);
+ for (j = 0; d > distc[j + 1] - 1; ++j)
+ ;
+ _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(j, 5), 5);
+ if (disteb[j]) _m3dstbiw___zlib_add(d - distc[j], disteb[j]);
+ i += best;
+ } else {
+ _m3dstbiw___zlib_huffb(data[i]);
+ ++i;
+ }
+ }
+ for (; i < data_len; ++i)
+ _m3dstbiw___zlib_huffb(data[i]);
+ _m3dstbiw___zlib_huff(256);
+ while (bitcount)
+ _m3dstbiw___zlib_add(0, 1);
+
+ for (i = 0; i < _m3dstbiw___ZHASH; ++i)
+ (void)_m3dstbiw___sbfree(hash_table[i]);
+ STBIW_FREE(hash_table);
+
+ {
+ unsigned int s1 = 1, s2 = 0;
+ int blocklen = (int)(data_len % 5552);
+ j = 0;
+ while (j < data_len) {
+ for (i = 0; i < blocklen; ++i)
+ s1 += data[j + i], s2 += s1;
+ s1 %= 65521, s2 %= 65521;
+ j += blocklen;
+ blocklen = 5552;
+ }
+ _m3dstbiw___sbpush(out, STBIW_UCHAR(s2 >> 8));
+ _m3dstbiw___sbpush(out, STBIW_UCHAR(s2));
+ _m3dstbiw___sbpush(out, STBIW_UCHAR(s1 >> 8));
+ _m3dstbiw___sbpush(out, STBIW_UCHAR(s1));
+ }
+ *out_len = _m3dstbiw___sbn(out);
+ STBIW_MEMMOVE(_m3dstbiw___sbraw(out), out, *out_len);
+ return (unsigned char *)_m3dstbiw___sbraw(out);
+}
+#define stbi_zlib_compress _m3dstbi_zlib_compress
+#else
+unsigned char *_m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality);
+#endif
+
+#define M3D_CHUNKMAGIC(m, a, b, c, d) ((m)[0] == (a) && (m)[1] == (b) && (m)[2] == (c) && (m)[3] == (d))
+
+#include <locale.h> /* sprintf and strtod cares about number locale */
+#include <stdio.h> /* get sprintf */
+#ifdef M3D_PROFILING
+#include <sys/time.h>
+#endif
+
+#if !defined(M3D_NOIMPORTER)
+/* helper functions for the ASCII parser */
+static char *_m3d_findarg(char *s) {
+ while (s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n')
+ s++;
+ while (s && *s && (*s == ' ' || *s == '\t'))
+ s++;
+ return s;
+}
+static char *_m3d_findnl(char *s) {
+ while (s && *s && *s != '\r' && *s != '\n')
+ s++;
+ if (*s == '\r') s++;
+ if (*s == '\n') s++;
+ return s;
+}
+static char *_m3d_gethex(char *s, uint32_t *ret) {
+ if (*s == '#') s++;
+ *ret = 0;
+ for (; *s; s++) {
+ if (*s >= '0' && *s <= '9') {
+ *ret <<= 4;
+ *ret += (uint32_t)(*s - '0');
+ } else if (*s >= 'a' && *s <= 'f') {
+ *ret <<= 4;
+ *ret += (uint32_t)(*s - 'a' + 10);
+ } else if (*s >= 'A' && *s <= 'F') {
+ *ret <<= 4;
+ *ret += (uint32_t)(*s - 'A' + 10);
+ } else
+ break;
+ }
+ return _m3d_findarg(s);
+}
+static char *_m3d_getint(char *s, uint32_t *ret) {
+ char *e = s;
+ if (!s || !*s || *s == '\r' || *s == '\n') return s;
+ for (; *e >= '0' && *e <= '9'; e++)
+ ;
+ *ret = atoi(s);
+ return e;
+}
+static char *_m3d_getfloat(char *s, M3D_FLOAT *ret) {
+ char *e = s;
+ if (!s || !*s || *s == '\r' || *s == '\n') return s;
+ for (; *e == '-' || *e == '+' || *e == '.' || (*e >= '0' && *e <= '9') || *e == 'e' || *e == 'E'; e++)
+ ;
+ *ret = (M3D_FLOAT)strtod(s, NULL);
+ return _m3d_findarg(e);
+}
+#endif
+#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER))
+/* helper function to create safe strings */
+char *_m3d_safestr(char *in, int morelines) {
+ char *out, *o, *i = in;
+ int l;
+ if (!in || !*in) {
+ out = (char *)M3D_MALLOC(1);
+ if (!out) return NULL;
+ out[0] = 0;
+ } else {
+ for (o = in, l = 0; *o && ((morelines & 1) || (*o != '\r' && *o != '\n')) && l < 256; o++, l++)
+ ;
+ out = o = (char *)M3D_MALLOC(l + 1);
+ if (!out) return NULL;
+ while (*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n'))
+ i++;
+ for (; *i && (morelines || (*i != '\r' && *i != '\n')) && o - out < l; i++) {
+ if (*i == '\r') continue;
+ if (*i == '\n') {
+ if (morelines >= 3 && o > out && *(o - 1) == '\n') break;
+ if (i > in && *(i - 1) == '\n') continue;
+ if (morelines & 1) {
+ if (morelines == 1) *o++ = '\r';
+ *o++ = '\n';
+ } else
+ break;
+ } else if (*i == ' ' || *i == '\t') {
+ *o++ = morelines ? ' ' : '_';
+ } else
+ *o++ = !morelines && (*i == '/' || *i == '\\') ? '_' : *i;
+ }
+ for (; o > out && (*(o - 1) == ' ' || *(o - 1) == '\t' || *(o - 1) == '\r' || *(o - 1) == '\n'); o--)
+ ;
+ *o = 0;
+ out = (char *)M3D_REALLOC(out, (uintptr_t)o - (uintptr_t)out + 1);
+ }
+ return out;
+}
+#endif
+#ifndef M3D_NOIMPORTER
+/* helper function to load and decode/generate a texture */
+M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char *fn) {
+ unsigned int i, len = 0;
+ unsigned char *buff = NULL;
+ char *fn2;
+#ifdef STBI__PNG_TYPE
+ unsigned int w, h;
+ stbi__context s;
+ stbi__result_info ri;
+#endif
+
+ /* do we have loaded this texture already? */
+ for (i = 0; i < model->numtexture; i++)
+ if (!strcmp(fn, model->texture[i].name)) return i;
+ /* see if it's inlined in the model */
+ if (model->inlined) {
+ for (i = 0; i < model->numinlined; i++)
+ if (!strcmp(fn, model->inlined[i].name)) {
+ buff = model->inlined[i].data;
+ len = model->inlined[i].length;
+ freecb = NULL;
+ break;
+ }
+ }
+ /* try to load from external source */
+ if (!buff && readfilecb) {
+ i = (unsigned int)strlen(fn);
+ if (i < 5 || fn[i - 4] != '.') {
+ fn2 = (char *)M3D_MALLOC(i + 5);
+ if (!fn2) {
+ model->errcode = M3D_ERR_ALLOC;
+ return M3D_UNDEF;
+ }
+ memcpy(fn2, fn, i);
+ memcpy(fn2 + i, ".png", 5);
+ buff = (*readfilecb)(fn2, &len);
+ M3D_FREE(fn2);
+ }
+ if (!buff) {
+ buff = (*readfilecb)(fn, &len);
+ if (!buff) return M3D_UNDEF;
+ }
+ }
+ /* add to textures array */
+ i = model->numtexture++;
+ model->texture = (m3dtx_t *)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t));
+ if (!model->texture) {
+ if (buff && freecb) (*freecb)(buff);
+ model->errcode = M3D_ERR_ALLOC;
+ return M3D_UNDEF;
+ }
+ model->texture[i].name = fn;
+ model->texture[i].w = model->texture[i].h = 0;
+ model->texture[i].d = NULL;
+ if (buff) {
+ if (buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') {
+#ifdef STBI__PNG_TYPE
+ s.read_from_callbacks = 0;
+ s.img_buffer = s.img_buffer_original = (unsigned char *)buff;
+ s.img_buffer_end = s.img_buffer_original_end = (unsigned char *)buff + len;
+ /* don't use model->texture[i].w directly, it's a uint16_t */
+ w = h = len = 0;
+ ri.bits_per_channel = 8;
+ model->texture[i].d = (uint8_t *)stbi__png_load(&s, (int *)&w, (int *)&h, (int *)&len, 0, &ri);
+ model->texture[i].w = (uint16_t)w;
+ model->texture[i].h = (uint16_t)h;
+ model->texture[i].f = (uint8_t)len;
+#endif
+ } else {
+#ifdef M3D_TX_INTERP
+ if ((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) {
+ M3D_LOG("Unable to generate texture");
+ M3D_LOG(fn);
+ }
+#else
+ M3D_LOG("Unimplemented interpreter");
+ M3D_LOG(fn);
+#endif
+ }
+ if (freecb) (*freecb)(buff);
+ }
+ if (!model->texture[i].d)
+ model->errcode = M3D_ERR_UNKIMG;
+ return i;
+}
+
+/* helper function to load and generate a procedural surface */
+void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused m3dfree_t freecb, _unused char *fn) {
+#ifdef M3D_PR_INTERP
+ unsigned int i, len = 0;
+ unsigned char *buff = readfilecb ? (*readfilecb)(fn, &len) : NULL;
+
+ if (!buff && model->inlined) {
+ for (i = 0; i < model->numinlined; i++)
+ if (!strcmp(fn, model->inlined[i].name)) {
+ buff = model->inlined[i].data;
+ len = model->inlined[i].length;
+ freecb = NULL;
+ break;
+ }
+ }
+ if (!buff || !len || (model->errcode = M3D_PR_INTERP(fn, buff, len, model)) != M3D_SUCCESS) {
+ M3D_LOG("Unable to generate procedural surface");
+ M3D_LOG(fn);
+ model->errcode = M3D_ERR_UNKIMG;
+ }
+ if (freecb && buff) (*freecb)(buff);
+#else
+ (void)readfilecb;
+ (void)freecb;
+ (void)fn;
+ M3D_LOG("Unimplemented interpreter");
+ M3D_LOG(fn);
+ model->errcode = M3D_ERR_UNIMPL;
+#endif
+}
+/* helpers to read indices from data stream */
+#define M3D_GETSTR(x) \
+ do { \
+ offs = 0; \
+ data = _m3d_getidx(data, model->si_s, &offs); \
+ x = offs ? ((char *)model->raw + 16 + offs) : NULL; \
+ } while (0)
+_inline static unsigned char *_m3d_getidx(unsigned char *data, char type, M3D_INDEX *idx) {
+ switch (type) {
+ case 1:
+ *idx = data[0] > 253 ? (int8_t)data[0] : data[0];
+ data++;
+ break;
+ case 2:
+ *idx = *((uint16_t *)data) > 65533 ? *((int16_t *)data) : *((uint16_t *)data);
+ data += 2;
+ break;
+ case 4:
+ *idx = *((int32_t *)data);
+ data += 4;
+ break;
+ }
+ return data;
+}
+
+#ifndef M3D_NOANIMATION
+/* multiply 4 x 4 matrices. Do not use float *r[16] as argument, because some compilers misinterpret that as
+ * 16 pointers each pointing to a float, but we need a single pointer to 16 floats. */
+void _m3d_mul(M3D_FLOAT *r, M3D_FLOAT *a, M3D_FLOAT *b) {
+ r[0] = b[0] * a[0] + b[4] * a[1] + b[8] * a[2] + b[12] * a[3];
+ r[1] = b[1] * a[0] + b[5] * a[1] + b[9] * a[2] + b[13] * a[3];
+ r[2] = b[2] * a[0] + b[6] * a[1] + b[10] * a[2] + b[14] * a[3];
+ r[3] = b[3] * a[0] + b[7] * a[1] + b[11] * a[2] + b[15] * a[3];
+ r[4] = b[0] * a[4] + b[4] * a[5] + b[8] * a[6] + b[12] * a[7];
+ r[5] = b[1] * a[4] + b[5] * a[5] + b[9] * a[6] + b[13] * a[7];
+ r[6] = b[2] * a[4] + b[6] * a[5] + b[10] * a[6] + b[14] * a[7];
+ r[7] = b[3] * a[4] + b[7] * a[5] + b[11] * a[6] + b[15] * a[7];
+ r[8] = b[0] * a[8] + b[4] * a[9] + b[8] * a[10] + b[12] * a[11];
+ r[9] = b[1] * a[8] + b[5] * a[9] + b[9] * a[10] + b[13] * a[11];
+ r[10] = b[2] * a[8] + b[6] * a[9] + b[10] * a[10] + b[14] * a[11];
+ r[11] = b[3] * a[8] + b[7] * a[9] + b[11] * a[10] + b[15] * a[11];
+ r[12] = b[0] * a[12] + b[4] * a[13] + b[8] * a[14] + b[12] * a[15];
+ r[13] = b[1] * a[12] + b[5] * a[13] + b[9] * a[14] + b[13] * a[15];
+ r[14] = b[2] * a[12] + b[6] * a[13] + b[10] * a[14] + b[14] * a[15];
+ r[15] = b[3] * a[12] + b[7] * a[13] + b[11] * a[14] + b[15] * a[15];
+}
+/* calculate 4 x 4 matrix inverse */
+void _m3d_inv(M3D_FLOAT *m) {
+ M3D_FLOAT r[16];
+ M3D_FLOAT det =
+ m[0] * m[5] * m[10] * m[15] - m[0] * m[5] * m[11] * m[14] + m[0] * m[6] * m[11] * m[13] - m[0] * m[6] * m[9] * m[15] + m[0] * m[7] * m[9] * m[14] - m[0] * m[7] * m[10] * m[13] - m[1] * m[6] * m[11] * m[12] + m[1] * m[6] * m[8] * m[15] - m[1] * m[7] * m[8] * m[14] + m[1] * m[7] * m[10] * m[12] - m[1] * m[4] * m[10] * m[15] + m[1] * m[4] * m[11] * m[14] + m[2] * m[7] * m[8] * m[13] - m[2] * m[7] * m[9] * m[12] + m[2] * m[4] * m[9] * m[15] - m[2] * m[4] * m[11] * m[13] + m[2] * m[5] * m[11] * m[12] - m[2] * m[5] * m[8] * m[15] - m[3] * m[4] * m[9] * m[14] + m[3] * m[4] * m[10] * m[13] - m[3] * m[5] * m[10] * m[12] + m[3] * m[5] * m[8] * m[14] - m[3] * m[6] * m[8] * m[13] + m[3] * m[6] * m[9] * m[12];
+ if (det == (M3D_FLOAT)0.0 || det == (M3D_FLOAT)-0.0)
+ det = (M3D_FLOAT)1.0;
+ else
+ det = (M3D_FLOAT)1.0 / det;
+ r[0] = det * (m[5] * (m[10] * m[15] - m[11] * m[14]) + m[6] * (m[11] * m[13] - m[9] * m[15]) + m[7] * (m[9] * m[14] - m[10] * m[13]));
+ r[1] = -det * (m[1] * (m[10] * m[15] - m[11] * m[14]) + m[2] * (m[11] * m[13] - m[9] * m[15]) + m[3] * (m[9] * m[14] - m[10] * m[13]));
+ r[2] = det * (m[1] * (m[6] * m[15] - m[7] * m[14]) + m[2] * (m[7] * m[13] - m[5] * m[15]) + m[3] * (m[5] * m[14] - m[6] * m[13]));
+ r[3] = -det * (m[1] * (m[6] * m[11] - m[7] * m[10]) + m[2] * (m[7] * m[9] - m[5] * m[11]) + m[3] * (m[5] * m[10] - m[6] * m[9]));
+ r[4] = -det * (m[4] * (m[10] * m[15] - m[11] * m[14]) + m[6] * (m[11] * m[12] - m[8] * m[15]) + m[7] * (m[8] * m[14] - m[10] * m[12]));
+ r[5] = det * (m[0] * (m[10] * m[15] - m[11] * m[14]) + m[2] * (m[11] * m[12] - m[8] * m[15]) + m[3] * (m[8] * m[14] - m[10] * m[12]));
+ r[6] = -det * (m[0] * (m[6] * m[15] - m[7] * m[14]) + m[2] * (m[7] * m[12] - m[4] * m[15]) + m[3] * (m[4] * m[14] - m[6] * m[12]));
+ r[7] = det * (m[0] * (m[6] * m[11] - m[7] * m[10]) + m[2] * (m[7] * m[8] - m[4] * m[11]) + m[3] * (m[4] * m[10] - m[6] * m[8]));
+ r[8] = det * (m[4] * (m[9] * m[15] - m[11] * m[13]) + m[5] * (m[11] * m[12] - m[8] * m[15]) + m[7] * (m[8] * m[13] - m[9] * m[12]));
+ r[9] = -det * (m[0] * (m[9] * m[15] - m[11] * m[13]) + m[1] * (m[11] * m[12] - m[8] * m[15]) + m[3] * (m[8] * m[13] - m[9] * m[12]));
+ r[10] = det * (m[0] * (m[5] * m[15] - m[7] * m[13]) + m[1] * (m[7] * m[12] - m[4] * m[15]) + m[3] * (m[4] * m[13] - m[5] * m[12]));
+ r[11] = -det * (m[0] * (m[5] * m[11] - m[7] * m[9]) + m[1] * (m[7] * m[8] - m[4] * m[11]) + m[3] * (m[4] * m[9] - m[5] * m[8]));
+ r[12] = -det * (m[4] * (m[9] * m[14] - m[10] * m[13]) + m[5] * (m[10] * m[12] - m[8] * m[14]) + m[6] * (m[8] * m[13] - m[9] * m[12]));
+ r[13] = det * (m[0] * (m[9] * m[14] - m[10] * m[13]) + m[1] * (m[10] * m[12] - m[8] * m[14]) + m[2] * (m[8] * m[13] - m[9] * m[12]));
+ r[14] = -det * (m[0] * (m[5] * m[14] - m[6] * m[13]) + m[1] * (m[6] * m[12] - m[4] * m[14]) + m[2] * (m[4] * m[13] - m[5] * m[12]));
+ r[15] = det * (m[0] * (m[5] * m[10] - m[6] * m[9]) + m[1] * (m[6] * m[8] - m[4] * m[10]) + m[2] * (m[4] * m[9] - m[5] * m[8]));
+ memcpy(m, &r, sizeof(r));
+}
+/* compose a column major 4 x 4 matrix from vec3 position and vec4 orientation/rotation quaternion */
+void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q) {
+ if (q->x == (M3D_FLOAT)0.0 && q->y == (M3D_FLOAT)0.0 && q->z >= (M3D_FLOAT)0.7071065 && q->z <= (M3D_FLOAT)0.7071075 &&
+ q->w == (M3D_FLOAT)0.0) {
+ r[1] = r[2] = r[4] = r[6] = r[8] = r[9] = (M3D_FLOAT)0.0;
+ r[0] = r[5] = r[10] = (M3D_FLOAT)-1.0;
+ } else {
+ r[0] = 1 - 2 * (q->y * q->y + q->z * q->z);
+ if (r[0] > -M3D_EPSILON && r[0] < M3D_EPSILON) r[0] = (M3D_FLOAT)0.0;
+ r[1] = 2 * (q->x * q->y - q->z * q->w);
+ if (r[1] > -M3D_EPSILON && r[1] < M3D_EPSILON) r[1] = (M3D_FLOAT)0.0;
+ r[2] = 2 * (q->x * q->z + q->y * q->w);
+ if (r[2] > -M3D_EPSILON && r[2] < M3D_EPSILON) r[2] = (M3D_FLOAT)0.0;
+ r[4] = 2 * (q->x * q->y + q->z * q->w);
+ if (r[4] > -M3D_EPSILON && r[4] < M3D_EPSILON) r[4] = (M3D_FLOAT)0.0;
+ r[5] = 1 - 2 * (q->x * q->x + q->z * q->z);
+ if (r[5] > -M3D_EPSILON && r[5] < M3D_EPSILON) r[5] = (M3D_FLOAT)0.0;
+ r[6] = 2 * (q->y * q->z - q->x * q->w);
+ if (r[6] > -M3D_EPSILON && r[6] < M3D_EPSILON) r[6] = (M3D_FLOAT)0.0;
+ r[8] = 2 * (q->x * q->z - q->y * q->w);
+ if (r[8] > -M3D_EPSILON && r[8] < M3D_EPSILON) r[8] = (M3D_FLOAT)0.0;
+ r[9] = 2 * (q->y * q->z + q->x * q->w);
+ if (r[9] > -M3D_EPSILON && r[9] < M3D_EPSILON) r[9] = (M3D_FLOAT)0.0;
+ r[10] = 1 - 2 * (q->x * q->x + q->y * q->y);
+ if (r[10] > -M3D_EPSILON && r[10] < M3D_EPSILON) r[10] = (M3D_FLOAT)0.0;
+ }
+ r[3] = p->x;
+ r[7] = p->y;
+ r[11] = p->z;
+ r[12] = 0;
+ r[13] = 0;
+ r[14] = 0;
+ r[15] = 1;
+}
+#endif
+#if !defined(M3D_NOANIMATION) || !defined(M3D_NONORMALS)
+/* portable fast inverse square root calculation. returns 1/sqrt(x) */
+static M3D_FLOAT _m3d_rsq(M3D_FLOAT x) {
+#ifdef M3D_DOUBLE
+ return ((M3D_FLOAT)15.0 / (M3D_FLOAT)8.0) + ((M3D_FLOAT)-5.0 / (M3D_FLOAT)4.0) * x + ((M3D_FLOAT)3.0 / (M3D_FLOAT)8.0) * x * x;
+#else
+ /* John Carmack's */
+ float x2 = x * 0.5f;
+ *((uint32_t *)&x) = (0x5f3759df - (*((uint32_t *)&x) >> 1));
+ return x * (1.5f - (x2 * x * x));
+#endif
+}
+#endif
+
+/**
+ * Function to decode a Model 3D into in-memory format
+ */
+m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib) {
+ unsigned char *end, *chunk, *buff, weights[8];
+ unsigned int i, j, k, l, n, am, len = 0, reclen, offs;
+ char *name, *lang;
+ float f;
+ m3d_t *model;
+ M3D_INDEX mi;
+ M3D_FLOAT w;
+ m3dcd_t *cd;
+ m3dtx_t *tx;
+ m3dh_t *h;
+ m3dm_t *m;
+ m3da_t *a;
+ m3di_t *t;
+#ifndef M3D_NONORMALS
+ char neednorm = 0;
+ m3dv_t *norm = NULL, *v0, *v1, *v2, va, vb;
+#endif
+#ifndef M3D_NOANIMATION
+ M3D_FLOAT r[16];
+#endif
+#if !defined(M3D_NOWEIGHTS) || !defined(M3D_NOANIMATION)
+ m3db_t *b;
+#endif
+#ifndef M3D_NOWEIGHTS
+ m3ds_t *sk;
+#endif
+ m3ds_t s;
+ M3D_INDEX bi[M3D_BONEMAXLEVEL + 1], level;
+ const char *ol;
+ char *ptr, *pe, *fn;
+#ifdef M3D_PROFILING
+ struct timeval tv0, tv1, tvd;
+ gettimeofday(&tv0, NULL);
+#endif
+
+ if (!data || (!M3D_CHUNKMAGIC(data, '3', 'D', 'M', 'O')
+ && !M3D_CHUNKMAGIC(data, '3', 'd', 'm', 'o')
+ ))
+ return NULL;
+ model = (m3d_t *)M3D_MALLOC(sizeof(m3d_t));
+ if (!model) {
+ M3D_LOG("Out of memory");
+ return NULL;
+ }
+ memset(model, 0, sizeof(m3d_t));
+
+ if (mtllib) {
+ model->nummaterial = mtllib->nummaterial;
+ model->material = mtllib->material;
+ model->numtexture = mtllib->numtexture;
+ model->texture = mtllib->texture;
+ model->flags |= M3D_FLG_MTLLIB;
+ }
+ /* ASCII variant? */
+ if (M3D_CHUNKMAGIC(data, '3', 'd', 'm', 'o')) {
+ model->errcode = M3D_ERR_BADFILE;
+ model->flags |= M3D_FLG_FREESTR;
+ model->raw = (m3dhdr_t *)data;
+ ptr = (char *)data;
+ ol = setlocale(LC_NUMERIC, NULL);
+ setlocale(LC_NUMERIC, "C");
+ /* parse header. Don't use sscanf, that's incredibly slow */
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ pe = _m3d_findnl(ptr);
+ model->scale = (float)strtod(ptr, NULL);
+ ptr = pe;
+ if (model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0;
+ model->name = _m3d_safestr(ptr, 2);
+ ptr = _m3d_findnl(ptr);
+ if (!*ptr) goto asciiend;
+ model->license = _m3d_safestr(ptr, 2);
+ ptr = _m3d_findnl(ptr);
+ if (!*ptr) goto asciiend;
+ model->author = _m3d_safestr(ptr, 2);
+ ptr = _m3d_findnl(ptr);
+ if (!*ptr) goto asciiend;
+ if (*ptr != '\r' && *ptr != '\n')
+ model->desc = _m3d_safestr(ptr, 3);
+ while (*ptr) {
+ while (*ptr && *ptr != '\n')
+ ptr++;
+ ptr++;
+ if (*ptr == '\r') ptr++;
+ if (*ptr == '\n') break;
+ }
+
+ /* the main chunk reader loop */
+ while (*ptr) {
+ while (*ptr && (*ptr == '\r' || *ptr == '\n'))
+ ptr++;
+ if (!*ptr || (ptr[0] == 'E' && ptr[1] == 'n' && ptr[2] == 'd')) break;
+ /* make sure there's at least one data row */
+ pe = ptr;
+ ptr = _m3d_findnl(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ /* Preview chunk */
+ if (!memcmp(pe, "Preview", 7)) {
+ if (readfilecb) {
+ pe = _m3d_safestr(ptr, 0);
+ if (!pe || !*pe) goto asciiend;
+ model->preview.data = (*readfilecb)(pe, &model->preview.length);
+ M3D_FREE(pe);
+ }
+ while (*ptr && *ptr != '\r' && *ptr != '\n')
+ ptr = _m3d_findnl(ptr);
+ } else
+ /* texture map chunk */
+ if (!memcmp(pe, "Textmap", 7)) {
+ if (model->tmap) {
+ M3D_LOG("More texture map chunks, should be unique");
+ goto asciiend;
+ }
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ i = model->numtmap++;
+ model->tmap = (m3dti_t *)M3D_REALLOC(model->tmap, model->numtmap * sizeof(m3dti_t));
+ if (!model->tmap) goto memerr;
+ ptr = _m3d_getfloat(ptr, &model->tmap[i].u);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ _m3d_getfloat(ptr, &model->tmap[i].v);
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* vertex chunk */
+ if (!memcmp(pe, "Vertex", 6)) {
+ if (model->vertex) {
+ M3D_LOG("More vertex chunks, should be unique");
+ goto asciiend;
+ }
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ i = model->numvertex++;
+ model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t));
+ if (!model->vertex) goto memerr;
+ memset(&model->vertex[i], 0, sizeof(m3dv_t));
+ model->vertex[i].skinid = M3D_UNDEF;
+ model->vertex[i].color = 0;
+ model->vertex[i].w = (M3D_FLOAT)1.0;
+ ptr = _m3d_getfloat(ptr, &model->vertex[i].x);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ ptr = _m3d_getfloat(ptr, &model->vertex[i].y);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ ptr = _m3d_getfloat(ptr, &model->vertex[i].z);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ ptr = _m3d_getfloat(ptr, &model->vertex[i].w);
+ if (!*ptr) goto asciiend;
+ if (*ptr == '#') {
+ ptr = _m3d_gethex(ptr, &model->vertex[i].color);
+ if (!*ptr) goto asciiend;
+ }
+ /* parse skin */
+ memset(&s, 0, sizeof(m3ds_t));
+ for (j = 0, w = (M3D_FLOAT)0.0; j < M3D_NUMBONE && *ptr && *ptr != '\r' && *ptr != '\n'; j++) {
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ ptr = _m3d_getint(ptr, &k);
+ s.boneid[j] = (M3D_INDEX)k;
+ if (*ptr == ':') {
+ ptr++;
+ ptr = _m3d_getfloat(ptr, &s.weight[j]);
+ w += s.weight[j];
+ } else if (!j)
+ s.weight[j] = (M3D_FLOAT)1.0;
+ if (!*ptr) goto asciiend;
+ }
+ if (s.boneid[0] != M3D_UNDEF && s.weight[0] > (M3D_FLOAT)0.0) {
+ if (w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0)
+ for (j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++)
+ s.weight[j] /= w;
+ k = M3D_NOTDEFINED;
+ if (model->skin) {
+ for (j = 0; j < model->numskin; j++)
+ if (!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) {
+ k = j;
+ break;
+ }
+ }
+ if (k == M3D_NOTDEFINED) {
+ k = model->numskin++;
+ model->skin = (m3ds_t *)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t));
+ memcpy(&model->skin[k], &s, sizeof(m3ds_t));
+ }
+ model->vertex[i].skinid = (M3D_INDEX)k;
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* Skeleton, bone hierarchy */
+ if (!memcmp(pe, "Bones", 5)) {
+ if (model->bone) {
+ M3D_LOG("More bones chunks, should be unique");
+ goto asciiend;
+ }
+ bi[0] = M3D_UNDEF;
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ i = model->numbone++;
+ model->bone = (m3db_t *)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t));
+ if (!model->bone) goto memerr;
+ for (level = 0; *ptr == '/'; ptr++, level++)
+ ;
+ if (level > M3D_BONEMAXLEVEL || !*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ bi[level + 1] = i;
+ model->bone[i].numweight = 0;
+ model->bone[i].weight = NULL;
+ model->bone[i].parent = bi[level];
+ ptr = _m3d_getint(ptr, &k);
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ model->bone[i].pos = (M3D_INDEX)k;
+ ptr = _m3d_getint(ptr, &k);
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ model->bone[i].ori = (M3D_INDEX)k;
+ model->vertex[k].skinid = M3D_INDEXMAX;
+ pe = _m3d_safestr(ptr, 0);
+ if (!pe || !*pe) goto asciiend;
+ model->bone[i].name = pe;
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* material chunk */
+ if (!memcmp(pe, "Material", 8)) {
+ pe = _m3d_findarg(pe);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ pe = _m3d_safestr(pe, 0);
+ if (!pe || !*pe) goto asciiend;
+ for (i = 0; i < model->nummaterial; i++)
+ if (!strcmp(pe, model->material[i].name)) {
+ M3D_LOG("Multiple definitions for material");
+ M3D_LOG(pe);
+ M3D_FREE(pe);
+ pe = NULL;
+ while (*ptr && *ptr != '\r' && *ptr != '\n')
+ ptr = _m3d_findnl(ptr);
+ break;
+ }
+ if (!pe) continue;
+ i = model->nummaterial++;
+ if (model->flags & M3D_FLG_MTLLIB) {
+ m = model->material;
+ model->material = (m3dm_t *)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t));
+ if (!model->material) goto memerr;
+ memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t));
+ if (model->texture) {
+ tx = model->texture;
+ model->texture = (m3dtx_t *)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t));
+ if (!model->texture) goto memerr;
+ memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t));
+ }
+ model->flags &= ~M3D_FLG_MTLLIB;
+ } else {
+ model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t));
+ if (!model->material) goto memerr;
+ }
+ m = &model->material[i];
+ m->name = pe;
+ m->numprop = 0;
+ m->prop = NULL;
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ k = n = 256;
+ if (*ptr == 'm' && *(ptr + 1) == 'a' && *(ptr + 2) == 'p' && *(ptr + 3) == '_') {
+ k = m3dpf_map;
+ ptr += 4;
+ }
+ for (j = 0; j < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); j++)
+ if (!memcmp(ptr, m3d_propertytypes[j].key, strlen(m3d_propertytypes[j].key))) {
+ n = m3d_propertytypes[j].id;
+ if (k != m3dpf_map) k = m3d_propertytypes[j].format;
+ break;
+ }
+ if (n != 256 && k != 256) {
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ j = m->numprop++;
+ m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
+ if (!m->prop) goto memerr;
+ m->prop[j].type = n + (k == m3dpf_map && n < 128 ? 128 : 0);
+ switch (k) {
+ case m3dpf_color: ptr = _m3d_gethex(ptr, &m->prop[j].value.color); break;
+ case m3dpf_uint8:
+ case m3dpf_uint16:
+ case m3dpf_uint32: ptr = _m3d_getint(ptr, &m->prop[j].value.num); break;
+ case m3dpf_float: ptr = _m3d_getfloat(ptr, &m->prop[j].value.fnum); break;
+ case m3dpf_map:
+ pe = _m3d_safestr(ptr, 0);
+ if (!pe || !*pe) goto asciiend;
+ m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe);
+ if (model->errcode == M3D_ERR_ALLOC) {
+ M3D_FREE(pe);
+ goto memerr;
+ }
+ /* this error code only returned if readfilecb was specified */
+ if (m->prop[j].value.textureid == M3D_UNDEF) {
+ M3D_LOG("Texture not found");
+ M3D_LOG(pe);
+ m->numprop--;
+ }
+ M3D_FREE(pe);
+ break;
+ }
+ } else {
+ M3D_LOG("Unknown material property in");
+ M3D_LOG(m->name);
+ model->errcode = M3D_ERR_UNKPROP;
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ if (!m->numprop) model->nummaterial--;
+ } else
+ /* procedural */
+ if (!memcmp(pe, "Procedural", 10)) {
+ pe = _m3d_safestr(ptr, 0);
+ _m3d_getpr(model, readfilecb, freecb, pe);
+ M3D_FREE(pe);
+ while (*ptr && *ptr != '\r' && *ptr != '\n')
+ ptr = _m3d_findnl(ptr);
+ } else
+ /* mesh */
+ if (!memcmp(pe, "Mesh", 4)) {
+ mi = M3D_UNDEF;
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ if (*ptr == 'u') {
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr) goto asciiend;
+ mi = M3D_UNDEF;
+ if (*ptr != '\r' && *ptr != '\n') {
+ pe = _m3d_safestr(ptr, 0);
+ if (!pe || !*pe) goto asciiend;
+ for (j = 0; j < model->nummaterial; j++)
+ if (!strcmp(pe, model->material[j].name)) {
+ mi = (M3D_INDEX)j;
+ break;
+ }
+ if (mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) {
+ mi = model->nummaterial++;
+ model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t));
+ if (!model->material) goto memerr;
+ model->material[mi].name = pe;
+ model->material[mi].numprop = 1;
+ model->material[mi].prop = NULL;
+ } else
+ M3D_FREE(pe);
+ }
+ } else {
+ i = model->numface++;
+ model->face = (m3df_t *)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t));
+ if (!model->face) goto memerr;
+ memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */
+ model->face[i].materialid = mi;
+ /* hardcoded triangles. */
+ for (j = 0; j < 3; j++) {
+ /* vertex */
+ ptr = _m3d_getint(ptr, &k);
+ model->face[i].vertex[j] = (M3D_INDEX)k;
+ if (!*ptr) goto asciiend;
+ if (*ptr == '/') {
+ ptr++;
+ if (*ptr != '/') {
+ /* texcoord */
+ ptr = _m3d_getint(ptr, &k);
+ model->face[i].texcoord[j] = (M3D_INDEX)k;
+ if (!*ptr) goto asciiend;
+ }
+ if (*ptr == '/') {
+ ptr++;
+ /* normal */
+ ptr = _m3d_getint(ptr, &k);
+ model->face[i].normal[j] = (M3D_INDEX)k;
+ if (!*ptr) goto asciiend;
+ }
+ }
+#ifndef M3D_NONORMALS
+ if (model->face[i].normal[j] == M3D_UNDEF) neednorm = 1;
+#endif
+ ptr = _m3d_findarg(ptr);
+ }
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* mathematical shape */
+ if (!memcmp(pe, "Shape", 5)) {
+ pe = _m3d_findarg(pe);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ pe = _m3d_safestr(pe, 0);
+ if (!pe || !*pe) goto asciiend;
+ i = model->numshape++;
+ model->shape = (m3dh_t *)M3D_REALLOC(model->shape, model->numshape * sizeof(m3ds_t));
+ if (!model->shape) goto memerr;
+ h = &model->shape[i];
+ h->name = pe;
+ h->group = M3D_UNDEF;
+ h->numcmd = 0;
+ h->cmd = NULL;
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ if (!memcmp(ptr, "group", 5)) {
+ ptr = _m3d_findarg(ptr);
+ ptr = _m3d_getint(ptr, &h->group);
+ ptr = _m3d_findnl(ptr);
+ if (h->group != M3D_UNDEF && h->group >= model->numbone) {
+ M3D_LOG("Unknown bone id as shape group in shape");
+ M3D_LOG(pe);
+ h->group = M3D_UNDEF;
+ model->errcode = M3D_ERR_SHPE;
+ }
+ continue;
+ }
+ for (cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])); k++) {
+ j = (unsigned int)strlen(m3d_commandtypes[k].key);
+ if (!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n')) {
+ cd = &m3d_commandtypes[k];
+ break;
+ }
+ }
+ if (cd) {
+ j = h->numcmd++;
+ h->cmd = (m3dc_t *)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t));
+ if (!h->cmd) goto memerr;
+ h->cmd[j].type = k;
+ h->cmd[j].arg = (uint32_t *)M3D_MALLOC(cd->p * sizeof(uint32_t));
+ if (!h->cmd[j].arg) goto memerr;
+ memset(h->cmd[j].arg, 0, cd->p * sizeof(uint32_t));
+ for (k = n = 0, l = cd->p; k < l; k++) {
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr) goto asciiend;
+ if (*ptr == '[') {
+ ptr = _m3d_findarg(ptr + 1);
+ if (!*ptr) goto asciiend;
+ }
+ if (*ptr == ']' || *ptr == '\r' || *ptr == '\n') break;
+ switch (cd->a[((k - n) % (cd->p - n)) + n]) {
+ case m3dcp_mi_t:
+ mi = M3D_UNDEF;
+ if (*ptr != '\r' && *ptr != '\n') {
+ pe = _m3d_safestr(ptr, 0);
+ if (!pe || !*pe) goto asciiend;
+ for (n = 0; n < model->nummaterial; n++)
+ if (!strcmp(pe, model->material[n].name)) {
+ mi = (M3D_INDEX)n;
+ break;
+ }
+ if (mi == M3D_UNDEF && !(model->flags & M3D_FLG_MTLLIB)) {
+ mi = model->nummaterial++;
+ model->material = (m3dm_t *)M3D_REALLOC(model->material,
+ model->nummaterial * sizeof(m3dm_t));
+ if (!model->material) goto memerr;
+ model->material[mi].name = pe;
+ model->material[mi].numprop = 1;
+ model->material[mi].prop = NULL;
+ } else
+ M3D_FREE(pe);
+ }
+ h->cmd[j].arg[k] = mi;
+ break;
+ case m3dcp_vc_t:
+ _m3d_getfloat(ptr, &w);
+ h->cmd[j].arg[k] = *((uint32_t *)&w);
+ break;
+ case m3dcp_va_t:
+ ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]);
+ n = k + 1;
+ l += (h->cmd[j].arg[k] - 1) * (cd->p - k - 1);
+ h->cmd[j].arg = (uint32_t *)M3D_REALLOC(h->cmd[j].arg, l * sizeof(uint32_t));
+ if (!h->cmd[j].arg) goto memerr;
+ memset(&h->cmd[j].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t));
+ break;
+ case m3dcp_qi_t:
+ ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]);
+ model->vertex[h->cmd[i].arg[k]].skinid = M3D_INDEXMAX;
+ break;
+ default:
+ ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]);
+ break;
+ }
+ }
+ } else {
+ M3D_LOG("Unknown shape command in");
+ M3D_LOG(h->name);
+ model->errcode = M3D_ERR_UNKCMD;
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ if (!h->numcmd) model->numshape--;
+ } else
+ /* annotation labels */
+ if (!memcmp(pe, "Labels", 6)) {
+ pe = _m3d_findarg(pe);
+ if (!*pe) goto asciiend;
+ if (*pe == '\r' || *pe == '\n')
+ pe = NULL;
+ else
+ pe = _m3d_safestr(pe, 0);
+ k = 0;
+ fn = NULL;
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ if (*ptr == 'c') {
+ ptr = _m3d_findarg(ptr);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ ptr = _m3d_gethex(ptr, &k);
+ } else if (*ptr == 'l') {
+ ptr = _m3d_findarg(ptr);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ fn = _m3d_safestr(ptr, 2);
+ } else {
+ i = model->numlabel++;
+ model->label = (m3dl_t *)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t));
+ if (!model->label) goto memerr;
+ model->label[i].name = pe;
+ model->label[i].lang = fn;
+ model->label[i].color = k;
+ ptr = _m3d_getint(ptr, &j);
+ model->label[i].vertexid = (M3D_INDEX)j;
+ ptr = _m3d_findarg(ptr);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ model->label[i].text = _m3d_safestr(ptr, 2);
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* action */
+ if (!memcmp(pe, "Action", 6)) {
+ pe = _m3d_findarg(pe);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ pe = _m3d_getint(pe, &k);
+ pe = _m3d_findarg(pe);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ pe = _m3d_safestr(pe, 0);
+ if (!pe || !*pe) goto asciiend;
+ i = model->numaction++;
+ model->action = (m3da_t *)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t));
+ if (!model->action) goto memerr;
+ a = &model->action[i];
+ a->name = pe;
+ a->durationmsec = k;
+ /* skip the first frame marker as there's always at least one frame */
+ a->numframe = 1;
+ a->frame = (m3dfr_t *)M3D_MALLOC(sizeof(m3dfr_t));
+ if (!a->frame) goto memerr;
+ a->frame[0].msec = 0;
+ a->frame[0].numtransform = 0;
+ a->frame[0].transform = NULL;
+ i = 0;
+ if (*ptr == 'f')
+ ptr = _m3d_findnl(ptr);
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ if (*ptr == 'f') {
+ i = a->numframe++;
+ a->frame = (m3dfr_t *)M3D_REALLOC(a->frame, a->numframe * sizeof(m3dfr_t));
+ if (!a->frame) goto memerr;
+ ptr = _m3d_findarg(ptr);
+ ptr = _m3d_getint(ptr, &a->frame[i].msec);
+ a->frame[i].numtransform = 0;
+ a->frame[i].transform = NULL;
+ } else {
+ j = a->frame[i].numtransform++;
+ a->frame[i].transform = (m3dtr_t *)M3D_REALLOC(a->frame[i].transform,
+ a->frame[i].numtransform * sizeof(m3dtr_t));
+ if (!a->frame[i].transform) goto memerr;
+ ptr = _m3d_getint(ptr, &k);
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ a->frame[i].transform[j].boneid = (M3D_INDEX)k;
+ ptr = _m3d_getint(ptr, &k);
+ ptr = _m3d_findarg(ptr);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ a->frame[i].transform[j].pos = (M3D_INDEX)k;
+ ptr = _m3d_getint(ptr, &k);
+ if (!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend;
+ a->frame[i].transform[j].ori = (M3D_INDEX)k;
+ model->vertex[k].skinid = M3D_INDEXMAX;
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* inlined assets chunk */
+ if (!memcmp(pe, "Assets", 6)) {
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ if (readfilecb) {
+ pe = _m3d_safestr(ptr, 2);
+ if (!pe || !*pe) goto asciiend;
+ i = model->numinlined++;
+ model->inlined = (m3di_t *)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t));
+ if (!model->inlined) goto memerr;
+ t = &model->inlined[i];
+ model->inlined[i].data = (*readfilecb)(pe, &model->inlined[i].length);
+ if (model->inlined[i].data) {
+ fn = strrchr(pe, '.');
+ if (fn && (fn[1] == 'p' || fn[1] == 'P') && (fn[2] == 'n' || fn[2] == 'N') &&
+ (fn[3] == 'g' || fn[3] == 'G')) *fn = 0;
+ fn = strrchr(pe, '/');
+ if (!fn) fn = strrchr(pe, '\\');
+ if (!fn)
+ fn = pe;
+ else
+ fn++;
+ model->inlined[i].name = _m3d_safestr(fn, 0);
+ } else
+ model->numinlined--;
+ M3D_FREE(pe);
+ }
+ ptr = _m3d_findnl(ptr);
+ }
+ } else
+ /* extra chunks */
+ if (!memcmp(pe, "Extra", 5)) {
+ pe = _m3d_findarg(pe);
+ if (!*pe || *pe == '\r' || *pe == '\n') goto asciiend;
+ buff = (unsigned char *)_m3d_findnl(ptr);
+ k = ((uint32_t)((uintptr_t)buff - (uintptr_t)ptr) / 3) + 1;
+ i = model->numextra++;
+ model->extra = (m3dchunk_t **)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t *));
+ if (!model->extra) goto memerr;
+ model->extra[i] = (m3dchunk_t *)M3D_MALLOC(k + sizeof(m3dchunk_t));
+ if (!model->extra[i]) goto memerr;
+ memcpy(&model->extra[i]->magic, pe, 4);
+ model->extra[i]->length = sizeof(m3dchunk_t);
+ pe = (char *)model->extra[i] + sizeof(m3dchunk_t);
+ while (*ptr && *ptr != '\r' && *ptr != '\n') {
+ ptr = _m3d_gethex(ptr, &k);
+ *pe++ = (uint8_t)k;
+ model->extra[i]->length++;
+ }
+ } else
+ goto asciiend;
+ }
+ model->errcode = M3D_SUCCESS;
+ asciiend:
+ setlocale(LC_NUMERIC, ol);
+ goto postprocess;
+ }
+ /* Binary variant */
+ if (!M3D_CHUNKMAGIC(data + 8, 'H', 'E', 'A', 'D')) {
+ buff = (unsigned char *)stbi_zlib_decode_malloc_guesssize_headerflag((const char *)data + 8, ((m3dchunk_t *)data)->length - 8,
+ 4096, (int *)&len, 1);
+ if (!buff || !len || !M3D_CHUNKMAGIC(buff, 'H', 'E', 'A', 'D')) {
+ if (buff) M3D_FREE(buff);
+ M3D_FREE(model);
+ return NULL;
+ }
+ buff = (unsigned char *)M3D_REALLOC(buff, len);
+ model->flags |= M3D_FLG_FREERAW; /* mark that we have to free the raw buffer */
+ data = buff;
+#ifdef M3D_PROFILING
+ gettimeofday(&tv1, NULL);
+ tvd.tv_sec = tv1.tv_sec - tv0.tv_sec;
+ tvd.tv_usec = tv1.tv_usec - tv0.tv_usec;
+ if (tvd.tv_usec < 0) {
+ tvd.tv_sec--;
+ tvd.tv_usec += 1000000L;
+ }
+ printf(" Deflate model %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec);
+ memcpy(&tv0, &tv1, sizeof(struct timeval));
+#endif
+ } else {
+ len = ((m3dhdr_t *)data)->length;
+ data += 8;
+ }
+ model->raw = (m3dhdr_t *)data;
+ end = data + len;
+
+ /* parse header */
+ data += sizeof(m3dhdr_t);
+ M3D_LOG(data);
+ model->name = (char *)data;
+ for (; data < end && *data; data++) {
+ };
+ data++;
+ model->license = (char *)data;
+ for (; data < end && *data; data++) {
+ };
+ data++;
+ model->author = (char *)data;
+ for (; data < end && *data; data++) {
+ };
+ data++;
+ model->desc = (char *)data;
+ chunk = (unsigned char *)model->raw + model->raw->length;
+ model->scale = (M3D_FLOAT)model->raw->scale;
+ if (model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0;
+ model->vc_s = 1 << ((model->raw->types >> 0) & 3); /* vertex coordinate size */
+ model->vi_s = 1 << ((model->raw->types >> 2) & 3); /* vertex index size */
+ model->si_s = 1 << ((model->raw->types >> 4) & 3); /* string offset size */
+ model->ci_s = 1 << ((model->raw->types >> 6) & 3); /* color index size */
+ model->ti_s = 1 << ((model->raw->types >> 8) & 3); /* tmap index size */
+ model->bi_s = 1 << ((model->raw->types >> 10) & 3); /* bone index size */
+ model->nb_s = 1 << ((model->raw->types >> 12) & 3); /* number of bones per vertex */
+ model->sk_s = 1 << ((model->raw->types >> 14) & 3); /* skin index size */
+ model->fc_s = 1 << ((model->raw->types >> 16) & 3); /* frame counter size */
+ model->hi_s = 1 << ((model->raw->types >> 18) & 3); /* shape index size */
+ model->fi_s = 1 << ((model->raw->types >> 20) & 3); /* face index size */
+ if (model->ci_s == 8) model->ci_s = 0; /* optional indices */
+ if (model->ti_s == 8) model->ti_s = 0;
+ if (model->bi_s == 8) model->bi_s = 0;
+ if (model->sk_s == 8) model->sk_s = 0;
+ if (model->fc_s == 8) model->fc_s = 0;
+ if (model->hi_s == 8) model->hi_s = 0;
+ if (model->fi_s == 8) model->fi_s = 0;
+
+ /* variable limit checks */
+ if (sizeof(M3D_FLOAT) == 4 && model->vc_s > 4) {
+ M3D_LOG("Double precision coordinates not supported, truncating to float...");
+ model->errcode = M3D_ERR_TRUNC;
+ }
+ if (sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 ||
+ model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) {
+ M3D_LOG("32 bit indices not supported, unable to load model");
+ M3D_FREE(model);
+ return NULL;
+ }
+ if (model->vi_s > 4 || model->si_s > 4) {
+ M3D_LOG("Invalid index size, unable to load model");
+ M3D_FREE(model);
+ return NULL;
+ }
+ if (model->nb_s > M3D_NUMBONE) {
+ M3D_LOG("Model has more bones per vertex than what importer was configured to support");
+ model->errcode = M3D_ERR_TRUNC;
+ }
+
+ /* look for inlined assets in advance, material and procedural chunks may need them */
+ buff = chunk;
+ while (buff < end && !M3D_CHUNKMAGIC(buff, 'O', 'M', 'D', '3')) {
+ data = buff;
+ len = ((m3dchunk_t *)data)->length;
+ buff += len;
+ if (len < sizeof(m3dchunk_t) || buff >= end) {
+ M3D_LOG("Invalid chunk size");
+ break;
+ }
+ len -= sizeof(m3dchunk_t) + model->si_s;
+
+ /* inlined assets */
+ if (M3D_CHUNKMAGIC(data, 'A', 'S', 'E', 'T') && len > 0) {
+ M3D_LOG("Inlined asset");
+ i = model->numinlined++;
+ model->inlined = (m3di_t *)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t));
+ if (!model->inlined) {
+ memerr:
+ M3D_LOG("Out of memory");
+ model->errcode = M3D_ERR_ALLOC;
+ return model;
+ }
+ data += sizeof(m3dchunk_t);
+ t = &model->inlined[i];
+ M3D_GETSTR(t->name);
+ M3D_LOG(t->name);
+ t->data = (uint8_t *)data;
+ t->length = len;
+ }
+ }
+
+ /* parse chunks */
+ while (chunk < end && !M3D_CHUNKMAGIC(chunk, 'O', 'M', 'D', '3')) {
+ data = chunk;
+ len = ((m3dchunk_t *)chunk)->length;
+ chunk += len;
+ if (len < sizeof(m3dchunk_t) || chunk >= end) {
+ M3D_LOG("Invalid chunk size");
+ break;
+ }
+ len -= sizeof(m3dchunk_t);
+
+ /* preview chunk */
+ if (M3D_CHUNKMAGIC(data, 'P', 'R', 'V', 'W') && len > 0) {
+ model->preview.length = len;
+ model->preview.data = data + sizeof(m3dchunk_t);
+ } else
+ /* color map */
+ if (M3D_CHUNKMAGIC(data, 'C', 'M', 'A', 'P')) {
+ M3D_LOG("Color map");
+ if (model->cmap) {
+ M3D_LOG("More color map chunks, should be unique");
+ model->errcode = M3D_ERR_CMAP;
+ continue;
+ }
+ if (!model->ci_s) {
+ M3D_LOG("Color map chunk, shouldn't be any");
+ model->errcode = M3D_ERR_CMAP;
+ continue;
+ }
+ model->numcmap = len / sizeof(uint32_t);
+ model->cmap = (uint32_t *)(data + sizeof(m3dchunk_t));
+ } else
+ /* texture map */
+ if (M3D_CHUNKMAGIC(data, 'T', 'M', 'A', 'P')) {
+ M3D_LOG("Texture map");
+ if (model->tmap) {
+ M3D_LOG("More texture map chunks, should be unique");
+ model->errcode = M3D_ERR_TMAP;
+ continue;
+ }
+ if (!model->ti_s) {
+ M3D_LOG("Texture map chunk, shouldn't be any");
+ model->errcode = M3D_ERR_TMAP;
+ continue;
+ }
+ reclen = model->vc_s + model->vc_s;
+ model->numtmap = len / reclen;
+ model->tmap = (m3dti_t *)M3D_MALLOC(model->numtmap * sizeof(m3dti_t));
+ if (!model->tmap) goto memerr;
+ for (i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) {
+ switch (model->vc_s) {
+ case 1:
+ model->tmap[i].u = (M3D_FLOAT)(data[0]) / (M3D_FLOAT)255.0;
+ model->tmap[i].v = (M3D_FLOAT)(data[1]) / (M3D_FLOAT)255.0;
+ break;
+ case 2:
+ model->tmap[i].u = (M3D_FLOAT)(*((int16_t *)(data + 0))) / (M3D_FLOAT)65535.0;
+ model->tmap[i].v = (M3D_FLOAT)(*((int16_t *)(data + 2))) / (M3D_FLOAT)65535.0;
+ break;
+ case 4:
+ model->tmap[i].u = (M3D_FLOAT)(*((float *)(data + 0)));
+ model->tmap[i].v = (M3D_FLOAT)(*((float *)(data + 4)));
+ break;
+ case 8:
+ model->tmap[i].u = (M3D_FLOAT)(*((double *)(data + 0)));
+ model->tmap[i].v = (M3D_FLOAT)(*((double *)(data + 8)));
+ break;
+ }
+ data += reclen;
+ }
+ } else
+ /* vertex list */
+ if (M3D_CHUNKMAGIC(data, 'V', 'R', 'T', 'S')) {
+ M3D_LOG("Vertex list");
+ if (model->vertex) {
+ M3D_LOG("More vertex chunks, should be unique");
+ model->errcode = M3D_ERR_VRTS;
+ continue;
+ }
+ if (model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP;
+ reclen = model->ci_s + model->sk_s + 4 * model->vc_s;
+ model->numvertex = len / reclen;
+ model->vertex = (m3dv_t *)M3D_MALLOC(model->numvertex * sizeof(m3dv_t));
+ if (!model->vertex) goto memerr;
+ memset(model->vertex, 0, model->numvertex * sizeof(m3dv_t));
+ for (i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) {
+ switch (model->vc_s) {
+ case 1:
+ model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / (M3D_FLOAT)127.0;
+ model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / (M3D_FLOAT)127.0;
+ model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / (M3D_FLOAT)127.0;
+ model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / (M3D_FLOAT)127.0;
+ data += 4;
+ break;
+ case 2:
+ model->vertex[i].x = (M3D_FLOAT)(*((int16_t *)(data + 0))) / (M3D_FLOAT)32767.0;
+ model->vertex[i].y = (M3D_FLOAT)(*((int16_t *)(data + 2))) / (M3D_FLOAT)32767.0;
+ model->vertex[i].z = (M3D_FLOAT)(*((int16_t *)(data + 4))) / (M3D_FLOAT)32767.0;
+ model->vertex[i].w = (M3D_FLOAT)(*((int16_t *)(data + 6))) / (M3D_FLOAT)32767.0;
+ data += 8;
+ break;
+ case 4:
+ model->vertex[i].x = (M3D_FLOAT)(*((float *)(data + 0)));
+ model->vertex[i].y = (M3D_FLOAT)(*((float *)(data + 4)));
+ model->vertex[i].z = (M3D_FLOAT)(*((float *)(data + 8)));
+ model->vertex[i].w = (M3D_FLOAT)(*((float *)(data + 12)));
+ data += 16;
+ break;
+ case 8:
+ model->vertex[i].x = (M3D_FLOAT)(*((double *)(data + 0)));
+ model->vertex[i].y = (M3D_FLOAT)(*((double *)(data + 8)));
+ model->vertex[i].z = (M3D_FLOAT)(*((double *)(data + 16)));
+ model->vertex[i].w = (M3D_FLOAT)(*((double *)(data + 24)));
+ data += 32;
+ break;
+ }
+ switch (model->ci_s) {
+ case 1:
+ model->vertex[i].color = model->cmap ? model->cmap[data[0]] : 0;
+ data++;
+ break;
+ case 2:
+ model->vertex[i].color = model->cmap ? model->cmap[*((uint16_t *)data)] : 0;
+ data += 2;
+ break;
+ case 4:
+ model->vertex[i].color = *((uint32_t *)data);
+ data += 4;
+ break;
+ /* case 8: break; */
+ }
+ model->vertex[i].skinid = M3D_UNDEF;
+ data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid);
+ }
+ } else
+ /* skeleton: bone hierarchy and skin */
+ if (M3D_CHUNKMAGIC(data, 'B', 'O', 'N', 'E')) {
+ M3D_LOG("Skeleton");
+ if (model->bone) {
+ M3D_LOG("More bone chunks, should be unique");
+ model->errcode = M3D_ERR_BONE;
+ continue;
+ }
+ if (!model->bi_s) {
+ M3D_LOG("Bone chunk, shouldn't be any");
+ model->errcode = M3D_ERR_BONE;
+ continue;
+ }
+ if (!model->vertex) {
+ M3D_LOG("No vertex chunk before bones");
+ model->errcode = M3D_ERR_VRTS;
+ break;
+ }
+ data += sizeof(m3dchunk_t);
+ model->numbone = 0;
+ data = _m3d_getidx(data, model->bi_s, &model->numbone);
+ if (model->numbone) {
+ model->bone = (m3db_t *)M3D_MALLOC(model->numbone * sizeof(m3db_t));
+ if (!model->bone) goto memerr;
+ }
+ model->numskin = 0;
+ data = _m3d_getidx(data, model->sk_s, &model->numskin);
+ /* read bone hierarchy */
+ for (i = 0; i < model->numbone; i++) {
+ data = _m3d_getidx(data, model->bi_s, &model->bone[i].parent);
+ M3D_GETSTR(model->bone[i].name);
+ data = _m3d_getidx(data, model->vi_s, &model->bone[i].pos);
+ data = _m3d_getidx(data, model->vi_s, &model->bone[i].ori);
+ model->bone[i].numweight = 0;
+ model->bone[i].weight = NULL;
+ }
+ /* read skin definitions */
+ if (model->numskin) {
+ model->skin = (m3ds_t *)M3D_MALLOC(model->numskin * sizeof(m3ds_t));
+ if (!model->skin) goto memerr;
+ for (i = 0; data < chunk && i < model->numskin; i++) {
+ for (j = 0; j < M3D_NUMBONE; j++) {
+ model->skin[i].boneid[j] = M3D_UNDEF;
+ model->skin[i].weight[j] = (M3D_FLOAT)0.0;
+ }
+ memset(&weights, 0, sizeof(weights));
+ if (model->nb_s == 1)
+ weights[0] = 255;
+ else {
+ memcpy(&weights, data, model->nb_s);
+ data += model->nb_s;
+ }
+ for (j = 0, w = (M3D_FLOAT)0.0; j < (unsigned int)model->nb_s; j++) {
+ if (weights[j]) {
+ if (j >= M3D_NUMBONE)
+ data += model->bi_s;
+ else {
+ model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / (M3D_FLOAT)255.0;
+ w += model->skin[i].weight[j];
+ data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]);
+ }
+ }
+ }
+ /* this can occur if model has more bones than what the importer is configured to handle */
+ if (w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) {
+ for (j = 0; j < M3D_NUMBONE; j++)
+ model->skin[i].weight[j] /= w;
+ }
+ }
+ }
+ } else
+ /* material */
+ if (M3D_CHUNKMAGIC(data, 'M', 'T', 'R', 'L')) {
+ data += sizeof(m3dchunk_t);
+ M3D_GETSTR(name);
+ M3D_LOG("Material");
+ M3D_LOG(name);
+ if (model->ci_s < 4 && !model->numcmap) model->errcode = M3D_ERR_CMAP;
+ for (i = 0; i < model->nummaterial; i++)
+ if (!strcmp(name, model->material[i].name)) {
+ model->errcode = M3D_ERR_MTRL;
+ M3D_LOG("Multiple definitions for material");
+ M3D_LOG(name);
+ name = NULL;
+ break;
+ }
+ if (name) {
+ i = model->nummaterial++;
+ if (model->flags & M3D_FLG_MTLLIB) {
+ m = model->material;
+ model->material = (m3dm_t *)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t));
+ if (!model->material) goto memerr;
+ memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t));
+ if (model->texture) {
+ tx = model->texture;
+ model->texture = (m3dtx_t *)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t));
+ if (!model->texture) goto memerr;
+ memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t));
+ }
+ model->flags &= ~M3D_FLG_MTLLIB;
+ } else {
+ model->material = (m3dm_t *)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t));
+ if (!model->material) goto memerr;
+ }
+ m = &model->material[i];
+ m->numprop = 0;
+ m->name = name;
+ m->prop = (m3dp_t *)M3D_MALLOC((len / 2) * sizeof(m3dp_t));
+ if (!m->prop) goto memerr;
+ while (data < chunk) {
+ i = m->numprop++;
+ m->prop[i].type = *data++;
+ m->prop[i].value.num = 0;
+ if (m->prop[i].type >= 128)
+ k = m3dpf_map;
+ else {
+ for (k = 256, j = 0; j < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); j++)
+ if (m->prop[i].type == m3d_propertytypes[j].id) {
+ k = m3d_propertytypes[j].format;
+ break;
+ }
+ }
+ switch (k) {
+ case m3dpf_color:
+ switch (model->ci_s) {
+ case 1:
+ m->prop[i].value.color = model->cmap ? model->cmap[data[0]] : 0;
+ data++;
+ break;
+ case 2:
+ m->prop[i].value.color = model->cmap ? model->cmap[*((uint16_t *)data)] : 0;
+ data += 2;
+ break;
+ case 4:
+ m->prop[i].value.color = *((uint32_t *)data);
+ data += 4;
+ break;
+ }
+ break;
+
+ case m3dpf_uint8: m->prop[i].value.num = *data++; break;
+ case m3dpf_uint16:
+ m->prop[i].value.num = *((uint16_t *)data);
+ data += 2;
+ break;
+ case m3dpf_uint32:
+ m->prop[i].value.num = *((uint32_t *)data);
+ data += 4;
+ break;
+ case m3dpf_float:
+ m->prop[i].value.fnum = *((float *)data);
+ data += 4;
+ break;
+
+ case m3dpf_map:
+ M3D_GETSTR(name);
+ m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name);
+ if (model->errcode == M3D_ERR_ALLOC) goto memerr;
+ /* this error code only returned if readfilecb was specified */
+ if (m->prop[i].value.textureid == M3D_UNDEF) {
+ M3D_LOG("Texture not found");
+ M3D_LOG(m->name);
+ m->numprop--;
+ }
+ break;
+
+ default:
+ M3D_LOG("Unknown material property in");
+ M3D_LOG(m->name);
+ model->errcode = M3D_ERR_UNKPROP;
+ data = chunk;
+ break;
+ }
+ }
+ m->prop = (m3dp_t *)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t));
+ if (!m->prop) goto memerr;
+ }
+ } else
+ /* face */
+ if (M3D_CHUNKMAGIC(data, 'P', 'R', 'O', 'C')) {
+ /* procedural surface */
+ M3D_GETSTR(name);
+ M3D_LOG("Procedural surface");
+ M3D_LOG(name);
+ _m3d_getpr(model, readfilecb, freecb, name);
+ } else if (M3D_CHUNKMAGIC(data, 'M', 'E', 'S', 'H')) {
+ M3D_LOG("Mesh data");
+ /* mesh */
+ data += sizeof(m3dchunk_t);
+ mi = M3D_UNDEF;
+ am = model->numface;
+ while (data < chunk) {
+ k = *data++;
+ n = k >> 4;
+ k &= 15;
+ if (!n) {
+ /* use material */
+ mi = M3D_UNDEF;
+ M3D_GETSTR(name);
+ if (name) {
+ for (j = 0; j < model->nummaterial; j++)
+ if (!strcmp(name, model->material[j].name)) {
+ mi = (M3D_INDEX)j;
+ break;
+ }
+ if (mi == M3D_UNDEF) model->errcode = M3D_ERR_MTRL;
+ }
+ continue;
+ }
+ if (n != 3) {
+ M3D_LOG("Only triangle mesh supported for now");
+ model->errcode = M3D_ERR_UNKMESH;
+ return model;
+ }
+ i = model->numface++;
+ if (model->numface > am) {
+ am = model->numface + 4095;
+ model->face = (m3df_t *)M3D_REALLOC(model->face, am * sizeof(m3df_t));
+ if (!model->face) goto memerr;
+ }
+ memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */
+ model->face[i].materialid = mi;
+ for (j = 0; j < n; j++) {
+ /* vertex */
+ data = _m3d_getidx(data, model->vi_s, &model->face[i].vertex[j]);
+ /* texcoord */
+ if (k & 1)
+ data = _m3d_getidx(data, model->ti_s, &model->face[i].texcoord[j]);
+ /* normal */
+ if (k & 2)
+ data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]);
+#ifndef M3D_NONORMALS
+ if (model->face[i].normal[j] == M3D_UNDEF) neednorm = 1;
+#endif
+ }
+ }
+ model->face = (m3df_t *)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t));
+ } else if (M3D_CHUNKMAGIC(data, 'S', 'H', 'P', 'E')) {
+ /* mathematical shape */
+ data += sizeof(m3dchunk_t);
+ M3D_GETSTR(name);
+ M3D_LOG("Mathematical Shape");
+ M3D_LOG(name);
+ i = model->numshape++;
+ model->shape = (m3dh_t *)M3D_REALLOC(model->shape, model->numshape * sizeof(m3dh_t));
+ if (!model->shape) goto memerr;
+ h = &model->shape[i];
+ h->numcmd = 0;
+ h->cmd = NULL;
+ h->name = name;
+ h->group = M3D_UNDEF;
+ data = _m3d_getidx(data, model->bi_s, &h->group);
+ if (h->group != M3D_UNDEF && h->group >= model->numbone) {
+ M3D_LOG("Unknown bone id as shape group in shape");
+ M3D_LOG(name);
+ h->group = M3D_UNDEF;
+ model->errcode = M3D_ERR_SHPE;
+ }
+ while (data < chunk) {
+ i = h->numcmd++;
+ h->cmd = (m3dc_t *)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t));
+ if (!h->cmd) goto memerr;
+ h->cmd[i].type = *data++;
+ if (h->cmd[i].type & 0x80) {
+ h->cmd[i].type &= 0x7F;
+ h->cmd[i].type |= (*data++ << 7);
+ }
+ if (h->cmd[i].type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0]))) {
+ M3D_LOG("Unknown shape command in");
+ M3D_LOG(h->name);
+ model->errcode = M3D_ERR_UNKCMD;
+ break;
+ }
+ cd = &m3d_commandtypes[h->cmd[i].type];
+ h->cmd[i].arg = (uint32_t *)M3D_MALLOC(cd->p * sizeof(uint32_t));
+ if (!h->cmd[i].arg) goto memerr;
+ memset(h->cmd[i].arg, 0, cd->p * sizeof(uint32_t));
+ for (k = n = 0, l = cd->p; k < l; k++)
+ switch (cd->a[((k - n) % (cd->p - n)) + n]) {
+ case m3dcp_mi_t:
+ h->cmd[i].arg[k] = M3D_NOTDEFINED;
+ M3D_GETSTR(name);
+ if (name) {
+ for (n = 0; n < model->nummaterial; n++)
+ if (!strcmp(name, model->material[n].name)) {
+ h->cmd[i].arg[k] = n;
+ break;
+ }
+ if (h->cmd[i].arg[k] == M3D_NOTDEFINED) model->errcode = M3D_ERR_MTRL;
+ }
+ break;
+ case m3dcp_vc_t:
+ f = 0.0f;
+ switch (model->vc_s) {
+ case 1: f = (float)((int8_t)data[0]) / 127; break;
+ case 2: f = (float)(*((int16_t *)(data + 0))) / 32767; break;
+ case 4: f = (float)(*((float *)(data + 0))); break;
+ case 8: f = (float)(*((double *)(data + 0))); break;
+ }
+ memcpy(&(h->cmd[i].arg[k]), &f, sizeof(uint32_t));
+ data += model->vc_s;
+ break;
+ case m3dcp_hi_t: data = _m3d_getidx(data, model->hi_s, &h->cmd[i].arg[k]); break;
+ case m3dcp_fi_t: data = _m3d_getidx(data, model->fi_s, &h->cmd[i].arg[k]); break;
+ case m3dcp_ti_t: data = _m3d_getidx(data, model->ti_s, &h->cmd[i].arg[k]); break;
+ case m3dcp_qi_t:
+ case m3dcp_vi_t: data = _m3d_getidx(data, model->vi_s, &h->cmd[i].arg[k]); break;
+ case m3dcp_i1_t: data = _m3d_getidx(data, 1, &h->cmd[i].arg[k]); break;
+ case m3dcp_i2_t: data = _m3d_getidx(data, 2, &h->cmd[i].arg[k]); break;
+ case m3dcp_i4_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); break;
+ case m3dcp_va_t:
+ data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]);
+ n = k + 1;
+ l += (h->cmd[i].arg[k] - 1) * (cd->p - k - 1);
+ h->cmd[i].arg = (uint32_t *)M3D_REALLOC(h->cmd[i].arg, l * sizeof(uint32_t));
+ if (!h->cmd[i].arg) goto memerr;
+ memset(&h->cmd[i].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t));
+ break;
+ }
+ }
+ } else
+ /* annotation label list */
+ if (M3D_CHUNKMAGIC(data, 'L', 'B', 'L', 'S')) {
+ data += sizeof(m3dchunk_t);
+ M3D_GETSTR(name);
+ M3D_GETSTR(lang);
+ M3D_LOG("Label list");
+ if (name) {
+ M3D_LOG(name);
+ }
+ if (lang) {
+ M3D_LOG(lang);
+ }
+ if (model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP;
+ k = 0;
+ switch (model->ci_s) {
+ case 1:
+ k = model->cmap ? model->cmap[data[0]] : 0;
+ data++;
+ break;
+ case 2:
+ k = model->cmap ? model->cmap[*((uint16_t *)data)] : 0;
+ data += 2;
+ break;
+ case 4:
+ k = *((uint32_t *)data);
+ data += 4;
+ break;
+ /* case 8: break; */
+ }
+ reclen = model->vi_s + model->si_s;
+ i = model->numlabel;
+ model->numlabel += len / reclen;
+ model->label = (m3dl_t *)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t));
+ if (!model->label) goto memerr;
+ memset(&model->label[i], 0, (model->numlabel - i) * sizeof(m3dl_t));
+ for (; data < chunk && i < model->numlabel; i++) {
+ model->label[i].name = name;
+ model->label[i].lang = lang;
+ model->label[i].color = k;
+ data = _m3d_getidx(data, model->vi_s, &model->label[i].vertexid);
+ M3D_GETSTR(model->label[i].text);
+ }
+ } else
+ /* action */
+ if (M3D_CHUNKMAGIC(data, 'A', 'C', 'T', 'N')) {
+ M3D_LOG("Action");
+ i = model->numaction++;
+ model->action = (m3da_t *)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t));
+ if (!model->action) goto memerr;
+ a = &model->action[i];
+ data += sizeof(m3dchunk_t);
+ M3D_GETSTR(a->name);
+ M3D_LOG(a->name);
+ a->numframe = *((uint16_t *)data);
+ data += 2;
+ if (a->numframe < 1) {
+ model->numaction--;
+ } else {
+ a->durationmsec = *((uint32_t *)data);
+ data += 4;
+ a->frame = (m3dfr_t *)M3D_MALLOC(a->numframe * sizeof(m3dfr_t));
+ if (!a->frame) goto memerr;
+ for (i = 0; data < chunk && i < a->numframe; i++) {
+ a->frame[i].msec = *((uint32_t *)data);
+ data += 4;
+ a->frame[i].numtransform = 0;
+ a->frame[i].transform = NULL;
+ data = _m3d_getidx(data, model->fc_s, &a->frame[i].numtransform);
+ if (a->frame[i].numtransform > 0) {
+ a->frame[i].transform = (m3dtr_t *)M3D_MALLOC(a->frame[i].numtransform * sizeof(m3dtr_t));
+ for (j = 0; j < a->frame[i].numtransform; j++) {
+ data = _m3d_getidx(data, model->bi_s, &a->frame[i].transform[j].boneid);
+ data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].pos);
+ data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].ori);
+ }
+ }
+ }
+ }
+ } else {
+ i = model->numextra++;
+ model->extra = (m3dchunk_t **)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t *));
+ if (!model->extra) goto memerr;
+ model->extra[i] = (m3dchunk_t *)data;
+ }
+ }
+ /* calculate normals, normalize skin weights, create bone/vertex cross-references and calculate transform matrices */
+postprocess:
+ if (model) {
+ M3D_LOG("Post-process");
+#ifdef M3D_PROFILING
+ gettimeofday(&tv1, NULL);
+ tvd.tv_sec = tv1.tv_sec - tv0.tv_sec;
+ tvd.tv_usec = tv1.tv_usec - tv0.tv_usec;
+ if (tvd.tv_usec < 0) {
+ tvd.tv_sec--;
+ tvd.tv_usec += 1000000L;
+ }
+ printf(" Parsing chunks %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec);
+#endif
+#ifndef M3D_NONORMALS
+ if (model->numface && model->face && neednorm) {
+ /* if they are missing, calculate triangle normals into a temporary buffer */
+ norm = (m3dv_t *)M3D_MALLOC(model->numface * sizeof(m3dv_t));
+ if (!norm) goto memerr;
+ for (i = 0, n = model->numvertex; i < model->numface; i++)
+ if (model->face[i].normal[0] == M3D_UNDEF) {
+ v0 = &model->vertex[model->face[i].vertex[0]];
+ v1 = &model->vertex[model->face[i].vertex[1]];
+ v2 = &model->vertex[model->face[i].vertex[2]];
+ va.x = v1->x - v0->x;
+ va.y = v1->y - v0->y;
+ va.z = v1->z - v0->z;
+ vb.x = v2->x - v0->x;
+ vb.y = v2->y - v0->y;
+ vb.z = v2->z - v0->z;
+ v0 = &norm[i];
+ v0->x = (va.y * vb.z) - (va.z * vb.y);
+ v0->y = (va.z * vb.x) - (va.x * vb.z);
+ v0->z = (va.x * vb.y) - (va.y * vb.x);
+ w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z));
+ v0->x *= w;
+ v0->y *= w;
+ v0->z *= w;
+ model->face[i].normal[0] = model->face[i].vertex[0] + n;
+ model->face[i].normal[1] = model->face[i].vertex[1] + n;
+ model->face[i].normal[2] = model->face[i].vertex[2] + n;
+ }
+ /* this is the fast way, we don't care if a normal is repeated in model->vertex */
+ M3D_LOG("Generating normals");
+ model->flags |= M3D_FLG_GENNORM;
+ model->numvertex <<= 1;
+ model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t));
+ if (!model->vertex) goto memerr;
+ memset(&model->vertex[n], 0, n * sizeof(m3dv_t));
+ for (i = 0; i < model->numface; i++)
+ for (j = 0; j < 3; j++) {
+ v0 = &model->vertex[model->face[i].vertex[j] + n];
+ v0->x += norm[i].x;
+ v0->y += norm[i].y;
+ v0->z += norm[i].z;
+ }
+ /* for each vertex, take the average of the temporary normals and use that */
+ for (i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) {
+ w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z));
+ v0->x *= w;
+ v0->y *= w;
+ v0->z *= w;
+ v0->skinid = M3D_UNDEF;
+ }
+ M3D_FREE(norm);
+ }
+#endif
+ if (model->numbone && model->bone && model->numskin && model->skin && model->numvertex && model->vertex) {
+#ifndef M3D_NOWEIGHTS
+ M3D_LOG("Generating weight cross-reference");
+ for (i = 0; i < model->numvertex; i++) {
+ if (model->vertex[i].skinid < model->numskin) {
+ sk = &model->skin[model->vertex[i].skinid];
+ w = (M3D_FLOAT)0.0;
+ for (j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++)
+ w += sk->weight[j];
+ for (j = 0; j < M3D_NUMBONE && sk->boneid[j] != M3D_UNDEF && sk->weight[j] > (M3D_FLOAT)0.0; j++) {
+ sk->weight[j] /= w;
+ b = &model->bone[sk->boneid[j]];
+ k = b->numweight++;
+ b->weight = (m3dw_t *)M3D_REALLOC(b->weight, b->numweight * sizeof(m3da_t));
+ if (!b->weight) goto memerr;
+ b->weight[k].vertexid = i;
+ b->weight[k].weight = sk->weight[j];
+ }
+ }
+ }
+#endif
+#ifndef M3D_NOANIMATION
+ M3D_LOG("Calculating bone transformation matrices");
+ for (i = 0; i < model->numbone; i++) {
+ b = &model->bone[i];
+ if (model->bone[i].parent == M3D_UNDEF) {
+ _m3d_mat((M3D_FLOAT *)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]);
+ } else {
+ _m3d_mat((M3D_FLOAT *)&r, &model->vertex[b->pos], &model->vertex[b->ori]);
+ _m3d_mul((M3D_FLOAT *)&b->mat4, (M3D_FLOAT *)&model->bone[b->parent].mat4, (M3D_FLOAT *)&r);
+ }
+ }
+ for (i = 0; i < model->numbone; i++)
+ _m3d_inv((M3D_FLOAT *)&model->bone[i].mat4);
+#endif
+ }
+#ifdef M3D_PROFILING
+ gettimeofday(&tv0, NULL);
+ tvd.tv_sec = tv0.tv_sec - tv1.tv_sec;
+ tvd.tv_usec = tv0.tv_usec - tv1.tv_usec;
+ if (tvd.tv_usec < 0) {
+ tvd.tv_sec--;
+ tvd.tv_usec += 1000000L;
+ }
+ printf(" Post-process %ld.%06ld sec\n", tvd.tv_sec, tvd.tv_usec);
+#endif
+ }
+ return model;
+}
+
+/**
+ * Calculates skeletons for animation frames, returns a working copy (should be freed after use)
+ */
+m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton) {
+ unsigned int i;
+ M3D_INDEX s = frameid;
+ m3dfr_t *fr;
+
+ if (!model || !model->numbone || !model->bone || (actionid != M3D_UNDEF && (!model->action || actionid >= model->numaction || frameid >= model->action[actionid].numframe))) {
+ model->errcode = M3D_ERR_UNKFRAME;
+ return skeleton;
+ }
+ model->errcode = M3D_SUCCESS;
+ if (!skeleton) {
+ skeleton = (m3dtr_t *)M3D_MALLOC(model->numbone * sizeof(m3dtr_t));
+ if (!skeleton) {
+ model->errcode = M3D_ERR_ALLOC;
+ return NULL;
+ }
+ goto gen;
+ }
+ if (actionid == M3D_UNDEF || !frameid) {
+ gen:
+ s = 0;
+ for (i = 0; i < model->numbone; i++) {
+ skeleton[i].boneid = i;
+ skeleton[i].pos = model->bone[i].pos;
+ skeleton[i].ori = model->bone[i].ori;
+ }
+ }
+ if (actionid < model->numaction && (frameid || !model->action[actionid].frame[0].msec)) {
+ for (; s <= frameid; s++) {
+ fr = &model->action[actionid].frame[s];
+ for (i = 0; i < fr->numtransform; i++) {
+ skeleton[fr->transform[i].boneid].pos = fr->transform[i].pos;
+ skeleton[fr->transform[i].boneid].ori = fr->transform[i].ori;
+ }
+ }
+ }
+ return skeleton;
+}
+
+#ifndef M3D_NOANIMATION
+/**
+ * Returns interpolated animation-pose, a working copy (should be freed after use)
+ */
+m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec) {
+ unsigned int i, j, l;
+ M3D_FLOAT r[16], t, c, d, s;
+ m3db_t *ret;
+ m3dv_t *v, *p, *f;
+ m3dtr_t *tmp;
+ m3dfr_t *fr;
+
+ if (!model || !model->numbone || !model->bone) {
+ model->errcode = M3D_ERR_UNKFRAME;
+ return NULL;
+ }
+ ret = (m3db_t *)M3D_MALLOC(model->numbone * sizeof(m3db_t));
+ if (!ret) {
+ model->errcode = M3D_ERR_ALLOC;
+ return NULL;
+ }
+ memcpy(ret, model->bone, model->numbone * sizeof(m3db_t));
+ for (i = 0; i < model->numbone; i++)
+ _m3d_inv((M3D_FLOAT *)&ret[i].mat4);
+ if (!model->action || actionid >= model->numaction) {
+ model->errcode = M3D_ERR_UNKFRAME;
+ return ret;
+ }
+ msec %= model->action[actionid].durationmsec;
+ model->errcode = M3D_SUCCESS;
+ fr = &model->action[actionid].frame[0];
+ for (j = l = 0; j < model->action[actionid].numframe && model->action[actionid].frame[j].msec <= msec; j++) {
+ fr = &model->action[actionid].frame[j];
+ l = fr->msec;
+ for (i = 0; i < fr->numtransform; i++) {
+ ret[fr->transform[i].boneid].pos = fr->transform[i].pos;
+ ret[fr->transform[i].boneid].ori = fr->transform[i].ori;
+ }
+ }
+ if (l != msec) {
+ model->vertex = (m3dv_t *)M3D_REALLOC(model->vertex, (model->numvertex + 2 * model->numbone) * sizeof(m3dv_t));
+ if (!model->vertex) {
+ free(ret);
+ model->errcode = M3D_ERR_ALLOC;
+ return NULL;
+ }
+ tmp = (m3dtr_t *)M3D_MALLOC(model->numbone * sizeof(m3dtr_t));
+ if (tmp) {
+ for (i = 0; i < model->numbone; i++) {
+ tmp[i].pos = ret[i].pos;
+ tmp[i].ori = ret[i].ori;
+ }
+ fr = &model->action[actionid].frame[j % model->action[actionid].numframe];
+ t = l >= fr->msec ? (M3D_FLOAT)1.0 : (M3D_FLOAT)(msec - l) / (M3D_FLOAT)(fr->msec - l);
+ for (i = 0; i < fr->numtransform; i++) {
+ tmp[fr->transform[i].boneid].pos = fr->transform[i].pos;
+ tmp[fr->transform[i].boneid].ori = fr->transform[i].ori;
+ }
+ for (i = 0, j = model->numvertex; i < model->numbone; i++) {
+ /* interpolation of position */
+ if (ret[i].pos != tmp[i].pos) {
+ p = &model->vertex[ret[i].pos];
+ f = &model->vertex[tmp[i].pos];
+ v = &model->vertex[j];
+ v->x = p->x + t * (f->x - p->x);
+ v->y = p->y + t * (f->y - p->y);
+ v->z = p->z + t * (f->z - p->z);
+ ret[i].pos = j++;
+ }
+ /* interpolation of orientation */
+ if (ret[i].ori != tmp[i].ori) {
+ p = &model->vertex[ret[i].ori];
+ f = &model->vertex[tmp[i].ori];
+ v = &model->vertex[j];
+ d = p->w * f->w + p->x * f->x + p->y * f->y + p->z * f->z;
+ if (d < 0) {
+ d = -d;
+ s = (M3D_FLOAT)-1.0;
+ } else
+ s = (M3D_FLOAT)1.0;
+#if 0
+ /* don't use SLERP, requires two more variables, libm linkage and it is slow (but nice) */
+ a = (M3D_FLOAT)1.0 - t; b = t;
+ if(d < (M3D_FLOAT)0.999999) { c = acosf(d); b = 1 / sinf(c); a = sinf(a * c) * b; b *= sinf(t * c) * s; }
+ v->x = p->x * a + f->x * b;
+ v->y = p->y * a + f->y * b;
+ v->z = p->z * a + f->z * b;
+ v->w = p->w * a + f->w * b;
+#else
+ /* approximated NLERP, original approximation by Arseny Kapoulkine, heavily optimized by me */
+ c = t - (M3D_FLOAT)0.5;
+ t += t * c * (t - (M3D_FLOAT)1.0) * (((M3D_FLOAT)1.0904 + d * ((M3D_FLOAT)-3.2452 + d * ((M3D_FLOAT)3.55645 - d * (M3D_FLOAT)1.43519))) * c * c + ((M3D_FLOAT)0.848013 + d * ((M3D_FLOAT)-1.06021 + d * (M3D_FLOAT)0.215638)));
+ v->x = p->x + t * (s * f->x - p->x);
+ v->y = p->y + t * (s * f->y - p->y);
+ v->z = p->z + t * (s * f->z - p->z);
+ v->w = p->w + t * (s * f->w - p->w);
+ d = _m3d_rsq(v->w * v->w + v->x * v->x + v->y * v->y + v->z * v->z);
+ v->x *= d;
+ v->y *= d;
+ v->z *= d;
+ v->w *= d;
+#endif
+ ret[i].ori = j++;
+ }
+ }
+ M3D_FREE(tmp);
+ }
+ }
+ for (i = 0; i < model->numbone; i++) {
+ if (ret[i].parent == M3D_UNDEF) {
+ _m3d_mat((M3D_FLOAT *)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]);
+ } else {
+ _m3d_mat((M3D_FLOAT *)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]);
+ _m3d_mul((M3D_FLOAT *)&ret[i].mat4, (M3D_FLOAT *)&ret[ret[i].parent].mat4, (M3D_FLOAT *)&r);
+ }
+ }
+ return ret;
+}
+
+#endif /* M3D_NOANIMATION */
+
+#endif /* M3D_IMPLEMENTATION */
+
+#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER))
+/**
+ * Free the in-memory model
+ */
+void m3d_free(m3d_t *model) {
+ unsigned int i, j;
+
+ if (!model) return;
+ /* if model imported from ASCII, we have to free all strings as well */
+ if (model->flags & M3D_FLG_FREESTR) {
+ if (model->name) M3D_FREE(model->name);
+ if (model->license) M3D_FREE(model->license);
+ if (model->author) M3D_FREE(model->author);
+ if (model->desc) M3D_FREE(model->desc);
+ if (model->bone)
+ for (i = 0; i < model->numbone; i++)
+ if (model->bone[i].name)
+ M3D_FREE(model->bone[i].name);
+ if (model->shape)
+ for (i = 0; i < model->numshape; i++)
+ if (model->shape[i].name)
+ M3D_FREE(model->shape[i].name);
+ if (model->material)
+ for (i = 0; i < model->nummaterial; i++)
+ if (model->material[i].name)
+ M3D_FREE(model->material[i].name);
+ if (model->action)
+ for (i = 0; i < model->numaction; i++)
+ if (model->action[i].name)
+ M3D_FREE(model->action[i].name);
+ if (model->texture)
+ for (i = 0; i < model->numtexture; i++)
+ if (model->texture[i].name)
+ M3D_FREE(model->texture[i].name);
+ if (model->inlined)
+ for (i = 0; i < model->numinlined; i++) {
+ if (model->inlined[i].name)
+ M3D_FREE(model->inlined[i].name);
+ if (model->inlined[i].data)
+ M3D_FREE(model->inlined[i].data);
+ }
+ if (model->extra)
+ for (i = 0; i < model->numextra; i++)
+ if (model->extra[i])
+ M3D_FREE(model->extra[i]);
+ if (model->label)
+ for (i = 0; i < model->numlabel; i++) {
+ if (model->label[i].name) {
+ for (j = i + 1; j < model->numlabel; j++)
+ if (model->label[j].name == model->label[i].name)
+ model->label[j].name = NULL;
+ M3D_FREE(model->label[i].name);
+ }
+ if (model->label[i].lang) {
+ for (j = i + 1; j < model->numlabel; j++)
+ if (model->label[j].lang == model->label[i].lang)
+ model->label[j].lang = NULL;
+ M3D_FREE(model->label[i].lang);
+ }
+ if (model->label[i].text)
+ M3D_FREE(model->label[i].text);
+ }
+ if (model->preview.data)
+ M3D_FREE(model->preview.data);
+ }
+ if (model->flags & M3D_FLG_FREERAW) M3D_FREE(model->raw);
+
+ if (model->tmap) M3D_FREE(model->tmap);
+ if (model->bone) {
+ for (i = 0; i < model->numbone; i++)
+ if (model->bone[i].weight)
+ M3D_FREE(model->bone[i].weight);
+ M3D_FREE(model->bone);
+ }
+ if (model->skin) M3D_FREE(model->skin);
+ if (model->vertex) M3D_FREE(model->vertex);
+ if (model->face) M3D_FREE(model->face);
+ if (model->shape) {
+ for (i = 0; i < model->numshape; i++) {
+ if (model->shape[i].cmd) {
+ for (j = 0; j < model->shape[i].numcmd; j++)
+ if (model->shape[i].cmd[j].arg) M3D_FREE(model->shape[i].cmd[j].arg);
+ M3D_FREE(model->shape[i].cmd);
+ }
+ }
+ M3D_FREE(model->shape);
+ }
+ if (model->material && !(model->flags & M3D_FLG_MTLLIB)) {
+ for (i = 0; i < model->nummaterial; i++)
+ if (model->material[i].prop) M3D_FREE(model->material[i].prop);
+ M3D_FREE(model->material);
+ }
+ if (model->texture) {
+ for (i = 0; i < model->numtexture; i++)
+ if (model->texture[i].d) M3D_FREE(model->texture[i].d);
+ M3D_FREE(model->texture);
+ }
+ if (model->action) {
+ for (i = 0; i < model->numaction; i++) {
+ if (model->action[i].frame) {
+ for (j = 0; j < model->action[i].numframe; j++)
+ if (model->action[i].frame[j].transform) M3D_FREE(model->action[i].frame[j].transform);
+ M3D_FREE(model->action[i].frame);
+ }
+ }
+ M3D_FREE(model->action);
+ }
+ if (model->label) M3D_FREE(model->label);
+ if (model->inlined) M3D_FREE(model->inlined);
+ if (model->extra) M3D_FREE(model->extra);
+ free(model);
+}
+#endif
+
+#ifdef M3D_EXPORTER
+typedef struct {
+ char *str;
+ uint32_t offs;
+} m3dstr_t;
+
+typedef struct {
+ m3dti_t data;
+ M3D_INDEX oldidx;
+ M3D_INDEX newidx;
+} m3dtisave_t;
+
+typedef struct {
+ m3dv_t data;
+ M3D_INDEX oldidx;
+ M3D_INDEX newidx;
+ unsigned char norm;
+} m3dvsave_t;
+
+typedef struct {
+ m3ds_t data;
+ M3D_INDEX oldidx;
+ M3D_INDEX newidx;
+} m3dssave_t;
+
+typedef struct {
+ m3df_t data;
+ int group;
+ uint8_t opacity;
+} m3dfsave_t;
+
+/* create unique list of strings */
+static m3dstr_t *_m3d_addstr(m3dstr_t *str, uint32_t *numstr, char *s) {
+ uint32_t i;
+ if (!s || !*s) return str;
+ if (str) {
+ for (i = 0; i < *numstr; i++)
+ if (str[i].str == s || !strcmp(str[i].str, s)) return str;
+ }
+ str = (m3dstr_t *)M3D_REALLOC(str, ((*numstr) + 1) * sizeof(m3dstr_t));
+ str[*numstr].str = s;
+ str[*numstr].offs = 0;
+ (*numstr)++;
+ return str;
+}
+
+/* add strings to header */
+m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s) {
+ int i;
+ char *safe = _m3d_safestr(s->str, 0);
+ i = (int)strlen(safe);
+ h = (m3dhdr_t *)M3D_REALLOC(h, h->length + i + 1);
+ if (!h) {
+ M3D_FREE(safe);
+ return NULL;
+ }
+ memcpy((uint8_t *)h + h->length, safe, i + 1);
+ s->offs = h->length - 16;
+ h->length += i + 1;
+ M3D_FREE(safe);
+ return h;
+}
+
+/* return offset of string */
+static uint32_t _m3d_stridx(m3dstr_t *str, uint32_t numstr, char *s) {
+ uint32_t i;
+ char *safe;
+ if (!s || !*s) return 0;
+ if (str) {
+ safe = _m3d_safestr(s, 0);
+ if (!safe) return 0;
+ if (!*safe) {
+ free(safe);
+ return 0;
+ }
+ for (i = 0; i < numstr; i++)
+ if (!strcmp(str[i].str, s)) {
+ free(safe);
+ return str[i].offs;
+ }
+ free(safe);
+ }
+ return 0;
+}
+
+/* compare to faces by their material */
+static int _m3d_facecmp(const void *a, const void *b) {
+ const m3dfsave_t *A = (const m3dfsave_t *)a, *B = (const m3dfsave_t *)b;
+ return A->group != B->group ? A->group - B->group : (A->opacity != B->opacity ? (int)B->opacity - (int)A->opacity : (int)A->data.materialid - (int)B->data.materialid);
+}
+/* compare face groups */
+static int _m3d_grpcmp(const void *a, const void *b) {
+ return *((uint32_t *)a) - *((uint32_t *)b);
+}
+/* compare UVs */
+static int _m3d_ticmp(const void *a, const void *b) {
+ return memcmp(a, b, sizeof(m3dti_t));
+}
+/* compare skin groups */
+static int _m3d_skincmp(const void *a, const void *b) {
+ return memcmp(a, b, sizeof(m3ds_t));
+}
+/* compare vertices */
+static int _m3d_vrtxcmp(const void *a, const void *b) {
+ int c = memcmp(a, b, 3 * sizeof(M3D_FLOAT));
+ if (c) return c;
+ c = ((m3dvsave_t *)a)->norm - ((m3dvsave_t *)b)->norm;
+ if (c) return c;
+ return memcmp(a, b, sizeof(m3dv_t));
+}
+/* compare labels */
+static _inline int _m3d_strcmp(char *a, char *b) {
+ if (a == NULL && b != NULL) return -1;
+ if (a != NULL && b == NULL) return 1;
+ if (a == NULL && b == NULL) return 0;
+ return strcmp(a, b);
+}
+static int _m3d_lblcmp(const void *a, const void *b) {
+ const m3dl_t *A = (const m3dl_t *)a, *B = (const m3dl_t *)b;
+ int c = _m3d_strcmp(A->lang, B->lang);
+ if (!c) c = _m3d_strcmp(A->name, B->name);
+ if (!c) c = _m3d_strcmp(A->text, B->text);
+ return c;
+}
+/* compare two colors by HSV value */
+_inline static int _m3d_cmapcmp(const void *a, const void *b) {
+ uint8_t *A = (uint8_t *)a, *B = (uint8_t *)b;
+ _register int m, vA, vB;
+ /* get HSV value for A */
+ m = A[2] < A[1] ? A[2] : A[1];
+ if (A[0] < m) m = A[0];
+ vA = A[2] > A[1] ? A[2] : A[1];
+ if (A[0] > vA) vA = A[0];
+ /* get HSV value for B */
+ m = B[2] < B[1] ? B[2] : B[1];
+ if (B[0] < m) m = B[0];
+ vB = B[2] > B[1] ? B[2] : B[1];
+ if (B[0] > vB) vB = B[0];
+ return vA - vB;
+}
+
+/* create sorted list of colors */
+static uint32_t *_m3d_addcmap(uint32_t *cmap, uint32_t *numcmap, uint32_t color) {
+ uint32_t i;
+ if (cmap) {
+ for (i = 0; i < *numcmap; i++)
+ if (cmap[i] == color) return cmap;
+ }
+ cmap = (uint32_t *)M3D_REALLOC(cmap, ((*numcmap) + 1) * sizeof(uint32_t));
+ for (i = 0; i < *numcmap && _m3d_cmapcmp(&color, &cmap[i]) > 0; i++)
+ ;
+ if (i < *numcmap) memmove(&cmap[i + 1], &cmap[i], ((*numcmap) - i) * sizeof(uint32_t));
+ cmap[i] = color;
+ (*numcmap)++;
+ return cmap;
+}
+
+/* look up a color and return its index */
+static uint32_t _m3d_cmapidx(uint32_t *cmap, uint32_t numcmap, uint32_t color) {
+ uint32_t i;
+ if (numcmap >= 65536)
+ return color;
+ for (i = 0; i < numcmap; i++)
+ if (cmap[i] == color) return i;
+ return 0;
+}
+
+/* add index to output */
+static unsigned char *_m3d_addidx(unsigned char *out, char type, uint32_t idx) {
+ switch (type) {
+ case 1: *out++ = (uint8_t)(idx); break;
+ case 2:
+ *((uint16_t *)out) = (uint16_t)(idx);
+ out += 2;
+ break;
+ case 4:
+ *((uint32_t *)out) = (uint32_t)(idx);
+ out += 4;
+ break;
+ /* case 0: case 8: break; */
+ }
+ return out;
+}
+
+/* round a vertex position */
+static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst) {
+ _register int t;
+ /* copy additional attributes */
+ if (src != dst) memcpy(dst, src, sizeof(m3dv_t));
+ /* round according to quality */
+ switch (quality) {
+ case M3D_EXP_INT8:
+ t = (int)(src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->x = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+ t = (int)(src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->y = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+ t = (int)(src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->z = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+ t = (int)(src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->w = (M3D_FLOAT)t / (M3D_FLOAT)127.0;
+ break;
+ case M3D_EXP_INT16:
+ t = (int)(src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->x = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+ t = (int)(src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->y = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+ t = (int)(src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->z = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+ t = (int)(src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5));
+ dst->w = (M3D_FLOAT)t / (M3D_FLOAT)32767.0;
+ break;
+ }
+ if (dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0;
+ if (dst->y == (M3D_FLOAT)-0.0) dst->y = (M3D_FLOAT)0.0;
+ if (dst->z == (M3D_FLOAT)-0.0) dst->z = (M3D_FLOAT)0.0;
+ if (dst->w == (M3D_FLOAT)-0.0) dst->w = (M3D_FLOAT)0.0;
+}
+
+/* add a bone to ascii output */
+static char *_m3d_prtbone(char *ptr, m3db_t *bone, M3D_INDEX numbone, M3D_INDEX parent, uint32_t level, M3D_INDEX *vrtxidx) {
+ uint32_t i, j;
+ char *sn;
+
+ if (level > M3D_BONEMAXLEVEL || !bone) return ptr;
+ for (i = 0; i < numbone; i++) {
+ if (bone[i].parent == parent) {
+ for (j = 0; j < level; j++)
+ *ptr++ = '/';
+ sn = _m3d_safestr(bone[i].name, 0);
+ ptr += sprintf(ptr, "%d %d %s\r\n", vrtxidx[bone[i].pos], vrtxidx[bone[i].ori], sn);
+ M3D_FREE(sn);
+ ptr = _m3d_prtbone(ptr, bone, numbone, i, level + 1, vrtxidx);
+ }
+ }
+ return ptr;
+}
+
+/**
+ * Function to encode an in-memory model into on storage Model 3D format
+ */
+unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size) {
+ const char *ol;
+ char *ptr;
+ char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s;
+ char *sn = NULL, *sl = NULL, *sa = NULL, *sd = NULL;
+ unsigned char *out = NULL, *z = NULL, weights[M3D_NUMBONE], *norm = NULL;
+ unsigned int i = 0, j = 0, k = 0, l = 0, n = 0, len = 0, chunklen = 0, *length = NULL;
+ M3D_FLOAT scale = (M3D_FLOAT)0.0, min_x, max_x, min_y, max_y, min_z, max_z;
+ M3D_INDEX last, *vrtxidx = NULL, *mtrlidx = NULL, *tmapidx = NULL, *skinidx = NULL;
+ uint32_t idx, numcmap = 0, *cmap = NULL, numvrtx = 0, maxvrtx = 0, numtmap = 0, maxtmap = 0, numproc = 0;
+ uint32_t numskin = 0, maxskin = 0, numstr = 0, maxt = 0, maxbone = 0, numgrp = 0, maxgrp = 0, *grpidx = NULL;
+ uint8_t *opa = nullptr;
+ m3dcd_t *cd;
+ m3dc_t *cmd;
+ m3dstr_t *str = NULL;
+ m3dvsave_t *vrtx = NULL, vertex;
+ m3dtisave_t *tmap = NULL, tcoord;
+ m3dssave_t *skin = NULL, sk;
+ m3dfsave_t *face = NULL;
+ m3dhdr_t *h = NULL;
+ m3dm_t *m;
+ m3da_t *a;
+
+ if (!model) {
+ if (size) *size = 0;
+ return NULL;
+ }
+ model->errcode = M3D_SUCCESS;
+ if (flags & M3D_EXP_ASCII) quality = M3D_EXP_DOUBLE;
+ vrtxidx = (M3D_INDEX *)M3D_MALLOC(model->numvertex * sizeof(M3D_INDEX));
+ if (!vrtxidx) goto memerr;
+ memset(vrtxidx, 255, model->numvertex * sizeof(M3D_INDEX));
+ if (model->numvertex && !(flags & M3D_EXP_NONORMAL)) {
+ norm = (unsigned char *)M3D_MALLOC(model->numvertex * sizeof(unsigned char));
+ if (!norm) goto memerr;
+ memset(norm, 0, model->numvertex * sizeof(unsigned char));
+ }
+ if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) {
+ mtrlidx = (M3D_INDEX *)M3D_MALLOC(model->nummaterial * sizeof(M3D_INDEX));
+ if (!mtrlidx) goto memerr;
+ memset(mtrlidx, 255, model->nummaterial * sizeof(M3D_INDEX));
+ opa = (uint8_t *)M3D_MALLOC(model->nummaterial * 2 * sizeof(M3D_INDEX));
+ if (!opa) goto memerr;
+ memset(opa, 255, model->nummaterial * 2 * sizeof(M3D_INDEX));
+ }
+ if (model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) {
+ tmapidx = (M3D_INDEX *)M3D_MALLOC(model->numtmap * sizeof(M3D_INDEX));
+ if (!tmapidx) goto memerr;
+ memset(tmapidx, 255, model->numtmap * sizeof(M3D_INDEX));
+ }
+ /** collect array elements that are actually referenced **/
+ if (!(flags & M3D_EXP_NOFACE)) {
+ /* face */
+ if (model->numface && model->face) {
+ M3D_LOG("Processing mesh face");
+ face = (m3dfsave_t *)M3D_MALLOC(model->numface * sizeof(m3dfsave_t));
+ if (!face) goto memerr;
+ for (i = 0; i < model->numface; i++) {
+ memcpy(&face[i].data, &model->face[i], sizeof(m3df_t));
+ face[i].group = 0;
+ face[i].opacity = 255;
+ if (!(flags & M3D_EXP_NOMATERIAL) && model->face[i].materialid < model->nummaterial) {
+ if (model->material[model->face[i].materialid].numprop) {
+ mtrlidx[model->face[i].materialid] = 0;
+ if (opa[model->face[i].materialid * 2]) {
+ m = &model->material[model->face[i].materialid];
+ for (j = 0; j < m->numprop; j++)
+ if (m->prop[j].type == m3dp_Kd) {
+ opa[model->face[i].materialid * 2 + 1] = ((uint8_t *)&m->prop[j].value.color)[3];
+ break;
+ }
+ for (j = 0; j < m->numprop; j++)
+ if (m->prop[j].type == m3dp_d) {
+ opa[model->face[i].materialid * 2 + 1] = (uint8_t)(m->prop[j].value.fnum * 255);
+ break;
+ }
+ opa[model->face[i].materialid * 2] = 0;
+ }
+ face[i].opacity = opa[model->face[i].materialid * 2 + 1];
+ } else
+ face[i].data.materialid = M3D_UNDEF;
+ }
+ for (j = 0; j < 3; j++) {
+ k = model->face[i].vertex[j];
+ if (k < model->numvertex)
+ vrtxidx[k] = 0;
+ if (!(flags & M3D_EXP_NOCMAP)) {
+ cmap = _m3d_addcmap(cmap, &numcmap, model->vertex[k].color);
+ if (!cmap) goto memerr;
+ }
+ k = model->face[i].normal[j];
+ if (k < model->numvertex && !(flags & M3D_EXP_NONORMAL)) {
+ vrtxidx[k] = 0;
+ norm[k] = 1;
+ }
+ k = model->face[i].texcoord[j];
+ if (k < model->numtmap && !(flags & M3D_EXP_NOTXTCRD))
+ tmapidx[k] = 0;
+ }
+ /* convert from CW to CCW */
+ if (flags & M3D_EXP_IDOSUCK) {
+ j = face[i].data.vertex[1];
+ face[i].data.vertex[1] = face[i].data.vertex[2];
+ face[i].data.vertex[2] = face[i].data.vertex[1];
+ j = face[i].data.normal[1];
+ face[i].data.normal[1] = face[i].data.normal[2];
+ face[i].data.normal[2] = face[i].data.normal[1];
+ j = face[i].data.texcoord[1];
+ face[i].data.texcoord[1] = face[i].data.texcoord[2];
+ face[i].data.texcoord[2] = face[i].data.texcoord[1];
+ }
+ }
+ }
+ if (model->numshape && model->shape) {
+ M3D_LOG("Processing shape face");
+ for (i = 0; i < model->numshape; i++) {
+ if (!model->shape[i].numcmd) continue;
+ str = _m3d_addstr(str, &numstr, model->shape[i].name);
+ if (!str) goto memerr;
+ for (j = 0; j < model->shape[i].numcmd; j++) {
+ cmd = &model->shape[i].cmd[j];
+ if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg)
+ continue;
+ if (cmd->type == m3dc_mesh) {
+ if (numgrp + 2 < maxgrp) {
+ maxgrp += 1024;
+ grpidx = (uint32_t *)realloc(grpidx, maxgrp * sizeof(uint32_t));
+ if (!grpidx) goto memerr;
+ if (!numgrp) {
+ grpidx[0] = 0;
+ grpidx[1] = model->numface;
+ numgrp += 2;
+ }
+ }
+ grpidx[numgrp + 0] = cmd->arg[0];
+ grpidx[numgrp + 1] = cmd->arg[0] + cmd->arg[1];
+ numgrp += 2;
+ }
+ cd = &m3d_commandtypes[cmd->type];
+ for (k = n = 0, l = cd->p; k < l; k++)
+ switch (cd->a[((k - n) % (cd->p - n)) + n]) {
+ case m3dcp_mi_t:
+ if (!(flags & M3D_EXP_NOMATERIAL) && cmd->arg[k] < model->nummaterial)
+ mtrlidx[cmd->arg[k]] = 0;
+ break;
+ case m3dcp_ti_t:
+ if (!(flags & M3D_EXP_NOTXTCRD) && cmd->arg[k] < model->numtmap)
+ tmapidx[cmd->arg[k]] = 0;
+ break;
+ case m3dcp_qi_t:
+ case m3dcp_vi_t:
+ if (cmd->arg[k] < model->numvertex)
+ vrtxidx[cmd->arg[k]] = 0;
+ break;
+ case m3dcp_va_t:
+ n = k + 1;
+ l += (cmd->arg[k] - 1) * (cd->p - k - 1);
+ break;
+ }
+ }
+ }
+ }
+ if (model->numface && face) {
+ if (numgrp && grpidx) {
+ qsort(grpidx, numgrp, sizeof(uint32_t), _m3d_grpcmp);
+ for (i = j = 0; i < model->numface && j < numgrp; i++) {
+ while (j < numgrp && grpidx[j] < i)
+ j++;
+ face[i].group = j;
+ }
+ }
+ qsort(face, model->numface, sizeof(m3dfsave_t), _m3d_facecmp);
+ }
+ if (grpidx) {
+ M3D_FREE(grpidx);
+ grpidx = NULL;
+ }
+ if (model->numlabel && model->label) {
+ M3D_LOG("Processing annotation labels");
+ for (i = 0; i < model->numlabel; i++) {
+ str = _m3d_addstr(str, &numstr, model->label[i].name);
+ str = _m3d_addstr(str, &numstr, model->label[i].lang);
+ str = _m3d_addstr(str, &numstr, model->label[i].text);
+ if (!(flags & M3D_EXP_NOCMAP)) {
+ cmap = _m3d_addcmap(cmap, &numcmap, model->label[i].color);
+ if (!cmap) goto memerr;
+ }
+ if (model->label[i].vertexid < model->numvertex)
+ vrtxidx[model->label[i].vertexid] = 0;
+ }
+ qsort(model->label, model->numlabel, sizeof(m3dl_t), _m3d_lblcmp);
+ }
+ } else if (!(flags & M3D_EXP_NOMATERIAL)) {
+ /* without a face, simply add all materials, because it can be an mtllib */
+ for (i = 0; i < model->nummaterial; i++)
+ mtrlidx[i] = i;
+ }
+ /* bind-pose skeleton */
+ if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) {
+ M3D_LOG("Processing bones");
+ for (i = 0; i < model->numbone; i++) {
+ str = _m3d_addstr(str, &numstr, model->bone[i].name);
+ if (!str) goto memerr;
+ k = model->bone[i].pos;
+ if (k < model->numvertex)
+ vrtxidx[k] = 0;
+ k = model->bone[i].ori;
+ if (k < model->numvertex)
+ vrtxidx[k] = 0;
+ }
+ }
+ /* actions, animated skeleton poses */
+ if (model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) {
+ M3D_LOG("Processing action list");
+ for (j = 0; j < model->numaction; j++) {
+ a = &model->action[j];
+ str = _m3d_addstr(str, &numstr, a->name);
+ if (!str) goto memerr;
+ if (a->numframe > 65535) a->numframe = 65535;
+ for (i = 0; i < a->numframe; i++) {
+ for (l = 0; l < a->frame[i].numtransform; l++) {
+ k = a->frame[i].transform[l].pos;
+ if (k < model->numvertex)
+ vrtxidx[k] = 0;
+ k = a->frame[i].transform[l].ori;
+ if (k < model->numvertex)
+ vrtxidx[k] = 0;
+ }
+ if (l > maxt) maxt = l;
+ }
+ }
+ }
+ /* add colors to color map and texture names to string table */
+ if (!(flags & M3D_EXP_NOMATERIAL)) {
+ M3D_LOG("Processing materials");
+ for (i = k = 0; i < model->nummaterial; i++) {
+ if (mtrlidx[i] == M3D_UNDEF || !model->material[i].numprop) continue;
+ mtrlidx[i] = k++;
+ m = &model->material[i];
+ str = _m3d_addstr(str, &numstr, m->name);
+ if (!str) goto memerr;
+ if (m->prop)
+ for (j = 0; j < m->numprop; j++) {
+ if (!(flags & M3D_EXP_NOCMAP) && m->prop[j].type < 128) {
+ for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++) {
+ if (m->prop[j].type == m3d_propertytypes[l].id && m3d_propertytypes[l].format == m3dpf_color) {
+ ((uint8_t *)&m->prop[j].value.color)[3] = opa[i * 2 + 1];
+ cmap = _m3d_addcmap(cmap, &numcmap, m->prop[j].value.color);
+ if (!cmap) goto memerr;
+ break;
+ }
+ }
+ }
+ if (m->prop[j].type >= 128 && m->prop[j].value.textureid < model->numtexture &&
+ model->texture[m->prop[j].value.textureid].name) {
+ str = _m3d_addstr(str, &numstr, model->texture[m->prop[j].value.textureid].name);
+ if (!str) goto memerr;
+ }
+ }
+ }
+ }
+ /* if there's only one black color, don't store it */
+ if (numcmap == 1 && cmap && !cmap[0]) numcmap = 0;
+
+ /** compress lists **/
+ if (model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) {
+ M3D_LOG("Compressing tmap");
+ tmap = (m3dtisave_t *)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t));
+ if (!tmap) goto memerr;
+ for (i = 0; i < model->numtmap; i++) {
+ if (tmapidx[i] == M3D_UNDEF) continue;
+ switch (quality) {
+ case M3D_EXP_INT8:
+ l = (unsigned int)(model->tmap[i].u * 255);
+ tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)255.0;
+ l = (unsigned int)(model->tmap[i].v * 255);
+ tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)255.0;
+ break;
+ case M3D_EXP_INT16:
+ l = (unsigned int)(model->tmap[i].u * 65535);
+ tcoord.data.u = (M3D_FLOAT)l / (M3D_FLOAT)65535.0;
+ l = (unsigned int)(model->tmap[i].v * 65535);
+ tcoord.data.v = (M3D_FLOAT)l / (M3D_FLOAT)65535.0;
+ break;
+ default:
+ tcoord.data.u = model->tmap[i].u;
+ tcoord.data.v = model->tmap[i].v;
+ break;
+ }
+ if (flags & M3D_EXP_FLIPTXTCRD)
+ tcoord.data.v = (M3D_FLOAT)1.0 - tcoord.data.v;
+ tcoord.oldidx = i;
+ memcpy(&tmap[numtmap++], &tcoord, sizeof(m3dtisave_t));
+ }
+ if (numtmap) {
+ qsort(tmap, numtmap, sizeof(m3dtisave_t), _m3d_ticmp);
+ memcpy(&tcoord.data, &tmap[0], sizeof(m3dti_t));
+ for (i = 0; i < numtmap; i++) {
+ if (memcmp(&tcoord.data, &tmap[i].data, sizeof(m3dti_t))) {
+ memcpy(&tcoord.data, &tmap[i].data, sizeof(m3dti_t));
+ maxtmap++;
+ }
+ tmap[i].newidx = maxtmap;
+ tmapidx[tmap[i].oldidx] = maxtmap;
+ }
+ maxtmap++;
+ }
+ }
+ if (model->numskin && model->skin && !(flags & M3D_EXP_NOBONE)) {
+ M3D_LOG("Compressing skin");
+ skinidx = (M3D_INDEX *)M3D_MALLOC(model->numskin * sizeof(M3D_INDEX));
+ if (!skinidx) goto memerr;
+ skin = (m3dssave_t *)M3D_MALLOC(model->numskin * sizeof(m3dssave_t));
+ if (!skin) goto memerr;
+ memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX));
+ for (i = 0; i < model->numvertex; i++) {
+ if (vrtxidx[i] != M3D_UNDEF && model->vertex[i].skinid < model->numskin)
+ skinidx[model->vertex[i].skinid] = 0;
+ }
+ for (i = 0; i < model->numskin; i++) {
+ if (skinidx[i] == M3D_UNDEF) continue;
+ memset(&sk, 0, sizeof(m3dssave_t));
+ for (j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != M3D_UNDEF &&
+ model->skin[i].weight[j] > (M3D_FLOAT)0.0;
+ j++) {
+ sk.data.boneid[j] = model->skin[i].boneid[j];
+ sk.data.weight[j] = model->skin[i].weight[j];
+ min_x += sk.data.weight[j];
+ }
+ if (j > maxbone) maxbone = j;
+ if (min_x != (M3D_FLOAT)1.0 && min_x != (M3D_FLOAT)0.0)
+ for (j = 0; j < M3D_NUMBONE && sk.data.weight[j] > (M3D_FLOAT)0.0; j++)
+ sk.data.weight[j] /= min_x;
+ sk.oldidx = i;
+ memcpy(&skin[numskin++], &sk, sizeof(m3dssave_t));
+ }
+ if (numskin) {
+ qsort(skin, numskin, sizeof(m3dssave_t), _m3d_skincmp);
+ memcpy(&sk.data, &skin[0].data, sizeof(m3ds_t));
+ for (i = 0; i < numskin; i++) {
+ if (memcmp(&sk.data, &skin[i].data, sizeof(m3ds_t))) {
+ memcpy(&sk.data, &skin[i].data, sizeof(m3ds_t));
+ maxskin++;
+ }
+ skin[i].newidx = maxskin;
+ skinidx[skin[i].oldidx] = maxskin;
+ }
+ maxskin++;
+ }
+ }
+
+ M3D_LOG("Compressing vertex list");
+ min_x = min_y = min_z = (M3D_FLOAT)1e10;
+ max_x = max_y = max_z = (M3D_FLOAT)-1e10;
+ if (vrtxidx) {
+ vrtx = (m3dvsave_t *)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t));
+ if (!vrtx) goto memerr;
+ for (i = numvrtx = 0; i < model->numvertex; i++) {
+ if (vrtxidx[i] == M3D_UNDEF) continue;
+ _m3d_round(quality, &model->vertex[i], &vertex.data);
+ vertex.norm = norm ? norm[i] : 0;
+ if (vertex.data.skinid != M3D_INDEXMAX && !vertex.norm) {
+ vertex.data.skinid = vertex.data.skinid != M3D_UNDEF && skinidx ? skinidx[vertex.data.skinid] : M3D_UNDEF;
+ if (vertex.data.x > max_x) max_x = vertex.data.x;
+ if (vertex.data.x < min_x) min_x = vertex.data.x;
+ if (vertex.data.y > max_y) max_y = vertex.data.y;
+ if (vertex.data.y < min_y) min_y = vertex.data.y;
+ if (vertex.data.z > max_z) max_z = vertex.data.z;
+ if (vertex.data.z < min_z) min_z = vertex.data.z;
+ }
+#ifdef M3D_VERTEXTYPE
+ vertex.data.type = 0;
+#endif
+ vertex.oldidx = i;
+ memcpy(&vrtx[numvrtx++], &vertex, sizeof(m3dvsave_t));
+ }
+ if (numvrtx) {
+ qsort(vrtx, numvrtx, sizeof(m3dvsave_t), _m3d_vrtxcmp);
+ memcpy(&vertex.data, &vrtx[0].data, sizeof(m3dv_t));
+ for (i = 0; i < numvrtx; i++) {
+ if (memcmp(&vertex.data, &vrtx[i].data, vrtx[i].norm ? 3 * sizeof(M3D_FLOAT) : sizeof(m3dv_t))) {
+ memcpy(&vertex.data, &vrtx[i].data, sizeof(m3dv_t));
+ maxvrtx++;
+ }
+ vrtx[i].newidx = maxvrtx;
+ vrtxidx[vrtx[i].oldidx] = maxvrtx;
+ }
+ maxvrtx++;
+ }
+ }
+ if (skinidx) {
+ M3D_FREE(skinidx);
+ skinidx = NULL;
+ }
+ if (norm) {
+ M3D_FREE(norm);
+ norm = NULL;
+ }
+
+ /* normalize to bounding cube */
+ if (numvrtx && !(flags & M3D_EXP_NORECALC)) {
+ M3D_LOG("Normalizing coordinates");
+ if (min_x < (M3D_FLOAT)0.0) min_x = -min_x;
+ if (max_x < (M3D_FLOAT)0.0) max_x = -max_x;
+ if (min_y < (M3D_FLOAT)0.0) min_y = -min_y;
+ if (max_y < (M3D_FLOAT)0.0) max_y = -max_y;
+ if (min_z < (M3D_FLOAT)0.0) min_z = -min_z;
+ if (max_z < (M3D_FLOAT)0.0) max_z = -max_z;
+ scale = min_x;
+ if (max_x > scale) scale = max_x;
+ if (min_y > scale) scale = min_y;
+ if (max_y > scale) scale = max_y;
+ if (min_z > scale) scale = min_z;
+ if (max_z > scale) scale = max_z;
+ if (scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0;
+ if (scale != (M3D_FLOAT)1.0) {
+ for (i = 0; i < numvrtx; i++) {
+ if (vrtx[i].data.skinid == M3D_INDEXMAX) continue;
+ vrtx[i].data.x /= scale;
+ vrtx[i].data.y /= scale;
+ vrtx[i].data.z /= scale;
+ }
+ }
+ }
+ if (model->scale > (M3D_FLOAT)0.0) scale = model->scale;
+ if (scale <= (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0;
+
+ /* meta info */
+ sn = _m3d_safestr(model->name && *model->name ? model->name : (char *)"(noname)", 2);
+ sl = _m3d_safestr(model->license ? model->license : (char *)"MIT", 2);
+ sa = _m3d_safestr(model->author ? model->author : getenv("LOGNAME"), 2);
+ if (!sn || !sl || !sa) {
+ memerr:
+ if (vrtxidx) M3D_FREE(vrtxidx);
+ if (mtrlidx) M3D_FREE(mtrlidx);
+ if (tmapidx) M3D_FREE(tmapidx);
+ if (skinidx) M3D_FREE(skinidx);
+ if (grpidx) M3D_FREE(grpidx);
+ if (norm) M3D_FREE(norm);
+ if (face) M3D_FREE(face);
+ if (cmap) M3D_FREE(cmap);
+ if (tmap) M3D_FREE(tmap);
+ if (skin) M3D_FREE(skin);
+ if (str) M3D_FREE(str);
+ if (vrtx) M3D_FREE(vrtx);
+ if (sn) M3D_FREE(sn);
+ if (sl) M3D_FREE(sl);
+ if (sa) M3D_FREE(sa);
+ if (sd) M3D_FREE(sd);
+ if (out) M3D_FREE(out);
+ if (h) M3D_FREE(h);
+ M3D_LOG("Out of memory");
+ model->errcode = M3D_ERR_ALLOC;
+ return NULL;
+ }
+
+ M3D_LOG("Serializing model");
+ if (flags & M3D_EXP_ASCII) {
+ /* use CRLF to make model creators on Win happy... */
+ sd = _m3d_safestr(model->desc, 1);
+ if (!sd) goto memerr;
+ ol = setlocale(LC_NUMERIC, NULL);
+ setlocale(LC_NUMERIC, "C");
+ /* header */
+ len = 64 + (unsigned int)(strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd));
+ out = (unsigned char *)M3D_MALLOC(len);
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr = (char *)out;
+ ptr += sprintf(ptr, "3dmodel %g\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", scale,
+ sn, sl, sa, sd);
+ M3D_FREE(sl);
+ M3D_FREE(sa);
+ M3D_FREE(sd);
+ sl = sa = sd = NULL;
+ /* preview chunk */
+ if (model->preview.data && model->preview.length) {
+ sl = _m3d_safestr(sn, 0);
+ if (sl) {
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)20);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl);
+ M3D_FREE(sl);
+ sl = NULL;
+ }
+ }
+ M3D_FREE(sn);
+ sn = NULL;
+ /* texture map */
+ if (numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) {
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxtmap * 32) + (uintptr_t)12);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Textmap\r\n");
+ last = M3D_UNDEF;
+ for (i = 0; i < numtmap; i++) {
+ if (tmap[i].newidx == last) continue;
+ last = tmap[i].newidx;
+ ptr += sprintf(ptr, "%g %g\r\n", tmap[i].data.u, tmap[i].data.v);
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ /* vertex chunk */
+ if (numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) {
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(maxvrtx * 128) + (uintptr_t)10);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Vertex\r\n");
+ last = M3D_UNDEF;
+ for (i = 0; i < numvrtx; i++) {
+ if (vrtx[i].newidx == last) continue;
+ last = vrtx[i].newidx;
+ ptr += sprintf(ptr, "%g %g %g %g", vrtx[i].data.x, vrtx[i].data.y, vrtx[i].data.z, vrtx[i].data.w);
+ if (!(flags & M3D_EXP_NOCMAP) && vrtx[i].data.color)
+ ptr += sprintf(ptr, " #%08x", vrtx[i].data.color);
+ if (!(flags & M3D_EXP_NOBONE) && model->numbone && maxskin && vrtx[i].data.skinid < M3D_INDEXMAX) {
+ if (skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0)
+ ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]);
+ else
+ for (j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != M3D_UNDEF &&
+ skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0;
+ j++)
+ ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j],
+ skin[vrtx[i].data.skinid].data.weight[j]);
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ /* bones chunk */
+ if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) {
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)9);
+ for (i = 0; i < model->numbone; i++) {
+ len += (unsigned int)strlen(model->bone[i].name) + 128;
+ }
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Bones\r\n");
+ ptr = _m3d_prtbone(ptr, model->bone, model->numbone, M3D_UNDEF, 0, vrtxidx);
+ ptr += sprintf(ptr, "\r\n");
+ }
+ /* materials */
+ if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) {
+ for (j = 0; j < model->nummaterial; j++) {
+ if (mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue;
+ m = &model->material[j];
+ sn = _m3d_safestr(m->name, 0);
+ if (!sn) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)12);
+ for (i = 0; i < m->numprop; i++) {
+ if (m->prop[i].type < 128)
+ len += 32;
+ else if (m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name)
+ len += (unsigned int)strlen(model->texture[m->prop[i].value.textureid].name) + 16;
+ }
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Material %s\r\n", sn);
+ M3D_FREE(sn);
+ sn = NULL;
+ for (i = 0; i < m->numprop; i++) {
+ k = 256;
+ if (m->prop[i].type >= 128) {
+ for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++)
+ if (m->prop[i].type == m3d_propertytypes[l].id) {
+ sn = m3d_propertytypes[l].key;
+ break;
+ }
+ if (!sn)
+ for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++)
+ if (m->prop[i].type - 128 == m3d_propertytypes[l].id) {
+ sn = m3d_propertytypes[l].key;
+ break;
+ }
+ k = sn ? m3dpf_map : 256;
+ } else {
+ for (l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++)
+ if (m->prop[i].type == m3d_propertytypes[l].id) {
+ sn = m3d_propertytypes[l].key;
+ k = m3d_propertytypes[l].format;
+ break;
+ }
+ }
+ switch (k) {
+ case m3dpf_color: ptr += sprintf(ptr, "%s #%08x\r\n", sn, m->prop[i].value.color); break;
+ case m3dpf_uint8:
+ case m3dpf_uint16:
+ case m3dpf_uint32: ptr += sprintf(ptr, "%s %d\r\n", sn, m->prop[i].value.num); break;
+ case m3dpf_float: ptr += sprintf(ptr, "%s %g\r\n", sn, m->prop[i].value.fnum); break;
+ case m3dpf_map:
+ if (m->prop[i].value.textureid < model->numtexture &&
+ model->texture[m->prop[i].value.textureid].name) {
+ sl = _m3d_safestr(model->texture[m->prop[i].value.textureid].name, 0);
+ if (!sl) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ if (*sl)
+ ptr += sprintf(ptr, "map_%s %s\r\n", sn, sl);
+ M3D_FREE(sn);
+ M3D_FREE(sl);
+ sl = NULL;
+ }
+ break;
+ }
+ sn = NULL;
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ }
+ /* procedural face */
+ if (model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) {
+ /* all inlined assets which are not textures should be procedural surfaces */
+ for (j = 0; j < model->numinlined; j++) {
+ if (!model->inlined[j].name || !*model->inlined[j].name || !model->inlined[j].length || !model->inlined[j].data ||
+ (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G'))
+ continue;
+ for (i = k = 0; i < model->numtexture; i++) {
+ if (!strcmp(model->inlined[j].name, model->texture[i].name)) {
+ k = 1;
+ break;
+ }
+ }
+ if (k) continue;
+ sn = _m3d_safestr(model->inlined[j].name, 0);
+ if (!sn) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)18);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn);
+ M3D_FREE(sn);
+ sn = NULL;
+ }
+ }
+ /* mesh face */
+ if (model->numface && face && !(flags & M3D_EXP_NOFACE)) {
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)(model->numface * 128) + (uintptr_t)6);
+ last = M3D_UNDEF;
+ if (!(flags & M3D_EXP_NOMATERIAL))
+ for (i = 0; i < model->numface; i++) {
+ j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF;
+ if (j != last) {
+ last = j;
+ if (last < model->nummaterial)
+ len += (unsigned int)strlen(model->material[last].name);
+ len += 6;
+ }
+ }
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Mesh\r\n");
+ last = M3D_UNDEF;
+ for (i = 0; i < model->numface; i++) {
+ j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : M3D_UNDEF;
+ if (!(flags & M3D_EXP_NOMATERIAL) && j != last) {
+ last = j;
+ if (last < model->nummaterial) {
+ sn = _m3d_safestr(model->material[last].name, 0);
+ if (!sn) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "use %s\r\n", sn);
+ M3D_FREE(sn);
+ sn = NULL;
+ } else
+ ptr += sprintf(ptr, "use\r\n");
+ }
+ /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */
+ for (j = 0; j < 3; j++) {
+ ptr += sprintf(ptr, "%s%d", j ? " " : "", vrtxidx[face[i].data.vertex[j]]);
+ k = M3D_NOTDEFINED;
+ if (!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != M3D_UNDEF) &&
+ (tmapidx[face[i].data.texcoord[j]] != M3D_UNDEF)) {
+ k = tmapidx[face[i].data.texcoord[j]];
+ ptr += sprintf(ptr, "/%d", k);
+ }
+ if (!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != M3D_UNDEF))
+ ptr += sprintf(ptr, "%s/%d", k == M3D_NOTDEFINED ? "/" : "", vrtxidx[face[i].data.normal[j]]);
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ /* mathematical shapes face */
+ if (model->numshape && (!(flags & M3D_EXP_NOFACE))) {
+ for (j = 0; j < model->numshape; j++) {
+ sn = _m3d_safestr(model->shape[j].name, 0);
+ if (!sn) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)33);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Shape %s\r\n", sn);
+ M3D_FREE(sn);
+ sn = NULL;
+ if (model->shape[j].group != M3D_UNDEF && !(flags & M3D_EXP_NOBONE))
+ ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group);
+ for (i = 0; i < model->shape[j].numcmd; i++) {
+ cmd = &model->shape[j].cmd[i];
+ if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg)
+ continue;
+ cd = &m3d_commandtypes[cmd->type];
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(cd->key) + (uintptr_t)3);
+ for (k = 0; k < cd->p; k++)
+ switch (cd->a[k]) {
+ case m3dcp_mi_t:
+ if (cmd->arg[k] != M3D_NOTDEFINED) {
+ len += (unsigned int)strlen(model->material[cmd->arg[k]].name) + 1;
+ }
+ break;
+ case m3dcp_va_t:
+ len += cmd->arg[k] * (cd->p - k - 1) * 16;
+ k = cd->p;
+ break;
+ default: len += 16; break;
+ }
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "%s", cd->key);
+ for (k = n = 0, l = cd->p; k < l; k++) {
+ switch (cd->a[((k - n) % (cd->p - n)) + n]) {
+ case m3dcp_mi_t:
+ if (cmd->arg[k] != M3D_NOTDEFINED) {
+ sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0);
+ if (!sn) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, " %s", sn);
+ M3D_FREE(sn);
+ sn = NULL;
+ }
+ break;
+ case m3dcp_vc_t: ptr += sprintf(ptr, " %g", *((float *)&cmd->arg[k])); break;
+ case m3dcp_va_t:
+ ptr += sprintf(ptr, " %d[", cmd->arg[k]);
+ n = k + 1;
+ l += (cmd->arg[k] - 1) * (cd->p - k - 1);
+ break;
+ default: ptr += sprintf(ptr, " %d", cmd->arg[k]); break;
+ }
+ }
+ ptr += sprintf(ptr, "%s\r\n", l > cd->p ? " ]" : "");
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ }
+ /* annotation labels */
+ if (model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) {
+ for (i = 0, j = 3, length = NULL; i < model->numlabel; i++) {
+ if (model->label[i].name) j += (unsigned int)strlen(model->label[i].name);
+ if (model->label[i].lang) j += (unsigned int)strlen(model->label[i].lang);
+ if (model->label[i].text) j += (unsigned int)strlen(model->label[i].text);
+ j += 40;
+ }
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ for (i = 0; i < model->numlabel; i++) {
+ if (!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) {
+ sl = model->label[i].lang;
+ sn = model->label[i].name;
+ sd = _m3d_safestr(sn, 0);
+ if (!sd) {
+ setlocale(LC_NUMERIC, ol);
+ sn = sl = NULL;
+ goto memerr;
+ }
+ if (i) ptr += sprintf(ptr, "\r\n");
+ ptr += sprintf(ptr, "Labels %s\r\n", sd);
+ M3D_FREE(sd);
+ sd = NULL;
+ if (model->label[i].color)
+ ptr += sprintf(ptr, "color #0x%08x\r\n", model->label[i].color);
+ if (sl && *sl) {
+ sd = _m3d_safestr(sl, 0);
+ if (!sd) {
+ setlocale(LC_NUMERIC, ol);
+ sn = sl = NULL;
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "lang %s\r\n", sd);
+ M3D_FREE(sd);
+ sd = NULL;
+ }
+ }
+ sd = _m3d_safestr(model->label[i].text, 2);
+ if (!sd) {
+ setlocale(LC_NUMERIC, ol);
+ sn = sl = NULL;
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "%d %s\r\n", model->label[i].vertexid, sd);
+ M3D_FREE(sd);
+ sd = NULL;
+ }
+ ptr += sprintf(ptr, "\r\n");
+ sn = sl = NULL;
+ }
+ /* actions */
+ if (model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) {
+ for (j = 0; j < model->numaction; j++) {
+ a = &model->action[j];
+ sn = _m3d_safestr(a->name, 0);
+ if (!sn) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)strlen(sn) + (uintptr_t)48);
+ for (i = 0; i < a->numframe; i++)
+ len += a->frame[i].numtransform * 128 + 8;
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Action %d %s\r\n", a->durationmsec, sn);
+ M3D_FREE(sn);
+ sn = NULL;
+ for (i = 0; i < a->numframe; i++) {
+ ptr += sprintf(ptr, "frame %d\r\n", a->frame[i].msec);
+ for (k = 0; k < a->frame[i].numtransform; k++) {
+ ptr += sprintf(ptr, "%d %d %d\r\n", a->frame[i].transform[k].boneid,
+ vrtxidx[a->frame[i].transform[k].pos], vrtxidx[a->frame[i].transform[k].ori]);
+ }
+ }
+ ptr += sprintf(ptr, "\r\n");
+ }
+ }
+ /* inlined assets */
+ if (model->numinlined && model->inlined) {
+ for (i = j = 0; i < model->numinlined; i++)
+ if (model->inlined[i].name)
+ j += (unsigned int)strlen(model->inlined[i].name) + 6;
+ if (j > 0) {
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)j + (uintptr_t)16);
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Assets\r\n");
+ for (i = 0; i < model->numinlined; i++)
+ if (model->inlined[i].name)
+ ptr += sprintf(ptr, "%s%s\r\n", model->inlined[i].name, strrchr(model->inlined[i].name, '.') ? "" : ".png");
+ ptr += sprintf(ptr, "\r\n");
+ }
+ }
+ /* extra info */
+ if (model->numextra && (flags & M3D_EXP_EXTRA)) {
+ for (i = 0; i < model->numextra; i++) {
+ if (model->extra[i]->length < 9) continue;
+ ptr -= (uintptr_t)out;
+ len = (unsigned int)((uintptr_t)ptr + (uintptr_t)17 + (uintptr_t)(model->extra[i]->length * 3));
+ out = (unsigned char *)M3D_REALLOC(out, len);
+ ptr += (uintptr_t)out;
+ if (!out) {
+ setlocale(LC_NUMERIC, ol);
+ goto memerr;
+ }
+ ptr += sprintf(ptr, "Extra %c%c%c%c\r\n",
+ model->extra[i]->magic[0] > ' ' ? model->extra[i]->magic[0] : '_',
+ model->extra[i]->magic[1] > ' ' ? model->extra[i]->magic[1] : '_',
+ model->extra[i]->magic[2] > ' ' ? model->extra[i]->magic[2] : '_',
+ model->extra[i]->magic[3] > ' ' ? model->extra[i]->magic[3] : '_');
+ for (j = 0; j < model->extra[i]->length; j++)
+ ptr += sprintf(ptr, "%02x ", *((unsigned char *)model->extra + sizeof(m3dchunk_t) + j));
+ ptr--;
+ ptr += sprintf(ptr, "\r\n\r\n");
+ }
+ }
+ setlocale(LC_NUMERIC, ol);
+ len = (unsigned int)((uintptr_t)ptr - (uintptr_t)out);
+ out = (unsigned char *)M3D_REALLOC(out, len + 1);
+ if (!out) goto memerr;
+ out[len] = 0;
+ } else
+ {
+ /* strictly only use LF (newline) in binary */
+ sd = _m3d_safestr(model->desc, 3);
+ if (!sd) goto memerr;
+ /* header */
+ h = (m3dhdr_t *)M3D_MALLOC(sizeof(m3dhdr_t) + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd) + 4);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h, "HEAD", 4);
+ h->length = sizeof(m3dhdr_t);
+ h->scale = scale;
+ i = (unsigned int)strlen(sn);
+ memcpy((uint8_t *)h + h->length, sn, i + 1);
+ h->length += i + 1;
+ M3D_FREE(sn);
+ i = (unsigned int)strlen(sl);
+ memcpy((uint8_t *)h + h->length, sl, i + 1);
+ h->length += i + 1;
+ M3D_FREE(sl);
+ i = (unsigned int)strlen(sa);
+ memcpy((uint8_t *)h + h->length, sa, i + 1);
+ h->length += i + 1;
+ M3D_FREE(sa);
+ i = (unsigned int)strlen(sd);
+ memcpy((uint8_t *)h + h->length, sd, i + 1);
+ h->length += i + 1;
+ M3D_FREE(sd);
+ sn = sl = sa = sd = NULL;
+ if (model->inlined)
+ for (i = 0; i < model->numinlined; i++) {
+ if (model->inlined[i].name && *model->inlined[i].name && model->inlined[i].length > 0) {
+ str = _m3d_addstr(str, &numstr, model->inlined[i].name);
+ if (!str) goto memerr;
+ }
+ }
+ if (str)
+ for (i = 0; i < numstr; i++) {
+ h = _m3d_addhdr(h, &str[i]);
+ if (!h) goto memerr;
+ }
+ vc_s = quality == M3D_EXP_INT8 ? 1 : (quality == M3D_EXP_INT16 ? 2 : (quality == M3D_EXP_DOUBLE ? 8 : 4));
+ vi_s = maxvrtx < 254 ? 1 : (maxvrtx < 65534 ? 2 : 4);
+ si_s = h->length - 16 < 254 ? 1 : (h->length - 16 < 65534 ? 2 : 4);
+ ci_s = !numcmap || !cmap ? 0 : (numcmap < 254 ? 1 : (numcmap < 65534 ? 2 : 4));
+ ti_s = !maxtmap || !tmap ? 0 : (maxtmap < 254 ? 1 : (maxtmap < 65534 ? 2 : 4));
+ bi_s = !model->numbone || !model->bone || (flags & M3D_EXP_NOBONE) ? 0 : (model->numbone < 254 ? 1 : (model->numbone < 65534 ? 2 : 4));
+ nb_s = maxbone < 2 ? 1 : (maxbone == 2 ? 2 : (maxbone <= 4 ? 4 : 8));
+ sk_s = !bi_s || !maxskin || !skin ? 0 : (maxskin < 254 ? 1 : (maxskin < 65534 ? 2 : 4));
+ fc_s = maxt < 254 ? 1 : (maxt < 65534 ? 2 : 4);
+ hi_s = !model->numshape || !model->shape || (flags & M3D_EXP_NOFACE) ? 0 : (model->numshape < 254 ? 1 : (model->numshape < 65534 ? 2 : 4));
+ fi_s = !model->numface || !model->face || (flags & M3D_EXP_NOFACE) ? 0 : (model->numface < 254 ? 1 : (model->numface < 65534 ? 2 : 4));
+ h->types = (vc_s == 8 ? (3 << 0) : (vc_s == 2 ? (1 << 0) : (vc_s == 1 ? (0 << 0) : (2 << 0)))) |
+ (vi_s == 2 ? (1 << 2) : (vi_s == 1 ? (0 << 2) : (2 << 2))) |
+ (si_s == 2 ? (1 << 4) : (si_s == 1 ? (0 << 4) : (2 << 4))) |
+ (ci_s == 2 ? (1 << 6) : (ci_s == 1 ? (0 << 6) : (ci_s == 4 ? (2 << 6) : (3 << 6)))) |
+ (ti_s == 2 ? (1 << 8) : (ti_s == 1 ? (0 << 8) : (ti_s == 4 ? (2 << 8) : (3 << 8)))) |
+ (bi_s == 2 ? (1 << 10) : (bi_s == 1 ? (0 << 10) : (bi_s == 4 ? (2 << 10) : (3 << 10)))) |
+ (nb_s == 2 ? (1 << 12) : (nb_s == 1 ? (0 << 12) : (2 << 12))) |
+ (sk_s == 2 ? (1 << 14) : (sk_s == 1 ? (0 << 14) : (sk_s == 4 ? (2 << 14) : (3 << 14)))) |
+ (fc_s == 2 ? (1 << 16) : (fc_s == 1 ? (0 << 16) : (2 << 16))) |
+ (hi_s == 2 ? (1 << 18) : (hi_s == 1 ? (0 << 18) : (hi_s == 4 ? (2 << 18) : (3 << 18)))) |
+ (fi_s == 2 ? (1 << 20) : (fi_s == 1 ? (0 << 20) : (fi_s == 4 ? (2 << 20) : (3 << 20))));
+ len = h->length;
+ /* preview image chunk, must be the first if exists */
+ if (model->preview.data && model->preview.length) {
+ chunklen = 8 + model->preview.length;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "PRVW", 4);
+ *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen;
+ memcpy((uint8_t *)h + len + 8, model->preview.data, model->preview.length);
+ len += chunklen;
+ }
+ /* color map */
+ if (numcmap && cmap && ci_s < 4 && !(flags & M3D_EXP_NOCMAP)) {
+ chunklen = 8 + numcmap * sizeof(uint32_t);
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "CMAP", 4);
+ *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen;
+ memcpy((uint8_t *)h + len + 8, cmap, chunklen - 8);
+ len += chunklen;
+ } else
+ numcmap = 0;
+ /* texture map */
+ if (numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) {
+ chunklen = 8 + maxtmap * vc_s * 2;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "TMAP", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ last = M3D_UNDEF;
+ for (i = 0; i < numtmap; i++) {
+ if (tmap[i].newidx == last) continue;
+ last = tmap[i].newidx;
+ switch (vc_s) {
+ case 1:
+ *out++ = (uint8_t)(tmap[i].data.u * 255);
+ *out++ = (uint8_t)(tmap[i].data.v * 255);
+ break;
+ case 2:
+ *((uint16_t *)out) = (uint16_t)(tmap[i].data.u * 65535);
+ out += 2;
+ *((uint16_t *)out) = (uint16_t)(tmap[i].data.v * 65535);
+ out += 2;
+ break;
+ case 4:
+ *((float *)out) = tmap[i].data.u;
+ out += 4;
+ *((float *)out) = tmap[i].data.v;
+ out += 4;
+ break;
+ case 8:
+ *((double *)out) = tmap[i].data.u;
+ out += 8;
+ *((double *)out) = tmap[i].data.v;
+ out += 8;
+ break;
+ }
+ }
+ *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ out = NULL;
+ len += *length;
+ }
+ /* vertex */
+ if (numvrtx && vrtx) {
+ chunklen = 8 + maxvrtx * (ci_s + sk_s + 4 * vc_s);
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "VRTS", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ last = M3D_UNDEF;
+ for (i = 0; i < numvrtx; i++) {
+ if (vrtx[i].newidx == last) continue;
+ last = vrtx[i].newidx;
+ switch (vc_s) {
+ case 1:
+ *out++ = (int8_t)(vrtx[i].data.x * 127);
+ *out++ = (int8_t)(vrtx[i].data.y * 127);
+ *out++ = (int8_t)(vrtx[i].data.z * 127);
+ *out++ = (int8_t)(vrtx[i].data.w * 127);
+ break;
+ case 2:
+ *((int16_t *)out) = (int16_t)(vrtx[i].data.x * 32767);
+ out += 2;
+ *((int16_t *)out) = (int16_t)(vrtx[i].data.y * 32767);
+ out += 2;
+ *((int16_t *)out) = (int16_t)(vrtx[i].data.z * 32767);
+ out += 2;
+ *((int16_t *)out) = (int16_t)(vrtx[i].data.w * 32767);
+ out += 2;
+ break;
+ case 4:
+ memcpy(out, &vrtx[i].data.x, sizeof(float));
+ out += 4;
+ memcpy(out, &vrtx[i].data.y, sizeof(float));
+ out += 4;
+ memcpy(out, &vrtx[i].data.z, sizeof(float));
+ out += 4;
+ memcpy(out, &vrtx[i].data.w, sizeof(float));
+ out += 4;
+ break;
+ case 8:
+ *((double *)out) = vrtx[i].data.x;
+ out += 8;
+ *((double *)out) = vrtx[i].data.y;
+ out += 8;
+ *((double *)out) = vrtx[i].data.z;
+ out += 8;
+ *((double *)out) = vrtx[i].data.w;
+ out += 8;
+ break;
+ }
+ idx = _m3d_cmapidx(cmap, numcmap, vrtx[i].data.color);
+ switch (ci_s) {
+ case 1: *out++ = (uint8_t)(idx); break;
+ case 2:
+ *((uint16_t *)out) = (uint16_t)(idx);
+ out += 2;
+ break;
+ case 4:
+ *((uint32_t *)out) = vrtx[i].data.color;
+ out += 4;
+ break;
+ }
+ out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid);
+ }
+ uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ memcpy(length, &v, sizeof(uint32_t));
+ //*length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ out = NULL;
+ len += v;
+ }
+ /* bones chunk */
+ if (model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) {
+ i = 8 + bi_s + sk_s + model->numbone * (bi_s + si_s + 2 * vi_s);
+ chunklen = i + numskin * nb_s * (bi_s + 1);
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "BONE", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, bi_s, model->numbone);
+ out = _m3d_addidx(out, sk_s, maxskin);
+ for (i = 0; i < model->numbone; i++) {
+ out = _m3d_addidx(out, bi_s, model->bone[i].parent);
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->bone[i].name));
+ out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].pos]);
+ out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]);
+ }
+ if (numskin && skin && sk_s) {
+ last = M3D_UNDEF;
+ for (i = 0; i < numskin; i++) {
+ if (skin[i].newidx == last) continue;
+ last = skin[i].newidx;
+ memset(&weights, 0, nb_s);
+ for (j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF &&
+ skin[i].data.weight[j] > (M3D_FLOAT)0.0;
+ j++)
+ weights[j] = (uint8_t)(skin[i].data.weight[j] * 255);
+ switch (nb_s) {
+ case 1: weights[0] = 255; break;
+ case 2:
+ *((uint16_t *)out) = *((uint16_t *)&weights[0]);
+ out += 2;
+ break;
+ case 4:
+ *((uint32_t *)out) = *((uint32_t *)&weights[0]);
+ out += 4;
+ break;
+ case 8:
+ *((uint64_t *)out) = *((uint64_t *)&weights[0]);
+ out += 8;
+ break;
+ }
+ for (j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != M3D_UNDEF && weights[j]; j++) {
+ out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]);
+ *length += bi_s;
+ }
+ }
+ }
+ *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ out = NULL;
+ len += *length;
+ }
+ /* materials */
+ if (model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) {
+ for (j = 0; j < model->nummaterial; j++) {
+ if (mtrlidx[j] == M3D_UNDEF || !model->material[j].numprop || !model->material[j].prop) continue;
+ m = &model->material[j];
+ chunklen = 12 + si_s + m->numprop * 5;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "MTRL", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, m->name));
+ for (i = 0; i < m->numprop; i++) {
+ if (m->prop[i].type >= 128) {
+ if (m->prop[i].value.textureid >= model->numtexture ||
+ !model->texture[m->prop[i].value.textureid].name) continue;
+ k = m3dpf_map;
+ } else {
+ for (k = 256, l = 0; l < sizeof(m3d_propertytypes) / sizeof(m3d_propertytypes[0]); l++)
+ if (m->prop[i].type == m3d_propertytypes[l].id) {
+ k = m3d_propertytypes[l].format;
+ break;
+ }
+ }
+ if (k == 256) continue;
+ *out++ = m->prop[i].type;
+ switch (k) {
+ case m3dpf_color:
+ if (!(flags & M3D_EXP_NOCMAP)) {
+ idx = _m3d_cmapidx(cmap, numcmap, m->prop[i].value.color);
+ switch (ci_s) {
+ case 1: *out++ = (uint8_t)(idx); break;
+ case 2:
+ *((uint16_t *)out) = (uint16_t)(idx);
+ out += 2;
+ break;
+ case 4:
+ *((uint32_t *)out) = (uint32_t)(m->prop[i].value.color);
+ out += 4;
+ break;
+ }
+ } else
+ out--;
+ break;
+ case m3dpf_uint8: *out++ = (uint8_t)m->prop[i].value.num; break;
+ case m3dpf_uint16:
+ *((uint16_t *)out) = (uint16_t)m->prop[i].value.num;
+ out += 2;
+ break;
+ case m3dpf_uint32:
+ *((uint32_t *)out) = m->prop[i].value.num;
+ out += 4;
+ break;
+ case m3dpf_float:
+ *((float *)out) = m->prop[i].value.fnum;
+ out += 4;
+ break;
+
+ case m3dpf_map:
+ idx = _m3d_stridx(str, numstr, model->texture[m->prop[i].value.textureid].name);
+ out = _m3d_addidx(out, si_s, idx);
+ break;
+ }
+ }
+ *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ len += *length;
+ out = NULL;
+ }
+ }
+ /* procedural face */
+ if (model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) {
+ /* all inlined assets which are not textures should be procedural surfaces */
+ for (j = 0; j < model->numinlined; j++) {
+ if (!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 ||
+ !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G'))
+ continue;
+ for (i = k = 0; i < model->numtexture; i++) {
+ if (!strcmp(model->inlined[j].name, model->texture[i].name)) {
+ k = 1;
+ break;
+ }
+ }
+ if (k) continue;
+ numproc++;
+ chunklen = 8 + si_s;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "PROC", 4);
+ *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen;
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name));
+ out = NULL;
+ len += chunklen;
+ }
+ }
+ /* mesh face */
+ if (model->numface && face && !(flags & M3D_EXP_NOFACE)) {
+ chunklen = 8 + si_s + model->numface * (6 * vi_s + 3 * ti_s + si_s + 1);
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "MESH", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ last = M3D_UNDEF;
+ for (i = 0; i < model->numface; i++) {
+ if (!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) {
+ last = face[i].data.materialid;
+ idx = last < model->nummaterial ? _m3d_stridx(str, numstr, model->material[last].name) : 0;
+ *out++ = 0;
+ out = _m3d_addidx(out, si_s, idx);
+ }
+ /* hardcoded triangles. */
+ k = (3 << 4) |
+ (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == M3D_UNDEF ||
+ face[i].data.texcoord[1] == M3D_UNDEF || face[i].data.texcoord[2] == M3D_UNDEF) ?
+ 0 :
+ 1) |
+ (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == M3D_UNDEF ||
+ face[i].data.normal[1] == M3D_UNDEF || face[i].data.normal[2] == M3D_UNDEF) ?
+ 0 :
+ 2);
+ *out++ = (uint8_t)k;
+ for (j = 0; j < 3; j++) {
+ out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]);
+ if (k & 1)
+ out = _m3d_addidx(out, ti_s, tmapidx[face[i].data.texcoord[j]]);
+ if (k & 2)
+ out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]);
+ }
+ }
+ uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ memcpy(length, &v, sizeof(uint32_t));
+ len += v;
+ out = NULL;
+ }
+ /* mathematical shapes face */
+ if (model->numshape && model->shape && !(flags & M3D_EXP_NOFACE)) {
+ for (j = 0; j < model->numshape; j++) {
+ chunklen = 12 + si_s + model->shape[j].numcmd * (M3D_CMDMAXARG + 1) * 4;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "SHPE", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->shape[j].name));
+ out = _m3d_addidx(out, bi_s, model->shape[j].group);
+ for (i = 0; i < model->shape[j].numcmd; i++) {
+ cmd = &model->shape[j].cmd[i];
+ if (cmd->type >= (unsigned int)(sizeof(m3d_commandtypes) / sizeof(m3d_commandtypes[0])) || !cmd->arg)
+ continue;
+ cd = &m3d_commandtypes[cmd->type];
+ *out++ = (cmd->type & 0x7F) | (cmd->type > 127 ? 0x80 : 0);
+ if (cmd->type > 127) *out++ = (cmd->type >> 7) & 0xff;
+ for (k = n = 0, l = cd->p; k < l; k++) {
+ switch (cd->a[((k - n) % (cd->p - n)) + n]) {
+ case m3dcp_mi_t:
+ out = _m3d_addidx(out, si_s, cmd->arg[k] < model->nummaterial ? _m3d_stridx(str, numstr, model->material[cmd->arg[k]].name) : 0);
+ break;
+ case m3dcp_vc_t:
+ min_x = *((float *)&cmd->arg[k]);
+ switch (vc_s) {
+ case 1: *out++ = (int8_t)(min_x * 127); break;
+ case 2:
+ *((int16_t *)out) = (int16_t)(min_x * 32767);
+ out += 2;
+ break;
+ case 4:
+ *((float *)out) = min_x;
+ out += 4;
+ break;
+ case 8:
+ *((double *)out) = min_x;
+ out += 8;
+ break;
+ }
+ break;
+ case m3dcp_hi_t: out = _m3d_addidx(out, hi_s, cmd->arg[k]); break;
+ case m3dcp_fi_t: out = _m3d_addidx(out, fi_s, cmd->arg[k]); break;
+ case m3dcp_ti_t: out = _m3d_addidx(out, ti_s, cmd->arg[k]); break;
+ case m3dcp_qi_t:
+ case m3dcp_vi_t: out = _m3d_addidx(out, vi_s, cmd->arg[k]); break;
+ case m3dcp_i1_t: out = _m3d_addidx(out, 1, cmd->arg[k]); break;
+ case m3dcp_i2_t: out = _m3d_addidx(out, 2, cmd->arg[k]); break;
+ case m3dcp_i4_t: out = _m3d_addidx(out, 4, cmd->arg[k]); break;
+ case m3dcp_va_t:
+ out = _m3d_addidx(out, 4, cmd->arg[k]);
+ n = k + 1;
+ l += (cmd->arg[k] - 1) * (cd->p - k - 1);
+ break;
+ }
+ }
+ }
+ uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ memcpy( length, &v, sizeof(uint32_t));
+ len += v;
+ out = NULL;
+ }
+ }
+ /* annotation labels */
+ if (model->numlabel && model->label) {
+ for (i = 0, length = NULL; i < model->numlabel; i++) {
+ if (!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) {
+ sl = model->label[i].lang;
+ sn = model->label[i].name;
+ if (length) {
+ *length = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ len += *length;
+ }
+ chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s);
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) {
+ sn = NULL;
+ sl = NULL;
+ goto memerr;
+ }
+ memcpy((uint8_t *)h + len, "LBLS", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].name));
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].lang));
+ idx = _m3d_cmapidx(cmap, numcmap, model->label[i].color);
+ switch (ci_s) {
+ case 1: *out++ = (uint8_t)(idx); break;
+ case 2:
+ *((uint16_t *)out) = (uint16_t)(idx);
+ out += 2;
+ break;
+ case 4:
+ *((uint32_t *)out) = model->label[i].color;
+ out += 4;
+ break;
+ }
+ }
+ out = _m3d_addidx(out, vi_s, vrtxidx[model->label[i].vertexid]);
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text));
+ }
+ if (length) {
+ uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ memcpy( length, &v, sizeof(uint32_t));
+ len += v;
+ }
+ out = NULL;
+ sn = sl = NULL;
+ }
+ /* actions */
+ if (model->numaction && model->action && model->numbone && model->bone && !(flags & M3D_EXP_NOACTION)) {
+ for (j = 0; j < model->numaction; j++) {
+ a = &model->action[j];
+ chunklen = 14 + si_s + a->numframe * (4 + fc_s + maxt * (bi_s + 2 * vi_s));
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "ACTN", 4);
+ length = (uint32_t *)((uint8_t *)h + len + 4);
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, a->name));
+ *((uint16_t *)out) = (uint16_t)(a->numframe);
+ out += 2;
+ *((uint32_t *)out) = (uint32_t)(a->durationmsec);
+ out += 4;
+ for (i = 0; i < a->numframe; i++) {
+ *((uint32_t *)out) = (uint32_t)(a->frame[i].msec);
+ out += 4;
+ out = _m3d_addidx(out, fc_s, a->frame[i].numtransform);
+ for (k = 0; k < a->frame[i].numtransform; k++) {
+ out = _m3d_addidx(out, bi_s, a->frame[i].transform[k].boneid);
+ out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].pos]);
+ out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]);
+ }
+ }
+ uint32_t v = (uint32_t)((uintptr_t)out - (uintptr_t)((uint8_t *)h + len));
+ memcpy( length, &v, sizeof(uint32_t));
+ len += v;
+ out = NULL;
+ }
+ }
+ /* inlined assets */
+ if (model->numinlined && model->inlined && (numproc || (flags & M3D_EXP_INLINE))) {
+ for (j = 0; j < model->numinlined; j++) {
+ if (!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || !model->inlined[j].data)
+ continue;
+ if (!(flags & M3D_EXP_INLINE)) {
+ if (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')
+ continue;
+ for (i = k = 0; i < model->numtexture; i++) {
+ if (!strcmp(model->inlined[j].name, model->texture[i].name)) {
+ k = 1;
+ break;
+ }
+ }
+ if (k) continue;
+ }
+ chunklen = 8 + si_s + model->inlined[j].length;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "ASET", 4);
+ *((uint32_t *)((uint8_t *)h + len + 4)) = chunklen;
+ out = (uint8_t *)h + len + 8;
+ out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name));
+ memcpy(out, model->inlined[j].data, model->inlined[j].length);
+ out = NULL;
+ len += chunklen;
+ }
+ }
+ /* extra chunks */
+ if (model->numextra && model->extra && (flags & M3D_EXP_EXTRA)) {
+ for (j = 0; j < model->numextra; j++) {
+ if (!model->extra[j] || model->extra[j]->length < 8)
+ continue;
+ chunklen = model->extra[j]->length;
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + chunklen);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, model->extra[j], chunklen);
+ len += chunklen;
+ }
+ }
+ /* add end chunk */
+ h = (m3dhdr_t *)M3D_REALLOC(h, len + 4);
+ if (!h) goto memerr;
+ memcpy((uint8_t *)h + len, "OMD3", 4);
+ len += 4;
+ /* zlib compress */
+ if (!(flags & M3D_EXP_NOZLIB)) {
+ M3D_LOG("Deflating chunks");
+ z = stbi_zlib_compress((unsigned char *)h, len, (int *)&l, 9);
+ if (z && l > 0 && l < len) {
+ len = l;
+ M3D_FREE(h);
+ h = (m3dhdr_t *)z;
+ }
+ }
+ /* add file header at the beginning */
+ len += 8;
+ out = (unsigned char *)M3D_MALLOC(len);
+ if (!out) goto memerr;
+ memcpy(out, "3DMO", 4);
+ *((uint32_t *)(out + 4)) = len;
+ memcpy(out + 8, h, len - 8);
+ }
+ if (size) *size = out ? len : 0;
+ if (vrtxidx) M3D_FREE(vrtxidx);
+ if (mtrlidx) M3D_FREE(mtrlidx);
+ if (tmapidx) M3D_FREE(tmapidx);
+ if (skinidx) M3D_FREE(skinidx);
+ if (norm) M3D_FREE(norm);
+ if (face) M3D_FREE(face);
+ if (cmap) M3D_FREE(cmap);
+ if (tmap) M3D_FREE(tmap);
+ if (skin) M3D_FREE(skin);
+ if (str) M3D_FREE(str);
+ if (vrtx) M3D_FREE(vrtx);
+ if (h) M3D_FREE(h);
+ return out;
+}
+#endif
+
+#endif /* M3D_IMPLEMENTATION */
+
+#ifdef __cplusplus
+}
+#ifdef M3D_CPPWRAPPER
+#include <memory>
+#include <string>
+#include <vector>
+
+/*** C++ wrapper class ***/
+namespace M3D {
+#ifdef M3D_IMPLEMENTATION
+
+class Model {
+public:
+ m3d_t *model;
+
+public:
+ Model() {
+ this->model = (m3d_t *)malloc(sizeof(m3d_t));
+ memset(this->model, 0, sizeof(m3d_t));
+ }
+ Model(_unused const std::string &data, _unused m3dread_t ReadFileCB,
+ _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) {
+#ifndef M3D_NOIMPORTER
+ this->model = m3d_load((unsigned char *)data.data(), ReadFileCB, FreeCB, mtllib.model);
+#else
+ Model();
+#endif
+ }
+ Model(_unused const std::vector<unsigned char> data, _unused m3dread_t ReadFileCB,
+ _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) {
+#ifndef M3D_NOIMPORTER
+ this->model = m3d_load((unsigned char *)&data[0], ReadFileCB, FreeCB, mtllib.model);
+#else
+ Model();
+#endif
+ }
+ Model(_unused const unsigned char *data, _unused m3dread_t ReadFileCB,
+ _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) {
+#ifndef M3D_NOIMPORTER
+ this->model = m3d_load((unsigned char *)data, ReadFileCB, FreeCB, mtllib.model);
+#else
+ Model();
+#endif
+ }
+ ~Model() { m3d_free(this->model); }
+
+public:
+ m3d_t *getCStruct() { return this->model; }
+ std::string getName() { return std::string(this->model->name); }
+ void setName(std::string name) { this->model->name = (char *)name.c_str(); }
+ std::string getLicense() { return std::string(this->model->license); }
+ void setLicense(std::string license) { this->model->license = (char *)license.c_str(); }
+ std::string getAuthor() { return std::string(this->model->author); }
+ void setAuthor(std::string author) { this->model->author = (char *)author.c_str(); }
+ std::string getDescription() { return std::string(this->model->desc); }
+ void setDescription(std::string desc) { this->model->desc = (char *)desc.c_str(); }
+ float getScale() { return this->model->scale; }
+ void setScale(float scale) { this->model->scale = scale; }
+ std::vector<unsigned char> getPreview() { return this->model->preview.data ?
+ std::vector<unsigned char>(this->model->preview.data, this->model->preview.data + this->model->preview.length) :
+ std::vector<unsigned char>(); }
+ std::vector<uint32_t> getColorMap() { return this->model->cmap ? std::vector<uint32_t>(this->model->cmap,
+ this->model->cmap + this->model->numcmap) :
+ std::vector<uint32_t>(); }
+ std::vector<m3dti_t> getTextureMap() { return this->model->tmap ? std::vector<m3dti_t>(this->model->tmap,
+ this->model->tmap + this->model->numtmap) :
+ std::vector<m3dti_t>(); }
+ std::vector<m3dtx_t> getTextures() { return this->model->texture ? std::vector<m3dtx_t>(this->model->texture,
+ this->model->texture + this->model->numtexture) :
+ std::vector<m3dtx_t>(); }
+ std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ?
+ std::string(this->model->texture[idx].name) :
+ nullptr; }
+ std::vector<m3db_t> getBones() { return this->model->bone ? std::vector<m3db_t>(this->model->bone, this->model->bone +
+ this->model->numbone) :
+ std::vector<m3db_t>(); }
+ std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ?
+ std::string(this->model->bone[idx].name) :
+ nullptr; }
+ std::vector<m3dm_t> getMaterials() { return this->model->material ? std::vector<m3dm_t>(this->model->material,
+ this->model->material + this->model->nummaterial) :
+ std::vector<m3dm_t>(); }
+ std::string getMaterialName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->nummaterial ?
+ std::string(this->model->material[idx].name) :
+ nullptr; }
+ int getMaterialPropertyInt(int idx, int type) {
+ if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 ||
+ !this->model->material[idx].prop) return -1;
+ for (int i = 0; i < this->model->material[idx].numprop; i++) {
+ if (this->model->material[idx].prop[i].type == type)
+ return this->model->material[idx].prop[i].value.num;
+ }
+ return -1;
+ }
+ uint32_t getMaterialPropertyColor(int idx, int type) { return this->getMaterialPropertyInt(idx, type); }
+ float getMaterialPropertyFloat(int idx, int type) {
+ if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 ||
+ !this->model->material[idx].prop) return -1.0f;
+ for (int i = 0; i < this->model->material[idx].numprop; i++) {
+ if (this->model->material[idx].prop[i].type == type)
+ return this->model->material[idx].prop[i].value.fnum;
+ }
+ return -1.0f;
+ }
+ m3dtx_t *getMaterialPropertyMap(int idx, int type) {
+ if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 128 || type > 255 ||
+ !this->model->material[idx].prop) return nullptr;
+ for (int i = 0; i < this->model->material[idx].numprop; i++) {
+ if (this->model->material[idx].prop[i].type == type)
+ return this->model->material[idx].prop[i].value.textureid < this->model->numtexture ?
+ &this->model->texture[this->model->material[idx].prop[i].value.textureid] :
+ nullptr;
+ }
+ return nullptr;
+ }
+ std::vector<m3dv_t> getVertices() { return this->model->vertex ? std::vector<m3dv_t>(this->model->vertex,
+ this->model->vertex + this->model->numvertex) :
+ std::vector<m3dv_t>(); }
+ std::vector<m3df_t> getFace() { return this->model->face ? std::vector<m3df_t>(this->model->face, this->model->face +
+ this->model->numface) :
+ std::vector<m3df_t>(); }
+ std::vector<m3dh_t> getShape() { return this->model->shape ? std::vector<m3dh_t>(this->model->shape,
+ this->model->shape + this->model->numshape) :
+ std::vector<m3dh_t>(); }
+ std::string getShapeName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape &&
+ this->model->shape[idx].name && this->model->shape[idx].name[0] ?
+ std::string(this->model->shape[idx].name) :
+ nullptr; }
+ unsigned int getShapeGroup(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape ?
+ this->model->shape[idx].group :
+ 0xFFFFFFFF; }
+ std::vector<m3dc_t> getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape &&
+ this->model->shape[idx].cmd ?
+ std::vector<m3dc_t>(this->model->shape[idx].cmd, this->model->shape[idx].cmd +
+ this->model->shape[idx].numcmd) :
+ std::vector<m3dc_t>(); }
+ std::vector<m3dl_t> getAnnotationLabels() { return this->model->label ? std::vector<m3dl_t>(this->model->label,
+ this->model->label + this->model->numlabel) :
+ std::vector<m3dl_t>(); }
+ std::vector<m3ds_t> getSkin() { return this->model->skin ? std::vector<m3ds_t>(this->model->skin, this->model->skin +
+ this->model->numskin) :
+ std::vector<m3ds_t>(); }
+ std::vector<m3da_t> getActions() { return this->model->action ? std::vector<m3da_t>(this->model->action,
+ this->model->action + this->model->numaction) :
+ std::vector<m3da_t>(); }
+ std::string getActionName(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+ std::string(this->model->action[aidx].name) :
+ nullptr; }
+ unsigned int getActionDuration(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+ this->model->action[aidx].durationmsec :
+ 0; }
+ std::vector<m3dfr_t> getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+ std::vector<m3dfr_t>(this->model->action[aidx].frame, this->model->action[aidx].frame +
+ this->model->action[aidx].numframe) :
+ std::vector<m3dfr_t>(); }
+ unsigned int getActionFrameTimestamp(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ?
+ (fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ?
+ this->model->action[aidx].frame[fidx].msec :
+ 0) :
+ 0; }
+ std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx) {
+ return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? (
+ fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ?
+ std::vector<m3dtr_t>(this->model->action[aidx].frame[fidx].transform,
+ this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) :
+ std::vector<m3dtr_t>()) :
+ std::vector<m3dtr_t>();
+ }
+ std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton) {
+ m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx,
+ skeleton.size() ? &skeleton[0] : nullptr);
+ return std::vector<m3dtr_t>(pose, pose + this->model->numbone);
+ }
+ std::vector<m3db_t> getActionPose(int aidx, unsigned int msec) {
+ m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec);
+ return std::vector<m3db_t>(pose, pose + this->model->numbone);
+ }
+ std::vector<m3di_t> getInlinedAssets() { return this->model->inlined ? std::vector<m3di_t>(this->model->inlined,
+ this->model->inlined + this->model->numinlined) :
+ std::vector<m3di_t>(); }
+ std::vector<std::unique_ptr<m3dchunk_t>> getExtras() { return this->model->extra ?
+ std::vector<std::unique_ptr<m3dchunk_t>>(this->model->extra,
+ this->model->extra + this->model->numextra) :
+ std::vector<std::unique_ptr<m3dchunk_t>>(); }
+ std::vector<unsigned char> Save(_unused int quality, _unused int flags) {
+#ifdef M3D_EXPORTER
+ unsigned int size;
+ unsigned char *ptr = m3d_save(this->model, quality, flags, &size);
+ return ptr && size ? std::vector<unsigned char>(ptr, ptr + size) : std::vector<unsigned char>();
+#else
+ return std::vector<unsigned char>();
+#endif
+ }
+};
+
+#else
+class Model {
+public:
+ m3d_t *model;
+
+public:
+ Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+ Model(const std::vector<unsigned char> data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+ Model(const unsigned char *data, m3dread_t ReadFileCB, m3dfree_t FreeCB);
+ Model();
+ ~Model();
+
+public:
+ m3d_t *getCStruct();
+ std::string getName();
+ void setName(std::string name);
+ std::string getLicense();
+ void setLicense(std::string license);
+ std::string getAuthor();
+ void setAuthor(std::string author);
+ std::string getDescription();
+ void setDescription(std::string desc);
+ float getScale();
+ void setScale(float scale);
+ std::vector<unsigned char> getPreview();
+ std::vector<uint32_t> getColorMap();
+ std::vector<m3dti_t> getTextureMap();
+ std::vector<m3dtx_t> getTextures();
+ std::string getTextureName(int idx);
+ std::vector<m3db_t> getBones();
+ std::string getBoneName(int idx);
+ std::vector<m3dm_t> getMaterials();
+ std::string getMaterialName(int idx);
+ int getMaterialPropertyInt(int idx, int type);
+ uint32_t getMaterialPropertyColor(int idx, int type);
+ float getMaterialPropertyFloat(int idx, int type);
+ m3dtx_t *getMaterialPropertyMap(int idx, int type);
+ std::vector<m3dv_t> getVertices();
+ std::vector<m3df_t> getFace();
+ std::vector<m3dh_t> getShape();
+ std::string getShapeName(int idx);
+ unsigned int getShapeGroup(int idx);
+ std::vector<m3dc_t> getShapeCommands(int idx);
+ std::vector<m3dl_t> getAnnotationLabels();
+ std::vector<m3ds_t> getSkin();
+ std::vector<m3da_t> getActions();
+ std::string getActionName(int aidx);
+ unsigned int getActionDuration(int aidx);
+ std::vector<m3dfr_t> getActionFrames(int aidx);
+ unsigned int getActionFrameTimestamp(int aidx, int fidx);
+ std::vector<m3dtr_t> getActionFrameTransforms(int aidx, int fidx);
+ std::vector<m3dtr_t> getActionFrame(int aidx, int fidx, std::vector<m3dtr_t> skeleton);
+ std::vector<m3db_t> getActionPose(int aidx, unsigned int msec);
+ std::vector<m3di_t> getInlinedAssets();
+ std::vector<std::unique_ptr<m3dchunk_t>> getExtras();
+ std::vector<unsigned char> Save(int quality, int flags);
+};
+
+#endif /* impl */
+} // namespace M3D
+
+#endif /* M3D_CPPWRAPPER */
+
+#if _MSC_VER > 1920 && !defined(__clang__)
+# pragma warning(pop)
+#endif /* _MSC_VER */
+
+#endif /* __cplusplus */
+
+#endif