Add automatic restoring Anim Event tracks when reimporting animation asset
#2363
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user