Browse Source

pull master

Yingying Wang 5 years ago
parent
commit
02cbd36271
86 changed files with 3973 additions and 1539 deletions
  1. 6 0
      .gitignore
  2. 6 0
      assimpTargets-debug.cmake.in
  3. 6 0
      assimpTargets-release.cmake.in
  4. 313 216
      code/AssetLib/Collada/ColladaExporter.cpp
  5. 115 80
      code/AssetLib/Collada/ColladaExporter.h
  6. 8 15
      code/AssetLib/Collada/ColladaHelper.cpp
  7. 185 224
      code/AssetLib/Collada/ColladaHelper.h
  8. 230 249
      code/AssetLib/Collada/ColladaLoader.cpp
  9. 176 327
      code/AssetLib/Collada/ColladaParser.cpp
  10. 235 236
      code/AssetLib/Collada/ColladaParser.h
  11. 8 4
      code/AssetLib/glTF/glTFAssetWriter.inl
  12. 3 3
      code/AssetLib/glTF/glTFCommon.cpp
  13. 1 1
      code/AssetLib/glTF/glTFExporter.cpp
  14. 50 26
      code/AssetLib/glTF2/glTF2Asset.h
  15. 57 25
      code/AssetLib/glTF2/glTF2Asset.inl
  16. 6 4
      code/AssetLib/glTF2/glTF2AssetWriter.inl
  17. 11 4
      code/AssetLib/glTF2/glTF2Exporter.cpp
  18. 1 0
      code/CMakeLists.txt
  19. 1704 0
      code/Collada/ColladaExporter.cpp
  20. 53 0
      include/assimp/ColladaMetaData.h
  21. 3 3
      include/assimp/config.h.in
  22. 6 0
      port/assimp_rs/Cargo.lock
  23. 9 0
      port/assimp_rs/Cargo.toml
  24. 1 0
      port/assimp_rs/src/camera/mod.rs
  25. 0 0
      port/assimp_rs/src/core/mod.rs
  26. 0 0
      port/assimp_rs/src/errors/mod.rs
  27. 0 0
      port/assimp_rs/src/formats/mod.rs
  28. 17 0
      port/assimp_rs/src/lib.rs
  29. 0 0
      port/assimp_rs/src/material/mod.rs
  30. 0 0
      port/assimp_rs/src/postprocess/mod.rs
  31. 0 0
      port/assimp_rs/src/shims/mod.rs
  32. 0 0
      port/assimp_rs/src/socket/mod.rs
  33. 44 0
      port/assimp_rs/src/structs/anim/anim.rs
  34. 6 0
      port/assimp_rs/src/structs/anim/mod.rs
  35. 0 0
      port/assimp_rs/src/structs/blob/blob.rs
  36. 2 0
      port/assimp_rs/src/structs/blob/mod.rs
  37. 0 0
      port/assimp_rs/src/structs/bone/bone.rs
  38. 2 0
      port/assimp_rs/src/structs/bone/mod.rs
  39. 0 0
      port/assimp_rs/src/structs/camera/camera.rs
  40. 2 0
      port/assimp_rs/src/structs/camera/mod.rs
  41. 27 0
      port/assimp_rs/src/structs/color/color.rs
  42. 5 0
      port/assimp_rs/src/structs/color/mod.rs
  43. 0 0
      port/assimp_rs/src/structs/face/face.rs
  44. 2 0
      port/assimp_rs/src/structs/face/mod.rs
  45. 0 0
      port/assimp_rs/src/structs/key/key.rs
  46. 2 0
      port/assimp_rs/src/structs/key/mod.rs
  47. 0 0
      port/assimp_rs/src/structs/light/light.rs
  48. 2 0
      port/assimp_rs/src/structs/light/mod.rs
  49. 0 0
      port/assimp_rs/src/structs/material/material.rs
  50. 2 0
      port/assimp_rs/src/structs/material/mod.rs
  51. 64 0
      port/assimp_rs/src/structs/matrix/matrix.rs
  52. 4 0
      port/assimp_rs/src/structs/matrix/mod.rs
  53. 35 0
      port/assimp_rs/src/structs/memory/memory.rs
  54. 2 0
      port/assimp_rs/src/structs/memory/mod.rs
  55. 0 0
      port/assimp_rs/src/structs/mesh/mesh.rs
  56. 3 0
      port/assimp_rs/src/structs/mesh/mod.rs
  57. 0 0
      port/assimp_rs/src/structs/meta/meta.rs
  58. 2 0
      port/assimp_rs/src/structs/meta/mod.rs
  59. 61 0
      port/assimp_rs/src/structs/mod.rs
  60. 2 0
      port/assimp_rs/src/structs/node/mod.rs
  61. 0 0
      port/assimp_rs/src/structs/node/node.rs
  62. 2 0
      port/assimp_rs/src/structs/plane/mod.rs
  63. 23 0
      port/assimp_rs/src/structs/plane/plane.rs
  64. 3 0
      port/assimp_rs/src/structs/quaternion/mod.rs
  65. 7 0
      port/assimp_rs/src/structs/quaternion/quaternion.rs
  66. 2 0
      port/assimp_rs/src/structs/ray/mod.rs
  67. 0 0
      port/assimp_rs/src/structs/ray/ray.rs
  68. 2 0
      port/assimp_rs/src/structs/scene/mod.rs
  69. 0 0
      port/assimp_rs/src/structs/scene/scene.rs
  70. 3 0
      port/assimp_rs/src/structs/string/mod.rs
  71. 41 0
      port/assimp_rs/src/structs/string/string.rs
  72. 3 0
      port/assimp_rs/src/structs/texture/mod.rs
  73. 19 0
      port/assimp_rs/src/structs/texture/texture.rs
  74. 2 0
      port/assimp_rs/src/structs/transform/mod.rs
  75. 0 0
      port/assimp_rs/src/structs/transform/transform.rs
  76. 2 0
      port/assimp_rs/src/structs/vec/mod.rs
  77. 48 0
      port/assimp_rs/src/structs/vec/vec.rs
  78. 2 0
      port/assimp_rs/src/structs/vertex/mod.rs
  79. 0 0
      port/assimp_rs/src/structs/vertex/vertex.rs
  80. 1 2
      test/CMakeLists.txt
  81. BIN
      test/models/glTF2/BoxBadNormals-glTF-Binary/BoxBadNormals.glb
  82. BIN
      test/models/glTF2/BoxWithInfinites-glTF-Binary/BoxWithInfinites.glb
  83. 49 2
      test/unit/utColladaExport.cpp
  84. 0 115
      test/unit/utColladaExportCamera.cpp
  85. 260 3
      test/unit/utColladaImportExport.cpp
  86. 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}"
     )

+ 313 - 216
code/AssetLib/Collada/ColladaExporter.cpp

@@ -44,8 +44,11 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
 
 #include "ColladaExporter.h"
+
 #include <assimp/Bitmap.h>
+#include <assimp/ColladaMetaData.h>
 #include <assimp/DefaultIOSystem.h>
+#include <assimp/Exceptional.h>
 #include <assimp/MathFunctions.h>
 #include <assimp/SceneCombiner.h>
 #include <assimp/StringUtils.h>
@@ -56,15 +59,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <assimp/Exporter.hpp>
 #include <assimp/IOSystem.hpp>
 
-#include <assimp/Exceptional.h>
-
 #include <ctime>
-#include <iostream>
 #include <memory>
-#include <set>
-#include <vector>
-
-using namespace Assimp;
 
 namespace Assimp {
 
@@ -91,8 +87,6 @@ void ExportSceneCollada(const char *pFile, IOSystem *pIOSystem, const aiScene *p
     outfile->Write(iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()), 1);
 }
 
-} // end of namespace Assimp
-
 // ------------------------------------------------------------------------------------------------
 // Encodes a string into a valid XML ID using the xsd:ID schema qualifications.
 static const std::string XMLIDEncode(const std::string &name) {
@@ -115,7 +109,7 @@ static const std::string XMLIDEncode(const std::string &name) {
         if (strchr(XML_ID_CHARS, *it) != nullptr) {
             idEncoded << *it;
         } else {
-            // Select placeholder character based on invalid character to prevent name collisions
+            // Select placeholder character based on invalid character to reduce ID collisions
             idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
         }
     }
@@ -123,20 +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) {
+        mIOSystem(pIOSystem),
+        mPath(path),
+        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();
 }
@@ -144,9 +155,6 @@ ColladaExporter::ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, con
 // ------------------------------------------------------------------------------------------------
 // Destructor
 ColladaExporter::~ColladaExporter() {
-    if (mSceneOwned) {
-        delete mScene;
-    }
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -161,6 +169,9 @@ void ColladaExporter::WriteFile() {
     WriteTextures();
     WriteHeader();
 
+    // Add node names to the unique id database first so they are most likely to use their names as unique ids
+    CreateNodeIds(mScene->mRootNode);
+
     WriteCamerasLibrary();
     WriteLightsLibrary();
     WriteMaterials();
@@ -172,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=\"#" + XMLIDEncode(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
+    mOutput << startstr << "<instance_visual_scene url=\"#" + mSceneId + "\" />" << endstr;
     PopTag();
     mOutput << startstr << "</scene>" << endstr;
     PopTag();
@@ -201,7 +213,7 @@ void ColladaExporter::WriteHeader() {
 
     static const unsigned int date_nb_chars = 20;
     char date_str[date_nb_chars];
-    std::time_t date = std::time(NULL);
+    std::time_t date = std::time(nullptr);
     std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
 
     aiVector3D scaling;
@@ -210,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";
@@ -227,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;
     }
@@ -352,7 +350,7 @@ void ColladaExporter::WriteTextures() {
             std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char *)texture->achFormatHint);
 
             std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
-            if (outfile == NULL) {
+            if (outfile == nullptr) {
                 throw DeadlyExportError("could not open output texture file: " + mPath + name);
             }
 
@@ -388,10 +386,10 @@ void ColladaExporter::WriteCamerasLibrary() {
 void ColladaExporter::WriteCamera(size_t pIndex) {
 
     const aiCamera *cam = mScene->mCameras[pIndex];
-    const std::string cameraName = XMLEscape(cam->mName.C_Str());
-    const std::string cameraId = XMLIDEncode(cam->mName.C_Str());
+    const std::string cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
+    const std::string cameraName = GetObjectName(AiObjectType::Camera, pIndex);
 
-    mOutput << startstr << "<camera id=\"" << cameraId << "-camera\" name=\"" << cameraName << "\" >" << endstr;
+    mOutput << startstr << "<camera id=\"" << cameraId << "\" name=\"" << cameraName << "\" >" << endstr;
     PushTag();
     mOutput << startstr << "<optics>" << endstr;
     PushTag();
@@ -441,10 +439,10 @@ void ColladaExporter::WriteLightsLibrary() {
 void ColladaExporter::WriteLight(size_t pIndex) {
 
     const aiLight *light = mScene->mLights[pIndex];
-    const std::string lightName = XMLEscape(light->mName.C_Str());
-    const std::string lightId = XMLIDEncode(light->mName.C_Str());
+    const std::string lightId = GetObjectUniqueId(AiObjectType::Light, pIndex);
+    const std::string lightName = GetObjectName(AiObjectType::Light, pIndex);
 
-    mOutput << startstr << "<light id=\"" << lightId << "-light\" name=\""
+    mOutput << startstr << "<light id=\"" << lightId << "\" name=\""
             << lightName << "\" >" << endstr;
     PushTag();
     mOutput << startstr << "<technique_common>" << endstr;
@@ -561,12 +559,11 @@ void ColladaExporter::WriteAmbienttLight(const aiLight *const light) {
 
 // ------------------------------------------------------------------------------------------------
 // Reads a single surface entry from the given material keys
-void ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial *pSrcMat,
-        aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex) {
-    if (pSrcMat->GetTextureCount(pTexture) > 0) {
+bool ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex) {
+    if (pSrcMat.GetTextureCount(pTexture) > 0) {
         aiString texfile;
         unsigned int uvChannel = 0;
-        pSrcMat->GetTexture(pTexture, 0, &texfile, NULL, &uvChannel);
+        pSrcMat.GetTexture(pTexture, 0, &texfile, nullptr, &uvChannel);
 
         std::string index_str(texfile.C_Str());
 
@@ -596,8 +593,9 @@ void ColladaExporter::ReadMaterialSurface(Surface &poSurface, const aiMaterial *
         poSurface.exist = true;
     } else {
         if (pKey)
-            poSurface.exist = pSrcMat->Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
+            poSurface.exist = pSrcMat.Get(pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
     }
+    return poSurface.exist;
 }
 
 // ------------------------------------------------------------------------------------------------
@@ -608,9 +606,9 @@ static bool isalnum_C(char c) {
 
 // ------------------------------------------------------------------------------------------------
 // Writes an image entry for the given surface
-void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &pNameAdd) {
+void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string &imageId) {
     if (!pSurface.texture.empty()) {
-        mOutput << startstr << "<image id=\"" << XMLIDEncode(pNameAdd) << "\">" << endstr;
+        mOutput << startstr << "<image id=\"" << imageId << "\">" << endstr;
         PushTag();
         mOutput << startstr << "<init_from>";
 
@@ -631,14 +629,14 @@ void ColladaExporter::WriteImageEntry(const Surface &pSurface, const std::string
 
 // ------------------------------------------------------------------------------------------------
 // Writes a color-or-texture entry into an effect definition
-void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &pImageName) {
+void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId) {
     if (pSurface.exist) {
         mOutput << startstr << "<" << pTypeName << ">" << endstr;
         PushTag();
         if (pSurface.texture.empty()) {
             mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
         } else {
-            mOutput << startstr << "<texture texture=\"" << XMLIDEncode(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+            mOutput << startstr << "<texture texture=\"" << imageId << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
         }
         PopTag();
         mOutput << startstr << "</" << pTypeName << ">" << endstr;
@@ -647,24 +645,24 @@ void ColladaExporter::WriteTextureColorEntry(const Surface &pSurface, const std:
 
 // ------------------------------------------------------------------------------------------------
 // Writes the two parameters necessary for referencing a texture in an effect entry
-void ColladaExporter::WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &pMatName) {
+void ColladaExporter::WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &materialId) {
     // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
     if (!pSurface.texture.empty()) {
-        mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
+        mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr;
         PushTag();
         mOutput << startstr << "<surface type=\"2D\">" << endstr;
         PushTag();
-        mOutput << startstr << "<init_from>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
+        mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr;
         PopTag();
         mOutput << startstr << "</surface>" << endstr;
         PopTag();
         mOutput << startstr << "</newparam>" << endstr;
 
-        mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
+        mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr;
         PushTag();
         mOutput << startstr << "<sampler2D>" << endstr;
         PushTag();
-        mOutput << startstr << "<source>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
+        mOutput << startstr << "<source>" << materialId << "-" << pTypeName << "-surface</source>" << endstr;
         PopTag();
         mOutput << startstr << "</sampler2D>" << endstr;
         PopTag();
@@ -687,80 +685,63 @@ void ColladaExporter::WriteFloatEntry(const Property &pProperty, const std::stri
 // ------------------------------------------------------------------------------------------------
 // Writes the material setup
 void ColladaExporter::WriteMaterials() {
+    std::vector<Material> materials;
     materials.resize(mScene->mNumMaterials);
 
     /// collect all materials from the scene
     size_t numTextures = 0;
     for (size_t a = 0; a < mScene->mNumMaterials; ++a) {
-        const aiMaterial *mat = mScene->mMaterials[a];
-
-        aiString name;
-        if (mat->Get(AI_MATKEY_NAME, name) != aiReturn_SUCCESS) {
-            name = "mat";
-            materials[a].name = std::string("m") + to_string(a) + name.C_Str();
-        } else {
-            // try to use the material's name if no other material has already taken it, else append #
-            std::string testName = name.C_Str();
-            size_t materialCountWithThisName = 0;
-            for (size_t i = 0; i < a; i++) {
-                if (materials[i].name == testName) {
-                    materialCountWithThisName++;
-                }
-            }
-            if (materialCountWithThisName == 0) {
-                materials[a].name = name.C_Str();
-            } else {
-                materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
-            }
-        }
+        Material &material = materials[a];
+        material.id = GetObjectUniqueId(AiObjectType::Material, a);
+        material.name = GetObjectName(AiObjectType::Material, a);
 
+        const aiMaterial &mat = *(mScene->mMaterials[a]);
         aiShadingMode shading = aiShadingMode_Flat;
-        materials[a].shading_model = "phong";
-        if (mat->Get(AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
+        material.shading_model = "phong";
+        if (mat.Get(AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
             if (shading == aiShadingMode_Phong) {
-                materials[a].shading_model = "phong";
+                material.shading_model = "phong";
             } else if (shading == aiShadingMode_Blinn) {
-                materials[a].shading_model = "blinn";
+                material.shading_model = "blinn";
             } else if (shading == aiShadingMode_NoShading) {
-                materials[a].shading_model = "constant";
+                material.shading_model = "constant";
             } else if (shading == aiShadingMode_Gouraud) {
-                materials[a].shading_model = "lambert";
+                material.shading_model = "lambert";
             }
         }
 
-        ReadMaterialSurface(materials[a].ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
-        if (!materials[a].ambient.texture.empty()) numTextures++;
-        ReadMaterialSurface(materials[a].diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
-        if (!materials[a].diffuse.texture.empty()) numTextures++;
-        ReadMaterialSurface(materials[a].specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR);
-        if (!materials[a].specular.texture.empty()) numTextures++;
-        ReadMaterialSurface(materials[a].emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE);
-        if (!materials[a].emissive.texture.empty()) numTextures++;
-        ReadMaterialSurface(materials[a].reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE);
-        if (!materials[a].reflective.texture.empty()) numTextures++;
-        ReadMaterialSurface(materials[a].transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT);
-        if (!materials[a].transparent.texture.empty()) numTextures++;
-        ReadMaterialSurface(materials[a].normal, mat, aiTextureType_NORMALS, NULL, 0, 0);
-        if (!materials[a].normal.texture.empty()) numTextures++;
-
-        materials[a].shininess.exist = mat->Get(AI_MATKEY_SHININESS, materials[a].shininess.value) == aiReturn_SUCCESS;
-        materials[a].transparency.exist = mat->Get(AI_MATKEY_OPACITY, materials[a].transparency.value) == aiReturn_SUCCESS;
-        materials[a].index_refraction.exist = mat->Get(AI_MATKEY_REFRACTI, materials[a].index_refraction.value) == aiReturn_SUCCESS;
+        if (ReadMaterialSurface(material.ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT))
+            ++numTextures;
+        if (ReadMaterialSurface(material.diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE))
+            ++numTextures;
+        if (ReadMaterialSurface(material.specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR))
+            ++numTextures;
+        if (ReadMaterialSurface(material.emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE))
+            ++numTextures;
+        if (ReadMaterialSurface(material.reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE))
+            ++numTextures;
+        if (ReadMaterialSurface(material.transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT))
+            ++numTextures;
+        if (ReadMaterialSurface(material.normal, mat, aiTextureType_NORMALS, nullptr, 0, 0))
+            ++numTextures;
+
+        material.shininess.exist = mat.Get(AI_MATKEY_SHININESS, material.shininess.value) == aiReturn_SUCCESS;
+        material.transparency.exist = mat.Get(AI_MATKEY_OPACITY, material.transparency.value) == aiReturn_SUCCESS;
+        material.index_refraction.exist = mat.Get(AI_MATKEY_REFRACTI, material.index_refraction.value) == aiReturn_SUCCESS;
     }
 
     // output textures if present
     if (numTextures > 0) {
         mOutput << startstr << "<library_images>" << endstr;
         PushTag();
-        for (std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it) {
-            const Material &mat = *it;
-            WriteImageEntry(mat.ambient, mat.name + "-ambient-image");
-            WriteImageEntry(mat.diffuse, mat.name + "-diffuse-image");
-            WriteImageEntry(mat.specular, mat.name + "-specular-image");
-            WriteImageEntry(mat.emissive, mat.name + "-emission-image");
-            WriteImageEntry(mat.reflective, mat.name + "-reflective-image");
-            WriteImageEntry(mat.transparent, mat.name + "-transparent-image");
-            WriteImageEntry(mat.normal, mat.name + "-normal-image");
+        for (const Material &mat : materials) {
+            WriteImageEntry(mat.ambient, mat.id + "-ambient-image");
+            WriteImageEntry(mat.diffuse, mat.id + "-diffuse-image");
+            WriteImageEntry(mat.specular, mat.id + "-specular-image");
+            WriteImageEntry(mat.emissive, mat.id + "-emission-image");
+            WriteImageEntry(mat.reflective, mat.id + "-reflective-image");
+            WriteImageEntry(mat.transparent, mat.id + "-transparent-image");
+            WriteImageEntry(mat.normal, mat.id + "-normal-image");
         }
         PopTag();
         mOutput << startstr << "</library_images>" << endstr;
@@ -770,40 +751,39 @@ void ColladaExporter::WriteMaterials() {
     if (!materials.empty()) {
         mOutput << startstr << "<library_effects>" << endstr;
         PushTag();
-        for (std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it) {
-            const Material &mat = *it;
+        for (const Material &mat : materials) {
             // this is so ridiculous it must be right
-            mOutput << startstr << "<effect id=\"" << XMLIDEncode(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+            mOutput << startstr << "<effect id=\"" << mat.id << "-fx\" name=\"" << mat.name << "\">" << endstr;
             PushTag();
             mOutput << startstr << "<profile_COMMON>" << endstr;
             PushTag();
 
             // write sampler- and surface params for the texture entries
-            WriteTextureParamEntry(mat.emissive, "emission", mat.name);
-            WriteTextureParamEntry(mat.ambient, "ambient", mat.name);
-            WriteTextureParamEntry(mat.diffuse, "diffuse", mat.name);
-            WriteTextureParamEntry(mat.specular, "specular", mat.name);
-            WriteTextureParamEntry(mat.reflective, "reflective", mat.name);
-            WriteTextureParamEntry(mat.transparent, "transparent", mat.name);
-            WriteTextureParamEntry(mat.normal, "normal", mat.name);
+            WriteTextureParamEntry(mat.emissive, "emission", mat.id);
+            WriteTextureParamEntry(mat.ambient, "ambient", mat.id);
+            WriteTextureParamEntry(mat.diffuse, "diffuse", mat.id);
+            WriteTextureParamEntry(mat.specular, "specular", mat.id);
+            WriteTextureParamEntry(mat.reflective, "reflective", mat.id);
+            WriteTextureParamEntry(mat.transparent, "transparent", mat.id);
+            WriteTextureParamEntry(mat.normal, "normal", mat.id);
 
             mOutput << startstr << "<technique sid=\"standard\">" << endstr;
             PushTag();
             mOutput << startstr << "<" << mat.shading_model << ">" << endstr;
             PushTag();
 
-            WriteTextureColorEntry(mat.emissive, "emission", mat.name + "-emission-sampler");
-            WriteTextureColorEntry(mat.ambient, "ambient", mat.name + "-ambient-sampler");
-            WriteTextureColorEntry(mat.diffuse, "diffuse", mat.name + "-diffuse-sampler");
-            WriteTextureColorEntry(mat.specular, "specular", mat.name + "-specular-sampler");
+            WriteTextureColorEntry(mat.emissive, "emission", mat.id + "-emission-sampler");
+            WriteTextureColorEntry(mat.ambient, "ambient", mat.id + "-ambient-sampler");
+            WriteTextureColorEntry(mat.diffuse, "diffuse", mat.id + "-diffuse-sampler");
+            WriteTextureColorEntry(mat.specular, "specular", mat.id + "-specular-sampler");
             WriteFloatEntry(mat.shininess, "shininess");
-            WriteTextureColorEntry(mat.reflective, "reflective", mat.name + "-reflective-sampler");
-            WriteTextureColorEntry(mat.transparent, "transparent", mat.name + "-transparent-sampler");
+            WriteTextureColorEntry(mat.reflective, "reflective", mat.id + "-reflective-sampler");
+            WriteTextureColorEntry(mat.transparent, "transparent", mat.id + "-transparent-sampler");
             WriteFloatEntry(mat.transparency, "transparency");
             WriteFloatEntry(mat.index_refraction, "index_of_refraction");
 
             if (!mat.normal.texture.empty()) {
-                WriteTextureColorEntry(mat.normal, "bump", mat.name + "-normal-sampler");
+                WriteTextureColorEntry(mat.normal, "bump", mat.id + "-normal-sampler");
             }
 
             PopTag();
@@ -823,9 +803,9 @@ void ColladaExporter::WriteMaterials() {
         PushTag();
         for (std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it) {
             const Material &mat = *it;
-            mOutput << startstr << "<material id=\"" << XMLIDEncode(mat.name) << "\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+            mOutput << startstr << "<material id=\"" << mat.id << "\" name=\"" << mat.name << "\">" << endstr;
             PushTag();
-            mOutput << startstr << "<instance_effect url=\"#" << XMLIDEncode(mat.name) << "-fx\"/>" << endstr;
+            mOutput << startstr << "<instance_effect url=\"#" << mat.id << "-fx\"/>" << endstr;
             PopTag();
             mOutput << startstr << "</material>" << endstr;
         }
@@ -852,20 +832,18 @@ void ColladaExporter::WriteControllerLibrary() {
 // Writes a skin controller of the given mesh
 void ColladaExporter::WriteController(size_t pIndex) {
     const aiMesh *mesh = mScene->mMeshes[pIndex];
-    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
-    const std::string idstrEscaped = XMLIDEncode(idstr);
-
-    if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
+    // Is there a skin controller?
+    if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
         return;
 
-    if (mesh->mNumBones == 0)
-        return;
+    const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
+    const std::string namestr = GetObjectName(AiObjectType::Mesh, pIndex);
 
-    mOutput << startstr << "<controller id=\"" << idstrEscaped << "-skin\" ";
+    mOutput << startstr << "<controller id=\"" << idstr << "-skin\" ";
     mOutput << "name=\"skinCluster" << pIndex << "\">" << endstr;
     PushTag();
 
-    mOutput << startstr << "<skin source=\"#" << idstrEscaped << "\">" << endstr;
+    mOutput << startstr << "<skin source=\"#" << idstr << "\">" << endstr;
     PushTag();
 
     // bind pose matrix
@@ -882,20 +860,20 @@ void ColladaExporter::WriteController(size_t pIndex) {
     PopTag();
     mOutput << startstr << "</bind_shape_matrix>" << endstr;
 
-    mOutput << startstr << "<source id=\"" << idstrEscaped << "-skin-joints\" name=\"" << idstrEscaped << "-skin-joints\">" << endstr;
+    mOutput << startstr << "<source id=\"" << idstr << "-skin-joints\" name=\"" << namestr << "-skin-joints\">" << endstr;
     PushTag();
 
-    mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
+    mOutput << startstr << "<Name_array id=\"" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
 
     for (size_t i = 0; i < mesh->mNumBones; ++i)
-        mOutput << XMLIDEncode(mesh->mBones[i]->mName.C_Str()) << " ";
+        mOutput << GetBoneUniqueId(mesh->mBones[i]) << ' ';
 
     mOutput << "</Name_array>" << endstr;
 
     mOutput << startstr << "<technique_common>" << endstr;
     PushTag();
 
-    mOutput << startstr << "<accessor source=\"#" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr;
+    mOutput << startstr << "<accessor source=\"#" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr;
     PushTag();
 
     mOutput << startstr << "<param name=\"JOINT\" type=\"Name\"></param>" << endstr;
@@ -932,8 +910,8 @@ void ColladaExporter::WriteController(size_t pIndex) {
     mOutput << startstr << "<joints>" << endstr;
     PushTag();
 
-    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstrEscaped << "-skin-joints\"></input>" << endstr;
-    mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstrEscaped << "-skin-bind_poses\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstr << "-skin-bind_poses\"></input>" << endstr;
 
     PopTag();
     mOutput << startstr << "</joints>" << endstr;
@@ -941,8 +919,8 @@ void ColladaExporter::WriteController(size_t pIndex) {
     mOutput << startstr << "<vertex_weights count=\"" << mesh->mNumVertices << "\">" << endstr;
     PushTag();
 
-    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstrEscaped << "-skin-joints\" offset=\"0\"></input>" << endstr;
-    mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstrEscaped << "-skin-weights\" offset=\"1\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstr << "-skin-joints\" offset=\"0\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstr << "-skin-weights\" offset=\"1\"></input>" << endstr;
 
     mOutput << startstr << "<vcount>";
 
@@ -1017,9 +995,8 @@ void ColladaExporter::WriteGeometryLibrary() {
 // Writes the given mesh
 void ColladaExporter::WriteGeometry(size_t pIndex) {
     const aiMesh *mesh = mScene->mMeshes[pIndex];
-    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
-    const std::string geometryName = XMLEscape(idstr);
-    const std::string geometryId = XMLIDEncode(idstr);
+    const std::string geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
+    const std::string geometryName = GetObjectName(AiObjectType::Mesh, pIndex);
 
     if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
         return;
@@ -1032,15 +1009,15 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
     PushTag();
 
     // Positions
-    WriteFloatArray(idstr + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
+    WriteFloatArray(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
     // Normals, if any
     if (mesh->HasNormals())
-        WriteFloatArray(idstr + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
+        WriteFloatArray(geometryId + "-normals", FloatType_Vector, (ai_real *)mesh->mNormals, mesh->mNumVertices);
 
     // texture coords
     for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
         if (mesh->HasTextureCoords(static_cast<unsigned int>(a))) {
-            WriteFloatArray(idstr + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
+            WriteFloatArray(geometryId + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
                     (ai_real *)mesh->mTextureCoords[a], mesh->mNumVertices);
         }
     }
@@ -1048,7 +1025,7 @@ void ColladaExporter::WriteGeometry(size_t pIndex) {
     // vertex colors
     for (size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
         if (mesh->HasVertexColors(static_cast<unsigned int>(a)))
-            WriteFloatArray(idstr + "-color" + to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices);
+            WriteFloatArray(geometryId + "-color" + to_string(a), FloatType_Color, (ai_real *)mesh->mColors[a], mesh->mNumVertices);
     }
 
     // assemble vertex structure
@@ -1248,17 +1225,29 @@ void ColladaExporter::WriteFloatArray(const std::string &pIdString, FloatDataTyp
 // ------------------------------------------------------------------------------------------------
 // Writes the scene library
 void ColladaExporter::WriteSceneLibrary() {
-    const std::string sceneName = XMLEscape(mScene->mRootNode->mName.C_Str());
-    const std::string sceneId = XMLIDEncode(mScene->mRootNode->mName.C_Str());
+    // 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, 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;
@@ -1272,20 +1261,10 @@ void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
     if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0)
         return;
 
-    const std::string animation_name_escaped = XMLEscape(anim->mName.C_Str());
-    std::string idstr = anim->mName.C_Str();
-    std::string ending = std::string("AnimId") + to_string(pIndex);
-    if (idstr.length() >= ending.length()) {
-        if (0 != idstr.compare(idstr.length() - ending.length(), ending.length(), ending)) {
-            idstr = idstr + ending;
-        }
-    } else {
-        idstr = idstr + ending;
-    }
-
-    const std::string idstrEscaped = XMLIDEncode(idstr);
+    const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
+    const std::string idstrEscaped = GetObjectUniqueId(AiObjectType::Animation, pIndex);
 
-    mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
+    mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animationNameEscaped + "\">" << endstr;
     PushTag();
 
     std::string cur_node_idstr;
@@ -1435,20 +1414,21 @@ void ColladaExporter::WriteAnimationsLibrary() {
 }
 // ------------------------------------------------------------------------------------------------
 // Helper to find a bone by name in the scene
-aiBone *findBone(const aiScene *scene, const char *name) {
+aiBone *findBone(const aiScene *scene, const aiString &name) {
     for (size_t m = 0; m < scene->mNumMeshes; m++) {
         aiMesh *mesh = scene->mMeshes[m];
         for (size_t b = 0; b < mesh->mNumBones; b++) {
             aiBone *bone = mesh->mBones[b];
-            if (0 == strcmp(name, bone->mName.C_Str())) {
+            if (name == bone->mName) {
                 return bone;
             }
         }
     }
-    return NULL;
+    return nullptr;
 }
 
 // ------------------------------------------------------------------------------------------------
+// Helper to find the node associated with a bone in the scene
 const aiNode *findBoneNode(const aiNode *aNode, const aiBone *bone) {
     if (aNode && bone && aNode->mName == bone->mName) {
         return aNode;
@@ -1457,15 +1437,17 @@ const aiNode *findBoneNode(const aiNode *aNode, const aiBone *bone) {
     if (aNode && bone) {
         for (unsigned int i = 0; i < aNode->mNumChildren; ++i) {
             aiNode *aChild = aNode->mChildren[i];
-            const aiNode *foundFromChild = 0;
+            const aiNode *foundFromChild = nullptr;
             if (aChild) {
                 foundFromChild = findBoneNode(aChild, bone);
-                if (foundFromChild) return foundFromChild;
+                if (foundFromChild) {
+                    return foundFromChild;
+                }
             }
         }
     }
 
-    return NULL;
+    return nullptr;
 }
 
 const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
@@ -1476,7 +1458,7 @@ const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
 
             const aiNode *node = findBoneNode(scene->mRootNode, bone);
             if (node) {
-                while (node->mParent && findBone(scene, node->mParent->mName.C_Str()) != 0) {
+                while (node->mParent && findBone(scene, node->mParent->mName) != nullptr) {
                     node = node->mParent;
                 }
                 topParentBoneNodes.insert(node);
@@ -1496,45 +1478,36 @@ const aiNode *findSkeletonRootNode(const aiScene *scene, const aiMesh *mesh) {
         }
     }
 
-    return NULL;
+    return nullptr;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Recursively writes the given node
-void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) {
-    // the node must have a name
-    if (pNode->mName.length == 0) {
-        std::stringstream ss;
-        ss << "Node_" << pNode;
-        pNode->mName.Set(ss.str());
-    }
-
+void ColladaExporter::WriteNode(const aiNode *pNode) {
     // If the node is associated with a bone, it is a joint node (JOINT)
     // otherwise it is a normal node (NODE)
+    // Assimp-specific: nodes with no name cannot be associated with bones
     const char *node_type;
     bool is_joint, is_skeleton_root = false;
-    if (nullptr == findBone(pScene, pNode->mName.C_Str())) {
+    if (pNode->mName.length == 0 || nullptr == findBone(mScene, pNode->mName)) {
         node_type = "NODE";
         is_joint = false;
     } else {
         node_type = "JOINT";
         is_joint = true;
-        if (!pNode->mParent || nullptr == findBone(pScene, pNode->mParent->mName.C_Str())) {
+        if (!pNode->mParent || nullptr == findBone(mScene, pNode->mParent->mName)) {
             is_skeleton_root = true;
         }
     }
 
-    const std::string node_id = XMLIDEncode(pNode->mName.data);
-    const std::string node_name = XMLEscape(pNode->mName.data);
+    const std::string node_id = GetNodeUniqueId(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 << " name=\"" << node_name
+    mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : "");
+    mOutput << "name=\"" << node_name
             << "\" type=\"" << node_type
             << "\">" << endstr;
     PushTag();
@@ -1573,14 +1546,14 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) {
         //check if it is a camera node
         for (size_t i = 0; i < mScene->mNumCameras; i++) {
             if (mScene->mCameras[i]->mName == pNode->mName) {
-                mOutput << startstr << "<instance_camera url=\"#" << node_id << "-camera\"/>" << endstr;
+                mOutput << startstr << "<instance_camera url=\"#" << GetObjectUniqueId(AiObjectType::Camera, i) << "\"/>" << endstr;
                 break;
             }
         }
         //check if it is a light node
         for (size_t i = 0; i < mScene->mNumLights; i++) {
             if (mScene->mLights[i]->mName == pNode->mName) {
-                mOutput << startstr << "<instance_light url=\"#" << node_id << "-light\"/>" << endstr;
+                mOutput << startstr << "<instance_light url=\"#" << GetObjectUniqueId(AiObjectType::Light, i) << "\"/>" << endstr;
                 break;
             }
         }
@@ -1593,22 +1566,22 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) {
             if (mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
                 continue;
 
-            const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str();
+            const std::string meshId = GetObjectUniqueId(AiObjectType::Mesh, pNode->mMeshes[a]);
 
             if (mesh->mNumBones == 0) {
-                mOutput << startstr << "<instance_geometry url=\"#" << XMLIDEncode(meshName) << "\">" << endstr;
+                mOutput << startstr << "<instance_geometry url=\"#" << meshId << "\">" << endstr;
                 PushTag();
             } else {
                 mOutput << startstr
-                        << "<instance_controller url=\"#" << XMLIDEncode(meshName) << "-skin\">"
+                        << "<instance_controller url=\"#" << meshId << "-skin\">"
                         << endstr;
                 PushTag();
 
                 // note! this mFoundSkeletonRootNodeID some how affects animation, it makes the mesh attaches to armature skeleton root node.
                 // use the first bone to find skeleton root
-                const aiNode *skeletonRootBoneNode = findSkeletonRootNode(pScene, mesh);
+                const aiNode *skeletonRootBoneNode = findSkeletonRootNode(mScene, mesh);
                 if (skeletonRootBoneNode) {
-                    mFoundSkeletonRootNodeID = XMLIDEncode(skeletonRootBoneNode->mName.C_Str());
+                    mFoundSkeletonRootNodeID = GetNodeUniqueId(skeletonRootBoneNode);
                 }
                 mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
             }
@@ -1616,7 +1589,7 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) {
             PushTag();
             mOutput << startstr << "<technique_common>" << endstr;
             PushTag();
-            mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLIDEncode(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
+            mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << GetObjectUniqueId(AiObjectType::Material, mesh->mMaterialIndex) << "\">" << endstr;
             PushTag();
             for (size_t aa = 0; aa < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++aa) {
                 if (mesh->HasTextureCoords(static_cast<unsigned int>(aa)))
@@ -1641,11 +1614,135 @@ void ColladaExporter::WriteNode(const aiScene *pScene, aiNode *pNode) {
 
     // recurse into subnodes
     for (size_t a = 0; a < pNode->mNumChildren; ++a)
-        WriteNode(pScene, pNode->mChildren[a]);
+        WriteNode(pNode->mChildren[a]);
 
     PopTag();
     mOutput << startstr << "</node>" << endstr;
 }
 
+void ColladaExporter::CreateNodeIds(const aiNode *node) {
+    GetNodeUniqueId(node);
+    for (size_t a = 0; a < node->mNumChildren; ++a)
+        CreateNodeIds(node->mChildren[a]);
+}
+
+std::string ColladaExporter::GetNodeUniqueId(const aiNode *node) {
+    // Use the pointer as the key. This is safe because the scene is immutable.
+    auto idIt = mNodeIdMap.find(node);
+    if (idIt != mNodeIdMap.cend())
+        return idIt->second;
+
+    // Prefer the requested Collada Id if extant
+    std::string idStr;
+    aiString origId;
+    if (node->mMetaData && node->mMetaData->Get(AI_METADATA_COLLADA_ID, origId)) {
+        idStr = origId.C_Str();
+    } else {
+        idStr = node->mName.C_Str();
+    }
+    // Make sure the requested id is valid
+    if (idStr.empty())
+        idStr = "node";
+    else
+        idStr = XMLIDEncode(idStr);
+
+    // Ensure it's unique
+    idStr = MakeUniqueId(mUniqueIds, idStr, std::string());
+    mUniqueIds.insert(idStr);
+    mNodeIdMap.insert(std::make_pair(node, idStr));
+    return idStr;
+}
+
+std::string ColladaExporter::GetNodeName(const aiNode *node) {
+
+    return XMLEscape(node->mName.C_Str());
+}
+
+std::string ColladaExporter::GetBoneUniqueId(const aiBone *bone) {
+    // Find the Node that is this Bone
+    const aiNode *boneNode = findBoneNode(mScene->mRootNode, bone);
+    if (boneNode == nullptr)
+        return std::string();
+
+    return GetNodeUniqueId(boneNode);
+}
+
+std::string ColladaExporter::GetObjectUniqueId(AiObjectType type, size_t pIndex) {
+    auto idIt = GetObjectIdMap(type).find(pIndex);
+    if (idIt != GetObjectIdMap(type).cend())
+        return idIt->second;
+
+    // Not seen this object before, create and add
+    NameIdPair result = AddObjectIndexToMaps(type, pIndex);
+    return result.second;
+}
+
+std::string ColladaExporter::GetObjectName(AiObjectType type, size_t pIndex) {
+    auto objectName = GetObjectNameMap(type).find(pIndex);
+    if (objectName != GetObjectNameMap(type).cend())
+        return objectName->second;
+
+    // Not seen this object before, create and add
+    NameIdPair result = AddObjectIndexToMaps(type, pIndex);
+    return result.first;
+}
+
+// Determine unique id and add the name and id to the maps
+// @param type object type
+// @param index object index
+// @param name in/out. Caller to set the original name if known.
+// @param idStr in/out. Caller to set the preferred id if known.
+ColladaExporter::NameIdPair ColladaExporter::AddObjectIndexToMaps(AiObjectType type, size_t index) {
+
+    std::string name;
+    std::string idStr;
+    std::string idPostfix;
+
+    // Get the name and id postfix
+    switch (type) {
+    case AiObjectType::Mesh: name = mScene->mMeshes[index]->mName.C_Str(); break;
+    case AiObjectType::Material: name = mScene->mMaterials[index]->GetName().C_Str(); break;
+    case AiObjectType::Animation: name = mScene->mAnimations[index]->mName.C_Str(); break;
+    case AiObjectType::Light:
+        name = mScene->mLights[index]->mName.C_Str();
+        idPostfix = "-light";
+        break;
+    case AiObjectType::Camera:
+        name = mScene->mCameras[index]->mName.C_Str();
+        idPostfix = "-camera";
+        break;
+    case AiObjectType::Count: throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
+    }
+
+    if (name.empty()) {
+        // Default ids if empty name
+        switch (type) {
+        case AiObjectType::Mesh: idStr = std::string("mesh_"); break;
+        case AiObjectType::Material: idStr = std::string("material_"); break; // This one should never happen
+        case AiObjectType::Animation: idStr = std::string("animation_"); break;
+        case AiObjectType::Light: idStr = std::string("light_"); break;
+        case AiObjectType::Camera: idStr = std::string("camera_"); break;
+        case AiObjectType::Count: throw std::logic_error("ColladaExporter::AiObjectType::Count is not an object type");
+        }
+        idStr.append(to_string(index));
+    } else {
+        idStr = XMLIDEncode(name);
+    }
+
+    if (!name.empty())
+        name = XMLEscape(name);
+
+    idStr = MakeUniqueId(mUniqueIds, idStr, idPostfix);
+
+    // Add to maps
+    mUniqueIds.insert(idStr);
+    GetObjectIdMap(type).insert(std::make_pair(index, idStr));
+    GetObjectNameMap(type).insert(std::make_pair(index, name));
+
+    return std::make_pair(name, idStr);
+}
+
+} // end of namespace Assimp
+
 #endif
 #endif

+ 115 - 80
code/AssetLib/Collada/ColladaExporter.h

@@ -48,28 +48,28 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <assimp/ai_assert.h>
 #include <assimp/material.h>
-#include <assimp/mesh.h>
-#include <assimp/light.h>
-#include <assimp/Exporter.hpp>
+
+#include <array>
+#include <map>
 #include <sstream>
+#include <unordered_set>
 #include <vector>
-#include <map>
-
-#include <assimp/StringUtils.h>
 
 struct aiScene;
 struct aiNode;
+struct aiLight;
+struct aiBone;
 
-namespace Assimp
-{
+namespace Assimp {
+
+class IOSystem;
 
 /// Helper class to export a given scene to a Collada file. Just for my personal
 /// comfort when implementing it.
-class ColladaExporter
-{
+class ColladaExporter {
 public:
     /// Constructor for a specific scene to export
-    ColladaExporter( const aiScene* pScene, IOSystem* pIOSystem, const std::string& path, const std::string& file);
+    ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file);
 
     /// Destructor
     virtual ~ColladaExporter();
@@ -107,51 +107,87 @@ protected:
     void WriteControllerLibrary();
 
     /// Writes a skin controller of the given mesh
-    void WriteController( size_t pIndex);
+    void WriteController(size_t pIndex);
 
     /// Writes the geometry library
     void WriteGeometryLibrary();
 
     /// Writes the given mesh
-    void WriteGeometry( size_t pIndex);
+    void WriteGeometry(size_t pIndex);
 
     //enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight };
     // customized to add animation related type
-	enum FloatDataType { FloatType_Vector, FloatType_TexCoord2, FloatType_TexCoord3, FloatType_Color, FloatType_Mat4x4, FloatType_Weight, FloatType_Time };
+    enum FloatDataType { FloatType_Vector,
+        FloatType_TexCoord2,
+        FloatType_TexCoord3,
+        FloatType_Color,
+        FloatType_Mat4x4,
+        FloatType_Weight,
+        FloatType_Time };
 
     /// Writes a float array of the given type
-    void WriteFloatArray( const std::string& pIdString, FloatDataType pType, const ai_real* pData, size_t pElementCount);
+    void WriteFloatArray(const std::string &pIdString, FloatDataType pType, const ai_real *pData, size_t pElementCount);
 
     /// Writes the scene library
     void WriteSceneLibrary();
 
-	// customized, Writes the animation library
-	void WriteAnimationsLibrary();
-	void WriteAnimationLibrary( size_t pIndex);
-	std::string mFoundSkeletonRootNodeID = "skeleton_root";	 	// will be replaced by found node id in the WriteNode call.
-	
+    // customized, Writes the animation library
+    void WriteAnimationsLibrary();
+    void WriteAnimationLibrary(size_t pIndex);
+    std::string mFoundSkeletonRootNodeID = "skeleton_root"; // will be replaced by found node id in the WriteNode call.
+
     /// Recursively writes the given node
-    void WriteNode( const aiScene* scene, aiNode* pNode);
+    void WriteNode(const aiNode *pNode);
 
     /// Enters a new xml element, which increases the indentation
-    void PushTag() { startstr.append( "  "); }
+    void PushTag() { startstr.append("  "); }
     /// Leaves an element, decreasing the indentation
-    void PopTag() { 
-        ai_assert( startstr.length() > 1); 
-        startstr.erase( startstr.length() - 2); 
+    void PopTag() {
+        ai_assert(startstr.length() > 1);
+        startstr.erase(startstr.length() - 2);
     }
 
-    /// Creates a mesh ID for the given mesh
-    std::string GetMeshId( size_t pIndex) const {
-        return std::string( "meshId" ) + to_string(pIndex);
-    }
+    void CreateNodeIds(const aiNode *node);
+
+    /// Get or Create a unique Node ID string for the given Node
+    std::string GetNodeUniqueId(const aiNode *node);
+    std::string GetNodeName(const aiNode *node);
+
+    std::string GetBoneUniqueId(const aiBone *bone);
+
+    enum class AiObjectType {
+        Mesh,
+        Material,
+        Animation,
+        Light,
+        Camera,
+        Count,
+    };
+    /// Get or Create a unique ID string for the given scene object index
+    std::string GetObjectUniqueId(AiObjectType type, size_t pIndex);
+    /// Get or Create a name string for the given scene object index
+    std::string GetObjectName(AiObjectType type, size_t pIndex);
+
+    typedef std::map<size_t, std::string> IndexIdMap;
+    typedef std::pair<std::string, std::string> NameIdPair;
+    NameIdPair AddObjectIndexToMaps(AiObjectType type, size_t pIndex);
+
+    // Helpers
+    inline IndexIdMap &GetObjectIdMap(AiObjectType type) { return mObjectIdMap[static_cast<size_t>(type)]; }
+    inline IndexIdMap &GetObjectNameMap(AiObjectType type) { return mObjectNameMap[static_cast<size_t>(type)]; }
+
+private:
+    std::unordered_set<std::string> mUniqueIds; // Cache of used unique ids
+    std::map<const void *, std::string> mNodeIdMap; // Cache of encoded node and bone ids
+    std::array<IndexIdMap, static_cast<size_t>(AiObjectType::Count)> mObjectIdMap; // Cache of encoded unique IDs
+    std::array<IndexIdMap, static_cast<size_t>(AiObjectType::Count)> mObjectNameMap; // Cache of encoded names
 
 public:
     /// Stringstream to write all output into
     std::stringstream mOutput;
 
     /// The IOSystem for output
-    IOSystem* mIOSystem;
+    IOSystem *mIOSystem;
 
     /// Path of the directory where the scene will be exported
     const std::string mPath;
@@ -160,63 +196,62 @@ 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;
-
-  // pair of color and texture - texture precedences color
-  struct Surface
-  {
-    bool exist;
-    aiColor4D color;
-    std::string texture;
-    size_t channel;
-    Surface() { exist = false; channel = 0; }
-  };
-
-  struct Property
-  {
-    bool exist;
-     ai_real value;
-     Property()
-         : exist(false)
-         , value(0.0)
-     {}
-  };
-
-  // summarize a material in an convenient way.
-  struct Material
-  {
-    std::string name;
-    std::string shading_model;
-    Surface ambient, diffuse, specular, emissive, reflective, transparent, normal;
-    Property shininess, transparency, index_refraction;
-
-    Material() {}
-  };
-
-  std::vector<Material> materials;
-
-  std::map<unsigned int, std::string> textures;
+    const std::string endstr;
+
+    // pair of color and texture - texture precedences color
+    struct Surface {
+        bool exist;
+        aiColor4D color;
+        std::string texture;
+        size_t channel;
+        Surface() {
+            exist = false;
+            channel = 0;
+        }
+    };
+
+    struct Property {
+        bool exist;
+        ai_real value;
+        Property() :
+                exist(false),
+                value(0.0) {}
+    };
+
+    // summarize a material in an convenient way.
+    struct Material {
+        std::string id;
+        std::string name;
+        std::string shading_model;
+        Surface ambient, diffuse, specular, emissive, reflective, transparent, normal;
+        Property shininess, transparency, index_refraction;
+
+        Material() {}
+    };
+
+    std::map<unsigned int, std::string> textures;
 
 public:
-  /// Dammit C++ - y u no compile two-pass? No I have to add all methods below the struct definitions
-  /// Reads a single surface entry from the given material keys
-  void ReadMaterialSurface( Surface& poSurface, const aiMaterial* pSrcMat, aiTextureType pTexture, const char* pKey, size_t pType, size_t pIndex);
-  /// Writes an image entry for the given surface
-  void WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd);
-  /// Writes the two parameters necessary for referencing a texture in an effect entry
-  void WriteTextureParamEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pMatName);
-  /// Writes a color-or-texture entry into an effect definition
-  void WriteTextureColorEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pImageName);
-  /// Writes a scalar property
-  void WriteFloatEntry( const Property& pProperty, const std::string& pTypeName);
+    /// Dammit C++ - y u no compile two-pass? No I have to add all methods below the struct definitions
+    /// Reads a single surface entry from the given material keys
+    bool ReadMaterialSurface(Surface &poSurface, const aiMaterial &pSrcMat, aiTextureType pTexture, const char *pKey, size_t pType, size_t pIndex);
+    /// Writes an image entry for the given surface
+    void WriteImageEntry(const Surface &pSurface, const std::string &imageId);
+    /// Writes the two parameters necessary for referencing a texture in an effect entry
+    void WriteTextureParamEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &materialId);
+    /// Writes a color-or-texture entry into an effect definition
+    void WriteTextureColorEntry(const Surface &pSurface, const std::string &pTypeName, const std::string &imageId);
+    /// Writes a scalar property
+    void WriteFloatEntry(const Property &pProperty, const std::string &pTypeName);
 };
 
-}
+} // namespace Assimp
 
 #endif // !! AI_COLLADAEXPORTER_H_INC

+ 8 - 15
code/AssetLib/Collada/ColladaHelper.cpp

@@ -43,8 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include "ColladaHelper.h"
 
-#include <assimp/commonMetaData.h>
 #include <assimp/ParsingUtils.h>
+#include <assimp/commonMetaData.h>
 
 namespace Assimp {
 namespace Collada {
@@ -63,42 +63,35 @@ const MetaKeyPairVector &GetColladaAssimpMetaKeys() {
 
 const MetaKeyPairVector MakeColladaAssimpMetaKeysCamelCase() {
     MetaKeyPairVector result = MakeColladaAssimpMetaKeys();
-    for (auto &val : result)
-    {
+    for (auto &val : result) {
         ToCamelCase(val.first);
     }
     return result;
 };
 
-const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase()
-{
+const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase() {
     static const MetaKeyPairVector result = MakeColladaAssimpMetaKeysCamelCase();
     return result;
 }
 
 // ------------------------------------------------------------------------------------------------
 // Convert underscore_separated to CamelCase: "authoring_tool" becomes "AuthoringTool"
-void ToCamelCase(std::string &text)
-{
+void ToCamelCase(std::string &text) {
     if (text.empty())
         return;
     // Capitalise first character
     auto it = text.begin();
     (*it) = ToUpper(*it);
     ++it;
-    for (/*started above*/ ; it != text.end(); /*iterated below*/)
-    {
-        if ((*it) == '_')
-        {
+    for (/*started above*/; it != text.end(); /*iterated below*/) {
+        if ((*it) == '_') {
             it = text.erase(it);
             if (it != text.end())
                 (*it) = ToUpper(*it);
-        }
-        else
-        {
+        } else {
             // Make lower case
             (*it) = ToLower(*it);
-            ++it;               
+            ++it;
         }
     }
 }

+ 185 - 224
code/AssetLib/Collada/ColladaHelper.h

@@ -45,31 +45,28 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #ifndef AI_COLLADAHELPER_H_INC
 #define AI_COLLADAHELPER_H_INC
 
-#include <map>
-#include <vector>
-#include <set>
-#include <stdint.h>
 #include <assimp/light.h>
-#include <assimp/mesh.h>
 #include <assimp/material.h>
+#include <assimp/mesh.h>
+#include <stdint.h>
+#include <map>
+#include <set>
+#include <vector>
 
 struct aiMaterial;
 
-namespace Assimp    {
-namespace Collada       {
+namespace Assimp {
+namespace Collada {
 
 /** Collada file versions which evolved during the years ... */
-enum FormatVersion
-{
+enum FormatVersion {
     FV_1_5_n,
     FV_1_4_n,
     FV_1_3_n
 };
 
-
 /** Transformation types that can be applied to a node */
-enum TransformType
-{
+enum TransformType {
     TF_LOOKAT,
     TF_ROTATE,
     TF_TRANSLATE,
@@ -79,10 +76,9 @@ enum TransformType
 };
 
 /** Different types of input data to a vertex or face */
-enum InputType
-{
+enum InputType {
     IT_Invalid,
-    IT_Vertex,  // special type for per-index data referring to the <vertices> element carrying the per-vertex data.
+    IT_Vertex, // special type for per-index data referring to the <vertices> element carrying the per-vertex data.
     IT_Position,
     IT_Normal,
     IT_Texcoord,
@@ -92,15 +88,13 @@ enum InputType
 };
 
 /** Supported controller types */
-enum ControllerType
-{
+enum ControllerType {
     Skin,
     Morph
 };
 
 /** Supported morph methods */
-enum MorphMethod
-{
+enum MorphMethod {
     Normalized,
     Relative
 };
@@ -118,24 +112,21 @@ const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase();
 void ToCamelCase(std::string &text);
 
 /** Contains all data for one of the different transformation types */
-struct Transform
-{
-    std::string mID;  ///< SID of the transform step, by which anim channels address their target node
+struct Transform {
+    std::string mID; ///< SID of the transform step, by which anim channels address their target node
     TransformType mType;
     ai_real f[16]; ///< Interpretation of data depends on the type of the transformation
 };
 
 /** A collada camera. */
-struct Camera
-{
-    Camera()
-        :   mOrtho  (false)
-        ,   mHorFov (10e10f)
-        ,   mVerFov (10e10f)
-        ,   mAspect (10e10f)
-        ,   mZNear  (0.1f)
-        ,   mZFar   (1000.f)
-    {}
+struct Camera {
+    Camera() :
+            mOrtho(false),
+            mHorFov(10e10f),
+            mVerFov(10e10f),
+            mAspect(10e10f),
+            mZNear(0.1f),
+            mZFar(1000.f) {}
 
     // Name of camera
     std::string mName;
@@ -159,19 +150,17 @@ struct Camera
 #define ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET 1e9f
 
 /** A collada light source. */
-struct Light
-{
-    Light()
-        :   mType            (aiLightSource_UNDEFINED)
-        ,   mAttConstant     (1.f)
-        ,   mAttLinear       (0.f)
-        ,   mAttQuadratic    (0.f)
-        ,   mFalloffAngle    (180.f)
-        ,   mFalloffExponent (0.f)
-        ,   mPenumbraAngle   (ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET)
-        ,   mOuterAngle      (ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET)
-        ,   mIntensity       (1.f)
-    {}
+struct Light {
+    Light() :
+            mType(aiLightSource_UNDEFINED),
+            mAttConstant(1.f),
+            mAttLinear(0.f),
+            mAttQuadratic(0.f),
+            mFalloffAngle(180.f),
+            mFalloffExponent(0.f),
+            mPenumbraAngle(ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET),
+            mOuterAngle(ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET),
+            mIntensity(1.f) {}
 
     //! Type of the light source aiLightSourceType + ambient
     unsigned int mType;
@@ -180,7 +169,7 @@ struct Light
     aiColor3D mColor;
 
     //! Light attenuation
-    ai_real mAttConstant,mAttLinear,mAttQuadratic;
+    ai_real mAttConstant, mAttLinear, mAttQuadratic;
 
     //! Spot light falloff
     ai_real mFalloffAngle;
@@ -198,12 +187,10 @@ struct Light
 };
 
 /** Short vertex index description */
-struct InputSemanticMapEntry
-{
-    InputSemanticMapEntry()
-        :   mSet(0)
-        ,   mType(IT_Invalid)
-    {}
+struct InputSemanticMapEntry {
+    InputSemanticMapEntry() :
+            mSet(0),
+            mType(IT_Invalid) {}
 
     //! Index of set, optional
     unsigned int mSet;
@@ -213,8 +200,7 @@ struct InputSemanticMapEntry
 };
 
 /** Table to map from effect to vertex input semantics */
-struct SemanticMappingTable
-{
+struct SemanticMappingTable {
     //! Name of material
     std::string mMatName;
 
@@ -222,7 +208,7 @@ struct SemanticMappingTable
     std::map<std::string, InputSemanticMapEntry> mMap;
 
     //! For std::find
-    bool operator == (const std::string& s) const {
+    bool operator==(const std::string &s) const {
         return s == mMatName;
     }
 };
@@ -230,8 +216,7 @@ struct SemanticMappingTable
 /** A reference to a mesh inside a node, including materials assigned to the various subgroups.
  * The ID refers to either a mesh or a controller which specifies the mesh
  */
-struct MeshInstance
-{
+struct MeshInstance {
     ///< ID of the mesh or controller to be instanced
     std::string mMeshOrController;
 
@@ -240,34 +225,30 @@ struct MeshInstance
 };
 
 /** A reference to a camera inside a node*/
-struct CameraInstance
-{
-     ///< ID of the camera
+struct CameraInstance {
+    ///< ID of the camera
     std::string mCamera;
 };
 
 /** A reference to a light inside a node*/
-struct LightInstance
-{
-     ///< ID of the camera
+struct LightInstance {
+    ///< ID of the camera
     std::string mLight;
 };
 
 /** A reference to a node inside a node*/
-struct NodeInstance
-{
-     ///< ID of the node
+struct NodeInstance {
+    ///< ID of the node
     std::string mNode;
 };
 
 /** A node in a scene hierarchy */
-struct Node
-{
+struct Node {
     std::string mName;
     std::string mID;
     std::string mSID;
-    Node* mParent;
-    std::vector<Node*> mChildren;
+    Node *mParent;
+    std::vector<Node *> mChildren;
 
     /** Operations in order to calculate the resulting transformation to parent. */
     std::vector<Transform> mTransforms;
@@ -288,80 +269,83 @@ struct Node
     std::string mPrimaryCamera;
 
     //! Constructor. Begin with a zero parent
-    Node()
-    : mParent( nullptr ){
+    Node() :
+            mParent(nullptr) {
         // empty
     }
 
     //! Destructor: delete all children subsequently
     ~Node() {
-        for( std::vector<Node*>::iterator it = mChildren.begin(); it != mChildren.end(); ++it)
+        for (std::vector<Node *>::iterator it = mChildren.begin(); it != mChildren.end(); ++it)
             delete *it;
     }
 };
 
 /** Data source array: either floats or strings */
-struct Data
-{
+struct Data {
     bool mIsStringArray;
     std::vector<ai_real> mValues;
     std::vector<std::string> mStrings;
 };
 
 /** Accessor to a data array */
-struct Accessor
-{
-    size_t mCount;   // in number of objects
-    size_t mSize;    // size of an object, in elements (floats or strings, mostly 1)
-    size_t mOffset;  // in number of values
-    size_t mStride;  // Stride in number of values
+struct Accessor {
+    size_t mCount; // in number of objects
+    size_t mSize; // size of an object, in elements (floats or strings, mostly 1)
+    size_t mOffset; // in number of values
+    size_t mStride; // Stride in number of values
     std::vector<std::string> mParams; // names of the data streams in the accessors. Empty string tells to ignore.
     size_t mSubOffset[4]; // Suboffset inside the object for the common 4 elements. For a vector, that's XYZ, for a color RGBA and so on.
-                          // For example, SubOffset[0] denotes which of the values inside the object is the vector X component.
-    std::string mSource;   // URL of the source array
-    mutable const Data* mData; // Pointer to the source array, if resolved. NULL else
-
-    Accessor()
-    {
-        mCount = 0; mSize = 0; mOffset = 0; mStride = 0; mData = NULL;
+            // For example, SubOffset[0] denotes which of the values inside the object is the vector X component.
+    std::string mSource; // URL of the source array
+    mutable const Data *mData; // Pointer to the source array, if resolved. NULL else
+
+    Accessor() {
+        mCount = 0;
+        mSize = 0;
+        mOffset = 0;
+        mStride = 0;
+        mData = NULL;
         mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0;
     }
 };
 
 /** A single face in a mesh */
-struct Face
-{
+struct Face {
     std::vector<size_t> mIndices;
 };
 
 /** An input channel for mesh data, referring to a single accessor */
-struct InputChannel
-{
-    InputType mType;      // Type of the data
-    size_t mIndex;        // Optional index, if multiple sets of the same data type are given
-    size_t mOffset;       // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better.
+struct InputChannel {
+    InputType mType; // Type of the data
+    size_t mIndex; // Optional index, if multiple sets of the same data type are given
+    size_t mOffset; // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better.
     std::string mAccessor; // ID of the accessor where to read the actual values from.
-    mutable const Accessor* mResolved; // Pointer to the accessor, if resolved. NULL else
+    mutable const Accessor *mResolved; // Pointer to the accessor, if resolved. NULL else
 
-    InputChannel() { mType = IT_Invalid; mIndex = 0; mOffset = 0; mResolved = NULL; }
+    InputChannel() {
+        mType = IT_Invalid;
+        mIndex = 0;
+        mOffset = 0;
+        mResolved = NULL;
+    }
 };
 
 /** Subset of a mesh with a certain material */
-struct SubMesh
-{
+struct SubMesh {
     std::string mMaterial; ///< subgroup identifier
     size_t mNumFaces; ///< number of faces in this submesh
 };
 
 /** Contains data for a single mesh */
-struct Mesh
-{
-    Mesh()
-    {
-        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS;++i)
+struct Mesh {
+    Mesh(const std::string &id) :
+            mId(id) {
+        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i)
             mNumUVComponents[i] = 2;
     }
 
+    const std::string mId;
     std::string mName;
 
     // just to check if there's some sophisticated addressing involved...
@@ -377,7 +361,7 @@ struct Mesh
     std::vector<aiVector3D> mTangents;
     std::vector<aiVector3D> mBitangents;
     std::vector<aiVector3D> mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS];
-    std::vector<aiColor4D>  mColors[AI_MAX_NUMBER_OF_COLOR_SETS];
+    std::vector<aiColor4D> mColors[AI_MAX_NUMBER_OF_COLOR_SETS];
 
     unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS];
 
@@ -394,8 +378,7 @@ struct Mesh
 };
 
 /** Which type of primitives the ReadPrimitives() function is going to read */
-enum PrimitiveType
-{
+enum PrimitiveType {
     Prim_Invalid,
     Prim_Lines,
     Prim_LineStrip,
@@ -407,8 +390,7 @@ enum PrimitiveType
 };
 
 /** A skeleton controller to deform a mesh with the use of joints */
-struct Controller
-{
+struct Controller {
     // controller type
     ControllerType mType;
 
@@ -436,36 +418,32 @@ struct Controller
     std::vector<size_t> mWeightCounts;
 
     // JointIndex-WeightIndex pairs for all vertices
-    std::vector< std::pair<size_t, size_t> > mWeights;
+    std::vector<std::pair<size_t, size_t>> mWeights;
 
     std::string mMorphTarget;
     std::string mMorphWeight;
 };
 
 /** A collada material. Pretty much the only member is a reference to an effect. */
-struct Material
-{
+struct Material {
     std::string mName;
     std::string mEffect;
 };
 
 /** Type of the effect param */
-enum ParamType
-{
+enum ParamType {
     Param_Sampler,
     Param_Surface
 };
 
 /** A param for an effect. Might be of several types, but they all just refer to each other, so I summarize them */
-struct EffectParam
-{
+struct EffectParam {
     ParamType mType;
     std::string mReference; // to which other thing the param is referring to.
 };
 
 /** Shading type supported by the standard effect spec of Collada */
-enum ShadeType
-{
+enum ShadeType {
     Shade_Invalid,
     Shade_Constant,
     Shade_Lambert,
@@ -474,18 +452,16 @@ enum ShadeType
 };
 
 /** Represents a texture sampler in collada */
-struct Sampler
-{
-    Sampler()
-        :   mWrapU      (true)
-        ,   mWrapV      (true)
-        ,   mMirrorU    ()
-        ,   mMirrorV    ()
-        ,   mOp         (aiTextureOp_Multiply)
-        ,   mUVId       (UINT_MAX)
-        ,   mWeighting  (1.f)
-        ,   mMixWithPrevious (1.f)
-    {}
+struct Sampler {
+    Sampler() :
+            mWrapU(true),
+            mWrapV(true),
+            mMirrorU(),
+            mMirrorV(),
+            mOp(aiTextureOp_Multiply),
+            mUVId(UINT_MAX),
+            mWeighting(1.f),
+            mMixWithPrevious(1.f) {}
 
     /** Name of image reference
      */
@@ -537,18 +513,17 @@ struct Sampler
 
 /** A collada effect. Can contain about anything according to the Collada spec,
     but we limit our version to a reasonable subset. */
-struct Effect
-{
+struct Effect {
     // Shading mode
     ShadeType mShadeType;
 
     // Colors
     aiColor4D mEmissive, mAmbient, mDiffuse, mSpecular,
-        mTransparent, mReflective;
+            mTransparent, mReflective;
 
     // Textures
     Sampler mTexEmissive, mTexAmbient, mTexDiffuse, mTexSpecular,
-        mTexTransparent, mTexBump, mTexReflective;
+            mTexTransparent, mTexBump, mTexReflective;
 
     // Scalar factory
     ai_real mShininess, mRefractIndex, mReflectivity;
@@ -566,30 +541,28 @@ struct Effect
     // Double-sided?
     bool mDoubleSided, mWireframe, mFaceted;
 
-    Effect()
-        : mShadeType    (Shade_Phong)
-        , mEmissive     ( 0, 0, 0, 1)
-        , mAmbient      ( 0.1f, 0.1f, 0.1f, 1)
-        , mDiffuse      ( 0.6f, 0.6f, 0.6f, 1)
-        , mSpecular     ( 0.4f, 0.4f, 0.4f, 1)
-        , mTransparent  ( 0, 0, 0, 1)
-        , mShininess    (10.0f)
-        , mRefractIndex (1.f)
-        , mReflectivity (0.f)
-        , mTransparency (1.f)
-        , mHasTransparency (false)
-        , mRGBTransparency(false)
-        , mInvertTransparency(false)
-        , mDoubleSided  (false)
-        , mWireframe    (false)
-        , mFaceted      (false)
-    {
+    Effect() :
+            mShadeType(Shade_Phong),
+            mEmissive(0, 0, 0, 1),
+            mAmbient(0.1f, 0.1f, 0.1f, 1),
+            mDiffuse(0.6f, 0.6f, 0.6f, 1),
+            mSpecular(0.4f, 0.4f, 0.4f, 1),
+            mTransparent(0, 0, 0, 1),
+            mShininess(10.0f),
+            mRefractIndex(1.f),
+            mReflectivity(0.f),
+            mTransparency(1.f),
+            mHasTransparency(false),
+            mRGBTransparency(false),
+            mInvertTransparency(false),
+            mDoubleSided(false),
+            mWireframe(false),
+            mFaceted(false) {
     }
 };
 
 /** An image, meaning texture */
-struct Image
-{
+struct Image {
     std::string mFileName;
 
     /** Embedded image data */
@@ -600,8 +573,7 @@ struct Image
 };
 
 /** An animation channel. */
-struct AnimationChannel
-{
+struct AnimationChannel {
     /** URL of the data to animate. Could be about anything, but we support only the
      * "NodeID/TransformID.SubElement" notation
      */
@@ -620,8 +592,7 @@ struct AnimationChannel
 };
 
 /** An animation. Container for 0-x animation channels or 0-x animations */
-struct Animation
-{
+struct Animation {
     /** Anim name */
     std::string mName;
 
@@ -629,96 +600,86 @@ struct Animation
     std::vector<AnimationChannel> mChannels;
 
     /** the sub-animations, if any */
-    std::vector<Animation*> mSubAnims;
+    std::vector<Animation *> mSubAnims;
 
     /** Destructor */
-    ~Animation()
-    {
-        for( std::vector<Animation*>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it)
+    ~Animation() {
+        for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it)
             delete *it;
     }
 
-	/** Collect all channels in the animation hierarchy into a single channel list. */
-	void CollectChannelsRecursively(std::vector<AnimationChannel> &channels)
-	{
-		channels.insert(channels.end(), mChannels.begin(), mChannels.end());
+    /** Collect all channels in the animation hierarchy into a single channel list. */
+    void CollectChannelsRecursively(std::vector<AnimationChannel> &channels) {
+        channels.insert(channels.end(), mChannels.begin(), mChannels.end());
 
-		for (std::vector<Animation*>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it)
-		{
-			Animation *pAnim = (*it);
+        for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it) {
+            Animation *pAnim = (*it);
 
-			pAnim->CollectChannelsRecursively(channels);
-		}
-	}
+            pAnim->CollectChannelsRecursively(channels);
+        }
+    }
 
-	/** Combine all single-channel animations' channel into the same (parent) animation channel list. */
-	void CombineSingleChannelAnimations()
-	{
-		CombineSingleChannelAnimationsRecursively(this);
-	}
+    /** Combine all single-channel animations' channel into the same (parent) animation channel list. */
+    void CombineSingleChannelAnimations() {
+        CombineSingleChannelAnimationsRecursively(this);
+    }
 
-	void CombineSingleChannelAnimationsRecursively(Animation *pParent)
-	{
-		std::set<std::string> childrenTargets;
-		bool childrenAnimationsHaveDifferentChannels = true;
+    void CombineSingleChannelAnimationsRecursively(Animation *pParent) {
+        std::set<std::string> childrenTargets;
+        bool childrenAnimationsHaveDifferentChannels = true;
 
-		for (std::vector<Animation*>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();)
-		{
-			Animation *anim = *it;
-			CombineSingleChannelAnimationsRecursively(anim);
+        for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) {
+            Animation *anim = *it;
+            CombineSingleChannelAnimationsRecursively(anim);
 
-			if (childrenAnimationsHaveDifferentChannels && anim->mChannels.size() == 1 &&
-				childrenTargets.find(anim->mChannels[0].mTarget) == childrenTargets.end()) {
-				childrenTargets.insert(anim->mChannels[0].mTarget);
-			} else {
-				childrenAnimationsHaveDifferentChannels = false;
-			}
+            if (childrenAnimationsHaveDifferentChannels && anim->mChannels.size() == 1 &&
+                    childrenTargets.find(anim->mChannels[0].mTarget) == childrenTargets.end()) {
+                childrenTargets.insert(anim->mChannels[0].mTarget);
+            } else {
+                childrenAnimationsHaveDifferentChannels = false;
+            }
 
-			++it;
-		}
+            ++it;
+        }
 
-		// We only want to combine animations if they have different channels
-		if (childrenAnimationsHaveDifferentChannels)
-		{
-			for (std::vector<Animation*>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();)
-			{
-				Animation *anim = *it;
+        // We only want to combine animations if they have different channels
+        if (childrenAnimationsHaveDifferentChannels) {
+            for (std::vector<Animation *>::iterator it = pParent->mSubAnims.begin(); it != pParent->mSubAnims.end();) {
+                Animation *anim = *it;
 
-				pParent->mChannels.push_back(anim->mChannels[0]);
+                pParent->mChannels.push_back(anim->mChannels[0]);
 
-				it = pParent->mSubAnims.erase(it);
+                it = pParent->mSubAnims.erase(it);
 
-				delete anim;
-				continue;
-			}
-		}
-	}
+                delete anim;
+                continue;
+            }
+        }
+    }
 };
 
 /** Description of a collada animation channel which has been determined to affect the current node */
-struct ChannelEntry
-{
-    const Collada::AnimationChannel* mChannel; ///> the source channel
+struct ChannelEntry {
+    const Collada::AnimationChannel *mChannel; ///> the source channel
     std::string mTargetId;
-    std::string mTransformId;   // the ID of the transformation step of the node which is influenced
+    std::string mTransformId; // the ID of the transformation step of the node which is influenced
     size_t mTransformIndex; // Index into the node's transform chain to apply the channel to
     size_t mSubElement; // starting index inside the transform data
 
     // resolved data references
-    const Collada::Accessor* mTimeAccessor; ///> Collada accessor to the time values
-    const Collada::Data* mTimeData; ///> Source data array for the time values
-    const Collada::Accessor* mValueAccessor; ///> Collada accessor to the key value values
-    const Collada::Data* mValueData; ///> Source datat array for the key value values
-
-    ChannelEntry()
-      : mChannel()
-      , mTransformIndex()
-      , mSubElement()
-      , mTimeAccessor()
-      , mTimeData()
-      , mValueAccessor()
-      , mValueData()
-   {}
+    const Collada::Accessor *mTimeAccessor; ///> Collada accessor to the time values
+    const Collada::Data *mTimeData; ///> Source data array for the time values
+    const Collada::Accessor *mValueAccessor; ///> Collada accessor to the key value values
+    const Collada::Data *mValueData; ///> Source datat array for the key value values
+
+    ChannelEntry() :
+            mChannel(),
+            mTransformIndex(),
+            mSubElement(),
+            mTimeAccessor(),
+            mTimeData(),
+            mValueAccessor(),
+            mValueData() {}
 };
 
 } // end of namespace Collada

File diff suppressed because it is too large
+ 230 - 249
code/AssetLib/Collada/ColladaLoader.cpp


File diff suppressed because it is too large
+ 176 - 327
code/AssetLib/Collada/ColladaParser.cpp


+ 235 - 236
code/AssetLib/Collada/ColladaParser.h

@@ -47,346 +47,345 @@
 #ifndef AI_COLLADAPARSER_H_INC
 #define AI_COLLADAPARSER_H_INC
 
-#include <assimp/irrXMLWrapper.h>
 #include "ColladaHelper.h"
-#include <assimp/ai_assert.h>
 #include <assimp/TinyFormatter.h>
+#include <assimp/ai_assert.h>
+#include <assimp/irrXMLWrapper.h>
 
-namespace Assimp
-{
-    class ZipArchiveIOSystem;
+namespace Assimp {
+class ZipArchiveIOSystem;
 
-    // ------------------------------------------------------------------------------------------
-    /** Parser helper class for the Collada loader.
+// ------------------------------------------------------------------------------------------
+/** Parser helper class for the Collada loader.
      *
      *  Does all the XML reading and builds internal data structures from it,
      *  but leaves the resolving of all the references to the loader.
      */
-    class ColladaParser
-    {
-        friend class ColladaLoader;
+class ColladaParser {
+    friend class ColladaLoader;
 
-        /** Converts a path read from a collada file to the usual representation */
-        static void UriDecodePath(aiString& ss);
+    /** Converts a path read from a collada file to the usual representation */
+    static void UriDecodePath(aiString &ss);
 
-    protected:
-        /** Map for generic metadata as aiString */
-        typedef std::map<std::string, aiString> StringMetaData;
+protected:
+    /** Map for generic metadata as aiString */
+    typedef std::map<std::string, aiString> StringMetaData;
 
-        /** Constructor from XML file */
-        ColladaParser(IOSystem* pIOHandler, const std::string& pFile);
+    /** Constructor from XML file */
+    ColladaParser(IOSystem *pIOHandler, const std::string &pFile);
 
-        /** Destructor */
-        ~ColladaParser();
+    /** Destructor */
+    ~ColladaParser();
 
-        /** Attempts to read the ZAE manifest and returns the DAE to open */
-        static std::string ReadZaeManifest(ZipArchiveIOSystem &zip_archive);
+    /** Attempts to read the ZAE manifest and returns the DAE to open */
+    static std::string ReadZaeManifest(ZipArchiveIOSystem &zip_archive);
 
-        /** Reads the contents of the file */
-        void ReadContents();
+    /** Reads the contents of the file */
+    void ReadContents();
 
-        /** Reads the structure of the file */
-        void ReadStructure();
+    /** Reads the structure of the file */
+    void ReadStructure();
 
-        /** Reads asset information such as coordinate system information and legal blah */
-        void ReadAssetInfo();
+    /** Reads asset information such as coordinate system information and legal blah */
+    void ReadAssetInfo();
 
-        /** Reads contributor information such as author and legal blah */
-        void ReadContributorInfo();
+    /** Reads contributor information such as author and legal blah */
+    void ReadContributorInfo();
 
-        /** Reads generic metadata into provided map and renames keys for Assimp */
-        void ReadMetaDataItem(StringMetaData &metadata);
+    /** Reads generic metadata into provided map and renames keys for Assimp */
+    void ReadMetaDataItem(StringMetaData &metadata);
 
-        /** Reads the animation library */
-        void ReadAnimationLibrary();
+    /** Reads the animation library */
+    void ReadAnimationLibrary();
 
-		/** Reads the animation clip library */
-		void ReadAnimationClipLibrary();
+    /** Reads the animation clip library */
+    void ReadAnimationClipLibrary();
 
-        /** Unwrap controllers dependency hierarchy */
-        void PostProcessControllers();
-    
-		/** Re-build animations from animation clip library, if present, otherwise combine single-channel animations */
-		void PostProcessRootAnimations();
+    /** Unwrap controllers dependency hierarchy */
+    void PostProcessControllers();
 
-        /** Reads an animation into the given parent structure */
-        void ReadAnimation( Collada::Animation* pParent);
+    /** Re-build animations from animation clip library, if present, otherwise combine single-channel animations */
+    void PostProcessRootAnimations();
 
-        /** Reads an animation sampler into the given anim channel */
-        void ReadAnimationSampler( Collada::AnimationChannel& pChannel);
+    /** Reads an animation into the given parent structure */
+    void ReadAnimation(Collada::Animation *pParent);
 
-        /** Reads the skeleton controller library */
-        void ReadControllerLibrary();
+    /** Reads an animation sampler into the given anim channel */
+    void ReadAnimationSampler(Collada::AnimationChannel &pChannel);
 
-        /** Reads a controller into the given mesh structure */
-        void ReadController( Collada::Controller& pController);
+    /** Reads the skeleton controller library */
+    void ReadControllerLibrary();
 
-        /** Reads the joint definitions for the given controller */
-        void ReadControllerJoints( Collada::Controller& pController);
+    /** Reads a controller into the given mesh structure */
+    void ReadController(Collada::Controller &pController);
 
-        /** Reads the joint weights for the given controller */
-        void ReadControllerWeights( Collada::Controller& pController);
+    /** Reads the joint definitions for the given controller */
+    void ReadControllerJoints(Collada::Controller &pController);
 
-        /** Reads the image library contents */
-        void ReadImageLibrary();
+    /** Reads the joint weights for the given controller */
+    void ReadControllerWeights(Collada::Controller &pController);
 
-        /** Reads an image entry into the given image */
-        void ReadImage( Collada::Image& pImage);
+    /** Reads the image library contents */
+    void ReadImageLibrary();
 
-        /** Reads the material library */
-        void ReadMaterialLibrary();
+    /** Reads an image entry into the given image */
+    void ReadImage(Collada::Image &pImage);
 
-        /** Reads a material entry into the given material */
-        void ReadMaterial( Collada::Material& pMaterial);
+    /** Reads the material library */
+    void ReadMaterialLibrary();
 
-        /** Reads the camera library */
-        void ReadCameraLibrary();
+    /** Reads a material entry into the given material */
+    void ReadMaterial(Collada::Material &pMaterial);
 
-        /** Reads a camera entry into the given camera */
-        void ReadCamera( Collada::Camera& pCamera);
+    /** Reads the camera library */
+    void ReadCameraLibrary();
 
-        /** Reads the light library */
-        void ReadLightLibrary();
+    /** Reads a camera entry into the given camera */
+    void ReadCamera(Collada::Camera &pCamera);
 
-        /** Reads a light entry into the given light */
-        void ReadLight( Collada::Light& pLight);
+    /** Reads the light library */
+    void ReadLightLibrary();
 
-        /** Reads the effect library */
-        void ReadEffectLibrary();
+    /** Reads a light entry into the given light */
+    void ReadLight(Collada::Light &pLight);
 
-        /** Reads an effect entry into the given effect*/
-        void ReadEffect( Collada::Effect& pEffect);
+    /** Reads the effect library */
+    void ReadEffectLibrary();
 
-        /** Reads an COMMON effect profile */
-        void ReadEffectProfileCommon( Collada::Effect& pEffect);
+    /** Reads an effect entry into the given effect*/
+    void ReadEffect(Collada::Effect &pEffect);
 
-        /** Read sampler properties */
-        void ReadSamplerProperties( Collada::Sampler& pSampler);
+    /** Reads an COMMON effect profile */
+    void ReadEffectProfileCommon(Collada::Effect &pEffect);
 
-        /** Reads an effect entry containing a color or a texture defining that color */
-        void ReadEffectColor( aiColor4D& pColor, Collada::Sampler& pSampler);
+    /** Read sampler properties */
+    void ReadSamplerProperties(Collada::Sampler &pSampler);
 
-        /** Reads an effect entry containing a float */
-        void ReadEffectFloat( ai_real& pFloat);
+    /** Reads an effect entry containing a color or a texture defining that color */
+    void ReadEffectColor(aiColor4D &pColor, Collada::Sampler &pSampler);
 
-        /** Reads an effect parameter specification of any kind */
-        void ReadEffectParam( Collada::EffectParam& pParam);
+    /** Reads an effect entry containing a float */
+    void ReadEffectFloat(ai_real &pFloat);
 
-        /** Reads the geometry library contents */
-        void ReadGeometryLibrary();
+    /** Reads an effect parameter specification of any kind */
+    void ReadEffectParam(Collada::EffectParam &pParam);
 
-        /** Reads a geometry from the geometry library. */
-        void ReadGeometry( Collada::Mesh* pMesh);
+    /** Reads the geometry library contents */
+    void ReadGeometryLibrary();
 
-        /** Reads a mesh from the geometry library */
-        void ReadMesh( Collada::Mesh* pMesh);
+    /** Reads a geometry from the geometry library. */
+    void ReadGeometry(Collada::Mesh &pMesh);
 
-        /** Reads a source element - a combination of raw data and an accessor defining
+    /** Reads a mesh from the geometry library */
+    void ReadMesh(Collada::Mesh &pMesh);
+
+    /** Reads a source element - a combination of raw data and an accessor defining
          * things that should not be redefinable. Yes, that's another rant.
          */
-        void ReadSource();
+    void ReadSource();
 
-        /** Reads a data array holding a number of elements, and stores it in the global library.
+    /** Reads a data array holding a number of elements, and stores it in the global library.
          * Currently supported are array of floats and arrays of strings.
          */
-        void ReadDataArray();
+    void ReadDataArray();
 
-        /** Reads an accessor and stores it in the global library under the given ID -
+    /** Reads an accessor and stores it in the global library under the given ID -
          * accessors use the ID of the parent <source> element
          */
-        void ReadAccessor( const std::string& pID);
+    void ReadAccessor(const std::string &pID);
 
-        /** Reads input declarations of per-vertex mesh data into the given mesh */
-        void ReadVertexData( Collada::Mesh* pMesh);
+    /** Reads input declarations of per-vertex mesh data into the given mesh */
+    void ReadVertexData(Collada::Mesh &pMesh);
 
-        /** Reads input declarations of per-index mesh data into the given mesh */
-        void ReadIndexData( Collada::Mesh* pMesh);
+    /** Reads input declarations of per-index mesh data into the given mesh */
+    void ReadIndexData(Collada::Mesh &pMesh);
 
-        /** Reads a single input channel element and stores it in the given array, if valid */
-        void ReadInputChannel( std::vector<Collada::InputChannel>& poChannels);
+    /** Reads a single input channel element and stores it in the given array, if valid */
+    void ReadInputChannel(std::vector<Collada::InputChannel> &poChannels);
 
-        /** Reads a <p> primitive index list and assembles the mesh data into the given mesh */
-        size_t ReadPrimitives( Collada::Mesh* pMesh, std::vector<Collada::InputChannel>& pPerIndexChannels,
-                              size_t pNumPrimitives, const std::vector<size_t>& pVCount, Collada::PrimitiveType pPrimType);
+    /** Reads a <p> primitive index list and assembles the mesh data into the given mesh */
+    size_t ReadPrimitives(Collada::Mesh &pMesh, std::vector<Collada::InputChannel> &pPerIndexChannels,
+            size_t pNumPrimitives, const std::vector<size_t> &pVCount, Collada::PrimitiveType pPrimType);
 
-        /** Copies the data for a single primitive into the mesh, based on the InputChannels */
-        void CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset,
-                        Collada::Mesh* pMesh, std::vector<Collada::InputChannel>& pPerIndexChannels,
-                        size_t currentPrimitive, const std::vector<size_t>& indices);
+    /** Copies the data for a single primitive into the mesh, based on the InputChannels */
+    void CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset,
+            Collada::Mesh &pMesh, std::vector<Collada::InputChannel> &pPerIndexChannels,
+            size_t currentPrimitive, const std::vector<size_t> &indices);
 
-        /** Reads one triangle of a tristrip into the mesh */
-        void ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Collada::Mesh* pMesh,
-                               std::vector<Collada::InputChannel>& pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t>& indices);
+    /** Reads one triangle of a tristrip into the mesh */
+    void ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Collada::Mesh &pMesh,
+            std::vector<Collada::InputChannel> &pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t> &indices);
 
-        /** Extracts a single object from an input channel and stores it in the appropriate mesh data array */
-        void ExtractDataObjectFromChannel( const Collada::InputChannel& pInput, size_t pLocalIndex, Collada::Mesh* pMesh);
+    /** Extracts a single object from an input channel and stores it in the appropriate mesh data array */
+    void ExtractDataObjectFromChannel(const Collada::InputChannel &pInput, size_t pLocalIndex, Collada::Mesh &pMesh);
 
-        /** Reads the library of node hierarchies and scene parts */
-        void ReadSceneLibrary();
+    /** Reads the library of node hierarchies and scene parts */
+    void ReadSceneLibrary();
 
-        /** Reads a scene node's contents including children and stores it in the given node */
-        void ReadSceneNode( Collada::Node* pNode);
+    /** Reads a scene node's contents including children and stores it in the given node */
+    void ReadSceneNode(Collada::Node *pNode);
 
-        /** Reads a node transformation entry of the given type and adds it to the given node's transformation list. */
-        void ReadNodeTransformation( Collada::Node* pNode, Collada::TransformType pType);
+    /** Reads a node transformation entry of the given type and adds it to the given node's transformation list. */
+    void ReadNodeTransformation(Collada::Node *pNode, Collada::TransformType pType);
 
-        /** Reads a mesh reference in a node and adds it to the node's mesh list */
-        void ReadNodeGeometry( Collada::Node* pNode);
+    /** Reads a mesh reference in a node and adds it to the node's mesh list */
+    void ReadNodeGeometry(Collada::Node *pNode);
 
-        /** Reads the collada scene */
-        void ReadScene();
+    /** Reads the collada scene */
+    void ReadScene();
 
-        // Processes bind_vertex_input and bind elements
-        void ReadMaterialVertexInputBinding( Collada::SemanticMappingTable& tbl);
+    // Processes bind_vertex_input and bind elements
+    void ReadMaterialVertexInputBinding(Collada::SemanticMappingTable &tbl);
 
-        /** Reads embedded textures from a ZAE archive*/
-        void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive);
+    /** Reads embedded textures from a ZAE archive*/
+    void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive);
 
-    protected:
-        /** Aborts the file reading with an exception */
-        AI_WONT_RETURN void ThrowException( const std::string& pError) const AI_WONT_RETURN_SUFFIX;
-        void ReportWarning(const char* msg,...);
+protected:
+    /** Aborts the file reading with an exception */
+    AI_WONT_RETURN void ThrowException(const std::string &pError) const AI_WONT_RETURN_SUFFIX;
+    void ReportWarning(const char *msg, ...);
 
-        /** Skips all data until the end node of the current element */
-        void SkipElement();
+    /** Skips all data until the end node of the current element */
+    void SkipElement();
 
-        /** Skips all data until the end node of the given element */
-        void SkipElement( const char* pElement);
+    /** Skips all data until the end node of the given element */
+    void SkipElement(const char *pElement);
 
-        /** Compares the current xml element name to the given string and returns true if equal */
-        bool IsElement( const char* pName) const;
+    /** Compares the current xml element name to the given string and returns true if equal */
+    bool IsElement(const char *pName) const;
 
-        /** Tests for the opening tag of the given element, throws an exception if not found */
-        void TestOpening( const char* pName);
+    /** Tests for the opening tag of the given element, throws an exception if not found */
+    void TestOpening(const char *pName);
 
-        /** Tests for the closing tag of the given element, throws an exception if not found */
-        void TestClosing( const char* pName);
+    /** Tests for the closing tag of the given element, throws an exception if not found */
+    void TestClosing(const char *pName);
 
-        /** Checks the present element for the presence of the attribute, returns its index
+    /** Checks the present element for the presence of the attribute, returns its index
          or throws an exception if not found */
-        int GetAttribute( const char* pAttr) const;
+    int GetAttribute(const char *pAttr) const;
 
-        /** Returns the index of the named attribute or -1 if not found. Does not throw,
+    /** Returns the index of the named attribute or -1 if not found. Does not throw,
          therefore useful for optional attributes */
-        int TestAttribute( const char* pAttr) const;
+    int TestAttribute(const char *pAttr) const;
 
-        /** Reads the text contents of an element, throws an exception if not given.
+    /** Reads the text contents of an element, throws an exception if not given.
          Skips leading whitespace. */
-        const char* GetTextContent();
+    const char *GetTextContent();
 
-        /** Reads the text contents of an element, returns NULL if not given.
+    /** Reads the text contents of an element, returns NULL if not given.
          Skips leading whitespace. */
-        const char* TestTextContent();
+    const char *TestTextContent();
 
-        /** Reads a single bool from current text content */
-        bool ReadBoolFromTextContent();
+    /** Reads a single bool from current text content */
+    bool ReadBoolFromTextContent();
 
-        /** Reads a single float from current text content */
-        ai_real ReadFloatFromTextContent();
+    /** Reads a single float from current text content */
+    ai_real ReadFloatFromTextContent();
 
-        /** Calculates the resulting transformation from all the given transform steps */
-        aiMatrix4x4 CalculateResultTransform( const std::vector<Collada::Transform>& pTransforms) const;
+    /** Calculates the resulting transformation from all the given transform steps */
+    aiMatrix4x4 CalculateResultTransform(const std::vector<Collada::Transform> &pTransforms) const;
 
-        /** Determines the input data type for the given semantic string */
-        Collada::InputType GetTypeForSemantic( const std::string& pSemantic);
+    /** Determines the input data type for the given semantic string */
+    Collada::InputType GetTypeForSemantic(const std::string &pSemantic);
 
-        /** Finds the item in the given library by its reference, throws if not found */
-        template <typename Type> const Type& ResolveLibraryReference( const std::map<std::string, Type>& pLibrary, const std::string& pURL) const;
+    /** Finds the item in the given library by its reference, throws if not found */
+    template <typename Type>
+    const Type &ResolveLibraryReference(const std::map<std::string, Type> &pLibrary, const std::string &pURL) const;
 
-    protected:
-        /** Filename, for a verbose error message */
-        std::string mFileName;
+protected:
+    /** Filename, for a verbose error message */
+    std::string mFileName;
 
-        /** XML reader, member for everyday use */
-        irr::io::IrrXMLReader* mReader;
+    /** XML reader, member for everyday use */
+    irr::io::IrrXMLReader *mReader;
 
-        /** All data arrays found in the file by ID. Might be referred to by actually
+    /** All data arrays found in the file by ID. Might be referred to by actually
          everyone. Collada, you are a steaming pile of indirection. */
-        typedef std::map<std::string, Collada::Data> DataLibrary;
-        DataLibrary mDataLibrary;
+    typedef std::map<std::string, Collada::Data> DataLibrary;
+    DataLibrary mDataLibrary;
 
-        /** Same for accessors which define how the data in a data array is accessed. */
-        typedef std::map<std::string, Collada::Accessor> AccessorLibrary;
-        AccessorLibrary mAccessorLibrary;
+    /** Same for accessors which define how the data in a data array is accessed. */
+    typedef std::map<std::string, Collada::Accessor> AccessorLibrary;
+    AccessorLibrary mAccessorLibrary;
 
-        /** Mesh library: mesh by ID */
-        typedef std::map<std::string, Collada::Mesh*> MeshLibrary;
-        MeshLibrary mMeshLibrary;
+    /** Mesh library: mesh by ID */
+    typedef std::map<std::string, Collada::Mesh *> MeshLibrary;
+    MeshLibrary mMeshLibrary;
 
-        /** node library: root node of the hierarchy part by ID */
-        typedef std::map<std::string, Collada::Node*> NodeLibrary;
-        NodeLibrary mNodeLibrary;
+    /** node library: root node of the hierarchy part by ID */
+    typedef std::map<std::string, Collada::Node *> NodeLibrary;
+    NodeLibrary mNodeLibrary;
 
-        /** Image library: stores texture properties by ID */
-        typedef std::map<std::string, Collada::Image> ImageLibrary;
-        ImageLibrary mImageLibrary;
+    /** Image library: stores texture properties by ID */
+    typedef std::map<std::string, Collada::Image> ImageLibrary;
+    ImageLibrary mImageLibrary;
 
-        /** Effect library: surface attributes by ID */
-        typedef std::map<std::string, Collada::Effect> EffectLibrary;
-        EffectLibrary mEffectLibrary;
+    /** Effect library: surface attributes by ID */
+    typedef std::map<std::string, Collada::Effect> EffectLibrary;
+    EffectLibrary mEffectLibrary;
 
-        /** Material library: surface material by ID */
-        typedef std::map<std::string, Collada::Material> MaterialLibrary;
-        MaterialLibrary mMaterialLibrary;
+    /** Material library: surface material by ID */
+    typedef std::map<std::string, Collada::Material> MaterialLibrary;
+    MaterialLibrary mMaterialLibrary;
 
-        /** Light library: surface light by ID */
-        typedef std::map<std::string, Collada::Light> LightLibrary;
-        LightLibrary mLightLibrary;
+    /** Light library: surface light by ID */
+    typedef std::map<std::string, Collada::Light> LightLibrary;
+    LightLibrary mLightLibrary;
 
-        /** Camera library: surface material by ID */
-        typedef std::map<std::string, Collada::Camera> CameraLibrary;
-        CameraLibrary mCameraLibrary;
+    /** Camera library: surface material by ID */
+    typedef std::map<std::string, Collada::Camera> CameraLibrary;
+    CameraLibrary mCameraLibrary;
 
-        /** Controller library: joint controllers by ID */
-        typedef std::map<std::string, Collada::Controller> ControllerLibrary;
-        ControllerLibrary mControllerLibrary;
+    /** Controller library: joint controllers by ID */
+    typedef std::map<std::string, Collada::Controller> ControllerLibrary;
+    ControllerLibrary mControllerLibrary;
 
-		/** Animation library: animation references by ID */
-		typedef std::map<std::string, Collada::Animation*> AnimationLibrary;
-		AnimationLibrary mAnimationLibrary;
+    /** Animation library: animation references by ID */
+    typedef std::map<std::string, Collada::Animation *> AnimationLibrary;
+    AnimationLibrary mAnimationLibrary;
 
-		/** Animation clip library: clip animation references by ID */
-		typedef std::vector<std::pair<std::string, std::vector<std::string> > > AnimationClipLibrary;
-		AnimationClipLibrary mAnimationClipLibrary;
+    /** Animation clip library: clip animation references by ID */
+    typedef std::vector<std::pair<std::string, std::vector<std::string>>> AnimationClipLibrary;
+    AnimationClipLibrary mAnimationClipLibrary;
 
-        /** Pointer to the root node. Don't delete, it just points to one of
+    /** Pointer to the root node. Don't delete, it just points to one of
          the nodes in the node library. */
-        Collada::Node* mRootNode;
-
-        /** Root animation container */
-        Collada::Animation mAnims;
-
-        /** Size unit: how large compared to a meter */
-        ai_real mUnitSize;
-
-        /** Which is the up vector */
-        enum { UP_X, UP_Y, UP_Z } mUpDirection;
-
-        /** Asset metadata (global for scene) */
-        StringMetaData mAssetMetaData;
-
-        /** Collada file format version */
-        Collada::FormatVersion mFormat;
-    };
-
-    // ------------------------------------------------------------------------------------------------
-    // Check for element match
-    inline bool ColladaParser::IsElement( const char* pName) const
-    {
-        ai_assert( mReader->getNodeType() == irr::io::EXN_ELEMENT);
-        return ::strcmp( mReader->getNodeName(), pName) == 0;
-    }
-
-    // ------------------------------------------------------------------------------------------------
-    // Finds the item in the given library by its reference, throws if not found
-    template <typename Type>
-    const Type& ColladaParser::ResolveLibraryReference( const std::map<std::string, Type>& pLibrary, const std::string& pURL) const
-    {
-        typename std::map<std::string, Type>::const_iterator it = pLibrary.find( pURL);
-        if( it == pLibrary.end())
-            ThrowException( Formatter::format() << "Unable to resolve library reference \"" << pURL << "\"." );
-        return it->second;
-    }
+    Collada::Node *mRootNode;
+
+    /** Root animation container */
+    Collada::Animation mAnims;
+
+    /** Size unit: how large compared to a meter */
+    ai_real mUnitSize;
+
+    /** Which is the up vector */
+    enum { UP_X,
+        UP_Y,
+        UP_Z } mUpDirection;
+
+    /** Asset metadata (global for scene) */
+    StringMetaData mAssetMetaData;
+
+    /** Collada file format version */
+    Collada::FormatVersion mFormat;
+};
+
+// ------------------------------------------------------------------------------------------------
+// Check for element match
+inline bool ColladaParser::IsElement(const char *pName) const {
+    ai_assert(mReader->getNodeType() == irr::io::EXN_ELEMENT);
+    return ::strcmp(mReader->getNodeName(), pName) == 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds the item in the given library by its reference, throws if not found
+template <typename Type>
+const Type &ColladaParser::ResolveLibraryReference(const std::map<std::string, Type> &pLibrary, const std::string &pURL) const {
+    typename std::map<std::string, Type>::const_iterator it = pLibrary.find(pURL);
+    if (it == pLibrary.end())
+        ThrowException(Formatter::format() << "Unable to resolve library reference \"" << pURL << "\".");
+    return it->second;
+}
 
 } // end of namespace Assimp
 

+ 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);

+ 50 - 26
code/AssetLib/glTF2/glTF2Asset.h

@@ -1,4 +1,4 @@
-/*
+/*
 Open Asset Import Library (assimp)
 ----------------------------------------------------------------------
 
@@ -175,19 +175,19 @@ enum ComponentType {
 
 inline unsigned int ComponentTypeSize(ComponentType t) {
     switch (t) {
-        case ComponentType_SHORT:
-        case ComponentType_UNSIGNED_SHORT:
-            return 2;
-
-        case ComponentType_UNSIGNED_INT:
-        case ComponentType_FLOAT:
-            return 4;
-
-        case ComponentType_BYTE:
-        case ComponentType_UNSIGNED_BYTE:
-            return 1;
-        default:
-            throw DeadlyImportError("GLTF: Unsupported Component Type " + to_string(t));
+    case ComponentType_SHORT:
+    case ComponentType_UNSIGNED_SHORT:
+        return 2;
+
+    case ComponentType_UNSIGNED_INT:
+    case ComponentType_FLOAT:
+        return 4;
+
+    case ComponentType_BYTE:
+    case ComponentType_UNSIGNED_BYTE:
+        return 1;
+    default:
+        throw DeadlyImportError("GLTF: Unsupported Component Type " + to_string(t));
     }
 }
 
@@ -318,9 +318,11 @@ class Ref {
 
 public:
     Ref() :
-            vector(0), index(0) {}
+            vector(0),
+            index(0) {}
     Ref(std::vector<T *> &vec, unsigned int idx) :
-            vector(&vec), index(idx) {}
+            vector(&vec),
+            index(idx) {}
 
     inline unsigned int GetIndex() const { return index; }
 
@@ -340,7 +342,8 @@ struct Nullable {
     Nullable() :
             isPresent(false) {}
     Nullable(T &val) :
-            value(val), isPresent(true) {}
+            value(val),
+            isPresent(true) {}
 };
 
 //! Base class for all glTF top-level objects
@@ -383,17 +386,19 @@ struct Accessor : public Object {
 
     inline uint8_t *GetPointer();
 
-    template<class T>
+    template <class T>
     void ExtractData(T *&outData);
 
     void WriteData(size_t count, const void *src_buffer, size_t src_stride);
+    void WriteSparseValues(size_t count, const void *src_data, size_t src_dataStride);
+    void WriteSparseIndices(size_t count, const void *src_idx, size_t src_idxStride);
 
     //! Helper class to iterate the data
     class Indexer {
         friend struct Accessor;
 
-    // This field is reported as not used, making it protectd is the easiest way to work around it without going to the bottom of what the problem is:
-    // ../code/glTF2/glTF2Asset.h:392:19: error: private field 'accessor' is not used [-Werror,-Wunused-private-field]
+        // This field is reported as not used, making it protectd is the easiest way to work around it without going to the bottom of what the problem is:
+        // ../code/glTF2/glTF2Asset.h:392:19: error: private field 'accessor' is not used [-Werror,-Wunused-private-field]
     protected:
         Accessor &accessor;
 
@@ -402,8 +407,7 @@ struct Accessor : public Object {
         size_t elemSize, stride;
 
         Indexer(Accessor &acc);
-    
-        
+
     public:
         //! Accesses the i-th value as defined by the accessor
         template <class T>
@@ -468,7 +472,11 @@ public:
         /// \param [in] pDecodedData_Length - size of encoded region, in bytes.
         /// \param [in] pID - ID of the region.
         SEncodedRegion(const size_t pOffset, const size_t pEncodedData_Length, uint8_t *pDecodedData, const size_t pDecodedData_Length, const std::string pID) :
-                Offset(pOffset), EncodedData_Length(pEncodedData_Length), DecodedData(pDecodedData), DecodedData_Length(pDecodedData_Length), ID(pID) {}
+                Offset(pOffset),
+                EncodedData_Length(pEncodedData_Length),
+                DecodedData(pDecodedData),
+                DecodedData_Length(pDecodedData_Length),
+                ID(pID) {}
 
         /// \fn ~SEncodedRegion()
         /// Destructor.
@@ -600,7 +608,8 @@ struct Camera : public Object {
     } cameraProperties;
 
     Camera() :
-            type(Perspective), cameraProperties() {
+            type(Perspective),
+            cameraProperties() {
         // empty
     }
     void Read(Value &obj, Asset &r);
@@ -905,7 +914,7 @@ class LazyDict : public LazyDictBase {
     Value *mDict; //! JSON dictionary object
     Asset &mAsset; //! The asset instance
 
-    std::gltf_unordered_set<unsigned int> mRecursiveReferenceCheck;  //! Used by Retrieve to prevent recursive lookups
+    std::gltf_unordered_set<unsigned int> mRecursiveReferenceCheck; //! Used by Retrieve to prevent recursive lookups
 
     void AttachToDocument(Document &doc);
     void DetachFromDocument();
@@ -1019,7 +1028,22 @@ public:
 
 public:
     Asset(IOSystem *io = 0) :
-            mIOSystem(io), asset(), accessors(*this, "accessors"), animations(*this, "animations"), buffers(*this, "buffers"), bufferViews(*this, "bufferViews"), cameras(*this, "cameras"), lights(*this, "lights", "KHR_lights_punctual"), images(*this, "images"), materials(*this, "materials"), meshes(*this, "meshes"), nodes(*this, "nodes"), samplers(*this, "samplers"), scenes(*this, "scenes"), skins(*this, "skins"), textures(*this, "textures") {
+            mIOSystem(io),
+            asset(),
+            accessors(*this, "accessors"),
+            animations(*this, "animations"),
+            buffers(*this, "buffers"),
+            bufferViews(*this, "bufferViews"),
+            cameras(*this, "cameras"),
+            lights(*this, "lights", "KHR_lights_punctual"),
+            images(*this, "images"),
+            materials(*this, "materials"),
+            meshes(*this, "meshes"),
+            nodes(*this, "nodes"),
+            samplers(*this, "samplers"),
+            scenes(*this, "scenes"),
+            skins(*this, "skins"),
+            textures(*this, "textures") {
         memset(&extensionsUsed, 0, sizeof(extensionsUsed));
         memset(&extensionsRequired, 0, sizeof(extensionsRequired));
     }

+ 57 - 25
code/AssetLib/glTF2/glTF2Asset.inl

@@ -1,30 +1,22 @@
 /*
 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
@@ -36,7 +28,6 @@ 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.
-
 ----------------------------------------------------------------------
 */
 
@@ -180,7 +171,10 @@ inline Value *FindObject(Value &val, const char *id) {
 
 template <class T>
 inline LazyDict<T>::LazyDict(Asset &asset, const char *dictId, const char *extId) :
-        mDictId(dictId), mExtId(extId), mDict(0), mAsset(asset) {
+        mDictId(dictId),
+        mExtId(extId),
+        mDict(0),
+        mAsset(asset) {
     asset.mDicts.push_back(this); // register to the list of dictionaries
 }
 
@@ -342,7 +336,10 @@ Ref<T> LazyDict<T>::Create(const char *id) {
 //
 
 inline Buffer::Buffer() :
-        byteLength(0), type(Type_arraybuffer), EncodedRegion_Current(nullptr), mIsSpecial(false) {}
+        byteLength(0),
+        type(Type_arraybuffer),
+        EncodedRegion_Current(nullptr),
+        mIsSpecial(false) {}
 
 inline Buffer::~Buffer() {
     for (SEncodedRegion *reg : EncodedRegion_List)
@@ -517,7 +514,7 @@ inline void Buffer::Grow(size_t amount) {
     if (amount <= 0) {
         return;
     }
-    
+
     // Capacity is big enough
     if (capacity >= byteLength + amount) {
         byteLength += amount;
@@ -565,9 +562,11 @@ inline uint8_t *BufferView::GetPointer(size_t accOffset) {
 
     return basePtr + offset;
 }
+
 //
 // struct Accessor
 //
+
 inline void Accessor::Sparse::PopulateData(size_t numBytes, uint8_t *bytes) {
     if (bytes) {
         data.assign(bytes, bytes + numBytes);
@@ -634,6 +633,7 @@ inline void Accessor::Read(Value &obj, Asset &r) {
             sparse->indicesByteOffset = MemberOrDefault(*indicesValue, "byteOffset", size_t(0));
             //indices componentType
             sparse->indicesType = MemberOrDefault(*indicesValue, "componentType", ComponentType_BYTE);
+            //sparse->indices->Read(*indicesValue, r);
         }
 
         // value
@@ -643,6 +643,7 @@ inline void Accessor::Read(Value &obj, Asset &r) {
             sparse->values = r.bufferViews.Retrieve(valueViewID->GetUint());
             //value byteOffset
             sparse->valuesByteOffset = MemberOrDefault(*valuesValue, "byteOffset", size_t(0));
+            //sparse->values->Read(*valuesValue, r);
         }
 
         // indicesType
@@ -709,10 +710,9 @@ inline void CopyData(size_t count,
 }
 } // namespace
 
-template<class T>
-void Accessor::ExtractData(T *&outData)
-{
-    uint8_t* data = GetPointer();
+template <class T>
+void Accessor::ExtractData(T *&outData) {
+    uint8_t *data = GetPointer();
     if (!data) {
         throw DeadlyImportError("GLTF: data is NULL");
     }
@@ -749,8 +749,38 @@ inline void Accessor::WriteData(size_t _count, const void *src_buffer, size_t sr
     CopyData(_count, src, src_stride, dst, dst_stride);
 }
 
+inline void Accessor::WriteSparseValues(size_t _count, const void *src_data, size_t src_dataStride) {
+    if (!sparse)
+        return;
+
+    // values
+    uint8_t *value_buffer_ptr = sparse->values->buffer->GetPointer();
+    size_t value_offset = sparse->valuesByteOffset + sparse->values->byteOffset;
+    size_t value_dst_stride = GetNumComponents() * GetBytesPerComponent();
+    const uint8_t *value_src = reinterpret_cast<const uint8_t *>(src_data);
+    uint8_t *value_dst = reinterpret_cast<uint8_t *>(value_buffer_ptr + value_offset);
+    ai_assert(value_dst + _count * value_dst_stride <= value_buffer_ptr + sparse->values->buffer->byteLength);
+    CopyData(_count, value_src, src_dataStride, value_dst, value_dst_stride);
+}
+
+inline void Accessor::WriteSparseIndices(size_t _count, const void *src_idx, size_t src_idxStride) {
+    if (!sparse)
+        return;
+
+    // indices
+    uint8_t *indices_buffer_ptr = sparse->indices->buffer->GetPointer();
+    size_t indices_offset = sparse->indicesByteOffset + sparse->indices->byteOffset;
+    size_t indices_dst_stride = 1 * sizeof(unsigned short);
+    const uint8_t *indices_src = reinterpret_cast<const uint8_t *>(src_idx);
+    uint8_t *indices_dst = reinterpret_cast<uint8_t *>(indices_buffer_ptr + indices_offset);
+    ai_assert(indices_dst + _count * indices_dst_stride <= indices_buffer_ptr + sparse->indices->buffer->byteLength);
+    CopyData(_count, indices_src, src_idxStride, indices_dst, indices_dst_stride);
+}
 inline Accessor::Indexer::Indexer(Accessor &acc) :
-        accessor(acc), data(acc.GetPointer()), elemSize(acc.GetElementSize()), stride(acc.bufferView && acc.bufferView->byteStride ? acc.bufferView->byteStride : elemSize) {
+        accessor(acc),
+        data(acc.GetPointer()),
+        elemSize(acc.GetElementSize()),
+        stride(acc.bufferView && acc.bufferView->byteStride ? acc.bufferView->byteStride : elemSize) {
 }
 
 //! Accesses the i-th value as defined by the accessor
@@ -765,7 +795,9 @@ T Accessor::Indexer::GetValue(int i) {
 }
 
 inline Image::Image() :
-        width(0), height(0), mDataLength(0) {
+        width(0),
+        height(0),
+        mDataLength(0) {
 }
 
 inline void Image::Read(Value &obj, Asset &r) {
@@ -1003,8 +1035,8 @@ inline int Compare(const char *attr, const char (&str)[N]) {
 }
 
 #ifdef _WIN32
-#    pragma warning(push)
-#    pragma warning(disable : 4706)
+#pragma warning(push)
+#pragma warning(disable : 4706)
 #endif // _WIN32
 
 inline bool GetAttribVector(Mesh::Primitive &p, const char *attr, Mesh::AccessorList *&v, int &pos) {
@@ -1124,11 +1156,11 @@ inline void Mesh::Read(Value &pJSON_Object, Asset &pAsset_Root) {
     }
 
     Value *extras = FindObject(pJSON_Object, "extras");
-    if (nullptr != extras ) {
-        if (Value* curTargetNames = FindArray(*extras, "targetNames")) {
+    if (nullptr != extras) {
+        if (Value *curTargetNames = FindArray(*extras, "targetNames")) {
             this->targetNames.resize(curTargetNames->Size());
             for (unsigned int i = 0; i < curTargetNames->Size(); ++i) {
-                Value& targetNameValue = (*curTargetNames)[i];
+                Value &targetNameValue = (*curTargetNames)[i];
                 if (targetNameValue.IsString()) {
                     this->targetNames[i] = targetNameValue.GetString();
                 }
@@ -1638,7 +1670,7 @@ inline std::string Asset::FindUniqueID(const std::string &str, const char *suffi
 }
 
 #ifdef _WIN32
-#    pragma warning(pop)
+#pragma warning(pop)
 #endif // _WIN32
 
-} // namespace glTF2
+} // namespace glTF2

+ 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 - 0
code/CMakeLists.txt

@@ -66,6 +66,7 @@ SET( PUBLIC_HEADERS
   ${HEADER_PATH}/color4.h
   ${HEADER_PATH}/color4.inl
   ${CMAKE_CURRENT_BINARY_DIR}/../include/assimp/config.h
+  ${HEADER_PATH}/ColladaMetaData.h
   ${HEADER_PATH}/commonMetaData.h
   ${HEADER_PATH}/defs.h
   ${HEADER_PATH}/Defines.h

+ 1704 - 0
code/Collada/ColladaExporter.cpp

@@ -0,0 +1,1704 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
+
+#include "ColladaExporter.h"
+#include <assimp/Bitmap.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/MathFunctions.h>
+#include <assimp/fast_atof.h>
+#include <assimp/SceneCombiner.h>
+#include <assimp/StringUtils.h>
+#include <assimp/XMLTools.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/scene.h>
+
+#include <assimp/Exceptional.h>
+
+#include <memory>
+#include <ctime>
+#include <set>
+#include <vector>
+#include <iostream>
+
+using namespace Assimp;
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+// Worker function for exporting a scene to Collada. Prototyped and registered in Exporter.cpp
+void ExportSceneCollada(const char* pFile, IOSystem* pIOSystem, const aiScene* pScene, const ExportProperties* /*pProperties*/) {
+    std::string path = DefaultIOSystem::absolutePath(std::string(pFile));
+    std::string file = DefaultIOSystem::completeBaseName(std::string(pFile));
+
+    // invoke the exporter
+    ColladaExporter iDoTheExportThing( pScene, pIOSystem, path, file);
+    
+    if (iDoTheExportThing.mOutput.fail()) {
+        throw DeadlyExportError("output data creation failed. Most likely the file became too large: " + std::string(pFile));
+    }
+
+    // we're still here - export successfully completed. Write result to the given IOSYstem
+    std::unique_ptr<IOStream> outfile (pIOSystem->Open(pFile,"wt"));
+    if(outfile == NULL) {
+        throw DeadlyExportError("could not open output .dae file: " + std::string(pFile));
+    }
+
+    // XXX maybe use a small wrapper around IOStream that behaves like std::stringstream in order to avoid the extra copy.
+    outfile->Write( iDoTheExportThing.mOutput.str().c_str(), static_cast<size_t>(iDoTheExportThing.mOutput.tellp()),1);
+}
+
+} // end of namespace Assimp
+
+// ------------------------------------------------------------------------------------------------
+// Encodes a string into a valid XML ID using the xsd:ID schema qualifications.
+static const std::string XMLIDEncode(const std::string& name) {
+    const char XML_ID_CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.";
+    const unsigned int XML_ID_CHARS_COUNT = sizeof(XML_ID_CHARS) / sizeof(char);
+
+    if (name.length() == 0) {
+        return name;
+    }
+
+    std::stringstream idEncoded;
+
+    // xsd:ID must start with letter or underscore
+    if (!((name[0] >= 'A' && name[0] <= 'z') || name[0] == '_')) {
+        idEncoded << '_';
+    }
+
+    for (std::string::const_iterator it = name.begin(); it != name.end(); ++it) {
+        // xsd:ID can only contain letters, digits, underscores, hyphens and periods
+        if (strchr(XML_ID_CHARS, *it) != nullptr) {
+            idEncoded << *it;
+        } else {
+            // Select placeholder character based on invalid character to prevent name collisions 
+            idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
+        }
+    }
+
+    return idEncoded.str();
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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) {
+    // 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();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor
+ColladaExporter::~ColladaExporter() {
+    if ( mSceneOwned ) {
+        delete mScene;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Starts writing the contents
+void ColladaExporter::WriteFile() {
+    // write the DTD
+    mOutput << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>" << endstr;
+    // COLLADA element start
+    mOutput << "<COLLADA xmlns=\"http://www.collada.org/2005/11/COLLADASchema\" version=\"1.4.1\">" << endstr;
+    PushTag();
+
+    WriteTextures();
+    WriteHeader();
+
+    WriteCamerasLibrary();
+    WriteLightsLibrary();
+    WriteMaterials();
+    WriteGeometryLibrary();
+    WriteControllerLibrary();
+
+    WriteSceneLibrary();
+	
+	// customized, Writes the animation library
+	WriteAnimationsLibrary();
+
+    // useless Collada fu at the end, just in case we haven't had enough indirections, yet.
+    mOutput << startstr << "<scene>" << endstr;
+    PushTag();
+    mOutput << startstr << "<instance_visual_scene url=\"#" + XMLIDEncode(mScene->mRootNode->mName.C_Str()) + "\" />" << endstr;
+    PopTag();
+    mOutput << startstr << "</scene>" << endstr;
+    PopTag();
+    mOutput << "</COLLADA>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the asset header
+void ColladaExporter::WriteHeader() {
+    static const ai_real epsilon = Math::getEpsilon<ai_real>();
+    static const aiQuaternion x_rot(aiMatrix3x3(
+        0, -1,  0,
+        1,  0,  0,
+        0,  0,  1));
+    static const aiQuaternion y_rot(aiMatrix3x3(
+        1,  0,  0,
+        0,  1,  0,
+        0,  0,  1));
+    static const aiQuaternion z_rot(aiMatrix3x3(
+        1,  0,  0,
+        0,  0,  1,
+        0, -1,  0));
+
+    static const unsigned int date_nb_chars = 20;
+    char date_str[date_nb_chars];
+    std::time_t date = std::time(NULL);
+    std::strftime(date_str, date_nb_chars, "%Y-%m-%dT%H:%M:%S", std::localtime(&date));
+
+    aiVector3D scaling;
+    aiQuaternion rotation;
+    aiVector3D position;
+    mScene->mRootNode->mTransformation.Decompose(scaling, rotation, position);
+    rotation.Normalize();
+
+    bool add_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;
+    }
+
+    std::string up_axis = "Y_UP";
+    if(rotation.Equal(x_rot, epsilon)) {
+        up_axis = "X_UP";
+    } else if(rotation.Equal(y_rot, epsilon)) {
+        up_axis = "Y_UP";
+    } else if(rotation.Equal(z_rot, epsilon)) {
+        up_axis = "Z_UP";
+    } else {
+        add_root_node = true;
+    }
+
+    if(! position.Equal(aiVector3D(0, 0, 0))) {
+        add_root_node = true;
+    }
+
+    if(mScene->mRootNode->mNumChildren == 0) {
+        add_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;
+
+        up_axis = "Y_UP";
+        scale = 1.0;
+    }
+
+    mOutput << startstr << "<asset>" << endstr;
+    PushTag();
+    mOutput << startstr << "<contributor>" << endstr;
+    PushTag();
+
+    // If no Scene metadata, use root node metadata
+    aiMetadata* meta = mScene->mMetaData;
+    if (nullptr == meta) {
+        meta = mScene->mRootNode->mMetaData;
+    }
+
+    aiString value;
+    if (!meta || !meta->Get("Author", value)) {
+        mOutput << startstr << "<author>" << "Assimp" << "</author>" << endstr;
+    } else {
+        mOutput << startstr << "<author>" << XMLEscape(value.C_Str()) << "</author>" << endstr;
+    }
+
+    if (nullptr == meta || !meta->Get(AI_METADATA_SOURCE_GENERATOR, value)) {
+        mOutput << startstr << "<authoring_tool>" << "Assimp Exporter" << "</authoring_tool>" << endstr;
+    } else {
+        mOutput << startstr << "<authoring_tool>" << XMLEscape(value.C_Str()) << "</authoring_tool>" << endstr;
+    }
+
+    if (meta) {
+        if (meta->Get("Comments", value)) {
+            mOutput << startstr << "<comments>" << XMLEscape(value.C_Str()) << "</comments>" << endstr;
+        }
+        if (meta->Get(AI_METADATA_SOURCE_COPYRIGHT, value)) {
+            mOutput << startstr << "<copyright>" << XMLEscape(value.C_Str()) << "</copyright>" << endstr;
+        }
+        if (meta->Get("SourceData", value)) {
+            mOutput << startstr << "<source_data>" << XMLEscape(value.C_Str()) << "</source_data>" << endstr;
+        }
+    }
+
+    PopTag();
+    mOutput << startstr << "</contributor>" << endstr;
+
+    if (nullptr == meta || !meta->Get("Created", value)) {
+        mOutput << startstr << "<created>" << date_str << "</created>" << endstr;
+    } else {
+        mOutput << startstr << "<created>" << XMLEscape(value.C_Str()) << "</created>" << endstr;
+    }
+
+    // Modified date is always the date saved
+    mOutput << startstr << "<modified>" << date_str << "</modified>" << endstr;
+
+    if (meta) {
+        if (meta->Get("Keywords", value)) {
+            mOutput << startstr << "<keywords>" << XMLEscape(value.C_Str()) << "</keywords>" << endstr;
+        }
+        if (meta->Get("Revision", value)) {
+            mOutput << startstr << "<revision>" << XMLEscape(value.C_Str()) << "</revision>" << endstr;
+        }
+        if (meta->Get("Subject", value)) {
+            mOutput << startstr << "<subject>" << XMLEscape(value.C_Str()) << "</subject>" << endstr;
+        }
+        if (meta->Get("Title", value)) {
+            mOutput << startstr << "<title>" << XMLEscape(value.C_Str()) << "</title>" << endstr;
+        }
+    }
+
+    mOutput << startstr << "<unit name=\"meter\" meter=\"" << scale << "\" />" << endstr;
+    mOutput << startstr << "<up_axis>" << up_axis << "</up_axis>" << endstr;
+    PopTag();
+    mOutput << startstr << "</asset>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Write the embedded textures
+void ColladaExporter::WriteTextures() {
+    static const unsigned int buffer_size = 1024;
+    char str[buffer_size];
+
+    if (mScene->HasTextures()) {
+        for(unsigned int i = 0; i < mScene->mNumTextures; i++) {
+            // It would be great to be able to create a directory in portable standard C++, but it's not the case,
+            // so we just write the textures in the current directory.
+
+            aiTexture* texture = mScene->mTextures[i];
+            if ( nullptr == texture ) {
+                continue;
+            }
+
+            ASSIMP_itoa10(str, buffer_size, i + 1);
+
+            std::string name = mFile + "_texture_" + (i < 1000 ? "0" : "") + (i < 100 ? "0" : "") + (i < 10 ? "0" : "") + str + "." + ((const char*) texture->achFormatHint);
+
+            std::unique_ptr<IOStream> outfile(mIOSystem->Open(mPath + mIOSystem->getOsSeparator() + name, "wb"));
+            if(outfile == NULL) {
+                throw DeadlyExportError("could not open output texture file: " + mPath + name);
+            }
+
+            if(texture->mHeight == 0) {
+                outfile->Write((void*) texture->pcData, texture->mWidth, 1);
+            } else {
+                Bitmap::Save(texture, outfile.get());
+            }
+
+            outfile->Flush();
+
+            textures.insert(std::make_pair(i, name));
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Write the embedded textures
+void ColladaExporter::WriteCamerasLibrary() {
+    if(mScene->HasCameras()) {
+
+        mOutput << startstr << "<library_cameras>" << endstr;
+        PushTag();
+
+        for( size_t a = 0; a < mScene->mNumCameras; ++a)
+            WriteCamera( a);
+
+        PopTag();
+        mOutput << startstr << "</library_cameras>" << endstr;
+
+    }
+}
+
+void ColladaExporter::WriteCamera(size_t pIndex){
+
+    const aiCamera *cam = mScene->mCameras[pIndex];
+    const std::string cameraName = XMLEscape(cam->mName.C_Str());
+    const std::string cameraId = XMLIDEncode(cam->mName.C_Str());
+
+    mOutput << startstr << "<camera id=\"" << cameraId << "-camera\" name=\"" << cameraName << "\" >" << endstr;
+    PushTag();
+    mOutput << startstr << "<optics>" << endstr;
+    PushTag();
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    //assimp doesn't support the import of orthographic cameras! se we write
+    //always perspective
+    mOutput << startstr << "<perspective>" << endstr;
+    PushTag();
+    mOutput << startstr << "<xfov sid=\"xfov\">"<<
+                                AI_RAD_TO_DEG(cam->mHorizontalFOV)
+                        <<"</xfov>" << endstr;
+    mOutput << startstr << "<aspect_ratio>"
+                        <<      cam->mAspect
+                        << "</aspect_ratio>" << endstr;
+    mOutput << startstr << "<znear sid=\"znear\">"
+                        <<      cam->mClipPlaneNear
+                        <<  "</znear>" << endstr;
+    mOutput << startstr << "<zfar sid=\"zfar\">"
+                        <<      cam->mClipPlaneFar
+                        << "</zfar>" << endstr;
+    PopTag();
+    mOutput << startstr << "</perspective>" << endstr;
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+    PopTag();
+    mOutput << startstr << "</optics>" << endstr;
+    PopTag();
+    mOutput << startstr << "</camera>" << endstr;
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// Write the embedded textures
+void ColladaExporter::WriteLightsLibrary() {
+    if(mScene->HasLights()) {
+
+        mOutput << startstr << "<library_lights>" << endstr;
+        PushTag();
+
+        for( size_t a = 0; a < mScene->mNumLights; ++a)
+            WriteLight( a);
+
+        PopTag();
+        mOutput << startstr << "</library_lights>" << endstr;
+
+    }
+}
+
+void ColladaExporter::WriteLight(size_t pIndex){
+
+    const aiLight *light = mScene->mLights[pIndex];
+    const std::string lightName = XMLEscape(light->mName.C_Str());
+    const std::string lightId = XMLIDEncode(light->mName.C_Str());
+
+    mOutput << startstr << "<light id=\"" << lightId << "-light\" name=\""
+            << lightName << "\" >" << endstr;
+    PushTag();
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    switch(light->mType){
+        case aiLightSource_AMBIENT:
+            WriteAmbienttLight(light);
+            break;
+        case aiLightSource_DIRECTIONAL:
+            WriteDirectionalLight(light);
+            break;
+        case aiLightSource_POINT:
+            WritePointLight(light);
+            break;
+        case aiLightSource_SPOT:
+            WriteSpotLight(light);
+            break;
+        case aiLightSource_AREA:
+        case aiLightSource_UNDEFINED:
+        case _aiLightSource_Force32Bit:
+            break;
+    }
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</light>" << endstr;
+
+}
+
+void ColladaExporter::WritePointLight(const aiLight *const light){
+    const aiColor3D &color=  light->mColorDiffuse;
+    mOutput << startstr << "<point>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+    mOutput << startstr << "<constant_attenuation>"
+                            << light->mAttenuationConstant
+                        <<"</constant_attenuation>" << endstr;
+    mOutput << startstr << "<linear_attenuation>"
+                            << light->mAttenuationLinear
+                        <<"</linear_attenuation>" << endstr;
+    mOutput << startstr << "<quadratic_attenuation>"
+                            << light->mAttenuationQuadratic
+                        <<"</quadratic_attenuation>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</point>" << endstr;
+
+}
+
+void ColladaExporter::WriteDirectionalLight(const aiLight *const light){
+    const aiColor3D &color=  light->mColorDiffuse;
+    mOutput << startstr << "<directional>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</directional>" << endstr;
+
+}
+
+void ColladaExporter::WriteSpotLight(const aiLight *const light){
+
+    const aiColor3D &color=  light->mColorDiffuse;
+    mOutput << startstr << "<spot>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+    mOutput << startstr << "<constant_attenuation>"
+                                << light->mAttenuationConstant
+                            <<"</constant_attenuation>" << endstr;
+    mOutput << startstr << "<linear_attenuation>"
+                            << light->mAttenuationLinear
+                        <<"</linear_attenuation>" << endstr;
+    mOutput << startstr << "<quadratic_attenuation>"
+                            << light->mAttenuationQuadratic
+                        <<"</quadratic_attenuation>" << endstr;
+
+    const ai_real fallOffAngle = AI_RAD_TO_DEG(light->mAngleInnerCone);
+    mOutput << startstr <<"<falloff_angle sid=\"fall_off_angle\">"
+                                << fallOffAngle
+                        <<"</falloff_angle>" << endstr;
+    double temp = light->mAngleOuterCone-light->mAngleInnerCone;
+
+    temp = std::cos(temp);
+    temp = std::log(temp)/std::log(0.1);
+    temp = 1/temp;
+    mOutput << startstr << "<falloff_exponent sid=\"fall_off_exponent\">"
+                            << temp
+                        <<"</falloff_exponent>" << endstr;
+
+
+    PopTag();
+    mOutput << startstr << "</spot>" << endstr;
+
+}
+
+void ColladaExporter::WriteAmbienttLight(const aiLight *const light){
+
+    const aiColor3D &color=  light->mColorAmbient;
+    mOutput << startstr << "<ambient>" << endstr;
+    PushTag();
+    mOutput << startstr << "<color sid=\"color\">"
+                            << color.r<<" "<<color.g<<" "<<color.b
+                        <<"</color>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</ambient>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a single surface entry from the given material keys
+void ColladaExporter::ReadMaterialSurface( Surface& poSurface, const aiMaterial* pSrcMat, 
+                                          aiTextureType pTexture, const char* pKey, size_t pType, size_t pIndex) {
+  if( pSrcMat->GetTextureCount( pTexture) > 0 ) {
+    aiString texfile;
+    unsigned int uvChannel = 0;
+    pSrcMat->GetTexture( pTexture, 0, &texfile, NULL, &uvChannel);
+
+    std::string index_str(texfile.C_Str());
+
+    if(index_str.size() != 0 && index_str[0] == '*') {
+        unsigned int index;
+
+        index_str = index_str.substr(1, std::string::npos);
+
+        try {
+            index = (unsigned int) strtoul10_64(index_str.c_str());
+        } catch(std::exception& error) {
+            throw DeadlyExportError(error.what());
+        }
+
+        std::map<unsigned int, std::string>::const_iterator name = textures.find(index);
+
+        if(name != textures.end()) {
+            poSurface.texture = name->second;
+        } else {
+            throw DeadlyExportError("could not find embedded texture at index " + index_str);
+        }
+    } else {
+        poSurface.texture = texfile.C_Str();
+    }
+
+    poSurface.channel = uvChannel;
+    poSurface.exist = true;
+  } else {
+    if( pKey )
+      poSurface.exist = pSrcMat->Get( pKey, static_cast<unsigned int>(pType), static_cast<unsigned int>(pIndex), poSurface.color) == aiReturn_SUCCESS;
+  }
+}
+
+static bool isalnum_C(char c) {
+  return ( nullptr != strchr("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",c) );
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes an image entry for the given surface
+void ColladaExporter::WriteImageEntry( const Surface& pSurface, const std::string& pNameAdd) {
+  if( !pSurface.texture.empty() )
+  {
+    mOutput << startstr << "<image id=\"" << XMLIDEncode(pNameAdd) << "\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<init_from>";
+
+    // URL encode image file name first, then XML encode on top
+    std::stringstream imageUrlEncoded;
+    for( std::string::const_iterator it = pSurface.texture.begin(); it != pSurface.texture.end(); ++it )
+    {
+      if( isalnum_C( (unsigned char) *it) || *it == ':' || *it == '_' || *it == '-' || *it == '.' || *it == '/' || *it == '\\' )
+        imageUrlEncoded << *it;
+      else
+        imageUrlEncoded << '%' << std::hex << size_t( (unsigned char) *it) << std::dec;
+    }
+    mOutput << XMLEscape(imageUrlEncoded.str());
+    mOutput << "</init_from>" << endstr;
+    PopTag();
+    mOutput << startstr << "</image>" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a color-or-texture entry into an effect definition
+void ColladaExporter::WriteTextureColorEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pImageName)
+{
+  if(pSurface.exist) {
+    mOutput << startstr << "<" << pTypeName << ">" << endstr;
+    PushTag();
+    if( pSurface.texture.empty() )
+    {
+      mOutput << startstr << "<color sid=\"" << pTypeName << "\">" << pSurface.color.r << "   " << pSurface.color.g << "   " << pSurface.color.b << "   " << pSurface.color.a << "</color>" << endstr;
+    }
+    else
+    {
+      mOutput << startstr << "<texture texture=\"" << XMLIDEncode(pImageName) << "\" texcoord=\"CHANNEL" << pSurface.channel << "\" />" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</" << pTypeName << ">" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the two parameters necessary for referencing a texture in an effect entry
+void ColladaExporter::WriteTextureParamEntry( const Surface& pSurface, const std::string& pTypeName, const std::string& pMatName)
+{
+  // if surface is a texture, write out the sampler and the surface parameters necessary to reference the texture
+  if( !pSurface.texture.empty() )
+  {
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<surface type=\"2D\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<init_from>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-image</init_from>" << endstr;
+    PopTag();
+    mOutput << startstr << "</surface>" << endstr;
+    PopTag();
+    mOutput << startstr << "</newparam>" << endstr;
+
+    mOutput << startstr << "<newparam sid=\"" << XMLIDEncode(pMatName) << "-" << pTypeName << "-sampler\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<sampler2D>" << endstr;
+    PushTag();
+    mOutput << startstr << "<source>" << XMLIDEncode(pMatName) << "-" << pTypeName << "-surface</source>" << endstr;
+    PopTag();
+    mOutput << startstr << "</sampler2D>" << endstr;
+    PopTag();
+    mOutput << startstr << "</newparam>" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a scalar property
+void ColladaExporter::WriteFloatEntry( const Property& pProperty, const std::string& pTypeName)
+{
+    if(pProperty.exist) {
+        mOutput << startstr << "<" << pTypeName << ">" << endstr;
+        PushTag();
+        mOutput << startstr << "<float sid=\"" << pTypeName << "\">" << pProperty.value << "</float>" << endstr;
+        PopTag();
+        mOutput << startstr << "</" << pTypeName << ">" << endstr;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the material setup
+void ColladaExporter::WriteMaterials()
+{
+  materials.resize( mScene->mNumMaterials);
+
+  /// collect all materials from the scene
+  size_t numTextures = 0;
+  for( size_t a = 0; a < mScene->mNumMaterials; ++a )
+  {
+    const aiMaterial* mat = mScene->mMaterials[a];
+
+    aiString name;
+    if( mat->Get( AI_MATKEY_NAME, name) != aiReturn_SUCCESS ) {
+      name = "mat";
+      materials[a].name = std::string( "m") + to_string(a) + name.C_Str();
+    } else {
+      // try to use the material's name if no other material has already taken it, else append #
+      std::string testName = name.C_Str();
+      size_t materialCountWithThisName = 0;
+      for( size_t i = 0; i < a; i ++ ) {
+        if( materials[i].name == testName ) {
+          materialCountWithThisName ++;
+        }
+      }
+      if( materialCountWithThisName == 0 ) {
+        materials[a].name = name.C_Str();
+      } else {
+        materials[a].name = std::string(name.C_Str()) + to_string(materialCountWithThisName);
+      }
+    }
+
+    aiShadingMode shading = aiShadingMode_Flat;
+    materials[a].shading_model = "phong";
+    if(mat->Get( AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
+        if(shading == aiShadingMode_Phong) {
+            materials[a].shading_model = "phong";
+        } else if(shading == aiShadingMode_Blinn) {
+            materials[a].shading_model = "blinn";
+        } else if(shading == aiShadingMode_NoShading) {
+            materials[a].shading_model = "constant";
+        } else if(shading == aiShadingMode_Gouraud) {
+            materials[a].shading_model = "lambert";
+        }
+    }
+
+    ReadMaterialSurface( materials[a].ambient, mat, aiTextureType_AMBIENT, AI_MATKEY_COLOR_AMBIENT);
+    if( !materials[a].ambient.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].diffuse, mat, aiTextureType_DIFFUSE, AI_MATKEY_COLOR_DIFFUSE);
+    if( !materials[a].diffuse.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].specular, mat, aiTextureType_SPECULAR, AI_MATKEY_COLOR_SPECULAR);
+    if( !materials[a].specular.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].emissive, mat, aiTextureType_EMISSIVE, AI_MATKEY_COLOR_EMISSIVE);
+    if( !materials[a].emissive.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].reflective, mat, aiTextureType_REFLECTION, AI_MATKEY_COLOR_REFLECTIVE);
+    if( !materials[a].reflective.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].transparent, mat, aiTextureType_OPACITY, AI_MATKEY_COLOR_TRANSPARENT);
+    if( !materials[a].transparent.texture.empty() ) numTextures++;
+    ReadMaterialSurface( materials[a].normal, mat, aiTextureType_NORMALS, NULL, 0, 0);
+    if( !materials[a].normal.texture.empty() ) numTextures++;
+
+    materials[a].shininess.exist = mat->Get( AI_MATKEY_SHININESS, materials[a].shininess.value) == aiReturn_SUCCESS;
+    materials[a].transparency.exist = mat->Get( AI_MATKEY_OPACITY, materials[a].transparency.value) == aiReturn_SUCCESS;
+    materials[a].index_refraction.exist = mat->Get( AI_MATKEY_REFRACTI, materials[a].index_refraction.value) == aiReturn_SUCCESS;
+  }
+
+  // output textures if present
+  if( numTextures > 0 )
+  {
+    mOutput << startstr << "<library_images>" << endstr;
+    PushTag();
+    for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
+    {
+      const Material& mat = *it;
+      WriteImageEntry( mat.ambient, mat.name + "-ambient-image");
+      WriteImageEntry( mat.diffuse, mat.name + "-diffuse-image");
+      WriteImageEntry( mat.specular, mat.name + "-specular-image");
+      WriteImageEntry( mat.emissive, mat.name + "-emission-image");
+      WriteImageEntry( mat.reflective, mat.name + "-reflective-image");
+      WriteImageEntry( mat.transparent, mat.name + "-transparent-image");
+      WriteImageEntry( mat.normal, mat.name + "-normal-image");
+    }
+    PopTag();
+    mOutput << startstr << "</library_images>" << endstr;
+  }
+
+  // output effects - those are the actual carriers of information
+  if( !materials.empty() )
+  {
+    mOutput << startstr << "<library_effects>" << endstr;
+    PushTag();
+    for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
+    {
+      const Material& mat = *it;
+      // this is so ridiculous it must be right
+      mOutput << startstr << "<effect id=\"" << XMLIDEncode(mat.name) << "-fx\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+      PushTag();
+      mOutput << startstr << "<profile_COMMON>" << endstr;
+      PushTag();
+
+      // write sampler- and surface params for the texture entries
+      WriteTextureParamEntry( mat.emissive, "emission", mat.name);
+      WriteTextureParamEntry( mat.ambient, "ambient", mat.name);
+      WriteTextureParamEntry( mat.diffuse, "diffuse", mat.name);
+      WriteTextureParamEntry( mat.specular, "specular", mat.name);
+      WriteTextureParamEntry( mat.reflective, "reflective", mat.name);
+      WriteTextureParamEntry( mat.transparent, "transparent", mat.name);
+      WriteTextureParamEntry( mat.normal, "normal", mat.name);
+
+      mOutput << startstr << "<technique sid=\"standard\">" << endstr;
+      PushTag();
+      mOutput << startstr << "<" << mat.shading_model << ">" << endstr;
+      PushTag();
+
+      WriteTextureColorEntry( mat.emissive, "emission", mat.name + "-emission-sampler");
+      WriteTextureColorEntry( mat.ambient, "ambient", mat.name + "-ambient-sampler");
+      WriteTextureColorEntry( mat.diffuse, "diffuse", mat.name + "-diffuse-sampler");
+      WriteTextureColorEntry( mat.specular, "specular", mat.name + "-specular-sampler");
+      WriteFloatEntry(mat.shininess, "shininess");
+      WriteTextureColorEntry( mat.reflective, "reflective", mat.name + "-reflective-sampler");
+      WriteTextureColorEntry( mat.transparent, "transparent", mat.name + "-transparent-sampler");
+      WriteFloatEntry(mat.transparency, "transparency");
+      WriteFloatEntry(mat.index_refraction, "index_of_refraction");
+
+      if(! mat.normal.texture.empty()) {
+        WriteTextureColorEntry( mat.normal, "bump", mat.name + "-normal-sampler");
+      }
+
+      PopTag();
+      mOutput << startstr << "</" << mat.shading_model << ">" << endstr;
+      PopTag();
+      mOutput << startstr << "</technique>" << endstr;
+      PopTag();
+      mOutput << startstr << "</profile_COMMON>" << endstr;
+      PopTag();
+      mOutput << startstr << "</effect>" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</library_effects>" << endstr;
+
+    // write materials - they're just effect references
+    mOutput << startstr << "<library_materials>" << endstr;
+    PushTag();
+    for( std::vector<Material>::const_iterator it = materials.begin(); it != materials.end(); ++it )
+    {
+      const Material& mat = *it;
+      mOutput << startstr << "<material id=\"" << XMLIDEncode(mat.name) << "\" name=\"" << XMLEscape(mat.name) << "\">" << endstr;
+      PushTag();
+      mOutput << startstr << "<instance_effect url=\"#" << XMLIDEncode(mat.name) << "-fx\"/>" << endstr;
+      PopTag();
+      mOutput << startstr << "</material>" << endstr;
+    }
+    PopTag();
+    mOutput << startstr << "</library_materials>" << endstr;
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the controller library
+void ColladaExporter::WriteControllerLibrary()
+{
+    mOutput << startstr << "<library_controllers>" << endstr;
+    PushTag();
+    
+    for( size_t a = 0; a < mScene->mNumMeshes; ++a) {
+        WriteController( a);
+    }
+
+    PopTag();
+    mOutput << startstr << "</library_controllers>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a skin controller of the given mesh
+void ColladaExporter::WriteController( size_t pIndex)
+{
+    const aiMesh* mesh = mScene->mMeshes[pIndex];
+    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
+    const std::string idstrEscaped = XMLIDEncode(idstr);
+
+    if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
+        return;
+
+    if ( mesh->mNumBones == 0 )
+        return;
+
+    mOutput << startstr << "<controller id=\"" << idstrEscaped << "-skin\" ";
+    mOutput << "name=\"skinCluster" << pIndex << "\">"<< endstr;
+    PushTag();
+
+    mOutput << startstr << "<skin source=\"#" << idstrEscaped << "\">" << endstr;
+    PushTag();
+
+    // bind pose matrix
+    mOutput << startstr << "<bind_shape_matrix>" << endstr;
+    PushTag();
+
+    // I think it is identity in general cases.
+    aiMatrix4x4 mat;
+    mOutput << startstr << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << endstr;
+    mOutput << startstr << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << endstr;
+    mOutput << startstr << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << endstr;
+    mOutput << startstr << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4 << endstr;
+
+    PopTag();
+    mOutput << startstr << "</bind_shape_matrix>" << endstr;
+
+    mOutput << startstr << "<source id=\"" << idstrEscaped << "-skin-joints\" name=\"" << idstrEscaped << "-skin-joints\">" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<Name_array id=\"" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
+
+    for( size_t i = 0; i < mesh->mNumBones; ++i )
+        mOutput << XMLIDEncode(mesh->mBones[i]->mName.C_Str()) << " ";
+
+    mOutput << "</Name_array>" << endstr;
+
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    
+    mOutput << startstr << "<accessor source=\"#" << idstrEscaped << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\" stride=\"" << 1 << "\">" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<param name=\"JOINT\" type=\"Name\"></param>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</accessor>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</source>" << endstr;
+
+    std::vector<ai_real> bind_poses;
+    bind_poses.reserve(mesh->mNumBones * 16);
+    for(unsigned int i = 0; i < mesh->mNumBones; ++i)
+        for( unsigned int j = 0; j < 4; ++j)
+            bind_poses.insert(bind_poses.end(), mesh->mBones[i]->mOffsetMatrix[j], mesh->mBones[i]->mOffsetMatrix[j] + 4);
+
+    WriteFloatArray( idstr + "-skin-bind_poses", FloatType_Mat4x4, (const ai_real*) bind_poses.data(), bind_poses.size() / 16);
+
+    bind_poses.clear();
+    
+    std::vector<ai_real> skin_weights;
+    skin_weights.reserve(mesh->mNumVertices * mesh->mNumBones);
+    for( size_t i = 0; i < mesh->mNumBones; ++i)
+        for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+            skin_weights.push_back(mesh->mBones[i]->mWeights[j].mWeight);
+
+    WriteFloatArray( idstr + "-skin-weights", FloatType_Weight, (const ai_real*) skin_weights.data(), skin_weights.size());
+
+    skin_weights.clear();
+
+    mOutput << startstr << "<joints>" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstrEscaped << "-skin-joints\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstrEscaped << "-skin-bind_poses\"></input>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</joints>" << endstr;
+
+    mOutput << startstr << "<vertex_weights count=\"" << mesh->mNumVertices << "\">" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<input semantic=\"JOINT\" source=\"#" << idstrEscaped << "-skin-joints\" offset=\"0\"></input>" << endstr;
+    mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstrEscaped << "-skin-weights\" offset=\"1\"></input>" << endstr;
+
+    mOutput << startstr << "<vcount>";
+
+    std::vector<ai_uint> num_influences(mesh->mNumVertices, (ai_uint)0);
+    for( size_t i = 0; i < mesh->mNumBones; ++i)
+        for( size_t j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+            ++num_influences[mesh->mBones[i]->mWeights[j].mVertexId];
+
+    for( size_t i = 0; i < mesh->mNumVertices; ++i)
+        mOutput << num_influences[i] << " ";
+
+    mOutput << "</vcount>" << endstr;
+
+    mOutput << startstr << "<v>";
+
+    ai_uint joint_weight_indices_length = 0;
+    std::vector<ai_uint> accum_influences;
+    accum_influences.reserve(num_influences.size());
+    for( size_t i = 0; i < num_influences.size(); ++i)
+    {
+        accum_influences.push_back(joint_weight_indices_length);
+        joint_weight_indices_length += num_influences[i];
+    }
+
+    ai_uint weight_index = 0;
+    std::vector<ai_int> joint_weight_indices(2 * joint_weight_indices_length, (ai_int)-1);
+    for( unsigned int i = 0; i < mesh->mNumBones; ++i)
+        for( unsigned j = 0; j < mesh->mBones[i]->mNumWeights; ++j)
+        {
+            unsigned int vId = mesh->mBones[i]->mWeights[j].mVertexId;
+            for( ai_uint k = 0; k < num_influences[vId]; ++k)
+            {
+                if (joint_weight_indices[2 * (accum_influences[vId] + k)] == -1)
+                {
+                    joint_weight_indices[2 * (accum_influences[vId] + k)] = i;
+                    joint_weight_indices[2 * (accum_influences[vId] + k) + 1] = weight_index;
+                    break;
+                }
+            }
+            ++weight_index;
+        }
+
+    for( size_t i = 0; i < joint_weight_indices.size(); ++i)
+        mOutput << joint_weight_indices[i] << " ";
+
+    num_influences.clear();
+    accum_influences.clear();
+    joint_weight_indices.clear();
+
+    mOutput << "</v>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</vertex_weights>" << endstr;
+
+    PopTag();
+    mOutput << startstr << "</skin>" << endstr;
+    
+    PopTag();
+    mOutput << startstr << "</controller>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the geometry library
+void ColladaExporter::WriteGeometryLibrary()
+{
+    mOutput << startstr << "<library_geometries>" << endstr;
+    PushTag();
+
+    for( size_t a = 0; a < mScene->mNumMeshes; ++a)
+        WriteGeometry( a);
+
+    PopTag();
+    mOutput << startstr << "</library_geometries>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the given mesh
+void ColladaExporter::WriteGeometry( size_t pIndex)
+{
+    const aiMesh* mesh = mScene->mMeshes[pIndex];
+    const std::string idstr = mesh->mName.length == 0 ? GetMeshId(pIndex) : mesh->mName.C_Str();
+    const std::string geometryName = XMLEscape(idstr);
+    const std::string geometryId = XMLIDEncode(idstr);
+
+    if ( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
+        return;
+
+    // opening tag
+    mOutput << startstr << "<geometry id=\"" << geometryId << "\" name=\"" << geometryName << "\" >" << endstr;
+    PushTag();
+
+    mOutput << startstr << "<mesh>" << endstr;
+    PushTag();
+
+    // Positions
+    WriteFloatArray( idstr + "-positions", FloatType_Vector, (ai_real*) mesh->mVertices, mesh->mNumVertices);
+    // Normals, if any
+    if( mesh->HasNormals() )
+        WriteFloatArray( idstr + "-normals", FloatType_Vector, (ai_real*) mesh->mNormals, mesh->mNumVertices);
+
+    // texture coords
+    for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a)
+    {
+        if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
+        {
+            WriteFloatArray( idstr + "-tex" + to_string(a), mesh->mNumUVComponents[a] == 3 ? FloatType_TexCoord3 : FloatType_TexCoord2,
+                (ai_real*) mesh->mTextureCoords[a], mesh->mNumVertices);
+        }
+    }
+
+    // vertex colors
+    for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a)
+    {
+        if( mesh->HasVertexColors(static_cast<unsigned int>(a)) )
+            WriteFloatArray( idstr + "-color" + to_string(a), FloatType_Color, (ai_real*) mesh->mColors[a], mesh->mNumVertices);
+    }
+
+    // assemble vertex structure
+    // Only write input for POSITION since we will write other as shared inputs in polygon definition
+    mOutput << startstr << "<vertices id=\"" << geometryId << "-vertices" << "\">" << endstr;
+    PushTag();
+    mOutput << startstr << "<input semantic=\"POSITION\" source=\"#" << geometryId << "-positions\" />" << endstr;
+    PopTag();
+    mOutput << startstr << "</vertices>" << endstr;
+
+    // count the number of lines, triangles and polygon meshes
+    int countLines = 0;
+    int countPoly = 0;
+    for( size_t a = 0; a < mesh->mNumFaces; ++a )
+    {
+        if (mesh->mFaces[a].mNumIndices == 2) countLines++;
+        else if (mesh->mFaces[a].mNumIndices >= 3) countPoly++;
+    }
+
+    // lines
+    if (countLines)
+    {
+        mOutput << startstr << "<lines count=\"" << countLines << "\" material=\"defaultMaterial\">" << endstr;
+        PushTag();
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
+        if( mesh->HasNormals() )
+            mOutput << startstr << "<input semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
+        {
+            if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
+                mOutput << startstr << "<input semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
+        {
+            if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
+                mOutput << startstr << "<input semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+
+        mOutput << startstr << "<p>";
+        for( size_t a = 0; a < mesh->mNumFaces; ++a )
+        {
+            const aiFace& face = mesh->mFaces[a];
+            if (face.mNumIndices != 2) continue;
+            for( size_t b = 0; b < face.mNumIndices; ++b )
+                mOutput << face.mIndices[b] << " ";
+        }
+        mOutput << "</p>" << endstr;
+        PopTag();
+        mOutput << startstr << "</lines>" << endstr;
+    }
+
+    // triangle - don't use it, because compatibility problems
+
+    // polygons
+    if (countPoly)
+    {
+        mOutput << startstr << "<polylist count=\"" << countPoly << "\" material=\"defaultMaterial\">" << endstr;
+        PushTag();
+        mOutput << startstr << "<input offset=\"0\" semantic=\"VERTEX\" source=\"#" << geometryId << "-vertices\" />" << endstr;
+        if( mesh->HasNormals() )
+            mOutput << startstr << "<input offset=\"0\" semantic=\"NORMAL\" source=\"#" << geometryId << "-normals\" />" << endstr;
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a )
+        {
+            if( mesh->HasTextureCoords(static_cast<unsigned int>(a)) )
+                mOutput << startstr << "<input offset=\"0\" semantic=\"TEXCOORD\" source=\"#" << geometryId << "-tex" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+        for( size_t a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a )
+        {
+            if( mesh->HasVertexColors(static_cast<unsigned int>(a) ) )
+                mOutput << startstr << "<input offset=\"0\" semantic=\"COLOR\" source=\"#" << geometryId << "-color" << a << "\" " << "set=\"" << a << "\""  << " />" << endstr;
+        }
+
+        mOutput << startstr << "<vcount>";
+        for( size_t a = 0; a < mesh->mNumFaces; ++a )
+        {
+            if (mesh->mFaces[a].mNumIndices < 3) continue;
+            mOutput << mesh->mFaces[a].mNumIndices << " ";
+        }
+        mOutput << "</vcount>" << endstr;
+
+        mOutput << startstr << "<p>";
+        for( size_t a = 0; a < mesh->mNumFaces; ++a )
+        {
+            const aiFace& face = mesh->mFaces[a];
+            if (face.mNumIndices < 3) continue;
+            for( size_t b = 0; b < face.mNumIndices; ++b )
+                mOutput << face.mIndices[b] << " ";
+        }
+        mOutput << "</p>" << endstr;
+        PopTag();
+        mOutput << startstr << "</polylist>" << endstr;
+    }
+
+    // closing tags
+    PopTag();
+    mOutput << startstr << "</mesh>" << endstr;
+    PopTag();
+    mOutput << startstr << "</geometry>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes a float array of the given type
+void ColladaExporter::WriteFloatArray( const std::string& pIdString, FloatDataType pType, const ai_real* pData, size_t pElementCount)
+{
+    size_t floatsPerElement = 0;
+    switch( pType )
+    {
+        case FloatType_Vector: floatsPerElement = 3; break;
+        case FloatType_TexCoord2: floatsPerElement = 2; break;
+        case FloatType_TexCoord3: floatsPerElement = 3; break;
+        case FloatType_Color: floatsPerElement = 3; break;
+        case FloatType_Mat4x4: floatsPerElement = 16; break;
+        case FloatType_Weight: floatsPerElement = 1; break;
+		case FloatType_Time: floatsPerElement = 1; break;
+        default:
+            return;
+    }
+
+    std::string arrayId = XMLIDEncode(pIdString) + "-array";
+
+    mOutput << startstr << "<source id=\"" << XMLIDEncode(pIdString) << "\" name=\"" << XMLEscape(pIdString) << "\">" << endstr;
+    PushTag();
+
+    // source array
+    mOutput << startstr << "<float_array id=\"" << arrayId << "\" count=\"" << pElementCount * floatsPerElement << "\"> ";
+    PushTag();
+
+    if( pType == FloatType_TexCoord2 )
+    {
+        for( size_t a = 0; a < pElementCount; ++a )
+        {
+            mOutput << pData[a*3+0] << " ";
+            mOutput << pData[a*3+1] << " ";
+        }
+    }
+    else if( pType == FloatType_Color )
+    {
+        for( size_t a = 0; a < pElementCount; ++a )
+        {
+            mOutput << pData[a*4+0] << " ";
+            mOutput << pData[a*4+1] << " ";
+            mOutput << pData[a*4+2] << " ";
+        }
+    }
+    else
+    {
+        for( size_t a = 0; a < pElementCount * floatsPerElement; ++a )
+            mOutput << pData[a] << " ";
+    }
+    mOutput << "</float_array>" << endstr;
+    PopTag();
+
+    // the usual Collada fun. Let's bloat it even more!
+    mOutput << startstr << "<technique_common>" << endstr;
+    PushTag();
+    mOutput << startstr << "<accessor count=\"" << pElementCount << "\" offset=\"0\" source=\"#" << arrayId << "\" stride=\"" << floatsPerElement << "\">" << endstr;
+    PushTag();
+
+    switch( pType )
+    {
+        case FloatType_Vector:
+            mOutput << startstr << "<param name=\"X\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"Y\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"Z\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_TexCoord2:
+            mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_TexCoord3:
+            mOutput << startstr << "<param name=\"S\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"T\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"P\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_Color:
+            mOutput << startstr << "<param name=\"R\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"G\" type=\"float\" />" << endstr;
+            mOutput << startstr << "<param name=\"B\" type=\"float\" />" << endstr;
+            break;
+
+        case FloatType_Mat4x4:
+            mOutput << startstr << "<param name=\"TRANSFORM\" type=\"float4x4\" />" << endstr;
+            break;
+
+        case FloatType_Weight:
+            mOutput << startstr << "<param name=\"WEIGHT\" type=\"float\" />" << endstr;
+            break;
+
+		// customized, add animation related
+		case FloatType_Time:
+			mOutput << startstr << "<param name=\"TIME\" type=\"float\" />" << endstr;
+			break;
+
+	}
+
+    PopTag();
+    mOutput << startstr << "</accessor>" << endstr;
+    PopTag();
+    mOutput << startstr << "</technique_common>" << endstr;
+    PopTag();
+    mOutput << startstr << "</source>" << endstr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Writes the scene library
+void ColladaExporter::WriteSceneLibrary()
+{
+    const std::string sceneName = XMLEscape(mScene->mRootNode->mName.C_Str());
+    const std::string sceneId = XMLIDEncode(mScene->mRootNode->mName.C_Str());
+
+    mOutput << startstr << "<library_visual_scenes>" << endstr;
+    PushTag();
+    mOutput << startstr << "<visual_scene id=\"" + sceneId + "\" name=\"" + sceneName + "\">" << endstr;
+    PushTag();
+
+    // start recursive write at the root node
+    for( size_t a = 0; a < mScene->mRootNode->mNumChildren; ++a )
+        WriteNode( mScene, mScene->mRootNode->mChildren[a]);
+
+    PopTag();
+    mOutput << startstr << "</visual_scene>" << endstr;
+    PopTag();
+    mOutput << startstr << "</library_visual_scenes>" << endstr;
+}
+// ------------------------------------------------------------------------------------------------
+void ColladaExporter::WriteAnimationLibrary(size_t pIndex)
+{
+    static const float kSecondsFromMilliseconds = .001f;
+
+    const aiAnimation * anim = mScene->mAnimations[pIndex];
+	
+    if ( anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels ==0 ) {
+        return;
+    }
+	
+	const std::string animation_name_escaped = XMLEscape( anim->mName.C_Str() );
+	std::string idstr = anim->mName.C_Str();
+	std::string ending = std::string( "AnimId" ) + to_string(pIndex);
+	if (idstr.length() >= ending.length()) {
+		if (0 != idstr.compare (idstr.length() - ending.length(), ending.length(), ending)) {
+			idstr = idstr + ending;
+		}
+	} else {
+		idstr = idstr + ending;
+	}
+
+	const std::string idstrEscaped = XMLIDEncode(idstr);
+	
+	mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animation_name_escaped + "\">" << endstr;
+	PushTag();
+
+    std::string cur_node_idstr;
+	for (size_t a = 0; a < anim->mNumChannels; ++a) {
+		const aiNodeAnim * nodeAnim = anim->mChannels[a];
+		
+		// sanity check
+        if (nodeAnim->mNumPositionKeys != nodeAnim->mNumScalingKeys || nodeAnim->mNumPositionKeys != nodeAnim->mNumRotationKeys) {
+            continue;
+        }
+		
+		{
+            cur_node_idstr.clear();
+            cur_node_idstr += nodeAnim->mNodeName.data;
+            cur_node_idstr += std::string("_matrix-input");
+
+			std::vector<ai_real> frames;
+			for( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
+				frames.push_back(static_cast<ai_real>(nodeAnim->mPositionKeys[i].mTime) * kSecondsFromMilliseconds);
+			}
+			
+			WriteFloatArray(cur_node_idstr, FloatType_Time, (const ai_real *)frames.data(), frames.size());
+			frames.clear();
+		}
+		
+		{
+            cur_node_idstr.clear();
+
+            cur_node_idstr += nodeAnim->mNodeName.data;
+            cur_node_idstr += std::string("_matrix-output");
+			
+			std::vector<ai_real> keyframes;
+			keyframes.reserve(nodeAnim->mNumPositionKeys * 16);
+			for( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
+				aiVector3D Scaling = nodeAnim->mScalingKeys[i].mValue;
+				aiMatrix4x4 ScalingM;  // identity
+				ScalingM[0][0] = Scaling.x; ScalingM[1][1] = Scaling.y; ScalingM[2][2] = Scaling.z;
+				
+				aiQuaternion RotationQ = nodeAnim->mRotationKeys[i].mValue;
+				aiMatrix4x4 s = aiMatrix4x4( RotationQ.GetMatrix() );
+				aiMatrix4x4 RotationM(s.a1, s.a2, s.a3, 0, s.b1, s.b2, s.b3, 0, s.c1, s.c2, s.c3, 0, 0, 0, 0, 1);
+				
+				aiVector3D Translation = nodeAnim->mPositionKeys[i].mValue;
+				aiMatrix4x4 TranslationM;	// identity
+				TranslationM[0][3] = Translation.x; TranslationM[1][3] = Translation.y; TranslationM[2][3] = Translation.z;
+				
+				// Combine the above transformations
+				aiMatrix4x4 mat = TranslationM * RotationM * ScalingM;
+				
+				for( unsigned int j = 0; j < 4; ++j) {
+					keyframes.insert(keyframes.end(), mat[j], mat[j] + 4);
+                }
+			}
+			
+			WriteFloatArray(cur_node_idstr, FloatType_Mat4x4, (const ai_real *)keyframes.data(), keyframes.size() / 16);
+		}
+		
+		{
+			std::vector<std::string> names;
+			for ( size_t i = 0; i < nodeAnim->mNumPositionKeys; ++i) {
+				if ( nodeAnim->mPreState == aiAnimBehaviour_DEFAULT
+					|| nodeAnim->mPreState == aiAnimBehaviour_LINEAR
+					|| nodeAnim->mPreState == aiAnimBehaviour_REPEAT
+					) {
+					names.push_back( "LINEAR" );
+				} else if (nodeAnim->mPostState == aiAnimBehaviour_CONSTANT) {
+					names.push_back( "STEP" );
+				}
+			}
+			
+			const std::string cur_node_idstr2 = nodeAnim->mNodeName.data + std::string("_matrix-interpolation");
+            std::string arrayId = XMLIDEncode(cur_node_idstr2) + "-array";
+			
+			mOutput << startstr << "<source id=\"" << XMLIDEncode(cur_node_idstr2) << "\">" << endstr;
+			PushTag();
+			
+			// source array
+			mOutput << startstr << "<Name_array id=\"" << arrayId << "\" count=\"" << names.size() << "\"> ";
+			for( size_t aa = 0; aa < names.size(); ++aa ) {
+				mOutput << names[aa] << " ";
+            }
+			mOutput << "</Name_array>" << endstr;
+			
+			mOutput << startstr << "<technique_common>" << endstr;
+			PushTag();
+
+			mOutput << startstr << "<accessor source=\"#" << arrayId << "\" count=\"" << names.size() << "\" stride=\"" << 1 << "\">" << endstr;
+			PushTag();
+			
+			mOutput << startstr << "<param name=\"INTERPOLATION\" type=\"name\"></param>" << endstr;
+			
+			PopTag();
+			mOutput << startstr << "</accessor>" << endstr;
+			
+			PopTag();
+			mOutput << startstr << "</technique_common>" << endstr;
+
+			PopTag();
+			mOutput << startstr << "</source>" << endstr;
+		}
+	}
+	
+	for (size_t a = 0; a < anim->mNumChannels; ++a) {
+		const aiNodeAnim * nodeAnim = anim->mChannels[a];
+		
+		{
+		// samplers
+			const std::string node_idstr = nodeAnim->mNodeName.data + std::string("_matrix-sampler");
+			mOutput << startstr << "<sampler id=\"" << XMLIDEncode(node_idstr) << "\">" << endstr;
+			PushTag();
+			
+			mOutput << startstr << "<input semantic=\"INPUT\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-input") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"OUTPUT\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-output") ) << "\"/>" << endstr;
+			mOutput << startstr << "<input semantic=\"INTERPOLATION\" source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-interpolation") ) << "\"/>" << endstr;
+			
+			PopTag();
+			mOutput << startstr << "</sampler>" << endstr;
+		}
+	}
+	
+	for (size_t a = 0; a < anim->mNumChannels; ++a) {
+		const aiNodeAnim * nodeAnim = anim->mChannels[a];
+		
+		{
+		// channels
+			mOutput << startstr << "<channel source=\"#" << XMLIDEncode( nodeAnim->mNodeName.data + std::string("_matrix-sampler") ) << "\" target=\"" << XMLIDEncode(nodeAnim->mNodeName.data) << "/matrix\"/>" << endstr;
+		}
+	}
+	
+	PopTag();
+	mOutput << startstr << "</animation>" << endstr;
+	
+}
+// ------------------------------------------------------------------------------------------------
+void ColladaExporter::WriteAnimationsLibrary()
+{
+	if ( mScene->mNumAnimations > 0 ) {
+		mOutput << startstr << "<library_animations>" << endstr;
+		PushTag();
+		
+		// start recursive write at the root node
+		for( size_t a = 0; a < mScene->mNumAnimations; ++a)
+			WriteAnimationLibrary( a );
+
+		PopTag();
+		mOutput << startstr << "</library_animations>" << endstr;
+	}
+}
+// ------------------------------------------------------------------------------------------------
+// Helper to find a bone by name in the scene
+aiBone* findBone( const aiScene* scene, const char * name) {
+    for (size_t m=0; m<scene->mNumMeshes; m++) {
+        aiMesh * mesh = scene->mMeshes[m];
+        for (size_t b=0; b<mesh->mNumBones; b++) {
+            aiBone * bone = mesh->mBones[b];
+            if (0 == strcmp(name, bone->mName.C_Str())) {
+                return bone;
+            }
+        }
+    }
+    return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiNode * findBoneNode( const aiNode* aNode, const aiBone* bone)
+{
+	if ( aNode && bone && aNode->mName == bone->mName ) {
+		return aNode;
+	}
+	
+	if ( aNode && bone ) {
+		for (unsigned int i=0; i < aNode->mNumChildren; ++i) {
+			aiNode * aChild = aNode->mChildren[i];
+			const aiNode * foundFromChild = 0;
+			if ( aChild ) {
+				foundFromChild = findBoneNode( aChild, bone );
+				if ( foundFromChild ) return foundFromChild;
+			}
+		}
+	}
+	
+	return NULL;
+}
+
+const aiNode * findSkeletonRootNode( const aiScene* scene, const aiMesh * mesh)
+{
+	std::set<const aiNode*> topParentBoneNodes;
+	if ( mesh && mesh->mNumBones > 0 ) {
+		for (unsigned int i=0; i < mesh->mNumBones; ++i) {
+			aiBone * bone = mesh->mBones[i];
+
+			const aiNode * node = findBoneNode( scene->mRootNode, bone);
+			if ( node ) {
+				while ( node->mParent && findBone(scene, node->mParent->mName.C_Str() ) != 0 ) {
+					node = node->mParent;
+				}
+				topParentBoneNodes.insert( node );
+			}
+		}
+	}
+	
+	if ( !topParentBoneNodes.empty() ) {
+		const aiNode * parentBoneNode = *topParentBoneNodes.begin();
+		if ( topParentBoneNodes.size() == 1 ) {
+			return parentBoneNode;
+		} else {
+			for (auto it : topParentBoneNodes) {
+				if ( it->mParent ) return it->mParent;
+			}
+			return parentBoneNode;
+		}
+	}
+	
+	return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively writes the given node
+void ColladaExporter::WriteNode( const aiScene* pScene, aiNode* pNode)
+{
+    // the node must have a name
+    if (pNode->mName.length == 0)
+    {
+        std::stringstream ss;
+        ss << "Node_" << pNode;
+        pNode->mName.Set(ss.str());
+    }
+
+    // If the node is associated with a bone, it is a joint node (JOINT)
+    // otherwise it is a normal node (NODE)
+    const char * node_type;
+    bool is_joint, is_skeleton_root = false;
+    if (nullptr == findBone(pScene, pNode->mName.C_Str())) {
+        node_type = "NODE";
+        is_joint = false;
+    } else {
+        node_type = "JOINT";
+        is_joint = true;
+        if (!pNode->mParent || nullptr == findBone(pScene, pNode->mParent->mName.C_Str())) {
+            is_skeleton_root = true;
+        }
+    }
+
+    const std::string node_id = XMLIDEncode(pNode->mName.data);
+    const std::string node_name = XMLEscape(pNode->mName.data);
+	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 +"\"": "") ;
+	}
+	
+    mOutput << " name=\"" << node_name
+            << "\" type=\"" << node_type
+            << "\">" << endstr;
+    PushTag();
+
+    // write transformation - we can directly put the matrix there
+    // TODO: (thom) decompose into scale - rot - quad to allow addressing it by animations afterwards
+    aiMatrix4x4 mat = pNode->mTransformation;
+
+    // If this node is a Camera node, the camera coordinate system needs to be multiplied in.
+    // When importing from Collada, the mLookAt is set to 0, 0, -1, and the node transform is unchanged.
+    // When importing from a different format, mLookAt is set to 0, 0, 1. Therefore, the local camera
+    // coordinate system must be changed to matche the Collada specification.
+    for (size_t i = 0; i<mScene->mNumCameras; i++){
+        if (mScene->mCameras[i]->mName == pNode->mName){
+            aiMatrix4x4 sourceView;
+            mScene->mCameras[i]->GetCameraMatrix(sourceView);
+
+            aiMatrix4x4 colladaView;
+            colladaView.a1 = colladaView.c3 = -1; // move into -z space.
+            mat *= (sourceView * colladaView);
+            break;
+        }
+    }
+	
+	// customized, sid should be 'matrix' to match with loader code.
+    //mOutput << startstr << "<matrix sid=\"transform\">";
+	mOutput << startstr << "<matrix sid=\"matrix\">";
+	
+    mOutput << mat.a1 << " " << mat.a2 << " " << mat.a3 << " " << mat.a4 << " ";
+    mOutput << mat.b1 << " " << mat.b2 << " " << mat.b3 << " " << mat.b4 << " ";
+    mOutput << mat.c1 << " " << mat.c2 << " " << mat.c3 << " " << mat.c4 << " ";
+    mOutput << mat.d1 << " " << mat.d2 << " " << mat.d3 << " " << mat.d4;
+    mOutput << "</matrix>" << endstr;
+
+    if(pNode->mNumMeshes==0){
+        //check if it is a camera node
+        for(size_t i=0; i<mScene->mNumCameras; i++){
+            if(mScene->mCameras[i]->mName == pNode->mName){
+                mOutput << startstr <<"<instance_camera url=\"#" << node_id << "-camera\"/>" << endstr;
+                break;
+            }
+        }
+        //check if it is a light node
+        for(size_t i=0; i<mScene->mNumLights; i++){
+            if(mScene->mLights[i]->mName == pNode->mName){
+                mOutput << startstr <<"<instance_light url=\"#" << node_id << "-light\"/>" << endstr;
+                break;
+            }
+        }
+
+    }else
+    // instance every geometry
+    for( size_t a = 0; a < pNode->mNumMeshes; ++a )
+    {
+        const aiMesh* mesh = mScene->mMeshes[pNode->mMeshes[a]];
+        // do not instantiate mesh if empty. I wonder how this could happen
+        if( mesh->mNumFaces == 0 || mesh->mNumVertices == 0 )
+            continue;
+
+        const std::string meshName = mesh->mName.length == 0 ? GetMeshId(pNode->mMeshes[a]) : mesh->mName.C_Str();
+
+        if( mesh->mNumBones == 0 )
+        {
+            mOutput << startstr << "<instance_geometry url=\"#" << XMLIDEncode(meshName) << "\">" << endstr;
+            PushTag();
+        }
+        else
+        {
+            mOutput << startstr
+                    << "<instance_controller url=\"#" << XMLIDEncode(meshName) << "-skin\">"
+                    << endstr;
+            PushTag();
+
+			// note! this mFoundSkeletonRootNodeID some how affects animation, it makes the mesh attaches to armature skeleton root node.
+			// use the first bone to find skeleton root
+			const aiNode * skeletonRootBoneNode = findSkeletonRootNode( pScene, mesh );
+			if ( skeletonRootBoneNode ) {
+				mFoundSkeletonRootNodeID = XMLIDEncode( skeletonRootBoneNode->mName.C_Str() );
+			}
+            mOutput << startstr << "<skeleton>#" << mFoundSkeletonRootNodeID << "</skeleton>" << endstr;
+        }
+        mOutput << startstr << "<bind_material>" << endstr;
+        PushTag();
+        mOutput << startstr << "<technique_common>" << endstr;
+        PushTag();
+        mOutput << startstr << "<instance_material symbol=\"defaultMaterial\" target=\"#" << XMLIDEncode(materials[mesh->mMaterialIndex].name) << "\">" << endstr;
+        PushTag();
+        for( size_t aa = 0; aa < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++aa )
+        {
+            if( mesh->HasTextureCoords( static_cast<unsigned int>(aa) ) )
+                // semantic       as in <texture texcoord=...>
+                // input_semantic as in <input semantic=...>
+                // input_set      as in <input set=...>
+                mOutput << startstr << "<bind_vertex_input semantic=\"CHANNEL" << aa << "\" input_semantic=\"TEXCOORD\" input_set=\"" << aa << "\"/>" << endstr;
+        }
+        PopTag();
+        mOutput << startstr << "</instance_material>" << endstr;
+        PopTag();
+        mOutput << startstr << "</technique_common>" << endstr;
+        PopTag();
+        mOutput << startstr << "</bind_material>" << endstr;
+        
+        PopTag();
+        if( mesh->mNumBones == 0)
+            mOutput << startstr << "</instance_geometry>" << endstr;
+        else
+            mOutput << startstr << "</instance_controller>" << endstr;
+    }
+
+    // recurse into subnodes
+    for( size_t a = 0; a < pNode->mNumChildren; ++a )
+        WriteNode( pScene, pNode->mChildren[a]);
+
+    PopTag();
+    mOutput << startstr << "</node>" << endstr;
+}
+
+#endif
+#endif

+ 53 - 0
include/assimp/ColladaMetaData.h

@@ -0,0 +1,53 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+/** @file ColladaMetaData.h
+ * Declares common metadata constants used by Collada files
+ */
+#pragma once
+#ifndef AI_COLLADAMETADATA_H_INC
+#define AI_COLLADAMETADATA_H_INC
+
+#define AI_METADATA_COLLADA_ID "Collada_id"
+#define AI_METADATA_COLLADA_SID "Collada_sid"
+
+#endif

+ 3 - 3
include/assimp/config.h.in

@@ -1030,10 +1030,10 @@ enum aiComponent
 #define AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION "IMPORT_COLLADA_IGNORE_UP_DIRECTION"
 
 // ---------------------------------------------------------------------------
-/** @brief Specifies whether the Collada loader should use Collada names as node names.
+/** @brief Specifies whether the Collada loader should use Collada names.
  *
- * If this property is set to true, the Collada names will be used as the
- * node name. The default is to use the id tag (resp. sid tag, if no id tag is present)
+ * If this property is set to true, the Collada names will be used as the node and
+ * mesh names. The default is to use the id tag (resp. sid tag, if no id tag is present)
  * instead.
  * Property type: Bool. Default value: false.
  */

+ 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

+ 260 - 3
test/unit/utColladaImportExport.cpp

@@ -41,16 +41,33 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include "AbstractImportExportBase.h"
 #include "UnitTestPCH.h"
 
+#include <assimp/ColladaMetaData.h>
+#include <assimp/SceneCombiner.h>
 #include <assimp/commonMetaData.h>
 #include <assimp/postprocess.h>
 #include <assimp/scene.h>
+#include <assimp/Exporter.hpp>
 #include <assimp/Importer.hpp>
 
 using namespace Assimp;
 
 class utColladaImportExport : public AbstractImportExportBase {
 public:
-    virtual bool importerTest() {
+    // 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);
         if (scene == nullptr)
@@ -80,15 +97,255 @@ public:
 
         return true;
     }
+
+    typedef std::pair<std::string, std::string> IdNameString;
+    typedef std::map<std::string, std::string> IdNameMap;
+
+    template <typename T>
+    static inline IdNameString GetColladaIdName(const T *item, size_t index) {
+        std::ostringstream stream;
+        stream << typeid(T).name() << "@" << index;
+        if (item->mMetaData) {
+            aiString aiStr;
+            if (item->mMetaData->Get(AI_METADATA_COLLADA_ID, aiStr))
+                return std::make_pair(std::string(aiStr.C_Str()), stream.str());
+        }
+        return std::make_pair(std::string(), stream.str());
+    }
+
+    template <typename T>
+    static inline IdNameString GetItemIdName(const T *item, size_t index) {
+        std::ostringstream stream;
+        stream << typeid(T).name() << "@" << index;
+        return std::make_pair(std::string(item->mName.C_Str()), stream.str());
+    }
+
+    // Specialisations
+    static inline IdNameString GetItemIdName(aiMaterial *item, size_t index) {
+        std::ostringstream stream;
+        stream << typeid(aiMaterial).name() << "@" << index;
+        return std::make_pair(std::string(item->GetName().C_Str()), stream.str());
+    }
+
+    static inline IdNameString GetItemIdName(aiTexture *item, size_t index) {
+        std::ostringstream stream;
+        stream << typeid(aiTexture).name() << "@" << index;
+        return std::make_pair(std::string(item->mFilename.C_Str()), stream.str());
+    }
+
+    static inline void ReportDuplicate(IdNameMap &itemIdMap, const IdNameString &namePair, const char *typeNameStr) {
+        const auto result = itemIdMap.insert(namePair);
+        EXPECT_TRUE(result.second) << "Duplicate '" << typeNameStr << "' name: '" << namePair.first << "'. " << namePair.second << " == " << result.first->second;
+    }
+
+    template <typename T>
+    static inline void CheckUniqueIds(IdNameMap &itemIdMap, unsigned int itemCount, T **itemArray) {
+        for (size_t idx = 0; idx < itemCount; ++idx) {
+            IdNameString namePair = GetItemIdName(itemArray[idx], idx);
+            ReportDuplicate(itemIdMap, namePair, typeid(T).name());
+        }
+    }
+
+    static inline void CheckUniqueIds(IdNameMap &itemIdMap, const aiNode *parent, size_t index) {
+        IdNameString namePair = GetItemIdName(parent, index);
+        ReportDuplicate(itemIdMap, namePair, typeid(aiNode).name());
+
+        for (size_t idx = 0; idx < parent->mNumChildren; ++idx) {
+            CheckUniqueIds(itemIdMap, parent->mChildren[idx], idx);
+        }
+    }
+
+    static inline void CheckNodeIdNames(IdNameMap &nodeIdMap, IdNameMap &nodeNameMap, const aiNode *parent, size_t index) {
+        IdNameString namePair = GetItemIdName(parent, index);
+        const auto result = nodeNameMap.insert(namePair);
+        IdNameString idPair = GetColladaIdName(parent, index);
+        ReportDuplicate(nodeIdMap, idPair, typeid(aiNode).name());
+
+        for (size_t idx = 0; idx < parent->mNumChildren; ++idx) {
+            CheckNodeIdNames(nodeIdMap, nodeNameMap, parent->mChildren[idx], idx);
+        }
+    }
+
+    static inline void SetAllNodeNames(const aiString &newName, aiNode *node) {
+        node->mName = newName;
+        for (size_t idx = 0; idx < node->mNumChildren; ++idx) {
+            SetAllNodeNames(newName, node->mChildren[idx]);
+        }
+    }
+
+    void ImportAndCheckIds(const char *file, const aiScene *origScene) {
+        // Import the Collada using the 'default' where aiNode and aiMesh names are the Collada ids
+        Assimp::Importer importer;
+        const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure);
+        ASSERT_TRUE(scene != nullptr) << "Fatal: could not re-import " << file;
+        EXPECT_EQ(origScene->mNumMeshes, scene->mNumMeshes) << "in " << file;
+
+        // Check the ids are unique
+        IdNameMap itemIdMap;
+        // Recurse the Nodes
+        CheckUniqueIds(itemIdMap, scene->mRootNode, 0);
+        // Check the lists
+        CheckUniqueIds(itemIdMap, scene->mNumMeshes, scene->mMeshes);
+        // The remaining will come in using the name, which may not be unique
+        // Check we have the right number
+        EXPECT_EQ(origScene->mNumAnimations, scene->mNumAnimations);
+        EXPECT_EQ(origScene->mNumMaterials, scene->mNumMaterials);
+        EXPECT_EQ(origScene->mNumTextures, scene->mNumTextures);
+        EXPECT_EQ(origScene->mNumLights, scene->mNumLights);
+        EXPECT_EQ(origScene->mNumCameras, scene->mNumCameras);
+    }
+
+    void ImportAsNames(const char *file, const aiScene *origScene) {
+        // Import the Collada but using the user-visible names for aiNode and aiMesh
+        // Note that this mode may not support bones or animations
+        Assimp::Importer importer;
+        importer.SetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 1);
+
+        const aiScene *scene = importer.ReadFile(file, aiProcess_ValidateDataStructure);
+        ASSERT_TRUE(scene != nullptr) << "Fatal: could not re-import " << file;
+        EXPECT_EQ(origScene->mNumMeshes, scene->mNumMeshes) << "in " << file;
+
+        // Check the node ids are unique but the node names are not
+        IdNameMap nodeIdMap;
+        IdNameMap nodeNameMap;
+        // Recurse the Nodes
+        CheckNodeIdNames(nodeIdMap, nodeNameMap, scene->mRootNode, 0);
+
+        // nodeNameMap should have fewer than nodeIdMap
+        EXPECT_LT(nodeNameMap.size(), nodeIdMap.size()) << "Some nodes should have the same names";
+        // Check the counts haven't changed
+        EXPECT_EQ(origScene->mNumAnimations, scene->mNumAnimations);
+        EXPECT_EQ(origScene->mNumMaterials, scene->mNumMaterials);
+        EXPECT_EQ(origScene->mNumTextures, scene->mNumTextures);
+        EXPECT_EQ(origScene->mNumLights, scene->mNumLights);
+        EXPECT_EQ(origScene->mNumCameras, scene->mNumCameras);
+    }
 };
 
-TEST_F(utColladaImportExport, importBlenFromFileTest) {
+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;
+    const char *outFileEmpty = "exportMeshIdTest_empty_out.dae";
+    const char *outFileNamed = "exportMeshIdTest_named_out.dae";
+
+    // Load a sample file containing multiple meshes
+    const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/teapots.DAE", aiProcess_ValidateDataStructure);
+
+    ASSERT_TRUE(scene != nullptr) << "Fatal: could not import teapots.DAE!";
+    ASSERT_EQ(3u, scene->mNumMeshes) << "Fatal: teapots.DAE initial load failed";
+
+    // Clear all the names
+    for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) {
+        scene->mMeshes[idx]->mName.Clear();
+    }
+    for (size_t idx = 0; idx < scene->mNumMaterials; ++idx) {
+        scene->mMaterials[idx]->RemoveProperty(AI_MATKEY_NAME);
+    }
+    for (size_t idx = 0; idx < scene->mNumAnimations; ++idx) {
+        scene->mAnimations[idx]->mName.Clear();
+    }
+    // Can't clear texture names
+    for (size_t idx = 0; idx < scene->mNumLights; ++idx) {
+        scene->mLights[idx]->mName.Clear();
+    }
+    for (size_t idx = 0; idx < scene->mNumCameras; ++idx) {
+        scene->mCameras[idx]->mName.Clear();
+    }
+
+    SetAllNodeNames(aiString(), scene->mRootNode);
+
+    ASSERT_EQ(AI_SUCCESS, exporter.Export(scene, "collada", outFileEmpty)) << "Fatal: Could not export un-named meshes file";
+
+    ImportAndCheckIds(outFileEmpty, scene);
+
+    // Force everything to have the same non-empty name
+    aiString testName("test_name");
+    for (size_t idx = 0; idx < scene->mNumMeshes; ++idx) {
+        scene->mMeshes[idx]->mName = testName;
+    }
+    for (size_t idx = 0; idx < scene->mNumMaterials; ++idx) {
+        scene->mMaterials[idx]->AddProperty(&testName, AI_MATKEY_NAME);
+    }
+    for (size_t idx = 0; idx < scene->mNumAnimations; ++idx) {
+        scene->mAnimations[idx]->mName = testName;
+    }
+    // Can't clear texture names
+    for (size_t idx = 0; idx < scene->mNumLights; ++idx) {
+        scene->mLights[idx]->mName = testName;
+    }
+    for (size_t idx = 0; idx < scene->mNumCameras; ++idx) {
+        scene->mCameras[idx]->mName = testName;
+    }
+
+    SetAllNodeNames(testName, scene->mRootNode);
+
+    ASSERT_EQ(AI_SUCCESS, exporter.Export(scene, "collada", outFileNamed)) << "Fatal: Could not export named meshes file";
+
+    ImportAndCheckIds(outFileNamed, scene);
+    ImportAsNames(outFileNamed, scene);
+}
+
 class utColladaZaeImportExport : public AbstractImportExportBase {
 public:
-    virtual bool importerTest() {
+    virtual bool importerTest() final {
         {
             Assimp::Importer importer;
             const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure);

+ 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) {

Some files were not shown because too many files changed in this diff