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) diff --git a/code/AssetLib/FBX/FBXMeshGeometry.cpp b/code/AssetLib/FBX/FBXMeshGeometry.cpp index f2f19d0b1..fd8a0ff98 100644 --- a/code/AssetLib/FBX/FBXMeshGeometry.cpp +++ b/code/AssetLib/FBX/FBXMeshGeometry.cpp @@ -508,6 +508,12 @@ void ResolveVertexDataArray(std::vector& data_out, const Scope& source, std::vector uvIndices; ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); + 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); 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"); 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 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; } } diff --git a/code/AssetLib/glTF2/glTF2Asset.h b/code/AssetLib/glTF2/glTF2Asset.h index 0e4ba6eda..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; @@ -1108,6 +1113,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..01a28d4b7 100644 --- a/code/AssetLib/glTF2/glTF2AssetWriter.inl +++ b/code/AssetLib/glTF2/glTF2AssetWriter.inl @@ -507,6 +507,20 @@ namespace glTF2 { Mesh::Primitive& p = m.primitives[i]; Value prim; prim.SetObject(); + + // Extensions + if (p.ngonEncoded) + { + 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 +888,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..51aef013d 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(); } @@ -955,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 e971bf85f..0f71320b8 100644 --- a/code/PostProcessing/TriangulateProcess.cpp +++ b/code/PostProcessing/TriangulateProcess.cpp @@ -76,6 +76,87 @@ 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() : mLastNGONFirstIndex((unsigned int)-1) {} + + /** + * @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 Triangle to encode. + */ + void ngonEncodeTriangle(aiFace * tri) { + 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]); + } + + 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]; + } + + /** + * @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->mNumIndices == 3); + return tri->mIndices[0] == mLastNGONFirstIndex; + } + + private: + unsigned int mLastNGONFirstIndex; + }; + +} + + // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer TriangulateProcess::TriangulateProcess() @@ -175,10 +256,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); + NGONEncoder ngonEncoder; + // Apply vertex colors to represent the face winding? #ifdef AI_BUILD_TRIANGULATE_COLOR_FACE_WINDING if (!pMesh->mColors[0]) @@ -220,8 +306,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) aiFace& nface = *curOut++; nface.mNumIndices = face.mNumIndices; 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 @@ -274,6 +363,9 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) // prevent double deletion of the indices field face.mIndices = nullptr; + + ngonEncoder.ngonEncodeQuad(&nface, &sface); + continue; } else @@ -284,11 +376,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]]; } @@ -508,6 +600,11 @@ bool TriangulateProcess::TriangulateMesh( aiMesh* pMesh) i[0] = idx[i[0]]; i[1] = idx[i[1]]; i[2] = idx[i[2]]; + + // 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; } 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/include/assimp/XmlParser.h b/include/assimp/XmlParser.h index 2f123c54a..15c4ff9ff 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; } diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index 8f17f541d..427dba008 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 = 0x10, + /** This value is not used. It is just here to force the * compiler to map this enum to a 32 Bit integer. */ 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()); }