Browse Source

Merge branch 'master' into ms3d-fixes

Kim Kulling 4 years ago
parent
commit
a3ee377af7

+ 2 - 35
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

+ 4 - 1
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");

+ 6 - 0
code/AssetLib/glTF2/glTF2Asset.h

@@ -813,6 +813,11 @@ struct Mesh : public Object {
             AccessorList position, normal, tangent;
         };
         std::vector<Target> targets;
+
+        // extension: FB_ngon_encoding
+        bool ngonEncoded;
+
+        Primitive(): ngonEncoded(false) {}
     };
 
     std::vector<Primitive> 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

+ 18 - 0
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())

+ 4 - 0
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<Accessor> v = ExportData(*mAsset, meshId, b, aim->mNumVertices, aim->mVertices, AttribType::VEC3, AttribType::VEC3, ComponentType_FLOAT, BufferViewTarget_ARRAY_BUFFER);

+ 100 - 3
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<aiVector3D> temp_verts3d(max_out+2); /* temporary storage for vertices */
     std::vector<aiVector2D> 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;
         }
 

+ 2 - 2
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<class T>
 inline T getEpsilon() {
@@ -97,7 +97,7 @@ inline T getEpsilon() {
 /// @brief  Will return the constant PI for the requested type.
 /// @return Pi
 template<class T>
-inline T PI() {
+inline T aiPi() {
     return static_cast<T>(3.14159265358979323846);
 }
 

+ 18 - 0
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.
      */

+ 1 - 1
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<float>() / 4.0f, aiVector3D(1).Normalize(), r);
+        aiMatrix4x4::Rotation(Math::aiPi<float>() / 4.0f, aiVector3D(1).Normalize(), r);
         return t * r;
     }
 

+ 3 - 3
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<float>() / 4.0f, r);
+    aiMatrix3x3::RotationZ(Math::aiPi<float>() / 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<float>() / 4.0f);
-    const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::PI<float>() / 2.0f);
+    const auto q1 = aiQuaternion(aiVector3D(-1,1,1).Normalize(), Math::aiPi<float>() / 4.0f);
+    const auto q2 = aiQuaternion(aiVector3D(1,2,1).Normalize(), Math::aiPi<float>() / 2.0f);
     aiQuaternion::Interpolate(result_cpp, q1, q2, INTERPOLATION);
     aiQuaternionInterpolate(&result_c, &q1, &q2, INTERPOLATION);
     EXPECT_EQ(result_cpp, result_c);

+ 1 - 1
test/unit/MathTest.cpp

@@ -51,6 +51,6 @@ const float AssimpMathTest::Epsilon = Math::getEpsilon<float>();
 RandomUniformFloatGenerator AssimpMathTest::RandNonZero(1.0f, 100.0f);
 
 // Initialize with an interval of [-PI,PI] inclusively.
-RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::PI<float>(), Math::PI<float>());
+RandomUniformFloatGenerator AssimpMathTest::RandPI(-Math::aiPi<float>(), Math::aiPi<float>());
 
 }