diff --git a/Source/Editor/Managed/ManagedEditor.Internal.cpp b/Source/Editor/Managed/ManagedEditor.Internal.cpp
index 969608676..8855ba58a 100644
--- a/Source/Editor/Managed/ManagedEditor.Internal.cpp
+++ b/Source/Editor/Managed/ManagedEditor.Internal.cpp
@@ -769,7 +769,7 @@ bool ManagedEditor::TryRestoreImportOptions(ModelTool::Options& options, String
// Get options from model
FileSystem::NormalizePath(assetPath);
- return ImportModelFile::TryGetImportOptions(assetPath, options);
+ return ImportModel::TryGetImportOptions(assetPath, options);
}
bool ManagedEditor::Import(const String& inputPath, const String& outputPath, const AudioTool::Options& options)
diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h
index 623d9ea6c..c69de8afa 100644
--- a/Source/Engine/Animations/AnimationData.h
+++ b/Source/Engine/Animations/AnimationData.h
@@ -98,7 +98,6 @@ public:
///
struct AnimationData
{
-public:
///
/// The duration of the animation (in frames).
///
@@ -114,6 +113,11 @@ public:
///
bool EnableRootMotion = false;
+ ///
+ /// The animation name.
+ ///
+ String Name;
+
///
/// The custom node name to be used as a root motion source. If not specified the actual root node will be used.
///
@@ -131,14 +135,14 @@ public:
FORCE_INLINE float GetLength() const
{
#if BUILD_DEBUG
- ASSERT(FramesPerSecond != 0);
+ ASSERT(FramesPerSecond > ZeroTolerance);
#endif
return static_cast(Duration / FramesPerSecond);
}
uint64 GetMemoryUsage() const
{
- uint64 result = RootNodeName.Length() * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
+ uint64 result = (Name.Length() + RootNodeName.Length()) * sizeof(Char) + Channels.Capacity() * sizeof(NodeAnimationData);
for (const auto& e : Channels)
result += e.GetMemoryUsage();
return result;
@@ -164,6 +168,7 @@ public:
::Swap(Duration, other.Duration);
::Swap(FramesPerSecond, other.FramesPerSecond);
::Swap(EnableRootMotion, other.EnableRootMotion);
+ ::Swap(Name, other.Name);
::Swap(RootNodeName, other.RootNodeName);
Channels.Swap(other.Channels);
}
@@ -173,6 +178,7 @@ public:
///
void Dispose()
{
+ Name.Clear();
Duration = 0.0;
FramesPerSecond = 0.0;
RootNodeName.Clear();
diff --git a/Source/Engine/ContentImporters/AssetsImportingManager.cpp b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
index cb366ffb3..91c391711 100644
--- a/Source/Engine/ContentImporters/AssetsImportingManager.cpp
+++ b/Source/Engine/ContentImporters/AssetsImportingManager.cpp
@@ -15,7 +15,7 @@
#include "Engine/Platform/Platform.h"
#include "Engine/Engine/Globals.h"
#include "ImportTexture.h"
-#include "ImportModelFile.h"
+#include "ImportModel.h"
#include "ImportAudio.h"
#include "ImportShader.h"
#include "ImportFont.h"
@@ -425,37 +425,37 @@ bool AssetsImportingManagerService::Init()
{ TEXT("otf"), ASSET_FILES_EXTENSION, ImportFont::Import },
// Models
- { TEXT("obj"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("x"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("dae"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("glb"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
+ { TEXT("obj"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("fbx"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("x"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("dae"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("gltf"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("glb"), ASSET_FILES_EXTENSION, ImportModel::Import },
// gettext PO files
{ TEXT("po"), TEXT("json"), CreateJson::ImportPo },
// Models (untested formats - may fail :/)
- { TEXT("blend"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("ase"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("ply"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("nff"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("smd"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("vta"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("md2"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("md3"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("ac"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("stl"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("lws"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
- { TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModelFile::Import },
+ { TEXT("blend"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("bvh"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("ase"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("ply"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("dxf"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("ifc"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("nff"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("smd"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("vta"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("mdl"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("md2"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("md3"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("md5mesh"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("q3o"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("q3s"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("ac"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("stl"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("lwo"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("lws"), ASSET_FILES_EXTENSION, ImportModel::Import },
+ { TEXT("lxo"), ASSET_FILES_EXTENSION, ImportModel::Import },
};
AssetsImportingManager::Importers.Add(InBuildImporters, ARRAY_COUNT(InBuildImporters));
@@ -473,7 +473,7 @@ bool AssetsImportingManagerService::Init()
{ AssetsImportingManager::CreateMaterialInstanceTag, CreateMaterialInstance::Create },
// Models
- { AssetsImportingManager::CreateModelTag, ImportModelFile::Create },
+ { AssetsImportingManager::CreateModelTag, ImportModel::Create },
// Other
{ AssetsImportingManager::CreateRawDataTag, CreateRawData::Create },
diff --git a/Source/Engine/ContentImporters/ImportModelFile.cpp b/Source/Engine/ContentImporters/ImportModel.cpp
similarity index 51%
rename from Source/Engine/ContentImporters/ImportModelFile.cpp
rename to Source/Engine/ContentImporters/ImportModel.cpp
index 64a24217e..5aa44ecc4 100644
--- a/Source/Engine/ContentImporters/ImportModelFile.cpp
+++ b/Source/Engine/ContentImporters/ImportModel.cpp
@@ -5,6 +5,8 @@
#if COMPILE_WITH_ASSETS_IMPORTER
#include "Engine/Core/Log.h"
+#include "Engine/Core/Collections/Sorting.h"
+#include "Engine/Core/Collections/ArrayExtensions.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Graphics/Models/ModelData.h"
@@ -16,7 +18,7 @@
#include "Engine/Platform/FileSystem.h"
#include "AssetsImportingManager.h"
-bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
+bool ImportModel::TryGetImportOptions(const StringView& path, Options& options)
{
if (FileSystem::FileExists(path))
{
@@ -88,7 +90,32 @@ void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData)
}
}
-CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
+void SetupMaterialSlots(ModelData& data, const Array& materials)
+{
+ Array materialSlotsTable;
+ materialSlotsTable.Resize(materials.Count());
+ materialSlotsTable.SetAll(-1);
+ for (auto& lod : data.LODs)
+ {
+ for (MeshData* mesh : lod.Meshes)
+ {
+ int32 newSlotIndex = materialSlotsTable[mesh->MaterialSlotIndex];
+ if (newSlotIndex == -1)
+ {
+ newSlotIndex = data.Materials.Count();
+ data.Materials.AddOne() = materials[mesh->MaterialSlotIndex];
+ }
+ mesh->MaterialSlotIndex = newSlotIndex;
+ }
+ }
+}
+
+bool SortMeshGroups(IGrouping const& i1, IGrouping const& i2)
+{
+ return i1.GetKey().Compare(i2.GetKey()) < 0;
+}
+
+CreateAssetResult ImportModel::Import(CreateAssetContext& context)
{
// Get import options
Options options;
@@ -105,9 +132,50 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
LOG(Warning, "Missing model import options. Using default values.");
}
}
+
+ // Import model file
+ ModelData* data = options.Cached ? options.Cached->Data : nullptr;
+ ModelData dataThis;
+ Array>* meshesByNamePtr = options.Cached ? (Array>*)options.Cached->MeshesByName : nullptr;
+ Array> meshesByNameThis;
+ if (!data)
+ {
+ String errorMsg;
+ String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
+ autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
+ if (ModelTool::ImportModel(context.InputPath, dataThis, options, errorMsg, autoImportOutput))
+ {
+ LOG(Error, "Cannot import model file. {0}", errorMsg);
+ return CreateAssetResult::Error;
+ }
+ data = &dataThis;
+
+ // Group meshes by the name (the same mesh name can be used by multiple meshes that use different materials)
+ if (data->LODs.Count() != 0)
+ {
+ const Function f = [](MeshData* const& x) -> StringView
+ {
+ return x->Name;
+ };
+ ArrayExtensions::GroupBy(data->LODs[0].Meshes, f, meshesByNameThis);
+ Sorting::QuickSort(meshesByNameThis.Get(), meshesByNameThis.Count(), &SortMeshGroups);
+ }
+ meshesByNamePtr = &meshesByNameThis;
+ }
+ Array>& meshesByName = *meshesByNamePtr;
+
+ // Import objects from file separately
if (options.SplitObjects)
{
- options.OnSplitImport.Bind([&context](Options& splitOptions, const String& objectName)
+ // Import the first object within this call
+ options.SplitObjects = false;
+ options.ObjectIndex = 0;
+
+ // Import rest of the objects recursive but use current model data to skip loading file again
+ ModelTool::Options::CachedData cached = { data, (void*)meshesByNamePtr };
+ options.Cached = &cached;
+ Function splitImport;
+ splitImport.Bind([&context](Options& splitOptions, const StringView& objectName)
{
// Recursive importing of the split object
String postFix = objectName;
@@ -117,42 +185,132 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
const String outputPath = String(StringUtils::GetPathWithoutExtension(context.TargetAssetPath)) + TEXT(" ") + postFix + TEXT(".flax");
return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions);
});
+ auto splitOptions = options;
+ switch (options.Type)
+ {
+ case ModelTool::ModelType::Model:
+ case ModelTool::ModelType::SkinnedModel:
+ LOG(Info, "Splitting imported {0} meshes", meshesByName.Count());
+ for (int32 groupIndex = 1; groupIndex < meshesByName.Count(); groupIndex++)
+ {
+ auto& group = meshesByName[groupIndex];
+ splitOptions.ObjectIndex = groupIndex;
+ splitImport(splitOptions, group.GetKey());
+ }
+ break;
+ case ModelTool::ModelType::Animation:
+ LOG(Info, "Splitting imported {0} animations", data->Animations.Count());
+ for (int32 i = 1; i < data->Animations.Count(); i++)
+ {
+ auto& animation = data->Animations[i];
+ splitOptions.ObjectIndex = i;
+ splitImport(splitOptions, animation.Name);
+ }
+ break;
+ }
}
- // Import model file
- ModelData modelData;
- String errorMsg;
- String autoImportOutput(StringUtils::GetDirectoryName(context.TargetAssetPath));
- autoImportOutput /= options.SubAssetFolder.HasChars() ? options.SubAssetFolder.TrimTrailing() : String(StringUtils::GetFileNameWithoutExtension(context.InputPath));
- if (ModelTool::ImportModel(context.InputPath, modelData, options, errorMsg, autoImportOutput))
+ // When importing a single object as model asset then select a specific mesh group
+ Array meshesToDelete;
+ if (options.ObjectIndex >= 0 &&
+ options.ObjectIndex < meshesByName.Count() &&
+ (options.Type == ModelTool::ModelType::Model || options.Type == ModelTool::ModelType::SkinnedModel))
{
- LOG(Error, "Cannot import model file. {0}", errorMsg);
- return CreateAssetResult::Error;
+ auto& group = meshesByName[options.ObjectIndex];
+ if (&dataThis == data)
+ {
+ // Use meshes only from the the grouping (others will be removed manually)
+ {
+ auto& lod = dataThis.LODs[0];
+ meshesToDelete.Add(lod.Meshes);
+ lod.Meshes.Clear();
+ for (MeshData* mesh : group)
+ {
+ lod.Meshes.Add(mesh);
+ meshesToDelete.Remove(mesh);
+ }
+ }
+ for (int32 lodIndex = 1; lodIndex < dataThis.LODs.Count(); lodIndex++)
+ {
+ auto& lod = dataThis.LODs[lodIndex];
+ Array lodMeshes = lod.Meshes;
+ lod.Meshes.Clear();
+ for (MeshData* lodMesh : lodMeshes)
+ {
+ if (lodMesh->Name == group.GetKey())
+ lod.Meshes.Add(lodMesh);
+ else
+ meshesToDelete.Add(lodMesh);
+ }
+ }
+
+ // Use only materials references by meshes from the first grouping
+ {
+ auto materials = dataThis.Materials;
+ dataThis.Materials.Clear();
+ SetupMaterialSlots(dataThis, materials);
+ }
+ }
+ else
+ {
+ // Copy data from others data
+ dataThis.Skeleton = data->Skeleton;
+ dataThis.Nodes = data->Nodes;
+
+ // Move meshes from this group (including any LODs of them)
+ {
+ auto& lod = dataThis.LODs.AddOne();
+ lod.ScreenSize = data->LODs[0].ScreenSize;
+ lod.Meshes.Add(group);
+ for (MeshData* mesh : group)
+ data->LODs[0].Meshes.Remove(mesh);
+ }
+ for (int32 lodIndex = 1; lodIndex < data->LODs.Count(); lodIndex++)
+ {
+ Array lodMeshes = data->LODs[lodIndex].Meshes;
+ for (int32 i = lodMeshes.Count() - 1; i >= 0; i--)
+ {
+ MeshData* lodMesh = lodMeshes[i];
+ if (lodMesh->Name == group.GetKey())
+ data->LODs[lodIndex].Meshes.Remove(lodMesh);
+ else
+ lodMeshes.RemoveAtKeepOrder(i);
+ }
+ if (lodMeshes.Count() == 0)
+ break; // No meshes of that name in this LOD so skip further ones
+ auto& lod = dataThis.LODs.AddOne();
+ lod.ScreenSize = data->LODs[lodIndex].ScreenSize;
+ lod.Meshes.Add(lodMeshes);
+ }
+
+ // Copy materials used by the meshes
+ SetupMaterialSlots(dataThis, data->Materials);
+ }
+ data = &dataThis;
}
// Check if restore materials on model reimport
- if (options.RestoreMaterialsOnReimport && modelData.Materials.HasItems())
+ if (options.RestoreMaterialsOnReimport && data->Materials.HasItems())
{
- TryRestoreMaterials(context, modelData);
+ TryRestoreMaterials(context, *data);
}
- // Auto calculate LODs transition settings
- modelData.CalculateLODsScreenSizes();
-
// Create destination asset type
CreateAssetResult result = CreateAssetResult::InvalidTypeID;
switch (options.Type)
{
case ModelTool::ModelType::Model:
- result = ImportModel(context, modelData, &options);
+ result = CreateModel(context, *data, &options);
break;
case ModelTool::ModelType::SkinnedModel:
- result = ImportSkinnedModel(context, modelData, &options);
+ result = CreateSkinnedModel(context, *data, &options);
break;
case ModelTool::ModelType::Animation:
- result = ImportAnimation(context, modelData, &options);
+ result = CreateAnimation(context, *data, &options);
break;
}
+ for (auto mesh : meshesToDelete)
+ Delete(mesh);
if (result != CreateAssetResult::Ok)
return result;
@@ -172,7 +330,7 @@ CreateAssetResult ImportModelFile::Import(CreateAssetContext& context)
return CreateAssetResult::Ok;
}
-CreateAssetResult ImportModelFile::Create(CreateAssetContext& context)
+CreateAssetResult ImportModel::Create(CreateAssetContext& context)
{
ASSERT(context.CustomArg != nullptr);
auto& modelData = *(ModelData*)context.CustomArg;
@@ -187,13 +345,11 @@ CreateAssetResult ImportModelFile::Create(CreateAssetContext& context)
// Auto calculate LODs transition settings
modelData.CalculateLODsScreenSizes();
- // Import
- return ImportModel(context, modelData);
+ return CreateModel(context, modelData);
}
-CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
+CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
- // Base
IMPORT_SETUP(Model, Model::SerializedVersion);
// Save model header
@@ -242,9 +398,8 @@ CreateAssetResult ImportModelFile::ImportModel(CreateAssetContext& context, Mode
return CreateAssetResult::Ok;
}
-CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
+CreateAssetResult ImportModel::CreateSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
- // Base
IMPORT_SETUP(SkinnedModel, SkinnedModel::SerializedVersion);
// Save skinned model header
@@ -284,14 +439,14 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex
return CreateAssetResult::Ok;
}
-CreateAssetResult ImportModelFile::ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
+CreateAssetResult ImportModel::CreateAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options)
{
- // Base
IMPORT_SETUP(Animation, Animation::SerializedVersion);
// Save animation data
MemoryWriteStream stream(8182);
- if (modelData.Pack2AnimationHeader(&stream))
+ const int32 animIndex = options && options->ObjectIndex != -1 ? options->ObjectIndex : 0; // Single animation per asset
+ if (modelData.Pack2AnimationHeader(&stream, animIndex))
return CreateAssetResult::Error;
if (context.AllocateChunk(0))
return CreateAssetResult::CannotAllocateChunk;
diff --git a/Source/Engine/ContentImporters/ImportModel.h b/Source/Engine/ContentImporters/ImportModel.h
index b7af2789e..31a525852 100644
--- a/Source/Engine/ContentImporters/ImportModel.h
+++ b/Source/Engine/ContentImporters/ImportModel.h
@@ -11,7 +11,7 @@
///
/// Importing models utility
///
-class ImportModelFile
+class ImportModel
{
public:
typedef ModelTool::Options Options;
@@ -40,9 +40,9 @@ public:
static CreateAssetResult Create(CreateAssetContext& context);
private:
- static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
- static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
- static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData, const Options* options = nullptr);
+ static CreateAssetResult CreateModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
+ static CreateAssetResult CreateSkinnedModel(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
+ static CreateAssetResult CreateAnimation(CreateAssetContext& context, ModelData& data, const Options* options = nullptr);
};
#endif
diff --git a/Source/Engine/ContentImporters/ImportModelFile.h b/Source/Engine/ContentImporters/ImportModelFile.h
deleted file mode 100644
index a40109601..000000000
--- a/Source/Engine/ContentImporters/ImportModelFile.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
-
-#pragma once
-
-#include "Types.h"
-
-#if COMPILE_WITH_ASSETS_IMPORTER
-
-#include "Engine/Content/Assets/Model.h"
-#include "Engine/Tools/ModelTool/ModelTool.h"
-
-///
-/// Enable/disable caching model import options
-///
-#define IMPORT_MODEL_CACHE_OPTIONS 1
-
-///
-/// Importing models utility
-///
-class ImportModelFile
-{
-public:
- typedef ModelTool::Options Options;
-
-public:
- ///
- /// Tries the get model import options from the target location asset.
- ///
- /// The asset path.
- /// The options.
- /// True if success, otherwise false.
- static bool TryGetImportOptions(String path, Options& options);
-
- ///
- /// Imports the model file.
- ///
- /// The importing context.
- /// Result.
- static CreateAssetResult Import(CreateAssetContext& context);
-
- ///
- /// Creates the model asset from the ModelData storage (input argument should be pointer to ModelData).
- ///
- /// The importing context.
- /// Result.
- static CreateAssetResult Create(CreateAssetContext& context);
-
-private:
- static CreateAssetResult ImportModel(CreateAssetContext& context, ModelData& modelData);
- static CreateAssetResult ImportSkinnedModel(CreateAssetContext& context, ModelData& modelData);
- static CreateAssetResult ImportAnimation(CreateAssetContext& context, ModelData& modelData);
-};
-
-#endif
diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp
index 1d67f4737..3a31c5771 100644
--- a/Source/Engine/Graphics/Models/ModelData.cpp
+++ b/Source/Engine/Graphics/Models/ModelData.cpp
@@ -625,6 +625,11 @@ bool MaterialSlotEntry::UsesProperties() const
Normals.TextureIndex != -1;
}
+ModelLodData::~ModelLodData()
+{
+ Meshes.ClearDelete();
+}
+
BoundingBox ModelLodData::GetBox() const
{
if (Meshes.IsEmpty())
@@ -644,11 +649,9 @@ void ModelData::CalculateLODsScreenSizes()
{
const float autoComputeLodPowerBase = 0.5f;
const int32 lodCount = LODs.Count();
-
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
{
auto& lod = LODs[lodIndex];
-
if (lodIndex == 0)
{
lod.ScreenSize = 1.0f;
@@ -675,6 +678,8 @@ void ModelData::TransformBuffer(const Matrix& matrix)
}
}
+#if USE_EDITOR
+
bool ModelData::Pack2ModelHeader(WriteStream* stream) const
{
// Validate input
@@ -880,20 +885,21 @@ bool ModelData::Pack2SkinnedModelHeader(WriteStream* stream) const
return false;
}
-bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
+bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const
{
// Validate input
- if (stream == nullptr)
+ if (stream == nullptr || animIndex < 0 || animIndex >= Animations.Count())
{
Log::ArgumentNullException();
return true;
}
- if (Animation.Duration <= ZeroTolerance || Animation.FramesPerSecond <= ZeroTolerance)
+ auto& anim = Animations.Get()[animIndex];
+ if (anim.Duration <= ZeroTolerance || anim.FramesPerSecond <= ZeroTolerance)
{
Log::InvalidOperationException(TEXT("Invalid animation duration."));
return true;
}
- if (Animation.Channels.IsEmpty())
+ if (anim.Channels.IsEmpty())
{
Log::ArgumentOutOfRangeException(TEXT("Channels"), TEXT("Animation channels collection cannot be empty."));
return true;
@@ -901,22 +907,23 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream) const
// Info
stream->WriteInt32(100); // Header version (for fast version upgrades without serialization format change)
- stream->WriteDouble(Animation.Duration);
- stream->WriteDouble(Animation.FramesPerSecond);
- stream->WriteBool(Animation.EnableRootMotion);
- stream->WriteString(Animation.RootNodeName, 13);
+ stream->WriteDouble(anim.Duration);
+ stream->WriteDouble(anim.FramesPerSecond);
+ stream->WriteBool(anim.EnableRootMotion);
+ stream->WriteString(anim.RootNodeName, 13);
// Animation channels
- stream->WriteInt32(Animation.Channels.Count());
- for (int32 i = 0; i < Animation.Channels.Count(); i++)
+ stream->WriteInt32(anim.Channels.Count());
+ for (int32 i = 0; i < anim.Channels.Count(); i++)
{
- auto& anim = Animation.Channels[i];
-
- stream->WriteString(anim.NodeName, 172);
- Serialization::Serialize(*stream, anim.Position);
- Serialization::Serialize(*stream, anim.Rotation);
- Serialization::Serialize(*stream, anim.Scale);
+ auto& channel = anim.Channels[i];
+ stream->WriteString(channel.NodeName, 172);
+ Serialization::Serialize(*stream, channel.Position);
+ Serialization::Serialize(*stream, channel.Rotation);
+ Serialization::Serialize(*stream, channel.Scale);
}
return false;
}
+
+#endif
diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h
index 1516554fe..65bedf876 100644
--- a/Source/Engine/Graphics/Models/ModelData.h
+++ b/Source/Engine/Graphics/Models/ModelData.h
@@ -405,10 +405,7 @@ struct FLAXENGINE_API ModelLodData
///
/// Finalizes an instance of the class.
///
- ~ModelLodData()
- {
- Meshes.ClearDelete();
- }
+ ~ModelLodData();
///
/// Gets the bounding box combined for all meshes in this model LOD.
@@ -455,15 +452,7 @@ public:
///
/// The node animations.
///
- AnimationData Animation;
-
-public:
- ///
- /// Initializes a new instance of the class.
- ///
- ModelData()
- {
- }
+ Array Animations;
public:
///
@@ -494,6 +483,7 @@ public:
/// The matrix to use for the transformation.
void TransformBuffer(const Matrix& matrix);
+#if USE_EDITOR
public:
///
/// Pack mesh data to the header stream
@@ -513,6 +503,8 @@ public:
/// Pack animation data to the header stream
///
/// Output stream
+ /// Index of animation.
/// True if cannot save data, otherwise false
- bool Pack2AnimationHeader(WriteStream* stream) const;
+ bool Pack2AnimationHeader(WriteStream* stream, int32 animIndex = 0) const;
+#endif
};
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
index ffcce99fc..e62d03b1c 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp
@@ -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();
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);
}
@@ -739,222 +740,166 @@ void ImportCurve(aiQuatKey* keys, uint32 keysCount, LinearCurve& 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(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 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(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(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;
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
index b3e5ecdc8..a6e6225b1 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp
@@ -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& 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 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 fileData;
+ if (File::ReadAllBytes(String(path), fileData))
{
- // Import file
- Array 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(scene->getGlobalSettings());
- ptr->UpAxis = (ofbx::UpVector)((int32)ptr->UpAxis + 1);
- }
-
- // Process imported scene
- context = New(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 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(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(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(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;
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp
index 38f010e28..4d6438522 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.cpp
+++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp
@@ -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 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 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((endTime - startTime).GetTotalMilliseconds()));
diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h
index e3da7e2c3..cec9bdcf2 100644
--- a/Source/Engine/Tools/ModelTool/ModelTool.h
+++ b/Source/Engine/Tools/ModelTool/ModelTool.h
@@ -296,13 +296,17 @@ public:
public: // Internals
- // Runtime data for objects splitting during import (used internally)
- void* SplitContext = nullptr;
- Function 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.
///
/// The file path.
- /// The output data.
+ /// The output data.
/// The import options.
/// The error message container.
/// The output folder for the additional imported data - optional. Used to auto-import textures and material assets.
/// True if fails, otherwise false.
- 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);