vrshoot

view libs/assimp/FBXConverter.cpp @ 1:e7ca128b8713

looks nice :)
author John Tsiombikas <nuclear@member.fsf.org>
date Sun, 02 Feb 2014 00:35:22 +0200
parents
children
line source
1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
5 Copyright (c) 2006-2012, assimp team
6 All rights reserved.
8 Redistribution and use of this software in source and binary forms,
9 with or without modification, are permitted provided that the
10 following conditions are met:
12 * Redistributions of source code must retain the above
13 copyright notice, this list of conditions and the
14 following disclaimer.
16 * Redistributions in binary form must reproduce the above
17 copyright notice, this list of conditions and the
18 following disclaimer in the documentation and/or other
19 materials provided with the distribution.
21 * Neither the name of the assimp team, nor the names of its
22 contributors may be used to endorse or promote products
23 derived from this software without specific prior
24 written permission of the assimp team.
26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 ----------------------------------------------------------------------
39 */
41 /** @file FBXConverter.cpp
42 * @brief Implementation of the FBX DOM -> aiScene converter
43 */
44 #include "AssimpPCH.h"
46 #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
48 #include <iterator>
49 #include <boost/tuple/tuple.hpp>
51 #include "FBXParser.h"
52 #include "FBXConverter.h"
53 #include "FBXDocument.h"
54 #include "FBXUtil.h"
55 #include "FBXProperties.h"
56 #include "FBXImporter.h"
58 namespace Assimp {
59 namespace FBX {
61 using namespace Util;
64 #define MAGIC_NODE_TAG "_$AssimpFbx$"
65 #define MAGIC_NULL_TAG "_$AssimpFbxNull$"
67 #define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
69 // XXX vc9's debugger won't step into anonymous namespaces
70 //namespace {
72 /** Dummy class to encapsulate the conversion process */
73 class Converter
74 {
75 public:
77 /** the different parts that make up the final local transformation of a fbx node */
78 enum TransformationComp
79 {
80 TransformationComp_Translation = 0,
81 TransformationComp_RotationOffset,
82 TransformationComp_RotationPivot,
83 TransformationComp_PreRotation,
84 TransformationComp_Rotation,
85 TransformationComp_PostRotation,
86 TransformationComp_RotationPivotInverse,
87 TransformationComp_ScalingOffset,
88 TransformationComp_ScalingPivot,
89 TransformationComp_Scaling,
90 TransformationComp_ScalingPivotInverse,
92 TransformationComp_MAXIMUM
93 };
97 public:
99 Converter(aiScene* out, const Document& doc)
100 : defaultMaterialIndex()
101 , out(out)
102 , doc(doc)
103 {
104 // animations need to be converted first since this will
105 // populate the node_anim_chain_bits map, which is needed
106 // to determine which nodes need to be generated.
107 ConvertAnimations();
108 ConvertRootNode();
110 if(doc.Settings().readAllMaterials) {
111 // unfortunately this means we have to evaluate all objects
112 BOOST_FOREACH(const ObjectMap::value_type& v,doc.Objects()) {
114 const Object* ob = v.second->Get();
115 if(!ob) {
116 continue;
117 }
119 const Material* mat = dynamic_cast<const Material*>(ob);
120 if(mat) {
122 if (materials_converted.find(mat) == materials_converted.end()) {
123 ConvertMaterial(*mat);
124 }
125 }
126 }
127 }
129 TransferDataToScene();
131 // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
132 // to make sure the scene passes assimp's validation. FBX files
133 // need not contain geometry (i.e. camera animations, raw armatures).
134 if (out->mNumMeshes == 0) {
135 out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
136 }
137 }
140 ~Converter()
141 {
142 std::for_each(meshes.begin(),meshes.end(),Util::delete_fun<aiMesh>());
143 std::for_each(materials.begin(),materials.end(),Util::delete_fun<aiMaterial>());
144 std::for_each(animations.begin(),animations.end(),Util::delete_fun<aiAnimation>());
145 std::for_each(lights.begin(),lights.end(),Util::delete_fun<aiLight>());
146 std::for_each(cameras.begin(),cameras.end(),Util::delete_fun<aiCamera>());
147 }
150 private:
152 // ------------------------------------------------------------------------------------------------
153 // find scene root and trigger recursive scene conversion
154 void ConvertRootNode()
155 {
156 out->mRootNode = new aiNode();
157 out->mRootNode->mName.Set("RootNode");
159 // root has ID 0
160 ConvertNodes(0L, *out->mRootNode);
161 }
164 // ------------------------------------------------------------------------------------------------
165 // collect and assign child nodes
166 void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4())
167 {
168 const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
170 std::vector<aiNode*> nodes;
171 nodes.reserve(conns.size());
173 std::vector<aiNode*> nodes_chain;
175 try {
176 BOOST_FOREACH(const Connection* con, conns) {
178 // ignore object-property links
179 if(con->PropertyName().length()) {
180 continue;
181 }
183 const Object* const object = con->SourceObject();
184 if(!object) {
185 FBXImporter::LogWarn("failed to convert source object for Model link");
186 continue;
187 }
189 const Model* const model = dynamic_cast<const Model*>(object);
191 if(model) {
192 nodes_chain.clear();
194 aiMatrix4x4 new_abs_transform = parent_transform;
196 // even though there is only a single input node, the design of
197 // assimp (or rather: the complicated transformation chain that
198 // is employed by fbx) means that we may need multiple aiNode's
199 // to represent a fbx node's transformation.
200 GenerateTransformationNodeChain(*model,nodes_chain);
202 ai_assert(nodes_chain.size());
204 const std::string& original_name = FixNodeName(model->Name());
206 // check if any of the nodes in the chain has the name the fbx node
207 // is supposed to have. If there is none, add another node to
208 // preserve the name - people might have scripts etc. that rely
209 // on specific node names.
210 aiNode* name_carrier = NULL;
211 BOOST_FOREACH(aiNode* prenode, nodes_chain) {
212 if ( !strcmp(prenode->mName.C_Str(), original_name.c_str()) ) {
213 name_carrier = prenode;
214 break;
215 }
216 }
218 if(!name_carrier) {
219 nodes_chain.push_back(new aiNode(original_name));
220 name_carrier = nodes_chain.back();
221 }
223 // link all nodes in a row
224 aiNode* last_parent = &parent;
225 BOOST_FOREACH(aiNode* prenode, nodes_chain) {
226 ai_assert(prenode);
228 if(last_parent != &parent) {
229 last_parent->mNumChildren = 1;
230 last_parent->mChildren = new aiNode*[1];
231 last_parent->mChildren[0] = prenode;
232 }
234 prenode->mParent = last_parent;
235 last_parent = prenode;
237 new_abs_transform *= prenode->mTransformation;
238 }
240 // attach geometry
241 ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
243 // attach sub-nodes
244 ConvertNodes(model->ID(), *nodes_chain.back(), new_abs_transform);
246 if(doc.Settings().readLights) {
247 ConvertLights(*model);
248 }
250 if(doc.Settings().readCameras) {
251 ConvertCameras(*model);
252 }
254 // preserve the info that a node was marked as Null node
255 // in the original file.
256 if(model->IsNull()) {
257 const std::string& new_name = original_name + MAGIC_NULL_TAG;
258 RenameNode(original_name, new_name);
259 name_carrier->mName.Set( new_name.c_str() );
260 }
262 nodes.push_back(nodes_chain.front());
263 nodes_chain.clear();
264 }
265 }
267 if(nodes.size()) {
268 parent.mChildren = new aiNode*[nodes.size()]();
269 parent.mNumChildren = static_cast<unsigned int>(nodes.size());
271 std::swap_ranges(nodes.begin(),nodes.end(),parent.mChildren);
272 }
273 }
274 catch(std::exception&) {
275 Util::delete_fun<aiNode> deleter;
276 std::for_each(nodes.begin(),nodes.end(),deleter);
277 std::for_each(nodes_chain.begin(),nodes_chain.end(),deleter);
278 }
279 }
282 // ------------------------------------------------------------------------------------------------
283 void ConvertLights(const Model& model)
284 {
285 const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
286 BOOST_FOREACH(const NodeAttribute* attr, node_attrs) {
287 const Light* const light = dynamic_cast<const Light*>(attr);
288 if(light) {
289 ConvertLight(model, *light);
290 }
291 }
292 }
295 // ------------------------------------------------------------------------------------------------
296 void ConvertCameras(const Model& model)
297 {
298 const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
299 BOOST_FOREACH(const NodeAttribute* attr, node_attrs) {
300 const Camera* const cam = dynamic_cast<const Camera*>(attr);
301 if(cam) {
302 ConvertCamera(model, *cam);
303 }
304 }
305 }
308 // ------------------------------------------------------------------------------------------------
309 void ConvertLight(const Model& model, const Light& light)
310 {
311 lights.push_back(new aiLight());
312 aiLight* const out_light = lights.back();
314 out_light->mName.Set(FixNodeName(model.Name()));
316 const float intensity = light.Intensity();
317 const aiVector3D& col = light.Color();
319 out_light->mColorDiffuse = aiColor3D(col.x,col.y,col.z);
320 out_light->mColorDiffuse.r *= intensity;
321 out_light->mColorDiffuse.g *= intensity;
322 out_light->mColorDiffuse.b *= intensity;
324 out_light->mColorSpecular = out_light->mColorDiffuse;
326 switch(light.LightType())
327 {
328 case Light::Type_Point:
329 out_light->mType = aiLightSource_POINT;
330 break;
332 case Light::Type_Directional:
333 out_light->mType = aiLightSource_DIRECTIONAL;
334 break;
336 case Light::Type_Spot:
337 out_light->mType = aiLightSource_SPOT;
338 out_light->mAngleOuterCone = AI_DEG_TO_RAD(light.OuterAngle());
339 out_light->mAngleInnerCone = AI_DEG_TO_RAD(light.InnerAngle());
340 break;
342 case Light::Type_Area:
343 FBXImporter::LogWarn("cannot represent area light, set to UNDEFINED");
344 out_light->mType = aiLightSource_UNDEFINED;
345 break;
347 case Light::Type_Volume:
348 FBXImporter::LogWarn("cannot represent volume light, set to UNDEFINED");
349 out_light->mType = aiLightSource_UNDEFINED;
350 break;
351 default:
352 ai_assert(false);
353 }
355 // XXX: how to best convert the near and far decay ranges?
356 switch(light.DecayType())
357 {
358 case Light::Decay_None:
359 out_light->mAttenuationConstant = 1.0f;
360 break;
361 case Light::Decay_Linear:
362 out_light->mAttenuationLinear = 1.0f;
363 break;
364 case Light::Decay_Quadratic:
365 out_light->mAttenuationQuadratic = 1.0f;
366 break;
367 case Light::Decay_Cubic:
368 FBXImporter::LogWarn("cannot represent cubic attenuation, set to Quadratic");
369 out_light->mAttenuationQuadratic = 1.0f;
370 break;
371 default:
372 ai_assert(false);
373 }
374 }
377 // ------------------------------------------------------------------------------------------------
378 void ConvertCamera(const Model& model, const Camera& cam)
379 {
380 cameras.push_back(new aiCamera());
381 aiCamera* const out_camera = cameras.back();
383 out_camera->mName.Set(FixNodeName(model.Name()));
385 out_camera->mAspect = cam.AspectWidth();
386 out_camera->mPosition = cam.Position();
387 out_camera->mLookAt = cam.InterestPosition() - out_camera->mPosition;
389 out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
390 }
393 // ------------------------------------------------------------------------------------------------
394 // this returns unified names usable within assimp identifiers (i.e. no space characters -
395 // while these would be allowed, they are a potential trouble spot so better not use them).
396 const char* NameTransformationComp(TransformationComp comp)
397 {
398 switch(comp)
399 {
400 case TransformationComp_Translation:
401 return "Translation";
402 case TransformationComp_RotationOffset:
403 return "RotationOffset";
404 case TransformationComp_RotationPivot:
405 return "RotationPivot";
406 case TransformationComp_PreRotation:
407 return "PreRotation";
408 case TransformationComp_Rotation:
409 return "Rotation";
410 case TransformationComp_PostRotation:
411 return "PostRotation";
412 case TransformationComp_RotationPivotInverse:
413 return "RotationPivotInverse";
414 case TransformationComp_ScalingOffset:
415 return "ScalingOffset";
416 case TransformationComp_ScalingPivot:
417 return "ScalingPivot";
418 case TransformationComp_Scaling:
419 return "Scaling";
420 case TransformationComp_ScalingPivotInverse:
421 return "ScalingPivotInverse";
422 case TransformationComp_MAXIMUM: // this is to silence compiler warnings
423 break;
424 }
426 ai_assert(false);
427 return NULL;
428 }
431 // ------------------------------------------------------------------------------------------------
432 // note: this returns the REAL fbx property names
433 const char* NameTransformationCompProperty(TransformationComp comp)
434 {
435 switch(comp)
436 {
437 case TransformationComp_Translation:
438 return "Lcl Translation";
439 case TransformationComp_RotationOffset:
440 return "RotationOffset";
441 case TransformationComp_RotationPivot:
442 return "RotationPivot";
443 case TransformationComp_PreRotation:
444 return "PreRotation";
445 case TransformationComp_Rotation:
446 return "Lcl Rotation";
447 case TransformationComp_PostRotation:
448 return "PostRotation";
449 case TransformationComp_RotationPivotInverse:
450 return "RotationPivotInverse";
451 case TransformationComp_ScalingOffset:
452 return "ScalingOffset";
453 case TransformationComp_ScalingPivot:
454 return "ScalingPivot";
455 case TransformationComp_Scaling:
456 return "Lcl Scaling";
457 case TransformationComp_ScalingPivotInverse:
458 return "ScalingPivotInverse";
459 case TransformationComp_MAXIMUM: // this is to silence compiler warnings
460 break;
461 }
463 ai_assert(false);
464 return NULL;
465 }
468 // ------------------------------------------------------------------------------------------------
469 aiVector3D TransformationCompDefaultValue(TransformationComp comp)
470 {
471 // XXX a neat way to solve the never-ending special cases for scaling
472 // would be to do everything in log space!
473 return comp == TransformationComp_Scaling ? aiVector3D(1.f,1.f,1.f) : aiVector3D();
474 }
477 // ------------------------------------------------------------------------------------------------
478 void GetRotationMatrix(Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out)
479 {
480 if(mode == Model::RotOrder_SphericXYZ) {
481 FBXImporter::LogError("Unsupported RotationMode: SphericXYZ");
482 out = aiMatrix4x4();
483 return;
484 }
486 const float angle_epsilon = 1e-6f;
488 out = aiMatrix4x4();
490 bool is_id[3] = { true, true, true };
492 aiMatrix4x4 temp[3];
493 if(fabs(rotation.z) > angle_epsilon) {
494 aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z),temp[2]);
495 is_id[2] = false;
496 }
497 if(fabs(rotation.y) > angle_epsilon) {
498 aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y),temp[1]);
499 is_id[1] = false;
500 }
501 if(fabs(rotation.x) > angle_epsilon) {
502 aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x),temp[0]);
503 is_id[0] = false;
504 }
506 int order[3] = {-1, -1, -1};
508 // note: rotation order is inverted since we're left multiplying as is usual in assimp
509 switch(mode)
510 {
511 case Model::RotOrder_EulerXYZ:
512 order[0] = 2;
513 order[1] = 1;
514 order[2] = 0;
515 break;
517 case Model::RotOrder_EulerXZY:
518 order[0] = 1;
519 order[1] = 2;
520 order[2] = 0;
521 break;
523 case Model::RotOrder_EulerYZX:
524 order[0] = 0;
525 order[1] = 2;
526 order[2] = 1;
527 break;
529 case Model::RotOrder_EulerYXZ:
530 order[0] = 2;
531 order[1] = 0;
532 order[2] = 1;
533 break;
535 case Model::RotOrder_EulerZXY:
536 order[0] = 1;
537 order[1] = 0;
538 order[2] = 2;
539 break;
541 case Model::RotOrder_EulerZYX:
542 order[0] = 0;
543 order[1] = 1;
544 order[2] = 2;
545 break;
547 default:
548 ai_assert(false);
549 }
551 if(!is_id[order[0]]) {
552 out = temp[order[0]];
553 }
555 if(!is_id[order[1]]) {
556 out = out * temp[order[1]];
557 }
559 if(!is_id[order[2]]) {
560 out = out * temp[order[2]];
561 }
562 }
565 // ------------------------------------------------------------------------------------------------
566 /** checks if a node has more than just scaling, rotation and translation components */
567 bool NeedsComplexTransformationChain(const Model& model)
568 {
569 const PropertyTable& props = model.Props();
570 bool ok;
572 const float zero_epsilon = 1e-6f;
573 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
574 const TransformationComp comp = static_cast<TransformationComp>(i);
576 if(comp == TransformationComp_Rotation || comp == TransformationComp_Scaling ||
577 comp == TransformationComp_Translation) {
579 continue;
580 }
582 const aiVector3D& v = PropertyGet<aiVector3D>(props,NameTransformationCompProperty(comp),ok);
583 if(ok && v.SquareLength() > zero_epsilon) {
584 return true;
585 }
586 }
588 return false;
589 }
592 // ------------------------------------------------------------------------------------------------
593 // note: name must be a FixNodeName() result
594 std::string NameTransformationChainNode(const std::string& name, TransformationComp comp)
595 {
596 return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp);
597 }
600 // ------------------------------------------------------------------------------------------------
601 /** note: memory for output_nodes will be managed by the caller */
602 void GenerateTransformationNodeChain(const Model& model,
603 std::vector<aiNode*>& output_nodes)
604 {
605 const PropertyTable& props = model.Props();
606 const Model::RotOrder rot = model.RotationOrder();
608 bool ok;
610 aiMatrix4x4 chain[TransformationComp_MAXIMUM];
611 std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
613 // generate transformation matrices for all the different transformation components
614 const float zero_epsilon = 1e-6f;
615 bool is_complex = false;
617 const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props,"PreRotation",ok);
618 if(ok && PreRotation.SquareLength() > zero_epsilon) {
619 is_complex = true;
621 GetRotationMatrix(rot, PreRotation, chain[TransformationComp_PreRotation]);
622 }
624 const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props,"PostRotation",ok);
625 if(ok && PostRotation.SquareLength() > zero_epsilon) {
626 is_complex = true;
628 GetRotationMatrix(rot, PostRotation, chain[TransformationComp_PostRotation]);
629 }
631 const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props,"RotationPivot",ok);
632 if(ok && RotationPivot.SquareLength() > zero_epsilon) {
633 is_complex = true;
635 aiMatrix4x4::Translation(RotationPivot,chain[TransformationComp_RotationPivot]);
636 aiMatrix4x4::Translation(-RotationPivot,chain[TransformationComp_RotationPivotInverse]);
637 }
639 const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props,"RotationOffset",ok);
640 if(ok && RotationOffset.SquareLength() > zero_epsilon) {
641 is_complex = true;
643 aiMatrix4x4::Translation(RotationOffset,chain[TransformationComp_RotationOffset]);
644 }
646 const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props,"ScalingOffset",ok);
647 if(ok && ScalingOffset.SquareLength() > zero_epsilon) {
648 is_complex = true;
650 aiMatrix4x4::Translation(ScalingOffset,chain[TransformationComp_ScalingOffset]);
651 }
653 const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props,"ScalingPivot",ok);
654 if(ok && ScalingPivot.SquareLength() > zero_epsilon) {
655 is_complex = true;
657 aiMatrix4x4::Translation(ScalingPivot,chain[TransformationComp_ScalingPivot]);
658 aiMatrix4x4::Translation(-ScalingPivot,chain[TransformationComp_ScalingPivotInverse]);
659 }
661 const aiVector3D& Translation = PropertyGet<aiVector3D>(props,"Lcl Translation",ok);
662 if(ok && Translation.SquareLength() > zero_epsilon) {
663 aiMatrix4x4::Translation(Translation,chain[TransformationComp_Translation]);
664 }
666 const aiVector3D& Scaling = PropertyGet<aiVector3D>(props,"Lcl Scaling",ok);
667 if(ok && fabs(Scaling.SquareLength()-1.0f) > zero_epsilon) {
668 aiMatrix4x4::Scaling(Scaling,chain[TransformationComp_Scaling]);
669 }
671 const aiVector3D& Rotation = PropertyGet<aiVector3D>(props,"Lcl Rotation",ok);
672 if(ok && Rotation.SquareLength() > zero_epsilon) {
673 GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
674 }
676 // is_complex needs to be consistent with NeedsComplexTransformationChain()
677 // or the interplay between this code and the animation converter would
678 // not be guaranteed.
679 ai_assert(NeedsComplexTransformationChain(model) == is_complex);
681 const std::string& name = FixNodeName(model.Name());
683 // now, if we have more than just Translation, Scaling and Rotation,
684 // we need to generate a full node chain to accommodate for assimp's
685 // lack to express pivots and offsets.
686 if(is_complex && doc.Settings().preservePivots) {
687 FBXImporter::LogInfo("generating full transformation chain for node: " + name);
689 // query the anim_chain_bits dictionary to find out which chain elements
690 // have associated node animation channels. These can not be dropped
691 // even if they have identity transform in bind pose.
692 NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find(name);
693 const unsigned int anim_chain_bitmask = (it == node_anim_chain_bits.end() ? 0 : (*it).second);
695 unsigned int bit = 0x1;
696 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
697 const TransformationComp comp = static_cast<TransformationComp>(i);
699 if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
700 continue;
701 }
703 aiNode* nd = new aiNode();
704 output_nodes.push_back(nd);
706 nd->mName.Set(NameTransformationChainNode(name, comp));
707 nd->mTransformation = chain[i];
708 }
710 ai_assert(output_nodes.size());
711 return;
712 }
714 // else, we can just multiply the matrices together
715 aiNode* nd = new aiNode();
716 output_nodes.push_back(nd);
718 nd->mName.Set(name);
720 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
721 nd->mTransformation = nd->mTransformation * chain[i];
722 }
723 }
726 // ------------------------------------------------------------------------------------------------
727 void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
728 {
729 const std::vector<const Geometry*>& geos = model.GetGeometry();
731 std::vector<unsigned int> meshes;
732 meshes.reserve(geos.size());
734 BOOST_FOREACH(const Geometry* geo, geos) {
736 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
737 if(mesh) {
738 const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform);
739 std::copy(indices.begin(),indices.end(),std::back_inserter(meshes) );
740 }
741 else {
742 FBXImporter::LogWarn("ignoring unrecognized geometry: " + geo->Name());
743 }
744 }
746 if(meshes.size()) {
747 nd.mMeshes = new unsigned int[meshes.size()]();
748 nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
750 std::swap_ranges(meshes.begin(),meshes.end(),nd.mMeshes);
751 }
752 }
755 // ------------------------------------------------------------------------------------------------
756 // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
757 std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh,const Model& model,
758 const aiMatrix4x4& node_global_transform)
759 {
760 std::vector<unsigned int> temp;
762 MeshMap::const_iterator it = meshes_converted.find(&mesh);
763 if (it != meshes_converted.end()) {
764 std::copy((*it).second.begin(),(*it).second.end(),std::back_inserter(temp));
765 return temp;
766 }
768 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
769 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
770 if(vertices.empty() || faces.empty()) {
771 FBXImporter::LogWarn("ignoring empty geometry: " + mesh.Name());
772 return temp;
773 }
775 // one material per mesh maps easily to aiMesh. Multiple material
776 // meshes need to be split.
777 const MatIndexArray& mindices = mesh.GetMaterialIndices();
778 if (doc.Settings().readMaterials && !mindices.empty()) {
779 const MatIndexArray::value_type base = mindices[0];
780 BOOST_FOREACH(MatIndexArray::value_type index, mindices) {
781 if(index != base) {
782 return ConvertMeshMultiMaterial(mesh, model, node_global_transform);
783 }
784 }
785 }
787 // faster codepath, just copy the data
788 temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform));
789 return temp;
790 }
793 // ------------------------------------------------------------------------------------------------
794 aiMesh* SetupEmptyMesh(const MeshGeometry& mesh)
795 {
796 aiMesh* const out_mesh = new aiMesh();
797 meshes.push_back(out_mesh);
798 meshes_converted[&mesh].push_back(static_cast<unsigned int>(meshes.size()-1));
800 // set name
801 std::string name = mesh.Name();
802 if (name.substr(0,10) == "Geometry::") {
803 name = name.substr(10);
804 }
806 if(name.length()) {
807 out_mesh->mName.Set(name);
808 }
810 return out_mesh;
811 }
814 // ------------------------------------------------------------------------------------------------
815 unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
816 const aiMatrix4x4& node_global_transform)
817 {
818 const MatIndexArray& mindices = mesh.GetMaterialIndices();
819 aiMesh* const out_mesh = SetupEmptyMesh(mesh);
821 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
822 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
824 // copy vertices
825 out_mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
826 out_mesh->mVertices = new aiVector3D[vertices.size()];
827 std::copy(vertices.begin(),vertices.end(),out_mesh->mVertices);
829 // generate dummy faces
830 out_mesh->mNumFaces = static_cast<unsigned int>(faces.size());
831 aiFace* fac = out_mesh->mFaces = new aiFace[faces.size()]();
833 unsigned int cursor = 0;
834 BOOST_FOREACH(unsigned int pcount, faces) {
835 aiFace& f = *fac++;
836 f.mNumIndices = pcount;
837 f.mIndices = new unsigned int[pcount];
838 switch(pcount)
839 {
840 case 1:
841 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
842 break;
843 case 2:
844 out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
845 break;
846 case 3:
847 out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
848 break;
849 default:
850 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
851 break;
852 }
853 for (unsigned int i = 0; i < pcount; ++i) {
854 f.mIndices[i] = cursor++;
855 }
856 }
858 // copy normals
859 const std::vector<aiVector3D>& normals = mesh.GetNormals();
860 if(normals.size()) {
861 ai_assert(normals.size() == vertices.size());
863 out_mesh->mNormals = new aiVector3D[vertices.size()];
864 std::copy(normals.begin(),normals.end(),out_mesh->mNormals);
865 }
867 // copy tangents - assimp requires both tangents and bitangents (binormals)
868 // to be present, or neither of them. Compute binormals from normals
869 // and tangents if needed.
870 const std::vector<aiVector3D>& tangents = mesh.GetTangents();
871 const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
873 if(tangents.size()) {
874 std::vector<aiVector3D> tempBinormals;
875 if (!binormals->size()) {
876 if (normals.size()) {
877 tempBinormals.resize(normals.size());
878 for (unsigned int i = 0; i < tangents.size(); ++i) {
879 tempBinormals[i] = normals[i] ^ tangents[i];
880 }
882 binormals = &tempBinormals;
883 }
884 else {
885 binormals = NULL;
886 }
887 }
889 if(binormals) {
890 ai_assert(tangents.size() == vertices.size() && binormals->size() == vertices.size());
892 out_mesh->mTangents = new aiVector3D[vertices.size()];
893 std::copy(tangents.begin(),tangents.end(),out_mesh->mTangents);
895 out_mesh->mBitangents = new aiVector3D[vertices.size()];
896 std::copy(binormals->begin(),binormals->end(),out_mesh->mBitangents);
897 }
898 }
900 // copy texture coords
901 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
902 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
903 if(uvs.empty()) {
904 break;
905 }
907 aiVector3D* out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
908 BOOST_FOREACH(const aiVector2D& v, uvs) {
909 *out_uv++ = aiVector3D(v.x,v.y,0.0f);
910 }
912 out_mesh->mNumUVComponents[i] = 2;
913 }
915 // copy vertex colors
916 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) {
917 const std::vector<aiColor4D>& colors = mesh.GetVertexColors(i);
918 if(colors.empty()) {
919 break;
920 }
922 out_mesh->mColors[i] = new aiColor4D[vertices.size()];
923 std::copy(colors.begin(),colors.end(),out_mesh->mColors[i]);
924 }
926 if(!doc.Settings().readMaterials || mindices.empty()) {
927 FBXImporter::LogError("no material assigned to mesh, setting default material");
928 out_mesh->mMaterialIndex = GetDefaultMaterial();
929 }
930 else {
931 ConvertMaterialForMesh(out_mesh,model,mesh,mindices[0]);
932 }
934 if(doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
935 ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
936 }
938 return static_cast<unsigned int>(meshes.size() - 1);
939 }
942 // ------------------------------------------------------------------------------------------------
943 std::vector<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
944 const aiMatrix4x4& node_global_transform)
945 {
946 const MatIndexArray& mindices = mesh.GetMaterialIndices();
947 ai_assert(mindices.size());
949 std::set<MatIndexArray::value_type> had;
950 std::vector<unsigned int> indices;
952 BOOST_FOREACH(MatIndexArray::value_type index, mindices) {
953 if(had.find(index) == had.end()) {
955 indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform));
956 had.insert(index);
957 }
958 }
960 return indices;
961 }
964 // ------------------------------------------------------------------------------------------------
965 unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
966 MatIndexArray::value_type index,
967 const aiMatrix4x4& node_global_transform)
968 {
969 aiMesh* const out_mesh = SetupEmptyMesh(mesh);
971 const MatIndexArray& mindices = mesh.GetMaterialIndices();
972 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
973 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
975 const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL;
977 unsigned int count_faces = 0;
978 unsigned int count_vertices = 0;
980 // count faces
981 std::vector<unsigned int>::const_iterator itf = faces.begin();
982 for(MatIndexArray::const_iterator it = mindices.begin(),
983 end = mindices.end(); it != end; ++it, ++itf)
984 {
985 if ((*it) != index) {
986 continue;
987 }
988 ++count_faces;
989 count_vertices += *itf;
990 }
992 ai_assert(count_faces);
993 ai_assert(count_vertices);
995 // mapping from output indices to DOM indexing, needed to resolve weights
996 std::vector<unsigned int> reverseMapping;
998 if (process_weights) {
999 reverseMapping.resize(count_vertices);
1002 // allocate output data arrays, but don't fill them yet
1003 out_mesh->mNumVertices = count_vertices;
1004 out_mesh->mVertices = new aiVector3D[count_vertices];
1006 out_mesh->mNumFaces = count_faces;
1007 aiFace* fac = out_mesh->mFaces = new aiFace[count_faces]();
1010 // allocate normals
1011 const std::vector<aiVector3D>& normals = mesh.GetNormals();
1012 if(normals.size()) {
1013 ai_assert(normals.size() == vertices.size());
1014 out_mesh->mNormals = new aiVector3D[vertices.size()];
1017 // allocate tangents, binormals.
1018 const std::vector<aiVector3D>& tangents = mesh.GetTangents();
1019 const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
1021 if(tangents.size()) {
1022 std::vector<aiVector3D> tempBinormals;
1023 if (!binormals->size()) {
1024 if (normals.size()) {
1025 // XXX this computes the binormals for the entire mesh, not only
1026 // the part for which we need them.
1027 tempBinormals.resize(normals.size());
1028 for (unsigned int i = 0; i < tangents.size(); ++i) {
1029 tempBinormals[i] = normals[i] ^ tangents[i];
1032 binormals = &tempBinormals;
1034 else {
1035 binormals = NULL;
1039 if(binormals) {
1040 ai_assert(tangents.size() == vertices.size() && binormals->size() == vertices.size());
1042 out_mesh->mTangents = new aiVector3D[vertices.size()];
1043 out_mesh->mBitangents = new aiVector3D[vertices.size()];
1047 // allocate texture coords
1048 unsigned int num_uvs = 0;
1049 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs) {
1050 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
1051 if(uvs.empty()) {
1052 break;
1055 out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
1056 out_mesh->mNumUVComponents[i] = 2;
1059 // allocate vertex colors
1060 unsigned int num_vcs = 0;
1061 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs) {
1062 const std::vector<aiColor4D>& colors = mesh.GetVertexColors(i);
1063 if(colors.empty()) {
1064 break;
1067 out_mesh->mColors[i] = new aiColor4D[vertices.size()];
1070 unsigned int cursor = 0, in_cursor = 0;
1072 itf = faces.begin();
1073 for(MatIndexArray::const_iterator it = mindices.begin(),
1074 end = mindices.end(); it != end; ++it, ++itf)
1076 const unsigned int pcount = *itf;
1077 if ((*it) != index) {
1078 in_cursor += pcount;
1079 continue;
1082 aiFace& f = *fac++;
1084 f.mNumIndices = pcount;
1085 f.mIndices = new unsigned int[pcount];
1086 switch(pcount)
1088 case 1:
1089 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
1090 break;
1091 case 2:
1092 out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
1093 break;
1094 case 3:
1095 out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
1096 break;
1097 default:
1098 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
1099 break;
1101 for (unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor) {
1102 f.mIndices[i] = cursor;
1104 if(reverseMapping.size()) {
1105 reverseMapping[cursor] = in_cursor;
1108 out_mesh->mVertices[cursor] = vertices[in_cursor];
1110 if(out_mesh->mNormals) {
1111 out_mesh->mNormals[cursor] = normals[in_cursor];
1114 if(out_mesh->mTangents) {
1115 out_mesh->mTangents[cursor] = tangents[in_cursor];
1116 out_mesh->mBitangents[cursor] = (*binormals)[in_cursor];
1119 for (unsigned int i = 0; i < num_uvs; ++i) {
1120 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
1121 out_mesh->mTextureCoords[i][cursor] = aiVector3D(uvs[in_cursor].x,uvs[in_cursor].y, 0.0f);
1124 for (unsigned int i = 0; i < num_vcs; ++i) {
1125 const std::vector<aiColor4D>& cols = mesh.GetVertexColors(i);
1126 out_mesh->mColors[i][cursor] = cols[in_cursor];
1131 ConvertMaterialForMesh(out_mesh,model,mesh,index);
1133 if(process_weights) {
1134 ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
1137 return static_cast<unsigned int>(meshes.size() - 1);
1140 static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
1141 static_cast<unsigned int>(-1);
1144 // ------------------------------------------------------------------------------------------------
1145 /** - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into
1146 * account when determining which weights to include.
1147 * - outputVertStartIndices is only used when a material index is specified, it gives for
1148 * each output vertex the DOM index it maps to. */
1149 void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
1150 const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
1151 unsigned int materialIndex = NO_MATERIAL_SEPARATION,
1152 std::vector<unsigned int>* outputVertStartIndices = NULL)
1154 ai_assert(geo.DeformerSkin());
1156 std::vector<size_t> out_indices;
1157 std::vector<size_t> index_out_indices;
1158 std::vector<size_t> count_out_indices;
1160 const Skin& sk = *geo.DeformerSkin();
1162 std::vector<aiBone*> bones;
1163 bones.reserve(sk.Clusters().size());
1165 const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
1166 ai_assert(no_mat_check || outputVertStartIndices);
1168 try {
1170 BOOST_FOREACH(const Cluster* cluster, sk.Clusters()) {
1171 ai_assert(cluster);
1173 const WeightIndexArray& indices = cluster->GetIndices();
1175 if(indices.empty()) {
1176 continue;
1179 const MatIndexArray& mats = geo.GetMaterialIndices();
1181 bool ok = false;
1183 const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
1185 count_out_indices.clear();
1186 index_out_indices.clear();
1187 out_indices.clear();
1189 // now check if *any* of these weights is contained in the output mesh,
1190 // taking notes so we don't need to do it twice.
1191 BOOST_FOREACH(WeightIndexArray::value_type index, indices) {
1193 unsigned int count;
1194 const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
1196 index_out_indices.push_back(no_index_sentinel);
1197 count_out_indices.push_back(0);
1199 for(unsigned int i = 0; i < count; ++i) {
1200 if (no_mat_check || static_cast<size_t>(mats[geo.FaceForVertexIndex(out_idx[i])]) == materialIndex) {
1202 if (index_out_indices.back() == no_index_sentinel) {
1203 index_out_indices.back() = out_indices.size();
1207 if (no_mat_check) {
1208 out_indices.push_back(out_idx[i]);
1210 else {
1211 // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn)
1212 const std::vector<unsigned int>::iterator it = std::lower_bound(
1213 outputVertStartIndices->begin(),
1214 outputVertStartIndices->end(),
1215 out_idx[i]
1216 );
1218 out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
1221 ++count_out_indices.back();
1222 ok = true;
1227 // if we found at least one, generate the output bones
1228 // XXX this could be heavily simplified by collecting the bone
1229 // data in a single step.
1230 if (ok) {
1231 ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
1232 count_out_indices, node_global_transform);
1236 catch (std::exception&) {
1237 std::for_each(bones.begin(),bones.end(),Util::delete_fun<aiBone>());
1238 throw;
1241 if(bones.empty()) {
1242 return;
1245 out->mBones = new aiBone*[bones.size()]();
1246 out->mNumBones = static_cast<unsigned int>(bones.size());
1248 std::swap_ranges(bones.begin(),bones.end(),out->mBones);
1253 // ------------------------------------------------------------------------------------------------
1254 void ConvertCluster(std::vector<aiBone*>& bones, const Model& model, const Cluster& cl,
1255 std::vector<size_t>& out_indices,
1256 std::vector<size_t>& index_out_indices,
1257 std::vector<size_t>& count_out_indices,
1258 const aiMatrix4x4& node_global_transform)
1261 aiBone* const bone = new aiBone();
1262 bones.push_back(bone);
1264 bone->mName = FixNodeName(cl.TargetNode()->Name());
1266 bone->mOffsetMatrix = cl.TransformLink();
1267 bone->mOffsetMatrix.Inverse();
1269 bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
1271 bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
1272 aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
1274 const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
1275 const WeightArray& weights = cl.GetWeights();
1277 const size_t c = index_out_indices.size();
1278 for (size_t i = 0; i < c; ++i) {
1279 const size_t index_index = index_out_indices[i];
1281 if (index_index == no_index_sentinel) {
1282 continue;
1285 const size_t cc = count_out_indices[i];
1286 for (size_t j = 0; j < cc; ++j) {
1287 aiVertexWeight& out_weight = *cursor++;
1289 out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
1290 out_weight.mWeight = weights[i];
1296 // ------------------------------------------------------------------------------------------------
1297 void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
1298 MatIndexArray::value_type materialIndex)
1300 // locate source materials for this mesh
1301 const std::vector<const Material*>& mats = model.GetMaterials();
1302 if (static_cast<unsigned int>(materialIndex) >= mats.size() || materialIndex < 0) {
1303 FBXImporter::LogError("material index out of bounds, setting default material");
1304 out->mMaterialIndex = GetDefaultMaterial();
1305 return;
1308 const Material* const mat = mats[materialIndex];
1309 MaterialMap::const_iterator it = materials_converted.find(mat);
1310 if (it != materials_converted.end()) {
1311 out->mMaterialIndex = (*it).second;
1312 return;
1315 out->mMaterialIndex = ConvertMaterial(*mat);
1316 materials_converted[mat] = out->mMaterialIndex;
1320 // ------------------------------------------------------------------------------------------------
1321 unsigned int GetDefaultMaterial()
1323 if (defaultMaterialIndex) {
1324 return defaultMaterialIndex - 1;
1327 aiMaterial* out_mat = new aiMaterial();
1328 materials.push_back(out_mat);
1330 const aiColor3D diffuse = aiColor3D(0.8f,0.8f,0.8f);
1331 out_mat->AddProperty(&diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
1333 aiString s;
1334 s.Set(AI_DEFAULT_MATERIAL_NAME);
1336 out_mat->AddProperty(&s,AI_MATKEY_NAME);
1338 defaultMaterialIndex = static_cast<unsigned int>(materials.size());
1339 return defaultMaterialIndex - 1;
1343 // ------------------------------------------------------------------------------------------------
1344 // Material -> aiMaterial
1345 unsigned int ConvertMaterial(const Material& material)
1347 const PropertyTable& props = material.Props();
1349 // generate empty output material
1350 aiMaterial* out_mat = new aiMaterial();
1351 materials_converted[&material] = static_cast<unsigned int>(materials.size());
1353 materials.push_back(out_mat);
1355 aiString str;
1357 // stip Material:: prefix
1358 std::string name = material.Name();
1359 if(name.substr(0,10) == "Material::") {
1360 name = name.substr(10);
1363 // set material name if not empty - this could happen
1364 // and there should be no key for it in this case.
1365 if(name.length()) {
1366 str.Set(name);
1367 out_mat->AddProperty(&str,AI_MATKEY_NAME);
1370 // shading stuff and colors
1371 SetShadingPropertiesCommon(out_mat,props);
1373 // texture assignments
1374 SetTextureProperties(out_mat,material.Textures());
1376 return static_cast<unsigned int>(materials.size() - 1);
1380 // ------------------------------------------------------------------------------------------------
1381 void TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures,
1382 const std::string& propName,
1383 aiTextureType target)
1385 TextureMap::const_iterator it = textures.find(propName);
1386 if(it == textures.end()) {
1387 return;
1390 const Texture* const tex = (*it).second;
1392 aiString path;
1393 path.Set(tex->RelativeFilename());
1395 out_mat->AddProperty(&path,_AI_MATKEY_TEXTURE_BASE,target,0);
1397 aiUVTransform uvTrafo;
1398 // XXX handle all kinds of UV transformations
1399 uvTrafo.mScaling = tex->UVScaling();
1400 uvTrafo.mTranslation = tex->UVTranslation();
1401 out_mat->AddProperty(&uvTrafo,1,_AI_MATKEY_UVTRANSFORM_BASE,target,0);
1403 const PropertyTable& props = tex->Props();
1405 int uvIndex = 0;
1407 bool ok;
1408 const std::string& uvSet = PropertyGet<std::string>(props,"UVSet",ok);
1409 if(ok) {
1410 // "default" is the name which usually appears in the FbxFileTexture template
1411 if(uvSet != "default" && uvSet.length()) {
1412 // this is a bit awkward - we need to find a mesh that uses this
1413 // material and scan its UV channels for the given UV name because
1414 // assimp references UV channels by index, not by name.
1416 // XXX: the case that UV channels may appear in different orders
1417 // in meshes is unhandled. A possible solution would be to sort
1418 // the UV channels alphabetically, but this would have the side
1419 // effect that the primary (first) UV channel would sometimes
1420 // be moved, causing trouble when users read only the first
1421 // UV channel and ignore UV channel assignments altogether.
1423 const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(),
1424 std::find(materials.begin(),materials.end(),out_mat)
1425 ));
1427 uvIndex = -1;
1428 BOOST_FOREACH(const MeshMap::value_type& v,meshes_converted) {
1429 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> (v.first);
1430 if(!mesh) {
1431 continue;
1434 const MatIndexArray& mats = mesh->GetMaterialIndices();
1435 if(std::find(mats.begin(),mats.end(),matIndex) == mats.end()) {
1436 continue;
1439 int index = -1;
1440 for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
1441 if(mesh->GetTextureCoords(i).empty()) {
1442 break;
1444 const std::string& name = mesh->GetTextureCoordChannelName(i);
1445 if(name == uvSet) {
1446 index = static_cast<int>(i);
1447 break;
1450 if(index == -1) {
1451 FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
1452 continue;
1455 if(uvIndex == -1) {
1456 uvIndex = index;
1458 else {
1459 FBXImporter::LogWarn("the UV channel named " + uvSet +
1460 " appears at different positions in meshes, results will be wrong");
1464 if(uvIndex == -1) {
1465 FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel");
1466 uvIndex = 0;
1471 out_mat->AddProperty(&uvIndex,1,_AI_MATKEY_UVWSRC_BASE,target,0);
1475 // ------------------------------------------------------------------------------------------------
1476 void SetTextureProperties(aiMaterial* out_mat, const TextureMap& textures)
1478 TrySetTextureProperties(out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE);
1479 TrySetTextureProperties(out_mat, textures, "AmbientColor", aiTextureType_AMBIENT);
1480 TrySetTextureProperties(out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE);
1481 TrySetTextureProperties(out_mat, textures, "SpecularColor", aiTextureType_SPECULAR);
1482 TrySetTextureProperties(out_mat, textures, "TransparentColor", aiTextureType_OPACITY);
1483 TrySetTextureProperties(out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION);
1484 TrySetTextureProperties(out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT);
1485 TrySetTextureProperties(out_mat, textures, "NormalMap", aiTextureType_NORMALS);
1486 TrySetTextureProperties(out_mat, textures, "Bump", aiTextureType_HEIGHT);
1491 // ------------------------------------------------------------------------------------------------
1492 aiColor3D GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName,
1493 bool& result)
1495 result = true;
1497 bool ok;
1498 const aiVector3D& Diffuse = PropertyGet<aiVector3D>(props,baseName,ok);
1499 if(ok) {
1500 return aiColor3D(Diffuse.x,Diffuse.y,Diffuse.z);
1502 else {
1503 aiVector3D DiffuseColor = PropertyGet<aiVector3D>(props,baseName + "Color",ok);
1504 if(ok) {
1505 float DiffuseFactor = PropertyGet<float>(props,baseName + "Factor",ok);
1506 if(ok) {
1507 DiffuseColor *= DiffuseFactor;
1510 return aiColor3D(DiffuseColor.x,DiffuseColor.y,DiffuseColor.z);
1513 result = false;
1514 return aiColor3D(0.0f,0.0f,0.0f);
1518 // ------------------------------------------------------------------------------------------------
1519 void SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props)
1521 // set shading properties. There are various, redundant ways in which FBX materials
1522 // specify their shading settings (depending on shading models, prop
1523 // template etc.). No idea which one is right in a particular context.
1524 // Just try to make sense of it - there's no spec to verify this against,
1525 // so why should we.
1526 bool ok;
1527 const aiColor3D& Diffuse = GetColorPropertyFromMaterial(props,"Diffuse",ok);
1528 if(ok) {
1529 out_mat->AddProperty(&Diffuse,1,AI_MATKEY_COLOR_DIFFUSE);
1532 const aiColor3D& Emissive = GetColorPropertyFromMaterial(props,"Emissive",ok);
1533 if(ok) {
1534 out_mat->AddProperty(&Emissive,1,AI_MATKEY_COLOR_EMISSIVE);
1537 const aiColor3D& Ambient = GetColorPropertyFromMaterial(props,"Ambient",ok);
1538 if(ok) {
1539 out_mat->AddProperty(&Ambient,1,AI_MATKEY_COLOR_AMBIENT);
1542 const aiColor3D& Specular = GetColorPropertyFromMaterial(props,"Specular",ok);
1543 if(ok) {
1544 out_mat->AddProperty(&Specular,1,AI_MATKEY_COLOR_SPECULAR);
1547 const float Opacity = PropertyGet<float>(props,"Opacity",ok);
1548 if(ok) {
1549 out_mat->AddProperty(&Opacity,1,AI_MATKEY_OPACITY);
1552 const float Reflectivity = PropertyGet<float>(props,"Reflectivity",ok);
1553 if(ok) {
1554 out_mat->AddProperty(&Reflectivity,1,AI_MATKEY_REFLECTIVITY);
1557 const float Shininess = PropertyGet<float>(props,"Shininess",ok);
1558 if(ok) {
1559 out_mat->AddProperty(&Shininess,1,AI_MATKEY_SHININESS_STRENGTH);
1562 const float ShininessExponent = PropertyGet<float>(props,"ShininessExponent",ok);
1563 if(ok) {
1564 out_mat->AddProperty(&ShininessExponent,1,AI_MATKEY_SHININESS);
1569 // ------------------------------------------------------------------------------------------------
1570 // get the number of fps for a FrameRate enumerated value
1571 static double FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0)
1573 switch(fp) {
1574 case FileGlobalSettings::FrameRate_DEFAULT:
1575 return 1.0;
1577 case FileGlobalSettings::FrameRate_120:
1578 return 120.0;
1580 case FileGlobalSettings::FrameRate_100:
1581 return 100.0;
1583 case FileGlobalSettings::FrameRate_60:
1584 return 60.0;
1586 case FileGlobalSettings::FrameRate_50:
1587 return 50.0;
1589 case FileGlobalSettings::FrameRate_48:
1590 return 48.0;
1592 case FileGlobalSettings::FrameRate_30:
1593 case FileGlobalSettings::FrameRate_30_DROP:
1594 return 30.0;
1596 case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME:
1597 case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME:
1598 return 29.9700262;
1600 case FileGlobalSettings::FrameRate_PAL:
1601 return 25.0;
1603 case FileGlobalSettings::FrameRate_CINEMA:
1604 return 24.0;
1606 case FileGlobalSettings::FrameRate_1000:
1607 return 1000.0;
1609 case FileGlobalSettings::FrameRate_CINEMA_ND:
1610 return 23.976;
1612 case FileGlobalSettings::FrameRate_CUSTOM:
1613 return customFPSVal;
1615 case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings
1616 break;
1619 ai_assert(false);
1620 return -1.0f;
1624 // ------------------------------------------------------------------------------------------------
1625 // convert animation data to aiAnimation et al
1626 void ConvertAnimations()
1628 // first of all determine framerate
1629 const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode();
1630 const float custom = doc.GlobalSettings().CustomFrameRate();
1631 anim_fps = FrameRateToDouble(fps, custom);
1633 const std::vector<const AnimationStack*>& animations = doc.AnimationStacks();
1634 BOOST_FOREACH(const AnimationStack* stack, animations) {
1635 ConvertAnimationStack(*stack);
1640 // ------------------------------------------------------------------------------------------------
1641 // rename a node already partially converted. fixed_name is a string previously returned by
1642 // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
1643 // which would previously have returned the old value.
1644 //
1645 // this also updates names in node animations, cameras and light sources and is thus slow.
1646 //
1647 // NOTE: the caller is responsible for ensuring that the new name is unique and does
1648 // not collide with any other identifiers. The best way to ensure this is to only
1649 // append to the old name, which is guaranteed to match these requirements.
1650 void RenameNode(const std::string& fixed_name, const std::string& new_name)
1652 ai_assert(node_names.find(fixed_name) != node_names.end());
1653 ai_assert(node_names.find(new_name) == node_names.end());
1655 renamed_nodes[fixed_name] = new_name;
1657 const aiString fn(fixed_name);
1659 BOOST_FOREACH(aiCamera* cam, cameras) {
1660 if (cam->mName == fn) {
1661 cam->mName.Set(new_name);
1662 break;
1666 BOOST_FOREACH(aiLight* light, lights) {
1667 if (light->mName == fn) {
1668 light->mName.Set(new_name);
1669 break;
1673 BOOST_FOREACH(aiAnimation* anim, animations) {
1674 for (unsigned int i = 0; i < anim->mNumChannels; ++i) {
1675 aiNodeAnim* const na = anim->mChannels[i];
1676 if (na->mNodeName == fn) {
1677 na->mNodeName.Set(new_name);
1678 break;
1685 // ------------------------------------------------------------------------------------------------
1686 // takes a fbx node name and returns the identifier to be used in the assimp output scene.
1687 // the function is guaranteed to provide consistent results over multiple invocations
1688 // UNLESS RenameNode() is called for a particular node name.
1689 std::string FixNodeName(const std::string& name)
1691 // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if
1692 // this causes ambiguities, well possible between empty identifiers,
1693 // such as "Model::" and ""). Make sure the behaviour is consistent
1694 // across multiple calls to FixNodeName().
1695 if(name.substr(0,7) == "Model::") {
1696 std::string temp = name.substr(7);
1698 const NodeNameMap::const_iterator it = node_names.find(temp);
1699 if (it != node_names.end()) {
1700 if (!(*it).second) {
1701 return FixNodeName(name + "_");
1704 node_names[temp] = true;
1706 const NameNameMap::const_iterator rit = renamed_nodes.find(temp);
1707 return rit == renamed_nodes.end() ? temp : (*rit).second;
1710 const NodeNameMap::const_iterator it = node_names.find(name);
1711 if (it != node_names.end()) {
1712 if ((*it).second) {
1713 return FixNodeName(name + "_");
1716 node_names[name] = false;
1718 const NameNameMap::const_iterator rit = renamed_nodes.find(name);
1719 return rit == renamed_nodes.end() ? name : (*rit).second;
1723 typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
1725 // XXX: better use multi_map ..
1726 typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
1729 // ------------------------------------------------------------------------------------------------
1730 void ConvertAnimationStack(const AnimationStack& st)
1732 const AnimationLayerList& layers = st.Layers();
1733 if(layers.empty()) {
1734 return;
1737 aiAnimation* const anim = new aiAnimation();
1738 animations.push_back(anim);
1740 // strip AnimationStack:: prefix
1741 std::string name = st.Name();
1742 if(name.substr(0,16) == "AnimationStack::") {
1743 name = name.substr(16);
1746 anim->mName.Set(name);
1748 // need to find all nodes for which we need to generate node animations -
1749 // it may happen that we need to merge multiple layers, though.
1750 NodeMap node_map;
1752 // reverse mapping from curves to layers, much faster than querying
1753 // the FBX DOM for it.
1754 LayerMap layer_map;
1756 const char* prop_whitelist[] = {
1757 "Lcl Scaling",
1758 "Lcl Rotation",
1759 "Lcl Translation"
1760 };
1762 BOOST_FOREACH(const AnimationLayer* layer, layers) {
1763 ai_assert(layer);
1765 const AnimationCurveNodeList& nodes = layer->Nodes(prop_whitelist, 3);
1766 BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
1767 ai_assert(node);
1769 const Model* const model = dynamic_cast<const Model*>(node->Target());
1770 // this can happen - it could also be a NodeAttribute (i.e. for camera animations)
1771 if(!model) {
1772 continue;
1775 const std::string& name = FixNodeName(model->Name());
1776 node_map[name].push_back(node);
1778 layer_map[node] = layer;
1782 // generate node animations
1783 std::vector<aiNodeAnim*> node_anims;
1785 double min_time = 1e10;
1786 double max_time = -1e10;
1788 try {
1789 BOOST_FOREACH(const NodeMap::value_type& kv, node_map) {
1790 GenerateNodeAnimations(node_anims,
1791 kv.first,
1792 kv.second,
1793 layer_map,
1794 max_time,
1795 min_time);
1798 catch(std::exception&) {
1799 std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>());
1800 throw;
1803 if(node_anims.size()) {
1804 anim->mChannels = new aiNodeAnim*[node_anims.size()]();
1805 anim->mNumChannels = static_cast<unsigned int>(node_anims.size());
1807 std::swap_ranges(node_anims.begin(),node_anims.end(),anim->mChannels);
1809 else {
1810 // empty animations would fail validation, so drop them
1811 delete anim;
1812 animations.pop_back();
1813 FBXImporter::LogInfo("ignoring empty AnimationStack (using IK?): " + name);
1814 return;
1817 // for some mysterious reason, mDuration is simply the maximum key -- the
1818 // validator always assumes animations to start at zero.
1819 anim->mDuration = max_time /*- min_time */;
1820 anim->mTicksPerSecond = anim_fps;
1824 // ------------------------------------------------------------------------------------------------
1825 void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
1826 const std::string& fixed_name,
1827 const std::vector<const AnimationCurveNode*>& curves,
1828 const LayerMap& layer_map,
1829 double& max_time,
1830 double& min_time)
1833 NodeMap node_property_map;
1834 ai_assert(curves.size());
1836 // sanity check whether the input is ok
1837 #ifdef _DEBUG
1838 { const Object* target = NULL;
1839 BOOST_FOREACH(const AnimationCurveNode* node, curves) {
1840 if(!target) {
1841 target = node->Target();
1843 ai_assert(node->Target() == target);
1844 }}
1845 #endif
1847 const AnimationCurveNode* curve_node;
1848 BOOST_FOREACH(const AnimationCurveNode* node, curves) {
1849 ai_assert(node);
1851 if (node->TargetProperty().empty()) {
1852 FBXImporter::LogWarn("target property for animation curve not set: " + node->Name());
1853 continue;
1856 curve_node = node;
1857 if (node->Curves().empty()) {
1858 FBXImporter::LogWarn("no animation curves assigned to AnimationCurveNode: " + node->Name());
1859 continue;
1862 node_property_map[node->TargetProperty()].push_back(node);
1865 ai_assert(curve_node);
1866 ai_assert(curve_node->TargetAsModel());
1868 const Model& target = *curve_node->TargetAsModel();
1870 // check for all possible transformation components
1871 NodeMap::const_iterator chain[TransformationComp_MAXIMUM];
1873 bool has_any = false;
1874 bool has_complex = false;
1876 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
1877 const TransformationComp comp = static_cast<TransformationComp>(i);
1879 // inverse pivots don't exist in the input, we just generate them
1880 if (comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse) {
1881 chain[i] = node_property_map.end();
1882 continue;
1885 chain[i] = node_property_map.find(NameTransformationCompProperty(comp));
1886 if (chain[i] != node_property_map.end()) {
1888 // check if this curves contains redundant information by looking
1889 // up the corresponding node's transformation chain.
1890 if (doc.Settings().optimizeEmptyAnimationCurves &&
1891 IsRedundantAnimationData(target, comp, (*chain[i]).second)) {
1893 FBXImporter::LogDebug("dropping redundant animation channel for node " + target.Name());
1894 continue;
1897 has_any = true;
1899 if (comp != TransformationComp_Rotation && comp != TransformationComp_Scaling &&
1900 comp != TransformationComp_Translation) {
1902 has_complex = true;
1907 if (!has_any) {
1908 FBXImporter::LogWarn("ignoring node animation, did not find any transformation key frames");
1909 return;
1912 // this needs to play nicely with GenerateTransformationNodeChain() which will
1913 // be invoked _later_ (animations come first). If this node has only rotation,
1914 // scaling and translation _and_ there are no animated other components either,
1915 // we can use a single node and also a single node animation channel.
1916 if (!has_complex && !NeedsComplexTransformationChain(target)) {
1918 aiNodeAnim* const nd = GenerateSimpleNodeAnim(fixed_name, target, chain,
1919 node_property_map.end(),
1920 layer_map,
1921 max_time,
1922 min_time,
1923 true // input is TRS order, assimp is SRT
1924 );
1926 ai_assert(nd);
1927 node_anims.push_back(nd);
1928 return;
1931 // otherwise, things get gruesome and we need separate animation channels
1932 // for each part of the transformation chain. Remember which channels
1933 // we generated and pass this information to the node conversion
1934 // code to avoid nodes that have identity transform, but non-identity
1935 // animations, being dropped.
1936 unsigned int flags = 0, bit = 0x1;
1937 for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
1938 const TransformationComp comp = static_cast<TransformationComp>(i);
1940 if (chain[i] != node_property_map.end()) {
1941 flags |= bit;
1943 ai_assert(comp != TransformationComp_RotationPivotInverse);
1944 ai_assert(comp != TransformationComp_ScalingPivotInverse);
1946 const std::string& chain_name = NameTransformationChainNode(fixed_name, comp);
1948 aiNodeAnim* na;
1949 switch(comp)
1951 case TransformationComp_Rotation:
1952 case TransformationComp_PreRotation:
1953 case TransformationComp_PostRotation:
1954 na = GenerateRotationNodeAnim(chain_name,
1955 target,
1956 (*chain[i]).second,
1957 layer_map,
1958 max_time,
1959 min_time);
1961 break;
1963 case TransformationComp_RotationOffset:
1964 case TransformationComp_RotationPivot:
1965 case TransformationComp_ScalingOffset:
1966 case TransformationComp_ScalingPivot:
1967 case TransformationComp_Translation:
1968 na = GenerateTranslationNodeAnim(chain_name,
1969 target,
1970 (*chain[i]).second,
1971 layer_map,
1972 max_time,
1973 min_time);
1975 // pivoting requires us to generate an implicit inverse channel to undo the pivot translation
1976 if (comp == TransformationComp_RotationPivot) {
1977 const std::string& invName = NameTransformationChainNode(fixed_name,
1978 TransformationComp_RotationPivotInverse);
1980 aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName,
1981 target,
1982 (*chain[i]).second,
1983 layer_map,
1984 max_time,
1985 min_time,
1986 true);
1988 ai_assert(inv);
1989 node_anims.push_back(inv);
1991 ai_assert(TransformationComp_RotationPivotInverse > i);
1992 flags |= bit << (TransformationComp_RotationPivotInverse - i);
1994 else if (comp == TransformationComp_ScalingPivot) {
1995 const std::string& invName = NameTransformationChainNode(fixed_name,
1996 TransformationComp_ScalingPivotInverse);
1998 aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName,
1999 target,
2000 (*chain[i]).second,
2001 layer_map,
2002 max_time,
2003 min_time,
2004 true);
2006 ai_assert(inv);
2007 node_anims.push_back(inv);
2009 ai_assert(TransformationComp_RotationPivotInverse > i);
2010 flags |= bit << (TransformationComp_RotationPivotInverse - i);
2013 break;
2015 case TransformationComp_Scaling:
2016 na = GenerateScalingNodeAnim(chain_name,
2017 target,
2018 (*chain[i]).second,
2019 layer_map,
2020 max_time,
2021 min_time);
2023 break;
2025 default:
2026 ai_assert(false);
2029 ai_assert(na);
2030 node_anims.push_back(na);
2031 continue;
2035 node_anim_chain_bits[fixed_name] = flags;
2039 // ------------------------------------------------------------------------------------------------
2040 bool IsRedundantAnimationData(const Model& target,
2041 TransformationComp comp,
2042 const std::vector<const AnimationCurveNode*>& curves)
2044 ai_assert(curves.size());
2046 // look for animation nodes with
2047 // * sub channels for all relevant components set
2048 // * one key/value pair per component
2049 // * combined values match up the corresponding value in the bind pose node transformation
2050 // only such nodes are 'redundant' for this function.
2052 if (curves.size() > 1) {
2053 return false;
2056 const AnimationCurveNode& nd = *curves.front();
2057 const AnimationCurveMap& sub_curves = nd.Curves();
2059 const AnimationCurveMap::const_iterator dx = sub_curves.find("d|X");
2060 const AnimationCurveMap::const_iterator dy = sub_curves.find("d|Y");
2061 const AnimationCurveMap::const_iterator dz = sub_curves.find("d|Z");
2063 if (dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end()) {
2064 return false;
2067 const KeyValueList& vx = (*dx).second->GetValues();
2068 const KeyValueList& vy = (*dy).second->GetValues();
2069 const KeyValueList& vz = (*dz).second->GetValues();
2071 if(vx.size() != 1 || vy.size() != 1 || vz.size() != 1) {
2072 return false;
2075 const aiVector3D dyn_val = aiVector3D(vx[0], vy[0], vz[0]);
2076 const aiVector3D& static_val = PropertyGet<aiVector3D>(target.Props(),
2077 NameTransformationCompProperty(comp),
2078 TransformationCompDefaultValue(comp)
2079 );
2081 const float epsilon = 1e-6f;
2082 return (dyn_val - static_val).SquareLength() < epsilon;
2086 // ------------------------------------------------------------------------------------------------
2087 aiNodeAnim* GenerateRotationNodeAnim(const std::string& name,
2088 const Model& target,
2089 const std::vector<const AnimationCurveNode*>& curves,
2090 const LayerMap& layer_map,
2091 double& max_time,
2092 double& min_time)
2094 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
2095 na->mNodeName.Set(name);
2097 ConvertRotationKeys(na, curves, layer_map, max_time,min_time, target.RotationOrder());
2099 // dummy scaling key
2100 na->mScalingKeys = new aiVectorKey[1];
2101 na->mNumScalingKeys = 1;
2103 na->mScalingKeys[0].mTime = 0.;
2104 na->mScalingKeys[0].mValue = aiVector3D(1.0f,1.0f,1.0f);
2106 // dummy position key
2107 na->mPositionKeys = new aiVectorKey[1];
2108 na->mNumPositionKeys = 1;
2110 na->mPositionKeys[0].mTime = 0.;
2111 na->mPositionKeys[0].mValue = aiVector3D();
2113 return na.dismiss();
2117 // ------------------------------------------------------------------------------------------------
2118 aiNodeAnim* GenerateScalingNodeAnim(const std::string& name,
2119 const Model& target,
2120 const std::vector<const AnimationCurveNode*>& curves,
2121 const LayerMap& layer_map,
2122 double& max_time,
2123 double& min_time)
2125 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
2126 na->mNodeName.Set(name);
2128 ConvertScaleKeys(na, curves, layer_map, max_time,min_time);
2130 // dummy rotation key
2131 na->mRotationKeys = new aiQuatKey[1];
2132 na->mNumRotationKeys = 1;
2134 na->mRotationKeys[0].mTime = 0.;
2135 na->mRotationKeys[0].mValue = aiQuaternion();
2137 // dummy position key
2138 na->mPositionKeys = new aiVectorKey[1];
2139 na->mNumPositionKeys = 1;
2141 na->mPositionKeys[0].mTime = 0.;
2142 na->mPositionKeys[0].mValue = aiVector3D();
2144 return na.dismiss();
2148 // ------------------------------------------------------------------------------------------------
2149 aiNodeAnim* GenerateTranslationNodeAnim(const std::string& name,
2150 const Model& target,
2151 const std::vector<const AnimationCurveNode*>& curves,
2152 const LayerMap& layer_map,
2153 double& max_time,
2154 double& min_time,
2155 bool inverse = false)
2157 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
2158 na->mNodeName.Set(name);
2160 ConvertTranslationKeys(na, curves, layer_map, max_time,min_time);
2162 if (inverse) {
2163 for (unsigned int i = 0; i < na->mNumPositionKeys; ++i) {
2164 na->mPositionKeys[i].mValue *= -1.0f;
2168 // dummy scaling key
2169 na->mScalingKeys = new aiVectorKey[1];
2170 na->mNumScalingKeys = 1;
2172 na->mScalingKeys[0].mTime = 0.;
2173 na->mScalingKeys[0].mValue = aiVector3D(1.0f,1.0f,1.0f);
2175 // dummy rotation key
2176 na->mRotationKeys = new aiQuatKey[1];
2177 na->mNumRotationKeys = 1;
2179 na->mRotationKeys[0].mTime = 0.;
2180 na->mRotationKeys[0].mValue = aiQuaternion();
2182 return na.dismiss();
2186 // ------------------------------------------------------------------------------------------------
2187 // generate node anim, extracting only Rotation, Scaling and Translation from the given chain
2188 aiNodeAnim* GenerateSimpleNodeAnim(const std::string& name,
2189 const Model& target,
2190 NodeMap::const_iterator chain[TransformationComp_MAXIMUM],
2191 NodeMap::const_iterator iter_end,
2192 const LayerMap& layer_map,
2193 double& max_time,
2194 double& min_time,
2195 bool reverse_order = false)
2198 ScopeGuard<aiNodeAnim> na(new aiNodeAnim());
2199 na->mNodeName.Set(name);
2201 const PropertyTable& props = target.Props();
2203 // need to convert from TRS order to SRT?
2204 if(reverse_order) {
2206 aiVector3D def_scale, def_translate;
2207 aiQuaternion def_rot;
2209 KeyFrameListList scaling;
2210 KeyFrameListList translation;
2211 KeyFrameListList rotation;
2213 if(chain[TransformationComp_Scaling] != iter_end) {
2214 scaling = GetKeyframeList((*chain[TransformationComp_Scaling]).second);
2216 else {
2217 def_scale = PropertyGet(props,"Lcl Scaling",aiVector3D(1.f,1.f,1.f));
2220 if(chain[TransformationComp_Translation] != iter_end) {
2221 translation = GetKeyframeList((*chain[TransformationComp_Translation]).second);
2223 else {
2224 def_translate = PropertyGet(props,"Lcl Translation",aiVector3D(0.f,0.f,0.f));
2227 if(chain[TransformationComp_Rotation] != iter_end) {
2228 rotation = GetKeyframeList((*chain[TransformationComp_Rotation]).second);
2230 else {
2231 def_rot = EulerToQuaternion(PropertyGet(props,"Lcl Rotation",aiVector3D(0.f,0.f,0.f)),
2232 target.RotationOrder());
2235 KeyFrameListList joined;
2236 joined.insert(joined.end(), scaling.begin(), scaling.end());
2237 joined.insert(joined.end(), translation.begin(), translation.end());
2238 joined.insert(joined.end(), rotation.begin(), rotation.end());
2240 const KeyTimeList& times = GetKeyTimeList(joined);
2242 aiQuatKey* out_quat = new aiQuatKey[times.size()];
2243 aiVectorKey* out_scale = new aiVectorKey[times.size()];
2244 aiVectorKey* out_translation = new aiVectorKey[times.size()];
2246 ConvertTransformOrder_TRStoSRT(out_quat, out_scale, out_translation,
2247 scaling,
2248 translation,
2249 rotation,
2250 times,
2251 max_time,
2252 min_time,
2253 target.RotationOrder(),
2254 def_scale,
2255 def_translate,
2256 def_rot);
2258 // XXX remove duplicates / redundant keys which this operation did
2259 // likely produce if not all three channels were equally dense.
2261 na->mNumScalingKeys = static_cast<unsigned int>(times.size());
2262 na->mNumRotationKeys = na->mNumScalingKeys;
2263 na->mNumPositionKeys = na->mNumScalingKeys;
2265 na->mScalingKeys = out_scale;
2266 na->mRotationKeys = out_quat;
2267 na->mPositionKeys = out_translation;
2269 else {
2271 // if a particular transformation is not given, grab it from
2272 // the corresponding node to meet the semantics of aiNodeAnim,
2273 // which requires all of rotation, scaling and translation
2274 // to be set.
2275 if(chain[TransformationComp_Scaling] != iter_end) {
2276 ConvertScaleKeys(na, (*chain[TransformationComp_Scaling]).second,
2277 layer_map,
2278 max_time,
2279 min_time);
2281 else {
2282 na->mScalingKeys = new aiVectorKey[1];
2283 na->mNumScalingKeys = 1;
2285 na->mScalingKeys[0].mTime = 0.;
2286 na->mScalingKeys[0].mValue = PropertyGet(props,"Lcl Scaling",
2287 aiVector3D(1.f,1.f,1.f));
2290 if(chain[TransformationComp_Rotation] != iter_end) {
2291 ConvertRotationKeys(na, (*chain[TransformationComp_Rotation]).second,
2292 layer_map,
2293 max_time,
2294 min_time,
2295 target.RotationOrder());
2297 else {
2298 na->mRotationKeys = new aiQuatKey[1];
2299 na->mNumRotationKeys = 1;
2301 na->mRotationKeys[0].mTime = 0.;
2302 na->mRotationKeys[0].mValue = EulerToQuaternion(
2303 PropertyGet(props,"Lcl Rotation",aiVector3D(0.f,0.f,0.f)),
2304 target.RotationOrder());
2307 if(chain[TransformationComp_Translation] != iter_end) {
2308 ConvertTranslationKeys(na, (*chain[TransformationComp_Translation]).second,
2309 layer_map,
2310 max_time,
2311 min_time);
2313 else {
2314 na->mPositionKeys = new aiVectorKey[1];
2315 na->mNumPositionKeys = 1;
2317 na->mPositionKeys[0].mTime = 0.;
2318 na->mPositionKeys[0].mValue = PropertyGet(props,"Lcl Translation",
2319 aiVector3D(0.f,0.f,0.f));
2323 return na.dismiss();
2328 // key (time), value, mapto (component index)
2329 typedef boost::tuple< const KeyTimeList*, const KeyValueList*, unsigned int > KeyFrameList;
2330 typedef std::vector<KeyFrameList> KeyFrameListList;
2334 // ------------------------------------------------------------------------------------------------
2335 KeyFrameListList GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes)
2337 KeyFrameListList inputs;
2338 inputs.reserve(nodes.size()*3);
2340 BOOST_FOREACH(const AnimationCurveNode* node, nodes) {
2341 ai_assert(node);
2343 const AnimationCurveMap& curves = node->Curves();
2344 BOOST_FOREACH(const AnimationCurveMap::value_type& kv, curves) {
2346 unsigned int mapto;
2347 if (kv.first == "d|X") {
2348 mapto = 0;
2350 else if (kv.first == "d|Y") {
2351 mapto = 1;
2353 else if (kv.first == "d|Z") {
2354 mapto = 2;
2356 else {
2357 FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component");
2358 continue;
2361 const AnimationCurve* const curve = kv.second;
2362 ai_assert(curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size());
2364 inputs.push_back(boost::make_tuple(&curve->GetKeys(), &curve->GetValues(), mapto));
2367 return inputs; // pray for NRVO :-)
2371 // ------------------------------------------------------------------------------------------------
2372 KeyTimeList GetKeyTimeList(const KeyFrameListList& inputs)
2374 ai_assert(inputs.size());
2376 // reserve some space upfront - it is likely that the keyframe lists
2377 // have matching time values, so max(of all keyframe lists) should
2378 // be a good estimate.
2379 KeyTimeList keys;
2381 size_t estimate = 0;
2382 BOOST_FOREACH(const KeyFrameList& kfl, inputs) {
2383 estimate = std::max(estimate, kfl.get<0>()->size());
2386 keys.reserve(estimate);
2388 std::vector<unsigned int> next_pos;
2389 next_pos.resize(inputs.size(),0);
2391 const size_t count = inputs.size();
2392 while(true) {
2394 uint64_t min_tick = std::numeric_limits<uint64_t>::max();
2395 for (size_t i = 0; i < count; ++i) {
2396 const KeyFrameList& kfl = inputs[i];
2398 if (kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) < min_tick) {
2399 min_tick = kfl.get<0>()->at(next_pos[i]);
2403 if (min_tick == std::numeric_limits<uint64_t>::max()) {
2404 break;
2406 keys.push_back(min_tick);
2408 for (size_t i = 0; i < count; ++i) {
2409 const KeyFrameList& kfl = inputs[i];
2412 while(kfl.get<0>()->size() > next_pos[i] && kfl.get<0>()->at(next_pos[i]) == min_tick) {
2413 ++next_pos[i];
2418 return keys;
2422 // ------------------------------------------------------------------------------------------------
2423 void InterpolateKeys(aiVectorKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs,
2424 const bool geom,
2425 double& max_time,
2426 double& min_time)
2429 ai_assert(keys.size());
2430 ai_assert(valOut);
2432 std::vector<unsigned int> next_pos;
2433 const size_t count = inputs.size();
2435 next_pos.resize(inputs.size(),0);
2437 BOOST_FOREACH(KeyTimeList::value_type time, keys) {
2438 float result[3] = {0.0f, 0.0f, 0.0f};
2439 if(geom) {
2440 result[0] = result[1] = result[2] = 1.0f;
2443 for (size_t i = 0; i < count; ++i) {
2444 const KeyFrameList& kfl = inputs[i];
2446 const size_t ksize = kfl.get<0>()->size();
2447 if (ksize > next_pos[i] && kfl.get<0>()->at(next_pos[i]) == time) {
2448 ++next_pos[i];
2451 const size_t id0 = next_pos[i]>0 ? next_pos[i]-1 : 0;
2452 const size_t id1 = next_pos[i]==ksize ? ksize-1 : next_pos[i];
2454 // use lerp for interpolation
2455 const KeyValueList::value_type valueA = kfl.get<1>()->at(id0);
2456 const KeyValueList::value_type valueB = kfl.get<1>()->at(id1);
2458 const KeyTimeList::value_type timeA = kfl.get<0>()->at(id0);
2459 const KeyTimeList::value_type timeB = kfl.get<0>()->at(id1);
2461 // do the actual interpolation in double-precision arithmetics
2462 // because it is a bit sensitive to rounding errors.
2463 const double factor = timeB == timeA ? 0. : static_cast<double>((time - timeA) / (timeB - timeA));
2464 const float interpValue = static_cast<float>(valueA + (valueB - valueA) * factor);
2466 if(geom) {
2467 result[kfl.get<2>()] *= interpValue;
2469 else {
2470 result[kfl.get<2>()] += interpValue;
2474 // magic value to convert fbx times to seconds
2475 valOut->mTime = CONVERT_FBX_TIME(time) * anim_fps;
2477 min_time = std::min(min_time, valOut->mTime);
2478 max_time = std::max(max_time, valOut->mTime);
2480 valOut->mValue.x = result[0];
2481 valOut->mValue.y = result[1];
2482 valOut->mValue.z = result[2];
2484 ++valOut;
2489 // ------------------------------------------------------------------------------------------------
2490 void InterpolateKeys(aiQuatKey* valOut,const KeyTimeList& keys, const KeyFrameListList& inputs,
2491 const bool geom,
2492 double& maxTime,
2493 double& minTime,
2494 Model::RotOrder order)
2496 ai_assert(keys.size());
2497 ai_assert(valOut);
2499 boost::scoped_array<aiVectorKey> temp(new aiVectorKey[keys.size()]);
2500 InterpolateKeys(temp.get(),keys,inputs,geom,maxTime, minTime);
2502 aiMatrix4x4 m;
2504 aiQuaternion lastq;
2506 for (size_t i = 0, c = keys.size(); i < c; ++i) {
2508 valOut[i].mTime = temp[i].mTime;
2511 GetRotationMatrix(order, temp[i].mValue, m);
2512 aiQuaternion quat = aiQuaternion(aiMatrix3x3(m));
2514 // take shortest path by checking the inner product
2515 // http://www.3dkingdoms.com/weekly/weekly.php?a=36
2516 if (quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0)
2518 quat.x = -quat.x;
2519 quat.y = -quat.y;
2520 quat.z = -quat.z;
2521 quat.w = -quat.w;
2523 lastq = quat;
2525 valOut[i].mValue = quat;
2530 // ------------------------------------------------------------------------------------------------
2531 void ConvertTransformOrder_TRStoSRT(aiQuatKey* out_quat, aiVectorKey* out_scale,
2532 aiVectorKey* out_translation,
2533 const KeyFrameListList& scaling,
2534 const KeyFrameListList& translation,
2535 const KeyFrameListList& rotation,
2536 const KeyTimeList& times,
2537 double& maxTime,
2538 double& minTime,
2539 Model::RotOrder order,
2540 const aiVector3D& def_scale,
2541 const aiVector3D& def_translate,
2542 const aiQuaternion& def_rotation)
2544 if (rotation.size()) {
2545 InterpolateKeys(out_quat, times, rotation, false, maxTime, minTime, order);
2547 else {
2548 for (size_t i = 0; i < times.size(); ++i) {
2549 out_quat[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
2550 out_quat[i].mValue = def_rotation;
2554 if (scaling.size()) {
2555 InterpolateKeys(out_scale, times, scaling, true, maxTime, minTime);
2557 else {
2558 for (size_t i = 0; i < times.size(); ++i) {
2559 out_scale[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
2560 out_scale[i].mValue = def_scale;
2564 if (translation.size()) {
2565 InterpolateKeys(out_translation, times, translation, false, maxTime, minTime);
2567 else {
2568 for (size_t i = 0; i < times.size(); ++i) {
2569 out_translation[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
2570 out_translation[i].mValue = def_translate;
2574 const size_t count = times.size();
2575 for (size_t i = 0; i < count; ++i) {
2576 aiQuaternion& r = out_quat[i].mValue;
2577 aiVector3D& s = out_scale[i].mValue;
2578 aiVector3D& t = out_translation[i].mValue;
2580 aiMatrix4x4 mat, temp;
2581 aiMatrix4x4::Translation(t, mat);
2582 mat *= aiMatrix4x4( r.GetMatrix() );
2583 mat *= aiMatrix4x4::Scaling(s, temp);
2585 mat.Decompose(s, r, t);
2590 // ------------------------------------------------------------------------------------------------
2591 // euler xyz -> quat
2592 aiQuaternion EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order)
2594 aiMatrix4x4 m;
2595 GetRotationMatrix(order, rot, m);
2597 return aiQuaternion(aiMatrix3x3(m));
2601 // ------------------------------------------------------------------------------------------------
2602 void ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& layers,
2603 double& maxTime,
2604 double& minTime)
2606 ai_assert(nodes.size());
2608 // XXX for now, assume scale should be blended geometrically (i.e. two
2609 // layers should be multiplied with each other). There is a FBX
2610 // property in the layer to specify the behaviour, though.
2612 const KeyFrameListList& inputs = GetKeyframeList(nodes);
2613 const KeyTimeList& keys = GetKeyTimeList(inputs);
2615 na->mNumScalingKeys = static_cast<unsigned int>(keys.size());
2616 na->mScalingKeys = new aiVectorKey[keys.size()];
2617 InterpolateKeys(na->mScalingKeys, keys, inputs, true, maxTime, minTime);
2621 // ------------------------------------------------------------------------------------------------
2622 void ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
2623 const LayerMap& layers,
2624 double& maxTime,
2625 double& minTime)
2627 ai_assert(nodes.size());
2629 // XXX see notes in ConvertScaleKeys()
2630 const KeyFrameListList& inputs = GetKeyframeList(nodes);
2631 const KeyTimeList& keys = GetKeyTimeList(inputs);
2633 na->mNumPositionKeys = static_cast<unsigned int>(keys.size());
2634 na->mPositionKeys = new aiVectorKey[keys.size()];
2635 InterpolateKeys(na->mPositionKeys, keys, inputs, false, maxTime, minTime);
2639 // ------------------------------------------------------------------------------------------------
2640 void ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
2641 const LayerMap& layers,
2642 double& maxTime,
2643 double& minTime,
2644 Model::RotOrder order)
2646 ai_assert(nodes.size());
2648 // XXX see notes in ConvertScaleKeys()
2649 const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes);
2650 const KeyTimeList& keys = GetKeyTimeList(inputs);
2652 na->mNumRotationKeys = static_cast<unsigned int>(keys.size());
2653 na->mRotationKeys = new aiQuatKey[keys.size()];
2654 InterpolateKeys(na->mRotationKeys, keys, inputs, false, maxTime, minTime, order);
2658 // ------------------------------------------------------------------------------------------------
2659 // copy generated meshes, animations, lights, cameras and textures to the output scene
2660 void TransferDataToScene()
2662 ai_assert(!out->mMeshes && !out->mNumMeshes);
2664 // note: the trailing () ensures initialization with NULL - not
2665 // many C++ users seem to know this, so pointing it out to avoid
2666 // confusion why this code works.
2668 if(meshes.size()) {
2669 out->mMeshes = new aiMesh*[meshes.size()]();
2670 out->mNumMeshes = static_cast<unsigned int>(meshes.size());
2672 std::swap_ranges(meshes.begin(),meshes.end(),out->mMeshes);
2675 if(materials.size()) {
2676 out->mMaterials = new aiMaterial*[materials.size()]();
2677 out->mNumMaterials = static_cast<unsigned int>(materials.size());
2679 std::swap_ranges(materials.begin(),materials.end(),out->mMaterials);
2682 if(animations.size()) {
2683 out->mAnimations = new aiAnimation*[animations.size()]();
2684 out->mNumAnimations = static_cast<unsigned int>(animations.size());
2686 std::swap_ranges(animations.begin(),animations.end(),out->mAnimations);
2689 if(lights.size()) {
2690 out->mLights = new aiLight*[lights.size()]();
2691 out->mNumLights = static_cast<unsigned int>(lights.size());
2693 std::swap_ranges(lights.begin(),lights.end(),out->mLights);
2696 if(cameras.size()) {
2697 out->mCameras = new aiCamera*[cameras.size()]();
2698 out->mNumCameras = static_cast<unsigned int>(cameras.size());
2700 std::swap_ranges(cameras.begin(),cameras.end(),out->mCameras);
2705 private:
2707 // 0: not assigned yet, others: index is value - 1
2708 unsigned int defaultMaterialIndex;
2710 std::vector<aiMesh*> meshes;
2711 std::vector<aiMaterial*> materials;
2712 std::vector<aiAnimation*> animations;
2713 std::vector<aiLight*> lights;
2714 std::vector<aiCamera*> cameras;
2716 typedef std::map<const Material*, unsigned int> MaterialMap;
2717 MaterialMap materials_converted;
2719 typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
2720 MeshMap meshes_converted;
2722 // fixed node name -> which trafo chain components have animations?
2723 typedef std::map<std::string, unsigned int> NodeAnimBitMap;
2724 NodeAnimBitMap node_anim_chain_bits;
2726 // name -> has had its prefix_stripped?
2727 typedef std::map<std::string, bool> NodeNameMap;
2728 NodeNameMap node_names;
2730 typedef std::map<std::string, std::string> NameNameMap;
2731 NameNameMap renamed_nodes;
2733 double anim_fps;
2735 aiScene* const out;
2736 const FBX::Document& doc;
2737 };
2739 //} // !anon
2741 // ------------------------------------------------------------------------------------------------
2742 void ConvertToAssimpScene(aiScene* out, const Document& doc)
2744 Converter converter(out,doc);
2747 } // !FBX
2748 } // !Assimp
2750 #endif