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