Add support for animating LocalizedString value in Scene Animation

This commit is contained in:
Wojtek Figat
2024-03-08 13:10:36 +01:00
parent 41cff47ca3
commit f4a352ec8d
5 changed files with 175 additions and 61 deletions

View File

@@ -7,6 +7,8 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using FlaxEditor.GUI.Timeline.Undo;
using FlaxEditor.Scripting;
using FlaxEditor.Utilities;
using FlaxEngine;
using FlaxEngine.GUI;
using FlaxEngine.Utilities;
@@ -56,7 +58,6 @@ namespace FlaxEditor.GUI.Timeline.Tracks
throw new Exception("Invalid track data.");
var keyframes = new KeyframesEditor.Keyframe[keyframesCount];
var dataBuffer = new byte[e.ValueSize];
var propertyType = TypeUtils.GetManagedType(e.MemberTypeName);
if (propertyType == null)
{
@@ -67,20 +68,40 @@ namespace FlaxEditor.GUI.Timeline.Tracks
return;
}
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
for (int i = 0; i < keyframesCount; i++)
if (e.ValueSize != 0)
{
var time = stream.ReadSingle();
stream.Read(dataBuffer, 0, e.ValueSize);
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
keyframes[i] = new KeyframesEditor.Keyframe
// POD value type - use raw memory
var dataBuffer = new byte[e.ValueSize];
GCHandle handle = GCHandle.Alloc(dataBuffer, GCHandleType.Pinned);
for (int i = 0; i < keyframesCount; i++)
{
Time = time,
Value = value,
};
var time = stream.ReadSingle();
stream.Read(dataBuffer, 0, e.ValueSize);
var value = Marshal.PtrToStructure(handle.AddrOfPinnedObject(), propertyType);
keyframes[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = value,
};
}
handle.Free();
}
else
{
// Generic value - use Json storage (as UTF-8)
for (int i = 0; i < keyframesCount; i++)
{
var time = stream.ReadSingle();
var len = stream.ReadInt32();
var value = len != 0 ? FlaxEngine.Json.JsonSerializer.Deserialize(Encoding.UTF8.GetString(stream.ReadBytes(len)), propertyType) : null;
keyframes[i] = new KeyframesEditor.Keyframe
{
Time = time,
Value = value,
};
}
}
handle.Free();
e.Keyframes.DefaultValue = e.GetDefaultValue(propertyType);
e.Keyframes.SetKeyframes(keyframes);
@@ -113,17 +134,35 @@ namespace FlaxEditor.GUI.Timeline.Tracks
stream.Write(propertyTypeNameData);
stream.Write('\0');
var dataBuffer = new byte[e.ValueSize];
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
for (int i = 0; i < keyframes.Count; i++)
if (e.ValueSize != 0)
{
var keyframe = keyframes[i];
Marshal.StructureToPtr(keyframe.Value, ptr, true);
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
stream.Write(keyframe.Time);
stream.Write(dataBuffer);
// POD value type - use raw memory
var dataBuffer = new byte[e.ValueSize];
IntPtr ptr = Marshal.AllocHGlobal(e.ValueSize);
for (int i = 0; i < keyframes.Count; i++)
{
var keyframe = keyframes[i];
Marshal.StructureToPtr(keyframe.Value, ptr, true);
Marshal.Copy(ptr, dataBuffer, 0, e.ValueSize);
stream.Write(keyframe.Time);
stream.Write(dataBuffer);
}
Marshal.FreeHGlobal(ptr);
}
else
{
// Generic value - use Json storage (as UTF-8)
for (int i = 0; i < keyframes.Count; i++)
{
var keyframe = keyframes[i];
stream.Write(keyframe.Time);
var json = keyframe.Value != null ? FlaxEngine.Json.JsonSerializer.Serialize(keyframe.Value) : null;
var len = json?.Length ?? 0;
stream.Write(len);
if (len > 0)
stream.Write(Encoding.UTF8.GetBytes(json));
}
}
Marshal.FreeHGlobal(ptr);
}
private byte[] _keyframesEditingStartData;
@@ -281,7 +320,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
private void OnKeyframesEditingEnd()
{
var after = EditTrackAction.CaptureData(this);
if (!Utils.ArraysEqual(_keyframesEditingStartData, after))
if (!FlaxEngine.Utils.ArraysEqual(_keyframesEditingStartData, after))
Timeline.AddBatchedUndoAction(new EditTrackAction(Timeline, this, _keyframesEditingStartData, after));
_keyframesEditingStartData = null;
}
@@ -308,7 +347,10 @@ namespace FlaxEditor.GUI.Timeline.Tracks
/// <returns>The default value.</returns>
protected virtual object GetDefaultValue(Type propertyType)
{
return Activator.CreateInstance(propertyType);
var value = TypeUtils.GetDefaultValue(new ScriptType(propertyType));
if (value == null)
value = Activator.CreateInstance(propertyType);
return value;
}
/// <inheritdoc />

View File

@@ -424,6 +424,7 @@ namespace FlaxEditor.GUI.Timeline.Tracks
{ typeof(Guid), KeyframesPropertyTrack.GetArchetype() },
{ typeof(DateTime), KeyframesPropertyTrack.GetArchetype() },
{ typeof(TimeSpan), KeyframesPropertyTrack.GetArchetype() },
{ typeof(LocalizedString), KeyframesPropertyTrack.GetArchetype() },
{ typeof(string), StringPropertyTrack.GetArchetype() },
};
}

View File

@@ -281,10 +281,26 @@ Asset::LoadResult SceneAnimation::load()
track.TrackStateIndex = TrackStatesCount++;
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize);
if (trackData->ValueSize == 0)
{
// When using json data (from non-POD types) read the sum of all keyframes data
const int32 keyframesDataStart = stream.GetPosition();
for (int32 j = 0; j < trackData->KeyframesCount; j++)
{
stream.Move<float>(); // Time
int32 jsonLen;
stream.ReadInt32(&jsonLen);
stream.Move(jsonLen);
}
const int32 keyframesDataEnd = stream.GetPosition();
stream.SetPosition(keyframesDataStart);
keyframesDataSize = keyframesDataEnd - keyframesDataStart;
}
trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
trackRuntime->KeyframesSize = keyframesDataSize;
needsParent = true;
break;
}
@@ -298,6 +314,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
const int32 keyframesDataSize = trackData->KeyframesCount * (sizeof(float) + trackData->ValueSize * 3);
ASSERT(trackData->ValueSize > 0);
trackRuntime->ValueSize = trackData->ValueSize;
trackRuntime->KeyframesCount = trackData->KeyframesCount;
trackRuntime->Keyframes = stream.Move(keyframesDataSize);
@@ -375,6 +392,7 @@ Asset::LoadResult SceneAnimation::load()
trackRuntime->PropertyName = stream.Move<char>(trackData->PropertyNameLength + 1);
trackRuntime->PropertyTypeName = stream.Move<char>(trackData->PropertyTypeNameLength + 1);
trackRuntime->ValueSize = trackData->ValueSize;
ASSERT(trackData->ValueSize > 0);
trackRuntime->KeyframesCount = trackData->KeyframesCount;
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackData->KeyframesCount);

View File

@@ -289,6 +289,7 @@ public:
/// The keyframes array (items count is KeyframesCount). Each keyframe is represented by pair of time (of type float) and the value data (of size ValueSize).
/// </summary>
void* Keyframes;
int32 KeyframesSize;
};
};

View File

@@ -7,6 +7,7 @@
#include "Engine/Level/SceneObjectsFactory.h"
#include "Engine/Level/Actors/Camera.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Audio/AudioClip.h"
#include "Engine/Audio/AudioSource.h"
#include "Engine/Graphics/RenderTask.h"
@@ -19,6 +20,7 @@
#include "Engine/Scripting/ManagedCLR/MField.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Scripting/ManagedCLR/MMethod.h"
#include "Engine/Scripting/Internal/ManagedSerialization.h"
// This could be Update, LateUpdate or FixedUpdate
#define UPDATE_POINT Update
@@ -370,47 +372,96 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
case SceneAnimation::Track::Types::KeyframesProperty:
case SceneAnimation::Track::Types::ObjectReferenceProperty:
{
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
const int32 count = trackDataKeyframes->KeyframesCount;
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::KeyframesPropertyTrack::Runtime>();
const int32 count = trackRuntime->KeyframesCount;
if (count == 0)
return false;
// Find the keyframe at time
int32 keyframeSize = sizeof(float) + trackDataKeyframes->ValueSize;
#define GET_KEY_TIME(idx) *(float*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (idx))
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
int32 start = 0;
int32 searchLength = count;
while (searchLength > 0)
// If size is 0 then track uses Json storage for keyframes data (variable memory length of keyframes), otherwise it's optimized simple data with O(1) access
if (trackRuntime->ValueSize != 0)
{
const int32 half = searchLength >> 1;
int32 mid = start + half;
if (keyTime < GET_KEY_TIME(mid))
// Find the keyframe at time (binary search)
int32 keyframeSize = sizeof(float) + trackRuntime->ValueSize;
#define GET_KEY_TIME(idx) *(float*)((byte*)trackRuntime->Keyframes + keyframeSize * (idx))
const float keyTime = Math::Clamp(time, 0.0f, GET_KEY_TIME(count - 1));
int32 start = 0;
int32 searchLength = count;
while (searchLength > 0)
{
searchLength = half;
const int32 half = searchLength >> 1;
int32 mid = start + half;
if (keyTime < GET_KEY_TIME(mid))
{
searchLength = half;
}
else
{
start = mid + 1;
searchLength -= half + 1;
}
}
int32 leftKey = Math::Max(0, start - 1);
#undef GET_KEY_TIME
// Return the value
void* value = (void*)((byte*)trackRuntime->Keyframes + keyframeSize * (leftKey) + sizeof(float));
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
{
// Object ref track uses Guid for object Id storage
Guid id = *(Guid*)value;
_objectsMapping.TryGet(id, id);
auto obj = Scripting::FindObject<ScriptingObject>(id);
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
*(void**)target = value;
}
else
{
start = mid + 1;
searchLength -= half + 1;
// POD memory
Platform::MemoryCopy(target, value, trackRuntime->ValueSize);
}
}
int32 leftKey = Math::Max(0, start - 1);
#undef GET_KEY_TIME
// Return the value
void* value = (void*)((byte*)trackDataKeyframes->Keyframes + keyframeSize * (leftKey) + sizeof(float));
if (track.Type == SceneAnimation::Track::Types::ObjectReferenceProperty)
{
Guid id = *(Guid*)value;
_objectsMapping.TryGet(id, id);
auto obj = Scripting::FindObject<ScriptingObject>(id);
value = obj ? obj->GetOrCreateManagedInstance() : nullptr;
*(void**)target = value;
}
else
{
Platform::MemoryCopy(target, value, trackDataKeyframes->ValueSize);
// Clear pointer
*(void**)target = nullptr;
// Find the keyframe at time (linear search)
MemoryReadStream stream((byte*)trackRuntime->Keyframes, trackRuntime->KeyframesSize);
int32 prevKeyPos = sizeof(float);
int32 jsonLen;
for (int32 key = 0; key < count; key++)
{
float keyTime;
stream.ReadFloat(&keyTime);
if (keyTime > time)
break;
prevKeyPos = stream.GetPosition();
stream.ReadInt32(&jsonLen);
stream.Move(jsonLen);
}
// Read json text
stream.SetPosition(prevKeyPos);
stream.ReadInt32(&jsonLen);
const StringAnsiView json((const char*)stream.GetPositionHandle(), jsonLen);
// Create empty value of the keyframe type
const auto trackData = track.GetData<SceneAnimation::KeyframesPropertyTrack::Data>();
const StringAnsiView propertyTypeName(trackRuntime->PropertyTypeName, trackData->PropertyTypeNameLength);
MClass* klass = Scripting::FindClass(propertyTypeName);
if (!klass)
return false;
MObject* obj = MCore::Object::New(klass);
if (!obj)
return false;
if (!klass->IsValueType())
MCore::Object::Init(obj);
// Deserialize value from json
ManagedSerialization::Deserialize(json, obj);
// Set value
*(void**)target = obj;
}
break;
}
@@ -479,13 +530,13 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
}
case SceneAnimation::Track::Types::StringProperty:
{
const auto trackDataKeyframes = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
const int32 count = trackDataKeyframes->KeyframesCount;
const auto trackRuntime = track.GetRuntimeData<SceneAnimation::StringPropertyTrack::Runtime>();
const int32 count = trackRuntime->KeyframesCount;
if (count == 0)
return false;
const auto keyframesTimes = (float*)((byte*)trackDataKeyframes + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackDataKeyframes->KeyframesCount);
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackDataKeyframes->KeyframesCount);
const auto keyframesTimes = (float*)((byte*)trackRuntime + sizeof(SceneAnimation::StringPropertyTrack::Runtime));
const auto keyframesLengths = (int32*)((byte*)keyframesTimes + sizeof(float) * trackRuntime->KeyframesCount);
const auto keyframesValues = (Char**)((byte*)keyframesLengths + sizeof(int32) * trackRuntime->KeyframesCount);
// Find the keyframe at time
#define GET_KEY_TIME(idx) keyframesTimes[idx]
@@ -522,7 +573,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
auto& childTrack = anim->Tracks[childTrackIndex];
if (childTrack.Disabled || childTrack.ParentIndex != trackIndex)
continue;
const auto childTrackRuntimeData = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
const auto childTrackRuntime = childTrack.GetRuntimeData<SceneAnimation::PropertyTrack::Runtime>();
auto& childTrackState = _tracks[stateIndexOffset + childTrack.TrackStateIndex];
// Cache field
@@ -532,7 +583,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
if (!type)
continue;
MClass* mclass = MCore::Type::GetClass(type);
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
childTrackState.Field = mclass->GetField(childTrackRuntime->PropertyName);
if (!childTrackState.Field)
continue;
}
@@ -956,7 +1007,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
{
// Set the value
if (MCore::Type::IsPointer(valueType))
auto valueTypes = MCore::Type::GetType(valueType);
if (valueTypes == MTypes::Object || MCore::Type::IsPointer(valueType))
value = (void*)*(intptr*)value;
if (state.Property)
{