1 | /* |
2 | --------------------------------------------------------------------------- |
3 | Open Asset Import Library (assimp) |
4 | --------------------------------------------------------------------------- |
5 | |
6 | Copyright (c) 2006-2017, assimp team |
7 | |
8 | |
9 | All rights reserved. |
10 | |
11 | Redistribution and use of this software in source and binary forms, |
12 | with or without modification, are permitted provided that the following |
13 | conditions are met: |
14 | |
15 | * Redistributions of source code must retain the above |
16 | copyright notice, this list of conditions and the |
17 | following disclaimer. |
18 | |
19 | * Redistributions in binary form must reproduce the above |
20 | copyright notice, this list of conditions and the |
21 | following disclaimer in the documentation and/or other |
22 | materials provided with the distribution. |
23 | |
24 | * Neither the name of the assimp team, nor the names of its |
25 | contributors may be used to endorse or promote products |
26 | derived from this software without specific prior |
27 | written permission of the assimp team. |
28 | |
29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
30 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
31 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
32 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
33 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
34 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
35 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
36 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
38 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
39 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
40 | --------------------------------------------------------------------------- |
41 | */ |
42 | |
43 | /** @file Implementation of the Collada loader */ |
44 | |
45 | |
46 | #ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER |
47 | |
48 | #include "ColladaLoader.h" |
49 | #include <assimp/anim.h> |
50 | #include <assimp/scene.h> |
51 | #include <assimp/DefaultLogger.hpp> |
52 | #include <assimp/Importer.hpp> |
53 | #include <assimp/importerdesc.h> |
54 | |
55 | #include "ColladaParser.h" |
56 | #include "fast_atof.h" |
57 | #include "ParsingUtils.h" |
58 | #include "SkeletonMeshBuilder.h" |
59 | #include "CreateAnimMesh.h" |
60 | |
61 | #include "time.h" |
62 | #include "math.h" |
63 | #include <algorithm> |
64 | #include <numeric> |
65 | #include <assimp/Defines.h> |
66 | |
67 | using namespace Assimp; |
68 | using namespace Assimp::Formatter; |
69 | |
70 | static const aiImporterDesc desc = { |
71 | "Collada Importer" , |
72 | "" , |
73 | "" , |
74 | "http://collada.org" , |
75 | aiImporterFlags_SupportTextFlavour, |
76 | 1, |
77 | 3, |
78 | 1, |
79 | 5, |
80 | "dae" |
81 | }; |
82 | |
83 | // ------------------------------------------------------------------------------------------------ |
84 | // Constructor to be privately used by Importer |
85 | ColladaLoader::ColladaLoader() |
86 | : mFileName() |
87 | , mMeshIndexByID() |
88 | , mMaterialIndexByName() |
89 | , mMeshes() |
90 | , newMats() |
91 | , mCameras() |
92 | , mLights() |
93 | , mTextures() |
94 | , mAnims() |
95 | , noSkeletonMesh( false ) |
96 | , ignoreUpDirection(false) |
97 | , mNodeNameCounter( 0 ) |
98 | {} |
99 | |
100 | // ------------------------------------------------------------------------------------------------ |
101 | // Destructor, private as well |
102 | ColladaLoader::~ColladaLoader() |
103 | {} |
104 | |
105 | // ------------------------------------------------------------------------------------------------ |
106 | // Returns whether the class can handle the format of the given file. |
107 | bool ColladaLoader::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const |
108 | { |
109 | // check file extension |
110 | std::string extension = GetExtension(pFile); |
111 | |
112 | if( extension == "dae" ) |
113 | return true; |
114 | |
115 | // XML - too generic, we need to open the file and search for typical keywords |
116 | if( extension == "xml" || !extension.length() || checkSig) { |
117 | /* If CanRead() is called in order to check whether we |
118 | * support a specific file extension in general pIOHandler |
119 | * might be NULL and it's our duty to return true here. |
120 | */ |
121 | if (!pIOHandler)return true; |
122 | const char* tokens[] = {"<collada" }; |
123 | return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); |
124 | } |
125 | return false; |
126 | } |
127 | |
128 | // ------------------------------------------------------------------------------------------------ |
129 | void ColladaLoader::SetupProperties(const Importer* pImp) |
130 | { |
131 | noSkeletonMesh = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_NO_SKELETON_MESHES,0) != 0; |
132 | ignoreUpDirection = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION,0) != 0; |
133 | } |
134 | |
135 | // ------------------------------------------------------------------------------------------------ |
136 | // Get file extension list |
137 | const aiImporterDesc* ColladaLoader::GetInfo () const |
138 | { |
139 | return &desc; |
140 | } |
141 | |
142 | // ------------------------------------------------------------------------------------------------ |
143 | // Imports the given file into the given scene structure. |
144 | void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) |
145 | { |
146 | mFileName = pFile; |
147 | |
148 | // clean all member arrays - just for safety, it should work even if we did not |
149 | mMeshIndexByID.clear(); |
150 | mMaterialIndexByName.clear(); |
151 | mMeshes.clear(); |
152 | mTargetMeshes.clear(); |
153 | newMats.clear(); |
154 | mLights.clear(); |
155 | mCameras.clear(); |
156 | mTextures.clear(); |
157 | mAnims.clear(); |
158 | |
159 | // parse the input file |
160 | ColladaParser parser( pIOHandler, pFile); |
161 | |
162 | if( !parser.mRootNode) |
163 | throw DeadlyImportError( "Collada: File came out empty. Something is wrong here." ); |
164 | |
165 | // reserve some storage to avoid unnecessary reallocs |
166 | newMats.reserve(parser.mMaterialLibrary.size()*2); |
167 | mMeshes.reserve(parser.mMeshLibrary.size()*2); |
168 | |
169 | mCameras.reserve(parser.mCameraLibrary.size()); |
170 | mLights.reserve(parser.mLightLibrary.size()); |
171 | |
172 | // create the materials first, for the meshes to find |
173 | BuildMaterials( parser, pScene); |
174 | |
175 | // build the node hierarchy from it |
176 | pScene->mRootNode = BuildHierarchy( parser, parser.mRootNode); |
177 | |
178 | // ... then fill the materials with the now adjusted settings |
179 | FillMaterials(parser, pScene); |
180 | |
181 | // Apply unitsize scale calculation |
182 | pScene->mRootNode->mTransformation *= aiMatrix4x4(parser.mUnitSize, 0, 0, 0, |
183 | 0, parser.mUnitSize, 0, 0, |
184 | 0, 0, parser.mUnitSize, 0, |
185 | 0, 0, 0, 1); |
186 | if( !ignoreUpDirection ) { |
187 | // Convert to Y_UP, if different orientation |
188 | if( parser.mUpDirection == ColladaParser::UP_X) |
189 | pScene->mRootNode->mTransformation *= aiMatrix4x4( |
190 | 0, -1, 0, 0, |
191 | 1, 0, 0, 0, |
192 | 0, 0, 1, 0, |
193 | 0, 0, 0, 1); |
194 | else if( parser.mUpDirection == ColladaParser::UP_Z) |
195 | pScene->mRootNode->mTransformation *= aiMatrix4x4( |
196 | 1, 0, 0, 0, |
197 | 0, 0, 1, 0, |
198 | 0, -1, 0, 0, |
199 | 0, 0, 0, 1); |
200 | } |
201 | // store all meshes |
202 | StoreSceneMeshes( pScene); |
203 | |
204 | // store all materials |
205 | StoreSceneMaterials( pScene); |
206 | |
207 | // store all lights |
208 | StoreSceneLights( pScene); |
209 | |
210 | // store all cameras |
211 | StoreSceneCameras( pScene); |
212 | |
213 | // store all animations |
214 | StoreAnimations( pScene, parser); |
215 | |
216 | |
217 | // If no meshes have been loaded, it's probably just an animated skeleton. |
218 | if (!pScene->mNumMeshes) { |
219 | |
220 | if (!noSkeletonMesh) { |
221 | SkeletonMeshBuilder hero(pScene); |
222 | } |
223 | pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; |
224 | } |
225 | } |
226 | |
227 | // ------------------------------------------------------------------------------------------------ |
228 | // Recursively constructs a scene node for the given parser node and returns it. |
229 | aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode) |
230 | { |
231 | // create a node for it |
232 | aiNode* node = new aiNode(); |
233 | |
234 | // find a name for the new node. It's more complicated than you might think |
235 | node->mName.Set( FindNameForNode( pNode)); |
236 | |
237 | // calculate the transformation matrix for it |
238 | node->mTransformation = pParser.CalculateResultTransform( pNode->mTransforms); |
239 | |
240 | // now resolve node instances |
241 | std::vector<const Collada::Node*> instances; |
242 | ResolveNodeInstances(pParser,pNode,instances); |
243 | |
244 | // add children. first the *real* ones |
245 | node->mNumChildren = static_cast<unsigned int>(pNode->mChildren.size()+instances.size()); |
246 | node->mChildren = new aiNode*[node->mNumChildren]; |
247 | |
248 | for( size_t a = 0; a < pNode->mChildren.size(); a++) |
249 | { |
250 | node->mChildren[a] = BuildHierarchy( pParser, pNode->mChildren[a]); |
251 | node->mChildren[a]->mParent = node; |
252 | } |
253 | |
254 | // ... and finally the resolved node instances |
255 | for( size_t a = 0; a < instances.size(); a++) |
256 | { |
257 | node->mChildren[pNode->mChildren.size() + a] = BuildHierarchy( pParser, instances[a]); |
258 | node->mChildren[pNode->mChildren.size() + a]->mParent = node; |
259 | } |
260 | |
261 | // construct meshes |
262 | BuildMeshesForNode( pParser, pNode, node); |
263 | |
264 | // construct cameras |
265 | BuildCamerasForNode(pParser, pNode, node); |
266 | |
267 | // construct lights |
268 | BuildLightsForNode(pParser, pNode, node); |
269 | return node; |
270 | } |
271 | |
272 | // ------------------------------------------------------------------------------------------------ |
273 | // Resolve node instances |
274 | void ColladaLoader::ResolveNodeInstances( const ColladaParser& pParser, const Collada::Node* pNode, |
275 | std::vector<const Collada::Node*>& resolved) |
276 | { |
277 | // reserve enough storage |
278 | resolved.reserve(pNode->mNodeInstances.size()); |
279 | |
280 | // ... and iterate through all nodes to be instanced as children of pNode |
281 | for (const auto &nodeInst: pNode->mNodeInstances) |
282 | { |
283 | // find the corresponding node in the library |
284 | const ColladaParser::NodeLibrary::const_iterator itt = pParser.mNodeLibrary.find(nodeInst.mNode); |
285 | const Collada::Node* nd = itt == pParser.mNodeLibrary.end() ? NULL : (*itt).second; |
286 | |
287 | // FIX for http://sourceforge.net/tracker/?func=detail&aid=3054873&group_id=226462&atid=1067632 |
288 | // need to check for both name and ID to catch all. To avoid breaking valid files, |
289 | // the workaround is only enabled when the first attempt to resolve the node has failed. |
290 | if (!nd) { |
291 | nd = FindNode(pParser.mRootNode, nodeInst.mNode); |
292 | } |
293 | if (!nd) |
294 | DefaultLogger::get()->error("Collada: Unable to resolve reference to instanced node " + nodeInst.mNode); |
295 | |
296 | else { |
297 | // attach this node to the list of children |
298 | resolved.push_back(nd); |
299 | } |
300 | } |
301 | } |
302 | |
303 | // ------------------------------------------------------------------------------------------------ |
304 | // Resolve UV channels |
305 | void ColladaLoader::ApplyVertexToEffectSemanticMapping(Collada::Sampler& sampler, |
306 | const Collada::SemanticMappingTable& table) |
307 | { |
308 | std::map<std::string, Collada::InputSemanticMapEntry>::const_iterator it = table.mMap.find(sampler.mUVChannel); |
309 | if (it != table.mMap.end()) { |
310 | if (it->second.mType != Collada::IT_Texcoord) |
311 | DefaultLogger::get()->error("Collada: Unexpected effect input mapping" ); |
312 | |
313 | sampler.mUVId = it->second.mSet; |
314 | } |
315 | } |
316 | |
317 | // ------------------------------------------------------------------------------------------------ |
318 | // Builds lights for the given node and references them |
319 | void ColladaLoader::BuildLightsForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) |
320 | { |
321 | for( const Collada::LightInstance& lid : pNode->mLights) |
322 | { |
323 | // find the referred light |
324 | ColladaParser::LightLibrary::const_iterator srcLightIt = pParser.mLightLibrary.find( lid.mLight); |
325 | if( srcLightIt == pParser.mLightLibrary.end()) |
326 | { |
327 | DefaultLogger::get()->warn("Collada: Unable to find light for ID \"" + lid.mLight + "\". Skipping." ); |
328 | continue; |
329 | } |
330 | const Collada::Light* srcLight = &srcLightIt->second; |
331 | |
332 | // now fill our ai data structure |
333 | aiLight* out = new aiLight(); |
334 | out->mName = pTarget->mName; |
335 | out->mType = (aiLightSourceType)srcLight->mType; |
336 | |
337 | // collada lights point in -Z by default, rest is specified in node transform |
338 | out->mDirection = aiVector3D(0.f,0.f,-1.f); |
339 | |
340 | out->mAttenuationConstant = srcLight->mAttConstant; |
341 | out->mAttenuationLinear = srcLight->mAttLinear; |
342 | out->mAttenuationQuadratic = srcLight->mAttQuadratic; |
343 | |
344 | out->mColorDiffuse = out->mColorSpecular = out->mColorAmbient = srcLight->mColor*srcLight->mIntensity; |
345 | if (out->mType == aiLightSource_AMBIENT) { |
346 | out->mColorDiffuse = out->mColorSpecular = aiColor3D(0, 0, 0); |
347 | out->mColorAmbient = srcLight->mColor*srcLight->mIntensity; |
348 | } |
349 | else { |
350 | // collada doesn't differentiate between these color types |
351 | out->mColorDiffuse = out->mColorSpecular = srcLight->mColor*srcLight->mIntensity; |
352 | out->mColorAmbient = aiColor3D(0, 0, 0); |
353 | } |
354 | |
355 | // convert falloff angle and falloff exponent in our representation, if given |
356 | if (out->mType == aiLightSource_SPOT) { |
357 | |
358 | out->mAngleInnerCone = AI_DEG_TO_RAD( srcLight->mFalloffAngle ); |
359 | |
360 | // ... some extension magic. |
361 | if (srcLight->mOuterAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f)) |
362 | { |
363 | // ... some deprecation magic. |
364 | if (srcLight->mPenumbraAngle >= ASSIMP_COLLADA_LIGHT_ANGLE_NOT_SET*(1-1e-6f)) |
365 | { |
366 | // Need to rely on falloff_exponent. I don't know how to interpret it, so I need to guess .... |
367 | // epsilon chosen to be 0.1 |
368 | out->mAngleOuterCone = std::acos(std::pow(0.1f,1.f/srcLight->mFalloffExponent))+ |
369 | out->mAngleInnerCone; |
370 | } |
371 | else { |
372 | out->mAngleOuterCone = out->mAngleInnerCone + AI_DEG_TO_RAD( srcLight->mPenumbraAngle ); |
373 | if (out->mAngleOuterCone < out->mAngleInnerCone) |
374 | std::swap(out->mAngleInnerCone,out->mAngleOuterCone); |
375 | } |
376 | } |
377 | else out->mAngleOuterCone = AI_DEG_TO_RAD( srcLight->mOuterAngle ); |
378 | } |
379 | |
380 | // add to light list |
381 | mLights.push_back(out); |
382 | } |
383 | } |
384 | |
385 | // ------------------------------------------------------------------------------------------------ |
386 | // Builds cameras for the given node and references them |
387 | void ColladaLoader::BuildCamerasForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) |
388 | { |
389 | for( const Collada::CameraInstance& cid : pNode->mCameras) |
390 | { |
391 | // find the referred light |
392 | ColladaParser::CameraLibrary::const_iterator srcCameraIt = pParser.mCameraLibrary.find( cid.mCamera); |
393 | if( srcCameraIt == pParser.mCameraLibrary.end()) |
394 | { |
395 | DefaultLogger::get()->warn("Collada: Unable to find camera for ID \"" + cid.mCamera + "\". Skipping." ); |
396 | continue; |
397 | } |
398 | const Collada::Camera* srcCamera = &srcCameraIt->second; |
399 | |
400 | // orthographic cameras not yet supported in Assimp |
401 | if (srcCamera->mOrtho) { |
402 | DefaultLogger::get()->warn("Collada: Orthographic cameras are not supported." ); |
403 | } |
404 | |
405 | // now fill our ai data structure |
406 | aiCamera* out = new aiCamera(); |
407 | out->mName = pTarget->mName; |
408 | |
409 | // collada cameras point in -Z by default, rest is specified in node transform |
410 | out->mLookAt = aiVector3D(0.f,0.f,-1.f); |
411 | |
412 | // near/far z is already ok |
413 | out->mClipPlaneFar = srcCamera->mZFar; |
414 | out->mClipPlaneNear = srcCamera->mZNear; |
415 | |
416 | // ... but for the rest some values are optional |
417 | // and we need to compute the others in any combination. |
418 | if (srcCamera->mAspect != 10e10f) |
419 | out->mAspect = srcCamera->mAspect; |
420 | |
421 | if (srcCamera->mHorFov != 10e10f) { |
422 | out->mHorizontalFOV = srcCamera->mHorFov; |
423 | |
424 | if (srcCamera->mVerFov != 10e10f && srcCamera->mAspect == 10e10f) { |
425 | out->mAspect = std::tan(AI_DEG_TO_RAD(srcCamera->mHorFov)) / |
426 | std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov)); |
427 | } |
428 | } |
429 | else if (srcCamera->mAspect != 10e10f && srcCamera->mVerFov != 10e10f) { |
430 | out->mHorizontalFOV = 2.0f * AI_RAD_TO_DEG(std::atan(srcCamera->mAspect * |
431 | std::tan(AI_DEG_TO_RAD(srcCamera->mVerFov) * 0.5f))); |
432 | } |
433 | |
434 | // Collada uses degrees, we use radians |
435 | out->mHorizontalFOV = AI_DEG_TO_RAD(out->mHorizontalFOV); |
436 | |
437 | // add to camera list |
438 | mCameras.push_back(out); |
439 | } |
440 | } |
441 | |
442 | // ------------------------------------------------------------------------------------------------ |
443 | // Builds meshes for the given node and references them |
444 | void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) |
445 | { |
446 | // accumulated mesh references by this node |
447 | std::vector<size_t> newMeshRefs; |
448 | newMeshRefs.reserve(pNode->mMeshes.size()); |
449 | |
450 | // add a mesh for each subgroup in each collada mesh |
451 | for( const Collada::MeshInstance& mid : pNode->mMeshes) |
452 | { |
453 | const Collada::Mesh* srcMesh = NULL; |
454 | const Collada::Controller* srcController = NULL; |
455 | |
456 | // find the referred mesh |
457 | ColladaParser::MeshLibrary::const_iterator srcMeshIt = pParser.mMeshLibrary.find( mid.mMeshOrController); |
458 | if( srcMeshIt == pParser.mMeshLibrary.end()) |
459 | { |
460 | // if not found in the mesh-library, it might also be a controller referring to a mesh |
461 | ColladaParser::ControllerLibrary::const_iterator srcContrIt = pParser.mControllerLibrary.find( mid.mMeshOrController); |
462 | if( srcContrIt != pParser.mControllerLibrary.end()) |
463 | { |
464 | srcController = &srcContrIt->second; |
465 | srcMeshIt = pParser.mMeshLibrary.find( srcController->mMeshId); |
466 | if( srcMeshIt != pParser.mMeshLibrary.end()) |
467 | srcMesh = srcMeshIt->second; |
468 | } |
469 | |
470 | if( !srcMesh) |
471 | { |
472 | DefaultLogger::get()->warn( format() << "Collada: Unable to find geometry for ID \"" << mid.mMeshOrController << "\". Skipping." ); |
473 | continue; |
474 | } |
475 | } else |
476 | { |
477 | // ID found in the mesh library -> direct reference to an unskinned mesh |
478 | srcMesh = srcMeshIt->second; |
479 | } |
480 | |
481 | // build a mesh for each of its subgroups |
482 | size_t vertexStart = 0, faceStart = 0; |
483 | for( size_t sm = 0; sm < srcMesh->mSubMeshes.size(); ++sm) |
484 | { |
485 | const Collada::SubMesh& submesh = srcMesh->mSubMeshes[sm]; |
486 | if( submesh.mNumFaces == 0) |
487 | continue; |
488 | |
489 | // find material assigned to this submesh |
490 | std::string meshMaterial; |
491 | std::map<std::string, Collada::SemanticMappingTable >::const_iterator meshMatIt = mid.mMaterials.find( submesh.mMaterial); |
492 | |
493 | const Collada::SemanticMappingTable* table = NULL; |
494 | if( meshMatIt != mid.mMaterials.end()) |
495 | { |
496 | table = &meshMatIt->second; |
497 | meshMaterial = table->mMatName; |
498 | } |
499 | else |
500 | { |
501 | DefaultLogger::get()->warn( format() << "Collada: No material specified for subgroup <" << submesh.mMaterial << "> in geometry <" << mid.mMeshOrController << ">." ); |
502 | if( !mid.mMaterials.empty() ) |
503 | meshMaterial = mid.mMaterials.begin()->second.mMatName; |
504 | } |
505 | |
506 | // OK ... here the *real* fun starts ... we have the vertex-input-to-effect-semantic-table |
507 | // given. The only mapping stuff which we do actually support is the UV channel. |
508 | std::map<std::string, size_t>::const_iterator matIt = mMaterialIndexByName.find( meshMaterial); |
509 | unsigned int matIdx; |
510 | if( matIt != mMaterialIndexByName.end()) |
511 | matIdx = static_cast<unsigned int>(matIt->second); |
512 | else |
513 | matIdx = 0; |
514 | |
515 | if (table && !table->mMap.empty() ) { |
516 | std::pair<Collada::Effect*, aiMaterial*>& mat = newMats[matIdx]; |
517 | |
518 | // Iterate through all texture channels assigned to the effect and |
519 | // check whether we have mapping information for it. |
520 | ApplyVertexToEffectSemanticMapping(mat.first->mTexDiffuse, *table); |
521 | ApplyVertexToEffectSemanticMapping(mat.first->mTexAmbient, *table); |
522 | ApplyVertexToEffectSemanticMapping(mat.first->mTexSpecular, *table); |
523 | ApplyVertexToEffectSemanticMapping(mat.first->mTexEmissive, *table); |
524 | ApplyVertexToEffectSemanticMapping(mat.first->mTexTransparent,*table); |
525 | ApplyVertexToEffectSemanticMapping(mat.first->mTexBump, *table); |
526 | } |
527 | |
528 | // built lookup index of the Mesh-Submesh-Material combination |
529 | ColladaMeshIndex index( mid.mMeshOrController, sm, meshMaterial); |
530 | |
531 | // if we already have the mesh at the library, just add its index to the node's array |
532 | std::map<ColladaMeshIndex, size_t>::const_iterator dstMeshIt = mMeshIndexByID.find( index); |
533 | if( dstMeshIt != mMeshIndexByID.end()) { |
534 | newMeshRefs.push_back( dstMeshIt->second); |
535 | } |
536 | else |
537 | { |
538 | // else we have to add the mesh to the collection and store its newly assigned index at the node |
539 | aiMesh* dstMesh = CreateMesh( pParser, srcMesh, submesh, srcController, vertexStart, faceStart); |
540 | |
541 | // store the mesh, and store its new index in the node |
542 | newMeshRefs.push_back( mMeshes.size()); |
543 | mMeshIndexByID[index] = mMeshes.size(); |
544 | mMeshes.push_back( dstMesh); |
545 | vertexStart += dstMesh->mNumVertices; faceStart += submesh.mNumFaces; |
546 | |
547 | // assign the material index |
548 | dstMesh->mMaterialIndex = matIdx; |
549 | if(dstMesh->mName.length == 0) |
550 | { |
551 | dstMesh->mName = mid.mMeshOrController; |
552 | } |
553 | } |
554 | } |
555 | } |
556 | |
557 | // now place all mesh references we gathered in the target node |
558 | pTarget->mNumMeshes = static_cast<unsigned int>(newMeshRefs.size()); |
559 | if( newMeshRefs.size()) |
560 | { |
561 | struct UIntTypeConverter |
562 | { |
563 | unsigned int operator()(const size_t& v) const |
564 | { |
565 | return static_cast<unsigned int>(v); |
566 | } |
567 | }; |
568 | |
569 | pTarget->mMeshes = new unsigned int[pTarget->mNumMeshes]; |
570 | std::transform( newMeshRefs.begin(), newMeshRefs.end(), pTarget->mMeshes, UIntTypeConverter()); |
571 | } |
572 | } |
573 | |
574 | // ------------------------------------------------------------------------------------------------ |
575 | // Find mesh from either meshes or morph target meshes |
576 | aiMesh *ColladaLoader::findMesh(std::string meshid) |
577 | { |
578 | for (unsigned int i = 0; i < mMeshes.size(); i++) |
579 | if (std::string(mMeshes[i]->mName.data) == meshid) |
580 | return mMeshes[i]; |
581 | |
582 | for (unsigned int i = 0; i < mTargetMeshes.size(); i++) |
583 | if (std::string(mTargetMeshes[i]->mName.data) == meshid) |
584 | return mTargetMeshes[i]; |
585 | |
586 | return NULL; |
587 | } |
588 | |
589 | // ------------------------------------------------------------------------------------------------ |
590 | // Creates a mesh for the given ColladaMesh face subset and returns the newly created mesh |
591 | aiMesh* ColladaLoader::CreateMesh( const ColladaParser& pParser, const Collada::Mesh* pSrcMesh, const Collada::SubMesh& pSubMesh, |
592 | const Collada::Controller* pSrcController, size_t pStartVertex, size_t pStartFace) |
593 | { |
594 | aiMesh* dstMesh = new aiMesh; |
595 | |
596 | dstMesh->mName = pSrcMesh->mName; |
597 | |
598 | // count the vertices addressed by its faces |
599 | const size_t numVertices = std::accumulate( pSrcMesh->mFaceSize.begin() + pStartFace, |
600 | pSrcMesh->mFaceSize.begin() + pStartFace + pSubMesh.mNumFaces, size_t(0)); |
601 | |
602 | // copy positions |
603 | dstMesh->mNumVertices = static_cast<unsigned int>(numVertices); |
604 | dstMesh->mVertices = new aiVector3D[numVertices]; |
605 | std::copy( pSrcMesh->mPositions.begin() + pStartVertex, pSrcMesh->mPositions.begin() + |
606 | pStartVertex + numVertices, dstMesh->mVertices); |
607 | |
608 | // normals, if given. HACK: (thom) Due to the glorious Collada spec we never |
609 | // know if we have the same number of normals as there are positions. So we |
610 | // also ignore any vertex attribute if it has a different count |
611 | if( pSrcMesh->mNormals.size() >= pStartVertex + numVertices) |
612 | { |
613 | dstMesh->mNormals = new aiVector3D[numVertices]; |
614 | std::copy( pSrcMesh->mNormals.begin() + pStartVertex, pSrcMesh->mNormals.begin() + |
615 | pStartVertex + numVertices, dstMesh->mNormals); |
616 | } |
617 | |
618 | // tangents, if given. |
619 | if( pSrcMesh->mTangents.size() >= pStartVertex + numVertices) |
620 | { |
621 | dstMesh->mTangents = new aiVector3D[numVertices]; |
622 | std::copy( pSrcMesh->mTangents.begin() + pStartVertex, pSrcMesh->mTangents.begin() + |
623 | pStartVertex + numVertices, dstMesh->mTangents); |
624 | } |
625 | |
626 | // bitangents, if given. |
627 | if( pSrcMesh->mBitangents.size() >= pStartVertex + numVertices) |
628 | { |
629 | dstMesh->mBitangents = new aiVector3D[numVertices]; |
630 | std::copy( pSrcMesh->mBitangents.begin() + pStartVertex, pSrcMesh->mBitangents.begin() + |
631 | pStartVertex + numVertices, dstMesh->mBitangents); |
632 | } |
633 | |
634 | // same for texturecoords, as many as we have |
635 | // empty slots are not allowed, need to pack and adjust UV indexes accordingly |
636 | for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; a++) |
637 | { |
638 | if( pSrcMesh->mTexCoords[a].size() >= pStartVertex + numVertices) |
639 | { |
640 | dstMesh->mTextureCoords[real] = new aiVector3D[numVertices]; |
641 | for( size_t b = 0; b < numVertices; ++b) |
642 | dstMesh->mTextureCoords[real][b] = pSrcMesh->mTexCoords[a][pStartVertex+b]; |
643 | |
644 | dstMesh->mNumUVComponents[real] = pSrcMesh->mNumUVComponents[a]; |
645 | ++real; |
646 | } |
647 | } |
648 | |
649 | // same for vertex colors, as many as we have. again the same packing to avoid empty slots |
650 | for( size_t a = 0, real = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS; a++) |
651 | { |
652 | if( pSrcMesh->mColors[a].size() >= pStartVertex + numVertices) |
653 | { |
654 | dstMesh->mColors[real] = new aiColor4D[numVertices]; |
655 | std::copy( pSrcMesh->mColors[a].begin() + pStartVertex, pSrcMesh->mColors[a].begin() + pStartVertex + numVertices,dstMesh->mColors[real]); |
656 | ++real; |
657 | } |
658 | } |
659 | |
660 | // create faces. Due to the fact that each face uses unique vertices, we can simply count up on each vertex |
661 | size_t vertex = 0; |
662 | dstMesh->mNumFaces = static_cast<unsigned int>(pSubMesh.mNumFaces); |
663 | dstMesh->mFaces = new aiFace[dstMesh->mNumFaces]; |
664 | for( size_t a = 0; a < dstMesh->mNumFaces; ++a) |
665 | { |
666 | size_t s = pSrcMesh->mFaceSize[ pStartFace + a]; |
667 | aiFace& face = dstMesh->mFaces[a]; |
668 | face.mNumIndices = static_cast<unsigned int>(s); |
669 | face.mIndices = new unsigned int[s]; |
670 | for( size_t b = 0; b < s; ++b) |
671 | face.mIndices[b] = static_cast<unsigned int>(vertex++); |
672 | } |
673 | |
674 | // create morph target meshes if any |
675 | std::vector<aiMesh*> targetMeshes; |
676 | std::vector<float> targetWeights; |
677 | Collada::MorphMethod method = Collada::Normalized; |
678 | |
679 | for(std::map<std::string, Collada::Controller>::const_iterator it = pParser.mControllerLibrary.begin(); |
680 | it != pParser.mControllerLibrary.end(); it++) |
681 | { |
682 | const Collada::Controller &c = it->second; |
683 | const Collada::Mesh* baseMesh = pParser.ResolveLibraryReference( pParser.mMeshLibrary, c.mMeshId); |
684 | |
685 | if (c.mType == Collada::Morph && baseMesh->mName == pSrcMesh->mName) |
686 | { |
687 | const Collada::Accessor& targetAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphTarget); |
688 | const Collada::Accessor& weightAccessor = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, c.mMorphWeight); |
689 | const Collada::Data& targetData = pParser.ResolveLibraryReference( pParser.mDataLibrary, targetAccessor.mSource); |
690 | const Collada::Data& weightData = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightAccessor.mSource); |
691 | |
692 | // take method |
693 | method = c.mMethod; |
694 | |
695 | if (!targetData.mIsStringArray) |
696 | throw DeadlyImportError( "target data must contain id. " ); |
697 | if (weightData.mIsStringArray) |
698 | throw DeadlyImportError( "target weight data must not be textual " ); |
699 | |
700 | for (unsigned int i = 0; i < targetData.mStrings.size(); ++i) |
701 | { |
702 | const Collada::Mesh* targetMesh = pParser.ResolveLibraryReference(pParser.mMeshLibrary, targetData.mStrings.at(i)); |
703 | |
704 | aiMesh *aimesh = findMesh(targetMesh->mName); |
705 | if (!aimesh) |
706 | { |
707 | if (targetMesh->mSubMeshes.size() > 1) |
708 | throw DeadlyImportError( "Morhing target mesh must be a single" ); |
709 | aimesh = CreateMesh(pParser, targetMesh, targetMesh->mSubMeshes.at(0), NULL, 0, 0); |
710 | mTargetMeshes.push_back(aimesh); |
711 | } |
712 | targetMeshes.push_back(aimesh); |
713 | } |
714 | for (unsigned int i = 0; i < weightData.mValues.size(); ++i) |
715 | targetWeights.push_back(weightData.mValues.at(i)); |
716 | } |
717 | } |
718 | if (targetMeshes.size() > 0 && targetWeights.size() == targetMeshes.size()) |
719 | { |
720 | std::vector<aiAnimMesh*> animMeshes; |
721 | for (unsigned int i = 0; i < targetMeshes.size(); i++) |
722 | { |
723 | aiAnimMesh *animMesh = aiCreateAnimMesh(targetMeshes.at(i)); |
724 | animMesh->mWeight = targetWeights[i]; |
725 | animMeshes.push_back(animMesh); |
726 | } |
727 | dstMesh->mMethod = (method == Collada::Relative) |
728 | ? aiMorphingMethod_MORPH_RELATIVE |
729 | : aiMorphingMethod_MORPH_NORMALIZED; |
730 | dstMesh->mAnimMeshes = new aiAnimMesh*[animMeshes.size()]; |
731 | dstMesh->mNumAnimMeshes = static_cast<unsigned int>(animMeshes.size()); |
732 | for (unsigned int i = 0; i < animMeshes.size(); i++) |
733 | dstMesh->mAnimMeshes[i] = animMeshes.at(i); |
734 | } |
735 | |
736 | // create bones if given |
737 | if( pSrcController && pSrcController->mType == Collada::Skin) |
738 | { |
739 | // refuse if the vertex count does not match |
740 | // if( pSrcController->mWeightCounts.size() != dstMesh->mNumVertices) |
741 | // throw DeadlyImportError( "Joint Controller vertex count does not match mesh vertex count"); |
742 | |
743 | // resolve references - joint names |
744 | const Collada::Accessor& jointNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointNameSource); |
745 | const Collada::Data& jointNames = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointNamesAcc.mSource); |
746 | // joint offset matrices |
747 | const Collada::Accessor& jointMatrixAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mJointOffsetMatrixSource); |
748 | const Collada::Data& jointMatrices = pParser.ResolveLibraryReference( pParser.mDataLibrary, jointMatrixAcc.mSource); |
749 | // joint vertex_weight name list - should refer to the same list as the joint names above. If not, report and reconsider |
750 | const Collada::Accessor& weightNamesAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputJoints.mAccessor); |
751 | if( &weightNamesAcc != &jointNamesAcc) |
752 | throw DeadlyImportError( "Temporary implementational laziness. If you read this, please report to the author." ); |
753 | // vertex weights |
754 | const Collada::Accessor& weightsAcc = pParser.ResolveLibraryReference( pParser.mAccessorLibrary, pSrcController->mWeightInputWeights.mAccessor); |
755 | const Collada::Data& weights = pParser.ResolveLibraryReference( pParser.mDataLibrary, weightsAcc.mSource); |
756 | |
757 | if( !jointNames.mIsStringArray || jointMatrices.mIsStringArray || weights.mIsStringArray) |
758 | throw DeadlyImportError( "Data type mismatch while resolving mesh joints" ); |
759 | // sanity check: we rely on the vertex weights always coming as pairs of BoneIndex-WeightIndex |
760 | if( pSrcController->mWeightInputJoints.mOffset != 0 || pSrcController->mWeightInputWeights.mOffset != 1) |
761 | throw DeadlyImportError( "Unsupported vertex_weight addressing scheme. " ); |
762 | |
763 | // create containers to collect the weights for each bone |
764 | size_t numBones = jointNames.mStrings.size(); |
765 | std::vector<std::vector<aiVertexWeight> > dstBones( numBones); |
766 | |
767 | // build a temporary array of pointers to the start of each vertex's weights |
768 | typedef std::vector< std::pair<size_t, size_t> > IndexPairVector; |
769 | std::vector<IndexPairVector::const_iterator> weightStartPerVertex; |
770 | weightStartPerVertex.resize(pSrcController->mWeightCounts.size(),pSrcController->mWeights.end()); |
771 | |
772 | IndexPairVector::const_iterator pit = pSrcController->mWeights.begin(); |
773 | for( size_t a = 0; a < pSrcController->mWeightCounts.size(); ++a) |
774 | { |
775 | weightStartPerVertex[a] = pit; |
776 | pit += pSrcController->mWeightCounts[a]; |
777 | } |
778 | |
779 | // now for each vertex put the corresponding vertex weights into each bone's weight collection |
780 | for( size_t a = pStartVertex; a < pStartVertex + numVertices; ++a) |
781 | { |
782 | // which position index was responsible for this vertex? that's also the index by which |
783 | // the controller assigns the vertex weights |
784 | size_t orgIndex = pSrcMesh->mFacePosIndices[a]; |
785 | // find the vertex weights for this vertex |
786 | IndexPairVector::const_iterator iit = weightStartPerVertex[orgIndex]; |
787 | size_t pairCount = pSrcController->mWeightCounts[orgIndex]; |
788 | |
789 | for( size_t b = 0; b < pairCount; ++b, ++iit) |
790 | { |
791 | size_t jointIndex = iit->first; |
792 | size_t vertexIndex = iit->second; |
793 | |
794 | ai_real weight = ReadFloat( weightsAcc, weights, vertexIndex, 0); |
795 | |
796 | // one day I gonna kill that XSI Collada exporter |
797 | if( weight > 0.0f) |
798 | { |
799 | aiVertexWeight w; |
800 | w.mVertexId = static_cast<unsigned int>(a - pStartVertex); |
801 | w.mWeight = weight; |
802 | dstBones[jointIndex].push_back( w); |
803 | } |
804 | } |
805 | } |
806 | |
807 | // count the number of bones which influence vertices of the current submesh |
808 | size_t numRemainingBones = 0; |
809 | for( std::vector<std::vector<aiVertexWeight> >::const_iterator it = dstBones.begin(); it != dstBones.end(); ++it) |
810 | if( it->size() > 0) |
811 | numRemainingBones++; |
812 | |
813 | // create bone array and copy bone weights one by one |
814 | dstMesh->mNumBones = static_cast<unsigned int>(numRemainingBones); |
815 | dstMesh->mBones = new aiBone*[numRemainingBones]; |
816 | size_t boneCount = 0; |
817 | for( size_t a = 0; a < numBones; ++a) |
818 | { |
819 | // omit bones without weights |
820 | if( dstBones[a].size() == 0) |
821 | continue; |
822 | |
823 | // create bone with its weights |
824 | aiBone* bone = new aiBone; |
825 | bone->mName = ReadString( jointNamesAcc, jointNames, a); |
826 | bone->mOffsetMatrix.a1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 0); |
827 | bone->mOffsetMatrix.a2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 1); |
828 | bone->mOffsetMatrix.a3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 2); |
829 | bone->mOffsetMatrix.a4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 3); |
830 | bone->mOffsetMatrix.b1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 4); |
831 | bone->mOffsetMatrix.b2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 5); |
832 | bone->mOffsetMatrix.b3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 6); |
833 | bone->mOffsetMatrix.b4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 7); |
834 | bone->mOffsetMatrix.c1 = ReadFloat( jointMatrixAcc, jointMatrices, a, 8); |
835 | bone->mOffsetMatrix.c2 = ReadFloat( jointMatrixAcc, jointMatrices, a, 9); |
836 | bone->mOffsetMatrix.c3 = ReadFloat( jointMatrixAcc, jointMatrices, a, 10); |
837 | bone->mOffsetMatrix.c4 = ReadFloat( jointMatrixAcc, jointMatrices, a, 11); |
838 | bone->mNumWeights = static_cast<unsigned int>(dstBones[a].size()); |
839 | bone->mWeights = new aiVertexWeight[bone->mNumWeights]; |
840 | std::copy( dstBones[a].begin(), dstBones[a].end(), bone->mWeights); |
841 | |
842 | // apply bind shape matrix to offset matrix |
843 | aiMatrix4x4 bindShapeMatrix; |
844 | bindShapeMatrix.a1 = pSrcController->mBindShapeMatrix[0]; |
845 | bindShapeMatrix.a2 = pSrcController->mBindShapeMatrix[1]; |
846 | bindShapeMatrix.a3 = pSrcController->mBindShapeMatrix[2]; |
847 | bindShapeMatrix.a4 = pSrcController->mBindShapeMatrix[3]; |
848 | bindShapeMatrix.b1 = pSrcController->mBindShapeMatrix[4]; |
849 | bindShapeMatrix.b2 = pSrcController->mBindShapeMatrix[5]; |
850 | bindShapeMatrix.b3 = pSrcController->mBindShapeMatrix[6]; |
851 | bindShapeMatrix.b4 = pSrcController->mBindShapeMatrix[7]; |
852 | bindShapeMatrix.c1 = pSrcController->mBindShapeMatrix[8]; |
853 | bindShapeMatrix.c2 = pSrcController->mBindShapeMatrix[9]; |
854 | bindShapeMatrix.c3 = pSrcController->mBindShapeMatrix[10]; |
855 | bindShapeMatrix.c4 = pSrcController->mBindShapeMatrix[11]; |
856 | bindShapeMatrix.d1 = pSrcController->mBindShapeMatrix[12]; |
857 | bindShapeMatrix.d2 = pSrcController->mBindShapeMatrix[13]; |
858 | bindShapeMatrix.d3 = pSrcController->mBindShapeMatrix[14]; |
859 | bindShapeMatrix.d4 = pSrcController->mBindShapeMatrix[15]; |
860 | bone->mOffsetMatrix *= bindShapeMatrix; |
861 | |
862 | // HACK: (thom) Some exporters address the bone nodes by SID, others address them by ID or even name. |
863 | // Therefore I added a little name replacement here: I search for the bone's node by either name, ID or SID, |
864 | // and replace the bone's name by the node's name so that the user can use the standard |
865 | // find-by-name method to associate nodes with bones. |
866 | const Collada::Node* bnode = FindNode( pParser.mRootNode, bone->mName.data); |
867 | if( !bnode) |
868 | bnode = FindNodeBySID( pParser.mRootNode, bone->mName.data); |
869 | |
870 | // assign the name that we would have assigned for the source node |
871 | if( bnode) |
872 | bone->mName.Set( FindNameForNode( bnode)); |
873 | else |
874 | DefaultLogger::get()->warn( format() << "ColladaLoader::CreateMesh(): could not find corresponding node for joint \"" << bone->mName.data << "\"." ); |
875 | |
876 | // and insert bone |
877 | dstMesh->mBones[boneCount++] = bone; |
878 | } |
879 | } |
880 | |
881 | return dstMesh; |
882 | } |
883 | |
884 | // ------------------------------------------------------------------------------------------------ |
885 | // Stores all meshes in the given scene |
886 | void ColladaLoader::StoreSceneMeshes( aiScene* pScene) |
887 | { |
888 | pScene->mNumMeshes = static_cast<unsigned int>(mMeshes.size()); |
889 | if( mMeshes.size() > 0) |
890 | { |
891 | pScene->mMeshes = new aiMesh*[mMeshes.size()]; |
892 | std::copy( mMeshes.begin(), mMeshes.end(), pScene->mMeshes); |
893 | mMeshes.clear(); |
894 | } |
895 | } |
896 | |
897 | // ------------------------------------------------------------------------------------------------ |
898 | // Stores all cameras in the given scene |
899 | void ColladaLoader::StoreSceneCameras( aiScene* pScene) |
900 | { |
901 | pScene->mNumCameras = static_cast<unsigned int>(mCameras.size()); |
902 | if( mCameras.size() > 0) |
903 | { |
904 | pScene->mCameras = new aiCamera*[mCameras.size()]; |
905 | std::copy( mCameras.begin(), mCameras.end(), pScene->mCameras); |
906 | mCameras.clear(); |
907 | } |
908 | } |
909 | |
910 | // ------------------------------------------------------------------------------------------------ |
911 | // Stores all lights in the given scene |
912 | void ColladaLoader::StoreSceneLights( aiScene* pScene) |
913 | { |
914 | pScene->mNumLights = static_cast<unsigned int>(mLights.size()); |
915 | if( mLights.size() > 0) |
916 | { |
917 | pScene->mLights = new aiLight*[mLights.size()]; |
918 | std::copy( mLights.begin(), mLights.end(), pScene->mLights); |
919 | mLights.clear(); |
920 | } |
921 | } |
922 | |
923 | // ------------------------------------------------------------------------------------------------ |
924 | // Stores all textures in the given scene |
925 | void ColladaLoader::StoreSceneTextures( aiScene* pScene) |
926 | { |
927 | pScene->mNumTextures = static_cast<unsigned int>(mTextures.size()); |
928 | if( mTextures.size() > 0) |
929 | { |
930 | pScene->mTextures = new aiTexture*[mTextures.size()]; |
931 | std::copy( mTextures.begin(), mTextures.end(), pScene->mTextures); |
932 | mTextures.clear(); |
933 | } |
934 | } |
935 | |
936 | // ------------------------------------------------------------------------------------------------ |
937 | // Stores all materials in the given scene |
938 | void ColladaLoader::StoreSceneMaterials( aiScene* pScene) |
939 | { |
940 | pScene->mNumMaterials = static_cast<unsigned int>(newMats.size()); |
941 | |
942 | if (newMats.size() > 0) { |
943 | pScene->mMaterials = new aiMaterial*[newMats.size()]; |
944 | for (unsigned int i = 0; i < newMats.size();++i) |
945 | pScene->mMaterials[i] = newMats[i].second; |
946 | |
947 | newMats.clear(); |
948 | } |
949 | } |
950 | |
951 | // ------------------------------------------------------------------------------------------------ |
952 | // Stores all animations |
953 | void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser) |
954 | { |
955 | // recursivly collect all animations from the collada scene |
956 | StoreAnimations( pScene, pParser, &pParser.mAnims, "" ); |
957 | |
958 | // catch special case: many animations with the same length, each affecting only a single node. |
959 | // we need to unite all those single-node-anims to a proper combined animation |
960 | for( size_t a = 0; a < mAnims.size(); ++a) |
961 | { |
962 | aiAnimation* templateAnim = mAnims[a]; |
963 | if( templateAnim->mNumChannels == 1) |
964 | { |
965 | // search for other single-channel-anims with the same duration |
966 | std::vector<size_t> collectedAnimIndices; |
967 | for( size_t b = a+1; b < mAnims.size(); ++b) |
968 | { |
969 | aiAnimation* other = mAnims[b]; |
970 | if( other->mNumChannels == 1 && other->mDuration == templateAnim->mDuration && other->mTicksPerSecond == templateAnim->mTicksPerSecond ) |
971 | collectedAnimIndices.push_back( b); |
972 | } |
973 | |
974 | // if there are other animations which fit the template anim, combine all channels into a single anim |
975 | if( !collectedAnimIndices.empty() ) |
976 | { |
977 | aiAnimation* combinedAnim = new aiAnimation(); |
978 | combinedAnim->mName = aiString( std::string( "combinedAnim_" ) + char( '0' + a)); |
979 | combinedAnim->mDuration = templateAnim->mDuration; |
980 | combinedAnim->mTicksPerSecond = templateAnim->mTicksPerSecond; |
981 | combinedAnim->mNumChannels = static_cast<unsigned int>(collectedAnimIndices.size() + 1); |
982 | combinedAnim->mChannels = new aiNodeAnim*[combinedAnim->mNumChannels]; |
983 | // add the template anim as first channel by moving its aiNodeAnim to the combined animation |
984 | combinedAnim->mChannels[0] = templateAnim->mChannels[0]; |
985 | templateAnim->mChannels[0] = NULL; |
986 | delete templateAnim; |
987 | // combined animation replaces template animation in the anim array |
988 | mAnims[a] = combinedAnim; |
989 | |
990 | // move the memory of all other anims to the combined anim and erase them from the source anims |
991 | for( size_t b = 0; b < collectedAnimIndices.size(); ++b) |
992 | { |
993 | aiAnimation* srcAnimation = mAnims[collectedAnimIndices[b]]; |
994 | combinedAnim->mChannels[1 + b] = srcAnimation->mChannels[0]; |
995 | srcAnimation->mChannels[0] = NULL; |
996 | delete srcAnimation; |
997 | } |
998 | |
999 | // in a second go, delete all the single-channel-anims that we've stripped from their channels |
1000 | // back to front to preserve indices - you know, removing an element from a vector moves all elements behind the removed one |
1001 | while( !collectedAnimIndices.empty() ) |
1002 | { |
1003 | mAnims.erase( mAnims.begin() + collectedAnimIndices.back()); |
1004 | collectedAnimIndices.pop_back(); |
1005 | } |
1006 | } |
1007 | } |
1008 | } |
1009 | |
1010 | // now store all anims in the scene |
1011 | if( !mAnims.empty()) |
1012 | { |
1013 | pScene->mNumAnimations = static_cast<unsigned int>(mAnims.size()); |
1014 | pScene->mAnimations = new aiAnimation*[mAnims.size()]; |
1015 | std::copy( mAnims.begin(), mAnims.end(), pScene->mAnimations); |
1016 | } |
1017 | |
1018 | mAnims.clear(); |
1019 | } |
1020 | |
1021 | // ------------------------------------------------------------------------------------------------ |
1022 | // Constructs the animations for the given source anim |
1023 | void ColladaLoader::StoreAnimations( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string &pPrefix) |
1024 | { |
1025 | std::string animName = pPrefix.empty() ? pSrcAnim->mName : pPrefix + "_" + pSrcAnim->mName; |
1026 | |
1027 | // create nested animations, if given |
1028 | for( std::vector<Collada::Animation*>::const_iterator it = pSrcAnim->mSubAnims.begin(); it != pSrcAnim->mSubAnims.end(); ++it) |
1029 | StoreAnimations( pScene, pParser, *it, animName); |
1030 | |
1031 | // create animation channels, if any |
1032 | if( !pSrcAnim->mChannels.empty()) |
1033 | CreateAnimation( pScene, pParser, pSrcAnim, animName); |
1034 | } |
1035 | |
1036 | struct MorphTimeValues |
1037 | { |
1038 | float mTime; |
1039 | struct key |
1040 | { |
1041 | float mWeight; |
1042 | unsigned int mValue; |
1043 | }; |
1044 | std::vector<key> mKeys; |
1045 | }; |
1046 | |
1047 | void insertMorphTimeValue(std::vector<MorphTimeValues> &values, float time, float weight, unsigned int value) |
1048 | { |
1049 | MorphTimeValues::key k; |
1050 | k.mValue = value; |
1051 | k.mWeight = weight; |
1052 | if (values.size() == 0 || time < values[0].mTime) |
1053 | { |
1054 | MorphTimeValues val; |
1055 | val.mTime = time; |
1056 | val.mKeys.push_back(k); |
1057 | values.insert(values.begin(), val); |
1058 | return; |
1059 | } |
1060 | if (time > values.back().mTime) |
1061 | { |
1062 | MorphTimeValues val; |
1063 | val.mTime = time; |
1064 | val.mKeys.push_back(k); |
1065 | values.insert(values.end(), val); |
1066 | return; |
1067 | } |
1068 | for (unsigned int i = 0; i < values.size(); i++) |
1069 | { |
1070 | if (std::abs(time - values[i].mTime) < 1e-6f) |
1071 | { |
1072 | values[i].mKeys.push_back(k); |
1073 | return; |
1074 | } else if (time > values[i].mTime && time < values[i+1].mTime) |
1075 | { |
1076 | MorphTimeValues val; |
1077 | val.mTime = time; |
1078 | val.mKeys.push_back(k); |
1079 | values.insert(values.begin() + i, val); |
1080 | return; |
1081 | } |
1082 | } |
1083 | // should not get here |
1084 | } |
1085 | |
1086 | float getWeightAtKey(const std::vector<MorphTimeValues> &values, int key, unsigned int value) |
1087 | { |
1088 | for (unsigned int i = 0; i < values[key].mKeys.size(); i++) |
1089 | { |
1090 | if (values[key].mKeys[i].mValue == value) |
1091 | return values[key].mKeys[i].mWeight; |
1092 | } |
1093 | // no value at key found, try to interpolate if present at other keys. if not, return zero |
1094 | // TODO: interpolation |
1095 | return 0.0f; |
1096 | } |
1097 | |
1098 | // ------------------------------------------------------------------------------------------------ |
1099 | // Constructs the animation for the given source anim |
1100 | void ColladaLoader::CreateAnimation( aiScene* pScene, const ColladaParser& pParser, const Collada::Animation* pSrcAnim, const std::string& pName) |
1101 | { |
1102 | // collect a list of animatable nodes |
1103 | std::vector<const aiNode*> nodes; |
1104 | CollectNodes( pScene->mRootNode, nodes); |
1105 | |
1106 | std::vector<aiNodeAnim*> anims; |
1107 | std::vector<aiMeshMorphAnim*> morphAnims; |
1108 | |
1109 | for( std::vector<const aiNode*>::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit) |
1110 | { |
1111 | // find all the collada anim channels which refer to the current node |
1112 | std::vector<Collada::ChannelEntry> entries; |
1113 | std::string nodeName = (*nit)->mName.data; |
1114 | |
1115 | // find the collada node corresponding to the aiNode |
1116 | const Collada::Node* srcNode = FindNode( pParser.mRootNode, nodeName); |
1117 | // ai_assert( srcNode != NULL); |
1118 | if( !srcNode) |
1119 | continue; |
1120 | |
1121 | // now check all channels if they affect the current node |
1122 | for( std::vector<Collada::AnimationChannel>::const_iterator cit = pSrcAnim->mChannels.begin(); |
1123 | cit != pSrcAnim->mChannels.end(); ++cit) |
1124 | { |
1125 | const Collada::AnimationChannel& srcChannel = *cit; |
1126 | Collada::ChannelEntry entry; |
1127 | |
1128 | // we expect the animation target to be of type "nodeName/transformID.subElement". Ignore all others |
1129 | // find the slash that separates the node name - there should be only one |
1130 | std::string::size_type slashPos = srcChannel.mTarget.find( '/'); |
1131 | if( slashPos == std::string::npos) |
1132 | { |
1133 | std::string::size_type targetPos = srcChannel.mTarget.find(srcNode->mID); |
1134 | if (targetPos == std::string::npos) |
1135 | continue; |
1136 | |
1137 | // not node transform, but something else. store as unknown animation channel for now |
1138 | entry.mChannel = &(*cit); |
1139 | entry.mTargetId = srcChannel.mTarget.substr(targetPos + pSrcAnim->mName.length(), |
1140 | srcChannel.mTarget.length() - targetPos - pSrcAnim->mName.length()); |
1141 | if (entry.mTargetId.front() == '-') |
1142 | entry.mTargetId = entry.mTargetId.substr(1); |
1143 | entries.push_back(entry); |
1144 | continue; |
1145 | } |
1146 | if( srcChannel.mTarget.find( '/', slashPos+1) != std::string::npos) |
1147 | continue; |
1148 | std::string targetID = srcChannel.mTarget.substr( 0, slashPos); |
1149 | if( targetID != srcNode->mID) |
1150 | continue; |
1151 | |
1152 | // find the dot that separates the transformID - there should be only one or zero |
1153 | std::string::size_type dotPos = srcChannel.mTarget.find( '.'); |
1154 | if( dotPos != std::string::npos) |
1155 | { |
1156 | if( srcChannel.mTarget.find( '.', dotPos+1) != std::string::npos) |
1157 | continue; |
1158 | |
1159 | entry.mTransformId = srcChannel.mTarget.substr( slashPos+1, dotPos - slashPos - 1); |
1160 | |
1161 | std::string subElement = srcChannel.mTarget.substr( dotPos+1); |
1162 | if( subElement == "ANGLE" ) |
1163 | entry.mSubElement = 3; // last number in an Axis-Angle-Transform is the angle |
1164 | else if( subElement == "X" ) |
1165 | entry.mSubElement = 0; |
1166 | else if( subElement == "Y" ) |
1167 | entry.mSubElement = 1; |
1168 | else if( subElement == "Z" ) |
1169 | entry.mSubElement = 2; |
1170 | else |
1171 | DefaultLogger::get()->warn( format() << "Unknown anim subelement <" << subElement << ">. Ignoring" ); |
1172 | } else |
1173 | { |
1174 | // no subelement following, transformId is remaining string |
1175 | entry.mTransformId = srcChannel.mTarget.substr( slashPos+1); |
1176 | } |
1177 | |
1178 | std::string::size_type bracketPos = srcChannel.mTarget.find('('); |
1179 | if (bracketPos != std::string::npos) |
1180 | { |
1181 | entry.mTransformId = srcChannel.mTarget.substr(slashPos + 1, bracketPos - slashPos - 1); |
1182 | std::string subElement = srcChannel.mTarget.substr(bracketPos); |
1183 | |
1184 | if (subElement == "(0)(0)" ) |
1185 | entry.mSubElement = 0; |
1186 | else if (subElement == "(1)(0)" ) |
1187 | entry.mSubElement = 1; |
1188 | else if (subElement == "(2)(0)" ) |
1189 | entry.mSubElement = 2; |
1190 | else if (subElement == "(3)(0)" ) |
1191 | entry.mSubElement = 3; |
1192 | else if (subElement == "(0)(1)" ) |
1193 | entry.mSubElement = 4; |
1194 | else if (subElement == "(1)(1)" ) |
1195 | entry.mSubElement = 5; |
1196 | else if (subElement == "(2)(1)" ) |
1197 | entry.mSubElement = 6; |
1198 | else if (subElement == "(3)(1)" ) |
1199 | entry.mSubElement = 7; |
1200 | else if (subElement == "(0)(2)" ) |
1201 | entry.mSubElement = 8; |
1202 | else if (subElement == "(1)(2)" ) |
1203 | entry.mSubElement = 9; |
1204 | else if (subElement == "(2)(2)" ) |
1205 | entry.mSubElement = 10; |
1206 | else if (subElement == "(3)(2)" ) |
1207 | entry.mSubElement = 11; |
1208 | else if (subElement == "(0)(3)" ) |
1209 | entry.mSubElement = 12; |
1210 | else if (subElement == "(1)(3)" ) |
1211 | entry.mSubElement = 13; |
1212 | else if (subElement == "(2)(3)" ) |
1213 | entry.mSubElement = 14; |
1214 | else if (subElement == "(3)(3)" ) |
1215 | entry.mSubElement = 15; |
1216 | |
1217 | } |
1218 | |
1219 | // determine which transform step is affected by this channel |
1220 | entry.mTransformIndex = SIZE_MAX; |
1221 | for( size_t a = 0; a < srcNode->mTransforms.size(); ++a) |
1222 | if( srcNode->mTransforms[a].mID == entry.mTransformId) |
1223 | entry.mTransformIndex = a; |
1224 | |
1225 | if( entry.mTransformIndex == SIZE_MAX) |
1226 | { |
1227 | if (entry.mTransformId.find("morph-weights" ) != std::string::npos) |
1228 | { |
1229 | entry.mTargetId = entry.mTransformId; |
1230 | entry.mTransformId = "" ; |
1231 | } else |
1232 | continue; |
1233 | } |
1234 | |
1235 | entry.mChannel = &(*cit); |
1236 | entries.push_back( entry); |
1237 | } |
1238 | |
1239 | // if there's no channel affecting the current node, we skip it |
1240 | if( entries.empty()) |
1241 | continue; |
1242 | |
1243 | // resolve the data pointers for all anim channels. Find the minimum time while we're at it |
1244 | ai_real startTime = ai_real( 1e20 ), endTime = ai_real( -1e20 ); |
1245 | for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it) |
1246 | { |
1247 | Collada::ChannelEntry& e = *it; |
1248 | e.mTimeAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceTimes); |
1249 | e.mTimeData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mTimeAccessor->mSource); |
1250 | e.mValueAccessor = &pParser.ResolveLibraryReference( pParser.mAccessorLibrary, e.mChannel->mSourceValues); |
1251 | e.mValueData = &pParser.ResolveLibraryReference( pParser.mDataLibrary, e.mValueAccessor->mSource); |
1252 | |
1253 | // time count and value count must match |
1254 | if( e.mTimeAccessor->mCount != e.mValueAccessor->mCount) |
1255 | throw DeadlyImportError( format() << "Time count / value count mismatch in animation channel \"" << e.mChannel->mTarget << "\"." ); |
1256 | |
1257 | if( e.mTimeAccessor->mCount > 0 ) |
1258 | { |
1259 | // find bounding times |
1260 | startTime = std::min( startTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, 0, 0)); |
1261 | endTime = std::max( endTime, ReadFloat( *e.mTimeAccessor, *e.mTimeData, e.mTimeAccessor->mCount-1, 0)); |
1262 | } |
1263 | } |
1264 | |
1265 | std::vector<aiMatrix4x4> resultTrafos; |
1266 | if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 ) |
1267 | { |
1268 | // create a local transformation chain of the node's transforms |
1269 | std::vector<Collada::Transform> transforms = srcNode->mTransforms; |
1270 | |
1271 | // now for every unique point in time, find or interpolate the key values for that time |
1272 | // and apply them to the transform chain. Then the node's present transformation can be calculated. |
1273 | ai_real time = startTime; |
1274 | while( 1) |
1275 | { |
1276 | for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it) |
1277 | { |
1278 | Collada::ChannelEntry& e = *it; |
1279 | |
1280 | // find the keyframe behind the current point in time |
1281 | size_t pos = 0; |
1282 | ai_real postTime = 0.0; |
1283 | while( 1) |
1284 | { |
1285 | if( pos >= e.mTimeAccessor->mCount) |
1286 | break; |
1287 | postTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos, 0); |
1288 | if( postTime >= time) |
1289 | break; |
1290 | ++pos; |
1291 | } |
1292 | |
1293 | pos = std::min( pos, e.mTimeAccessor->mCount-1); |
1294 | |
1295 | // read values from there |
1296 | ai_real temp[16]; |
1297 | for( size_t c = 0; c < e.mValueAccessor->mSize; ++c) |
1298 | temp[c] = ReadFloat( *e.mValueAccessor, *e.mValueData, pos, c); |
1299 | |
1300 | // if not exactly at the key time, interpolate with previous value set |
1301 | if( postTime > time && pos > 0) |
1302 | { |
1303 | ai_real preTime = ReadFloat( *e.mTimeAccessor, *e.mTimeData, pos-1, 0); |
1304 | ai_real factor = (time - postTime) / (preTime - postTime); |
1305 | |
1306 | for( size_t c = 0; c < e.mValueAccessor->mSize; ++c) |
1307 | { |
1308 | ai_real v = ReadFloat( *e.mValueAccessor, *e.mValueData, pos-1, c); |
1309 | temp[c] += (v - temp[c]) * factor; |
1310 | } |
1311 | } |
1312 | |
1313 | // Apply values to current transformation |
1314 | std::copy( temp, temp + e.mValueAccessor->mSize, transforms[e.mTransformIndex].f + e.mSubElement); |
1315 | } |
1316 | |
1317 | // Calculate resulting transformation |
1318 | aiMatrix4x4 mat = pParser.CalculateResultTransform( transforms); |
1319 | |
1320 | // out of laziness: we store the time in matrix.d4 |
1321 | mat.d4 = time; |
1322 | resultTrafos.push_back( mat); |
1323 | |
1324 | // find next point in time to evaluate. That's the closest frame larger than the current in any channel |
1325 | ai_real nextTime = ai_real( 1e20 ); |
1326 | for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it) |
1327 | { |
1328 | Collada::ChannelEntry& channelElement = *it; |
1329 | |
1330 | // find the next time value larger than the current |
1331 | size_t pos = 0; |
1332 | while( pos < channelElement.mTimeAccessor->mCount) |
1333 | { |
1334 | const ai_real t = ReadFloat( *channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); |
1335 | if( t > time) |
1336 | { |
1337 | nextTime = std::min( nextTime, t); |
1338 | break; |
1339 | } |
1340 | ++pos; |
1341 | } |
1342 | |
1343 | // https://github.com/assimp/assimp/issues/458 |
1344 | // Sub-sample axis-angle channels if the delta between two consecutive |
1345 | // key-frame angles is >= 180 degrees. |
1346 | if (transforms[channelElement.mTransformIndex].mType == Collada::TF_ROTATE && channelElement.mSubElement == 3 && pos > 0 && pos < channelElement.mTimeAccessor->mCount) { |
1347 | const ai_real cur_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos, 0); |
1348 | const ai_real last_key_angle = ReadFloat(*channelElement.mValueAccessor, *channelElement.mValueData, pos - 1, 0); |
1349 | const ai_real cur_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos, 0); |
1350 | const ai_real last_key_time = ReadFloat(*channelElement.mTimeAccessor, *channelElement.mTimeData, pos - 1, 0); |
1351 | 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); |
1352 | const ai_real delta = std::abs(cur_key_angle - last_eval_angle); |
1353 | if (delta >= 180.0) { |
1354 | const int subSampleCount = static_cast<int>(std::floor(delta / 90.0)); |
1355 | if (cur_key_time != time) { |
1356 | const ai_real nextSampleTime = time + (cur_key_time - time) / subSampleCount; |
1357 | nextTime = std::min(nextTime, nextSampleTime); |
1358 | } |
1359 | } |
1360 | } |
1361 | } |
1362 | |
1363 | // no more keys on any channel after the current time -> we're done |
1364 | if( nextTime > 1e19) |
1365 | break; |
1366 | |
1367 | // else construct next keyframe at this following time point |
1368 | time = nextTime; |
1369 | } |
1370 | } |
1371 | |
1372 | // there should be some keyframes, but we aren't that fixated on valid input data |
1373 | // ai_assert( resultTrafos.size() > 0); |
1374 | |
1375 | // build an animation channel for the given node out of these trafo keys |
1376 | if( !resultTrafos.empty() ) |
1377 | { |
1378 | aiNodeAnim* dstAnim = new aiNodeAnim; |
1379 | dstAnim->mNodeName = nodeName; |
1380 | dstAnim->mNumPositionKeys = static_cast<unsigned int>(resultTrafos.size()); |
1381 | dstAnim->mNumRotationKeys = static_cast<unsigned int>(resultTrafos.size()); |
1382 | dstAnim->mNumScalingKeys = static_cast<unsigned int>(resultTrafos.size()); |
1383 | dstAnim->mPositionKeys = new aiVectorKey[resultTrafos.size()]; |
1384 | dstAnim->mRotationKeys = new aiQuatKey[resultTrafos.size()]; |
1385 | dstAnim->mScalingKeys = new aiVectorKey[resultTrafos.size()]; |
1386 | |
1387 | for( size_t a = 0; a < resultTrafos.size(); ++a) |
1388 | { |
1389 | aiMatrix4x4 mat = resultTrafos[a]; |
1390 | double time = double( mat.d4); // remember? time is stored in mat.d4 |
1391 | mat.d4 = 1.0f; |
1392 | |
1393 | dstAnim->mPositionKeys[a].mTime = time; |
1394 | dstAnim->mRotationKeys[a].mTime = time; |
1395 | dstAnim->mScalingKeys[a].mTime = time; |
1396 | mat.Decompose( dstAnim->mScalingKeys[a].mValue, dstAnim->mRotationKeys[a].mValue, dstAnim->mPositionKeys[a].mValue); |
1397 | } |
1398 | |
1399 | anims.push_back( dstAnim); |
1400 | } else |
1401 | { |
1402 | DefaultLogger::get()->warn( "Collada loader: found empty animation channel, ignored. Please check your exporter." ); |
1403 | } |
1404 | |
1405 | if( !entries.empty() && entries.front().mTimeAccessor->mCount > 0 ) |
1406 | { |
1407 | std::vector<Collada::ChannelEntry> morphChannels; |
1408 | for( std::vector<Collada::ChannelEntry>::iterator it = entries.begin(); it != entries.end(); ++it) |
1409 | { |
1410 | Collada::ChannelEntry& e = *it; |
1411 | |
1412 | // skip non-transform types |
1413 | if (e.mTargetId.empty()) |
1414 | continue; |
1415 | |
1416 | if (e.mTargetId.find("morph-weights" ) != std::string::npos) |
1417 | morphChannels.push_back(e); |
1418 | } |
1419 | if (morphChannels.size() > 0) |
1420 | { |
1421 | // either 1) morph weight animation count should contain morph target count channels |
1422 | // or 2) one channel with morph target count arrays |
1423 | // assume first |
1424 | |
1425 | aiMeshMorphAnim *morphAnim = new aiMeshMorphAnim; |
1426 | morphAnim->mName.Set(nodeName); |
1427 | |
1428 | std::vector<MorphTimeValues> morphTimeValues; |
1429 | |
1430 | int morphAnimChannelIndex = 0; |
1431 | for( std::vector<Collada::ChannelEntry>::iterator it = morphChannels.begin(); it != morphChannels.end(); ++it) |
1432 | { |
1433 | Collada::ChannelEntry& e = *it; |
1434 | std::string::size_type apos = e.mTargetId.find('('); |
1435 | std::string::size_type bpos = e.mTargetId.find(')'); |
1436 | if (apos == std::string::npos || bpos == std::string::npos) |
1437 | // unknown way to specify weight -> ignore this animation |
1438 | continue; |
1439 | |
1440 | // weight target can be in format Weight_M_N, Weight_N, WeightN, or some other way |
1441 | // we ignore the name and just assume the channels are in the right order |
1442 | for (unsigned int i = 0; i < e.mTimeData->mValues.size(); i++) |
1443 | insertMorphTimeValue(morphTimeValues, e.mTimeData->mValues.at(i), e.mValueData->mValues.at(i), morphAnimChannelIndex); |
1444 | |
1445 | ++morphAnimChannelIndex; |
1446 | } |
1447 | |
1448 | morphAnim->mNumKeys = static_cast<unsigned int>(morphTimeValues.size()); |
1449 | morphAnim->mKeys = new aiMeshMorphKey[morphAnim->mNumKeys]; |
1450 | for (unsigned int key = 0; key < morphAnim->mNumKeys; key++) |
1451 | { |
1452 | morphAnim->mKeys[key].mNumValuesAndWeights = static_cast<unsigned int>(morphChannels.size()); |
1453 | morphAnim->mKeys[key].mValues = new unsigned int [morphChannels.size()]; |
1454 | morphAnim->mKeys[key].mWeights = new double [morphChannels.size()]; |
1455 | |
1456 | morphAnim->mKeys[key].mTime = morphTimeValues[key].mTime; |
1457 | for (unsigned int valueIndex = 0; valueIndex < morphChannels.size(); valueIndex++) |
1458 | { |
1459 | morphAnim->mKeys[key].mValues[valueIndex] = valueIndex; |
1460 | morphAnim->mKeys[key].mWeights[valueIndex] = getWeightAtKey(morphTimeValues, key, valueIndex); |
1461 | } |
1462 | } |
1463 | |
1464 | morphAnims.push_back(morphAnim); |
1465 | } |
1466 | } |
1467 | } |
1468 | |
1469 | if( !anims.empty() || !morphAnims.empty()) |
1470 | { |
1471 | aiAnimation* anim = new aiAnimation; |
1472 | anim->mName.Set( pName); |
1473 | anim->mNumChannels = static_cast<unsigned int>(anims.size()); |
1474 | if (anim->mNumChannels > 0) |
1475 | { |
1476 | anim->mChannels = new aiNodeAnim*[anims.size()]; |
1477 | std::copy( anims.begin(), anims.end(), anim->mChannels); |
1478 | } |
1479 | anim->mNumMorphMeshChannels = static_cast<unsigned int>(morphAnims.size()); |
1480 | if (anim->mNumMorphMeshChannels > 0) |
1481 | { |
1482 | anim->mMorphMeshChannels = new aiMeshMorphAnim*[anim->mNumMorphMeshChannels]; |
1483 | std::copy( morphAnims.begin(), morphAnims.end(), anim->mMorphMeshChannels); |
1484 | } |
1485 | anim->mDuration = 0.0f; |
1486 | for( size_t a = 0; a < anims.size(); ++a) |
1487 | { |
1488 | anim->mDuration = std::max( anim->mDuration, anims[a]->mPositionKeys[anims[a]->mNumPositionKeys-1].mTime); |
1489 | anim->mDuration = std::max( anim->mDuration, anims[a]->mRotationKeys[anims[a]->mNumRotationKeys-1].mTime); |
1490 | anim->mDuration = std::max( anim->mDuration, anims[a]->mScalingKeys[anims[a]->mNumScalingKeys-1].mTime); |
1491 | } |
1492 | for (size_t a = 0; a < morphAnims.size(); ++a) |
1493 | { |
1494 | anim->mDuration = std::max(anim->mDuration, morphAnims[a]->mKeys[morphAnims[a]->mNumKeys-1].mTime); |
1495 | } |
1496 | anim->mTicksPerSecond = 1; |
1497 | mAnims.push_back( anim); |
1498 | } |
1499 | } |
1500 | |
1501 | // ------------------------------------------------------------------------------------------------ |
1502 | // Add a texture to a material structure |
1503 | void ColladaLoader::AddTexture ( aiMaterial& mat, const ColladaParser& pParser, |
1504 | const Collada::Effect& effect, |
1505 | const Collada::Sampler& sampler, |
1506 | aiTextureType type, unsigned int idx) |
1507 | { |
1508 | // first of all, basic file name |
1509 | const aiString name = FindFilenameForEffectTexture( pParser, effect, sampler.mName ); |
1510 | mat.AddProperty( &name, _AI_MATKEY_TEXTURE_BASE, type, idx ); |
1511 | |
1512 | // mapping mode |
1513 | int map = aiTextureMapMode_Clamp; |
1514 | if (sampler.mWrapU) |
1515 | map = aiTextureMapMode_Wrap; |
1516 | if (sampler.mWrapU && sampler.mMirrorU) |
1517 | map = aiTextureMapMode_Mirror; |
1518 | |
1519 | mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_U_BASE, type, idx); |
1520 | |
1521 | map = aiTextureMapMode_Clamp; |
1522 | if (sampler.mWrapV) |
1523 | map = aiTextureMapMode_Wrap; |
1524 | if (sampler.mWrapV && sampler.mMirrorV) |
1525 | map = aiTextureMapMode_Mirror; |
1526 | |
1527 | mat.AddProperty( &map, 1, _AI_MATKEY_MAPPINGMODE_V_BASE, type, idx); |
1528 | |
1529 | // UV transformation |
1530 | mat.AddProperty(&sampler.mTransform, 1, |
1531 | _AI_MATKEY_UVTRANSFORM_BASE, type, idx); |
1532 | |
1533 | // Blend mode |
1534 | mat.AddProperty((int*)&sampler.mOp , 1, |
1535 | _AI_MATKEY_TEXBLEND_BASE, type, idx); |
1536 | |
1537 | // Blend factor |
1538 | mat.AddProperty((ai_real*)&sampler.mWeighting , 1, |
1539 | _AI_MATKEY_TEXBLEND_BASE, type, idx); |
1540 | |
1541 | // UV source index ... if we didn't resolve the mapping, it is actually just |
1542 | // a guess but it works in most cases. We search for the frst occurrence of a |
1543 | // number in the channel name. We assume it is the zero-based index into the |
1544 | // UV channel array of all corresponding meshes. It could also be one-based |
1545 | // for some exporters, but we won't care of it unless someone complains about. |
1546 | if (sampler.mUVId != UINT_MAX) |
1547 | map = sampler.mUVId; |
1548 | else { |
1549 | map = -1; |
1550 | for (std::string::const_iterator it = sampler.mUVChannel.begin();it != sampler.mUVChannel.end(); ++it){ |
1551 | if (IsNumeric(*it)) { |
1552 | map = strtoul10(&(*it)); |
1553 | break; |
1554 | } |
1555 | } |
1556 | if (-1 == map) { |
1557 | DefaultLogger::get()->warn("Collada: unable to determine UV channel for texture" ); |
1558 | map = 0; |
1559 | } |
1560 | } |
1561 | mat.AddProperty(&map,1,_AI_MATKEY_UVWSRC_BASE,type,idx); |
1562 | } |
1563 | |
1564 | // ------------------------------------------------------------------------------------------------ |
1565 | // Fills materials from the collada material definitions |
1566 | void ColladaLoader::FillMaterials( const ColladaParser& pParser, aiScene* /*pScene*/) |
1567 | { |
1568 | for (auto &elem : newMats) |
1569 | { |
1570 | aiMaterial& mat = (aiMaterial&)*elem.second; |
1571 | Collada::Effect& effect = *elem.first; |
1572 | |
1573 | // resolve shading mode |
1574 | int shadeMode; |
1575 | if (effect.mFaceted) /* fixme */ |
1576 | shadeMode = aiShadingMode_Flat; |
1577 | else { |
1578 | switch( effect.mShadeType) |
1579 | { |
1580 | case Collada::Shade_Constant: |
1581 | shadeMode = aiShadingMode_NoShading; |
1582 | break; |
1583 | case Collada::Shade_Lambert: |
1584 | shadeMode = aiShadingMode_Gouraud; |
1585 | break; |
1586 | case Collada::Shade_Blinn: |
1587 | shadeMode = aiShadingMode_Blinn; |
1588 | break; |
1589 | case Collada::Shade_Phong: |
1590 | shadeMode = aiShadingMode_Phong; |
1591 | break; |
1592 | |
1593 | default: |
1594 | DefaultLogger::get()->warn("Collada: Unrecognized shading mode, using gouraud shading" ); |
1595 | shadeMode = aiShadingMode_Gouraud; |
1596 | break; |
1597 | } |
1598 | } |
1599 | mat.AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); |
1600 | |
1601 | // double-sided? |
1602 | shadeMode = effect.mDoubleSided; |
1603 | mat.AddProperty<int>( &shadeMode, 1, AI_MATKEY_TWOSIDED); |
1604 | |
1605 | // wireframe? |
1606 | shadeMode = effect.mWireframe; |
1607 | mat.AddProperty<int>( &shadeMode, 1, AI_MATKEY_ENABLE_WIREFRAME); |
1608 | |
1609 | // add material colors |
1610 | mat.AddProperty( &effect.mAmbient, 1,AI_MATKEY_COLOR_AMBIENT); |
1611 | mat.AddProperty( &effect.mDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
1612 | mat.AddProperty( &effect.mSpecular, 1,AI_MATKEY_COLOR_SPECULAR); |
1613 | mat.AddProperty( &effect.mEmissive, 1, AI_MATKEY_COLOR_EMISSIVE); |
1614 | mat.AddProperty( &effect.mReflective, 1, AI_MATKEY_COLOR_REFLECTIVE); |
1615 | |
1616 | // scalar properties |
1617 | mat.AddProperty( &effect.mShininess, 1, AI_MATKEY_SHININESS); |
1618 | mat.AddProperty( &effect.mReflectivity, 1, AI_MATKEY_REFLECTIVITY); |
1619 | mat.AddProperty( &effect.mRefractIndex, 1, AI_MATKEY_REFRACTI); |
1620 | |
1621 | // transparency, a very hard one. seemingly not all files are following the |
1622 | // specification here (1.0 transparency => completely opaque)... |
1623 | // therefore, we let the opportunity for the user to manually invert |
1624 | // the transparency if necessary and we add preliminary support for RGB_ZERO mode |
1625 | if(effect.mTransparency >= 0.f && effect.mTransparency <= 1.f) { |
1626 | // handle RGB transparency completely, cf Collada specs 1.5.0 pages 249 and 304 |
1627 | if(effect.mRGBTransparency) { |
1628 | // use luminance as defined by ISO/CIE color standards (see ITU-R Recommendation BT.709-4) |
1629 | effect.mTransparency *= ( |
1630 | 0.212671f * effect.mTransparent.r + |
1631 | 0.715160f * effect.mTransparent.g + |
1632 | 0.072169f * effect.mTransparent.b |
1633 | ); |
1634 | |
1635 | effect.mTransparent.a = 1.f; |
1636 | |
1637 | mat.AddProperty( &effect.mTransparent, 1, AI_MATKEY_COLOR_TRANSPARENT ); |
1638 | } else { |
1639 | effect.mTransparency *= effect.mTransparent.a; |
1640 | } |
1641 | |
1642 | if(effect.mInvertTransparency) { |
1643 | effect.mTransparency = 1.f - effect.mTransparency; |
1644 | } |
1645 | |
1646 | // Is the material finally transparent ? |
1647 | if (effect.mHasTransparency || effect.mTransparency < 1.f) { |
1648 | mat.AddProperty( &effect.mTransparency, 1, AI_MATKEY_OPACITY ); |
1649 | } |
1650 | } |
1651 | |
1652 | // add textures, if given |
1653 | if( !effect.mTexAmbient.mName.empty()) |
1654 | /* It is merely a lightmap */ |
1655 | AddTexture( mat, pParser, effect, effect.mTexAmbient, aiTextureType_LIGHTMAP); |
1656 | |
1657 | if( !effect.mTexEmissive.mName.empty()) |
1658 | AddTexture( mat, pParser, effect, effect.mTexEmissive, aiTextureType_EMISSIVE); |
1659 | |
1660 | if( !effect.mTexSpecular.mName.empty()) |
1661 | AddTexture( mat, pParser, effect, effect.mTexSpecular, aiTextureType_SPECULAR); |
1662 | |
1663 | if( !effect.mTexDiffuse.mName.empty()) |
1664 | AddTexture( mat, pParser, effect, effect.mTexDiffuse, aiTextureType_DIFFUSE); |
1665 | |
1666 | if( !effect.mTexBump.mName.empty()) |
1667 | AddTexture( mat, pParser, effect, effect.mTexBump, aiTextureType_NORMALS); |
1668 | |
1669 | if( !effect.mTexTransparent.mName.empty()) |
1670 | AddTexture( mat, pParser, effect, effect.mTexTransparent, aiTextureType_OPACITY); |
1671 | |
1672 | if( !effect.mTexReflective.mName.empty()) |
1673 | AddTexture( mat, pParser, effect, effect.mTexReflective, aiTextureType_REFLECTION); |
1674 | } |
1675 | } |
1676 | |
1677 | // ------------------------------------------------------------------------------------------------ |
1678 | // Constructs materials from the collada material definitions |
1679 | void ColladaLoader::BuildMaterials( ColladaParser& pParser, aiScene* /*pScene*/) |
1680 | { |
1681 | newMats.reserve(pParser.mMaterialLibrary.size()); |
1682 | |
1683 | for( ColladaParser::MaterialLibrary::const_iterator matIt = pParser.mMaterialLibrary.begin(); matIt != pParser.mMaterialLibrary.end(); ++matIt) |
1684 | { |
1685 | const Collada::Material& material = matIt->second; |
1686 | // a material is only a reference to an effect |
1687 | ColladaParser::EffectLibrary::iterator effIt = pParser.mEffectLibrary.find( material.mEffect); |
1688 | if( effIt == pParser.mEffectLibrary.end()) |
1689 | continue; |
1690 | Collada::Effect& effect = effIt->second; |
1691 | |
1692 | // create material |
1693 | aiMaterial* mat = new aiMaterial; |
1694 | aiString name( material.mName.empty() ? matIt->first : material.mName ); |
1695 | mat->AddProperty(&name,AI_MATKEY_NAME); |
1696 | |
1697 | // store the material |
1698 | mMaterialIndexByName[matIt->first] = newMats.size(); |
1699 | newMats.push_back( std::pair<Collada::Effect*, aiMaterial*>( &effect,mat) ); |
1700 | } |
1701 | // ScenePreprocessor generates a default material automatically if none is there. |
1702 | // All further code here in this loader works well without a valid material so |
1703 | // we can safely let it to ScenePreprocessor. |
1704 | #if 0 |
1705 | if( newMats.size() == 0) |
1706 | { |
1707 | aiMaterial* mat = new aiMaterial; |
1708 | aiString name( AI_DEFAULT_MATERIAL_NAME ); |
1709 | mat->AddProperty( &name, AI_MATKEY_NAME); |
1710 | |
1711 | const int shadeMode = aiShadingMode_Phong; |
1712 | mat->AddProperty<int>( &shadeMode, 1, AI_MATKEY_SHADING_MODEL); |
1713 | aiColor4D colAmbient( 0.2, 0.2, 0.2, 1.0), colDiffuse( 0.8, 0.8, 0.8, 1.0), colSpecular( 0.5, 0.5, 0.5, 0.5); |
1714 | mat->AddProperty( &colAmbient, 1, AI_MATKEY_COLOR_AMBIENT); |
1715 | mat->AddProperty( &colDiffuse, 1, AI_MATKEY_COLOR_DIFFUSE); |
1716 | mat->AddProperty( &colSpecular, 1, AI_MATKEY_COLOR_SPECULAR); |
1717 | const ai_real specExp = 5.0; |
1718 | mat->AddProperty( &specExp, 1, AI_MATKEY_SHININESS); |
1719 | } |
1720 | #endif |
1721 | } |
1722 | |
1723 | // ------------------------------------------------------------------------------------------------ |
1724 | // Resolves the texture name for the given effect texture entry |
1725 | aiString ColladaLoader::FindFilenameForEffectTexture( const ColladaParser& pParser, |
1726 | const Collada::Effect& pEffect, const std::string& pName) |
1727 | { |
1728 | aiString result; |
1729 | |
1730 | // recurse through the param references until we end up at an image |
1731 | std::string name = pName; |
1732 | while( 1) |
1733 | { |
1734 | // the given string is a param entry. Find it |
1735 | Collada::Effect::ParamLibrary::const_iterator it = pEffect.mParams.find( name); |
1736 | // if not found, we're at the end of the recursion. The resulting string should be the image ID |
1737 | if( it == pEffect.mParams.end()) |
1738 | break; |
1739 | |
1740 | // else recurse on |
1741 | name = it->second.mReference; |
1742 | } |
1743 | |
1744 | // find the image referred by this name in the image library of the scene |
1745 | ColladaParser::ImageLibrary::const_iterator imIt = pParser.mImageLibrary.find( name); |
1746 | if( imIt == pParser.mImageLibrary.end()) |
1747 | { |
1748 | //missing texture should not stop the conversion |
1749 | //throw DeadlyImportError( format() << |
1750 | // "Collada: Unable to resolve effect texture entry \"" << pName << "\", ended up at ID \"" << name << "\"." ); |
1751 | |
1752 | DefaultLogger::get()->warn("Collada: Unable to resolve effect texture entry \"" + pName + "\", ended up at ID \"" + name + "\"." ); |
1753 | |
1754 | //set default texture file name |
1755 | result.Set(name + ".jpg" ); |
1756 | ConvertPath(result); |
1757 | return result; |
1758 | } |
1759 | |
1760 | // if this is an embedded texture image setup an aiTexture for it |
1761 | if (imIt->second.mFileName.empty()) |
1762 | { |
1763 | if (imIt->second.mImageData.empty()) { |
1764 | throw DeadlyImportError("Collada: Invalid texture, no data or file reference given" ); |
1765 | } |
1766 | |
1767 | aiTexture* tex = new aiTexture(); |
1768 | |
1769 | // setup format hint |
1770 | if (imIt->second.mEmbeddedFormat.length() > 3) { |
1771 | DefaultLogger::get()->warn("Collada: texture format hint is too long, truncating to 3 characters" ); |
1772 | } |
1773 | strncpy(tex->achFormatHint,imIt->second.mEmbeddedFormat.c_str(),3); |
1774 | |
1775 | // and copy texture data |
1776 | tex->mHeight = 0; |
1777 | tex->mWidth = static_cast<unsigned int>(imIt->second.mImageData.size()); |
1778 | tex->pcData = (aiTexel*)new char[tex->mWidth]; |
1779 | memcpy(tex->pcData,&imIt->second.mImageData[0],tex->mWidth); |
1780 | |
1781 | // setup texture reference string |
1782 | result.data[0] = '*'; |
1783 | result.length = 1 + ASSIMP_itoa10(result.data+1,static_cast<unsigned int>(MAXLEN-1),static_cast<int32_t>(mTextures.size())); |
1784 | |
1785 | // and add this texture to the list |
1786 | mTextures.push_back(tex); |
1787 | } |
1788 | else |
1789 | { |
1790 | result.Set( imIt->second.mFileName ); |
1791 | ConvertPath(result); |
1792 | } |
1793 | return result; |
1794 | } |
1795 | |
1796 | // ------------------------------------------------------------------------------------------------ |
1797 | // Convert a path read from a collada file to the usual representation |
1798 | void ColladaLoader::ConvertPath (aiString& ss) |
1799 | { |
1800 | // TODO: collada spec, p 22. Handle URI correctly. |
1801 | // For the moment we're just stripping the file:// away to make it work. |
1802 | // Windoes doesn't seem to be able to find stuff like |
1803 | // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' |
1804 | if (0 == strncmp(ss.data,"file://" ,7)) |
1805 | { |
1806 | ss.length -= 7; |
1807 | memmove(ss.data,ss.data+7,ss.length); |
1808 | ss.data[ss.length] = '\0'; |
1809 | } |
1810 | |
1811 | // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... |
1812 | // I need to filter it without destroying linux paths starting with "/somewhere" |
1813 | if( ss.data[0] == '/' && isalpha( ss.data[1]) && ss.data[2] == ':' ) |
1814 | { |
1815 | ss.length--; |
1816 | memmove( ss.data, ss.data+1, ss.length); |
1817 | ss.data[ss.length] = 0; |
1818 | } |
1819 | |
1820 | // find and convert all %xy special chars |
1821 | char* out = ss.data; |
1822 | for( const char* it = ss.data; it != ss.data + ss.length; /**/ ) |
1823 | { |
1824 | if( *it == '%' && (it + 3) < ss.data + ss.length ) |
1825 | { |
1826 | // separate the number to avoid dragging in chars from behind into the parsing |
1827 | char mychar[3] = { it[1], it[2], 0 }; |
1828 | size_t nbr = strtoul16( mychar); |
1829 | it += 3; |
1830 | *out++ = (char)(nbr & 0xFF); |
1831 | } else |
1832 | { |
1833 | *out++ = *it++; |
1834 | } |
1835 | } |
1836 | |
1837 | // adjust length and terminator of the shortened string |
1838 | *out = 0; |
1839 | ss.length = (ptrdiff_t) (out - ss.data); |
1840 | } |
1841 | |
1842 | // ------------------------------------------------------------------------------------------------ |
1843 | // Reads a float value from an accessor and its data array. |
1844 | ai_real ColladaLoader::ReadFloat( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const |
1845 | { |
1846 | // FIXME: (thom) Test for data type here in every access? For the moment, I leave this to the caller |
1847 | size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset + pOffset; |
1848 | ai_assert( pos < pData.mValues.size()); |
1849 | return pData.mValues[pos]; |
1850 | } |
1851 | |
1852 | // ------------------------------------------------------------------------------------------------ |
1853 | // Reads a string value from an accessor and its data array. |
1854 | const std::string& ColladaLoader::ReadString( const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex) const |
1855 | { |
1856 | size_t pos = pAccessor.mStride * pIndex + pAccessor.mOffset; |
1857 | ai_assert( pos < pData.mStrings.size()); |
1858 | return pData.mStrings[pos]; |
1859 | } |
1860 | |
1861 | // ------------------------------------------------------------------------------------------------ |
1862 | // Collects all nodes into the given array |
1863 | void ColladaLoader::CollectNodes( const aiNode* pNode, std::vector<const aiNode*>& poNodes) const |
1864 | { |
1865 | poNodes.push_back( pNode); |
1866 | |
1867 | for( size_t a = 0; a < pNode->mNumChildren; ++a) |
1868 | CollectNodes( pNode->mChildren[a], poNodes); |
1869 | } |
1870 | |
1871 | // ------------------------------------------------------------------------------------------------ |
1872 | // Finds a node in the collada scene by the given name |
1873 | const Collada::Node* ColladaLoader::FindNode( const Collada::Node* pNode, const std::string& pName) const |
1874 | { |
1875 | if( pNode->mName == pName || pNode->mID == pName) |
1876 | return pNode; |
1877 | |
1878 | for( size_t a = 0; a < pNode->mChildren.size(); ++a) |
1879 | { |
1880 | const Collada::Node* node = FindNode( pNode->mChildren[a], pName); |
1881 | if( node) |
1882 | return node; |
1883 | } |
1884 | |
1885 | return NULL; |
1886 | } |
1887 | |
1888 | // ------------------------------------------------------------------------------------------------ |
1889 | // Finds a node in the collada scene by the given SID |
1890 | const Collada::Node* ColladaLoader::FindNodeBySID( const Collada::Node* pNode, const std::string& pSID) const |
1891 | { |
1892 | if( pNode->mSID == pSID) |
1893 | return pNode; |
1894 | |
1895 | for( size_t a = 0; a < pNode->mChildren.size(); ++a) |
1896 | { |
1897 | const Collada::Node* node = FindNodeBySID( pNode->mChildren[a], pSID); |
1898 | if( node) |
1899 | return node; |
1900 | } |
1901 | |
1902 | return NULL; |
1903 | } |
1904 | |
1905 | // ------------------------------------------------------------------------------------------------ |
1906 | // Finds a proper unique name for a node derived from the collada-node's properties. |
1907 | // The name must be unique for proper node-bone association. |
1908 | std::string ColladaLoader::FindNameForNode( const Collada::Node* pNode) |
1909 | { |
1910 | // Now setup the name of the assimp node. The collada name might not be |
1911 | // unique, so we use the collada ID. |
1912 | if (!pNode->mID.empty()) |
1913 | return pNode->mID; |
1914 | else if (!pNode->mSID.empty()) |
1915 | return pNode->mSID; |
1916 | else |
1917 | { |
1918 | // No need to worry. Unnamed nodes are no problem at all, except |
1919 | // if cameras or lights need to be assigned to them. |
1920 | return format() << "$ColladaAutoName$_" << mNodeNameCounter++; |
1921 | } |
1922 | } |
1923 | |
1924 | #endif // !! ASSIMP_BUILD_NO_DAE_IMPORTER |
1925 | |