Add **Spring Bone Physics** node to Anim Graph
This commit is contained in:
@@ -1092,6 +1092,40 @@ namespace FlaxEditor.Surface.Archetypes
|
|||||||
Utils.GetEmptyArray<byte>(),
|
Utils.GetEmptyArray<byte>(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
new NodeArchetype
|
||||||
|
{
|
||||||
|
TypeID = 35,
|
||||||
|
Title = "Spring Bone Physics",
|
||||||
|
Description = "Simulates a soft physics-based trailing motion―like antennae, tails, or cloth strips―for a connected chain of skeletal bones.",
|
||||||
|
Flags = NodeFlags.AnimGraph,
|
||||||
|
Size = new Float2(250, 160),
|
||||||
|
DefaultValues = new object[]
|
||||||
|
{
|
||||||
|
string.Empty, // End Node
|
||||||
|
1, // Nodes
|
||||||
|
1.0f, // Weight
|
||||||
|
0.0f, // Stiffness
|
||||||
|
0.1f, // Drag
|
||||||
|
0.0f, // Stretch Limit
|
||||||
|
1.0f, // Gravity Scale
|
||||||
|
Vector3.Zero, // Force
|
||||||
|
},
|
||||||
|
Elements = new[]
|
||||||
|
{
|
||||||
|
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(void), 0),
|
||||||
|
NodeElementArchetype.Factory.Input(0, string.Empty, true, typeof(void), 1),
|
||||||
|
NodeElementArchetype.Factory.Input(1, "Weight", true, typeof(float), 2, 2),
|
||||||
|
NodeElementArchetype.Factory.Input(2, "Stiffness", true, typeof(float), 3, 3),
|
||||||
|
NodeElementArchetype.Factory.Input(3, "Drag", true, typeof(float), 4, 4),
|
||||||
|
NodeElementArchetype.Factory.Input(4, "Stretch Limit", true, typeof(float), 5, 5),
|
||||||
|
NodeElementArchetype.Factory.Input(5, "Gravity Scale", true, typeof(float), 6, 6),
|
||||||
|
NodeElementArchetype.Factory.Input(6, "Force", true, typeof(Vector3), 7, 7),
|
||||||
|
NodeElementArchetype.Factory.SkeletonNodeNameSelect(60, Surface.Constants.LayoutOffsetY * 7, 140, 0),
|
||||||
|
NodeElementArchetype.Factory.Integer(60, Surface.Constants.LayoutOffsetY * 8, 1, -1, 1, 32),
|
||||||
|
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 7, "End Node:", tooltip:"The last (tip) node of the spring bones chain to simulate."),
|
||||||
|
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 8, "Nodes:", tooltip:"Amount of nodes in a chain to simulate, starting from the End Node going up in the hierarchy. Excluding root node the chain is attached to."),
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,12 @@ void InstanceDataBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
|||||||
bucket.InstanceData.Init = true;
|
bucket.InstanceData.Init = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpringBonePhysicsBucketInit(AnimGraphInstanceData::Bucket& bucket)
|
||||||
|
{
|
||||||
|
bucket.SpringBonePhysics.LastUpdateFrame = 0;
|
||||||
|
bucket.SpringBonePhysics.StateDataStart = -1;
|
||||||
|
}
|
||||||
|
|
||||||
bool SortMultiBlend1D(const ANIM_GRAPH_MULTI_BLEND_INDEX& a, const ANIM_GRAPH_MULTI_BLEND_INDEX& b, AnimGraphNode* n)
|
bool SortMultiBlend1D(const ANIM_GRAPH_MULTI_BLEND_INDEX& a, const ANIM_GRAPH_MULTI_BLEND_INDEX& b, AnimGraphNode* n)
|
||||||
{
|
{
|
||||||
// Sort items by X location from the lowest to the highest
|
// Sort items by X location from the lowest to the highest
|
||||||
@@ -313,18 +319,24 @@ bool AnimGraphBase::onNodeLoaded(Node* n)
|
|||||||
// Transform Node (local/model space)
|
// Transform Node (local/model space)
|
||||||
// Get Node Transform (local/model space)
|
// Get Node Transform (local/model space)
|
||||||
// IK Aim, Two Bone IK
|
// IK Aim, Two Bone IK
|
||||||
|
// Spring Bone Physics
|
||||||
case 25:
|
case 25:
|
||||||
case 26:
|
case 26:
|
||||||
case 28:
|
case 28:
|
||||||
case 29:
|
case 29:
|
||||||
case 30:
|
case 30:
|
||||||
case 31:
|
case 31:
|
||||||
|
case 35:
|
||||||
{
|
{
|
||||||
auto& data = n->Data.TransformNode;
|
auto& data = n->Data.TransformNode;
|
||||||
if (_graph->BaseModel && !_graph->BaseModel->WaitForLoaded())
|
if (_graph->BaseModel && !_graph->BaseModel->WaitForLoaded())
|
||||||
data.NodeIndex = _graph->BaseModel->FindNode((StringView)n->Values[0]);
|
data.NodeIndex = _graph->BaseModel->FindNode((StringView)n->Values[0]);
|
||||||
else
|
else
|
||||||
data.NodeIndex = -1;
|
data.NodeIndex = -1;
|
||||||
|
if (n->TypeID == 35)
|
||||||
|
{
|
||||||
|
ADD_BUCKET(SpringBonePhysicsBucketInit);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Copy Node
|
// Copy Node
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Engine/Animations/AnimEvent.h"
|
#include "Engine/Animations/AnimEvent.h"
|
||||||
#include "Engine/Content/Assets/SkinnedModel.h"
|
#include "Engine/Content/Assets/SkinnedModel.h"
|
||||||
#include "Engine/Graphics/Models/SkeletonData.h"
|
#include "Engine/Graphics/Models/SkeletonData.h"
|
||||||
|
#include "Engine/Level/Actor.h"
|
||||||
#include "Engine/Scripting/Scripting.h"
|
#include "Engine/Scripting/Scripting.h"
|
||||||
#include "Engine/Threading/Threading.h"
|
#include "Engine/Threading/Threading.h"
|
||||||
|
|
||||||
@@ -24,17 +25,33 @@ Transform AnimGraphImpulse::GetNodeModelTransformation(SkeletonData& skeleton, i
|
|||||||
return parentTransform.LocalToWorld(Nodes[nodeIndex]);
|
return parentTransform.LocalToWorld(Nodes[nodeIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value)
|
void AnimGraphImpulse::SetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value, float weight)
|
||||||
{
|
{
|
||||||
|
Transform transform = value;
|
||||||
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
|
const int32 parentIndex = skeleton.Nodes[nodeIndex].ParentIndex;
|
||||||
if (parentIndex == -1)
|
if (parentIndex != -1)
|
||||||
{
|
{
|
||||||
Nodes[nodeIndex] = value;
|
const Transform parentTransform = GetNodeModelTransformation(skeleton, parentIndex);
|
||||||
return;
|
parentTransform.WorldToLocal(transform, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
const Transform parentTransform = GetNodeModelTransformation(skeleton, parentIndex);
|
if (weight >= 1.0f)
|
||||||
parentTransform.WorldToLocal(value, Nodes[nodeIndex]);
|
Nodes[nodeIndex] = transform;
|
||||||
|
else
|
||||||
|
Transform::Lerp(Nodes[nodeIndex], transform, weight, Nodes[nodeIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform AnimGraphImpulse::GetNodeWorldTransformation(const AnimGraphContext& context, SkeletonData& skeleton, int32 nodeIndex) const
|
||||||
|
{
|
||||||
|
Transform transform = GetNodeModelTransformation(skeleton, nodeIndex);
|
||||||
|
return context.Data->GetObjectTransform().LocalToWorld(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimGraphImpulse::SetNodeWorldTransformation(const AnimGraphContext& context, SkeletonData& skeleton, int32 nodeIndex, const Transform& value)
|
||||||
|
{
|
||||||
|
Transform transform;
|
||||||
|
context.Data->GetObjectTransform().WorldToLocal(value, transform);
|
||||||
|
SetNodeModelTransformation(skeleton, nodeIndex, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphInstanceData::Clear()
|
void AnimGraphInstanceData::Clear()
|
||||||
@@ -56,6 +73,7 @@ void AnimGraphInstanceData::ClearState()
|
|||||||
RootTransform = Transform::Identity;
|
RootTransform = Transform::Identity;
|
||||||
RootMotion = Transform::Identity;
|
RootMotion = Transform::Identity;
|
||||||
State.Resize(0);
|
State.Resize(0);
|
||||||
|
DynamicState.Resize(0);
|
||||||
NodesPose.Resize(0);
|
NodesPose.Resize(0);
|
||||||
TraceEvents.Clear();
|
TraceEvents.Clear();
|
||||||
}
|
}
|
||||||
@@ -91,6 +109,13 @@ void AnimGraphInstanceData::InvokeAnimEvents()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Transform AnimGraphInstanceData::GetObjectTransform() const
|
||||||
|
{
|
||||||
|
if (auto* actor = ScriptingObject::Cast<Actor>(Object))
|
||||||
|
return actor->GetTransform();
|
||||||
|
return Transform::Identity;
|
||||||
|
}
|
||||||
|
|
||||||
AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(AnimatedModel* actor) const
|
AnimGraphInstanceData::OutgoingEvent AnimGraphInstanceData::ActiveEvent::End(AnimatedModel* actor) const
|
||||||
{
|
{
|
||||||
OutgoingEvent out;
|
OutgoingEvent out;
|
||||||
@@ -285,6 +310,7 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt)
|
|||||||
{
|
{
|
||||||
// Prepare memory for buckets state information
|
// Prepare memory for buckets state information
|
||||||
data.State.Resize(_graph.BucketsCountTotal, false);
|
data.State.Resize(_graph.BucketsCountTotal, false);
|
||||||
|
data.DynamicState.Clear();
|
||||||
|
|
||||||
// Initialize buckets
|
// Initialize buckets
|
||||||
ResetBuckets(context, &_graph);
|
ResetBuckets(context, &_graph);
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ class AnimGraphExecutor;
|
|||||||
class AnimatedModel;
|
class AnimatedModel;
|
||||||
class AnimEvent;
|
class AnimEvent;
|
||||||
class AnimContinuousEvent;
|
class AnimContinuousEvent;
|
||||||
|
struct AnimGraphContext;
|
||||||
class SkinnedModel;
|
class SkinnedModel;
|
||||||
class SkeletonData;
|
class SkeletonData;
|
||||||
|
|
||||||
@@ -60,14 +61,16 @@ struct FLAXENGINE_API AnimGraphImpulse
|
|||||||
{
|
{
|
||||||
return Nodes[nodeIndex];
|
return Nodes[nodeIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE_INLINE void SetNodeLocalTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value)
|
FORCE_INLINE void SetNodeLocalTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value)
|
||||||
{
|
{
|
||||||
Nodes[nodeIndex] = value;
|
Nodes[nodeIndex] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Transform GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const;
|
Transform GetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex) const;
|
||||||
void SetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value);
|
void SetNodeModelTransformation(SkeletonData& skeleton, int32 nodeIndex, const Transform& value, float weight = 1.0f);
|
||||||
|
|
||||||
|
Transform GetNodeWorldTransformation(const AnimGraphContext& context, SkeletonData& skeleton, int32 nodeIndex) const;
|
||||||
|
void SetNodeWorldTransformation(const AnimGraphContext& context, SkeletonData& skeleton, int32 nodeIndex, const Transform& value);
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -270,6 +273,18 @@ public:
|
|||||||
float Data[4];
|
float Data[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SpringBonePhysicsBucket
|
||||||
|
{
|
||||||
|
uint64 LastUpdateFrame;
|
||||||
|
int32 StateDataStart;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpringBonePhysicsDynamic
|
||||||
|
{
|
||||||
|
Vector3 CurrentPosition;
|
||||||
|
Vector3 PreviousPosition;
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The single data storage bucket for the instanced animation graph node. Used to store the node state (playback position, state, transition data).
|
/// The single data storage bucket for the instanced animation graph node. Used to store the node state (playback position, state, transition data).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -283,6 +298,7 @@ public:
|
|||||||
StateMachineBucket StateMachine;
|
StateMachineBucket StateMachine;
|
||||||
SlotBucket Slot;
|
SlotBucket Slot;
|
||||||
InstanceDataBucket InstanceData;
|
InstanceDataBucket InstanceData;
|
||||||
|
SpringBonePhysicsBucket SpringBonePhysics;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -322,6 +338,11 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Array<Bucket> State;
|
Array<Bucket> State;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The animation state extended data. Managed by the specific node types and shared for a whole instance. Can be resized at runtime.
|
||||||
|
/// </summary>
|
||||||
|
Array<byte> DynamicState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The per-node final transformations in actor local-space.
|
/// The per-node final transformations in actor local-space.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -368,6 +389,12 @@ public:
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void InvokeAnimEvents();
|
void InvokeAnimEvents();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the world-space transformation of the animated object.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Transformation from model/local space to world space.</returns>
|
||||||
|
Transform GetObjectTransform() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations.
|
// Anim Graph logic tracing feature that allows to collect insights of animations sampling and skeleton poses operations.
|
||||||
bool EnableTracing = false;
|
bool EnableTracing = false;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "Engine/Animations/AnimEvent.h"
|
#include "Engine/Animations/AnimEvent.h"
|
||||||
#include "Engine/Animations/InverseKinematics.h"
|
#include "Engine/Animations/InverseKinematics.h"
|
||||||
#include "Engine/Level/Actors/AnimatedModel.h"
|
#include "Engine/Level/Actors/AnimatedModel.h"
|
||||||
|
#include "Engine/Physics/Physics.h"
|
||||||
|
|
||||||
struct AnimSampleData
|
struct AnimSampleData
|
||||||
{
|
{
|
||||||
@@ -2513,6 +2514,145 @@ void AnimGraphExecutor::ProcessGroupAnimation(Box* boxBase, Node* nodeBase, Valu
|
|||||||
value = *(Float4*)bucket.Data;
|
value = *(Float4*)bucket.Data;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// Spring Bone Physics
|
||||||
|
case 35:
|
||||||
|
{
|
||||||
|
// Get bucket
|
||||||
|
auto& bucket = context.Data->State[node->BucketIndex].SpringBonePhysics;
|
||||||
|
Transform objectTransform = context.Data->GetObjectTransform();
|
||||||
|
|
||||||
|
// Get input
|
||||||
|
auto input = tryGetValue(node->GetBox(1), Value::Null);
|
||||||
|
const auto endNodeIndex = node->Data.TransformNode.NodeIndex;
|
||||||
|
float weight = (float)tryGetValue(node->GetBox(2), node->Values[2]);
|
||||||
|
if (endNodeIndex < 0 || endNodeIndex >= _skeletonNodesCount || weight < ANIM_GRAPH_BLEND_THRESHOLD)
|
||||||
|
{
|
||||||
|
value = input;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto nodes = node->GetNodes(this, input);
|
||||||
|
|
||||||
|
// Get parameters
|
||||||
|
weight = Math::Min(weight, 1.0f);
|
||||||
|
float deltaTime = context.DeltaTime;
|
||||||
|
int32 nodesCount = Math::Clamp((int32)node->Values[1] + 1, 1, _skeletonNodesCount);
|
||||||
|
float stiffness = (float)tryGetValue(node->GetBox(3), node->Values[3]);
|
||||||
|
float drag = (float)tryGetValue(node->GetBox(4), node->Values[4]);
|
||||||
|
float stretchLimit = (float)tryGetValue(node->GetBox(5), node->Values[5]) + 1.0f;
|
||||||
|
float gravityScale = (float)tryGetValue(node->GetBox(6), node->Values[6]);
|
||||||
|
Vector3 force = (Vector3)tryGetValue(node->GetBox(7), node->Values[7]) + Physics::GetGravity() * gravityScale;
|
||||||
|
|
||||||
|
// Get world-space transforms of the nodes (from root to end)
|
||||||
|
Array<int32, InlinedAllocation<8>> nodesIndices;
|
||||||
|
Array<Transform, InlinedAllocation<8>> nodesRef;
|
||||||
|
nodesIndices.Resize(nodesCount);
|
||||||
|
nodesRef.Resize(nodesCount);
|
||||||
|
auto& skeleton = _graph.BaseModel->Skeleton;
|
||||||
|
for (int32 i = nodesCount - 1, nodeIndex = endNodeIndex; i >= 0; i--)
|
||||||
|
{
|
||||||
|
nodesIndices[i] = nodeIndex;
|
||||||
|
nodeIndex = skeleton.Nodes[nodeIndex].ParentIndex;
|
||||||
|
}
|
||||||
|
nodesRef[0] = nodes->GetNodeWorldTransformation(context, skeleton, nodesIndices[0]); // Root comes from animation (incl. animated model movement)
|
||||||
|
for (int32 i = 1; i < nodesCount; i++)
|
||||||
|
nodesRef[i] = nodesRef[i - 1].LocalToWorld(nodes->Nodes[nodesIndices[i]]);
|
||||||
|
|
||||||
|
// Check if we reset the simulation (start from the reference pose)
|
||||||
|
bool reset = bucket.LastUpdateFrame < context.CurrentFrameIndex - 1 || context.CurrentFrameIndex == 1;
|
||||||
|
if (bucket.StateDataStart == -1)
|
||||||
|
{
|
||||||
|
// Allocate a dynamic state
|
||||||
|
bucket.StateDataStart = context.Data->DynamicState.Count();
|
||||||
|
context.Data->DynamicState.AddUninitialized(sizeof(AnimGraphInstanceData::SpringBonePhysicsDynamic) * nodesCount);
|
||||||
|
reset = true;
|
||||||
|
}
|
||||||
|
auto* dynamic = (AnimGraphInstanceData::SpringBonePhysicsDynamic*)&context.Data->DynamicState[bucket.StateDataStart];
|
||||||
|
if (reset)
|
||||||
|
{
|
||||||
|
// Initialize the simulation from the reference pose
|
||||||
|
for (int32 i = 0; i < nodesCount; i++)
|
||||||
|
dynamic[i].CurrentPosition = dynamic[i].PreviousPosition = nodesRef[i].Translation;
|
||||||
|
}
|
||||||
|
bucket.LastUpdateFrame = context.CurrentFrameIndex;
|
||||||
|
|
||||||
|
// Move each node by velocity and forces
|
||||||
|
dynamic[0].PreviousPosition = dynamic[0].CurrentPosition;
|
||||||
|
dynamic[0].CurrentPosition = nodesRef[0].Translation;
|
||||||
|
for (int32 i = 1; i < nodesCount; i++)
|
||||||
|
{
|
||||||
|
Vector3 position = dynamic[i].CurrentPosition;
|
||||||
|
Vector3 prevPosition = dynamic[i].PreviousPosition;
|
||||||
|
Vector3 refPosition = nodesRef[i].Translation;
|
||||||
|
|
||||||
|
// Stiffness force
|
||||||
|
Vector3 delta = (refPosition - position) * stiffness;
|
||||||
|
|
||||||
|
// Gravity and wind forces
|
||||||
|
delta += force * (deltaTime * deltaTime);
|
||||||
|
|
||||||
|
// Verlet integration
|
||||||
|
delta += (position - prevPosition) * (1 - drag);
|
||||||
|
|
||||||
|
dynamic[i].PreviousPosition = position;
|
||||||
|
dynamic[i].CurrentPosition = position + delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Length constraints
|
||||||
|
for (int32 i = 1; i < nodesCount; i++)
|
||||||
|
{
|
||||||
|
Vector3 offset = dynamic[i].CurrentPosition - dynamic[i - 1].CurrentPosition;
|
||||||
|
Real dist = offset.Length();
|
||||||
|
Real boneLength = Vector3::Distance(nodesRef[i].Translation, nodesRef[i - 1].Translation);
|
||||||
|
Real maxBoneLength = boneLength * stretchLimit;
|
||||||
|
if (dist > maxBoneLength && dist > ANIM_GRAPH_BLEND_THRESHOLD)
|
||||||
|
{
|
||||||
|
Real scale = ((dist - maxBoneLength) / dist);
|
||||||
|
dynamic[i].CurrentPosition -= offset * scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the nodes with the new transforms (move back from world-space to node-space)
|
||||||
|
Quaternion prevRotation = Quaternion::Identity;
|
||||||
|
for (int32 i = 0; i < nodesCount; i++)
|
||||||
|
{
|
||||||
|
Transform nodeTransform = nodesRef[i];
|
||||||
|
nodeTransform.Translation = dynamic[i].CurrentPosition;
|
||||||
|
|
||||||
|
// Move back from world-space to model-space
|
||||||
|
nodeTransform = objectTransform.WorldToLocal(nodeTransform);
|
||||||
|
|
||||||
|
if (i + 1 < nodesCount)
|
||||||
|
{
|
||||||
|
// Orient node to point to the next node in the chain
|
||||||
|
Vector3 childNewPos = objectTransform.WorldToLocal(dynamic[i + 1].CurrentPosition);
|
||||||
|
int32 childIndex = nodesIndices[i + 1];
|
||||||
|
Vector3 childRefPos = nodes->Nodes[childIndex].Translation;
|
||||||
|
Vector3 childOldPos = nodeTransform.LocalToWorld(childRefPos);
|
||||||
|
Vector3 oldDir = (childOldPos - nodeTransform.Translation).GetNormalized();
|
||||||
|
Vector3 newDir = (childNewPos - nodeTransform.Translation).GetNormalized();
|
||||||
|
nodeTransform.Orientation = Quaternion::FindBetween(oldDir, newDir) * nodeTransform.Orientation;
|
||||||
|
prevRotation = nodeTransform.Orientation;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Tip (last) node copies the orientation of the parent
|
||||||
|
nodeTransform.Orientation = prevRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes->SetNodeModelTransformation(skeleton, nodesIndices[i], nodeTransform, weight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we blend between animated and simulated poses then fetch the final pose from the nodes instead of using the simulated pose directly
|
||||||
|
if (weight < 1.0f)
|
||||||
|
{
|
||||||
|
// TODO: optimize it by getting root node, then using LocalToWorld for following children
|
||||||
|
for (int32 i = 1; i < nodesCount; i++)
|
||||||
|
dynamic[i].CurrentPosition = nodes->GetNodeWorldTransformation(context, skeleton, nodesIndices[i]).Translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = nodes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user