Files
FlaxEngine/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
2023-07-02 15:46:29 +02:00

848 lines
28 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_MODEL_TOOL && USE_ASSIMP
#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"
#include "Engine/Tools/TextureTool/TextureTool.h"
// Import Assimp library
// Source: https://github.com/assimp/assimp
#define ASSIMP_BUILD_NO_EXPORT
#include <ThirdParty/assimp/Importer.hpp>
#include <ThirdParty/assimp/types.h>
#include <ThirdParty/assimp/config.h>
#include <ThirdParty/assimp/scene.h>
#include <ThirdParty/assimp/version.h>
#include <ThirdParty/assimp/postprocess.h>
#include <ThirdParty/assimp/LogStream.hpp>
#include <ThirdParty/assimp/DefaultLogger.hpp>
#include <ThirdParty/assimp/Logger.hpp>
using namespace Assimp;
class AssimpLogStream : public LogStream
{
public:
AssimpLogStream()
{
DefaultLogger::create("");
DefaultLogger::get()->attachStream(this);
}
~AssimpLogStream()
{
DefaultLogger::get()->detatchStream(this);
DefaultLogger::kill();
}
void write(const char* message) override
{
String s(message);
s.Replace('\n', ' ');
LOG(Info, "[Assimp]: {0}", s);
}
};
Float2 ToFloat2(const aiVector2D& v)
{
return Float2(v.x, v.y);
}
Float2 ToFloat2(const aiVector3D& v)
{
return Float2(v.x, v.y);
}
Float3 ToFloat3(const aiVector3D& v)
{
return Float3(v.x, v.y, v.z);
}
Color ToColor(const aiColor3D& v)
{
return Color(v.r, v.g, v.b, 1.0f);
}
Color ToColor(const aiColor4D& v)
{
return Color(v.r, v.g, v.b, v.a);
}
Quaternion ToQuaternion(const aiQuaternion& v)
{
return Quaternion(v.x, v.y, v.z, v.w);
}
Matrix ToMatrix(const aiMatrix4x4& mat)
{
return Matrix(mat.a1, mat.b1, mat.c1, mat.d1,
mat.a2, mat.b2, mat.c2, mat.d2,
mat.a3, mat.b3, mat.c3, mat.d3,
mat.a4, mat.b4, mat.c4, mat.d4);
}
struct AssimpNode
{
/// <summary>
/// The parent index. The root node uses value -1.
/// </summary>
int32 ParentIndex;
/// <summary>
/// The local transformation of the bone, relative to parent bone.
/// </summary>
Transform LocalTransform;
/// <summary>
/// The name of this bone.
/// </summary>
String Name;
/// <summary>
/// The LOD index of the data in this node (used to separate meshes across different level of details).
/// </summary>
int32 LodIndex;
};
struct AssimpBone
{
/// <summary>
/// The index of the related node.
/// </summary>
int32 NodeIndex;
/// <summary>
/// The parent bone index. The root bone uses value -1.
/// </summary>
int32 ParentBoneIndex;
/// <summary>
/// The name of this bone.
/// </summary>
String Name;
/// <summary>
/// The matrix that transforms from mesh space to bone space in bind pose.
/// </summary>
Matrix OffsetMatrix;
bool operator<(const AssimpBone& other) const
{
return NodeIndex < other.NodeIndex;
}
};
struct AssimpImporterData
{
Importer AssimpImporter;
AssimpLogStream AssimpLogStream;
const String Path;
const aiScene* Scene = nullptr;
const ModelTool::Options& Options;
Array<AssimpNode> Nodes;
Array<AssimpBone> Bones;
Dictionary<int32, Array<int32>> MeshIndexToNodeIndex;
AssimpImporterData(const char* path, const ModelTool::Options& options)
: Path(path)
, Options(options)
{
}
int32 FindNode(const String& name, StringSearchCase caseSensitivity = StringSearchCase::CaseSensitive)
{
for (int32 i = 0; i < Nodes.Count(); i++)
{
if (Nodes[i].Name.Compare(name, caseSensitivity) == 0)
return i;
}
return -1;
}
int32 FindBone(const String& name, StringSearchCase caseSensitivity = StringSearchCase::CaseSensitive)
{
for (int32 i = 0; i < Bones.Count(); i++)
{
if (Bones[i].Name.Compare(name, caseSensitivity) == 0)
return i;
}
return -1;
}
int32 FindBone(const int32 nodeIndex)
{
for (int32 i = 0; i < Bones.Count(); i++)
{
if (Bones[i].NodeIndex == nodeIndex)
return i;
}
return -1;
}
};
void ProcessNodes(AssimpImporterData& data, aiNode* aNode, int32 parentIndex)
{
const int32 nodeIndex = data.Nodes.Count();
// Assign the index of the node to the index of the mesh
for (unsigned i = 0; i < aNode->mNumMeshes; i++)
{
int meshIndex = aNode->mMeshes[i];
data.MeshIndexToNodeIndex[meshIndex].Add(nodeIndex);
}
// Create node
AssimpNode node;
node.ParentIndex = parentIndex;
node.Name = aNode->mName.C_Str();
// Pick node LOD index
if (parentIndex == -1 || !data.Options.ImportLODs)
{
node.LodIndex = 0;
}
else
{
node.LodIndex = data.Nodes[parentIndex].LodIndex;
if (node.LodIndex == 0)
{
node.LodIndex = ModelTool::DetectLodIndex(node.Name);
}
ASSERT(Math::IsInRange(node.LodIndex, 0, MODEL_MAX_LODS - 1));
}
Matrix transform = ToMatrix(aNode->mTransformation);
transform.Decompose(node.LocalTransform);
data.Nodes.Add(node);
// Process the children
for (unsigned i = 0; i < aNode->mNumChildren; i++)
{
ProcessNodes(data, aNode->mChildren[i], nodeIndex);
}
}
bool ProcessMesh(ImportedModelData& result, AssimpImporterData& data, const aiMesh* aMesh, MeshData& mesh, String& errorMsg)
{
// Properties
mesh.Name = aMesh->mName.C_Str();
mesh.MaterialSlotIndex = aMesh->mMaterialIndex;
// Vertex positions
mesh.Positions.Set((const Float3*)aMesh->mVertices, aMesh->mNumVertices);
// Texture coordinates
if (aMesh->mTextureCoords[0])
{
mesh.UVs.Resize(aMesh->mNumVertices, false);
aiVector3D* a = aMesh->mTextureCoords[0];
for (uint32 v = 0; v < aMesh->mNumVertices; v++)
{
mesh.UVs[v] = *(Float2*)a;
a++;
}
}
// Indices
const int32 indicesCount = aMesh->mNumFaces * 3;
mesh.Indices.Resize(indicesCount, false);
for (unsigned faceIndex = 0, i = 0; faceIndex < aMesh->mNumFaces; faceIndex++)
{
const auto face = &aMesh->mFaces[faceIndex];
if (face->mNumIndices != 3)
{
errorMsg = TEXT("All faces in a mesh must be trangles!");
return true;
}
mesh.Indices[i++] = face->mIndices[0];
mesh.Indices[i++] = face->mIndices[1];
mesh.Indices[i++] = face->mIndices[2];
}
// Normals
if (data.Options.CalculateNormals || !aMesh->mNormals)
{
// Support generation of normals when using assimp.
if (mesh.GenerateNormals(data.Options.SmoothingNormalsAngle))
{
errorMsg = TEXT("Failed to generate normals.");
return true;
}
}
else if (aMesh->mNormals)
{
mesh.Normals.Set((const Float3*)aMesh->mNormals, aMesh->mNumVertices);
}
// Tangents
if (aMesh->mTangents)
{
mesh.Tangents.Set((const Float3*)aMesh->mTangents, aMesh->mNumVertices);
}
// Lightmap UVs
if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Disable)
{
// No lightmap UVs
}
else if (data.Options.LightmapUVsSource == ModelLightmapUVsSource::Generate)
{
// Generate lightmap UVs
if (mesh.GenerateLightmapUVs())
{
LOG(Error, "Failed to generate lightmap uvs");
}
}
else
{
// Select input channel index
int32 inputChannelIndex;
switch (data.Options.LightmapUVsSource)
{
case ModelLightmapUVsSource::Channel0:
inputChannelIndex = 0;
break;
case ModelLightmapUVsSource::Channel1:
inputChannelIndex = 1;
break;
case ModelLightmapUVsSource::Channel2:
inputChannelIndex = 2;
break;
case ModelLightmapUVsSource::Channel3:
inputChannelIndex = 3;
break;
default:
inputChannelIndex = INVALID_INDEX;
break;
}
// Check if has that channel texcoords
if (inputChannelIndex >= 0 && inputChannelIndex < AI_MAX_NUMBER_OF_TEXTURECOORDS && aMesh->mTextureCoords[inputChannelIndex])
{
mesh.LightmapUVs.Resize(aMesh->mNumVertices, false);
aiVector3D* a = aMesh->mTextureCoords[inputChannelIndex];
for (uint32 v = 0; v < aMesh->mNumVertices; v++)
{
mesh.LightmapUVs[v] = *(Float2*)a;
a++;
}
}
else
{
LOG(Warning, "Cannot import result lightmap uvs. Missing texcoords channel {0}.", inputChannelIndex);
}
}
// Vertex Colors
if (data.Options.ImportVertexColors && aMesh->mColors[0])
{
mesh.Colors.Resize(aMesh->mNumVertices, false);
aiColor4D* a = aMesh->mColors[0];
for (uint32 v = 0; v < aMesh->mNumVertices; v++)
{
mesh.Colors[v] = *(Color*)a;
a++;
}
}
// Blend Indices and Blend Weights
if (aMesh->mNumBones > 0 && aMesh->mBones && EnumHasAnyFlags(result.Types, ImportDataTypes::Skeleton))
{
const int32 vertexCount = mesh.Positions.Count();
mesh.BlendIndices.Resize(vertexCount);
mesh.BlendWeights.Resize(vertexCount);
mesh.BlendIndices.SetAll(Int4::Zero);
mesh.BlendWeights.SetAll(Float4::Zero);
// Build skinning clusters and fill controls points data structure
for (unsigned boneId = 0; boneId < aMesh->mNumBones; boneId++)
{
const auto aBone = aMesh->mBones[boneId];
// Find the node where the bone is mapped - based on the name
const String boneName(aBone->mName.C_Str());
const int32 nodeIndex = data.FindNode(boneName);
if (nodeIndex == -1)
{
LOG(Warning, "Invalid mesh bone linkage. Mesh: {0}, bone: {1}. Skipping...", mesh.Name, boneName);
continue;
}
// Create bone if missing
int32 boneIndex = data.FindBone(boneName);
if (boneIndex == -1)
{
// Find the parent bone
int32 parentBoneIndex = -1;
for (int32 i = nodeIndex; i != -1; i = data.Nodes[i].ParentIndex)
{
parentBoneIndex = data.FindBone(i);
if (parentBoneIndex != -1)
break;
}
// Add bone
boneIndex = data.Bones.Count();
data.Bones.EnsureCapacity(Math::Max(128, boneIndex + 16));
data.Bones.Resize(boneIndex + 1);
auto& bone = data.Bones[boneIndex];
// Setup bone
bone.Name = boneName;
bone.NodeIndex = nodeIndex;
bone.ParentBoneIndex = parentBoneIndex;
bone.OffsetMatrix = ToMatrix(aBone->mOffsetMatrix);
}
// Apply the bone influences
for (unsigned vtxWeightId = 0; vtxWeightId < aBone->mNumWeights; vtxWeightId++)
{
const auto vtxWeight = aBone->mWeights[vtxWeightId];
if (vtxWeight.mWeight <= 0 || vtxWeight.mVertexId >= (unsigned)vertexCount)
continue;
auto& indices = mesh.BlendIndices[vtxWeight.mVertexId];
auto& weights = mesh.BlendWeights[vtxWeight.mVertexId];
for (int32 k = 0; k < 4; k++)
{
if (vtxWeight.mWeight >= weights.Raw[k])
{
for (int32 l = 2; l >= k; l--)
{
indices.Raw[l + 1] = indices.Raw[l];
weights.Raw[l + 1] = weights.Raw[l];
}
indices.Raw[k] = boneIndex;
weights.Raw[k] = vtxWeight.mWeight;
break;
}
}
}
}
mesh.NormalizeBlendWeights();
}
// Blend Shapes
if (aMesh->mNumAnimMeshes > 0 && EnumHasAnyFlags(result.Types, ImportDataTypes::Skeleton) && data.Options.ImportBlendShapes)
{
mesh.BlendShapes.EnsureCapacity(aMesh->mNumAnimMeshes);
for (unsigned int animMeshIndex = 0; animMeshIndex < aMesh->mNumAnimMeshes; animMeshIndex++)
{
const aiAnimMesh* aAnimMesh = aMesh->mAnimMeshes[animMeshIndex];
BlendShape& blendShapeData = mesh.BlendShapes.AddOne();
blendShapeData.Name = aAnimMesh->mName.C_Str();
if (blendShapeData.Name.IsEmpty())
blendShapeData.Name = mesh.Name + TEXT("_blend_shape_") + StringUtils::ToString(animMeshIndex);
blendShapeData.Weight = aAnimMesh->mWeight;
blendShapeData.Vertices.Resize(aAnimMesh->mNumVertices);
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
blendShapeData.Vertices[i].VertexIndex = i;
const aiVector3D* shapeVertices = aAnimMesh->mVertices;
if (shapeVertices)
{
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
blendShapeData.Vertices[i].PositionDelta = ToFloat3(shapeVertices[i]) - mesh.Positions[i];
}
else
{
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
blendShapeData.Vertices[i].PositionDelta = Float3::Zero;
}
const aiVector3D* shapeNormals = aAnimMesh->mNormals;
if (shapeNormals)
{
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
blendShapeData.Vertices[i].NormalDelta = ToFloat3(shapeNormals[i]) - mesh.Normals[i];
}
else
{
for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++)
blendShapeData.Vertices[i].NormalDelta = Float3::Zero;
}
}
}
return false;
}
bool ImportTexture(ImportedModelData& result, AssimpImporterData& data, aiString& aFilename, int32& textureIndex, TextureEntry::TypeHint type)
{
// Find texture file path
const String filename = String(aFilename.C_Str()).TrimTrailing();
String path;
if (ModelTool::FindTexture(data.Path, filename, path))
return true;
// Check if already used
textureIndex = 0;
while (textureIndex < result.Textures.Count())
{
if (result.Textures[textureIndex].FilePath == path)
return true;
textureIndex++;
}
// Import texture
auto& texture = result.Textures.AddOne();
texture.FilePath = path;
texture.Type = type;
texture.AssetID = Guid::Empty;
return true;
}
bool ImportMaterialTexture(ImportedModelData& result, AssimpImporterData& data, const aiMaterial* aMaterial, aiTextureType aTextureType, int32& textureIndex, TextureEntry::TypeHint type)
{
aiString aFilename;
return aMaterial->GetTexture(aTextureType, 0, &aFilename, nullptr, nullptr, nullptr, nullptr) == AI_SUCCESS &&
ImportTexture(result, data, aFilename, textureIndex, type);
}
bool ImportMaterials(ImportedModelData& result, AssimpImporterData& data, String& errorMsg)
{
const uint32 materialsCount = data.Scene->mNumMaterials;
result.Materials.Resize(materialsCount, false);
for (uint32 i = 0; i < materialsCount; i++)
{
auto& materialSlot = result.Materials[i];
const aiMaterial* aMaterial = data.Scene->mMaterials[i];
aiString aName;
if (aMaterial->Get(AI_MATKEY_NAME, aName) == AI_SUCCESS)
materialSlot.Name = String(aName.C_Str()).TrimTrailing();
materialSlot.AssetID = Guid::Empty;
if (EnumHasAnyFlags(result.Types, ImportDataTypes::Materials))
{
aiColor3D aColor;
if (aMaterial->Get(AI_MATKEY_COLOR_DIFFUSE, aColor) == AI_SUCCESS)
materialSlot.Diffuse.Color = ToColor(aColor);
bool aBoolean;
if (aMaterial->Get(AI_MATKEY_TWOSIDED, aBoolean) == AI_SUCCESS)
materialSlot.TwoSided = aBoolean;
bool aFloat;
if (aMaterial->Get(AI_MATKEY_OPACITY, aFloat) == AI_SUCCESS)
materialSlot.Opacity.Value = aFloat;
if (EnumHasAnyFlags(result.Types, ImportDataTypes::Textures))
{
ImportMaterialTexture(result, data, aMaterial, aiTextureType_DIFFUSE, materialSlot.Diffuse.TextureIndex, TextureEntry::TypeHint::ColorRGB);
ImportMaterialTexture(result, data, aMaterial, aiTextureType_EMISSIVE, materialSlot.Emissive.TextureIndex, TextureEntry::TypeHint::ColorRGB);
ImportMaterialTexture(result, data, aMaterial, aiTextureType_NORMALS, materialSlot.Normals.TextureIndex, TextureEntry::TypeHint::Normals);
ImportMaterialTexture(result, data, aMaterial, aiTextureType_OPACITY, materialSlot.Opacity.TextureIndex, TextureEntry::TypeHint::ColorRGBA);
if (materialSlot.Diffuse.TextureIndex != -1)
{
// Detect using alpha mask in diffuse texture
materialSlot.Diffuse.HasAlphaMask = TextureTool::HasAlpha(result.Textures[materialSlot.Diffuse.TextureIndex].FilePath);
if (materialSlot.Diffuse.HasAlphaMask)
result.Textures[materialSlot.Diffuse.TextureIndex].Type = TextureEntry::TypeHint::ColorRGBA;
}
}
}
}
return false;
}
bool ImportMesh(int32 i, ImportedModelData& result, AssimpImporterData& data, String& errorMsg)
{
const auto aMesh = data.Scene->mMeshes[i];
// Skip invalid meshes
if (aMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE || aMesh->mNumVertices == 0 || aMesh->mNumFaces == 0 || aMesh->mFaces[0].mNumIndices != 3)
return false;
// Skip unused meshes
if (!data.MeshIndexToNodeIndex.ContainsKey(i))
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];
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)
{
meshData = New<MeshData>(*meshData);
}
// Link mesh
meshData->NodeIndex = nodeIndex;
if (result.LODs.Count() <= lodIndex)
result.LODs.Resize(lodIndex + 1);
result.LODs[lodIndex].Meshes.Add(meshData);
}
return false;
}
void ImportCurve(aiVectorKey* keys, uint32 keysCount, LinearCurve<Float3>& curve)
{
if (keys == nullptr || keysCount == 0)
return;
const auto keyframes = curve.Resize(keysCount);
for (uint32 i = 0; i < keysCount; i++)
{
auto& aKey = keys[i];
auto& key = keyframes[i];
key.Time = (float)aKey.mTime;
key.Value = ToFloat3(aKey.mValue);
}
}
void ImportCurve(aiQuatKey* keys, uint32 keysCount, LinearCurve<Quaternion>& curve)
{
if (keys == nullptr || keysCount == 0)
return;
const auto keyframes = curve.Resize(keysCount);
for (uint32 i = 0; i < keysCount; i++)
{
auto& aKey = keys[i];
auto& key = keyframes[i];
key.Time = (float)aKey.mTime;
key.Value = ToQuaternion(aKey.mValue);
}
}
bool ModelTool::ImportDataAssimp(const char* path, ImportedModelData& data, Options& options, String& errorMsg)
{
auto context = (AssimpImporterData*)options.SplitContext;
if (!context)
{
static bool AssimpInited = false;
if (!AssimpInited)
{
AssimpInited = true;
LOG(Info, "Assimp {0}.{1}.{2}", aiGetVersionMajor(), aiGetVersionMinor(), aiGetVersionRevision());
}
bool importMeshes = EnumHasAnyFlags(data.Types, ImportDataTypes::Geometry);
bool importAnimations = EnumHasAnyFlags(data.Types, 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;
}
// Process imported scene nodes
ProcessNodes(*context, context->Scene->mRootNode, -1);
}
DeleteMe<AssimpImporterData> contextCleanup(options.SplitContext ? nullptr : context);
// Import materials
if (ImportMaterials(data, *context, errorMsg))
{
LOG(Warning, "Failed to import materials.");
return true;
}
// Import geometry
if (EnumHasAnyFlags(data.Types, ImportDataTypes::Geometry) && context->Scene->HasMeshes())
{
const int meshCount = context->Scene->mNumMeshes;
if (options.SplitObjects && options.ObjectIndex == -1)
{
// Import the first object within this call
options.SplitObjects = false;
options.ObjectIndex = 0;
if (meshCount > 1 && 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))
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(data.Types, ImportDataTypes::Skeleton))
{
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];
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++)
{
auto& bone = data.Skeleton.Bones[i];
auto& aBone = context->Bones[i];
const auto boneNodeIndex = aBone.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.OffsetMatrix = aBone.OffsetMatrix;
}
}
// Import animations
if (EnumHasAnyFlags(data.Types, ImportDataTypes::Animations) && context->Scene->HasAnimations())
{
const int32 animCount = (int32)context->Scene->mNumAnimations;
if (options.SplitObjects && options.ObjectIndex == -1)
{
// Import the first object within this call
options.SplitObjects = false;
options.ObjectIndex = 0;
if (animCount > 1 && 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 != 0.0 ? animations->mTicksPerSecond : 25.0;
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);
}
}
}
// Import nodes
if (EnumHasAnyFlags(data.Types, ImportDataTypes::Nodes))
{
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];
node.Name = aNode.Name;
node.ParentIndex = aNode.ParentIndex;
node.LocalTransform = aNode.LocalTransform;
}
}
return false;
}
#endif