From 8e589221d7cd1e49769671219c2179af9e4ab66f Mon Sep 17 00:00:00 2001 From: Clement Jacob Date: Wed, 10 Mar 2021 09:48:12 +0100 Subject: [PATCH 01/11] [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 02/11] 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 03/11] 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 04/11] 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 05/11] 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 06/11] 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 07/11] 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 08/11] 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 010c0853ea30b5ea9d74665ea3b6bc0ebf029d0d Mon Sep 17 00:00:00 2001 From: Kim Kulling Date: Sun, 4 Apr 2021 14:40:50 +0200 Subject: [PATCH 09/11] Update INSTALL --- INSTALL | 37 ++----------------------------------- 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/INSTALL b/INSTALL index ecec2585b..2cfd38078 100644 --- a/INSTALL +++ b/INSTALL @@ -8,43 +8,10 @@ Getting the documentation ------------------------------ A regularly-updated copy is available at -http://assimp.sourceforge.net/lib_html/index.html - -A CHM file is included in the SVN repos: ./doc/AssimpDoc_Html/AssimpDoc.chm. -To build the doxygen documentation on your own, follow these steps: - -a) download & install latest doxygen -b) make sure doxygen is in the executable search path -c) navigate to ./doc -d) and run 'doxygen' - -Open the generated HTML (AssimpDoc_Html/index.html) in the browser of your choice. -Windows only: To generate the CHM doc, install 'Microsoft HTML Workshop' -and configure the path to it in the DOXYFILE first. +https://assimp-docs.readthedocs.io/en/latest/ ------------------------------ Building Assimp ------------------------------ -More detailed build instructions can be found in the documentation, -this section is just for the inpatient among you. - -CMake is the preferred build system for Assimp. The minimum required version -is 2.6. If you don't have it yet, downloads for CMake can be found on -http://www.cmake.org/. - -For Unix: - -1. mkdir build && cd build -2. cmake .. -G 'Unix Makefiles' -3. make -j4 - -For Windows: -1. Open a command prompt -2. mkdir build -3. cd build -4. cmake .. -5. cmake --build . - -For iOS: -Just check the following project, which deploys a compiler toolchain for different iOS-versions: https://github.com/assimp/assimp/tree/master/port/iOS +Just check the build-instaructions which you can find here: https://github.com/assimp/assimp/blob/master/Build.md From 31e80420714949395e1d971e73bab8f9e97e6954 Mon Sep 17 00:00:00 2001 From: Scott Baldric Date: Mon, 5 Apr 2021 09:59:22 -0500 Subject: [PATCH 10/11] 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 11/11] 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");