diff options
author | sanine <sanine.not@pm.me> | 2022-04-16 11:55:09 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-04-16 11:55:09 -0500 |
commit | db81b925d776103326128bf629cbdda576a223e7 (patch) | |
tree | 58bea8155c686733310009f6bed7363f91fbeb9d /libs/assimp/code/AssetLib/Collada | |
parent | 55860037b14fb3893ba21cf2654c83d349cc1082 (diff) |
move 3rd-party librarys into libs/ and add built-in honeysuckle
Diffstat (limited to 'libs/assimp/code/AssetLib/Collada')
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaExporter.cpp | 1748 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaExporter.h | 257 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaHelper.cpp | 99 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaHelper.h | 679 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaLoader.cpp | 1828 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaLoader.h | 249 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaParser.cpp | 2402 | ||||
-rw-r--r-- | libs/assimp/code/AssetLib/Collada/ColladaParser.h | 348 |
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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tName = 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 ¤tChildName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : 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 ¤tNode : 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tName = 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 ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tName = 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 ¤tName = 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 ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tName = 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 ¤tName = 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 ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 ¤tName = 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 ¤tNode : node.children()) { + const std::string ¤tName = 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 |