Refactor objects splitting in models importing to be handled by ModelTool not the importer code itself
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ModelTool.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/DeleteMe.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Collections/Dictionary.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
@@ -28,7 +27,6 @@ using namespace Assimp;
|
||||
class AssimpLogStream : public LogStream
|
||||
{
|
||||
public:
|
||||
|
||||
AssimpLogStream()
|
||||
{
|
||||
DefaultLogger::create("");
|
||||
@@ -612,32 +610,35 @@ bool IsMeshInvalid(const aiMesh* aMesh)
|
||||
return aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3;
|
||||
}
|
||||
|
||||
bool ImportMesh(int32 i, ModelData& result, AssimpImporterData& data, String& errorMsg)
|
||||
bool ImportMesh(int32 index, ModelData& result, AssimpImporterData& data, String& errorMsg)
|
||||
{
|
||||
const auto aMesh = data.Scene->mMeshes[i];
|
||||
const auto aMesh = data.Scene->mMeshes[index];
|
||||
|
||||
// Skip invalid meshes
|
||||
if (IsMeshInvalid(aMesh))
|
||||
return false;
|
||||
|
||||
// Skip unused meshes
|
||||
if (!data.MeshIndexToNodeIndex.ContainsKey(i))
|
||||
if (!data.MeshIndexToNodeIndex.ContainsKey(index))
|
||||
return false;
|
||||
|
||||
// Import mesh data
|
||||
MeshData* meshData = New<MeshData>();
|
||||
if (ProcessMesh(result, data, aMesh, *meshData, errorMsg))
|
||||
return true;
|
||||
|
||||
auto& nodesWithMesh = data.MeshIndexToNodeIndex[i];
|
||||
for (int32 j = 0; j < nodesWithMesh.Count(); j++)
|
||||
{
|
||||
const auto nodeIndex = nodesWithMesh[j];
|
||||
Delete(meshData);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& nodesWithMesh = data.MeshIndexToNodeIndex[index];
|
||||
for (int32 i = 0; i < nodesWithMesh.Count(); i++)
|
||||
{
|
||||
const auto nodeIndex = nodesWithMesh[i];
|
||||
auto& node = data.Nodes[nodeIndex];
|
||||
const int32 lodIndex = node.LodIndex;
|
||||
|
||||
// The first mesh instance uses meshData directly while others have to clone it
|
||||
if (j != 0)
|
||||
if (i != 0)
|
||||
{
|
||||
meshData = New<MeshData>(*meshData);
|
||||
}
|
||||
@@ -739,222 +740,166 @@ void ImportCurve(aiQuatKey* keys, uint32 keysCount, LinearCurve<Quaternion>& cur
|
||||
}
|
||||
}
|
||||
|
||||
void ImportAnimation(int32 index, ModelData& data, AssimpImporterData& importerData)
|
||||
{
|
||||
const auto animations = importerData.Scene->mAnimations[index];
|
||||
auto& anim = data.Animations.AddOne();
|
||||
anim.Channels.Resize(animations->mNumChannels, false);
|
||||
anim.Duration = animations->mDuration;
|
||||
anim.FramesPerSecond = animations->mTicksPerSecond;
|
||||
if (anim.FramesPerSecond <= 0)
|
||||
{
|
||||
anim.FramesPerSecond = importerData.Options.DefaultFrameRate;
|
||||
if (anim.FramesPerSecond <= 0)
|
||||
anim.FramesPerSecond = 30.0f;
|
||||
}
|
||||
anim.Name = animations->mName.C_Str();
|
||||
|
||||
for (unsigned i = 0; i < animations->mNumChannels; i++)
|
||||
{
|
||||
const auto aAnim = animations->mChannels[i];
|
||||
auto& channel = anim.Channels[i];
|
||||
|
||||
channel.NodeName = aAnim->mNodeName.C_Str();
|
||||
|
||||
ImportCurve(aAnim->mPositionKeys, aAnim->mNumPositionKeys, channel.Position);
|
||||
ImportCurve(aAnim->mRotationKeys, aAnim->mNumRotationKeys, channel.Rotation);
|
||||
if (importerData.Options.ImportScaleTracks)
|
||||
ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, channel.Scale);
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelTool::ImportDataAssimp(const char* path, ModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
auto context = (AssimpImporterData*)options.SplitContext;
|
||||
if (!context)
|
||||
static bool AssimpInited = false;
|
||||
if (!AssimpInited)
|
||||
{
|
||||
static bool AssimpInited = false;
|
||||
if (!AssimpInited)
|
||||
{
|
||||
AssimpInited = true;
|
||||
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
|
||||
}
|
||||
bool importMeshes = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry);
|
||||
bool importAnimations = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations);
|
||||
context = New<AssimpImporterData>(path, options);
|
||||
|
||||
// Setup import flags
|
||||
unsigned int flags =
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
aiProcess_LimitBoneWeights |
|
||||
aiProcess_Triangulate |
|
||||
aiProcess_SortByPType |
|
||||
aiProcess_GenUVCoords |
|
||||
aiProcess_FindDegenerates |
|
||||
aiProcess_FindInvalidData |
|
||||
//aiProcess_ValidateDataStructure |
|
||||
aiProcess_ConvertToLeftHanded;
|
||||
if (importMeshes)
|
||||
{
|
||||
if (options.CalculateNormals)
|
||||
flags |= aiProcess_FixInfacingNormals | aiProcess_GenSmoothNormals;
|
||||
if (options.CalculateTangents)
|
||||
flags |= aiProcess_CalcTangentSpace;
|
||||
if (options.OptimizeMeshes)
|
||||
flags |= aiProcess_OptimizeMeshes | aiProcess_SplitLargeMeshes | aiProcess_ImproveCacheLocality;
|
||||
if (options.MergeMeshes)
|
||||
flags |= aiProcess_RemoveRedundantMaterials;
|
||||
}
|
||||
|
||||
// Setup import options
|
||||
context->AssimpImporter.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, options.SmoothingNormalsAngle);
|
||||
context->AssimpImporter.SetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, options.SmoothingTangentsAngle);
|
||||
//context->AssimpImporter.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, MAX_uint16);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, false);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, false);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, false);
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, importAnimations);
|
||||
//context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); // TODO: optimize pivots when https://github.com/assimp/assimp/issues/1068 gets fixed
|
||||
context->AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
|
||||
|
||||
// Import file
|
||||
context->Scene = context->AssimpImporter.ReadFile(path, flags);
|
||||
if (context->Scene == nullptr)
|
||||
{
|
||||
LOG_STR(Warning, String(context->AssimpImporter.GetErrorString()));
|
||||
LOG_STR(Warning, String(path));
|
||||
LOG_STR(Warning, StringUtils::ToString(flags));
|
||||
errorMsg = context->AssimpImporter.GetErrorString();
|
||||
Delete(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create root node
|
||||
AssimpNode& rootNode = context->Nodes.AddOne();
|
||||
rootNode.ParentIndex = -1;
|
||||
rootNode.LodIndex = 0;
|
||||
rootNode.Name = TEXT("Root");
|
||||
rootNode.LocalTransform = Transform::Identity;
|
||||
|
||||
// Process imported scene nodes
|
||||
ProcessNodes(*context, context->Scene->mRootNode, 0);
|
||||
AssimpInited = true;
|
||||
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
|
||||
}
|
||||
DeleteMe<AssimpImporterData> contextCleanup(options.SplitContext ? nullptr : context);
|
||||
bool importMeshes = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry);
|
||||
bool importAnimations = EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations);
|
||||
AssimpImporterData context(path, options);
|
||||
|
||||
// Setup import flags
|
||||
unsigned int flags =
|
||||
aiProcess_JoinIdenticalVertices |
|
||||
aiProcess_LimitBoneWeights |
|
||||
aiProcess_Triangulate |
|
||||
aiProcess_SortByPType |
|
||||
aiProcess_GenUVCoords |
|
||||
aiProcess_FindDegenerates |
|
||||
aiProcess_FindInvalidData |
|
||||
//aiProcess_ValidateDataStructure |
|
||||
aiProcess_ConvertToLeftHanded;
|
||||
if (importMeshes)
|
||||
{
|
||||
if (options.CalculateNormals)
|
||||
flags |= aiProcess_FixInfacingNormals | aiProcess_GenSmoothNormals;
|
||||
if (options.CalculateTangents)
|
||||
flags |= aiProcess_CalcTangentSpace;
|
||||
if (options.OptimizeMeshes)
|
||||
flags |= aiProcess_OptimizeMeshes | aiProcess_SplitLargeMeshes | aiProcess_ImproveCacheLocality;
|
||||
if (options.MergeMeshes)
|
||||
flags |= aiProcess_RemoveRedundantMaterials;
|
||||
}
|
||||
|
||||
// Setup import options
|
||||
context.AssimpImporter.SetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE, options.SmoothingNormalsAngle);
|
||||
context.AssimpImporter.SetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE, options.SmoothingTangentsAngle);
|
||||
//context.AssimpImporter.SetPropertyInteger(AI_CONFIG_PP_SLM_TRIANGLE_LIMIT, MAX_uint16);
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, false);
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, false);
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, false);
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, importAnimations);
|
||||
//context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); // TODO: optimize pivots when https://github.com/assimp/assimp/issues/1068 gets fixed
|
||||
context.AssimpImporter.SetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
|
||||
|
||||
// Import file
|
||||
context.Scene = context.AssimpImporter.ReadFile(path, flags);
|
||||
if (context.Scene == nullptr)
|
||||
{
|
||||
LOG_STR(Warning, String(context.AssimpImporter.GetErrorString()));
|
||||
LOG_STR(Warning, String(path));
|
||||
LOG_STR(Warning, StringUtils::ToString(flags));
|
||||
errorMsg = context.AssimpImporter.GetErrorString();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create root node
|
||||
AssimpNode& rootNode = context.Nodes.AddOne();
|
||||
rootNode.ParentIndex = -1;
|
||||
rootNode.LodIndex = 0;
|
||||
rootNode.Name = TEXT("Root");
|
||||
rootNode.LocalTransform = Transform::Identity;
|
||||
|
||||
// Process imported scene nodes
|
||||
ProcessNodes(context, context.Scene->mRootNode, 0);
|
||||
|
||||
// Import materials
|
||||
if (ImportMaterials(data, *context, errorMsg))
|
||||
if (ImportMaterials(data, context, errorMsg))
|
||||
{
|
||||
LOG(Warning, "Failed to import materials.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Import geometry
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry) && context->Scene->HasMeshes())
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry) && context.Scene->HasMeshes())
|
||||
{
|
||||
const int meshCount = context->Scene->mNumMeshes;
|
||||
if (options.SplitObjects && options.ObjectIndex == -1 && meshCount > 1)
|
||||
for (unsigned meshIndex = 0; meshIndex < context.Scene->mNumMeshes; meshIndex++)
|
||||
{
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} meshes", meshCount);
|
||||
for (int32 i = 1; i < meshCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const auto aMesh = context->Scene->mMeshes[i];
|
||||
const String objectName(aMesh->mName.C_Str());
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.ObjectIndex != -1)
|
||||
{
|
||||
// Import the selected mesh
|
||||
const auto meshIndex = Math::Clamp<int32>(options.ObjectIndex, 0, meshCount - 1);
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
if (ImportMesh((int32)meshIndex, data, context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Import all meshes
|
||||
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
|
||||
{
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import skeleton
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton))
|
||||
{
|
||||
data.Skeleton.Nodes.Resize(context->Nodes.Count(), false);
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
data.Skeleton.Nodes.Resize(context.Nodes.Count(), false);
|
||||
for (int32 i = 0; i < context.Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Skeleton.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
auto& aNode = context.Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
node.LocalTransform = aNode.LocalTransform;
|
||||
}
|
||||
|
||||
data.Skeleton.Bones.Resize(context->Bones.Count(), false);
|
||||
for (int32 i = 0; i < context->Bones.Count(); i++)
|
||||
data.Skeleton.Bones.Resize(context.Bones.Count(), false);
|
||||
for (int32 i = 0; i < context.Bones.Count(); i++)
|
||||
{
|
||||
auto& bone = data.Skeleton.Bones[i];
|
||||
auto& aBone = context->Bones[i];
|
||||
auto& aBone = context.Bones[i];
|
||||
|
||||
const auto boneNodeIndex = aBone.NodeIndex;
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : context->Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : context.Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
|
||||
bone.ParentIndex = aBone.ParentBoneIndex;
|
||||
bone.NodeIndex = aBone.NodeIndex;
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(context->Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(context.Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.OffsetMatrix = aBone.OffsetMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
// Import animations
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations) && context->Scene->HasAnimations())
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations))
|
||||
{
|
||||
const int32 animCount = (int32)context->Scene->mNumAnimations;
|
||||
if (options.SplitObjects && options.ObjectIndex == -1 && animCount > 1)
|
||||
for (unsigned animIndex = 0; animIndex < context.Scene->mNumAnimations; animIndex++)
|
||||
{
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} animations", animCount);
|
||||
for (int32 i = 1; i < animCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const auto animations = context->Scene->mAnimations[i];
|
||||
const String objectName(animations->mName.C_Str());
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import the animation
|
||||
{
|
||||
const auto animIndex = Math::Clamp<int32>(options.ObjectIndex, 0, context->Scene->mNumAnimations - 1);
|
||||
const auto animations = context->Scene->mAnimations[animIndex];
|
||||
data.Animation.Channels.Resize(animations->mNumChannels, false);
|
||||
data.Animation.Duration = animations->mDuration;
|
||||
data.Animation.FramesPerSecond = animations->mTicksPerSecond;
|
||||
if (data.Animation.FramesPerSecond <= 0)
|
||||
{
|
||||
data.Animation.FramesPerSecond = context->Options.DefaultFrameRate;
|
||||
if (data.Animation.FramesPerSecond <= 0)
|
||||
data.Animation.FramesPerSecond = 30.0f;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < animations->mNumChannels; i++)
|
||||
{
|
||||
const auto aAnim = animations->mChannels[i];
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
|
||||
anim.NodeName = aAnim->mNodeName.C_Str();
|
||||
|
||||
ImportCurve(aAnim->mPositionKeys, aAnim->mNumPositionKeys, anim.Position);
|
||||
ImportCurve(aAnim->mRotationKeys, aAnim->mNumRotationKeys, anim.Rotation);
|
||||
if (options.ImportScaleTracks)
|
||||
ImportCurve(aAnim->mScalingKeys, aAnim->mNumScalingKeys, anim.Scale);
|
||||
}
|
||||
ImportAnimation((int32)animIndex, data, context);
|
||||
}
|
||||
}
|
||||
|
||||
// Import nodes
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Nodes))
|
||||
{
|
||||
data.Nodes.Resize(context->Nodes.Count());
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
data.Nodes.Resize(context.Nodes.Count());
|
||||
for (int32 i = 0; i < context.Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
auto& aNode = context.Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "ModelTool.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/DeleteMe.h"
|
||||
#include "Engine/Core/Math/Mathd.h"
|
||||
#include "Engine/Core/Math/Matrix.h"
|
||||
#include "Engine/Core/Collections/Sorting.h"
|
||||
@@ -1006,27 +1005,19 @@ void ImportCurve(const ofbx::AnimationCurveNode* curveNode, LinearCurve<T>& curv
|
||||
}
|
||||
}
|
||||
|
||||
bool ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importerData)
|
||||
void ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importerData)
|
||||
{
|
||||
const ofbx::AnimationStack* stack = importerData.Scene->getAnimationStack(index);
|
||||
const ofbx::AnimationLayer* layer = stack->getLayer(0);
|
||||
const ofbx::TakeInfo* takeInfo = importerData.Scene->getTakeInfo(stack->name);
|
||||
if (takeInfo == nullptr)
|
||||
return true;
|
||||
return;
|
||||
|
||||
// Initialize animation animation keyframes sampling
|
||||
const float frameRate = importerData.FrameRate;
|
||||
data.Animation.FramesPerSecond = frameRate;
|
||||
const double localDuration = takeInfo->local_time_to - takeInfo->local_time_from;
|
||||
if (localDuration <= ZeroTolerance)
|
||||
return true;
|
||||
data.Animation.Duration = (double)(int32)(localDuration * frameRate + 0.5f);
|
||||
AnimInfo info;
|
||||
info.TimeStart = takeInfo->local_time_from;
|
||||
info.TimeEnd = takeInfo->local_time_to;
|
||||
info.Duration = localDuration;
|
||||
info.FramesCount = (int32)data.Animation.Duration;
|
||||
info.SamplingPeriod = 1.0f / frameRate;
|
||||
return;
|
||||
|
||||
// Count valid animation channels
|
||||
Array<int32> animatedNodes(importerData.Nodes.Count());
|
||||
@@ -1042,15 +1033,32 @@ bool ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importer
|
||||
animatedNodes.Add(nodeIndex);
|
||||
}
|
||||
if (animatedNodes.IsEmpty())
|
||||
return true;
|
||||
data.Animation.Channels.Resize(animatedNodes.Count(), false);
|
||||
return;
|
||||
|
||||
// Setup animation descriptor
|
||||
auto& animation = data.Animations.AddOne();
|
||||
animation.Duration = (double)(int32)(localDuration * frameRate + 0.5f);
|
||||
animation.FramesPerSecond = frameRate;
|
||||
char nameData[256];
|
||||
takeInfo->name.toString(nameData);
|
||||
animation.Name = nameData;
|
||||
animation.Name = animation.Name.TrimTrailing();
|
||||
if (animation.Name.IsEmpty())
|
||||
animation.Name = String(layer->name);
|
||||
animation.Channels.Resize(animatedNodes.Count(), false);
|
||||
AnimInfo info;
|
||||
info.TimeStart = takeInfo->local_time_from;
|
||||
info.TimeEnd = takeInfo->local_time_to;
|
||||
info.Duration = localDuration;
|
||||
info.FramesCount = (int32)animation.Duration;
|
||||
info.SamplingPeriod = 1.0f / frameRate;
|
||||
|
||||
// Import curves
|
||||
for (int32 i = 0; i < animatedNodes.Count(); i++)
|
||||
{
|
||||
const int32 nodeIndex = animatedNodes[i];
|
||||
auto& aNode = importerData.Nodes[nodeIndex];
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
auto& anim = animation.Channels[i];
|
||||
|
||||
const ofbx::AnimationCurveNode* translationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Translation");
|
||||
const ofbx::AnimationCurveNode* rotationNode = layer->getCurveNode(*aNode.FbxObj, "Lcl Rotation");
|
||||
@@ -1066,9 +1074,8 @@ bool ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importer
|
||||
|
||||
if (importerData.ConvertRH)
|
||||
{
|
||||
for (int32 i = 0; i < data.Animation.Channels.Count(); i++)
|
||||
for (auto& anim : animation.Channels)
|
||||
{
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
auto& posKeys = anim.Position.GetKeyframes();
|
||||
auto& rotKeys = anim.Rotation.GetKeyframes();
|
||||
|
||||
@@ -1084,8 +1091,6 @@ bool ImportAnimation(int32 index, ModelData& data, OpenFbxImporterData& importer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
|
||||
@@ -1105,239 +1110,185 @@ static Float3 FbxVectorFromAxisAndSign(int axis, int sign)
|
||||
|
||||
bool ModelTool::ImportDataOpenFBX(const char* path, ModelData& data, Options& options, String& errorMsg)
|
||||
{
|
||||
auto context = (OpenFbxImporterData*)options.SplitContext;
|
||||
if (!context)
|
||||
// Import file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(String(path), fileData))
|
||||
{
|
||||
// Import file
|
||||
Array<byte> fileData;
|
||||
if (File::ReadAllBytes(String(path), fileData))
|
||||
{
|
||||
errorMsg = TEXT("Cannot load file.");
|
||||
return true;
|
||||
}
|
||||
ofbx::u64 loadFlags = 0;
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry))
|
||||
{
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::TRIANGULATE;
|
||||
if (!options.ImportBlendShapes)
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_GEOMETRY | (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
|
||||
}
|
||||
ofbx::IScene* scene = ofbx::load(fileData.Get(), fileData.Count(), loadFlags);
|
||||
if (!scene)
|
||||
{
|
||||
errorMsg = ofbx::getError();
|
||||
return true;
|
||||
}
|
||||
fileData.Resize(0);
|
||||
|
||||
// Tweak scene if exported by Blender
|
||||
auto& globalInfo = *scene->getGlobalInfo();
|
||||
if (StringAnsiView(globalInfo.AppName).StartsWith(StringAnsiView("Blender"), StringSearchCase::IgnoreCase))
|
||||
{
|
||||
auto ptr = const_cast<ofbx::GlobalSettings*>(scene->getGlobalSettings());
|
||||
ptr->UpAxis = (ofbx::UpVector)((int32)ptr->UpAxis + 1);
|
||||
}
|
||||
|
||||
// Process imported scene
|
||||
context = New<OpenFbxImporterData>(path, options, scene);
|
||||
auto& globalSettings = context->GlobalSettings;
|
||||
ProcessNodes(*context, scene->getRoot(), -1);
|
||||
|
||||
// Apply model scene global scale factor
|
||||
context->Nodes[0].LocalTransform = Transform(Vector3::Zero, Quaternion::Identity, globalSettings.UnitScaleFactor) * context->Nodes[0].LocalTransform;
|
||||
|
||||
// Log scene info
|
||||
LOG(Info, "Loaded FBX model, Frame Rate: {0}, Unit Scale Factor: {1}", context->FrameRate, globalSettings.UnitScaleFactor);
|
||||
LOG(Info, "{0}, {1}, {2}", String(globalInfo.AppName), String(globalInfo.AppVersion), String(globalInfo.AppVendor));
|
||||
LOG(Info, "Up: {1}{0}", globalSettings.UpAxis == ofbx::UpVector_AxisX ? TEXT("X") : globalSettings.UpAxis == ofbx::UpVector_AxisY ? TEXT("Y") : TEXT("Z"), globalSettings.UpAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "Front: {1}{0}", globalSettings.FrontAxis == ofbx::FrontVector_ParityEven ? TEXT("ParityEven") : TEXT("ParityOdd"), globalSettings.FrontAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "{0} Handed{1}", globalSettings.CoordAxis == ofbx::CoordSystem_RightHanded ? TEXT("Right") : TEXT("Left"), globalSettings.CoordAxisSign == 1 ? TEXT("") : TEXT(" (negative)"));
|
||||
#if OPEN_FBX_CONVERT_SPACE
|
||||
LOG(Info, "Imported scene: Up={0}, Front={1}, Right={2}", context->Up, context->Front, context->Right);
|
||||
#endif
|
||||
|
||||
// Extract embedded textures
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Textures))
|
||||
{
|
||||
String outputPath;
|
||||
for (int i = 0, c = scene->getEmbeddedDataCount(); i < c; i++)
|
||||
{
|
||||
const ofbx::DataView aEmbedded = scene->getEmbeddedData(i);
|
||||
ofbx::DataView aFilename = scene->getEmbeddedFilename(i);
|
||||
char filenameData[256];
|
||||
aFilename.toString(filenameData);
|
||||
if (outputPath.IsEmpty())
|
||||
{
|
||||
String pathStr(path);
|
||||
outputPath = String(StringUtils::GetDirectoryName(pathStr)) / TEXT("textures");
|
||||
FileSystem::CreateDirectory(outputPath);
|
||||
}
|
||||
const String filenameStr(filenameData);
|
||||
String embeddedPath = outputPath / StringUtils::GetFileName(filenameStr);
|
||||
if (FileSystem::FileExists(embeddedPath))
|
||||
continue;
|
||||
LOG(Info, "Extracing embedded resource to {0}", embeddedPath);
|
||||
if (File::WriteAllBytes(embeddedPath, aEmbedded.begin + 4, (int32)(aEmbedded.end - aEmbedded.begin - 4)))
|
||||
{
|
||||
LOG(Error, "Failed to write data to file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if OPEN_FBX_CONVERT_SPACE
|
||||
// Transform nodes to match the engine coordinates system - DirectX (UpVector = +Y, FrontVector = +Z, CoordSystem = -X (LeftHanded))
|
||||
if (context->Up == Float3(1, 0, 0) && context->Front == Float3(0, 0, 1) && context->Right == Float3(0, 1, 0))
|
||||
{
|
||||
context->RootConvertRotation = Quaternion::Euler(0, 180, 0);
|
||||
}
|
||||
else if (context->Up == Float3(0, 1, 0) && context->Front == Float3(-1, 0, 0) && context->Right == Float3(0, 0, 1))
|
||||
{
|
||||
context->RootConvertRotation = Quaternion::Euler(90, -90, 0);
|
||||
}
|
||||
/*Float3 engineUp(0, 1, 0);
|
||||
Float3 engineFront(0, 0, 1);
|
||||
Float3 engineRight(-1, 0, 0);*/
|
||||
/*Float3 engineUp(1, 0, 0);
|
||||
Float3 engineFront(0, 0, 1);
|
||||
Float3 engineRight(0, 1, 0);
|
||||
if (context->Up != engineUp || context->Front != engineFront || context->Right != engineRight)
|
||||
{
|
||||
LOG(Info, "Converting imported scene nodes to match engine coordinates system");
|
||||
context->RootConvertRotation = Quaternion::GetRotationFromTo(context->Up, engineUp, engineUp);
|
||||
//context->RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * context->Right, engineRight, engineRight);
|
||||
//context->RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * context->Front, engineFront, engineFront);
|
||||
}*/
|
||||
/*Float3 hackUp = FbxVectorFromAxisAndSign(globalSettings.UpAxis, globalSettings.UpAxisSign);
|
||||
if (hackUp == Float3::UnitX)
|
||||
context->RootConvertRotation = Quaternion::Euler(-90, 0, 0);
|
||||
else if (hackUp == Float3::UnitZ)
|
||||
context->RootConvertRotation = Quaternion::Euler(90, 0, 0);*/
|
||||
if (!context->RootConvertRotation.IsIdentity())
|
||||
{
|
||||
for (auto& node : context->Nodes)
|
||||
{
|
||||
if (node.ParentIndex == -1)
|
||||
{
|
||||
node.LocalTransform.Orientation = context->RootConvertRotation * node.LocalTransform.Orientation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
errorMsg = TEXT("Cannot load file.");
|
||||
return true;
|
||||
}
|
||||
DeleteMe<OpenFbxImporterData> contextCleanup(options.SplitContext ? nullptr : context);
|
||||
ofbx::u64 loadFlags = 0;
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry))
|
||||
{
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::TRIANGULATE;
|
||||
if (!options.ImportBlendShapes)
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
|
||||
}
|
||||
else
|
||||
{
|
||||
loadFlags |= (ofbx::u64)ofbx::LoadFlags::IGNORE_GEOMETRY | (ofbx::u64)ofbx::LoadFlags::IGNORE_BLEND_SHAPES;
|
||||
}
|
||||
ofbx::IScene* scene = ofbx::load(fileData.Get(), fileData.Count(), loadFlags);
|
||||
if (!scene)
|
||||
{
|
||||
errorMsg = ofbx::getError();
|
||||
return true;
|
||||
}
|
||||
fileData.Resize(0);
|
||||
|
||||
// Tweak scene if exported by Blender
|
||||
auto& globalInfo = *scene->getGlobalInfo();
|
||||
if (StringAnsiView(globalInfo.AppName).StartsWith(StringAnsiView("Blender"), StringSearchCase::IgnoreCase))
|
||||
{
|
||||
auto ptr = const_cast<ofbx::GlobalSettings*>(scene->getGlobalSettings());
|
||||
ptr->UpAxis = (ofbx::UpVector)((int32)ptr->UpAxis + 1);
|
||||
}
|
||||
|
||||
// Process imported scene
|
||||
OpenFbxImporterData context(path, options, scene);
|
||||
auto& globalSettings = context.GlobalSettings;
|
||||
ProcessNodes(context, scene->getRoot(), -1);
|
||||
|
||||
// Apply model scene global scale factor
|
||||
context.Nodes[0].LocalTransform = Transform(Vector3::Zero, Quaternion::Identity, globalSettings.UnitScaleFactor) * context.Nodes[0].LocalTransform;
|
||||
|
||||
// Log scene info
|
||||
LOG(Info, "Loaded FBX model, Frame Rate: {0}, Unit Scale Factor: {1}", context.FrameRate, globalSettings.UnitScaleFactor);
|
||||
LOG(Info, "{0}, {1}, {2}", String(globalInfo.AppName), String(globalInfo.AppVersion), String(globalInfo.AppVendor));
|
||||
LOG(Info, "Up: {1}{0}", globalSettings.UpAxis == ofbx::UpVector_AxisX ? TEXT("X") : globalSettings.UpAxis == ofbx::UpVector_AxisY ? TEXT("Y") : TEXT("Z"), globalSettings.UpAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "Front: {1}{0}", globalSettings.FrontAxis == ofbx::FrontVector_ParityEven ? TEXT("ParityEven") : TEXT("ParityOdd"), globalSettings.FrontAxisSign == 1 ? TEXT("+") : TEXT("-"));
|
||||
LOG(Info, "{0} Handed{1}", globalSettings.CoordAxis == ofbx::CoordSystem_RightHanded ? TEXT("Right") : TEXT("Left"), globalSettings.CoordAxisSign == 1 ? TEXT("") : TEXT(" (negative)"));
|
||||
#if OPEN_FBX_CONVERT_SPACE
|
||||
LOG(Info, "Imported scene: Up={0}, Front={1}, Right={2}", context.Up, context.Front, context.Right);
|
||||
#endif
|
||||
|
||||
// Extract embedded textures
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Textures))
|
||||
{
|
||||
String outputPath;
|
||||
for (int i = 0, c = scene->getEmbeddedDataCount(); i < c; i++)
|
||||
{
|
||||
const ofbx::DataView aEmbedded = scene->getEmbeddedData(i);
|
||||
ofbx::DataView aFilename = scene->getEmbeddedFilename(i);
|
||||
char filenameData[256];
|
||||
aFilename.toString(filenameData);
|
||||
if (outputPath.IsEmpty())
|
||||
{
|
||||
String pathStr(path);
|
||||
outputPath = String(StringUtils::GetDirectoryName(pathStr)) / TEXT("textures");
|
||||
FileSystem::CreateDirectory(outputPath);
|
||||
}
|
||||
const String filenameStr(filenameData);
|
||||
String embeddedPath = outputPath / StringUtils::GetFileName(filenameStr);
|
||||
if (FileSystem::FileExists(embeddedPath))
|
||||
continue;
|
||||
LOG(Info, "Extracing embedded resource to {0}", embeddedPath);
|
||||
if (File::WriteAllBytes(embeddedPath, aEmbedded.begin + 4, (int32)(aEmbedded.end - aEmbedded.begin - 4)))
|
||||
{
|
||||
LOG(Error, "Failed to write data to file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if OPEN_FBX_CONVERT_SPACE
|
||||
// Transform nodes to match the engine coordinates system - DirectX (UpVector = +Y, FrontVector = +Z, CoordSystem = -X (LeftHanded))
|
||||
if (context.Up == Float3(1, 0, 0) && context.Front == Float3(0, 0, 1) && context.Right == Float3(0, 1, 0))
|
||||
{
|
||||
context.RootConvertRotation = Quaternion::Euler(0, 180, 0);
|
||||
}
|
||||
else if (context.Up == Float3(0, 1, 0) && context.Front == Float3(-1, 0, 0) && context.Right == Float3(0, 0, 1))
|
||||
{
|
||||
context.RootConvertRotation = Quaternion::Euler(90, -90, 0);
|
||||
}
|
||||
/*Float3 engineUp(0, 1, 0);
|
||||
Float3 engineFront(0, 0, 1);
|
||||
Float3 engineRight(-1, 0, 0);*/
|
||||
/*Float3 engineUp(1, 0, 0);
|
||||
Float3 engineFront(0, 0, 1);
|
||||
Float3 engineRight(0, 1, 0);
|
||||
if (context.Up != engineUp || context.Front != engineFront || context.Right != engineRight)
|
||||
{
|
||||
LOG(Info, "Converting imported scene nodes to match engine coordinates system");
|
||||
context.RootConvertRotation = Quaternion::GetRotationFromTo(context.Up, engineUp, engineUp);
|
||||
//context.RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * context.Right, engineRight, engineRight);
|
||||
//context.RootConvertRotation *= Quaternion::GetRotationFromTo(rotation * context.Front, engineFront, engineFront);
|
||||
}*/
|
||||
/*Float3 hackUp = FbxVectorFromAxisAndSign(globalSettings.UpAxis, globalSettings.UpAxisSign);
|
||||
if (hackUp == Float3::UnitX)
|
||||
context.RootConvertRotation = Quaternion::Euler(-90, 0, 0);
|
||||
else if (hackUp == Float3::UnitZ)
|
||||
context.RootConvertRotation = Quaternion::Euler(90, 0, 0);*/
|
||||
if (!context.RootConvertRotation.IsIdentity())
|
||||
{
|
||||
for (auto& node : context.Nodes)
|
||||
{
|
||||
if (node.ParentIndex == -1)
|
||||
{
|
||||
node.LocalTransform.Orientation = context.RootConvertRotation * node.LocalTransform.Orientation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Build final skeleton bones hierarchy before importing meshes
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton))
|
||||
{
|
||||
if (ImportBones(*context, errorMsg))
|
||||
if (ImportBones(context, errorMsg))
|
||||
{
|
||||
LOG(Warning, "Failed to import skeleton bones.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Sorting::QuickSort(context->Bones.Get(), context->Bones.Count());
|
||||
Sorting::QuickSort(context.Bones.Get(), context.Bones.Count());
|
||||
}
|
||||
|
||||
// Import geometry (meshes and materials)
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry) && context->Scene->getMeshCount() > 0)
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Geometry) && context.Scene->getMeshCount() > 0)
|
||||
{
|
||||
const int meshCount = context->Scene->getMeshCount();
|
||||
if (options.SplitObjects && options.ObjectIndex == -1 && meshCount > 1)
|
||||
const int meshCount = context.Scene->getMeshCount();
|
||||
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
|
||||
{
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} meshes", meshCount);
|
||||
for (int32 i = 1; i < meshCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const auto aMesh = context->Scene->getMesh(i);
|
||||
const String objectName(aMesh->name);
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.ObjectIndex != -1)
|
||||
{
|
||||
// Import the selected mesh
|
||||
const auto meshIndex = Math::Clamp<int32>(options.ObjectIndex, 0, meshCount - 1);
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
if (ImportMesh(meshIndex, data, context, errorMsg))
|
||||
return true;
|
||||
|
||||
// Let the firstly imported mesh import all materials from all meshes (index 0 is importing all following ones before itself during splitting - see code above)
|
||||
if (options.ObjectIndex == 1)
|
||||
{
|
||||
for (int32 i = 0; i < meshCount; i++)
|
||||
{
|
||||
const auto aMesh = context->Scene->getMesh(i);
|
||||
if (i == 1 || IsMeshInvalid(aMesh))
|
||||
continue;
|
||||
for (int32 j = 0; j < aMesh->getMaterialCount(); j++)
|
||||
{
|
||||
const ofbx::Material* aMaterial = aMesh->getMaterial(j);
|
||||
context->AddMaterial(data, aMaterial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Import all meshes
|
||||
for (int32 meshIndex = 0; meshIndex < meshCount; meshIndex++)
|
||||
{
|
||||
if (ImportMesh(meshIndex, data, *context, errorMsg))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import skeleton
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton))
|
||||
{
|
||||
data.Skeleton.Nodes.Resize(context->Nodes.Count(), false);
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
data.Skeleton.Nodes.Resize(context.Nodes.Count(), false);
|
||||
for (int32 i = 0; i < context.Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Skeleton.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
auto& aNode = context.Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
node.LocalTransform = aNode.LocalTransform;
|
||||
}
|
||||
|
||||
data.Skeleton.Bones.Resize(context->Bones.Count(), false);
|
||||
for (int32 i = 0; i < context->Bones.Count(); i++)
|
||||
data.Skeleton.Bones.Resize(context.Bones.Count(), false);
|
||||
for (int32 i = 0; i < context.Bones.Count(); i++)
|
||||
{
|
||||
auto& bone = data.Skeleton.Bones[i];
|
||||
auto& aBone = context->Bones[i];
|
||||
auto& aBone = context.Bones[i];
|
||||
const auto boneNodeIndex = aBone.NodeIndex;
|
||||
|
||||
// Find the parent bone
|
||||
int32 parentBoneIndex = -1;
|
||||
for (int32 j = context->Nodes[boneNodeIndex].ParentIndex; j != -1; j = context->Nodes[j].ParentIndex)
|
||||
for (int32 j = context.Nodes[boneNodeIndex].ParentIndex; j != -1; j = context.Nodes[j].ParentIndex)
|
||||
{
|
||||
parentBoneIndex = context->FindBone(j);
|
||||
parentBoneIndex = context.FindBone(j);
|
||||
if (parentBoneIndex != -1)
|
||||
break;
|
||||
}
|
||||
aBone.ParentBoneIndex = parentBoneIndex;
|
||||
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : context->Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
const auto parentBoneNodeIndex = aBone.ParentBoneIndex == -1 ? -1 : context.Bones[aBone.ParentBoneIndex].NodeIndex;
|
||||
|
||||
bone.ParentIndex = aBone.ParentBoneIndex;
|
||||
bone.NodeIndex = aBone.NodeIndex;
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(context->Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.LocalTransform = CombineTransformsFromNodeIndices(context.Nodes, parentBoneNodeIndex, boneNodeIndex);
|
||||
bone.OffsetMatrix = aBone.OffsetMatrix;
|
||||
}
|
||||
}
|
||||
@@ -1345,54 +1296,21 @@ bool ModelTool::ImportDataOpenFBX(const char* path, ModelData& data, Options& op
|
||||
// Import animations
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Animations))
|
||||
{
|
||||
const int animCount = context->Scene->getAnimationStackCount();
|
||||
if (options.SplitObjects && options.ObjectIndex == -1 && animCount > 1)
|
||||
const int animCount = context.Scene->getAnimationStackCount();
|
||||
for (int32 animIndex = 0; animIndex < animCount; animIndex++)
|
||||
{
|
||||
// Import the first object within this call
|
||||
options.SplitObjects = false;
|
||||
options.ObjectIndex = 0;
|
||||
|
||||
if (options.OnSplitImport.IsBinded())
|
||||
{
|
||||
// Split all animations into separate assets
|
||||
LOG(Info, "Splitting imported {0} animations", animCount);
|
||||
for (int32 i = 1; i < animCount; i++)
|
||||
{
|
||||
auto splitOptions = options;
|
||||
splitOptions.ObjectIndex = i;
|
||||
splitOptions.SplitContext = context;
|
||||
const ofbx::AnimationStack* stack = context->Scene->getAnimationStack(i);
|
||||
const ofbx::AnimationLayer* layer = stack->getLayer(0);
|
||||
const String objectName(layer->name);
|
||||
options.OnSplitImport(splitOptions, objectName);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.ObjectIndex != -1)
|
||||
{
|
||||
// Import selected animation
|
||||
const auto animIndex = Math::Clamp<int32>(options.ObjectIndex, 0, animCount - 1);
|
||||
ImportAnimation(animIndex, data, *context);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Import first valid animation
|
||||
for (int32 animIndex = 0; animIndex < animCount; animIndex++)
|
||||
{
|
||||
if (!ImportAnimation(animIndex, data, *context))
|
||||
break;
|
||||
}
|
||||
ImportAnimation(animIndex, data, context);
|
||||
}
|
||||
}
|
||||
|
||||
// Import nodes
|
||||
if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Nodes))
|
||||
{
|
||||
data.Nodes.Resize(context->Nodes.Count());
|
||||
for (int32 i = 0; i < context->Nodes.Count(); i++)
|
||||
data.Nodes.Resize(context.Nodes.Count());
|
||||
for (int32 i = 0; i < context.Nodes.Count(); i++)
|
||||
{
|
||||
auto& node = data.Nodes[i];
|
||||
auto& aNode = context->Nodes[i];
|
||||
auto& aNode = context.Nodes[i];
|
||||
|
||||
node.Name = aNode.Name;
|
||||
node.ParentIndex = aNode.ParentIndex;
|
||||
|
||||
@@ -452,6 +452,9 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
|
||||
options.FramesRange.Y = Math::Max(options.FramesRange.Y, options.FramesRange.X);
|
||||
options.DefaultFrameRate = Math::Max(0.0f, options.DefaultFrameRate);
|
||||
options.SamplingRate = Math::Max(0.0f, options.SamplingRate);
|
||||
if (options.SplitObjects)
|
||||
options.MergeMeshes = false; // Meshes merging doesn't make sense when we want to import each mesh individually
|
||||
// TODO: maybe we could update meshes merger to collapse meshes within the same name if splitting is enabled?
|
||||
|
||||
// Validate path
|
||||
// Note: Assimp/Autodesk supports only ANSI characters in imported file path
|
||||
@@ -511,8 +514,6 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
|
||||
FileSystem::DeleteFile(tmpPath);
|
||||
}
|
||||
|
||||
// TODO: check model LODs sequence (eg. {LOD0, LOD2, LOD5} is invalid)
|
||||
|
||||
// Remove namespace prefixes from the nodes names
|
||||
{
|
||||
for (auto& node : data.Nodes)
|
||||
@@ -523,9 +524,10 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
|
||||
{
|
||||
RemoveNamespace(node.Name);
|
||||
}
|
||||
for (auto& channel : data.Animation.Channels)
|
||||
for (auto& animation : data.Animations)
|
||||
{
|
||||
RemoveNamespace(channel.NodeName);
|
||||
for (auto& channel : animation.Channels)
|
||||
RemoveNamespace(channel.NodeName);
|
||||
}
|
||||
for (auto& lod : data.LODs)
|
||||
{
|
||||
@@ -533,18 +535,19 @@ bool ModelTool::ImportData(const String& path, ModelData& data, Options& options
|
||||
{
|
||||
RemoveNamespace(mesh->Name);
|
||||
for (auto& blendShape : mesh->BlendShapes)
|
||||
{
|
||||
RemoveNamespace(blendShape.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the animation channels
|
||||
if (data.Animation.Channels.HasItems())
|
||||
for (auto& animation : data.Animations)
|
||||
{
|
||||
auto& channels = animation.Channels;
|
||||
if (channels.IsEmpty())
|
||||
continue;
|
||||
|
||||
// Validate bone animations uniqueness
|
||||
auto& channels = data.Animation.Channels;
|
||||
for (int32 i = 0; i < channels.Count(); i++)
|
||||
{
|
||||
for (int32 j = i + 1; j < channels.Count(); j++)
|
||||
@@ -742,7 +745,7 @@ void TrySetupMaterialParameter(MaterialInstance* instance, Span<const Char*> par
|
||||
{
|
||||
if (type == MaterialParameterType::Color)
|
||||
{
|
||||
if (paramType != MaterialParameterType::Vector3 ||
|
||||
if (paramType != MaterialParameterType::Vector3 ||
|
||||
paramType != MaterialParameterType::Vector4)
|
||||
continue;
|
||||
}
|
||||
@@ -757,7 +760,7 @@ void TrySetupMaterialParameter(MaterialInstance* instance, Span<const Char*> par
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput)
|
||||
bool ModelTool::ImportModel(const String& path, ModelData& data, Options& options, String& errorMsg, const String& autoImportOutput)
|
||||
{
|
||||
LOG(Info, "Importing model from \'{0}\'", path);
|
||||
const auto startTime = DateTime::NowUTC();
|
||||
@@ -785,7 +788,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
ModelData data;
|
||||
if (ImportData(path, data, options, errorMsg))
|
||||
return true;
|
||||
|
||||
@@ -926,13 +928,20 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
case ModelType::Animation:
|
||||
{
|
||||
// Validate
|
||||
if (data.Animation.Channels.IsEmpty())
|
||||
if (data.Animations.IsEmpty())
|
||||
{
|
||||
errorMsg = TEXT("Imported file has no valid animations.");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOG(Info, "Imported animation has {0} channels, duration: {1} frames, frames per second: {2}", data.Animation.Channels.Count(), data.Animation.Duration, data.Animation.FramesPerSecond);
|
||||
for (auto& animation : data.Animations)
|
||||
{
|
||||
LOG(Info, "Imported animation '{}' has {} channels, duration: {} frames, frames per second: {}", animation.Name, animation.Channels.Count(), animation.Duration, animation.FramesPerSecond);
|
||||
if (animation.Duration <= ZeroTolerance || animation.FramesPerSecond <= ZeroTolerance)
|
||||
{
|
||||
errorMsg = TEXT("Invalid animation duration.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -976,7 +985,6 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
case TextureEntry::TypeHint::Normals:
|
||||
textureOptions.Type = TextureFormatType::NormalMap;
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
AssetsImportingManager::ImportIfEdited(texture.FilePath, assetPath, texture.AssetID, &textureOptions);
|
||||
#endif
|
||||
@@ -1461,65 +1469,67 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
}
|
||||
else if (options.Type == ModelType::Animation)
|
||||
{
|
||||
// Trim the animation keyframes range if need to
|
||||
if (options.Duration == AnimationDuration::Custom)
|
||||
for (auto& animation : data.Animations)
|
||||
{
|
||||
// Custom animation import, frame index start and end
|
||||
const float start = options.FramesRange.X;
|
||||
const float end = options.FramesRange.Y;
|
||||
for (int32 i = 0; i < data.Animation.Channels.Count(); i++)
|
||||
// Trim the animation keyframes range if need to
|
||||
if (options.Duration == AnimationDuration::Custom)
|
||||
{
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
anim.Position.Trim(start, end);
|
||||
anim.Rotation.Trim(start, end);
|
||||
anim.Scale.Trim(start, end);
|
||||
}
|
||||
data.Animation.Duration = end - start;
|
||||
}
|
||||
|
||||
// Change the sampling rate if need to
|
||||
if (!Math::IsZero(options.SamplingRate))
|
||||
{
|
||||
const float timeScale = (float)(data.Animation.FramesPerSecond / options.SamplingRate);
|
||||
if (!Math::IsOne(timeScale))
|
||||
{
|
||||
data.Animation.FramesPerSecond = options.SamplingRate;
|
||||
for (int32 i = 0; i < data.Animation.Channels.Count(); i++)
|
||||
// Custom animation import, frame index start and end
|
||||
const float start = options.FramesRange.X;
|
||||
const float end = options.FramesRange.Y;
|
||||
for (int32 i = 0; i < animation.Channels.Count(); i++)
|
||||
{
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
auto& anim = animation.Channels[i];
|
||||
anim.Position.Trim(start, end);
|
||||
anim.Rotation.Trim(start, end);
|
||||
anim.Scale.Trim(start, end);
|
||||
}
|
||||
animation.Duration = end - start;
|
||||
}
|
||||
|
||||
anim.Position.TransformTime(timeScale, 0.0f);
|
||||
anim.Rotation.TransformTime(timeScale, 0.0f);
|
||||
anim.Scale.TransformTime(timeScale, 0.0f);
|
||||
// Change the sampling rate if need to
|
||||
if (!Math::IsZero(options.SamplingRate))
|
||||
{
|
||||
const float timeScale = (float)(animation.FramesPerSecond / options.SamplingRate);
|
||||
if (!Math::IsOne(timeScale))
|
||||
{
|
||||
animation.FramesPerSecond = options.SamplingRate;
|
||||
for (int32 i = 0; i < animation.Channels.Count(); i++)
|
||||
{
|
||||
auto& anim = animation.Channels[i];
|
||||
anim.Position.TransformTime(timeScale, 0.0f);
|
||||
anim.Rotation.TransformTime(timeScale, 0.0f);
|
||||
anim.Scale.TransformTime(timeScale, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize the keyframes
|
||||
if (options.OptimizeKeyframes)
|
||||
{
|
||||
const int32 before = data.Animation.GetKeyframesCount();
|
||||
for (int32 i = 0; i < data.Animation.Channels.Count(); i++)
|
||||
// Optimize the keyframes
|
||||
if (options.OptimizeKeyframes)
|
||||
{
|
||||
auto& anim = data.Animation.Channels[i];
|
||||
|
||||
// Optimize keyframes
|
||||
OptimizeCurve(anim.Position);
|
||||
OptimizeCurve(anim.Rotation);
|
||||
OptimizeCurve(anim.Scale);
|
||||
|
||||
// Remove empty channels
|
||||
if (anim.GetKeyframesCount() == 0)
|
||||
const int32 before = animation.GetKeyframesCount();
|
||||
for (int32 i = 0; i < animation.Channels.Count(); i++)
|
||||
{
|
||||
data.Animation.Channels.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
const int32 after = data.Animation.GetKeyframesCount();
|
||||
LOG(Info, "Optimized {0} animation keyframe(s). Before: {1}, after: {2}, Ratio: {3}%", before - after, before, after, Utilities::RoundTo2DecimalPlaces((float)after / before));
|
||||
}
|
||||
auto& anim = animation.Channels[i];
|
||||
|
||||
data.Animation.EnableRootMotion = options.EnableRootMotion;
|
||||
data.Animation.RootNodeName = options.RootNodeName;
|
||||
// Optimize keyframes
|
||||
OptimizeCurve(anim.Position);
|
||||
OptimizeCurve(anim.Rotation);
|
||||
OptimizeCurve(anim.Scale);
|
||||
|
||||
// Remove empty channels
|
||||
if (anim.GetKeyframesCount() == 0)
|
||||
{
|
||||
animation.Channels.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
const int32 after = animation.GetKeyframesCount();
|
||||
LOG(Info, "Optimized {0} animation keyframe(s). Before: {1}, after: {2}, Ratio: {3}%", before - after, before, after, Utilities::RoundTo2DecimalPlaces((float)after / before));
|
||||
}
|
||||
|
||||
animation.EnableRootMotion = options.EnableRootMotion;
|
||||
animation.RootNodeName = options.RootNodeName;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge meshes with the same parent nodes, material and skinning
|
||||
@@ -1696,27 +1706,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
}
|
||||
}
|
||||
|
||||
// Export imported data to the output container (we reduce vertex data copy operations to minimum)
|
||||
{
|
||||
meshData.Textures.Swap(data.Textures);
|
||||
meshData.Materials.Swap(data.Materials);
|
||||
meshData.LODs.Resize(data.LODs.Count(), false);
|
||||
for (int32 i = 0; i < data.LODs.Count(); i++)
|
||||
{
|
||||
auto& dst = meshData.LODs[i];
|
||||
auto& src = data.LODs[i];
|
||||
|
||||
dst.Meshes = src.Meshes;
|
||||
}
|
||||
meshData.Skeleton.Swap(data.Skeleton);
|
||||
meshData.Animation.Swap(data.Animation);
|
||||
|
||||
// Clear meshes from imported data (we link them to result model data). This reduces amount of allocations.
|
||||
data.LODs.Resize(0);
|
||||
}
|
||||
|
||||
// Calculate blend shapes vertices ranges
|
||||
for (auto& lod : meshData.LODs)
|
||||
for (auto& lod : data.LODs)
|
||||
{
|
||||
for (auto& mesh : lod.Meshes)
|
||||
{
|
||||
@@ -1737,6 +1728,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& meshData, Options& op
|
||||
}
|
||||
}
|
||||
|
||||
// Auto calculate LODs transition settings
|
||||
data.CalculateLODsScreenSizes();
|
||||
|
||||
const auto endTime = DateTime::NowUTC();
|
||||
LOG(Info, "Model file imported in {0} ms", static_cast<int32>((endTime - startTime).GetTotalMilliseconds()));
|
||||
|
||||
|
||||
@@ -296,13 +296,17 @@ public:
|
||||
|
||||
public: // Internals
|
||||
|
||||
// Runtime data for objects splitting during import (used internally)
|
||||
void* SplitContext = nullptr;
|
||||
Function<bool(Options& splitOptions, const String& objectName)> OnSplitImport;
|
||||
|
||||
// Internal flags for objects to import.
|
||||
ImportDataTypes ImportTypes = ImportDataTypes::None;
|
||||
|
||||
struct CachedData
|
||||
{
|
||||
ModelData* Data = nullptr;
|
||||
void* MeshesByName = nullptr;
|
||||
};
|
||||
// Cached model data - used when performing nested importing (eg. via objects splitting). Allows to read and process source file only once and use those results for creation of multiple assets (permutation via ObjectIndex).
|
||||
CachedData* Cached = nullptr;
|
||||
|
||||
public:
|
||||
// [ISerializable]
|
||||
void Serialize(SerializeStream& stream, const void* otherObj) override;
|
||||
@@ -324,12 +328,12 @@ public:
|
||||
/// Imports the model.
|
||||
/// </summary>
|
||||
/// <param name="path">The file path.</param>
|
||||
/// <param name="meshData">The output data.</param>
|
||||
/// <param name="data">The output data.</param>
|
||||
/// <param name="options">The import options.</param>
|
||||
/// <param name="errorMsg">The error message container.</param>
|
||||
/// <param name="autoImportOutput">The output folder for the additional imported data - optional. Used to auto-import textures and material assets.</param>
|
||||
/// <returns>True if fails, otherwise false.</returns>
|
||||
static bool ImportModel(const String& path, ModelData& meshData, Options& options, String& errorMsg, const String& autoImportOutput = String::Empty);
|
||||
static bool ImportModel(const String& path, ModelData& data, Options& options, String& errorMsg, const String& autoImportOutput = String::Empty);
|
||||
|
||||
public:
|
||||
static int32 DetectLodIndex(const String& nodeName);
|
||||
|
||||
Reference in New Issue
Block a user