From 9ad49976919e457dd63bb738c702e5f4311dcff2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 15 Apr 2024 19:27:28 +0200 Subject: [PATCH] Add automatic restoring Anim Event tracks when reimporting animation asset #2363 --- Source/Engine/Animations/AnimationData.h | 18 +++- .../Engine/ContentImporters/ImportModel.cpp | 101 ++++++++++-------- Source/Engine/Graphics/Models/ModelData.cpp | 14 ++- Source/Engine/Serialization/Stream.cpp | 7 ++ Source/Engine/Serialization/WriteStream.h | 1 + 5 files changed, 94 insertions(+), 47 deletions(-) diff --git a/Source/Engine/Animations/AnimationData.h b/Source/Engine/Animations/AnimationData.h index a1f7a7a81..c37396243 100644 --- a/Source/Engine/Animations/AnimationData.h +++ b/Source/Engine/Animations/AnimationData.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Types/String.h" +#include "Engine/Core/Types/Pair.h" #include "Engine/Core/Math/Transform.h" #include "Engine/Animations/Curve.h" @@ -68,6 +69,16 @@ public: uint64 GetMemoryUsage() const; }; +/// +/// Single track with events. +/// +struct EventAnimationData +{ + float Duration = 0.0f; + StringAnsi TypeName; + StringAnsi JsonData; +}; + /// /// Root Motion modes that can be applied by the animation. Used as flags for selective behavior. /// @@ -120,10 +131,15 @@ struct AnimationData String RootNodeName; /// - /// The per skeleton node animation channels. + /// The per-skeleton node animation channels. /// Array Channels; + /// + /// The animation event tracks. + /// + Array>> Events; + public: /// /// Gets the length of the animation (in seconds). diff --git a/Source/Engine/ContentImporters/ImportModel.cpp b/Source/Engine/ContentImporters/ImportModel.cpp index 4e48dc813..75a25b5fa 100644 --- a/Source/Engine/ContentImporters/ImportModel.cpp +++ b/Source/Engine/ContentImporters/ImportModel.cpp @@ -15,6 +15,7 @@ #include "Engine/Content/Storage/ContentStorageManager.h" #include "Engine/Content/Assets/Animation.h" #include "Engine/Content/Content.h" +#include "Engine/Animations/AnimEvent.h" #include "Engine/Level/Actors/EmptyActor.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/Prefabs/Prefab.h" @@ -141,48 +142,6 @@ void RepackMeshLightmapUVs(ModelData& data) } } -void TryRestoreMaterials(CreateAssetContext& context, ModelData& modelData) -{ - // Skip if file is missing - if (!FileSystem::FileExists(context.TargetAssetPath)) - return; - - // Try to load asset that gets reimported - AssetReference asset = Content::LoadAsync(context.TargetAssetPath); - if (asset == nullptr) - return; - if (asset->WaitForLoaded()) - return; - - // Get model object - ModelBase* model = nullptr; - if (asset.Get()->GetTypeName() == Model::TypeName) - { - model = ((Model*)asset.Get()); - } - else if (asset.Get()->GetTypeName() == SkinnedModel::TypeName) - { - model = ((SkinnedModel*)asset.Get()); - } - if (!model) - return; - - // Peek materials - for (int32 i = 0; i < modelData.Materials.Count(); i++) - { - auto& dstSlot = modelData.Materials[i]; - - if (model->MaterialSlots.Count() > i) - { - auto& srcSlot = model->MaterialSlots[i]; - - dstSlot.Name = srcSlot.Name; - dstSlot.ShadowsMode = srcSlot.ShadowsMode; - dstSlot.AssetID = srcSlot.Material.GetID(); - } - } -} - void SetupMaterialSlots(ModelData& data, const Array& materials) { Array materialSlotsTable; @@ -458,10 +417,62 @@ CreateAssetResult ImportModel::Import(CreateAssetContext& context) data = &dataThis; } - // Check if restore materials on model reimport - if (options.RestoreMaterialsOnReimport && data->Materials.HasItems()) + // Check if restore local changes on asset reimport + constexpr bool RestoreAnimEventsOnReimport = true; + const bool restoreMaterials = options.RestoreMaterialsOnReimport && data->Materials.HasItems(); + const bool restoreAnimEvents = RestoreAnimEventsOnReimport && options.Type == ModelTool::ModelType::Animation && data->Animations.HasItems(); + if ((restoreMaterials || restoreAnimEvents) && FileSystem::FileExists(context.TargetAssetPath)) { - TryRestoreMaterials(context, *data); + AssetReference asset = Content::LoadAsync(context.TargetAssetPath); + if (asset && !asset->WaitForLoaded()) + { + auto* model = ScriptingObject::Cast(asset); + auto* animation = ScriptingObject::Cast(asset); + if (restoreMaterials && model) + { + // Copy material settings + for (int32 i = 0; i < data->Materials.Count(); i++) + { + auto& dstSlot = data->Materials[i]; + if (model->MaterialSlots.Count() > i) + { + auto& srcSlot = model->MaterialSlots[i]; + dstSlot.Name = srcSlot.Name; + dstSlot.ShadowsMode = srcSlot.ShadowsMode; + dstSlot.AssetID = srcSlot.Material.GetID(); + } + } + } + if (restoreAnimEvents && animation) + { + // Copy anim event tracks + for (const auto& e : animation->Events) + { + auto& clone = data->Animations[0].Events.AddOne(); + clone.First = e.First; + const auto& eKeys = e.Second.GetKeyframes(); + auto& cloneKeys = clone.Second.GetKeyframes(); + clone.Second.Resize(eKeys.Count()); + for (int32 i = 0; i < eKeys.Count(); i++) + { + const auto& eKey = eKeys[i]; + auto& cloneKey = cloneKeys[i]; + cloneKey.Time = eKey.Time; + cloneKey.Value.Duration = eKey.Value.Duration; + if (eKey.Value.Instance) + { + cloneKey.Value.TypeName = eKey.Value.Instance->GetType().Fullname; + rapidjson_flax::StringBuffer buffer; + CompactJsonWriter writer(buffer); + writer.StartObject(); + eKey.Value.Instance->Serialize(writer, nullptr); + writer.EndObject(); + cloneKey.Value.JsonData.Set(buffer.GetString(), buffer.GetSize()); + } + } + } + } + } } // When using generated lightmap UVs those coordinates needs to be moved so all meshes are in unique locations in [0-1]x[0-1] coordinates space diff --git a/Source/Engine/Graphics/Models/ModelData.cpp b/Source/Engine/Graphics/Models/ModelData.cpp index abb97d794..dc9527b1d 100644 --- a/Source/Engine/Graphics/Models/ModelData.cpp +++ b/Source/Engine/Graphics/Models/ModelData.cpp @@ -941,7 +941,19 @@ bool ModelData::Pack2AnimationHeader(WriteStream* stream, int32 animIndex) const } // Animation events - stream->WriteInt32(0); + stream->WriteInt32(anim.Events.Count()); + for (auto& e : anim.Events) + { + stream->WriteString(e.First, 172); + stream->WriteInt32(e.Second.GetKeyframes().Count()); + for (const auto& k : e.Second.GetKeyframes()) + { + stream->WriteFloat(k.Time); + stream->WriteFloat(k.Value.Duration); + stream->WriteStringAnsi(k.Value.TypeName, 17); + stream->WriteJson(k.Value.JsonData); + } + } // Nested animations stream->WriteInt32(0); diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index 7d678fbc7..18060b62f 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -948,6 +948,13 @@ void WriteStream::WriteJson(ISerializable* obj, const void* otherObj) WriteInt32(0); } +void WriteStream::WriteJson(const StringAnsiView& json) +{ + WriteInt32(FLAXENGINE_VERSION_BUILD); + WriteInt32((int32)json.Length()); + WriteBytes((byte*)json.Get(), (int32)json.Length()); +} + void WriteStream::WriteString(const StringView& data) { Write(data); diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index e9edc44b0..028588ac4 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -233,6 +233,7 @@ public: /// The object to serialize. /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. void WriteJson(ISerializable* obj, const void* otherObj = nullptr); + void WriteJson(const StringAnsiView& json); public: // Writes String to the stream