From cc7e229c3c1ed78a7e2ca2371ffbaad041fe06f5 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Fri, 12 Mar 2021 08:55:11 +0100 Subject: [PATCH 01/17] disable size check --- code/AssetLib/FBX/FBXMeshGeometry.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/FBX/FBXMeshGeometry.cpp b/code/AssetLib/FBX/FBXMeshGeometry.cpp index f2f19d0b1..0a5074fb4 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -508,11 +508,11 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, std::vector uvIndices; ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); - if (uvIndices.size() != vertex_count) { - FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygonVertex mapping: ") - << uvIndices.size() << ", expected " << vertex_count); - return; - } + // if (uvIndices.size() != vertex_count) { + // FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygonVertex mapping: ") + // << uvIndices.size() << ", expected " << vertex_count); + // return; + // } data_out.resize(vertex_count); From c0c7e6a0de94c450275775d8a94582f7a6457871 Mon Sep 17 00:00:00 2001 From: Urs Hanselmann Date: Fri, 12 Mar 2021 14:48:38 +0100 Subject: [PATCH 02/17] trim uvIndices to fix import of Cheetah3D generated fbx files --- code/AssetLib/FBX/FBXMeshGeometry.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/FBX/FBXMeshGeometry.cpp b/code/AssetLib/FBX/FBXMeshGeometry.cpp index 0a5074fb4..fd8a0ff98 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -508,11 +508,17 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, std::vector uvIndices; ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); - // if (uvIndices.size() != vertex_count) { - // FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygonVertex mapping: ") - // << uvIndices.size() << ", expected " << vertex_count); - // return; - // } + if (uvIndices.size() > vertex_count) { + FBXImporter::LogWarn(Formatter::format("trimming length of input array for ByPolygonVertex mapping: ") + << uvIndices.size() << ", expected " << vertex_count); + uvIndices.resize(vertex_count); + } + + if (uvIndices.size() != vertex_count) { + FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygonVertex mapping: ") + << uvIndices.size() << ", expected " << vertex_count); + return; + } data_out.resize(vertex_count); From 8e589221d7cd1e49769671219c2179af9e4ab66f Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Wed, 10 Mar 2021 09:48:12 +0100 Subject: [PATCH 03/17] [gltf2-exporter] Adding FB_ngon_encoding support --- code/AssetLib/glTF2/glTF2Asset.h | 1 + code/AssetLib/glTF2/glTF2AssetWriter.inl | 17 +++++++++++++++++ code/AssetLib/glTF2/glTF2Exporter.cpp | 3 +++ code/PostProcessing/TriangulateProcess.cpp | 11 +++++++++++ 4 files changed, 32 insertions(+) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 0e4ba6eda..37aacd2fb 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -1108,6 +1108,7 @@ public: bool KHR_materials_clearcoat; bool KHR_materials_transmission; bool KHR_draco_mesh_compression; + bool FB_ngon_encoding; } extensionsUsed; //! Keeps info about the required extensions diff --git a/code/AssetLib/glTF2/glTF2AssetWriter.inl b/code/AssetLib/glTF2/glTF2AssetWriter.inl index 166eada0f..aae96d0f7 100644 --- a/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -507,6 +507,19 @@ namespace glTF2 { Mesh::Primitive& p = m.primitives[i]; Value prim; prim.SetObject(); + + // Extensions + { + Value exts; + exts.SetObject(); + + Value FB_ngon_encoding; + FB_ngon_encoding.SetObject(); + + exts.AddMember(StringRef("FB_ngon_encoding"), FB_ngon_encoding, w.mAl); + prim.AddMember("extensions", exts, w.mAl); + } + { prim.AddMember("mode", Value(int(p.mode)).Move(), w.mAl); @@ -874,6 +887,10 @@ namespace glTF2 { if (this->mAsset.extensionsUsed.KHR_materials_transmission) { exts.PushBack(StringRef("KHR_materials_transmission"), mAl); } + + if (this->mAsset.extensionsUsed.FB_ngon_encoding) { + exts.PushBack(StringRef("FB_ngon_encoding"), mAl); + } } if (!exts.Empty()) diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index aa89e96da..d244e19f5 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -97,6 +97,9 @@ glTF2Exporter::glTF2Exporter(const char* filename, IOSystem* pIOSystem, const ai mAsset.reset( new Asset( pIOSystem ) ); + // Always on as our triangulation process is aware of this type of encoding + mAsset->extensionsUsed.FB_ngon_encoding = true; + if (isBinary) { mAsset->SetAsBinary(); } diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index e971bf85f..f8e63c304 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -217,10 +217,21 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // if it's a simple point,line or triangle: just copy it if( face.mNumIndices <= 3) { + // ngon encoding: making sure that triangles are not recognized as false ngons. + // To do so, we make sure the first indice is not the same as previous triangle emitted. + unsigned int prev_first_indice = (unsigned int)-1; + if (curOut != out) prev_first_indice = (curOut - 1)->mIndices[0]; + aiFace& nface = *curOut++; nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; + if (nface.mIndices[0] == prev_first_indice) { + // rotate indices to avoid ngon encoding false ngons + std::swap(nface.mIndices[0], nface.mIndices[2]); + std::swap(nface.mIndices[1], nface.mIndices[2]); + } + face.mIndices = nullptr; continue; } From 6d1a0c60546d79d65cd2042a34406b2c76fbd883 Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Mon, 29 Mar 2021 15:20:40 +0200 Subject: [PATCH 04/17] Adding quad proper handling --- code/PostProcessing/TriangulateProcess.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index f8e63c304..e5b3dbdf3 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -214,14 +214,14 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) aiFace* const last_face = curOut; + // ngon encoding: making sure that triangles are not recognized as false ngons. + // To do so, we make sure the first indice is not the same as previous triangle emitted. + unsigned int prev_first_indice = (unsigned int)-1; + if (curOut != out) prev_first_indice = (curOut - 1)->mIndices[0]; + // if it's a simple point,line or triangle: just copy it if( face.mNumIndices <= 3) { - // ngon encoding: making sure that triangles are not recognized as false ngons. - // To do so, we make sure the first indice is not the same as previous triangle emitted. - unsigned int prev_first_indice = (unsigned int)-1; - if (curOut != out) prev_first_indice = (curOut - 1)->mIndices[0]; - aiFace& nface = *curOut++; nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; @@ -241,6 +241,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // quads can have at maximum one concave vertex. Determine // this vertex (if it exists) and start tri-fanning from // it. + // + // Due to ngon encoding, if this concave vertex is the same as the previously + // emitted triangle, we use the opposite vertex which also happens to work + // for tri-fanning a concave quad. + // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 unsigned int start_vertex = 0; for (unsigned int i = 0; i < 4; ++i) { const aiVector3D& v0 = verts[face.mIndices[(i+3) % 4]]; @@ -259,8 +264,10 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) const float angle = std::acos(left*diag) + std::acos(right*diag); if (angle > AI_MATH_PI_F) { - // this is the concave point - start_vertex = i; + // i is the concave point + // ngon encoding: if the concave vertex is same as last triangle first index, + // then we chose the opposite vertex. + start_vertex = (face.mIndices[i] != prev_first_indice) ? i : ((i+2) % 4); break; } } From 81019d5e589833d86686acdf4c42a36d6aad622e Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Mon, 29 Mar 2021 15:31:48 +0200 Subject: [PATCH 05/17] Fixing quad encoding --- code/PostProcessing/TriangulateProcess.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index e5b3dbdf3..00a55e35d 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -246,6 +246,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // emitted triangle, we use the opposite vertex which also happens to work // for tri-fanning a concave quad. // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 + unsigned int start_vertex = 0; for (unsigned int i = 0; i < 4; ++i) { const aiVector3D& v0 = verts[face.mIndices[(i+3) % 4]]; @@ -265,13 +266,16 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) const float angle = std::acos(left*diag) + std::acos(right*diag); if (angle > AI_MATH_PI_F) { // i is the concave point - // ngon encoding: if the concave vertex is same as last triangle first index, - // then we chose the opposite vertex. - start_vertex = (face.mIndices[i] != prev_first_indice) ? i : ((i+2) % 4); + start_vertex = i; break; } } + // ngon encoding: if vertex is same as last triangle first index, + // then we chose the opposite vertex (works for both concave & convex quad). + if (face.mIndices[start_vertex] == prev_first_indice) + start_vertex = (start_vertex+2) % 4; + const unsigned int temp[] = {face.mIndices[0], face.mIndices[1], face.mIndices[2], face.mIndices[3]}; aiFace& nface = *curOut++; From 596001c89c672f2dc559e040693c95a893d6907c Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Mon, 29 Mar 2021 19:03:01 +0200 Subject: [PATCH 06/17] Refactoring the extension --- code/AssetLib/glTF2/glTF2Asset.h | 5 +++ code/AssetLib/glTF2/glTF2AssetWriter.inl | 1 + code/AssetLib/glTF2/glTF2Exporter.cpp | 1 + code/PostProcessing/TriangulateProcess.cpp | 38 +++++++++++++--------- include/assimp/mesh.h | 18 ++++++++++ 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 37aacd2fb..85eff4ac8 100644 --- a/code/AssetLib/glTF2/glTF2Asset.h +++ b/code/AssetLib/glTF2/glTF2Asset.h @@ -813,6 +813,11 @@ struct Mesh : public Object { AccessorList position, normal, tangent; }; std::vector targets; + + // extension: FB_ngon_encoding + bool ngonEncoded; + + Primitive(): ngonEncoded(false) {} }; std::vector primitives; diff --git a/code/AssetLib/glTF2/glTF2AssetWriter.inl b/code/AssetLib/glTF2/glTF2AssetWriter.inl index aae96d0f7..01a28d4b7 100644 --- a/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -509,6 +509,7 @@ namespace glTF2 { prim.SetObject(); // Extensions + if (p.ngonEncoded) { Value exts; exts.SetObject(); diff --git a/code/AssetLib/glTF2/glTF2Exporter.cpp b/code/AssetLib/glTF2/glTF2Exporter.cpp index d244e19f5..51aef013d 100644 --- a/code/AssetLib/glTF2/glTF2Exporter.cpp +++ b/code/AssetLib/glTF2/glTF2Exporter.cpp @@ -958,6 +958,7 @@ void glTF2Exporter::ExportMeshes() m->name = name; p.material = mAsset->materials.Get(aim->mMaterialIndex); + p.ngonEncoded = (aim->mPrimitiveTypes & aiPrimitiveType_NGONEncodingFlag) != 0; /******************* Vertices ********************/ Ref v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER); diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index 00a55e35d..c982d9292 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -195,6 +195,13 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) const aiVector3D* verts = pMesh->mVertices; + // NGON encoding note: making sure that triangles are not recognized as false ngons. + // To do so, we make sure the first indice of the new emitted triangle is not the same as previous one. + unsigned int prev_first_indice = (unsigned int)-1; + + // The mesh becomes NGON encoded now, during the triangulation process. + pMesh->mPrimitiveTypes |= aiPrimitiveType_NGONEncodingFlag; + // use std::unique_ptr to avoid slow std::vector specialiations std::unique_ptr done(new bool[max_out]); for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { @@ -214,24 +221,12 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) aiFace* const last_face = curOut; - // ngon encoding: making sure that triangles are not recognized as false ngons. - // To do so, we make sure the first indice is not the same as previous triangle emitted. - unsigned int prev_first_indice = (unsigned int)-1; - if (curOut != out) prev_first_indice = (curOut - 1)->mIndices[0]; - // if it's a simple point,line or triangle: just copy it if( face.mNumIndices <= 3) { aiFace& nface = *curOut++; nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; - - if (nface.mIndices[0] == prev_first_indice) { - // rotate indices to avoid ngon encoding false ngons - std::swap(nface.mIndices[0], nface.mIndices[2]); - std::swap(nface.mIndices[1], nface.mIndices[2]); - } - face.mIndices = nullptr; continue; } @@ -242,7 +237,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // this vertex (if it exists) and start tri-fanning from // it. // - // Due to ngon encoding, if this concave vertex is the same as the previously + // Due to NGON encoding, if this concave vertex is the same as the previously // emitted triangle, we use the opposite vertex which also happens to work // for tri-fanning a concave quad. // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 @@ -265,7 +260,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) const float angle = std::acos(left*diag) + std::acos(right*diag); if (angle > AI_MATH_PI_F) { - // i is the concave point + // this is the concave point start_vertex = i; break; } @@ -306,11 +301,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // modeling suite to make extensive use of highly concave, monster polygons ... // so we need to apply the full 'ear cutting' algorithm to get it right. - // RERQUIREMENT: polygon is expected to be simple and *nearly* planar. + // REQUIREMENT: polygon is expected to be simple and *nearly* planar. // We project it onto a plane to get a 2d triangle. // Collect all vertices of of the polygon. - for (tmp = 0; tmp < max; ++tmp) { + for (tmp = 0; tmp < max; ++tmp) { temp_verts3d[tmp] = verts[idx[tmp]]; } @@ -530,6 +525,17 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) i[0] = idx[i[0]]; i[1] = idx[i[1]]; i[2] = idx[i[2]]; + + // NGON encoding: only quads are supported. + // For everything else, we make sure we don't emit 'false' ngons. We thus avoid having + // 2 consecutive triangles with their first index identical. + if (face.mNumIndices != 4 && i[0] == prev_first_indice) { + // rotate indices + std::swap(i[0], i[2]); + std::swap(i[1], i[2]); + } + + prev_first_indice = i[0]; ++f; } diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 8f17f541d..95651bd17 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -398,6 +398,24 @@ enum aiPrimitiveType { */ aiPrimitiveType_POLYGON = 0x8, + /** + * A flag to determine whether this triangles only mesh is NGON encoded. + * + * NGON encoding is a special encoding that tells whether 2 or more consecutive triangles + * should be considered as a triangle fan. This is identified by looking at the first vertex index. + * 2 consecutive triangles with the same 1st vertex index are part of the same + * NGON. + * + * At the moment, only quads (concave or convex) are supported, meaning that polygons are 'seen' as + * triangles, as usual after a triangulation pass. + * + * To get an NGON encoded mesh, please use the aiProcess_Triangulate post process. + * + * @see aiProcess_Triangulate + * @link https://github.com/KhronosGroup/glTF/pull/1620 + */ + aiPrimitiveType_NGONEncodingFlag = 0x16, + /** This value is not used. It is just here to force the * compiler to map this enum to a 32 Bit integer. */ From 2e90fed5273e89099638b3c400553465cdd0b6a3 Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Tue, 30 Mar 2021 09:06:51 +0200 Subject: [PATCH 07/17] Refactoring and fixing remaining issues in triangulation process --- code/PostProcessing/TriangulateProcess.cpp | 115 ++++++++++++++++----- 1 file changed, 87 insertions(+), 28 deletions(-) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index c982d9292..bc3db799e 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -76,6 +76,69 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; +namespace { + + /** + * @brief Helper struct used to simplify NGON encoding functions. + */ + struct NGONEncoder { + NGONEncoder(const aiFace * outTriArray, size_t outTriArraySize) + : mOutTriArrayBeg(outTriArray), mOutTriArrayEnd(outTriArray + outTriArraySize) {} + + /** + * @brief Encode the current triangle, and make sure it is recognized as a triangle. + * + * This method will rotate indices in tri if needed in order to avoid tri to be considered + * part of the previous ngon. This method is to be used whenever you want to emit a real triangle, + * and make sure it is seen as a triangle. + * + * @param tri Current triangle, must be in bounds of the outTriArray (= be a cell within the array). + */ + void ngonEncodeTriangle(aiFace * tri) const { + ai_assert(tri >= mOutTriArrayBeg && tri < mOutTriArrayEnd); + ai_assert(tri->mNumIndices == 3); + + // Rotate indices in new triangle to avoid ngon encoding false ngons + // Otherwise, the new triangle would be considered part of the previous NGON. + if (isConsideredSameAsLastNgon(tri)) { + std::swap(tri->mIndices[0], tri->mIndices[2]); + std::swap(tri->mIndices[1], tri->mIndices[2]); + } + } + + /** + * @brief Check whether this triangle would be considered part of the lastly emitted ngon or not. + * + * @param tri Current triangle. + * @return true If used as is, this triangle will be part of last ngon. + * @return false If used as is, this triangle is not considered part of the last ngon. + */ + bool isConsideredSameAsLastNgon(const aiFace * tri) const { + ai_assert(tri >= mOutTriArrayBeg && tri < mOutTriArrayEnd); + ai_assert(tri->mNumIndices == 3); + + // First triangle to be emitted, so no problem here + if (tri == mOutTriArrayBeg) return false; + + const aiFace * prevTri = tri - 1; + return tri->mIndices[0] == prevTri->mIndices[0]; + } + + private: + /** + * @brief Begining of triangulation process out triangles array + */ + const aiFace * mOutTriArrayBeg; + + /** + * @brief End of triangulation process out triangles array (out of bounds, a la C++ iterators). + */ + const aiFace * mOutTriArrayEnd; + }; + +} + + // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer TriangulateProcess::TriangulateProcess() @@ -175,10 +238,15 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) pMesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; pMesh->mPrimitiveTypes &= ~aiPrimitiveType_POLYGON; + // The mesh becomes NGON encoded now, during the triangulation process. + pMesh->mPrimitiveTypes |= aiPrimitiveType_NGONEncodingFlag; + aiFace* out = new aiFace[numOut](), *curOut = out; std::vector temp_verts3d(max_out+2); /* temporary storage for vertices */ std::vector temp_verts(max_out+2); + const NGONEncoder ngonEncoder(out, numOut); + // Apply vertex colors to represent the face winding? #ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING if (!pMesh->mColors[0]) @@ -195,13 +263,6 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) const aiVector3D* verts = pMesh->mVertices; - // NGON encoding note: making sure that triangles are not recognized as false ngons. - // To do so, we make sure the first indice of the new emitted triangle is not the same as previous one. - unsigned int prev_first_indice = (unsigned int)-1; - - // The mesh becomes NGON encoded now, during the triangulation process. - pMesh->mPrimitiveTypes |= aiPrimitiveType_NGONEncodingFlag; - // use std::unique_ptr to avoid slow std::vector specialiations std::unique_ptr done(new bool[max_out]); for( unsigned int a = 0; a < pMesh->mNumFaces; a++) { @@ -228,6 +289,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; face.mIndices = nullptr; + ngonEncoder.ngonEncodeTriangle(&nface); continue; } // optimized code for quadrilaterals @@ -236,12 +298,6 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // quads can have at maximum one concave vertex. Determine // this vertex (if it exists) and start tri-fanning from // it. - // - // Due to NGON encoding, if this concave vertex is the same as the previously - // emitted triangle, we use the opposite vertex which also happens to work - // for tri-fanning a concave quad. - // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 - unsigned int start_vertex = 0; for (unsigned int i = 0; i < 4; ++i) { const aiVector3D& v0 = verts[face.mIndices[(i+3) % 4]]; @@ -266,11 +322,6 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) } } - // ngon encoding: if vertex is same as last triangle first index, - // then we chose the opposite vertex (works for both concave & convex quad). - if (face.mIndices[start_vertex] == prev_first_indice) - start_vertex = (start_vertex+2) % 4; - const unsigned int temp[] = {face.mIndices[0], face.mIndices[1], face.mIndices[2], face.mIndices[3]}; aiFace& nface = *curOut++; @@ -281,6 +332,20 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) nface.mIndices[1] = temp[(start_vertex + 1) % 4]; nface.mIndices[2] = temp[(start_vertex + 2) % 4]; + // Due to NGON encoding, if the selected fanning vertex is the same as the previously + // emitted ngon, we use the opposite vertex which also happens to work + // for tri-fanning a concave quad. + // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 + // + // @warning No need to call ngonEncoder.ngonEncodeTriangle() here. We want these 2 faces to be seen as + // a single quad, not 2 separate triangles. This is the whole purpose! + if (ngonEncoder.isConsideredSameAsLastNgon(&nface)) { + start_vertex = (start_vertex+2) % 4; + nface.mIndices[0] = temp[start_vertex]; + nface.mIndices[1] = temp[(start_vertex + 1) % 4]; + nface.mIndices[2] = temp[(start_vertex + 2) % 4]; + } + aiFace& sface = *curOut++; sface.mNumIndices = 3; sface.mIndices = new unsigned int[3]; @@ -526,16 +591,10 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) i[1] = idx[i[1]]; i[2] = idx[i[2]]; - // NGON encoding: only quads are supported. - // For everything else, we make sure we don't emit 'false' ngons. We thus avoid having - // 2 consecutive triangles with their first index identical. - if (face.mNumIndices != 4 && i[0] == prev_first_indice) { - // rotate indices - std::swap(i[0], i[2]); - std::swap(i[1], i[2]); - } - - prev_first_indice = i[0]; + // IMPROVEMENT: Polygons are not supported yet by this ngon encoding + triangulation step. + // So we encode polygons as regular triangles. No way to reconstruct the original + // polygon in this case. + ngonEncoder.ngonEncodeTriangle(f); ++f; } From fc0cf2ea8d29167f330412b817b0619f120cd0a1 Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Tue, 30 Mar 2021 09:19:50 +0200 Subject: [PATCH 08/17] NGON encoding triangles only --- code/PostProcessing/TriangulateProcess.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index bc3db799e..6cc6def4f 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -289,7 +289,8 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) nface.mNumIndices = face.mNumIndices; nface.mIndices = face.mIndices; face.mIndices = nullptr; - ngonEncoder.ngonEncodeTriangle(&nface); + + if (nface.mNumIndices == 3) ngonEncoder.ngonEncodeTriangle(&nface); continue; } // optimized code for quadrilaterals From fc2c12ad6a5ac5995cc79b0ee5d66e65cb9d3383 Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Tue, 30 Mar 2021 09:58:28 +0200 Subject: [PATCH 09/17] Fixing encoding when lines and points are encountered --- code/PostProcessing/TriangulateProcess.cpp | 81 ++++++++++++---------- 1 file changed, 45 insertions(+), 36 deletions(-) diff --git a/code/PostProcessing/TriangulateProcess.cpp b/code/PostProcessing/TriangulateProcess.cpp index 6cc6def4f..0f71320b8 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -82,8 +82,7 @@ namespace { * @brief Helper struct used to simplify NGON encoding functions. */ struct NGONEncoder { - NGONEncoder(const aiFace * outTriArray, size_t outTriArraySize) - : mOutTriArrayBeg(outTriArray), mOutTriArrayEnd(outTriArray + outTriArraySize) {} + NGONEncoder() : mLastNGONFirstIndex((unsigned int)-1) {} /** * @brief Encode the current triangle, and make sure it is recognized as a triangle. @@ -92,10 +91,9 @@ namespace { * part of the previous ngon. This method is to be used whenever you want to emit a real triangle, * and make sure it is seen as a triangle. * - * @param tri Current triangle, must be in bounds of the outTriArray (= be a cell within the array). + * @param tri Triangle to encode. */ - void ngonEncodeTriangle(aiFace * tri) const { - ai_assert(tri >= mOutTriArrayBeg && tri < mOutTriArrayEnd); + void ngonEncodeTriangle(aiFace * tri) { ai_assert(tri->mNumIndices == 3); // Rotate indices in new triangle to avoid ngon encoding false ngons @@ -104,6 +102,40 @@ namespace { std::swap(tri->mIndices[0], tri->mIndices[2]); std::swap(tri->mIndices[1], tri->mIndices[2]); } + + mLastNGONFirstIndex = tri->mIndices[0]; + } + + /** + * @brief Encode a quad (2 triangles) in ngon encoding, and make sure they are seen as a single ngon. + * + * @param tri1 First quad triangle + * @param tri2 Second quad triangle + * + * @pre Triangles must be properly fanned from the most appropriate vertex. + */ + void ngonEncodeQuad(aiFace *tri1, aiFace *tri2) { + ai_assert(tri1->mNumIndices == 3); + ai_assert(tri2->mNumIndices == 3); + ai_assert(tri1->mIndices[0] == tri2->mIndices[0]); + + // If the selected fanning vertex is the same as the previously + // emitted ngon, we use the opposite vertex which also happens to work + // for tri-fanning a concave quad. + // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 + if (isConsideredSameAsLastNgon(tri1)) { + // Right-rotate indices for tri1 (index 2 becomes the new fanning vertex) + std::swap(tri1->mIndices[0], tri1->mIndices[2]); + std::swap(tri1->mIndices[1], tri1->mIndices[2]); + + // Left-rotate indices for tri2 (index 2 becomes the new fanning vertex) + std::swap(tri2->mIndices[1], tri2->mIndices[2]); + std::swap(tri2->mIndices[0], tri2->mIndices[2]); + + ai_assert(tri1->mIndices[0] == tri2->mIndices[0]); + } + + mLastNGONFirstIndex = tri1->mIndices[0]; } /** @@ -114,26 +146,12 @@ namespace { * @return false If used as is, this triangle is not considered part of the last ngon. */ bool isConsideredSameAsLastNgon(const aiFace * tri) const { - ai_assert(tri >= mOutTriArrayBeg && tri < mOutTriArrayEnd); ai_assert(tri->mNumIndices == 3); - - // First triangle to be emitted, so no problem here - if (tri == mOutTriArrayBeg) return false; - - const aiFace * prevTri = tri - 1; - return tri->mIndices[0] == prevTri->mIndices[0]; + return tri->mIndices[0] == mLastNGONFirstIndex; } private: - /** - * @brief Begining of triangulation process out triangles array - */ - const aiFace * mOutTriArrayBeg; - - /** - * @brief End of triangulation process out triangles array (out of bounds, a la C++ iterators). - */ - const aiFace * mOutTriArrayEnd; + unsigned int mLastNGONFirstIndex; }; } @@ -245,7 +263,7 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) std::vector temp_verts3d(max_out+2); /* temporary storage for vertices */ std::vector temp_verts(max_out+2); - const NGONEncoder ngonEncoder(out, numOut); + NGONEncoder ngonEncoder; // Apply vertex colors to represent the face winding? #ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING @@ -290,7 +308,9 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) nface.mIndices = face.mIndices; face.mIndices = nullptr; + // points and lines don't require ngon encoding (and are not supported either!) if (nface.mNumIndices == 3) ngonEncoder.ngonEncodeTriangle(&nface); + continue; } // optimized code for quadrilaterals @@ -333,20 +353,6 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) nface.mIndices[1] = temp[(start_vertex + 1) % 4]; nface.mIndices[2] = temp[(start_vertex + 2) % 4]; - // Due to NGON encoding, if the selected fanning vertex is the same as the previously - // emitted ngon, we use the opposite vertex which also happens to work - // for tri-fanning a concave quad. - // ref: https://github.com/assimp/assimp/pull/3695#issuecomment-805999760 - // - // @warning No need to call ngonEncoder.ngonEncodeTriangle() here. We want these 2 faces to be seen as - // a single quad, not 2 separate triangles. This is the whole purpose! - if (ngonEncoder.isConsideredSameAsLastNgon(&nface)) { - start_vertex = (start_vertex+2) % 4; - nface.mIndices[0] = temp[start_vertex]; - nface.mIndices[1] = temp[(start_vertex + 1) % 4]; - nface.mIndices[2] = temp[(start_vertex + 2) % 4]; - } - aiFace& sface = *curOut++; sface.mNumIndices = 3; sface.mIndices = new unsigned int[3]; @@ -357,6 +363,9 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // prevent double deletion of the indices field face.mIndices = nullptr; + + ngonEncoder.ngonEncodeQuad(&nface, &sface); + continue; } else From 5d09105a4991145f4cf30947e9f6dd363e017eb8 Mon Sep 17 00:00:00 2001 From: Clement JACOB Date: Tue, 30 Mar 2021 20:24:03 +0200 Subject: [PATCH 10/17] Update mesh.h Fixing ngon encoding flag value --- include/assimp/mesh.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 95651bd17..427dba008 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -414,7 +414,7 @@ enum aiPrimitiveType { * @see aiProcess_Triangulate * @link https://github.com/KhronosGroup/glTF/pull/1620 */ - aiPrimitiveType_NGONEncodingFlag = 0x16, + aiPrimitiveType_NGONEncodingFlag = 0x10, /** This value is not used. It is just here to force the * compiler to map this enum to a 32 Bit integer. From 437d7bf8b82bc048b20653a64efd913799988886 Mon Sep 17 00:00:00 2001 From: Garux Date: Mon, 5 Apr 2021 16:39:41 +0300 Subject: [PATCH 11/17] Fix MDC loader placement new was clearing input buffer by constructor, which was nothing to do with adequate parsing changes are not analyzed in depth, but at least test model and models of Wolf:ET are loading and not crashing --- code/AssetLib/MDC/MDCLoader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/AssetLib/MDC/MDCLoader.cpp b/code/AssetLib/MDC/MDCLoader.cpp index 90b88f7da..17a349768 100644 --- a/code/AssetLib/MDC/MDCLoader.cpp +++ b/code/AssetLib/MDC/MDCLoader.cpp @@ -250,7 +250,7 @@ void MDCImporter::InternReadFile( // get the number of valid surfaces BE_NCONST MDC::Surface *pcSurface, *pcSurface2; - pcSurface = pcSurface2 = new (mBuffer + pcHeader->ulOffsetSurfaces) MDC::Surface; + pcSurface = pcSurface2 = reinterpret_cast(mBuffer + pcHeader->ulOffsetSurfaces); unsigned int iNumShaders = 0; for (unsigned int i = 0; i < pcHeader->ulNumSurfaces; ++i) { // validate the surface header @@ -260,7 +260,7 @@ void MDCImporter::InternReadFile( ++pScene->mNumMeshes; } iNumShaders += pcSurface2->ulNumShaders; - pcSurface2 = new ((int8_t *)pcSurface2 + pcSurface2->ulOffsetEnd) MDC::Surface; + pcSurface2 = reinterpret_cast((BE_NCONST int8_t *)pcSurface2 + pcSurface2->ulOffsetEnd); } aszShaders.reserve(iNumShaders); pScene->mMeshes = new aiMesh *[pScene->mNumMeshes]; @@ -405,7 +405,7 @@ void MDCImporter::InternReadFile( pcFaceCur->mIndices[2] = iOutIndex + 0; } - pcSurface = new ((int8_t *)pcSurface + pcSurface->ulOffsetEnd) MDC::Surface; + pcSurface = reinterpret_cast((BE_NCONST int8_t *)pcSurface + pcSurface->ulOffsetEnd); } // create a flat node graph with a root node and one child for each surface From 31e80420714949395e1d971e73bab8f9e97e6954 Mon Sep 17 00:00:00 2001 From: Scott Baldric Date: Mon, 5 Apr 2021 09:59:22 -0500 Subject: [PATCH 12/17] Renaming PI to aiPi. --- include/assimp/MathFunctions.h | 4 ++-- test/unit/AssimpAPITest_aiMatrix4x4.cpp | 2 +- test/unit/AssimpAPITest_aiQuaternion.cpp | 6 +++--- test/unit/MathTest.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/assimp/MathFunctions.h b/include/assimp/MathFunctions.h index 1031eab9d..2088c394c 100644 --- a/include/assimp/MathFunctions.h +++ b/include/assimp/MathFunctions.h @@ -87,7 +87,7 @@ inline IntegerType lcm( IntegerType a, IntegerType b ) { } return a / t * b; } -/// @brief Will return the smallest epsilon-value for the requested type. +/// @brief Will return the smallest epsilon-value for the requested type. /// @return The numercical limit epsilon depending on its type. template inline T getEpsilon() { @@ -97,7 +97,7 @@ inline T getEpsilon() { /// @brief Will return the constant PI for the requested type. /// @return Pi template -inline T PI() { +inline T aiPi() { return static_cast(3.14159265358979323846); } diff --git a/test/unit/AssimpAPITest_aiMatrix4x4.cpp b/test/unit/AssimpAPITest_aiMatrix4x4.cpp index c47da877d..d2f57b19d 100644 --- a/test/unit/AssimpAPITest_aiMatrix4x4.cpp +++ b/test/unit/AssimpAPITest_aiMatrix4x4.cpp @@ -57,7 +57,7 @@ protected: aiMatrix4x4 get_predetermined_transformation_matrix_for_decomposition() const { aiMatrix4x4 t, r; aiMatrix4x4::Translation(aiVector3D(14,-25,-8), t); - aiMatrix4x4::Rotation(Math::PI() / 4.0f, aiVector3D(1).Normalize(), r); + aiMatrix4x4::Rotation(Math::aiPi() / 4.0f, aiVector3D(1).Normalize(), r); return t * r; } diff --git a/test/unit/AssimpAPITest_aiQuaternion.cpp b/test/unit/AssimpAPITest_aiQuaternion.cpp index 93c93f674..79f89337e 100644 --- a/test/unit/AssimpAPITest_aiQuaternion.cpp +++ b/test/unit/AssimpAPITest_aiQuaternion.cpp @@ -59,7 +59,7 @@ TEST_F(AssimpAPITest_aiQuaternion, aiCreateQuaternionFromMatrixTest) { // to prevent running into division by zero. aiMatrix3x3 m, r; aiMatrix3x3::Translation(aiVector2D(14,-25), m); - aiMatrix3x3::RotationZ(Math::PI() / 4.0f, r); + aiMatrix3x3::RotationZ(Math::aiPi() / 4.0f, r); m = m * r; result_cpp = aiQuaternion(m); @@ -127,8 +127,8 @@ TEST_F(AssimpAPITest_aiQuaternion, aiQuaternionInterpolateTest) { // Use predetermined quaternions to prevent division by zero // during slerp calculations. const float INTERPOLATION(0.5f); - const auto q1 = aiQuaternion(aiVector3D(-1,1,1).Normalize(), Math::PI() / 4.0f); - const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::PI() / 2.0f); + const auto q1 = aiQuaternion(aiVector3D(-1,1,1).Normalize(), Math::aiPi() / 4.0f); + const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::aiPi() / 2.0f); aiQuaternion::Interpolate(result_cpp, q1, q2, INTERPOLATION); aiQuaternionInterpolate(&result_c, &q1, &q2, INTERPOLATION); EXPECT_EQ(result_cpp, result_c); diff --git a/test/unit/MathTest.cpp b/test/unit/MathTest.cpp index 88c95006c..ecc23545c 100644 --- a/test/unit/MathTest.cpp +++ b/test/unit/MathTest.cpp @@ -51,6 +51,6 @@ const float AssimpMathTest::Epsilon = Math::getEpsilon(); RandomUniformFloatGenerator AssimpMathTest::RandNonZero(1.0f, 100.0f); // Initialize with an interval of [-PI,PI] inclusively. -RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::PI(), Math::PI()); +RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::aiPi(), Math::aiPi()); } From 7fee91477691df3434a638578607ed5b098d1f32 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 5 Apr 2021 21:24:54 +0200 Subject: [PATCH 13/17] Fix direct leak closes https://github.com/assimp/assimp/issues/3747 --- code/AssetLib/HMP/HMPLoader.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/code/AssetLib/HMP/HMPLoader.cpp b/code/AssetLib/HMP/HMPLoader.cpp index 9151e9d5d..56401f5c9 100644 --- a/code/AssetLib/HMP/HMPLoader.cpp +++ b/code/AssetLib/HMP/HMPLoader.cpp @@ -157,7 +157,10 @@ void HMPImporter::InternReadFile(const std::string &pFile, szBuffer[2] = ((char *)&iMagic)[2]; szBuffer[3] = ((char *)&iMagic)[3]; szBuffer[4] = '\0'; - + + delete[] mBuffer; + mBuffer = nullptr; + // We're definitely unable to load this file throw DeadlyImportError("Unknown HMP subformat ", pFile, ". Magic word (", szBuffer, ") is not known"); From a44ba4141343e18632d159e0f9a9cde8a2d9b224 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Mon, 5 Apr 2021 19:14:48 -0700 Subject: [PATCH 14/17] Fix incorrect indices in the MilkShape 3D loader --- code/AssetLib/MS3D/MS3DLoader.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/code/AssetLib/MS3D/MS3DLoader.cpp b/code/AssetLib/MS3D/MS3DLoader.cpp index 10bc17ea3..192bcbe41 100644 --- a/code/AssetLib/MS3D/MS3DLoader.cpp +++ b/code/AssetLib/MS3D/MS3DLoader.cpp @@ -500,7 +500,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile, throw DeadlyImportError("MS3D: Encountered invalid triangle index, file is malformed"); } - TempTriangle& t = triangles[g.triangles[i]]; + TempTriangle& t = triangles[g.triangles[j]]; f.mIndices = new unsigned int[f.mNumIndices=3]; for (unsigned int k = 0; k < 3; ++k,++n) { @@ -508,7 +508,7 @@ void MS3DImporter::InternReadFile( const std::string& pFile, throw DeadlyImportError("MS3D: Encountered invalid vertex index, file is malformed"); } - const TempVertex& v = vertices[t.indices[i]]; + const TempVertex& v = vertices[t.indices[k]]; for(unsigned int a = 0; a < 4; ++a) { if (v.bone_id[a] != UINT_MAX) { if (v.bone_id[a] >= joints.size()) { @@ -524,9 +524,9 @@ void MS3DImporter::InternReadFile( const std::string& pFile, // collect vertex components m->mVertices[n] = v.pos; - m->mNormals[n] = t.normals[i]; - m->mTextureCoords[0][n] = aiVector3D(t.uv[i].x,1.f-t.uv[i].y,0.0); - f.mIndices[i] = n; + m->mNormals[n] = t.normals[k]; + m->mTextureCoords[0][n] = aiVector3D(t.uv[k].x,1.f-t.uv[k].y,0.0); + f.mIndices[k] = n; } } From d1eeb80e1a7f2ff4b585e0911c000e64f508bb84 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 12 Apr 2021 21:05:46 +0200 Subject: [PATCH 15/17] Not resize empty vectors. closes https://github.com/assimp/assimp/issues/3754 --- include/assimp/XmlParser.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/assimp/XmlParser.h b/include/assimp/XmlParser.h index 2f123c54a..ba1f040d6 100644 --- a/include/assimp/XmlParser.h +++ b/include/assimp/XmlParser.h @@ -50,6 +50,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { +/// @brief Will find a node by its name. struct find_node_by_name_predicate { std::string mName; find_node_by_name_predicate(const std::string &name) : @@ -88,7 +89,11 @@ public: } void clear() { - mData.resize(0); + if(mData.empty() { + mDoc = nullptr; + return; + } + mData.clear(); delete mDoc; mDoc = nullptr; } From a6e4672df1f393b72419269dd1f4419ecdd2b06c Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Mon, 12 Apr 2021 23:19:00 +0200 Subject: [PATCH 16/17] Fix a typo. --- include/assimp/XmlParser.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/assimp/XmlParser.h b/include/assimp/XmlParser.h index ba1f040d6..15c4ff9ff 100644 --- a/include/assimp/XmlParser.h +++ b/include/assimp/XmlParser.h @@ -89,7 +89,7 @@ public: } void clear() { - if(mData.empty() { + if(mData.empty()) { mDoc = nullptr; return; } From bef2304c5357a0ed761b98e7d5ae725fdf8f2591 Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Wed, 14 Apr 2021 08:55:55 +0200 Subject: [PATCH 17/17] Update repo for assimp-net --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 51684c4aa..9fecfedf5 100644 --- a/Readme.md +++ b/Readme.md @@ -39,7 +39,7 @@ Take a look into the https://github.com/assimp/assimp/blob/master/Build.md file. ### Ports ### * [Android](port/AndroidJNI/README.md) * [Python](port/PyAssimp/README.md) -* [.NET](https://github.com/assimp/assimp-net) +* [.NET](https://bitbucket.org/Starnick/assimpnet/src/master/) * [Pascal](port/AssimpPascal/Readme.md) * [Javascript (Alpha)](https://github.com/makc/assimp2json) * [Unity 3d Plugin](https://www.assetstore.unity3d.com/en/#!/content/91777)