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

196 lines
6.0 KiB
C#

using Rukhanka.Toolbox;
using Rukhanka.WaybackMachine;
using Unity;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Mathematics;
//=================================================================================================================//
namespace Rukhanka
{
public partial struct AnimationEventEmitSystem
{
[BurstCompile]
partial struct EmitAnimationEventsJob : IJobEntity
{
public float deltaTime;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(Entity e, in DynamicBuffer<AnimationToProcessComponent> atp, in DynamicBuffer<PreviousProcessedAnimationComponent> ppa, ref DynamicBuffer<AnimationEventComponent> aec)
{
aec.Clear();
var ppaArr = ppa.AsNativeArray();
var atpArr = atp.AsNativeArray();
ulong prevAnimationsProcessedIndices = 0;
var maxBitsCount = UnsafeUtility.SizeOf<ulong>() * 8;
BurstAssert.IsTrue(ppaArr.Length <= maxBitsCount, "Too many simultaneous animations! Change bits holder to the NativeBitArray if this error occurs.");
for (var i = 0; i < atpArr.Length; ++i)
{
var a = atpArr[i];
if (a.animation == BlobAssetReference<AnimationClipBlob>.Null)
continue;
var curTime = a.time;
var prevBufferId = GetPreviousBufferAnimationIndex(a.motionId, i, ppaArr);
var prevTime = 0.0f;
if (prevBufferId < 0)
{
// There is no such animation in "previous buffer". Assume that this animation advances by dt already
var animationAdjustedDeltaTime = deltaTime / a.animation.Value.length;
prevTime = curTime - animationAdjustedDeltaTime;
}
else
{
prevAnimationsProcessedIndices |= 1ul << prevBufferId;
prevTime = ppaArr[prevBufferId].animationTime;
}
if (prevTime == curTime)
continue;
var negativeAnimationDT = prevTime > curTime;
if (negativeAnimationDT)
(prevTime, curTime) = (curTime, prevTime);
ref var aes = ref a.animation.Value.events;
if (a.animation.Value.looped)
{
ProcessEventsForLoopedAnimation(e, ref aec, ref aes, prevTime, curTime, negativeAnimationDT);
}
else
{
if (prevTime < 1 && curTime > 0)
ProcessEventsForAnimation(e, ref aec, ref aes, prevTime, curTime, negativeAnimationDT);
}
}
// Now we need check for animations that missed now, but were there at previous frame
// Animation can ending playing for at least one frame (dt)
for (var i = 0; i < ppaArr.Length; ++i)
{
var bitMask = 1ul << i;
if ((prevAnimationsProcessedIndices & bitMask) != 0)
continue;
var p = ppaArr[i];
ref var aes = ref p.animation.Value.events;
ProcessEventsForAnimation(e, ref aec, ref aes, p.animationTime, p.animationTime + deltaTime, false);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ProcessEventsForLoopedAnimation(Entity e, ref DynamicBuffer<AnimationEventComponent> aec, ref BlobArray<AnimationEventBlob> aes, float prevTime, float curTime, bool negativeAnimationDT)
{
var t0 = prevTime;
var dt = curTime - prevTime;
if (t0 < 0)
t0 = t0 - math.floor(t0);
var t1 = t0 + dt;
var it0 = t0;
// Divide whole range to sections that fit in [0..1] range, and execute events calculation for them individually
do
{
it0 = math.floor(it0);
var tStart = math.max(it0, t0);
var tEnd = math.min(it0 + 1, t1);
tEnd -= tStart;
tStart = math.frac(tStart);
tEnd += tStart;
ProcessEventsForAnimation(e, ref aec, ref aes, tStart, tEnd, negativeAnimationDT);
it0 += 1;
}
while (it0 < t1);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ProcessEventsForAnimation(Entity e, ref DynamicBuffer<AnimationEventComponent> outEvents, ref BlobArray<AnimationEventBlob> events, float fStart, float fEnd, bool reverseEventIteration)
{
for (var i = 0; i < events.Length; ++i)
{
// Reverse events iteration order to preserve events order in output buffer
var idx = reverseEventIteration ? events.Length - i - 1 : i;
ref var ae = ref events[idx];
var eventTime = ae.time;
var emitEvent = eventTime >= fStart && eventTime <= fEnd;
if (emitEvent)
{
var evt = new AnimationEventComponent(ref ae);
outEvents.Add(evt);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int GetPreviousBufferAnimationIndex(uint motionId, int animationBufferIndex, NativeArray<PreviousProcessedAnimationComponent> ppa)
{
// Fast path
if (animationBufferIndex < ppa.Length && ppa[animationBufferIndex].motionId == motionId)
return animationBufferIndex;
// Full search
for (var i = 0; i < ppa.Length; ++i)
{
if (motionId == ppa[i].motionId)
return i;
}
return -1;
}
}
//=================================================================================================================//
[BurstCompile]
partial struct MakeProcessedAnimationsSnapshotJob: IJobEntity
{
void Execute(in DynamicBuffer<AnimationToProcessComponent> atp, ref DynamicBuffer<PreviousProcessedAnimationComponent> ppa)
{
ppa.Resize(atp.Length, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < atp.Length; ++i)
{
var a = atp[i];
var p = new PreviousProcessedAnimationComponent()
{
animationTime = a.time,
motionId = a.motionId,
animation = a.animation
};
ppa[i] = p;
}
}
}
//=================================================================================================================//
[BurstCompile]
[WithAll(typeof(RecordComponent))]
partial struct CopyAnimationEventsToWaybackMachineRecordingJob: IJobEntity
{
public NativeList<AnimationEventComponent> outEvents;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in DynamicBuffer<AnimationEventComponent> aec)
{
outEvents.AddRange(aec.AsNativeArray());
}
}
}
}