Add support for animating LocalizedString value in Scene Animation
This commit is contained in:
@@ -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 />
|
||||
|
||||
@@ -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() },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user