1065 lines
37 KiB
C#
1065 lines
37 KiB
C#
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
using Rukhanka.Toolbox;
|
|
using Unity.Burst;
|
|
using Unity.Burst.CompilerServices;
|
|
using Unity.Burst.Intrinsics;
|
|
using Unity.Collections;
|
|
using Unity.Collections.LowLevel.Unsafe;
|
|
using Unity.Entities;
|
|
using Unity.Jobs;
|
|
using Unity.Mathematics;
|
|
using Unity.Transforms;
|
|
using UnityEngine;
|
|
|
|
//=================================================================================================================//
|
|
|
|
[assembly: InternalsVisibleTo("Rukhanka.Tests")]
|
|
|
|
namespace Rukhanka
|
|
{
|
|
partial struct AnimationProcessSystem
|
|
{
|
|
|
|
[BurstCompile]
|
|
public struct ComputeBoneAnimationJob: IJobParallelForDefer
|
|
{
|
|
[NativeDisableParallelForRestriction]
|
|
public NativeList<BoneTransform> animatedBonesBuffer;
|
|
[NativeDisableParallelForRestriction]
|
|
public NativeList<ulong> boneTransformFlagsArr;
|
|
[ReadOnly]
|
|
public NativeList<int3> boneToEntityArr;
|
|
[ReadOnly]
|
|
public BufferLookup<AnimationToProcessComponent> animationsToProcessLookup;
|
|
[ReadOnly]
|
|
public NativeList<RigDefinitionComponent> rigDefs;
|
|
[ReadOnly]
|
|
public NativeList<Entity> entityArr;
|
|
|
|
[NativeDisableParallelForRestriction]
|
|
public BufferLookup<RootMotionAnimationStateComponent> rootMotionAnimStateBufferLookup;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
[SkipLocalsInit]
|
|
public void Execute(int globalBoneIndex)
|
|
{
|
|
var boneToEntityIndex = boneToEntityArr[globalBoneIndex];
|
|
var (rigBoneIndex, entityIndex, rigBoneCount) = (boneToEntityIndex.y & 0xffff, boneToEntityIndex.x, boneToEntityIndex.y >> 16);
|
|
var e = entityArr[entityIndex];
|
|
|
|
var rigDef = rigDefs[entityIndex];
|
|
var rigBlobAsset = rigDef.rigBlob;
|
|
ref var rb = ref rigBlobAsset.Value.bones[rigBoneIndex];
|
|
var animationsToProcess = animationsToProcessLookup[e];
|
|
|
|
// Early exit if no animations
|
|
if (animationsToProcess.IsEmpty)
|
|
return;
|
|
|
|
var transformFlags = RuntimeAnimationData.GetAnimationTransformFlagsRW(boneToEntityArr, boneTransformFlagsArr, globalBoneIndex, rigBoneCount);
|
|
GetHumanRotationDataForSkeletonBone(out var humanBoneInfo, ref rigBlobAsset.Value.humanData, rigBoneIndex);
|
|
|
|
// There are separate tracks for root motion
|
|
var boneNameHash = rb.hash;
|
|
if (rigDef.applyRootMotion && (rigBlobAsset.Value.rootBoneIndex == rigBoneIndex || rigBoneIndex == 0))
|
|
boneNameHash = ModifyBoneHashForRootMotion(boneNameHash);
|
|
|
|
var rootMotionDeltaBone = rigDef.applyRootMotion && rigBoneIndex == 0;
|
|
PrepareRootMotionStateBuffers(e, animationsToProcess, out var curRootMotionState, out var newRootMotionState, rootMotionDeltaBone);
|
|
|
|
// Reference pose for root motion delta should be identity
|
|
var refPose = Hint.Unlikely(rootMotionDeltaBone) ? BoneTransform.Identity() : rb.refPose;
|
|
|
|
var blendedBonePose = refPose;
|
|
var layerPose = new BoneTransform();
|
|
var weightSum = 0.0f;
|
|
float3 layerFlags = 0;
|
|
float3 totalFlags = 0;
|
|
LayerInfo layerInfo = default;
|
|
|
|
for (int ai = 0; ai < animationsToProcess.Length; ++ai)
|
|
{
|
|
var atp = animationsToProcess[ai];
|
|
// Root bone should be always included in animation computation
|
|
var inAvatarMask = IsBoneInAvatarMask(rigBoneIndex, rb.humanBodyPart, atp.avatarMask) || rootMotionDeltaBone;
|
|
if (atp.animation == BlobAssetReference<AnimationClipBlob>.Null || atp.weight == 0 || atp.layerWeight == 0 || !inAvatarMask)
|
|
continue;
|
|
|
|
var curLayerInfo = GetLayerInfoFromAnimation(atp);
|
|
|
|
// Apply layer animations
|
|
if (layerInfo.index != curLayerInfo.index)
|
|
{
|
|
blendedBonePose = BlendLayerPose(blendedBonePose, layerPose, refPose, layerInfo, weightSum, layerFlags);
|
|
weightSum = 0;
|
|
layerFlags = 0;
|
|
layerPose = new BoneTransform();
|
|
}
|
|
layerInfo = curLayerInfo;
|
|
|
|
var animTime = NormalizeAnimationTime(atp.time, ref atp.animation.Value);
|
|
ref var clipTracks = ref atp.animation.Value.clipTracks;
|
|
|
|
var boneHasAnimation = SampleAnimation
|
|
(
|
|
ref atp.animation.Value,
|
|
animTime,
|
|
rigBoneIndex,
|
|
boneNameHash,
|
|
atp.blendMode,
|
|
humanBoneInfo,
|
|
out var bonePose,
|
|
out var flags,
|
|
out var trackRange
|
|
);
|
|
|
|
if (boneHasAnimation)
|
|
{
|
|
if (Hint.Unlikely(rootMotionDeltaBone))
|
|
ProcessRootMotionDeltas(ref bonePose, ref clipTracks, trackRange, atp, curRootMotionState, ref newRootMotionState);
|
|
weightSum += atp.weight;
|
|
layerFlags += flags;
|
|
totalFlags += flags;
|
|
layerPose = AppendScaledPose(layerPose, bonePose, atp.weight);
|
|
}
|
|
}
|
|
|
|
// Apply top layer pose
|
|
blendedBonePose = BlendLayerPose(blendedBonePose, layerPose, refPose, layerInfo, weightSum, layerFlags);
|
|
|
|
SetTransformFlags(totalFlags, transformFlags, rigBoneIndex);
|
|
animatedBonesBuffer[globalBoneIndex] = blendedBonePose;
|
|
if (rootMotionDeltaBone)
|
|
SetRootMotionStateToComponentBuffer(newRootMotionState, e);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
BoneTransform BlendLayerPose(in BoneTransform curPose, BoneTransform layerPose, in BoneTransform refPose, in LayerInfo layerInfo, float weightSum, float3 layerFlags)
|
|
{
|
|
BoneTransform rv = curPose;
|
|
if (Hint.Likely(layerInfo.blendMode == AnimationBlendingMode.Override))
|
|
{
|
|
// For override layers we need to apply reminder from the reference pose if layer total animation weight is not equal to one
|
|
if (Hint.Unlikely(weightSum < 1))
|
|
layerPose = AppendScaledPose(layerPose, refPose, math.max(0, 1 - weightSum));
|
|
|
|
if (Hint.Likely(layerFlags.x > 0))
|
|
{
|
|
rv.pos = math.lerp(curPose.pos, layerPose.pos, layerInfo.weight);
|
|
}
|
|
if (Hint.Likely(layerFlags.y > 0))
|
|
{
|
|
layerPose.rot = math.normalizesafe(layerPose.rot);
|
|
layerPose.rot = MathUtils.ShortestRotation(curPose.rot, layerPose.rot);
|
|
rv.rot = math.nlerp(curPose.rot.value, layerPose.rot.value, layerInfo.weight);
|
|
}
|
|
if (Hint.Likely(layerFlags.z > 0))
|
|
{
|
|
rv.scale = math.lerp(curPose.scale, layerPose.scale, layerInfo.weight);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Hint.Likely(layerFlags.x > 0))
|
|
{
|
|
rv.pos = curPose.pos + layerPose.pos * layerInfo.weight;
|
|
}
|
|
if (Hint.Likely(layerFlags.y > 0))
|
|
{
|
|
quaternion layerRot = math.normalizesafe(new float4(layerPose.rot.value.xyz * layerInfo.weight, layerPose.rot.value.w));
|
|
layerRot = MathUtils.ShortestRotation(curPose.rot, layerRot);
|
|
rv.rot = math.mul(curPose.rot, layerRot);
|
|
}
|
|
if (Hint.Likely(layerFlags.z > 0))
|
|
{
|
|
rv.scale = curPose.scale * math.lerp(1, layerPose.scale, layerInfo.weight);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
BoneTransform AppendScaledPose(in BoneTransform curPose, BoneTransform addedPose, float weight)
|
|
{
|
|
addedPose.rot = MathUtils.ShortestRotation(curPose.rot, addedPose.rot);
|
|
BoneTransform rv = new BoneTransform()
|
|
{
|
|
pos = curPose.pos + addedPose.pos * weight,
|
|
rot = new quaternion(curPose.rot.value + addedPose.rot.value * weight),
|
|
scale = curPose.scale + addedPose.scale * weight
|
|
};
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public static uint ModifyBoneHashForRootMotion(uint h)
|
|
{
|
|
var rv = math.hash(new uint2(h, h * 2));
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void PrepareRootMotionStateBuffers
|
|
(
|
|
Entity e,
|
|
in DynamicBuffer<AnimationToProcessComponent> atps,
|
|
out NativeArray<RootMotionAnimationStateComponent> curRootMotionState,
|
|
out NativeList<RootMotionAnimationStateComponent> newRootMotionState,
|
|
bool isRootMotionBone
|
|
)
|
|
{
|
|
curRootMotionState = default;
|
|
newRootMotionState = default;
|
|
|
|
if (Hint.Likely(!isRootMotionBone)) return;
|
|
|
|
if (rootMotionAnimStateBufferLookup.HasBuffer(e))
|
|
curRootMotionState = rootMotionAnimStateBufferLookup[e].AsNativeArray();
|
|
|
|
newRootMotionState = new NativeList<RootMotionAnimationStateComponent>(atps.Length, Allocator.Temp);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ProcessRootMotionDeltas
|
|
(
|
|
ref BoneTransform bonePose,
|
|
ref TrackSet trackSet,
|
|
int2 trackRange,
|
|
in AnimationToProcessComponent atp,
|
|
in NativeArray<RootMotionAnimationStateComponent> curRootMotionState,
|
|
ref NativeList<RootMotionAnimationStateComponent> newRootMotionState
|
|
)
|
|
{
|
|
// Special care for root motion animation loops
|
|
HandleRootMotionLoops(ref bonePose, ref trackSet, trackRange, atp);
|
|
|
|
BoneTransform rootMotionPrevPose = bonePose;
|
|
|
|
// Find animation history in history buffer
|
|
var historyBufferIndex = 0;
|
|
for (; curRootMotionState.IsCreated && historyBufferIndex < curRootMotionState.Length && curRootMotionState[historyBufferIndex].uniqueMotionId != atp.motionId; ++historyBufferIndex){ }
|
|
|
|
var initialFrame = historyBufferIndex >= curRootMotionState.Length;
|
|
|
|
if (Hint.Unlikely(!initialFrame))
|
|
{
|
|
rootMotionPrevPose = curRootMotionState[historyBufferIndex].animationState;
|
|
}
|
|
|
|
var rmasc = new RootMotionAnimationStateComponent() { uniqueMotionId = atp.motionId, animationState = bonePose };
|
|
newRootMotionState.Add(rmasc);
|
|
|
|
var invPrevPose = BoneTransform.Inverse(rootMotionPrevPose);
|
|
var deltaPose = BoneTransform.Multiply(invPrevPose, bonePose);
|
|
|
|
bonePose = deltaPose;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SetRootMotionStateToComponentBuffer(in NativeList<RootMotionAnimationStateComponent> newRootMotionData, Entity e)
|
|
{
|
|
rootMotionAnimStateBufferLookup[e].CopyFrom(newRootMotionData.AsArray());
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SetTransformFlags(float3 flags, in AnimationTransformFlags flagArr, int boneIndex)
|
|
{
|
|
if (flags.x > 0)
|
|
flagArr.SetTranslationFlag(boneIndex);
|
|
if (flags.y > 0)
|
|
flagArr.SetRotationFlag(boneIndex);
|
|
if (flags.z > 0)
|
|
flagArr.SetScaleFlag(boneIndex);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void GetHumanRotationDataForSkeletonBone(out HumanRotationData rv, ref BlobPtr<HumanData> hd, int rigBoneIndex)
|
|
{
|
|
rv = default;
|
|
if (hd.IsValid)
|
|
{
|
|
rv = hd.Value.humanRotData[rigBoneIndex];
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
internal static float3 MuscleRangeToRadians(float3 minA, float3 maxA, float3 muscle)
|
|
{
|
|
// Map [-1; +1] range into [minRot; maxRot]
|
|
var negativeRange = math.min(muscle, 0);
|
|
var positiveRange = math.max(0, muscle);
|
|
var negativeRot = math.lerp(0, minA, -negativeRange);
|
|
var positiveRot = math.lerp(0, maxA, +positiveRange);
|
|
|
|
var rv = negativeRot + positiveRot;
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void MuscleValuesToQuaternion(in HumanRotationData humanBoneInfo, ref BoneTransform bt)
|
|
{
|
|
var r = MuscleRangeToRadians(humanBoneInfo.minMuscleAngles, humanBoneInfo.maxMuscleAngles, bt.rot.value.xyz);
|
|
r *= humanBoneInfo.sign;
|
|
|
|
var qx = quaternion.AxisAngle(math.right(), r.x);
|
|
var qy = quaternion.AxisAngle(math.up(), r.y);
|
|
var qz = quaternion.AxisAngle(math.forward(), r.z);
|
|
var qzy = math.mul(qz, qy);
|
|
qzy.value.x = 0;
|
|
bt.rot = math.mul(math.normalize(qzy), qx);
|
|
|
|
ApplyHumanoidPostTransform(humanBoneInfo, ref bt);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public static float2 NormalizeAnimationTime(float at, ref AnimationClipBlob ac)
|
|
{
|
|
at += ac.cycleOffset;
|
|
if (at < 0) at = 1 + at;
|
|
var normalizedTime = ac.looped ? math.frac(at) : math.saturate(at);
|
|
var rv = normalizedTime * ac.length;
|
|
return new (rv, normalizedTime);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void CalculateLoopPose(ref TrackSet trackSet, int2 trackRange, ref BoneTransform bonePose, in HumanRotationData hrd, float normalizedTime)
|
|
{
|
|
var lerpFactor = normalizedTime;
|
|
var rootPoseStart = GetTransformFrame(ref trackSet, trackRange, hrd, TrackFrame.First);
|
|
var rootPoseEnd = GetTransformFrame(ref trackSet, trackRange, hrd, TrackFrame.Last);
|
|
|
|
var dPos = rootPoseEnd.pos - rootPoseStart.pos;
|
|
var dRot = math.mul(math.conjugate(rootPoseEnd.rot), rootPoseStart.rot);
|
|
bonePose.pos -= dPos * lerpFactor;
|
|
bonePose.rot = math.mul(bonePose.rot, math.slerp(quaternion.identity, dRot, lerpFactor));
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void HandleRootMotionLoops(ref BoneTransform bonePose, ref TrackSet ts, int2 trackRange, in AnimationToProcessComponent atp)
|
|
{
|
|
ref var animBlob = ref atp.animation.Value;
|
|
if (!animBlob.looped)
|
|
return;
|
|
|
|
var numLoopCycles = (int)math.floor(atp.time + atp.animation.Value.cycleOffset);
|
|
var cycleSign = math.sign(numLoopCycles);
|
|
numLoopCycles = math.abs(numLoopCycles);
|
|
if (numLoopCycles < 1)
|
|
return;
|
|
|
|
var endFramePose = GetTransformFrame(ref ts, trackRange, default, TrackFrame.Last);
|
|
var startFramePose = GetTransformFrame(ref ts, trackRange, default, TrackFrame.First);
|
|
|
|
var deltaPose = BoneTransform.Multiply(endFramePose, BoneTransform.Inverse(startFramePose));
|
|
if (cycleSign < 0)
|
|
deltaPose = BoneTransform.Inverse(deltaPose);
|
|
|
|
BoneTransform accumCyclePose = BoneTransform.Identity();
|
|
for (var c = numLoopCycles; c > 0; c >>= 1)
|
|
{
|
|
if ((c & 1) == 1)
|
|
accumCyclePose = BoneTransform.Multiply(accumCyclePose, deltaPose);
|
|
deltaPose = BoneTransform.Multiply(deltaPose, deltaPose);
|
|
}
|
|
bonePose = BoneTransform.Multiply(accumCyclePose, bonePose);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
BoneTransform MixPoses(in BoneTransform curPose, BoneTransform inPose, float3 flags, float weight, AnimationBlendingMode blendMode)
|
|
{
|
|
BoneTransform rv = curPose;
|
|
if (Hint.Likely(blendMode == AnimationBlendingMode.Override))
|
|
{
|
|
if (Hint.Likely(flags.x > 0))
|
|
{
|
|
rv.pos = math.lerp(curPose.pos, inPose.pos, weight);
|
|
}
|
|
if (Hint.Likely(flags.y > 0))
|
|
{
|
|
inPose.rot = MathUtils.ShortestRotation(curPose.rot, inPose.rot);
|
|
rv.rot = math.nlerp(curPose.rot.value, inPose.rot.value, weight);
|
|
}
|
|
if (Hint.Likely(flags.z > 0))
|
|
{
|
|
rv.scale = math.lerp(curPose.scale, inPose.scale, weight);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (Hint.Likely(flags.x > 0))
|
|
{
|
|
rv.pos = curPose.pos + inPose.pos * weight;
|
|
}
|
|
if (Hint.Likely(flags.y > 0))
|
|
{
|
|
quaternion layerRot = math.normalizesafe(new float4(inPose.rot.value.xyz * weight, inPose.rot.value.w));
|
|
layerRot = MathUtils.ShortestRotation(curPose.rot, layerRot);
|
|
rv.rot = math.mul(curPose.rot, layerRot);
|
|
}
|
|
if (Hint.Likely(flags.z > 0))
|
|
{
|
|
rv.scale = curPose.scale * math.lerp(1, inPose.scale, weight);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void ApplyHumanoidPostTransform(HumanRotationData hrd, ref BoneTransform bt)
|
|
{
|
|
bt.rot = math.mul(math.mul(hrd.preRot, bt.rot), hrd.postRot);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public static bool IsBoneInAvatarMask(int boneIndex, AvatarMaskBodyPart humanAvatarMaskBodyPart, BlobAssetReference<AvatarMaskBlob> am)
|
|
{
|
|
// If no avatar mask defined or bone hash is all zeroes assume that bone included
|
|
if (!am.IsCreated || boneIndex < 0)
|
|
return true;
|
|
|
|
return (int)humanAvatarMaskBodyPart >= 0 ?
|
|
IsBoneInHumanAvatarMask(humanAvatarMaskBodyPart, am) :
|
|
am.Value.IsBoneIncluded(boneIndex);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public static bool IsBoneInHumanAvatarMask(AvatarMaskBodyPart humanBoneAvatarMaskIndex, BlobAssetReference<AvatarMaskBlob> am)
|
|
{
|
|
var rv = (am.Value.humanBodyPartsAvatarMask & 1 << (int)humanBoneAvatarMaskIndex) != 0;
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SampleAnimation
|
|
(
|
|
ref AnimationClipBlob acb,
|
|
float2 animTime,
|
|
int rigBoneIndex,
|
|
uint boneHash,
|
|
AnimationBlendingMode blendMode,
|
|
in HumanRotationData hrd,
|
|
out BoneTransform animatedPose,
|
|
out float3 flags,
|
|
out int2 clipTrackRange
|
|
)
|
|
{
|
|
animatedPose = BoneTransform.Identity();
|
|
flags = 0;
|
|
clipTrackRange = 0;
|
|
|
|
var trackIndex = acb.clipTracks.GetTrackGroupIndex(boneHash);
|
|
if (Hint.Unlikely(trackIndex < 0))
|
|
return false;
|
|
|
|
var time = animTime.x;
|
|
var timeNrm = animTime.y;
|
|
|
|
clipTrackRange = new int2(acb.clipTracks.trackGroups[trackIndex], acb.clipTracks.trackGroups[trackIndex + 1]); // This is safe because of trailing trackGroup
|
|
(animatedPose, flags) = ProcessAnimationCurves(ref acb.clipTracks, clipTrackRange, hrd, time);
|
|
|
|
// Make additive animation if requested
|
|
var isAnimationAdditive = blendMode == AnimationBlendingMode.Additive;
|
|
if (Hint.Unlikely(isAnimationAdditive))
|
|
{
|
|
// Get separate additive frame if requested
|
|
ref var additiveTrackSet = ref Hint.Unlikely(acb.additiveReferencePoseFrame.keyframes.Length > 0) ? ref acb.additiveReferencePoseFrame : ref acb.clipTracks;
|
|
var additiveTrackIndex = additiveTrackSet.trackGroupPHT.Query(boneHash);
|
|
if (additiveTrackIndex >= 0)
|
|
{
|
|
var additivePoseTrackRange = new int2(additiveTrackSet.trackGroups[additiveTrackIndex], additiveTrackSet.trackGroups[additiveTrackIndex + 1]);
|
|
var additiveFramePose = GetTransformFrame(ref additiveTrackSet, additivePoseTrackRange, hrd, TrackFrame.First);
|
|
MakeAdditiveAnimation(ref animatedPose, additiveFramePose);
|
|
}
|
|
}
|
|
|
|
// Loop Pose calculus for all bones except root motion
|
|
var calculateLoopPose = acb.loopPoseBlend && rigBoneIndex != 0;
|
|
if (Hint.Unlikely(calculateLoopPose))
|
|
{
|
|
CalculateLoopPose(ref acb.clipTracks, clipTrackRange, ref animatedPose, hrd, timeNrm);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void MakeAdditiveAnimation(ref BoneTransform rv, in BoneTransform zeroFramePose)
|
|
{
|
|
// If additive layer make difference between reference pose and current animated pose
|
|
rv.pos = rv.pos - zeroFramePose.pos;
|
|
var conjugateZFRot = math.normalizesafe(math.conjugate(zeroFramePose.rot));
|
|
conjugateZFRot = MathUtils.ShortestRotation(rv.rot, conjugateZFRot);
|
|
rv.rot = math.mul(conjugateZFRot, rv.rot);
|
|
rv.scale = rv.scale / zeroFramePose.scale;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
BoneTransform GetTransformFrame(ref TrackSet ts, int2 trackRange, HumanRotationData hrd, TrackFrame tf)
|
|
{
|
|
var rv = BoneTransform.Identity();
|
|
|
|
bool eulerToQuaternion = false;
|
|
var isHumanMuscle = false;
|
|
for (int i = trackRange.x; i < trackRange.y; ++i)
|
|
{
|
|
var track = ts.tracks[i];
|
|
|
|
var kfIndex = track.keyFrameRange.x;
|
|
if (tf == TrackFrame.Last)
|
|
kfIndex += track.keyFrameRange.y - 1;
|
|
|
|
var trackValue = ts.keyframes[kfIndex].v;
|
|
|
|
var ci = (int)track.channelIndex;
|
|
switch (track.bindingType)
|
|
{
|
|
case BindingType.Translation:
|
|
rv.pos[ci] = trackValue;
|
|
break;
|
|
case BindingType.Quaternion:
|
|
rv.rot.value[ci] = trackValue;
|
|
break;
|
|
case BindingType.EulerAngles:
|
|
rv.rot.value[ci] = trackValue;
|
|
eulerToQuaternion = true;
|
|
break;
|
|
case BindingType.HumanMuscle:
|
|
rv.rot.value[ci] = trackValue;
|
|
isHumanMuscle = true;
|
|
break;
|
|
case BindingType.Scale:
|
|
rv.scale[ci] = trackValue;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have got Euler angles instead of quaternion, convert them here
|
|
if (eulerToQuaternion)
|
|
{
|
|
rv.rot = quaternion.EulerZXY(math.radians(rv.rot.value.xyz));
|
|
}
|
|
|
|
if (isHumanMuscle)
|
|
{
|
|
MuscleValuesToQuaternion(hrd, ref rv);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
(BoneTransform, float3) ProcessAnimationCurves(ref TrackSet ts, int2 trackRange, HumanRotationData hrd, float time)
|
|
{
|
|
var rv = BoneTransform.Identity();
|
|
|
|
bool eulerToQuaternion = false;
|
|
|
|
float3 flags = 0;
|
|
var isHumanMuscle = false;
|
|
for (int i = trackRange.x; i < trackRange.y; ++i)
|
|
{
|
|
var track = ts.tracks[i];
|
|
var interpolatedCurveValue = BlobCurve.SampleAnimationCurve(ref ts, i, time);
|
|
|
|
var ci = (int)track.channelIndex;
|
|
switch (track.bindingType)
|
|
{
|
|
case BindingType.Translation:
|
|
rv.pos[ci] = interpolatedCurveValue;
|
|
flags.x = 1;
|
|
break;
|
|
case BindingType.Quaternion:
|
|
rv.rot.value[ci] = interpolatedCurveValue;
|
|
flags.y = 1;
|
|
break;
|
|
case BindingType.EulerAngles:
|
|
eulerToQuaternion = true;
|
|
rv.rot.value[ci] = interpolatedCurveValue;
|
|
flags.y = 1;
|
|
break;
|
|
case BindingType.HumanMuscle:
|
|
rv.rot.value[ci] = interpolatedCurveValue;
|
|
isHumanMuscle = true;
|
|
flags.y = 1;
|
|
break;
|
|
case BindingType.Scale:
|
|
rv.scale[ci] = interpolatedCurveValue;
|
|
flags.z = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If we have got Euler angles instead of quaternion, convert them here
|
|
if (eulerToQuaternion)
|
|
{
|
|
rv.rot = quaternion.EulerZXY(math.radians(rv.rot.value.xyz));
|
|
}
|
|
|
|
if (isHumanMuscle)
|
|
{
|
|
MuscleValuesToQuaternion(hrd, ref rv);
|
|
}
|
|
|
|
return (rv, flags);
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
[WithNone(typeof(GPUAnimationEngineTag))]
|
|
partial struct ProcessAnimatorParameterCurveJob: IJobEntity
|
|
{
|
|
void Execute(in DynamicBuffer<AnimationToProcessComponent> animationsToProcess, ref DynamicBuffer<AnimatorControllerParameterComponent> acpc)
|
|
{
|
|
if (animationsToProcess.IsEmpty) return;
|
|
|
|
for (var i = 0; i < acpc.Length; ++i)
|
|
{
|
|
ref var p = ref acpc.ElementAt(i);
|
|
var parameterNameHash = Track.CalculateHash(p.hash);
|
|
p.value.floatValue = ComputeAnimatedProperty(p.value.floatValue, animationsToProcess.AsNativeArray(), SpecialBones.AnimatorTypeNameHash, parameterNameHash);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
[WithNone(typeof(GPUAnimationEngineTag))]
|
|
partial struct AnimateBlendShapeWeightsJob: IJobEntity
|
|
{
|
|
[ReadOnly]
|
|
public BufferLookup<AnimationToProcessComponent> animationToProcessLookup;
|
|
[ReadOnly]
|
|
public ComponentLookup<CullAnimationsTag> cullAnimationsLookup;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Execute(DynamicBuffer<BlendShapeWeight> blendShapeWeights, in SkinnedMeshRendererComponent asm)
|
|
{
|
|
if (IsAnimationsCulled(asm.animatedRigEntity) || !animationToProcessLookup.TryGetBuffer(asm.animatedRigEntity, out var animationsToProcess))
|
|
return;
|
|
|
|
if (animationsToProcess.IsEmpty) return;
|
|
|
|
for (var i = 0; i < blendShapeWeights.Length; ++i)
|
|
{
|
|
ref var p = ref blendShapeWeights.ElementAt(i);
|
|
var bsi = asm.smrInfoBlob.Value.blendShapes[i].hash;
|
|
var parameterNameHash = Track.CalculateHash(bsi);
|
|
|
|
p.Value = ComputeAnimatedProperty(0, animationsToProcess.AsNativeArray(), asm.nameHash, parameterNameHash);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool IsAnimationsCulled(Entity animatedEntity)
|
|
{
|
|
return cullAnimationsLookup.HasComponent(animatedEntity) && cullAnimationsLookup.IsComponentEnabled(animatedEntity);
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
struct CalculateBoneOffsetsJob: IJobChunk
|
|
{
|
|
[ReadOnly]
|
|
public ComponentTypeHandle<RigDefinitionComponent> rigDefinitionTypeHandle;
|
|
[ReadOnly]
|
|
public ComponentLookup<CullAnimationsTag> cullAnimationsTagLookup;
|
|
[ReadOnly]
|
|
public NativeArray<int> chunkBaseEntityIndices;
|
|
[ReadOnly]
|
|
public NativeList<Entity> entities;
|
|
|
|
[WriteOnly, NativeDisableContainerSafetyRestriction]
|
|
public NativeList<int2> bonePosesOffsets;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
|
|
{
|
|
var rigDefAccessor = chunk.GetNativeArray(ref rigDefinitionTypeHandle);
|
|
int baseEntityIndex = chunkBaseEntityIndices[unfilteredChunkIndex];
|
|
int validEntitiesInChunk = 0;
|
|
|
|
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
|
|
bonePosesOffsets[0] = 0;
|
|
|
|
while (cee.NextEntityIndex(out var i))
|
|
{
|
|
var rigDef = rigDefAccessor[i];
|
|
|
|
int entityInQueryIndex = baseEntityIndex + validEntitiesInChunk;
|
|
++validEntitiesInChunk;
|
|
|
|
var e = entities[entityInQueryIndex];
|
|
var boneCount = GetRigBoneCountWithRespectToCulling(e, rigDef, cullAnimationsTagLookup);
|
|
|
|
var v = new int2
|
|
(
|
|
// Bone count
|
|
boneCount,
|
|
// Number of ulong values that can hold bone transform flags
|
|
(boneCount * 4 >> 6) + 1
|
|
);
|
|
bonePosesOffsets[entityInQueryIndex + 1] = v;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int GetRigBoneCountWithRespectToCulling(Entity e, in RigDefinitionComponent rdc, in ComponentLookup<CullAnimationsTag> cullAnimationsTagLookup)
|
|
{
|
|
if (!cullAnimationsTagLookup.HasComponent(e) || !cullAnimationsTagLookup.IsComponentEnabled(e))
|
|
return rdc.rigBlob.Value.bones.Length;
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
struct CalculatePerBoneInfoJob: IJobChunk
|
|
{
|
|
[ReadOnly]
|
|
public NativeArray<int> chunkBaseEntityIndices;
|
|
[ReadOnly]
|
|
public NativeList<int2> bonePosesOffsets;
|
|
|
|
[WriteOnly, NativeDisableContainerSafetyRestriction]
|
|
public NativeList<int3> boneToEntityIndices;
|
|
|
|
public ComponentTypeHandle<RigDefinitionComponent> rigDefinitionTypeHandle;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
|
|
{
|
|
var rigDefArray = chunk.GetNativeArray(ref rigDefinitionTypeHandle);
|
|
int baseEntityIndex = chunkBaseEntityIndices[unfilteredChunkIndex];
|
|
int validEntitiesInChunk = 0;
|
|
|
|
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
|
|
|
|
while (cee.NextEntityIndex(out var i))
|
|
{
|
|
int entityInQueryIndex = baseEntityIndex + validEntitiesInChunk;
|
|
++validEntitiesInChunk;
|
|
var offset = bonePosesOffsets[entityInQueryIndex];
|
|
var nextOffset = bonePosesOffsets[entityInQueryIndex + 1]; // This is always valid because we have entities count + 1 array
|
|
|
|
var boneCount = nextOffset.x - offset.x;
|
|
|
|
var boneCountHighWORD = boneCount << 16;
|
|
for (int k = 0; k < boneCount; ++k)
|
|
{
|
|
var boneIndexAndBoneCount = k | boneCountHighWORD;
|
|
boneToEntityIndices[k + offset.x] = new int3(entityInQueryIndex, boneIndexAndBoneCount, offset.y);
|
|
}
|
|
|
|
var rigFrameData = new DynamicFrameData()
|
|
{
|
|
bonePoseOffset = offset.x,
|
|
boneFlagsOffset = offset.y,
|
|
rigBoneCount = boneCount,
|
|
};
|
|
var rigDef = rigDefArray[i];
|
|
rigDef.dynamicFrameData = rigFrameData;
|
|
rigDefArray[i] = rigDef;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
struct DoPrefixSumJob: IJob
|
|
{
|
|
public NativeList<int2> boneOffsets;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public void Execute()
|
|
{
|
|
var sum = new int2(0);
|
|
for (int i = 0; i < boneOffsets.Length; ++i)
|
|
{
|
|
var v = boneOffsets[i];
|
|
sum += v;
|
|
boneOffsets[i] = sum;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
struct ResizeDataBuffersJob: IJob
|
|
{
|
|
[ReadOnly]
|
|
public NativeList<int2> boneOffsets;
|
|
public RuntimeAnimationData runtimeData;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public void Execute()
|
|
{
|
|
var boneBufferLen = boneOffsets[^1];
|
|
runtimeData.animatedBonesBuffer.Resize(boneBufferLen.x, NativeArrayOptions.UninitializedMemory);
|
|
runtimeData.worldSpaceBonesBuffer.Resize(boneBufferLen.x, NativeArrayOptions.UninitializedMemory);
|
|
runtimeData.boneToEntityArr.Resize(boneBufferLen.x, NativeArrayOptions.UninitializedMemory);
|
|
|
|
// Clear flags by two resizes
|
|
runtimeData.boneTransformFlagsHolderArr.Resize(0, NativeArrayOptions.UninitializedMemory);
|
|
runtimeData.boneTransformFlagsHolderArr.Resize(boneBufferLen.y, NativeArrayOptions.ClearMemory);
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
partial struct CopyEntityBoneTransformsToAnimationBuffer: IJobEntity
|
|
{
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeList<BoneTransform> animatedBoneTransforms;
|
|
[ReadOnly]
|
|
public ComponentLookup<RigDefinitionComponent> rigDefComponentLookup;
|
|
[ReadOnly]
|
|
public ComponentLookup<GPUAnimationEngineTag> gpuAnimationEngineTagLookup;
|
|
[ReadOnly]
|
|
public ComponentLookup<Parent> parentComponentLookup;
|
|
[ReadOnly]
|
|
public ComponentLookup<PostTransformMatrix> ptmComponentLookup;
|
|
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeList<ulong> boneTransformFlags;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Execute(Entity e, in AnimatorEntityRefComponent aer, in LocalTransform lt)
|
|
{
|
|
if (!rigDefComponentLookup.TryGetComponent(aer.animatorEntity, out var rdc))
|
|
return;
|
|
|
|
if (gpuAnimationEngineTagLookup.IsComponentEnabled(aer.animatorEntity))
|
|
return;
|
|
|
|
var entityBoneData = rdc.dynamicFrameData;
|
|
if (entityBoneData.bonePoseOffset < 0)
|
|
return;
|
|
|
|
// If animation calculation was culled, we need operate only on valid bone range
|
|
if (aer.boneIndexInAnimationRig >= entityBoneData.rigBoneCount)
|
|
return;
|
|
|
|
var bonePoses = RuntimeAnimationData.GetAnimationDataForRigRW(animatedBoneTransforms, entityBoneData.bonePoseOffset, entityBoneData.rigBoneCount);
|
|
var transformFlags = AnimationTransformFlags.CreateFromBufferRW(boneTransformFlags, entityBoneData.boneFlagsOffset, entityBoneData.rigBoneCount);
|
|
var boneFlags = new bool3
|
|
(
|
|
transformFlags.IsTranslationSet(aer.boneIndexInAnimationRig),
|
|
transformFlags.IsRotationSet(aer.boneIndexInAnimationRig),
|
|
transformFlags.IsScaleSet(aer.boneIndexInAnimationRig)
|
|
);
|
|
|
|
if (!math.any(boneFlags))
|
|
{
|
|
var entityPose = new BoneTransform(lt);
|
|
if (ptmComponentLookup.TryGetComponent(e, out var ptm))
|
|
entityPose = new BoneTransform(lt, ptm);
|
|
|
|
// Root motion delta should be zero
|
|
if (rdc.applyRootMotion && aer.boneIndexInAnimationRig == 0)
|
|
entityPose = BoneTransform.Identity();
|
|
|
|
ref var bonePose = ref bonePoses[aer.boneIndexInAnimationRig];
|
|
|
|
if (!boneFlags.x)
|
|
bonePose.pos = entityPose.pos;
|
|
if (!boneFlags.y)
|
|
bonePose.rot = entityPose.rot;
|
|
if (!boneFlags.z)
|
|
bonePose.scale = entityPose.scale;
|
|
|
|
// For entities without parent we indicate that bone pose is in world space
|
|
if (!parentComponentLookup.HasComponent(e))
|
|
transformFlags.SetAbsoluteTransformFlag(aer.boneIndexInAnimationRig);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
struct MakeAbsoluteTransformsJob: IJobChunk
|
|
{
|
|
[ReadOnly]
|
|
public ComponentTypeHandle<RigDefinitionComponent> rigDefTypeHandle;
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeList<BoneTransform> localBoneTransforms;
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeList<BoneTransform> worldBoneTransforms;
|
|
[ReadOnly]
|
|
public NativeList<ulong> boneTransformFlags;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
|
|
{
|
|
var rigDefArray = chunk.GetNativeArray(ref rigDefTypeHandle);
|
|
|
|
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
|
|
var flagsHolder = new NativeBitArray(0xff, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
|
|
|
while (cee.NextEntityIndex(out var i))
|
|
{
|
|
var rigDef = rigDefArray[i];
|
|
|
|
ref var rigBones = ref rigDef.rigBlob.Value.bones;
|
|
var rigFrameData = rigDef.dynamicFrameData;
|
|
var rigBonesCount = rigFrameData.rigBoneCount;
|
|
flagsHolder.Resize(rigBonesCount);
|
|
flagsHolder.Clear();
|
|
|
|
var localBoneTransformsForRig = localBoneTransforms.GetSpan(rigFrameData.bonePoseOffset, rigBonesCount);
|
|
var worldBoneTransformsForRig = worldBoneTransforms.GetSpan(rigFrameData.bonePoseOffset, rigBonesCount);
|
|
var boneFlags = AnimationTransformFlags.CreateFromBufferRO(boneTransformFlags, rigFrameData.boneFlagsOffset, rigBonesCount);
|
|
|
|
// Iterate over all animated bones and calculate absolute transform in-place
|
|
for (int animationBoneIndex = 0; animationBoneIndex < rigBonesCount; ++animationBoneIndex)
|
|
{
|
|
if (boneFlags.IsAbsoluteTransform(animationBoneIndex))
|
|
{
|
|
flagsHolder.Set(animationBoneIndex, true);
|
|
worldBoneTransformsForRig[animationBoneIndex] = localBoneTransformsForRig[animationBoneIndex];
|
|
}
|
|
|
|
MakeAbsoluteTransform(flagsHolder, animationBoneIndex, localBoneTransformsForRig, worldBoneTransformsForRig, rigDef.rigBlob);
|
|
}
|
|
|
|
// For all initially absolute bones calculate local transforms
|
|
for (int animationBoneIndex = 0; animationBoneIndex < rigBonesCount; ++animationBoneIndex)
|
|
{
|
|
var parentBoneIndex = rigBones[animationBoneIndex].parentBoneIndex;
|
|
if (!boneFlags.IsAbsoluteTransform(animationBoneIndex) || parentBoneIndex < 0)
|
|
continue;
|
|
|
|
var parentWorldTransform = worldBoneTransformsForRig[parentBoneIndex];
|
|
var worldTransform = worldBoneTransformsForRig[animationBoneIndex];
|
|
|
|
var localTransform = BoneTransform.Multiply(BoneTransform.Inverse(parentWorldTransform), worldTransform);
|
|
localBoneTransformsForRig[animationBoneIndex] = localTransform;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void MakeAbsoluteTransform
|
|
(
|
|
NativeBitArray absTransformFlags,
|
|
int boneIndex,
|
|
Span<BoneTransform> localBoneTransformsForRig,
|
|
Span<BoneTransform> worldBoneTransformsForRig,
|
|
in BlobAssetReference<RigDefinitionBlob> rigBlob
|
|
)
|
|
{
|
|
var resultBoneTransform = BoneTransform.Identity();
|
|
var myBoneIndex = boneIndex;
|
|
ref var rigBones = ref rigBlob.Value.bones;
|
|
bool absTransformFlag;
|
|
|
|
do
|
|
{
|
|
absTransformFlag = absTransformFlags.IsSet(boneIndex);
|
|
var animatedBoneTransform = absTransformFlag ? worldBoneTransformsForRig[boneIndex] : localBoneTransformsForRig[boneIndex];
|
|
resultBoneTransform = BoneTransform.Multiply(animatedBoneTransform, resultBoneTransform);
|
|
|
|
boneIndex = rigBones[boneIndex].parentBoneIndex;
|
|
}
|
|
while (boneIndex >= 0 && !absTransformFlag);
|
|
|
|
worldBoneTransformsForRig[myBoneIndex] = resultBoneTransform;
|
|
absTransformFlags.Set(myBoneIndex, true);
|
|
}
|
|
}
|
|
|
|
//=================================================================================================================//
|
|
|
|
[BurstCompile]
|
|
[WithDisabled(typeof(GPUAnimationEngineTag))]
|
|
partial struct ComputeRootMotionJob: IJobEntity
|
|
{
|
|
[NativeDisableContainerSafetyRestriction]
|
|
public NativeList<BoneTransform> animatedBonePoses;
|
|
[ReadOnly]
|
|
public ComponentLookup<Parent> parentLookup;
|
|
[ReadOnly]
|
|
public ComponentLookup<PostTransformMatrix> ptmLookup;
|
|
[ReadOnly]
|
|
public ComponentLookup<LocalTransform> localTransformLookup;
|
|
|
|
public float deltaTime;
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void Execute(Entity e, in RigDefinitionComponent rdc, ref RootMotionVelocityComponent rmvc, in LocalTransform lt)
|
|
{
|
|
if (!rdc.applyRootMotion)
|
|
return;
|
|
|
|
var boneData = RuntimeAnimationData.GetAnimationDataForRigRW(animatedBonePoses, rdc);
|
|
if (boneData.IsEmpty)
|
|
return;
|
|
|
|
var motionDeltaPose = boneData[0];
|
|
var curEntityTransform = new BoneTransform(lt);
|
|
|
|
if (rmvc.removeBuiltinEntityMovement)
|
|
{
|
|
boneData[0] = curEntityTransform;
|
|
}
|
|
else
|
|
{
|
|
var newEntityPose = BoneTransform.Multiply(curEntityTransform, motionDeltaPose);
|
|
boneData[0] = newEntityPose;
|
|
}
|
|
|
|
rmvc.deltaPos = motionDeltaPose.pos;
|
|
rmvc.deltaRot = motionDeltaPose.rot;
|
|
rmvc.worldVelocity = motionDeltaPose.pos / deltaTime;
|
|
TransformHelpers.ComputeWorldTransformMatrix(e, out var localTransformMatrix, ref localTransformLookup, ref parentLookup, ref ptmLookup);
|
|
rmvc.worldVelocity = math.rotate(localTransformMatrix, rmvc.worldVelocity);
|
|
}
|
|
}
|
|
}
|
|
}
|