Files
2026-05-31 14:27:52 -07:00

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);
}
}
}
}