summaryrefslogtreecommitdiff
path: root/libs/assimp/code/AssetLib/Collada
diff options
context:
space:
mode:
Diffstat (limited to 'libs/assimp/code/AssetLib/Collada')
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp1748
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaExporter.h257
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp99
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaHelper.h679
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp1828
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaLoader.h249
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaParser.cpp2402
-rw-r--r--libs/assimp/code/AssetLib/Collada/ColladaParser.h348
8 files changed, 7610 insertions, 0 deletions
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp b/libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp
new file mode 100644
index 0000000..5c91daa
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp
@@ -0,0 +1,1748 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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/ColladaMetaData.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Exceptional.h>
+#include <assimp/MathFunctions.h>
+#include <assimp/SceneCombiner.h>
+#include <assimp/StringUtils.h>
+#include <assimp/XMLTools.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/fast_atof.h>
+#include <assimp/scene.h>
+#include <assimp/Exporter.hpp>
+#include <assimp/IOSystem.hpp>
+
+#include <ctime>
+#include <memory>
+
+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 == nullptr) {
+ 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);
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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 reduce ID collisions
+ idEncoded << XML_ID_CHARS[(*it) % XML_ID_CHARS_COUNT];
+ }
+ }
+
+ 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 + '_' + ai_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),
+ 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);
+
+ // start writing the file
+ WriteFile();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor
+ColladaExporter::~ColladaExporter() {
+}
+
+// ------------------------------------------------------------------------------------------------
+// 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();
+
+ // 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();
+ WriteGeometryLibrary();
+ WriteControllerLibrary();
+
+ WriteSceneLibrary();
+
+ // customized, Writes the animation library
+ WriteAnimationsLibrary();
+
+ // instantiate the scene(s)
+ // For Assimp there will only ever be one
+ mOutput << startstr << "<scene>" << endstr;
+ PushTag();
+ mOutput << startstr << "<instance_visual_scene url=\"#" + mSceneId + "\" />" << 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(nullptr);
+ 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();
+
+ 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 {
+ mAdd_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 {
+ mAdd_root_node = true;
+ }
+
+ if (!position.Equal(aiVector3D(0, 0, 0))) {
+ mAdd_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 (mAdd_root_node) {
+ 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 == nullptr) {
+ 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 cameraId = GetObjectUniqueId(AiObjectType::Camera, pIndex);
+ const std::string cameraName = GetObjectName(AiObjectType::Camera, pIndex);
+
+ mOutput << startstr << "<camera id=\"" << cameraId << "\" 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 lightId = GetObjectUniqueId(AiObjectType::Light, pIndex);
+ const std::string lightName = GetObjectName(AiObjectType::Light, pIndex);
+
+ mOutput << startstr << "<light id=\"" << lightId << "\" 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;
+ /*
+ out->mAngleOuterCone = AI_DEG_TO_RAD (std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+
+ srcLight->mFalloffAngle);
+ */
+
+ 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
+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, nullptr, &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<DeadlyExportError>(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;
+ }
+ return poSurface.exist;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reimplementation of isalnum(,C locale), because AppVeyor does not see standard version.
+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 &imageId) {
+ if (!pSurface.texture.empty()) {
+ mOutput << startstr << "<image id=\"" << imageId << "\">" << 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 &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=\"" << imageId << "\" 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 &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=\"" << materialId << "-" << pTypeName << "-surface\">" << endstr;
+ PushTag();
+ mOutput << startstr << "<surface type=\"2D\">" << endstr;
+ PushTag();
+ mOutput << startstr << "<init_from>" << materialId << "-" << pTypeName << "-image</init_from>" << endstr;
+ PopTag();
+ mOutput << startstr << "</surface>" << endstr;
+ PopTag();
+ mOutput << startstr << "</newparam>" << endstr;
+
+ mOutput << startstr << "<newparam sid=\"" << materialId << "-" << pTypeName << "-sampler\">" << endstr;
+ PushTag();
+ mOutput << startstr << "<sampler2D>" << endstr;
+ PushTag();
+ mOutput << startstr << "<source>" << materialId << "-" << 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() {
+ 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) {
+ 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;
+ material.shading_model = "phong";
+ if (mat.Get(AI_MATKEY_SHADING_MODEL, shading) == aiReturn_SUCCESS) {
+ if (shading == aiShadingMode_Phong) {
+ material.shading_model = "phong";
+ } else if (shading == aiShadingMode_Blinn) {
+ material.shading_model = "blinn";
+ } else if (shading == aiShadingMode_NoShading) {
+ material.shading_model = "constant";
+ } else if (shading == aiShadingMode_Gouraud) {
+ material.shading_model = "lambert";
+ }
+ }
+
+ 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 (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;
+ }
+
+ // output effects - those are the actual carriers of information
+ if (!materials.empty()) {
+ mOutput << startstr << "<library_effects>" << endstr;
+ PushTag();
+ for (const Material &mat : materials) {
+ // this is so ridiculous it must be right
+ 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.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.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.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.id + "-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=\"" << mat.id << "\" name=\"" << mat.name << "\">" << endstr;
+ PushTag();
+ mOutput << startstr << "<instance_effect url=\"#" << mat.id << "-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];
+ // Is there a skin controller?
+ if (mesh->mNumBones == 0 || mesh->mNumFaces == 0 || mesh->mNumVertices == 0)
+ return;
+
+ const std::string idstr = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
+ const std::string namestr = GetObjectName(AiObjectType::Mesh, pIndex);
+
+ mOutput << startstr << "<controller id=\"" << idstr << "-skin\" ";
+ mOutput << "name=\"skinCluster" << pIndex << "\">" << endstr;
+ PushTag();
+
+ mOutput << startstr << "<skin source=\"#" << idstr << "\">" << 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=\"" << idstr << "-skin-joints\" name=\"" << namestr << "-skin-joints\">" << endstr;
+ PushTag();
+
+ mOutput << startstr << "<Name_array id=\"" << idstr << "-skin-joints-array\" count=\"" << mesh->mNumBones << "\">";
+
+ for (size_t i = 0; i < mesh->mNumBones; ++i)
+ mOutput << GetBoneUniqueId(mesh->mBones[i]) << ' ';
+
+ mOutput << "</Name_array>" << endstr;
+
+ mOutput << startstr << "<technique_common>" << endstr;
+ PushTag();
+
+ mOutput << startstr << "<accessor source=\"#" << idstr << "-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=\"#" << idstr << "-skin-joints\"></input>" << endstr;
+ mOutput << startstr << "<input semantic=\"INV_BIND_MATRIX\" source=\"#" << idstr << "-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=\"#" << idstr << "-skin-joints\" offset=\"0\"></input>" << endstr;
+ mOutput << startstr << "<input semantic=\"WEIGHT\" source=\"#" << idstr << "-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 geometryId = GetObjectUniqueId(AiObjectType::Mesh, pIndex);
+ const std::string geometryName = GetObjectName(AiObjectType::Mesh, pIndex);
+
+ 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(geometryId + "-positions", FloatType_Vector, (ai_real *)mesh->mVertices, mesh->mNumVertices);
+ // Normals, if any
+ if (mesh->HasNormals())
+ 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(geometryId + "-tex" + ai_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(geometryId + "-color" + ai_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() {
+ // 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=\"" + mSceneId + "\" name=\"" + sceneName + "\">" << endstr;
+ PushTag();
+
+ 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;
+ PopTag();
+ mOutput << startstr << "</library_visual_scenes>" << endstr;
+}
+// ------------------------------------------------------------------------------------------------
+void ColladaExporter::WriteAnimationLibrary(size_t pIndex) {
+ const aiAnimation *anim = mScene->mAnimations[pIndex];
+
+ if (anim->mNumChannels == 0 && anim->mNumMeshChannels == 0 && anim->mNumMorphMeshChannels == 0)
+ return;
+
+ const std::string animationNameEscaped = GetObjectName(AiObjectType::Animation, pIndex);
+ const std::string idstrEscaped = GetObjectUniqueId(AiObjectType::Animation, pIndex);
+
+ mOutput << startstr << "<animation id=\"" + idstrEscaped + "\" name=\"" + animationNameEscaped + "\">" << 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));
+ }
+
+ 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 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 (name == bone->mName) {
+ return bone;
+ }
+ }
+ }
+ 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;
+ }
+
+ if (aNode && bone) {
+ for (unsigned int i = 0; i < aNode->mNumChildren; ++i) {
+ aiNode *aChild = aNode->mChildren[i];
+ const aiNode *foundFromChild = nullptr;
+ if (aChild) {
+ foundFromChild = findBoneNode(aChild, bone);
+ if (foundFromChild) {
+ return foundFromChild;
+ }
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+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) != nullptr) {
+ 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 nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively writes the given node
+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 (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(mScene, pNode->mParent->mName)) {
+ is_skeleton_root = true;
+ }
+ }
+
+ const std::string node_id = GetNodeUniqueId(pNode);
+ const std::string node_name = GetNodeName(pNode);
+ mOutput << startstr << "<node ";
+ if (is_skeleton_root) {
+ mFoundSkeletonRootNodeID = node_id; // For now, only support one skeleton in a scene.
+ }
+ mOutput << "id=\"" << node_id << "\" " << (is_joint ? "sid=\"" + node_id + "\" " : "");
+ mOutput << "name=\"" << node_name
+ << "\" type=\"" << node_type
+ << "\">" << endstr;
+ 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=\"#" << 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=\"#" << GetObjectUniqueId(AiObjectType::Light, i) << "\"/>" << 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 meshId = GetObjectUniqueId(AiObjectType::Mesh, pNode->mMeshes[a]);
+
+ if (mesh->mNumBones == 0) {
+ mOutput << startstr << "<instance_geometry url=\"#" << meshId << "\">" << endstr;
+ PushTag();
+ } else {
+ mOutput << startstr
+ << "<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(mScene, mesh);
+ if (skeletonRootBoneNode) {
+ mFoundSkeletonRootNodeID = GetNodeUniqueId(skeletonRootBoneNode);
+ }
+ 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=\"#" << 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)))
+ // 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(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(ai_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
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaExporter.h b/libs/assimp/code/AssetLib/Collada/ColladaExporter.h
new file mode 100644
index 0000000..56415fb
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaExporter.h
@@ -0,0 +1,257 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 ColladaExporter.h
+ * Declares the exporter class to write a scene to a Collada file
+ */
+#ifndef AI_COLLADAEXPORTER_H_INC
+#define AI_COLLADAEXPORTER_H_INC
+
+#include <assimp/ai_assert.h>
+#include <assimp/material.h>
+
+#include <array>
+#include <map>
+#include <sstream>
+#include <unordered_set>
+#include <vector>
+
+struct aiScene;
+struct aiNode;
+struct aiLight;
+struct aiBone;
+
+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 {
+public:
+ /// Constructor for a specific scene to export
+ ColladaExporter(const aiScene *pScene, IOSystem *pIOSystem, const std::string &path, const std::string &file);
+
+ /// Destructor
+ virtual ~ColladaExporter();
+
+protected:
+ /// Starts writing the contents
+ void WriteFile();
+
+ /// Writes the asset header
+ void WriteHeader();
+
+ /// Writes the embedded textures
+ void WriteTextures();
+
+ /// Writes the material setup
+ void WriteMaterials();
+
+ /// Writes the cameras library
+ void WriteCamerasLibrary();
+
+ // Write a camera entry
+ void WriteCamera(size_t pIndex);
+
+ /// Writes the cameras library
+ void WriteLightsLibrary();
+
+ // Write a camera entry
+ void WriteLight(size_t pIndex);
+ void WritePointLight(const aiLight *const light);
+ void WriteDirectionalLight(const aiLight *const light);
+ void WriteSpotLight(const aiLight *const light);
+ void WriteAmbienttLight(const aiLight *const light);
+
+ /// Writes the controller library
+ void WriteControllerLibrary();
+
+ /// Writes a skin controller of the given mesh
+ void WriteController(size_t pIndex);
+
+ /// Writes the geometry library
+ void WriteGeometryLibrary();
+
+ /// Writes the given mesh
+ 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 };
+
+ /// Writes a float array of the given type
+ 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.
+
+ /// Recursively writes the given node
+ void WriteNode(const aiNode *pNode);
+
+ /// Enters a new xml element, which increases the indentation
+ void PushTag() { startstr.append(" "); }
+ /// Leaves an element, decreasing the indentation
+ void PopTag() {
+ ai_assert(startstr.length() > 1);
+ startstr.erase(startstr.length() - 2);
+ }
+
+ 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;
+
+ /// Path of the directory where the scene will be exported
+ const std::string mPath;
+
+ /// Name of the file (without extension) where the scene will be exported
+ const std::string mFile;
+
+ /// The scene to be written
+ 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
+ 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
+ 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
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp b/libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp
new file mode 100644
index 0000000..0fb172f
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp
@@ -0,0 +1,99 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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.
+
+----------------------------------------------------------------------
+*/
+/** Helper structures for the Collada loader */
+
+#include "ColladaHelper.h"
+
+#include <assimp/ParsingUtils.h>
+#include <assimp/commonMetaData.h>
+
+namespace Assimp {
+namespace Collada {
+
+const MetaKeyPairVector MakeColladaAssimpMetaKeys() {
+ MetaKeyPairVector result;
+ result.emplace_back("authoring_tool", AI_METADATA_SOURCE_GENERATOR);
+ result.emplace_back("copyright", AI_METADATA_SOURCE_COPYRIGHT);
+ return result;
+}
+
+const MetaKeyPairVector &GetColladaAssimpMetaKeys() {
+ static const MetaKeyPairVector result = MakeColladaAssimpMetaKeys();
+ return result;
+}
+
+const MetaKeyPairVector MakeColladaAssimpMetaKeysCamelCase() {
+ MetaKeyPairVector result = MakeColladaAssimpMetaKeys();
+ for (auto &val : result) {
+ ToCamelCase(val.first);
+ }
+ return result;
+}
+
+const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase() {
+ static const MetaKeyPairVector result = MakeColladaAssimpMetaKeysCamelCase();
+ return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert underscore_separated to CamelCase: "authoring_tool" becomes "AuthoringTool"
+void ToCamelCase(std::string &text) {
+ if (text.empty())
+ return;
+ // Capitalise first character
+ auto it = text.begin();
+ (*it) = ai_toupper(*it);
+ ++it;
+ for (/*started above*/; it != text.end(); /*iterated below*/) {
+ if ((*it) == '_') {
+ it = text.erase(it);
+ if (it != text.end())
+ (*it) = ai_toupper(*it);
+ } else {
+ // Make lower case
+ (*it) = ai_tolower(*it);
+ ++it;
+ }
+ }
+}
+
+} // namespace Collada
+} // namespace Assimp
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaHelper.h b/libs/assimp/code/AssetLib/Collada/ColladaHelper.h
new file mode 100644
index 0000000..31d7b5a
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaHelper.h
@@ -0,0 +1,679 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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.
+
+----------------------------------------------------------------------
+*/
+
+/** Helper structures for the Collada loader */
+
+#ifndef AI_COLLADAHELPER_H_INC
+#define AI_COLLADAHELPER_H_INC
+
+#include <assimp/light.h>
+#include <assimp/material.h>
+#include <assimp/mesh.h>
+
+#include <cstdint>
+#include <map>
+#include <set>
+#include <vector>
+
+struct aiMaterial;
+
+namespace Assimp {
+namespace Collada {
+
+/// Collada file versions which evolved during the years ...
+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 {
+ TF_LOOKAT,
+ TF_ROTATE,
+ TF_TRANSLATE,
+ TF_SCALE,
+ TF_SKEW,
+ TF_MATRIX
+};
+
+/// Different types of input data to a vertex or face
+enum InputType {
+ IT_Invalid,
+ IT_Vertex, // special type for per-index data referring to the <vertices> element carrying the per-vertex data.
+ IT_Position,
+ IT_Normal,
+ IT_Texcoord,
+ IT_Color,
+ IT_Tangent,
+ IT_Bitangent
+};
+
+/// Supported controller types
+enum ControllerType {
+ Skin,
+ Morph
+};
+
+/// Supported morph methods
+enum MorphMethod {
+ Normalized,
+ Relative
+};
+
+/// Common metadata keys as <Collada, Assimp>
+using MetaKeyPair = std::pair<std::string, std::string>;
+using MetaKeyPairVector = std::vector<MetaKeyPair>;
+
+/// Collada as lower_case (native)
+const MetaKeyPairVector &GetColladaAssimpMetaKeys();
+
+// Collada as CamelCase (used by Assimp for consistency)
+const MetaKeyPairVector &GetColladaAssimpMetaKeysCamelCase();
+
+/// Convert underscore_separated to CamelCase "authoring_tool" becomes "AuthoringTool"
+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
+ 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) {}
+
+ /// Name of camera
+ std::string mName;
+
+ /// True if it is an orthographic camera
+ bool mOrtho;
+
+ /// Horizontal field of view in degrees
+ ai_real mHorFov;
+
+ /// Vertical field of view in degrees
+ ai_real mVerFov;
+
+ /// Screen aspect
+ ai_real mAspect;
+
+ /// Near& far z
+ ai_real mZNear, mZFar;
+};
+
+#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) {}
+
+ /// Type of the light source aiLightSourceType + ambient
+ unsigned int mType;
+
+ /// Color of the light
+ aiColor3D mColor;
+
+ /// Light attenuation
+ ai_real mAttConstant, mAttLinear, mAttQuadratic;
+
+ /// Spot light falloff
+ ai_real mFalloffAngle;
+ ai_real mFalloffExponent;
+
+ // -----------------------------------------------------
+ // FCOLLADA extension from here
+
+ /// ... related stuff from maja and max extensions
+ ai_real mPenumbraAngle;
+ ai_real mOuterAngle;
+
+ /// Common light intensity
+ ai_real mIntensity;
+};
+
+/** Short vertex index description */
+struct InputSemanticMapEntry {
+ InputSemanticMapEntry() :
+ mSet(0),
+ mType(IT_Invalid) {}
+
+ /// Index of set, optional
+ unsigned int mSet;
+
+ /// Type of referenced vertex input
+ InputType mType;
+};
+
+/// Table to map from effect to vertex input semantics
+struct SemanticMappingTable {
+ /// Name of material
+ std::string mMatName;
+
+ /// List of semantic map commands, grouped by effect semantic name
+ using InputSemanticMap = std::map<std::string, InputSemanticMapEntry>;
+ InputSemanticMap mMap;
+
+ /// For std::find
+ bool operator==(const std::string &s) const {
+ return s == mMatName;
+ }
+};
+
+/// 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 {
+ ///< ID of the mesh or controller to be instanced
+ std::string mMeshOrController;
+
+ ///< Map of materials by the subgroup ID they're applied to
+ std::map<std::string, SemanticMappingTable> mMaterials;
+};
+
+/// A reference to a camera inside a node
+struct CameraInstance {
+ ///< ID of the camera
+ std::string mCamera;
+};
+
+/// A reference to a light inside a node
+struct LightInstance {
+ ///< ID of the camera
+ std::string mLight;
+};
+
+/// A reference to a node inside a node
+struct NodeInstance {
+ ///< ID of the node
+ std::string mNode;
+};
+
+/// A node in a scene hierarchy
+struct Node {
+ std::string mName;
+ std::string mID;
+ std::string mSID;
+ Node *mParent;
+ std::vector<Node *> mChildren;
+
+ /// Operations in order to calculate the resulting transformation to parent.
+ std::vector<Transform> mTransforms;
+
+ /// Meshes at this node
+ std::vector<MeshInstance> mMeshes;
+
+ /// Lights at this node
+ std::vector<LightInstance> mLights;
+
+ /// Cameras at this node
+ std::vector<CameraInstance> mCameras;
+
+ /// Node instances at this node
+ std::vector<NodeInstance> mNodeInstances;
+
+ /// Root-nodes: Name of primary camera, if any
+ std::string mPrimaryCamera;
+
+ /// Constructor. Begin with a zero parent
+ Node() :
+ mParent(nullptr) {
+ // empty
+ }
+
+ /// Destructor: delete all children subsequently
+ ~Node() {
+ for (std::vector<Node *>::iterator it = mChildren.begin(); it != mChildren.end(); ++it) {
+ delete *it;
+ }
+ }
+};
+
+/// Data source array: either floats or strings
+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
+ std::vector<std::string> mParams; // names of the data streams in the accessors. Empty string tells to ignore.
+ size_t mSubOffset[4]; // Sub-offset 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. nullptr else
+
+ Accessor() {
+ mCount = 0;
+ mSize = 0;
+ mOffset = 0;
+ mStride = 0;
+ mData = nullptr;
+ mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0;
+ }
+};
+
+/// A single face in a mesh
+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.
+ std::string mAccessor; // ID of the accessor where to read the actual values from.
+ mutable const Accessor *mResolved; // Pointer to the accessor, if resolved. nullptr else
+
+ InputChannel() {
+ mType = IT_Invalid;
+ mIndex = 0;
+ mOffset = 0;
+ mResolved = nullptr;
+ }
+};
+
+/// Subset of a mesh with a certain material
+struct SubMesh {
+ std::string mMaterial; ///< subgroup identifier
+ size_t mNumFaces; ///< number of faces in this sub-mesh
+};
+
+/// Contains data for a single mesh
+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...
+ // which we don't support, and therefore should warn about.
+ std::string mVertexID;
+
+ // Vertex data addressed by vertex indices
+ std::vector<InputChannel> mPerVertexData;
+
+ // actual mesh data, assembled on encounter of a <p> element. Verbose format, not indexed
+ std::vector<aiVector3D> mPositions;
+ std::vector<aiVector3D> mNormals;
+ 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];
+
+ unsigned int mNumUVComponents[AI_MAX_NUMBER_OF_TEXTURECOORDS];
+
+ // Faces. Stored are only the number of vertices for each face.
+ // 1 == point, 2 == line, 3 == triangle, 4+ == poly
+ std::vector<size_t> mFaceSize;
+
+ // Position indices for all faces in the sequence given in mFaceSize -
+ // necessary for bone weight assignment
+ std::vector<size_t> mFacePosIndices;
+
+ // Sub-meshes in this mesh, each with a given material
+ std::vector<SubMesh> mSubMeshes;
+};
+
+/// Which type of primitives the ReadPrimitives() function is going to read
+enum PrimitiveType {
+ Prim_Invalid,
+ Prim_Lines,
+ Prim_LineStrip,
+ Prim_Triangles,
+ Prim_TriStrips,
+ Prim_TriFans,
+ Prim_Polylist,
+ Prim_Polygon
+};
+
+/// A skeleton controller to deform a mesh with the use of joints
+struct Controller {
+ // controller type
+ ControllerType mType;
+
+ // Morphing method if type is Morph
+ MorphMethod mMethod;
+
+ // the URL of the mesh deformed by the controller.
+ std::string mMeshId;
+
+ // accessor URL of the joint names
+ std::string mJointNameSource;
+
+ ///< The bind shape matrix, as array of floats. I'm not sure what this matrix actually describes, but it can't be ignored in all cases
+ ai_real mBindShapeMatrix[16];
+
+ // accessor URL of the joint inverse bind matrices
+ std::string mJointOffsetMatrixSource;
+
+ // input channel: joint names.
+ InputChannel mWeightInputJoints;
+ // input channel: joint weights
+ InputChannel mWeightInputWeights;
+
+ // Number of weights per vertex.
+ std::vector<size_t> mWeightCounts;
+
+ // JointIndex-WeightIndex pairs for all vertices
+ 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 {
+ std::string mName;
+ std::string mEffect;
+};
+
+/// Type of the effect param
+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 {
+ 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 {
+ Shade_Invalid,
+ Shade_Constant,
+ Shade_Lambert,
+ Shade_Phong,
+ Shade_Blinn
+};
+
+/// 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) {}
+
+ /// Name of image reference
+ std::string mName;
+
+ /// Wrap U?
+ bool mWrapU;
+
+ /// Wrap V?
+ bool mWrapV;
+
+ /// Mirror U?
+ bool mMirrorU;
+
+ /// Mirror V?
+ bool mMirrorV;
+
+ /// Blend mode
+ aiTextureOp mOp;
+
+ /// UV transformation
+ aiUVTransform mTransform;
+
+ /// Name of source UV channel
+ std::string mUVChannel;
+
+ /// Resolved UV channel index or UINT_MAX if not known
+ unsigned int mUVId;
+
+ // OKINO/MAX3D extensions from here
+ // -------------------------------------------------------
+
+ /// Weighting factor
+ ai_real mWeighting;
+
+ /// Mixing factor from OKINO
+ ai_real mMixWithPrevious;
+};
+
+/// A collada effect. Can contain about anything according to the Collada spec,
+/// but we limit our version to a reasonable subset.
+struct Effect {
+ /// Shading mode
+ ShadeType mShadeType;
+
+ /// Colors
+ aiColor4D mEmissive, mAmbient, mDiffuse, mSpecular,
+ mTransparent, mReflective;
+
+ /// Textures
+ Sampler mTexEmissive, mTexAmbient, mTexDiffuse, mTexSpecular,
+ mTexTransparent, mTexBump, mTexReflective;
+
+ /// Scalar factory
+ ai_real mShininess, mRefractIndex, mReflectivity;
+ ai_real mTransparency;
+ bool mHasTransparency;
+ bool mRGBTransparency;
+ bool mInvertTransparency;
+
+ /// local params referring to each other by their SID
+ using ParamLibrary = std::map<std::string, Collada::EffectParam>;
+ ParamLibrary mParams;
+
+ // MAX3D extensions
+ // ---------------------------------------------------------
+ // 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) {
+ }
+};
+
+/// An image, meaning texture
+struct Image {
+ std::string mFileName;
+
+ /// Embedded image data
+ std::vector<uint8_t> mImageData;
+
+ /// File format hint of embedded image data
+ std::string mEmbeddedFormat;
+};
+
+/// An animation channel.
+struct AnimationChannel {
+ /// URL of the data to animate. Could be about anything, but we support only the
+ /// "NodeID/TransformID.SubElement" notation
+ std::string mTarget;
+
+ /// Source URL of the time values. Collada calls them "input". Meh.
+ std::string mSourceTimes;
+ /// Source URL of the value values. Collada calls them "output".
+ std::string mSourceValues;
+ /// Source URL of the IN_TANGENT semantic values.
+ std::string mInTanValues;
+ /// Source URL of the OUT_TANGENT semantic values.
+ std::string mOutTanValues;
+ /// Source URL of the INTERPOLATION semantic values.
+ std::string mInterpolationValues;
+};
+
+/// An animation. Container for 0-x animation channels or 0-x animations
+struct Animation {
+ /// Anim name
+ std::string mName;
+
+ /// the animation channels, if any
+ std::vector<AnimationChannel> mChannels;
+
+ /// the sub-animations, if any
+ std::vector<Animation *> mSubAnims;
+
+ /// Destructor
+ ~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());
+
+ for (std::vector<Animation *>::iterator it = mSubAnims.begin(); it != mSubAnims.end(); ++it) {
+ Animation *pAnim = (*it);
+ pAnim->CollectChannelsRecursively(channels);
+ }
+ }
+
+ /// 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;
+
+ 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;
+ }
+
+ ++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]);
+
+ it = pParent->mSubAnims.erase(it);
+
+ 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
+ std::string mTargetId;
+ 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() {}
+};
+
+} // end of namespace Collada
+} // end of namespace Assimp
+
+#endif // AI_COLLADAHELPER_H_INC
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp b/libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp
new file mode 100644
index 0000000..775ba44
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp
@@ -0,0 +1,1828 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 Implementation of the Collada loader */
+
+#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
+
+#include "ColladaLoader.h"
+#include "ColladaParser.h"
+#include <assimp/ColladaMetaData.h>
+#include <assimp/CreateAnimMesh.h>
+#include <assimp/ParsingUtils.h>
+#include <assimp/SkeletonMeshBuilder.h>
+#include <assimp/ZipArchiveIOSystem.h>
+#include <assimp/anim.h>
+#include <assimp/fast_atof.h>
+#include <assimp/importerdesc.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Importer.hpp>
+
+#include <numeric>
+
+namespace Assimp {
+
+using namespace Assimp::Formatter;
+using namespace Assimp::Collada;
+
+static const aiImporterDesc desc = {
+ "Collada Importer",
+ "",
+ "",
+ "http://collada.org",
+ aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour,
+ 1,
+ 3,
+ 1,
+ 5,
+ "dae xml zae"
+};
+
+static const float kMillisecondsFromSeconds = 1000.f;
+
+// Add an item of metadata to a node
+// Assumes the key is not already in the list
+template <typename T>
+inline void AddNodeMetaData(aiNode *node, const std::string &key, const T &value) {
+ if (nullptr == node->mMetaData) {
+ node->mMetaData = new aiMetadata();
+ }
+ node->mMetaData->Add(key, value);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ColladaLoader::ColladaLoader() :
+ mFileName(),
+ mMeshIndexByID(),
+ mMaterialIndexByName(),
+ mMeshes(),
+ newMats(),
+ mCameras(),
+ mLights(),
+ mTextures(),
+ mAnims(),
+ noSkeletonMesh(false),
+ ignoreUpDirection(false),
+ useColladaName(false),
+ mNodeNameCounter(0) {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ColladaLoader::~ColladaLoader() {
+ // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool ColladaLoader::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /*checkSig*/) const {
+ // Look for a DAE file inside, but don't extract it
+ ZipArchiveIOSystem zip_archive(pIOHandler, pFile);
+ if (zip_archive.isOpen()) {
+ return !ColladaParser::ReadZaeManifest(zip_archive).empty();
+ }
+
+ static const char *tokens[] = { "<collada" };
+ return SearchFileHeaderForToken(pIOHandler, pFile, tokens, AI_COUNT_OF(tokens));
+}
+
+// ------------------------------------------------------------------------------------------------
+void ColladaLoader::SetupProperties(const Importer *pImp) {
+ noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES, 0) != 0;
+ ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION, 0) != 0;
+ useColladaName = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES, 0) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get file extension list
+const aiImporterDesc *ColladaLoader::GetInfo() const {
+ return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void ColladaLoader::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
+ mFileName = pFile;
+
+ // clean all member arrays - just for safety, it should work even if we did not
+ mMeshIndexByID.clear();
+ mMaterialIndexByName.clear();
+ mMeshes.clear();
+ mTargetMeshes.clear();
+ newMats.clear();
+ mLights.clear();
+ mCameras.clear();
+ mTextures.clear();
+ mAnims.clear();
+
+ // parse the input file
+ ColladaParser parser(pIOHandler, pFile);
+
+ if (!parser.mRootNode) {
+ throw DeadlyImportError("Collada: File came out empty. Something is wrong here.");
+ }
+
+ // reserve some storage to avoid unnecessary reallocs
+ newMats.reserve(parser.mMaterialLibrary.size() * 2u);
+ mMeshes.reserve(parser.mMeshLibrary.size() * 2u);
+
+ mCameras.reserve(parser.mCameraLibrary.size());
+ mLights.reserve(parser.mLightLibrary.size());
+
+ // create the materials first, for the meshes to find
+ BuildMaterials(parser, pScene);
+
+ // build the node hierarchy from it
+ pScene->mRootNode = BuildHierarchy(parser, parser.mRootNode);
+
+ // ... then fill the materials with the now adjusted settings
+ FillMaterials(parser, pScene);
+
+ // Apply unit-size scale calculation
+
+ pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0,
+ 0, parser.mUnitSize, 0, 0,
+ 0, 0, parser.mUnitSize, 0,
+ 0, 0, 0, 1);
+ if (!ignoreUpDirection) {
+ // Convert to Y_UP, if different orientation
+ if (parser.mUpDirection == ColladaParser::UP_X) {
+ pScene->mRootNode->mTransformation *= aiMatrix4x4(
+ 0, -1, 0, 0,
+ 1, 0, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1);
+ } else if (parser.mUpDirection == ColladaParser::UP_Z) {
+ pScene->mRootNode->mTransformation *= aiMatrix4x4(
+ 1, 0, 0, 0,
+ 0, 0, 1, 0,
+ 0, -1, 0, 0,
+ 0, 0, 0, 1);
+ }
+ }
+
+ // Store scene metadata
+ if (!parser.mAssetMetaData.empty()) {
+ const size_t numMeta(parser.mAssetMetaData.size());
+ pScene->mMetaData = aiMetadata::Alloc(static_cast<unsigned int>(numMeta));
+ size_t i = 0;
+ for (auto it = parser.mAssetMetaData.cbegin(); it != parser.mAssetMetaData.cend(); ++it, ++i) {
+ pScene->mMetaData->Set(static_cast<unsigned int>(i), (*it).first, (*it).second);
+ }
+ }
+
+ StoreSceneMeshes(pScene);
+ StoreSceneMaterials(pScene);
+ StoreSceneTextures(pScene);
+ StoreSceneLights(pScene);
+ StoreSceneCameras(pScene);
+ StoreAnimations(pScene, parser);
+
+ // If no meshes have been loaded, it's probably just an animated skeleton.
+ if (0u == pScene->mNumMeshes) {
+ if (!noSkeletonMesh) {
+ SkeletonMeshBuilder hero(pScene);
+ }
+ pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively constructs a scene node for the given parser node and returns it.
+aiNode *ColladaLoader::BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode) {
+ // create a node for it
+ aiNode *node = new aiNode();
+
+ // find a name for the new node. It's more complicated than you might think
+ node->mName.Set(FindNameForNode(pNode));
+ // if we're not using the unique IDs, hold onto them for reference and export
+ if (useColladaName) {
+ if (!pNode->mID.empty()) {
+ AddNodeMetaData(node, AI_METADATA_COLLADA_ID, aiString(pNode->mID));
+ }
+ if (!pNode->mSID.empty()) {
+ AddNodeMetaData(node, AI_METADATA_COLLADA_SID, aiString(pNode->mSID));
+ }
+ }
+
+ // calculate the transformation matrix for it
+ node->mTransformation = pParser.CalculateResultTransform(pNode->mTransforms);
+
+ // now resolve node instances
+ std::vector<const Node*> instances;
+ ResolveNodeInstances(pParser, pNode, instances);
+
+ // add children. first the *real* ones
+ node->mNumChildren = static_cast<unsigned int>(pNode->mChildren.size() + instances.size());
+ node->mChildren = new aiNode *[node->mNumChildren];
+
+ for (size_t a = 0; a < pNode->mChildren.size(); ++a) {
+ node->mChildren[a] = BuildHierarchy(pParser, pNode->mChildren[a]);
+ node->mChildren[a]->mParent = node;
+ }
+
+ // ... and finally the resolved node instances
+ for (size_t a = 0; a < instances.size(); ++a) {
+ node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy(pParser, instances[a]);
+ node->mChildren[pNode->mChildren.size() + a]->mParent = node;
+ }
+
+ BuildMeshesForNode(pParser, pNode, node);
+ BuildCamerasForNode(pParser, pNode, node);
+ BuildLightsForNode(pParser, pNode, node);
+
+ return node;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Resolve node instances
+void ColladaLoader::ResolveNodeInstances(const ColladaParser &pParser, const Node *pNode,
+ std::vector<const Node*> &resolved) {
+ // reserve enough storage
+ resolved.reserve(pNode->mNodeInstances.size());
+
+ // ... and iterate through all nodes to be instanced as children of pNode
+ for (const auto &nodeInst : pNode->mNodeInstances) {
+ // find the corresponding node in the library
+ const ColladaParser::NodeLibrary::const_iterator itt = pParser.mNodeLibrary.find(nodeInst.mNode);
+ const Node *nd = itt == pParser.mNodeLibrary.end() ? nullptr : (*itt).second;
+
+ // FIX for http://sourceforge.net/tracker/?func=detail&aid=3054873&group_id=226462&atid=1067632
+ // need to check for both name and ID to catch all. To avoid breaking valid files,
+ // the workaround is only enabled when the first attempt to resolve the node has failed.
+ if (nullptr == nd) {
+ nd = FindNode(pParser.mRootNode, nodeInst.mNode);
+ }
+ if (nullptr == nd) {
+ ASSIMP_LOG_ERROR("Collada: Unable to resolve reference to instanced node ", nodeInst.mNode);
+ } else {
+ // attach this node to the list of children
+ resolved.push_back(nd);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Resolve UV channels
+void ColladaLoader::ApplyVertexToEffectSemanticMapping(Sampler &sampler, const SemanticMappingTable &table) {
+ SemanticMappingTable::InputSemanticMap::const_iterator it = table.mMap.find(sampler.mUVChannel);
+ if (it == table.mMap.end()) {
+ return;
+ }
+
+ if (it->second.mType != IT_Texcoord) {
+ ASSIMP_LOG_ERROR("Collada: Unexpected effect input mapping");
+ }
+
+ sampler.mUVId = it->second.mSet;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Builds lights for the given node and references them
+void ColladaLoader::BuildLightsForNode(const ColladaParser &pParser, const Node *pNode, aiNode *pTarget) {
+ for (const LightInstance &lid : pNode->mLights) {
+ // find the referred light
+ ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find(lid.mLight);
+ if (srcLightIt == pParser.mLightLibrary.end()) {
+ ASSIMP_LOG_WARN("Collada: Unable to find light for ID \"", lid.mLight, "\". Skipping.");
+ continue;
+ }
+ const Collada::Light *srcLight = &srcLightIt->second;
+
+ // now fill our ai data structure
+ aiLight *out = new aiLight();
+ out->mName = pTarget->mName;
+ out->mType = (aiLightSourceType)srcLight->mType;
+
+ // collada lights point in -Z by default, rest is specified in node transform
+ out->mDirection = aiVector3D(0.f, 0.f, -1.f);
+
+ out->mAttenuationConstant = srcLight->mAttConstant;
+ out->mAttenuationLinear = srcLight->mAttLinear;
+ out->mAttenuationQuadratic = srcLight->mAttQuadratic;
+
+ out->mColorDiffuse = out->mColorSpecular = out->mColorAmbient = srcLight->mColor * srcLight->mIntensity;
+ if (out->mType == aiLightSource_AMBIENT) {
+ out->mColorDiffuse = out->mColorSpecular = aiColor3D(0, 0, 0);
+ out->mColorAmbient = srcLight->mColor * srcLight->mIntensity;
+ } else {
+ // collada doesn't differentiate between these color types
+ out->mColorDiffuse = out->mColorSpecular = srcLight->mColor * srcLight->mIntensity;
+ out->mColorAmbient = aiColor3D(0, 0, 0);
+ }
+
+ // convert falloff angle and falloff exponent in our representation, if given
+ if (out->mType == aiLightSource_SPOT) {
+ out->mAngleInnerCone = AI_DEG_TO_RAD(srcLight->mFalloffAngle);
+
+ // ... some extension magic.
+ if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * (1 - ai_epsilon)) {
+ // ... some deprecation magic.
+ if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * (1 - ai_epsilon)) {
+ // Need to rely on falloff_exponent. I don't know how to interpret it, so I need to guess ....
+ // epsilon chosen to be 0.1
+ float f = 1.0f;
+ if ( 0.0f != srcLight->mFalloffExponent ) {
+ f = 1.f / srcLight->mFalloffExponent;
+ }
+ out->mAngleOuterCone = std::acos(std::pow(0.1f, f)) +
+ out->mAngleInnerCone;
+ } else {
+ out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD(srcLight->mPenumbraAngle);
+ if (out->mAngleOuterCone < out->mAngleInnerCone)
+ std::swap(out->mAngleInnerCone, out->mAngleOuterCone);
+ }
+ } else {
+ out->mAngleOuterCone = AI_DEG_TO_RAD(srcLight->mOuterAngle);
+ }
+ }
+
+ // add to light list
+ mLights.push_back(out);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Builds cameras for the given node and references them
+void ColladaLoader::BuildCamerasForNode(const ColladaParser &pParser, const Node *pNode, aiNode *pTarget) {
+ for (const CameraInstance &cid : pNode->mCameras) {
+ // find the referred light
+ ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find(cid.mCamera);
+ if (srcCameraIt == pParser.mCameraLibrary.end()) {
+ ASSIMP_LOG_WARN("Collada: Unable to find camera for ID \"", cid.mCamera, "\". Skipping.");
+ continue;
+ }
+ const Collada::Camera *srcCamera = &srcCameraIt->second;
+
+ // orthographic cameras not yet supported in Assimp
+ if (srcCamera->mOrtho) {
+ ASSIMP_LOG_WARN("Collada: Orthographic cameras are not supported.");
+ }
+
+ // now fill our ai data structure
+ aiCamera *out = new aiCamera();
+ out->mName = pTarget->mName;
+
+ // collada cameras point in -Z by default, rest is specified in node transform
+ out->mLookAt = aiVector3D(0.f, 0.f, -1.f);
+
+ // near/far z is already ok
+ out->mClipPlaneFar = srcCamera->mZFar;
+ out->mClipPlaneNear = srcCamera->mZNear;
+
+ // ... but for the rest some values are optional
+ // and we need to compute the others in any combination.
+ if (srcCamera->mAspect != 10e10f) {
+ out->mAspect = srcCamera->mAspect;
+ }
+
+ if (srcCamera->mHorFov != 10e10f) {
+ out->mHorizontalFOV = srcCamera->mHorFov;
+
+ if (srcCamera->mVerFov != 10e10f && srcCamera->mAspect == 10e10f) {
+ out->mAspect = std::tan(AI_DEG_TO_RAD(srcCamera->mHorFov)) /
+ std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov));
+ }
+
+ } else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f) {
+ out->mHorizontalFOV = 2.0f * AI_RAD_TO_DEG(std::atan(srcCamera->mAspect *
+ std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov) * 0.5f)));
+ }
+
+ // Collada uses degrees, we use radians
+ out->mHorizontalFOV = AI_DEG_TO_RAD(out->mHorizontalFOV);
+
+ // add to camera list
+ mCameras.push_back(out);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Builds meshes for the given node and references them
+void ColladaLoader::BuildMeshesForNode(const ColladaParser &pParser, const Node *pNode, aiNode *pTarget) {
+ // accumulated mesh references by this node
+ std::vector<size_t> newMeshRefs;
+ newMeshRefs.reserve(pNode->mMeshes.size());
+
+ // add a mesh for each subgroup in each collada mesh
+ for (const MeshInstance &mid : pNode->mMeshes) {
+ const Mesh *srcMesh = nullptr;
+ const Controller *srcController = nullptr;
+
+ // find the referred mesh
+ ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find(mid.mMeshOrController);
+ if (srcMeshIt == pParser.mMeshLibrary.end()) {
+ // if not found in the mesh-library, it might also be a controller referring to a mesh
+ ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find(mid.mMeshOrController);
+ if (srcContrIt != pParser.mControllerLibrary.end()) {
+ srcController = &srcContrIt->second;
+ srcMeshIt = pParser.mMeshLibrary.find(srcController->mMeshId);
+ if (srcMeshIt != pParser.mMeshLibrary.end()) {
+ srcMesh = srcMeshIt->second;
+ }
+ }
+
+ if (nullptr == srcMesh) {
+ ASSIMP_LOG_WARN("Collada: Unable to find geometry for ID \"", mid.mMeshOrController, "\". Skipping.");
+ continue;
+ }
+ } else {
+ // ID found in the mesh library -> direct reference to an unskinned mesh
+ srcMesh = srcMeshIt->second;
+ }
+
+ // build a mesh for each of its subgroups
+ size_t vertexStart = 0, faceStart = 0;
+ for (size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm) {
+ const Collada::SubMesh &submesh = srcMesh->mSubMeshes[sm];
+ if (submesh.mNumFaces == 0) {
+ continue;
+ }
+
+ // find material assigned to this submesh
+ std::string meshMaterial;
+ std::map<std::string, SemanticMappingTable>::const_iterator meshMatIt = mid.mMaterials.find(submesh.mMaterial);
+
+ const Collada::SemanticMappingTable *table = nullptr;
+ if (meshMatIt != mid.mMaterials.end()) {
+ table = &meshMatIt->second;
+ meshMaterial = table->mMatName;
+ } else {
+ ASSIMP_LOG_WARN("Collada: No material specified for subgroup <", submesh.mMaterial, "> in geometry <",
+ mid.mMeshOrController, ">.");
+ if (!mid.mMaterials.empty()) {
+ meshMaterial = mid.mMaterials.begin()->second.mMatName;
+ }
+ }
+
+ // OK ... here the *real* fun starts ... we have the vertex-input-to-effect-semantic-table
+ // given. The only mapping stuff which we do actually support is the UV channel.
+ std::map<std::string, size_t>::const_iterator matIt = mMaterialIndexByName.find(meshMaterial);
+ unsigned int matIdx = 0;
+ if (matIt != mMaterialIndexByName.end()) {
+ matIdx = static_cast<unsigned int>(matIt->second);
+ }
+
+ if (table && !table->mMap.empty()) {
+ std::pair<Collada::Effect *, aiMaterial *> &mat = newMats[matIdx];
+
+ // Iterate through all texture channels assigned to the effect and
+ // check whether we have mapping information for it.
+ ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse, *table);
+ ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient, *table);
+ ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular, *table);
+ ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive, *table);
+ ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent, *table);
+ ApplyVertexToEffectSemanticMapping(mat.first->mTexBump, *table);
+ }
+
+ // built lookup index of the Mesh-Submesh-Material combination
+ ColladaMeshIndex index(mid.mMeshOrController, sm, meshMaterial);
+
+ // if we already have the mesh at the library, just add its index to the node's array
+ std::map<ColladaMeshIndex, size_t>::const_iterator dstMeshIt = mMeshIndexByID.find(index);
+ if (dstMeshIt != mMeshIndexByID.end()) {
+ newMeshRefs.push_back(dstMeshIt->second);
+ } else {
+ // else we have to add the mesh to the collection and store its newly assigned index at the node
+ aiMesh *dstMesh = CreateMesh(pParser, srcMesh, submesh, srcController, vertexStart, faceStart);
+
+ // store the mesh, and store its new index in the node
+ newMeshRefs.push_back(mMeshes.size());
+ mMeshIndexByID[index] = mMeshes.size();
+ mMeshes.push_back(dstMesh);
+ vertexStart += dstMesh->mNumVertices;
+ faceStart += submesh.mNumFaces;
+
+ // assign the material index
+ std::map<std::string, size_t>::const_iterator subMatIt = mMaterialIndexByName.find(submesh.mMaterial);
+ if (subMatIt != mMaterialIndexByName.end()) {
+ dstMesh->mMaterialIndex = static_cast<unsigned int>(subMatIt->second);
+ } else {
+ dstMesh->mMaterialIndex = matIdx;
+ }
+ if (dstMesh->mName.length == 0) {
+ dstMesh->mName = mid.mMeshOrController;
+ }
+ }
+ }
+ }
+
+ // now place all mesh references we gathered in the target node
+ pTarget->mNumMeshes = static_cast<unsigned int>(newMeshRefs.size());
+ if (!newMeshRefs.empty()) {
+ struct UIntTypeConverter {
+ unsigned int operator()(const size_t &v) const {
+ return static_cast<unsigned int>(v);
+ }
+ };
+
+ pTarget->mMeshes = new unsigned int[pTarget->mNumMeshes];
+ std::transform(newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes, UIntTypeConverter());
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find mesh from either meshes or morph target meshes
+aiMesh *ColladaLoader::findMesh(const std::string &meshid) {
+ if (meshid.empty()) {
+ return nullptr;
+ }
+
+ for (auto & mMeshe : mMeshes) {
+ if (std::string(mMeshe->mName.data) == meshid) {
+ return mMeshe;
+ }
+ }
+
+ for (auto & mTargetMeshe : mTargetMeshes) {
+ if (std::string(mTargetMeshe->mName.data) == meshid) {
+ return mTargetMeshe;
+ }
+ }
+
+ return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh
+aiMesh *ColladaLoader::CreateMesh(const ColladaParser &pParser, const Mesh *pSrcMesh, const SubMesh &pSubMesh,
+ const Controller *pSrcController, size_t pStartVertex, size_t pStartFace) {
+ std::unique_ptr<aiMesh> dstMesh(new aiMesh);
+
+ if (useColladaName) {
+ dstMesh->mName = pSrcMesh->mName;
+ } else {
+ dstMesh->mName = pSrcMesh->mId;
+ }
+
+ if (pSrcMesh->mPositions.empty()) {
+ return dstMesh.release();
+ }
+
+ // count the vertices addressed by its faces
+ const size_t numVertices = std::accumulate(pSrcMesh->mFaceSize.begin() + pStartFace,
+ pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, size_t(0));
+
+ // copy positions
+ dstMesh->mNumVertices = static_cast<unsigned int>(numVertices);
+ dstMesh->mVertices = new aiVector3D[numVertices];
+ std::copy(pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() + pStartVertex + numVertices, dstMesh->mVertices);
+
+ // normals, if given. HACK: (thom) Due to the glorious Collada spec we never
+ // know if we have the same number of normals as there are positions. So we
+ // also ignore any vertex attribute if it has a different count
+ if (pSrcMesh->mNormals.size() >= pStartVertex + numVertices) {
+ dstMesh->mNormals = new aiVector3D[numVertices];
+ std::copy(pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() + pStartVertex + numVertices, dstMesh->mNormals);
+ }
+
+ // tangents, if given.
+ if (pSrcMesh->mTangents.size() >= pStartVertex + numVertices) {
+ dstMesh->mTangents = new aiVector3D[numVertices];
+ std::copy(pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() + pStartVertex + numVertices, dstMesh->mTangents);
+ }
+
+ // bitangents, if given.
+ if (pSrcMesh->mBitangents.size() >= pStartVertex + numVertices) {
+ dstMesh->mBitangents = new aiVector3D[numVertices];
+ std::copy(pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() + pStartVertex + numVertices, dstMesh->mBitangents);
+ }
+
+ // same for texture coords, as many as we have
+ // empty slots are not allowed, need to pack and adjust UV indexes accordingly
+ for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
+ if (pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) {
+ dstMesh->mTextureCoords[real] = new aiVector3D[numVertices];
+ for (size_t b = 0; b < numVertices; ++b) {
+ dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex + b];
+ }
+
+ dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a];
+ ++real;
+ }
+ }
+
+ // same for vertex colors, as many as we have. again the same packing to avoid empty slots
+ for (size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; ++a) {
+ if (pSrcMesh->mColors[a].size() >= pStartVertex + numVertices) {
+ dstMesh->mColors[real] = new aiColor4D[numVertices];
+ std::copy(pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices, dstMesh->mColors[real]);
+ ++real;
+ }
+ }
+
+ // create faces. Due to the fact that each face uses unique vertices, we can simply count up on each vertex
+ size_t vertex = 0;
+ dstMesh->mNumFaces = static_cast<unsigned int>(pSubMesh.mNumFaces);
+ dstMesh->mFaces = new aiFace[dstMesh->mNumFaces];
+ for (size_t a = 0; a < dstMesh->mNumFaces; ++a) {
+ size_t s = pSrcMesh->mFaceSize[pStartFace + a];
+ aiFace &face = dstMesh->mFaces[a];
+ face.mNumIndices = static_cast<unsigned int>(s);
+ face.mIndices = new unsigned int[s];
+ for (size_t b = 0; b < s; ++b) {
+ face.mIndices[b] = static_cast<unsigned int>(vertex++);
+ }
+ }
+
+ // create morph target meshes if any
+ std::vector<aiMesh *> targetMeshes;
+ std::vector<float> targetWeights;
+ Collada::MorphMethod method = Normalized;
+
+ for (std::map<std::string, Controller>::const_iterator it = pParser.mControllerLibrary.begin();
+ it != pParser.mControllerLibrary.end(); ++it) {
+ const Controller &c = it->second;
+ const Collada::Mesh *baseMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, c.mMeshId);
+
+ if (c.mType == Collada::Morph && baseMesh->mName == pSrcMesh->mName) {
+ const Collada::Accessor &targetAccessor = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, c.mMorphTarget);
+ const Collada::Accessor &weightAccessor = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, c.mMorphWeight);
+ const Collada::Data &targetData = pParser.ResolveLibraryReference(pParser.mDataLibrary, targetAccessor.mSource);
+ const Collada::Data &weightData = pParser.ResolveLibraryReference(pParser.mDataLibrary, weightAccessor.mSource);
+
+ // take method
+ method = c.mMethod;
+
+ if (!targetData.mIsStringArray) {
+ throw DeadlyImportError("target data must contain id. ");
+ }
+ if (weightData.mIsStringArray) {
+ throw DeadlyImportError("target weight data must not be textual ");
+ }
+
+ for (const auto & mString : targetData.mStrings) {
+ const Mesh *targetMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, mString);
+
+ aiMesh *aimesh = findMesh(useColladaName ? targetMesh->mName : targetMesh->mId);
+ if (!aimesh) {
+ if (targetMesh->mSubMeshes.size() > 1) {
+ throw DeadlyImportError("Morphing target mesh must be a single");
+ }
+ aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), nullptr, 0, 0);
+ mTargetMeshes.push_back(aimesh);
+ }
+ targetMeshes.push_back(aimesh);
+ }
+ for (float mValue : weightData.mValues) {
+ targetWeights.push_back(mValue);
+ }
+ }
+ }
+ if (!targetMeshes.empty() && targetWeights.size() == targetMeshes.size()) {
+ std::vector<aiAnimMesh *> animMeshes;
+ for (unsigned int i = 0; i < targetMeshes.size(); ++i) {
+ aiMesh *targetMesh = targetMeshes.at(i);
+ aiAnimMesh *animMesh = aiCreateAnimMesh(targetMesh);
+ float weight = targetWeights[i];
+ animMesh->mWeight = weight == 0 ? 1.0f : weight;
+ animMesh->mName = targetMesh->mName;
+ animMeshes.push_back(animMesh);
+ }
+ dstMesh->mMethod = (method == Relative) ? aiMorphingMethod_MORPH_RELATIVE : aiMorphingMethod_MORPH_NORMALIZED;
+ dstMesh->mAnimMeshes = new aiAnimMesh *[animMeshes.size()];
+ dstMesh->mNumAnimMeshes = static_cast<unsigned int>(animMeshes.size());
+ for (unsigned int i = 0; i < animMeshes.size(); ++i) {
+ dstMesh->mAnimMeshes[i] = animMeshes.at(i);
+ }
+ }
+
+ // create bones if given
+ if (pSrcController && pSrcController->mType == Collada::Skin) {
+ // resolve references - joint names
+ const Collada::Accessor &jointNamesAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mJointNameSource);
+ const Collada::Data &jointNames = pParser.ResolveLibraryReference(pParser.mDataLibrary, jointNamesAcc.mSource);
+ // joint offset matrices
+ const Collada::Accessor &jointMatrixAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource);
+ const Collada::Data &jointMatrices = pParser.ResolveLibraryReference(pParser.mDataLibrary, jointMatrixAcc.mSource);
+ // joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider
+ const Collada::Accessor &weightNamesAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor);
+ if (&weightNamesAcc != &jointNamesAcc)
+ throw DeadlyImportError("Temporary implementational laziness. If you read this, please report to the author.");
+ // vertex weights
+ const Collada::Accessor &weightsAcc = pParser.ResolveLibraryReference(pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor);
+ const Collada::Data &weights = pParser.ResolveLibraryReference(pParser.mDataLibrary, weightsAcc.mSource);
+
+ if (!jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray) {
+ throw DeadlyImportError("Data type mismatch while resolving mesh joints");
+ }
+ // sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex
+ if (pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1) {
+ throw DeadlyImportError("Unsupported vertex_weight addressing scheme. ");
+ }
+
+ // create containers to collect the weights for each bone
+ size_t numBones = jointNames.mStrings.size();
+ std::vector<std::vector<aiVertexWeight>> dstBones(numBones);
+
+ // build a temporary array of pointers to the start of each vertex's weights
+ using IndexPairVector = std::vector<std::pair<size_t, size_t>>;
+ std::vector<IndexPairVector::const_iterator> weightStartPerVertex;
+ weightStartPerVertex.resize(pSrcController->mWeightCounts.size(), pSrcController->mWeights.end());
+
+ IndexPairVector::const_iterator pit = pSrcController->mWeights.begin();
+ for (size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a) {
+ weightStartPerVertex[a] = pit;
+ pit += pSrcController->mWeightCounts[a];
+ }
+
+ // now for each vertex put the corresponding vertex weights into each bone's weight collection
+ for (size_t a = pStartVertex; a < pStartVertex + numVertices; ++a) {
+ // which position index was responsible for this vertex? that's also the index by which
+ // the controller assigns the vertex weights
+ size_t orgIndex = pSrcMesh->mFacePosIndices[a];
+ // find the vertex weights for this vertex
+ IndexPairVector::const_iterator iit = weightStartPerVertex[orgIndex];
+ size_t pairCount = pSrcController->mWeightCounts[orgIndex];
+
+ for (size_t b = 0; b < pairCount; ++b, ++iit) {
+ const size_t jointIndex = iit->first;
+ const size_t vertexIndex = iit->second;
+ ai_real weight = 1.0f;
+ if (!weights.mValues.empty()) {
+ weight = ReadFloat(weightsAcc, weights, vertexIndex, 0);
+ }
+
+ // one day I gonna kill that XSI Collada exporter
+ if (weight > 0.0f) {
+ aiVertexWeight w;
+ w.mVertexId = static_cast<unsigned int>(a - pStartVertex);
+ w.mWeight = weight;
+ dstBones[jointIndex].push_back(w);
+ }
+ }
+ }
+
+ // count the number of bones which influence vertices of the current submesh
+ size_t numRemainingBones = 0;
+ for (const auto & dstBone : dstBones) {
+ if (!dstBone.empty()) {
+ ++numRemainingBones;
+ }
+ }
+
+ // create bone array and copy bone weights one by one
+ dstMesh->mNumBones = static_cast<unsigned int>(numRemainingBones);
+ dstMesh->mBones = new aiBone *[numRemainingBones];
+ size_t boneCount = 0;
+ for (size_t a = 0; a < numBones; ++a) {
+ // omit bones without weights
+ if (dstBones[a].empty()) {
+ continue;
+ }
+
+ // create bone with its weights
+ aiBone *bone = new aiBone;
+ bone->mName = ReadString(jointNamesAcc, jointNames, a);
+ bone->mOffsetMatrix.a1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 0);
+ bone->mOffsetMatrix.a2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 1);
+ bone->mOffsetMatrix.a3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 2);
+ bone->mOffsetMatrix.a4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 3);
+ bone->mOffsetMatrix.b1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 4);
+ bone->mOffsetMatrix.b2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 5);
+ bone->mOffsetMatrix.b3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 6);
+ bone->mOffsetMatrix.b4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 7);
+ bone->mOffsetMatrix.c1 = ReadFloat(jointMatrixAcc, jointMatrices, a, 8);
+ bone->mOffsetMatrix.c2 = ReadFloat(jointMatrixAcc, jointMatrices, a, 9);
+ bone->mOffsetMatrix.c3 = ReadFloat(jointMatrixAcc, jointMatrices, a, 10);
+ bone->mOffsetMatrix.c4 = ReadFloat(jointMatrixAcc, jointMatrices, a, 11);
+ bone->mNumWeights = static_cast<unsigned int>(dstBones[a].size());
+ bone->mWeights = new aiVertexWeight[bone->mNumWeights];
+ std::copy(dstBones[a].begin(), dstBones[a].end(), bone->mWeights);
+
+ // apply bind shape matrix to offset matrix
+ aiMatrix4x4 bindShapeMatrix;
+ bindShapeMatrix.a1 = pSrcController->mBindShapeMatrix[0];
+ bindShapeMatrix.a2 = pSrcController->mBindShapeMatrix[1];
+ bindShapeMatrix.a3 = pSrcController->mBindShapeMatrix[2];
+ bindShapeMatrix.a4 = pSrcController->mBindShapeMatrix[3];
+ bindShapeMatrix.b1 = pSrcController->mBindShapeMatrix[4];
+ bindShapeMatrix.b2 = pSrcController->mBindShapeMatrix[5];
+ bindShapeMatrix.b3 = pSrcController->mBindShapeMatrix[6];
+ bindShapeMatrix.b4 = pSrcController->mBindShapeMatrix[7];
+ bindShapeMatrix.c1 = pSrcController->mBindShapeMatrix[8];
+ bindShapeMatrix.c2 = pSrcController->mBindShapeMatrix[9];
+ bindShapeMatrix.c3 = pSrcController->mBindShapeMatrix[10];
+ bindShapeMatrix.c4 = pSrcController->mBindShapeMatrix[11];
+ bindShapeMatrix.d1 = pSrcController->mBindShapeMatrix[12];
+ bindShapeMatrix.d2 = pSrcController->mBindShapeMatrix[13];
+ bindShapeMatrix.d3 = pSrcController->mBindShapeMatrix[14];
+ bindShapeMatrix.d4 = pSrcController->mBindShapeMatrix[15];
+ bone->mOffsetMatrix *= bindShapeMatrix;
+
+ // HACK: (thom) Some exporters address the bone nodes by SID, others address them by ID or even name.
+ // Therefore I added a little name replacement here: I search for the bone's node by either name, ID or SID,
+ // and replace the bone's name by the node's name so that the user can use the standard
+ // find-by-name method to associate nodes with bones.
+ const Collada::Node *bnode = FindNode(pParser.mRootNode, bone->mName.data);
+ if (nullptr == bnode) {
+ bnode = FindNodeBySID(pParser.mRootNode, bone->mName.data);
+ }
+
+ // assign the name that we would have assigned for the source node
+ if (nullptr != bnode) {
+ bone->mName.Set(FindNameForNode(bnode));
+ } else {
+ ASSIMP_LOG_WARN("ColladaLoader::CreateMesh(): could not find corresponding node for joint \"", bone->mName.data, "\".");
+ }
+
+ // and insert bone
+ dstMesh->mBones[boneCount++] = bone;
+ }
+ }
+
+ return dstMesh.release();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all meshes in the given scene
+void ColladaLoader::StoreSceneMeshes(aiScene *pScene) {
+ pScene->mNumMeshes = static_cast<unsigned int>(mMeshes.size());
+ if (mMeshes.empty()) {
+ return;
+ }
+ pScene->mMeshes = new aiMesh *[mMeshes.size()];
+ std::copy(mMeshes.begin(), mMeshes.end(), pScene->mMeshes);
+ mMeshes.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all cameras in the given scene
+void ColladaLoader::StoreSceneCameras(aiScene *pScene) {
+ pScene->mNumCameras = static_cast<unsigned int>(mCameras.size());
+ if (mCameras.empty()) {
+ return;
+ }
+ pScene->mCameras = new aiCamera *[mCameras.size()];
+ std::copy(mCameras.begin(), mCameras.end(), pScene->mCameras);
+ mCameras.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all lights in the given scene
+void ColladaLoader::StoreSceneLights(aiScene *pScene) {
+ pScene->mNumLights = static_cast<unsigned int>(mLights.size());
+ if (mLights.empty()) {
+ return;
+ }
+ pScene->mLights = new aiLight *[mLights.size()];
+ std::copy(mLights.begin(), mLights.end(), pScene->mLights);
+ mLights.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all textures in the given scene
+void ColladaLoader::StoreSceneTextures(aiScene *pScene) {
+ pScene->mNumTextures = static_cast<unsigned int>(mTextures.size());
+ if (mTextures.empty()) {
+ return;
+ }
+ pScene->mTextures = new aiTexture *[mTextures.size()];
+ std::copy(mTextures.begin(), mTextures.end(), pScene->mTextures);
+ mTextures.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all materials in the given scene
+void ColladaLoader::StoreSceneMaterials(aiScene *pScene) {
+ pScene->mNumMaterials = static_cast<unsigned int>(newMats.size());
+ if (newMats.empty()) {
+ return;
+ }
+ pScene->mMaterials = new aiMaterial *[newMats.size()];
+ for (unsigned int i = 0; i < newMats.size(); ++i) {
+ pScene->mMaterials[i] = newMats[i].second;
+ }
+ newMats.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Stores all animations
+void ColladaLoader::StoreAnimations(aiScene *pScene, const ColladaParser &pParser) {
+ // recursively collect all animations from the collada scene
+ StoreAnimations(pScene, pParser, &pParser.mAnims, "");
+
+ // catch special case: many animations with the same length, each affecting only a single node.
+ // we need to unite all those single-node-anims to a proper combined animation
+ for (size_t a = 0; a < mAnims.size(); ++a) {
+ aiAnimation *templateAnim = mAnims[a];
+
+ if (templateAnim->mNumChannels == 1) {
+ // search for other single-channel-anims with the same duration
+ std::vector<size_t> collectedAnimIndices;
+ for (size_t b = a + 1; b < mAnims.size(); ++b) {
+ aiAnimation *other = mAnims[b];
+ if (other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration &&
+ other->mTicksPerSecond == templateAnim->mTicksPerSecond)
+ collectedAnimIndices.push_back(b);
+ }
+
+ // We only want to combine the animations if they have different channels
+ std::set<std::string> animTargets;
+ animTargets.insert(templateAnim->mChannels[0]->mNodeName.C_Str());
+ bool collectedAnimationsHaveDifferentChannels = true;
+ for (unsigned long long collectedAnimIndice : collectedAnimIndices) {
+ aiAnimation *srcAnimation = mAnims[(int)collectedAnimIndice];
+ std::string channelName = std::string(srcAnimation->mChannels[0]->mNodeName.C_Str());
+ if (animTargets.find(channelName) == animTargets.end()) {
+ animTargets.insert(channelName);
+ } else {
+ collectedAnimationsHaveDifferentChannels = false;
+ break;
+ }
+ }
+
+ if (!collectedAnimationsHaveDifferentChannels) {
+ continue;
+ }
+
+ // if there are other animations which fit the template anim, combine all channels into a single anim
+ if (!collectedAnimIndices.empty()) {
+ aiAnimation *combinedAnim = new aiAnimation();
+ combinedAnim->mName = aiString(std::string("combinedAnim_") + char('0' + a));
+ combinedAnim->mDuration = templateAnim->mDuration;
+ combinedAnim->mTicksPerSecond = templateAnim->mTicksPerSecond;
+ combinedAnim->mNumChannels = static_cast<unsigned int>(collectedAnimIndices.size() + 1);
+ combinedAnim->mChannels = new aiNodeAnim *[combinedAnim->mNumChannels];
+ // add the template anim as first channel by moving its aiNodeAnim to the combined animation
+ combinedAnim->mChannels[0] = templateAnim->mChannels[0];
+ templateAnim->mChannels[0] = nullptr;
+ delete templateAnim;
+ // combined animation replaces template animation in the anim array
+ mAnims[a] = combinedAnim;
+
+ // move the memory of all other anims to the combined anim and erase them from the source anims
+ for (size_t b = 0; b < collectedAnimIndices.size(); ++b) {
+ aiAnimation *srcAnimation = mAnims[collectedAnimIndices[b]];
+ combinedAnim->mChannels[1 + b] = srcAnimation->mChannels[0];
+ srcAnimation->mChannels[0] = nullptr;
+ delete srcAnimation;
+ }
+
+ // in a second go, delete all the single-channel-anims that we've stripped from their channels
+ // back to front to preserve indices - you know, removing an element from a vector moves all elements behind the removed one
+ while (!collectedAnimIndices.empty()) {
+ mAnims.erase(mAnims.begin() + collectedAnimIndices.back());
+ collectedAnimIndices.pop_back();
+ }
+ }
+ }
+ }
+
+ // now store all anims in the scene
+ if (!mAnims.empty()) {
+ pScene->mNumAnimations = static_cast<unsigned int>(mAnims.size());
+ pScene->mAnimations = new aiAnimation *[mAnims.size()];
+ std::copy(mAnims.begin(), mAnims.end(), pScene->mAnimations);
+ }
+
+ mAnims.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs the animations for the given source anim
+void ColladaLoader::StoreAnimations(aiScene *pScene, const ColladaParser &pParser, const Animation *pSrcAnim, const std::string &pPrefix) {
+ std::string animName = pPrefix.empty() ? pSrcAnim->mName : pPrefix + "_" + pSrcAnim->mName;
+
+ // create nested animations, if given
+ for (auto mSubAnim : pSrcAnim->mSubAnims) {
+ StoreAnimations(pScene, pParser, mSubAnim, animName);
+ }
+
+ // create animation channels, if any
+ if (!pSrcAnim->mChannels.empty()) {
+ CreateAnimation(pScene, pParser, pSrcAnim, animName);
+ }
+}
+
+struct MorphTimeValues {
+ float mTime;
+ struct key {
+ float mWeight;
+ unsigned int mValue;
+ };
+ std::vector<key> mKeys;
+};
+
+void insertMorphTimeValue(std::vector<MorphTimeValues> &values, float time, float weight, unsigned int value) {
+ MorphTimeValues::key k;
+ k.mValue = value;
+ k.mWeight = weight;
+ if (values.empty() || time < values[0].mTime) {
+ MorphTimeValues val;
+ val.mTime = time;
+ val.mKeys.push_back(k);
+ values.insert(values.begin(), val);
+ return;
+ }
+ if (time > values.back().mTime) {
+ MorphTimeValues val;
+ val.mTime = time;
+ val.mKeys.push_back(k);
+ values.insert(values.end(), val);
+ return;
+ }
+ for (unsigned int i = 0; i < values.size(); i++) {
+ if (std::abs(time - values[i].mTime) < ai_epsilon) {
+ values[i].mKeys.push_back(k);
+ return;
+ } else if (time > values[i].mTime && time < values[i + 1].mTime) {
+ MorphTimeValues val;
+ val.mTime = time;
+ val.mKeys.push_back(k);
+ values.insert(values.begin() + i, val);
+ return;
+ }
+ }
+}
+
+static float getWeightAtKey(const std::vector<MorphTimeValues> &values, int key, unsigned int value) {
+ for (auto mKey : values[key].mKeys) {
+ if (mKey.mValue == value) {
+ return mKey.mWeight;
+ }
+ }
+ // no value at key found, try to interpolate if present at other keys. if not, return zero
+ // TODO: interpolation
+ return 0.0f;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs the animation for the given source anim
+void ColladaLoader::CreateAnimation(aiScene *pScene, const ColladaParser &pParser, const Animation *pSrcAnim, const std::string &pName) {
+ // collect a list of animatable nodes
+ std::vector<const aiNode *> nodes;
+ CollectNodes(pScene->mRootNode, nodes);
+
+ std::vector<aiNodeAnim *> anims;
+ std::vector<aiMeshMorphAnim *> morphAnims;
+
+ for (auto node : nodes) {
+ // find all the collada anim channels which refer to the current node
+ std::vector<ChannelEntry> entries;
+ std::string nodeName = node->mName.data;
+
+ // find the collada node corresponding to the aiNode
+ const Node *srcNode = FindNode(pParser.mRootNode, nodeName);
+ if (!srcNode) {
+ continue;
+ }
+
+ // now check all channels if they affect the current node
+ std::string targetID, subElement;
+ for (std::vector<AnimationChannel>::const_iterator cit = pSrcAnim->mChannels.begin();
+ cit != pSrcAnim->mChannels.end(); ++cit) {
+ const AnimationChannel &srcChannel = *cit;
+ ChannelEntry entry;
+
+ // we expect the animation target to be of type "nodeName/transformID.subElement". Ignore all others
+ // find the slash that separates the node name - there should be only one
+ std::string::size_type slashPos = srcChannel.mTarget.find('/');
+ if (slashPos == std::string::npos) {
+ std::string::size_type targetPos = srcChannel.mTarget.find(srcNode->mID);
+ if (targetPos == std::string::npos) {
+ continue;
+ }
+
+ // not node transform, but something else. store as unknown animation channel for now
+ entry.mChannel = &(*cit);
+ entry.mTargetId = srcChannel.mTarget.substr(targetPos + pSrcAnim->mName.length(),
+ srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length());
+ if (entry.mTargetId.front() == '-') {
+ entry.mTargetId = entry.mTargetId.substr(1);
+ }
+ entries.push_back(entry);
+ continue;
+ }
+ if (srcChannel.mTarget.find('/', slashPos + 1) != std::string::npos) {
+ continue;
+ }
+
+ targetID.clear();
+ targetID = srcChannel.mTarget.substr(0, slashPos);
+ if (targetID != srcNode->mID) {
+ continue;
+ }
+
+ // find the dot that separates the transformID - there should be only one or zero
+ std::string::size_type dotPos = srcChannel.mTarget.find('.');
+ if (dotPos != std::string::npos) {
+ if (srcChannel.mTarget.find('.', dotPos + 1) != std::string::npos) {
+ continue;
+ }
+
+ entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, dotPos - slashPos - 1);
+
+ subElement.clear();
+ subElement = srcChannel.mTarget.substr(dotPos + 1);
+ if (subElement == "ANGLE")
+ entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle
+ else if (subElement == "X")
+ entry.mSubElement = 0;
+ else if (subElement == "Y")
+ entry.mSubElement = 1;
+ else if (subElement == "Z")
+ entry.mSubElement = 2;
+ else
+ ASSIMP_LOG_WARN("Unknown anim subelement <", subElement, ">. Ignoring");
+ } else {
+ // no sub-element following, transformId is remaining string
+ entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1);
+ }
+
+ std::string::size_type bracketPos = srcChannel.mTarget.find('(');
+ if (bracketPos != std::string::npos) {
+ entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, bracketPos - slashPos - 1);
+ subElement.clear();
+ subElement = srcChannel.mTarget.substr(bracketPos);
+
+ if (subElement == "(0)(0)")
+ entry.mSubElement = 0;
+ else if (subElement == "(1)(0)")
+ entry.mSubElement = 1;
+ else if (subElement == "(2)(0)")
+ entry.mSubElement = 2;
+ else if (subElement == "(3)(0)")
+ entry.mSubElement = 3;
+ else if (subElement == "(0)(1)")
+ entry.mSubElement = 4;
+ else if (subElement == "(1)(1)")
+ entry.mSubElement = 5;
+ else if (subElement == "(2)(1)")
+ entry.mSubElement = 6;
+ else if (subElement == "(3)(1)")
+ entry.mSubElement = 7;
+ else if (subElement == "(0)(2)")
+ entry.mSubElement = 8;
+ else if (subElement == "(1)(2)")
+ entry.mSubElement = 9;
+ else if (subElement == "(2)(2)")
+ entry.mSubElement = 10;
+ else if (subElement == "(3)(2)")
+ entry.mSubElement = 11;
+ else if (subElement == "(0)(3)")
+ entry.mSubElement = 12;
+ else if (subElement == "(1)(3)")
+ entry.mSubElement = 13;
+ else if (subElement == "(2)(3)")
+ entry.mSubElement = 14;
+ else if (subElement == "(3)(3)")
+ entry.mSubElement = 15;
+ }
+
+ // determine which transform step is affected by this channel
+ entry.mTransformIndex = SIZE_MAX;
+ for (size_t a = 0; a < srcNode->mTransforms.size(); ++a)
+ if (srcNode->mTransforms[a].mID == entry.mTransformId)
+ entry.mTransformIndex = a;
+
+ if (entry.mTransformIndex == SIZE_MAX) {
+ if (entry.mTransformId.find("morph-weights") == std::string::npos) {
+ continue;
+ }
+ entry.mTargetId = entry.mTransformId;
+ entry.mTransformId = std::string();
+ }
+
+ entry.mChannel = &(*cit);
+ entries.push_back(entry);
+ }
+
+ // if there's no channel affecting the current node, we skip it
+ if (entries.empty()) {
+ continue;
+ }
+
+ // resolve the data pointers for all anim channels. Find the minimum time while we're at it
+ ai_real startTime = ai_real(1e20), endTime = ai_real(-1e20);
+ for (ChannelEntry & e : entries) {
+ e.mTimeAccessor = &pParser.ResolveLibraryReference(pParser.mAccessorLibrary, e.mChannel->mSourceTimes);
+ e.mTimeData = &pParser.ResolveLibraryReference(pParser.mDataLibrary, e.mTimeAccessor->mSource);
+ e.mValueAccessor = &pParser.ResolveLibraryReference(pParser.mAccessorLibrary, e.mChannel->mSourceValues);
+ e.mValueData = &pParser.ResolveLibraryReference(pParser.mDataLibrary, e.mValueAccessor->mSource);
+
+ // time count and value count must match
+ if (e.mTimeAccessor->mCount != e.mValueAccessor->mCount) {
+ throw DeadlyImportError("Time count / value count mismatch in animation channel \"", e.mChannel->mTarget, "\".");
+ }
+
+ if (e.mTimeAccessor->mCount > 0) {
+ // find bounding times
+ startTime = std::min(startTime, ReadFloat(*e.mTimeAccessor, *e.mTimeData, 0, 0));
+ endTime = std::max(endTime, ReadFloat(*e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount - 1, 0));
+ }
+ }
+
+ std::vector<aiMatrix4x4> resultTrafos;
+ if (!entries.empty() && entries.front().mTimeAccessor->mCount > 0) {
+ // create a local transformation chain of the node's transforms
+ std::vector<Collada::Transform> transforms = srcNode->mTransforms;
+
+ // now for every unique point in time, find or interpolate the key values for that time
+ // and apply them to the transform chain. Then the node's present transformation can be calculated.
+ ai_real time = startTime;
+ while (1) {
+ for (ChannelEntry & e : entries) {
+ // find the keyframe behind the current point in time
+ size_t pos = 0;
+ ai_real postTime = 0.0;
+ while (1) {
+ if (pos >= e.mTimeAccessor->mCount) {
+ break;
+ }
+ postTime = ReadFloat(*e.mTimeAccessor, *e.mTimeData, pos, 0);
+ if (postTime >= time) {
+ break;
+ }
+ ++pos;
+ }
+
+ pos = std::min(pos, e.mTimeAccessor->mCount - 1);
+
+ // read values from there
+ ai_real temp[16];
+ for (size_t c = 0; c < e.mValueAccessor->mSize; ++c) {
+ temp[c] = ReadFloat(*e.mValueAccessor, *e.mValueData, pos, c);
+ }
+
+ // if not exactly at the key time, interpolate with previous value set
+ if (postTime > time && pos > 0) {
+ ai_real preTime = ReadFloat(*e.mTimeAccessor, *e.mTimeData, pos - 1, 0);
+ ai_real factor = (time - postTime) / (preTime - postTime);
+
+ for (size_t c = 0; c < e.mValueAccessor->mSize; ++c) {
+ ai_real v = ReadFloat(*e.mValueAccessor, *e.mValueData, pos - 1, c);
+ temp[c] += (v - temp[c]) * factor;
+ }
+ }
+
+ // Apply values to current transformation
+ std::copy(temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement);
+ }
+
+ // Calculate resulting transformation
+ aiMatrix4x4 mat = pParser.CalculateResultTransform(transforms);
+
+ // out of laziness: we store the time in matrix.d4
+ mat.d4 = time;
+ resultTrafos.push_back(mat);
+
+ // find next point in time to evaluate. That's the closest frame larger than the current in any channel
+ ai_real nextTime = ai_real(1e20);
+ for (ChannelEntry & channelElement : entries) {
+ // find the next time value larger than the current
+ size_t pos = 0;
+ while (pos < channelElement.mTimeAccessor->mCount) {
+ const ai_real t = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0);
+ if (t > time) {
+ nextTime = std::min(nextTime, t);
+ break;
+ }
+ ++pos;
+ }
+
+ // https://github.com/assimp/assimp/issues/458
+ // Sub-sample axis-angle channels if the delta between two consecutive
+ // key-frame angles is >= 180 degrees.
+ if (transforms[channelElement.mTransformIndex].mType == TF_ROTATE && channelElement.mSubElement == 3 && pos > 0 && pos < channelElement.mTimeAccessor->mCount) {
+ const ai_real cur_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos, 0);
+ const ai_real last_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos - 1, 0);
+ const ai_real cur_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0);
+ const ai_real last_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos - 1, 0);
+ const ai_real last_eval_angle = last_key_angle + (cur_key_angle - last_key_angle) * (time - last_key_time) / (cur_key_time - last_key_time);
+ const ai_real delta = std::abs(cur_key_angle - last_eval_angle);
+ if (delta >= 180.0) {
+ const int subSampleCount = static_cast<int>(std::floor(delta / 90.0));
+ if (cur_key_time != time) {
+ const ai_real nextSampleTime = time + (cur_key_time - time) / subSampleCount;
+ nextTime = std::min(nextTime, nextSampleTime);
+ }
+ }
+ }
+ }
+
+ // no more keys on any channel after the current time -> we're done
+ if (nextTime > 1e19) {
+ break;
+ }
+
+ // else construct next key-frame at this following time point
+ time = nextTime;
+ }
+ }
+
+ // build an animation channel for the given node out of these trafo keys
+ if (!resultTrafos.empty()) {
+ aiNodeAnim *dstAnim = new aiNodeAnim;
+ dstAnim->mNodeName = nodeName;
+ dstAnim->mNumPositionKeys = static_cast<unsigned int>(resultTrafos.size());
+ dstAnim->mNumRotationKeys = static_cast<unsigned int>(resultTrafos.size());
+ dstAnim->mNumScalingKeys = static_cast<unsigned int>(resultTrafos.size());
+ dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()];
+ dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()];
+ dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()];
+
+ for (size_t a = 0; a < resultTrafos.size(); ++a) {
+ aiMatrix4x4 mat = resultTrafos[a];
+ double time = double(mat.d4); // remember? time is stored in mat.d4
+ mat.d4 = 1.0f;
+
+ dstAnim->mPositionKeys[a].mTime = time * kMillisecondsFromSeconds;
+ dstAnim->mRotationKeys[a].mTime = time * kMillisecondsFromSeconds;
+ dstAnim->mScalingKeys[a].mTime = time * kMillisecondsFromSeconds;
+ mat.Decompose(dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue);
+ }
+
+ anims.push_back(dstAnim);
+ } else {
+ ASSIMP_LOG_WARN("Collada loader: found empty animation channel, ignored. Please check your exporter.");
+ }
+
+ if (!entries.empty() && entries.front().mTimeAccessor->mCount > 0) {
+ std::vector<ChannelEntry> morphChannels;
+ for (ChannelEntry & e : entries) {
+ // skip non-transform types
+ if (e.mTargetId.empty()) {
+ continue;
+ }
+
+ if (e.mTargetId.find("morph-weights") != std::string::npos) {
+ morphChannels.push_back(e);
+ }
+ }
+ if (!morphChannels.empty()) {
+ // either 1) morph weight animation count should contain morph target count channels
+ // or 2) one channel with morph target count arrays
+ // assume first
+
+ aiMeshMorphAnim *morphAnim = new aiMeshMorphAnim;
+ morphAnim->mName.Set(nodeName);
+
+ std::vector<MorphTimeValues> morphTimeValues;
+ int morphAnimChannelIndex = 0;
+ for (ChannelEntry & e : morphChannels) {
+ std::string::size_type apos = e.mTargetId.find('(');
+ std::string::size_type bpos = e.mTargetId.find(')');
+
+ // If unknown way to specify weight -> ignore this animation
+ if (apos == std::string::npos || bpos == std::string::npos) {
+ continue;
+ }
+
+ // weight target can be in format Weight_M_N, Weight_N, WeightN, or some other way
+ // we ignore the name and just assume the channels are in the right order
+ for (unsigned int i = 0; i < e.mTimeData->mValues.size(); i++) {
+ insertMorphTimeValue(morphTimeValues, e.mTimeData->mValues[i], e.mValueData->mValues[i], morphAnimChannelIndex);
+ }
+
+ ++morphAnimChannelIndex;
+ }
+
+ morphAnim->mNumKeys = static_cast<unsigned int>(morphTimeValues.size());
+ morphAnim->mKeys = new aiMeshMorphKey[morphAnim->mNumKeys];
+ for (unsigned int key = 0; key < morphAnim->mNumKeys; key++) {
+ morphAnim->mKeys[key].mNumValuesAndWeights = static_cast<unsigned int>(morphChannels.size());
+ morphAnim->mKeys[key].mValues = new unsigned int[morphChannels.size()];
+ morphAnim->mKeys[key].mWeights = new double[morphChannels.size()];
+
+ morphAnim->mKeys[key].mTime = morphTimeValues[key].mTime * kMillisecondsFromSeconds;
+ for (unsigned int valueIndex = 0; valueIndex < morphChannels.size(); ++valueIndex) {
+ morphAnim->mKeys[key].mValues[valueIndex] = valueIndex;
+ morphAnim->mKeys[key].mWeights[valueIndex] = getWeightAtKey(morphTimeValues, key, valueIndex);
+ }
+ }
+
+ morphAnims.push_back(morphAnim);
+ }
+ }
+ }
+
+ if (!anims.empty() || !morphAnims.empty()) {
+ aiAnimation *anim = new aiAnimation;
+ anim->mName.Set(pName);
+ anim->mNumChannels = static_cast<unsigned int>(anims.size());
+ if (anim->mNumChannels > 0) {
+ anim->mChannels = new aiNodeAnim *[anims.size()];
+ std::copy(anims.begin(), anims.end(), anim->mChannels);
+ }
+ anim->mNumMorphMeshChannels = static_cast<unsigned int>(morphAnims.size());
+ if (anim->mNumMorphMeshChannels > 0) {
+ anim->mMorphMeshChannels = new aiMeshMorphAnim *[anim->mNumMorphMeshChannels];
+ std::copy(morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels);
+ }
+ anim->mDuration = 0.0f;
+ for (auto & a : anims) {
+ anim->mDuration = std::max(anim->mDuration, a->mPositionKeys[a->mNumPositionKeys - 1].mTime);
+ anim->mDuration = std::max(anim->mDuration, a->mRotationKeys[a->mNumRotationKeys - 1].mTime);
+ anim->mDuration = std::max(anim->mDuration, a->mScalingKeys[a->mNumScalingKeys - 1].mTime);
+ }
+ for (auto & morphAnim : morphAnims) {
+ anim->mDuration = std::max(anim->mDuration, morphAnim->mKeys[morphAnim->mNumKeys - 1].mTime);
+ }
+ anim->mTicksPerSecond = 1000.0;
+ mAnims.push_back(anim);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Add a texture to a material structure
+void ColladaLoader::AddTexture(aiMaterial &mat,
+ const ColladaParser &pParser,
+ const Effect &effect,
+ const Sampler &sampler,
+ aiTextureType type,
+ unsigned int idx) {
+ // first of all, basic file name
+ const aiString name = FindFilenameForEffectTexture(pParser, effect, sampler.mName);
+ mat.AddProperty(&name, _AI_MATKEY_TEXTURE_BASE, type, idx);
+
+ // mapping mode
+ int map = aiTextureMapMode_Clamp;
+ if (sampler.mWrapU) {
+ map = aiTextureMapMode_Wrap;
+ }
+ if (sampler.mWrapU && sampler.mMirrorU) {
+ map = aiTextureMapMode_Mirror;
+ }
+
+ mat.AddProperty(&map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx);
+
+ map = aiTextureMapMode_Clamp;
+ if (sampler.mWrapV) {
+ map = aiTextureMapMode_Wrap;
+ }
+ if (sampler.mWrapV && sampler.mMirrorV) {
+ map = aiTextureMapMode_Mirror;
+ }
+
+ mat.AddProperty(&map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx);
+
+ // UV transformation
+ mat.AddProperty(&sampler.mTransform, 1,
+ _AI_MATKEY_UVTRANSFORM_BASE, type, idx);
+
+ // Blend mode
+ mat.AddProperty((int *)&sampler.mOp, 1,
+ _AI_MATKEY_TEXBLEND_BASE, type, idx);
+
+ // Blend factor
+ mat.AddProperty((ai_real *)&sampler.mWeighting, 1,
+ _AI_MATKEY_TEXBLEND_BASE, type, idx);
+
+ // UV source index ... if we didn't resolve the mapping, it is actually just
+ // a guess but it works in most cases. We search for the frst occurrence of a
+ // number in the channel name. We assume it is the zero-based index into the
+ // UV channel array of all corresponding meshes. It could also be one-based
+ // for some exporters, but we won't care of it unless someone complains about.
+ if (sampler.mUVId != UINT_MAX) {
+ map = sampler.mUVId;
+ } else {
+ map = -1;
+ for (std::string::const_iterator it = sampler.mUVChannel.begin(); it != sampler.mUVChannel.end(); ++it) {
+ if (IsNumeric(*it)) {
+ map = strtoul10(&(*it));
+ break;
+ }
+ }
+ if (-1 == map) {
+ ASSIMP_LOG_WARN("Collada: unable to determine UV channel for texture");
+ map = 0;
+ }
+ }
+ mat.AddProperty(&map, 1, _AI_MATKEY_UVWSRC_BASE, type, idx);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Fills materials from the collada material definitions
+void ColladaLoader::FillMaterials(const ColladaParser &pParser, aiScene * /*pScene*/) {
+ for (auto &elem : newMats) {
+ aiMaterial &mat = (aiMaterial &)*elem.second;
+ Collada::Effect &effect = *elem.first;
+
+ // resolve shading mode
+ int shadeMode;
+ if (effect.mFaceted) {
+ shadeMode = aiShadingMode_Flat;
+ } else {
+ switch (effect.mShadeType) {
+ case Collada::Shade_Constant:
+ shadeMode = aiShadingMode_NoShading;
+ break;
+ case Collada::Shade_Lambert:
+ shadeMode = aiShadingMode_Gouraud;
+ break;
+ case Collada::Shade_Blinn:
+ shadeMode = aiShadingMode_Blinn;
+ break;
+ case Collada::Shade_Phong:
+ shadeMode = aiShadingMode_Phong;
+ break;
+
+ default:
+ ASSIMP_LOG_WARN("Collada: Unrecognized shading mode, using gouraud shading");
+ shadeMode = aiShadingMode_Gouraud;
+ break;
+ }
+ }
+ mat.AddProperty<int>(&shadeMode, 1, AI_MATKEY_SHADING_MODEL);
+
+ // double-sided?
+ shadeMode = effect.mDoubleSided;
+ mat.AddProperty<int>(&shadeMode, 1, AI_MATKEY_TWOSIDED);
+
+ // wire-frame?
+ shadeMode = effect.mWireframe;
+ mat.AddProperty<int>(&shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME);
+
+ // add material colors
+ mat.AddProperty(&effect.mAmbient, 1, AI_MATKEY_COLOR_AMBIENT);
+ mat.AddProperty(&effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+ mat.AddProperty(&effect.mSpecular, 1, AI_MATKEY_COLOR_SPECULAR);
+ mat.AddProperty(&effect.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+ mat.AddProperty(&effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE);
+
+ // scalar properties
+ mat.AddProperty(&effect.mShininess, 1, AI_MATKEY_SHININESS);
+ mat.AddProperty(&effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY);
+ mat.AddProperty(&effect.mRefractIndex, 1, AI_MATKEY_REFRACTI);
+
+ // transparency, a very hard one. seemingly not all files are following the
+ // specification here (1.0 transparency => completely opaque)...
+ // therefore, we let the opportunity for the user to manually invert
+ // the transparency if necessary and we add preliminary support for RGB_ZERO mode
+ if (effect.mTransparency >= 0.f && effect.mTransparency <= 1.f) {
+ // handle RGB transparency completely, cf Collada specs 1.5.0 pages 249 and 304
+ if (effect.mRGBTransparency) {
+ // use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4)
+ effect.mTransparency *= (0.212671f * effect.mTransparent.r +
+ 0.715160f * effect.mTransparent.g +
+ 0.072169f * effect.mTransparent.b);
+
+ effect.mTransparent.a = 1.f;
+
+ mat.AddProperty(&effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT);
+ } else {
+ effect.mTransparency *= effect.mTransparent.a;
+ }
+
+ if (effect.mInvertTransparency) {
+ effect.mTransparency = 1.f - effect.mTransparency;
+ }
+
+ // Is the material finally transparent ?
+ if (effect.mHasTransparency || effect.mTransparency < 1.f) {
+ mat.AddProperty(&effect.mTransparency, 1, AI_MATKEY_OPACITY);
+ }
+ }
+
+ // add textures, if given
+ if (!effect.mTexAmbient.mName.empty()) {
+ // It is merely a light-map
+ AddTexture(mat, pParser, effect, effect.mTexAmbient, aiTextureType_LIGHTMAP);
+ }
+
+ if (!effect.mTexEmissive.mName.empty())
+ AddTexture(mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE);
+
+ if (!effect.mTexSpecular.mName.empty())
+ AddTexture(mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR);
+
+ if (!effect.mTexDiffuse.mName.empty())
+ AddTexture(mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE);
+
+ if (!effect.mTexBump.mName.empty())
+ AddTexture(mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS);
+
+ if (!effect.mTexTransparent.mName.empty())
+ AddTexture(mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY);
+
+ if (!effect.mTexReflective.mName.empty())
+ AddTexture(mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructs materials from the collada material definitions
+void ColladaLoader::BuildMaterials(ColladaParser &pParser, aiScene * /*pScene*/) {
+ newMats.reserve(pParser.mMaterialLibrary.size());
+
+ for (ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin();
+ matIt != pParser.mMaterialLibrary.end(); ++matIt) {
+ const Material &material = matIt->second;
+ // a material is only a reference to an effect
+ ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find(material.mEffect);
+ if (effIt == pParser.mEffectLibrary.end())
+ continue;
+ Effect &effect = effIt->second;
+
+ // create material
+ aiMaterial *mat = new aiMaterial;
+ aiString name(material.mName.empty() ? matIt->first : material.mName);
+ mat->AddProperty(&name, AI_MATKEY_NAME);
+
+ // store the material
+ mMaterialIndexByName[matIt->first] = newMats.size();
+ newMats.emplace_back(&effect, mat);
+ }
+ // ScenePreprocessor generates a default material automatically if none is there.
+ // All further code here in this loader works well without a valid material so
+ // we can safely let it to ScenePreprocessor.
+}
+
+// ------------------------------------------------------------------------------------------------
+// Resolves the texture name for the given effect texture entry and loads the texture data
+aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser &pParser,
+ const Effect &pEffect, const std::string &pName) {
+ aiString result;
+
+ // recurse through the param references until we end up at an image
+ std::string name = pName;
+ while (1) {
+ // the given string is a param entry. Find it
+ Effect::ParamLibrary::const_iterator it = pEffect.mParams.find(name);
+ // if not found, we're at the end of the recursion. The resulting string should be the image ID
+ if (it == pEffect.mParams.end())
+ break;
+
+ // else recurse on
+ name = it->second.mReference;
+ }
+
+ // find the image referred by this name in the image library of the scene
+ ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find(name);
+ if (imIt == pParser.mImageLibrary.end()) {
+ ASSIMP_LOG_WARN("Collada: Unable to resolve effect texture entry \"", pName, "\", ended up at ID \"", name, "\".");
+
+ //set default texture file name
+ result.Set(name + ".jpg");
+ ColladaParser::UriDecodePath(result);
+ return result;
+ }
+
+ // if this is an embedded texture image setup an aiTexture for it
+ if (!imIt->second.mImageData.empty()) {
+ aiTexture *tex = new aiTexture();
+
+ // Store embedded texture name reference
+ tex->mFilename.Set(imIt->second.mFileName.c_str());
+ result.Set(imIt->second.mFileName);
+
+ // setup format hint
+ if (imIt->second.mEmbeddedFormat.length() >= HINTMAXTEXTURELEN) {
+ ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters");
+ }
+ strncpy(tex->achFormatHint, imIt->second.mEmbeddedFormat.c_str(), 3);
+
+ // and copy texture data
+ tex->mHeight = 0;
+ tex->mWidth = static_cast<unsigned int>(imIt->second.mImageData.size());
+ tex->pcData = (aiTexel *)new char[tex->mWidth];
+ memcpy(tex->pcData, &imIt->second.mImageData[0], tex->mWidth);
+
+ // and add this texture to the list
+ mTextures.push_back(tex);
+ return result;
+ }
+
+ if (imIt->second.mFileName.empty()) {
+ throw DeadlyImportError("Collada: Invalid texture, no data or file reference given");
+ }
+
+ result.Set(imIt->second.mFileName);
+
+ return result;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a float value from an accessor and its data array.
+ai_real ColladaLoader::ReadFloat(const Accessor &pAccessor, const Data &pData, size_t pIndex, size_t pOffset) const {
+ size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset + pOffset;
+ ai_assert(pos < pData.mValues.size());
+ return pData.mValues[pos];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a string value from an accessor and its data array.
+const std::string &ColladaLoader::ReadString(const Accessor &pAccessor, const Data &pData, size_t pIndex) const {
+ size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset;
+ ai_assert(pos < pData.mStrings.size());
+ return pData.mStrings[pos];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Collects all nodes into the given array
+void ColladaLoader::CollectNodes(const aiNode *pNode, std::vector<const aiNode *> &poNodes) const {
+ poNodes.push_back(pNode);
+ for (size_t a = 0; a < pNode->mNumChildren; ++a) {
+ CollectNodes(pNode->mChildren[a], poNodes);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds a node in the collada scene by the given name
+const Node *ColladaLoader::FindNode(const Node *pNode, const std::string &pName) const {
+ if (pNode->mName == pName || pNode->mID == pName)
+ return pNode;
+
+ for (auto a : pNode->mChildren) {
+ const Collada::Node *node = FindNode(a, pName);
+ if (node) {
+ return node;
+ }
+ }
+
+ return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds a node in the collada scene by the given SID
+const Node *ColladaLoader::FindNodeBySID(const Node *pNode, const std::string &pSID) const {
+ if (nullptr == pNode) {
+ return nullptr;
+ }
+
+ if (pNode->mSID == pSID) {
+ return pNode;
+ }
+
+ for (auto a : pNode->mChildren) {
+ const Collada::Node *node = FindNodeBySID(a, pSID);
+ if (node) {
+ return node;
+ }
+ }
+
+ return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Finds a proper unique name for a node derived from the collada-node's properties.
+// The name must be unique for proper node-bone association.
+std::string ColladaLoader::FindNameForNode(const Node *pNode) {
+ // If explicitly requested, just use the collada name.
+ if (useColladaName) {
+ if (!pNode->mName.empty()) {
+ return pNode->mName;
+ } else {
+ return format() << "$ColladaAutoName$_" << mNodeNameCounter++;
+ }
+ } else {
+ // Now setup the name of the assimp node. The collada name might not be
+ // unique, so we use the collada ID.
+ if (!pNode->mID.empty())
+ return pNode->mID;
+ else if (!pNode->mSID.empty())
+ return pNode->mSID;
+ else {
+ // No need to worry. Unnamed nodes are no problem at all, except
+ // if cameras or lights need to be assigned to them.
+ return format() << "$ColladaAutoName$_" << mNodeNameCounter++;
+ }
+ }
+}
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaLoader.h b/libs/assimp/code/AssetLib/Collada/ColladaLoader.h
new file mode 100644
index 0000000..246abed
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaLoader.h
@@ -0,0 +1,249 @@
+/** Defines the collada loader class */
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 AI_COLLADALOADER_H_INC
+#define AI_COLLADALOADER_H_INC
+
+#include "ColladaParser.h"
+#include <assimp/BaseImporter.h>
+
+struct aiNode;
+struct aiCamera;
+struct aiLight;
+struct aiTexture;
+struct aiAnimation;
+
+namespace Assimp {
+
+struct ColladaMeshIndex {
+ std::string mMeshID;
+ size_t mSubMesh;
+ std::string mMaterial;
+ ColladaMeshIndex(const std::string &pMeshID, size_t pSubMesh, const std::string &pMaterial) :
+ mMeshID(pMeshID), mSubMesh(pSubMesh), mMaterial(pMaterial) {
+ ai_assert(!pMeshID.empty());
+ }
+
+ bool operator<(const ColladaMeshIndex &p) const {
+ if (mMeshID == p.mMeshID) {
+ if (mSubMesh == p.mSubMesh)
+ return mMaterial < p.mMaterial;
+ else
+ return mSubMesh < p.mSubMesh;
+ } else {
+ return mMeshID < p.mMeshID;
+ }
+ }
+};
+
+/** Loader class to read Collada scenes. Collada is over-engineered to death, with every new iteration bringing
+ * more useless stuff, so I limited the data to what I think is useful for games.
+*/
+class ColladaLoader : public BaseImporter {
+public:
+ /// The class constructor.
+ ColladaLoader();
+
+ /// The class destructor.
+ ~ColladaLoader() override;
+
+ /// Returns whether the class can handle the format of the given file.
+ /// @see BaseImporter::CanRead() for more details.
+ bool CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const override;
+
+protected:
+ /// See #BaseImporter::GetInfo for the details
+ const aiImporterDesc *GetInfo() const override;
+
+ /// See #BaseImporter::SetupProperties for the details
+ void SetupProperties(const Importer *pImp) override;
+
+ /// See #BaseImporter::InternReadFile for the details
+ void InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) override;
+
+ /** Recursively constructs a scene node for the given parser node and returns it. */
+ aiNode *BuildHierarchy(const ColladaParser &pParser, const Collada::Node *pNode);
+
+ /** Resolve node instances */
+ void ResolveNodeInstances(const ColladaParser &pParser, const Collada::Node *pNode,
+ std::vector<const Collada::Node *> &resolved);
+
+ /** Builds meshes for the given node and references them */
+ void BuildMeshesForNode(const ColladaParser &pParser, const Collada::Node *pNode,
+ aiNode *pTarget);
+
+ aiMesh *findMesh(const std::string &meshid);
+
+ /** Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh */
+ aiMesh *CreateMesh(const ColladaParser &pParser, const Collada::Mesh *pSrcMesh, const Collada::SubMesh &pSubMesh,
+ const Collada::Controller *pSrcController, size_t pStartVertex, size_t pStartFace);
+
+ /** Builds cameras for the given node and references them */
+ void BuildCamerasForNode(const ColladaParser &pParser, const Collada::Node *pNode,
+ aiNode *pTarget);
+
+ /** Builds lights for the given node and references them */
+ void BuildLightsForNode(const ColladaParser &pParser, const Collada::Node *pNode,
+ aiNode *pTarget);
+
+ /** Stores all meshes in the given scene */
+ void StoreSceneMeshes(aiScene *pScene);
+
+ /** Stores all materials in the given scene */
+ void StoreSceneMaterials(aiScene *pScene);
+
+ /** Stores all lights in the given scene */
+ void StoreSceneLights(aiScene *pScene);
+
+ /** Stores all cameras in the given scene */
+ void StoreSceneCameras(aiScene *pScene);
+
+ /** Stores all textures in the given scene */
+ void StoreSceneTextures(aiScene *pScene);
+
+ /** Stores all animations
+ * @param pScene target scene to store the anims
+ */
+ void StoreAnimations(aiScene *pScene, const ColladaParser &pParser);
+
+ /** Stores all animations for the given source anim and its nested child animations
+ * @param pScene target scene to store the anims
+ * @param pSrcAnim the source animation to process
+ * @param pPrefix Prefix to the name in case of nested animations
+ */
+ void StoreAnimations(aiScene *pScene, const ColladaParser &pParser, const Collada::Animation *pSrcAnim, const std::string &pPrefix);
+
+ /** Constructs the animation for the given source anim */
+ void CreateAnimation(aiScene *pScene, const ColladaParser &pParser, const Collada::Animation *pSrcAnim, const std::string &pName);
+
+ /** Constructs materials from the collada material definitions */
+ void BuildMaterials(ColladaParser &pParser, aiScene *pScene);
+
+ /** Fill materials from the collada material definitions */
+ void FillMaterials(const ColladaParser &pParser, aiScene *pScene);
+
+ /** Resolve UV channel mappings*/
+ void ApplyVertexToEffectSemanticMapping(Collada::Sampler &sampler,
+ const Collada::SemanticMappingTable &table);
+
+ /** Add a texture and all of its sampling properties to a material*/
+ void AddTexture(aiMaterial &mat, const ColladaParser &pParser,
+ const Collada::Effect &effect,
+ const Collada::Sampler &sampler,
+ aiTextureType type, unsigned int idx = 0);
+
+ /** Resolves the texture name for the given effect texture entry */
+ aiString FindFilenameForEffectTexture(const ColladaParser &pParser,
+ const Collada::Effect &pEffect, const std::string &pName);
+
+ /** Reads a float value from an accessor and its data array.
+ * @param pAccessor The accessor to use for reading
+ * @param pData The data array to read from
+ * @param pIndex The index of the element to retrieve
+ * @param pOffset Offset into the element, for multipart elements such as vectors or matrices
+ * @return the specified value
+ */
+ ai_real ReadFloat(const Collada::Accessor &pAccessor, const Collada::Data &pData, size_t pIndex, size_t pOffset) const;
+
+ /** Reads a string value from an accessor and its data array.
+ * @param pAccessor The accessor to use for reading
+ * @param pData The data array to read from
+ * @param pIndex The index of the element to retrieve
+ * @return the specified value
+ */
+ const std::string &ReadString(const Collada::Accessor &pAccessor, const Collada::Data &pData, size_t pIndex) const;
+
+ /** Recursively collects all nodes into the given array */
+ void CollectNodes(const aiNode *pNode, std::vector<const aiNode *> &poNodes) const;
+
+ /** Finds a node in the collada scene by the given name */
+ const Collada::Node *FindNode(const Collada::Node *pNode, const std::string &pName) const;
+ /** Finds a node in the collada scene by the given SID */
+ const Collada::Node *FindNodeBySID(const Collada::Node *pNode, const std::string &pSID) const;
+
+ /** Finds a proper name for a node derived from the collada-node's properties */
+ std::string FindNameForNode(const Collada::Node *pNode);
+
+protected:
+ /** Filename, for a verbose error message */
+ std::string mFileName;
+
+ /** Which mesh-material compound was stored under which mesh ID */
+ std::map<ColladaMeshIndex, size_t> mMeshIndexByID;
+
+ /** Which material was stored under which index in the scene */
+ std::map<std::string, size_t> mMaterialIndexByName;
+
+ /** Accumulated meshes for the target scene */
+ std::vector<aiMesh *> mMeshes;
+
+ /** Accumulated morph target meshes */
+ std::vector<aiMesh *> mTargetMeshes;
+
+ /** Temporary material list */
+ std::vector<std::pair<Collada::Effect *, aiMaterial *>> newMats;
+
+ /** Temporary camera list */
+ std::vector<aiCamera *> mCameras;
+
+ /** Temporary light list */
+ std::vector<aiLight *> mLights;
+
+ /** Temporary texture list */
+ std::vector<aiTexture *> mTextures;
+
+ /** Accumulated animations for the target scene */
+ std::vector<aiAnimation *> mAnims;
+
+ bool noSkeletonMesh;
+ bool ignoreUpDirection;
+ bool useColladaName;
+
+ /** Used by FindNameForNode() to generate unique node names */
+ unsigned int mNodeNameCounter;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_COLLADALOADER_H_INC
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaParser.cpp b/libs/assimp/code/AssetLib/Collada/ColladaParser.cpp
new file mode 100644
index 0000000..922d1f6
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaParser.cpp
@@ -0,0 +1,2402 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2022, 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 ColladaParser.cpp
+ * @brief Implementation of the Collada parser helper
+ */
+
+#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
+
+#include "ColladaParser.h"
+#include <assimp/ParsingUtils.h>
+#include <assimp/StringUtils.h>
+#include <assimp/ZipArchiveIOSystem.h>
+#include <assimp/commonMetaData.h>
+#include <assimp/fast_atof.h>
+#include <assimp/light.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/IOSystem.hpp>
+#include <memory>
+
+using namespace Assimp;
+using namespace Assimp::Collada;
+using namespace Assimp::Formatter;
+
+static void ReportWarning(const char *msg, ...) {
+ ai_assert(nullptr != msg);
+
+ va_list args;
+ va_start(args, msg);
+
+ char szBuffer[3000];
+ const int iLen = vsprintf(szBuffer, msg, args);
+ ai_assert(iLen > 0);
+
+ va_end(args);
+ ASSIMP_LOG_WARN("Validation warning: ", std::string(szBuffer, iLen));
+}
+
+static bool FindCommonKey(const std::string &collada_key, const MetaKeyPairVector &key_renaming, size_t &found_index) {
+ for (size_t i = 0; i < key_renaming.size(); ++i) {
+ if (key_renaming[i].first == collada_key) {
+ found_index = i;
+ return true;
+ }
+ }
+ found_index = std::numeric_limits<size_t>::max();
+
+ return false;
+}
+
+static void readUrlAttribute(XmlNode &node, std::string &url) {
+ url.clear();
+ if (!XmlParser::getStdStrAttribute(node, "url", url)) {
+ return;
+ }
+ if (url[0] != '#') {
+ throw DeadlyImportError("Unknown reference format");
+ }
+ url = url.c_str() + 1;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ColladaParser::ColladaParser(IOSystem *pIOHandler, const std::string &pFile) :
+ mFileName(pFile),
+ mXmlParser(),
+ mDataLibrary(),
+ mAccessorLibrary(),
+ mMeshLibrary(),
+ mNodeLibrary(),
+ mImageLibrary(),
+ mEffectLibrary(),
+ mMaterialLibrary(),
+ mLightLibrary(),
+ mCameraLibrary(),
+ mControllerLibrary(),
+ mRootNode(nullptr),
+ mAnims(),
+ mUnitSize(1.0f),
+ mUpDirection(UP_Y),
+ mFormat(FV_1_5_n) {
+ if (nullptr == pIOHandler) {
+ throw DeadlyImportError("IOSystem is nullptr.");
+ }
+
+ std::unique_ptr<IOStream> daefile;
+ std::unique_ptr<ZipArchiveIOSystem> zip_archive;
+
+ // Determine type
+ std::string extension = BaseImporter::GetExtension(pFile);
+ if (extension != "dae") {
+ zip_archive.reset(new ZipArchiveIOSystem(pIOHandler, pFile));
+ }
+
+ if (zip_archive && zip_archive->isOpen()) {
+ std::string dae_filename = ReadZaeManifest(*zip_archive);
+
+ if (dae_filename.empty()) {
+ throw DeadlyImportError("Invalid ZAE");
+ }
+
+ daefile.reset(zip_archive->Open(dae_filename.c_str()));
+ if (daefile == nullptr) {
+ throw DeadlyImportError("Invalid ZAE manifest: '", dae_filename, "' is missing");
+ }
+ } else {
+ // attempt to open the file directly
+ daefile.reset(pIOHandler->Open(pFile));
+ if (daefile.get() == nullptr) {
+ throw DeadlyImportError("Failed to open file '", pFile, "'.");
+ }
+ }
+
+ // generate a XML reader for it
+ if (!mXmlParser.parse(daefile.get())) {
+ throw DeadlyImportError("Unable to read file, malformed XML");
+ }
+ // start reading
+ XmlNode node = mXmlParser.getRootNode();
+ XmlNode colladaNode = node.child("COLLADA");
+ if (colladaNode.empty()) {
+ return;
+ }
+
+ // Read content and embedded textures
+ ReadContents(colladaNode);
+ if (zip_archive && zip_archive->isOpen()) {
+ ReadEmbeddedTextures(*zip_archive);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ColladaParser::~ColladaParser() {
+ for (auto &it : mNodeLibrary) {
+ delete it.second;
+ }
+ for (auto &it : mMeshLibrary) {
+ delete it.second;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read a ZAE manifest and return the filename to attempt to open
+std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) {
+ // Open the manifest
+ std::unique_ptr<IOStream> manifestfile(zip_archive.Open("manifest.xml"));
+ if (manifestfile == nullptr) {
+ // No manifest, hope there is only one .DAE inside
+ std::vector<std::string> file_list;
+ zip_archive.getFileListExtension(file_list, "dae");
+
+ if (file_list.empty()) {
+ return std::string();
+ }
+
+ return file_list.front();
+ }
+ XmlParser manifestParser;
+ if (!manifestParser.parse(manifestfile.get())) {
+ return std::string();
+ }
+
+ XmlNode root = manifestParser.getRootNode();
+ const std::string &name = root.name();
+ if (name != "dae_root") {
+ root = *manifestParser.findNode("dae_root");
+ if (nullptr == root) {
+ return std::string();
+ }
+ std::string v;
+ XmlParser::getValueAsString(root, v);
+ aiString ai_str(v);
+ UriDecodePath(ai_str);
+ return std::string(ai_str.C_Str());
+ }
+
+ return std::string();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a path read from a collada file to the usual representation
+void ColladaParser::UriDecodePath(aiString &ss) {
+ // TODO: collada spec, p 22. Handle URI correctly.
+ // For the moment we're just stripping the file:// away to make it work.
+ // Windows doesn't seem to be able to find stuff like
+ // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg'
+ if (0 == strncmp(ss.data, "file://", 7)) {
+ ss.length -= 7;
+ memmove(ss.data, ss.data + 7, ss.length);
+ ss.data[ss.length] = '\0';
+ }
+
+ // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes...
+ // I need to filter it without destroying linux paths starting with "/somewhere"
+ if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') {
+ --ss.length;
+ ::memmove(ss.data, ss.data + 1, ss.length);
+ ss.data[ss.length] = 0;
+ }
+
+ // find and convert all %xy special chars
+ char *out = ss.data;
+ for (const char *it = ss.data; it != ss.data + ss.length; /**/) {
+ if (*it == '%' && (it + 3) < ss.data + ss.length) {
+ // separate the number to avoid dragging in chars from behind into the parsing
+ char mychar[3] = { it[1], it[2], 0 };
+ size_t nbr = strtoul16(mychar);
+ it += 3;
+ *out++ = (char)(nbr & 0xFF);
+ } else {
+ *out++ = *it++;
+ }
+ }
+
+ // adjust length and terminator of the shortened string
+ *out = 0;
+ ai_assert(out > ss.data);
+ ss.length = static_cast<ai_uint32>(out - ss.data);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the contents of the file
+void ColladaParser::ReadContents(XmlNode &node) {
+ const std::string name = node.name();
+ if (name == "COLLADA") {
+ std::string version;
+ if (XmlParser::getStdStrAttribute(node, "version", version)) {
+ aiString v;
+ v.Set(version.c_str());
+ mAssetMetaData.emplace(AI_METADATA_SOURCE_FORMAT_VERSION, v);
+ if (!::strncmp(version.c_str(), "1.5", 3)) {
+ mFormat = FV_1_5_n;
+ ASSIMP_LOG_DEBUG("Collada schema version is 1.5.n");
+ } else if (!::strncmp(version.c_str(), "1.4", 3)) {
+ mFormat = FV_1_4_n;
+ ASSIMP_LOG_DEBUG("Collada schema version is 1.4.n");
+ } else if (!::strncmp(version.c_str(), "1.3", 3)) {
+ mFormat = FV_1_3_n;
+ ASSIMP_LOG_DEBUG("Collada schema version is 1.3.n");
+ }
+ }
+ ReadStructure(node);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the structure of the file
+void ColladaParser::ReadStructure(XmlNode &node) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "asset") {
+ ReadAssetInfo(currentNode);
+ } else if (currentName == "library_animations") {
+ ReadAnimationLibrary(currentNode);
+ } else if (currentName == "library_animation_clips") {
+ ReadAnimationClipLibrary(currentNode);
+ } else if (currentName == "library_controllers") {
+ ReadControllerLibrary(currentNode);
+ } else if (currentName == "library_images") {
+ ReadImageLibrary(currentNode);
+ } else if (currentName == "library_materials") {
+ ReadMaterialLibrary(currentNode);
+ } else if (currentName == "library_effects") {
+ ReadEffectLibrary(currentNode);
+ } else if (currentName == "library_geometries") {
+ ReadGeometryLibrary(currentNode);
+ } else if (currentName == "library_visual_scenes") {
+ ReadSceneLibrary(currentNode);
+ } else if (currentName == "library_lights") {
+ ReadLightLibrary(currentNode);
+ } else if (currentName == "library_cameras") {
+ ReadCameraLibrary(currentNode);
+ } else if (currentName == "library_nodes") {
+ ReadSceneNode(currentNode, nullptr); /* some hacking to reuse this piece of code */
+ } else if (currentName == "scene") {
+ ReadScene(currentNode);
+ }
+ }
+
+ PostProcessRootAnimations();
+ PostProcessControllers();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads asset information such as coordinate system information and legal blah
+void ColladaParser::ReadAssetInfo(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "unit") {
+ mUnitSize = 1.f;
+ std::string tUnitSizeString;
+ if (XmlParser::getStdStrAttribute(currentNode, "meter", tUnitSizeString)) {
+ try {
+ fast_atoreal_move<ai_real>(tUnitSizeString.data(), mUnitSize);
+ } catch (const DeadlyImportError& die) {
+ std::string warning("Collada: Failed to parse meter parameter to real number. Exception:\n");
+ warning.append(die.what());
+ ASSIMP_LOG_WARN(warning.data());
+ }
+ }
+ } else if (currentName == "up_axis") {
+ std::string v;
+ if (!XmlParser::getValueAsString(currentNode, v)) {
+ continue;
+ }
+ if (v == "X_UP") {
+ mUpDirection = UP_X;
+ } else if (v == "Z_UP") {
+ mUpDirection = UP_Z;
+ } else {
+ mUpDirection = UP_Y;
+ }
+ } else if (currentName == "contributor") {
+ for (XmlNode currentChildNode : currentNode.children()) {
+ ReadMetaDataItem(currentChildNode, mAssetMetaData);
+ }
+ } else {
+ ReadMetaDataItem(currentNode, mAssetMetaData);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a single string metadata item
+void ColladaParser::ReadMetaDataItem(XmlNode &node, StringMetaData &metadata) {
+ const Collada::MetaKeyPairVector &key_renaming = GetColladaAssimpMetaKeysCamelCase();
+ const std::string name = node.name();
+ if (name.empty()) {
+ return;
+ }
+
+ std::string v;
+ if (!XmlParser::getValueAsString(node, v)) {
+ return;
+ }
+
+ v = ai_trim(v);
+ aiString aistr;
+ aistr.Set(v);
+
+ std::string camel_key_str(name);
+ ToCamelCase(camel_key_str);
+
+ size_t found_index;
+ if (FindCommonKey(camel_key_str, key_renaming, found_index)) {
+ metadata.emplace(key_renaming[found_index].second, aistr);
+ } else {
+ metadata.emplace(camel_key_str, aistr);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the animation clips
+void ColladaParser::ReadAnimationClipLibrary(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ std::string animName;
+ if (!XmlParser::getStdStrAttribute(node, "name", animName)) {
+ if (!XmlParser::getStdStrAttribute(node, "id", animName)) {
+ animName = std::string("animation_") + ai_to_string(mAnimationClipLibrary.size());
+ }
+ }
+
+ std::pair<std::string, std::vector<std::string>> clip;
+ clip.first = animName;
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "instance_animation") {
+ std::string url;
+ readUrlAttribute(currentNode, url);
+ clip.second.push_back(url);
+ }
+
+ if (clip.second.size() > 0) {
+ mAnimationClipLibrary.push_back(clip);
+ }
+ }
+}
+
+void ColladaParser::PostProcessControllers() {
+ std::string meshId;
+ for (auto &it : mControllerLibrary) {
+ meshId = it.second.mMeshId;
+ if (meshId.empty()) {
+ continue;
+ }
+
+ ControllerLibrary::iterator findItr = mControllerLibrary.find(meshId);
+ while (findItr != mControllerLibrary.end()) {
+ meshId = findItr->second.mMeshId;
+ findItr = mControllerLibrary.find(meshId);
+ }
+
+ it.second.mMeshId = meshId;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Re-build animations from animation clip library, if present, otherwise combine single-channel animations
+void ColladaParser::PostProcessRootAnimations() {
+ if (mAnimationClipLibrary.empty()) {
+ mAnims.CombineSingleChannelAnimations();
+ return;
+ }
+
+ Animation temp;
+ for (auto &it : mAnimationClipLibrary) {
+ std::string clipName = it.first;
+
+ Animation *clip = new Animation();
+ clip->mName = clipName;
+
+ temp.mSubAnims.push_back(clip);
+
+ for (const std::string &animationID : it.second) {
+ AnimationLibrary::iterator animation = mAnimationLibrary.find(animationID);
+
+ if (animation != mAnimationLibrary.end()) {
+ Animation *pSourceAnimation = animation->second;
+ pSourceAnimation->CollectChannelsRecursively(clip->mChannels);
+ }
+ }
+ }
+
+ mAnims = temp;
+
+ // Ensure no double deletes.
+ temp.mSubAnims.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the animation library
+void ColladaParser::ReadAnimationLibrary(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "animation") {
+ ReadAnimation(currentNode, &mAnims);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an animation into the given parent structure
+void ColladaParser::ReadAnimation(XmlNode &node, Collada::Animation *pParent) {
+ if (node.empty()) {
+ return;
+ }
+
+ // an <animation> element may be a container for grouping sub-elements or an animation channel
+ // this is the channel collection by ID, in case it has channels
+ using ChannelMap = std::map<std::string, AnimationChannel>;
+ ChannelMap channels;
+ // this is the anim container in case we're a container
+ Animation *anim = nullptr;
+
+ // optional name given as an attribute
+ std::string animName;
+ if (!XmlParser::getStdStrAttribute(node, "name", animName)) {
+ animName = "animation";
+ }
+
+ std::string animID;
+ pugi::xml_attribute idAttr = node.attribute("id");
+ if (idAttr) {
+ animID = idAttr.as_string();
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "animation") {
+ if (!anim) {
+ anim = new Animation;
+ anim->mName = animName;
+ pParent->mSubAnims.push_back(anim);
+ }
+
+ // recurse into the sub-element
+ ReadAnimation(currentNode, anim);
+ } else if (currentName == "source") {
+ ReadSource(currentNode);
+ } else if (currentName == "sampler") {
+ std::string id;
+ if (XmlParser::getStdStrAttribute(currentNode, "id", id)) {
+ // have it read into a channel
+ ChannelMap::iterator newChannel = channels.insert(std::make_pair(id, AnimationChannel())).first;
+ ReadAnimationSampler(currentNode, newChannel->second);
+ }
+ } else if (currentName == "channel") {
+ std::string source_name, target;
+ XmlParser::getStdStrAttribute(currentNode, "source", source_name);
+ XmlParser::getStdStrAttribute(currentNode, "target", target);
+ if (source_name[0] == '#') {
+ source_name = source_name.substr(1, source_name.size() - 1);
+ }
+ ChannelMap::iterator cit = channels.find(source_name);
+ if (cit != channels.end()) {
+ cit->second.mTarget = target;
+ }
+ }
+ }
+
+ // it turned out to have channels - add them
+ if (!channels.empty()) {
+ if (nullptr == anim) {
+ anim = new Animation;
+ anim->mName = animName;
+ pParent->mSubAnims.push_back(anim);
+ }
+
+ for (const auto &channel : channels) {
+ anim->mChannels.push_back(channel.second);
+ }
+
+ if (idAttr) {
+ mAnimationLibrary[animID] = anim;
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an animation sampler into the given anim channel
+void ColladaParser::ReadAnimationSampler(XmlNode &node, Collada::AnimationChannel &pChannel) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "input") {
+ if (XmlParser::hasAttribute(currentNode, "semantic")) {
+ std::string semantic, sourceAttr;
+ XmlParser::getStdStrAttribute(currentNode, "semantic", semantic);
+ if (XmlParser::hasAttribute(currentNode, "source")) {
+ XmlParser::getStdStrAttribute(currentNode, "source", sourceAttr);
+ const char *source = sourceAttr.c_str();
+ if (source[0] != '#') {
+ throw DeadlyImportError("Unsupported URL format");
+ }
+ source++;
+
+ if (semantic == "INPUT") {
+ pChannel.mSourceTimes = source;
+ } else if (semantic == "OUTPUT") {
+ pChannel.mSourceValues = source;
+ } else if (semantic == "IN_TANGENT") {
+ pChannel.mInTanValues = source;
+ } else if (semantic == "OUT_TANGENT") {
+ pChannel.mOutTanValues = source;
+ } else if (semantic == "INTERPOLATION") {
+ pChannel.mInterpolationValues = source;
+ }
+ }
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the skeleton controller library
+void ColladaParser::ReadControllerLibrary(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName != "controller") {
+ continue;
+ }
+ std::string id;
+ if (XmlParser::getStdStrAttribute(currentNode, "id", id)) {
+ mControllerLibrary[id] = Controller();
+ ReadController(currentNode, mControllerLibrary[id]);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a controller into the given mesh structure
+void ColladaParser::ReadController(XmlNode &node, Collada::Controller &controller) {
+ // initial values
+ controller.mType = Skin;
+ controller.mMethod = Normalized;
+
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "morph") {
+ controller.mType = Morph;
+ controller.mMeshId = currentNode.attribute("source").as_string();
+ int methodIndex = currentNode.attribute("method").as_int();
+ if (methodIndex > 0) {
+ std::string method;
+ XmlParser::getValueAsString(currentNode, method);
+
+ if (method == "RELATIVE") {
+ controller.mMethod = Relative;
+ }
+ }
+ } else if (currentName == "skin") {
+ std::string id;
+ if (XmlParser::getStdStrAttribute(currentNode, "source", id)) {
+ controller.mMeshId = id.substr(1, id.size() - 1);
+ }
+ } else if (currentName == "bind_shape_matrix") {
+ std::string v;
+ XmlParser::getValueAsString(currentNode, v);
+ const char *content = v.c_str();
+ for (unsigned int a = 0; a < 16; a++) {
+ SkipSpacesAndLineEnd(&content);
+ // read a number
+ content = fast_atoreal_move<ai_real>(content, controller.mBindShapeMatrix[a]);
+ // skip whitespace after it
+ SkipSpacesAndLineEnd(&content);
+ }
+ } else if (currentName == "source") {
+ ReadSource(currentNode);
+ } else if (currentName == "joints") {
+ ReadControllerJoints(currentNode, controller);
+ } else if (currentName == "vertex_weights") {
+ ReadControllerWeights(currentNode, controller);
+ } else if (currentName == "targets") {
+ for (XmlNode currentChildNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
+ const std::string &currentChildName = currentChildNode.name();
+ if (currentChildName == "input") {
+ const char *semantics = currentChildNode.attribute("semantic").as_string();
+ const char *source = currentChildNode.attribute("source").as_string();
+ if (strcmp(semantics, "MORPH_TARGET") == 0) {
+ controller.mMorphTarget = source + 1;
+ } else if (strcmp(semantics, "MORPH_WEIGHT") == 0) {
+ controller.mMorphWeight = source + 1;
+ }
+ }
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the joint definitions for the given controller
+void ColladaParser::ReadControllerJoints(XmlNode &node, Collada::Controller &pController) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "input") {
+ const char *attrSemantic = currentNode.attribute("semantic").as_string();
+ const char *attrSource = currentNode.attribute("source").as_string();
+ if (attrSource[0] != '#') {
+ throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of <joints> data <input> element");
+ }
+ ++attrSource;
+ // parse source URL to corresponding source
+ if (strcmp(attrSemantic, "JOINT") == 0) {
+ pController.mJointNameSource = attrSource;
+ } else if (strcmp(attrSemantic, "INV_BIND_MATRIX") == 0) {
+ pController.mJointOffsetMatrixSource = attrSource;
+ } else {
+ throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in <joints> data <input> element");
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the joint weights for the given controller
+void ColladaParser::ReadControllerWeights(XmlNode &node, Collada::Controller &pController) {
+ // Read vertex count from attributes and resize the array accordingly
+ int vertexCount = 0;
+ XmlParser::getIntAttribute(node, "count", vertexCount);
+ pController.mWeightCounts.resize(vertexCount);
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "input") {
+ InputChannel channel;
+
+ const char *attrSemantic = currentNode.attribute("semantic").as_string();
+ const char *attrSource = currentNode.attribute("source").as_string();
+ channel.mOffset = currentNode.attribute("offset").as_int();
+
+ // local URLS always start with a '#'. We don't support global URLs
+ if (attrSource[0] != '#') {
+ throw DeadlyImportError("Unsupported URL format in \"", attrSource, "\" in source attribute of <vertex_weights> data <input> element");
+ }
+ channel.mAccessor = attrSource + 1;
+
+ // parse source URL to corresponding source
+ if (strcmp(attrSemantic, "JOINT") == 0) {
+ pController.mWeightInputJoints = channel;
+ } else if (strcmp(attrSemantic, "WEIGHT") == 0) {
+ pController.mWeightInputWeights = channel;
+ } else {
+ throw DeadlyImportError("Unknown semantic \"", attrSemantic, "\" in <vertex_weights> data <input> element");
+ }
+ } else if (currentName == "vcount" && vertexCount > 0) {
+ const char *text = currentNode.text().as_string();
+ size_t numWeights = 0;
+ for (std::vector<size_t>::iterator it = pController.mWeightCounts.begin(); it != pController.mWeightCounts.end(); ++it) {
+ if (*text == 0) {
+ throw DeadlyImportError("Out of data while reading <vcount>");
+ }
+
+ *it = strtoul10(text, &text);
+ numWeights += *it;
+ SkipSpacesAndLineEnd(&text);
+ }
+ // reserve weight count
+ pController.mWeights.resize(numWeights);
+ } else if (currentName == "v" && vertexCount > 0) {
+ // read JointIndex - WeightIndex pairs
+ std::string stdText;
+ XmlParser::getValueAsString(currentNode, stdText);
+ const char *text = stdText.c_str();
+ for (std::vector<std::pair<size_t, size_t>>::iterator it = pController.mWeights.begin(); it != pController.mWeights.end(); ++it) {
+ if (text == 0) {
+ throw DeadlyImportError("Out of data while reading <vertex_weights>");
+ }
+ it->first = strtoul10(text, &text);
+ SkipSpacesAndLineEnd(&text);
+ if (*text == 0) {
+ throw DeadlyImportError("Out of data while reading <vertex_weights>");
+ }
+ it->second = strtoul10(text, &text);
+ SkipSpacesAndLineEnd(&text);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the image library contents
+void ColladaParser::ReadImageLibrary(XmlNode &node) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "image") {
+ std::string id;
+ if (XmlParser::getStdStrAttribute(currentNode, "id", id)) {
+ mImageLibrary[id] = Image();
+ // read on from there
+ ReadImage(currentNode, mImageLibrary[id]);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an image entry into the given image
+void ColladaParser::ReadImage(XmlNode &node, Collada::Image &pImage) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string currentName = currentNode.name();
+ if (currentName == "image") {
+ // Ignore
+ continue;
+ } else if (currentName == "init_from") {
+ if (mFormat == FV_1_4_n) {
+ // FIX: C4D exporter writes empty <init_from/> tags
+ if (!currentNode.empty()) {
+ // element content is filename - hopefully
+ const char *sz = currentNode.text().as_string();
+ if (nullptr != sz) {
+ aiString filepath(sz);
+ UriDecodePath(filepath);
+ pImage.mFileName = filepath.C_Str();
+ }
+ }
+ if (!pImage.mFileName.length()) {
+ pImage.mFileName = "unknown_texture";
+ }
+ }
+ } else if (mFormat == FV_1_5_n) {
+ std::string value;
+ XmlNode refChild = currentNode.child("ref");
+ XmlNode hexChild = currentNode.child("hex");
+ if (refChild) {
+ // element content is filename - hopefully
+ if (XmlParser::getValueAsString(refChild, value)) {
+ aiString filepath(value);
+ UriDecodePath(filepath);
+ pImage.mFileName = filepath.C_Str();
+ }
+ } else if (hexChild && !pImage.mFileName.length()) {
+ // embedded image. get format
+ pImage.mEmbeddedFormat = hexChild.attribute("format").as_string();
+ if (pImage.mEmbeddedFormat.empty()) {
+ ASSIMP_LOG_WARN("Collada: Unknown image file format");
+ }
+
+ XmlParser::getValueAsString(hexChild, value);
+ const char *data = value.c_str();
+ // hexadecimal-encoded binary octets. First of all, find the
+ // required buffer size to reserve enough storage.
+ const char *cur = data;
+ while (!IsSpaceOrNewLine(*cur)) {
+ ++cur;
+ }
+
+ const unsigned int size = (unsigned int)(cur - data) * 2;
+ pImage.mImageData.resize(size);
+ for (unsigned int i = 0; i < size; ++i) {
+ pImage.mImageData[i] = HexOctetToDecimal(data + (i << 1));
+ }
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the material library
+void ColladaParser::ReadMaterialLibrary(XmlNode &node) {
+ std::map<std::string, int> names;
+ for (XmlNode &currentNode : node.children()) {
+ std::string id = currentNode.attribute("id").as_string();
+ std::string name = currentNode.attribute("name").as_string();
+ mMaterialLibrary[id] = Material();
+
+ if (!name.empty()) {
+ std::map<std::string, int>::iterator it = names.find(name);
+ if (it != names.end()) {
+ std::ostringstream strStream;
+ strStream << ++it->second;
+ name.append(" " + strStream.str());
+ } else {
+ names[name] = 0;
+ }
+
+ mMaterialLibrary[id].mName = name;
+ }
+
+ ReadMaterial(currentNode, mMaterialLibrary[id]);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the light library
+void ColladaParser::ReadLightLibrary(XmlNode &node) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "light") {
+ std::string id;
+ if (XmlParser::getStdStrAttribute(currentNode, "id", id)) {
+ ReadLight(currentNode, mLightLibrary[id] = Light());
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the camera library
+void ColladaParser::ReadCameraLibrary(XmlNode &node) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "camera") {
+ std::string id;
+ if (!XmlParser::getStdStrAttribute(currentNode, "id", id)) {
+ continue;
+ }
+
+ // create an entry and store it in the library under its ID
+ Camera &cam = mCameraLibrary[id];
+ std::string name;
+ if (!XmlParser::getStdStrAttribute(currentNode, "name", name)) {
+ continue;
+ }
+ if (!name.empty()) {
+ cam.mName = name;
+ }
+ ReadCamera(currentNode, cam);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a material entry into the given material
+void ColladaParser::ReadMaterial(XmlNode &node, Collada::Material &pMaterial) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "instance_effect") {
+ std::string url;
+ readUrlAttribute(currentNode, url);
+ pMaterial.mEffect = url;
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a light entry into the given light
+void ColladaParser::ReadLight(XmlNode &node, Collada::Light &pLight) {
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ // TODO: Check the current technique and skip over unsupported extra techniques
+
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "spot") {
+ pLight.mType = aiLightSource_SPOT;
+ } else if (currentName == "ambient") {
+ pLight.mType = aiLightSource_AMBIENT;
+ } else if (currentName == "directional") {
+ pLight.mType = aiLightSource_DIRECTIONAL;
+ } else if (currentName == "point") {
+ pLight.mType = aiLightSource_POINT;
+ } else if (currentName == "color") {
+ // text content contains 3 floats
+ std::string v;
+ XmlParser::getValueAsString(currentNode, v);
+ const char *content = v.c_str();
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pLight.mColor.r);
+ SkipSpacesAndLineEnd(&content);
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pLight.mColor.g);
+ SkipSpacesAndLineEnd(&content);
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pLight.mColor.b);
+ SkipSpacesAndLineEnd(&content);
+ } else if (currentName == "constant_attenuation") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mAttConstant);
+ } else if (currentName == "linear_attenuation") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mAttLinear);
+ } else if (currentName == "quadratic_attenuation") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mAttQuadratic);
+ } else if (currentName == "falloff_angle") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle);
+ } else if (currentName == "falloff_exponent") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mFalloffExponent);
+ }
+ // FCOLLADA extensions
+ // -------------------------------------------------------
+ else if (currentName == "outer_cone") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle);
+ } else if (currentName == "penumbra_angle") { // this one is deprecated, now calculated using outer_cone
+ XmlParser::getValueAsFloat(currentNode, pLight.mPenumbraAngle);
+ } else if (currentName == "intensity") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mIntensity);
+ }
+ else if (currentName == "falloff") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle);
+ } else if (currentName == "hotspot_beam") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mFalloffAngle);
+ }
+ // OpenCOLLADA extensions
+ // -------------------------------------------------------
+ else if (currentName == "decay_falloff") {
+ XmlParser::getValueAsFloat(currentNode, pLight.mOuterAngle);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a camera entry into the given light
+void ColladaParser::ReadCamera(XmlNode &node, Collada::Camera &camera) {
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "orthographic") {
+ camera.mOrtho = true;
+ } else if (currentName == "xfov" || currentName == "xmag") {
+ XmlParser::getValueAsFloat(currentNode, camera.mHorFov);
+ } else if (currentName == "yfov" || currentName == "ymag") {
+ XmlParser::getValueAsFloat(currentNode, camera.mVerFov);
+ } else if (currentName == "aspect_ratio") {
+ XmlParser::getValueAsFloat(currentNode, camera.mAspect);
+ } else if (currentName == "znear") {
+ XmlParser::getValueAsFloat(currentNode, camera.mZNear);
+ } else if (currentName == "zfar") {
+ XmlParser::getValueAsFloat(currentNode, camera.mZFar);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the effect library
+void ColladaParser::ReadEffectLibrary(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "effect") {
+ // read ID. Do I have to repeat my ranting about "optional" attributes?
+ std::string id;
+ XmlParser::getStdStrAttribute(currentNode, "id", id);
+
+ // create an entry and store it in the library under its ID
+ mEffectLibrary[id] = Effect();
+
+ // read on from there
+ ReadEffect(currentNode, mEffectLibrary[id]);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect entry into the given effect
+void ColladaParser::ReadEffect(XmlNode &node, Collada::Effect &pEffect) {
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "profile_COMMON") {
+ ReadEffectProfileCommon(currentNode, pEffect);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an COMMON effect profile
+void ColladaParser::ReadEffectProfileCommon(XmlNode &node, Collada::Effect &pEffect) {
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string currentName = currentNode.name();
+ if (currentName == "newparam") {
+ // save ID
+ std::string sid = currentNode.attribute("sid").as_string();
+ pEffect.mParams[sid] = EffectParam();
+ ReadEffectParam(currentNode, pEffect.mParams[sid]);
+ } else if (currentName == "technique" || currentName == "extra") {
+ // just syntactic sugar
+ } else if (mFormat == FV_1_4_n && currentName == "image") {
+ // read ID. Another entry which is "optional" by design but obligatory in reality
+ std::string id = currentNode.attribute("id").as_string();
+
+ // create an entry and store it in the library under its ID
+ mImageLibrary[id] = Image();
+
+ // read on from there
+ ReadImage(currentNode, mImageLibrary[id]);
+ } else if (currentName == "phong")
+ pEffect.mShadeType = Shade_Phong;
+ else if (currentName == "constant")
+ pEffect.mShadeType = Shade_Constant;
+ else if (currentName == "lambert")
+ pEffect.mShadeType = Shade_Lambert;
+ else if (currentName == "blinn")
+ pEffect.mShadeType = Shade_Blinn;
+
+ /* Color + texture properties */
+ else if (currentName == "emission")
+ ReadEffectColor(currentNode, pEffect.mEmissive, pEffect.mTexEmissive);
+ else if (currentName == "ambient")
+ ReadEffectColor(currentNode, pEffect.mAmbient, pEffect.mTexAmbient);
+ else if (currentName == "diffuse")
+ ReadEffectColor(currentNode, pEffect.mDiffuse, pEffect.mTexDiffuse);
+ else if (currentName == "specular")
+ ReadEffectColor(currentNode, pEffect.mSpecular, pEffect.mTexSpecular);
+ else if (currentName == "reflective") {
+ ReadEffectColor(currentNode, pEffect.mReflective, pEffect.mTexReflective);
+ } else if (currentName == "transparent") {
+ pEffect.mHasTransparency = true;
+ const char *opaque = currentNode.attribute("opaque").as_string();
+ //const char *opaque = mReader->getAttributeValueSafe("opaque");
+
+ if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "RGB_ONE") == 0) {
+ pEffect.mRGBTransparency = true;
+ }
+
+ // In RGB_ZERO mode, the transparency is interpreted in reverse, go figure...
+ if (::strcmp(opaque, "RGB_ZERO") == 0 || ::strcmp(opaque, "A_ZERO") == 0) {
+ pEffect.mInvertTransparency = true;
+ }
+
+ ReadEffectColor(currentNode, pEffect.mTransparent, pEffect.mTexTransparent);
+ } else if (currentName == "shininess")
+ ReadEffectFloat(currentNode, pEffect.mShininess);
+ else if (currentName == "reflectivity")
+ ReadEffectFloat(currentNode, pEffect.mReflectivity);
+
+ /* Single scalar properties */
+ else if (currentName == "transparency")
+ ReadEffectFloat(currentNode, pEffect.mTransparency);
+ else if (currentName == "index_of_refraction")
+ ReadEffectFloat(currentNode, pEffect.mRefractIndex);
+
+ // GOOGLEEARTH/OKINO extensions
+ // -------------------------------------------------------
+ else if (currentName == "double_sided")
+ XmlParser::getValueAsBool(currentNode, pEffect.mDoubleSided);
+
+ // FCOLLADA extensions
+ // -------------------------------------------------------
+ else if (currentName == "bump") {
+ aiColor4D dummy;
+ ReadEffectColor(currentNode, dummy, pEffect.mTexBump);
+ }
+
+ // MAX3D extensions
+ // -------------------------------------------------------
+ else if (currentName == "wireframe") {
+ XmlParser::getValueAsBool(currentNode, pEffect.mWireframe);
+ } else if (currentName == "faceted") {
+ XmlParser::getValueAsBool(currentNode, pEffect.mFaceted);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Read texture wrapping + UV transform settings from a profile==Maya chunk
+void ColladaParser::ReadSamplerProperties(XmlNode &node, Sampler &out) {
+ if (node.empty()) {
+ return;
+ }
+
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ // MAYA extensions
+ // -------------------------------------------------------
+ if (currentName == "wrapU") {
+ XmlParser::getValueAsBool(currentNode, out.mWrapU);
+ } else if (currentName == "wrapV") {
+ XmlParser::getValueAsBool(currentNode, out.mWrapV);
+ } else if (currentName == "mirrorU") {
+ XmlParser::getValueAsBool(currentNode, out.mMirrorU);
+ } else if (currentName == "mirrorV") {
+ XmlParser::getValueAsBool(currentNode, out.mMirrorV);
+ } else if (currentName == "repeatU") {
+ XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.x);
+ } else if (currentName == "repeatV") {
+ XmlParser::getValueAsFloat(currentNode, out.mTransform.mScaling.y);
+ } else if (currentName == "offsetU") {
+ XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.x);
+ } else if (currentName == "offsetV") {
+ XmlParser::getValueAsFloat(currentNode, out.mTransform.mTranslation.y);
+ } else if (currentName == "rotateUV") {
+ XmlParser::getValueAsFloat(currentNode, out.mTransform.mRotation);
+ } else if (currentName == "blend_mode") {
+ std::string v;
+ XmlParser::getValueAsString(currentNode, v);
+ const char *sz = v.c_str();
+ // http://www.feelingsoftware.com/content/view/55/72/lang,en/
+ // NONE, OVER, IN, OUT, ADD, SUBTRACT, MULTIPLY, DIFFERENCE, LIGHTEN, DARKEN, SATURATE, DESATURATE and ILLUMINATE
+ if (0 == ASSIMP_strincmp(sz, "ADD", 3))
+ out.mOp = aiTextureOp_Add;
+ else if (0 == ASSIMP_strincmp(sz, "SUBTRACT", 8))
+ out.mOp = aiTextureOp_Subtract;
+ else if (0 == ASSIMP_strincmp(sz, "MULTIPLY", 8))
+ out.mOp = aiTextureOp_Multiply;
+ else {
+ ASSIMP_LOG_WARN("Collada: Unsupported MAYA texture blend mode");
+ }
+ }
+ // OKINO extensions
+ // -------------------------------------------------------
+ else if (currentName == "weighting") {
+ XmlParser::getValueAsFloat(currentNode, out.mWeighting);
+ } else if (currentName == "mix_with_previous_layer") {
+ XmlParser::getValueAsFloat(currentNode, out.mMixWithPrevious);
+ }
+ // MAX3D extensions
+ // -------------------------------------------------------
+ else if (currentName == "amount") {
+ XmlParser::getValueAsFloat(currentNode, out.mWeighting);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect entry containing a color or a texture defining that color
+void ColladaParser::ReadEffectColor(XmlNode &node, aiColor4D &pColor, Sampler &pSampler) {
+ if (node.empty()) {
+ return;
+ }
+
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "color") {
+ // text content contains 4 floats
+ std::string v;
+ XmlParser::getValueAsString(currentNode, v);
+ const char *content = v.c_str();
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.r);
+ SkipSpacesAndLineEnd(&content);
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.g);
+ SkipSpacesAndLineEnd(&content);
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.b);
+ SkipSpacesAndLineEnd(&content);
+
+ content = fast_atoreal_move<ai_real>(content, (ai_real &)pColor.a);
+ SkipSpacesAndLineEnd(&content);
+ } else if (currentName == "texture") {
+ // get name of source texture/sampler
+ XmlParser::getStdStrAttribute(currentNode, "texture", pSampler.mName);
+
+ // get name of UV source channel. Specification demands it to be there, but some exporters
+ // don't write it. It will be the default UV channel in case it's missing.
+ XmlParser::getStdStrAttribute(currentNode, "texcoord", pSampler.mUVChannel);
+
+ // as we've read texture, the color needs to be 1,1,1,1
+ pColor = aiColor4D(1.f, 1.f, 1.f, 1.f);
+ } else if (currentName == "technique") {
+ std::string profile;
+ XmlParser::getStdStrAttribute(currentNode, "profile", profile);
+
+ // Some extensions are quite useful ... ReadSamplerProperties processes
+ // several extensions in MAYA, OKINO and MAX3D profiles.
+ if (!::strcmp(profile.c_str(), "MAYA") || !::strcmp(profile.c_str(), "MAX3D") || !::strcmp(profile.c_str(), "OKINO")) {
+ // get more information on this sampler
+ ReadSamplerProperties(currentNode, pSampler);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect entry containing a float
+void ColladaParser::ReadEffectFloat(XmlNode &node, ai_real &pFloat) {
+ pFloat = 0.f;
+ XmlNode floatNode = node.child("float");
+ if (floatNode.empty()) {
+ return;
+ }
+ XmlParser::getValueAsFloat(floatNode, pFloat);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an effect parameter specification of any kind
+void ColladaParser::ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam) {
+ if (node.empty()) {
+ return;
+ }
+
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "surface") {
+ // image ID given inside <init_from> tags
+ XmlNode initNode = currentNode.child("init_from");
+ if (initNode) {
+ std::string v;
+ XmlParser::getValueAsString(initNode, v);
+ pParam.mType = Param_Surface;
+ pParam.mReference = v.c_str();
+ }
+ } else if (currentName == "sampler2D" && (FV_1_4_n == mFormat || FV_1_3_n == mFormat)) {
+ // surface ID is given inside <source> tags
+ const char *content = currentNode.value();
+ pParam.mType = Param_Sampler;
+ pParam.mReference = content;
+ } else if (currentName == "sampler2D") {
+ // surface ID is given inside <instance_image> tags
+ std::string url;
+ XmlParser::getStdStrAttribute(currentNode, "url", url);
+ if (url[0] != '#') {
+ throw DeadlyImportError("Unsupported URL format in instance_image");
+ }
+ pParam.mType = Param_Sampler;
+ pParam.mReference = url.c_str() + 1;
+ } else if (currentName == "source") {
+ const char *source = currentNode.child_value();
+ if (nullptr != source) {
+ pParam.mReference = source;
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the geometry library contents
+void ColladaParser::ReadGeometryLibrary(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "geometry") {
+ // read ID. Another entry which is "optional" by design but obligatory in reality
+
+ std::string id;
+ XmlParser::getStdStrAttribute(currentNode, "id", id);
+ // create a mesh and store it in the library under its (resolved) ID
+ // Skip and warn if ID is not unique
+ if (mMeshLibrary.find(id) == mMeshLibrary.cend()) {
+ std::unique_ptr<Mesh> mesh(new Mesh(id));
+
+ XmlParser::getStdStrAttribute(currentNode, "name", mesh->mName);
+
+ // read on from there
+ ReadGeometry(currentNode, *mesh);
+ // Read successfully, add to library
+ mMeshLibrary.insert({ id, mesh.release() });
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a geometry from the geometry library.
+void ColladaParser::ReadGeometry(XmlNode &node, Collada::Mesh &pMesh) {
+ if (node.empty()) {
+ return;
+ }
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "mesh") {
+ ReadMesh(currentNode, pMesh);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a mesh from the geometry library
+void ColladaParser::ReadMesh(XmlNode &node, Mesh &pMesh) {
+ if (node.empty()) {
+ return;
+ }
+
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "source") {
+ ReadSource(currentNode);
+ } else if (currentName == "vertices") {
+ ReadVertexData(currentNode, pMesh);
+ } else if (currentName == "triangles" || currentName == "lines" || currentName == "linestrips" ||
+ currentName == "polygons" || currentName == "polylist" || currentName == "trifans" ||
+ currentName == "tristrips") {
+ ReadIndexData(currentNode, pMesh);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a source element
+void ColladaParser::ReadSource(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ std::string sourceID;
+ XmlParser::getStdStrAttribute(node, "id", sourceID);
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "float_array" || currentName == "IDREF_array" || currentName == "Name_array") {
+ ReadDataArray(currentNode);
+ } else if (currentName == "technique_common") {
+ XmlNode technique = currentNode.child("accessor");
+ if (!technique.empty()) {
+ ReadAccessor(technique, sourceID);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a data array holding a number of floats, and stores it in the global library
+void ColladaParser::ReadDataArray(XmlNode &node) {
+ std::string name = node.name();
+ bool isStringArray = (name == "IDREF_array" || name == "Name_array");
+
+ // read attributes
+ std::string id;
+ XmlParser::getStdStrAttribute(node, "id", id);
+ unsigned int count = 0;
+ XmlParser::getUIntAttribute(node, "count", count);
+ std::string v;
+ XmlParser::getValueAsString(node, v);
+ v = ai_trim(v);
+ const char *content = v.c_str();
+
+ // read values and store inside an array in the data library
+ mDataLibrary[id] = Data();
+ Data &data = mDataLibrary[id];
+ data.mIsStringArray = isStringArray;
+
+ // some exporters write empty data arrays, but we need to conserve them anyways because others might reference them
+ if (content) {
+ if (isStringArray) {
+ data.mStrings.reserve(count);
+ std::string s;
+
+ for (unsigned int a = 0; a < count; a++) {
+ if (*content == 0) {
+ throw DeadlyImportError("Expected more values while reading IDREF_array contents.");
+ }
+
+ s.clear();
+ while (!IsSpaceOrNewLine(*content))
+ s += *content++;
+ data.mStrings.push_back(s);
+
+ SkipSpacesAndLineEnd(&content);
+ }
+ } else {
+ data.mValues.reserve(count);
+
+ for (unsigned int a = 0; a < count; a++) {
+ if (*content == 0) {
+ throw DeadlyImportError("Expected more values while reading float_array contents.");
+ }
+
+ // read a number
+ ai_real value;
+ content = fast_atoreal_move<ai_real>(content, value);
+ data.mValues.push_back(value);
+ // skip whitespace after it
+ SkipSpacesAndLineEnd(&content);
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads an accessor and stores it in the global library
+void ColladaParser::ReadAccessor(XmlNode &node, const std::string &pID) {
+ // read accessor attributes
+ std::string source;
+ XmlParser::getStdStrAttribute(node, "source", source);
+ if (source[0] != '#') {
+ throw DeadlyImportError("Unknown reference format in url \"", source, "\" in source attribute of <accessor> element.");
+ }
+ int count = 0;
+ XmlParser::getIntAttribute(node, "count", count);
+
+ unsigned int offset = 0;
+ if (XmlParser::hasAttribute(node, "offset")) {
+ XmlParser::getUIntAttribute(node, "offset", offset);
+ }
+ unsigned int stride = 1;
+ if (XmlParser::hasAttribute(node, "stride")) {
+ XmlParser::getUIntAttribute(node, "stride", stride);
+ }
+ // store in the library under the given ID
+ mAccessorLibrary[pID] = Accessor();
+ Accessor &acc = mAccessorLibrary[pID];
+ acc.mCount = count;
+ acc.mOffset = offset;
+ acc.mStride = stride;
+ acc.mSource = source.c_str() + 1; // ignore the leading '#'
+ acc.mSize = 0; // gets incremented with every param
+
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "param") {
+ // read data param
+ std::string name;
+ if (XmlParser::hasAttribute(currentNode, "name")) {
+ XmlParser::getStdStrAttribute(currentNode, "name", name);
+
+ // analyse for common type components and store it's sub-offset in the corresponding field
+
+ // Cartesian coordinates
+ if (name == "X")
+ acc.mSubOffset[0] = acc.mParams.size();
+ else if (name == "Y")
+ acc.mSubOffset[1] = acc.mParams.size();
+ else if (name == "Z")
+ acc.mSubOffset[2] = acc.mParams.size();
+
+ /* RGBA colors */
+ else if (name == "R")
+ acc.mSubOffset[0] = acc.mParams.size();
+ else if (name == "G")
+ acc.mSubOffset[1] = acc.mParams.size();
+ else if (name == "B")
+ acc.mSubOffset[2] = acc.mParams.size();
+ else if (name == "A")
+ acc.mSubOffset[3] = acc.mParams.size();
+
+ /* UVWQ (STPQ) texture coordinates */
+ else if (name == "S")
+ acc.mSubOffset[0] = acc.mParams.size();
+ else if (name == "T")
+ acc.mSubOffset[1] = acc.mParams.size();
+ else if (name == "P")
+ acc.mSubOffset[2] = acc.mParams.size();
+ /* Generic extra data, interpreted as UV data, too*/
+ else if (name == "U")
+ acc.mSubOffset[0] = acc.mParams.size();
+ else if (name == "V")
+ acc.mSubOffset[1] = acc.mParams.size();
+ }
+ if (XmlParser::hasAttribute(currentNode, "type")) {
+ // read data type
+ // TODO: (thom) I don't have a spec here at work. Check if there are other multi-value types
+ // which should be tested for here.
+ std::string type;
+
+ XmlParser::getStdStrAttribute(currentNode, "type", type);
+ if (type == "float4x4")
+ acc.mSize += 16;
+ else
+ acc.mSize += 1;
+ }
+
+ acc.mParams.push_back(name);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads input declarations of per-vertex mesh data into the given mesh
+void ColladaParser::ReadVertexData(XmlNode &node, Mesh &pMesh) {
+ // extract the ID of the <vertices> element. Not that we care, but to catch strange referencing schemes we should warn about
+ XmlParser::getStdStrAttribute(node, "id", pMesh.mVertexID);
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "input") {
+ ReadInputChannel(currentNode, pMesh.mPerVertexData);
+ } else {
+ throw DeadlyImportError("Unexpected sub element <", currentName, "> in tag <vertices>");
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads input declarations of per-index mesh data into the given mesh
+void ColladaParser::ReadIndexData(XmlNode &node, Mesh &pMesh) {
+ std::vector<size_t> vcount;
+ std::vector<InputChannel> perIndexData;
+
+ unsigned int numPrimitives = 0;
+ XmlParser::getUIntAttribute(node, "count", numPrimitives);
+ // read primitive count from the attribute
+ //int attrCount = GetAttribute("count");
+ //size_t numPrimitives = (size_t)mReader->getAttributeValueAsInt(attrCount);
+ // some mesh types (e.g. tristrips) don't specify primitive count upfront,
+ // so we need to sum up the actual number of primitives while we read the <p>-tags
+ size_t actualPrimitives = 0;
+ SubMesh subgroup;
+ if (XmlParser::hasAttribute(node, "material")) {
+ XmlParser::getStdStrAttribute(node, "material", subgroup.mMaterial);
+ }
+
+ // distinguish between polys and triangles
+ std::string elementName = node.name();
+ PrimitiveType primType = Prim_Invalid;
+ if (elementName == "lines")
+ primType = Prim_Lines;
+ else if (elementName == "linestrips")
+ primType = Prim_LineStrip;
+ else if (elementName == "polygons")
+ primType = Prim_Polygon;
+ else if (elementName == "polylist")
+ primType = Prim_Polylist;
+ else if (elementName == "triangles")
+ primType = Prim_Triangles;
+ else if (elementName == "trifans")
+ primType = Prim_TriFans;
+ else if (elementName == "tristrips")
+ primType = Prim_TriStrips;
+
+ ai_assert(primType != Prim_Invalid);
+
+ // also a number of <input> elements, but in addition a <p> primitive collection and probably index counts for all primitives
+ XmlNodeIterator xmlIt(node, XmlNodeIterator::PreOrderMode);
+ XmlNode currentNode;
+ while (xmlIt.getNext(currentNode)) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "input") {
+ ReadInputChannel(currentNode, perIndexData);
+ } else if (currentName == "vcount") {
+ if (!currentNode.empty()) {
+ if (numPrimitives) // It is possible to define a mesh without any primitives
+ {
+ // case <polylist> - specifies the number of indices for each polygon
+ std::string v;
+ XmlParser::getValueAsString(currentNode, v);
+ const char *content = v.c_str();
+ vcount.reserve(numPrimitives);
+ for (unsigned int a = 0; a < numPrimitives; a++) {
+ if (*content == 0) {
+ throw DeadlyImportError("Expected more values while reading <vcount> contents.");
+ }
+ // read a number
+ vcount.push_back((size_t)strtoul10(content, &content));
+ // skip whitespace after it
+ SkipSpacesAndLineEnd(&content);
+ }
+ }
+ }
+ } else if (currentName == "p") {
+ if (!currentNode.empty()) {
+ // now here the actual fun starts - these are the indices to construct the mesh data from
+ actualPrimitives += ReadPrimitives(currentNode, pMesh, perIndexData, numPrimitives, vcount, primType);
+ }
+ } else if (currentName == "extra") {
+ // skip
+ } else if (currentName == "ph") {
+ // skip
+ } else {
+ throw DeadlyImportError("Unexpected sub element <", currentName, "> in tag <", elementName, ">");
+ }
+ }
+
+#ifdef ASSIMP_BUILD_DEBUG
+ if (primType != Prim_TriFans && primType != Prim_TriStrips && primType != Prim_LineStrip &&
+ primType != Prim_Lines) { // this is ONLY to workaround a bug in SketchUp 15.3.331 where it writes the wrong 'count' when it writes out the 'lines'.
+ ai_assert(actualPrimitives == numPrimitives);
+ }
+#endif
+
+ // only when we're done reading all <p> tags (and thus know the final vertex count) can we commit the submesh
+ subgroup.mNumFaces = actualPrimitives;
+ pMesh.mSubMeshes.push_back(subgroup);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a single input channel element and stores it in the given array, if valid
+void ColladaParser::ReadInputChannel(XmlNode &node, std::vector<InputChannel> &poChannels) {
+ InputChannel channel;
+
+ // read semantic
+ std::string semantic;
+ XmlParser::getStdStrAttribute(node, "semantic", semantic);
+ channel.mType = GetTypeForSemantic(semantic);
+
+ // read source
+ std::string source;
+ XmlParser::getStdStrAttribute(node, "source", source);
+ if (source[0] != '#') {
+ throw DeadlyImportError("Unknown reference format in url \"", source, "\" in source attribute of <input> element.");
+ }
+ channel.mAccessor = source.c_str() + 1; // skipping the leading #, hopefully the remaining text is the accessor ID only
+
+ // read index offset, if per-index <input>
+ if (XmlParser::hasAttribute(node, "offset")) {
+ XmlParser::getUIntAttribute(node, "offset", (unsigned int &)channel.mOffset);
+ }
+
+ // read set if texture coordinates
+ if (channel.mType == IT_Texcoord || channel.mType == IT_Color) {
+ unsigned int attrSet = 0;
+ if (XmlParser::getUIntAttribute(node, "set", attrSet))
+ channel.mIndex = attrSet;
+ }
+
+ // store, if valid type
+ if (channel.mType != IT_Invalid)
+ poChannels.push_back(channel);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a <p> primitive index list and assembles the mesh data into the given mesh
+size_t ColladaParser::ReadPrimitives(XmlNode &node, Mesh &pMesh, std::vector<InputChannel> &pPerIndexChannels,
+ size_t pNumPrimitives, const std::vector<size_t> &pVCount, PrimitiveType pPrimType) {
+ // determine number of indices coming per vertex
+ // find the offset index for all per-vertex channels
+ size_t numOffsets = 1;
+ size_t perVertexOffset = SIZE_MAX; // invalid value
+ for (const InputChannel &channel : pPerIndexChannels) {
+ numOffsets = std::max(numOffsets, channel.mOffset + 1);
+ if (channel.mType == IT_Vertex)
+ perVertexOffset = channel.mOffset;
+ }
+
+ // determine the expected number of indices
+ size_t expectedPointCount = 0;
+ switch (pPrimType) {
+ case Prim_Polylist: {
+ for (size_t i : pVCount)
+ expectedPointCount += i;
+ break;
+ }
+ case Prim_Lines:
+ expectedPointCount = 2 * pNumPrimitives;
+ break;
+ case Prim_Triangles:
+ expectedPointCount = 3 * pNumPrimitives;
+ break;
+ default:
+ // other primitive types don't state the index count upfront... we need to guess
+ break;
+ }
+
+ // and read all indices into a temporary array
+ std::vector<size_t> indices;
+ if (expectedPointCount > 0) {
+ indices.reserve(expectedPointCount * numOffsets);
+ }
+
+ // It is possible to not contain any indices
+ if (pNumPrimitives > 0) {
+ std::string v;
+ XmlParser::getValueAsString(node, v);
+ const char *content = v.c_str();
+ SkipSpacesAndLineEnd(&content);
+ while (*content != 0) {
+ // read a value.
+ // Hack: (thom) Some exporters put negative indices sometimes. We just try to carry on anyways.
+ int value = std::max(0, strtol10(content, &content));
+ indices.push_back(size_t(value));
+ // skip whitespace after it
+ SkipSpacesAndLineEnd(&content);
+ }
+ }
+
+ // complain if the index count doesn't fit
+ if (expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) {
+ if (pPrimType == Prim_Lines) {
+ // HACK: We just fix this number since SketchUp 15.3.331 writes the wrong 'count' for 'lines'
+ ReportWarning("Expected different index count in <p> element, %zu instead of %zu.", indices.size(), expectedPointCount * numOffsets);
+ pNumPrimitives = (indices.size() / numOffsets) / 2;
+ } else {
+ throw DeadlyImportError("Expected different index count in <p> element.");
+ }
+ } else if (expectedPointCount == 0 && (indices.size() % numOffsets) != 0) {
+ throw DeadlyImportError("Expected different index count in <p> element.");
+ }
+
+ // find the data for all sources
+ for (std::vector<InputChannel>::iterator it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) {
+ InputChannel &input = *it;
+ if (input.mResolved) {
+ continue;
+ }
+
+ // find accessor
+ input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor);
+ // resolve accessor's data pointer as well, if necessary
+ const Accessor *acc = input.mResolved;
+ if (!acc->mData) {
+ acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource);
+ }
+ }
+ // and the same for the per-index channels
+ for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) {
+ InputChannel &input = *it;
+ if (input.mResolved) {
+ continue;
+ }
+
+ // ignore vertex pointer, it doesn't refer to an accessor
+ if (input.mType == IT_Vertex) {
+ // warn if the vertex channel does not refer to the <vertices> element in the same mesh
+ if (input.mAccessor != pMesh.mVertexID) {
+ throw DeadlyImportError("Unsupported vertex referencing scheme.");
+ }
+ continue;
+ }
+
+ // find accessor
+ input.mResolved = &ResolveLibraryReference(mAccessorLibrary, input.mAccessor);
+ // resolve accessor's data pointer as well, if necessary
+ const Accessor *acc = input.mResolved;
+ if (!acc->mData) {
+ acc->mData = &ResolveLibraryReference(mDataLibrary, acc->mSource);
+ }
+ }
+
+ // For continued primitives, the given count does not come all in one <p>, but only one primitive per <p>
+ size_t numPrimitives = pNumPrimitives;
+ if (pPrimType == Prim_TriFans || pPrimType == Prim_Polygon)
+ numPrimitives = 1;
+ // For continued primitives, the given count is actually the number of <p>'s inside the parent tag
+ if (pPrimType == Prim_TriStrips) {
+ size_t numberOfVertices = indices.size() / numOffsets;
+ numPrimitives = numberOfVertices - 2;
+ }
+ if (pPrimType == Prim_LineStrip) {
+ size_t numberOfVertices = indices.size() / numOffsets;
+ numPrimitives = numberOfVertices - 1;
+ }
+
+ pMesh.mFaceSize.reserve(numPrimitives);
+ pMesh.mFacePosIndices.reserve(indices.size() / numOffsets);
+
+ size_t polylistStartVertex = 0;
+ for (size_t currentPrimitive = 0; currentPrimitive < numPrimitives; currentPrimitive++) {
+ // determine number of points for this primitive
+ size_t numPoints = 0;
+ switch (pPrimType) {
+ case Prim_Lines:
+ numPoints = 2;
+ for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+ CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ break;
+ case Prim_LineStrip:
+ numPoints = 2;
+ for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+ CopyVertex(currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ break;
+ case Prim_Triangles:
+ numPoints = 3;
+ for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+ CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ break;
+ case Prim_TriStrips:
+ numPoints = 3;
+ ReadPrimTriStrips(numOffsets, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ break;
+ case Prim_Polylist:
+ numPoints = pVCount[currentPrimitive];
+ for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+ CopyVertex(polylistStartVertex + currentVertex, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, 0, indices);
+ polylistStartVertex += numPoints;
+ break;
+ case Prim_TriFans:
+ case Prim_Polygon:
+ numPoints = indices.size() / numOffsets;
+ for (size_t currentVertex = 0; currentVertex < numPoints; currentVertex++)
+ CopyVertex(currentVertex, numOffsets, numPoints, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ break;
+ default:
+ // LineStrip is not supported due to expected index unmangling
+ throw DeadlyImportError("Unsupported primitive type.");
+ break;
+ }
+
+ // store the face size to later reconstruct the face from
+ pMesh.mFaceSize.push_back(numPoints);
+ }
+
+ // if I ever get my hands on that guy who invented this steaming pile of indirection...
+ return numPrimitives;
+}
+
+///@note This function won't work correctly if both PerIndex and PerVertex channels have same channels.
+///For example if TEXCOORD present in both <vertices> and <polylist> tags this function will create wrong uv coordinates.
+///It's not clear from COLLADA documentation is this allowed or not. For now only exporter fixed to avoid such behavior
+void ColladaParser::CopyVertex(size_t currentVertex, size_t numOffsets, size_t numPoints, size_t perVertexOffset, Mesh &pMesh,
+ std::vector<InputChannel> &pPerIndexChannels, size_t currentPrimitive, const std::vector<size_t> &indices) {
+ // calculate the base offset of the vertex whose attributes we ant to copy
+ size_t baseOffset = currentPrimitive * numOffsets * numPoints + currentVertex * numOffsets;
+
+ // don't overrun the boundaries of the index list
+ ai_assert((baseOffset + numOffsets - 1) < indices.size());
+
+ // extract per-vertex channels using the global per-vertex offset
+ for (std::vector<InputChannel>::iterator it = pMesh.mPerVertexData.begin(); it != pMesh.mPerVertexData.end(); ++it) {
+ ExtractDataObjectFromChannel(*it, indices[baseOffset + perVertexOffset], pMesh);
+ }
+ // and extract per-index channels using there specified offset
+ for (std::vector<InputChannel>::iterator it = pPerIndexChannels.begin(); it != pPerIndexChannels.end(); ++it) {
+ ExtractDataObjectFromChannel(*it, indices[baseOffset + it->mOffset], pMesh);
+ }
+
+ // store the vertex-data index for later assignment of bone vertex weights
+ pMesh.mFacePosIndices.push_back(indices[baseOffset + perVertexOffset]);
+}
+
+void ColladaParser::ReadPrimTriStrips(size_t numOffsets, size_t perVertexOffset, Mesh &pMesh, std::vector<InputChannel> &pPerIndexChannels,
+ size_t currentPrimitive, const std::vector<size_t> &indices) {
+ if (currentPrimitive % 2 != 0) {
+ //odd tristrip triangles need their indices mangled, to preserve winding direction
+ CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ } else { //for non tristrips or even tristrip triangles
+ CopyVertex(0, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ CopyVertex(1, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ CopyVertex(2, numOffsets, 1, perVertexOffset, pMesh, pPerIndexChannels, currentPrimitive, indices);
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Extracts a single object from an input channel and stores it in the appropriate mesh data array
+void ColladaParser::ExtractDataObjectFromChannel(const InputChannel &pInput, size_t pLocalIndex, Mesh &pMesh) {
+ // ignore vertex referrer - we handle them that separate
+ if (pInput.mType == IT_Vertex) {
+ return;
+ }
+
+ const Accessor &acc = *pInput.mResolved;
+ if (pLocalIndex >= acc.mCount) {
+ throw DeadlyImportError("Invalid data index (", pLocalIndex, "/", acc.mCount, ") in primitive specification");
+ }
+
+ // get a pointer to the start of the data object referred to by the accessor and the local index
+ const ai_real *dataObject = &(acc.mData->mValues[0]) + acc.mOffset + pLocalIndex * acc.mStride;
+
+ // assemble according to the accessors component sub-offset list. We don't care, yet,
+ // what kind of object exactly we're extracting here
+ ai_real obj[4];
+ for (size_t c = 0; c < 4; ++c) {
+ obj[c] = dataObject[acc.mSubOffset[c]];
+ }
+
+ // now we reinterpret it according to the type we're reading here
+ switch (pInput.mType) {
+ case IT_Position: // ignore all position streams except 0 - there can be only one position
+ if (pInput.mIndex == 0) {
+ pMesh.mPositions.push_back(aiVector3D(obj[0], obj[1], obj[2]));
+ } else {
+ ASSIMP_LOG_ERROR("Collada: just one vertex position stream supported");
+ }
+ break;
+ case IT_Normal:
+ // pad to current vertex count if necessary
+ if (pMesh.mNormals.size() < pMesh.mPositions.size() - 1)
+ pMesh.mNormals.insert(pMesh.mNormals.end(), pMesh.mPositions.size() - pMesh.mNormals.size() - 1, aiVector3D(0, 1, 0));
+
+ // ignore all normal streams except 0 - there can be only one normal
+ if (pInput.mIndex == 0) {
+ pMesh.mNormals.push_back(aiVector3D(obj[0], obj[1], obj[2]));
+ } else {
+ ASSIMP_LOG_ERROR("Collada: just one vertex normal stream supported");
+ }
+ break;
+ case IT_Tangent:
+ // pad to current vertex count if necessary
+ if (pMesh.mTangents.size() < pMesh.mPositions.size() - 1)
+ pMesh.mTangents.insert(pMesh.mTangents.end(), pMesh.mPositions.size() - pMesh.mTangents.size() - 1, aiVector3D(1, 0, 0));
+
+ // ignore all tangent streams except 0 - there can be only one tangent
+ if (pInput.mIndex == 0) {
+ pMesh.mTangents.push_back(aiVector3D(obj[0], obj[1], obj[2]));
+ } else {
+ ASSIMP_LOG_ERROR("Collada: just one vertex tangent stream supported");
+ }
+ break;
+ case IT_Bitangent:
+ // pad to current vertex count if necessary
+ if (pMesh.mBitangents.size() < pMesh.mPositions.size() - 1) {
+ pMesh.mBitangents.insert(pMesh.mBitangents.end(), pMesh.mPositions.size() - pMesh.mBitangents.size() - 1, aiVector3D(0, 0, 1));
+ }
+
+ // ignore all bitangent streams except 0 - there can be only one bitangent
+ if (pInput.mIndex == 0) {
+ pMesh.mBitangents.push_back(aiVector3D(obj[0], obj[1], obj[2]));
+ } else {
+ ASSIMP_LOG_ERROR("Collada: just one vertex bitangent stream supported");
+ }
+ break;
+ case IT_Texcoord:
+ // up to 4 texture coord sets are fine, ignore the others
+ if (pInput.mIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS) {
+ // pad to current vertex count if necessary
+ if (pMesh.mTexCoords[pInput.mIndex].size() < pMesh.mPositions.size() - 1)
+ pMesh.mTexCoords[pInput.mIndex].insert(pMesh.mTexCoords[pInput.mIndex].end(),
+ pMesh.mPositions.size() - pMesh.mTexCoords[pInput.mIndex].size() - 1, aiVector3D(0, 0, 0));
+
+ pMesh.mTexCoords[pInput.mIndex].push_back(aiVector3D(obj[0], obj[1], obj[2]));
+ if (0 != acc.mSubOffset[2] || 0 != acc.mSubOffset[3]) {
+ pMesh.mNumUVComponents[pInput.mIndex] = 3;
+ }
+ } else {
+ ASSIMP_LOG_ERROR("Collada: too many texture coordinate sets. Skipping.");
+ }
+ break;
+ case IT_Color:
+ // up to 4 color sets are fine, ignore the others
+ if (pInput.mIndex < AI_MAX_NUMBER_OF_COLOR_SETS) {
+ // pad to current vertex count if necessary
+ if (pMesh.mColors[pInput.mIndex].size() < pMesh.mPositions.size() - 1)
+ pMesh.mColors[pInput.mIndex].insert(pMesh.mColors[pInput.mIndex].end(),
+ pMesh.mPositions.size() - pMesh.mColors[pInput.mIndex].size() - 1, aiColor4D(0, 0, 0, 1));
+
+ aiColor4D result(0, 0, 0, 1);
+ for (size_t i = 0; i < pInput.mResolved->mSize; ++i) {
+ result[static_cast<unsigned int>(i)] = obj[pInput.mResolved->mSubOffset[i]];
+ }
+ pMesh.mColors[pInput.mIndex].push_back(result);
+ } else {
+ ASSIMP_LOG_ERROR("Collada: too many vertex color sets. Skipping.");
+ }
+
+ break;
+ default:
+ // IT_Invalid and IT_Vertex
+ ai_assert(false && "shouldn't ever get here");
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the library of node hierarchies and scene parts
+void ColladaParser::ReadSceneLibrary(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "visual_scene") {
+ // read ID. Is optional according to the spec, but how on earth should a scene_instance refer to it then?
+ std::string id;
+ XmlParser::getStdStrAttribute(currentNode, "id", id);
+
+ // read name if given.
+ std::string attrName = "Scene";
+ if (XmlParser::hasAttribute(currentNode, "name")) {
+ XmlParser::getStdStrAttribute(currentNode, "name", attrName);
+ }
+
+ // create a node and store it in the library under its ID
+ Node *sceneNode = new Node;
+ sceneNode->mID = id;
+ sceneNode->mName = attrName;
+ mNodeLibrary[sceneNode->mID] = sceneNode;
+
+ ReadSceneNode(currentNode, sceneNode);
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a scene node's contents including children and stores it in the given node
+void ColladaParser::ReadSceneNode(XmlNode &node, Node *pNode) {
+ // quit immediately on <bla/> elements
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "node") {
+ Node *child = new Node;
+ if (XmlParser::hasAttribute(currentNode, "id")) {
+ XmlParser::getStdStrAttribute(currentNode, "id", child->mID);
+ }
+ if (XmlParser::hasAttribute(currentNode, "sid")) {
+ XmlParser::getStdStrAttribute(currentNode, "id", child->mSID);
+ }
+ if (XmlParser::hasAttribute(currentNode, "name")) {
+ XmlParser::getStdStrAttribute(currentNode, "name", child->mName);
+ }
+ if (pNode) {
+ pNode->mChildren.push_back(child);
+ child->mParent = pNode;
+ } else {
+ // no parent node given, probably called from <library_nodes> element.
+ // create new node in node library
+ mNodeLibrary[child->mID] = child;
+ }
+
+ // read on recursively from there
+ ReadSceneNode(currentNode, child);
+ continue;
+ } else if (!pNode) {
+ // For any further stuff we need a valid node to work on
+ continue;
+ }
+ if (currentName == "lookat") {
+ ReadNodeTransformation(currentNode, pNode, TF_LOOKAT);
+ } else if (currentName == "matrix") {
+ ReadNodeTransformation(currentNode, pNode, TF_MATRIX);
+ } else if (currentName == "rotate") {
+ ReadNodeTransformation(currentNode, pNode, TF_ROTATE);
+ } else if (currentName == "scale") {
+ ReadNodeTransformation(currentNode, pNode, TF_SCALE);
+ } else if (currentName == "skew") {
+ ReadNodeTransformation(currentNode, pNode, TF_SKEW);
+ } else if (currentName == "translate") {
+ ReadNodeTransformation(currentNode, pNode, TF_TRANSLATE);
+ } else if (currentName == "render" && pNode->mParent == nullptr && 0 == pNode->mPrimaryCamera.length()) {
+ // ... scene evaluation or, in other words, postprocessing pipeline,
+ // or, again in other words, a turing-complete description how to
+ // render a Collada scene. The only thing that is interesting for
+ // us is the primary camera.
+ if (XmlParser::hasAttribute(currentNode, "camera_node")) {
+ std::string s;
+ XmlParser::getStdStrAttribute(currentNode, "camera_node", s);
+ if (s[0] != '#') {
+ ASSIMP_LOG_ERROR("Collada: Unresolved reference format of camera");
+ } else {
+ pNode->mPrimaryCamera = s.c_str() + 1;
+ }
+ }
+ } else if (currentName == "instance_node") {
+ // find the node in the library
+ if (XmlParser::hasAttribute(currentNode, "url")) {
+ std::string s;
+ XmlParser::getStdStrAttribute(currentNode, "url", s);
+ if (s[0] != '#') {
+ ASSIMP_LOG_ERROR("Collada: Unresolved reference format of node");
+ } else {
+ pNode->mNodeInstances.push_back(NodeInstance());
+ pNode->mNodeInstances.back().mNode = s.c_str() + 1;
+ }
+ }
+ } else if (currentName == "instance_geometry" || currentName == "instance_controller") {
+ // Reference to a mesh or controller, with possible material associations
+ ReadNodeGeometry(currentNode, pNode);
+ } else if (currentName == "instance_light") {
+ // Reference to a light, name given in 'url' attribute
+ if (XmlParser::hasAttribute(currentNode, "url")) {
+ std::string url;
+ XmlParser::getStdStrAttribute(currentNode, "url", url);
+ if (url[0] != '#') {
+ throw DeadlyImportError("Unknown reference format in <instance_light> element");
+ }
+
+ pNode->mLights.push_back(LightInstance());
+ pNode->mLights.back().mLight = url.c_str() + 1;
+ }
+ } else if (currentName == "instance_camera") {
+ // Reference to a camera, name given in 'url' attribute
+ if (XmlParser::hasAttribute(currentNode, "url")) {
+ std::string url;
+ XmlParser::getStdStrAttribute(currentNode, "url", url);
+ if (url[0] != '#') {
+ throw DeadlyImportError("Unknown reference format in <instance_camera> element");
+ }
+ pNode->mCameras.push_back(CameraInstance());
+ pNode->mCameras.back().mCamera = url.c_str() + 1;
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a node transformation entry of the given type and adds it to the given node's transformation list.
+void ColladaParser::ReadNodeTransformation(XmlNode &node, Node *pNode, TransformType pType) {
+ if (node.empty()) {
+ return;
+ }
+
+ std::string tagName = node.name();
+
+ Transform tf;
+ tf.mType = pType;
+
+ // read SID
+ if (XmlParser::hasAttribute(node, "sid")) {
+ XmlParser::getStdStrAttribute(node, "sid", tf.mID);
+ }
+
+ // how many parameters to read per transformation type
+ static const unsigned int sNumParameters[] = { 9, 4, 3, 3, 7, 16 };
+ std::string value;
+ XmlParser::getValueAsString(node, value);
+ const char *content = value.c_str();
+
+ // read as many parameters and store in the transformation
+ for (unsigned int a = 0; a < sNumParameters[pType]; a++) {
+ // skip whitespace before the number
+ SkipSpacesAndLineEnd(&content);
+ // read a number
+ content = fast_atoreal_move<ai_real>(content, tf.f[a]);
+ }
+
+ // place the transformation at the queue of the node
+ pNode->mTransforms.push_back(tf);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Processes bind_vertex_input and bind elements
+void ColladaParser::ReadMaterialVertexInputBinding(XmlNode &node, Collada::SemanticMappingTable &tbl) {
+ std::string name = node.name();
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "bind_vertex_input") {
+ Collada::InputSemanticMapEntry vn;
+
+ // effect semantic
+ if (XmlParser::hasAttribute(currentNode, "semantic")) {
+ std::string s;
+ XmlParser::getStdStrAttribute(currentNode, "semantic", s);
+ XmlParser::getUIntAttribute(currentNode, "input_semantic", (unsigned int &)vn.mType);
+ }
+ std::string s;
+ XmlParser::getStdStrAttribute(currentNode, "semantic", s);
+
+ // input semantic
+ XmlParser::getUIntAttribute(currentNode, "input_semantic", (unsigned int &)vn.mType);
+
+ // index of input set
+ if (XmlParser::hasAttribute(currentNode, "input_set")) {
+ XmlParser::getUIntAttribute(currentNode, "input_set", vn.mSet);
+ }
+
+ tbl.mMap[s] = vn;
+ } else if (currentName == "bind") {
+ ASSIMP_LOG_WARN("Collada: Found unsupported <bind> element");
+ }
+ }
+}
+
+void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive) {
+ // Attempt to load any undefined Collada::Image in ImageLibrary
+ for (auto &it : mImageLibrary) {
+ Collada::Image &image = it.second;
+
+ if (image.mImageData.empty()) {
+ std::unique_ptr<IOStream> image_file(zip_archive.Open(image.mFileName.c_str()));
+ if (image_file) {
+ image.mImageData.resize(image_file->FileSize());
+ image_file->Read(image.mImageData.data(), image_file->FileSize(), 1);
+ image.mEmbeddedFormat = BaseImporter::GetExtension(image.mFileName);
+ if (image.mEmbeddedFormat == "jpeg") {
+ image.mEmbeddedFormat = "jpg";
+ }
+ }
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads a mesh reference in a node and adds it to the node's mesh list
+void ColladaParser::ReadNodeGeometry(XmlNode &node, Node *pNode) {
+ // referred mesh is given as an attribute of the <instance_geometry> element
+ std::string url;
+ XmlParser::getStdStrAttribute(node, "url", url);
+ if (url[0] != '#') {
+ throw DeadlyImportError("Unknown reference format");
+ }
+
+ Collada::MeshInstance instance;
+ instance.mMeshOrController = url.c_str() + 1; // skipping the leading #
+
+ for (XmlNode currentNode = node.first_child(); currentNode; currentNode = currentNode.next_sibling()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "bind_material") {
+ XmlNode techNode = currentNode.child("technique_common");
+ if (techNode) {
+ for (XmlNode instanceMatNode = techNode.child("instance_material"); instanceMatNode; instanceMatNode = instanceMatNode.next_sibling())
+ {
+ const std::string &instance_name = instanceMatNode.name();
+ if (instance_name == "instance_material")
+ {
+ // read ID of the geometry subgroup and the target material
+ std::string group;
+ XmlParser::getStdStrAttribute(instanceMatNode, "symbol", group);
+ XmlParser::getStdStrAttribute(instanceMatNode, "target", url);
+ const char *urlMat = url.c_str();
+ Collada::SemanticMappingTable s;
+ if (urlMat[0] == '#')
+ urlMat++;
+
+ s.mMatName = urlMat;
+ // store the association
+ instance.mMaterials[group] = s;
+ ReadMaterialVertexInputBinding(instanceMatNode, s);
+ }
+ }
+ }
+ }
+ }
+
+ // store it
+ pNode->mMeshes.push_back(instance);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the collada scene
+void ColladaParser::ReadScene(XmlNode &node) {
+ if (node.empty()) {
+ return;
+ }
+
+ for (XmlNode &currentNode : node.children()) {
+ const std::string &currentName = currentNode.name();
+ if (currentName == "instance_visual_scene") {
+ // should be the first and only occurrence
+ if (mRootNode) {
+ throw DeadlyImportError("Invalid scene containing multiple root nodes in <instance_visual_scene> element");
+ }
+
+ // read the url of the scene to instance. Should be of format "#some_name"
+ std::string url;
+ XmlParser::getStdStrAttribute(currentNode, "url", url);
+ if (url[0] != '#') {
+ throw DeadlyImportError("Unknown reference format in <instance_visual_scene> element");
+ }
+
+ // find the referred scene, skip the leading #
+ NodeLibrary::const_iterator sit = mNodeLibrary.find(url.c_str() + 1);
+ if (sit == mNodeLibrary.end()) {
+ throw DeadlyImportError("Unable to resolve visual_scene reference \"", std::string(url), "\" in <instance_visual_scene> element.");
+ }
+ mRootNode = sit->second;
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Calculates the resulting transformation from all the given transform steps
+aiMatrix4x4 ColladaParser::CalculateResultTransform(const std::vector<Transform> &pTransforms) const {
+ aiMatrix4x4 res;
+
+ for (std::vector<Transform>::const_iterator it = pTransforms.begin(); it != pTransforms.end(); ++it) {
+ const Transform &tf = *it;
+ switch (tf.mType) {
+ case TF_LOOKAT: {
+ aiVector3D pos(tf.f[0], tf.f[1], tf.f[2]);
+ aiVector3D dstPos(tf.f[3], tf.f[4], tf.f[5]);
+ aiVector3D up = aiVector3D(tf.f[6], tf.f[7], tf.f[8]).Normalize();
+ aiVector3D dir = aiVector3D(dstPos - pos).Normalize();
+ aiVector3D right = (dir ^ up).Normalize();
+
+ res *= aiMatrix4x4(
+ right.x, up.x, -dir.x, pos.x,
+ right.y, up.y, -dir.y, pos.y,
+ right.z, up.z, -dir.z, pos.z,
+ 0, 0, 0, 1);
+ break;
+ }
+ case TF_ROTATE: {
+ aiMatrix4x4 rot;
+ ai_real angle = tf.f[3] * ai_real(AI_MATH_PI) / ai_real(180.0);
+ aiVector3D axis(tf.f[0], tf.f[1], tf.f[2]);
+ aiMatrix4x4::Rotation(angle, axis, rot);
+ res *= rot;
+ break;
+ }
+ case TF_TRANSLATE: {
+ aiMatrix4x4 trans;
+ aiMatrix4x4::Translation(aiVector3D(tf.f[0], tf.f[1], tf.f[2]), trans);
+ res *= trans;
+ break;
+ }
+ case TF_SCALE: {
+ aiMatrix4x4 scale(tf.f[0], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[1], 0.0f, 0.0f, 0.0f, 0.0f, tf.f[2], 0.0f,
+ 0.0f, 0.0f, 0.0f, 1.0f);
+ res *= scale;
+ break;
+ }
+ case TF_SKEW:
+ // TODO: (thom)
+ ai_assert(false);
+ break;
+ case TF_MATRIX: {
+ aiMatrix4x4 mat(tf.f[0], tf.f[1], tf.f[2], tf.f[3], tf.f[4], tf.f[5], tf.f[6], tf.f[7],
+ tf.f[8], tf.f[9], tf.f[10], tf.f[11], tf.f[12], tf.f[13], tf.f[14], tf.f[15]);
+ res *= mat;
+ break;
+ }
+ default:
+ ai_assert(false);
+ break;
+ }
+ }
+
+ return res;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Determines the input data type for the given semantic string
+Collada::InputType ColladaParser::GetTypeForSemantic(const std::string &semantic) {
+ if (semantic.empty()) {
+ ASSIMP_LOG_WARN("Vertex input type is empty.");
+ return IT_Invalid;
+ }
+
+ if (semantic == "POSITION")
+ return IT_Position;
+ else if (semantic == "TEXCOORD")
+ return IT_Texcoord;
+ else if (semantic == "NORMAL")
+ return IT_Normal;
+ else if (semantic == "COLOR")
+ return IT_Color;
+ else if (semantic == "VERTEX")
+ return IT_Vertex;
+ else if (semantic == "BINORMAL" || semantic == "TEXBINORMAL")
+ return IT_Bitangent;
+ else if (semantic == "TANGENT" || semantic == "TEXTANGENT")
+ return IT_Tangent;
+
+ ASSIMP_LOG_WARN("Unknown vertex input type \"", semantic, "\". Ignoring.");
+ return IT_Invalid;
+}
+
+#endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER
diff --git a/libs/assimp/code/AssetLib/Collada/ColladaParser.h b/libs/assimp/code/AssetLib/Collada/ColladaParser.h
new file mode 100644
index 0000000..1598293
--- /dev/null
+++ b/libs/assimp/code/AssetLib/Collada/ColladaParser.h
@@ -0,0 +1,348 @@
+/*
+ Open Asset Import Library (assimp)
+ ----------------------------------------------------------------------
+
+ Copyright (c) 2006-2022, 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 ColladaParser.h
+ * @brief Defines the parser helper class for the collada loader
+ */
+
+#pragma once
+#ifndef AI_COLLADAPARSER_H_INC
+#define AI_COLLADAPARSER_H_INC
+
+#include "ColladaHelper.h"
+#include <assimp/TinyFormatter.h>
+#include <assimp/ai_assert.h>
+#include <assimp/XmlParser.h>
+
+#include <map>
+
+namespace Assimp {
+
+class ZipArchiveIOSystem;
+
+// ------------------------------------------------------------------------------------------
+/** 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;
+
+ /** 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;
+
+ /** Constructor from XML file */
+ ColladaParser(IOSystem *pIOHandler, const std::string &pFile);
+
+ /** Destructor */
+ ~ColladaParser();
+
+ /** 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(XmlNode &node);
+
+ /** Reads the structure of the file */
+ void ReadStructure(XmlNode &node);
+
+ /** Reads asset information such as coordinate system information and legal blah */
+ void ReadAssetInfo(XmlNode &node);
+
+ /** Reads contributor information such as author and legal blah */
+ void ReadContributorInfo(XmlNode &node);
+
+ /** Reads generic metadata into provided map and renames keys for Assimp */
+ void ReadMetaDataItem(XmlNode &node, StringMetaData &metadata);
+
+ /** Reads the animation library */
+ void ReadAnimationLibrary(XmlNode &node);
+
+ /** Reads the animation clip library */
+ void ReadAnimationClipLibrary(XmlNode &node);
+
+ /** Unwrap controllers dependency hierarchy */
+ void PostProcessControllers();
+
+ /** Re-build animations from animation clip library, if present, otherwise combine single-channel animations */
+ void PostProcessRootAnimations();
+
+ /** Reads an animation into the given parent structure */
+ void ReadAnimation(XmlNode &node, Collada::Animation *pParent);
+
+ /** Reads an animation sampler into the given anim channel */
+ void ReadAnimationSampler(XmlNode &node, Collada::AnimationChannel &pChannel);
+
+ /** Reads the skeleton controller library */
+ void ReadControllerLibrary(XmlNode &node);
+
+ /** Reads a controller into the given mesh structure */
+ void ReadController(XmlNode &node, Collada::Controller &pController);
+
+ /** Reads the joint definitions for the given controller */
+ void ReadControllerJoints(XmlNode &node, Collada::Controller &pController);
+
+ /** Reads the joint weights for the given controller */
+ void ReadControllerWeights(XmlNode &node, Collada::Controller &pController);
+
+ /** Reads the image library contents */
+ void ReadImageLibrary(XmlNode &node);
+
+ /** Reads an image entry into the given image */
+ void ReadImage(XmlNode &node, Collada::Image &pImage);
+
+ /** Reads the material library */
+ void ReadMaterialLibrary(XmlNode &node);
+
+ /** Reads a material entry into the given material */
+ void ReadMaterial(XmlNode &node, Collada::Material &pMaterial);
+
+ /** Reads the camera library */
+ void ReadCameraLibrary(XmlNode &node);
+
+ /** Reads a camera entry into the given camera */
+ void ReadCamera(XmlNode &node, Collada::Camera &pCamera);
+
+ /** Reads the light library */
+ void ReadLightLibrary(XmlNode &node);
+
+ /** Reads a light entry into the given light */
+ void ReadLight(XmlNode &node, Collada::Light &pLight);
+
+ /** Reads the effect library */
+ void ReadEffectLibrary(XmlNode &node);
+
+ /** Reads an effect entry into the given effect*/
+ void ReadEffect(XmlNode &node, Collada::Effect &pEffect);
+
+ /** Reads an COMMON effect profile */
+ void ReadEffectProfileCommon(XmlNode &node, Collada::Effect &pEffect);
+
+ /** Read sampler properties */
+ void ReadSamplerProperties(XmlNode &node, Collada::Sampler &pSampler);
+
+ /** Reads an effect entry containing a color or a texture defining that color */
+ void ReadEffectColor(XmlNode &node, aiColor4D &pColor, Collada::Sampler &pSampler);
+
+ /** Reads an effect entry containing a float */
+ void ReadEffectFloat(XmlNode &node, ai_real &pFloat);
+
+ /** Reads an effect parameter specification of any kind */
+ void ReadEffectParam(XmlNode &node, Collada::EffectParam &pParam);
+
+ /** Reads the geometry library contents */
+ void ReadGeometryLibrary(XmlNode &node);
+
+ /** Reads a geometry from the geometry library. */
+ void ReadGeometry(XmlNode &node, Collada::Mesh &pMesh);
+
+ /** Reads a mesh from the geometry library */
+ void ReadMesh(XmlNode &node, 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(XmlNode &node);
+
+ /** 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(XmlNode &node);
+
+ /** 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(XmlNode &node, const std::string &pID);
+
+ /** Reads input declarations of per-vertex mesh data into the given mesh */
+ void ReadVertexData(XmlNode &node, Collada::Mesh &pMesh);
+
+ /** Reads input declarations of per-index mesh data into the given mesh */
+ void ReadIndexData(XmlNode &node, Collada::Mesh &pMesh);
+
+ /** Reads a single input channel element and stores it in the given array, if valid */
+ void ReadInputChannel(XmlNode &node, std::vector<Collada::InputChannel> &poChannels);
+
+ /** Reads a <p> primitive index list and assembles the mesh data into the given mesh */
+ size_t ReadPrimitives(XmlNode &node, 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);
+
+ /** 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);
+
+ /** Reads the library of node hierarchies and scene parts */
+ void ReadSceneLibrary(XmlNode &node);
+
+ /** Reads a scene node's contents including children and stores it in the given node */
+ void ReadSceneNode(XmlNode &node, Collada::Node *pNode);
+
+ /** Reads a node transformation entry of the given type and adds it to the given node's transformation list. */
+ void ReadNodeTransformation(XmlNode &node, Collada::Node *pNode, Collada::TransformType pType);
+
+ /** Reads a mesh reference in a node and adds it to the node's mesh list */
+ void ReadNodeGeometry(XmlNode &node, Collada::Node *pNode);
+
+ /** Reads the collada scene */
+ void ReadScene(XmlNode &node);
+
+ // Processes bind_vertex_input and bind elements
+ void ReadMaterialVertexInputBinding(XmlNode &node, Collada::SemanticMappingTable &tbl);
+
+ /** Reads embedded textures from a ZAE archive*/
+ void ReadEmbeddedTextures(ZipArchiveIOSystem &zip_archive);
+
+protected:
+ /** 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);
+
+ /** 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;
+
+ // XML reader, member for everyday use
+ XmlParser mXmlParser;
+
+ /** All data arrays found in the file by ID. Might be referred to by actually
+ everyone. Collada, you are a steaming pile of indirection. */
+ using DataLibrary = std::map<std::string, Collada::Data> ;
+ DataLibrary mDataLibrary;
+
+ /** Same for accessors which define how the data in a data array is accessed. */
+ using AccessorLibrary = std::map<std::string, Collada::Accessor> ;
+ AccessorLibrary mAccessorLibrary;
+
+ /** Mesh library: mesh by ID */
+ using MeshLibrary = std::map<std::string, Collada::Mesh *>;
+ MeshLibrary mMeshLibrary;
+
+ /** node library: root node of the hierarchy part by ID */
+ using NodeLibrary = std::map<std::string, Collada::Node *>;
+ NodeLibrary mNodeLibrary;
+
+ /** Image library: stores texture properties by ID */
+ using ImageLibrary = std::map<std::string, Collada::Image> ;
+ ImageLibrary mImageLibrary;
+
+ /** Effect library: surface attributes by ID */
+ using EffectLibrary = std::map<std::string, Collada::Effect> ;
+ EffectLibrary mEffectLibrary;
+
+ /** Material library: surface material by ID */
+ using MaterialLibrary = std::map<std::string, Collada::Material> ;
+ MaterialLibrary mMaterialLibrary;
+
+ /** Light library: surface light by ID */
+ using LightLibrary = std::map<std::string, Collada::Light> ;
+ LightLibrary mLightLibrary;
+
+ /** Camera library: surface material by ID */
+ using CameraLibrary = std::map<std::string, Collada::Camera> ;
+ CameraLibrary mCameraLibrary;
+
+ /** Controller library: joint controllers by ID */
+ using ControllerLibrary = std::map<std::string, Collada::Controller> ;
+ ControllerLibrary mControllerLibrary;
+
+ /** Animation library: animation references by ID */
+ using AnimationLibrary = std::map<std::string, Collada::Animation *> ;
+ AnimationLibrary mAnimationLibrary;
+
+ /** Animation clip library: clip animation references by ID */
+ using AnimationClipLibrary = std::vector<std::pair<std::string, std::vector<std::string>>> ;
+ AnimationClipLibrary mAnimationClipLibrary;
+
+ /** 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;
+};
+
+// ------------------------------------------------------------------------------------------------
+// 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()) {
+ throw DeadlyImportError("Unable to resolve library reference \"", pURL, "\".");
+ }
+ return it->second;
+}
+
+} // end of namespace Assimp
+
+#endif // AI_COLLADAPARSER_H_INC