Add automatic restoring Anim Event tracks when reimporting animation asset

#2363
This commit is contained in:
Wojtek Figat
2024-04-15 19:27:28 +02:00
parent 37a02e3a7e
commit 9ad4997691
5 changed files with 94 additions and 47 deletions

View File

@@ -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;
};
/// <summary>
/// Single track with events.
/// </summary>
struct EventAnimationData
{
float Duration = 0.0f;
StringAnsi TypeName;
StringAnsi JsonData;
};
/// <summary>
/// Root Motion modes that can be applied by the animation. Used as flags for selective behavior.
/// </summary>
@@ -120,10 +131,15 @@ struct AnimationData
String RootNodeName;
/// <summary>
/// The per skeleton node animation channels.
/// The per-skeleton node animation channels.
/// </summary>
Array<NodeAnimationData> Channels;
/// <summary>
/// The animation event tracks.
/// </summary>
Array<Pair<String, StepCurve<EventAnimationData>>> Events;
public:
/// <summary>
/// Gets the length of the animation (in seconds).

View File

@@ -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> asset = Content::LoadAsync<Asset>(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<MaterialSlotEntry>& materials)
{
Array<int32> 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> asset = Content::LoadAsync<Asset>(context.TargetAssetPath);
if (asset && !asset->WaitForLoaded())
{
auto* model = ScriptingObject::Cast<ModelBase>(asset);
auto* animation = ScriptingObject::Cast<Animation>(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

View File

@@ -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);

View File

@@ -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);

View File

@@ -233,6 +233,7 @@ public:
/// <param name="obj">The object to serialize.</param>
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
void WriteJson(const StringAnsiView& json);
public:
// Writes String to the stream