From 9a363e2882b2cca89702ea77fea477200733e575 Mon Sep 17 00:00:00 2001 From: alsed Date: Sat, 30 Aug 2025 08:51:21 -0400 Subject: [PATCH 1/4] Implement prefab detection of skinned models --- .../Engine/ContentImporters/ImportModel.cpp | 40 ++++++++++++++----- Source/Engine/Tools/ModelTool/ModelTool.cpp | 23 +++++++---- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index be71236ab..a007a740d 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -19,6 +19,7 @@ #include "Engine/Animations/AnimEvent.h" #include "Engine/Level/Actors/EmptyActor.h" #include "Engine/Level/Actors/StaticModel.h" +#include "Engine/Level/Actors/AnimatedModel.h" #include "Engine/Level/Prefabs/Prefab.h" #include "Engine/Level/Prefabs/PrefabManager.h" #include "Engine/Level/Scripts/ModelPrefab.h" @@ -84,6 +85,7 @@ struct PrefabObject int32 NodeIndex; String Name; String AssetPath; + bool IsSkinned = false; }; void RepackMeshLightmapUVs(ModelData& data) @@ -320,8 +322,10 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) return AssetsImportingManager::Import(context.InputPath, outputPath, &splitOptions); }; + auto splitOptions = options; LOG(Info, "Splitting imported {0} meshes", meshesByName.Count()); + PrefabObject prefabObject; for (int32 groupIndex = 0; groupIndex < meshesByName.Count(); groupIndex++) { @@ -331,7 +335,17 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) prefabObject.NodeIndex = group.First()->NodeIndex; prefabObject.Name = group.First()->Name; + // Defaul value for ModelType splitOptions.Type = ModelTool::ModelType::Model; + + // Search for Skinned Model + if (group.First()->BlendShapes.HasItems()) + { + LOG(Info, "Mesh {0} is Skinned", prefabObject.Name); + splitOptions.Type = ModelTool::ModelType::SkinnedModel; + prefabObject.IsSkinned = true; + } + splitOptions.ObjectIndex = groupIndex; if (!splitImport(splitOptions, group.GetKey(), prefabObject.AssetPath, group.First())) { @@ -594,8 +608,8 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context) CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); - IMPORT_SETUP(Model, Model::SerializedVersion); - static_assert(Model::SerializedVersion == 30, "Update code."); + IMPORT_SETUP(Model, Model::SerializedVersion); + static_assert(Model::SerializedVersion == 30, "Update code."); // Save model header MemoryWriteStream stream(4096); @@ -722,22 +736,30 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M { if (e.NodeIndex == nodeIndex) { - auto* actor = New(); - actor->SetName(e.Name); - if (auto* model = Content::LoadAsync(e.AssetPath)) + if(e.IsSkinned) { - actor->Model = model; + LOG(Info,"Model {0} is Animated", e.Name); + auto* actor = New(); + actor->SetName(e.Name); + if (auto* skinnedModel = Content::LoadAsync(e.AssetPath)) + actor->SkinnedModel = skinnedModel; + nodeActors.Add(actor); + } + else + { + auto* actor = New(); + actor->SetName(e.Name); + if (auto* model = Content::LoadAsync(e.AssetPath)) + actor->Model = model; + nodeActors.Add(actor); } - nodeActors.Add(actor); } } Actor* nodeActor = nodeActors.Count() == 1 ? nodeActors[0] : New(); if (nodeActors.Count() > 1) { for (Actor* e : nodeActors) - { e->SetParent(nodeActor); - } } if (nodeActors.Count() != 1) { diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index fe268c350..cc483f333 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1131,7 +1131,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option options.ImportTypes |= ImportDataTypes::Skeleton; break; case ModelType::Prefab: - options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Animations; + options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Skeleton | ImportDataTypes::Animations; if (options.ImportMaterials) options.ImportTypes |= ImportDataTypes::Materials; if (options.ImportTextures) @@ -1157,6 +1157,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option { for (auto& mesh : lod.Meshes) { + if (mesh->BlendShapes.IsEmpty()) + continue; + for (int32 blendShapeIndex = mesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--) { auto& blendShape = mesh->BlendShapes[blendShapeIndex]; @@ -2111,12 +2114,13 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option #undef REMAP_VERTEX_BUFFER // Remap blend shapes - dstMesh->BlendShapes.Resize(srcMesh->BlendShapes.Count()); + dstMesh->BlendShapes.Clear(); + dstMesh->BlendShapes.EnsureCapacity(srcMesh->BlendShapes.Count(), false); for (int32 blendShapeIndex = 0; blendShapeIndex < srcMesh->BlendShapes.Count(); blendShapeIndex++) { const auto& srcBlendShape = srcMesh->BlendShapes[blendShapeIndex]; - auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex]; - + //auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex]; + BlendShape dstBlendShape; dstBlendShape.Name = srcBlendShape.Name; dstBlendShape.Weight = srcBlendShape.Weight; dstBlendShape.Vertices.EnsureCapacity(srcBlendShape.Vertices.Count()); @@ -2125,19 +2129,21 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option auto v = srcBlendShape.Vertices[i]; v.VertexIndex = remap[v.VertexIndex]; if (v.VertexIndex != ~0u) - { dstBlendShape.Vertices.Add(v); - } } + + if (dstBlendShape.Vertices.HasItems()) + dstMesh->BlendShapes.Add(dstBlendShape); } + /* // Remove empty blend shapes for (int32 blendShapeIndex = dstMesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--) { if (dstMesh->BlendShapes[blendShapeIndex].Vertices.IsEmpty()) dstMesh->BlendShapes.RemoveAt(blendShapeIndex); } - + */ // Optimize generated LOD meshopt_optimizeVertexCache(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, dstMeshVertexCount); meshopt_optimizeOverdraw(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, (const float*)dstMesh->Positions.Get(), dstMeshVertexCount, sizeof(Float3), 1.05f); @@ -2182,6 +2188,9 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option { for (auto& mesh : lod.Meshes) { + if (mesh->BlendShapes.IsEmpty()) + continue; + for (auto& blendShape : mesh->BlendShapes) { // Compute min/max for used vertex indices From a8768f918eb28c0d4e2d3a6da1fe0b57861c2490 Mon Sep 17 00:00:00 2001 From: alsed Date: Sun, 7 Sep 2025 13:45:21 -0300 Subject: [PATCH 2/4] Add more conditions for skeleton import and add blendshapes for prefab recognition --- Source/Engine/ContentImporters/ImportModel.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 2 +- Source/Engine/Tools/ModelTool/ModelTool.cpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index a007a740d..f1d83daa7 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -339,7 +339,7 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) splitOptions.Type = ModelTool::ModelType::Model; // Search for Skinned Model - if (group.First()->BlendShapes.HasItems()) + if (group.First()->BlendWeights.HasItems() || group.First()->BlendShapes.HasItems() ) { LOG(Info, "Mesh {0} is Skinned", prefabObject.Name); splitOptions.Type = ModelTool::ModelType::SkinnedModel; diff --git a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp index f5b043994..a61f19668 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.Assimp.cpp @@ -765,7 +765,7 @@ bool ModelTool::ImportDataAssimp(const String& path, ModelData& data, Options& o } // Import skeleton - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton)) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && context.Bones.HasItems()) { data.Skeleton.Nodes.Resize(context.Nodes.Count(), false); for (int32 i = 0; i < context.Nodes.Count(); i++) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index 81ce46d6c..e530e7b7c 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -1404,7 +1404,7 @@ bool ModelTool::ImportDataOpenFBX(const String& path, ModelData& data, Options& } // Import skeleton - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton)) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && context.Bones.HasItems()) { data.Skeleton.Nodes.Resize(context.Nodes.Count(), false); for (int32 i = 0; i < context.Nodes.Count(); i++) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index cc483f333..103797781 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1184,7 +1184,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton)) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && data.Skeleton.Bones.HasItems()) { LOG(Info, "Imported skeleton has {0} bones and {1} nodes", data.Skeleton.Bones.Count(), data.Nodes.Count()); @@ -1324,7 +1324,7 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option for (int32 i = 0; i < meshesCount; i++) { const auto mesh = data.LODs[0].Meshes[i]; - if (mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) + if ((mesh->BlendIndices.IsEmpty() || mesh->BlendWeights.IsEmpty()) && data.Skeleton.Bones.HasItems()) { auto indices = Int4::Zero; auto weights = Float4::UnitX; From 52ee8b395328cf6094ca0566f7d97dc0176dc780 Mon Sep 17 00:00:00 2001 From: alsed Date: Sun, 7 Sep 2025 20:40:28 -0300 Subject: [PATCH 3/4] better comment for animated prefab and remove unused code in remap blendshapes --- Source/Engine/ContentImporters/ImportModel.cpp | 6 +++--- Source/Engine/Tools/ModelTool/ModelTool.cpp | 11 +---------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index f1d83daa7..5a3a34735 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -608,8 +608,8 @@ CreateAssetResult ImportModel::Create(CreateAssetContext& context) CreateAssetResult ImportModel::CreateModel(CreateAssetContext& context, const ModelData& modelData, const Options* options) { PROFILE_CPU(); - IMPORT_SETUP(Model, Model::SerializedVersion); - static_assert(Model::SerializedVersion == 30, "Update code."); + IMPORT_SETUP(Model, Model::SerializedVersion); + static_assert(Model::SerializedVersion == 30, "Update code."); // Save model header MemoryWriteStream stream(4096); @@ -738,7 +738,7 @@ CreateAssetResult ImportModel::CreatePrefab(CreateAssetContext& context, const M { if(e.IsSkinned) { - LOG(Info,"Model {0} is Animated", e.Name); + LOG(Info,"Creating animated model prefab {0}.", e.Name); auto* actor = New(); actor->SetName(e.Name); if (auto* skinnedModel = Content::LoadAsync(e.AssetPath)) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index 103797781..ebd19e169 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -2114,12 +2114,10 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option #undef REMAP_VERTEX_BUFFER // Remap blend shapes - dstMesh->BlendShapes.Clear(); dstMesh->BlendShapes.EnsureCapacity(srcMesh->BlendShapes.Count(), false); for (int32 blendShapeIndex = 0; blendShapeIndex < srcMesh->BlendShapes.Count(); blendShapeIndex++) { const auto& srcBlendShape = srcMesh->BlendShapes[blendShapeIndex]; - //auto& dstBlendShape = dstMesh->BlendShapes[blendShapeIndex]; BlendShape dstBlendShape; dstBlendShape.Name = srcBlendShape.Name; dstBlendShape.Weight = srcBlendShape.Weight; @@ -2132,18 +2130,11 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option dstBlendShape.Vertices.Add(v); } + // Add only valid blend shapes if (dstBlendShape.Vertices.HasItems()) dstMesh->BlendShapes.Add(dstBlendShape); } - /* - // Remove empty blend shapes - for (int32 blendShapeIndex = dstMesh->BlendShapes.Count() - 1; blendShapeIndex >= 0; blendShapeIndex--) - { - if (dstMesh->BlendShapes[blendShapeIndex].Vertices.IsEmpty()) - dstMesh->BlendShapes.RemoveAt(blendShapeIndex); - } - */ // Optimize generated LOD meshopt_optimizeVertexCache(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, dstMeshVertexCount); meshopt_optimizeOverdraw(dstMesh->Indices.Get(), dstMesh->Indices.Get(), dstMeshIndexCount, (const float*)dstMesh->Positions.Get(), dstMeshVertexCount, sizeof(Float3), 1.05f); From 05f08db66eacad3b28bc4d0764ecbb29e0e424b3 Mon Sep 17 00:00:00 2001 From: alsed Date: Mon, 8 Sep 2025 01:28:49 -0300 Subject: [PATCH 4/4] Fixed a crash when model with no Bones was imported as Skinned Model --- Source/Engine/Tools/ModelTool/ModelTool.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.cpp b/Source/Engine/Tools/ModelTool/ModelTool.cpp index ebd19e169..6820532bb 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.cpp @@ -1119,6 +1119,12 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option options.ImportTypes |= ImportDataTypes::Textures; break; case ModelType::SkinnedModel: + if (!data.Skeleton.Bones.HasItems()) + { + LOG(Warning, "Model is not Skinned, it will be imported as Static"); + options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes; + options.Type = ModelType::Model; + } options.ImportTypes = ImportDataTypes::Geometry | ImportDataTypes::Nodes | ImportDataTypes::Skeleton; if (options.ImportMaterials) options.ImportTypes |= ImportDataTypes::Materials; @@ -1184,7 +1190,8 @@ bool ModelTool::ImportModel(const String& path, ModelData& data, Options& option } } } - if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) && data.Skeleton.Bones.HasItems()) + if (EnumHasAnyFlags(options.ImportTypes, ImportDataTypes::Skeleton) + && (data.Skeleton.Bones.HasItems() || data.LODs[0].Meshes[0]->BlendShapes.HasItems())) { LOG(Info, "Imported skeleton has {0} bones and {1} nodes", data.Skeleton.Bones.Count(), data.Nodes.Count());