2
0
Эх сурвалжийг харах

Merge branch 'master' into verboseLogging

Kim Kulling 5 жил өмнө
parent
commit
ecfca1d5c5
78 өөрчлөгдсөн 715 нэмэгдсэн , 204 устгасан
  1. 6 0
      .gitignore
  2. 6 0
      assimpTargets-debug.cmake.in
  3. 6 0
      assimpTargets-release.cmake.in
  4. 53 62
      code/AssetLib/Collada/ColladaExporter.cpp
  5. 4 3
      code/AssetLib/Collada/ColladaExporter.h
  6. 1 1
      code/AssetLib/Collada/ColladaParser.cpp
  7. 8 4
      code/AssetLib/glTF/glTFAssetWriter.inl
  8. 3 3
      code/AssetLib/glTF/glTFCommon.cpp
  9. 1 1
      code/AssetLib/glTF/glTFExporter.cpp
  10. 4 2
      code/AssetLib/glTF2/glTF2Asset.inl
  11. 6 4
      code/AssetLib/glTF2/glTF2AssetWriter.inl
  12. 11 4
      code/AssetLib/glTF2/glTF2Exporter.cpp
  13. 1 1
      code/PostProcessing/FindInvalidDataProcess.cpp
  14. 6 0
      port/assimp_rs/Cargo.lock
  15. 9 0
      port/assimp_rs/Cargo.toml
  16. 1 0
      port/assimp_rs/src/camera/mod.rs
  17. 0 0
      port/assimp_rs/src/core/mod.rs
  18. 0 0
      port/assimp_rs/src/errors/mod.rs
  19. 0 0
      port/assimp_rs/src/formats/mod.rs
  20. 17 0
      port/assimp_rs/src/lib.rs
  21. 0 0
      port/assimp_rs/src/material/mod.rs
  22. 0 0
      port/assimp_rs/src/postprocess/mod.rs
  23. 0 0
      port/assimp_rs/src/shims/mod.rs
  24. 0 0
      port/assimp_rs/src/socket/mod.rs
  25. 44 0
      port/assimp_rs/src/structs/anim/anim.rs
  26. 6 0
      port/assimp_rs/src/structs/anim/mod.rs
  27. 0 0
      port/assimp_rs/src/structs/blob/blob.rs
  28. 2 0
      port/assimp_rs/src/structs/blob/mod.rs
  29. 0 0
      port/assimp_rs/src/structs/bone/bone.rs
  30. 2 0
      port/assimp_rs/src/structs/bone/mod.rs
  31. 0 0
      port/assimp_rs/src/structs/camera/camera.rs
  32. 2 0
      port/assimp_rs/src/structs/camera/mod.rs
  33. 27 0
      port/assimp_rs/src/structs/color/color.rs
  34. 5 0
      port/assimp_rs/src/structs/color/mod.rs
  35. 0 0
      port/assimp_rs/src/structs/face/face.rs
  36. 2 0
      port/assimp_rs/src/structs/face/mod.rs
  37. 0 0
      port/assimp_rs/src/structs/key/key.rs
  38. 2 0
      port/assimp_rs/src/structs/key/mod.rs
  39. 0 0
      port/assimp_rs/src/structs/light/light.rs
  40. 2 0
      port/assimp_rs/src/structs/light/mod.rs
  41. 0 0
      port/assimp_rs/src/structs/material/material.rs
  42. 2 0
      port/assimp_rs/src/structs/material/mod.rs
  43. 64 0
      port/assimp_rs/src/structs/matrix/matrix.rs
  44. 4 0
      port/assimp_rs/src/structs/matrix/mod.rs
  45. 35 0
      port/assimp_rs/src/structs/memory/memory.rs
  46. 2 0
      port/assimp_rs/src/structs/memory/mod.rs
  47. 0 0
      port/assimp_rs/src/structs/mesh/mesh.rs
  48. 3 0
      port/assimp_rs/src/structs/mesh/mod.rs
  49. 0 0
      port/assimp_rs/src/structs/meta/meta.rs
  50. 2 0
      port/assimp_rs/src/structs/meta/mod.rs
  51. 61 0
      port/assimp_rs/src/structs/mod.rs
  52. 2 0
      port/assimp_rs/src/structs/node/mod.rs
  53. 0 0
      port/assimp_rs/src/structs/node/node.rs
  54. 2 0
      port/assimp_rs/src/structs/plane/mod.rs
  55. 23 0
      port/assimp_rs/src/structs/plane/plane.rs
  56. 3 0
      port/assimp_rs/src/structs/quaternion/mod.rs
  57. 7 0
      port/assimp_rs/src/structs/quaternion/quaternion.rs
  58. 2 0
      port/assimp_rs/src/structs/ray/mod.rs
  59. 0 0
      port/assimp_rs/src/structs/ray/ray.rs
  60. 2 0
      port/assimp_rs/src/structs/scene/mod.rs
  61. 0 0
      port/assimp_rs/src/structs/scene/scene.rs
  62. 3 0
      port/assimp_rs/src/structs/string/mod.rs
  63. 41 0
      port/assimp_rs/src/structs/string/string.rs
  64. 3 0
      port/assimp_rs/src/structs/texture/mod.rs
  65. 19 0
      port/assimp_rs/src/structs/texture/texture.rs
  66. 2 0
      port/assimp_rs/src/structs/transform/mod.rs
  67. 0 0
      port/assimp_rs/src/structs/transform/transform.rs
  68. 2 0
      port/assimp_rs/src/structs/vec/mod.rs
  69. 48 0
      port/assimp_rs/src/structs/vec/vec.rs
  70. 2 0
      port/assimp_rs/src/structs/vertex/mod.rs
  71. 0 0
      port/assimp_rs/src/structs/vertex/vertex.rs
  72. 1 2
      test/CMakeLists.txt
  73. BIN
      test/models/glTF2/BoxBadNormals-glTF-Binary/BoxBadNormals.glb
  74. BIN
      test/models/glTF2/BoxWithInfinites-glTF-Binary/BoxWithInfinites.glb
  75. 49 2
      test/unit/utColladaExport.cpp
  76. 0 115
      test/unit/utColladaExportCamera.cpp
  77. 69 0
      test/unit/utColladaImportExport.cpp
  78. 25 0
      test/unit/utglTF2ImportExport.cpp

+ 6 - 0
.gitignore

@@ -79,6 +79,12 @@ test/gtest/src/gtest-stamp/Debug/
 tools/assimp_view/assimp_viewer.vcxproj.user
 *.pyc
 
+### Rust ###
+# Generated by Cargo; will have compiled files and executables
+port/assimp_rs/target/
+# Backup files generated by rustfmt
+port/assimp_rs/**/*.rs.bk
+
 # Unix editor backups
 *~
 test/gtest/src/gtest-stamp/gtest-gitinfo.txt

+ 6 - 0
assimpTargets-debug.cmake.in

@@ -73,6 +73,9 @@ else()
     else()
       set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
     endif()
+
+    # Import target "assimp::assimp" for configuration "Debug"
+    set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_DEBUG "${sharedLibraryName}"
       IMPORTED_LOCATION_DEBUG "@CMAKE_INSTALL_FULL_LIBDIR@/${sharedLibraryName}"
@@ -81,6 +84,9 @@ else()
     list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "@CMAKE_INSTALL_FULL_LIBDIR@/${sharedLibraryName}" )
   else()
     set(staticLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@")
+
+    # Import target "assimp::assimp" for configuration "Debug"
+    set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_LOCATION_DEBUG "@CMAKE_INSTALL_FULL_LIBDIR@/${staticLibraryName}"
     )

+ 6 - 0
assimpTargets-release.cmake.in

@@ -73,6 +73,9 @@ else()
     else()
       set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@")
     endif()
+
+    # Import target "assimp::assimp" for configuration "Release"
+    set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_SONAME_RELEASE "${sharedLibraryName}"
       IMPORTED_LOCATION_RELEASE "@CMAKE_INSTALL_FULL_LIBDIR@/${sharedLibraryName}"
@@ -81,6 +84,9 @@ else()
     list(APPEND _IMPORT_CHECK_FILES_FOR_assimp::assimp "@CMAKE_INSTALL_FULL_LIBDIR@/${sharedLibraryName}" )
   else()
     set(staticLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_STATIC_LIBRARY_SUFFIX@")
+
+    # Import target "assimp::assimp" for configuration "Release"
+    set_property(TARGET assimp::assimp APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
     set_target_properties(assimp::assimp PROPERTIES
       IMPORTED_LOCATION_RELEASE "@CMAKE_INSTALL_FULL_LIBDIR@/${staticLibraryName}"
     )

+ 53 - 62
code/AssetLib/Collada/ColladaExporter.cpp

@@ -117,22 +117,37 @@ static const std::string XMLIDEncode(const std::string &name) {
     return idEncoded.str();
 }
 
+// ------------------------------------------------------------------------------------------------
+// Helper functions to create unique ids
+inline bool IsUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idStr) {
+    return (idSet.find(idStr) == idSet.end());
+}
+
+inline std::string MakeUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idPrefix, const std::string &postfix) {
+    std::string result(idPrefix + postfix);
+    if (!IsUniqueId(idSet, result)) {
+        // Select a number to append
+        size_t idnum = 1;
+        do {
+            result = idPrefix + '_' + to_string(idnum) + postfix;
+            ++idnum;
+        } while (!IsUniqueId(idSet, result));
+    }
+    return result;
+}
+
 // ------------------------------------------------------------------------------------------------
 // Constructor for a specific scene to export
 ColladaExporter::ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file) :
         mIOSystem(pIOSystem),
         mPath(path),
-        mFile(file) {
+        mFile(file),
+        mScene(pScene),
+        endstr("\n") {
     // make sure that all formatting happens using the standard, C locale and not the user's current locale
     mOutput.imbue(std::locale("C"));
     mOutput.precision(ASSIMP_AI_REAL_TEXT_PRECISION);
 
-    mScene = pScene;
-    mSceneOwned = false;
-
-    // set up strings
-    endstr = "\n";
-
     // start writing the file
     WriteFile();
 }
@@ -140,9 +155,6 @@ ColladaExporter::ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, con
 // ------------------------------------------------------------------------------------------------
 // Destructor
 ColladaExporter::~ColladaExporter() {
-    if (mSceneOwned) {
-        delete mScene;
-    }
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -171,10 +183,11 @@ void ColladaExporter::WriteFile() {
     // customized, Writes the animation library
     WriteAnimationsLibrary();
 
-    // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
+    // instantiate the scene(s)
+    // For Assimp there will only ever be one
     mOutput << startstr << "<scene>" << endstr;
     PushTag();
-    mOutput << startstr << "<instance_visual_scene url=\"#" + GetNodeUniqueId(mScene->mRootNode) + "\" />" << endstr;
+    mOutput << startstr << "<instance_visual_scene url=\"#" + mSceneId + "\" />" << endstr;
     PopTag();
     mOutput << startstr << "</scene>" << endstr;
     PopTag();
@@ -209,13 +222,13 @@ void ColladaExporter::WriteHeader() {
     mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
     rotation.Normalize();
 
-    bool add_root_node = false;
+    mAdd_root_node = false;
 
     ai_real scale = 1.0;
     if (std::abs(scaling.x - scaling.y) <= epsilon && std::abs(scaling.x - scaling.z) <= epsilon && std::abs(scaling.y - scaling.z) <= epsilon) {
         scale = (ai_real)((((double)scaling.x) + ((double)scaling.y) + ((double)scaling.z)) / 3.0);
     } else {
-        add_root_node = true;
+        mAdd_root_node = true;
     }
 
     std::string up_axis = "Y_UP";
@@ -226,33 +239,19 @@ void ColladaExporter::WriteHeader() {
     } else if (rotation.Equal(z_rot, epsilon)) {
         up_axis = "Z_UP";
     } else {
-        add_root_node = true;
+        mAdd_root_node = true;
     }
 
     if (!position.Equal(aiVector3D(0, 0, 0))) {
-        add_root_node = true;
+        mAdd_root_node = true;
     }
 
-    if (mScene->mRootNode->mNumChildren == 0) {
-        add_root_node = true;
+    // Assimp root nodes can have meshes, Collada Scenes cannot
+    if (mScene->mRootNode->mNumChildren == 0 || mScene->mRootNode->mMeshes != 0) {
+        mAdd_root_node = true;
     }
 
-    if (add_root_node) {
-        aiScene *scene;
-        SceneCombiner::CopyScene(&scene, mScene);
-
-        aiNode *root = new aiNode("Scene");
-
-        root->mNumChildren = 1;
-        root->mChildren = new aiNode *[root->mNumChildren];
-
-        root->mChildren[0] = scene->mRootNode;
-        scene->mRootNode->mParent = root;
-        scene->mRootNode = root;
-
-        mScene = scene;
-        mSceneOwned = true;
-
+    if (mAdd_root_node) {
         up_axis = "Y_UP";
         scale = 1.0;
     }
@@ -1226,17 +1225,29 @@ void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataTyp
 // ------------------------------------------------------------------------------------------------
 // Writes the scene library
 void ColladaExporter::WriteSceneLibrary() {
-    const std::string sceneId = GetNodeUniqueId(mScene->mRootNode);
-    const std::string sceneName = GetNodeName(mScene->mRootNode);
+    // Determine if we are using the aiScene root or our own
+    std::string sceneName("Scene");
+    if (mAdd_root_node) {
+        mSceneId = MakeUniqueId(mUniqueIds, sceneName, std::string());
+        mUniqueIds.insert(mSceneId);
+    } else {
+        mSceneId = GetNodeUniqueId(mScene->mRootNode);
+        sceneName = GetNodeName(mScene->mRootNode);
+    }
 
     mOutput << startstr << "<library_visual_scenes>" << endstr;
     PushTag();
-    mOutput << startstr << "<visual_scene id=\"" + sceneId + "\" name=\"" + sceneName + "\">" << endstr;
+    mOutput << startstr << "<visual_scene id=\"" + mSceneId + "\" name=\"" + sceneName + "\">" << endstr;
     PushTag();
 
-    // start recursive write at the root node
-    for (size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a)
-        WriteNode(mScene->mRootNode->mChildren[a]);
+    if (mAdd_root_node) {
+        // Export the root node
+        WriteNode(mScene->mRootNode);
+    } else {
+        // Have already exported the root node
+        for (size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a)
+            WriteNode(mScene->mRootNode->mChildren[a]);
+    }
 
     PopTag();
     mOutput << startstr << "</visual_scene>" << endstr;
@@ -1493,12 +1504,9 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
     const std::string node_name = GetNodeName(pNode);
     mOutput << startstr << "<node ";
     if (is_skeleton_root) {
-        mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : ""); // For now, only support one skeleton in a scene.
-        mFoundSkeletonRootNodeID = node_id;
-    } else {
-        mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : "");
+        mFoundSkeletonRootNodeID = node_id; // For now, only support one skeleton in a scene.
     }
-
+    mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : "");
     mOutput << "name=\"" << node_name
             << "\" type=\"" << node_type
             << "\">" << endstr;
@@ -1612,23 +1620,6 @@ void ColladaExporter::WriteNode(const aiNode *pNode) {
     mOutput << startstr << "</node>" << endstr;
 }
 
-inline bool IsUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idStr) {
-    return (idSet.find(idStr) == idSet.end());
-}
-
-inline std::string MakeUniqueId(const std::unordered_set<std::string> &idSet, const std::string &idPrefix, const std::string &postfix) {
-    std::string result(idPrefix + postfix);
-    if (!IsUniqueId(idSet, result)) {
-        // Select a number to append
-        size_t idnum = 1;
-        do {
-            result = idPrefix + '_' + to_string(idnum) + postfix;
-            ++idnum;
-        } while (!IsUniqueId(idSet, result));
-    }
-    return result;
-}
-
 void ColladaExporter::CreateNodeIds(const aiNode *node) {
     GetNodeUniqueId(node);
     for (size_t a = 0; a < node->mNumChildren; ++a)

+ 4 - 3
code/AssetLib/Collada/ColladaExporter.h

@@ -196,13 +196,14 @@ public:
     const std::string mFile;
 
     /// The scene to be written
-    const aiScene *mScene;
-    bool mSceneOwned;
+    const aiScene *const mScene;
+    std::string mSceneId;
+    bool mAdd_root_node = false;
 
     /// current line start string, contains the current indentation for simple stream insertion
     std::string startstr;
     /// current line end string for simple stream insertion
-    std::string endstr;
+    const std::string endstr;
 
     // pair of color and texture - texture precedences color
     struct Surface {

+ 1 - 1
code/AssetLib/Collada/ColladaParser.cpp

@@ -2479,7 +2479,7 @@ void ColladaParser::ReadSceneLibrary() {
 
                 // read name if given.
                 int indexName = TestAttribute("name");
-                const char *attrName = "unnamed";
+                const char *attrName = "Scene";
                 if (indexName > -1)
                     attrName = mReader->getAttributeValue(indexName);
 

+ 8 - 4
code/AssetLib/glTF/glTFAssetWriter.inl

@@ -59,7 +59,7 @@ namespace glTF {
     namespace {
 
         template<typename T, size_t N>
-        inline 
+        inline
         Value& MakeValue(Value& val, T(&r)[N], MemoryPoolAllocator<>& al) {
             val.SetArray();
             val.Reserve(N, al);
@@ -70,7 +70,7 @@ namespace glTF {
         }
 
         template<typename T>
-        inline 
+        inline
         Value& MakeValue(Value& val, const std::vector<T> & r, MemoryPoolAllocator<>& al) {
             val.SetArray();
             val.Reserve(static_cast<rapidjson::SizeType>(r.size()), al);
@@ -530,7 +530,9 @@ namespace glTF {
         StringBuffer docBuffer;
 
         PrettyWriter<StringBuffer> writer(docBuffer);
-        mDoc.Accept(writer);
+        if (!mDoc.Accept(writer)) {
+            throw DeadlyExportError("Failed to write scene data!");
+        }
 
         if (jsonOutFile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) {
             throw DeadlyExportError("Failed to write scene data!");
@@ -569,7 +571,9 @@ namespace glTF {
 
         StringBuffer docBuffer;
         Writer<StringBuffer> writer(docBuffer);
-        mDoc.Accept(writer);
+        if (!mDoc.Accept(writer)) {
+            throw DeadlyExportError("Failed to write scene data!");
+        }
 
         if (outfile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) {
             throw DeadlyExportError("Failed to write scene data!");

+ 3 - 3
code/AssetLib/glTF/glTFCommon.cpp

@@ -145,13 +145,13 @@ bool ParseDataURI(const char *const_uri, size_t uriLen, DataURI &out) {
         size_t i = 5, j;
         if (uri[i] != ';' && uri[i] != ',') { // has media type?
             uri[1] = char(i);
-            for (; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {
+            for (;i < uriLen && uri[i] != ';' && uri[i] != ','; ++i) {
                 // nothing to do!
             }
         }
-        while (uri[i] == ';' && i < uriLen) {
+        while (i < uriLen && uri[i] == ';') {
             uri[i++] = '\0';
-            for (j = i; uri[i] != ';' && uri[i] != ',' && i < uriLen; ++i) {
+            for (j = i; i < uriLen && uri[i] != ';' && uri[i] != ','; ++i) {
                 // nothing to do!
             }
 

+ 1 - 1
code/AssetLib/glTF/glTFExporter.cpp

@@ -348,7 +348,7 @@ void glTFExporter::GetMatColorOrTex(const aiMaterial* mat, glTF::TexProperty& pr
 
                     if (path[0] == '*') { // embedded
                         aiTexture* curTex = mScene->mTextures[atoi(&path[1])];
-						
+
                         prop.texture->source->name = curTex->mFilename.C_Str();
 
                         uint8_t *data = reinterpret_cast<uint8_t *>(curTex->pcData);

+ 4 - 2
code/AssetLib/glTF2/glTF2Asset.inl

@@ -1188,9 +1188,11 @@ inline void Node::Read(Value &obj, Asset &r) {
         }
     }
 
+    // Do not retrieve a skin here, just take a reference, to avoid infinite recursion
+    // Skins will be properly loaded later
     Value *curSkin = FindUInt(obj, "skin");
     if (nullptr != curSkin) {
-        this->skin = r.skins.Retrieve(curSkin->GetUint());
+        this->skin = r.skins.Get(curSkin->GetUint());
     }
 
     Value *curCamera = FindUInt(obj, "camera");
@@ -1483,7 +1485,7 @@ inline void Asset::Load(const std::string &pFile, bool isBinary) {
         }
     }
 
-    // Force reading of skins since they're not always directly referenced
+    // Read skins after nodes have been loaded to avoid infinite recursion
     if (Value *skinsArray = FindArray(doc, "skins")) {
         for (unsigned int i = 0; i < skinsArray->Size(); ++i) {
             skins.Retrieve(i);

+ 6 - 4
code/AssetLib/glTF2/glTF2AssetWriter.inl

@@ -613,7 +613,9 @@ namespace glTF2 {
         StringBuffer docBuffer;
 
         PrettyWriter<StringBuffer> writer(docBuffer);
-        mDoc.Accept(writer);
+        if (!mDoc.Accept(writer)) {
+            throw DeadlyExportError("Failed to write scene data!");
+        }
 
         if (jsonOutFile->Write(docBuffer.GetString(), docBuffer.GetSize(), 1) != 1) {
             throw DeadlyExportError("Failed to write scene data!");
@@ -664,7 +666,9 @@ namespace glTF2 {
 
         StringBuffer docBuffer;
         Writer<StringBuffer> writer(docBuffer);
-        mDoc.Accept(writer);
+        if (!mDoc.Accept(writer)) {
+            throw DeadlyExportError("Failed to write scene data!");
+        }
 
         uint32_t jsonChunkLength = (docBuffer.GetSize() + 3) & ~3; // Round up to next multiple of 4
         auto paddingLength = jsonChunkLength - docBuffer.GetSize();
@@ -816,5 +820,3 @@ namespace glTF2 {
     }
 
 }
-
-

+ 11 - 4
code/AssetLib/glTF2/glTF2Exporter.cpp

@@ -1,4 +1,4 @@
-/*
+/*
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
@@ -172,6 +172,13 @@ void SetAccessorRange(Ref<Accessor> acc, void* data, size_t count,
 		for (unsigned int j = 0 ; j < numCompsOut ; j++) {
 			double valueTmp = buffer_ptr[j];
 
+			// Gracefully tolerate rogue NaN's in buffer data
+			// Any NaNs/Infs introduced in accessor bounds will end up in
+			// document and prevent rapidjson from writing out valid JSON
+			if (!std::isfinite(valueTmp)) {
+				continue;
+			}
+
 			if (valueTmp < acc->min[j]) {
 				acc->min[j] = valueTmp;
 			}
@@ -348,7 +355,7 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref<Texture>& texture, aiTe
 
                     if (path[0] == '*') { // embedded
                         aiTexture* curTex = mScene->mTextures[atoi(&path[1])];
-						
+
                         texture->source->name = curTex->mFilename.C_Str();
 
                         // The asset has its own buffer, see Image::SetData
@@ -751,7 +758,7 @@ void glTF2Exporter::ExportMeshes()
         // Normalize all normals as the validator can emit a warning otherwise
         if ( nullptr != aim->mNormals) {
             for ( auto i = 0u; i < aim->mNumVertices; ++i ) {
-                aim->mNormals[ i ].Normalize();
+                aim->mNormals[ i ].NormalizeSafe();
             }
         }
 
@@ -762,7 +769,7 @@ void glTF2Exporter::ExportMeshes()
         for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
 			if (!aim->HasTextureCoords(i))
 				continue;
-			
+
             // Flip UV y coords
             if (aim -> mNumUVComponents[i] > 1) {
                 for (unsigned int j = 0; j < aim->mNumVertices; ++j) {

+ 1 - 1
code/PostProcessing/FindInvalidDataProcess.cpp

@@ -124,7 +124,7 @@ void FindInvalidDataProcess::Execute(aiScene *pScene) {
         if (2 == result) {
             // remove this mesh
             delete pScene->mMeshes[a];
-            AI_DEBUG_INVALIDATE_PTR(pScene->mMeshes[a]);
+            pScene->mMeshes[a] = NULL;
 
             meshMapping[a] = UINT_MAX;
             continue;

+ 6 - 0
port/assimp_rs/Cargo.lock

@@ -0,0 +1,6 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "assimp_rs"
+version = "0.1.0"
+

+ 9 - 0
port/assimp_rs/Cargo.toml

@@ -0,0 +1,9 @@
+[package]
+name = "assimp_rs"
+version = "0.1.0"
+authors = ["David Golembiowski <[email protected]>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]

+ 1 - 0
port/assimp_rs/src/camera/mod.rs

@@ -0,0 +1 @@
+pub use self::structs::{Camera};

+ 0 - 0
port/assimp_rs/src/core/mod.rs


+ 0 - 0
port/assimp_rs/src/errors/mod.rs


+ 0 - 0
port/assimp_rs/src/formats/mod.rs


+ 17 - 0
port/assimp_rs/src/lib.rs

@@ -0,0 +1,17 @@
+pub mod camera;
+pub mod core;
+pub mod errors;
+pub mod formats;
+pub mod material;
+pub mod postprocess;
+pub mod shims;
+pub mod socket;
+pub mod structs;
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        assert_eq!(true, true);
+    }
+}

+ 0 - 0
port/assimp_rs/src/material/mod.rs


+ 0 - 0
port/assimp_rs/src/postprocess/mod.rs


+ 0 - 0
port/assimp_rs/src/shims/mod.rs


+ 0 - 0
port/assimp_rs/src/socket/mod.rs


+ 44 - 0
port/assimp_rs/src/structs/anim/anim.rs

@@ -0,0 +1,44 @@
+pub struct Animation<'mA, 'mMA, 'nA> {
+    /* The name of the animation. If the modeling package this data was
+     * exported from does support only a single animation channel, this
+     * name is usually empty (length is zero).
+     */
+    m_name: Option<String>,
+    // Duration of the animation in ticks
+    m_duration: f64,
+    // Ticks per second. Zero (0.000... ticks/second) if not
+    // specified in the imported file
+    m_ticks_per_second: Option<f64>,
+    /* Number of bone animation channels.
+       Each channel affects a single node.
+       */
+    m_num_channels: u64,
+    /* Node animation channels. Each channel
+       affects a single node. 
+       ?? -> The array is m_num_channels in size.
+       (maybe refine to a derivative type of usize?)
+       */
+    m_channels: &'nA NodeAnim,
+    /* Number of mesh animation channels. Each
+       channel affects a single mesh and defines
+       vertex-based animation.
+       */
+    m_num_mesh_channels: u64,
+    /* The mesh animation channels. Each channel
+       affects a single mesh.
+       The array is m_num_mesh_channels in size
+       (maybe refine to a derivative of usize?)
+       */
+    m_mesh_channels: &'mA MeshAnim,
+    /* The number of mesh animation channels. Each channel
+       affects a single mesh and defines some morphing animation.
+       */
+    m_num_morph_mesh_channels: u64,
+    /* The morph mesh animation channels. Each channel affects a single mesh.
+       The array is mNumMorphMeshChannels in size.
+       */
+    m_morph_mesh_channels: &'mMA MeshMorphAnim    
+}
+pub struct NodeAnim {}
+pub struct MeshAnim {}
+pub struct MeshMorphAnim {}

+ 6 - 0
port/assimp_rs/src/structs/anim/mod.rs

@@ -0,0 +1,6 @@
+mod anim;
+pub use self::anim::{
+    Animation,
+    NodeAnim,
+    MeshAnim,
+    MeshMorphAnim};

+ 0 - 0
port/assimp_rs/src/structs/blob/blob.rs


+ 2 - 0
port/assimp_rs/src/structs/blob/mod.rs

@@ -0,0 +1,2 @@
+mod blob;
+

+ 0 - 0
port/assimp_rs/src/structs/bone/bone.rs


+ 2 - 0
port/assimp_rs/src/structs/bone/mod.rs

@@ -0,0 +1,2 @@
+mod bone;
+

+ 0 - 0
port/assimp_rs/src/structs/camera/camera.rs


+ 2 - 0
port/assimp_rs/src/structs/camera/mod.rs

@@ -0,0 +1,2 @@
+mod camera;
+

+ 27 - 0
port/assimp_rs/src/structs/color/color.rs

@@ -0,0 +1,27 @@
+#[derive(Clone, Debug, Copy)]
+struct Color3D {
+    r: f32,
+    g: f32,
+    b: f32
+}
+
+impl Color3D {
+    pub fn new(r_f32: f32, g_f32: f32, b_f32: f32) -> Color3D {
+        Color3D {r: r_f32, g: g_f32, b: b_f32 }
+    }
+}
+
+#[derive(Clone, Debug, Copy)]
+struct Color4D {
+    r: f32,
+    g: f32,
+    b: f32,
+    a: f32
+}
+
+impl Color4D {
+    pub fn new(r_f32: f32, g_f32: f32, b_f32: f32, a_f32: f32) -> Color4D {
+        Color4D {r: r_f32, g: g_f32, b: b_f32, a: a_f32 }
+    }
+}
+

+ 5 - 0
port/assimp_rs/src/structs/color/mod.rs

@@ -0,0 +1,5 @@
+mod color;
+pub use self::color::{
+    Color3D,
+    Color4D
+};

+ 0 - 0
port/assimp_rs/src/structs/face/face.rs


+ 2 - 0
port/assimp_rs/src/structs/face/mod.rs

@@ -0,0 +1,2 @@
+mod face;
+

+ 0 - 0
port/assimp_rs/src/structs/key/key.rs


+ 2 - 0
port/assimp_rs/src/structs/key/mod.rs

@@ -0,0 +1,2 @@
+mod key;
+

+ 0 - 0
port/assimp_rs/src/structs/light/light.rs


+ 2 - 0
port/assimp_rs/src/structs/light/mod.rs

@@ -0,0 +1,2 @@
+mod light;
+

+ 0 - 0
port/assimp_rs/src/structs/material/material.rs


+ 2 - 0
port/assimp_rs/src/structs/material/mod.rs

@@ -0,0 +1,2 @@
+mod material;
+

+ 64 - 0
port/assimp_rs/src/structs/matrix/matrix.rs

@@ -0,0 +1,64 @@
+#[derive(Clone, Debug, Copy)]
+struct Matrix3x3 {
+    a1: f32,
+    a2: f32,
+    a3: f32,
+    b1: f32,
+    b2: f32,
+    b3: f32,
+    c1: f32,
+    c2: f32,
+    c3: f32
+}
+
+#[derive(Clone, Debug, Copy)]
+struct Matrix4x4 {
+    a1: f32,
+    a2: f32,
+    a3: f32,
+    a4: f32,
+    b1: f32,
+    b2: f32,
+    b3: f32,
+    b4: f32,
+    c1: f32,
+    c2: f32,
+    c3: f32,
+    c4: f32,
+    d1: f32,
+    d2: f32,
+    d3: f32,
+    d4: f32
+}
+
+impl Matrix3x3 {
+    pub fn new(
+        a1_f32: f32, a2_f32: f32, a3_f32: f32,
+        b1_f32: f32, b2_f32: f32, b3_f32: f32,
+        c1_f32: f32, c2_f32: f32, c3_f32: f32
+    ) -> Matrix3x3 {
+        Matrix3x3 {
+            a1: a1_f32, a2: a2_f32, a3: a3_f32,
+            b1: b1_f32, b2: b2_f32, b3: b3_f32,
+            c1: c1_f32, c2: c2_f32, c3: c3_f32
+        }
+    }
+}
+
+impl Matrix4x4 {
+    pub fn new(
+        a1_f32: f32, a2_f32: f32, a3_f32: f32, a4_f32: f32,
+        b1_f32: f32, b2_f32: f32, b3_f32: f32, b4_f32: f32,
+        c1_f32: f32, c2_f32: f32, c3_f32: f32, c4_f32: f32,
+        d1_f32: f32, d2_f32: f32, d3_f32: f32, d4_f32: f32
+    ) -> Matrix4x4 {
+        Matrix4x4 {
+            a1: a1_f32, a2: a2_f32, a3: a3_f32, a4: a4_f32,
+            b1: b1_f32, b2: b2_f32, b3: b3_f32, b4: b4_f32,
+            c1: c1_f32, c2: c2_f32, c3: c3_f32, c4: c4_f32,
+            d1: d1_f32, d2: d2_f32, d3: d3_f32, d4: d4_f32 
+        }
+    }
+}
+
+

+ 4 - 0
port/assimp_rs/src/structs/matrix/mod.rs

@@ -0,0 +1,4 @@
+mod matrix;
+pub use self::matrix::{
+    Matrix3x3,
+    Matrix4x4};

+ 35 - 0
port/assimp_rs/src/structs/memory/memory.rs

@@ -0,0 +1,35 @@
+#[derive(Clone, Debug, Copy)]
+struct MemoryInfo {
+    textures: u32,
+    materials: u32,
+    meshes: u32,
+    nodes: u32,
+    animations: u32,
+    cameras: u32,
+    lights: u32,
+    total: u32
+}
+
+impl MemoryInfo {
+    pub fn new(
+            textures_uint: u32,
+            materials_uint: u32,
+            meshes_uint: u32,
+            nodes_uint: u32,
+            animations_uint: u32,
+            cameras_uint: u32,
+            lights_uint: u32,
+            total_uint: u32) -> MemoryInfo {
+        
+        MemoryInfo {
+            textures: textures_uint,
+            materials: materials_uint,
+            meshes: meshes_uint,
+            nodes: nodes_uint,
+            animations: animations_uint,
+            cameras: cameras_uint,
+            lights: lights_uint,
+            total: total_uint
+        }
+    }
+}

+ 2 - 0
port/assimp_rs/src/structs/memory/mod.rs

@@ -0,0 +1,2 @@
+mod memory;
+pub use self::memory::MemoryInfo;

+ 0 - 0
port/assimp_rs/src/structs/mesh/mesh.rs


+ 3 - 0
port/assimp_rs/src/structs/mesh/mod.rs

@@ -0,0 +1,3 @@
+mod mesh;
+
+

+ 0 - 0
port/assimp_rs/src/structs/meta/meta.rs


+ 2 - 0
port/assimp_rs/src/structs/meta/mod.rs

@@ -0,0 +1,2 @@
+mod meta;
+

+ 61 - 0
port/assimp_rs/src/structs/mod.rs

@@ -0,0 +1,61 @@
+mod anim;
+/* Animation
+ * NodeAnim
+ * MeshAnim
+ * MeshMorphAnim
+ */
+mod blob;
+/* ExportDataBlob
+ */
+mod vec;
+/* Vector2d
+ * Vector3d
+ * */
+mod matrix;
+/* Matrix3by3
+ * Matrix4by4
+ */
+mod camera;
+/* Camera */
+mod color;
+/* Color3d
+ * Color4d
+ */
+mod key;
+/* MeshKey
+ * MeshMorphKey
+ * QuatKey
+ * VectorKey
+ */
+mod texel;
+mod plane;
+mod string;
+/* String
+ */
+mod material;
+/* Material
+ * MaterialPropery
+ * MaterialPropertyString
+ */
+mod mem;
+mod quaternion;
+mod face;
+mod vertex_weight;
+mod mesh;
+/* Mesh
+ */
+mod meta;
+/* Metadata
+ * MetadataEntry
+ */
+mod node;
+/* Node
+ * */
+mod light;
+mod texture;
+mod ray;
+mod transform;
+/* UVTransform */
+mod bone;
+mod scene;
+/* Scene */

+ 2 - 0
port/assimp_rs/src/structs/node/mod.rs

@@ -0,0 +1,2 @@
+mod node;
+

+ 0 - 0
port/assimp_rs/src/structs/node/node.rs


+ 2 - 0
port/assimp_rs/src/structs/plane/mod.rs

@@ -0,0 +1,2 @@
+mod plane;
+

+ 23 - 0
port/assimp_rs/src/structs/plane/plane.rs

@@ -0,0 +1,23 @@
+#[derive(Clone, Debug, Copy)]
+struct Plane {
+    a: f32,
+    b: f32,
+    c: f32,
+    d: f32
+}
+
+impl Plane {
+    pub fn new(
+        a_f32: f32,
+        b_f32: f32,
+        c_f32: f32,
+        d_f32: f32
+    ) -> Plane {
+        Plane {
+            a: a_f32,
+            b: b_f32,
+            c: b_f32,
+            d: d_f32
+        }
+    }
+}

+ 3 - 0
port/assimp_rs/src/structs/quaternion/mod.rs

@@ -0,0 +1,3 @@
+mod quaternion;
+
+pub use self::quaternion::Quaternion;

+ 7 - 0
port/assimp_rs/src/structs/quaternion/quaternion.rs

@@ -0,0 +1,7 @@
+use crate::vec;
+
+#[derive(Clone, Debug, Copy)]
+pub struct Quaternion {
+    _coordinates: vec::Vector4d
+
+}

+ 2 - 0
port/assimp_rs/src/structs/ray/mod.rs

@@ -0,0 +1,2 @@
+mod ray;
+

+ 0 - 0
port/assimp_rs/src/structs/ray/ray.rs


+ 2 - 0
port/assimp_rs/src/structs/scene/mod.rs

@@ -0,0 +1,2 @@
+mod scene;
+

+ 0 - 0
port/assimp_rs/src/structs/scene/scene.rs


+ 3 - 0
port/assimp_rs/src/structs/string/mod.rs

@@ -0,0 +1,3 @@
+mod string;
+pub use self::string::MAXLEN;
+pub use self::string::Str;

+ 41 - 0
port/assimp_rs/src/structs/string/string.rs

@@ -0,0 +1,41 @@
+pub const MAXLEN: usize = 1024;
+
+/// Want to consider replacing `Vec<char>`
+/// with a comparable definition at 
+/// https://doc.rust-lang.org/src/alloc/string.rs.html#415-417
+#[derive(Clone, Debug)]
+struct Str {
+    length: usize,
+    data: Vec<char>
+}
+
+impl Str {
+    pub fn new(len_u32: usize, data_string: String) -> Str {
+        Str {
+            length: len_u32,
+            data: data_string.chars().collect()
+        }
+    }
+}
+
+/// MaterialPropertyStr
+/// The size of length is truncated to 4 bytes on a 64-bit platform when used as a 
+/// material property (see MaterialSystem.cpp, as aiMaterial::AddProperty() ).
+#[derive(Clone, Debug)]
+struct MaterialPropertyStr {
+    length: usize,
+    data: Vec<char>
+}
+
+
+impl MaterialPropertyStr {
+    pub fn new(len_u32: usize, data_string: String) -> MaterialPropertyStr {
+        MaterialPropertyStr {
+            length: len_u32,
+            data: data_string.chars().collect()
+        }
+    }
+}
+
+   
+

+ 3 - 0
port/assimp_rs/src/structs/texture/mod.rs

@@ -0,0 +1,3 @@
+mod texture;
+pub use self::texture::Texel;
+

+ 19 - 0
port/assimp_rs/src/structs/texture/texture.rs

@@ -0,0 +1,19 @@
+#[derive(Clone, Debug, Copy)]
+struct Texel {
+    b: u32,
+    g: u32,
+    r: u32,
+    a: u32
+}
+
+impl Texel {
+    pub fn new(b_u32: u32, g_u32: u32,
+               r_u32: u32, a_u32: u32) -> Texel {
+        Texel {
+            b: b_u32,
+            g: g_u32,
+            r: r_u32,
+            a: a_u32
+        }
+    }
+}

+ 2 - 0
port/assimp_rs/src/structs/transform/mod.rs

@@ -0,0 +1,2 @@
+mod transform;
+

+ 0 - 0
port/assimp_rs/src/structs/transform/transform.rs


+ 2 - 0
port/assimp_rs/src/structs/vec/mod.rs

@@ -0,0 +1,2 @@
+mod vec;
+

+ 48 - 0
port/assimp_rs/src/structs/vec/vec.rs

@@ -0,0 +1,48 @@
+struct Vector2d {
+    x: f32,
+    y: f32
+}
+
+struct Vector3d {
+    x: f32,
+    y: f32,
+    z: f32
+}
+
+struct Vector4d {
+    x: f32,
+    y: f32,
+    z: f32,
+    w: f32
+}
+
+impl Vector2d {
+    pub fn new(x_f32: f32, y_f32: f32) -> Vector2d {
+        Vector2d {
+            x: x_f32,
+            y: y_f32
+        }
+    }
+}
+
+impl Vector3d {
+    pub fn new(x_f32: f32, y_f32: f32, z_f32: f32) -> Vector3d {
+        Vector3d {
+            x: x_f32,
+            y: y_f32,
+            z: z_f32
+        }
+    }
+}
+
+impl Vector4d {
+    pub fn new(x_f32: f32, y_f32: f32, z_f32: f32, w_f32: f32) -> Vector4d {
+        Vector4d {
+            x: x_f32,
+            y: y_f32,
+            z: z_f32,
+            w: w_f32
+        }
+    }
+}
+

+ 2 - 0
port/assimp_rs/src/structs/vertex/mod.rs

@@ -0,0 +1,2 @@
+mod vertex;
+// pub use self::vertex::

+ 0 - 0
port/assimp_rs/src/structs/vertex/vertex.rs


+ 1 - 2
test/CMakeLists.txt

@@ -124,8 +124,7 @@ SET( IMPORTERS
   unit/utBlendImportMaterials.cpp
   unit/utBlenderWork.cpp
   unit/utBVHImportExport.cpp
-  unit/utColladaExportCamera.cpp
-  unit/utColladaExportLight.cpp
+  unit/utColladaExport.cpp
   unit/utColladaImportExport.cpp
   unit/utCSMImportExport.cpp
   unit/utB3DImportExport.cpp

BIN
test/models/glTF2/BoxBadNormals-glTF-Binary/BoxBadNormals.glb


BIN
test/models/glTF2/BoxWithInfinites-glTF-Binary/BoxWithInfinites.glb


+ 49 - 2
test/unit/utColladaExportLight.cpp → test/unit/utColladaExport.cpp

@@ -49,7 +49,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #ifndef ASSIMP_BUILD_NO_EXPORT
 
-class ColladaExportLight : public ::testing::Test {
+class utColladaExport : public ::testing::Test {
 public:
     void SetUp() override {
         ex = new Assimp::Exporter();
@@ -58,7 +58,9 @@ public:
 
     void TearDown() override {
         delete ex;
+        ex = nullptr;
         delete im;
+        im = nullptr;
     }
 
 protected:
@@ -66,8 +68,53 @@ protected:
     Assimp::Importer *im;
 };
 
+TEST_F(utColladaExport, testExportCamera) {
+    const char *file = "cameraExp.dae";
+
+    const aiScene *pTest = im->ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/cameras.dae", aiProcess_ValidateDataStructure);
+    ASSERT_NE(nullptr, pTest);
+    ASSERT_TRUE(pTest->HasCameras());
+
+    EXPECT_EQ(AI_SUCCESS, ex->Export(pTest, "collada", file));
+    const unsigned int origNumCams(pTest->mNumCameras);
+    std::unique_ptr<float[]> origFOV(new float[origNumCams]);
+    std::unique_ptr<float[]> orifClipPlaneNear(new float[origNumCams]);
+    std::unique_ptr<float[]> orifClipPlaneFar(new float[origNumCams]);
+    std::unique_ptr<aiString[]> names(new aiString[origNumCams]);
+    std::unique_ptr<aiVector3D[]> pos(new aiVector3D[origNumCams]);
+    for (size_t i = 0; i < origNumCams; i++) {
+        const aiCamera *orig = pTest->mCameras[i];
+        ASSERT_NE(nullptr, orig);
+
+        origFOV[i] = orig->mHorizontalFOV;
+        orifClipPlaneNear[i] = orig->mClipPlaneNear;
+        orifClipPlaneFar[i] = orig->mClipPlaneFar;
+        names[i] = orig->mName;
+        pos[i] = orig->mPosition;
+    }
+    const aiScene *imported = im->ReadFile(file, aiProcess_ValidateDataStructure);
+
+    ASSERT_NE(nullptr, imported);
+
+    EXPECT_TRUE(imported->HasCameras());
+    EXPECT_EQ(origNumCams, imported->mNumCameras);
+
+    for (size_t i = 0; i < imported->mNumCameras; i++) {
+        const aiCamera *read = imported->mCameras[i];
+
+        EXPECT_TRUE(names[i] == read->mName);
+        EXPECT_NEAR(origFOV[i], read->mHorizontalFOV, 0.0001f);
+        EXPECT_FLOAT_EQ(orifClipPlaneNear[i], read->mClipPlaneNear);
+        EXPECT_FLOAT_EQ(orifClipPlaneFar[i], read->mClipPlaneFar);
+
+        EXPECT_FLOAT_EQ(pos[i].x, read->mPosition.x);
+        EXPECT_FLOAT_EQ(pos[i].y, read->mPosition.y);
+        EXPECT_FLOAT_EQ(pos[i].z, read->mPosition.z);
+    }
+}
+
 // ------------------------------------------------------------------------------------------------
-TEST_F(ColladaExportLight, testExportLight) {
+TEST_F(utColladaExport, testExportLight) {
     const char *file = "lightsExp.dae";
 
     const aiScene *pTest = im->ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/lights.dae", aiProcess_ValidateDataStructure);

+ 0 - 115
test/unit/utColladaExportCamera.cpp

@@ -1,115 +0,0 @@
-/*
----------------------------------------------------------------------------
-Open Asset Import Library (assimp)
----------------------------------------------------------------------------
-
-Copyright (c) 2006-2020, assimp team
-
-All rights reserved.
-
-Redistribution and use of this software in source and binary forms,
-with or without modification, are permitted provided that the following
-conditions are met:
-
-* Redistributions of source code must retain the above
-copyright notice, this list of conditions and the
-following disclaimer.
-
-* Redistributions in binary form must reproduce the above
-copyright notice, this list of conditions and the
-following disclaimer in the documentation and/or other
-materials provided with the distribution.
-
-* Neither the name of the assimp team, nor the names of its
-contributors may be used to endorse or promote products
-derived from this software without specific prior
-written permission of the assimp team.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
----------------------------------------------------------------------------
-*/
-#include "UnitTestPCH.h"
-
-#include <assimp/cexport.h>
-#include <assimp/postprocess.h>
-#include <assimp/scene.h>
-#include <assimp/Exporter.hpp>
-#include <assimp/Importer.hpp>
-
-#ifndef ASSIMP_BUILD_NO_EXPORT
-
-class ColladaExportCamera : public ::testing::Test {
-public:
-    void SetUp() override {
-        ex = new Assimp::Exporter();
-        im = new Assimp::Importer();
-    }
-
-    void TearDown() override {
-        delete ex;
-        ex = nullptr;
-        delete im;
-        im = nullptr;
-    }
-
-protected:
-    Assimp::Exporter *ex;
-    Assimp::Importer *im;
-};
-
-TEST_F(ColladaExportCamera, testExportCamera) {
-    const char *file = "cameraExp.dae";
-
-    const aiScene *pTest = im->ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/cameras.dae", aiProcess_ValidateDataStructure);
-    ASSERT_NE(nullptr, pTest);
-    ASSERT_TRUE(pTest->HasCameras());
-
-    EXPECT_EQ(AI_SUCCESS, ex->Export(pTest, "collada", file));
-    const unsigned int origNumCams(pTest->mNumCameras);
-    std::unique_ptr<float[]> origFOV(new float[origNumCams]);
-    std::unique_ptr<float[]> orifClipPlaneNear(new float[origNumCams]);
-    std::unique_ptr<float[]> orifClipPlaneFar(new float[origNumCams]);
-    std::unique_ptr<aiString[]> names(new aiString[origNumCams]);
-    std::unique_ptr<aiVector3D[]> pos(new aiVector3D[origNumCams]);
-    for (size_t i = 0; i < origNumCams; i++) {
-        const aiCamera *orig = pTest->mCameras[i];
-        ASSERT_NE(nullptr, orig);
-
-        origFOV[i] = orig->mHorizontalFOV;
-        orifClipPlaneNear[i] = orig->mClipPlaneNear;
-        orifClipPlaneFar[i] = orig->mClipPlaneFar;
-        names[i] = orig->mName;
-        pos[i] = orig->mPosition;
-    }
-    const aiScene *imported = im->ReadFile(file, aiProcess_ValidateDataStructure);
-
-    ASSERT_NE(nullptr, imported);
-
-    EXPECT_TRUE(imported->HasCameras());
-    EXPECT_EQ(origNumCams, imported->mNumCameras);
-
-    for (size_t i = 0; i < imported->mNumCameras; i++) {
-        const aiCamera *read = imported->mCameras[i];
-
-        EXPECT_TRUE(names[i] == read->mName);
-        EXPECT_NEAR(origFOV[i], read->mHorizontalFOV, 0.0001f);
-        EXPECT_FLOAT_EQ(orifClipPlaneNear[i], read->mClipPlaneNear);
-        EXPECT_FLOAT_EQ(orifClipPlaneFar[i], read->mClipPlaneFar);
-
-        EXPECT_FLOAT_EQ(pos[i].x, read->mPosition.x);
-        EXPECT_FLOAT_EQ(pos[i].y, read->mPosition.y);
-        EXPECT_FLOAT_EQ(pos[i].z, read->mPosition.z);
-    }
-}
-
-#endif // ASSIMP_BUILD_NO_EXPORT

+ 69 - 0
test/unit/utColladaImportExport.cpp

@@ -42,6 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "UnitTestPCH.h"
 
 #include <assimp/ColladaMetaData.h>
+#include <assimp/SceneCombiner.h>
 #include <assimp/commonMetaData.h>
 #include <assimp/postprocess.h>
 #include <assimp/scene.h>
@@ -52,6 +53,20 @@ using namespace Assimp;
 
 class utColladaImportExport : public AbstractImportExportBase {
 public:
+    // Clones the scene in an exception-safe way
+    struct SceneCloner {
+        SceneCloner(const aiScene *scene) {
+            sceneCopy = nullptr;
+            SceneCombiner::CopyScene(&sceneCopy, scene);
+        }
+
+        ~SceneCloner() {
+            delete sceneCopy;
+            sceneCopy = nullptr;
+        }
+        aiScene *sceneCopy;
+    };
+
     virtual bool importerTest() final {
         Assimp::Importer importer;
         const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
@@ -211,6 +226,60 @@ TEST_F(utColladaImportExport, importDaeFromFileTest) {
     EXPECT_TRUE(importerTest());
 }
 
+unsigned int GetMeshUseCount(const aiNode *rootNode) {
+    unsigned int result = rootNode->mNumMeshes;
+    for (unsigned int i = 0; i < rootNode->mNumChildren; ++i) {
+        result += GetMeshUseCount(rootNode->mChildren[i]);
+    }
+    return result;
+}
+
+TEST_F(utColladaImportExport, exportRootNodeMeshTest) {
+    Assimp::Importer importer;
+    Assimp::Exporter exporter;
+    const char *outFile = "exportRootNodeMeshTest_out.dae";
+
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure);
+    ASSERT_TRUE(scene != nullptr) << "Fatal: could not import duck.dae!";
+
+    ASSERT_EQ(0u, scene->mRootNode->mNumMeshes) << "Collada import should not give the root node a mesh";
+
+    {
+        SceneCloner clone(scene);
+        ASSERT_TRUE(clone.sceneCopy != nullptr) << "Fatal: could not copy scene!";
+        // Do this by moving the meshes from the first child that has some
+        aiNode *rootNode = clone.sceneCopy->mRootNode;
+        ASSERT_TRUE(rootNode->mNumChildren > 0) << "Fatal: root has no children";
+        aiNode *meshNode = rootNode->mChildren[0];
+        ASSERT_EQ(1u, meshNode->mNumMeshes) << "Fatal: First child node has no duck mesh";
+
+        // Move the meshes to the parent
+        rootNode->mNumMeshes = meshNode->mNumMeshes;
+        rootNode->mMeshes = new unsigned int[rootNode->mNumMeshes];
+        for (unsigned int i = 0; i < rootNode->mNumMeshes; ++i) {
+            rootNode->mMeshes[i] = meshNode->mMeshes[i];
+        }
+
+        // Remove the meshes from the original node
+        meshNode->mNumMeshes = 0;
+        delete[] meshNode->mMeshes;
+        meshNode->mMeshes = nullptr;
+
+        ASSERT_EQ(AI_SUCCESS, exporter.Export(clone.sceneCopy, "collada", outFile)) << "Fatal: Could not export file";
+    }
+
+    // Reimport and look for meshes
+    scene = importer.ReadFile(outFile, aiProcess_ValidateDataStructure);
+    ASSERT_TRUE(scene != nullptr) << "Fatal: could not reimport!";
+
+    // A Collada root node is not allowed to have a mesh
+    ASSERT_EQ(0u, scene->mRootNode->mNumMeshes) << "Collada reimport should not give the root node a mesh";
+
+    // Walk nodes and counts used meshes
+    // Should be exactly one
+    EXPECT_EQ(1u, GetMeshUseCount(scene->mRootNode)) << "Nodes had unexpected number of meshes in use";
+}
+
 TEST_F(utColladaImportExport, exporterUniqueIdsTest) {
     Assimp::Importer importer;
     Assimp::Exporter exporter;

+ 25 - 0
test/unit/utglTF2ImportExport.cpp

@@ -436,6 +436,31 @@ TEST_F(utglTF2ImportExport, error_string_preserved) {
     ASSERT_NE(error.find("BoxTextured0.bin"), std::string::npos) << "Error string should contain an error about missing .bin file";
 }
 
+TEST_F(utglTF2ImportExport, export_bad_accessor_bounds) {
+    Assimp::Importer importer;
+    Assimp::Exporter exporter;
+    const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxWithInfinites-glTF-Binary/BoxWithInfinites.glb", aiProcess_ValidateDataStructure);
+    ASSERT_NE(scene, nullptr);
+
+    EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxWithInfinites-glTF-Binary/BoxWithInfinites_out.glb"));
+    EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "gltf2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxWithInfinites-glTF-Binary/BoxWithInfinites_out.gltf"));
+}
+
+TEST_F(utglTF2ImportExport, export_normalized_normals) {
+    Assimp::Importer importer;
+    Assimp::Exporter exporter;
+    const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxBadNormals-glTF-Binary/BoxBadNormals.glb", aiProcess_ValidateDataStructure);
+    ASSERT_NE(scene, nullptr);
+    EXPECT_EQ(aiReturn_SUCCESS, exporter.Export(scene, "glb2", ASSIMP_TEST_MODELS_DIR "/glTF2/BoxBadNormals-glTF-Binary/BoxBadNormals_out.glb"));
+
+    // load in again and ensure normal-length normals but no Nan's or Inf's introduced
+    scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/BoxBadNormals-glTF-Binary/BoxBadNormals_out.glb", aiProcess_ValidateDataStructure);
+    for ( auto i = 0u; i < scene->mMeshes[0]->mNumVertices; ++i ) {
+        const auto length = scene->mMeshes[0]->mNormals[i].Length();
+        EXPECT_TRUE(abs(length) < 1e-6 || abs(length - 1) < 1e-6);
+    }
+}
+
 #endif // ASSIMP_BUILD_NO_EXPORT
 
 TEST_F(utglTF2ImportExport, sceneMetadata) {