1/*
2Open Asset Import Library (assimp)
3----------------------------------------------------------------------
4
5Copyright (c) 2006-2017, assimp team
6
7All rights reserved.
8
9Redistribution and use of this software in source and binary forms,
10with or without modification, are permitted provided that the
11following 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
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF 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
64namespace Assimp {
65namespace FBX {
66
67using 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 */
78class Converter
79{
80public:
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
104public:
105 Converter( aiScene* out, const Document& doc );
106 ~Converter();
107
108private:
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
403private:
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
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
453Converter::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
494Converter::~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
504void 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
514void 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
624void 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
635void 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
646void 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
724void 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
742const 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
783const 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
823aiVector3D 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
830void 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
920bool 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
943std::string Converter::NameTransformationChainNode( const std::string& name, TransformationComp comp )
944{
945 return name + std::string( MAGIC_NODE_TAG ) + "_" + NameTransformationComp( comp );
946}
947
948void 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
1089void 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
1126void 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
1153std::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
1188aiMesh* 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
1207unsigned 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
1334std::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
1354unsigned 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
1529void 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
1634void 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
1675void 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
1697unsigned 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
1719unsigned 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
1754unsigned 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
1782void 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
1930void 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
2056void 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
2071void 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
2086aiColor3D 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
2112void 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
2172double 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
2225void 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
2238void 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
2282std::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.