1 /*
2 Open Asset Import Library (assimp)
3 ----------------------------------------------------------------------
4
5 Copyright (c) 2006-2017, assimp team
6
7 All rights reserved.
8
9 Redistribution and use of this software in source and binary forms,
10 with or without modification, are permitted provided that the
11 following conditions are met:
12
13 * Redistributions of source code must retain the above
14 copyright notice, this list of conditions and the
15 following disclaimer.
16
17 * Redistributions in binary form must reproduce the above
18 copyright notice, this list of conditions and the
19 following disclaimer in the documentation and/or other
20 materials provided with the distribution.
21
22 * Neither the name of the assimp team, nor the names of its
23 contributors may be used to endorse or promote products
24 derived from this software without specific prior
25 written permission of the assimp team.
26
27 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38
39 ----------------------------------------------------------------------
40 */
41
42 /** @file FBXConverter.cpp
43 * @brief Implementation of the FBX DOM -> aiScene converter
44 */
45
46 #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
47
48 #include "FBXConverter.h"
49 #include "FBXParser.h"
50 #include "FBXMeshGeometry.h"
51 #include "FBXDocument.h"
52 #include "FBXUtil.h"
53 #include "FBXProperties.h"
54 #include "FBXImporter.h"
55 #include "StringComparison.h"
56
57 #include <assimp/scene.h>
58
59 #include <tuple>
60 #include <memory>
61 #include <iterator>
62 #include <vector>
63
64 namespace Assimp {
65 namespace FBX {
66
67 using namespace Util;
68
69
70 #define MAGIC_NODE_TAG "_$AssimpFbx$"
71
72 #define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
73
74 // XXX vc9's debugger won't step into anonymous namespaces
75 //namespace {
76
77 /** Dummy class to encapsulate the conversion process */
78 class Converter
79 {
80 public:
81 /**
82 * The different parts that make up the final local transformation of a fbx-node
83 */
84 enum TransformationComp
85 {
86 TransformationComp_Translation = 0,
87 TransformationComp_RotationOffset,
88 TransformationComp_RotationPivot,
89 TransformationComp_PreRotation,
90 TransformationComp_Rotation,
91 TransformationComp_PostRotation,
92 TransformationComp_RotationPivotInverse,
93 TransformationComp_ScalingOffset,
94 TransformationComp_ScalingPivot,
95 TransformationComp_Scaling,
96 TransformationComp_ScalingPivotInverse,
97 TransformationComp_GeometricTranslation,
98 TransformationComp_GeometricRotation,
99 TransformationComp_GeometricScaling,
100
101 TransformationComp_MAXIMUM
102 };
103
104 public:
105 Converter( aiScene* out, const Document& doc );
106 ~Converter();
107
108 private:
109 // ------------------------------------------------------------------------------------------------
110 // find scene root and trigger recursive scene conversion
111 void ConvertRootNode();
112
113 // ------------------------------------------------------------------------------------------------
114 // collect and assign child nodes
115 void ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4() );
116
117 // ------------------------------------------------------------------------------------------------
118 void ConvertLights( const Model& model );
119
120 // ------------------------------------------------------------------------------------------------
121 void ConvertCameras( const Model& model );
122
123 // ------------------------------------------------------------------------------------------------
124 void ConvertLight( const Model& model, const Light& light );
125
126 // ------------------------------------------------------------------------------------------------
127 void ConvertCamera( const Model& model, const Camera& cam );
128
129 // ------------------------------------------------------------------------------------------------
130 // this returns unified names usable within assimp identifiers (i.e. no space characters -
131 // while these would be allowed, they are a potential trouble spot so better not use them).
132 const char* NameTransformationComp( TransformationComp comp );
133
134 // ------------------------------------------------------------------------------------------------
135 // note: this returns the REAL fbx property names
136 const char* NameTransformationCompProperty( TransformationComp comp );
137
138 // ------------------------------------------------------------------------------------------------
139 aiVector3D TransformationCompDefaultValue( TransformationComp comp );
140
141 // ------------------------------------------------------------------------------------------------
142 void GetRotationMatrix( Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out );
143 // ------------------------------------------------------------------------------------------------
144 /**
145 * checks if a node has more than just scaling, rotation and translation components
146 */
147 bool NeedsComplexTransformationChain( const Model& model );
148
149 // ------------------------------------------------------------------------------------------------
150 // note: name must be a FixNodeName() result
151 std::string NameTransformationChainNode( const std::string& name, TransformationComp comp );
152
153 // ------------------------------------------------------------------------------------------------
154 /**
155 * note: memory for output_nodes will be managed by the caller
156 */
157 void GenerateTransformationNodeChain( const Model& model, std::vector<aiNode*>& output_nodes );
158
159 // ------------------------------------------------------------------------------------------------
160 void SetupNodeMetadata( const Model& model, aiNode& nd );
161
162 // ------------------------------------------------------------------------------------------------
163 void ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform );
164
165 // ------------------------------------------------------------------------------------------------
166 // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
167 std::vector<unsigned int> ConvertMesh( const MeshGeometry& mesh, const Model& model,
168 const aiMatrix4x4& node_global_transform );
169
170 // ------------------------------------------------------------------------------------------------
171 aiMesh* SetupEmptyMesh( const MeshGeometry& mesh );
172
173 // ------------------------------------------------------------------------------------------------
174 unsigned int ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model,
175 const aiMatrix4x4& node_global_transform );
176
177 // ------------------------------------------------------------------------------------------------
178 std::vector<unsigned int> ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
179 const aiMatrix4x4& node_global_transform );
180
181 // ------------------------------------------------------------------------------------------------
182 unsigned int ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
183 MatIndexArray::value_type index,
184 const aiMatrix4x4& node_global_transform );
185
186 // ------------------------------------------------------------------------------------------------
187 static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
188 static_cast<unsigned int>(-1);
189
190 // ------------------------------------------------------------------------------------------------
191 /**
192 * - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into
193 * account when determining which weights to include.
194 * - outputVertStartIndices is only used when a material index is specified, it gives for
195 * each output vertex the DOM index it maps to.
196 */
197 void ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo,
198 const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
199 unsigned int materialIndex = NO_MATERIAL_SEPARATION,
200 std::vector<unsigned int>* outputVertStartIndices = NULL );
201
202 // ------------------------------------------------------------------------------------------------
203 void ConvertCluster( std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
204 std::vector<size_t>& out_indices,
205 std::vector<size_t>& index_out_indices,
206 std::vector<size_t>& count_out_indices,
207 const aiMatrix4x4& node_global_transform );
208
209 // ------------------------------------------------------------------------------------------------
210 void ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo,
211 MatIndexArray::value_type materialIndex );
212
213 // ------------------------------------------------------------------------------------------------
214 unsigned int GetDefaultMaterial();
215
216
217 // ------------------------------------------------------------------------------------------------
218 // Material -> aiMaterial
219 unsigned int ConvertMaterial( const Material& material, const MeshGeometry* const mesh );
220
221 // ------------------------------------------------------------------------------------------------
222 // Video -> aiTexture
223 unsigned int ConvertVideo( const Video& video );
224
225 // ------------------------------------------------------------------------------------------------
226 void TrySetTextureProperties( aiMaterial* out_mat, const TextureMap& textures,
227 const std::string& propName,
228 aiTextureType target, const MeshGeometry* const mesh );
229
230 // ------------------------------------------------------------------------------------------------
231 void TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
232 const std::string& propName,
233 aiTextureType target, const MeshGeometry* const mesh );
234
235 // ------------------------------------------------------------------------------------------------
236 void SetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh );
237
238 // ------------------------------------------------------------------------------------------------
239 void SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh );
240
241 // ------------------------------------------------------------------------------------------------
242 aiColor3D GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName,
243 bool& result );
244
245 // ------------------------------------------------------------------------------------------------
246 void SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props );
247
248 // ------------------------------------------------------------------------------------------------
249 // get the number of fps for a FrameRate enumerated value
250 static double FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0 );
251
252 // ------------------------------------------------------------------------------------------------
253 // convert animation data to aiAnimation et al
254 void ConvertAnimations();
255
256 // ------------------------------------------------------------------------------------------------
257 // rename a node already partially converted. fixed_name is a string previously returned by
258 // FixNodeName, new_name specifies the string FixNodeName should return on all further invocations
259 // which would previously have returned the old value.
260 //
261 // this also updates names in node animations, cameras and light sources and is thus slow.
262 //
263 // NOTE: the caller is responsible for ensuring that the new name is unique and does
264 // not collide with any other identifiers. The best way to ensure this is to only
265 // append to the old name, which is guaranteed to match these requirements.
266 void RenameNode( const std::string& fixed_name, const std::string& new_name );
267
268 // ------------------------------------------------------------------------------------------------
269 // takes a fbx node name and returns the identifier to be used in the assimp output scene.
270 // the function is guaranteed to provide consistent results over multiple invocations
271 // UNLESS RenameNode() is called for a particular node name.
272 std::string FixNodeName( const std::string& name );
273
274 typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
275
276 // XXX: better use multi_map ..
277 typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
278
279
280 // ------------------------------------------------------------------------------------------------
281 void ConvertAnimationStack( const AnimationStack& st );
282
283 // ------------------------------------------------------------------------------------------------
284 void GenerateNodeAnimations( std::vector<aiNodeAnim*>& node_anims,
285 const std::string& fixed_name,
286 const std::vector<const AnimationCurveNode*>& curves,
287 const LayerMap& layer_map,
288 int64_t start, int64_t stop,
289 double& max_time,
290 double& min_time );
291
292 // ------------------------------------------------------------------------------------------------
293 bool IsRedundantAnimationData( const Model& target,
294 TransformationComp comp,
295 const std::vector<const AnimationCurveNode*>& curves );
296
297 // ------------------------------------------------------------------------------------------------
298 aiNodeAnim* GenerateRotationNodeAnim( const std::string& name,
299 const Model& target,
300 const std::vector<const AnimationCurveNode*>& curves,
301 const LayerMap& layer_map,
302 int64_t start, int64_t stop,
303 double& max_time,
304 double& min_time );
305
306 // ------------------------------------------------------------------------------------------------
307 aiNodeAnim* GenerateScalingNodeAnim( const std::string& name,
308 const Model& /*target*/,
309 const std::vector<const AnimationCurveNode*>& curves,
310 const LayerMap& layer_map,
311 int64_t start, int64_t stop,
312 double& max_time,
313 double& min_time );
314
315 // ------------------------------------------------------------------------------------------------
316 aiNodeAnim* GenerateTranslationNodeAnim( const std::string& name,
317 const Model& /*target*/,
318 const std::vector<const AnimationCurveNode*>& curves,
319 const LayerMap& layer_map,
320 int64_t start, int64_t stop,
321 double& max_time,
322 double& min_time,
323 bool inverse = false );
324
325 // ------------------------------------------------------------------------------------------------
326 // generate node anim, extracting only Rotation, Scaling and Translation from the given chain
327 aiNodeAnim* GenerateSimpleNodeAnim( const std::string& name,
328 const Model& target,
329 NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ],
330 NodeMap::const_iterator iter_end,
331 const LayerMap& layer_map,
332 int64_t start, int64_t stop,
333 double& max_time,
334 double& min_time,
335 bool reverse_order = false );
336
337 // key (time), value, mapto (component index)
338 typedef std::tuple<std::shared_ptr<KeyTimeList>, std::shared_ptr<KeyValueList>, unsigned int > KeyFrameList;
339 typedef std::vector<KeyFrameList> KeyFrameListList;
340
341 // ------------------------------------------------------------------------------------------------
342 KeyFrameListList GetKeyframeList( const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop );
343
344 // ------------------------------------------------------------------------------------------------
345 KeyTimeList GetKeyTimeList( const KeyFrameListList& inputs );
346
347 // ------------------------------------------------------------------------------------------------
348 void InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
349 const aiVector3D& def_value,
350 double& max_time,
351 double& min_time );
352
353 // ------------------------------------------------------------------------------------------------
354 void InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
355 const aiVector3D& def_value,
356 double& maxTime,
357 double& minTime,
358 Model::RotOrder order );
359
360 // ------------------------------------------------------------------------------------------------
361 void ConvertTransformOrder_TRStoSRT( aiQuatKey* out_quat, aiVectorKey* out_scale,
362 aiVectorKey* out_translation,
363 const KeyFrameListList& scaling,
364 const KeyFrameListList& translation,
365 const KeyFrameListList& rotation,
366 const KeyTimeList& times,
367 double& maxTime,
368 double& minTime,
369 Model::RotOrder order,
370 const aiVector3D& def_scale,
371 const aiVector3D& def_translate,
372 const aiVector3D& def_rotation );
373
374 // ------------------------------------------------------------------------------------------------
375 // euler xyz -> quat
376 aiQuaternion EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order );
377
378 // ------------------------------------------------------------------------------------------------
379 void ConvertScaleKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& /*layers*/,
380 int64_t start, int64_t stop,
381 double& maxTime,
382 double& minTime );
383
384 // ------------------------------------------------------------------------------------------------
385 void ConvertTranslationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
386 const LayerMap& /*layers*/,
387 int64_t start, int64_t stop,
388 double& maxTime,
389 double& minTime );
390
391 // ------------------------------------------------------------------------------------------------
392 void ConvertRotationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
393 const LayerMap& /*layers*/,
394 int64_t start, int64_t stop,
395 double& maxTime,
396 double& minTime,
397 Model::RotOrder order );
398
399 // ------------------------------------------------------------------------------------------------
400 // copy generated meshes, animations, lights, cameras and textures to the output scene
401 void TransferDataToScene();
402
403 private:
404
405 // 0: not assigned yet, others: index is value - 1
406 unsigned int defaultMaterialIndex;
407
408 std::vector<aiMesh*> meshes;
409 std::vector<aiMaterial*> materials;
410 std::vector<aiAnimation*> animations;
411 std::vector<aiLight*> lights;
412 std::vector<aiCamera*> cameras;
413 std::vector<aiTexture*> textures;
414
415 typedef std::map<const Material*, unsigned int> MaterialMap;
416 MaterialMap materials_converted;
417
418 typedef std::map<const Video*, unsigned int> VideoMap;
419 VideoMap textures_converted;
420
421 typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
422 MeshMap meshes_converted;
423
424 // fixed node name -> which trafo chain components have animations?
425 typedef std::map<std::string, unsigned int> NodeAnimBitMap;
426 NodeAnimBitMap node_anim_chain_bits;
427
428 // name -> has had its prefix_stripped?
429 typedef std::map<std::string, bool> NodeNameMap;
430 NodeNameMap node_names;
431
432 typedef std::map<std::string, std::string> NameNameMap;
433 NameNameMap renamed_nodes;
434
435 double anim_fps;
436
437 aiScene* const out;
438 const FBX::Document& doc;
439
FindTextureIndexByFilename(const Video & video,unsigned int & index)440 bool FindTextureIndexByFilename(const Video& video, unsigned int& index) {
441 index = 0;
442 const char* videoFileName = video.FileName().c_str();
443 for (auto texture = textures_converted.begin(); texture != textures_converted.end(); ++texture) {
444 if (!strcmp(texture->first->FileName().c_str(), videoFileName)) {
445 index = texture->second;
446 return true;
447 }
448 }
449 return false;
450 }
451 };
452
Converter(aiScene * out,const Document & doc)453 Converter::Converter( aiScene* out, const Document& doc )
454 : defaultMaterialIndex()
455 , out( out )
456 , doc( doc )
457 {
458 // animations need to be converted first since this will
459 // populate the node_anim_chain_bits map, which is needed
460 // to determine which nodes need to be generated.
461 ConvertAnimations();
462 ConvertRootNode();
463
464 if ( doc.Settings().readAllMaterials ) {
465 // unfortunately this means we have to evaluate all objects
466 for( const ObjectMap::value_type& v : doc.Objects() ) {
467
468 const Object* ob = v.second->Get();
469 if ( !ob ) {
470 continue;
471 }
472
473 const Material* mat = dynamic_cast<const Material*>( ob );
474 if ( mat ) {
475
476 if ( materials_converted.find( mat ) == materials_converted.end() ) {
477 ConvertMaterial( *mat, 0 );
478 }
479 }
480 }
481 }
482
483 TransferDataToScene();
484
485 // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
486 // to make sure the scene passes assimp's validation. FBX files
487 // need not contain geometry (i.e. camera animations, raw armatures).
488 if ( out->mNumMeshes == 0 ) {
489 out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
490 }
491 }
492
493
~Converter()494 Converter::~Converter()
495 {
496 std::for_each( meshes.begin(), meshes.end(), Util::delete_fun<aiMesh>() );
497 std::for_each( materials.begin(), materials.end(), Util::delete_fun<aiMaterial>() );
498 std::for_each( animations.begin(), animations.end(), Util::delete_fun<aiAnimation>() );
499 std::for_each( lights.begin(), lights.end(), Util::delete_fun<aiLight>() );
500 std::for_each( cameras.begin(), cameras.end(), Util::delete_fun<aiCamera>() );
501 std::for_each( textures.begin(), textures.end(), Util::delete_fun<aiTexture>() );
502 }
503
ConvertRootNode()504 void Converter::ConvertRootNode()
505 {
506 out->mRootNode = new aiNode();
507 out->mRootNode->mName.Set( "RootNode" );
508
509 // root has ID 0
510 ConvertNodes( 0L, *out->mRootNode );
511 }
512
513
ConvertNodes(uint64_t id,aiNode & parent,const aiMatrix4x4 & parent_transform)514 void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform )
515 {
516 const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" );
517
518 std::vector<aiNode*> nodes;
519 nodes.reserve( conns.size() );
520
521 std::vector<aiNode*> nodes_chain;
522
523 try {
524 for( const Connection* con : conns ) {
525
526 // ignore object-property links
527 if ( con->PropertyName().length() ) {
528 continue;
529 }
530
531 const Object* const object = con->SourceObject();
532 if ( !object ) {
533 FBXImporter::LogWarn( "failed to convert source object for Model link" );
534 continue;
535 }
536
537 const Model* const model = dynamic_cast<const Model*>( object );
538
539 if ( model ) {
540 nodes_chain.clear();
541
542 aiMatrix4x4 new_abs_transform = parent_transform;
543
544 // even though there is only a single input node, the design of
545 // assimp (or rather: the complicated transformation chain that
546 // is employed by fbx) means that we may need multiple aiNode's
547 // to represent a fbx node's transformation.
548 GenerateTransformationNodeChain( *model, nodes_chain );
549
550 ai_assert( nodes_chain.size() );
551
552 const std::string& original_name = FixNodeName( model->Name() );
553
554 // check if any of the nodes in the chain has the name the fbx node
555 // is supposed to have. If there is none, add another node to
556 // preserve the name - people might have scripts etc. that rely
557 // on specific node names.
558 aiNode* name_carrier = NULL;
559 for( aiNode* prenode : nodes_chain ) {
560 if ( !strcmp( prenode->mName.C_Str(), original_name.c_str() ) ) {
561 name_carrier = prenode;
562 break;
563 }
564 }
565
566 if ( !name_carrier ) {
567 nodes_chain.push_back( new aiNode( original_name ) );
568 }
569
570 //setup metadata on newest node
571 SetupNodeMetadata( *model, *nodes_chain.back() );
572
573 // link all nodes in a row
574 aiNode* last_parent = &parent;
575 for( aiNode* prenode : nodes_chain ) {
576 ai_assert( prenode );
577
578 if ( last_parent != &parent ) {
579 last_parent->mNumChildren = 1;
580 last_parent->mChildren = new aiNode*[ 1 ];
581 last_parent->mChildren[ 0 ] = prenode;
582 }
583
584 prenode->mParent = last_parent;
585 last_parent = prenode;
586
587 new_abs_transform *= prenode->mTransformation;
588 }
589
590 // attach geometry
591 ConvertModel( *model, *nodes_chain.back(), new_abs_transform );
592
593 // attach sub-nodes
594 ConvertNodes( model->ID(), *nodes_chain.back(), new_abs_transform );
595
596 if ( doc.Settings().readLights ) {
597 ConvertLights( *model );
598 }
599
600 if ( doc.Settings().readCameras ) {
601 ConvertCameras( *model );
602 }
603
604 nodes.push_back( nodes_chain.front() );
605 nodes_chain.clear();
606 }
607 }
608
609 if ( nodes.size() ) {
610 parent.mChildren = new aiNode*[ nodes.size() ]();
611 parent.mNumChildren = static_cast<unsigned int>( nodes.size() );
612
613 std::swap_ranges( nodes.begin(), nodes.end(), parent.mChildren );
614 }
615 }
616 catch ( std::exception& ) {
617 Util::delete_fun<aiNode> deleter;
618 std::for_each( nodes.begin(), nodes.end(), deleter );
619 std::for_each( nodes_chain.begin(), nodes_chain.end(), deleter );
620 }
621 }
622
623
ConvertLights(const Model & model)624 void Converter::ConvertLights( const Model& model )
625 {
626 const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
627 for( const NodeAttribute* attr : node_attrs ) {
628 const Light* const light = dynamic_cast<const Light*>( attr );
629 if ( light ) {
630 ConvertLight( model, *light );
631 }
632 }
633 }
634
ConvertCameras(const Model & model)635 void Converter::ConvertCameras( const Model& model )
636 {
637 const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
638 for( const NodeAttribute* attr : node_attrs ) {
639 const Camera* const cam = dynamic_cast<const Camera*>( attr );
640 if ( cam ) {
641 ConvertCamera( model, *cam );
642 }
643 }
644 }
645
ConvertLight(const Model & model,const Light & light)646 void Converter::ConvertLight( const Model& model, const Light& light )
647 {
648 lights.push_back( new aiLight() );
649 aiLight* const out_light = lights.back();
650
651 out_light->mName.Set( FixNodeName( model.Name() ) );
652
653 const float intensity = light.Intensity() / 100.0f;
654 const aiVector3D& col = light.Color();
655
656 out_light->mColorDiffuse = aiColor3D( col.x, col.y, col.z );
657 out_light->mColorDiffuse.r *= intensity;
658 out_light->mColorDiffuse.g *= intensity;
659 out_light->mColorDiffuse.b *= intensity;
660
661 out_light->mColorSpecular = out_light->mColorDiffuse;
662
663 //lights are defined along negative y direction
664 out_light->mPosition = aiVector3D(0.0f);
665 out_light->mDirection = aiVector3D(0.0f, -1.0f, 0.0f);
666 out_light->mUp = aiVector3D(0.0f, 0.0f, -1.0f);
667
668 switch ( light.LightType() )
669 {
670 case Light::Type_Point:
671 out_light->mType = aiLightSource_POINT;
672 break;
673
674 case Light::Type_Directional:
675 out_light->mType = aiLightSource_DIRECTIONAL;
676 break;
677
678 case Light::Type_Spot:
679 out_light->mType = aiLightSource_SPOT;
680 out_light->mAngleOuterCone = AI_DEG_TO_RAD( light.OuterAngle() );
681 out_light->mAngleInnerCone = AI_DEG_TO_RAD( light.InnerAngle() );
682 break;
683
684 case Light::Type_Area:
685 FBXImporter::LogWarn( "cannot represent area light, set to UNDEFINED" );
686 out_light->mType = aiLightSource_UNDEFINED;
687 break;
688
689 case Light::Type_Volume:
690 FBXImporter::LogWarn( "cannot represent volume light, set to UNDEFINED" );
691 out_light->mType = aiLightSource_UNDEFINED;
692 break;
693 default:
694 ai_assert( false );
695 }
696
697 float decay = light.DecayStart();
698 switch ( light.DecayType() )
699 {
700 case Light::Decay_None:
701 out_light->mAttenuationConstant = decay;
702 out_light->mAttenuationLinear = 0.0f;
703 out_light->mAttenuationQuadratic = 0.0f;
704 break;
705 case Light::Decay_Linear:
706 out_light->mAttenuationConstant = 0.0f;
707 out_light->mAttenuationLinear = 2.0f / decay;
708 out_light->mAttenuationQuadratic = 0.0f;
709 break;
710 case Light::Decay_Quadratic:
711 out_light->mAttenuationConstant = 0.0f;
712 out_light->mAttenuationLinear = 0.0f;
713 out_light->mAttenuationQuadratic = 2.0f / (decay * decay);
714 break;
715 case Light::Decay_Cubic:
716 FBXImporter::LogWarn( "cannot represent cubic attenuation, set to Quadratic" );
717 out_light->mAttenuationQuadratic = 1.0f;
718 break;
719 default:
720 ai_assert( false );
721 }
722 }
723
ConvertCamera(const Model & model,const Camera & cam)724 void Converter::ConvertCamera( const Model& model, const Camera& cam )
725 {
726 cameras.push_back( new aiCamera() );
727 aiCamera* const out_camera = cameras.back();
728
729 out_camera->mName.Set( FixNodeName( model.Name() ) );
730
731 out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
732 //cameras are defined along positive x direction
733 out_camera->mPosition = aiVector3D(0.0f);
734 out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f);
735 out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
736 out_camera->mHorizontalFOV = AI_DEG_TO_RAD( cam.FieldOfView() );
737 out_camera->mClipPlaneNear = cam.NearPlane();
738 out_camera->mClipPlaneFar = cam.FarPlane();
739 }
740
741
NameTransformationComp(TransformationComp comp)742 const char* Converter::NameTransformationComp( TransformationComp comp )
743 {
744 switch ( comp )
745 {
746 case TransformationComp_Translation:
747 return "Translation";
748 case TransformationComp_RotationOffset:
749 return "RotationOffset";
750 case TransformationComp_RotationPivot:
751 return "RotationPivot";
752 case TransformationComp_PreRotation:
753 return "PreRotation";
754 case TransformationComp_Rotation:
755 return "Rotation";
756 case TransformationComp_PostRotation:
757 return "PostRotation";
758 case TransformationComp_RotationPivotInverse:
759 return "RotationPivotInverse";
760 case TransformationComp_ScalingOffset:
761 return "ScalingOffset";
762 case TransformationComp_ScalingPivot:
763 return "ScalingPivot";
764 case TransformationComp_Scaling:
765 return "Scaling";
766 case TransformationComp_ScalingPivotInverse:
767 return "ScalingPivotInverse";
768 case TransformationComp_GeometricScaling:
769 return "GeometricScaling";
770 case TransformationComp_GeometricRotation:
771 return "GeometricRotation";
772 case TransformationComp_GeometricTranslation:
773 return "GeometricTranslation";
774 case TransformationComp_MAXIMUM: // this is to silence compiler warnings
775 default:
776 break;
777 }
778
779 ai_assert( false );
780 return NULL;
781 }
782
NameTransformationCompProperty(TransformationComp comp)783 const char* Converter::NameTransformationCompProperty( TransformationComp comp )
784 {
785 switch ( comp )
786 {
787 case TransformationComp_Translation:
788 return "Lcl Translation";
789 case TransformationComp_RotationOffset:
790 return "RotationOffset";
791 case TransformationComp_RotationPivot:
792 return "RotationPivot";
793 case TransformationComp_PreRotation:
794 return "PreRotation";
795 case TransformationComp_Rotation:
796 return "Lcl Rotation";
797 case TransformationComp_PostRotation:
798 return "PostRotation";
799 case TransformationComp_RotationPivotInverse:
800 return "RotationPivotInverse";
801 case TransformationComp_ScalingOffset:
802 return "ScalingOffset";
803 case TransformationComp_ScalingPivot:
804 return "ScalingPivot";
805 case TransformationComp_Scaling:
806 return "Lcl Scaling";
807 case TransformationComp_ScalingPivotInverse:
808 return "ScalingPivotInverse";
809 case TransformationComp_GeometricScaling:
810 return "GeometricScaling";
811 case TransformationComp_GeometricRotation:
812 return "GeometricRotation";
813 case TransformationComp_GeometricTranslation:
814 return "GeometricTranslation";
815 case TransformationComp_MAXIMUM: // this is to silence compiler warnings
816 break;
817 }
818
819 ai_assert( false );
820 return NULL;
821 }
822
TransformationCompDefaultValue(TransformationComp comp)823 aiVector3D Converter::TransformationCompDefaultValue( TransformationComp comp )
824 {
825 // XXX a neat way to solve the never-ending special cases for scaling
826 // would be to do everything in log space!
827 return comp == TransformationComp_Scaling ? aiVector3D( 1.f, 1.f, 1.f ) : aiVector3D();
828 }
829
GetRotationMatrix(Model::RotOrder mode,const aiVector3D & rotation,aiMatrix4x4 & out)830 void Converter::GetRotationMatrix( Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out )
831 {
832 if ( mode == Model::RotOrder_SphericXYZ ) {
833 FBXImporter::LogError( "Unsupported RotationMode: SphericXYZ" );
834 out = aiMatrix4x4();
835 return;
836 }
837
838 const float angle_epsilon = 1e-6f;
839
840 out = aiMatrix4x4();
841
842 bool is_id[ 3 ] = { true, true, true };
843
844 aiMatrix4x4 temp[ 3 ];
845 if ( std::fabs( rotation.z ) > angle_epsilon ) {
846 aiMatrix4x4::RotationZ( AI_DEG_TO_RAD( rotation.z ), temp[ 2 ] );
847 is_id[ 2 ] = false;
848 }
849 if ( std::fabs( rotation.y ) > angle_epsilon ) {
850 aiMatrix4x4::RotationY( AI_DEG_TO_RAD( rotation.y ), temp[ 1 ] );
851 is_id[ 1 ] = false;
852 }
853 if ( std::fabs( rotation.x ) > angle_epsilon ) {
854 aiMatrix4x4::RotationX( AI_DEG_TO_RAD( rotation.x ), temp[ 0 ] );
855 is_id[ 0 ] = false;
856 }
857
858 int order[ 3 ] = { -1, -1, -1 };
859
860 // note: rotation order is inverted since we're left multiplying as is usual in assimp
861 switch ( mode )
862 {
863 case Model::RotOrder_EulerXYZ:
864 order[ 0 ] = 2;
865 order[ 1 ] = 1;
866 order[ 2 ] = 0;
867 break;
868
869 case Model::RotOrder_EulerXZY:
870 order[ 0 ] = 1;
871 order[ 1 ] = 2;
872 order[ 2 ] = 0;
873 break;
874
875 case Model::RotOrder_EulerYZX:
876 order[ 0 ] = 0;
877 order[ 1 ] = 2;
878 order[ 2 ] = 1;
879 break;
880
881 case Model::RotOrder_EulerYXZ:
882 order[ 0 ] = 2;
883 order[ 1 ] = 0;
884 order[ 2 ] = 1;
885 break;
886
887 case Model::RotOrder_EulerZXY:
888 order[ 0 ] = 1;
889 order[ 1 ] = 0;
890 order[ 2 ] = 2;
891 break;
892
893 case Model::RotOrder_EulerZYX:
894 order[ 0 ] = 0;
895 order[ 1 ] = 1;
896 order[ 2 ] = 2;
897 break;
898
899 default:
900 ai_assert( false );
901 }
902
903 ai_assert( ( order[ 0 ] >= 0 ) && ( order[ 0 ] <= 2 ) );
904 ai_assert( ( order[ 1 ] >= 0 ) && ( order[ 1 ] <= 2 ) );
905 ai_assert( ( order[ 2 ] >= 0 ) && ( order[ 2 ] <= 2 ) );
906
907 if ( !is_id[ order[ 0 ] ] ) {
908 out = temp[ order[ 0 ] ];
909 }
910
911 if ( !is_id[ order[ 1 ] ] ) {
912 out = out * temp[ order[ 1 ] ];
913 }
914
915 if ( !is_id[ order[ 2 ] ] ) {
916 out = out * temp[ order[ 2 ] ];
917 }
918 }
919
NeedsComplexTransformationChain(const Model & model)920 bool Converter::NeedsComplexTransformationChain( const Model& model )
921 {
922 const PropertyTable& props = model.Props();
923 bool ok;
924
925 const float zero_epsilon = 1e-6f;
926 for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) {
927 const TransformationComp comp = static_cast< TransformationComp >( i );
928
929 if ( comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation ||
930 comp == TransformationComp_GeometricScaling || comp == TransformationComp_GeometricRotation || comp == TransformationComp_GeometricTranslation ) {
931 continue;
932 }
933
934 const aiVector3D& v = PropertyGet<aiVector3D>( props, NameTransformationCompProperty( comp ), ok );
935 if ( ok && v.SquareLength() > zero_epsilon ) {
936 return true;
937 }
938 }
939
940 return false;
941 }
942
NameTransformationChainNode(const std::string & name,TransformationComp comp)943 std::string Converter::NameTransformationChainNode( const std::string& name, TransformationComp comp )
944 {
945 return name + std::string( MAGIC_NODE_TAG ) + "_" + NameTransformationComp( comp );
946 }
947
GenerateTransformationNodeChain(const Model & model,std::vector<aiNode * > & output_nodes)948 void Converter::GenerateTransformationNodeChain( const Model& model, std::vector<aiNode*>& output_nodes )
949 {
950 const PropertyTable& props = model.Props();
951 const Model::RotOrder rot = model.RotationOrder();
952
953 bool ok;
954
955 aiMatrix4x4 chain[ TransformationComp_MAXIMUM ];
956 std::fill_n( chain, static_cast<unsigned int>( TransformationComp_MAXIMUM ), aiMatrix4x4() );
957
958 // generate transformation matrices for all the different transformation components
959 const float zero_epsilon = 1e-6f;
960 bool is_complex = false;
961
962 const aiVector3D& PreRotation = PropertyGet<aiVector3D>( props, "PreRotation", ok );
963 if ( ok && PreRotation.SquareLength() > zero_epsilon ) {
964 is_complex = true;
965
966 GetRotationMatrix( rot, PreRotation, chain[ TransformationComp_PreRotation ] );
967 }
968
969 const aiVector3D& PostRotation = PropertyGet<aiVector3D>( props, "PostRotation", ok );
970 if ( ok && PostRotation.SquareLength() > zero_epsilon ) {
971 is_complex = true;
972
973 GetRotationMatrix( rot, PostRotation, chain[ TransformationComp_PostRotation ] );
974 }
975
976 const aiVector3D& RotationPivot = PropertyGet<aiVector3D>( props, "RotationPivot", ok );
977 if ( ok && RotationPivot.SquareLength() > zero_epsilon ) {
978 is_complex = true;
979
980 aiMatrix4x4::Translation( RotationPivot, chain[ TransformationComp_RotationPivot ] );
981 aiMatrix4x4::Translation( -RotationPivot, chain[ TransformationComp_RotationPivotInverse ] );
982 }
983
984 const aiVector3D& RotationOffset = PropertyGet<aiVector3D>( props, "RotationOffset", ok );
985 if ( ok && RotationOffset.SquareLength() > zero_epsilon ) {
986 is_complex = true;
987
988 aiMatrix4x4::Translation( RotationOffset, chain[ TransformationComp_RotationOffset ] );
989 }
990
991 const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>( props, "ScalingOffset", ok );
992 if ( ok && ScalingOffset.SquareLength() > zero_epsilon ) {
993 is_complex = true;
994
995 aiMatrix4x4::Translation( ScalingOffset, chain[ TransformationComp_ScalingOffset ] );
996 }
997
998 const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>( props, "ScalingPivot", ok );
999 if ( ok && ScalingPivot.SquareLength() > zero_epsilon ) {
1000 is_complex = true;
1001
1002 aiMatrix4x4::Translation( ScalingPivot, chain[ TransformationComp_ScalingPivot ] );
1003 aiMatrix4x4::Translation( -ScalingPivot, chain[ TransformationComp_ScalingPivotInverse ] );
1004 }
1005
1006 const aiVector3D& Translation = PropertyGet<aiVector3D>( props, "Lcl Translation", ok );
1007 if ( ok && Translation.SquareLength() > zero_epsilon ) {
1008 aiMatrix4x4::Translation( Translation, chain[ TransformationComp_Translation ] );
1009 }
1010
1011 const aiVector3D& Scaling = PropertyGet<aiVector3D>( props, "Lcl Scaling", ok );
1012 if ( ok && std::fabs( Scaling.SquareLength() - 1.0f ) > zero_epsilon ) {
1013 aiMatrix4x4::Scaling( Scaling, chain[ TransformationComp_Scaling ] );
1014 }
1015
1016 const aiVector3D& Rotation = PropertyGet<aiVector3D>( props, "Lcl Rotation", ok );
1017 if ( ok && Rotation.SquareLength() > zero_epsilon ) {
1018 GetRotationMatrix( rot, Rotation, chain[ TransformationComp_Rotation ] );
1019 }
1020
1021 const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>( props, "GeometricScaling", ok );
1022 if ( ok && std::fabs( GeometricScaling.SquareLength() - 1.0f ) > zero_epsilon ) {
1023 aiMatrix4x4::Scaling( GeometricScaling, chain[ TransformationComp_GeometricScaling ] );
1024 }
1025
1026 const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>( props, "GeometricRotation", ok );
1027 if ( ok && GeometricRotation.SquareLength() > zero_epsilon ) {
1028 GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotation ] );
1029 }
1030
1031 const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>( props, "GeometricTranslation", ok );
1032 if ( ok && GeometricTranslation.SquareLength() > zero_epsilon ) {
1033 aiMatrix4x4::Translation( GeometricTranslation, chain[ TransformationComp_GeometricTranslation ] );
1034 }
1035
1036 // is_complex needs to be consistent with NeedsComplexTransformationChain()
1037 // or the interplay between this code and the animation converter would
1038 // not be guaranteed.
1039 ai_assert( NeedsComplexTransformationChain( model ) == is_complex );
1040
1041 const std::string& name = FixNodeName( model.Name() );
1042
1043 // now, if we have more than just Translation, Scaling and Rotation,
1044 // we need to generate a full node chain to accommodate for assimp's
1045 // lack to express pivots and offsets.
1046 if ( is_complex && doc.Settings().preservePivots ) {
1047 FBXImporter::LogInfo( "generating full transformation chain for node: " + name );
1048
1049 // query the anim_chain_bits dictionary to find out which chain elements
1050 // have associated node animation channels. These can not be dropped
1051 // even if they have identity transform in bind pose.
1052 NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find( name );
1053 const unsigned int anim_chain_bitmask = ( it == node_anim_chain_bits.end() ? 0 : ( *it ).second );
1054
1055 unsigned int bit = 0x1;
1056 for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) {
1057 const TransformationComp comp = static_cast<TransformationComp>( i );
1058
1059 if ( chain[ i ].IsIdentity() && ( anim_chain_bitmask & bit ) == 0 ) {
1060 continue;
1061 }
1062
1063 if ( comp == TransformationComp_PostRotation ) {
1064 chain[ i ] = chain[ i ].Inverse();
1065 }
1066
1067 aiNode* nd = new aiNode();
1068 output_nodes.push_back( nd );
1069
1070 nd->mName.Set( NameTransformationChainNode( name, comp ) );
1071 nd->mTransformation = chain[ i ];
1072 }
1073
1074 ai_assert( output_nodes.size() );
1075 return;
1076 }
1077
1078 // else, we can just multiply the matrices together
1079 aiNode* nd = new aiNode();
1080 output_nodes.push_back( nd );
1081
1082 nd->mName.Set( name );
1083
1084 for (const auto &transform : chain) {
1085 nd->mTransformation = nd->mTransformation * transform;
1086 }
1087 }
1088
SetupNodeMetadata(const Model & model,aiNode & nd)1089 void Converter::SetupNodeMetadata( const Model& model, aiNode& nd )
1090 {
1091 const PropertyTable& props = model.Props();
1092 DirectPropertyMap unparsedProperties = props.GetUnparsedProperties();
1093
1094 // create metadata on node
1095 const std::size_t numStaticMetaData = 2;
1096 aiMetadata* data = aiMetadata::Alloc( static_cast<unsigned int>(unparsedProperties.size() + numStaticMetaData) );
1097 nd.mMetaData = data;
1098 int index = 0;
1099
1100 // find user defined properties (3ds Max)
1101 data->Set( index++, "UserProperties", aiString( PropertyGet<std::string>( props, "UDP3DSMAX", "" ) ) );
1102 // preserve the info that a node was marked as Null node in the original file.
1103 data->Set( index++, "IsNull", model.IsNull() ? true : false );
1104
1105 // add unparsed properties to the node's metadata
1106 for( const DirectPropertyMap::value_type& prop : unparsedProperties ) {
1107 // Interpret the property as a concrete type
1108 if ( const TypedProperty<bool>* interpreted = prop.second->As<TypedProperty<bool> >() ) {
1109 data->Set( index++, prop.first, interpreted->Value() );
1110 } else if ( const TypedProperty<int>* interpreted = prop.second->As<TypedProperty<int> >() ) {
1111 data->Set( index++, prop.first, interpreted->Value() );
1112 } else if ( const TypedProperty<uint64_t>* interpreted = prop.second->As<TypedProperty<uint64_t> >() ) {
1113 data->Set( index++, prop.first, interpreted->Value() );
1114 } else if ( const TypedProperty<float>* interpreted = prop.second->As<TypedProperty<float> >() ) {
1115 data->Set( index++, prop.first, interpreted->Value() );
1116 } else if ( const TypedProperty<std::string>* interpreted = prop.second->As<TypedProperty<std::string> >() ) {
1117 data->Set( index++, prop.first, aiString( interpreted->Value() ) );
1118 } else if ( const TypedProperty<aiVector3D>* interpreted = prop.second->As<TypedProperty<aiVector3D> >() ) {
1119 data->Set( index++, prop.first, interpreted->Value() );
1120 } else {
1121 ai_assert( false );
1122 }
1123 }
1124 }
1125
ConvertModel(const Model & model,aiNode & nd,const aiMatrix4x4 & node_global_transform)1126 void Converter::ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform )
1127 {
1128 const std::vector<const Geometry*>& geos = model.GetGeometry();
1129
1130 std::vector<unsigned int> meshes;
1131 meshes.reserve( geos.size() );
1132
1133 for( const Geometry* geo : geos ) {
1134
1135 const MeshGeometry* const mesh = dynamic_cast< const MeshGeometry* >( geo );
1136 if ( mesh ) {
1137 const std::vector<unsigned int>& indices = ConvertMesh( *mesh, model, node_global_transform );
1138 std::copy( indices.begin(), indices.end(), std::back_inserter( meshes ) );
1139 }
1140 else {
1141 FBXImporter::LogWarn( "ignoring unrecognized geometry: " + geo->Name() );
1142 }
1143 }
1144
1145 if ( meshes.size() ) {
1146 nd.mMeshes = new unsigned int[ meshes.size() ]();
1147 nd.mNumMeshes = static_cast< unsigned int >( meshes.size() );
1148
1149 std::swap_ranges( meshes.begin(), meshes.end(), nd.mMeshes );
1150 }
1151 }
1152
ConvertMesh(const MeshGeometry & mesh,const Model & model,const aiMatrix4x4 & node_global_transform)1153 std::vector<unsigned int> Converter::ConvertMesh( const MeshGeometry& mesh, const Model& model,
1154 const aiMatrix4x4& node_global_transform )
1155 {
1156 std::vector<unsigned int> temp;
1157
1158 MeshMap::const_iterator it = meshes_converted.find( &mesh );
1159 if ( it != meshes_converted.end() ) {
1160 std::copy( ( *it ).second.begin(), ( *it ).second.end(), std::back_inserter( temp ) );
1161 return temp;
1162 }
1163
1164 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
1165 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
1166 if ( vertices.empty() || faces.empty() ) {
1167 FBXImporter::LogWarn( "ignoring empty geometry: " + mesh.Name() );
1168 return temp;
1169 }
1170
1171 // one material per mesh maps easily to aiMesh. Multiple material
1172 // meshes need to be split.
1173 const MatIndexArray& mindices = mesh.GetMaterialIndices();
1174 if ( doc.Settings().readMaterials && !mindices.empty() ) {
1175 const MatIndexArray::value_type base = mindices[ 0 ];
1176 for( MatIndexArray::value_type index : mindices ) {
1177 if ( index != base ) {
1178 return ConvertMeshMultiMaterial( mesh, model, node_global_transform );
1179 }
1180 }
1181 }
1182
1183 // faster code-path, just copy the data
1184 temp.push_back( ConvertMeshSingleMaterial( mesh, model, node_global_transform ) );
1185 return temp;
1186 }
1187
SetupEmptyMesh(const MeshGeometry & mesh)1188 aiMesh* Converter::SetupEmptyMesh( const MeshGeometry& mesh )
1189 {
1190 aiMesh* const out_mesh = new aiMesh();
1191 meshes.push_back( out_mesh );
1192 meshes_converted[ &mesh ].push_back( static_cast<unsigned int>( meshes.size() - 1 ) );
1193
1194 // set name
1195 std::string name = mesh.Name();
1196 if ( name.substr( 0, 10 ) == "Geometry::" ) {
1197 name = name.substr( 10 );
1198 }
1199
1200 if ( name.length() ) {
1201 out_mesh->mName.Set( name );
1202 }
1203
1204 return out_mesh;
1205 }
1206
ConvertMeshSingleMaterial(const MeshGeometry & mesh,const Model & model,const aiMatrix4x4 & node_global_transform)1207 unsigned int Converter::ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model,
1208 const aiMatrix4x4& node_global_transform )
1209 {
1210 const MatIndexArray& mindices = mesh.GetMaterialIndices();
1211 aiMesh* const out_mesh = SetupEmptyMesh( mesh );
1212
1213 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
1214 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
1215
1216 // copy vertices
1217 out_mesh->mNumVertices = static_cast<unsigned int>( vertices.size() );
1218 out_mesh->mVertices = new aiVector3D[ vertices.size() ];
1219 std::copy( vertices.begin(), vertices.end(), out_mesh->mVertices );
1220
1221 // generate dummy faces
1222 out_mesh->mNumFaces = static_cast<unsigned int>( faces.size() );
1223 aiFace* fac = out_mesh->mFaces = new aiFace[ faces.size() ]();
1224
1225 unsigned int cursor = 0;
1226 for( unsigned int pcount : faces ) {
1227 aiFace& f = *fac++;
1228 f.mNumIndices = pcount;
1229 f.mIndices = new unsigned int[ pcount ];
1230 switch ( pcount )
1231 {
1232 case 1:
1233 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
1234 break;
1235 case 2:
1236 out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
1237 break;
1238 case 3:
1239 out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
1240 break;
1241 default:
1242 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
1243 break;
1244 }
1245 for ( unsigned int i = 0; i < pcount; ++i ) {
1246 f.mIndices[ i ] = cursor++;
1247 }
1248 }
1249
1250 // copy normals
1251 const std::vector<aiVector3D>& normals = mesh.GetNormals();
1252 if ( normals.size() ) {
1253 ai_assert( normals.size() == vertices.size() );
1254
1255 out_mesh->mNormals = new aiVector3D[ vertices.size() ];
1256 std::copy( normals.begin(), normals.end(), out_mesh->mNormals );
1257 }
1258
1259 // copy tangents - assimp requires both tangents and bitangents (binormals)
1260 // to be present, or neither of them. Compute binormals from normals
1261 // and tangents if needed.
1262 const std::vector<aiVector3D>& tangents = mesh.GetTangents();
1263 const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
1264
1265 if ( tangents.size() ) {
1266 std::vector<aiVector3D> tempBinormals;
1267 if ( !binormals->size() ) {
1268 if ( normals.size() ) {
1269 tempBinormals.resize( normals.size() );
1270 for ( unsigned int i = 0; i < tangents.size(); ++i ) {
1271 tempBinormals[ i ] = normals[ i ] ^ tangents[ i ];
1272 }
1273
1274 binormals = &tempBinormals;
1275 }
1276 else {
1277 binormals = NULL;
1278 }
1279 }
1280
1281 if ( binormals ) {
1282 ai_assert( tangents.size() == vertices.size() );
1283 ai_assert( binormals->size() == vertices.size() );
1284
1285 out_mesh->mTangents = new aiVector3D[ vertices.size() ];
1286 std::copy( tangents.begin(), tangents.end(), out_mesh->mTangents );
1287
1288 out_mesh->mBitangents = new aiVector3D[ vertices.size() ];
1289 std::copy( binormals->begin(), binormals->end(), out_mesh->mBitangents );
1290 }
1291 }
1292
1293 // copy texture coords
1294 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
1295 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
1296 if ( uvs.empty() ) {
1297 break;
1298 }
1299
1300 aiVector3D* out_uv = out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ];
1301 for( const aiVector2D& v : uvs ) {
1302 *out_uv++ = aiVector3D( v.x, v.y, 0.0f );
1303 }
1304
1305 out_mesh->mNumUVComponents[ i ] = 2;
1306 }
1307
1308 // copy vertex colors
1309 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i ) {
1310 const std::vector<aiColor4D>& colors = mesh.GetVertexColors( i );
1311 if ( colors.empty() ) {
1312 break;
1313 }
1314
1315 out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ];
1316 std::copy( colors.begin(), colors.end(), out_mesh->mColors[ i ] );
1317 }
1318
1319 if ( !doc.Settings().readMaterials || mindices.empty() ) {
1320 FBXImporter::LogError( "no material assigned to mesh, setting default material" );
1321 out_mesh->mMaterialIndex = GetDefaultMaterial();
1322 }
1323 else {
1324 ConvertMaterialForMesh( out_mesh, model, mesh, mindices[ 0 ] );
1325 }
1326
1327 if ( doc.Settings().readWeights && mesh.DeformerSkin() != NULL ) {
1328 ConvertWeights( out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION );
1329 }
1330
1331 return static_cast<unsigned int>( meshes.size() - 1 );
1332 }
1333
ConvertMeshMultiMaterial(const MeshGeometry & mesh,const Model & model,const aiMatrix4x4 & node_global_transform)1334 std::vector<unsigned int> Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
1335 const aiMatrix4x4& node_global_transform )
1336 {
1337 const MatIndexArray& mindices = mesh.GetMaterialIndices();
1338 ai_assert( mindices.size() );
1339
1340 std::set<MatIndexArray::value_type> had;
1341 std::vector<unsigned int> indices;
1342
1343 for( MatIndexArray::value_type index : mindices ) {
1344 if ( had.find( index ) == had.end() ) {
1345
1346 indices.push_back( ConvertMeshMultiMaterial( mesh, model, index, node_global_transform ) );
1347 had.insert( index );
1348 }
1349 }
1350
1351 return indices;
1352 }
1353
ConvertMeshMultiMaterial(const MeshGeometry & mesh,const Model & model,MatIndexArray::value_type index,const aiMatrix4x4 & node_global_transform)1354 unsigned int Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
1355 MatIndexArray::value_type index,
1356 const aiMatrix4x4& node_global_transform )
1357 {
1358 aiMesh* const out_mesh = SetupEmptyMesh( mesh );
1359
1360 const MatIndexArray& mindices = mesh.GetMaterialIndices();
1361 const std::vector<aiVector3D>& vertices = mesh.GetVertices();
1362 const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
1363
1364 const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL;
1365
1366 unsigned int count_faces = 0;
1367 unsigned int count_vertices = 0;
1368
1369 // count faces
1370 std::vector<unsigned int>::const_iterator itf = faces.begin();
1371 for ( MatIndexArray::const_iterator it = mindices.begin(),
1372 end = mindices.end(); it != end; ++it, ++itf )
1373 {
1374 if ( ( *it ) != index ) {
1375 continue;
1376 }
1377 ++count_faces;
1378 count_vertices += *itf;
1379 }
1380
1381 ai_assert( count_faces );
1382 ai_assert( count_vertices );
1383
1384 // mapping from output indices to DOM indexing, needed to resolve weights
1385 std::vector<unsigned int> reverseMapping;
1386
1387 if ( process_weights ) {
1388 reverseMapping.resize( count_vertices );
1389 }
1390
1391 // allocate output data arrays, but don't fill them yet
1392 out_mesh->mNumVertices = count_vertices;
1393 out_mesh->mVertices = new aiVector3D[ count_vertices ];
1394
1395 out_mesh->mNumFaces = count_faces;
1396 aiFace* fac = out_mesh->mFaces = new aiFace[ count_faces ]();
1397
1398
1399 // allocate normals
1400 const std::vector<aiVector3D>& normals = mesh.GetNormals();
1401 if ( normals.size() ) {
1402 ai_assert( normals.size() == vertices.size() );
1403 out_mesh->mNormals = new aiVector3D[ vertices.size() ];
1404 }
1405
1406 // allocate tangents, binormals.
1407 const std::vector<aiVector3D>& tangents = mesh.GetTangents();
1408 const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
1409 std::vector<aiVector3D> tempBinormals;
1410
1411 if ( tangents.size() ) {
1412 if ( !binormals->size() ) {
1413 if ( normals.size() ) {
1414 // XXX this computes the binormals for the entire mesh, not only
1415 // the part for which we need them.
1416 tempBinormals.resize( normals.size() );
1417 for ( unsigned int i = 0; i < tangents.size(); ++i ) {
1418 tempBinormals[ i ] = normals[ i ] ^ tangents[ i ];
1419 }
1420
1421 binormals = &tempBinormals;
1422 }
1423 else {
1424 binormals = NULL;
1425 }
1426 }
1427
1428 if ( binormals ) {
1429 ai_assert( tangents.size() == vertices.size() && binormals->size() == vertices.size() );
1430
1431 out_mesh->mTangents = new aiVector3D[ vertices.size() ];
1432 out_mesh->mBitangents = new aiVector3D[ vertices.size() ];
1433 }
1434 }
1435
1436 // allocate texture coords
1437 unsigned int num_uvs = 0;
1438 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs ) {
1439 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
1440 if ( uvs.empty() ) {
1441 break;
1442 }
1443
1444 out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ];
1445 out_mesh->mNumUVComponents[ i ] = 2;
1446 }
1447
1448 // allocate vertex colors
1449 unsigned int num_vcs = 0;
1450 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs ) {
1451 const std::vector<aiColor4D>& colors = mesh.GetVertexColors( i );
1452 if ( colors.empty() ) {
1453 break;
1454 }
1455
1456 out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ];
1457 }
1458
1459 unsigned int cursor = 0, in_cursor = 0;
1460
1461 itf = faces.begin();
1462 for ( MatIndexArray::const_iterator it = mindices.begin(),
1463 end = mindices.end(); it != end; ++it, ++itf )
1464 {
1465 const unsigned int pcount = *itf;
1466 if ( ( *it ) != index ) {
1467 in_cursor += pcount;
1468 continue;
1469 }
1470
1471 aiFace& f = *fac++;
1472
1473 f.mNumIndices = pcount;
1474 f.mIndices = new unsigned int[ pcount ];
1475 switch ( pcount )
1476 {
1477 case 1:
1478 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
1479 break;
1480 case 2:
1481 out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
1482 break;
1483 case 3:
1484 out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
1485 break;
1486 default:
1487 out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
1488 break;
1489 }
1490 for ( unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor ) {
1491 f.mIndices[ i ] = cursor;
1492
1493 if ( reverseMapping.size() ) {
1494 reverseMapping[ cursor ] = in_cursor;
1495 }
1496
1497 out_mesh->mVertices[ cursor ] = vertices[ in_cursor ];
1498
1499 if ( out_mesh->mNormals ) {
1500 out_mesh->mNormals[ cursor ] = normals[ in_cursor ];
1501 }
1502
1503 if ( out_mesh->mTangents ) {
1504 out_mesh->mTangents[ cursor ] = tangents[ in_cursor ];
1505 out_mesh->mBitangents[ cursor ] = ( *binormals )[ in_cursor ];
1506 }
1507
1508 for ( unsigned int i = 0; i < num_uvs; ++i ) {
1509 const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
1510 out_mesh->mTextureCoords[ i ][ cursor ] = aiVector3D( uvs[ in_cursor ].x, uvs[ in_cursor ].y, 0.0f );
1511 }
1512
1513 for ( unsigned int i = 0; i < num_vcs; ++i ) {
1514 const std::vector<aiColor4D>& cols = mesh.GetVertexColors( i );
1515 out_mesh->mColors[ i ][ cursor ] = cols[ in_cursor ];
1516 }
1517 }
1518 }
1519
1520 ConvertMaterialForMesh( out_mesh, model, mesh, index );
1521
1522 if ( process_weights ) {
1523 ConvertWeights( out_mesh, model, mesh, node_global_transform, index, &reverseMapping );
1524 }
1525
1526 return static_cast<unsigned int>( meshes.size() - 1 );
1527 }
1528
ConvertWeights(aiMesh * out,const Model & model,const MeshGeometry & geo,const aiMatrix4x4 & node_global_transform,unsigned int materialIndex,std::vector<unsigned int> * outputVertStartIndices)1529 void Converter::ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo,
1530 const aiMatrix4x4& node_global_transform ,
1531 unsigned int materialIndex,
1532 std::vector<unsigned int>* outputVertStartIndices )
1533 {
1534 ai_assert( geo.DeformerSkin() );
1535
1536 std::vector<size_t> out_indices;
1537 std::vector<size_t> index_out_indices;
1538 std::vector<size_t> count_out_indices;
1539
1540 const Skin& sk = *geo.DeformerSkin();
1541
1542 std::vector<aiBone*> bones;
1543 bones.reserve( sk.Clusters().size() );
1544
1545 const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
1546 ai_assert( no_mat_check || outputVertStartIndices );
1547
1548 try {
1549
1550 for( const Cluster* cluster : sk.Clusters() ) {
1551 ai_assert( cluster );
1552
1553 const WeightIndexArray& indices = cluster->GetIndices();
1554
1555 if ( indices.empty() ) {
1556 continue;
1557 }
1558
1559 const MatIndexArray& mats = geo.GetMaterialIndices();
1560
1561 bool ok = false;
1562
1563 const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
1564
1565 count_out_indices.clear();
1566 index_out_indices.clear();
1567 out_indices.clear();
1568
1569 // now check if *any* of these weights is contained in the output mesh,
1570 // taking notes so we don't need to do it twice.
1571 for( WeightIndexArray::value_type index : indices ) {
1572
1573 unsigned int count = 0;
1574 const unsigned int* const out_idx = geo.ToOutputVertexIndex( index, count );
1575 // ToOutputVertexIndex only returns NULL if index is out of bounds
1576 // which should never happen
1577 ai_assert( out_idx != NULL );
1578
1579 index_out_indices.push_back( no_index_sentinel );
1580 count_out_indices.push_back( 0 );
1581
1582 for ( unsigned int i = 0; i < count; ++i ) {
1583 if ( no_mat_check || static_cast<size_t>( mats[ geo.FaceForVertexIndex( out_idx[ i ] ) ] ) == materialIndex ) {
1584
1585 if ( index_out_indices.back() == no_index_sentinel ) {
1586 index_out_indices.back() = out_indices.size();
1587
1588 }
1589
1590 if ( no_mat_check ) {
1591 out_indices.push_back( out_idx[ i ] );
1592 }
1593 else {
1594 // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn)
1595 const std::vector<unsigned int>::iterator it = std::lower_bound(
1596 outputVertStartIndices->begin(),
1597 outputVertStartIndices->end(),
1598 out_idx[ i ]
1599 );
1600
1601 out_indices.push_back( std::distance( outputVertStartIndices->begin(), it ) );
1602 }
1603
1604 ++count_out_indices.back();
1605 ok = true;
1606 }
1607 }
1608 }
1609
1610 // if we found at least one, generate the output bones
1611 // XXX this could be heavily simplified by collecting the bone
1612 // data in a single step.
1613 if ( ok ) {
1614 ConvertCluster( bones, model, *cluster, out_indices, index_out_indices,
1615 count_out_indices, node_global_transform );
1616 }
1617 }
1618 }
1619 catch ( std::exception& ) {
1620 std::for_each( bones.begin(), bones.end(), Util::delete_fun<aiBone>() );
1621 throw;
1622 }
1623
1624 if ( bones.empty() ) {
1625 return;
1626 }
1627
1628 out->mBones = new aiBone*[ bones.size() ]();
1629 out->mNumBones = static_cast<unsigned int>( bones.size() );
1630
1631 std::swap_ranges( bones.begin(), bones.end(), out->mBones );
1632 }
1633
ConvertCluster(std::vector<aiBone * > & bones,const Model &,const Cluster & cl,std::vector<size_t> & out_indices,std::vector<size_t> & index_out_indices,std::vector<size_t> & count_out_indices,const aiMatrix4x4 & node_global_transform)1634 void Converter::ConvertCluster( std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
1635 std::vector<size_t>& out_indices,
1636 std::vector<size_t>& index_out_indices,
1637 std::vector<size_t>& count_out_indices,
1638 const aiMatrix4x4& node_global_transform )
1639 {
1640
1641 aiBone* const bone = new aiBone();
1642 bones.push_back( bone );
1643
1644 bone->mName = FixNodeName( cl.TargetNode()->Name() );
1645
1646 bone->mOffsetMatrix = cl.TransformLink();
1647 bone->mOffsetMatrix.Inverse();
1648
1649 bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
1650
1651 bone->mNumWeights = static_cast<unsigned int>( out_indices.size() );
1652 aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[ out_indices.size() ];
1653
1654 const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
1655 const WeightArray& weights = cl.GetWeights();
1656
1657 const size_t c = index_out_indices.size();
1658 for ( size_t i = 0; i < c; ++i ) {
1659 const size_t index_index = index_out_indices[ i ];
1660
1661 if ( index_index == no_index_sentinel ) {
1662 continue;
1663 }
1664
1665 const size_t cc = count_out_indices[ i ];
1666 for ( size_t j = 0; j < cc; ++j ) {
1667 aiVertexWeight& out_weight = *cursor++;
1668
1669 out_weight.mVertexId = static_cast<unsigned int>( out_indices[ index_index + j ] );
1670 out_weight.mWeight = weights[ i ];
1671 }
1672 }
1673 }
1674
ConvertMaterialForMesh(aiMesh * out,const Model & model,const MeshGeometry & geo,MatIndexArray::value_type materialIndex)1675 void Converter::ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo,
1676 MatIndexArray::value_type materialIndex )
1677 {
1678 // locate source materials for this mesh
1679 const std::vector<const Material*>& mats = model.GetMaterials();
1680 if ( static_cast<unsigned int>( materialIndex ) >= mats.size() || materialIndex < 0 ) {
1681 FBXImporter::LogError( "material index out of bounds, setting default material" );
1682 out->mMaterialIndex = GetDefaultMaterial();
1683 return;
1684 }
1685
1686 const Material* const mat = mats[ materialIndex ];
1687 MaterialMap::const_iterator it = materials_converted.find( mat );
1688 if ( it != materials_converted.end() ) {
1689 out->mMaterialIndex = ( *it ).second;
1690 return;
1691 }
1692
1693 out->mMaterialIndex = ConvertMaterial( *mat, &geo );
1694 materials_converted[ mat ] = out->mMaterialIndex;
1695 }
1696
GetDefaultMaterial()1697 unsigned int Converter::GetDefaultMaterial()
1698 {
1699 if ( defaultMaterialIndex ) {
1700 return defaultMaterialIndex - 1;
1701 }
1702
1703 aiMaterial* out_mat = new aiMaterial();
1704 materials.push_back( out_mat );
1705
1706 const aiColor3D diffuse = aiColor3D( 0.8f, 0.8f, 0.8f );
1707 out_mat->AddProperty( &diffuse, 1, AI_MATKEY_COLOR_DIFFUSE );
1708
1709 aiString s;
1710 s.Set( AI_DEFAULT_MATERIAL_NAME );
1711
1712 out_mat->AddProperty( &s, AI_MATKEY_NAME );
1713
1714 defaultMaterialIndex = static_cast< unsigned int >( materials.size() );
1715 return defaultMaterialIndex - 1;
1716 }
1717
1718
ConvertMaterial(const Material & material,const MeshGeometry * const mesh)1719 unsigned int Converter::ConvertMaterial( const Material& material, const MeshGeometry* const mesh )
1720 {
1721 const PropertyTable& props = material.Props();
1722
1723 // generate empty output material
1724 aiMaterial* out_mat = new aiMaterial();
1725 materials_converted[ &material ] = static_cast<unsigned int>( materials.size() );
1726
1727 materials.push_back( out_mat );
1728
1729 aiString str;
1730
1731 // stip Material:: prefix
1732 std::string name = material.Name();
1733 if ( name.substr( 0, 10 ) == "Material::" ) {
1734 name = name.substr( 10 );
1735 }
1736
1737 // set material name if not empty - this could happen
1738 // and there should be no key for it in this case.
1739 if ( name.length() ) {
1740 str.Set( name );
1741 out_mat->AddProperty( &str, AI_MATKEY_NAME );
1742 }
1743
1744 // shading stuff and colors
1745 SetShadingPropertiesCommon( out_mat, props );
1746
1747 // texture assignments
1748 SetTextureProperties( out_mat, material.Textures(), mesh );
1749 SetTextureProperties( out_mat, material.LayeredTextures(), mesh );
1750
1751 return static_cast<unsigned int>( materials.size() - 1 );
1752 }
1753
ConvertVideo(const Video & video)1754 unsigned int Converter::ConvertVideo( const Video& video )
1755 {
1756 // generate empty output texture
1757 aiTexture* out_tex = new aiTexture();
1758 textures.push_back( out_tex );
1759
1760 // assuming the texture is compressed
1761 out_tex->mWidth = static_cast<unsigned int>( video.ContentLength() ); // total data size
1762 out_tex->mHeight = 0; // fixed to 0
1763
1764 // steal the data from the Video to avoid an additional copy
1765 out_tex->pcData = reinterpret_cast<aiTexel*>( const_cast<Video&>( video ).RelinquishContent() );
1766
1767 // try to extract a hint from the file extension
1768 const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName();
1769 std::string ext = BaseImporter::GetExtension( filename );
1770
1771 if ( ext == "jpeg" ) {
1772 ext = "jpg";
1773 }
1774
1775 if ( ext.size() <= 3 ) {
1776 memcpy( out_tex->achFormatHint, ext.c_str(), ext.size() );
1777 }
1778
1779 return static_cast<unsigned int>( textures.size() - 1 );
1780 }
1781
TrySetTextureProperties(aiMaterial * out_mat,const TextureMap & textures,const std::string & propName,aiTextureType target,const MeshGeometry * const mesh)1782 void Converter::TrySetTextureProperties( aiMaterial* out_mat, const TextureMap& textures,
1783 const std::string& propName,
1784 aiTextureType target, const MeshGeometry* const mesh )
1785 {
1786 TextureMap::const_iterator it = textures.find( propName );
1787 if ( it == textures.end() ) {
1788 return;
1789 }
1790
1791 const Texture* const tex = ( *it ).second;
1792 if ( tex != 0 )
1793 {
1794 aiString path;
1795 path.Set( tex->RelativeFilename() );
1796
1797 const Video* media = tex->Media();
1798 if (media != 0) {
1799 bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
1800 unsigned int index;
1801
1802 VideoMap::const_iterator it = textures_converted.find(media);
1803 if (it != textures_converted.end()) {
1804 index = (*it).second;
1805 textureReady = true;
1806 }
1807 else {
1808 if (media->ContentLength() > 0) {
1809 index = ConvertVideo(*media);
1810 textures_converted[media] = index;
1811 textureReady = true;
1812 }
1813 else if (doc.Settings().searchEmbeddedTextures) { //try to find the texture on the already-loaded textures by the filename, if the flag is on
1814 textureReady = FindTextureIndexByFilename(*media, index);
1815 }
1816 }
1817
1818 // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture), if the texture is ready
1819 if (textureReady) {
1820 path.data[0] = '*';
1821 path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index);
1822 }
1823 }
1824
1825 out_mat->AddProperty( &path, _AI_MATKEY_TEXTURE_BASE, target, 0 );
1826
1827 aiUVTransform uvTrafo;
1828 // XXX handle all kinds of UV transformations
1829 uvTrafo.mScaling = tex->UVScaling();
1830 uvTrafo.mTranslation = tex->UVTranslation();
1831 out_mat->AddProperty( &uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0 );
1832
1833 const PropertyTable& props = tex->Props();
1834
1835 int uvIndex = 0;
1836
1837 bool ok;
1838 const std::string& uvSet = PropertyGet<std::string>( props, "UVSet", ok );
1839 if ( ok ) {
1840 // "default" is the name which usually appears in the FbxFileTexture template
1841 if ( uvSet != "default" && uvSet.length() ) {
1842 // this is a bit awkward - we need to find a mesh that uses this
1843 // material and scan its UV channels for the given UV name because
1844 // assimp references UV channels by index, not by name.
1845
1846 // XXX: the case that UV channels may appear in different orders
1847 // in meshes is unhandled. A possible solution would be to sort
1848 // the UV channels alphabetically, but this would have the side
1849 // effect that the primary (first) UV channel would sometimes
1850 // be moved, causing trouble when users read only the first
1851 // UV channel and ignore UV channel assignments altogether.
1852
1853 const unsigned int matIndex = static_cast<unsigned int>( std::distance( materials.begin(),
1854 std::find( materials.begin(), materials.end(), out_mat )
1855 ) );
1856
1857
1858 uvIndex = -1;
1859 if ( !mesh )
1860 {
1861 for( const MeshMap::value_type& v : meshes_converted ) {
1862 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> ( v.first );
1863 if ( !mesh ) {
1864 continue;
1865 }
1866
1867 const MatIndexArray& mats = mesh->GetMaterialIndices();
1868 if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) {
1869 continue;
1870 }
1871
1872 int index = -1;
1873 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
1874 if ( mesh->GetTextureCoords( i ).empty() ) {
1875 break;
1876 }
1877 const std::string& name = mesh->GetTextureCoordChannelName( i );
1878 if ( name == uvSet ) {
1879 index = static_cast<int>( i );
1880 break;
1881 }
1882 }
1883 if ( index == -1 ) {
1884 FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
1885 continue;
1886 }
1887
1888 if ( uvIndex == -1 ) {
1889 uvIndex = index;
1890 }
1891 else {
1892 FBXImporter::LogWarn( "the UV channel named " + uvSet +
1893 " appears at different positions in meshes, results will be wrong" );
1894 }
1895 }
1896 }
1897 else
1898 {
1899 int index = -1;
1900 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
1901 if ( mesh->GetTextureCoords( i ).empty() ) {
1902 break;
1903 }
1904 const std::string& name = mesh->GetTextureCoordChannelName( i );
1905 if ( name == uvSet ) {
1906 index = static_cast<int>( i );
1907 break;
1908 }
1909 }
1910 if ( index == -1 ) {
1911 FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
1912 }
1913
1914 if ( uvIndex == -1 ) {
1915 uvIndex = index;
1916 }
1917 }
1918
1919 if ( uvIndex == -1 ) {
1920 FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" );
1921 uvIndex = 0;
1922 }
1923 }
1924 }
1925
1926 out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0 );
1927 }
1928 }
1929
TrySetTextureProperties(aiMaterial * out_mat,const LayeredTextureMap & layeredTextures,const std::string & propName,aiTextureType target,const MeshGeometry * const mesh)1930 void Converter::TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
1931 const std::string& propName,
1932 aiTextureType target, const MeshGeometry* const mesh )
1933 {
1934 LayeredTextureMap::const_iterator it = layeredTextures.find( propName );
1935 if ( it == layeredTextures.end() ) {
1936 return;
1937 }
1938
1939 int texCount = (*it).second->textureCount();
1940
1941 // Set the blend mode for layered textures
1942 int blendmode= (*it).second->GetBlendMode();
1943 out_mat->AddProperty(&blendmode,1,_AI_MATKEY_TEXOP_BASE,target,0);
1944
1945 for(int texIndex = 0; texIndex < texCount; texIndex++){
1946
1947 const Texture* const tex = ( *it ).second->getTexture(texIndex);
1948
1949 aiString path;
1950 path.Set( tex->RelativeFilename() );
1951
1952 out_mat->AddProperty( &path, _AI_MATKEY_TEXTURE_BASE, target, texIndex );
1953
1954 aiUVTransform uvTrafo;
1955 // XXX handle all kinds of UV transformations
1956 uvTrafo.mScaling = tex->UVScaling();
1957 uvTrafo.mTranslation = tex->UVTranslation();
1958 out_mat->AddProperty( &uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex );
1959
1960 const PropertyTable& props = tex->Props();
1961
1962 int uvIndex = 0;
1963
1964 bool ok;
1965 const std::string& uvSet = PropertyGet<std::string>( props, "UVSet", ok );
1966 if ( ok ) {
1967 // "default" is the name which usually appears in the FbxFileTexture template
1968 if ( uvSet != "default" && uvSet.length() ) {
1969 // this is a bit awkward - we need to find a mesh that uses this
1970 // material and scan its UV channels for the given UV name because
1971 // assimp references UV channels by index, not by name.
1972
1973 // XXX: the case that UV channels may appear in different orders
1974 // in meshes is unhandled. A possible solution would be to sort
1975 // the UV channels alphabetically, but this would have the side
1976 // effect that the primary (first) UV channel would sometimes
1977 // be moved, causing trouble when users read only the first
1978 // UV channel and ignore UV channel assignments altogether.
1979
1980 const unsigned int matIndex = static_cast<unsigned int>( std::distance( materials.begin(),
1981 std::find( materials.begin(), materials.end(), out_mat )
1982 ) );
1983
1984 uvIndex = -1;
1985 if ( !mesh )
1986 {
1987 for( const MeshMap::value_type& v : meshes_converted ) {
1988 const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> ( v.first );
1989 if ( !mesh ) {
1990 continue;
1991 }
1992
1993 const MatIndexArray& mats = mesh->GetMaterialIndices();
1994 if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) {
1995 continue;
1996 }
1997
1998 int index = -1;
1999 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
2000 if ( mesh->GetTextureCoords( i ).empty() ) {
2001 break;
2002 }
2003 const std::string& name = mesh->GetTextureCoordChannelName( i );
2004 if ( name == uvSet ) {
2005 index = static_cast<int>( i );
2006 break;
2007 }
2008 }
2009 if ( index == -1 ) {
2010 FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
2011 continue;
2012 }
2013
2014 if ( uvIndex == -1 ) {
2015 uvIndex = index;
2016 }
2017 else {
2018 FBXImporter::LogWarn( "the UV channel named " + uvSet +
2019 " appears at different positions in meshes, results will be wrong" );
2020 }
2021 }
2022 }
2023 else
2024 {
2025 int index = -1;
2026 for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
2027 if ( mesh->GetTextureCoords( i ).empty() ) {
2028 break;
2029 }
2030 const std::string& name = mesh->GetTextureCoordChannelName( i );
2031 if ( name == uvSet ) {
2032 index = static_cast<int>( i );
2033 break;
2034 }
2035 }
2036 if ( index == -1 ) {
2037 FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
2038 }
2039
2040 if ( uvIndex == -1 ) {
2041 uvIndex = index;
2042 }
2043 }
2044
2045 if ( uvIndex == -1 ) {
2046 FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" );
2047 uvIndex = 0;
2048 }
2049 }
2050 }
2051
2052 out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, texIndex );
2053 }
2054 }
2055
SetTextureProperties(aiMaterial * out_mat,const TextureMap & textures,const MeshGeometry * const mesh)2056 void Converter::SetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh )
2057 {
2058 TrySetTextureProperties( out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE, mesh );
2059 TrySetTextureProperties( out_mat, textures, "AmbientColor", aiTextureType_AMBIENT, mesh );
2060 TrySetTextureProperties( out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE, mesh );
2061 TrySetTextureProperties( out_mat, textures, "SpecularColor", aiTextureType_SPECULAR, mesh );
2062 TrySetTextureProperties( out_mat, textures, "SpecularFactor", aiTextureType_SPECULAR, mesh);
2063 TrySetTextureProperties( out_mat, textures, "TransparentColor", aiTextureType_OPACITY, mesh );
2064 TrySetTextureProperties( out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION, mesh );
2065 TrySetTextureProperties( out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh );
2066 TrySetTextureProperties( out_mat, textures, "NormalMap", aiTextureType_NORMALS, mesh );
2067 TrySetTextureProperties( out_mat, textures, "Bump", aiTextureType_HEIGHT, mesh );
2068 TrySetTextureProperties( out_mat, textures, "ShininessExponent", aiTextureType_SHININESS, mesh );
2069 }
2070
SetTextureProperties(aiMaterial * out_mat,const LayeredTextureMap & layeredTextures,const MeshGeometry * const mesh)2071 void Converter::SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh )
2072 {
2073 TrySetTextureProperties( out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE, mesh );
2074 TrySetTextureProperties( out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT, mesh );
2075 TrySetTextureProperties( out_mat, layeredTextures, "EmissiveColor", aiTextureType_EMISSIVE, mesh );
2076 TrySetTextureProperties( out_mat, layeredTextures, "SpecularColor", aiTextureType_SPECULAR, mesh );
2077 TrySetTextureProperties( out_mat, layeredTextures, "SpecularFactor", aiTextureType_SPECULAR, mesh);
2078 TrySetTextureProperties( out_mat, layeredTextures, "TransparentColor", aiTextureType_OPACITY, mesh );
2079 TrySetTextureProperties( out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION, mesh );
2080 TrySetTextureProperties( out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh );
2081 TrySetTextureProperties( out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS, mesh );
2082 TrySetTextureProperties( out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT, mesh );
2083 TrySetTextureProperties( out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh );
2084 }
2085
GetColorPropertyFromMaterial(const PropertyTable & props,const std::string & baseName,bool & result)2086 aiColor3D Converter::GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName,
2087 bool& result )
2088 {
2089 result = true;
2090
2091 bool ok;
2092 const aiVector3D& Diffuse = PropertyGet<aiVector3D>( props, baseName, ok );
2093 if ( ok ) {
2094 return aiColor3D( Diffuse.x, Diffuse.y, Diffuse.z );
2095 }
2096 else {
2097 aiVector3D DiffuseColor = PropertyGet<aiVector3D>( props, baseName + "Color", ok );
2098 if ( ok ) {
2099 float DiffuseFactor = PropertyGet<float>( props, baseName + "Factor", ok );
2100 if ( ok ) {
2101 DiffuseColor *= DiffuseFactor;
2102 }
2103
2104 return aiColor3D( DiffuseColor.x, DiffuseColor.y, DiffuseColor.z );
2105 }
2106 }
2107 result = false;
2108 return aiColor3D( 0.0f, 0.0f, 0.0f );
2109 }
2110
2111
SetShadingPropertiesCommon(aiMaterial * out_mat,const PropertyTable & props)2112 void Converter::SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props )
2113 {
2114 // set shading properties. There are various, redundant ways in which FBX materials
2115 // specify their shading settings (depending on shading models, prop
2116 // template etc.). No idea which one is right in a particular context.
2117 // Just try to make sense of it - there's no spec to verify this against,
2118 // so why should we.
2119 bool ok;
2120 const aiColor3D& Diffuse = GetColorPropertyFromMaterial( props, "Diffuse", ok );
2121 if ( ok ) {
2122 out_mat->AddProperty( &Diffuse, 1, AI_MATKEY_COLOR_DIFFUSE );
2123 }
2124
2125 const aiColor3D& Emissive = GetColorPropertyFromMaterial( props, "Emissive", ok );
2126 if ( ok ) {
2127 out_mat->AddProperty( &Emissive, 1, AI_MATKEY_COLOR_EMISSIVE );
2128 }
2129
2130 const aiColor3D& Ambient = GetColorPropertyFromMaterial( props, "Ambient", ok );
2131 if ( ok ) {
2132 out_mat->AddProperty( &Ambient, 1, AI_MATKEY_COLOR_AMBIENT );
2133 }
2134
2135 const aiColor3D& Specular = GetColorPropertyFromMaterial( props, "Specular", ok );
2136 if ( ok ) {
2137 out_mat->AddProperty( &Specular, 1, AI_MATKEY_COLOR_SPECULAR );
2138 }
2139
2140 const float Opacity = PropertyGet<float>( props, "Opacity", ok );
2141 if ( ok ) {
2142 out_mat->AddProperty( &Opacity, 1, AI_MATKEY_OPACITY );
2143 }
2144
2145 const float Reflectivity = PropertyGet<float>( props, "Reflectivity", ok );
2146 if ( ok ) {
2147 out_mat->AddProperty( &Reflectivity, 1, AI_MATKEY_REFLECTIVITY );
2148 }
2149
2150 const float Shininess = PropertyGet<float>( props, "Shininess", ok );
2151 if ( ok ) {
2152 out_mat->AddProperty( &Shininess, 1, AI_MATKEY_SHININESS_STRENGTH );
2153 }
2154
2155 const float ShininessExponent = PropertyGet<float>( props, "ShininessExponent", ok );
2156 if ( ok ) {
2157 out_mat->AddProperty( &ShininessExponent, 1, AI_MATKEY_SHININESS );
2158 }
2159
2160 const float BumpFactor = PropertyGet<float>(props, "BumpFactor", ok);
2161 if (ok) {
2162 out_mat->AddProperty(&BumpFactor, 1, AI_MATKEY_BUMPSCALING);
2163 }
2164
2165 const float DispFactor = PropertyGet<float>(props, "DisplacementFactor", ok);
2166 if (ok) {
2167 out_mat->AddProperty(&DispFactor, 1, "$mat.displacementscaling", 0, 0);
2168 }
2169 }
2170
2171
FrameRateToDouble(FileGlobalSettings::FrameRate fp,double customFPSVal)2172 double Converter::FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal )
2173 {
2174 switch ( fp ) {
2175 case FileGlobalSettings::FrameRate_DEFAULT:
2176 return 1.0;
2177
2178 case FileGlobalSettings::FrameRate_120:
2179 return 120.0;
2180
2181 case FileGlobalSettings::FrameRate_100:
2182 return 100.0;
2183
2184 case FileGlobalSettings::FrameRate_60:
2185 return 60.0;
2186
2187 case FileGlobalSettings::FrameRate_50:
2188 return 50.0;
2189
2190 case FileGlobalSettings::FrameRate_48:
2191 return 48.0;
2192
2193 case FileGlobalSettings::FrameRate_30:
2194 case FileGlobalSettings::FrameRate_30_DROP:
2195 return 30.0;
2196
2197 case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME:
2198 case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME:
2199 return 29.9700262;
2200
2201 case FileGlobalSettings::FrameRate_PAL:
2202 return 25.0;
2203
2204 case FileGlobalSettings::FrameRate_CINEMA:
2205 return 24.0;
2206
2207 case FileGlobalSettings::FrameRate_1000:
2208 return 1000.0;
2209
2210 case FileGlobalSettings::FrameRate_CINEMA_ND:
2211 return 23.976;
2212
2213 case FileGlobalSettings::FrameRate_CUSTOM:
2214 return customFPSVal;
2215
2216 case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings
2217 break;
2218 }
2219
2220 ai_assert( false );
2221 return -1.0f;
2222 }
2223
2224
ConvertAnimations()2225 void Converter::ConvertAnimations()
2226 {
2227 // first of all determine framerate
2228 const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode();
2229 const float custom = doc.GlobalSettings().CustomFrameRate();
2230 anim_fps = FrameRateToDouble( fps, custom );
2231
2232 const std::vector<const AnimationStack*>& animations = doc.AnimationStacks();
2233 for( const AnimationStack* stack : animations ) {
2234 ConvertAnimationStack( *stack );
2235 }
2236 }
2237
RenameNode(const std::string & fixed_name,const std::string & new_name)2238 void Converter::RenameNode( const std::string& fixed_name, const std::string& new_name ) {
2239 if ( node_names.find( fixed_name ) == node_names.end() ) {
2240 FBXImporter::LogError( "Cannot rename node " + fixed_name + ", not existing.");
2241 return;
2242 }
2243
2244 if ( node_names.find( new_name ) != node_names.end() ) {
2245 FBXImporter::LogError( "Cannot rename node " + fixed_name + " to " + new_name +", name already existing." );
2246 return;
2247 }
2248
2249 ai_assert( node_names.find( fixed_name ) != node_names.end() );
2250 ai_assert( node_names.find( new_name ) == node_names.end() );
2251
2252 renamed_nodes[ fixed_name ] = new_name;
2253
2254 const aiString fn( fixed_name );
2255
2256 for( aiCamera* cam : cameras ) {
2257 if ( cam->mName == fn ) {
2258 cam->mName.Set( new_name );
2259 break;
2260 }
2261 }
2262
2263 for( aiLight* light : lights ) {
2264 if ( light->mName == fn ) {
2265 light->mName.Set( new_name );
2266 break;
2267 }
2268 }
2269
2270 for( aiAnimation* anim : animations ) {
2271 for ( unsigned int i = 0; i < anim->mNumChannels; ++i ) {
2272 aiNodeAnim* const na = anim->mChannels[ i ];
2273 if ( na->mNodeName == fn ) {
2274 na->mNodeName.Set( new_name );
2275 break;
2276 }
2277 }
2278 }
2279 }
2280
2281
FixNodeName(const std::string & name)2282 std::string Converter::FixNodeName( const std::string& name )
2283 {
2284 // strip Model:: prefix, avoiding ambiguities (i.e. don't strip if
2285 // this causes ambiguities, well possible between empty identifiers,
2286 // such as "Model::" and ""). Make sure the behaviour is consistent
2287 // across multiple calls to FixNodeName().
2288 if ( name.substr( 0, 7 ) == "Model::" ) {
2289 std::string temp = name.substr( 7 );
2290
2291 const NodeNameMap::const_iterator it = node_names.find( temp );
2292 if ( it != node_names.end() ) {
2293 if ( !( *it ).second ) {
2294 return FixNodeName( name + "_" );
2295 }
2296 }
2297 node_names[ temp ] = true;
2298
2299 const NameNameMap::const_iterator rit = renamed_nodes.find( temp );
2300 return rit == renamed_nodes.end() ? temp : ( *rit ).second;
2301 }
2302
2303 const NodeNameMap::const_iterator it = node_names.find( name );
2304 if ( it != node_names.end() ) {
2305 if ( ( *it ).second ) {
2306 return FixNodeName( name + "_" );
2307 }
2308 }
2309 node_names[ name ] = false;
2310
2311 const NameNameMap::const_iterator rit = renamed_nodes.find( name );
2312 return rit == renamed_nodes.end() ? name : ( *rit ).second;
2313 }
2314
ConvertAnimationStack(const AnimationStack & st)2315 void Converter::ConvertAnimationStack( const AnimationStack& st )
2316 {
2317 const AnimationLayerList& layers = st.Layers();
2318 if ( layers.empty() ) {
2319 return;
2320 }
2321
2322 aiAnimation* const anim = new aiAnimation();
2323 animations.push_back( anim );
2324
2325 // strip AnimationStack:: prefix
2326 std::string name = st.Name();
2327 if ( name.substr( 0, 16 ) == "AnimationStack::" ) {
2328 name = name.substr( 16 );
2329 }
2330 else if ( name.substr( 0, 11 ) == "AnimStack::" ) {
2331 name = name.substr( 11 );
2332 }
2333
2334 anim->mName.Set( name );
2335
2336 // need to find all nodes for which we need to generate node animations -
2337 // it may happen that we need to merge multiple layers, though.
2338 NodeMap node_map;
2339
2340 // reverse mapping from curves to layers, much faster than querying
2341 // the FBX DOM for it.
2342 LayerMap layer_map;
2343
2344 const char* prop_whitelist[] = {
2345 "Lcl Scaling",
2346 "Lcl Rotation",
2347 "Lcl Translation"
2348 };
2349
2350 for( const AnimationLayer* layer : layers ) {
2351 ai_assert( layer );
2352
2353 const AnimationCurveNodeList& nodes = layer->Nodes( prop_whitelist, 3 );
2354 for( const AnimationCurveNode* node : nodes ) {
2355 ai_assert( node );
2356
2357 const Model* const model = dynamic_cast<const Model*>( node->Target() );
2358 // this can happen - it could also be a NodeAttribute (i.e. for camera animations)
2359 if ( !model ) {
2360 continue;
2361 }
2362
2363 const std::string& name = FixNodeName( model->Name() );
2364 node_map[ name ].push_back( node );
2365
2366 layer_map[ node ] = layer;
2367 }
2368 }
2369
2370 // generate node animations
2371 std::vector<aiNodeAnim*> node_anims;
2372
2373 double min_time = 1e10;
2374 double max_time = -1e10;
2375
2376 int64_t start_time = st.LocalStart();
2377 int64_t stop_time = st.LocalStop();
2378 bool has_local_startstop = start_time != 0 || stop_time != 0;
2379 if ( !has_local_startstop ) {
2380 // no time range given, so accept every keyframe and use the actual min/max time
2381 // the numbers are INT64_MIN/MAX, the 20000 is for safety because GenerateNodeAnimations uses an epsilon of 10000
2382 start_time = -9223372036854775807ll + 20000;
2383 stop_time = 9223372036854775807ll - 20000;
2384 }
2385
2386 try {
2387 for( const NodeMap::value_type& kv : node_map ) {
2388 GenerateNodeAnimations( node_anims,
2389 kv.first,
2390 kv.second,
2391 layer_map,
2392 start_time, stop_time,
2393 max_time,
2394 min_time );
2395 }
2396 }
2397 catch ( std::exception& ) {
2398 std::for_each( node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>() );
2399 throw;
2400 }
2401
2402 if ( node_anims.size() ) {
2403 anim->mChannels = new aiNodeAnim*[ node_anims.size() ]();
2404 anim->mNumChannels = static_cast<unsigned int>( node_anims.size() );
2405
2406 std::swap_ranges( node_anims.begin(), node_anims.end(), anim->mChannels );
2407 }
2408 else {
2409 // empty animations would fail validation, so drop them
2410 delete anim;
2411 animations.pop_back();
2412 FBXImporter::LogInfo( "ignoring empty AnimationStack (using IK?): " + name );
2413 return;
2414 }
2415
2416 double start_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(start_time) * anim_fps) : min_time;
2417 double stop_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(stop_time) * anim_fps) : max_time;
2418
2419 // adjust relative timing for animation
2420 for ( unsigned int c = 0; c < anim->mNumChannels; c++ ) {
2421 aiNodeAnim* channel = anim->mChannels[ c ];
2422 for ( uint32_t i = 0; i < channel->mNumPositionKeys; i++ )
2423 channel->mPositionKeys[ i ].mTime -= start_time_fps;
2424 for ( uint32_t i = 0; i < channel->mNumRotationKeys; i++ )
2425 channel->mRotationKeys[ i ].mTime -= start_time_fps;
2426 for ( uint32_t i = 0; i < channel->mNumScalingKeys; i++ )
2427 channel->mScalingKeys[ i ].mTime -= start_time_fps;
2428 }
2429
2430 // for some mysterious reason, mDuration is simply the maximum key -- the
2431 // validator always assumes animations to start at zero.
2432 anim->mDuration = stop_time_fps - start_time_fps;
2433 anim->mTicksPerSecond = anim_fps;
2434 }
2435
2436 #ifdef ASSIMP_BUILD_DEBUG
2437 // ------------------------------------------------------------------------------------------------
2438 // sanity check whether the input is ok
validateAnimCurveNodes(const std::vector<const AnimationCurveNode * > & curves,bool strictMode)2439 static void validateAnimCurveNodes( const std::vector<const AnimationCurveNode*>& curves,
2440 bool strictMode ) {
2441 const Object* target( NULL );
2442 for( const AnimationCurveNode* node : curves ) {
2443 if ( !target ) {
2444 target = node->Target();
2445 }
2446 if ( node->Target() != target ) {
2447 FBXImporter::LogWarn( "Node target is nullptr type." );
2448 }
2449 if ( strictMode ) {
2450 ai_assert( node->Target() == target );
2451 }
2452 }
2453 }
2454 #endif // ASSIMP_BUILD_DEBUG
2455
2456 // ------------------------------------------------------------------------------------------------
GenerateNodeAnimations(std::vector<aiNodeAnim * > & node_anims,const std::string & fixed_name,const std::vector<const AnimationCurveNode * > & curves,const LayerMap & layer_map,int64_t start,int64_t stop,double & max_time,double & min_time)2457 void Converter::GenerateNodeAnimations( std::vector<aiNodeAnim*>& node_anims,
2458 const std::string& fixed_name,
2459 const std::vector<const AnimationCurveNode*>& curves,
2460 const LayerMap& layer_map,
2461 int64_t start, int64_t stop,
2462 double& max_time,
2463 double& min_time )
2464 {
2465
2466 NodeMap node_property_map;
2467 ai_assert( curves.size() );
2468
2469 #ifdef ASSIMP_BUILD_DEBUG
2470 validateAnimCurveNodes( curves, doc.Settings().strictMode );
2471 #endif
2472 const AnimationCurveNode* curve_node = NULL;
2473 for( const AnimationCurveNode* node : curves ) {
2474 ai_assert( node );
2475
2476 if ( node->TargetProperty().empty() ) {
2477 FBXImporter::LogWarn( "target property for animation curve not set: " + node->Name() );
2478 continue;
2479 }
2480
2481 curve_node = node;
2482 if ( node->Curves().empty() ) {
2483 FBXImporter::LogWarn( "no animation curves assigned to AnimationCurveNode: " + node->Name() );
2484 continue;
2485 }
2486
2487 node_property_map[ node->TargetProperty() ].push_back( node );
2488 }
2489
2490 ai_assert( curve_node );
2491 ai_assert( curve_node->TargetAsModel() );
2492
2493 const Model& target = *curve_node->TargetAsModel();
2494
2495 // check for all possible transformation components
2496 NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ];
2497
2498 bool has_any = false;
2499 bool has_complex = false;
2500
2501 for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) {
2502 const TransformationComp comp = static_cast<TransformationComp>( i );
2503
2504 // inverse pivots don't exist in the input, we just generate them
2505 if ( comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse ) {
2506 chain[ i ] = node_property_map.end();
2507 continue;
2508 }
2509
2510 chain[ i ] = node_property_map.find( NameTransformationCompProperty( comp ) );
2511 if ( chain[ i ] != node_property_map.end() ) {
2512
2513 // check if this curves contains redundant information by looking
2514 // up the corresponding node's transformation chain.
2515 if ( doc.Settings().optimizeEmptyAnimationCurves &&
2516 IsRedundantAnimationData( target, comp, ( *chain[ i ] ).second ) ) {
2517
2518 FBXImporter::LogDebug( "dropping redundant animation channel for node " + target.Name() );
2519 continue;
2520 }
2521
2522 has_any = true;
2523
2524 if ( comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation &&
2525 comp != TransformationComp_GeometricScaling && comp != TransformationComp_GeometricRotation && comp != TransformationComp_GeometricTranslation )
2526 {
2527 has_complex = true;
2528 }
2529 }
2530 }
2531
2532 if ( !has_any ) {
2533 FBXImporter::LogWarn( "ignoring node animation, did not find any transformation key frames" );
2534 return;
2535 }
2536
2537 // this needs to play nicely with GenerateTransformationNodeChain() which will
2538 // be invoked _later_ (animations come first). If this node has only rotation,
2539 // scaling and translation _and_ there are no animated other components either,
2540 // we can use a single node and also a single node animation channel.
2541 if ( !has_complex && !NeedsComplexTransformationChain( target ) ) {
2542
2543 aiNodeAnim* const nd = GenerateSimpleNodeAnim( fixed_name, target, chain,
2544 node_property_map.end(),
2545 layer_map,
2546 start, stop,
2547 max_time,
2548 min_time,
2549 true // input is TRS order, assimp is SRT
2550 );
2551
2552 ai_assert( nd );
2553 if ( nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0 ) {
2554 delete nd;
2555 }
2556 else {
2557 node_anims.push_back( nd );
2558 }
2559 return;
2560 }
2561
2562 // otherwise, things get gruesome and we need separate animation channels
2563 // for each part of the transformation chain. Remember which channels
2564 // we generated and pass this information to the node conversion
2565 // code to avoid nodes that have identity transform, but non-identity
2566 // animations, being dropped.
2567 unsigned int flags = 0, bit = 0x1;
2568 for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) {
2569 const TransformationComp comp = static_cast<TransformationComp>( i );
2570
2571 if ( chain[ i ] != node_property_map.end() ) {
2572 flags |= bit;
2573
2574 ai_assert( comp != TransformationComp_RotationPivotInverse );
2575 ai_assert( comp != TransformationComp_ScalingPivotInverse );
2576
2577 const std::string& chain_name = NameTransformationChainNode( fixed_name, comp );
2578
2579 aiNodeAnim* na = nullptr;
2580 switch ( comp )
2581 {
2582 case TransformationComp_Rotation:
2583 case TransformationComp_PreRotation:
2584 case TransformationComp_PostRotation:
2585 case TransformationComp_GeometricRotation:
2586 na = GenerateRotationNodeAnim( chain_name,
2587 target,
2588 ( *chain[ i ] ).second,
2589 layer_map,
2590 start, stop,
2591 max_time,
2592 min_time );
2593
2594 break;
2595
2596 case TransformationComp_RotationOffset:
2597 case TransformationComp_RotationPivot:
2598 case TransformationComp_ScalingOffset:
2599 case TransformationComp_ScalingPivot:
2600 case TransformationComp_Translation:
2601 case TransformationComp_GeometricTranslation:
2602 na = GenerateTranslationNodeAnim( chain_name,
2603 target,
2604 ( *chain[ i ] ).second,
2605 layer_map,
2606 start, stop,
2607 max_time,
2608 min_time );
2609
2610 // pivoting requires us to generate an implicit inverse channel to undo the pivot translation
2611 if ( comp == TransformationComp_RotationPivot ) {
2612 const std::string& invName = NameTransformationChainNode( fixed_name,
2613 TransformationComp_RotationPivotInverse );
2614
2615 aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName,
2616 target,
2617 ( *chain[ i ] ).second,
2618 layer_map,
2619 start, stop,
2620 max_time,
2621 min_time,
2622 true );
2623
2624 ai_assert( inv );
2625 if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) {
2626 delete inv;
2627 }
2628 else {
2629 node_anims.push_back( inv );
2630 }
2631
2632 ai_assert( TransformationComp_RotationPivotInverse > i );
2633 flags |= bit << ( TransformationComp_RotationPivotInverse - i );
2634 }
2635 else if ( comp == TransformationComp_ScalingPivot ) {
2636 const std::string& invName = NameTransformationChainNode( fixed_name,
2637 TransformationComp_ScalingPivotInverse );
2638
2639 aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName,
2640 target,
2641 ( *chain[ i ] ).second,
2642 layer_map,
2643 start, stop,
2644 max_time,
2645 min_time,
2646 true );
2647
2648 ai_assert( inv );
2649 if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) {
2650 delete inv;
2651 }
2652 else {
2653 node_anims.push_back( inv );
2654 }
2655
2656 ai_assert( TransformationComp_RotationPivotInverse > i );
2657 flags |= bit << ( TransformationComp_RotationPivotInverse - i );
2658 }
2659
2660 break;
2661
2662 case TransformationComp_Scaling:
2663 case TransformationComp_GeometricScaling:
2664 na = GenerateScalingNodeAnim( chain_name,
2665 target,
2666 ( *chain[ i ] ).second,
2667 layer_map,
2668 start, stop,
2669 max_time,
2670 min_time );
2671
2672 break;
2673
2674 default:
2675 ai_assert( false );
2676 }
2677
2678 ai_assert( na );
2679 if ( na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0 ) {
2680 delete na;
2681 }
2682 else {
2683 node_anims.push_back( na );
2684 }
2685 continue;
2686 }
2687 }
2688
2689 node_anim_chain_bits[ fixed_name ] = flags;
2690 }
2691
IsRedundantAnimationData(const Model & target,TransformationComp comp,const std::vector<const AnimationCurveNode * > & curves)2692 bool Converter::IsRedundantAnimationData( const Model& target,
2693 TransformationComp comp,
2694 const std::vector<const AnimationCurveNode*>& curves )
2695 {
2696 ai_assert( curves.size() );
2697
2698 // look for animation nodes with
2699 // * sub channels for all relevant components set
2700 // * one key/value pair per component
2701 // * combined values match up the corresponding value in the bind pose node transformation
2702 // only such nodes are 'redundant' for this function.
2703
2704 if ( curves.size() > 1 ) {
2705 return false;
2706 }
2707
2708 const AnimationCurveNode& nd = *curves.front();
2709 const AnimationCurveMap& sub_curves = nd.Curves();
2710
2711 const AnimationCurveMap::const_iterator dx = sub_curves.find( "d|X" );
2712 const AnimationCurveMap::const_iterator dy = sub_curves.find( "d|Y" );
2713 const AnimationCurveMap::const_iterator dz = sub_curves.find( "d|Z" );
2714
2715 if ( dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end() ) {
2716 return false;
2717 }
2718
2719 const KeyValueList& vx = ( *dx ).second->GetValues();
2720 const KeyValueList& vy = ( *dy ).second->GetValues();
2721 const KeyValueList& vz = ( *dz ).second->GetValues();
2722
2723 if ( vx.size() != 1 || vy.size() != 1 || vz.size() != 1 ) {
2724 return false;
2725 }
2726
2727 const aiVector3D dyn_val = aiVector3D( vx[ 0 ], vy[ 0 ], vz[ 0 ] );
2728 const aiVector3D& static_val = PropertyGet<aiVector3D>( target.Props(),
2729 NameTransformationCompProperty( comp ),
2730 TransformationCompDefaultValue( comp )
2731 );
2732
2733 const float epsilon = 1e-6f;
2734 return ( dyn_val - static_val ).SquareLength() < epsilon;
2735 }
2736
2737
GenerateRotationNodeAnim(const std::string & name,const Model & target,const std::vector<const AnimationCurveNode * > & curves,const LayerMap & layer_map,int64_t start,int64_t stop,double & max_time,double & min_time)2738 aiNodeAnim* Converter::GenerateRotationNodeAnim( const std::string& name,
2739 const Model& target,
2740 const std::vector<const AnimationCurveNode*>& curves,
2741 const LayerMap& layer_map,
2742 int64_t start, int64_t stop,
2743 double& max_time,
2744 double& min_time )
2745 {
2746 std::unique_ptr<aiNodeAnim> na( new aiNodeAnim() );
2747 na->mNodeName.Set( name );
2748
2749 ConvertRotationKeys( na.get(), curves, layer_map, start, stop, max_time, min_time, target.RotationOrder() );
2750
2751 // dummy scaling key
2752 na->mScalingKeys = new aiVectorKey[ 1 ];
2753 na->mNumScalingKeys = 1;
2754
2755 na->mScalingKeys[ 0 ].mTime = 0.;
2756 na->mScalingKeys[ 0 ].mValue = aiVector3D( 1.0f, 1.0f, 1.0f );
2757
2758 // dummy position key
2759 na->mPositionKeys = new aiVectorKey[ 1 ];
2760 na->mNumPositionKeys = 1;
2761
2762 na->mPositionKeys[ 0 ].mTime = 0.;
2763 na->mPositionKeys[ 0 ].mValue = aiVector3D();
2764
2765 return na.release();
2766 }
2767
GenerateScalingNodeAnim(const std::string & name,const Model &,const std::vector<const AnimationCurveNode * > & curves,const LayerMap & layer_map,int64_t start,int64_t stop,double & max_time,double & min_time)2768 aiNodeAnim* Converter::GenerateScalingNodeAnim( const std::string& name,
2769 const Model& /*target*/,
2770 const std::vector<const AnimationCurveNode*>& curves,
2771 const LayerMap& layer_map,
2772 int64_t start, int64_t stop,
2773 double& max_time,
2774 double& min_time )
2775 {
2776 std::unique_ptr<aiNodeAnim> na( new aiNodeAnim() );
2777 na->mNodeName.Set( name );
2778
2779 ConvertScaleKeys( na.get(), curves, layer_map, start, stop, max_time, min_time );
2780
2781 // dummy rotation key
2782 na->mRotationKeys = new aiQuatKey[ 1 ];
2783 na->mNumRotationKeys = 1;
2784
2785 na->mRotationKeys[ 0 ].mTime = 0.;
2786 na->mRotationKeys[ 0 ].mValue = aiQuaternion();
2787
2788 // dummy position key
2789 na->mPositionKeys = new aiVectorKey[ 1 ];
2790 na->mNumPositionKeys = 1;
2791
2792 na->mPositionKeys[ 0 ].mTime = 0.;
2793 na->mPositionKeys[ 0 ].mValue = aiVector3D();
2794
2795 return na.release();
2796 }
2797
2798
GenerateTranslationNodeAnim(const std::string & name,const Model &,const std::vector<const AnimationCurveNode * > & curves,const LayerMap & layer_map,int64_t start,int64_t stop,double & max_time,double & min_time,bool inverse)2799 aiNodeAnim* Converter::GenerateTranslationNodeAnim( const std::string& name,
2800 const Model& /*target*/,
2801 const std::vector<const AnimationCurveNode*>& curves,
2802 const LayerMap& layer_map,
2803 int64_t start, int64_t stop,
2804 double& max_time,
2805 double& min_time,
2806 bool inverse )
2807 {
2808 std::unique_ptr<aiNodeAnim> na( new aiNodeAnim() );
2809 na->mNodeName.Set( name );
2810
2811 ConvertTranslationKeys( na.get(), curves, layer_map, start, stop, max_time, min_time );
2812
2813 if ( inverse ) {
2814 for ( unsigned int i = 0; i < na->mNumPositionKeys; ++i ) {
2815 na->mPositionKeys[ i ].mValue *= -1.0f;
2816 }
2817 }
2818
2819 // dummy scaling key
2820 na->mScalingKeys = new aiVectorKey[ 1 ];
2821 na->mNumScalingKeys = 1;
2822
2823 na->mScalingKeys[ 0 ].mTime = 0.;
2824 na->mScalingKeys[ 0 ].mValue = aiVector3D( 1.0f, 1.0f, 1.0f );
2825
2826 // dummy rotation key
2827 na->mRotationKeys = new aiQuatKey[ 1 ];
2828 na->mNumRotationKeys = 1;
2829
2830 na->mRotationKeys[ 0 ].mTime = 0.;
2831 na->mRotationKeys[ 0 ].mValue = aiQuaternion();
2832
2833 return na.release();
2834 }
2835
GenerateSimpleNodeAnim(const std::string & name,const Model & target,NodeMap::const_iterator chain[TransformationComp_MAXIMUM],NodeMap::const_iterator iter_end,const LayerMap & layer_map,int64_t start,int64_t stop,double & max_time,double & min_time,bool reverse_order)2836 aiNodeAnim* Converter::GenerateSimpleNodeAnim( const std::string& name,
2837 const Model& target,
2838 NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ],
2839 NodeMap::const_iterator iter_end,
2840 const LayerMap& layer_map,
2841 int64_t start, int64_t stop,
2842 double& max_time,
2843 double& min_time,
2844 bool reverse_order )
2845
2846 {
2847 std::unique_ptr<aiNodeAnim> na( new aiNodeAnim() );
2848 na->mNodeName.Set( name );
2849
2850 const PropertyTable& props = target.Props();
2851
2852 // need to convert from TRS order to SRT?
2853 if ( reverse_order ) {
2854
2855 aiVector3D def_scale = PropertyGet( props, "Lcl Scaling", aiVector3D( 1.f, 1.f, 1.f ) );
2856 aiVector3D def_translate = PropertyGet( props, "Lcl Translation", aiVector3D( 0.f, 0.f, 0.f ) );
2857 aiVector3D def_rot = PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) );
2858
2859 KeyFrameListList scaling;
2860 KeyFrameListList translation;
2861 KeyFrameListList rotation;
2862
2863 if ( chain[ TransformationComp_Scaling ] != iter_end ) {
2864 scaling = GetKeyframeList( ( *chain[ TransformationComp_Scaling ] ).second, start, stop );
2865 }
2866
2867 if ( chain[ TransformationComp_Translation ] != iter_end ) {
2868 translation = GetKeyframeList( ( *chain[ TransformationComp_Translation ] ).second, start, stop );
2869 }
2870
2871 if ( chain[ TransformationComp_Rotation ] != iter_end ) {
2872 rotation = GetKeyframeList( ( *chain[ TransformationComp_Rotation ] ).second, start, stop );
2873 }
2874
2875 KeyFrameListList joined;
2876 joined.insert( joined.end(), scaling.begin(), scaling.end() );
2877 joined.insert( joined.end(), translation.begin(), translation.end() );
2878 joined.insert( joined.end(), rotation.begin(), rotation.end() );
2879
2880 const KeyTimeList& times = GetKeyTimeList( joined );
2881
2882 aiQuatKey* out_quat = new aiQuatKey[ times.size() ];
2883 aiVectorKey* out_scale = new aiVectorKey[ times.size() ];
2884 aiVectorKey* out_translation = new aiVectorKey[ times.size() ];
2885
2886 if ( times.size() )
2887 {
2888 ConvertTransformOrder_TRStoSRT( out_quat, out_scale, out_translation,
2889 scaling,
2890 translation,
2891 rotation,
2892 times,
2893 max_time,
2894 min_time,
2895 target.RotationOrder(),
2896 def_scale,
2897 def_translate,
2898 def_rot );
2899 }
2900
2901 // XXX remove duplicates / redundant keys which this operation did
2902 // likely produce if not all three channels were equally dense.
2903
2904 na->mNumScalingKeys = static_cast<unsigned int>( times.size() );
2905 na->mNumRotationKeys = na->mNumScalingKeys;
2906 na->mNumPositionKeys = na->mNumScalingKeys;
2907
2908 na->mScalingKeys = out_scale;
2909 na->mRotationKeys = out_quat;
2910 na->mPositionKeys = out_translation;
2911 }
2912 else {
2913
2914 // if a particular transformation is not given, grab it from
2915 // the corresponding node to meet the semantics of aiNodeAnim,
2916 // which requires all of rotation, scaling and translation
2917 // to be set.
2918 if ( chain[ TransformationComp_Scaling ] != iter_end ) {
2919 ConvertScaleKeys( na.get(), ( *chain[ TransformationComp_Scaling ] ).second,
2920 layer_map,
2921 start, stop,
2922 max_time,
2923 min_time );
2924 }
2925 else {
2926 na->mScalingKeys = new aiVectorKey[ 1 ];
2927 na->mNumScalingKeys = 1;
2928
2929 na->mScalingKeys[ 0 ].mTime = 0.;
2930 na->mScalingKeys[ 0 ].mValue = PropertyGet( props, "Lcl Scaling",
2931 aiVector3D( 1.f, 1.f, 1.f ) );
2932 }
2933
2934 if ( chain[ TransformationComp_Rotation ] != iter_end ) {
2935 ConvertRotationKeys( na.get(), ( *chain[ TransformationComp_Rotation ] ).second,
2936 layer_map,
2937 start, stop,
2938 max_time,
2939 min_time,
2940 target.RotationOrder() );
2941 }
2942 else {
2943 na->mRotationKeys = new aiQuatKey[ 1 ];
2944 na->mNumRotationKeys = 1;
2945
2946 na->mRotationKeys[ 0 ].mTime = 0.;
2947 na->mRotationKeys[ 0 ].mValue = EulerToQuaternion(
2948 PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) ),
2949 target.RotationOrder() );
2950 }
2951
2952 if ( chain[ TransformationComp_Translation ] != iter_end ) {
2953 ConvertTranslationKeys( na.get(), ( *chain[ TransformationComp_Translation ] ).second,
2954 layer_map,
2955 start, stop,
2956 max_time,
2957 min_time );
2958 }
2959 else {
2960 na->mPositionKeys = new aiVectorKey[ 1 ];
2961 na->mNumPositionKeys = 1;
2962
2963 na->mPositionKeys[ 0 ].mTime = 0.;
2964 na->mPositionKeys[ 0 ].mValue = PropertyGet( props, "Lcl Translation",
2965 aiVector3D( 0.f, 0.f, 0.f ) );
2966 }
2967
2968 }
2969 return na.release();
2970 }
2971
GetKeyframeList(const std::vector<const AnimationCurveNode * > & nodes,int64_t start,int64_t stop)2972 Converter::KeyFrameListList Converter::GetKeyframeList( const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop )
2973 {
2974 KeyFrameListList inputs;
2975 inputs.reserve( nodes.size() * 3 );
2976
2977 //give some breathing room for rounding errors
2978 int64_t adj_start = start - 10000;
2979 int64_t adj_stop = stop + 10000;
2980
2981 for( const AnimationCurveNode* node : nodes ) {
2982 ai_assert( node );
2983
2984 const AnimationCurveMap& curves = node->Curves();
2985 for( const AnimationCurveMap::value_type& kv : curves ) {
2986
2987 unsigned int mapto;
2988 if ( kv.first == "d|X" ) {
2989 mapto = 0;
2990 }
2991 else if ( kv.first == "d|Y" ) {
2992 mapto = 1;
2993 }
2994 else if ( kv.first == "d|Z" ) {
2995 mapto = 2;
2996 }
2997 else {
2998 FBXImporter::LogWarn( "ignoring scale animation curve, did not recognize target component" );
2999 continue;
3000 }
3001
3002 const AnimationCurve* const curve = kv.second;
3003 ai_assert( curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size() );
3004
3005 //get values within the start/stop time window
3006 std::shared_ptr<KeyTimeList> Keys( new KeyTimeList() );
3007 std::shared_ptr<KeyValueList> Values( new KeyValueList() );
3008 const size_t count = curve->GetKeys().size();
3009 Keys->reserve( count );
3010 Values->reserve( count );
3011 for (size_t n = 0; n < count; n++ )
3012 {
3013 int64_t k = curve->GetKeys().at( n );
3014 if ( k >= adj_start && k <= adj_stop )
3015 {
3016 Keys->push_back( k );
3017 Values->push_back( curve->GetValues().at( n ) );
3018 }
3019 }
3020
3021 inputs.push_back( std::make_tuple( Keys, Values, mapto ) );
3022 }
3023 }
3024 return inputs; // pray for NRVO :-)
3025 }
3026
3027
GetKeyTimeList(const KeyFrameListList & inputs)3028 KeyTimeList Converter::GetKeyTimeList( const KeyFrameListList& inputs )
3029 {
3030 ai_assert( inputs.size() );
3031
3032 // reserve some space upfront - it is likely that the keyframe lists
3033 // have matching time values, so max(of all keyframe lists) should
3034 // be a good estimate.
3035 KeyTimeList keys;
3036
3037 size_t estimate = 0;
3038 for( const KeyFrameList& kfl : inputs ) {
3039 estimate = std::max( estimate, std::get<0>(kfl)->size() );
3040 }
3041
3042 keys.reserve( estimate );
3043
3044 std::vector<unsigned int> next_pos;
3045 next_pos.resize( inputs.size(), 0 );
3046
3047 const size_t count = inputs.size();
3048 while ( true ) {
3049
3050 int64_t min_tick = std::numeric_limits<int64_t>::max();
3051 for ( size_t i = 0; i < count; ++i ) {
3052 const KeyFrameList& kfl = inputs[ i ];
3053
3054 if ( std::get<0>(kfl)->size() > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) < min_tick ) {
3055 min_tick = std::get<0>(kfl)->at( next_pos[ i ] );
3056 }
3057 }
3058
3059 if ( min_tick == std::numeric_limits<int64_t>::max() ) {
3060 break;
3061 }
3062 keys.push_back( min_tick );
3063
3064 for ( size_t i = 0; i < count; ++i ) {
3065 const KeyFrameList& kfl = inputs[ i ];
3066
3067
3068 while ( std::get<0>(kfl)->size() > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) == min_tick ) {
3069 ++next_pos[ i ];
3070 }
3071 }
3072 }
3073
3074 return keys;
3075 }
3076
InterpolateKeys(aiVectorKey * valOut,const KeyTimeList & keys,const KeyFrameListList & inputs,const aiVector3D & def_value,double & max_time,double & min_time)3077 void Converter::InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
3078 const aiVector3D& def_value,
3079 double& max_time,
3080 double& min_time )
3081
3082 {
3083 ai_assert( keys.size() );
3084 ai_assert( valOut );
3085
3086 std::vector<unsigned int> next_pos;
3087 const size_t count = inputs.size();
3088
3089 next_pos.resize( inputs.size(), 0 );
3090
3091 for( KeyTimeList::value_type time : keys ) {
3092 ai_real result[ 3 ] = { def_value.x, def_value.y, def_value.z };
3093
3094 for ( size_t i = 0; i < count; ++i ) {
3095 const KeyFrameList& kfl = inputs[ i ];
3096
3097 const size_t ksize = std::get<0>(kfl)->size();
3098 if ( ksize > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) == time ) {
3099 ++next_pos[ i ];
3100 }
3101
3102 const size_t id0 = next_pos[ i ]>0 ? next_pos[ i ] - 1 : 0;
3103 const size_t id1 = next_pos[ i ] == ksize ? ksize - 1 : next_pos[ i ];
3104
3105 // use lerp for interpolation
3106 const KeyValueList::value_type valueA = std::get<1>(kfl)->at( id0 );
3107 const KeyValueList::value_type valueB = std::get<1>(kfl)->at( id1 );
3108
3109 const KeyTimeList::value_type timeA = std::get<0>(kfl)->at( id0 );
3110 const KeyTimeList::value_type timeB = std::get<0>(kfl)->at( id1 );
3111
3112 const ai_real factor = timeB == timeA ? ai_real(0.) : static_cast<ai_real>( ( time - timeA ) ) / ( timeB - timeA );
3113 const ai_real interpValue = static_cast<ai_real>( valueA + ( valueB - valueA ) * factor );
3114
3115 result[ std::get<2>(kfl) ] = interpValue;
3116 }
3117
3118 // magic value to convert fbx times to seconds
3119 valOut->mTime = CONVERT_FBX_TIME( time ) * anim_fps;
3120
3121 min_time = std::min( min_time, valOut->mTime );
3122 max_time = std::max( max_time, valOut->mTime );
3123
3124 valOut->mValue.x = result[ 0 ];
3125 valOut->mValue.y = result[ 1 ];
3126 valOut->mValue.z = result[ 2 ];
3127
3128 ++valOut;
3129 }
3130 }
3131
InterpolateKeys(aiQuatKey * valOut,const KeyTimeList & keys,const KeyFrameListList & inputs,const aiVector3D & def_value,double & maxTime,double & minTime,Model::RotOrder order)3132 void Converter::InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
3133 const aiVector3D& def_value,
3134 double& maxTime,
3135 double& minTime,
3136 Model::RotOrder order )
3137 {
3138 ai_assert( keys.size() );
3139 ai_assert( valOut );
3140
3141 std::unique_ptr<aiVectorKey[]> temp( new aiVectorKey[ keys.size() ] );
3142 InterpolateKeys( temp.get(), keys, inputs, def_value, maxTime, minTime );
3143
3144 aiMatrix4x4 m;
3145
3146 aiQuaternion lastq;
3147
3148 for ( size_t i = 0, c = keys.size(); i < c; ++i ) {
3149
3150 valOut[ i ].mTime = temp[ i ].mTime;
3151
3152 GetRotationMatrix( order, temp[ i ].mValue, m );
3153 aiQuaternion quat = aiQuaternion( aiMatrix3x3( m ) );
3154
3155 // take shortest path by checking the inner product
3156 // http://www.3dkingdoms.com/weekly/weekly.php?a=36
3157 if ( quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0 )
3158 {
3159 quat.x = -quat.x;
3160 quat.y = -quat.y;
3161 quat.z = -quat.z;
3162 quat.w = -quat.w;
3163 }
3164 lastq = quat;
3165
3166 valOut[ i ].mValue = quat;
3167 }
3168 }
3169
ConvertTransformOrder_TRStoSRT(aiQuatKey * out_quat,aiVectorKey * out_scale,aiVectorKey * out_translation,const KeyFrameListList & scaling,const KeyFrameListList & translation,const KeyFrameListList & rotation,const KeyTimeList & times,double & maxTime,double & minTime,Model::RotOrder order,const aiVector3D & def_scale,const aiVector3D & def_translate,const aiVector3D & def_rotation)3170 void Converter::ConvertTransformOrder_TRStoSRT( aiQuatKey* out_quat, aiVectorKey* out_scale,
3171 aiVectorKey* out_translation,
3172 const KeyFrameListList& scaling,
3173 const KeyFrameListList& translation,
3174 const KeyFrameListList& rotation,
3175 const KeyTimeList& times,
3176 double& maxTime,
3177 double& minTime,
3178 Model::RotOrder order,
3179 const aiVector3D& def_scale,
3180 const aiVector3D& def_translate,
3181 const aiVector3D& def_rotation )
3182 {
3183 if ( rotation.size() ) {
3184 InterpolateKeys( out_quat, times, rotation, def_rotation, maxTime, minTime, order );
3185 }
3186 else {
3187 for ( size_t i = 0; i < times.size(); ++i ) {
3188 out_quat[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps;
3189 out_quat[ i ].mValue = EulerToQuaternion( def_rotation, order );
3190 }
3191 }
3192
3193 if ( scaling.size() ) {
3194 InterpolateKeys( out_scale, times, scaling, def_scale, maxTime, minTime );
3195 }
3196 else {
3197 for ( size_t i = 0; i < times.size(); ++i ) {
3198 out_scale[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps;
3199 out_scale[ i ].mValue = def_scale;
3200 }
3201 }
3202
3203 if ( translation.size() ) {
3204 InterpolateKeys( out_translation, times, translation, def_translate, maxTime, minTime );
3205 }
3206 else {
3207 for ( size_t i = 0; i < times.size(); ++i ) {
3208 out_translation[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps;
3209 out_translation[ i ].mValue = def_translate;
3210 }
3211 }
3212
3213 const size_t count = times.size();
3214 for ( size_t i = 0; i < count; ++i ) {
3215 aiQuaternion& r = out_quat[ i ].mValue;
3216 aiVector3D& s = out_scale[ i ].mValue;
3217 aiVector3D& t = out_translation[ i ].mValue;
3218
3219 aiMatrix4x4 mat, temp;
3220 aiMatrix4x4::Translation( t, mat );
3221 mat *= aiMatrix4x4( r.GetMatrix() );
3222 mat *= aiMatrix4x4::Scaling( s, temp );
3223
3224 mat.Decompose( s, r, t );
3225 }
3226 }
3227
EulerToQuaternion(const aiVector3D & rot,Model::RotOrder order)3228 aiQuaternion Converter::EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order )
3229 {
3230 aiMatrix4x4 m;
3231 GetRotationMatrix( order, rot, m );
3232
3233 return aiQuaternion( aiMatrix3x3( m ) );
3234 }
3235
ConvertScaleKeys(aiNodeAnim * na,const std::vector<const AnimationCurveNode * > & nodes,const LayerMap &,int64_t start,int64_t stop,double & maxTime,double & minTime)3236 void Converter::ConvertScaleKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& /*layers*/,
3237 int64_t start, int64_t stop,
3238 double& maxTime,
3239 double& minTime )
3240 {
3241 ai_assert( nodes.size() );
3242
3243 // XXX for now, assume scale should be blended geometrically (i.e. two
3244 // layers should be multiplied with each other). There is a FBX
3245 // property in the layer to specify the behaviour, though.
3246
3247 const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop );
3248 const KeyTimeList& keys = GetKeyTimeList( inputs );
3249
3250 na->mNumScalingKeys = static_cast<unsigned int>( keys.size() );
3251 na->mScalingKeys = new aiVectorKey[ keys.size() ];
3252 if ( keys.size() > 0 )
3253 InterpolateKeys( na->mScalingKeys, keys, inputs, aiVector3D( 1.0f, 1.0f, 1.0f ), maxTime, minTime );
3254 }
3255
ConvertTranslationKeys(aiNodeAnim * na,const std::vector<const AnimationCurveNode * > & nodes,const LayerMap &,int64_t start,int64_t stop,double & maxTime,double & minTime)3256 void Converter::ConvertTranslationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
3257 const LayerMap& /*layers*/,
3258 int64_t start, int64_t stop,
3259 double& maxTime,
3260 double& minTime )
3261 {
3262 ai_assert( nodes.size() );
3263
3264 // XXX see notes in ConvertScaleKeys()
3265 const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop );
3266 const KeyTimeList& keys = GetKeyTimeList( inputs );
3267
3268 na->mNumPositionKeys = static_cast<unsigned int>( keys.size() );
3269 na->mPositionKeys = new aiVectorKey[ keys.size() ];
3270 if ( keys.size() > 0 )
3271 InterpolateKeys( na->mPositionKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime );
3272 }
3273
ConvertRotationKeys(aiNodeAnim * na,const std::vector<const AnimationCurveNode * > & nodes,const LayerMap &,int64_t start,int64_t stop,double & maxTime,double & minTime,Model::RotOrder order)3274 void Converter::ConvertRotationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
3275 const LayerMap& /*layers*/,
3276 int64_t start, int64_t stop,
3277 double& maxTime,
3278 double& minTime,
3279 Model::RotOrder order )
3280 {
3281 ai_assert( nodes.size() );
3282
3283 // XXX see notes in ConvertScaleKeys()
3284 const std::vector< KeyFrameList >& inputs = GetKeyframeList( nodes, start, stop );
3285 const KeyTimeList& keys = GetKeyTimeList( inputs );
3286
3287 na->mNumRotationKeys = static_cast<unsigned int>( keys.size() );
3288 na->mRotationKeys = new aiQuatKey[ keys.size() ];
3289 if ( keys.size() > 0 )
3290 InterpolateKeys( na->mRotationKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime, order );
3291 }
3292
TransferDataToScene()3293 void Converter::TransferDataToScene()
3294 {
3295 ai_assert( !out->mMeshes );
3296 ai_assert( !out->mNumMeshes );
3297
3298 // note: the trailing () ensures initialization with NULL - not
3299 // many C++ users seem to know this, so pointing it out to avoid
3300 // confusion why this code works.
3301
3302 if ( meshes.size() ) {
3303 out->mMeshes = new aiMesh*[ meshes.size() ]();
3304 out->mNumMeshes = static_cast<unsigned int>( meshes.size() );
3305
3306 std::swap_ranges( meshes.begin(), meshes.end(), out->mMeshes );
3307 }
3308
3309 if ( materials.size() ) {
3310 out->mMaterials = new aiMaterial*[ materials.size() ]();
3311 out->mNumMaterials = static_cast<unsigned int>( materials.size() );
3312
3313 std::swap_ranges( materials.begin(), materials.end(), out->mMaterials );
3314 }
3315
3316 if ( animations.size() ) {
3317 out->mAnimations = new aiAnimation*[ animations.size() ]();
3318 out->mNumAnimations = static_cast<unsigned int>( animations.size() );
3319
3320 std::swap_ranges( animations.begin(), animations.end(), out->mAnimations );
3321 }
3322
3323 if ( lights.size() ) {
3324 out->mLights = new aiLight*[ lights.size() ]();
3325 out->mNumLights = static_cast<unsigned int>( lights.size() );
3326
3327 std::swap_ranges( lights.begin(), lights.end(), out->mLights );
3328 }
3329
3330 if ( cameras.size() ) {
3331 out->mCameras = new aiCamera*[ cameras.size() ]();
3332 out->mNumCameras = static_cast<unsigned int>( cameras.size() );
3333
3334 std::swap_ranges( cameras.begin(), cameras.end(), out->mCameras );
3335 }
3336
3337 if ( textures.size() ) {
3338 out->mTextures = new aiTexture*[ textures.size() ]();
3339 out->mNumTextures = static_cast<unsigned int>( textures.size() );
3340
3341 std::swap_ranges( textures.begin(), textures.end(), out->mTextures );
3342 }
3343 }
3344
3345 //} // !anon
3346
3347 // ------------------------------------------------------------------------------------------------
ConvertToAssimpScene(aiScene * out,const Document & doc)3348 void ConvertToAssimpScene(aiScene* out, const Document& doc)
3349 {
3350 Converter converter(out,doc);
3351 }
3352
3353 } // !FBX
3354 } // !Assimp
3355
3356 #endif
3357