#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
#include "FBXConverter.h"
#include "FBXParser.h"
#include "FBXMeshGeometry.h"
#include "FBXDocument.h"
#include "FBXUtil.h"
#include "FBXProperties.h"
#include "FBXImporter.h"
#include "StringComparison.h"
#include <assimp/scene.h>
#include <tuple>
#include <memory>
#include <iterator>
#include <vector>
namespace Assimp {
namespace FBX {
using namespace Util;
#define MAGIC_NODE_TAG "_$AssimpFbx$"
#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
class Converter
{
public:
enum TransformationComp
{
TransformationComp_Translation = 0,
TransformationComp_RotationOffset,
TransformationComp_RotationPivot,
TransformationComp_PreRotation,
TransformationComp_Rotation,
TransformationComp_PostRotation,
TransformationComp_RotationPivotInverse,
TransformationComp_ScalingOffset,
TransformationComp_ScalingPivot,
TransformationComp_Scaling,
TransformationComp_ScalingPivotInverse,
TransformationComp_GeometricTranslation,
TransformationComp_GeometricRotation,
TransformationComp_GeometricScaling,
TransformationComp_MAXIMUM
};
public:
Converter( aiScene* out, const Document& doc );
~Converter();
private:
void ConvertRootNode();
void ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4() );
void ConvertLights( const Model& model );
void ConvertCameras( const Model& model );
void ConvertLight( const Model& model, const Light& light );
void ConvertCamera( const Model& model, const Camera& cam );
const char* NameTransformationComp( TransformationComp comp );
const char* NameTransformationCompProperty( TransformationComp comp );
aiVector3D TransformationCompDefaultValue( TransformationComp comp );
void GetRotationMatrix( Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out );
bool NeedsComplexTransformationChain( const Model& model );
std::string NameTransformationChainNode( const std::string& name, TransformationComp comp );
void GenerateTransformationNodeChain( const Model& model, std::vector<aiNode*>& output_nodes );
void SetupNodeMetadata( const Model& model, aiNode& nd );
void ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform );
std::vector<unsigned int> ConvertMesh( const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform );
aiMesh* SetupEmptyMesh( const MeshGeometry& mesh );
unsigned int ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform );
std::vector<unsigned int> ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform );
unsigned int ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
MatIndexArray::value_type index,
const aiMatrix4x4& node_global_transform );
static const unsigned int NO_MATERIAL_SEPARATION =
static_cast<unsigned int>(-1);
void ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo,
const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
unsigned int materialIndex = NO_MATERIAL_SEPARATION,
std::vector<unsigned int>* outputVertStartIndices = NULL );
void 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 );
void ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo,
MatIndexArray::value_type materialIndex );
unsigned int GetDefaultMaterial();
unsigned int ConvertMaterial( const Material& material, const MeshGeometry* const mesh );
unsigned int ConvertVideo( const Video& video );
void TrySetTextureProperties( aiMaterial* out_mat, const TextureMap& textures,
const std::string& propName,
aiTextureType target, const MeshGeometry* const mesh );
void TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
const std::string& propName,
aiTextureType target, const MeshGeometry* const mesh );
void SetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh );
void SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh );
aiColor3D GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName,
bool& result );
void SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props );
static double FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0 );
void ConvertAnimations();
void RenameNode( const std::string& fixed_name, const std::string& new_name );
std::string FixNodeName( const std::string& name );
typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
void ConvertAnimationStack( const AnimationStack& st );
void 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 );
bool IsRedundantAnimationData( const Model& target,
TransformationComp comp,
const std::vector<const AnimationCurveNode*>& curves );
aiNodeAnim* 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 );
aiNodeAnim* 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 );
aiNodeAnim* 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 = false );
aiNodeAnim* 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 = false );
typedef std::tuple<std::shared_ptr<KeyTimeList>, std::shared_ptr<KeyValueList>, unsigned int > KeyFrameList;
typedef std::vector<KeyFrameList> KeyFrameListList;
KeyFrameListList GetKeyframeList( const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop );
KeyTimeList GetKeyTimeList( const KeyFrameListList& inputs );
void InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
const aiVector3D& def_value,
double& max_time,
double& min_time );
void InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
const aiVector3D& def_value,
double& maxTime,
double& minTime,
Model::RotOrder order );
void 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 );
aiQuaternion EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order );
void ConvertScaleKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& ,
int64_t start, int64_t stop,
double& maxTime,
double& minTime );
void ConvertTranslationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
const LayerMap& ,
int64_t start, int64_t stop,
double& maxTime,
double& minTime );
void ConvertRotationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
const LayerMap& ,
int64_t start, int64_t stop,
double& maxTime,
double& minTime,
Model::RotOrder order );
void TransferDataToScene();
private:
unsigned int defaultMaterialIndex;
std::vector<aiMesh*> meshes;
std::vector<aiMaterial*> materials;
std::vector<aiAnimation*> animations;
std::vector<aiLight*> lights;
std::vector<aiCamera*> cameras;
std::vector<aiTexture*> textures;
typedef std::map<const Material*, unsigned int> MaterialMap;
MaterialMap materials_converted;
typedef std::map<const Video*, unsigned int> VideoMap;
VideoMap textures_converted;
typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
MeshMap meshes_converted;
typedef std::map<std::string, unsigned int> NodeAnimBitMap;
NodeAnimBitMap node_anim_chain_bits;
typedef std::map<std::string, bool> NodeNameMap;
NodeNameMap node_names;
typedef std::map<std::string, std::string> NameNameMap;
NameNameMap renamed_nodes;
double anim_fps;
aiScene* const out;
const FBX::Document& doc;
bool FindTextureIndexByFilename(const Video& video, unsigned int& index) {
index = 0;
const char* videoFileName = video.FileName().c_str();
for (auto texture = textures_converted.begin(); texture != textures_converted.end(); ++texture)
{
if (!strcmp(texture->first->FileName().c_str(), videoFileName)) {
return true;
}
index++;
}
return false;
}
};
Converter::Converter( aiScene* out, const Document& doc )
: defaultMaterialIndex()
, out( out )
, doc( doc )
{
ConvertAnimations();
ConvertRootNode();
if ( doc.Settings().readAllMaterials ) {
for( const ObjectMap::value_type& v : doc.Objects() ) {
const Object* ob = v.second->Get();
if ( !ob ) {
continue;
}
const Material* mat = dynamic_cast<const Material*>( ob );
if ( mat ) {
if ( materials_converted.find( mat ) == materials_converted.end() ) {
ConvertMaterial( *mat, 0 );
}
}
}
}
TransferDataToScene();
if ( out->mNumMeshes == 0 ) {
out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
}
}
Converter::~Converter()
{
std::for_each( meshes.begin(), meshes.end(), Util::delete_fun<aiMesh>() );
std::for_each( materials.begin(), materials.end(), Util::delete_fun<aiMaterial>() );
std::for_each( animations.begin(), animations.end(), Util::delete_fun<aiAnimation>() );
std::for_each( lights.begin(), lights.end(), Util::delete_fun<aiLight>() );
std::for_each( cameras.begin(), cameras.end(), Util::delete_fun<aiCamera>() );
std::for_each( textures.begin(), textures.end(), Util::delete_fun<aiTexture>() );
}
void Converter::ConvertRootNode()
{
out->mRootNode = new aiNode();
out->mRootNode->mName.Set( "RootNode" );
ConvertNodes( 0L, *out->mRootNode );
}
void Converter::ConvertNodes( uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform )
{
const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced( id, "Model" );
std::vector<aiNode*> nodes;
nodes.reserve( conns.size() );
std::vector<aiNode*> nodes_chain;
try {
for( const Connection* con : conns ) {
if ( con->PropertyName().length() ) {
continue;
}
const Object* const object = con->SourceObject();
if ( !object ) {
FBXImporter::LogWarn( "failed to convert source object for Model link" );
continue;
}
const Model* const model = dynamic_cast<const Model*>( object );
if ( model ) {
nodes_chain.clear();
aiMatrix4x4 new_abs_transform = parent_transform;
GenerateTransformationNodeChain( *model, nodes_chain );
ai_assert( nodes_chain.size() );
const std::string& original_name = FixNodeName( model->Name() );
aiNode* name_carrier = NULL;
for( aiNode* prenode : nodes_chain ) {
if ( !strcmp( prenode->mName.C_Str(), original_name.c_str() ) ) {
name_carrier = prenode;
break;
}
}
if ( !name_carrier ) {
nodes_chain.push_back( new aiNode( original_name ) );
name_carrier = nodes_chain.back();
}
SetupNodeMetadata( *model, *nodes_chain.back() );
aiNode* last_parent = &parent;
for( aiNode* prenode : nodes_chain ) {
ai_assert( prenode );
if ( last_parent != &parent ) {
last_parent->mNumChildren = 1;
last_parent->mChildren = new aiNode*[ 1 ];
last_parent->mChildren[ 0 ] = prenode;
}
prenode->mParent = last_parent;
last_parent = prenode;
new_abs_transform *= prenode->mTransformation;
}
ConvertModel( *model, *nodes_chain.back(), new_abs_transform );
ConvertNodes( model->ID(), *nodes_chain.back(), new_abs_transform );
if ( doc.Settings().readLights ) {
ConvertLights( *model );
}
if ( doc.Settings().readCameras ) {
ConvertCameras( *model );
}
nodes.push_back( nodes_chain.front() );
nodes_chain.clear();
}
}
if ( nodes.size() ) {
parent.mChildren = new aiNode*[ nodes.size() ]();
parent.mNumChildren = static_cast<unsigned int>( nodes.size() );
std::swap_ranges( nodes.begin(), nodes.end(), parent.mChildren );
}
}
catch ( std::exception& ) {
Util::delete_fun<aiNode> deleter;
std::for_each( nodes.begin(), nodes.end(), deleter );
std::for_each( nodes_chain.begin(), nodes_chain.end(), deleter );
}
}
void Converter::ConvertLights( const Model& model )
{
const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
for( const NodeAttribute* attr : node_attrs ) {
const Light* const light = dynamic_cast<const Light*>( attr );
if ( light ) {
ConvertLight( model, *light );
}
}
}
void Converter::ConvertCameras( const Model& model )
{
const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
for( const NodeAttribute* attr : node_attrs ) {
const Camera* const cam = dynamic_cast<const Camera*>( attr );
if ( cam ) {
ConvertCamera( model, *cam );
}
}
}
void Converter::ConvertLight( const Model& model, const Light& light )
{
lights.push_back( new aiLight() );
aiLight* const out_light = lights.back();
out_light->mName.Set( FixNodeName( model.Name() ) );
const float intensity = light.Intensity() / 100.0f;
const aiVector3D& col = light.Color();
out_light->mColorDiffuse = aiColor3D( col.x, col.y, col.z );
out_light->mColorDiffuse.r *= intensity;
out_light->mColorDiffuse.g *= intensity;
out_light->mColorDiffuse.b *= intensity;
out_light->mColorSpecular = out_light->mColorDiffuse;
out_light->mPosition = aiVector3D(0.0f);
out_light->mDirection = aiVector3D(0.0f, -1.0f, 0.0f);
out_light->mUp = aiVector3D(0.0f, 0.0f, -1.0f);
switch ( light.LightType() )
{
case Light::Type_Point:
out_light->mType = aiLightSource_POINT;
break;
case Light::Type_Directional:
out_light->mType = aiLightSource_DIRECTIONAL;
break;
case Light::Type_Spot:
out_light->mType = aiLightSource_SPOT;
out_light->mAngleOuterCone = AI_DEG_TO_RAD( light.OuterAngle() );
out_light->mAngleInnerCone = AI_DEG_TO_RAD( light.InnerAngle() );
break;
case Light::Type_Area:
FBXImporter::LogWarn( "cannot represent area light, set to UNDEFINED" );
out_light->mType = aiLightSource_UNDEFINED;
break;
case Light::Type_Volume:
FBXImporter::LogWarn( "cannot represent volume light, set to UNDEFINED" );
out_light->mType = aiLightSource_UNDEFINED;
break;
default:
ai_assert( false );
}
float decay = light.DecayStart();
switch ( light.DecayType() )
{
case Light::Decay_None:
out_light->mAttenuationConstant = decay;
out_light->mAttenuationLinear = 0.0f;
out_light->mAttenuationQuadratic = 0.0f;
break;
case Light::Decay_Linear:
out_light->mAttenuationConstant = 0.0f;
out_light->mAttenuationLinear = 2.0f / decay;
out_light->mAttenuationQuadratic = 0.0f;
break;
case Light::Decay_Quadratic:
out_light->mAttenuationConstant = 0.0f;
out_light->mAttenuationLinear = 0.0f;
out_light->mAttenuationQuadratic = 2.0f / (decay * decay);
break;
case Light::Decay_Cubic:
FBXImporter::LogWarn( "cannot represent cubic attenuation, set to Quadratic" );
out_light->mAttenuationQuadratic = 1.0f;
break;
default:
ai_assert( false );
}
}
void Converter::ConvertCamera( const Model& model, const Camera& cam )
{
cameras.push_back( new aiCamera() );
aiCamera* const out_camera = cameras.back();
out_camera->mName.Set( FixNodeName( model.Name() ) );
out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
out_camera->mPosition = aiVector3D(0.0f);
out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f);
out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
out_camera->mHorizontalFOV = AI_DEG_TO_RAD( cam.FieldOfView() );
out_camera->mClipPlaneNear = cam.NearPlane();
out_camera->mClipPlaneFar = cam.FarPlane();
}
const char* Converter::NameTransformationComp( TransformationComp comp )
{
switch ( comp )
{
case TransformationComp_Translation:
return "Translation";
case TransformationComp_RotationOffset:
return "RotationOffset";
case TransformationComp_RotationPivot:
return "RotationPivot";
case TransformationComp_PreRotation:
return "PreRotation";
case TransformationComp_Rotation:
return "Rotation";
case TransformationComp_PostRotation:
return "PostRotation";
case TransformationComp_RotationPivotInverse:
return "RotationPivotInverse";
case TransformationComp_ScalingOffset:
return "ScalingOffset";
case TransformationComp_ScalingPivot:
return "ScalingPivot";
case TransformationComp_Scaling:
return "Scaling";
case TransformationComp_ScalingPivotInverse:
return "ScalingPivotInverse";
case TransformationComp_GeometricScaling:
return "GeometricScaling";
case TransformationComp_GeometricRotation:
return "GeometricRotation";
case TransformationComp_GeometricTranslation:
return "GeometricTranslation";
case TransformationComp_MAXIMUM: default:
break;
}
ai_assert( false );
return NULL;
}
const char* Converter::NameTransformationCompProperty( TransformationComp comp )
{
switch ( comp )
{
case TransformationComp_Translation:
return "Lcl Translation";
case TransformationComp_RotationOffset:
return "RotationOffset";
case TransformationComp_RotationPivot:
return "RotationPivot";
case TransformationComp_PreRotation:
return "PreRotation";
case TransformationComp_Rotation:
return "Lcl Rotation";
case TransformationComp_PostRotation:
return "PostRotation";
case TransformationComp_RotationPivotInverse:
return "RotationPivotInverse";
case TransformationComp_ScalingOffset:
return "ScalingOffset";
case TransformationComp_ScalingPivot:
return "ScalingPivot";
case TransformationComp_Scaling:
return "Lcl Scaling";
case TransformationComp_ScalingPivotInverse:
return "ScalingPivotInverse";
case TransformationComp_GeometricScaling:
return "GeometricScaling";
case TransformationComp_GeometricRotation:
return "GeometricRotation";
case TransformationComp_GeometricTranslation:
return "GeometricTranslation";
case TransformationComp_MAXIMUM: break;
}
ai_assert( false );
return NULL;
}
aiVector3D Converter::TransformationCompDefaultValue( TransformationComp comp )
{
return comp == TransformationComp_Scaling ? aiVector3D( 1.f, 1.f, 1.f ) : aiVector3D();
}
void Converter::GetRotationMatrix( Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out )
{
if ( mode == Model::RotOrder_SphericXYZ ) {
FBXImporter::LogError( "Unsupported RotationMode: SphericXYZ" );
out = aiMatrix4x4();
return;
}
const float angle_epsilon = 1e-6f;
out = aiMatrix4x4();
bool is_id[ 3 ] = { true, true, true };
aiMatrix4x4 temp[ 3 ];
if ( std::fabs( rotation.z ) > angle_epsilon ) {
aiMatrix4x4::RotationZ( AI_DEG_TO_RAD( rotation.z ), temp[ 2 ] );
is_id[ 2 ] = false;
}
if ( std::fabs( rotation.y ) > angle_epsilon ) {
aiMatrix4x4::RotationY( AI_DEG_TO_RAD( rotation.y ), temp[ 1 ] );
is_id[ 1 ] = false;
}
if ( std::fabs( rotation.x ) > angle_epsilon ) {
aiMatrix4x4::RotationX( AI_DEG_TO_RAD( rotation.x ), temp[ 0 ] );
is_id[ 0 ] = false;
}
int order[ 3 ] = { -1, -1, -1 };
switch ( mode )
{
case Model::RotOrder_EulerXYZ:
order[ 0 ] = 2;
order[ 1 ] = 1;
order[ 2 ] = 0;
break;
case Model::RotOrder_EulerXZY:
order[ 0 ] = 1;
order[ 1 ] = 2;
order[ 2 ] = 0;
break;
case Model::RotOrder_EulerYZX:
order[ 0 ] = 0;
order[ 1 ] = 2;
order[ 2 ] = 1;
break;
case Model::RotOrder_EulerYXZ:
order[ 0 ] = 2;
order[ 1 ] = 0;
order[ 2 ] = 1;
break;
case Model::RotOrder_EulerZXY:
order[ 0 ] = 1;
order[ 1 ] = 0;
order[ 2 ] = 2;
break;
case Model::RotOrder_EulerZYX:
order[ 0 ] = 0;
order[ 1 ] = 1;
order[ 2 ] = 2;
break;
default:
ai_assert( false );
}
ai_assert( ( order[ 0 ] >= 0 ) && ( order[ 0 ] <= 2 ) );
ai_assert( ( order[ 1 ] >= 0 ) && ( order[ 1 ] <= 2 ) );
ai_assert( ( order[ 2 ] >= 0 ) && ( order[ 2 ] <= 2 ) );
if ( !is_id[ order[ 0 ] ] ) {
out = temp[ order[ 0 ] ];
}
if ( !is_id[ order[ 1 ] ] ) {
out = out * temp[ order[ 1 ] ];
}
if ( !is_id[ order[ 2 ] ] ) {
out = out * temp[ order[ 2 ] ];
}
}
bool Converter::NeedsComplexTransformationChain( const Model& model )
{
const PropertyTable& props = model.Props();
bool ok;
const float zero_epsilon = 1e-6f;
for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) {
const TransformationComp comp = static_cast< TransformationComp >( i );
if ( comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation ||
comp == TransformationComp_GeometricScaling || comp == TransformationComp_GeometricRotation || comp == TransformationComp_GeometricTranslation ) {
continue;
}
const aiVector3D& v = PropertyGet<aiVector3D>( props, NameTransformationCompProperty( comp ), ok );
if ( ok && v.SquareLength() > zero_epsilon ) {
return true;
}
}
return false;
}
std::string Converter::NameTransformationChainNode( const std::string& name, TransformationComp comp )
{
return name + std::string( MAGIC_NODE_TAG ) + "_" + NameTransformationComp( comp );
}
void Converter::GenerateTransformationNodeChain( const Model& model,
std::vector<aiNode*>& output_nodes )
{
const PropertyTable& props = model.Props();
const Model::RotOrder rot = model.RotationOrder();
bool ok;
aiMatrix4x4 chain[ TransformationComp_MAXIMUM ];
std::fill_n( chain, static_cast<unsigned int>( TransformationComp_MAXIMUM ), aiMatrix4x4() );
const float zero_epsilon = 1e-6f;
bool is_complex = false;
const aiVector3D& PreRotation = PropertyGet<aiVector3D>( props, "PreRotation", ok );
if ( ok && PreRotation.SquareLength() > zero_epsilon ) {
is_complex = true;
GetRotationMatrix( rot, PreRotation, chain[ TransformationComp_PreRotation ] );
}
const aiVector3D& PostRotation = PropertyGet<aiVector3D>( props, "PostRotation", ok );
if ( ok && PostRotation.SquareLength() > zero_epsilon ) {
is_complex = true;
GetRotationMatrix( rot, PostRotation, chain[ TransformationComp_PostRotation ] );
}
const aiVector3D& RotationPivot = PropertyGet<aiVector3D>( props, "RotationPivot", ok );
if ( ok && RotationPivot.SquareLength() > zero_epsilon ) {
is_complex = true;
aiMatrix4x4::Translation( RotationPivot, chain[ TransformationComp_RotationPivot ] );
aiMatrix4x4::Translation( -RotationPivot, chain[ TransformationComp_RotationPivotInverse ] );
}
const aiVector3D& RotationOffset = PropertyGet<aiVector3D>( props, "RotationOffset", ok );
if ( ok && RotationOffset.SquareLength() > zero_epsilon ) {
is_complex = true;
aiMatrix4x4::Translation( RotationOffset, chain[ TransformationComp_RotationOffset ] );
}
const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>( props, "ScalingOffset", ok );
if ( ok && ScalingOffset.SquareLength() > zero_epsilon ) {
is_complex = true;
aiMatrix4x4::Translation( ScalingOffset, chain[ TransformationComp_ScalingOffset ] );
}
const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>( props, "ScalingPivot", ok );
if ( ok && ScalingPivot.SquareLength() > zero_epsilon ) {
is_complex = true;
aiMatrix4x4::Translation( ScalingPivot, chain[ TransformationComp_ScalingPivot ] );
aiMatrix4x4::Translation( -ScalingPivot, chain[ TransformationComp_ScalingPivotInverse ] );
}
const aiVector3D& Translation = PropertyGet<aiVector3D>( props, "Lcl Translation", ok );
if ( ok && Translation.SquareLength() > zero_epsilon ) {
aiMatrix4x4::Translation( Translation, chain[ TransformationComp_Translation ] );
}
const aiVector3D& Scaling = PropertyGet<aiVector3D>( props, "Lcl Scaling", ok );
if ( ok && std::fabs( Scaling.SquareLength() - 1.0f ) > zero_epsilon ) {
aiMatrix4x4::Scaling( Scaling, chain[ TransformationComp_Scaling ] );
}
const aiVector3D& Rotation = PropertyGet<aiVector3D>( props, "Lcl Rotation", ok );
if ( ok && Rotation.SquareLength() > zero_epsilon ) {
GetRotationMatrix( rot, Rotation, chain[ TransformationComp_Rotation ] );
}
const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>( props, "GeometricScaling", ok );
if ( ok && std::fabs( GeometricScaling.SquareLength() - 1.0f ) > zero_epsilon ) {
aiMatrix4x4::Scaling( GeometricScaling, chain[ TransformationComp_GeometricScaling ] );
}
const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>( props, "GeometricRotation", ok );
if ( ok && GeometricRotation.SquareLength() > zero_epsilon ) {
GetRotationMatrix( rot, GeometricRotation, chain[ TransformationComp_GeometricRotation ] );
}
const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>( props, "GeometricTranslation", ok );
if ( ok && GeometricTranslation.SquareLength() > zero_epsilon ) {
aiMatrix4x4::Translation( GeometricTranslation, chain[ TransformationComp_GeometricTranslation ] );
}
ai_assert( NeedsComplexTransformationChain( model ) == is_complex );
const std::string& name = FixNodeName( model.Name() );
if ( is_complex && doc.Settings().preservePivots ) {
FBXImporter::LogInfo( "generating full transformation chain for node: " + name );
NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find( name );
const unsigned int anim_chain_bitmask = ( it == node_anim_chain_bits.end() ? 0 : ( *it ).second );
unsigned int bit = 0x1;
for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) {
const TransformationComp comp = static_cast<TransformationComp>( i );
if ( chain[ i ].IsIdentity() && ( anim_chain_bitmask & bit ) == 0 ) {
continue;
}
aiNode* nd = new aiNode();
output_nodes.push_back( nd );
nd->mName.Set( NameTransformationChainNode( name, comp ) );
nd->mTransformation = chain[ i ];
}
ai_assert( output_nodes.size() );
return;
}
aiNode* nd = new aiNode();
output_nodes.push_back( nd );
nd->mName.Set( name );
for (const auto &transform : chain) {
nd->mTransformation = nd->mTransformation * transform;
}
}
void Converter::SetupNodeMetadata( const Model& model, aiNode& nd )
{
const PropertyTable& props = model.Props();
DirectPropertyMap unparsedProperties = props.GetUnparsedProperties();
std::size_t numStaticMetaData = 2;
aiMetadata* data = aiMetadata::Alloc( static_cast<unsigned int>(unparsedProperties.size() + numStaticMetaData) );
nd.mMetaData = data;
int index = 0;
data->Set( index++, "UserProperties", aiString( PropertyGet<std::string>( props, "UDP3DSMAX", "" ) ) );
data->Set( index++, "IsNull", model.IsNull() ? true : false );
for( const DirectPropertyMap::value_type& prop : unparsedProperties ) {
if ( const TypedProperty<bool>* interpreted = prop.second->As<TypedProperty<bool> >() ) {
data->Set( index++, prop.first, interpreted->Value() );
} else if ( const TypedProperty<int>* interpreted = prop.second->As<TypedProperty<int> >() ) {
data->Set( index++, prop.first, interpreted->Value() );
} else if ( const TypedProperty<uint64_t>* interpreted = prop.second->As<TypedProperty<uint64_t> >() ) {
data->Set( index++, prop.first, interpreted->Value() );
} else if ( const TypedProperty<float>* interpreted = prop.second->As<TypedProperty<float> >() ) {
data->Set( index++, prop.first, interpreted->Value() );
} else if ( const TypedProperty<std::string>* interpreted = prop.second->As<TypedProperty<std::string> >() ) {
data->Set( index++, prop.first, aiString( interpreted->Value() ) );
} else if ( const TypedProperty<aiVector3D>* interpreted = prop.second->As<TypedProperty<aiVector3D> >() ) {
data->Set( index++, prop.first, interpreted->Value() );
} else {
ai_assert( false );
}
}
}
void Converter::ConvertModel( const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform )
{
const std::vector<const Geometry*>& geos = model.GetGeometry();
std::vector<unsigned int> meshes;
meshes.reserve( geos.size() );
for( const Geometry* geo : geos ) {
const MeshGeometry* const mesh = dynamic_cast< const MeshGeometry* >( geo );
if ( mesh ) {
const std::vector<unsigned int>& indices = ConvertMesh( *mesh, model, node_global_transform );
std::copy( indices.begin(), indices.end(), std::back_inserter( meshes ) );
}
else {
FBXImporter::LogWarn( "ignoring unrecognized geometry: " + geo->Name() );
}
}
if ( meshes.size() ) {
nd.mMeshes = new unsigned int[ meshes.size() ]();
nd.mNumMeshes = static_cast< unsigned int >( meshes.size() );
std::swap_ranges( meshes.begin(), meshes.end(), nd.mMeshes );
}
}
std::vector<unsigned int> Converter::ConvertMesh( const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform )
{
std::vector<unsigned int> temp;
MeshMap::const_iterator it = meshes_converted.find( &mesh );
if ( it != meshes_converted.end() ) {
std::copy( ( *it ).second.begin(), ( *it ).second.end(), std::back_inserter( temp ) );
return temp;
}
const std::vector<aiVector3D>& vertices = mesh.GetVertices();
const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
if ( vertices.empty() || faces.empty() ) {
FBXImporter::LogWarn( "ignoring empty geometry: " + mesh.Name() );
return temp;
}
const MatIndexArray& mindices = mesh.GetMaterialIndices();
if ( doc.Settings().readMaterials && !mindices.empty() ) {
const MatIndexArray::value_type base = mindices[ 0 ];
for( MatIndexArray::value_type index : mindices ) {
if ( index != base ) {
return ConvertMeshMultiMaterial( mesh, model, node_global_transform );
}
}
}
temp.push_back( ConvertMeshSingleMaterial( mesh, model, node_global_transform ) );
return temp;
}
aiMesh* Converter::SetupEmptyMesh( const MeshGeometry& mesh )
{
aiMesh* const out_mesh = new aiMesh();
meshes.push_back( out_mesh );
meshes_converted[ &mesh ].push_back( static_cast<unsigned int>( meshes.size() - 1 ) );
std::string name = mesh.Name();
if ( name.substr( 0, 10 ) == "Geometry::" ) {
name = name.substr( 10 );
}
if ( name.length() ) {
out_mesh->mName.Set( name );
}
return out_mesh;
}
unsigned int Converter::ConvertMeshSingleMaterial( const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform )
{
const MatIndexArray& mindices = mesh.GetMaterialIndices();
aiMesh* const out_mesh = SetupEmptyMesh( mesh );
const std::vector<aiVector3D>& vertices = mesh.GetVertices();
const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
out_mesh->mNumVertices = static_cast<unsigned int>( vertices.size() );
out_mesh->mVertices = new aiVector3D[ vertices.size() ];
std::copy( vertices.begin(), vertices.end(), out_mesh->mVertices );
out_mesh->mNumFaces = static_cast<unsigned int>( faces.size() );
aiFace* fac = out_mesh->mFaces = new aiFace[ faces.size() ]();
unsigned int cursor = 0;
for( unsigned int pcount : faces ) {
aiFace& f = *fac++;
f.mNumIndices = pcount;
f.mIndices = new unsigned int[ pcount ];
switch ( pcount )
{
case 1:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
break;
case 2:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
break;
case 3:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
break;
default:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
break;
}
for ( unsigned int i = 0; i < pcount; ++i ) {
f.mIndices[ i ] = cursor++;
}
}
const std::vector<aiVector3D>& normals = mesh.GetNormals();
if ( normals.size() ) {
ai_assert( normals.size() == vertices.size() );
out_mesh->mNormals = new aiVector3D[ vertices.size() ];
std::copy( normals.begin(), normals.end(), out_mesh->mNormals );
}
const std::vector<aiVector3D>& tangents = mesh.GetTangents();
const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
if ( tangents.size() ) {
std::vector<aiVector3D> tempBinormals;
if ( !binormals->size() ) {
if ( normals.size() ) {
tempBinormals.resize( normals.size() );
for ( unsigned int i = 0; i < tangents.size(); ++i ) {
tempBinormals[ i ] = normals[ i ] ^ tangents[ i ];
}
binormals = &tempBinormals;
}
else {
binormals = NULL;
}
}
if ( binormals ) {
ai_assert( tangents.size() == vertices.size() );
ai_assert( binormals->size() == vertices.size() );
out_mesh->mTangents = new aiVector3D[ vertices.size() ];
std::copy( tangents.begin(), tangents.end(), out_mesh->mTangents );
out_mesh->mBitangents = new aiVector3D[ vertices.size() ];
std::copy( binormals->begin(), binormals->end(), out_mesh->mBitangents );
}
}
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
if ( uvs.empty() ) {
break;
}
aiVector3D* out_uv = out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ];
for( const aiVector2D& v : uvs ) {
*out_uv++ = aiVector3D( v.x, v.y, 0.0f );
}
out_mesh->mNumUVComponents[ i ] = 2;
}
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i ) {
const std::vector<aiColor4D>& colors = mesh.GetVertexColors( i );
if ( colors.empty() ) {
break;
}
out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ];
std::copy( colors.begin(), colors.end(), out_mesh->mColors[ i ] );
}
if ( !doc.Settings().readMaterials || mindices.empty() ) {
FBXImporter::LogError( "no material assigned to mesh, setting default material" );
out_mesh->mMaterialIndex = GetDefaultMaterial();
}
else {
ConvertMaterialForMesh( out_mesh, model, mesh, mindices[ 0 ] );
}
if ( doc.Settings().readWeights && mesh.DeformerSkin() != NULL ) {
ConvertWeights( out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION );
}
return static_cast<unsigned int>( meshes.size() - 1 );
}
std::vector<unsigned int> Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform )
{
const MatIndexArray& mindices = mesh.GetMaterialIndices();
ai_assert( mindices.size() );
std::set<MatIndexArray::value_type> had;
std::vector<unsigned int> indices;
for( MatIndexArray::value_type index : mindices ) {
if ( had.find( index ) == had.end() ) {
indices.push_back( ConvertMeshMultiMaterial( mesh, model, index, node_global_transform ) );
had.insert( index );
}
}
return indices;
}
unsigned int Converter::ConvertMeshMultiMaterial( const MeshGeometry& mesh, const Model& model,
MatIndexArray::value_type index,
const aiMatrix4x4& node_global_transform )
{
aiMesh* const out_mesh = SetupEmptyMesh( mesh );
const MatIndexArray& mindices = mesh.GetMaterialIndices();
const std::vector<aiVector3D>& vertices = mesh.GetVertices();
const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL;
unsigned int count_faces = 0;
unsigned int count_vertices = 0;
std::vector<unsigned int>::const_iterator itf = faces.begin();
for ( MatIndexArray::const_iterator it = mindices.begin(),
end = mindices.end(); it != end; ++it, ++itf )
{
if ( ( *it ) != index ) {
continue;
}
++count_faces;
count_vertices += *itf;
}
ai_assert( count_faces );
ai_assert( count_vertices );
std::vector<unsigned int> reverseMapping;
if ( process_weights ) {
reverseMapping.resize( count_vertices );
}
out_mesh->mNumVertices = count_vertices;
out_mesh->mVertices = new aiVector3D[ count_vertices ];
out_mesh->mNumFaces = count_faces;
aiFace* fac = out_mesh->mFaces = new aiFace[ count_faces ]();
const std::vector<aiVector3D>& normals = mesh.GetNormals();
if ( normals.size() ) {
ai_assert( normals.size() == vertices.size() );
out_mesh->mNormals = new aiVector3D[ vertices.size() ];
}
const std::vector<aiVector3D>& tangents = mesh.GetTangents();
const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
std::vector<aiVector3D> tempBinormals;
if ( tangents.size() ) {
if ( !binormals->size() ) {
if ( normals.size() ) {
tempBinormals.resize( normals.size() );
for ( unsigned int i = 0; i < tangents.size(); ++i ) {
tempBinormals[ i ] = normals[ i ] ^ tangents[ i ];
}
binormals = &tempBinormals;
}
else {
binormals = NULL;
}
}
if ( binormals ) {
ai_assert( tangents.size() == vertices.size() && binormals->size() == vertices.size() );
out_mesh->mTangents = new aiVector3D[ vertices.size() ];
out_mesh->mBitangents = new aiVector3D[ vertices.size() ];
}
}
unsigned int num_uvs = 0;
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs ) {
const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
if ( uvs.empty() ) {
break;
}
out_mesh->mTextureCoords[ i ] = new aiVector3D[ vertices.size() ];
out_mesh->mNumUVComponents[ i ] = 2;
}
unsigned int num_vcs = 0;
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs ) {
const std::vector<aiColor4D>& colors = mesh.GetVertexColors( i );
if ( colors.empty() ) {
break;
}
out_mesh->mColors[ i ] = new aiColor4D[ vertices.size() ];
}
unsigned int cursor = 0, in_cursor = 0;
itf = faces.begin();
for ( MatIndexArray::const_iterator it = mindices.begin(),
end = mindices.end(); it != end; ++it, ++itf )
{
const unsigned int pcount = *itf;
if ( ( *it ) != index ) {
in_cursor += pcount;
continue;
}
aiFace& f = *fac++;
f.mNumIndices = pcount;
f.mIndices = new unsigned int[ pcount ];
switch ( pcount )
{
case 1:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
break;
case 2:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
break;
case 3:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
break;
default:
out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
break;
}
for ( unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor ) {
f.mIndices[ i ] = cursor;
if ( reverseMapping.size() ) {
reverseMapping[ cursor ] = in_cursor;
}
out_mesh->mVertices[ cursor ] = vertices[ in_cursor ];
if ( out_mesh->mNormals ) {
out_mesh->mNormals[ cursor ] = normals[ in_cursor ];
}
if ( out_mesh->mTangents ) {
out_mesh->mTangents[ cursor ] = tangents[ in_cursor ];
out_mesh->mBitangents[ cursor ] = ( *binormals )[ in_cursor ];
}
for ( unsigned int i = 0; i < num_uvs; ++i ) {
const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords( i );
out_mesh->mTextureCoords[ i ][ cursor ] = aiVector3D( uvs[ in_cursor ].x, uvs[ in_cursor ].y, 0.0f );
}
for ( unsigned int i = 0; i < num_vcs; ++i ) {
const std::vector<aiColor4D>& cols = mesh.GetVertexColors( i );
out_mesh->mColors[ i ][ cursor ] = cols[ in_cursor ];
}
}
}
ConvertMaterialForMesh( out_mesh, model, mesh, index );
if ( process_weights ) {
ConvertWeights( out_mesh, model, mesh, node_global_transform, index, &reverseMapping );
}
return static_cast<unsigned int>( meshes.size() - 1 );
}
void Converter::ConvertWeights( aiMesh* out, const Model& model, const MeshGeometry& geo,
const aiMatrix4x4& node_global_transform ,
unsigned int materialIndex,
std::vector<unsigned int>* outputVertStartIndices )
{
ai_assert( geo.DeformerSkin() );
std::vector<size_t> out_indices;
std::vector<size_t> index_out_indices;
std::vector<size_t> count_out_indices;
const Skin& sk = *geo.DeformerSkin();
std::vector<aiBone*> bones;
bones.reserve( sk.Clusters().size() );
const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
ai_assert( no_mat_check || outputVertStartIndices );
try {
for( const Cluster* cluster : sk.Clusters() ) {
ai_assert( cluster );
const WeightIndexArray& indices = cluster->GetIndices();
if ( indices.empty() ) {
continue;
}
const MatIndexArray& mats = geo.GetMaterialIndices();
bool ok = false;
const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
count_out_indices.clear();
index_out_indices.clear();
out_indices.clear();
for( WeightIndexArray::value_type index : indices ) {
unsigned int count = 0;
const unsigned int* const out_idx = geo.ToOutputVertexIndex( index, count );
ai_assert( out_idx != NULL );
index_out_indices.push_back( no_index_sentinel );
count_out_indices.push_back( 0 );
for ( unsigned int i = 0; i < count; ++i ) {
if ( no_mat_check || static_cast<size_t>( mats[ geo.FaceForVertexIndex( out_idx[ i ] ) ] ) == materialIndex ) {
if ( index_out_indices.back() == no_index_sentinel ) {
index_out_indices.back() = out_indices.size();
}
if ( no_mat_check ) {
out_indices.push_back( out_idx[ i ] );
}
else {
const std::vector<unsigned int>::iterator it = std::lower_bound(
outputVertStartIndices->begin(),
outputVertStartIndices->end(),
out_idx[ i ]
);
out_indices.push_back( std::distance( outputVertStartIndices->begin(), it ) );
}
++count_out_indices.back();
ok = true;
}
}
}
if ( ok ) {
ConvertCluster( bones, model, *cluster, out_indices, index_out_indices,
count_out_indices, node_global_transform );
}
}
}
catch ( std::exception& ) {
std::for_each( bones.begin(), bones.end(), Util::delete_fun<aiBone>() );
throw;
}
if ( bones.empty() ) {
return;
}
out->mBones = new aiBone*[ bones.size() ]();
out->mNumBones = static_cast<unsigned int>( bones.size() );
std::swap_ranges( bones.begin(), bones.end(), out->mBones );
}
void Converter::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 )
{
aiBone* const bone = new aiBone();
bones.push_back( bone );
bone->mName = FixNodeName( cl.TargetNode()->Name() );
bone->mOffsetMatrix = cl.TransformLink();
bone->mOffsetMatrix.Inverse();
bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
bone->mNumWeights = static_cast<unsigned int>( out_indices.size() );
aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[ out_indices.size() ];
const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
const WeightArray& weights = cl.GetWeights();
const size_t c = index_out_indices.size();
for ( size_t i = 0; i < c; ++i ) {
const size_t index_index = index_out_indices[ i ];
if ( index_index == no_index_sentinel ) {
continue;
}
const size_t cc = count_out_indices[ i ];
for ( size_t j = 0; j < cc; ++j ) {
aiVertexWeight& out_weight = *cursor++;
out_weight.mVertexId = static_cast<unsigned int>( out_indices[ index_index + j ] );
out_weight.mWeight = weights[ i ];
}
}
}
void Converter::ConvertMaterialForMesh( aiMesh* out, const Model& model, const MeshGeometry& geo,
MatIndexArray::value_type materialIndex )
{
const std::vector<const Material*>& mats = model.GetMaterials();
if ( static_cast<unsigned int>( materialIndex ) >= mats.size() || materialIndex < 0 ) {
FBXImporter::LogError( "material index out of bounds, setting default material" );
out->mMaterialIndex = GetDefaultMaterial();
return;
}
const Material* const mat = mats[ materialIndex ];
MaterialMap::const_iterator it = materials_converted.find( mat );
if ( it != materials_converted.end() ) {
out->mMaterialIndex = ( *it ).second;
return;
}
out->mMaterialIndex = ConvertMaterial( *mat, &geo );
materials_converted[ mat ] = out->mMaterialIndex;
}
unsigned int Converter::GetDefaultMaterial()
{
if ( defaultMaterialIndex ) {
return defaultMaterialIndex - 1;
}
aiMaterial* out_mat = new aiMaterial();
materials.push_back( out_mat );
const aiColor3D diffuse = aiColor3D( 0.8f, 0.8f, 0.8f );
out_mat->AddProperty( &diffuse, 1, AI_MATKEY_COLOR_DIFFUSE );
aiString s;
s.Set( AI_DEFAULT_MATERIAL_NAME );
out_mat->AddProperty( &s, AI_MATKEY_NAME );
defaultMaterialIndex = static_cast< unsigned int >( materials.size() );
return defaultMaterialIndex - 1;
}
unsigned int Converter::ConvertMaterial( const Material& material, const MeshGeometry* const mesh )
{
const PropertyTable& props = material.Props();
aiMaterial* out_mat = new aiMaterial();
materials_converted[ &material ] = static_cast<unsigned int>( materials.size() );
materials.push_back( out_mat );
aiString str;
std::string name = material.Name();
if ( name.substr( 0, 10 ) == "Material::" ) {
name = name.substr( 10 );
}
if ( name.length() ) {
str.Set( name );
out_mat->AddProperty( &str, AI_MATKEY_NAME );
}
SetShadingPropertiesCommon( out_mat, props );
SetTextureProperties( out_mat, material.Textures(), mesh );
SetTextureProperties( out_mat, material.LayeredTextures(), mesh );
return static_cast<unsigned int>( materials.size() - 1 );
}
unsigned int Converter::ConvertVideo( const Video& video )
{
aiTexture* out_tex = new aiTexture();
textures.push_back( out_tex );
out_tex->mWidth = static_cast<unsigned int>( video.ContentLength() ); out_tex->mHeight = 0;
out_tex->pcData = reinterpret_cast<aiTexel*>( const_cast<Video&>( video ).RelinquishContent() );
const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName();
std::string ext = BaseImporter::GetExtension( filename );
if ( ext == "jpeg" ) {
ext = "jpg";
}
if ( ext.size() <= 3 ) {
memcpy( out_tex->achFormatHint, ext.c_str(), ext.size() );
}
return static_cast<unsigned int>( textures.size() - 1 );
}
void Converter::TrySetTextureProperties( aiMaterial* out_mat, const TextureMap& textures,
const std::string& propName,
aiTextureType target, const MeshGeometry* const mesh )
{
TextureMap::const_iterator it = textures.find( propName );
if ( it == textures.end() ) {
return;
}
const Texture* const tex = ( *it ).second;
if ( tex != 0 )
{
aiString path;
path.Set( tex->RelativeFilename() );
const Video* media = tex->Media();
if (media != 0) {
bool textureReady = false; unsigned int index;
VideoMap::const_iterator it = textures_converted.find(media);
if (it != textures_converted.end()) {
index = (*it).second;
textureReady = true;
}
else {
if (media->ContentLength() > 0) {
index = ConvertVideo(*media);
textures_converted[media] = index;
textureReady = true;
}
else if (doc.Settings().searchEmbeddedTextures) { textureReady = FindTextureIndexByFilename(*media, index);
}
}
if (textureReady) {
path.data[0] = '*';
path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index);
}
}
out_mat->AddProperty( &path, _AI_MATKEY_TEXTURE_BASE, target, 0 );
aiUVTransform uvTrafo;
uvTrafo.mScaling = tex->UVScaling();
uvTrafo.mTranslation = tex->UVTranslation();
out_mat->AddProperty( &uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0 );
const PropertyTable& props = tex->Props();
int uvIndex = 0;
bool ok;
const std::string& uvSet = PropertyGet<std::string>( props, "UVSet", ok );
if ( ok ) {
if ( uvSet != "default" && uvSet.length() ) {
const unsigned int matIndex = static_cast<unsigned int>( std::distance( materials.begin(),
std::find( materials.begin(), materials.end(), out_mat )
) );
uvIndex = -1;
if ( !mesh )
{
for( const MeshMap::value_type& v : meshes_converted ) {
const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> ( v.first );
if ( !mesh ) {
continue;
}
const MatIndexArray& mats = mesh->GetMaterialIndices();
if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) {
continue;
}
int index = -1;
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
if ( mesh->GetTextureCoords( i ).empty() ) {
break;
}
const std::string& name = mesh->GetTextureCoordChannelName( i );
if ( name == uvSet ) {
index = static_cast<int>( i );
break;
}
}
if ( index == -1 ) {
FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
continue;
}
if ( uvIndex == -1 ) {
uvIndex = index;
}
else {
FBXImporter::LogWarn( "the UV channel named " + uvSet +
" appears at different positions in meshes, results will be wrong" );
}
}
}
else
{
int index = -1;
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
if ( mesh->GetTextureCoords( i ).empty() ) {
break;
}
const std::string& name = mesh->GetTextureCoordChannelName( i );
if ( name == uvSet ) {
index = static_cast<int>( i );
break;
}
}
if ( index == -1 ) {
FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
}
if ( uvIndex == -1 ) {
uvIndex = index;
}
}
if ( uvIndex == -1 ) {
FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" );
uvIndex = 0;
}
}
}
out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0 );
}
}
void Converter::TrySetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
const std::string& propName,
aiTextureType target, const MeshGeometry* const mesh )
{
LayeredTextureMap::const_iterator it = layeredTextures.find( propName );
if ( it == layeredTextures.end() ) {
return;
}
int texCount = (*it).second->textureCount();
int blendmode= (*it).second->GetBlendMode();
out_mat->AddProperty(&blendmode,1,_AI_MATKEY_TEXOP_BASE,target,0);
for(int texIndex = 0; texIndex < texCount; texIndex++){
const Texture* const tex = ( *it ).second->getTexture(texIndex);
aiString path;
path.Set( tex->RelativeFilename() );
out_mat->AddProperty( &path, _AI_MATKEY_TEXTURE_BASE, target, texIndex );
aiUVTransform uvTrafo;
uvTrafo.mScaling = tex->UVScaling();
uvTrafo.mTranslation = tex->UVTranslation();
out_mat->AddProperty( &uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex );
const PropertyTable& props = tex->Props();
int uvIndex = 0;
bool ok;
const std::string& uvSet = PropertyGet<std::string>( props, "UVSet", ok );
if ( ok ) {
if ( uvSet != "default" && uvSet.length() ) {
const unsigned int matIndex = static_cast<unsigned int>( std::distance( materials.begin(),
std::find( materials.begin(), materials.end(), out_mat )
) );
uvIndex = -1;
if ( !mesh )
{
for( const MeshMap::value_type& v : meshes_converted ) {
const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*> ( v.first );
if ( !mesh ) {
continue;
}
const MatIndexArray& mats = mesh->GetMaterialIndices();
if ( std::find( mats.begin(), mats.end(), matIndex ) == mats.end() ) {
continue;
}
int index = -1;
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
if ( mesh->GetTextureCoords( i ).empty() ) {
break;
}
const std::string& name = mesh->GetTextureCoordChannelName( i );
if ( name == uvSet ) {
index = static_cast<int>( i );
break;
}
}
if ( index == -1 ) {
FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
continue;
}
if ( uvIndex == -1 ) {
uvIndex = index;
}
else {
FBXImporter::LogWarn( "the UV channel named " + uvSet +
" appears at different positions in meshes, results will be wrong" );
}
}
}
else
{
int index = -1;
for ( unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i ) {
if ( mesh->GetTextureCoords( i ).empty() ) {
break;
}
const std::string& name = mesh->GetTextureCoordChannelName( i );
if ( name == uvSet ) {
index = static_cast<int>( i );
break;
}
}
if ( index == -1 ) {
FBXImporter::LogWarn( "did not find UV channel named " + uvSet + " in a mesh using this material" );
}
if ( uvIndex == -1 ) {
uvIndex = index;
}
}
if ( uvIndex == -1 ) {
FBXImporter::LogWarn( "failed to resolve UV channel " + uvSet + ", using first UV channel" );
uvIndex = 0;
}
}
}
out_mat->AddProperty( &uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, texIndex );
}
}
void Converter::SetTextureProperties( aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh )
{
TrySetTextureProperties( out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE, mesh );
TrySetTextureProperties( out_mat, textures, "AmbientColor", aiTextureType_AMBIENT, mesh );
TrySetTextureProperties( out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE, mesh );
TrySetTextureProperties( out_mat, textures, "SpecularColor", aiTextureType_SPECULAR, mesh );
TrySetTextureProperties( out_mat, textures, "SpecularFactor", aiTextureType_SPECULAR, mesh);
TrySetTextureProperties( out_mat, textures, "TransparentColor", aiTextureType_OPACITY, mesh );
TrySetTextureProperties( out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION, mesh );
TrySetTextureProperties( out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh );
TrySetTextureProperties( out_mat, textures, "NormalMap", aiTextureType_NORMALS, mesh );
TrySetTextureProperties( out_mat, textures, "Bump", aiTextureType_HEIGHT, mesh );
TrySetTextureProperties( out_mat, textures, "ShininessExponent", aiTextureType_SHININESS, mesh );
}
void Converter::SetTextureProperties( aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh )
{
TrySetTextureProperties( out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "EmissiveColor", aiTextureType_EMISSIVE, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "SpecularColor", aiTextureType_SPECULAR, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "SpecularFactor", aiTextureType_SPECULAR, mesh);
TrySetTextureProperties( out_mat, layeredTextures, "TransparentColor", aiTextureType_OPACITY, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT, mesh );
TrySetTextureProperties( out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh );
}
aiColor3D Converter::GetColorPropertyFromMaterial( const PropertyTable& props, const std::string& baseName,
bool& result )
{
result = true;
bool ok;
const aiVector3D& Diffuse = PropertyGet<aiVector3D>( props, baseName, ok );
if ( ok ) {
return aiColor3D( Diffuse.x, Diffuse.y, Diffuse.z );
}
else {
aiVector3D DiffuseColor = PropertyGet<aiVector3D>( props, baseName + "Color", ok );
if ( ok ) {
float DiffuseFactor = PropertyGet<float>( props, baseName + "Factor", ok );
if ( ok ) {
DiffuseColor *= DiffuseFactor;
}
return aiColor3D( DiffuseColor.x, DiffuseColor.y, DiffuseColor.z );
}
}
result = false;
return aiColor3D( 0.0f, 0.0f, 0.0f );
}
void Converter::SetShadingPropertiesCommon( aiMaterial* out_mat, const PropertyTable& props )
{
bool ok;
const aiColor3D& Diffuse = GetColorPropertyFromMaterial( props, "Diffuse", ok );
if ( ok ) {
out_mat->AddProperty( &Diffuse, 1, AI_MATKEY_COLOR_DIFFUSE );
}
const aiColor3D& Emissive = GetColorPropertyFromMaterial( props, "Emissive", ok );
if ( ok ) {
out_mat->AddProperty( &Emissive, 1, AI_MATKEY_COLOR_EMISSIVE );
}
const aiColor3D& Ambient = GetColorPropertyFromMaterial( props, "Ambient", ok );
if ( ok ) {
out_mat->AddProperty( &Ambient, 1, AI_MATKEY_COLOR_AMBIENT );
}
const aiColor3D& Specular = GetColorPropertyFromMaterial( props, "Specular", ok );
if ( ok ) {
out_mat->AddProperty( &Specular, 1, AI_MATKEY_COLOR_SPECULAR );
}
const float Opacity = PropertyGet<float>( props, "Opacity", ok );
if ( ok ) {
out_mat->AddProperty( &Opacity, 1, AI_MATKEY_OPACITY );
}
const float Reflectivity = PropertyGet<float>( props, "Reflectivity", ok );
if ( ok ) {
out_mat->AddProperty( &Reflectivity, 1, AI_MATKEY_REFLECTIVITY );
}
const float Shininess = PropertyGet<float>( props, "Shininess", ok );
if ( ok ) {
out_mat->AddProperty( &Shininess, 1, AI_MATKEY_SHININESS_STRENGTH );
}
const float ShininessExponent = PropertyGet<float>( props, "ShininessExponent", ok );
if ( ok ) {
out_mat->AddProperty( &ShininessExponent, 1, AI_MATKEY_SHININESS );
}
const float BumpFactor = PropertyGet<float>(props, "BumpFactor", ok);
if (ok) {
out_mat->AddProperty(&BumpFactor, 1, AI_MATKEY_BUMPSCALING);
}
const float DispFactor = PropertyGet<float>(props, "DisplacementFactor", ok);
if (ok) {
out_mat->AddProperty(&DispFactor, 1, "$mat.displacementscaling", 0, 0);
}
}
double Converter::FrameRateToDouble( FileGlobalSettings::FrameRate fp, double customFPSVal )
{
switch ( fp ) {
case FileGlobalSettings::FrameRate_DEFAULT:
return 1.0;
case FileGlobalSettings::FrameRate_120:
return 120.0;
case FileGlobalSettings::FrameRate_100:
return 100.0;
case FileGlobalSettings::FrameRate_60:
return 60.0;
case FileGlobalSettings::FrameRate_50:
return 50.0;
case FileGlobalSettings::FrameRate_48:
return 48.0;
case FileGlobalSettings::FrameRate_30:
case FileGlobalSettings::FrameRate_30_DROP:
return 30.0;
case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME:
case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME:
return 29.9700262;
case FileGlobalSettings::FrameRate_PAL:
return 25.0;
case FileGlobalSettings::FrameRate_CINEMA:
return 24.0;
case FileGlobalSettings::FrameRate_1000:
return 1000.0;
case FileGlobalSettings::FrameRate_CINEMA_ND:
return 23.976;
case FileGlobalSettings::FrameRate_CUSTOM:
return customFPSVal;
case FileGlobalSettings::FrameRate_MAX: break;
}
ai_assert( false );
return -1.0f;
}
void Converter::ConvertAnimations()
{
const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode();
const float custom = doc.GlobalSettings().CustomFrameRate();
anim_fps = FrameRateToDouble( fps, custom );
const std::vector<const AnimationStack*>& animations = doc.AnimationStacks();
for( const AnimationStack* stack : animations ) {
ConvertAnimationStack( *stack );
}
}
void Converter::RenameNode( const std::string& fixed_name, const std::string& new_name )
{
ai_assert( node_names.find( fixed_name ) != node_names.end() );
ai_assert( node_names.find( new_name ) == node_names.end() );
renamed_nodes[ fixed_name ] = new_name;
const aiString fn( fixed_name );
for( aiCamera* cam : cameras ) {
if ( cam->mName == fn ) {
cam->mName.Set( new_name );
break;
}
}
for( aiLight* light : lights ) {
if ( light->mName == fn ) {
light->mName.Set( new_name );
break;
}
}
for( aiAnimation* anim : animations ) {
for ( unsigned int i = 0; i < anim->mNumChannels; ++i ) {
aiNodeAnim* const na = anim->mChannels[ i ];
if ( na->mNodeName == fn ) {
na->mNodeName.Set( new_name );
break;
}
}
}
}
std::string Converter::FixNodeName( const std::string& name )
{
if ( name.substr( 0, 7 ) == "Model::" ) {
std::string temp = name.substr( 7 );
const NodeNameMap::const_iterator it = node_names.find( temp );
if ( it != node_names.end() ) {
if ( !( *it ).second ) {
return FixNodeName( name + "_" );
}
}
node_names[ temp ] = true;
const NameNameMap::const_iterator rit = renamed_nodes.find( temp );
return rit == renamed_nodes.end() ? temp : ( *rit ).second;
}
const NodeNameMap::const_iterator it = node_names.find( name );
if ( it != node_names.end() ) {
if ( ( *it ).second ) {
return FixNodeName( name + "_" );
}
}
node_names[ name ] = false;
const NameNameMap::const_iterator rit = renamed_nodes.find( name );
return rit == renamed_nodes.end() ? name : ( *rit ).second;
}
void Converter::ConvertAnimationStack( const AnimationStack& st )
{
const AnimationLayerList& layers = st.Layers();
if ( layers.empty() ) {
return;
}
aiAnimation* const anim = new aiAnimation();
animations.push_back( anim );
std::string name = st.Name();
if ( name.substr( 0, 16 ) == "AnimationStack::" ) {
name = name.substr( 16 );
}
else if ( name.substr( 0, 11 ) == "AnimStack::" ) {
name = name.substr( 11 );
}
anim->mName.Set( name );
NodeMap node_map;
LayerMap layer_map;
const char* prop_whitelist[] = {
"Lcl Scaling",
"Lcl Rotation",
"Lcl Translation"
};
for( const AnimationLayer* layer : layers ) {
ai_assert( layer );
const AnimationCurveNodeList& nodes = layer->Nodes( prop_whitelist, 3 );
for( const AnimationCurveNode* node : nodes ) {
ai_assert( node );
const Model* const model = dynamic_cast<const Model*>( node->Target() );
if ( !model ) {
continue;
}
const std::string& name = FixNodeName( model->Name() );
node_map[ name ].push_back( node );
layer_map[ node ] = layer;
}
}
std::vector<aiNodeAnim*> node_anims;
double min_time = 1e10;
double max_time = -1e10;
int64_t start_time = st.LocalStart();
int64_t stop_time = st.LocalStop();
double start_timeF = CONVERT_FBX_TIME( start_time );
double stop_timeF = CONVERT_FBX_TIME( stop_time );
try {
for( const NodeMap::value_type& kv : node_map ) {
GenerateNodeAnimations( node_anims,
kv.first,
kv.second,
layer_map,
start_time, stop_time,
max_time,
min_time );
}
}
catch ( std::exception& ) {
std::for_each( node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>() );
throw;
}
if ( node_anims.size() ) {
anim->mChannels = new aiNodeAnim*[ node_anims.size() ]();
anim->mNumChannels = static_cast<unsigned int>( node_anims.size() );
std::swap_ranges( node_anims.begin(), node_anims.end(), anim->mChannels );
}
else {
delete anim;
animations.pop_back();
FBXImporter::LogInfo( "ignoring empty AnimationStack (using IK?): " + name );
return;
}
{
double start_fps = start_timeF * anim_fps;
for ( unsigned int c = 0; c < anim->mNumChannels; c++ )
{
aiNodeAnim* channel = anim->mChannels[ c ];
for ( uint32_t i = 0; i < channel->mNumPositionKeys; i++ )
channel->mPositionKeys[ i ].mTime -= start_fps;
for ( uint32_t i = 0; i < channel->mNumRotationKeys; i++ )
channel->mRotationKeys[ i ].mTime -= start_fps;
for ( uint32_t i = 0; i < channel->mNumScalingKeys; i++ )
channel->mScalingKeys[ i ].mTime -= start_fps;
}
max_time -= min_time;
}
anim->mDuration = ( stop_timeF - start_timeF ) * anim_fps;
anim->mTicksPerSecond = anim_fps;
}
static void validateAnimCurveNodes( const std::vector<const AnimationCurveNode*>& curves,
bool strictMode ) {
const Object* target( NULL );
for( const AnimationCurveNode* node : curves ) {
if ( !target ) {
target = node->Target();
}
if ( node->Target() != target ) {
FBXImporter::LogWarn( "Node target is nullptr type." );
}
if ( strictMode ) {
ai_assert( node->Target() == target );
}
}
}
void Converter::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 )
{
NodeMap node_property_map;
ai_assert( curves.size() );
#ifdef ASSIMP_BUILD_DEBUG
validateAnimCurveNodes( curves, doc.Settings().strictMode );
#endif
const AnimationCurveNode* curve_node = NULL;
for( const AnimationCurveNode* node : curves ) {
ai_assert( node );
if ( node->TargetProperty().empty() ) {
FBXImporter::LogWarn( "target property for animation curve not set: " + node->Name() );
continue;
}
curve_node = node;
if ( node->Curves().empty() ) {
FBXImporter::LogWarn( "no animation curves assigned to AnimationCurveNode: " + node->Name() );
continue;
}
node_property_map[ node->TargetProperty() ].push_back( node );
}
ai_assert( curve_node );
ai_assert( curve_node->TargetAsModel() );
const Model& target = *curve_node->TargetAsModel();
NodeMap::const_iterator chain[ TransformationComp_MAXIMUM ];
bool has_any = false;
bool has_complex = false;
for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i ) {
const TransformationComp comp = static_cast<TransformationComp>( i );
if ( comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse ) {
chain[ i ] = node_property_map.end();
continue;
}
chain[ i ] = node_property_map.find( NameTransformationCompProperty( comp ) );
if ( chain[ i ] != node_property_map.end() ) {
if ( doc.Settings().optimizeEmptyAnimationCurves &&
IsRedundantAnimationData( target, comp, ( *chain[ i ] ).second ) ) {
FBXImporter::LogDebug( "dropping redundant animation channel for node " + target.Name() );
continue;
}
has_any = true;
if ( comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation &&
comp != TransformationComp_GeometricScaling && comp != TransformationComp_GeometricRotation && comp != TransformationComp_GeometricTranslation )
{
has_complex = true;
}
}
}
if ( !has_any ) {
FBXImporter::LogWarn( "ignoring node animation, did not find any transformation key frames" );
return;
}
if ( !has_complex && !NeedsComplexTransformationChain( target ) ) {
aiNodeAnim* const nd = GenerateSimpleNodeAnim( fixed_name, target, chain,
node_property_map.end(),
layer_map,
start, stop,
max_time,
min_time,
true );
ai_assert( nd );
if ( nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0 ) {
delete nd;
}
else {
node_anims.push_back( nd );
}
return;
}
unsigned int flags = 0, bit = 0x1;
for ( size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1 ) {
const TransformationComp comp = static_cast<TransformationComp>( i );
if ( chain[ i ] != node_property_map.end() ) {
flags |= bit;
ai_assert( comp != TransformationComp_RotationPivotInverse );
ai_assert( comp != TransformationComp_ScalingPivotInverse );
const std::string& chain_name = NameTransformationChainNode( fixed_name, comp );
aiNodeAnim* na = nullptr;
switch ( comp )
{
case TransformationComp_Rotation:
case TransformationComp_PreRotation:
case TransformationComp_PostRotation:
case TransformationComp_GeometricRotation:
na = GenerateRotationNodeAnim( chain_name,
target,
( *chain[ i ] ).second,
layer_map,
start, stop,
max_time,
min_time );
break;
case TransformationComp_RotationOffset:
case TransformationComp_RotationPivot:
case TransformationComp_ScalingOffset:
case TransformationComp_ScalingPivot:
case TransformationComp_Translation:
case TransformationComp_GeometricTranslation:
na = GenerateTranslationNodeAnim( chain_name,
target,
( *chain[ i ] ).second,
layer_map,
start, stop,
max_time,
min_time );
if ( comp == TransformationComp_RotationPivot ) {
const std::string& invName = NameTransformationChainNode( fixed_name,
TransformationComp_RotationPivotInverse );
aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName,
target,
( *chain[ i ] ).second,
layer_map,
start, stop,
max_time,
min_time,
true );
ai_assert( inv );
if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) {
delete inv;
}
else {
node_anims.push_back( inv );
}
ai_assert( TransformationComp_RotationPivotInverse > i );
flags |= bit << ( TransformationComp_RotationPivotInverse - i );
}
else if ( comp == TransformationComp_ScalingPivot ) {
const std::string& invName = NameTransformationChainNode( fixed_name,
TransformationComp_ScalingPivotInverse );
aiNodeAnim* const inv = GenerateTranslationNodeAnim( invName,
target,
( *chain[ i ] ).second,
layer_map,
start, stop,
max_time,
min_time,
true );
ai_assert( inv );
if ( inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0 ) {
delete inv;
}
else {
node_anims.push_back( inv );
}
ai_assert( TransformationComp_RotationPivotInverse > i );
flags |= bit << ( TransformationComp_RotationPivotInverse - i );
}
break;
case TransformationComp_Scaling:
case TransformationComp_GeometricScaling:
na = GenerateScalingNodeAnim( chain_name,
target,
( *chain[ i ] ).second,
layer_map,
start, stop,
max_time,
min_time );
break;
default:
ai_assert( false );
}
ai_assert( na );
if ( na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0 ) {
delete na;
}
else {
node_anims.push_back( na );
}
continue;
}
}
node_anim_chain_bits[ fixed_name ] = flags;
}
bool Converter::IsRedundantAnimationData( const Model& target,
TransformationComp comp,
const std::vector<const AnimationCurveNode*>& curves )
{
ai_assert( curves.size() );
if ( curves.size() > 1 ) {
return false;
}
const AnimationCurveNode& nd = *curves.front();
const AnimationCurveMap& sub_curves = nd.Curves();
const AnimationCurveMap::const_iterator dx = sub_curves.find( "d|X" );
const AnimationCurveMap::const_iterator dy = sub_curves.find( "d|Y" );
const AnimationCurveMap::const_iterator dz = sub_curves.find( "d|Z" );
if ( dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end() ) {
return false;
}
const KeyValueList& vx = ( *dx ).second->GetValues();
const KeyValueList& vy = ( *dy ).second->GetValues();
const KeyValueList& vz = ( *dz ).second->GetValues();
if ( vx.size() != 1 || vy.size() != 1 || vz.size() != 1 ) {
return false;
}
const aiVector3D dyn_val = aiVector3D( vx[ 0 ], vy[ 0 ], vz[ 0 ] );
const aiVector3D& static_val = PropertyGet<aiVector3D>( target.Props(),
NameTransformationCompProperty( comp ),
TransformationCompDefaultValue( comp )
);
const float epsilon = 1e-6f;
return ( dyn_val - static_val ).SquareLength() < epsilon;
}
aiNodeAnim* Converter::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 )
{
ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
na->mNodeName.Set( name );
ConvertRotationKeys( na, curves, layer_map, start, stop, max_time, min_time, target.RotationOrder() );
na->mScalingKeys = new aiVectorKey[ 1 ];
na->mNumScalingKeys = 1;
na->mScalingKeys[ 0 ].mTime = 0.;
na->mScalingKeys[ 0 ].mValue = aiVector3D( 1.0f, 1.0f, 1.0f );
na->mPositionKeys = new aiVectorKey[ 1 ];
na->mNumPositionKeys = 1;
na->mPositionKeys[ 0 ].mTime = 0.;
na->mPositionKeys[ 0 ].mValue = aiVector3D();
return na.dismiss();
}
aiNodeAnim* Converter::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 )
{
ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
na->mNodeName.Set( name );
ConvertScaleKeys( na, curves, layer_map, start, stop, max_time, min_time );
na->mRotationKeys = new aiQuatKey[ 1 ];
na->mNumRotationKeys = 1;
na->mRotationKeys[ 0 ].mTime = 0.;
na->mRotationKeys[ 0 ].mValue = aiQuaternion();
na->mPositionKeys = new aiVectorKey[ 1 ];
na->mNumPositionKeys = 1;
na->mPositionKeys[ 0 ].mTime = 0.;
na->mPositionKeys[ 0 ].mValue = aiVector3D();
return na.dismiss();
}
aiNodeAnim* Converter::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 )
{
ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
na->mNodeName.Set( name );
ConvertTranslationKeys( na, curves, layer_map, start, stop, max_time, min_time );
if ( inverse ) {
for ( unsigned int i = 0; i < na->mNumPositionKeys; ++i ) {
na->mPositionKeys[ i ].mValue *= -1.0f;
}
}
na->mScalingKeys = new aiVectorKey[ 1 ];
na->mNumScalingKeys = 1;
na->mScalingKeys[ 0 ].mTime = 0.;
na->mScalingKeys[ 0 ].mValue = aiVector3D( 1.0f, 1.0f, 1.0f );
na->mRotationKeys = new aiQuatKey[ 1 ];
na->mNumRotationKeys = 1;
na->mRotationKeys[ 0 ].mTime = 0.;
na->mRotationKeys[ 0 ].mValue = aiQuaternion();
return na.dismiss();
}
aiNodeAnim* Converter::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 )
{
ScopeGuard<aiNodeAnim> na( new aiNodeAnim() );
na->mNodeName.Set( name );
const PropertyTable& props = target.Props();
if ( reverse_order ) {
aiVector3D def_scale = PropertyGet( props, "Lcl Scaling", aiVector3D( 1.f, 1.f, 1.f ) );
aiVector3D def_translate = PropertyGet( props, "Lcl Translation", aiVector3D( 0.f, 0.f, 0.f ) );
aiVector3D def_rot = PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) );
KeyFrameListList scaling;
KeyFrameListList translation;
KeyFrameListList rotation;
if ( chain[ TransformationComp_Scaling ] != iter_end ) {
scaling = GetKeyframeList( ( *chain[ TransformationComp_Scaling ] ).second, start, stop );
}
if ( chain[ TransformationComp_Translation ] != iter_end ) {
translation = GetKeyframeList( ( *chain[ TransformationComp_Translation ] ).second, start, stop );
}
if ( chain[ TransformationComp_Rotation ] != iter_end ) {
rotation = GetKeyframeList( ( *chain[ TransformationComp_Rotation ] ).second, start, stop );
}
KeyFrameListList joined;
joined.insert( joined.end(), scaling.begin(), scaling.end() );
joined.insert( joined.end(), translation.begin(), translation.end() );
joined.insert( joined.end(), rotation.begin(), rotation.end() );
const KeyTimeList& times = GetKeyTimeList( joined );
aiQuatKey* out_quat = new aiQuatKey[ times.size() ];
aiVectorKey* out_scale = new aiVectorKey[ times.size() ];
aiVectorKey* out_translation = new aiVectorKey[ times.size() ];
if ( times.size() )
{
ConvertTransformOrder_TRStoSRT( out_quat, out_scale, out_translation,
scaling,
translation,
rotation,
times,
max_time,
min_time,
target.RotationOrder(),
def_scale,
def_translate,
def_rot );
}
na->mNumScalingKeys = static_cast<unsigned int>( times.size() );
na->mNumRotationKeys = na->mNumScalingKeys;
na->mNumPositionKeys = na->mNumScalingKeys;
na->mScalingKeys = out_scale;
na->mRotationKeys = out_quat;
na->mPositionKeys = out_translation;
}
else {
if ( chain[ TransformationComp_Scaling ] != iter_end ) {
ConvertScaleKeys( na, ( *chain[ TransformationComp_Scaling ] ).second,
layer_map,
start, stop,
max_time,
min_time );
}
else {
na->mScalingKeys = new aiVectorKey[ 1 ];
na->mNumScalingKeys = 1;
na->mScalingKeys[ 0 ].mTime = 0.;
na->mScalingKeys[ 0 ].mValue = PropertyGet( props, "Lcl Scaling",
aiVector3D( 1.f, 1.f, 1.f ) );
}
if ( chain[ TransformationComp_Rotation ] != iter_end ) {
ConvertRotationKeys( na, ( *chain[ TransformationComp_Rotation ] ).second,
layer_map,
start, stop,
max_time,
min_time,
target.RotationOrder() );
}
else {
na->mRotationKeys = new aiQuatKey[ 1 ];
na->mNumRotationKeys = 1;
na->mRotationKeys[ 0 ].mTime = 0.;
na->mRotationKeys[ 0 ].mValue = EulerToQuaternion(
PropertyGet( props, "Lcl Rotation", aiVector3D( 0.f, 0.f, 0.f ) ),
target.RotationOrder() );
}
if ( chain[ TransformationComp_Translation ] != iter_end ) {
ConvertTranslationKeys( na, ( *chain[ TransformationComp_Translation ] ).second,
layer_map,
start, stop,
max_time,
min_time );
}
else {
na->mPositionKeys = new aiVectorKey[ 1 ];
na->mNumPositionKeys = 1;
na->mPositionKeys[ 0 ].mTime = 0.;
na->mPositionKeys[ 0 ].mValue = PropertyGet( props, "Lcl Translation",
aiVector3D( 0.f, 0.f, 0.f ) );
}
}
return na.dismiss();
}
Converter::KeyFrameListList Converter::GetKeyframeList( const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop )
{
KeyFrameListList inputs;
inputs.reserve( nodes.size() * 3 );
int64_t adj_start = start - 10000;
int64_t adj_stop = stop + 10000;
for( const AnimationCurveNode* node : nodes ) {
ai_assert( node );
const AnimationCurveMap& curves = node->Curves();
for( const AnimationCurveMap::value_type& kv : curves ) {
unsigned int mapto;
if ( kv.first == "d|X" ) {
mapto = 0;
}
else if ( kv.first == "d|Y" ) {
mapto = 1;
}
else if ( kv.first == "d|Z" ) {
mapto = 2;
}
else {
FBXImporter::LogWarn( "ignoring scale animation curve, did not recognize target component" );
continue;
}
const AnimationCurve* const curve = kv.second;
ai_assert( curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size() );
std::shared_ptr<KeyTimeList> Keys( new KeyTimeList() );
std::shared_ptr<KeyValueList> Values( new KeyValueList() );
const size_t count = curve->GetKeys().size();
Keys->reserve( count );
Values->reserve( count );
for (size_t n = 0; n < count; n++ )
{
int64_t k = curve->GetKeys().at( n );
if ( k >= adj_start && k <= adj_stop )
{
Keys->push_back( k );
Values->push_back( curve->GetValues().at( n ) );
}
}
inputs.push_back( std::make_tuple( Keys, Values, mapto ) );
}
}
return inputs; }
KeyTimeList Converter::GetKeyTimeList( const KeyFrameListList& inputs )
{
ai_assert( inputs.size() );
KeyTimeList keys;
size_t estimate = 0;
for( const KeyFrameList& kfl : inputs ) {
estimate = std::max( estimate, std::get<0>(kfl)->size() );
}
keys.reserve( estimate );
std::vector<unsigned int> next_pos;
next_pos.resize( inputs.size(), 0 );
const size_t count = inputs.size();
while ( true ) {
int64_t min_tick = std::numeric_limits<int64_t>::max();
for ( size_t i = 0; i < count; ++i ) {
const KeyFrameList& kfl = inputs[ i ];
if ( std::get<0>(kfl)->size() > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) < min_tick ) {
min_tick = std::get<0>(kfl)->at( next_pos[ i ] );
}
}
if ( min_tick == std::numeric_limits<int64_t>::max() ) {
break;
}
keys.push_back( min_tick );
for ( size_t i = 0; i < count; ++i ) {
const KeyFrameList& kfl = inputs[ i ];
while ( std::get<0>(kfl)->size() > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) == min_tick ) {
++next_pos[ i ];
}
}
}
return keys;
}
void Converter::InterpolateKeys( aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
const aiVector3D& def_value,
double& max_time,
double& min_time )
{
ai_assert( keys.size() );
ai_assert( valOut );
std::vector<unsigned int> next_pos;
const size_t count = inputs.size();
next_pos.resize( inputs.size(), 0 );
for( KeyTimeList::value_type time : keys ) {
ai_real result[ 3 ] = { def_value.x, def_value.y, def_value.z };
for ( size_t i = 0; i < count; ++i ) {
const KeyFrameList& kfl = inputs[ i ];
const size_t ksize = std::get<0>(kfl)->size();
if ( ksize > next_pos[ i ] && std::get<0>(kfl)->at( next_pos[ i ] ) == time ) {
++next_pos[ i ];
}
const size_t id0 = next_pos[ i ]>0 ? next_pos[ i ] - 1 : 0;
const size_t id1 = next_pos[ i ] == ksize ? ksize - 1 : next_pos[ i ];
const KeyValueList::value_type valueA = std::get<1>(kfl)->at( id0 );
const KeyValueList::value_type valueB = std::get<1>(kfl)->at( id1 );
const KeyTimeList::value_type timeA = std::get<0>(kfl)->at( id0 );
const KeyTimeList::value_type timeB = std::get<0>(kfl)->at( id1 );
const ai_real factor = timeB == timeA ? ai_real(0.) : static_cast<ai_real>( ( time - timeA ) ) / ( timeB - timeA );
const ai_real interpValue = static_cast<ai_real>( valueA + ( valueB - valueA ) * factor );
result[ std::get<2>(kfl) ] = interpValue;
}
valOut->mTime = CONVERT_FBX_TIME( time ) * anim_fps;
min_time = std::min( min_time, valOut->mTime );
max_time = std::max( max_time, valOut->mTime );
valOut->mValue.x = result[ 0 ];
valOut->mValue.y = result[ 1 ];
valOut->mValue.z = result[ 2 ];
++valOut;
}
}
void Converter::InterpolateKeys( aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
const aiVector3D& def_value,
double& maxTime,
double& minTime,
Model::RotOrder order )
{
ai_assert( keys.size() );
ai_assert( valOut );
std::unique_ptr<aiVectorKey[]> temp( new aiVectorKey[ keys.size() ] );
InterpolateKeys( temp.get(), keys, inputs, def_value, maxTime, minTime );
aiMatrix4x4 m;
aiQuaternion lastq;
for ( size_t i = 0, c = keys.size(); i < c; ++i ) {
valOut[ i ].mTime = temp[ i ].mTime;
GetRotationMatrix( order, temp[ i ].mValue, m );
aiQuaternion quat = aiQuaternion( aiMatrix3x3( m ) );
if ( quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0 )
{
quat.x = -quat.x;
quat.y = -quat.y;
quat.z = -quat.z;
quat.w = -quat.w;
}
lastq = quat;
valOut[ i ].mValue = quat;
}
}
void Converter::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 )
{
if ( rotation.size() ) {
InterpolateKeys( out_quat, times, rotation, def_rotation, maxTime, minTime, order );
}
else {
for ( size_t i = 0; i < times.size(); ++i ) {
out_quat[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps;
out_quat[ i ].mValue = EulerToQuaternion( def_rotation, order );
}
}
if ( scaling.size() ) {
InterpolateKeys( out_scale, times, scaling, def_scale, maxTime, minTime );
}
else {
for ( size_t i = 0; i < times.size(); ++i ) {
out_scale[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps;
out_scale[ i ].mValue = def_scale;
}
}
if ( translation.size() ) {
InterpolateKeys( out_translation, times, translation, def_translate, maxTime, minTime );
}
else {
for ( size_t i = 0; i < times.size(); ++i ) {
out_translation[ i ].mTime = CONVERT_FBX_TIME( times[ i ] ) * anim_fps;
out_translation[ i ].mValue = def_translate;
}
}
const size_t count = times.size();
for ( size_t i = 0; i < count; ++i ) {
aiQuaternion& r = out_quat[ i ].mValue;
aiVector3D& s = out_scale[ i ].mValue;
aiVector3D& t = out_translation[ i ].mValue;
aiMatrix4x4 mat, temp;
aiMatrix4x4::Translation( t, mat );
mat *= aiMatrix4x4( r.GetMatrix() );
mat *= aiMatrix4x4::Scaling( s, temp );
mat.Decompose( s, r, t );
}
}
aiQuaternion Converter::EulerToQuaternion( const aiVector3D& rot, Model::RotOrder order )
{
aiMatrix4x4 m;
GetRotationMatrix( order, rot, m );
return aiQuaternion( aiMatrix3x3( m ) );
}
void Converter::ConvertScaleKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& ,
int64_t start, int64_t stop,
double& maxTime,
double& minTime )
{
ai_assert( nodes.size() );
const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop );
const KeyTimeList& keys = GetKeyTimeList( inputs );
na->mNumScalingKeys = static_cast<unsigned int>( keys.size() );
na->mScalingKeys = new aiVectorKey[ keys.size() ];
if ( keys.size() > 0 )
InterpolateKeys( na->mScalingKeys, keys, inputs, aiVector3D( 1.0f, 1.0f, 1.0f ), maxTime, minTime );
}
void Converter::ConvertTranslationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
const LayerMap& ,
int64_t start, int64_t stop,
double& maxTime,
double& minTime )
{
ai_assert( nodes.size() );
const KeyFrameListList& inputs = GetKeyframeList( nodes, start, stop );
const KeyTimeList& keys = GetKeyTimeList( inputs );
na->mNumPositionKeys = static_cast<unsigned int>( keys.size() );
na->mPositionKeys = new aiVectorKey[ keys.size() ];
if ( keys.size() > 0 )
InterpolateKeys( na->mPositionKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime );
}
void Converter::ConvertRotationKeys( aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
const LayerMap& ,
int64_t start, int64_t stop,
double& maxTime,
double& minTime,
Model::RotOrder order )
{
ai_assert( nodes.size() );
const std::vector< KeyFrameList >& inputs = GetKeyframeList( nodes, start, stop );
const KeyTimeList& keys = GetKeyTimeList( inputs );
na->mNumRotationKeys = static_cast<unsigned int>( keys.size() );
na->mRotationKeys = new aiQuatKey[ keys.size() ];
if ( keys.size() > 0 )
InterpolateKeys( na->mRotationKeys, keys, inputs, aiVector3D( 0.0f, 0.0f, 0.0f ), maxTime, minTime, order );
}
void Converter::TransferDataToScene()
{
ai_assert( !out->mMeshes && !out->mNumMeshes );
if ( meshes.size() ) {
out->mMeshes = new aiMesh*[ meshes.size() ]();
out->mNumMeshes = static_cast<unsigned int>( meshes.size() );
std::swap_ranges( meshes.begin(), meshes.end(), out->mMeshes );
}
if ( materials.size() ) {
out->mMaterials = new aiMaterial*[ materials.size() ]();
out->mNumMaterials = static_cast<unsigned int>( materials.size() );
std::swap_ranges( materials.begin(), materials.end(), out->mMaterials );
}
if ( animations.size() ) {
out->mAnimations = new aiAnimation*[ animations.size() ]();
out->mNumAnimations = static_cast<unsigned int>( animations.size() );
std::swap_ranges( animations.begin(), animations.end(), out->mAnimations );
}
if ( lights.size() ) {
out->mLights = new aiLight*[ lights.size() ]();
out->mNumLights = static_cast<unsigned int>( lights.size() );
std::swap_ranges( lights.begin(), lights.end(), out->mLights );
}
if ( cameras.size() ) {
out->mCameras = new aiCamera*[ cameras.size() ]();
out->mNumCameras = static_cast<unsigned int>( cameras.size() );
std::swap_ranges( cameras.begin(), cameras.end(), out->mCameras );
}
if ( textures.size() ) {
out->mTextures = new aiTexture*[ textures.size() ]();
out->mNumTextures = static_cast<unsigned int>( textures.size() );
std::swap_ranges( textures.begin(), textures.end(), out->mTextures );
}
}
void ConvertToAssimpScene(aiScene* out, const Document& doc)
{
Converter converter(out,doc);
}
} }
#endif