Merge remote-tracking branch 'origin/1.6'
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "AnimEvent.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/ManagedSerialization.h"
|
||||
#include "Engine/Scripting/Internal/ManagedSerialization.h"
|
||||
#include "Engine/Serialization/SerializationFwd.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
@@ -44,6 +45,7 @@ namespace FlaxEngine
|
||||
/// The node evaluation context structure.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[NativeMarshalling(typeof(ContextMarshaller))]
|
||||
public struct Context
|
||||
{
|
||||
/// <summary>
|
||||
@@ -92,6 +94,61 @@ namespace FlaxEngine
|
||||
public AnimatedModel Instance;
|
||||
}
|
||||
|
||||
[CustomMarshaller(typeof(Context), MarshalMode.Default, typeof(ContextMarshaller))]
|
||||
internal static class ContextMarshaller
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct ContextNative
|
||||
{
|
||||
public IntPtr Graph;
|
||||
public IntPtr GraphExecutor;
|
||||
public IntPtr Node;
|
||||
public uint NodeId;
|
||||
public int BoxId;
|
||||
public float DeltaTime;
|
||||
public ulong CurrentFrameIndex;
|
||||
public IntPtr BaseModel;
|
||||
public IntPtr Instance;
|
||||
}
|
||||
|
||||
internal static Context ConvertToManaged(ContextNative unmanaged) => ToManaged(unmanaged);
|
||||
internal static ContextNative ConvertToUnmanaged(Context managed) => ToNative(managed);
|
||||
|
||||
internal static Context ToManaged(ContextNative managed)
|
||||
{
|
||||
return new Context()
|
||||
{
|
||||
Graph = managed.Graph,
|
||||
GraphExecutor = managed.GraphExecutor,
|
||||
Node = managed.Node,
|
||||
NodeId = managed.NodeId,
|
||||
BoxId = managed.BoxId,
|
||||
DeltaTime = managed.DeltaTime,
|
||||
CurrentFrameIndex = managed.CurrentFrameIndex,
|
||||
BaseModel = SkinnedModelMarshaller.ConvertToManaged(managed.BaseModel),
|
||||
Instance = AnimatedModelMarshaller.ConvertToManaged(managed.Instance),
|
||||
};
|
||||
}
|
||||
internal static ContextNative ToNative(Context managed)
|
||||
{
|
||||
return new ContextNative()
|
||||
{
|
||||
Graph = managed.Graph,
|
||||
GraphExecutor = managed.GraphExecutor,
|
||||
Node = managed.Node,
|
||||
NodeId = managed.NodeId,
|
||||
BoxId = managed.BoxId,
|
||||
DeltaTime = managed.DeltaTime,
|
||||
CurrentFrameIndex = managed.CurrentFrameIndex,
|
||||
BaseModel = SkinnedModelMarshaller.ConvertToUnmanaged(managed.BaseModel),
|
||||
Instance = AnimatedModelMarshaller.ConvertToUnmanaged(managed.Instance),
|
||||
};
|
||||
}
|
||||
internal static void Free(ContextNative unmanaged)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The animation graph 'impulse' connections data container (the actual transfer is done via pointer as it gives better performance).
|
||||
/// Container for skeleton nodes transformation hierarchy and any other required data.
|
||||
@@ -118,12 +175,7 @@ namespace FlaxEngine
|
||||
/// <summary>
|
||||
/// The root motion data.
|
||||
/// </summary>
|
||||
public Vector3 RootMotionTranslation;
|
||||
|
||||
/// <summary>
|
||||
/// The root motion data.
|
||||
/// </summary>
|
||||
public Quaternion RootMotionRotation;
|
||||
public Transform RootMotion;
|
||||
|
||||
/// <summary>
|
||||
/// The animation time position (in seconds).
|
||||
@@ -194,8 +246,7 @@ namespace FlaxEngine
|
||||
destination->NodesCount = source->NodesCount;
|
||||
destination->Unused = source->Unused;
|
||||
Utils.MemoryCopy(new IntPtr(destination->Nodes), new IntPtr(source->Nodes), (ulong)(source->NodesCount * sizeof(Transform)));
|
||||
destination->RootMotionTranslation = source->RootMotionTranslation;
|
||||
destination->RootMotionRotation = source->RootMotionRotation;
|
||||
destination->RootMotion = source->RootMotion;
|
||||
destination->Position = source->Position;
|
||||
destination->Length = source->Length;
|
||||
}
|
||||
@@ -203,14 +254,16 @@ namespace FlaxEngine
|
||||
|
||||
#region Internal Calls
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern bool Internal_HasConnection(ref CustomNode.Context context, int boxId);
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_HasConnection")]
|
||||
[return: MarshalAs(UnmanagedType.U1)]
|
||||
internal static partial bool Internal_HasConnection(ref AnimationGraph.CustomNode.Context context, int boxId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern object Internal_GetInputValue(ref CustomNode.Context context, int boxId);
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_GetInputValue")]
|
||||
[return: MarshalUsing(typeof(FlaxEngine.Interop.ManagedHandleMarshaller))]
|
||||
internal static partial object Internal_GetInputValue(ref AnimationGraph.CustomNode.Context context, int boxId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern IntPtr Internal_GetOutputImpulseData(ref CustomNode.Context context);
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "AnimGraphInternal_GetOutputImpulseData")]
|
||||
internal static partial IntPtr Internal_GetOutputImpulseData(ref AnimationGraph.CustomNode.Context context);
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -29,6 +29,22 @@ public:
|
||||
void PostExecute(TaskGraph* graph) override;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
FORCE_INLINE bool CanUpdateModel(AnimatedModel* animatedModel)
|
||||
{
|
||||
auto skinnedModel = animatedModel->SkinnedModel.Get();
|
||||
auto animGraph = animatedModel->AnimationGraph.Get();
|
||||
return animGraph && animGraph->IsLoaded()
|
||||
&& skinnedModel && skinnedModel->IsLoaded()
|
||||
#if USE_EDITOR
|
||||
// It may happen in editor so just add safe check to prevent any crashes
|
||||
&& animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count()
|
||||
#endif
|
||||
&& animGraph->Graph.IsReady();
|
||||
}
|
||||
}
|
||||
|
||||
AnimationsService AnimationManagerInstance;
|
||||
Array<AnimatedModel*> UpdateList;
|
||||
TaskGraphSystem* Animations::System = nullptr;
|
||||
@@ -53,14 +69,9 @@ void AnimationsSystem::Job(int32 index)
|
||||
{
|
||||
PROFILE_CPU_NAMED("Animations.Job");
|
||||
auto animatedModel = UpdateList[index];
|
||||
auto skinnedModel = animatedModel->SkinnedModel.Get();
|
||||
auto graph = animatedModel->AnimationGraph.Get();
|
||||
if (graph && graph->IsLoaded() && graph->Graph.CanUseWithSkeleton(skinnedModel)
|
||||
#if USE_EDITOR
|
||||
&& graph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes
|
||||
#endif
|
||||
)
|
||||
if (CanUpdateModel(animatedModel))
|
||||
{
|
||||
auto graph = animatedModel->AnimationGraph.Get();
|
||||
#if COMPILE_WITH_PROFILER && TRACY_ENABLE
|
||||
const StringView graphName(graph->GetPath());
|
||||
ZoneName(*graphName, graphName.Length());
|
||||
@@ -120,13 +131,7 @@ void AnimationsSystem::PostExecute(TaskGraph* graph)
|
||||
for (int32 index = 0; index < UpdateList.Count(); index++)
|
||||
{
|
||||
auto animatedModel = UpdateList[index];
|
||||
auto skinnedModel = animatedModel->SkinnedModel.Get();
|
||||
auto animGraph = animatedModel->AnimationGraph.Get();
|
||||
if (animGraph && animGraph->IsLoaded() && animGraph->Graph.CanUseWithSkeleton(skinnedModel)
|
||||
#if USE_EDITOR
|
||||
&& animGraph->Graph.Parameters.Count() == animatedModel->GraphInstance.Parameters.Count() // It may happen in editor so just add safe check to prevent any crashes
|
||||
#endif
|
||||
)
|
||||
if (CanUpdateModel(animatedModel))
|
||||
{
|
||||
animatedModel->OnAnimationUpdated_Sync();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ AnimSubGraph* AnimGraphBase::LoadSubGraph(const void* data, int32 dataLength, co
|
||||
auto subGraph = New<AnimSubGraph>(_graph);
|
||||
|
||||
// Load graph
|
||||
MemoryReadStream stream((byte*)data, dataLength);
|
||||
MemoryReadStream stream((const byte*)data, dataLength);
|
||||
if (subGraph->Load(&stream, false))
|
||||
{
|
||||
// Load failed
|
||||
@@ -180,8 +180,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
const Float4 range = n->Values[0].AsFloat4();
|
||||
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
|
||||
{
|
||||
auto data0 = n->Values[i * 2 + 4].AsFloat4();
|
||||
data0.X = Math::Clamp(data0.X, range.X, range.Y);
|
||||
n->Assets[i] = Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
|
||||
n->Data.MultiBlend1D.IndicesSorted[i] = n->Assets[i] ? i : ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS;
|
||||
}
|
||||
@@ -200,9 +198,6 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
const Float4 range = n->Values[0].AsFloat4();
|
||||
for (int32 i = 0; i < ANIM_GRAPH_MULTI_BLEND_MAX_ANIMS; i++)
|
||||
{
|
||||
auto data0 = n->Values[i * 2 + 4].AsFloat4();
|
||||
data0.X = Math::Clamp(data0.X, range.X, range.Y);
|
||||
data0.Y = Math::Clamp(data0.Y, range.Z, range.W);
|
||||
n->Assets[i] = (Asset*)Content::LoadAsync<Animation>((Guid)n->Values[i * 2 + 5]);
|
||||
if (n->Assets[i])
|
||||
{
|
||||
@@ -283,91 +278,11 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
Value& surfaceData = n->Values[1];
|
||||
data.Graph = LoadSubGraph(surfaceData.AsBlob.Data, surfaceData.AsBlob.Length, (const Char*)name.AsBlob.Data);
|
||||
|
||||
// Initialize transitions
|
||||
Value& transitionsData = n->Values[2];
|
||||
int32 validTransitions = 0;
|
||||
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
|
||||
{
|
||||
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
|
||||
|
||||
int32 version;
|
||||
stream.ReadInt32(&version);
|
||||
if (version != 1)
|
||||
{
|
||||
LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
|
||||
return true;
|
||||
}
|
||||
|
||||
int32 transitionsCount;
|
||||
stream.ReadInt32(&transitionsCount);
|
||||
|
||||
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
|
||||
|
||||
AnimGraphStateTransition transition;
|
||||
for (int32 i = 0; i < transitionsCount; i++)
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
int32 Destination;
|
||||
int32 Flags;
|
||||
int32 Order;
|
||||
float BlendDuration;
|
||||
int32 BlendMode;
|
||||
int32 Unused0;
|
||||
int32 Unused1;
|
||||
int32 Unused2;
|
||||
};
|
||||
Data transitionData;
|
||||
stream.ReadBytes(&transitionData, sizeof(transitionData));
|
||||
|
||||
transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
|
||||
transition.BlendDuration = transitionData.BlendDuration;
|
||||
transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
|
||||
transition.Destination = GetNode(transitionData.Destination);
|
||||
transition.RuleGraph = nullptr;
|
||||
|
||||
int32 ruleSize;
|
||||
stream.ReadInt32(&ruleSize);
|
||||
const auto ruleBytes = (byte*)stream.Move(ruleSize);
|
||||
|
||||
if (static_cast<int32>(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
|
||||
{
|
||||
// Skip disabled transitions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ruleSize != 0)
|
||||
{
|
||||
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
|
||||
if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing root node for the state machine transition rule graph.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (transition.Destination == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing target node for the state machine transition.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
|
||||
{
|
||||
LOG(Warning, "State uses too many transitions.");
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
|
||||
StateTransitions.Add(transition);
|
||||
}
|
||||
}
|
||||
if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
|
||||
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
|
||||
|
||||
// Release data to don't use that memory
|
||||
surfaceData = Value::Null;
|
||||
transitionsData = Value::Null;
|
||||
|
||||
// Initialize transitions
|
||||
LoadStateTransitions(data, n->Values[2]);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -443,6 +358,10 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
case 33:
|
||||
ADD_BUCKET(InstanceDataBucketInit);
|
||||
break;
|
||||
// Any State
|
||||
case 34:
|
||||
LoadStateTransitions(n->Data.AnyState, n->Values[0]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// Custom
|
||||
@@ -465,4 +384,91 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
||||
return VisjectGraph::onNodeLoaded(n);
|
||||
}
|
||||
|
||||
void AnimGraphBase::LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData)
|
||||
{
|
||||
int32 validTransitions = 0;
|
||||
if (transitionsData.Type == VariantType::Blob && transitionsData.AsBlob.Length)
|
||||
{
|
||||
MemoryReadStream stream((byte*)transitionsData.AsBlob.Data, transitionsData.AsBlob.Length);
|
||||
|
||||
int32 version;
|
||||
stream.ReadInt32(&version);
|
||||
if (version != 1)
|
||||
{
|
||||
LOG(Warning, "Invalid version of the Anim Graph state transitions data.");
|
||||
return;
|
||||
}
|
||||
|
||||
int32 transitionsCount;
|
||||
stream.ReadInt32(&transitionsCount);
|
||||
|
||||
StateTransitions.EnsureCapacity(StateTransitions.Count() + transitionsCount);
|
||||
|
||||
AnimGraphStateTransition transition;
|
||||
for (int32 i = 0; i < transitionsCount; i++)
|
||||
{
|
||||
// Must match StateMachineTransition.Data in C#
|
||||
struct Data
|
||||
{
|
||||
int32 Destination;
|
||||
int32 Flags;
|
||||
int32 Order;
|
||||
float BlendDuration;
|
||||
int32 BlendMode;
|
||||
int32 Unused0;
|
||||
int32 Unused1;
|
||||
int32 Unused2;
|
||||
};
|
||||
Data transitionData;
|
||||
stream.ReadBytes(&transitionData, sizeof(transitionData));
|
||||
|
||||
transition.Flags = (AnimGraphStateTransition::FlagTypes)transitionData.Flags;
|
||||
transition.BlendDuration = transitionData.BlendDuration;
|
||||
transition.BlendMode = (AlphaBlendMode)transitionData.BlendMode;
|
||||
transition.Destination = GetNode(transitionData.Destination);
|
||||
transition.RuleGraph = nullptr;
|
||||
|
||||
int32 ruleSize;
|
||||
stream.ReadInt32(&ruleSize);
|
||||
const auto ruleBytes = (byte*)stream.Move(ruleSize);
|
||||
|
||||
if (static_cast<int32>(transition.Flags & AnimGraphStateTransition::FlagTypes::Enabled) == 0)
|
||||
{
|
||||
// Skip disabled transitions
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ruleSize != 0)
|
||||
{
|
||||
transition.RuleGraph = LoadSubGraph(ruleBytes, ruleSize, TEXT("Rule"));
|
||||
if (transition.RuleGraph && transition.RuleGraph->GetRootNode() == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing root node for the state machine transition rule graph.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (transition.Destination == nullptr)
|
||||
{
|
||||
LOG(Warning, "Missing target node for the state machine transition.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (validTransitions == ANIM_GRAPH_MAX_STATE_TRANSITIONS)
|
||||
{
|
||||
LOG(Warning, "State uses too many transitions.");
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Transitions[validTransitions++] = (uint16)StateTransitions.Count();
|
||||
StateTransitions.Add(transition);
|
||||
}
|
||||
}
|
||||
if (validTransitions != ANIM_GRAPH_MAX_STATE_TRANSITIONS)
|
||||
data.Transitions[validTransitions] = AnimGraphNode::StateData::InvalidTransitionIndex;
|
||||
|
||||
// Release data to don't use that memory
|
||||
transitionsData = AnimGraphExecutor::Value::Null;
|
||||
}
|
||||
|
||||
#undef ADD_BUCKET
|
||||
|
||||
@@ -2,24 +2,23 @@
|
||||
|
||||
#include "AnimGraph.h"
|
||||
#include "Engine/Debug/DebugLog.h"
|
||||
#include "Engine/Scripting/InternalCalls.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MDomain.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/MException.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Scripting/Internal/InternalCalls.h"
|
||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||
|
||||
#if USE_MONO
|
||||
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||
#if USE_CSHARP
|
||||
|
||||
struct InternalInitData
|
||||
{
|
||||
MonoArray* Values;
|
||||
MonoObject* BaseModel;
|
||||
MArray* Values;
|
||||
MObject* BaseModel;
|
||||
};
|
||||
|
||||
struct InternalContext
|
||||
@@ -31,8 +30,8 @@ struct InternalContext
|
||||
int32 BoxId;
|
||||
float DeltaTime;
|
||||
uint64 CurrentFrameIndex;
|
||||
MonoObject* BaseModel;
|
||||
MonoObject* Instance;
|
||||
MObject* BaseModel;
|
||||
MObject* Instance;
|
||||
};
|
||||
|
||||
struct InternalImpulse
|
||||
@@ -40,63 +39,57 @@ struct InternalImpulse
|
||||
int32 NodesCount;
|
||||
int32 Unused;
|
||||
Transform* Nodes;
|
||||
Vector3 RootMotionTranslation;
|
||||
Quaternion RootMotionRotation;
|
||||
Transform RootMotion;
|
||||
float Position;
|
||||
float Length;
|
||||
};
|
||||
|
||||
static_assert(sizeof(InternalImpulse) == sizeof(AnimGraphImpulse), "Please update managed impulse type for Anim Graph to match the C++ backend data layout.");
|
||||
|
||||
namespace AnimGraphInternal
|
||||
DEFINE_INTERNAL_CALL(bool) AnimGraphInternal_HasConnection(InternalContext* context, int32 boxId)
|
||||
{
|
||||
bool HasConnection(InternalContext* context, int32 boxId)
|
||||
{
|
||||
const auto box = context->Node->TryGetBox(boxId);
|
||||
if (box == nullptr)
|
||||
DebugLog::ThrowArgumentOutOfRange("boxId");
|
||||
return box->HasConnection();
|
||||
}
|
||||
const auto box = context->Node->TryGetBox(boxId);
|
||||
if (box == nullptr)
|
||||
DebugLog::ThrowArgumentOutOfRange("boxId");
|
||||
return box->HasConnection();
|
||||
}
|
||||
|
||||
MonoObject* GetInputValue(InternalContext* context, int32 boxId)
|
||||
{
|
||||
const auto box = context->Node->TryGetBox(boxId);
|
||||
if (box == nullptr)
|
||||
DebugLog::ThrowArgumentOutOfRange("boxId");
|
||||
if (!box->HasConnection())
|
||||
DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value.");
|
||||
DEFINE_INTERNAL_CALL(MObject*) AnimGraphInternal_GetInputValue(InternalContext* context, int32 boxId)
|
||||
{
|
||||
const auto box = context->Node->TryGetBox(boxId);
|
||||
if (box == nullptr)
|
||||
DebugLog::ThrowArgumentOutOfRange("boxId");
|
||||
if (!box->HasConnection())
|
||||
DebugLog::ThrowArgument("boxId", "This box has no connection. Use HasConnection to check if can get input value.");
|
||||
|
||||
Variant value = Variant::Null;
|
||||
context->GraphExecutor->GetInputValue(box, value);
|
||||
Variant value = Variant::Null;
|
||||
context->GraphExecutor->GetInputValue(box, value);
|
||||
|
||||
// Cast value to prevent implicit value conversion issues and handling this on C# side
|
||||
if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer))
|
||||
value = Variant::Cast(value, box->Type);
|
||||
return MUtils::BoxVariant(value);
|
||||
}
|
||||
// Cast value to prevent implicit value conversion issues and handling this on C# side
|
||||
if (!(box->Type.Type == VariantType::Void && value.Type.Type == VariantType::Pointer))
|
||||
value = Variant::Cast(value, box->Type);
|
||||
return MUtils::BoxVariant(value);
|
||||
}
|
||||
|
||||
AnimGraphImpulse* GetOutputImpulseData(InternalContext* context)
|
||||
{
|
||||
const auto nodes = context->Node->GetNodes(context->GraphExecutor);
|
||||
context->GraphExecutor->InitNodes(nodes);
|
||||
return nodes;
|
||||
}
|
||||
DEFINE_INTERNAL_CALL(AnimGraphImpulse*) AnimGraphInternal_GetOutputImpulseData(InternalContext* context)
|
||||
{
|
||||
const auto nodes = context->Node->GetNodes(context->GraphExecutor);
|
||||
context->GraphExecutor->InitNodes(nodes);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void AnimGraphExecutor::initRuntime()
|
||||
{
|
||||
#if USE_MONO
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal::HasConnection);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal::GetInputValue);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal::GetOutputImpulseData);
|
||||
#endif
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_HasConnection", &AnimGraphInternal_HasConnection);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetInputValue", &AnimGraphInternal_GetInputValue);
|
||||
ADD_INTERNAL_CALL("FlaxEngine.AnimationGraph::Internal_GetOutputImpulseData", &AnimGraphInternal_GetOutputImpulseData);
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value& value)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
auto& context = Context.Get();
|
||||
if (context.ValueCache.TryGet(boxBase, value))
|
||||
return;
|
||||
@@ -122,7 +115,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
|
||||
internalContext.Instance = context.Data->Object ? context.Data->Object->GetOrCreateManagedInstance() : nullptr;
|
||||
|
||||
// Peek managed object
|
||||
const auto obj = mono_gchandle_get_target(data.Handle);
|
||||
const auto obj = MCore::GCHandle::GetTarget(data.Handle);
|
||||
if (obj == nullptr)
|
||||
{
|
||||
LOG(Warning, "Custom node instance is null.");
|
||||
@@ -133,7 +126,7 @@ void AnimGraphExecutor::ProcessGroupCustom(Box* boxBase, Node* nodeBase, Value&
|
||||
void* params[1];
|
||||
params[0] = &internalContext;
|
||||
MObject* exception = nullptr;
|
||||
MonoObject* result = data.Evaluate->Invoke(obj, params, &exception);
|
||||
MObject* result = data.Evaluate->Invoke(obj, params, &exception);
|
||||
if (exception)
|
||||
{
|
||||
MException ex(exception);
|
||||
@@ -152,12 +145,6 @@ bool AnimGraph::IsReady() const
|
||||
return BaseModel && BaseModel->IsLoaded();
|
||||
}
|
||||
|
||||
bool AnimGraph::CanUseWithSkeleton(SkinnedModel* other) const
|
||||
{
|
||||
// All data loaded and nodes count the same
|
||||
return IsReady() && other && other->IsLoaded() && other->Skeleton.Nodes.Count() == BaseModel->Skeleton.Nodes.Count();
|
||||
}
|
||||
|
||||
void AnimGraph::ClearCustomNode(Node* node)
|
||||
{
|
||||
// Clear data
|
||||
@@ -165,16 +152,14 @@ void AnimGraph::ClearCustomNode(Node* node)
|
||||
data.Evaluate = nullptr;
|
||||
if (data.Handle)
|
||||
{
|
||||
#if USE_MONO
|
||||
mono_gchandle_free(data.Handle);
|
||||
#endif
|
||||
MCore::GCHandle::Free(data.Handle);
|
||||
data.Handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool AnimGraph::InitCustomNode(Node* node)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
// Fetch the node logic controller type
|
||||
if (node->Values.Count() < 2 || node->Values[0].Type.Type != ValueType::String)
|
||||
{
|
||||
@@ -182,7 +167,7 @@ bool AnimGraph::InitCustomNode(Node* node)
|
||||
return false;
|
||||
}
|
||||
const StringView typeName(node->Values[0]);
|
||||
const MString typeNameStd = typeName.ToStringAnsi();
|
||||
const StringAnsi typeNameStd = typeName.ToStringAnsi();
|
||||
MClass* type = Scripting::FindClass(typeNameStd);
|
||||
if (type == nullptr)
|
||||
{
|
||||
@@ -205,18 +190,15 @@ bool AnimGraph::InitCustomNode(Node* node)
|
||||
}
|
||||
|
||||
// Create node values managed array
|
||||
if (mono_domain_get() == nullptr)
|
||||
Scripting::GetScriptsDomain()->Dispatch();
|
||||
const auto values = mono_array_new(mono_domain_get(), mono_get_object_class(), node->Values.Count());
|
||||
MCore::Thread::Attach();
|
||||
MArray* values = MCore::Array::New( MCore::TypeCache::Object, node->Values.Count());
|
||||
MObject** valuesPtr = MCore::Array::GetAddress<MObject*>(values);
|
||||
for (int32 i = 0; i < node->Values.Count(); i++)
|
||||
{
|
||||
const auto v = MUtils::BoxVariant(node->Values[i]);
|
||||
mono_array_set(values, MonoObject*, i, v);
|
||||
}
|
||||
valuesPtr[i] = MUtils::BoxVariant(node->Values[i]);
|
||||
|
||||
// Allocate managed node object (create GC handle to prevent destruction)
|
||||
const auto obj = type->CreateInstance();
|
||||
const auto handleGC = mono_gchandle_new(obj, false);
|
||||
MObject* obj = type->CreateInstance();
|
||||
const MGCHandle handleGC = MCore::GCHandle::New(obj);
|
||||
|
||||
// Initialize node
|
||||
InternalInitData initData;
|
||||
@@ -228,7 +210,7 @@ bool AnimGraph::InitCustomNode(Node* node)
|
||||
load->Invoke(obj, params, &exception);
|
||||
if (exception)
|
||||
{
|
||||
mono_gchandle_free(handleGC);
|
||||
MCore::GCHandle::Free(handleGC);
|
||||
|
||||
MException ex(exception);
|
||||
ex.Log(LogType::Warning, TEXT("AnimGraph"));
|
||||
|
||||
@@ -7,55 +7,10 @@
|
||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
|
||||
extern void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& sourceMapping, Transform& node, int32 i);
|
||||
|
||||
ThreadLocal<AnimGraphContext> AnimGraphExecutor::Context;
|
||||
|
||||
RootMotionData RootMotionData::Identity = { Vector3(0.0f), Quaternion(0.0f, 0.0f, 0.0f, 1.0f) };
|
||||
|
||||
RootMotionData& RootMotionData::operator+=(const RootMotionData& b)
|
||||
{
|
||||
Translation += b.Translation;
|
||||
Rotation *= b.Rotation;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RootMotionData& RootMotionData::operator+=(const Transform& b)
|
||||
{
|
||||
Translation += b.Translation;
|
||||
Rotation *= b.Orientation;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RootMotionData& RootMotionData::operator-=(const Transform& b)
|
||||
{
|
||||
Translation -= b.Translation;
|
||||
Quaternion invRotation = Rotation;
|
||||
invRotation.Invert();
|
||||
Quaternion::Multiply(invRotation, b.Orientation, Rotation);
|
||||
return *this;
|
||||
}
|
||||
|
||||
RootMotionData RootMotionData::operator+(const RootMotionData& b) const
|
||||
{
|
||||
RootMotionData result;
|
||||
|
||||
result.Translation = Translation + b.Translation;
|
||||
result.Rotation = Rotation * b.Rotation;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
RootMotionData RootMotionData::operator-(const RootMotionData& b) const
|
||||
{
|
||||
RootMotionData result;
|
||||
|
||||
result.Rotation = Rotation;
|
||||
result.Rotation.Invert();
|
||||
Vector3::Transform(b.Translation - Translation, result.Rotation, result.Translation);
|
||||
Quaternion::Multiply(result.Rotation, b.Rotation, result.Rotation);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const
|
||||
{
|
||||
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
|
||||
@@ -87,7 +42,7 @@ void AnimGraphInstanceData::Clear()
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = RootMotionData::Identity;
|
||||
RootMotion = Transform::Identity;
|
||||
Parameters.Resize(0);
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
@@ -103,7 +58,7 @@ void AnimGraphInstanceData::ClearState()
|
||||
LastUpdateTime = -1;
|
||||
CurrentFrame = 0;
|
||||
RootTransform = Transform::Identity;
|
||||
RootMotion = RootMotionData::Identity;
|
||||
RootMotion = Transform::Identity;
|
||||
State.Resize(0);
|
||||
NodesPose.Resize(0);
|
||||
Slots.Clear();
|
||||
@@ -257,15 +212,12 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
e.Hit = false;
|
||||
|
||||
// Init empty nodes data
|
||||
context.EmptyNodes.RootMotion = RootMotionData::Identity;
|
||||
context.EmptyNodes.RootMotion = Transform::Identity;
|
||||
context.EmptyNodes.Position = 0.0f;
|
||||
context.EmptyNodes.Length = 0.0f;
|
||||
context.EmptyNodes.Nodes.Resize(_skeletonNodesCount, false);
|
||||
for (int32 i = 0; i < _skeletonNodesCount; i++)
|
||||
{
|
||||
auto& node = skeleton.Nodes[i];
|
||||
context.EmptyNodes.Nodes[i] = node.LocalTransform;
|
||||
}
|
||||
context.EmptyNodes.Nodes[i] = skeleton.Nodes[i].LocalTransform;
|
||||
}
|
||||
|
||||
// Update the animation graph and gather skeleton nodes transformations in nodes local space
|
||||
@@ -301,6 +253,44 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
}
|
||||
}
|
||||
}
|
||||
SkeletonData* animResultSkeleton = &skeleton;
|
||||
|
||||
// Retarget animation when using output pose from other skeleton
|
||||
AnimGraphImpulse retargetNodes;
|
||||
if (_graph.BaseModel != data.NodesSkeleton)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Retarget");
|
||||
|
||||
// Init nodes for the target skeleton
|
||||
auto& targetSkeleton = data.NodesSkeleton->Skeleton;
|
||||
retargetNodes = *animResult;
|
||||
retargetNodes.Nodes.Resize(targetSkeleton.Nodes.Count());
|
||||
Transform* targetNodes = retargetNodes.Nodes.Get();
|
||||
for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
|
||||
targetNodes[i] = targetSkeleton.Nodes[i].LocalTransform;
|
||||
|
||||
// Use skeleton mapping
|
||||
const SkinnedModel::SkeletonMapping mapping = data.NodesSkeleton->GetSkeletonMapping(_graph.BaseModel);
|
||||
if (mapping.NodesMapping.IsValid())
|
||||
{
|
||||
const auto& sourceSkeleton = _graph.BaseModel->Skeleton;
|
||||
Transform* sourceNodes = animResult->Nodes.Get();
|
||||
for (int32 i = 0; i < retargetNodes.Nodes.Count(); i++)
|
||||
{
|
||||
const int32 nodeToNode = mapping.NodesMapping[i];
|
||||
if (nodeToNode != -1)
|
||||
{
|
||||
Transform node = sourceNodes[nodeToNode];
|
||||
RetargetSkeletonNode(sourceSkeleton, targetSkeleton, mapping, node, i);
|
||||
targetNodes[i] = node;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
animResult = &retargetNodes;
|
||||
animResultSkeleton = &targetSkeleton;
|
||||
}
|
||||
|
||||
// Allow for external override of the local pose (eg. by the ragdoll)
|
||||
data.LocalPoseOverride(animResult);
|
||||
@@ -309,13 +299,14 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Global Pose");
|
||||
|
||||
data.NodesPose.Resize(_skeletonNodesCount, false);
|
||||
ASSERT(animResultSkeleton->Nodes.Count() == animResult->Nodes.Count());
|
||||
data.NodesPose.Resize(animResultSkeleton->Nodes.Count(), false);
|
||||
Transform* nodesTransformations = animResult->Nodes.Get();
|
||||
|
||||
// Note: this assumes that nodes are sorted (parents first)
|
||||
for (int32 nodeIndex = 0; nodeIndex < _skeletonNodesCount; nodeIndex++)
|
||||
for (int32 nodeIndex = 0; nodeIndex < animResultSkeleton->Nodes.Count(); nodeIndex++)
|
||||
{
|
||||
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
|
||||
const int32 parentIndex = animResultSkeleton->Nodes[nodeIndex].ParentIndex;
|
||||
if (parentIndex != -1)
|
||||
{
|
||||
nodesTransformations[parentIndex].LocalToWorld(nodesTransformations[nodeIndex], nodesTransformations[nodeIndex]);
|
||||
|
||||
@@ -26,58 +26,6 @@ class AnimGraphExecutor;
|
||||
class SkinnedModel;
|
||||
class SkeletonData;
|
||||
|
||||
/// <summary>
|
||||
/// The root motion data container. Supports displacement and rotation (no scale component).
|
||||
/// </summary>
|
||||
struct RootMotionData
|
||||
{
|
||||
static RootMotionData Identity;
|
||||
|
||||
Vector3 Translation;
|
||||
Quaternion Rotation;
|
||||
|
||||
RootMotionData()
|
||||
{
|
||||
}
|
||||
|
||||
RootMotionData(const Vector3& translation, const Quaternion& rotation)
|
||||
{
|
||||
Translation = translation;
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
RootMotionData(const RootMotionData& other)
|
||||
{
|
||||
Translation = other.Translation;
|
||||
Rotation = other.Rotation;
|
||||
}
|
||||
|
||||
explicit RootMotionData(const Transform& other)
|
||||
{
|
||||
Translation = other.Translation;
|
||||
Rotation = other.Orientation;
|
||||
}
|
||||
|
||||
RootMotionData& operator=(const Transform& other)
|
||||
{
|
||||
Translation = other.Translation;
|
||||
Rotation = other.Orientation;
|
||||
return *this;
|
||||
}
|
||||
|
||||
RootMotionData& operator+=(const RootMotionData& b);
|
||||
RootMotionData& operator+=(const Transform& b);
|
||||
RootMotionData& operator-=(const Transform& b);
|
||||
RootMotionData operator+(const RootMotionData& b) const;
|
||||
RootMotionData operator-(const RootMotionData& b) const;
|
||||
|
||||
static void Lerp(const RootMotionData& t1, const RootMotionData& t2, float amount, RootMotionData& result)
|
||||
{
|
||||
Vector3::Lerp(t1.Translation, t2.Translation, amount, result.Translation);
|
||||
Quaternion::Slerp(t1.Rotation, t2.Rotation, amount, result.Rotation);
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The animation graph 'impulse' connections data container (the actual transfer is done via pointer as it gives better performance).
|
||||
/// Container for skeleton nodes transformation hierarchy and any other required data.
|
||||
@@ -93,7 +41,7 @@ struct FLAXENGINE_API AnimGraphImpulse
|
||||
/// <summary>
|
||||
/// The root motion extracted from the animation to apply on animated object.
|
||||
/// </summary>
|
||||
RootMotionData RootMotion = RootMotionData::Identity;
|
||||
Transform RootMotion = Transform::Identity;
|
||||
|
||||
/// <summary>
|
||||
/// The animation time position (in seconds).
|
||||
@@ -172,25 +120,12 @@ public:
|
||||
/// </summary>
|
||||
enum class FlagTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// The none.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The enabled flag.
|
||||
/// </summary>
|
||||
Enabled = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The solo flag.
|
||||
/// </summary>
|
||||
Solo = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The use default rule flag.
|
||||
/// </summary>
|
||||
UseDefaultRule = 4,
|
||||
InterruptionRuleRechecking = 8,
|
||||
InterruptionInstant = 16,
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -361,7 +296,7 @@ public:
|
||||
/// <summary>
|
||||
/// The current root motion delta to apply on a target object.
|
||||
/// </summary>
|
||||
RootMotionData RootMotion = RootMotionData::Identity;
|
||||
Transform RootMotion = Transform::Identity;
|
||||
|
||||
/// <summary>
|
||||
/// The animation graph parameters collection (instanced, override the default values).
|
||||
@@ -384,7 +319,12 @@ public:
|
||||
ScriptingObject* Object;
|
||||
|
||||
/// <summary>
|
||||
/// The custom event called after local pose evaluation.
|
||||
/// The output nodes pose skeleton asset to use. Allows to remap evaluated animation pose for Base Model of the Anim Graph to the target Animated Model that plays it. Nodes Pose will match its skeleton. Use null if disable retargetting.
|
||||
/// </summary>
|
||||
SkinnedModel* NodesSkeleton = nullptr;
|
||||
|
||||
/// <summary>
|
||||
/// The custom event called after local pose evaluation and retargetting.
|
||||
/// </summary>
|
||||
Delegate<AnimGraphImpulse*> LocalPoseOverride;
|
||||
|
||||
@@ -495,24 +435,31 @@ public:
|
||||
AnimSubGraph* Graph;
|
||||
};
|
||||
|
||||
struct StateData
|
||||
struct StateBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// The invalid transition valid used in Transitions to indicate invalid transition linkage.
|
||||
/// </summary>
|
||||
const static uint16 InvalidTransitionIndex = MAX_uint16;
|
||||
|
||||
/// <summary>
|
||||
/// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
|
||||
/// </summary>
|
||||
AnimSubGraph* Graph;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The outgoing transitions from this state to the other states. Each array item contains index of the transition data from the state node graph transitions cache. Value InvalidTransitionIndex is used for last transition to indicate the transitions amount.
|
||||
/// </summary>
|
||||
uint16 Transitions[ANIM_GRAPH_MAX_STATE_TRANSITIONS];
|
||||
};
|
||||
|
||||
struct StateData : StateBaseData
|
||||
{
|
||||
/// <summary>
|
||||
/// The graph of the state. Contains the state animation evaluation graph. Its root node is the state output node with an input box for the state blend pose sampling.
|
||||
/// </summary>
|
||||
AnimSubGraph* Graph;
|
||||
};
|
||||
|
||||
struct AnyStateData : StateBaseData
|
||||
{
|
||||
};
|
||||
|
||||
struct CustomData
|
||||
{
|
||||
/// <summary>
|
||||
@@ -523,7 +470,7 @@ public:
|
||||
/// <summary>
|
||||
/// The GC handle to the managed instance of the node object.
|
||||
/// </summary>
|
||||
uint32 Handle;
|
||||
MGCHandle Handle;
|
||||
};
|
||||
|
||||
struct CurveData
|
||||
@@ -564,6 +511,7 @@ public:
|
||||
MultiBlend2DData MultiBlend2D;
|
||||
StateMachineData StateMachine;
|
||||
StateData State;
|
||||
AnyStateData AnyState;
|
||||
CustomData Custom;
|
||||
CurveData Curve;
|
||||
AnimationGraphFunctionData AnimationGraphFunction;
|
||||
@@ -681,6 +629,9 @@ public:
|
||||
protected:
|
||||
// [Graph]
|
||||
bool onNodeLoaded(Node* n) override;
|
||||
|
||||
private:
|
||||
void LoadStateTransitions(AnimGraphNode::StateBaseData& data, Value& transitionsData);
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -763,13 +714,6 @@ public:
|
||||
/// </summary>
|
||||
bool IsReady() const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this graph can be used with the specified skeleton.
|
||||
/// </summary>
|
||||
/// <param name="other">The other skinned model to check.</param>
|
||||
/// <returns>True if can perform the update, otherwise false.</returns>
|
||||
bool CanUseWithSkeleton(SkinnedModel* other) const;
|
||||
|
||||
private:
|
||||
void ClearCustomNode(Node* node);
|
||||
bool InitCustomNode(Node* node);
|
||||
@@ -887,7 +831,6 @@ private:
|
||||
};
|
||||
|
||||
int32 GetRootNodeIndex(Animation* anim);
|
||||
void ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion);
|
||||
void ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed);
|
||||
void ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode* node, bool loop, float length, float pos, float prevPos, Animation* anim, float speed, float weight = 1.0f, ProcessAnimationMode mode = ProcessAnimationMode::Override);
|
||||
Variant SampleAnimation(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* anim, float speed);
|
||||
@@ -895,4 +838,5 @@ private:
|
||||
Variant SampleAnimationsWithBlend(AnimGraphNode* node, bool loop, float length, float startTimePos, float prevTimePos, float& newTimePos, Animation* animA, Animation* animB, Animation* animC, float speedA, float speedB, float speedC, float alphaA, float alphaB, float alphaC);
|
||||
Variant Blend(AnimGraphNode* node, const Value& poseA, const Value& poseB, float alpha, AlphaBlendMode alphaMode);
|
||||
Variant SampleState(AnimGraphNode* state);
|
||||
void UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
|
||||
FORCE_INLINE void BlendAdditiveWeightedRotation(Quaternion& base, Quaternion& additive, float weight)
|
||||
{
|
||||
// Pick a shortest path between rotation to fix blending artifacts
|
||||
additive *= weight;
|
||||
@@ -19,6 +19,36 @@ namespace
|
||||
additive *= -1;
|
||||
base += additive;
|
||||
}
|
||||
|
||||
FORCE_INLINE void NormalizeRotations(AnimGraphImpulse* nodes, RootMotionMode rootMotionMode)
|
||||
{
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
nodes->Nodes[i].Orientation.Normalize();
|
||||
}
|
||||
if (rootMotionMode != RootMotionMode::NoExtraction)
|
||||
{
|
||||
nodes->RootMotion.Orientation.Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RetargetSkeletonNode(const SkeletonData& sourceSkeleton, const SkeletonData& targetSkeleton, const SkinnedModel::SkeletonMapping& mapping, Transform& node, int32 i)
|
||||
{
|
||||
const int32 nodeToNode = mapping.NodesMapping[i];
|
||||
if (nodeToNode == -1)
|
||||
return;
|
||||
|
||||
// Map source skeleton node to the target skeleton (use ref pose difference)
|
||||
const auto& sourceNode = sourceSkeleton.Nodes[nodeToNode];
|
||||
const auto& targetNode = targetSkeleton.Nodes[i];
|
||||
Transform value = node;
|
||||
const Transform sourceToTarget = targetNode.LocalTransform - sourceNode.LocalTransform;
|
||||
value.Translation += sourceToTarget.Translation;
|
||||
value.Scale *= sourceToTarget.Scale;
|
||||
value.Orientation = sourceToTarget.Orientation * value.Orientation; // TODO: find out why this doesn't match referenced animation when played on that skeleton originally
|
||||
value.Orientation.Normalize();
|
||||
node = value;
|
||||
}
|
||||
|
||||
int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
|
||||
@@ -40,52 +70,6 @@ int32 AnimGraphExecutor::GetRootNodeIndex(Animation* anim)
|
||||
return rootNodeIndex;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ExtractRootMotion(const Animation::NodeToChannel* mapping, int32 rootNodeIndex, Animation* anim, float pos, float prevPos, Transform& rootNode, RootMotionData& rootMotion)
|
||||
{
|
||||
const Transform& refPose = GetEmptyNodes()->Nodes[rootNodeIndex];
|
||||
const int32 nodeToChannel = mapping->At(rootNodeIndex);
|
||||
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
|
||||
{
|
||||
// Get the root bone transformation
|
||||
Transform rootBefore = refPose;
|
||||
const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
|
||||
rootChannel.Evaluate(prevPos, &rootBefore, false);
|
||||
|
||||
// Check if animation looped
|
||||
if (pos < prevPos)
|
||||
{
|
||||
const float length = anim->GetLength();
|
||||
const float endPos = length * static_cast<float>(anim->Data.FramesPerSecond);
|
||||
const float timeToEnd = endPos - prevPos;
|
||||
|
||||
Transform rootBegin = refPose;
|
||||
rootChannel.Evaluate(0, &rootBegin, false);
|
||||
|
||||
Transform rootEnd = refPose;
|
||||
rootChannel.Evaluate(endPos, &rootEnd, false);
|
||||
|
||||
//rootChannel.Evaluate(pos - timeToEnd, &rootNow, true);
|
||||
|
||||
// Complex motion calculation to preserve the looped movement
|
||||
// (end - before + now - begin)
|
||||
// It sums the motion since the last update to anim end and since the start to now
|
||||
rootMotion.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
|
||||
rootMotion.Rotation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
|
||||
//rootMotion.Rotation = Quaternion::Identity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple motion delta
|
||||
// (now - before)
|
||||
rootMotion.Translation = rootNode.Translation - rootBefore.Translation;
|
||||
rootMotion.Rotation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove root node motion after extraction
|
||||
rootNode = refPose;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::ProcessAnimEvents(AnimGraphNode* node, bool loop, float length, float animPos, float animPrevPos, Animation* anim, float speed)
|
||||
{
|
||||
if (anim->Events.Count() == 0)
|
||||
@@ -185,13 +169,15 @@ float GetAnimPos(float& timePos, float startTimePos, bool loop, float length)
|
||||
{
|
||||
// Animation looped
|
||||
result = Math::Mod(result, length);
|
||||
|
||||
// Remove start time offset to properly loop from animation start during the next frame
|
||||
timePos = result - startTimePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Animation ended
|
||||
result = length;
|
||||
timePos = result = length;
|
||||
}
|
||||
timePos = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -257,19 +243,33 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
}
|
||||
}
|
||||
|
||||
// Get skeleton nodes mapping descriptor
|
||||
const SkinnedModel::SkeletonMapping mapping = _graph.BaseModel->GetSkeletonMapping(anim);
|
||||
if (mapping.NodesMapping.IsInvalid())
|
||||
return;
|
||||
|
||||
// Evaluate nodes animations
|
||||
const auto mapping = anim->GetMapping(_graph.BaseModel);
|
||||
const bool weighted = weight < 1.0f;
|
||||
const bool retarget = mapping.SourceSkeleton && mapping.SourceSkeleton != mapping.TargetSkeleton;
|
||||
const auto emptyNodes = GetEmptyNodes();
|
||||
SkinnedModel::SkeletonMapping sourceMapping;
|
||||
if (retarget)
|
||||
sourceMapping = _graph.BaseModel->GetSkeletonMapping(mapping.SourceSkeleton);
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
const int32 nodeToChannel = mapping->At(i);
|
||||
const int32 nodeToChannel = mapping.NodesMapping[i];
|
||||
Transform& dstNode = nodes->Nodes[i];
|
||||
Transform srcNode = emptyNodes->Nodes[i];
|
||||
if (nodeToChannel != -1)
|
||||
{
|
||||
// Calculate the animated node transformation
|
||||
anim->Data.Channels[nodeToChannel].Evaluate(animPos, &srcNode, false);
|
||||
|
||||
// Optionally retarget animation into the skeleton used by the Anim Graph
|
||||
if (retarget)
|
||||
{
|
||||
RetargetSkeletonNode(mapping.SourceSkeleton->Skeleton, mapping.TargetSkeleton->Skeleton, sourceMapping, srcNode, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Blend node
|
||||
@@ -302,26 +302,76 @@ void AnimGraphExecutor::ProcessAnimation(AnimGraphImpulse* nodes, AnimGraphNode*
|
||||
{
|
||||
// Calculate the root motion node transformation
|
||||
const int32 rootNodeIndex = GetRootNodeIndex(anim);
|
||||
Transform rootNode = emptyNodes->Nodes[rootNodeIndex];
|
||||
RootMotionData& dstNode = nodes->RootMotion;
|
||||
RootMotionData srcNode(rootNode);
|
||||
ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, rootNode, srcNode);
|
||||
const Transform& refPose = emptyNodes->Nodes[rootNodeIndex];
|
||||
Transform& rootNode = nodes->Nodes[rootNodeIndex];
|
||||
Transform& dstNode = nodes->RootMotion;
|
||||
Transform srcNode = Transform::Identity;
|
||||
const int32 nodeToChannel = mapping.NodesMapping[rootNodeIndex];
|
||||
if (_rootMotionMode == RootMotionMode::Enable && nodeToChannel != -1)
|
||||
{
|
||||
// Get the root bone transformation
|
||||
Transform rootBefore = refPose;
|
||||
const NodeAnimationData& rootChannel = anim->Data.Channels[nodeToChannel];
|
||||
rootChannel.Evaluate(animPrevPos, &rootBefore, false);
|
||||
|
||||
// Check if animation looped
|
||||
if (animPos < animPrevPos)
|
||||
{
|
||||
const float endPos = anim->GetLength() * static_cast<float>(anim->Data.FramesPerSecond);
|
||||
const float timeToEnd = endPos - animPrevPos;
|
||||
|
||||
Transform rootBegin = refPose;
|
||||
rootChannel.Evaluate(0, &rootBegin, false);
|
||||
|
||||
Transform rootEnd = refPose;
|
||||
rootChannel.Evaluate(endPos, &rootEnd, false);
|
||||
|
||||
//rootChannel.Evaluate(animPos - timeToEnd, &rootNow, true);
|
||||
|
||||
// Complex motion calculation to preserve the looped movement
|
||||
// (end - before + now - begin)
|
||||
// It sums the motion since the last update to anim end and since the start to now
|
||||
srcNode.Translation = rootEnd.Translation - rootBefore.Translation + rootNode.Translation - rootBegin.Translation;
|
||||
srcNode.Orientation = rootEnd.Orientation * rootBefore.Orientation.Conjugated() * (rootNode.Orientation * rootBegin.Orientation.Conjugated());
|
||||
//srcNode.Orientation = Quaternion::Identity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple motion delta
|
||||
// (now - before)
|
||||
srcNode.Translation = rootNode.Translation - rootBefore.Translation;
|
||||
srcNode.Orientation = rootBefore.Orientation.Conjugated() * rootNode.Orientation;
|
||||
}
|
||||
|
||||
// Convert root motion from local-space to the actor-space (eg. if root node is not actually a root and its parents have rotation/scale)
|
||||
auto& skeleton = _graph.BaseModel->Skeleton;
|
||||
int32 parentIndex = skeleton.Nodes[rootNodeIndex].ParentIndex;
|
||||
while (parentIndex != -1)
|
||||
{
|
||||
const Transform& parentNode = nodes->Nodes[parentIndex];
|
||||
srcNode.Translation = parentNode.LocalToWorld(srcNode.Translation);
|
||||
parentIndex = skeleton.Nodes[parentIndex].ParentIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove root node motion after extraction
|
||||
rootNode = refPose;
|
||||
|
||||
// Blend root motion
|
||||
if (mode == ProcessAnimationMode::BlendAdditive)
|
||||
{
|
||||
dstNode.Translation += srcNode.Translation * weight;
|
||||
BlendAdditiveWeightedRotation(dstNode.Rotation, srcNode.Rotation, weight);
|
||||
BlendAdditiveWeightedRotation(dstNode.Orientation, srcNode.Orientation, weight);
|
||||
}
|
||||
else if (mode == ProcessAnimationMode::Add)
|
||||
{
|
||||
dstNode.Translation += srcNode.Translation * weight;
|
||||
dstNode.Rotation += srcNode.Rotation * weight;
|
||||
dstNode.Orientation += srcNode.Orientation * weight;
|
||||
}
|
||||
else if (weighted)
|
||||
{
|
||||
dstNode.Translation = srcNode.Translation * weight;
|
||||
dstNode.Rotation = srcNode.Rotation * weight;
|
||||
dstNode.Orientation = srcNode.Orientation * weight;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -349,6 +399,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float
|
||||
nodes->Position = pos;
|
||||
nodes->Length = length;
|
||||
ProcessAnimation(nodes, node, loop, length, pos, prevPos, anim, speed);
|
||||
NormalizeRotations(nodes, _rootMotionMode);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
@@ -370,16 +421,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
|
||||
nodes->Length = length;
|
||||
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, 1.0f - alpha, ProcessAnimationMode::Override);
|
||||
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alpha, ProcessAnimationMode::BlendAdditive);
|
||||
|
||||
// Normalize rotations
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
nodes->Nodes[i].Orientation.Normalize();
|
||||
}
|
||||
if (_rootMotionMode != RootMotionMode::NoExtraction)
|
||||
{
|
||||
nodes->RootMotion.Rotation.Normalize();
|
||||
}
|
||||
NormalizeRotations(nodes, _rootMotionMode);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
@@ -404,16 +446,7 @@ Variant AnimGraphExecutor::SampleAnimationsWithBlend(AnimGraphNode* node, bool l
|
||||
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animA, speedA, alphaA, ProcessAnimationMode::Override);
|
||||
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animB, speedB, alphaB, ProcessAnimationMode::BlendAdditive);
|
||||
ProcessAnimation(nodes, node, loop, length, pos, prevPos, animC, speedC, alphaC, ProcessAnimationMode::BlendAdditive);
|
||||
|
||||
// Normalize rotations
|
||||
for (int32 i = 0; i < nodes->Nodes.Count(); i++)
|
||||
{
|
||||
nodes->Nodes[i].Orientation.Normalize();
|
||||
}
|
||||
if (_rootMotionMode != RootMotionMode::NoExtraction)
|
||||
{
|
||||
nodes->RootMotion.Rotation.Normalize();
|
||||
}
|
||||
NormalizeRotations(nodes, _rootMotionMode);
|
||||
|
||||
return nodes;
|
||||
}
|
||||
@@ -437,7 +470,7 @@ Variant AnimGraphExecutor::Blend(AnimGraphNode* node, const Value& poseA, const
|
||||
{
|
||||
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
|
||||
}
|
||||
RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
nodes->Position = Math::Lerp(nodesA->Position, nodesB->Position, alpha);
|
||||
nodes->Length = Math::Lerp(nodesA->Length, nodesB->Length, alpha);
|
||||
|
||||
@@ -463,6 +496,79 @@ Variant AnimGraphExecutor::SampleState(AnimGraphNode* state)
|
||||
return result;
|
||||
}
|
||||
|
||||
void AnimGraphExecutor::UpdateStateTransitions(AnimGraphContext& context, const AnimGraphNode::StateMachineData& stateMachineData, AnimGraphInstanceData::StateMachineBucket& stateMachineBucket, const AnimGraphNode::StateBaseData& stateData)
|
||||
{
|
||||
int32 transitionIndex = 0;
|
||||
while (transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS && stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex)
|
||||
{
|
||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||
ASSERT(idx < stateMachineData.Graph->StateTransitions.Count());
|
||||
auto& transition = stateMachineData.Graph->StateTransitions[idx];
|
||||
if (transition.Destination == stateMachineBucket.CurrentState)
|
||||
{
|
||||
// Ignore transition to the current state
|
||||
transitionIndex++;
|
||||
continue;
|
||||
}
|
||||
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
||||
if (transition.RuleGraph && !useDefaultRule)
|
||||
{
|
||||
// Execute transition rule
|
||||
auto rootNode = transition.RuleGraph->GetRootNode();
|
||||
ASSERT(rootNode);
|
||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||
{
|
||||
transitionIndex++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate source state transition data (position, length, etc.)
|
||||
const Value sourceStatePtr = SampleState(stateMachineBucket.CurrentState);
|
||||
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
||||
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
||||
{
|
||||
// Use source state as data provider
|
||||
const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
|
||||
auto sourceLength = Math::Max(sourceState->Length, 0.0f);
|
||||
transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
|
||||
transitionData.Length = sourceLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset
|
||||
transitionData.Position = 0;
|
||||
transitionData.Length = ZeroTolerance;
|
||||
}
|
||||
|
||||
// Check if can trigger the transition
|
||||
bool canEnter = false;
|
||||
if (useDefaultRule)
|
||||
{
|
||||
// Start transition when the current state animation is about to end (split blend duration evenly into two states)
|
||||
const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
|
||||
const auto endPos = transitionData.Length - transitionDurationHalf;
|
||||
canEnter = transitionData.Position >= endPos;
|
||||
}
|
||||
else if (transition.RuleGraph)
|
||||
canEnter = true;
|
||||
if (canEnter)
|
||||
{
|
||||
// Start transition
|
||||
stateMachineBucket.ActiveTransition = &transition;
|
||||
stateMachineBucket.TransitionPosition = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip after Solo transition
|
||||
// TODO: don't load transitions after first enabled Solo transition and remove this check here
|
||||
if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
|
||||
break;
|
||||
|
||||
transitionIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
void ComputeMultiBlendLength(float& length, AnimGraphNode* node)
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Setup Mutli Blend Length");
|
||||
@@ -643,9 +749,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
const auto anim = node->Assets[0].As<Animation>();
|
||||
auto& bucket = context.Data->State[node->BucketIndex].Animation;
|
||||
const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
|
||||
const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
|
||||
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
|
||||
|
||||
switch (box->ID)
|
||||
{
|
||||
@@ -653,6 +756,9 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
case 0:
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("Animation");
|
||||
const float speed = (float)tryGetValue(node->GetBox(5), node->Values[1]);
|
||||
const bool loop = (bool)tryGetValue(node->GetBox(6), node->Values[2]);
|
||||
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
|
||||
const float length = anim ? anim->GetLength() : 0.0f;
|
||||
|
||||
// Calculate new time position
|
||||
@@ -672,14 +778,20 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
// Normalized Time
|
||||
case 1:
|
||||
{
|
||||
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
|
||||
value = startTimePos + bucket.TimePosition;
|
||||
if (anim && anim->IsLoaded())
|
||||
value.AsFloat /= anim->GetLength();
|
||||
break;
|
||||
}
|
||||
// Time
|
||||
case 2:
|
||||
{
|
||||
const float startTimePos = (float)tryGetValue(node->GetBox(7), node->Values[3]);
|
||||
value = startTimePos + bucket.TimePosition;
|
||||
break;
|
||||
}
|
||||
// Length
|
||||
case 3:
|
||||
value = anim ? anim->GetLength() : 0.0f;
|
||||
@@ -912,7 +1024,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
{
|
||||
Transform::Lerp(nodesA->Nodes[i], nodesB->Nodes[i], alpha, nodes->Nodes[i]);
|
||||
}
|
||||
RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
value = nodes;
|
||||
}
|
||||
|
||||
@@ -958,7 +1070,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
t.Orientation.Normalize();
|
||||
Transform::Lerp(tA, t, alpha, nodes->Nodes[i]);
|
||||
}
|
||||
RootMotionData::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
Transform::Lerp(nodesA->RootMotion, nodesA->RootMotion + nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
value = nodes;
|
||||
}
|
||||
}
|
||||
@@ -1006,7 +1118,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
nodes->Nodes[nodeIndex] = tA;
|
||||
}
|
||||
}
|
||||
RootMotionData::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
Transform::Lerp(nodesA->RootMotion, nodesB->RootMotion, alpha, nodes->RootMotion);
|
||||
|
||||
value = nodes;
|
||||
}
|
||||
@@ -1391,7 +1503,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
value = poseData->RootMotion.Translation;
|
||||
break;
|
||||
case 1:
|
||||
value = poseData->RootMotion.Rotation;
|
||||
value = poseData->RootMotion.Orientation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1423,7 +1535,7 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
auto nodes = node->GetNodes(this);
|
||||
nodes->Nodes = poseData->Nodes;
|
||||
nodes->RootMotion.Translation = (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
|
||||
nodes->RootMotion.Rotation = (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
|
||||
nodes->RootMotion.Orientation = (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
|
||||
value = nodes;
|
||||
break;
|
||||
}
|
||||
@@ -1441,19 +1553,18 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
auto nodes = node->GetNodes(this);
|
||||
nodes->Nodes = poseData->Nodes;
|
||||
nodes->RootMotion.Translation = poseData->RootMotion.Translation + (Vector3)tryGetValue(node->GetBox(2), Value::Zero);
|
||||
nodes->RootMotion.Rotation = poseData->RootMotion.Rotation * (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
|
||||
nodes->RootMotion.Orientation = poseData->RootMotion.Orientation * (Quaternion)tryGetValue(node->GetBox(3), Value::Zero);
|
||||
value = nodes;
|
||||
break;
|
||||
}
|
||||
// State Machine
|
||||
case 18:
|
||||
{
|
||||
ANIM_GRAPH_PROFILE_EVENT("State Machine");
|
||||
const int32 maxTransitionsPerUpdate = node->Values[2].AsInt;
|
||||
const bool reinitializeOnBecomingRelevant = node->Values[3].AsBool;
|
||||
const bool skipFirstUpdateTransition = node->Values[4].AsBool;
|
||||
|
||||
ANIM_GRAPH_PROFILE_EVENT("State Machine");
|
||||
|
||||
// Prepare
|
||||
auto& bucket = context.Data->State[node->BucketIndex].StateMachine;
|
||||
auto& data = node->Data.StateMachine;
|
||||
@@ -1485,6 +1596,11 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// Reset all state buckets pof the graphs and nodes included inside the state machine
|
||||
ResetBuckets(context, data.Graph);
|
||||
}
|
||||
#define END_TRANSITION() \
|
||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph); \
|
||||
bucket.CurrentState = bucket.ActiveTransition->Destination; \
|
||||
bucket.ActiveTransition = nullptr; \
|
||||
bucket.TransitionPosition = 0.0f
|
||||
|
||||
// Update the active transition
|
||||
if (bucket.ActiveTransition)
|
||||
@@ -1494,95 +1610,66 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
// Check for transition end
|
||||
if (bucket.TransitionPosition >= bucket.ActiveTransition->BlendDuration)
|
||||
{
|
||||
// End transition
|
||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||
bucket.CurrentState = bucket.ActiveTransition->Destination;
|
||||
bucket.ActiveTransition = nullptr;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
END_TRANSITION();
|
||||
}
|
||||
// Check for transition interruption
|
||||
else if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionRuleRechecking))
|
||||
{
|
||||
const bool useDefaultRule = EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
||||
if (bucket.ActiveTransition->RuleGraph && !useDefaultRule)
|
||||
{
|
||||
// Execute transition rule
|
||||
auto rootNode = bucket.ActiveTransition->RuleGraph->GetRootNode();
|
||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||
{
|
||||
bool cancelTransition = false;
|
||||
if (EnumHasAnyFlags(bucket.ActiveTransition->Flags, AnimGraphStateTransition::FlagTypes::InterruptionInstant))
|
||||
{
|
||||
cancelTransition = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blend back to the source state (remove currently applied delta and rewind transition)
|
||||
bucket.TransitionPosition -= context.DeltaTime;
|
||||
bucket.TransitionPosition -= context.DeltaTime;
|
||||
if (bucket.TransitionPosition <= ZeroTolerance)
|
||||
{
|
||||
cancelTransition = true;
|
||||
}
|
||||
}
|
||||
if (cancelTransition)
|
||||
{
|
||||
// Go back to the source state
|
||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||
bucket.ActiveTransition = nullptr;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(bucket.CurrentState && bucket.CurrentState->GroupID == 9 && bucket.CurrentState->TypeID == 20);
|
||||
ASSERT(bucket.CurrentState && bucket.CurrentState->Type == GRAPH_NODE_MAKE_TYPE(9, 20));
|
||||
|
||||
// Update transitions
|
||||
// Note: this logic assumes that all transitions are sorted by Order property and Enabled
|
||||
// Note: this logic assumes that all transitions are sorted by Order property and Enabled (by Editor when saving Anim Graph asset)
|
||||
while (!bucket.ActiveTransition && transitionsLeft-- > 0)
|
||||
{
|
||||
// Check if can change the current state
|
||||
const auto& stateData = bucket.CurrentState->Data.State;
|
||||
int32 transitionIndex = 0;
|
||||
while (stateData.Transitions[transitionIndex] != AnimGraphNode::StateData::InvalidTransitionIndex
|
||||
&& transitionIndex < ANIM_GRAPH_MAX_STATE_TRANSITIONS)
|
||||
// State transitions
|
||||
UpdateStateTransitions(context, data, bucket, bucket.CurrentState->Data.State);
|
||||
|
||||
// Any state transitions
|
||||
// TODO: cache Any state nodes inside State Machine to optimize the loop below
|
||||
for (const AnimGraphNode& anyStateNode : data.Graph->Nodes)
|
||||
{
|
||||
const uint16 idx = stateData.Transitions[transitionIndex];
|
||||
ASSERT(idx < data.Graph->StateTransitions.Count());
|
||||
auto& transition = data.Graph->StateTransitions[idx];
|
||||
const bool useDefaultRule = EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::UseDefaultRule);
|
||||
if (transition.RuleGraph && !useDefaultRule)
|
||||
{
|
||||
// Execute transition rule
|
||||
auto rootNode = transition.RuleGraph->GetRootNode();
|
||||
ASSERT(rootNode);
|
||||
if (!(bool)eatBox((Node*)rootNode, &rootNode->Boxes[0]))
|
||||
{
|
||||
transitionIndex++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate source state transition data (position, length, etc.)
|
||||
const Value sourceStatePtr = SampleState(bucket.CurrentState);
|
||||
auto& transitionData = context.TransitionData; // Note: this could support nested transitions but who uses state machine inside transition rule?
|
||||
if (ANIM_GRAPH_IS_VALID_PTR(sourceStatePtr))
|
||||
{
|
||||
// Use source state as data provider
|
||||
const auto sourceState = (AnimGraphImpulse*)sourceStatePtr.AsPointer;
|
||||
auto sourceLength = Math::Max(sourceState->Length, 0.0f);
|
||||
transitionData.Position = Math::Clamp(sourceState->Position, 0.0f, sourceLength);
|
||||
transitionData.Length = sourceLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reset
|
||||
transitionData.Position = 0;
|
||||
transitionData.Length = ZeroTolerance;
|
||||
}
|
||||
|
||||
// Check if can trigger the transition
|
||||
bool canEnter = false;
|
||||
if (useDefaultRule)
|
||||
{
|
||||
// Start transition when the current state animation is about to end (split blend duration evenly into two states)
|
||||
const auto transitionDurationHalf = transition.BlendDuration * 0.5f + ZeroTolerance;
|
||||
const auto endPos = transitionData.Length - transitionDurationHalf;
|
||||
canEnter = transitionData.Position >= endPos;
|
||||
}
|
||||
else if (transition.RuleGraph)
|
||||
canEnter = true;
|
||||
if (canEnter)
|
||||
{
|
||||
// Start transition
|
||||
bucket.ActiveTransition = &transition;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
// Skip after Solo transition
|
||||
// TODO: don't load transitions after first enabled Solo transition and remove this check here
|
||||
if (EnumHasAnyFlags(transition.Flags, AnimGraphStateTransition::FlagTypes::Solo))
|
||||
break;
|
||||
|
||||
transitionIndex++;
|
||||
if (anyStateNode.Type == GRAPH_NODE_MAKE_TYPE(9, 34))
|
||||
UpdateStateTransitions(context, data, bucket, anyStateNode.Data.AnyState);
|
||||
}
|
||||
|
||||
// Check for instant transitions
|
||||
if (bucket.ActiveTransition && bucket.ActiveTransition->BlendDuration <= ZeroTolerance)
|
||||
{
|
||||
// End transition
|
||||
ResetBuckets(context, bucket.CurrentState->Data.State.Graph);
|
||||
bucket.CurrentState = bucket.ActiveTransition->Destination;
|
||||
bucket.ActiveTransition = nullptr;
|
||||
bucket.TransitionPosition = 0.0f;
|
||||
END_TRANSITION();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1603,18 +1690,16 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
|
||||
// Update bucket
|
||||
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||
#undef END_TRANSITION
|
||||
|
||||
break;
|
||||
}
|
||||
// Entry
|
||||
case 19:
|
||||
{
|
||||
// Not used
|
||||
CRASH;
|
||||
break;
|
||||
}
|
||||
// State
|
||||
case 20:
|
||||
// Any State
|
||||
case 34:
|
||||
{
|
||||
// Not used
|
||||
CRASH;
|
||||
@@ -1622,8 +1707,6 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
||||
}
|
||||
// State Output
|
||||
case 21:
|
||||
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
|
||||
break;
|
||||
// Rule Output
|
||||
case 22:
|
||||
value = box->HasConnection() ? eatBox(nodeBase, box->FirstConnection()) : Value::Null;
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
#include "Engine/Renderer/RenderList.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/Script.h"
|
||||
#include "Engine/Scripting/MException.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MProperty.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MType.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MField.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
|
||||
// This could be Update, LateUpdate or FixedUpdate
|
||||
#define UPDATE_POINT Update
|
||||
@@ -260,7 +260,7 @@ void SceneAnimationPlayer::MapTrack(const StringView& from, const Guid& to)
|
||||
|
||||
void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
// Restore all tracks
|
||||
for (int32 j = 0; j < anim->Tracks.Count(); j++)
|
||||
{
|
||||
@@ -364,7 +364,7 @@ void SceneAnimationPlayer::Restore(SceneAnimation* anim, int32 stateIndexOffset)
|
||||
|
||||
bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexOffset, SceneAnimation* anim, float time, const SceneAnimation::Track& track, TrackInstance& state, void* target)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
switch (track.Type)
|
||||
{
|
||||
case SceneAnimation::Track::Types::KeyframesProperty:
|
||||
@@ -511,7 +511,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
|
||||
// Return the value
|
||||
StringView str(keyframesValues[leftKey], keyframesLengths[leftKey]);
|
||||
*(MonoString**)target = MUtils::ToString(str);
|
||||
*(MString**)target = MUtils::ToString(str);
|
||||
break;
|
||||
}
|
||||
case SceneAnimation::Track::Types::StructProperty:
|
||||
@@ -528,10 +528,10 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
// Cache field
|
||||
if (!childTrackState.Field)
|
||||
{
|
||||
MType type = state.Property ? state.Property->GetType() : (state.Field ? state.Field->GetType() : MType());
|
||||
MType* type = state.Property ? state.Property->GetType() : (state.Field ? state.Field->GetType() : nullptr);
|
||||
if (!type)
|
||||
continue;
|
||||
MClass* mclass = Scripting::FindClass(mono_type_get_class(type.GetNative()));
|
||||
MClass* mclass = MCore::Type::GetClass(type);
|
||||
childTrackState.Field = mclass->GetField(childTrackRuntimeData->PropertyName);
|
||||
if (!childTrackState.Field)
|
||||
continue;
|
||||
@@ -556,7 +556,7 @@ bool SceneAnimationPlayer::TickPropertyTrack(int32 trackIndex, int32 stateIndexO
|
||||
|
||||
void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int32 stateIndexOffset, CallStack& callStack)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
const float fps = anim->FramesPerSecond;
|
||||
#if !BUILD_RELEASE || USE_EDITOR
|
||||
callStack.Add(anim);
|
||||
@@ -873,7 +873,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
// Cache property or field
|
||||
if (!state.Property && !state.Field)
|
||||
{
|
||||
MClass* mclass = Scripting::FindClass(mono_object_get_class(instance));
|
||||
MClass* mclass = MCore::Object::GetClass(instance);
|
||||
state.Property = mclass->GetProperty(runtimeData->PropertyName);
|
||||
if (!state.Property)
|
||||
{
|
||||
@@ -886,9 +886,8 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
}
|
||||
|
||||
// Get stack memory for data value
|
||||
MType valueType = state.Property ? state.Property->GetType() : state.Field->GetType();
|
||||
int32 valueAlignment;
|
||||
int32 valueSize = mono_type_stack_size(valueType.GetNative(), &valueAlignment);
|
||||
MType* valueType = state.Property ? state.Property->GetType() : state.Field->GetType();
|
||||
int32 valueSize = MCore::Type::GetSize(valueType);
|
||||
_tracksDataStack.AddDefault(valueSize);
|
||||
void* value = &_tracksDataStack[_tracksDataStack.Count() - valueSize];
|
||||
|
||||
@@ -906,10 +905,10 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
MException ex(exception);
|
||||
ex.Log(LogType::Error, TEXT("Property"));
|
||||
}
|
||||
else if (!valueType.IsPointer())
|
||||
else if (!MCore::Type::IsPointer(valueType))
|
||||
{
|
||||
if (boxed)
|
||||
Platform::MemoryCopy(value, mono_object_unbox(boxed), valueSize);
|
||||
Platform::MemoryCopy(value, MCore::Object::Unbox(boxed), valueSize);
|
||||
else
|
||||
Platform::MemoryClear(value, valueSize);
|
||||
}
|
||||
@@ -932,7 +931,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
case SceneAnimation::Track::Types::StringProperty:
|
||||
{
|
||||
StringView str;
|
||||
MUtils::ToString(*(MonoString**)value, str);
|
||||
MUtils::ToString(*(MString**)value, str);
|
||||
_restoreData.Add((byte*)str.Get(), str.Length());
|
||||
_restoreData.Add('\0');
|
||||
break;
|
||||
@@ -957,7 +956,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
if (TickPropertyTrack(j, stateIndexOffset, anim, time, track, state, value))
|
||||
{
|
||||
// Set the value
|
||||
if (valueType.IsPointer())
|
||||
if (MCore::Type::IsPointer(valueType))
|
||||
value = (void*)*(intptr*)value;
|
||||
if (state.Property)
|
||||
{
|
||||
@@ -1027,7 +1026,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
// Cache method
|
||||
if (!state.Method)
|
||||
{
|
||||
state.Method = mono_class_get_method_from_name(mono_object_get_class(instance), runtimeData->EventName, runtimeData->EventParamsCount);
|
||||
state.Method = MCore::Object::GetClass(instance)->FindMethod(runtimeData->EventName, runtimeData->EventParamsCount);
|
||||
if (!state.Method)
|
||||
break;
|
||||
}
|
||||
@@ -1035,7 +1034,7 @@ void SceneAnimationPlayer::Tick(SceneAnimation* anim, float time, float dt, int3
|
||||
// Invoke the method
|
||||
Variant result;
|
||||
MObject* exception = nullptr;
|
||||
mono_runtime_invoke((MonoMethod*)state.Method, instance, paramsData, &exception);
|
||||
state.Method->Invoke(instance, paramsData, &exception);
|
||||
if (exception)
|
||||
{
|
||||
MException ex(exception);
|
||||
|
||||
@@ -46,7 +46,7 @@ private:
|
||||
MObject* ManagedObject = nullptr;
|
||||
MProperty* Property = nullptr;
|
||||
MField* Field = nullptr;
|
||||
void* Method = nullptr;
|
||||
MMethod* Method = nullptr;
|
||||
int32 RestoreStateIndex = -1;
|
||||
bool Warn = true;
|
||||
|
||||
|
||||
@@ -57,6 +57,9 @@ public class Audio : EngineModule
|
||||
case TargetPlatform.Mac:
|
||||
useOpenAL = true;
|
||||
break;
|
||||
case TargetPlatform.iOS:
|
||||
useOpenAL = true;
|
||||
break;
|
||||
default: throw new InvalidPlatformException(options.Platform.Target);
|
||||
}
|
||||
|
||||
@@ -91,6 +94,11 @@ public class Audio : EngineModule
|
||||
options.Libraries.Add("AudioUnit.framework");
|
||||
options.Libraries.Add("AudioToolbox.framework");
|
||||
break;
|
||||
case TargetPlatform.iOS:
|
||||
options.OutputFiles.Add(Path.Combine(depsRoot, "libopenal.a"));
|
||||
options.Libraries.Add("CoreAudio.framework");
|
||||
options.Libraries.Add("AudioToolbox.framework");
|
||||
break;
|
||||
default: throw new InvalidPlatformException(options.Platform.Target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Engine/Threading/ConcurrentTaskQueue.h"
|
||||
#if USE_MONO
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/mono-gc.h>
|
||||
#endif
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
|
||||
AssetReferenceBase::~AssetReferenceBase()
|
||||
{
|
||||
@@ -269,9 +267,7 @@ void Asset::OnManagedInstanceDeleted()
|
||||
// Cleanup
|
||||
if (_gcHandle)
|
||||
{
|
||||
#if USE_MONO
|
||||
mono_gchandle_free(_gcHandle);
|
||||
#endif
|
||||
MCore::GCHandle::Free(_gcHandle);
|
||||
_gcHandle = 0;
|
||||
}
|
||||
|
||||
@@ -399,6 +395,11 @@ bool Asset::WaitForLoaded(double timeoutInMilliseconds) const
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if loading failed
|
||||
Platform::MemoryBarrier();
|
||||
if (LastLoadFailed())
|
||||
return true;
|
||||
|
||||
// Check if has missing loading task
|
||||
Platform::MemoryBarrier();
|
||||
const auto loadingTask = _loadingTask;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "Animation.h"
|
||||
#include "SkinnedModel.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Animations/CurveSerialization.h"
|
||||
#include "Engine/Animations/AnimEvent.h"
|
||||
@@ -30,9 +29,7 @@ void Animation::OnScriptsReloadStart()
|
||||
for (auto& e : Events)
|
||||
{
|
||||
for (auto& k : e.Second.GetKeyframes())
|
||||
{
|
||||
Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,72 +64,11 @@ Animation::InfoData Animation::GetInfo() const
|
||||
}
|
||||
info.MemoryUsage += Events.Capacity() * sizeof(Pair<String, StepCurve<AnimEventData>>);
|
||||
info.MemoryUsage += NestedAnims.Capacity() * sizeof(Pair<String, NestedAnimData>);
|
||||
info.MemoryUsage += MappingCache.Capacity() * (sizeof(void*) + sizeof(NodeToChannel) + 1);
|
||||
for (auto& e : Events)
|
||||
info.MemoryUsage += e.Second.GetKeyframes().Capacity() * sizeof(StepCurve<AnimEventData>);
|
||||
for (auto& e : MappingCache)
|
||||
info.MemoryUsage += e.Value.Capacity() * sizeof(int32);
|
||||
return info;
|
||||
}
|
||||
|
||||
void Animation::ClearCache()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Unlink events
|
||||
for (auto i = MappingCache.Begin(); i.IsNotEnd(); ++i)
|
||||
{
|
||||
i->Key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
i->Key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
}
|
||||
|
||||
// Free memory
|
||||
MappingCache.Clear();
|
||||
MappingCache.SetCapacity(0);
|
||||
}
|
||||
|
||||
const Animation::NodeToChannel* Animation::GetMapping(SkinnedModel* obj)
|
||||
{
|
||||
ASSERT(obj && obj->IsLoaded() && IsLoaded());
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Try quick lookup
|
||||
NodeToChannel* result = MappingCache.TryGet(obj);
|
||||
if (result == nullptr)
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Add to cache
|
||||
NodeToChannel tmp;
|
||||
auto bucket = MappingCache.Add(obj, tmp);
|
||||
result = &bucket->Value;
|
||||
obj->OnUnloaded.Bind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
obj->OnReloading.Bind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
|
||||
// Initialize the mapping
|
||||
const auto& skeleton = obj->Skeleton;
|
||||
const int32 nodesCount = skeleton.Nodes.Count();
|
||||
result->Resize(nodesCount, false);
|
||||
result->SetAll(-1);
|
||||
for (int32 i = 0; i < Data.Channels.Count(); i++)
|
||||
{
|
||||
auto& nodeAnim = Data.Channels[i];
|
||||
|
||||
for (int32 j = 0; j < nodesCount; j++)
|
||||
{
|
||||
if (skeleton.Nodes[j].Name == nodeAnim.NodeName)
|
||||
{
|
||||
result->At(j) = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
void Animation::LoadTimeline(BytesContainer& result) const
|
||||
@@ -552,23 +488,6 @@ bool Animation::Save(const StringView& path)
|
||||
|
||||
#endif
|
||||
|
||||
void Animation::OnSkinnedModelUnloaded(Asset* obj)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
const auto key = static_cast<SkinnedModel*>(obj);
|
||||
auto i = MappingCache.Find(key);
|
||||
ASSERT(i != MappingCache.End());
|
||||
|
||||
// Unlink event
|
||||
key->OnUnloaded.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
key->OnReloading.Unbind<Animation, &Animation::OnSkinnedModelUnloaded>(this);
|
||||
|
||||
// Clear cache
|
||||
i->Value.Resize(0, false);
|
||||
MappingCache.Remove(i);
|
||||
}
|
||||
|
||||
uint64 Animation::GetMemoryUsage() const
|
||||
{
|
||||
Locker.Lock();
|
||||
@@ -579,9 +498,6 @@ uint64 Animation::GetMemoryUsage() const
|
||||
for (const auto& e : Events)
|
||||
result += e.First.Length() * sizeof(Char) + e.Second.GetMemoryUsage();
|
||||
result += NestedAnims.Capacity() * sizeof(Pair<String, NestedAnimData>);
|
||||
result += MappingCache.Capacity() * sizeof(Pair<String, StepCurve<AnimEventData>>);
|
||||
for (const auto& e : MappingCache)
|
||||
result += e.Value.Capacity() * sizeof(int32);
|
||||
Locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
@@ -738,7 +654,6 @@ void Animation::unload(bool isReloading)
|
||||
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(this);
|
||||
}
|
||||
#endif
|
||||
ClearCache();
|
||||
Data.Dispose();
|
||||
for (const auto& e : Events)
|
||||
{
|
||||
|
||||
@@ -98,17 +98,6 @@ public:
|
||||
/// </summary>
|
||||
Array<Pair<String, NestedAnimData>> NestedAnims;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the mapping for every skeleton node to the animation data channels.
|
||||
/// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index).
|
||||
/// </summary>
|
||||
typedef Array<int32> NodeToChannel;
|
||||
|
||||
/// <summary>
|
||||
/// The skeleton nodes to animation channel indices mapping cache. Use it as read-only. It's being maintained internally by the asset.
|
||||
/// </summary>
|
||||
Dictionary<SkinnedModel*, NodeToChannel> MappingCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the length of the animation (in seconds).
|
||||
@@ -139,18 +128,6 @@ public:
|
||||
/// </summary>
|
||||
API_PROPERTY() InfoData GetInfo() const;
|
||||
|
||||
/// <summary>
|
||||
/// Clears the skeleton mapping cache.
|
||||
/// </summary>
|
||||
void ClearCache();
|
||||
|
||||
/// <summary>
|
||||
/// Clears the skeleton mapping cache.
|
||||
/// </summary>
|
||||
/// <param name="obj">The target skinned model to get mapping to its skeleton.</param>
|
||||
/// <returns>The cached node-to-channel mapping for the fast animation sampling for the skinned model skeleton nodes.</returns>
|
||||
const NodeToChannel* GetMapping(SkinnedModel* obj);
|
||||
|
||||
#if USE_EDITOR
|
||||
/// <summary>
|
||||
/// Gets the animation as serialized timeline data. Used to show it in Editor.
|
||||
@@ -174,9 +151,6 @@ public:
|
||||
bool Save(const StringView& path = StringView::Empty);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void OnSkinnedModelUnloaded(Asset* obj);
|
||||
|
||||
public:
|
||||
// [BinaryAsset]
|
||||
uint64 GetMemoryUsage() const override;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "SkinnedModel.h"
|
||||
#include "Animation.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Engine/Engine.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
@@ -11,10 +12,12 @@
|
||||
#include "Engine/Graphics/RenderTask.h"
|
||||
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
|
||||
#include "Engine/Graphics/Models/Config.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/WeakAssetReference.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#include "Engine/Profiler/ProfilerCPU.h"
|
||||
#include "Engine/Renderer/DrawCall.h"
|
||||
|
||||
#define CHECK_INVALID_BUFFER(model, buffer) \
|
||||
@@ -116,6 +119,7 @@ SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info)
|
||||
SkinnedModel::~SkinnedModel()
|
||||
{
|
||||
ASSERT(_streamingTask == nullptr);
|
||||
ASSERT(_skeletonMappingCache.Count() == 0);
|
||||
}
|
||||
|
||||
bool SkinnedModel::HasAnyLODInitialized() const
|
||||
@@ -152,23 +156,165 @@ void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
|
||||
GetChunkData(chunkIndex, data);
|
||||
}
|
||||
|
||||
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source)
|
||||
{
|
||||
SkeletonMapping mapping;
|
||||
mapping.TargetSkeleton = this;
|
||||
if (WaitForLoaded() || !source || source->WaitForLoaded())
|
||||
return mapping;
|
||||
ScopeLock lock(Locker);
|
||||
SkeletonMappingData mappingData;
|
||||
if (!_skeletonMappingCache.TryGet(source, mappingData))
|
||||
{
|
||||
PROFILE_CPU();
|
||||
|
||||
// Initialize the mapping
|
||||
const int32 nodesCount = Skeleton.Nodes.Count();
|
||||
mappingData.NodesMapping = Span<int32>((int32*)Allocator::Allocate(nodesCount * sizeof(int32)), nodesCount);
|
||||
for (int32 i = 0; i < nodesCount; i++)
|
||||
mappingData.NodesMapping[i] = -1;
|
||||
SkeletonRetarget* retarget = nullptr;
|
||||
const Guid sourceId = source->GetID();
|
||||
for (auto& e : _skeletonRetargets)
|
||||
{
|
||||
if (e.SourceAsset == sourceId)
|
||||
{
|
||||
retarget = &e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (const auto* sourceAnim = Cast<Animation>(source))
|
||||
{
|
||||
const auto& channels = sourceAnim->Data.Channels;
|
||||
if (retarget && retarget->SkeletonAsset)
|
||||
{
|
||||
// Map retarget skeleton nodes from animation channels
|
||||
if (auto* skeleton = Content::Load<SkinnedModel>(retarget->SkeletonAsset))
|
||||
{
|
||||
const SkeletonMapping skeletonMapping = GetSkeletonMapping(skeleton);
|
||||
mappingData.SourceSkeleton = skeleton;
|
||||
if (skeletonMapping.NodesMapping.Length() == nodesCount)
|
||||
{
|
||||
const auto& nodes = skeleton->Skeleton.Nodes;
|
||||
for (int32 j = 0; j < nodesCount; j++)
|
||||
{
|
||||
if (skeletonMapping.NodesMapping[j] != -1)
|
||||
{
|
||||
const Char* nodeName = nodes[skeletonMapping.NodesMapping[j]].Name.GetText();
|
||||
for (int32 i = 0; i < channels.Count(); i++)
|
||||
{
|
||||
if (StringUtils::CompareIgnoreCase(nodeName, channels[i].NodeName.GetText()) == 0)
|
||||
{
|
||||
mappingData.NodesMapping[j] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
LOG(Error, "Missing asset {0} to use for skeleton mapping of {1}", retarget->SkeletonAsset, ToString());
|
||||
#endif
|
||||
return mapping;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Map animation channels to the skeleton nodes (by name)
|
||||
for (int32 i = 0; i < channels.Count(); i++)
|
||||
{
|
||||
const NodeAnimationData& nodeAnim = channels[i];
|
||||
for (int32 j = 0; j < nodesCount; j++)
|
||||
{
|
||||
if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), nodeAnim.NodeName.GetText()) == 0)
|
||||
{
|
||||
mappingData.NodesMapping[j] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (const auto* sourceModel = Cast<SkinnedModel>(source))
|
||||
{
|
||||
if (retarget)
|
||||
{
|
||||
// Use nodes retargeting
|
||||
for (const auto& e : retarget->NodesMapping)
|
||||
{
|
||||
const int32 dstIndex = Skeleton.FindNode(e.Key);
|
||||
const int32 srcIndex = sourceModel->Skeleton.FindNode(e.Value);
|
||||
if (dstIndex != -1 && srcIndex != -1)
|
||||
{
|
||||
mappingData.NodesMapping[dstIndex] = srcIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Map source skeleton nodes to the target skeleton nodes (by name)
|
||||
const auto& nodes = sourceModel->Skeleton.Nodes;
|
||||
for (int32 i = 0; i < nodes.Count(); i++)
|
||||
{
|
||||
const SkeletonNode& node = nodes[i];
|
||||
for (int32 j = 0; j < nodesCount; j++)
|
||||
{
|
||||
if (StringUtils::CompareIgnoreCase(Skeleton.Nodes[j].Name.GetText(), node.Name.GetText()) == 0)
|
||||
{
|
||||
mappingData.NodesMapping[j] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
#if !BUILD_RELEASE
|
||||
LOG(Error, "Invalid asset type {0} to use for skeleton mapping of {1}", source->GetTypeName(), ToString());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Add to cache
|
||||
_skeletonMappingCache.Add(source, mappingData);
|
||||
source->OnUnloaded.Bind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
source->OnReloading.Bind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
}
|
||||
mapping.SourceSkeleton = mappingData.SourceSkeleton;
|
||||
mapping.NodesMapping = mappingData.NodesMapping;
|
||||
return mapping;
|
||||
}
|
||||
|
||||
bool SkinnedModel::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
|
||||
{
|
||||
if (LODs.Count() == 0)
|
||||
return false;
|
||||
return LODs[lodIndex].Intersects(ray, world, distance, normal, mesh);
|
||||
}
|
||||
|
||||
bool SkinnedModel::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh, int32 lodIndex)
|
||||
{
|
||||
if (LODs.Count() == 0)
|
||||
return false;
|
||||
return LODs[lodIndex].Intersects(ray, transform, distance, normal, mesh);
|
||||
}
|
||||
|
||||
BoundingBox SkinnedModel::GetBox(const Matrix& world, int32 lodIndex) const
|
||||
{
|
||||
if (LODs.Count() == 0)
|
||||
return BoundingBox::Zero;
|
||||
return LODs[lodIndex].GetBox(world);
|
||||
}
|
||||
|
||||
BoundingBox SkinnedModel::GetBox(int32 lodIndex) const
|
||||
{
|
||||
if (LODs.Count() == 0)
|
||||
return BoundingBox::Zero;
|
||||
return LODs[lodIndex].GetBox();
|
||||
}
|
||||
|
||||
@@ -305,10 +451,8 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
|
||||
|
||||
ScopeLock lock(model->Locker);
|
||||
|
||||
// Setup nodes
|
||||
// Setup
|
||||
model->Skeleton.Nodes = nodes;
|
||||
|
||||
// Setup bones
|
||||
model->Skeleton.Bones.Resize(nodes.Count());
|
||||
for (int32 i = 0; i < nodes.Count(); i++)
|
||||
{
|
||||
@@ -317,6 +461,7 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes)
|
||||
model->Skeleton.Bones[i].LocalTransform = node.LocalTransform;
|
||||
model->Skeleton.Bones[i].NodeIndex = i;
|
||||
}
|
||||
ClearSkeletonMapping();
|
||||
|
||||
// Calculate offset matrix (inverse bind pose transform) for every bone manually
|
||||
for (int32 i = 0; i < model->Skeleton.Bones.Count(); i++)
|
||||
@@ -351,11 +496,10 @@ bool SkinnedModel::SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<S
|
||||
|
||||
ScopeLock lock(model->Locker);
|
||||
|
||||
// Setup nodes
|
||||
// Setup
|
||||
model->Skeleton.Nodes = nodes;
|
||||
|
||||
// Setup bones
|
||||
model->Skeleton.Bones = bones;
|
||||
ClearSkeletonMapping();
|
||||
|
||||
// Calculate offset matrix (inverse bind pose transform) for every bone manually
|
||||
if (autoCalculateOffsetMatrix)
|
||||
@@ -412,6 +556,9 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
MemoryWriteStream headerStream(1024);
|
||||
MemoryWriteStream* stream = &headerStream;
|
||||
{
|
||||
// Header Version
|
||||
stream->WriteByte(1);
|
||||
|
||||
// Min Screen Size
|
||||
stream->WriteFloat(MinScreenSize);
|
||||
|
||||
@@ -499,6 +646,17 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
stream->Write(bone.OffsetMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
stream->WriteInt32(_skeletonRetargets.Count());
|
||||
for (const auto& retarget : _skeletonRetargets)
|
||||
{
|
||||
stream->Write(retarget.SourceAsset);
|
||||
stream->Write(retarget.SkeletonAsset);
|
||||
stream->Write(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
@@ -552,8 +710,6 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for all
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
tasks.Clear();
|
||||
@@ -562,12 +718,10 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
int32 dataSize = meshesCount * (2 * sizeof(uint32) + sizeof(bool));
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
dataSize += meshesData[meshIndex].DataSize();
|
||||
}
|
||||
|
||||
MemoryWriteStream meshesStream(dataSize);
|
||||
MemoryWriteStream meshesStream(Math::RoundUpToPowerOf2(dataSize));
|
||||
|
||||
meshesStream.WriteByte(1);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = lod.Meshes[meshIndex];
|
||||
@@ -597,10 +751,10 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
return true;
|
||||
}
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
meshesStream.WriteUint32(vertices);
|
||||
meshesStream.WriteUint32(triangles);
|
||||
meshesStream.WriteUint16(mesh.BlendShapes.Count());
|
||||
|
||||
for (const auto& blendShape : mesh.BlendShapes)
|
||||
{
|
||||
meshesStream.WriteBool(blendShape.UseNormals);
|
||||
@@ -609,9 +763,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
meshesStream.WriteUint32(blendShape.Vertices.Count());
|
||||
meshesStream.WriteBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
|
||||
meshesStream.WriteBytes(meshData.VB0.Get(), vb0Size);
|
||||
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
meshesStream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
@@ -690,11 +842,10 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
// Setup
|
||||
MaterialSlots.Resize(1);
|
||||
MinScreenSize = 0.0f;
|
||||
|
||||
// Setup LODs
|
||||
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
|
||||
LODs[lodIndex].Dispose();
|
||||
LODs.Resize(meshesCountPerLod.Length());
|
||||
_initialized = true;
|
||||
|
||||
// Setup meshes
|
||||
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
|
||||
@@ -720,6 +871,49 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkinnedModel::ClearSkeletonMapping()
|
||||
{
|
||||
for (auto& e : _skeletonMappingCache)
|
||||
{
|
||||
e.Key->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
e.Key->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
Allocator::Free(e.Value.NodesMapping.Get());
|
||||
}
|
||||
_skeletonMappingCache.Clear();
|
||||
}
|
||||
|
||||
void SkinnedModel::OnSkeletonMappingSourceAssetUnloaded(Asset* obj)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
auto i = _skeletonMappingCache.Find(obj);
|
||||
ASSERT(i != _skeletonMappingCache.End());
|
||||
|
||||
// Unlink event
|
||||
obj->OnUnloaded.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#if USE_EDITOR
|
||||
obj->OnReloading.Unbind<SkinnedModel, &SkinnedModel::OnSkeletonMappingSourceAssetUnloaded>(this);
|
||||
#endif
|
||||
|
||||
// Clear cache
|
||||
Allocator::Free(i->Value.NodesMapping.Get());
|
||||
_skeletonMappingCache.Remove(i);
|
||||
}
|
||||
|
||||
uint64 SkinnedModel::GetMemoryUsage() const
|
||||
{
|
||||
Locker.Lock();
|
||||
uint64 result = BinaryAsset::GetMemoryUsage();
|
||||
result += sizeof(SkinnedModel) - sizeof(BinaryAsset);
|
||||
result += Skeleton.GetMemoryUsage();
|
||||
result += _skeletonMappingCache.Capacity() * sizeof(Dictionary<Asset*, Span<int32>>::Bucket);
|
||||
for (const auto& e : _skeletonMappingCache)
|
||||
result += e.Value.NodesMapping.Length();
|
||||
Locker.Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
void SkinnedModel::SetupMaterialSlots(int32 slotsCount)
|
||||
{
|
||||
ModelBase::SetupMaterialSlots(slotsCount);
|
||||
@@ -754,6 +948,7 @@ void SkinnedModel::InitAsVirtual()
|
||||
// Init with one mesh and single bone
|
||||
int32 meshesCount = 1;
|
||||
Init(ToSpan(&meshesCount, 1));
|
||||
ClearSkeletonMapping();
|
||||
Skeleton.Dispose();
|
||||
//
|
||||
Skeleton.Nodes.Resize(1);
|
||||
@@ -874,13 +1069,16 @@ Asset::LoadResult SkinnedModel::load()
|
||||
MemoryReadStream headerStream(chunk0->Get(), chunk0->Size());
|
||||
ReadStream* stream = &headerStream;
|
||||
|
||||
// Header Version
|
||||
byte version = stream->ReadByte();
|
||||
|
||||
// Min Screen Size
|
||||
stream->ReadFloat(&MinScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
int32 materialSlotsCount;
|
||||
stream->ReadInt32(&materialSlotsCount);
|
||||
if (materialSlotsCount <= 0 || materialSlotsCount > 4096)
|
||||
if (materialSlotsCount < 0 || materialSlotsCount > 4096)
|
||||
return LoadResult::InvalidData;
|
||||
MaterialSlots.Resize(materialSlotsCount, false);
|
||||
|
||||
@@ -904,9 +1102,10 @@ Asset::LoadResult SkinnedModel::load()
|
||||
// Amount of LODs
|
||||
byte lods;
|
||||
stream->ReadByte(&lods);
|
||||
if (lods == 0 || lods > MODEL_MAX_LODS)
|
||||
if (lods > MODEL_MAX_LODS)
|
||||
return LoadResult::InvalidData;
|
||||
LODs.Resize(lods);
|
||||
_initialized = true;
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
|
||||
@@ -972,12 +1171,9 @@ Asset::LoadResult SkinnedModel::load()
|
||||
if (nodesCount <= 0)
|
||||
return LoadResult::InvalidData;
|
||||
Skeleton.Nodes.Resize(nodesCount, false);
|
||||
|
||||
// For each node
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
|
||||
{
|
||||
auto& node = Skeleton.Nodes[nodeIndex];
|
||||
|
||||
auto& node = Skeleton.Nodes.Get()[nodeIndex];
|
||||
stream->Read(node.ParentIndex);
|
||||
stream->ReadTransform(&node.LocalTransform);
|
||||
stream->ReadString(&node.Name, 71);
|
||||
@@ -988,12 +1184,9 @@ Asset::LoadResult SkinnedModel::load()
|
||||
if (bonesCount <= 0)
|
||||
return LoadResult::InvalidData;
|
||||
Skeleton.Bones.Resize(bonesCount, false);
|
||||
|
||||
// For each bone
|
||||
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
|
||||
{
|
||||
auto& bone = Skeleton.Bones[boneIndex];
|
||||
|
||||
auto& bone = Skeleton.Bones.Get()[boneIndex];
|
||||
stream->Read(bone.ParentIndex);
|
||||
stream->Read(bone.NodeIndex);
|
||||
stream->ReadTransform(&bone.LocalTransform);
|
||||
@@ -1001,6 +1194,20 @@ Asset::LoadResult SkinnedModel::load()
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
int32 entriesCount;
|
||||
stream->ReadInt32(&entriesCount);
|
||||
_skeletonRetargets.Resize(entriesCount);
|
||||
for (int32 entryIndex = 0; entryIndex < entriesCount; entryIndex++)
|
||||
{
|
||||
auto& retarget = _skeletonRetargets[entryIndex];
|
||||
stream->Read(retarget.SourceAsset);
|
||||
stream->Read(retarget.SkeletonAsset);
|
||||
stream->Read(retarget.NodesMapping);
|
||||
}
|
||||
}
|
||||
|
||||
// Request resource streaming
|
||||
StartStreaming(true);
|
||||
|
||||
@@ -1023,7 +1230,10 @@ void SkinnedModel::unload(bool isReloading)
|
||||
LODs[i].Dispose();
|
||||
LODs.Clear();
|
||||
Skeleton.Dispose();
|
||||
_initialized = false;
|
||||
_loadedLODs = 0;
|
||||
_skeletonRetargets.Clear();
|
||||
ClearSkeletonMapping();
|
||||
}
|
||||
|
||||
bool SkinnedModel::init(AssetInitData& initData)
|
||||
|
||||
@@ -14,12 +14,32 @@ class StreamSkinnedModelLODTask;
|
||||
/// </summary>
|
||||
API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
|
||||
{
|
||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 4);
|
||||
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
|
||||
friend SkinnedMesh;
|
||||
friend StreamSkinnedModelLODTask;
|
||||
public:
|
||||
// Skeleton mapping descriptor.
|
||||
struct FLAXENGINE_API SkeletonMapping
|
||||
{
|
||||
// Target skeleton.
|
||||
AssetReference<SkinnedModel> TargetSkeleton;
|
||||
// Source skeleton.
|
||||
AssetReference<SkinnedModel> SourceSkeleton;
|
||||
// The node-to-node mapping for the fast animation sampling for the skinned model skeleton nodes. Each item is index of the source skeleton node into target skeleton node.
|
||||
Span<int32> NodesMapping;
|
||||
};
|
||||
|
||||
private:
|
||||
struct SkeletonMappingData
|
||||
{
|
||||
AssetReference<SkinnedModel> SourceSkeleton;
|
||||
Span<int32> NodesMapping;
|
||||
};
|
||||
|
||||
bool _initialized = false;
|
||||
int32 _loadedLODs = 0;
|
||||
StreamSkinnedModelLODTask* _streamingTask = nullptr;
|
||||
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
@@ -44,7 +64,7 @@ public:
|
||||
/// </summary>
|
||||
FORCE_INLINE bool IsInitialized() const
|
||||
{
|
||||
return LODs.HasItems();
|
||||
return _initialized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -76,13 +96,11 @@ public:
|
||||
/// <summary>
|
||||
/// Determines whether any LOD has been initialized.
|
||||
/// </summary>
|
||||
/// <returns>True if any LOD has been initialized, otherwise false.</returns>
|
||||
bool HasAnyLODInitialized() const;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this model can be rendered.
|
||||
/// </summary>
|
||||
/// <returns>True if can render that model, otherwise false.</returns>
|
||||
FORCE_INLINE bool CanBeRendered() const
|
||||
{
|
||||
return _loadedLODs > 0;
|
||||
@@ -109,7 +127,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the node.</param>
|
||||
/// <returns>The index of the node or -1 if not found.</returns>
|
||||
API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name)
|
||||
API_FUNCTION() FORCE_INLINE int32 FindNode(const StringView& name) const
|
||||
{
|
||||
return Skeleton.FindNode(name);
|
||||
}
|
||||
@@ -119,7 +137,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the node used by the bone.</param>
|
||||
/// <returns>The index of the bone or -1 if not found.</returns>
|
||||
API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name)
|
||||
API_FUNCTION() FORCE_INLINE int32 FindBone(const StringView& name) const
|
||||
{
|
||||
return FindBone(FindNode(name));
|
||||
}
|
||||
@@ -129,7 +147,7 @@ public:
|
||||
/// </summary>
|
||||
/// <param name="nodeIndex">The index of the node.</param>
|
||||
/// <returns>The index of the bone or -1 if not found.</returns>
|
||||
API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex)
|
||||
API_FUNCTION() FORCE_INLINE int32 FindBone(int32 nodeIndex) const
|
||||
{
|
||||
return Skeleton.FindBone(nodeIndex);
|
||||
}
|
||||
@@ -154,7 +172,13 @@ public:
|
||||
/// <param name="data">The data (may be missing if failed to get it).</param>
|
||||
void GetLODData(int32 lodIndex, BytesContainer& data) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
|
||||
/// </summary>
|
||||
/// <param name="source">The source asset (animation or other skinned model) to get mapping to its skeleton.</param>
|
||||
/// <returns>The skeleton mapping for the source asset into this skeleton.</returns>
|
||||
SkeletonMapping GetSkeletonMapping(Asset* source);
|
||||
|
||||
/// <summary>
|
||||
/// Determines if there is an intersection between the SkinnedModel and a Ray in given world using given instance.
|
||||
/// </summary>
|
||||
@@ -194,7 +218,6 @@ public:
|
||||
/// <returns>The bounding box.</returns>
|
||||
API_FUNCTION() BoundingBox GetBox(int32 lodIndex = 0) const;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Draws the meshes. Binds vertex and index buffers and invokes the draw calls.
|
||||
/// </summary>
|
||||
@@ -219,7 +242,6 @@ public:
|
||||
/// <param name="info">The packed drawing info data.</param>
|
||||
void Draw(const RenderContextBatch& renderContextBatch, const SkinnedMesh::DrawInfo& info);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Setups the model LODs collection including meshes creation.
|
||||
/// </summary>
|
||||
@@ -244,7 +266,6 @@ public:
|
||||
API_FUNCTION() bool SetupSkeleton(const Array<SkeletonNode>& nodes, const Array<SkeletonBone>& bones, bool autoCalculateOffsetMatrix);
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
/// <summary>
|
||||
/// Saves this asset to the file. Supported only in Editor.
|
||||
/// </summary>
|
||||
@@ -253,7 +274,6 @@ public:
|
||||
/// <param name="path">The custom asset path to use for the saving. Use empty value to save this asset to its own storage location. Can be used to duplicate asset. Must be specified when saving virtual asset.</param>
|
||||
/// <returns>True if cannot save data, otherwise false.</returns>
|
||||
API_FUNCTION() bool Save(bool withMeshDataFromGpu = false, const StringView& path = StringView::Empty);
|
||||
|
||||
#endif
|
||||
|
||||
private:
|
||||
@@ -264,8 +284,38 @@ private:
|
||||
/// <returns>True if failed, otherwise false.</returns>
|
||||
bool Init(const Span<int32>& meshesCountPerLod);
|
||||
|
||||
void ClearSkeletonMapping();
|
||||
void OnSkeletonMappingSourceAssetUnloaded(Asset* obj);
|
||||
|
||||
#if USE_EDITOR
|
||||
public:
|
||||
// Skeleton retargeting setup (internal use only - accessed by Editor)
|
||||
API_STRUCT(NoDefault) struct SkeletonRetarget
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(SkeletonRetarget);
|
||||
// Source asset id.
|
||||
API_FIELD() Guid SourceAsset;
|
||||
// Skeleton asset id to use for remapping.
|
||||
API_FIELD() Guid SkeletonAsset;
|
||||
// Skeleton nodes remapping table (maps this skeleton node name to other skeleton node).
|
||||
API_FIELD() Dictionary<String, String, HeapAllocation> NodesMapping;
|
||||
};
|
||||
// Gets or sets the skeleton retarget entries (accessed in Editor only).
|
||||
API_PROPERTY() const Array<SkeletonRetarget>& GetSkeletonRetargets() const { return _skeletonRetargets; }
|
||||
API_PROPERTY() void SetSkeletonRetargets(const Array<SkeletonRetarget>& value) { Locker.Lock(); _skeletonRetargets = value; ClearSkeletonMapping(); Locker.Unlock(); }
|
||||
private:
|
||||
#else
|
||||
struct SkeletonRetarget
|
||||
{
|
||||
Guid SourceAsset, SkeletonAsset;
|
||||
Dictionary<String, String, HeapAllocation> NodesMapping;
|
||||
};
|
||||
#endif
|
||||
Array<SkeletonRetarget> _skeletonRetargets;
|
||||
|
||||
public:
|
||||
// [ModelBase]
|
||||
uint64 GetMemoryUsage() const override;
|
||||
void SetupMaterialSlots(int32 slotsCount) override;
|
||||
int32 GetLODsCount() const override;
|
||||
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Graphics/RenderTools.h"
|
||||
#include "Engine/Graphics/Textures/TextureData.h"
|
||||
#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Tools/TextureTool/TextureTool.h"
|
||||
|
||||
REGISTER_BINARY_ASSET_WITH_UPGRADER(Texture, "FlaxEngine.Texture", TextureAssetUpgrader, true);
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
#include "Engine/Core/Types/DataContainer.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/Factories/BinaryAssetFactory.h"
|
||||
#include "Engine/Scripting/MException.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/Events.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MField.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MType.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
@@ -310,15 +309,15 @@ void VisualScriptExecutor::ProcessGroupTools(Box* box, Node* node, Value& value)
|
||||
const StringAsANSI<100> typeNameAnsi(typeName.Get(), typeName.Length());
|
||||
if (StringUtils::Compare(typeNameAnsi.Get(), obj.Type.GetTypeName()) != 0)
|
||||
{
|
||||
#if USE_MONO
|
||||
MonoClass* klass = Scripting::FindClassNative(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
|
||||
MonoClass* objKlass = MUtils::GetClass(obj);
|
||||
if (!klass || !objKlass || mono_class_is_subclass_of(objKlass, klass, false) == 0)
|
||||
#if USE_CSHARP
|
||||
MClass* klass = Scripting::FindClass(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
|
||||
MClass* objKlass = MUtils::GetClass(obj);
|
||||
if (!klass || !objKlass || !objKlass->IsSubClassOf(klass))
|
||||
obj = Value::Null;
|
||||
#else
|
||||
const ScriptingTypeHandle type = Scripting::FindScriptingType(StringAnsiView(typeNameAnsi.Get(), typeName.Length()));
|
||||
const ScriptingTypeHandle objType = Scripting::FindScriptingType(obj.Type.GetTypeName());
|
||||
if (!type || !objType || objType.IsSubclassOf(type))
|
||||
if (!type || !objType || !objType.IsSubclassOf(type))
|
||||
obj = Value::Null;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -760,9 +760,14 @@ bool Content::CloneAssetFile(const StringView& dstPath, const StringView& srcPat
|
||||
// Change asset ID
|
||||
{
|
||||
auto storage = ContentStorageManager::GetStorage(tmpPath);
|
||||
if (!storage)
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
}
|
||||
FlaxStorage::Entry e;
|
||||
storage->GetEntry(0, e);
|
||||
if (!storage || storage->ChangeAssetID(e, dstId))
|
||||
if (storage->ChangeAssetID(e, dstId))
|
||||
{
|
||||
LOG(Warning, "Cannot change asset ID.");
|
||||
return true;
|
||||
|
||||
@@ -416,9 +416,13 @@ void JsonAsset::DeleteInstance()
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// C# instance
|
||||
if (MObject* object = GetManagedInstance())
|
||||
MObject* object = GetManagedInstance();
|
||||
MClass* klass = GetClass();
|
||||
if (object && klass)
|
||||
{
|
||||
GetClass()->GetField("_instance")->SetValue(object, nullptr);
|
||||
const MField* field = klass->GetField("_instance");
|
||||
if (field)
|
||||
field->SetValue(object, nullptr);
|
||||
}
|
||||
|
||||
// C++ instance
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace FlaxEngine
|
||||
Debug.LogError(string.Format("Missing typename of data in Json asset '{0}'.", Path), this);
|
||||
return null;
|
||||
}
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
var assemblies = Utils.GetAssemblies();
|
||||
for (int i = 0; i < assemblies.Length; i++)
|
||||
{
|
||||
var assembly = assemblies[i];
|
||||
|
||||
@@ -211,6 +211,12 @@ FlaxStorage::~FlaxStorage()
|
||||
CHECK(_chunksLock == 0);
|
||||
CHECK(_refCount == 0);
|
||||
ASSERT(_chunks.IsEmpty());
|
||||
|
||||
#if USE_EDITOR
|
||||
// Ensure to close any outstanding file handles to prevent file locking in case it failed to load
|
||||
_file.DeleteAll();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
FlaxStorage::LockData FlaxStorage::LockSafe()
|
||||
|
||||
@@ -26,10 +26,10 @@ public:
|
||||
{
|
||||
static const Upgrader upgraders[] =
|
||||
{
|
||||
{ 24, 25, &Upgrade_With_Repack },
|
||||
{ 23, 24, &Upgrade_22OrNewer_To_Newest },
|
||||
{ 22, 24, &Upgrade_22OrNewer_To_Newest },
|
||||
{ 1, 24, &Upgrade_Old_To_Newest },
|
||||
{ 24, 25, &Upgrade_With_Repack }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
{ 23, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
{ 22, 24, &Upgrade_22OrNewer_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
{ 1, 24, &Upgrade_Old_To_Newest }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
|
||||
@@ -28,9 +28,10 @@ public:
|
||||
{
|
||||
static const Upgrader upgraders[] =
|
||||
{
|
||||
{ 1, 2, &Upgrade_1_To_2 },
|
||||
{ 2, 3, &Upgrade_2_To_3 },
|
||||
{ 3, 4, &Upgrade_3_To_4 },
|
||||
{ 1, 2, &Upgrade_1_To_2 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
{ 2, 3, &Upgrade_2_To_3 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
{ 3, 4, &Upgrade_3_To_4 }, // [Deprecated on 28.04.2023, expires on 01.01.2024]
|
||||
{ 4, 5, &Upgrade_4_To_5 }, // [Deprecated on 28.04.2023, expires on 28.04.2026]
|
||||
};
|
||||
setup(upgraders, ARRAY_COUNT(upgraders));
|
||||
}
|
||||
@@ -57,7 +58,6 @@ private:
|
||||
MemoryWriteStream output(srcData->Size());
|
||||
do
|
||||
{
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
@@ -422,6 +422,241 @@ private:
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool Upgrade_4_To_5(AssetMigrationContext& context)
|
||||
{
|
||||
ASSERT(context.Input.SerializedVersion == 4 && context.Output.SerializedVersion == 5);
|
||||
|
||||
// Changes:
|
||||
// - added version number to header (for easier changes in future)
|
||||
// - added version number to mesh data (for easier changes in future)
|
||||
// - added skeleton retarget setups to header
|
||||
|
||||
// Rewrite header chunk (added header version and retarget entries)
|
||||
byte lodCount;
|
||||
Array<uint16> meshesCounts;
|
||||
{
|
||||
const auto srcData = context.Input.Header.Chunks[0];
|
||||
if (srcData == nullptr || srcData->IsMissing())
|
||||
{
|
||||
LOG(Warning, "Missing model header chunk");
|
||||
return true;
|
||||
}
|
||||
MemoryReadStream stream(srcData->Get(), srcData->Size());
|
||||
MemoryWriteStream output(srcData->Size());
|
||||
|
||||
// Header Version
|
||||
output.WriteByte(1);
|
||||
|
||||
// Min Screen Size
|
||||
float minScreenSize;
|
||||
stream.ReadFloat(&minScreenSize);
|
||||
output.WriteFloat(minScreenSize);
|
||||
|
||||
// Amount of material slots
|
||||
int32 materialSlotsCount;
|
||||
stream.ReadInt32(&materialSlotsCount);
|
||||
output.WriteInt32(materialSlotsCount);
|
||||
|
||||
// For each material slot
|
||||
for (int32 materialSlotIndex = 0; materialSlotIndex < materialSlotsCount; materialSlotIndex++)
|
||||
{
|
||||
// Material
|
||||
Guid materialId;
|
||||
stream.Read(materialId);
|
||||
output.Write(materialId);
|
||||
|
||||
// Shadows Mode
|
||||
output.WriteByte(stream.ReadByte());
|
||||
|
||||
// Name
|
||||
String name;
|
||||
stream.ReadString(&name, 11);
|
||||
output.WriteString(name, 11);
|
||||
}
|
||||
|
||||
// Amount of LODs
|
||||
stream.ReadByte(&lodCount);
|
||||
output.WriteByte(lodCount);
|
||||
meshesCounts.Resize(lodCount);
|
||||
|
||||
// For each LOD
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
// Screen Size
|
||||
float screenSize;
|
||||
stream.ReadFloat(&screenSize);
|
||||
output.WriteFloat(screenSize);
|
||||
|
||||
// Amount of meshes
|
||||
uint16 meshesCount;
|
||||
stream.ReadUint16(&meshesCount);
|
||||
output.WriteUint16(meshesCount);
|
||||
meshesCounts[lodIndex] = meshesCount;
|
||||
|
||||
// For each mesh
|
||||
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
// Material Slot index
|
||||
int32 materialSlotIndex;
|
||||
stream.ReadInt32(&materialSlotIndex);
|
||||
output.WriteInt32(materialSlotIndex);
|
||||
|
||||
// Box
|
||||
BoundingBox box;
|
||||
stream.Read(box);
|
||||
output.Write(box);
|
||||
|
||||
// Sphere
|
||||
BoundingSphere sphere;
|
||||
stream.Read(sphere);
|
||||
output.Write(sphere);
|
||||
|
||||
// Blend Shapes
|
||||
uint16 blendShapes;
|
||||
stream.ReadUint16(&blendShapes);
|
||||
output.WriteUint16(blendShapes);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapes; blendShapeIndex++)
|
||||
{
|
||||
String blendShapeName;
|
||||
stream.ReadString(&blendShapeName, 13);
|
||||
output.WriteString(blendShapeName, 13);
|
||||
float blendShapeWeight;
|
||||
stream.ReadFloat(&blendShapeWeight);
|
||||
output.WriteFloat(blendShapeWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skeleton
|
||||
{
|
||||
int32 nodesCount;
|
||||
stream.ReadInt32(&nodesCount);
|
||||
output.WriteInt32(nodesCount);
|
||||
|
||||
// For each node
|
||||
for (int32 nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++)
|
||||
{
|
||||
int32 parentIndex;
|
||||
stream.ReadInt32(&parentIndex);
|
||||
output.WriteInt32(parentIndex);
|
||||
|
||||
Transform localTransform;
|
||||
stream.Read(localTransform);
|
||||
output.Write(localTransform);
|
||||
|
||||
String name;
|
||||
stream.ReadString(&name, 71);
|
||||
output.WriteString(name, 71);
|
||||
}
|
||||
|
||||
int32 bonesCount;
|
||||
stream.ReadInt32(&bonesCount);
|
||||
output.WriteInt32(bonesCount);
|
||||
|
||||
// For each bone
|
||||
for (int32 boneIndex = 0; boneIndex < bonesCount; boneIndex++)
|
||||
{
|
||||
int32 parentIndex;
|
||||
stream.ReadInt32(&parentIndex);
|
||||
output.WriteInt32(parentIndex);
|
||||
|
||||
int32 nodeIndex;
|
||||
stream.ReadInt32(&nodeIndex);
|
||||
output.WriteInt32(nodeIndex);
|
||||
|
||||
Transform localTransform;
|
||||
stream.Read(localTransform);
|
||||
output.Write(localTransform);
|
||||
|
||||
Matrix offsetMatrix;
|
||||
stream.ReadBytes(&offsetMatrix, sizeof(Matrix));
|
||||
output.WriteBytes(&offsetMatrix, sizeof(Matrix));
|
||||
}
|
||||
}
|
||||
|
||||
// Retargeting
|
||||
{
|
||||
output.WriteInt32(0);
|
||||
}
|
||||
|
||||
// Save new data
|
||||
if (stream.GetPosition() != stream.GetLength())
|
||||
{
|
||||
LOG(Error, "Invalid position after upgrading skinned model header data.");
|
||||
return true;
|
||||
}
|
||||
if (context.AllocateChunk(0))
|
||||
return true;
|
||||
context.Output.Header.Chunks[0]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
// Rewrite meshes data chunks
|
||||
for (int32 lodIndex = 0; lodIndex < lodCount; lodIndex++)
|
||||
{
|
||||
const int32 chunkIndex = lodIndex + 1;
|
||||
const auto srcData = context.Input.Header.Chunks[chunkIndex];
|
||||
if (srcData == nullptr || srcData->IsMissing())
|
||||
{
|
||||
LOG(Warning, "Missing skinned model LOD meshes data chunk");
|
||||
return true;
|
||||
}
|
||||
MemoryReadStream stream(srcData->Get(), srcData->Size());
|
||||
MemoryWriteStream output(srcData->Size());
|
||||
|
||||
// Mesh Data Version
|
||||
output.WriteByte(1);
|
||||
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCounts[lodIndex]; meshIndex++)
|
||||
{
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
output.WriteUint32(vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
output.WriteUint32(triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
output.WriteUint16(blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
output.WriteBool(stream.ReadBool());
|
||||
uint32 minVertexIndex, maxVertexIndex;
|
||||
stream.ReadUint32(&minVertexIndex);
|
||||
output.WriteUint32(minVertexIndex);
|
||||
stream.ReadUint32(&maxVertexIndex);
|
||||
output.WriteUint32(maxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
output.WriteUint32(blendShapeVertices);
|
||||
const uint32 blendShapeDataSize = blendShapeVertices * sizeof(BlendShapeVertex);
|
||||
const auto blendShapeData = stream.Move<byte>(blendShapeDataSize);
|
||||
output.WriteBytes(blendShapeData, blendShapeDataSize);
|
||||
}
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
const auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
|
||||
output.WriteBytes(vb0, vertices * sizeof(VB0SkinnedElementType));
|
||||
const auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
output.WriteBytes(ib, indicesCount * ibStride);
|
||||
}
|
||||
|
||||
// Save new data
|
||||
if (stream.GetPosition() != stream.GetLength())
|
||||
{
|
||||
LOG(Error, "Invalid position after upgrading skinned model LOD meshes data.");
|
||||
return true;
|
||||
}
|
||||
if (context.AllocateChunk(chunkIndex))
|
||||
return true;
|
||||
context.Output.Header.Chunks[chunkIndex]->Data.Copy(output.GetHandle(), output.GetPosition());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -151,6 +151,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
// Extract all meshes
|
||||
const auto& lod = asset->LODs[lodIndex];
|
||||
int32 vertexStart = 1; // OBJ counts vertices from 1 not from 0
|
||||
byte version = stream.ReadByte();
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& mesh = lod.Meshes[meshIndex];
|
||||
@@ -160,6 +161,18 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
for (int32 blendShapeIndex = 0; blendShapeIndex < blendShapesCount; blendShapeIndex++)
|
||||
{
|
||||
stream.ReadBool();
|
||||
uint32 tmp;
|
||||
stream.ReadUint32(&tmp);
|
||||
stream.ReadUint32(&tmp);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
stream.Move(blendShapeVertices * sizeof(BlendShapeVertex));
|
||||
}
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
|
||||
@@ -30,7 +30,7 @@ bool CreateJson::Create(const StringView& path, rapidjson_flax::StringBuffer& da
|
||||
return Create(path, data1, data2);
|
||||
}
|
||||
|
||||
bool CreateJson::Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename)
|
||||
bool CreateJson::Create(const StringView& path, const StringAnsiView& data, const StringAnsiView& dataTypename)
|
||||
{
|
||||
Guid id = Guid::New();
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ class CreateJson
|
||||
public:
|
||||
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const String& dataTypename);
|
||||
static bool Create(const StringView& path, rapidjson_flax::StringBuffer& data, const char* dataTypename);
|
||||
static bool Create(const StringView& path, StringAnsiView& data, StringAnsiView& dataTypename);
|
||||
static bool Create(const StringView& path, const StringAnsiView& data, const StringAnsiView& dataTypename);
|
||||
static CreateAssetResult ImportPo(CreateAssetContext& context);
|
||||
};
|
||||
|
||||
|
||||
@@ -45,12 +45,11 @@ void ImportAudio::Options::Deserialize(DeserializeStream& stream, ISerializeModi
|
||||
DESERIALIZE(BitDepth);
|
||||
}
|
||||
|
||||
bool ImportAudio::TryGetImportOptions(const String& path, Options& options)
|
||||
bool ImportAudio::TryGetImportOptions(const StringView& path, Options& options)
|
||||
{
|
||||
#if IMPORT_AUDIO_CACHE_OPTIONS
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
// Try to load asset file and asset info
|
||||
auto tmpFile = ContentStorageManager::GetStorage(path);
|
||||
AssetInitData data;
|
||||
if (tmpFile
|
||||
@@ -64,13 +63,11 @@ bool ImportAudio::TryGetImportOptions(const String& path, Options& options)
|
||||
metadata.Parse(data.Metadata.Get<const char>(), data.Metadata.Length());
|
||||
if (!metadata.HasParseError())
|
||||
{
|
||||
// Success
|
||||
options.Deserialize(metadata, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public:
|
||||
/// <param name="path">The asset path.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>True if success, otherwise false.</returns>
|
||||
static bool TryGetImportOptions(const String& path, Options& options);
|
||||
static bool TryGetImportOptions(const StringView& path, Options& options);
|
||||
|
||||
/// <summary>
|
||||
/// Imports the audio data (with given audio decoder).
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
/// <param name="path">The asset path.</param>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>True if success, otherwise false.</returns>
|
||||
static bool TryGetImportOptions(String path, Options& options);
|
||||
static bool TryGetImportOptions(const StringView& path, Options& options);
|
||||
|
||||
/// <summary>
|
||||
/// Imports the model file.
|
||||
|
||||
@@ -16,11 +16,9 @@
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "AssetsImportingManager.h"
|
||||
|
||||
bool ImportModelFile::TryGetImportOptions(String path, Options& options)
|
||||
bool ImportModelFile::TryGetImportOptions(const StringView& path, Options& options)
|
||||
{
|
||||
#if IMPORT_MODEL_CACHE_OPTIONS
|
||||
|
||||
// Check if target asset file exists
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
// Try to load asset file and asset info
|
||||
@@ -41,15 +39,12 @@ bool ImportModelFile::TryGetImportOptions(String path, Options& options)
|
||||
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
|
||||
if (metadata.HasParseError() == false)
|
||||
{
|
||||
// Success
|
||||
options.Deserialize(metadata, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -269,6 +264,9 @@ CreateAssetResult ImportModelFile::ImportSkinnedModel(CreateAssetContext& contex
|
||||
{
|
||||
stream.SetPosition(0);
|
||||
|
||||
// Mesh Data Version
|
||||
stream.WriteByte(1);
|
||||
|
||||
// Pack meshes
|
||||
auto& meshes = modelData.LODs[lodIndex].Meshes;
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ImportTexture.h"
|
||||
|
||||
#if COMPILE_WITH_ASSETS_IMPORTER
|
||||
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Serialization/Serialization.h"
|
||||
#include "Engine/Serialization/JsonWriters.h"
|
||||
@@ -28,11 +26,8 @@ bool IsSpriteAtlasOrTexture(const String& typeName)
|
||||
bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options)
|
||||
{
|
||||
#if IMPORT_TEXTURE_CACHE_OPTIONS
|
||||
|
||||
// Check if target asset texture exists
|
||||
if (FileSystem::FileExists(path))
|
||||
{
|
||||
// Try to load asset file and asset info (also check for Sprite Atlas or Texture assets)
|
||||
auto tmpFile = ContentStorageManager::GetStorage(path);
|
||||
AssetInitData data;
|
||||
if (tmpFile
|
||||
@@ -48,12 +43,11 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options
|
||||
if (chunk15 != nullptr && !tmpFile->LoadAssetChunk(chunk15) && chunk15->Data.IsValid())
|
||||
{
|
||||
MemoryReadStream stream(chunk15->Data.Get(), chunk15->Data.Length());
|
||||
|
||||
// Load tiles data
|
||||
int32 tilesVersion, tilesCount;
|
||||
stream.ReadInt32(&tilesVersion);
|
||||
if (tilesVersion == 1)
|
||||
{
|
||||
options.Sprites.Clear();
|
||||
stream.ReadInt32(&tilesCount);
|
||||
for (int32 i = 0; i < tilesCount; i++)
|
||||
{
|
||||
@@ -72,15 +66,12 @@ bool ImportTexture::TryGetImportOptions(const StringView& path, Options& options
|
||||
metadata.Parse((const char*)data.Metadata.Get(), data.Metadata.Length());
|
||||
if (metadata.HasParseError() == false)
|
||||
{
|
||||
// Success
|
||||
options.Deserialize(metadata, nullptr);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,37 +13,37 @@
|
||||
/// </summary>
|
||||
API_CLASS(sealed, Namespace="FlaxEditor.Content.Settings") class FLAXENGINE_API BuildSettings : public SettingsBase
|
||||
{
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
|
||||
public:
|
||||
DECLARE_SCRIPTING_TYPE_MINIMAL(BuildSettings);
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// The maximum amount of assets to include into a single assets package. Asset packages will split into several packages if need to.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(10), DefaultValue(4096), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")")
|
||||
API_FIELD(Attributes="EditorOrder(10), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max assets per package\")")
|
||||
int32 MaxAssetsPerPackage = 4096;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum size of the single assets package (in megabytes). Asset packages will split into several packages if need to.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(20), DefaultValue(1024), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")")
|
||||
API_FIELD(Attributes="EditorOrder(20), Limit(1, ushort.MaxValue), EditorDisplay(\"General\", \"Max package size (in MB)\")")
|
||||
int32 MaxPackageSizeMB = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// The game content cooking keycode. Use the same value for a game and DLC packages to support loading them by the build game. Use 0 to randomize it during building.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(30), DefaultValue(0), EditorDisplay(\"General\")")
|
||||
API_FIELD(Attributes="EditorOrder(30), EditorDisplay(\"General\")")
|
||||
int32 ContentKey = 0;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, the builds produced by the Game Cooker will be treated as for final game distribution (eg. for game store upload). Builds done this way cannot be tested on console devkits (eg. Xbox One, Xbox Scarlett).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(40), DefaultValue(false), EditorDisplay(\"General\")")
|
||||
API_FIELD(Attributes="EditorOrder(40), EditorDisplay(\"General\")")
|
||||
bool ForDistribution = false;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, the output build files won't be packaged for the destination platform. Useful when debugging build from local PC.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(50), DefaultValue(false), EditorDisplay(\"General\")")
|
||||
API_FIELD(Attributes="EditorOrder(50), EditorDisplay(\"General\")")
|
||||
bool SkipPackaging = false;
|
||||
|
||||
/// <summary>
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(1000), EditorDisplay(\"Additional Data\")")
|
||||
Array<AssetReference<Asset>> AdditionalAssets;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The list of additional scenes to include into build (into root assets set).
|
||||
/// </summary>
|
||||
@@ -67,17 +67,28 @@ public:
|
||||
/// <summary>
|
||||
/// Disables shaders compiler optimizations in cooked game. Can be used to debug shaders on a target platform or to speed up the shaders compilation time.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2000), DefaultValue(false), EditorDisplay(\"Content\", \"Shaders No Optimize\")")
|
||||
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Content\", \"Shaders No Optimize\")")
|
||||
bool ShadersNoOptimize = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enables shader debug data generation for shaders in cooked game (depends on the target platform rendering backend).
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(2010), DefaultValue(false), EditorDisplay(\"Content\")")
|
||||
API_FIELD(Attributes="EditorOrder(2010), EditorDisplay(\"Content\")")
|
||||
bool ShadersGenerateDebugData = false;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(3000), EditorDisplay(\"Scripting\", \"Skip .NET Runtime Packaging\")")
|
||||
bool SkipDotnetPackaging = false;
|
||||
|
||||
/// <summary>
|
||||
/// If checked, .NET Runtime packaging will skip unused libraries from packaging resulting in smaller game builds.
|
||||
/// </summary>
|
||||
API_FIELD(Attributes="EditorOrder(3010), EditorDisplay(\"Scripting\", \"Skip Unused .NET Runtime Libs Packaging\")")
|
||||
bool SkipUnusedDotnetLibsPackaging = true;
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.
|
||||
/// </summary>
|
||||
@@ -95,5 +106,7 @@ public:
|
||||
DESERIALIZE(AdditionalAssetFolders);
|
||||
DESERIALIZE(ShadersNoOptimize);
|
||||
DESERIALIZE(ShadersGenerateDebugData);
|
||||
DESERIALIZE(SkipDotnetPackaging);
|
||||
DESERIALIZE(SkipUnusedDotnetLibsPackaging);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -71,6 +71,8 @@ IMPLEMENT_ENGINE_SETTINGS_GETTER(AndroidPlatformSettings, AndroidPlatform);
|
||||
IMPLEMENT_ENGINE_SETTINGS_GETTER(SwitchPlatformSettings, SwitchPlatform);
|
||||
#elif PLATFORM_MAC
|
||||
IMPLEMENT_ENGINE_SETTINGS_GETTER(MacPlatformSettings, MacPlatform);
|
||||
#elif PLATFORM_IOS
|
||||
IMPLEMENT_ENGINE_SETTINGS_GETTER(iOSPlatformSettings, iOSPlatform);
|
||||
#else
|
||||
#error Unknown platform
|
||||
#endif
|
||||
@@ -254,6 +256,7 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo
|
||||
DESERIALIZE(SwitchPlatform);
|
||||
DESERIALIZE(PS5Platform);
|
||||
DESERIALIZE(MacPlatform);
|
||||
DESERIALIZE(iOSPlatform);
|
||||
}
|
||||
|
||||
void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Content.Settings
|
||||
@@ -201,6 +202,14 @@ namespace FlaxEditor.Content.Settings
|
||||
public JsonAsset MacPlatform;
|
||||
#endif
|
||||
|
||||
#if FLAX_EDITOR || PLATFORM_IOS
|
||||
/// <summary>
|
||||
/// Reference to <see cref="iOSPlatformSettings"/> asset. Used to apply configuration on iOS platform.
|
||||
/// </summary>
|
||||
[EditorOrder(2100), EditorDisplay("Platform Settings", "iOS"), AssetReference(typeof(iOSPlatformSettings), true), Tooltip("Reference to iOS Platform Settings asset")]
|
||||
public JsonAsset iOSPlatform;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Gets the absolute path to the game settings asset file.
|
||||
/// </summary>
|
||||
@@ -332,6 +341,10 @@ namespace FlaxEditor.Content.Settings
|
||||
if (type == typeof(MacPlatformSettings))
|
||||
return Load<MacPlatformSettings>(gameSettings.MacPlatform) as T;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_IOS
|
||||
if (type == typeof(iOSPlatformSettings))
|
||||
return Load<iOSPlatformSettings>(gameSettings.iOSPlatform) as T;
|
||||
#endif
|
||||
|
||||
if (gameSettings.CustomSettings != null)
|
||||
{
|
||||
@@ -426,6 +439,10 @@ namespace FlaxEditor.Content.Settings
|
||||
if (type == typeof(MacPlatformSettings))
|
||||
return gameSettings.MacPlatform;
|
||||
#endif
|
||||
#if FLAX_EDITOR || PLATFORM_IOS
|
||||
if (type == typeof(iOSPlatformSettings))
|
||||
return gameSettings.iOSPlatform;
|
||||
#endif
|
||||
|
||||
if (gameSettings.CustomSettings != null)
|
||||
{
|
||||
@@ -538,6 +555,8 @@ namespace FlaxEditor.Content.Settings
|
||||
return SaveAsset(gameSettings, ref gameSettings.Audio, obj);
|
||||
if (type == typeof(MacPlatformSettings))
|
||||
return SaveAsset(gameSettings, ref gameSettings.MacPlatform, obj);
|
||||
if (type == typeof(iOSPlatformSettings))
|
||||
return SaveAsset(gameSettings, ref gameSettings.iOSPlatform, obj);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -572,8 +591,8 @@ namespace FlaxEditor.Content.Settings
|
||||
/// <summary>
|
||||
/// Loads the current game settings asset and applies it to the engine runtime configuration.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern void Apply();
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "GameSettingsInternal_Apply")]
|
||||
public static partial void Apply();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ public:
|
||||
Guid SwitchPlatform;
|
||||
Guid PS5Platform;
|
||||
Guid MacPlatform;
|
||||
Guid iOSPlatform;
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using FlaxEngine;
|
||||
|
||||
namespace FlaxEditor.Content.Settings
|
||||
@@ -24,7 +25,13 @@ namespace FlaxEditor.Content.Settings
|
||||
/// Gets the current layer names (max 32 items but trims last empty items).
|
||||
/// </summary>
|
||||
/// <returns>The layers.</returns>
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
public static extern string[] GetCurrentLayers();
|
||||
public static string[] GetCurrentLayers()
|
||||
{
|
||||
return GetCurrentLayers(out int _);
|
||||
}
|
||||
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "LayersAndTagsSettingsInternal_GetCurrentLayers", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(FlaxEngine.Interop.StringMarshaller))]
|
||||
[return: MarshalUsing(typeof(FlaxEngine.Interop.ArrayMarshaller<,>), CountElementName = "layerCount")]
|
||||
internal static partial string[] GetCurrentLayers(out int layerCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,3 +35,6 @@
|
||||
#if PLATFORM_MAC
|
||||
#include "Engine/Platform/Mac/MacPlatformSettings.h"
|
||||
#endif
|
||||
#if PLATFORM_IOS
|
||||
#include "Engine/Platform/iOS/iOSPlatformSettings.h"
|
||||
#endif
|
||||
|
||||
@@ -18,8 +18,7 @@ namespace fmt_flax
|
||||
FORCE_INLINE static void format(fmt::basic_memory_buffer<T, fmt::inline_buffer_size, std_flax::allocator<T>>& buffer, const T* format, const Args& ... args)
|
||||
{
|
||||
typedef fmt::buffer_context<T> context;
|
||||
fmt::format_arg_store<context, Args...> as{ args... };
|
||||
fmt::internal::vformat_to(buffer, fmt::to_string_view(format), fmt::basic_format_args<context>(as));
|
||||
fmt::detail::vformat_to(buffer, fmt::basic_string_view<T>(format), fmt::make_format_args<context>(args...), {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +36,7 @@ namespace fmt_flax
|
||||
template<typename FormatContext> \
|
||||
auto format(const type& v, FormatContext& ctx) -> decltype(ctx.out()) \
|
||||
{ \
|
||||
return format_to(ctx.out(), TEXT(formatText), ##__VA_ARGS__); \
|
||||
return fmt::format_to(ctx.out(), basic_string_view<Char>(TEXT(formatText)), ##__VA_ARGS__); \
|
||||
} \
|
||||
}; \
|
||||
}
|
||||
@@ -57,7 +56,7 @@ namespace fmt_flax
|
||||
auto format(const type& v, FormatContext& ctx) -> decltype(ctx.out()) \
|
||||
{ \
|
||||
const String str = v.ToString(); \
|
||||
return fmt::internal::copy(str.Get(), str.Get() + str.Length(), ctx.out()); \
|
||||
return fmt::detail::copy_str<Char>(str.Get(), str.Get() + str.Length(), ctx.out()); \
|
||||
} \
|
||||
}; \
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
/// <summary>
|
||||
/// Sends a formatted message to the log file (message type - describes level of the log (see LogType enum))
|
||||
/// </summary>
|
||||
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, TEXT(format), ##__VA_ARGS__)
|
||||
#define LOG(messageType, format, ...) Log::Logger::Write(LogType::messageType, ::String::Format(TEXT(format), ##__VA_ARGS__))
|
||||
|
||||
/// <summary>
|
||||
/// Sends a string message to the log file (message type - describes level of the log (see LogType enum))
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -9,7 +8,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.ColorConverter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.ColorConverter))]
|
||||
#endif
|
||||
partial struct Color
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -55,7 +55,6 @@ using Real = System.Single;
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -63,7 +62,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Double2Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double2Converter))]
|
||||
#endif
|
||||
partial struct Double2 : IEquatable<Double2>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -56,7 +56,6 @@ using Real = System.Single;
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -64,7 +63,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Double3Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double3Converter))]
|
||||
#endif
|
||||
partial struct Double3 : IEquatable<Double3>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -56,7 +56,6 @@ using Real = System.Single;
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -64,7 +63,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Double4Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Double4Converter))]
|
||||
#endif
|
||||
partial struct Double4 : IEquatable<Double4>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Float2Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float2Converter))]
|
||||
#endif
|
||||
partial struct Float2 : IEquatable<Float2>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Float3Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float3Converter))]
|
||||
#endif
|
||||
partial struct Float3 : IEquatable<Float3>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Float4Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Float4Converter))]
|
||||
#endif
|
||||
partial struct Float4 : IEquatable<Float4>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -12,7 +11,9 @@ namespace FlaxEngine
|
||||
/// Represents a two dimensional mathematical vector (signed integers).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Int2Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int2Converter))]
|
||||
#endif
|
||||
partial struct Int2 : IEquatable<Int2>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1}";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -12,7 +11,9 @@ namespace FlaxEngine
|
||||
/// Represents a three dimensional mathematical vector (signed integers).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Int3Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int3Converter))]
|
||||
#endif
|
||||
partial struct Int3 : IEquatable<Int3>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1} Z:{2}";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -12,7 +11,9 @@ namespace FlaxEngine
|
||||
/// Represents a four dimensional mathematical vector (signed integers).
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.Int4Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Int4Converter))]
|
||||
#endif
|
||||
partial struct Int4 : IEquatable<Int4>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0} Y:{1} Z:{2} W:{3}";
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -58,7 +57,9 @@ using System.Runtime.InteropServices;
|
||||
namespace FlaxEngine
|
||||
{
|
||||
[Serializable]
|
||||
[TypeConverter(typeof(TypeConverters.QuaternionConverter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.QuaternionConverter))]
|
||||
#endif
|
||||
partial struct Quaternion : IEquatable<Quaternion>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,6 +19,16 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
@@ -25,9 +36,9 @@ namespace FlaxEngine.TypeConverters
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
if (v.Length == 4)
|
||||
return new Color(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
|
||||
return new Color(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
if (v.Length == 3)
|
||||
return new Color(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), 1.0f);
|
||||
return new Color(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), 1.0f);
|
||||
throw new FormatException("Invalid Color value format.");
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
@@ -39,9 +50,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Color)value;
|
||||
return v.R + "," + v.G + "," + v.B + "," + v.A;
|
||||
return v.R.ToString(culture) + "," + v.G.ToString(culture) + "," + v.B.ToString(culture) + "," + v.A.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Double2(double.Parse(v[0]), double.Parse(v[1]));
|
||||
return new Double2(double.Parse(v[0], culture), double.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Double2)value;
|
||||
return v.X + "," + v.Y;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Double3(double.Parse(v[0]), double.Parse(v[1]), double.Parse(v[2]));
|
||||
return new Double3(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Double3)value;
|
||||
return v.X + "," + v.Y + "," + v.Z;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Double4(double.Parse(v[0]), double.Parse(v[1]), double.Parse(v[2]), double.Parse(v[3]));
|
||||
return new Double4(double.Parse(v[0], culture), double.Parse(v[1], culture), double.Parse(v[2], culture), double.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Double4)value;
|
||||
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Float2(float.Parse(v[0]), float.Parse(v[1]));
|
||||
return new Float2(float.Parse(v[0], culture), float.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Float2)value;
|
||||
return v.X + "," + v.Y;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Float3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
|
||||
return new Float3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Float3)value;
|
||||
return v.X + "," + v.Y + "," + v.Z;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Float4(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
|
||||
return new Float4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Float4)value;
|
||||
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Int2(int.Parse(v[0]), int.Parse(v[1]));
|
||||
return new Int2(int.Parse(v[0], culture), int.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Int2)value;
|
||||
return v.X + "," + v.Y;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Int3(int.Parse(v[0]), int.Parse(v[1]), int.Parse(v[2]));
|
||||
return new Int3(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Int3)value;
|
||||
return v.X + "," + v.Y + "," + v.Z;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Int4(int.Parse(v[0]), int.Parse(v[1]), int.Parse(v[2]), int.Parse(v[3]));
|
||||
return new Int4(int.Parse(v[0], culture), int.Parse(v[1], culture), int.Parse(v[2], culture), int.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Int4)value;
|
||||
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Quaternion(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
|
||||
return new Quaternion(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Quaternion)value;
|
||||
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Vector2(float.Parse(v[0]), float.Parse(v[1]));
|
||||
return new Vector2(float.Parse(v[0], culture), float.Parse(v[1], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Vector2)value;
|
||||
return v.X + "," + v.Y;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Vector3(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]));
|
||||
return new Vector3(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Vector3)value;
|
||||
return v.X + "," + v.Y + "," + v.Z;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
|
||||
|
||||
#if FLAX_EDITOR
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
@@ -18,13 +19,23 @@ namespace FlaxEngine.TypeConverters
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return base.CanConvertTo(context, destinationType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
|
||||
{
|
||||
if (value is string str)
|
||||
{
|
||||
string[] v = str.Split(',');
|
||||
return new Vector4(float.Parse(v[0]), float.Parse(v[1]), float.Parse(v[2]), float.Parse(v[3]));
|
||||
return new Vector4(float.Parse(v[0], culture), float.Parse(v[1], culture), float.Parse(v[2], culture), float.Parse(v[3], culture));
|
||||
}
|
||||
return base.ConvertFrom(context, culture, value);
|
||||
}
|
||||
@@ -35,9 +46,10 @@ namespace FlaxEngine.TypeConverters
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
var v = (Vector4)value;
|
||||
return v.X + "," + v.Y + "," + v.Z + "," + v.W;
|
||||
return v.X.ToString(culture) + "," + v.Y.ToString(culture) + "," + v.Z.ToString(culture) + "," + v.W.ToString(culture);
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -70,7 +69,9 @@ namespace FlaxEngine
|
||||
[Unmanaged]
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[TypeConverter(typeof(TypeConverters.Vector2Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector2Converter))]
|
||||
#endif
|
||||
public unsafe partial struct Vector2 : IEquatable<Vector2>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2}";
|
||||
|
||||
@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -70,7 +69,9 @@ namespace FlaxEngine
|
||||
[Unmanaged]
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[TypeConverter(typeof(TypeConverters.Vector3Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector3Converter))]
|
||||
#endif
|
||||
public unsafe partial struct Vector3 : IEquatable<Vector3>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2}";
|
||||
|
||||
@@ -57,7 +57,6 @@ using Mathr = FlaxEngine.Mathf;
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
@@ -70,7 +69,9 @@ namespace FlaxEngine
|
||||
[Unmanaged]
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[TypeConverter(typeof(TypeConverters.Vector4Converter))]
|
||||
#if FLAX_EDITOR
|
||||
[System.ComponentModel.TypeConverter(typeof(TypeConverters.Vector4Converter))]
|
||||
#endif
|
||||
public partial struct Vector4 : IEquatable<Vector4>, IFormattable
|
||||
{
|
||||
private static readonly string _formatString = "X:{0:F2} Y:{1:F2} Z:{2:F2} W:{3:F2}";
|
||||
|
||||
@@ -309,7 +309,7 @@ namespace fmt
|
||||
{
|
||||
int32 year, month, day;
|
||||
v.GetDate(year, month, day);
|
||||
return format_to(ctx.out(), TEXT("{0}-{1:0>2}-{2:0>2} {3:0>2}:{4:0>2}:{5:0>2}"), year, month, day, v.GetHour(), v.GetMinute(), v.GetSecond());
|
||||
return fmt::format_to(ctx.out(), basic_string_view<Char>(TEXT("{0}-{1:0>2}-{2:0>2} {3:0>2}:{4:0>2}:{5:0>2}")), year, month, day, v.GetHour(), v.GetMinute(), v.GetSecond());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ protected:
|
||||
int32 _length = 0;
|
||||
|
||||
public:
|
||||
typedef T CharType;
|
||||
|
||||
/// <summary>
|
||||
/// Finalizes an instance of the <see cref="StringBase"/> class.
|
||||
/// </summary>
|
||||
@@ -1237,7 +1239,7 @@ namespace fmt
|
||||
template<typename FormatContext>
|
||||
auto format(const String& v, FormatContext& ctx) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
return fmt::detail::copy_str<Char>(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1808,7 +1810,7 @@ namespace fmt
|
||||
template<typename FormatContext>
|
||||
auto format(const StringAnsi& v, FormatContext& ctx) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
return fmt::detail::copy_str<char>(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ namespace fmt
|
||||
template<typename FormatContext>
|
||||
auto format(const String& v, FormatContext& ctx) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
return fmt::detail::copy_str<Char>(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ protected:
|
||||
}
|
||||
|
||||
public:
|
||||
typedef T CharType;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specific const character from this string.
|
||||
/// </summary>
|
||||
@@ -381,7 +383,7 @@ namespace fmt
|
||||
template<typename FormatContext>
|
||||
auto format(const StringView& v, FormatContext& ctx) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
return fmt::detail::copy_str<Char>(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -551,7 +553,7 @@ namespace fmt
|
||||
template<typename FormatContext>
|
||||
auto format(const StringAnsiView& v, FormatContext& ctx) -> decltype(ctx.out())
|
||||
{
|
||||
return fmt::internal::copy(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
return fmt::detail::copy_str<char>(v.Get(), v.Get() + v.Length(), ctx.out());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,12 +23,10 @@
|
||||
#include "Engine/Scripting/ScriptingObject.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MUtils.h"
|
||||
#include "Engine/Utilities/Crc.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#if USE_MONO
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/object.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -108,15 +106,14 @@ VariantType::VariantType(Types type, const StringAnsiView& typeName)
|
||||
}
|
||||
}
|
||||
|
||||
VariantType::VariantType(Types type, _MonoClass* klass)
|
||||
VariantType::VariantType(Types type, MClass* klass)
|
||||
{
|
||||
Type = type;
|
||||
TypeName = nullptr;
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
if (klass)
|
||||
{
|
||||
MString typeName;
|
||||
MUtils::GetClassFullname(klass, typeName);
|
||||
const StringAnsi& typeName = klass->GetFullName();
|
||||
const int32 length = typeName.Length();
|
||||
TypeName = static_cast<char*>(Allocator::Allocate(length + 1));
|
||||
Platform::MemoryCopy(TypeName, typeName.Get(), length);
|
||||
@@ -166,7 +163,7 @@ VariantType::VariantType(const StringAnsiView& typeName)
|
||||
}
|
||||
|
||||
// Try using managed class
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
if (const auto mclass = Scripting::FindClass(typeName))
|
||||
{
|
||||
new(this) VariantType(ManagedObject, typeName);
|
||||
@@ -615,17 +612,21 @@ Variant::Variant(Asset* v)
|
||||
}
|
||||
}
|
||||
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
|
||||
Variant::Variant(_MonoObject* v)
|
||||
: Type(VariantType::ManagedObject, v ? mono_object_get_class(v) : nullptr)
|
||||
Variant::Variant(MObject* v)
|
||||
: Type(VariantType::ManagedObject, v ? MCore::Object::GetClass(v) : nullptr)
|
||||
{
|
||||
AsUint = v ? mono_gchandle_new(v, true) : 0;
|
||||
#if USE_NETCORE
|
||||
AsUint64 = v ? MCore::GCHandle::New(v) : 0;
|
||||
#else
|
||||
AsUint = v ? MCore::GCHandle::New(v) : 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
Variant::Variant(_MonoObject* v)
|
||||
Variant::Variant(MObject* v)
|
||||
: Type(VariantType::ManagedObject, nullptr)
|
||||
{
|
||||
AsUint = 0;
|
||||
@@ -958,11 +959,14 @@ Variant::~Variant()
|
||||
Delete(AsDictionary);
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
#if USE_NETCORE
|
||||
if (AsUint64)
|
||||
MCore::GCHandle::Free(AsUint64);
|
||||
#elif USE_MONO
|
||||
if (AsUint)
|
||||
mono_gchandle_free(AsUint);
|
||||
break;
|
||||
MCore::GCHandle::Free(AsUint);
|
||||
#endif
|
||||
break;
|
||||
default: ;
|
||||
}
|
||||
}
|
||||
@@ -1089,8 +1093,10 @@ Variant& Variant::operator=(const Variant& other)
|
||||
AsDictionary = New<Dictionary<Variant, Variant>>(*other.AsDictionary);
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
AsUint = other.AsUint ? mono_gchandle_new(mono_gchandle_get_target(other.AsUint), true) : 0;
|
||||
#if USE_NETCORE
|
||||
AsUint64 = other.AsUint64 ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint64)) : 0;
|
||||
#elif USE_MONO
|
||||
AsUint = other.AsUint ? MCore::GCHandle::New(MCore::GCHandle::GetTarget(other.AsUint)) : 0;
|
||||
#endif
|
||||
break;
|
||||
case VariantType::Null:
|
||||
@@ -1216,9 +1222,13 @@ bool Variant::operator==(const Variant& other) const
|
||||
return false;
|
||||
return AsBlob.Length == other.AsBlob.Length && StringUtils::Compare(static_cast<const char*>(AsBlob.Data), static_cast<const char*>(other.AsBlob.Data), AsBlob.Length - 1) == 0;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
#if USE_NETCORE
|
||||
// TODO: invoke C# Equality logic?
|
||||
return AsUint == other.AsUint || mono_gchandle_get_target(AsUint) == mono_gchandle_get_target(other.AsUint);
|
||||
return AsUint64 == other.AsUint64 || MCore::GCHandle::GetTarget(AsUint64) == MCore::GCHandle::GetTarget(other.AsUint64);
|
||||
#elif USE_MONO
|
||||
return AsUint == other.AsUint || MCore::GCHandle::GetTarget(AsUint) == MCore::GCHandle::GetTarget(other.AsUint);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
@@ -1309,8 +1319,12 @@ Variant::operator bool() const
|
||||
case VariantType::Asset:
|
||||
return AsAsset != nullptr;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
return AsUint != 0 && mono_gchandle_get_target(AsUint) != nullptr;
|
||||
#if USE_NETCORE
|
||||
return AsUint64 != 0 && MCore::GCHandle::GetTarget(AsUint64) != nullptr;
|
||||
#elif USE_MONO
|
||||
return AsUint != 0 && MCore::GCHandle::GetTarget(AsUint) != nullptr;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
@@ -1579,8 +1593,12 @@ Variant::operator void*() const
|
||||
case VariantType::Blob:
|
||||
return AsBlob.Data;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
return AsUint ? mono_gchandle_get_target(AsUint) : nullptr;
|
||||
#if USE_NETCORE
|
||||
return AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr;
|
||||
#elif USE_MONO
|
||||
return AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
default:
|
||||
return nullptr;
|
||||
@@ -1622,10 +1640,12 @@ Variant::operator ScriptingObject*() const
|
||||
}
|
||||
}
|
||||
|
||||
Variant::operator _MonoObject*() const
|
||||
Variant::operator MObject*() const
|
||||
{
|
||||
#if USE_MONO
|
||||
return Type.Type == VariantType::ManagedObject && AsUint ? mono_gchandle_get_target(AsUint) : nullptr;
|
||||
#if USE_NETCORE
|
||||
return Type.Type == VariantType::ManagedObject && AsUint64 ? MCore::GCHandle::GetTarget(AsUint64) : nullptr;
|
||||
#elif USE_MONO
|
||||
return Type.Type == VariantType::ManagedObject && AsUint ? MCore::GCHandle::GetTarget(AsUint) : nullptr;
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
@@ -2338,9 +2358,12 @@ void Variant::SetType(const VariantType& type)
|
||||
Delete(AsDictionary);
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
#if USE_NETCORE
|
||||
if (AsUint64)
|
||||
MCore::GCHandle::Free(AsUint64);
|
||||
#elif USE_MONO
|
||||
if (AsUint)
|
||||
mono_gchandle_free(AsUint);
|
||||
MCore::GCHandle::Free(AsUint);
|
||||
#endif
|
||||
break;
|
||||
default: ;
|
||||
@@ -2448,9 +2471,12 @@ void Variant::SetType(VariantType&& type)
|
||||
Delete(AsDictionary);
|
||||
break;
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
#if USE_NETCORE
|
||||
if (AsUint64)
|
||||
MCore::GCHandle::Free(AsUint64);
|
||||
#elif USE_MONO
|
||||
if (AsUint)
|
||||
mono_gchandle_free(AsUint);
|
||||
MCore::GCHandle::Free(AsUint);
|
||||
#endif
|
||||
break;
|
||||
default: ;
|
||||
@@ -2626,14 +2652,18 @@ void Variant::SetObject(ScriptingObject* object)
|
||||
object->Deleted.Bind<Variant, &Variant::OnObjectDeleted>(this);
|
||||
}
|
||||
|
||||
void Variant::SetManagedObject(_MonoObject* object)
|
||||
void Variant::SetManagedObject(MObject* object)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
if (object)
|
||||
{
|
||||
if (Type.Type != VariantType::ManagedObject)
|
||||
SetType(VariantType(VariantType::ManagedObject, mono_object_get_class(object)));
|
||||
AsUint = mono_gchandle_new(object, true);
|
||||
SetType(VariantType(VariantType::ManagedObject, MCore::Object::GetClass(object)));
|
||||
#if USE_NETCORE
|
||||
AsUint64 = MCore::GCHandle::New(object);
|
||||
#else
|
||||
AsUint = MCore::GCHandle::New(object);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2752,8 +2782,12 @@ String Variant::ToString() const
|
||||
case VariantType::Typename:
|
||||
return String((const char*)AsBlob.Data, AsBlob.Length ? AsBlob.Length - 1 : 0);
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
return AsUint ? String(MUtils::ToString(mono_object_to_string(mono_gchandle_get_target(AsUint), nullptr))) : TEXT("null");
|
||||
#if USE_NETCORE
|
||||
return AsUint64 ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint64)))) : TEXT("null");
|
||||
#elif USE_MONO
|
||||
return AsUint ? String(MUtils::ToString(MCore::Object::ToString(MCore::GCHandle::GetTarget(AsUint)))) : TEXT("null");
|
||||
#else
|
||||
return String::Empty;
|
||||
#endif
|
||||
default:
|
||||
return String::Empty;
|
||||
@@ -3656,23 +3690,28 @@ void Variant::AllocStructure()
|
||||
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
|
||||
*((int16*)AsBlob.Data) = 0;
|
||||
}
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
else if (const auto mclass = Scripting::FindClass(typeName))
|
||||
{
|
||||
// Fallback to C#-only types
|
||||
MCore::AttachThread();
|
||||
auto instance = mclass->CreateInstance();
|
||||
MCore::Thread::Attach();
|
||||
MObject* instance = mclass->CreateInstance();
|
||||
if (instance)
|
||||
{
|
||||
#if 0
|
||||
void* data = mono_object_unbox(instance);
|
||||
int32 instanceSize = mono_class_instance_size(mclass->GetNative());
|
||||
void* data = MCore::Object::Unbox(instance);
|
||||
int32 instanceSize = mclass->GetInstanceSize();
|
||||
AsBlob.Length = instanceSize - (int32)((uintptr)data - (uintptr)instance);
|
||||
AsBlob.Data = Allocator::Allocate(AsBlob.Length);
|
||||
Platform::MemoryCopy(AsBlob.Data, data, AsBlob.Length);
|
||||
#else
|
||||
Type.Type = VariantType::ManagedObject;
|
||||
AsUint = mono_gchandle_new(instance, true);
|
||||
|
||||
#if USE_NETCORE
|
||||
AsUint64 = MCore::GCHandle::New(instance);
|
||||
#else
|
||||
AsUint = MCore::GCHandle::New(instance);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
else
|
||||
@@ -3764,8 +3803,12 @@ uint32 GetHash(const Variant& key)
|
||||
case VariantType::Typename:
|
||||
return GetHash((const char*)key.AsBlob.Data);
|
||||
case VariantType::ManagedObject:
|
||||
#if USE_MONO
|
||||
return key.AsUint ? (uint32)mono_object_hash(mono_gchandle_get_target(key.AsUint)) : 0;
|
||||
#if USE_NETCORE
|
||||
return key.AsUint64 ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint64)) : 0;
|
||||
#elif USE_MONO
|
||||
return key.AsUint ? (uint32)MCore::Object::GetHashCode(MCore::GCHandle::GetTarget(key.AsUint)) : 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
default:
|
||||
return 0;
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Scripting/Types.h"
|
||||
|
||||
class Asset;
|
||||
class ScriptingObject;
|
||||
struct ScriptingType;
|
||||
struct Transform;
|
||||
struct CommonValue;
|
||||
template<typename T>
|
||||
@@ -105,7 +104,7 @@ public:
|
||||
|
||||
explicit VariantType(Types type, const StringView& typeName);
|
||||
explicit VariantType(Types type, const StringAnsiView& typeName);
|
||||
explicit VariantType(Types type, struct _MonoClass* klass);
|
||||
explicit VariantType(Types type, MClass* klass);
|
||||
explicit VariantType(const StringAnsiView& typeName);
|
||||
VariantType(const VariantType& other);
|
||||
VariantType(VariantType&& other) noexcept;
|
||||
@@ -215,7 +214,7 @@ public:
|
||||
Variant(void* v);
|
||||
Variant(ScriptingObject* v);
|
||||
Variant(Asset* v);
|
||||
Variant(struct _MonoObject* v);
|
||||
Variant(MObject* v);
|
||||
Variant(const StringView& v);
|
||||
Variant(const StringAnsiView& v);
|
||||
Variant(const Char* v);
|
||||
@@ -296,7 +295,7 @@ public:
|
||||
explicit operator StringView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
|
||||
explicit operator StringAnsiView() const; // Returned StringView, if not empty, is guaranteed to point to a null terminated buffer.
|
||||
explicit operator ScriptingObject*() const;
|
||||
explicit operator struct _MonoObject*() const;
|
||||
explicit operator MObject*() const;
|
||||
explicit operator Asset*() const;
|
||||
explicit operator Float2() const;
|
||||
explicit operator Float3() const;
|
||||
@@ -356,7 +355,7 @@ public:
|
||||
void SetBlob(int32 length);
|
||||
void SetBlob(const void* data, int32 length);
|
||||
void SetObject(ScriptingObject* object);
|
||||
void SetManagedObject(struct _MonoObject* object);
|
||||
void SetManagedObject(MObject* object);
|
||||
void SetAsset(Asset* asset);
|
||||
String ToString() const;
|
||||
|
||||
|
||||
@@ -454,7 +454,12 @@ inline void DrawText3D(const DebugText3D& t, const RenderContext& renderContext,
|
||||
{
|
||||
Matrix w, fw, m;
|
||||
if (t.FaceCamera)
|
||||
Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, w);
|
||||
{
|
||||
Matrix s, ss;
|
||||
Matrix::Scaling(t.Transform.Scale.X, s);
|
||||
Matrix::CreateWorld(t.Transform.Translation, renderContext.View.Direction, viewUp, ss);
|
||||
Matrix::Multiply(s, ss, w);
|
||||
}
|
||||
else
|
||||
t.Transform.GetWorld(w);
|
||||
Matrix::Multiply(f, w, fw);
|
||||
@@ -1995,7 +2000,7 @@ void DebugDraw::DrawText(const StringView& text, const Float2& position, const C
|
||||
t.TimeLeft = duration;
|
||||
}
|
||||
|
||||
void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration)
|
||||
void DebugDraw::DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size, float duration, float scale)
|
||||
{
|
||||
if (text.Length() == 0 || size < 4)
|
||||
return;
|
||||
@@ -2005,6 +2010,7 @@ void DebugDraw::DrawText(const StringView& text, const Vector3& position, const
|
||||
Platform::MemoryCopy(t.Text.Get(), text.Get(), text.Length() * sizeof(Char));
|
||||
t.Text[text.Length()] = 0;
|
||||
t.Transform = position - Context->Origin;
|
||||
t.Transform.Scale.X = scale;
|
||||
t.FaceCamera = true;
|
||||
t.Size = size;
|
||||
t.Color = color;
|
||||
|
||||
@@ -590,7 +590,8 @@ API_CLASS(Static) class FLAXENGINE_API DebugDraw
|
||||
/// <param name="color">The color.</param>
|
||||
/// <param name="size">The font size.</param>
|
||||
/// <param name="duration">The duration (in seconds). Use 0 to draw it only once.</param>
|
||||
API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f);
|
||||
/// <param name="scale">The text scale.</param>
|
||||
API_FUNCTION() static void DrawText(const StringView& text, const Vector3& position, const Color& color, int32 size = 32, float duration = 0.0f, float scale = 1.0f);
|
||||
|
||||
/// <summary>
|
||||
/// Draws the text (3D).
|
||||
|
||||
@@ -3,17 +3,15 @@
|
||||
#include "DebugLog.h"
|
||||
#include "Engine/Scripting/Scripting.h"
|
||||
#include "Engine/Scripting/BinaryModule.h"
|
||||
#include "Engine/Scripting/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MDomain.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/Internal/MainThreadManagedInvokeAction.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
|
||||
#if USE_MONO
|
||||
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/exception.h>
|
||||
#include <ThirdParty/mono-2.0/mono/metadata/appdomain.h>
|
||||
#if USE_CSHARP
|
||||
|
||||
namespace Impl
|
||||
{
|
||||
@@ -68,19 +66,19 @@ bool CacheMethods()
|
||||
|
||||
void DebugLog::Log(LogType type, const StringView& message)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
if (CacheMethods())
|
||||
return;
|
||||
|
||||
auto scriptsDomain = Scripting::GetScriptsDomain();
|
||||
MainThreadManagedInvokeAction::ParamsBuilder params;
|
||||
params.AddParam(type);
|
||||
params.AddParam(message, scriptsDomain->GetNative());
|
||||
params.AddParam(message, scriptsDomain);
|
||||
#if BUILD_RELEASE
|
||||
params.AddParam(StringView::Empty, scriptsDomain->GetNative());
|
||||
params.AddParam(StringView::Empty, scriptsDomain);
|
||||
#else
|
||||
const String stackTrace = Platform::GetStackTrace(1);
|
||||
params.AddParam(stackTrace, scriptsDomain->GetNative());
|
||||
params.AddParam(stackTrace, scriptsDomain);
|
||||
#endif
|
||||
MainThreadManagedInvokeAction::Invoke(Internal_SendLog, params);
|
||||
#endif
|
||||
@@ -88,7 +86,7 @@ void DebugLog::Log(LogType type, const StringView& message)
|
||||
|
||||
void DebugLog::LogException(MObject* exceptionObject)
|
||||
{
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
if (exceptionObject == nullptr || CacheMethods())
|
||||
return;
|
||||
|
||||
@@ -101,11 +99,11 @@ void DebugLog::LogException(MObject* exceptionObject)
|
||||
String DebugLog::GetStackTrace()
|
||||
{
|
||||
String result;
|
||||
#if USE_MONO
|
||||
#if USE_CSHARP
|
||||
if (!CacheMethods())
|
||||
{
|
||||
auto stackTraceObj = Internal_GetStackTrace->Invoke(nullptr, nullptr, nullptr);
|
||||
MUtils::ToString((MonoString*)stackTraceObj, result);
|
||||
MUtils::ToString((MString*)stackTraceObj, result);
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
@@ -113,10 +111,9 @@ String DebugLog::GetStackTrace()
|
||||
|
||||
void DebugLog::ThrowException(const char* msg)
|
||||
{
|
||||
#if USE_MONO
|
||||
// Throw exception to the C# world
|
||||
auto ex = mono_exception_from_name_msg(mono_get_corlib(), "System", "Exception", msg);
|
||||
mono_raise_exception(ex);
|
||||
#if USE_CSHARP
|
||||
auto ex = MCore::Exception::Get(msg);
|
||||
MCore::Exception::Throw(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -125,45 +122,40 @@ void DebugLog::ThrowNullReference()
|
||||
//LOG(Warning, "Invalid null reference.");
|
||||
//LOG_STR(Warning, DebugLog::GetStackTrace());
|
||||
|
||||
#if USE_MONO
|
||||
// Throw exception to the C# world
|
||||
auto ex = mono_get_exception_null_reference();
|
||||
mono_raise_exception(ex);
|
||||
#if USE_CSHARP
|
||||
auto ex = MCore::Exception::GetNullReference();
|
||||
MCore::Exception::Throw(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugLog::ThrowArgument(const char* arg, const char* msg)
|
||||
{
|
||||
#if USE_MONO
|
||||
// Throw exception to the C# world
|
||||
auto ex = mono_get_exception_argument(arg, msg);
|
||||
mono_raise_exception(ex);
|
||||
#if USE_CSHARP
|
||||
auto ex = MCore::Exception::GetArgument(arg, msg);
|
||||
MCore::Exception::Throw(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugLog::ThrowArgumentNull(const char* arg)
|
||||
{
|
||||
#if USE_MONO
|
||||
// Throw exception to the C# world
|
||||
auto ex = mono_get_exception_argument_null(arg);
|
||||
mono_raise_exception(ex);
|
||||
#if USE_CSHARP
|
||||
auto ex = MCore::Exception::GetArgumentNull(arg);
|
||||
MCore::Exception::Throw(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugLog::ThrowArgumentOutOfRange(const char* arg)
|
||||
{
|
||||
#if USE_MONO
|
||||
// Throw exception to the C# world
|
||||
auto ex = mono_get_exception_argument_out_of_range(arg);
|
||||
mono_raise_exception(ex);
|
||||
#if USE_CSHARP
|
||||
auto ex = MCore::Exception::GetArgumentOutOfRange(arg);
|
||||
MCore::Exception::Throw(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DebugLog::ThrowNotSupported(const char* msg)
|
||||
{
|
||||
#if USE_MONO
|
||||
// Throw exception to the C# world
|
||||
auto ex = mono_get_exception_not_supported(msg);
|
||||
mono_raise_exception(ex);
|
||||
#if USE_CSHARP
|
||||
auto ex = MCore::Exception::GetNotSupported(msg);
|
||||
MCore::Exception::Throw(ex);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
ArgumentException(const String& additionalInfo)
|
||||
: Exception(TEXT("One or more of provided arguments are not valid."), additionalInfo)
|
||||
{
|
||||
@@ -33,7 +33,8 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="argumentName">Argument name</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
ArgumentException(const String& argumentName, const String& additionalInfo)
|
||||
: Exception(String::Format(TEXT("Provided argument {0} is not valid."), argumentName), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
ArgumentNullException(const String& additionalInfo)
|
||||
: Exception(TEXT("One or more of provided arguments is null"), additionalInfo)
|
||||
{
|
||||
@@ -33,7 +33,8 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="argumentName">Argument name</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
ArgumentNullException(const String& argumentName, const String& additionalInfo)
|
||||
: Exception(String::Format(TEXT("Provided argument {0} is null."), argumentName), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
ArgumentOutOfRangeException(const String& additionalInfo)
|
||||
: Exception(TEXT("One or more of provided arguments has index out of range"), additionalInfo)
|
||||
{
|
||||
@@ -33,7 +33,8 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="argumentName">Argument name</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
ArgumentOutOfRangeException(const String& argumentName, const String& additionalInfo)
|
||||
: Exception(String::Format(TEXT("Provided argument {0} is out of range."), argumentName), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Debug/Exception.h"
|
||||
#include "Engine/Scripting/Types.h"
|
||||
|
||||
namespace Log
|
||||
{
|
||||
@@ -24,15 +25,15 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
CLRInnerException(const String& additionalInfo)
|
||||
: Exception(String::Format(TEXT("Current {0} CLR method has thrown an inner exception"),
|
||||
#if USE_MONO
|
||||
TEXT("Mono")
|
||||
#elif USE_CORECLR
|
||||
TEXT(".NET Core")
|
||||
TEXT("Mono")
|
||||
#elif USE_NETCORE
|
||||
TEXT(".NET Core")
|
||||
#else
|
||||
TEXT("Unknown engine")
|
||||
TEXT("Unknown engine")
|
||||
#endif
|
||||
), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
DivideByZeroException(const String& additionalInfo)
|
||||
: Exception(TEXT("Tried to divide value by zero"), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
FileNotFoundException(const String& additionalInfo)
|
||||
: Exception(TEXT("File not found"), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
IOException(const String& additionalInfo)
|
||||
: Exception(TEXT("I/O error occurred."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
IndexOutOfRangeException(const String& additionalInfo)
|
||||
: Exception(TEXT("Index is out of range for items in array"), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
InvalidOperationException(const String& additionalInfo)
|
||||
: Exception(TEXT("Current object didn't exists or its state was invalid."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
NotImplementedException(const String& additionalInfo)
|
||||
: Exception(TEXT("Current method or operation is not implemented."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
NotSupportedException(const String& additionalInfo)
|
||||
: Exception(TEXT("Current method or operation is not supported in current context"), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
OverflowException(const String& additionalInfo)
|
||||
: Exception(TEXT("Arithmetic, casting, or conversion operation results in an overflow."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
PathTooLongException(const String& additionalInfo)
|
||||
: Exception(TEXT("Path too long."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
PlatformNotSupportedException(const String& additionalInfo)
|
||||
: Exception(TEXT("Method or operation in not supported on current platform."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Log
|
||||
/// <summary>
|
||||
/// Creates default exception with additional data
|
||||
/// </summary>
|
||||
/// <param name="message">Additional information that help describe error</param>
|
||||
/// <param name="additionalInfo">Additional information that help describe error</param>
|
||||
TimeoutException(const String& additionalInfo)
|
||||
: Exception(TEXT("Current operation has timed out."), additionalInfo)
|
||||
{
|
||||
|
||||
@@ -4,10 +4,12 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
|
||||
namespace FlaxEngine
|
||||
{
|
||||
internal sealed class DebugLogHandler : ILogHandler
|
||||
internal partial class DebugLogHandler : ILogHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Occurs on sending a log message.
|
||||
@@ -64,14 +66,14 @@ namespace FlaxEngine
|
||||
Debug.Logger.LogException(exception);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern void Internal_LogWrite(LogType level, string msg);
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_LogWrite", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial void Internal_LogWrite(LogType level, string msg);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern void Internal_Log(LogType level, string msg, IntPtr obj, string stackTrace);
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_Log", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial void Internal_Log(LogType level, string msg, IntPtr obj, string stackTrace);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall)]
|
||||
internal static extern void Internal_LogException(Exception exception, IntPtr obj);
|
||||
[LibraryImport("FlaxEngine", EntryPoint = "DebugLogHandlerInternal_LogException", StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(Interop.StringMarshaller))]
|
||||
internal static partial void Internal_LogException([MarshalUsing(typeof(Interop.ExceptionMarshaller))] Exception exception, IntPtr obj);
|
||||
|
||||
[SecuritySafeCritical]
|
||||
public static string Internal_GetStackTrace()
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#include "Engine/Threading/ThreadRegistry.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MCore.h"
|
||||
#include "Engine/Scripting/ScriptingType.h"
|
||||
#include "Engine/Content/Content.h"
|
||||
#include "Engine/Content/JsonAsset.h"
|
||||
@@ -39,7 +40,7 @@
|
||||
#include "Engine/Scripting/ManagedCLR/MAssembly.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MClass.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MMethod.h"
|
||||
#include "Engine/Scripting/MException.h"
|
||||
#include "Engine/Scripting/ManagedCLR/MException.h"
|
||||
#include "Engine/Core/Config/PlatformSettings.h"
|
||||
#endif
|
||||
|
||||
@@ -65,6 +66,7 @@ Action Engine::FixedUpdate;
|
||||
Action Engine::Update;
|
||||
TaskGraph* Engine::UpdateGraph = nullptr;
|
||||
Action Engine::LateUpdate;
|
||||
Action Engine::LateFixedUpdate;
|
||||
Action Engine::Draw;
|
||||
Action Engine::Pause;
|
||||
Action Engine::Unpause;
|
||||
@@ -198,6 +200,7 @@ int32 Engine::Main(const Char* cmdLine)
|
||||
if (Time::OnBeginPhysics())
|
||||
{
|
||||
OnFixedUpdate();
|
||||
OnLateFixedUpdate();
|
||||
Time::OnEndPhysics();
|
||||
}
|
||||
|
||||
@@ -273,6 +276,17 @@ void Engine::OnFixedUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
void Engine::OnLateFixedUpdate()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Late Fixed Update");
|
||||
|
||||
// Call event
|
||||
LateFixedUpdate();
|
||||
|
||||
// Update services
|
||||
EngineService::OnLateFixedUpdate();
|
||||
}
|
||||
|
||||
void Engine::OnUpdate()
|
||||
{
|
||||
PROFILE_CPU_NAMED("Update");
|
||||
@@ -309,6 +323,14 @@ void Engine::OnUpdate()
|
||||
|
||||
// Update services
|
||||
EngineService::OnUpdate();
|
||||
|
||||
#ifdef USE_NETCORE
|
||||
// Force GC to run in background periodically to avoid large blocking collections causing hitches
|
||||
if (Time::Update.TicksCount % 60 == 0)
|
||||
{
|
||||
MCore::GC::Collect(MCore::GC::MaxGeneration(), MGCCollectionMode::Forced, false, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Engine::OnLateUpdate()
|
||||
@@ -547,6 +569,7 @@ void EngineImpl::InitPaths()
|
||||
#endif
|
||||
#if USE_EDITOR
|
||||
Globals::EngineContentFolder = Globals::StartupFolder / TEXT("Content");
|
||||
#if USE_MONO
|
||||
#if PLATFORM_WINDOWS
|
||||
Globals::MonoPath = Globals::StartupFolder / TEXT("Source/Platforms/Editor/Windows/Mono");
|
||||
#elif PLATFORM_LINUX
|
||||
@@ -556,8 +579,11 @@ void EngineImpl::InitPaths()
|
||||
#else
|
||||
#error "Please specify the Mono data location for Editor on this platform."
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#if USE_MONO
|
||||
Globals::MonoPath = Globals::StartupFolder / TEXT("Mono");
|
||||
#endif
|
||||
#endif
|
||||
Globals::ProjectContentFolder = Globals::ProjectFolder / TEXT("Content");
|
||||
#if USE_EDITOR
|
||||
|
||||
@@ -54,6 +54,11 @@ public:
|
||||
/// </summary>
|
||||
static Action LateUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Event called after engine update.
|
||||
/// </summary>
|
||||
static Action LateFixedUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Event called during frame rendering and can be used to invoke custom rendering with GPUDevice.
|
||||
/// </summary>
|
||||
@@ -107,6 +112,11 @@ public:
|
||||
/// </summary>
|
||||
static void OnLateUpdate();
|
||||
|
||||
/// <summary>
|
||||
/// Late fixed update callback.
|
||||
/// </summary>
|
||||
static void OnLateFixedUpdate();
|
||||
|
||||
/// <summary>
|
||||
/// Draw callback.
|
||||
/// </summary>
|
||||
|
||||
@@ -33,6 +33,7 @@ static bool CompareEngineServices(EngineService* const& a, EngineService* const&
|
||||
DEFINE_ENGINE_SERVICE_EVENT(FixedUpdate);
|
||||
DEFINE_ENGINE_SERVICE_EVENT(Update);
|
||||
DEFINE_ENGINE_SERVICE_EVENT(LateUpdate);
|
||||
DEFINE_ENGINE_SERVICE_EVENT(LateFixedUpdate);
|
||||
DEFINE_ENGINE_SERVICE_EVENT(Draw);
|
||||
DEFINE_ENGINE_SERVICE_EVENT_INVERTED(BeforeExit);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ public:
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, FixedUpdate);
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, Update);
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, LateUpdate);
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, LateFixedUpdate);
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, Draw);
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, BeforeExit);
|
||||
DECLARE_ENGINE_SERVICE_EVENT(void, Dispose);
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "Platforms/Switch/Engine/Engine/SwitchGame.h"
|
||||
#elif PLATFORM_MAC
|
||||
#include "Mac/MacGame.h"
|
||||
#elif PLATFORM_IOS
|
||||
#include "iOS/iOSGame.h"
|
||||
#else
|
||||
#error Missing Game implementation!
|
||||
#endif
|
||||
|
||||
@@ -15,7 +15,9 @@ String Globals::EngineContentFolder;
|
||||
String Globals::ProjectSourceFolder;
|
||||
#endif
|
||||
String Globals::ProjectContentFolder;
|
||||
#if USE_MONO
|
||||
String Globals::MonoPath;
|
||||
#endif
|
||||
bool Globals::FatalErrorOccurred;
|
||||
bool Globals::IsRequestingExit;
|
||||
int32 Globals::ExitCode;
|
||||
|
||||
@@ -49,8 +49,10 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Globals);
|
||||
// Project content directory path
|
||||
API_FIELD(ReadOnly) static String ProjectContentFolder;
|
||||
|
||||
#if USE_MONO
|
||||
// Mono library folder path
|
||||
API_FIELD(ReadOnly) static String MonoPath;
|
||||
#endif
|
||||
|
||||
// State
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user