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