2019-02-08 16:25:43 -06:00
/*
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Open Asset Import Library ( assimp )
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2026-06-09 12:46:56 -05:00
Copyright ( c ) 2006 - 2026 , assimp team
2019-03-05 14:39:38 -06:00
2019-02-08 16:25:43 -06:00
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"
2019-03-05 14:39:38 -06:00
# include "ColladaParser.h"
2022-04-26 11:56:24 -05:00
# include <assimp/ColladaMetaData.h>
# include <assimp/CreateAnimMesh.h>
# include <assimp/ParsingUtils.h>
# include <assimp/SkeletonMeshBuilder.h>
# include <assimp/ZipArchiveIOSystem.h>
2019-02-08 16:25:43 -06:00
# include <assimp/anim.h>
2022-04-26 11:56:24 -05:00
# include <assimp/fast_atof.h>
# include <assimp/importerdesc.h>
2019-02-08 16:25:43 -06:00
# include <assimp/scene.h>
# include <assimp/DefaultLogger.hpp>
# include <assimp/Importer.hpp>
# include <numeric>
2019-11-10 14:40:50 +00:00
namespace Assimp {
2022-04-26 11:56:24 -05:00
2019-02-08 16:25:43 -06:00
using namespace Assimp : : Formatter ;
2022-04-26 11:56:24 -05:00
using namespace Assimp : : Collada ;
2019-02-08 16:25:43 -06:00
2024-12-09 20:22:47 +00:00
static constexpr aiImporterDesc desc = {
2019-02-08 16:25:43 -06:00
" Collada Importer " ,
" " ,
" " ,
" http://collada.org " ,
2019-11-10 14:40:50 +00:00
aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportCompressedFlavour ,
2019-02-08 16:25:43 -06:00
1 ,
3 ,
1 ,
5 ,
2022-04-26 11:56:24 -05:00
" dae xml zae "
2019-02-08 16:25:43 -06:00
} ;
2026-06-09 12:46:56 -05:00
static constexpr float kMillisecondsFromSeconds = 1000.f ;
2022-04-26 11:56:24 -05:00
// Add an item of metadata to a node
// Assumes the key is not already in the list
template < typename T >
2026-06-09 12:46:56 -05:00
void AddNodeMetaData ( aiNode * node , const std : : string & key , const T & value ) {
2022-04-26 11:56:24 -05:00
if ( nullptr = = node - > mMetaData ) {
node - > mMetaData = new aiMetadata ( ) ;
}
node - > mMetaData - > Add ( key , value ) ;
}
2026-06-09 12:46:56 -05:00
// ------------------------------------------------------------------------------------------------
// Reads a float value from an accessor and its data array.
static ai_real 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 ] ;
}
2019-02-08 16:25:43 -06:00
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
2022-04-26 11:56:24 -05:00
ColladaLoader : : ColladaLoader ( ) :
noSkeletonMesh ( false ) ,
2022-10-02 19:02:49 +01:00
removeEmptyBones ( false ) ,
2022-04-26 11:56:24 -05:00
ignoreUpDirection ( false ) ,
2024-12-09 20:22:47 +00:00
ignoreUnitSize ( false ) ,
2022-04-26 11:56:24 -05:00
useColladaName ( false ) ,
mNodeNameCounter ( 0 ) {
2019-03-05 14:39:38 -06:00
// empty
}
2019-02-08 16:25:43 -06:00
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
2022-04-26 11:56:24 -05:00
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 ( ) ;
2019-03-05 14:39:38 -06:00
}
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
static const char * tokens [ ] = { " <collada " } ;
return SearchFileHeaderForToken ( pIOHandler , pFile , tokens , AI_COUNT_OF ( tokens ) ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
2022-04-26 11:56:24 -05:00
void ColladaLoader : : SetupProperties ( const Importer * pImp ) {
2019-11-10 14:40:50 +00:00
noSkeletonMesh = pImp - > GetPropertyInteger ( AI_CONFIG_IMPORT_NO_SKELETON_MESHES , 0 ) ! = 0 ;
2022-10-02 19:02:49 +01:00
removeEmptyBones = pImp - > GetPropertyInteger ( AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES , true ) ! = 0 ;
2019-11-10 14:40:50 +00:00
ignoreUpDirection = pImp - > GetPropertyInteger ( AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION , 0 ) ! = 0 ;
2024-12-09 20:22:47 +00:00
ignoreUnitSize = pImp - > GetPropertyInteger ( AI_CONFIG_IMPORT_COLLADA_IGNORE_UNIT_SIZE , 0 ) ! = 0 ;
2019-11-10 14:40:50 +00:00
useColladaName = pImp - > GetPropertyInteger ( AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES , 0 ) ! = 0 ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Get file extension list
2022-04-26 11:56:24 -05:00
const aiImporterDesc * ColladaLoader : : GetInfo ( ) const {
2019-02-08 16:25:43 -06:00
return & desc ;
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
2022-04-26 11:56:24 -05:00
void ColladaLoader : : InternReadFile ( const std : : string & pFile , aiScene * pScene , IOSystem * pIOHandler ) {
2019-02-08 16:25:43 -06:00
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
2019-11-10 14:40:50 +00:00
ColladaParser parser ( pIOHandler , pFile ) ;
2022-04-26 11:56:24 -05:00
if ( ! parser . mRootNode ) {
throw DeadlyImportError ( " Collada: File came out empty. Something is wrong here. " ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
2026-06-09 12:46:56 -05:00
// reserve some storage to avoid unnecessary reallocates
2022-04-26 11:56:24 -05:00
newMats . reserve ( parser . mMaterialLibrary . size ( ) * 2u ) ;
mMeshes . reserve ( parser . mMeshLibrary . size ( ) * 2u ) ;
2019-02-08 16:25:43 -06:00
mCameras . reserve ( parser . mCameraLibrary . size ( ) ) ;
mLights . reserve ( parser . mLightLibrary . size ( ) ) ;
// create the materials first, for the meshes to find
2019-11-10 14:40:50 +00:00
BuildMaterials ( parser , pScene ) ;
2019-02-08 16:25:43 -06:00
// build the node hierarchy from it
2019-11-10 14:40:50 +00:00
pScene - > mRootNode = BuildHierarchy ( parser , parser . mRootNode ) ;
2019-02-08 16:25:43 -06:00
// ... then fill the materials with the now adjusted settings
FillMaterials ( parser , pScene ) ;
2024-12-09 20:22:47 +00:00
if ( ! ignoreUnitSize ) {
// 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 ) ;
}
2026-06-09 12:46:56 -05:00
2022-04-26 11:56:24 -05:00
if ( ! ignoreUpDirection ) {
2019-11-10 14:40:50 +00:00
// Convert to Y_UP, if different orientation
2022-04-26 11:56:24 -05:00
if ( parser . mUpDirection = = ColladaParser : : UP_X ) {
2019-11-10 14:40:50 +00:00
pScene - > mRootNode - > mTransformation * = aiMatrix4x4 (
2022-04-26 11:56:24 -05:00
0 , - 1 , 0 , 0 ,
1 , 0 , 0 , 0 ,
0 , 0 , 1 , 0 ,
0 , 0 , 0 , 1 ) ;
} else if ( parser . mUpDirection = = ColladaParser : : UP_Z ) {
2019-11-10 14:40:50 +00:00
pScene - > mRootNode - > mTransformation * = aiMatrix4x4 (
2022-04-26 11:56:24 -05:00
1 , 0 , 0 , 0 ,
0 , 0 , 1 , 0 ,
0 , - 1 , 0 , 0 ,
0 , 0 , 0 , 1 ) ;
2019-11-10 14:40:50 +00:00
}
}
// 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 ) ;
}
2019-03-05 14:39:38 -06:00
}
2019-11-10 14:40:50 +00:00
StoreSceneMeshes ( pScene ) ;
StoreSceneMaterials ( pScene ) ;
StoreSceneTextures ( pScene ) ;
StoreSceneLights ( pScene ) ;
StoreSceneCameras ( pScene ) ;
StoreAnimations ( pScene , parser ) ;
2019-02-08 16:25:43 -06:00
// If no meshes have been loaded, it's probably just an animated skeleton.
2022-04-26 11:56:24 -05:00
if ( 0u = = pScene - > mNumMeshes ) {
2019-02-08 16:25:43 -06:00
if ( ! noSkeletonMesh ) {
SkeletonMeshBuilder hero ( pScene ) ;
}
pScene - > mFlags | = AI_SCENE_FLAGS_INCOMPLETE ;
}
}
// ------------------------------------------------------------------------------------------------
// Recursively constructs a scene node for the given parser node and returns it.
2022-04-26 11:56:24 -05:00
aiNode * ColladaLoader : : BuildHierarchy ( const ColladaParser & pParser , const Collada : : Node * pNode ) {
2019-02-08 16:25:43 -06:00
// create a node for it
2026-06-09 12:46:56 -05:00
auto * node = new aiNode ( ) ;
2019-02-08 16:25:43 -06:00
// find a name for the new node. It's more complicated than you might think
2019-11-10 14:40:50 +00:00
node - > mName . Set ( FindNameForNode ( pNode ) ) ;
2022-04-26 11:56:24 -05:00
// 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 ) ) ;
}
}
2019-02-08 16:25:43 -06:00
// calculate the transformation matrix for it
2019-11-10 14:40:50 +00:00
node - > mTransformation = pParser . CalculateResultTransform ( pNode - > mTransforms ) ;
2019-02-08 16:25:43 -06:00
// now resolve node instances
2022-04-26 11:56:24 -05:00
std : : vector < const Node * > instances ;
2019-11-10 14:40:50 +00:00
ResolveNodeInstances ( pParser , pNode , instances ) ;
2019-02-08 16:25:43 -06:00
// add children. first the *real* ones
2019-11-10 14:40:50 +00:00
node - > mNumChildren = static_cast < unsigned int > ( pNode - > mChildren . size ( ) + instances . size ( ) ) ;
2024-12-09 20:22:47 +00:00
if ( node - > mNumChildren ! = 0 ) {
node - > mChildren = new aiNode * [ node - > mNumChildren ] ;
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
for ( size_t a = 0 ; a < pNode - > mChildren . size ( ) ; + + a ) {
node - > mChildren [ a ] = BuildHierarchy ( pParser , pNode - > mChildren [ a ] ) ;
2019-02-08 16:25:43 -06:00
node - > mChildren [ a ] - > mParent = node ;
}
// ... and finally the resolved node instances
2019-11-10 14:40:50 +00:00
for ( size_t a = 0 ; a < instances . size ( ) ; + + a ) {
node - > mChildren [ pNode - > mChildren . size ( ) + a ] = BuildHierarchy ( pParser , instances [ a ] ) ;
2019-02-08 16:25:43 -06:00
node - > mChildren [ pNode - > mChildren . size ( ) + a ] - > mParent = node ;
}
2019-11-10 14:40:50 +00:00
BuildMeshesForNode ( pParser , pNode , node ) ;
2019-02-08 16:25:43 -06:00
BuildCamerasForNode ( pParser , pNode , node ) ;
BuildLightsForNode ( pParser , pNode , node ) ;
2019-11-10 14:40:50 +00:00
2019-02-08 16:25:43 -06:00
return node ;
}
// ------------------------------------------------------------------------------------------------
// Resolve node instances
2022-04-26 11:56:24 -05:00
void ColladaLoader : : ResolveNodeInstances ( const ColladaParser & pParser , const Node * pNode ,
2026-06-09 12:46:56 -05:00
std : : vector < const Node * > & resolved ) const {
2019-02-08 16:25:43 -06:00
// reserve enough storage
resolved . reserve ( pNode - > mNodeInstances . size ( ) ) ;
// ... and iterate through all nodes to be instanced as children of pNode
2026-06-09 12:46:56 -05:00
for ( const auto & [ mNode ] : pNode - > mNodeInstances ) {
2019-02-08 16:25:43 -06:00
// find the corresponding node in the library
2026-06-09 12:46:56 -05:00
const auto itt = pParser . mNodeLibrary . find ( mNode ) ;
2022-04-26 11:56:24 -05:00
const Node * nd = itt = = pParser . mNodeLibrary . end ( ) ? nullptr : ( * itt ) . second ;
2019-02-08 16:25:43 -06:00
// 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.
2019-11-10 14:40:50 +00:00
if ( nullptr = = nd ) {
2026-06-09 12:46:56 -05:00
nd = FindNode ( pParser . mRootNode , mNode ) ;
2019-02-08 16:25:43 -06:00
}
2019-11-10 14:40:50 +00:00
if ( nullptr = = nd ) {
2026-06-09 12:46:56 -05:00
ASSIMP_LOG_ERROR ( " Collada: Unable to resolve reference to instanced node " , mNode ) ;
2019-11-10 14:40:50 +00:00
} else {
2019-02-08 16:25:43 -06:00
// attach this node to the list of children
resolved . push_back ( nd ) ;
}
}
}
// ------------------------------------------------------------------------------------------------
// Resolve UV channels
2026-06-09 12:46:56 -05:00
static void ApplyVertexToEffectSemanticMapping ( Sampler & sampler , const SemanticMappingTable & table ) {
const auto it = table . mMap . find ( sampler . mUVChannel ) ;
2022-04-26 11:56:24 -05:00
if ( it = = table . mMap . end ( ) ) {
return ;
}
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
if ( it - > second . mType ! = IT_Texcoord ) {
ASSIMP_LOG_ERROR ( " Collada: Unexpected effect input mapping " ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
sampler . mUVId = it - > second . mSet ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Builds lights for the given node and references them
2022-04-26 11:56:24 -05:00
void ColladaLoader : : BuildLightsForNode ( const ColladaParser & pParser , const Node * pNode , aiNode * pTarget ) {
for ( const LightInstance & lid : pNode - > mLights ) {
2019-02-08 16:25:43 -06:00
// find the referred light
2026-06-09 12:46:56 -05:00
auto srcLightIt = pParser . mLightLibrary . find ( lid . mLight ) ;
2019-11-10 14:40:50 +00:00
if ( srcLightIt = = pParser . mLightLibrary . end ( ) ) {
2022-04-26 11:56:24 -05:00
ASSIMP_LOG_WARN ( " Collada: Unable to find light for ID \" " , lid . mLight , " \" . Skipping. " ) ;
2019-02-08 16:25:43 -06:00
continue ;
}
2022-04-26 11:56:24 -05:00
const Collada : : Light * srcLight = & srcLightIt - > second ;
2019-02-08 16:25:43 -06:00
// now fill our ai data structure
2026-06-09 12:46:56 -05:00
auto out = new aiLight ( ) ;
2019-02-08 16:25:43 -06:00
out - > mName = pTarget - > mName ;
out - > mType = ( aiLightSourceType ) srcLight - > mType ;
// collada lights point in -Z by default, rest is specified in node transform
2019-11-10 14:40:50 +00:00
out - > mDirection = aiVector3D ( 0.f , 0.f , - 1.f ) ;
2019-02-08 16:25:43 -06:00
out - > mAttenuationConstant = srcLight - > mAttConstant ;
out - > mAttenuationLinear = srcLight - > mAttLinear ;
out - > mAttenuationQuadratic = srcLight - > mAttQuadratic ;
2022-04-26 11:56:24 -05:00
out - > mColorDiffuse = out - > mColorSpecular = out - > mColorAmbient = srcLight - > mColor * srcLight - > mIntensity ;
2019-02-08 16:25:43 -06:00
if ( out - > mType = = aiLightSource_AMBIENT ) {
out - > mColorDiffuse = out - > mColorSpecular = aiColor3D ( 0 , 0 , 0 ) ;
2022-04-26 11:56:24 -05:00
out - > mColorAmbient = srcLight - > mColor * srcLight - > mIntensity ;
} else {
2019-02-08 16:25:43 -06:00
// collada doesn't differentiate between these color types
2022-04-26 11:56:24 -05:00
out - > mColorDiffuse = out - > mColorSpecular = srcLight - > mColor * srcLight - > mIntensity ;
2019-02-08 16:25:43 -06:00
out - > mColorAmbient = aiColor3D ( 0 , 0 , 0 ) ;
}
// convert falloff angle and falloff exponent in our representation, if given
if ( out - > mType = = aiLightSource_SPOT ) {
2019-11-10 14:40:50 +00:00
out - > mAngleInnerCone = AI_DEG_TO_RAD ( srcLight - > mFalloffAngle ) ;
2019-02-08 16:25:43 -06:00
// ... some extension magic.
2022-04-26 11:56:24 -05:00
if ( srcLight - > mOuterAngle > = ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * ( 1 - ai_epsilon ) ) {
2019-02-08 16:25:43 -06:00
// ... some deprecation magic.
2022-04-26 11:56:24 -05:00
if ( srcLight - > mPenumbraAngle > = ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET * ( 1 - ai_epsilon ) ) {
2019-02-08 16:25:43 -06:00
// 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
2022-04-26 11:56:24 -05:00
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 {
2019-11-10 14:40:50 +00:00
out - > mAngleOuterCone = out - > mAngleInnerCone + AI_DEG_TO_RAD ( srcLight - > mPenumbraAngle ) ;
2019-02-08 16:25:43 -06:00
if ( out - > mAngleOuterCone < out - > mAngleInnerCone )
2019-11-10 14:40:50 +00:00
std : : swap ( out - > mAngleInnerCone , out - > mAngleOuterCone ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
} else {
2019-11-10 14:40:50 +00:00
out - > mAngleOuterCone = AI_DEG_TO_RAD ( srcLight - > mOuterAngle ) ;
}
2019-02-08 16:25:43 -06:00
}
// add to light list
mLights . push_back ( out ) ;
}
}
// ------------------------------------------------------------------------------------------------
// Builds cameras for the given node and references them
2022-04-26 11:56:24 -05:00
void ColladaLoader : : BuildCamerasForNode ( const ColladaParser & pParser , const Node * pNode , aiNode * pTarget ) {
for ( const CameraInstance & cid : pNode - > mCameras ) {
2019-02-08 16:25:43 -06:00
// find the referred light
2026-06-09 12:46:56 -05:00
auto srcCameraIt = pParser . mCameraLibrary . find ( cid . mCamera ) ;
2019-11-10 14:40:50 +00:00
if ( srcCameraIt = = pParser . mCameraLibrary . end ( ) ) {
2022-04-26 11:56:24 -05:00
ASSIMP_LOG_WARN ( " Collada: Unable to find camera for ID \" " , cid . mCamera , " \" . Skipping. " ) ;
2019-02-08 16:25:43 -06:00
continue ;
}
2022-04-26 11:56:24 -05:00
const Collada : : Camera * srcCamera = & srcCameraIt - > second ;
2019-02-08 16:25:43 -06:00
// orthographic cameras not yet supported in Assimp
if ( srcCamera - > mOrtho ) {
2019-03-05 14:39:38 -06:00
ASSIMP_LOG_WARN ( " Collada: Orthographic cameras are not supported. " ) ;
2019-02-08 16:25:43 -06:00
}
// now fill our ai data structure
2026-06-09 12:46:56 -05:00
auto * out = new aiCamera ( ) ;
2019-02-08 16:25:43 -06:00
out - > mName = pTarget - > mName ;
// collada cameras point in -Z by default, rest is specified in node transform
2019-11-10 14:40:50 +00:00
out - > mLookAt = aiVector3D ( 0.f , 0.f , - 1.f ) ;
2019-02-08 16:25:43 -06:00
// 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.
2019-11-10 14:40:50 +00:00
if ( srcCamera - > mAspect ! = 10e10 f ) {
2019-02-08 16:25:43 -06:00
out - > mAspect = srcCamera - > mAspect ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
if ( srcCamera - > mHorFov ! = 10e10 f ) {
out - > mHorizontalFOV = srcCamera - > mHorFov ;
if ( srcCamera - > mVerFov ! = 10e10 f & & srcCamera - > mAspect = = 10e10 f ) {
out - > mAspect = std : : tan ( AI_DEG_TO_RAD ( srcCamera - > mHorFov ) ) /
2022-04-26 11:56:24 -05:00
std : : tan ( AI_DEG_TO_RAD ( srcCamera - > mVerFov ) ) ;
2019-02-08 16:25:43 -06:00
}
2019-11-10 14:40:50 +00:00
2022-04-26 11:56:24 -05:00
} else if ( srcCamera - > mAspect ! = 10e10 f & & srcCamera - > mVerFov ! = 10e10 f ) {
2019-02-08 16:25:43 -06:00
out - > mHorizontalFOV = 2.0f * AI_RAD_TO_DEG ( std : : atan ( srcCamera - > mAspect *
2022-04-26 11:56:24 -05:00
std : : tan ( AI_DEG_TO_RAD ( srcCamera - > mVerFov ) * 0.5f ) ) ) ;
2019-02-08 16:25:43 -06:00
}
// 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
2022-04-26 11:56:24 -05:00
void ColladaLoader : : BuildMeshesForNode ( const ColladaParser & pParser , const Node * pNode , aiNode * pTarget ) {
2019-02-08 16:25:43 -06:00
// 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
2022-04-26 11:56:24 -05:00
for ( const MeshInstance & mid : pNode - > mMeshes ) {
const Mesh * srcMesh = nullptr ;
const Controller * srcController = nullptr ;
2019-02-08 16:25:43 -06:00
// find the referred mesh
2026-06-09 12:46:56 -05:00
auto srcMeshIt = pParser . mMeshLibrary . find ( mid . mMeshOrController ) ;
2019-11-10 14:40:50 +00:00
if ( srcMeshIt = = pParser . mMeshLibrary . end ( ) ) {
2019-02-08 16:25:43 -06:00
// if not found in the mesh-library, it might also be a controller referring to a mesh
2026-06-09 12:46:56 -05:00
auto srcContrIt = pParser . mControllerLibrary . find ( mid . mMeshOrController ) ;
2019-11-10 14:40:50 +00:00
if ( srcContrIt ! = pParser . mControllerLibrary . end ( ) ) {
2019-02-08 16:25:43 -06:00
srcController = & srcContrIt - > second ;
2019-11-10 14:40:50 +00:00
srcMeshIt = pParser . mMeshLibrary . find ( srcController - > mMeshId ) ;
if ( srcMeshIt ! = pParser . mMeshLibrary . end ( ) ) {
2019-02-08 16:25:43 -06:00
srcMesh = srcMeshIt - > second ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
if ( nullptr = = srcMesh ) {
ASSIMP_LOG_WARN ( " Collada: Unable to find geometry for ID \" " , mid . mMeshOrController , " \" . Skipping. " ) ;
2019-02-08 16:25:43 -06:00
continue ;
}
2022-04-26 11:56:24 -05:00
} else {
2026-06-09 12:46:56 -05:00
// ID found in the mesh library -> direct reference to a not skinned mesh
2019-02-08 16:25:43 -06:00
srcMesh = srcMeshIt - > second ;
}
// build a mesh for each of its subgroups
size_t vertexStart = 0 , faceStart = 0 ;
2019-11-10 14:40:50 +00:00
for ( size_t sm = 0 ; sm < srcMesh - > mSubMeshes . size ( ) ; + + sm ) {
2022-04-26 11:56:24 -05:00
const Collada : : SubMesh & submesh = srcMesh - > mSubMeshes [ sm ] ;
2019-11-10 14:40:50 +00:00
if ( submesh . mNumFaces = = 0 ) {
2019-02-08 16:25:43 -06:00
continue ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
// find material assigned to this submesh
std : : string meshMaterial ;
2026-06-09 12:46:56 -05:00
auto meshMatIt = mid . mMaterials . find ( submesh . mMaterial ) ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
const Collada : : SemanticMappingTable * table = nullptr ;
2019-11-10 14:40:50 +00:00
if ( meshMatIt ! = mid . mMaterials . end ( ) ) {
2019-02-08 16:25:43 -06:00
table = & meshMatIt - > second ;
meshMaterial = table - > mMatName ;
2022-04-26 11:56:24 -05:00
} else {
ASSIMP_LOG_WARN ( " Collada: No material specified for subgroup < " , submesh . mMaterial , " > in geometry < " ,
mid . mMeshOrController , " >. " ) ;
2019-11-10 14:40:50 +00:00
if ( ! mid . mMaterials . empty ( ) ) {
2019-02-08 16:25:43 -06:00
meshMaterial = mid . mMaterials . begin ( ) - > second . mMatName ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
}
// 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.
2026-06-09 12:46:56 -05:00
auto matIt = mMaterialIndexByName . find ( meshMaterial ) ;
2019-11-10 14:40:50 +00:00
unsigned int matIdx = 0 ;
if ( matIt ! = mMaterialIndexByName . end ( ) ) {
2019-02-08 16:25:43 -06:00
matIdx = static_cast < unsigned int > ( matIt - > second ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( table & & ! table - > mMap . empty ( ) ) {
2026-06-09 12:46:56 -05:00
if ( matIdx < newMats . size ( ) ) {
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 ) ;
} else {
ASSIMP_LOG_WARN ( " Collada: Ignoring material mapping for mesh \" " , mid . mMeshOrController , " \" . Material index " , matIdx , " is out of bounds (newMats.size()= " , newMats . size ( ) , " ). " ) ;
}
2019-02-08 16:25:43 -06:00
}
// built lookup index of the Mesh-Submesh-Material combination
2019-11-10 14:40:50 +00:00
ColladaMeshIndex index ( mid . mMeshOrController , sm , meshMaterial ) ;
2019-02-08 16:25:43 -06:00
// if we already have the mesh at the library, just add its index to the node's array
2026-06-09 12:46:56 -05:00
auto dstMeshIt = mMeshIndexByID . find ( index ) ;
2019-11-10 14:40:50 +00:00
if ( dstMeshIt ! = mMeshIndexByID . end ( ) ) {
newMeshRefs . push_back ( dstMeshIt - > second ) ;
2022-04-26 11:56:24 -05:00
} else {
2019-02-08 16:25:43 -06:00
// else we have to add the mesh to the collection and store its newly assigned index at the node
2022-04-26 11:56:24 -05:00
aiMesh * dstMesh = CreateMesh ( pParser , srcMesh , submesh , srcController , vertexStart , faceStart ) ;
2019-02-08 16:25:43 -06:00
// store the mesh, and store its new index in the node
2019-11-10 14:40:50 +00:00
newMeshRefs . push_back ( mMeshes . size ( ) ) ;
2019-02-08 16:25:43 -06:00
mMeshIndexByID [ index ] = mMeshes . size ( ) ;
2019-11-10 14:40:50 +00:00
mMeshes . push_back ( dstMesh ) ;
2022-04-26 11:56:24 -05:00
vertexStart + = dstMesh - > mNumVertices ;
faceStart + = submesh . mNumFaces ;
2019-02-08 16:25:43 -06:00
// assign the material index
2026-06-09 12:46:56 -05:00
auto subMatIt = mMaterialIndexByName . find ( submesh . mMaterial ) ;
2022-04-26 11:56:24 -05:00
if ( subMatIt ! = mMaterialIndexByName . end ( ) ) {
dstMesh - > mMaterialIndex = static_cast < unsigned int > ( subMatIt - > second ) ;
} else {
dstMesh - > mMaterialIndex = matIdx ;
}
2019-11-10 14:40:50 +00:00
if ( dstMesh - > mName . length = = 0 ) {
2019-02-08 16:25:43 -06:00
dstMesh - > mName = mid . mMeshOrController ;
}
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
}
}
// now place all mesh references we gathered in the target node
pTarget - > mNumMeshes = static_cast < unsigned int > ( newMeshRefs . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( ! newMeshRefs . empty ( ) ) {
2019-11-10 14:40:50 +00:00
struct UIntTypeConverter {
2022-04-26 11:56:24 -05:00
unsigned int operator ( ) ( const size_t & v ) const {
2019-02-08 16:25:43 -06:00
return static_cast < unsigned int > ( v ) ;
}
} ;
pTarget - > mMeshes = new unsigned int [ pTarget - > mNumMeshes ] ;
2019-11-10 14:40:50 +00:00
std : : transform ( newMeshRefs . begin ( ) , newMeshRefs . end ( ) , pTarget - > mMeshes , UIntTypeConverter ( ) ) ;
2019-02-08 16:25:43 -06:00
}
}
// ------------------------------------------------------------------------------------------------
// Find mesh from either meshes or morph target meshes
2022-04-26 11:56:24 -05:00
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 ;
2019-11-10 14:40:50 +00:00
}
}
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
for ( auto & mTargetMeshe : mTargetMeshes ) {
if ( std : : string ( mTargetMeshe - > mName . data ) = = meshid ) {
return mTargetMeshe ;
2019-11-10 14:40:50 +00:00
}
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
return nullptr ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh
2022-04-26 11:56:24 -05:00
aiMesh * ColladaLoader : : CreateMesh ( const ColladaParser & pParser , const Mesh * pSrcMesh , const SubMesh & pSubMesh ,
const Controller * pSrcController , size_t pStartVertex , size_t pStartFace ) {
2019-11-10 14:40:50 +00:00
std : : unique_ptr < aiMesh > dstMesh ( new aiMesh ) ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
if ( useColladaName ) {
dstMesh - > mName = pSrcMesh - > mName ;
} else {
dstMesh - > mName = pSrcMesh - > mId ;
}
if ( pSrcMesh - > mPositions . empty ( ) ) {
return dstMesh . release ( ) ;
}
2019-02-08 16:25:43 -06:00
// count the vertices addressed by its faces
2019-11-10 14:40:50 +00:00
const size_t numVertices = std : : accumulate ( pSrcMesh - > mFaceSize . begin ( ) + pStartFace ,
2022-04-26 11:56:24 -05:00
pSrcMesh - > mFaceSize . begin ( ) + pStartFace + pSubMesh . mNumFaces , size_t ( 0 ) ) ;
2019-02-08 16:25:43 -06:00
// copy positions
dstMesh - > mNumVertices = static_cast < unsigned int > ( numVertices ) ;
dstMesh - > mVertices = new aiVector3D [ numVertices ] ;
2022-04-26 11:56:24 -05:00
std : : copy ( pSrcMesh - > mPositions . begin ( ) + pStartVertex , pSrcMesh - > mPositions . begin ( ) + pStartVertex + numVertices , dstMesh - > mVertices ) ;
2019-02-08 16:25:43 -06:00
// 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
2019-11-10 14:40:50 +00:00
if ( pSrcMesh - > mNormals . size ( ) > = pStartVertex + numVertices ) {
2019-02-08 16:25:43 -06:00
dstMesh - > mNormals = new aiVector3D [ numVertices ] ;
2022-04-26 11:56:24 -05:00
std : : copy ( pSrcMesh - > mNormals . begin ( ) + pStartVertex , pSrcMesh - > mNormals . begin ( ) + pStartVertex + numVertices , dstMesh - > mNormals ) ;
2019-02-08 16:25:43 -06:00
}
// tangents, if given.
2019-11-10 14:40:50 +00:00
if ( pSrcMesh - > mTangents . size ( ) > = pStartVertex + numVertices ) {
2019-02-08 16:25:43 -06:00
dstMesh - > mTangents = new aiVector3D [ numVertices ] ;
2022-04-26 11:56:24 -05:00
std : : copy ( pSrcMesh - > mTangents . begin ( ) + pStartVertex , pSrcMesh - > mTangents . begin ( ) + pStartVertex + numVertices , dstMesh - > mTangents ) ;
2019-02-08 16:25:43 -06:00
}
2026-06-09 12:46:56 -05:00
// bi-tangents, if given.
2019-11-10 14:40:50 +00:00
if ( pSrcMesh - > mBitangents . size ( ) > = pStartVertex + numVertices ) {
2019-02-08 16:25:43 -06:00
dstMesh - > mBitangents = new aiVector3D [ numVertices ] ;
2022-04-26 11:56:24 -05:00
std : : copy ( pSrcMesh - > mBitangents . begin ( ) + pStartVertex , pSrcMesh - > mBitangents . begin ( ) + pStartVertex + numVertices , dstMesh - > mBitangents ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
// same for texture coords, as many as we have
2024-12-09 20:22:47 +00:00
for ( size_t a = 0 ; a < AI_MAX_NUMBER_OF_TEXTURECOORDS ; + + a ) {
2019-11-10 14:40:50 +00:00
if ( pSrcMesh - > mTexCoords [ a ] . size ( ) > = pStartVertex + numVertices ) {
2024-12-09 20:22:47 +00:00
dstMesh - > mTextureCoords [ a ] = new aiVector3D [ numVertices ] ;
2019-11-10 14:40:50 +00:00
for ( size_t b = 0 ; b < numVertices ; + + b ) {
2024-12-09 20:22:47 +00:00
dstMesh - > mTextureCoords [ a ] [ b ] = pSrcMesh - > mTexCoords [ a ] [ pStartVertex + b ] ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
2024-12-09 20:22:47 +00:00
dstMesh - > mNumUVComponents [ a ] = pSrcMesh - > mNumUVComponents [ a ] ;
2019-02-08 16:25:43 -06:00
}
}
// same for vertex colors, as many as we have. again the same packing to avoid empty slots
2019-11-10 14:40:50 +00:00
for ( size_t a = 0 , real = 0 ; a < AI_MAX_NUMBER_OF_COLOR_SETS ; + + a ) {
if ( pSrcMesh - > mColors [ a ] . size ( ) > = pStartVertex + numVertices ) {
2019-02-08 16:25:43 -06:00
dstMesh - > mColors [ real ] = new aiColor4D [ numVertices ] ;
2019-11-10 14:40:50 +00:00
std : : copy ( pSrcMesh - > mColors [ a ] . begin ( ) + pStartVertex , pSrcMesh - > mColors [ a ] . begin ( ) + pStartVertex + numVertices , dstMesh - > mColors [ real ] ) ;
2019-02-08 16:25:43 -06:00
+ + 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 ] ;
2019-11-10 14:40:50 +00:00
for ( size_t a = 0 ; a < dstMesh - > mNumFaces ; + + a ) {
size_t s = pSrcMesh - > mFaceSize [ pStartFace + a ] ;
2022-04-26 11:56:24 -05:00
aiFace & face = dstMesh - > mFaces [ a ] ;
2019-02-08 16:25:43 -06:00
face . mNumIndices = static_cast < unsigned int > ( s ) ;
face . mIndices = new unsigned int [ s ] ;
2019-11-10 14:40:50 +00:00
for ( size_t b = 0 ; b < s ; + + b ) {
2019-02-08 16:25:43 -06:00
face . mIndices [ b ] = static_cast < unsigned int > ( vertex + + ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
}
// create morph target meshes if any
2022-04-26 11:56:24 -05:00
std : : vector < aiMesh * > targetMeshes ;
2019-02-08 16:25:43 -06:00
std : : vector < float > targetWeights ;
2022-04-26 11:56:24 -05:00
Collada : : MorphMethod method = Normalized ;
2019-02-08 16:25:43 -06:00
2026-06-09 12:46:56 -05:00
for ( auto it = pParser . mControllerLibrary . begin ( ) ;
2022-04-26 11:56:24 -05:00
it ! = pParser . mControllerLibrary . end ( ) ; + + it ) {
const Controller & c = it - > second ;
const Collada : : Mesh * baseMesh = pParser . ResolveLibraryReference ( pParser . mMeshLibrary , c . mMeshId ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( c . mType = = Collada : : Morph & & baseMesh - > mName = = pSrcMesh - > mName ) {
2022-04-26 11:56:24 -05:00
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 ) ;
2019-02-08 16:25:43 -06:00
// take method
method = c . mMethod ;
2019-11-10 14:40:50 +00:00
if ( ! targetData . mIsStringArray ) {
throw DeadlyImportError ( " target data must contain id. " ) ;
}
if ( weightData . mIsStringArray ) {
throw DeadlyImportError ( " target weight data must not be textual " ) ;
}
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
for ( const auto & mString : targetData . mStrings ) {
const Mesh * targetMesh = pParser . ResolveLibraryReference ( pParser . mMeshLibrary , mString ) ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
aiMesh * aimesh = findMesh ( useColladaName ? targetMesh - > mName : targetMesh - > mId ) ;
2019-11-10 14:40:50 +00:00
if ( ! aimesh ) {
if ( targetMesh - > mSubMeshes . size ( ) > 1 ) {
2022-04-26 11:56:24 -05:00
throw DeadlyImportError ( " Morphing target mesh must be a single " ) ;
2019-11-10 14:40:50 +00:00
}
2022-04-26 11:56:24 -05:00
aimesh = CreateMesh ( pParser , targetMesh , targetMesh - > mSubMeshes . at ( 0 ) , nullptr , 0 , 0 ) ;
2019-02-08 16:25:43 -06:00
mTargetMeshes . push_back ( aimesh ) ;
}
targetMeshes . push_back ( aimesh ) ;
}
2022-04-26 11:56:24 -05:00
for ( float mValue : weightData . mValues ) {
targetWeights . push_back ( mValue ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
}
}
2022-04-26 11:56:24 -05:00
if ( ! targetMeshes . empty ( ) & & targetWeights . size ( ) = = targetMeshes . size ( ) ) {
std : : vector < aiAnimMesh * > animMeshes ;
2019-11-10 14:40:50 +00:00
for ( unsigned int i = 0 ; i < targetMeshes . size ( ) ; + + i ) {
2022-04-26 11:56:24 -05:00
aiMesh * targetMesh = targetMeshes . at ( i ) ;
2019-03-05 14:39:38 -06:00
aiAnimMesh * animMesh = aiCreateAnimMesh ( targetMesh ) ;
float weight = targetWeights [ i ] ;
animMesh - > mWeight = weight = = 0 ? 1.0f : weight ;
animMesh - > mName = targetMesh - > mName ;
2019-02-08 16:25:43 -06:00
animMeshes . push_back ( animMesh ) ;
}
2022-04-26 11:56:24 -05:00
dstMesh - > mMethod = ( method = = Relative ) ? aiMorphingMethod_MORPH_RELATIVE : aiMorphingMethod_MORPH_NORMALIZED ;
dstMesh - > mAnimMeshes = new aiAnimMesh * [ animMeshes . size ( ) ] ;
2019-02-08 16:25:43 -06:00
dstMesh - > mNumAnimMeshes = static_cast < unsigned int > ( animMeshes . size ( ) ) ;
2019-11-10 14:40:50 +00:00
for ( unsigned int i = 0 ; i < animMeshes . size ( ) ; + + i ) {
2019-02-08 16:25:43 -06:00
dstMesh - > mAnimMeshes [ i ] = animMeshes . at ( i ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
}
// create bones if given
2019-11-10 14:40:50 +00:00
if ( pSrcController & & pSrcController - > mType = = Collada : : Skin ) {
2019-02-08 16:25:43 -06:00
// resolve references - joint names
2022-04-26 11:56:24 -05:00
const Collada : : Accessor & jointNamesAcc = pParser . ResolveLibraryReference ( pParser . mAccessorLibrary , pSrcController - > mJointNameSource ) ;
const Collada : : Data & jointNames = pParser . ResolveLibraryReference ( pParser . mDataLibrary , jointNamesAcc . mSource ) ;
2019-02-08 16:25:43 -06:00
// joint offset matrices
2022-04-26 11:56:24 -05:00
const Collada : : Accessor & jointMatrixAcc = pParser . ResolveLibraryReference ( pParser . mAccessorLibrary , pSrcController - > mJointOffsetMatrixSource ) ;
const Collada : : Data & jointMatrices = pParser . ResolveLibraryReference ( pParser . mDataLibrary , jointMatrixAcc . mSource ) ;
2019-02-08 16:25:43 -06:00
// joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider
2022-04-26 11:56:24 -05:00
const Collada : : Accessor & weightNamesAcc = pParser . ResolveLibraryReference ( pParser . mAccessorLibrary , pSrcController - > mWeightInputJoints . mAccessor ) ;
2019-11-10 14:40:50 +00:00
if ( & weightNamesAcc ! = & jointNamesAcc )
throw DeadlyImportError ( " Temporary implementational laziness. If you read this, please report to the author. " ) ;
2019-02-08 16:25:43 -06:00
// vertex weights
2022-04-26 11:56:24 -05:00
const Collada : : Accessor & weightsAcc = pParser . ResolveLibraryReference ( pParser . mAccessorLibrary , pSrcController - > mWeightInputWeights . mAccessor ) ;
const Collada : : Data & weights = pParser . ResolveLibraryReference ( pParser . mDataLibrary , weightsAcc . mSource ) ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
if ( ! jointNames . mIsStringArray | | jointMatrices . mIsStringArray | | weights . mIsStringArray ) {
2019-11-10 14:40:50 +00:00
throw DeadlyImportError ( " Data type mismatch while resolving mesh joints " ) ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex
2022-04-26 11:56:24 -05:00
if ( pSrcController - > mWeightInputJoints . mOffset ! = 0 | | pSrcController - > mWeightInputWeights . mOffset ! = 1 ) {
2019-11-10 14:40:50 +00:00
throw DeadlyImportError ( " Unsupported vertex_weight addressing scheme. " ) ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// create containers to collect the weights for each bone
size_t numBones = jointNames . mStrings . size ( ) ;
2022-04-26 11:56:24 -05:00
std : : vector < std : : vector < aiVertexWeight > > dstBones ( numBones ) ;
2019-02-08 16:25:43 -06:00
// build a temporary array of pointers to the start of each vertex's weights
2022-04-26 11:56:24 -05:00
using IndexPairVector = std : : vector < std : : pair < size_t , size_t > > ;
2019-02-08 16:25:43 -06:00
std : : vector < IndexPairVector : : const_iterator > weightStartPerVertex ;
2019-11-10 14:40:50 +00:00
weightStartPerVertex . resize ( pSrcController - > mWeightCounts . size ( ) , pSrcController - > mWeights . end ( ) ) ;
2019-02-08 16:25:43 -06:00
2026-06-09 12:46:56 -05:00
auto pit = pSrcController - > mWeights . begin ( ) ;
2019-11-10 14:40:50 +00:00
for ( size_t a = 0 ; a < pSrcController - > mWeightCounts . size ( ) ; + + a ) {
2019-02-08 16:25:43 -06:00
weightStartPerVertex [ a ] = pit ;
pit + = pSrcController - > mWeightCounts [ a ] ;
}
// now for each vertex put the corresponding vertex weights into each bone's weight collection
2019-11-10 14:40:50 +00:00
for ( size_t a = pStartVertex ; a < pStartVertex + numVertices ; + + a ) {
2019-02-08 16:25:43 -06:00
// 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
2026-06-09 12:46:56 -05:00
auto iit = weightStartPerVertex [ orgIndex ] ;
2019-02-08 16:25:43 -06:00
size_t pairCount = pSrcController - > mWeightCounts [ orgIndex ] ;
2022-04-26 11:56:24 -05:00
for ( size_t b = 0 ; b < pairCount ; + + b , + + iit ) {
2019-11-10 14:40:50 +00:00
const size_t jointIndex = iit - > first ;
2022-04-26 11:56:24 -05:00
const size_t vertexIndex = iit - > second ;
2019-11-10 14:40:50 +00:00
ai_real weight = 1.0f ;
if ( ! weights . mValues . empty ( ) ) {
weight = ReadFloat ( weightsAcc , weights , vertexIndex , 0 ) ;
}
2019-02-08 16:25:43 -06:00
// one day I gonna kill that XSI Collada exporter
2022-04-26 11:56:24 -05:00
if ( weight > 0.0f ) {
2019-02-08 16:25:43 -06:00
aiVertexWeight w ;
w . mVertexId = static_cast < unsigned int > ( a - pStartVertex ) ;
w . mWeight = weight ;
2019-11-10 14:40:50 +00:00
dstBones [ jointIndex ] . push_back ( w ) ;
2019-02-08 16:25:43 -06:00
}
}
}
// count the number of bones which influence vertices of the current submesh
size_t numRemainingBones = 0 ;
2022-04-26 11:56:24 -05:00
for ( const auto & dstBone : dstBones ) {
2022-10-02 19:02:49 +01:00
if ( dstBone . empty ( ) & & removeEmptyBones ) {
continue ;
2019-11-10 14:40:50 +00:00
}
2022-10-02 19:02:49 +01:00
+ + numRemainingBones ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
// create bone array and copy bone weights one by one
dstMesh - > mNumBones = static_cast < unsigned int > ( numRemainingBones ) ;
2022-04-26 11:56:24 -05:00
dstMesh - > mBones = new aiBone * [ numRemainingBones ] ;
2019-02-08 16:25:43 -06:00
size_t boneCount = 0 ;
2022-04-26 11:56:24 -05:00
for ( size_t a = 0 ; a < numBones ; + + a ) {
2019-02-08 16:25:43 -06:00
// omit bones without weights
2022-10-02 19:02:49 +01:00
if ( dstBones [ a ] . empty ( ) & & removeEmptyBones ) {
2019-02-08 16:25:43 -06:00
continue ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
// create bone with its weights
2026-06-09 12:46:56 -05:00
auto bone = new aiBone ;
2019-11-10 14:40:50 +00:00
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 ) ;
2019-02-08 16:25:43 -06:00
bone - > mNumWeights = static_cast < unsigned int > ( dstBones [ a ] . size ( ) ) ;
bone - > mWeights = new aiVertexWeight [ bone - > mNumWeights ] ;
2019-11-10 14:40:50 +00:00
std : : copy ( dstBones [ a ] . begin ( ) , dstBones [ a ] . end ( ) , bone - > mWeights ) ;
2019-02-08 16:25:43 -06:00
// 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.
2022-04-26 11:56:24 -05:00
const Collada : : Node * bnode = FindNode ( pParser . mRootNode , bone - > mName . data ) ;
if ( nullptr = = bnode ) {
bnode = FindNodeBySID ( pParser . mRootNode , bone - > mName . data ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
// assign the name that we would have assigned for the source node
2022-04-26 11:56:24 -05:00
if ( nullptr ! = bnode ) {
bone - > mName . Set ( FindNameForNode ( bnode ) ) ;
2019-11-10 14:40:50 +00:00
} else {
2022-04-26 11:56:24 -05:00
ASSIMP_LOG_WARN ( " ColladaLoader::CreateMesh(): could not find corresponding node for joint \" " , bone - > mName . data , " \" . " ) ;
2019-11-10 14:40:50 +00:00
}
2019-02-08 16:25:43 -06:00
// and insert bone
dstMesh - > mBones [ boneCount + + ] = bone ;
}
}
2019-11-10 14:40:50 +00:00
return dstMesh . release ( ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Stores all meshes in the given scene
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreSceneMeshes ( aiScene * pScene ) {
2019-02-08 16:25:43 -06:00
pScene - > mNumMeshes = static_cast < unsigned int > ( mMeshes . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( mMeshes . empty ( ) ) {
2019-11-10 14:40:50 +00:00
return ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
pScene - > mMeshes = new aiMesh * [ mMeshes . size ( ) ] ;
std : : copy ( mMeshes . begin ( ) , mMeshes . end ( ) , pScene - > mMeshes ) ;
2019-11-10 14:40:50 +00:00
mMeshes . clear ( ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Stores all cameras in the given scene
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreSceneCameras ( aiScene * pScene ) {
2019-02-08 16:25:43 -06:00
pScene - > mNumCameras = static_cast < unsigned int > ( mCameras . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( mCameras . empty ( ) ) {
2019-11-10 14:40:50 +00:00
return ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
pScene - > mCameras = new aiCamera * [ mCameras . size ( ) ] ;
std : : copy ( mCameras . begin ( ) , mCameras . end ( ) , pScene - > mCameras ) ;
2019-11-10 14:40:50 +00:00
mCameras . clear ( ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Stores all lights in the given scene
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreSceneLights ( aiScene * pScene ) {
2019-02-08 16:25:43 -06:00
pScene - > mNumLights = static_cast < unsigned int > ( mLights . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( mLights . empty ( ) ) {
2019-11-10 14:40:50 +00:00
return ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
pScene - > mLights = new aiLight * [ mLights . size ( ) ] ;
std : : copy ( mLights . begin ( ) , mLights . end ( ) , pScene - > mLights ) ;
2019-11-10 14:40:50 +00:00
mLights . clear ( ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Stores all textures in the given scene
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreSceneTextures ( aiScene * pScene ) {
2019-02-08 16:25:43 -06:00
pScene - > mNumTextures = static_cast < unsigned int > ( mTextures . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( mTextures . empty ( ) ) {
2019-11-10 14:40:50 +00:00
return ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
pScene - > mTextures = new aiTexture * [ mTextures . size ( ) ] ;
std : : copy ( mTextures . begin ( ) , mTextures . end ( ) , pScene - > mTextures ) ;
2019-11-10 14:40:50 +00:00
mTextures . clear ( ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Stores all materials in the given scene
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreSceneMaterials ( aiScene * pScene ) {
2019-02-08 16:25:43 -06:00
pScene - > mNumMaterials = static_cast < unsigned int > ( newMats . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( newMats . empty ( ) ) {
2019-11-10 14:40:50 +00:00
return ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
pScene - > mMaterials = new aiMaterial * [ newMats . size ( ) ] ;
for ( unsigned int i = 0 ; i < newMats . size ( ) ; + + i ) {
2019-11-10 14:40:50 +00:00
pScene - > mMaterials [ i ] = newMats [ i ] . second ;
}
newMats . clear ( ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Stores all animations
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreAnimations ( aiScene * pScene , const ColladaParser & pParser ) {
2019-03-05 14:39:38 -06:00
// recursively collect all animations from the collada scene
2019-11-10 14:40:50 +00:00
StoreAnimations ( pScene , pParser , & pParser . mAnims , " " ) ;
2019-02-08 16:25:43 -06:00
// 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
2022-04-26 11:56:24 -05:00
for ( size_t a = 0 ; a < mAnims . size ( ) ; + + a ) {
aiAnimation * templateAnim = mAnims [ a ] ;
if ( templateAnim - > mNumChannels = = 1 ) {
2019-02-08 16:25:43 -06:00
// search for other single-channel-anims with the same duration
std : : vector < size_t > collectedAnimIndices ;
2022-04-26 11:56:24 -05:00
for ( size_t b = a + 1 ; b < mAnims . size ( ) ; + + b ) {
aiAnimation * other = mAnims [ b ] ;
2019-11-10 14:40:50 +00:00
if ( other - > mNumChannels = = 1 & & other - > mDuration = = templateAnim - > mDuration & &
2022-04-26 11:56:24 -05:00
other - > mTicksPerSecond = = templateAnim - > mTicksPerSecond )
2021-10-21 21:14:55 -04:00
collectedAnimIndices . push_back ( b ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
// 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 ;
}
2019-02-08 16:25:43 -06:00
// if there are other animations which fit the template anim, combine all channels into a single anim
2022-04-26 11:56:24 -05:00
if ( ! collectedAnimIndices . empty ( ) ) {
2026-06-09 12:46:56 -05:00
auto * combinedAnim = new aiAnimation ( ) ;
2019-11-10 14:40:50 +00:00
combinedAnim - > mName = aiString ( std : : string ( " combinedAnim_ " ) + char ( ' 0 ' + a ) ) ;
2019-02-08 16:25:43 -06:00
combinedAnim - > mDuration = templateAnim - > mDuration ;
combinedAnim - > mTicksPerSecond = templateAnim - > mTicksPerSecond ;
combinedAnim - > mNumChannels = static_cast < unsigned int > ( collectedAnimIndices . size ( ) + 1 ) ;
2022-04-26 11:56:24 -05:00
combinedAnim - > mChannels = new aiNodeAnim * [ combinedAnim - > mNumChannels ] ;
2019-02-08 16:25:43 -06:00
// add the template anim as first channel by moving its aiNodeAnim to the combined animation
combinedAnim - > mChannels [ 0 ] = templateAnim - > mChannels [ 0 ] ;
2022-04-26 11:56:24 -05:00
templateAnim - > mChannels [ 0 ] = nullptr ;
2019-02-08 16:25:43 -06:00
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
2022-04-26 11:56:24 -05:00
for ( size_t b = 0 ; b < collectedAnimIndices . size ( ) ; + + b ) {
aiAnimation * srcAnimation = mAnims [ collectedAnimIndices [ b ] ] ;
2019-02-08 16:25:43 -06:00
combinedAnim - > mChannels [ 1 + b ] = srcAnimation - > mChannels [ 0 ] ;
2022-04-26 11:56:24 -05:00
srcAnimation - > mChannels [ 0 ] = nullptr ;
2019-02-08 16:25:43 -06:00
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
2022-04-26 11:56:24 -05:00
while ( ! collectedAnimIndices . empty ( ) ) {
2019-11-10 14:40:50 +00:00
mAnims . erase ( mAnims . begin ( ) + collectedAnimIndices . back ( ) ) ;
2019-02-08 16:25:43 -06:00
collectedAnimIndices . pop_back ( ) ;
}
}
}
}
// now store all anims in the scene
2022-04-26 11:56:24 -05:00
if ( ! mAnims . empty ( ) ) {
2019-02-08 16:25:43 -06:00
pScene - > mNumAnimations = static_cast < unsigned int > ( mAnims . size ( ) ) ;
2022-04-26 11:56:24 -05:00
pScene - > mAnimations = new aiAnimation * [ mAnims . size ( ) ] ;
2019-11-10 14:40:50 +00:00
std : : copy ( mAnims . begin ( ) , mAnims . end ( ) , pScene - > mAnimations ) ;
2019-02-08 16:25:43 -06:00
}
mAnims . clear ( ) ;
}
// ------------------------------------------------------------------------------------------------
// Constructs the animations for the given source anim
2022-04-26 11:56:24 -05:00
void ColladaLoader : : StoreAnimations ( aiScene * pScene , const ColladaParser & pParser , const Animation * pSrcAnim , const std : : string & pPrefix ) {
2019-02-08 16:25:43 -06:00
std : : string animName = pPrefix . empty ( ) ? pSrcAnim - > mName : pPrefix + " _ " + pSrcAnim - > mName ;
// create nested animations, if given
2022-04-26 11:56:24 -05:00
for ( auto mSubAnim : pSrcAnim - > mSubAnims ) {
StoreAnimations ( pScene , pParser , mSubAnim , animName ) ;
}
2019-02-08 16:25:43 -06:00
// create animation channels, if any
2022-04-26 11:56:24 -05:00
if ( ! pSrcAnim - > mChannels . empty ( ) ) {
2019-11-10 14:40:50 +00:00
CreateAnimation ( pScene , pParser , pSrcAnim , animName ) ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
struct MorphTimeValues {
2019-02-08 16:25:43 -06:00
float mTime ;
2022-04-26 11:56:24 -05:00
struct key {
2019-02-08 16:25:43 -06:00
float mWeight ;
unsigned int mValue ;
} ;
std : : vector < key > mKeys ;
} ;
2022-04-26 11:56:24 -05:00
void insertMorphTimeValue ( std : : vector < MorphTimeValues > & values , float time , float weight , unsigned int value ) {
2026-06-09 12:46:56 -05:00
MorphTimeValues : : key k { } ;
2019-02-08 16:25:43 -06:00
k . mValue = value ;
k . mWeight = weight ;
2022-04-26 11:56:24 -05:00
if ( values . empty ( ) | | time < values [ 0 ] . mTime ) {
2019-02-08 16:25:43 -06:00
MorphTimeValues val ;
val . mTime = time ;
val . mKeys . push_back ( k ) ;
values . insert ( values . begin ( ) , val ) ;
return ;
}
2022-04-26 11:56:24 -05:00
if ( time > values . back ( ) . mTime ) {
2019-02-08 16:25:43 -06:00
MorphTimeValues val ;
val . mTime = time ;
val . mKeys . push_back ( k ) ;
values . insert ( values . end ( ) , val ) ;
return ;
}
2022-04-26 11:56:24 -05:00
for ( unsigned int i = 0 ; i < values . size ( ) ; i + + ) {
if ( std : : abs ( time - values [ i ] . mTime ) < ai_epsilon ) {
2019-02-08 16:25:43 -06:00
values [ i ] . mKeys . push_back ( k ) ;
return ;
2022-04-26 11:56:24 -05:00
} else if ( time > values [ i ] . mTime & & time < values [ i + 1 ] . mTime ) {
2019-02-08 16:25:43 -06:00
MorphTimeValues val ;
val . mTime = time ;
val . mKeys . push_back ( k ) ;
values . insert ( values . begin ( ) + i , val ) ;
return ;
}
}
}
2022-04-26 11:56:24 -05:00
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 ;
}
2019-02-08 16:25:43 -06:00
}
2026-06-09 12:46:56 -05:00
2019-02-08 16:25:43 -06:00
// 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
2022-04-26 11:56:24 -05:00
void ColladaLoader : : CreateAnimation ( aiScene * pScene , const ColladaParser & pParser , const Animation * pSrcAnim , const std : : string & pName ) {
2019-02-08 16:25:43 -06:00
// collect a list of animatable nodes
2022-04-26 11:56:24 -05:00
std : : vector < const aiNode * > nodes ;
2019-11-10 14:40:50 +00:00
CollectNodes ( pScene - > mRootNode , nodes ) ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
std : : vector < aiNodeAnim * > anims ;
std : : vector < aiMeshMorphAnim * > morphAnims ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
for ( auto node : nodes ) {
2019-02-08 16:25:43 -06:00
// find all the collada anim channels which refer to the current node
2022-04-26 11:56:24 -05:00
std : : vector < ChannelEntry > entries ;
std : : string nodeName = node - > mName . data ;
2019-02-08 16:25:43 -06:00
// find the collada node corresponding to the aiNode
2022-04-26 11:56:24 -05:00
const Node * srcNode = FindNode ( pParser . mRootNode , nodeName ) ;
if ( ! srcNode ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// now check all channels if they affect the current node
2019-03-05 14:39:38 -06:00
std : : string targetID , subElement ;
2026-06-09 12:46:56 -05:00
for ( auto cit = pSrcAnim - > mChannels . begin ( ) ;
2022-04-26 11:56:24 -05:00
cit ! = pSrcAnim - > mChannels . end ( ) ; + + cit ) {
const AnimationChannel & srcChannel = * cit ;
ChannelEntry entry ;
2019-02-08 16:25:43 -06:00
// 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
2019-11-10 14:40:50 +00:00
std : : string : : size_type slashPos = srcChannel . mTarget . find ( ' / ' ) ;
2022-04-26 11:56:24 -05:00
if ( slashPos = = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
std : : string : : size_type targetPos = srcChannel . mTarget . find ( srcNode - > mID ) ;
2022-04-26 11:56:24 -05:00
if ( targetPos = = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// 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 ( ) ,
2022-04-26 11:56:24 -05:00
srcChannel . mTarget . length ( ) - targetPos - pSrcAnim - > mName . length ( ) ) ;
if ( entry . mTargetId . front ( ) = = ' - ' ) {
2019-02-08 16:25:43 -06:00
entry . mTargetId = entry . mTargetId . substr ( 1 ) ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
entries . push_back ( entry ) ;
continue ;
}
2022-04-26 11:56:24 -05:00
if ( srcChannel . mTarget . find ( ' / ' , slashPos + 1 ) ! = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-03-05 14:39:38 -06:00
targetID . clear ( ) ;
2019-11-10 14:40:50 +00:00
targetID = srcChannel . mTarget . substr ( 0 , slashPos ) ;
2022-04-26 11:56:24 -05:00
if ( targetID ! = srcNode - > mID ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// find the dot that separates the transformID - there should be only one or zero
2019-11-10 14:40:50 +00:00
std : : string : : size_type dotPos = srcChannel . mTarget . find ( ' . ' ) ;
2022-04-26 11:56:24 -05:00
if ( dotPos ! = std : : string : : npos ) {
if ( srcChannel . mTarget . find ( ' . ' , dotPos + 1 ) ! = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
entry . mTransformId = srcChannel . mTarget . substr ( slashPos + 1 , dotPos - slashPos - 1 ) ;
2019-02-08 16:25:43 -06:00
2019-03-05 14:39:38 -06:00
subElement . clear ( ) ;
2019-11-10 14:40:50 +00:00
subElement = srcChannel . mTarget . substr ( dotPos + 1 ) ;
if ( subElement = = " ANGLE " )
2019-02-08 16:25:43 -06:00
entry . mSubElement = 3 ; // last number in an Axis-Angle-Transform is the angle
2019-11-10 14:40:50 +00:00
else if ( subElement = = " X " )
2019-02-08 16:25:43 -06:00
entry . mSubElement = 0 ;
2019-11-10 14:40:50 +00:00
else if ( subElement = = " Y " )
2019-02-08 16:25:43 -06:00
entry . mSubElement = 1 ;
2019-11-10 14:40:50 +00:00
else if ( subElement = = " Z " )
2019-02-08 16:25:43 -06:00
entry . mSubElement = 2 ;
else
2022-04-26 11:56:24 -05:00
ASSIMP_LOG_WARN ( " Unknown anim subelement < " , subElement , " >. Ignoring " ) ;
} else {
// no sub-element following, transformId is remaining string
2019-11-10 14:40:50 +00:00
entry . mTransformId = srcChannel . mTarget . substr ( slashPos + 1 ) ;
2019-02-08 16:25:43 -06:00
}
std : : string : : size_type bracketPos = srcChannel . mTarget . find ( ' ( ' ) ;
2022-04-26 11:56:24 -05:00
if ( bracketPos ! = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
entry . mTransformId = srcChannel . mTarget . substr ( slashPos + 1 , bracketPos - slashPos - 1 ) ;
2019-03-05 14:39:38 -06:00
subElement . clear ( ) ;
subElement = srcChannel . mTarget . substr ( bracketPos ) ;
2019-02-08 16:25:43 -06:00
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 ;
2019-11-10 14:40:50 +00:00
for ( size_t a = 0 ; a < srcNode - > mTransforms . size ( ) ; + + a )
if ( srcNode - > mTransforms [ a ] . mID = = entry . mTransformId )
2019-02-08 16:25:43 -06:00
entry . mTransformIndex = a ;
2022-04-26 11:56:24 -05:00
if ( entry . mTransformIndex = = SIZE_MAX ) {
if ( entry . mTransformId . find ( " morph-weights " ) = = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
entry . mTargetId = entry . mTransformId ;
entry . mTransformId = std : : string ( ) ;
2019-02-08 16:25:43 -06:00
}
entry . mChannel = & ( * cit ) ;
2019-11-10 14:40:50 +00:00
entries . push_back ( entry ) ;
2019-02-08 16:25:43 -06:00
}
// if there's no channel affecting the current node, we skip it
2022-04-26 11:56:24 -05:00
if ( entries . empty ( ) ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// resolve the data pointers for all anim channels. Find the minimum time while we're at it
2019-11-10 14:40:50 +00:00
ai_real startTime = ai_real ( 1e20 ) , endTime = ai_real ( - 1e20 ) ;
2022-04-26 11:56:24 -05:00
for ( ChannelEntry & e : entries ) {
2019-11-10 14:40:50 +00:00
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 ) ;
2019-02-08 16:25:43 -06:00
// time count and value count must match
2022-04-26 11:56:24 -05:00
if ( e . mTimeAccessor - > mCount ! = e . mValueAccessor - > mCount ) {
throw DeadlyImportError ( " Time count / value count mismatch in animation channel \" " , e . mChannel - > mTarget , " \" . " ) ;
}
2019-11-10 14:40:50 +00:00
2022-04-26 11:56:24 -05:00
if ( e . mTimeAccessor - > mCount > 0 ) {
2019-11-10 14:40:50 +00:00
// 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 ) ) ;
}
2019-02-08 16:25:43 -06:00
}
2019-11-10 14:40:50 +00:00
std : : vector < aiMatrix4x4 > resultTrafos ;
2022-04-26 11:56:24 -05:00
if ( ! entries . empty ( ) & & entries . front ( ) . mTimeAccessor - > mCount > 0 ) {
2019-11-10 14:40:50 +00:00
// 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 ;
2024-12-09 20:22:47 +00:00
while ( true ) {
2022-04-26 11:56:24 -05:00
for ( ChannelEntry & e : entries ) {
2019-11-10 14:40:50 +00:00
// find the keyframe behind the current point in time
size_t pos = 0 ;
ai_real postTime = 0.0 ;
2024-12-09 20:22:47 +00:00
while ( true ) {
2022-04-26 11:56:24 -05:00
if ( pos > = e . mTimeAccessor - > mCount ) {
2019-11-10 14:40:50 +00:00
break ;
2022-04-26 11:56:24 -05:00
}
2019-11-10 14:40:50 +00:00
postTime = ReadFloat ( * e . mTimeAccessor , * e . mTimeData , pos , 0 ) ;
2022-04-26 11:56:24 -05:00
if ( postTime > = time ) {
2019-11-10 14:40:50 +00:00
break ;
2022-04-26 11:56:24 -05:00
}
2019-11-10 14:40:50 +00:00
+ + pos ;
}
pos = std : : min ( pos , e . mTimeAccessor - > mCount - 1 ) ;
// read values from there
ai_real temp [ 16 ] ;
2022-04-26 11:56:24 -05:00
for ( size_t c = 0 ; c < e . mValueAccessor - > mSize ; + + c ) {
2019-11-10 14:40:50 +00:00
temp [ c ] = ReadFloat ( * e . mValueAccessor , * e . mValueData , pos , c ) ;
2022-04-26 11:56:24 -05:00
}
2019-11-10 14:40:50 +00:00
// if not exactly at the key time, interpolate with previous value set
2022-04-26 11:56:24 -05:00
if ( postTime > time & & pos > 0 ) {
2019-11-10 14:40:50 +00:00
ai_real preTime = ReadFloat ( * e . mTimeAccessor , * e . mTimeData , pos - 1 , 0 ) ;
ai_real factor = ( time - postTime ) / ( preTime - postTime ) ;
2022-04-26 11:56:24 -05:00
for ( size_t c = 0 ; c < e . mValueAccessor - > mSize ; + + c ) {
2019-11-10 14:40:50 +00:00
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 ) ;
2022-04-26 11:56:24 -05:00
for ( ChannelEntry & channelElement : entries ) {
2019-11-10 14:40:50 +00:00
// find the next time value larger than the current
size_t pos = 0 ;
2022-04-26 11:56:24 -05:00
while ( pos < channelElement . mTimeAccessor - > mCount ) {
2019-11-10 14:40:50 +00:00
const ai_real t = ReadFloat ( * channelElement . mTimeAccessor , * channelElement . mTimeData , pos , 0 ) ;
2022-04-26 11:56:24 -05:00
if ( t > time ) {
2019-11-10 14:40:50 +00:00
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.
2022-04-26 11:56:24 -05:00
if ( transforms [ channelElement . mTransformIndex ] . mType = = TF_ROTATE & & channelElement . mSubElement = = 3 & & pos > 0 & & pos < channelElement . mTimeAccessor - > mCount ) {
2019-11-10 14:40:50 +00:00
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
2022-04-26 11:56:24 -05:00
if ( nextTime > 1e19 ) {
2019-11-10 14:40:50 +00:00
break ;
2022-04-26 11:56:24 -05:00
}
2019-11-10 14:40:50 +00:00
2022-04-26 11:56:24 -05:00
// else construct next key-frame at this following time point
2019-11-10 14:40:50 +00:00
time = nextTime ;
}
}
2019-02-08 16:25:43 -06:00
// build an animation channel for the given node out of these trafo keys
2022-04-26 11:56:24 -05:00
if ( ! resultTrafos . empty ( ) ) {
2026-06-09 12:46:56 -05:00
auto * dstAnim = new aiNodeAnim ;
2019-11-10 14:40:50 +00:00
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 ( ) ] ;
2022-04-26 11:56:24 -05:00
for ( size_t a = 0 ; a < resultTrafos . size ( ) ; + + a ) {
2019-11-10 14:40:50 +00:00
aiMatrix4x4 mat = resultTrafos [ a ] ;
double time = double ( mat . d4 ) ; // remember? time is stored in mat.d4
mat . d4 = 1.0f ;
2022-04-26 11:56:24 -05:00
dstAnim - > mPositionKeys [ a ] . mTime = time * kMillisecondsFromSeconds ;
dstAnim - > mRotationKeys [ a ] . mTime = time * kMillisecondsFromSeconds ;
dstAnim - > mScalingKeys [ a ] . mTime = time * kMillisecondsFromSeconds ;
2019-11-10 14:40:50 +00:00
mat . Decompose ( dstAnim - > mScalingKeys [ a ] . mValue , dstAnim - > mRotationKeys [ a ] . mValue , dstAnim - > mPositionKeys [ a ] . mValue ) ;
}
anims . push_back ( dstAnim ) ;
2022-04-26 11:56:24 -05:00
} else {
2019-11-10 14:40:50 +00:00
ASSIMP_LOG_WARN ( " Collada loader: found empty animation channel, ignored. Please check your exporter. " ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
if ( ! entries . empty ( ) & & entries . front ( ) . mTimeAccessor - > mCount > 0 ) {
std : : vector < ChannelEntry > morphChannels ;
for ( ChannelEntry & e : entries ) {
2019-02-08 16:25:43 -06:00
// skip non-transform types
2022-04-26 11:56:24 -05:00
if ( e . mTargetId . empty ( ) ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
if ( e . mTargetId . find ( " morph-weights " ) ! = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
morphChannels . push_back ( e ) ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
if ( ! morphChannels . empty ( ) ) {
2019-02-08 16:25:43 -06:00
// either 1) morph weight animation count should contain morph target count channels
// or 2) one channel with morph target count arrays
// assume first
2026-06-09 12:46:56 -05:00
auto * morphAnim = new aiMeshMorphAnim ;
2019-02-08 16:25:43 -06:00
morphAnim - > mName . Set ( nodeName ) ;
std : : vector < MorphTimeValues > morphTimeValues ;
int morphAnimChannelIndex = 0 ;
2022-04-26 11:56:24 -05:00
for ( ChannelEntry & e : morphChannels ) {
2019-02-08 16:25:43 -06:00
std : : string : : size_type apos = e . mTargetId . find ( ' ( ' ) ;
std : : string : : size_type bpos = e . mTargetId . find ( ' ) ' ) ;
2022-04-26 11:56:24 -05:00
// If unknown way to specify weight -> ignore this animation
if ( apos = = std : : string : : npos | | bpos = = std : : string : : npos ) {
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
// 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
2022-04-26 11:56:24 -05:00
for ( unsigned int i = 0 ; i < e . mTimeData - > mValues . size ( ) ; i + + ) {
insertMorphTimeValue ( morphTimeValues , e . mTimeData - > mValues [ i ] , e . mValueData - > mValues [ i ] , morphAnimChannelIndex ) ;
}
2019-02-08 16:25:43 -06:00
+ + morphAnimChannelIndex ;
}
morphAnim - > mNumKeys = static_cast < unsigned int > ( morphTimeValues . size ( ) ) ;
morphAnim - > mKeys = new aiMeshMorphKey [ morphAnim - > mNumKeys ] ;
2022-04-26 11:56:24 -05:00
for ( unsigned int key = 0 ; key < morphAnim - > mNumKeys ; key + + ) {
2019-02-08 16:25:43 -06:00
morphAnim - > mKeys [ key ] . mNumValuesAndWeights = static_cast < unsigned int > ( morphChannels . size ( ) ) ;
2019-11-10 14:40:50 +00:00
morphAnim - > mKeys [ key ] . mValues = new unsigned int [ morphChannels . size ( ) ] ;
morphAnim - > mKeys [ key ] . mWeights = new double [ morphChannels . size ( ) ] ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
morphAnim - > mKeys [ key ] . mTime = morphTimeValues [ key ] . mTime * kMillisecondsFromSeconds ;
for ( unsigned int valueIndex = 0 ; valueIndex < morphChannels . size ( ) ; + + valueIndex ) {
2019-02-08 16:25:43 -06:00
morphAnim - > mKeys [ key ] . mValues [ valueIndex ] = valueIndex ;
morphAnim - > mKeys [ key ] . mWeights [ valueIndex ] = getWeightAtKey ( morphTimeValues , key , valueIndex ) ;
}
}
morphAnims . push_back ( morphAnim ) ;
}
}
}
2022-04-26 11:56:24 -05:00
if ( ! anims . empty ( ) | | ! morphAnims . empty ( ) ) {
2026-06-09 12:46:56 -05:00
auto anim = new aiAnimation ;
2019-11-10 14:40:50 +00:00
anim - > mName . Set ( pName ) ;
2019-02-08 16:25:43 -06:00
anim - > mNumChannels = static_cast < unsigned int > ( anims . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( anim - > mNumChannels > 0 ) {
anim - > mChannels = new aiNodeAnim * [ anims . size ( ) ] ;
2019-11-10 14:40:50 +00:00
std : : copy ( anims . begin ( ) , anims . end ( ) , anim - > mChannels ) ;
2019-02-08 16:25:43 -06:00
}
anim - > mNumMorphMeshChannels = static_cast < unsigned int > ( morphAnims . size ( ) ) ;
2022-04-26 11:56:24 -05:00
if ( anim - > mNumMorphMeshChannels > 0 ) {
anim - > mMorphMeshChannels = new aiMeshMorphAnim * [ anim - > mNumMorphMeshChannels ] ;
2019-11-10 14:40:50 +00:00
std : : copy ( morphAnims . begin ( ) , morphAnims . end ( ) , anim - > mMorphMeshChannels ) ;
2019-02-08 16:25:43 -06:00
}
anim - > mDuration = 0.0f ;
2022-04-26 11:56:24 -05:00
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 ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
for ( auto & morphAnim : morphAnims ) {
anim - > mDuration = std : : max ( anim - > mDuration , morphAnim - > mKeys [ morphAnim - > mNumKeys - 1 ] . mTime ) ;
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
anim - > mTicksPerSecond = 1000.0 ;
2019-11-10 14:40:50 +00:00
mAnims . push_back ( anim ) ;
2019-02-08 16:25:43 -06:00
}
}
// ------------------------------------------------------------------------------------------------
// Add a texture to a material structure
2022-04-26 11:56:24 -05:00
void ColladaLoader : : AddTexture ( aiMaterial & mat ,
const ColladaParser & pParser ,
const Effect & effect ,
const Sampler & sampler ,
aiTextureType type ,
unsigned int idx ) {
2019-02-08 16:25:43 -06:00
// first of all, basic file name
2019-11-10 14:40:50 +00:00
const aiString name = FindFilenameForEffectTexture ( pParser , effect , sampler . mName ) ;
mat . AddProperty ( & name , _AI_MATKEY_TEXTURE_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
// mapping mode
int map = aiTextureMapMode_Clamp ;
2022-04-26 11:56:24 -05:00
if ( sampler . mWrapU ) {
2019-02-08 16:25:43 -06:00
map = aiTextureMapMode_Wrap ;
2022-04-26 11:56:24 -05:00
}
if ( sampler . mWrapU & & sampler . mMirrorU ) {
2019-02-08 16:25:43 -06:00
map = aiTextureMapMode_Mirror ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
mat . AddProperty ( & map , 1 , _AI_MATKEY_MAPPINGMODE_U_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
map = aiTextureMapMode_Clamp ;
2022-04-26 11:56:24 -05:00
if ( sampler . mWrapV ) {
2019-02-08 16:25:43 -06:00
map = aiTextureMapMode_Wrap ;
2022-04-26 11:56:24 -05:00
}
if ( sampler . mWrapV & & sampler . mMirrorV ) {
2019-02-08 16:25:43 -06:00
map = aiTextureMapMode_Mirror ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
mat . AddProperty ( & map , 1 , _AI_MATKEY_MAPPINGMODE_V_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
// UV transformation
mat . AddProperty ( & sampler . mTransform , 1 ,
2022-04-26 11:56:24 -05:00
_AI_MATKEY_UVTRANSFORM_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
// Blend mode
2022-04-26 11:56:24 -05:00
mat . AddProperty ( ( int * ) & sampler . mOp , 1 ,
_AI_MATKEY_TEXBLEND_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
// Blend factor
2022-04-26 11:56:24 -05:00
mat . AddProperty ( ( ai_real * ) & sampler . mWeighting , 1 ,
_AI_MATKEY_TEXBLEND_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
// 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.
2022-04-26 11:56:24 -05:00
if ( sampler . mUVId ! = UINT_MAX ) {
2019-02-08 16:25:43 -06:00
map = sampler . mUVId ;
2022-04-26 11:56:24 -05:00
} else {
2019-02-08 16:25:43 -06:00
map = - 1 ;
2026-06-09 12:46:56 -05:00
for ( auto it = sampler . mUVChannel . begin ( ) ; it ! = sampler . mUVChannel . end ( ) ; + + it ) {
2019-02-08 16:25:43 -06:00
if ( IsNumeric ( * it ) ) {
2024-12-09 20:22:47 +00:00
map = strtoul10 ( & ( * it ) ) ;
2019-02-08 16:25:43 -06:00
break ;
}
}
if ( - 1 = = map ) {
2019-03-05 14:39:38 -06:00
ASSIMP_LOG_WARN ( " Collada: unable to determine UV channel for texture " ) ;
2019-02-08 16:25:43 -06:00
map = 0 ;
}
}
2019-11-10 14:40:50 +00:00
mat . AddProperty ( & map , 1 , _AI_MATKEY_UVWSRC_BASE , type , idx ) ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Fills materials from the collada material definitions
2022-04-26 11:56:24 -05:00
void ColladaLoader : : FillMaterials ( const ColladaParser & pParser , aiScene * /*pScene*/ ) {
for ( auto & elem : newMats ) {
2026-06-09 12:46:56 -05:00
auto & mat = ( aiMaterial & ) * elem . second ;
2022-04-26 11:56:24 -05:00
Collada : : Effect & effect = * elem . first ;
2019-02-08 16:25:43 -06:00
// resolve shading mode
int shadeMode ;
2022-04-26 11:56:24 -05:00
if ( effect . mFaceted ) {
2019-02-08 16:25:43 -06:00
shadeMode = aiShadingMode_Flat ;
2022-04-26 11:56:24 -05:00
} else {
switch ( effect . mShadeType ) {
2019-02-08 16:25:43 -06:00
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 :
2019-03-05 14:39:38 -06:00
ASSIMP_LOG_WARN ( " Collada: Unrecognized shading mode, using gouraud shading " ) ;
2019-02-08 16:25:43 -06:00
shadeMode = aiShadingMode_Gouraud ;
break ;
}
}
2019-11-10 14:40:50 +00:00
mat . AddProperty < int > ( & shadeMode , 1 , AI_MATKEY_SHADING_MODEL ) ;
2019-02-08 16:25:43 -06:00
// double-sided?
shadeMode = effect . mDoubleSided ;
2019-11-10 14:40:50 +00:00
mat . AddProperty < int > ( & shadeMode , 1 , AI_MATKEY_TWOSIDED ) ;
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
// wire-frame?
2019-02-08 16:25:43 -06:00
shadeMode = effect . mWireframe ;
2019-11-10 14:40:50 +00:00
mat . AddProperty < int > ( & shadeMode , 1 , AI_MATKEY_ENABLE_WIREFRAME ) ;
2019-02-08 16:25:43 -06:00
// add material colors
2019-11-10 14:40:50 +00:00
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 ) ;
2019-02-08 16:25:43 -06:00
// scalar properties
2019-11-10 14:40:50 +00:00
mat . AddProperty ( & effect . mShininess , 1 , AI_MATKEY_SHININESS ) ;
mat . AddProperty ( & effect . mReflectivity , 1 , AI_MATKEY_REFLECTIVITY ) ;
mat . AddProperty ( & effect . mRefractIndex , 1 , AI_MATKEY_REFRACTI ) ;
2019-02-08 16:25:43 -06:00
// 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
2019-11-10 14:40:50 +00:00
if ( effect . mTransparency > = 0.f & & effect . mTransparency < = 1.f ) {
2019-02-08 16:25:43 -06:00
// handle RGB transparency completely, cf Collada specs 1.5.0 pages 249 and 304
2019-11-10 14:40:50 +00:00
if ( effect . mRGBTransparency ) {
// use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4)
2022-04-26 11:56:24 -05:00
effect . mTransparency * = ( 0.212671f * effect . mTransparent . r +
0.715160f * effect . mTransparent . g +
0.072169f * effect . mTransparent . b ) ;
2019-02-08 16:25:43 -06:00
effect . mTransparent . a = 1.f ;
2019-11-10 14:40:50 +00:00
mat . AddProperty ( & effect . mTransparent , 1 , AI_MATKEY_COLOR_TRANSPARENT ) ;
2022-04-26 11:56:24 -05:00
} else {
2019-11-10 14:40:50 +00:00
effect . mTransparency * = effect . mTransparent . a ;
2019-02-08 16:25:43 -06:00
}
2019-11-10 14:40:50 +00:00
if ( effect . mInvertTransparency ) {
2019-02-08 16:25:43 -06:00
effect . mTransparency = 1.f - effect . mTransparency ;
}
// Is the material finally transparent ?
if ( effect . mHasTransparency | | effect . mTransparency < 1.f ) {
2019-11-10 14:40:50 +00:00
mat . AddProperty ( & effect . mTransparency , 1 , AI_MATKEY_OPACITY ) ;
2019-02-08 16:25:43 -06:00
}
}
// add textures, if given
2019-03-05 14:39:38 -06:00
if ( ! effect . mTexAmbient . mName . empty ( ) ) {
// It is merely a light-map
AddTexture ( mat , pParser , effect , effect . mTexAmbient , aiTextureType_LIGHTMAP ) ;
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( ! effect . mTexEmissive . mName . empty ( ) )
AddTexture ( mat , pParser , effect , effect . mTexEmissive , aiTextureType_EMISSIVE ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( ! effect . mTexSpecular . mName . empty ( ) )
AddTexture ( mat , pParser , effect , effect . mTexSpecular , aiTextureType_SPECULAR ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( ! effect . mTexDiffuse . mName . empty ( ) )
AddTexture ( mat , pParser , effect , effect . mTexDiffuse , aiTextureType_DIFFUSE ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( ! effect . mTexBump . mName . empty ( ) )
AddTexture ( mat , pParser , effect , effect . mTexBump , aiTextureType_NORMALS ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( ! effect . mTexTransparent . mName . empty ( ) )
AddTexture ( mat , pParser , effect , effect . mTexTransparent , aiTextureType_OPACITY ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( ! effect . mTexReflective . mName . empty ( ) )
AddTexture ( mat , pParser , effect , effect . mTexReflective , aiTextureType_REFLECTION ) ;
2019-02-08 16:25:43 -06:00
}
}
// ------------------------------------------------------------------------------------------------
// Constructs materials from the collada material definitions
2022-04-26 11:56:24 -05:00
void ColladaLoader : : BuildMaterials ( ColladaParser & pParser , aiScene * /*pScene*/ ) {
2019-02-08 16:25:43 -06:00
newMats . reserve ( pParser . mMaterialLibrary . size ( ) ) ;
2026-06-09 12:46:56 -05:00
for ( auto matIt = pParser . mMaterialLibrary . begin ( ) ;
2022-04-26 11:56:24 -05:00
matIt ! = pParser . mMaterialLibrary . end ( ) ; + + matIt ) {
const Material & material = matIt - > second ;
2019-02-08 16:25:43 -06:00
// a material is only a reference to an effect
2026-06-09 12:46:56 -05:00
auto effIt = pParser . mEffectLibrary . find ( material . mEffect ) ;
2019-11-10 14:40:50 +00:00
if ( effIt = = pParser . mEffectLibrary . end ( ) )
2019-02-08 16:25:43 -06:00
continue ;
2022-04-26 11:56:24 -05:00
Effect & effect = effIt - > second ;
2019-02-08 16:25:43 -06:00
// create material
2026-06-09 12:46:56 -05:00
auto * mat = new aiMaterial ;
2019-11-10 14:40:50 +00:00
aiString name ( material . mName . empty ( ) ? matIt - > first : material . mName ) ;
mat - > AddProperty ( & name , AI_MATKEY_NAME ) ;
2019-02-08 16:25:43 -06:00
// store the material
mMaterialIndexByName [ matIt - > first ] = newMats . size ( ) ;
2022-04-26 11:56:24 -05:00
newMats . emplace_back ( & effect , mat ) ;
2019-02-08 16:25:43 -06:00
}
// 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.
}
// ------------------------------------------------------------------------------------------------
2022-04-26 11:56:24 -05:00
// 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 ) {
2019-02-08 16:25:43 -06:00
aiString result ;
// recurse through the param references until we end up at an image
std : : string name = pName ;
2024-12-09 20:22:47 +00:00
while ( true ) {
2019-02-08 16:25:43 -06:00
// the given string is a param entry. Find it
2026-06-09 12:46:56 -05:00
auto it = pEffect . mParams . find ( name ) ;
2019-02-08 16:25:43 -06:00
// if not found, we're at the end of the recursion. The resulting string should be the image ID
2019-11-10 14:40:50 +00:00
if ( it = = pEffect . mParams . end ( ) )
2019-02-08 16:25:43 -06:00
break ;
// else recurse on
name = it - > second . mReference ;
}
// find the image referred by this name in the image library of the scene
2026-06-09 12:46:56 -05:00
auto imIt = pParser . mImageLibrary . find ( name ) ;
2022-04-26 11:56:24 -05:00
if ( imIt = = pParser . mImageLibrary . end ( ) ) {
ASSIMP_LOG_WARN ( " Collada: Unable to resolve effect texture entry \" " , pName , " \" , ended up at ID \" " , name , " \" . " ) ;
2019-02-08 16:25:43 -06:00
//set default texture file name
result . Set ( name + " .jpg " ) ;
2022-04-26 11:56:24 -05:00
ColladaParser : : UriDecodePath ( result ) ;
2019-02-08 16:25:43 -06:00
return result ;
}
// if this is an embedded texture image setup an aiTexture for it
2022-04-26 11:56:24 -05:00
if ( ! imIt - > second . mImageData . empty ( ) ) {
2026-06-09 12:46:56 -05:00
auto * tex = new aiTexture ( ) ;
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
// Store embedded texture name reference
tex - > mFilename . Set ( imIt - > second . mFileName . c_str ( ) ) ;
result . Set ( imIt - > second . mFileName ) ;
2019-02-08 16:25:43 -06:00
// setup format hint
2022-04-26 11:56:24 -05:00
if ( imIt - > second . mEmbeddedFormat . length ( ) > = HINTMAXTEXTURELEN ) {
2019-03-05 14:39:38 -06:00
ASSIMP_LOG_WARN ( " Collada: texture format hint is too long, truncating to 3 characters " ) ;
2019-02-08 16:25:43 -06:00
}
2019-11-10 14:40:50 +00:00
strncpy ( tex - > achFormatHint , imIt - > second . mEmbeddedFormat . c_str ( ) , 3 ) ;
2019-02-08 16:25:43 -06:00
// and copy texture data
tex - > mHeight = 0 ;
tex - > mWidth = static_cast < unsigned int > ( imIt - > second . mImageData . size ( ) ) ;
2022-04-26 11:56:24 -05:00
tex - > pcData = ( aiTexel * ) new char [ tex - > mWidth ] ;
2019-11-10 14:40:50 +00:00
memcpy ( tex - > pcData , & imIt - > second . mImageData [ 0 ] , tex - > mWidth ) ;
2019-02-08 16:25:43 -06:00
// and add this texture to the list
mTextures . push_back ( tex ) ;
2022-04-26 11:56:24 -05:00
return result ;
2021-10-21 21:14:55 -04:00
}
2022-04-26 11:56:24 -05:00
if ( imIt - > second . mFileName . empty ( ) ) {
throw DeadlyImportError ( " Collada: Invalid texture, no data or file reference given " ) ;
2021-10-21 21:14:55 -04:00
}
2022-04-26 11:56:24 -05:00
result . Set ( imIt - > second . mFileName ) ;
2021-10-21 21:14:55 -04:00
2022-04-26 11:56:24 -05:00
return result ;
2021-10-21 21:14:55 -04:00
}
2019-02-08 16:25:43 -06:00
// ------------------------------------------------------------------------------------------------
// Reads a string value from an accessor and its data array.
2022-04-26 11:56:24 -05:00
const std : : string & ColladaLoader : : ReadString ( const Accessor & pAccessor , const Data & pData , size_t pIndex ) const {
2019-02-08 16:25:43 -06:00
size_t pos = pAccessor . mStride * pIndex + pAccessor . mOffset ;
2019-11-10 14:40:50 +00:00
ai_assert ( pos < pData . mStrings . size ( ) ) ;
2019-02-08 16:25:43 -06:00
return pData . mStrings [ pos ] ;
}
// ------------------------------------------------------------------------------------------------
// Collects all nodes into the given array
2022-04-26 11:56:24 -05:00
void ColladaLoader : : CollectNodes ( const aiNode * pNode , std : : vector < const aiNode * > & poNodes ) const {
2019-11-10 14:40:50 +00:00
poNodes . push_back ( pNode ) ;
2019-03-05 14:39:38 -06:00
for ( size_t a = 0 ; a < pNode - > mNumChildren ; + + a ) {
CollectNodes ( pNode - > mChildren [ a ] , poNodes ) ;
}
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Finds a node in the collada scene by the given name
2022-04-26 11:56:24 -05:00
const Node * ColladaLoader : : FindNode ( const Node * pNode , const std : : string & pName ) const {
2019-11-10 14:40:50 +00:00
if ( pNode - > mName = = pName | | pNode - > mID = = pName )
2019-02-08 16:25:43 -06:00
return pNode ;
2022-04-26 11:56:24 -05:00
for ( auto a : pNode - > mChildren ) {
const Collada : : Node * node = FindNode ( a , pName ) ;
if ( node ) {
2019-02-08 16:25:43 -06:00
return node ;
2022-04-26 11:56:24 -05:00
}
2019-02-08 16:25:43 -06:00
}
2022-04-26 11:56:24 -05:00
return nullptr ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// Finds a node in the collada scene by the given SID
2022-04-26 11:56:24 -05:00
const Node * ColladaLoader : : FindNodeBySID ( const Node * pNode , const std : : string & pSID ) const {
2019-11-10 14:40:50 +00:00
if ( nullptr = = pNode ) {
return nullptr ;
}
2019-02-08 16:25:43 -06:00
2019-11-10 14:40:50 +00:00
if ( pNode - > mSID = = pSID ) {
return pNode ;
}
2019-02-08 16:25:43 -06:00
2022-04-26 11:56:24 -05:00
for ( auto a : pNode - > mChildren ) {
const Collada : : Node * node = FindNodeBySID ( a , pSID ) ;
2019-11-10 14:40:50 +00:00
if ( node ) {
return node ;
}
}
return nullptr ;
2019-02-08 16:25:43 -06:00
}
// ------------------------------------------------------------------------------------------------
// 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.
2022-04-26 11:56:24 -05:00
std : : string ColladaLoader : : FindNameForNode ( const Node * pNode ) {
2019-03-05 14:39:38 -06:00
// If explicitly requested, just use the collada name.
2022-04-26 11:56:24 -05:00
if ( useColladaName ) {
2019-03-05 14:39:38 -06:00
if ( ! pNode - > mName . empty ( ) ) {
return pNode - > mName ;
2022-04-26 11:56:24 -05:00
} else {
2019-03-05 14:39:38 -06:00
return format ( ) < < " $ColladaAutoName$_ " < < mNodeNameCounter + + ;
}
2022-04-26 11:56:24 -05:00
} else {
2019-03-05 14:39:38 -06:00
// 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 ;
2022-04-26 11:56:24 -05:00
else {
2019-03-05 14:39:38 -06:00
// 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 + + ;
}
2019-02-08 16:25:43 -06:00
}
}
2019-11-10 14:40:50 +00:00
} // Namespace Assimp
2026-06-09 12:46:56 -05:00
# endif // !! ASSIMP_BUILD_NO_COLLADA_IMPORTER