Netcode Bootstrap

This commit is contained in:
Luis Gonzalez
2026-05-31 14:27:52 -07:00
parent 99d8d2d2a9
commit 7fa77ce821
1813 changed files with 2921554 additions and 84 deletions
@@ -0,0 +1,177 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Deformations;
using Unity.Entities;
using Unity.Jobs;
using Unity.Rendering;
using Unity.Transforms;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[UpdateAfter(typeof(RukhankaAnimationInjectionSystemGroup))]
partial struct AnimationApplicationSystem: ISystem
{
private EntityQuery
boneObjectEntitiesWithParentQuery,
boneObjectEntitiesNoParentQuery;
NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>> rigToSkinnedMeshRemapTables;
int newRemapTablesCounter;
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var eqb0 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<AnimatorEntityRefComponent, Parent>()
.WithAllRW<LocalTransform>();
boneObjectEntitiesWithParentQuery = ss.GetEntityQuery(eqb0);
var eqb1 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<AnimatorEntityRefComponent>()
.WithNone<Parent>()
.WithAllRW<LocalTransform>();
boneObjectEntitiesNoParentQuery = ss.GetEntityQuery(eqb1);
var rq = new EntityQueryBuilder(Allocator.Temp)
.WithAll<AnimatorEntityRefComponent>()
.Build(ref ss);
ss.RequireForUpdate(rq);
rigToSkinnedMeshRemapTables = new NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>>(128, Allocator.Persistent);
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnDestroy(ref SystemState ss)
{
foreach (var kv in rigToSkinnedMeshRemapTables)
kv.Value.Dispose();
rigToSkinnedMeshRemapTables.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var fillRigToSkinnedMeshRemapTablesJH = FillRigToSkinBonesRemapTableCache(ref ss, ss.Dependency);
// Propagate local animated transforms to the entities with and without parents
var propagateTRSToEntitiesWithParentsJH = PropagateAnimatedBonesToEntitiesTRS(ref ss, runtimeData, boneObjectEntitiesWithParentQuery, true, ss.Dependency);
var propagateTRSToEntitiesNoParentsJH = PropagateAnimatedBonesToEntitiesTRS(ref ss, runtimeData, boneObjectEntitiesNoParentQuery, false, propagateTRSToEntitiesWithParentsJH);
// Make corresponding skin matrices for all skinned meshes
var jh = JobHandle.CombineDependencies(fillRigToSkinnedMeshRemapTablesJH, propagateTRSToEntitiesNoParentsJH);
var applySkinJH = ApplySkinning(ref ss, runtimeData, jh);
// Update render bounds for meshes that request this
var updateRenderBoundsJH = UpdateRenderBounds(ref ss, runtimeData, ss.Dependency);
ss.Dependency = JobHandle.CombineDependencies(applySkinJH, updateRenderBoundsJH);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe JobHandle FillRigToSkinBonesRemapTableCache(ref SystemState ss, JobHandle dependsOn)
{
var rigDefinitionComponentLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
newRemapTablesCounter = 0;
// Count new remap tables count
var countNumberOfNewRemapTablesJob = new CountNumberOfNewRemapTablesJob()
{
rigDefinitionArr = rigDefinitionComponentLookup,
numberOfNewRemapTables = new UnsafeAtomicCounter32(UnsafeUtility.AddressOf(ref newRemapTablesCounter)),
rigToSkinnedMeshRemapTables = rigToSkinnedMeshRemapTables
};
var countNumberOfNewRemapTablesJH = countNumberOfNewRemapTablesJob.ScheduleParallel(dependsOn);
// Reserve necessary space in remap table cache
var increaseRigRemapTableCapacityJob = new IncreaseRigRemapTableCapacityJob()
{
numberOfNewRemapTables = (int*)UnsafeUtility.AddressOf(ref newRemapTablesCounter),
rigToSkinnedMeshRemapTables = rigToSkinnedMeshRemapTables
};
var increaseRigRemapTableCapacityJH = increaseRigRemapTableCapacityJob.Schedule(countNumberOfNewRemapTablesJH);
// Fill table cache with new tables
var fillRigToSkinBonesRemapTableCacheJob = new FillRigToSkinBonesRemapTableCacheJob()
{
rigDefinitionArr = rigDefinitionComponentLookup,
rigToSkinnedMeshRemapTables = rigToSkinnedMeshRemapTables.AsParallelWriter(),
newRemapTablesCounter = (int*)UnsafeUtility.AddressOf(ref newRemapTablesCounter),
};
var rv = fillRigToSkinBonesRemapTableCacheJob.ScheduleParallelByRef(increaseRigRemapTableCapacityJH);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle UpdateRenderBounds(ref SystemState ss, in RuntimeAnimationData runtimeData, JobHandle dependsOn)
{
var updateSkinnedMeshBoundsJob = new UpdateSkinnedMeshBoundsJob()
{
worldBonePoses = runtimeData.worldSpaceBonesBuffer,
rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true)
};
var updateSkinnedMeshBoundsJH = updateSkinnedMeshBoundsJob.ScheduleParallel(dependsOn);
return updateSkinnedMeshBoundsJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle PropagateAnimatedBonesToEntitiesTRS(ref SystemState ss, in RuntimeAnimationData runtimeData, EntityQuery eq, bool withParents, JobHandle dependsOn)
{
var propagateAnimationJob = new PropagateBoneTransformToEntityTRSJob()
{
boneTransforms = withParents ? runtimeData.animatedBonesBuffer : runtimeData.worldSpaceBonesBuffer,
postTransformMatrixLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(),
rigDefLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true),
gpuEngineTagLookup = SystemAPI.GetComponentLookup<GPUAnimationEngineTag>(true)
};
var jh = propagateAnimationJob.ScheduleParallel(eq, dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle ApplySkinning(ref SystemState ss, in RuntimeAnimationData runtimeData, JobHandle dependsOn)
{
var rigDefinitionComponentLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var cullAnimationsTagComponentLookup = SystemAPI.GetComponentLookup<CullAnimationsTag>(true);
var localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true);
var parentLookup = SystemAPI.GetComponentLookup<Parent>(true);
var rigBoneLookup = SystemAPI.GetComponentLookup<AnimatorEntityRefComponent>(true);
var gpuAnimationEngineLookup = SystemAPI.GetComponentLookup<GPUAnimationEngineTag>(true);
var animationApplyJob = new ApplyAnimationToSkinnedMeshJob()
{
boneTransforms = runtimeData.worldSpaceBonesBuffer,
rigDefinitionLookup = rigDefinitionComponentLookup,
rigToSkinnedMeshRemapTables = rigToSkinnedMeshRemapTables,
cullAnimationsTagLookup = cullAnimationsTagComponentLookup,
localTransformLookup = localTransformLookup,
parentLookup = parentLookup,
rigBoneLookup = rigBoneLookup,
gpuAnimationEngineTagLookup = gpuAnimationEngineLookup
};
var jh = animationApplyJob.ScheduleParallel(dependsOn);
return jh;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 196d4783de470ae4fa673c90f47403db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationApplicationSystem.cs
uploadId: 897522
@@ -0,0 +1,302 @@
using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Rendering;
using Unity.Transforms;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
partial struct AnimationApplicationSystem
{
//=================================================================================================================//
[BurstCompile]
partial struct ApplyAnimationToSkinnedMeshJob: IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefinitionLookup;
[ReadOnly]
public ComponentLookup<CullAnimationsTag> cullAnimationsTagLookup;
[ReadOnly]
public ComponentLookup<Parent> parentLookup;
[ReadOnly]
public ComponentLookup<LocalTransform> localTransformLookup;
[ReadOnly]
public ComponentLookup<AnimatorEntityRefComponent> rigBoneLookup;
[ReadOnly]
public ComponentLookup<GPUAnimationEngineTag> gpuAnimationEngineTagLookup;
[ReadOnly]
public NativeList<BoneTransform> boneTransforms;
[ReadOnly]
public NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>> rigToSkinnedMeshRemapTables;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(Entity e, in SkinnedMeshRendererComponent smc, in LocalTransform lt, ref DynamicBuffer<SkinMatrix> outSkinMatricesBuf)
{
var rigEntity = smc.animatedRigEntity;
if (cullAnimationsTagLookup.HasComponent(rigEntity) && cullAnimationsTagLookup.IsComponentEnabled(rigEntity))
return;
if (!rigDefinitionLookup.TryGetComponent(rigEntity, out var rigDef))
return;
if (smc.IsGPUAnimator(gpuAnimationEngineTagLookup))
return;
var boneDataOffset = rigDef.dynamicFrameData;
ref var boneRemapTable = ref GetBoneRemapTable(smc.smrInfoBlob, rigDef.rigBlob);
var absoluteBoneTransforms = RuntimeAnimationData.GetAnimationDataForRigRO(boneTransforms, boneDataOffset.bonePoseOffset, boneDataOffset.rigBoneCount);
var skinMeshBonesInfo = smc.smrInfoBlob;
var meshRenderToRigRootTransform = SkinnedMeshToRigRootTransform(e, smc.animatedRigEntity, lt, absoluteBoneTransforms);
// Iterate over all skinned mesh bones and set skin matrix from corresponding animation pose
for (int skinnedMeshBoneIndex = 0; skinnedMeshBoneIndex < skinMeshBonesInfo.Value.bones.Length; ++skinnedMeshBoneIndex)
{
var animationBoneIndex = boneRemapTable.remapIndices[skinnedMeshBoneIndex];
// Skip bone if it is not present in animation
if (animationBoneIndex < 0)
continue;
var absBonePose = absoluteBoneTransforms[animationBoneIndex];
absBonePose = BoneTransform.Multiply(meshRenderToRigRootTransform, absBonePose);
var boneXForm = absBonePose.ToFloat4x4();
ref var boneInfo = ref skinMeshBonesInfo.Value.bones[skinnedMeshBoneIndex];
var skinMatrix = MakeSkinMatrixForBone(ref boneInfo, boneXForm);
outSkinMatricesBuf[skinnedMeshBoneIndex] = skinMatrix;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
BoneTransform SkinnedMeshToRigRootTransform(Entity e, Entity selfAnimatedEntity, in LocalTransform lt, ReadOnlySpan<BoneTransform> bonePoses)
{
var rv = BoneTransform.Identity();
var parent = new Parent() { Value = e };
do
{
e = parent.Value;
if (rigBoneLookup.TryGetComponent(e, out var rb) && rb.animatorEntity == selfAnimatedEntity)
{
var absBonePose = bonePoses[rb.boneIndexInAnimationRig];
rv = BoneTransform.Multiply(absBonePose, rv);
break;
}
var entityLocalPose = new BoneTransform(localTransformLookup[e]);
rv = BoneTransform.Multiply(entityLocalPose, rv);
}
while (parentLookup.TryGetComponent(e, out parent));
return BoneTransform.Inverse(rv);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static Hash128 CalculateBoneRemapTableHash(in BlobAssetReference<SkinnedMeshInfoBlob> skinnedMesh, in BlobAssetReference<RigDefinitionBlob> rigDef)
{
var rv = new Hash128(skinnedMesh.Value.hash.Value.x, skinnedMesh.Value.hash.Value.y, rigDef.Value.hash.Value.x, rigDef.Value.hash.Value.y);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ref BoneRemapTableBlob GetBoneRemapTable(in BlobAssetReference<SkinnedMeshInfoBlob> skinnedMesh, in BlobAssetReference<RigDefinitionBlob> rigDef)
{
var h = CalculateBoneRemapTableHash(skinnedMesh, rigDef);
return ref rigToSkinnedMeshRemapTables[h].Value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SkinMatrix MakeSkinMatrixForBone(ref SkinnedMeshBoneInfo boneInfo, in float4x4 boneXForm)
{
var boneTransformMatrix = math.mul(boneXForm, boneInfo.bindPose);
var skinMatrix = new SkinMatrix() { Value = new float3x4(boneTransformMatrix.c0.xyz, boneTransformMatrix.c1.xyz, boneTransformMatrix.c2.xyz, boneTransformMatrix.c3.xyz) };
return skinMatrix;
}
}
//=================================================================================================================//
[BurstCompile]
partial struct PropagateBoneTransformToEntityTRSJob: IJobEntity
{
[ReadOnly]
public NativeList<BoneTransform> boneTransforms;
[NativeDisableParallelForRestriction]
public ComponentLookup<PostTransformMatrix> postTransformMatrixLookup;
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public ComponentLookup<GPUAnimationEngineTag> gpuEngineTagLookup;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(Entity e, in AnimatorEntityRefComponent animatorRef, ref LocalTransform lt)
{
if (gpuEngineTagLookup.IsComponentEnabled(animatorRef.animatorEntity))
return;
var rigDefinition = rigDefLookup[animatorRef.animatorEntity];
var boneData = RuntimeAnimationData.GetAnimationDataForRigRO(boneTransforms, rigDefinition);
if (boneData.IsEmpty)
return;
if (animatorRef.boneIndexInAnimationRig >= boneData.Length)
return;
var boneTransform = boneData[animatorRef.boneIndexInAnimationRig];
lt = boneTransform.ToLocalTransformComponent();
if (postTransformMatrixLookup.HasComponent(e))
{
lt.Scale = 1;
var ptm = float4x4.Scale(boneTransform.scale);
postTransformMatrixLookup[e] = new PostTransformMatrix() { Value = ptm };
}
}
}
//=================================================================================================================//
[BurstCompile]
partial struct CountNumberOfNewRemapTablesJob: IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefinitionArr;
[ReadOnly]
public NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>> rigToSkinnedMeshRemapTables;
[NativeDisableUnsafePtrRestriction]
public UnsafeAtomicCounter32 numberOfNewRemapTables;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in SkinnedMeshRendererComponent asmc)
{
if (!rigDefinitionArr.TryGetComponent(asmc.animatedRigEntity, out var rigDef))
return;
var h = ApplyAnimationToSkinnedMeshJob.CalculateBoneRemapTableHash(asmc.smrInfoBlob, rigDef.rigBlob);
if (!rigToSkinnedMeshRemapTables.ContainsKey(h))
numberOfNewRemapTables.Add(1);
}
}
//=================================================================================================================//
[BurstCompile]
unsafe struct IncreaseRigRemapTableCapacityJob: IJob
{
public NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>> rigToSkinnedMeshRemapTables;
[ReadOnly, NativeDisableUnsafePtrRestriction]
public int *numberOfNewRemapTables;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute()
{
rigToSkinnedMeshRemapTables.Capacity += *numberOfNewRemapTables;
}
}
//=================================================================================================================//
[BurstCompile]
unsafe partial struct FillRigToSkinBonesRemapTableCacheJob: IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefinitionArr;
public NativeParallelHashMap<Hash128, BlobAssetReference<BoneRemapTableBlob>>.ParallelWriter rigToSkinnedMeshRemapTables;
[ReadOnly, NativeDisableUnsafePtrRestriction]
public int *newRemapTablesCounter;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in SkinnedMeshRendererComponent asmc)
{
if (*newRemapTablesCounter == 0 || !rigDefinitionArr.TryGetComponent(asmc.animatedRigEntity, out var rigDef))
return;
MakeRigToSkinnedMeshRemapTable(asmc, rigDef);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void MakeRigToSkinnedMeshRemapTable(in SkinnedMeshRendererComponent sm, in RigDefinitionComponent rigDef)
{
// Try cache first
var h = ApplyAnimationToSkinnedMeshJob.CalculateBoneRemapTableHash(sm.smrInfoBlob, rigDef.rigBlob);
if (UnsafeParallelHashMapBase<Hash128, BlobAssetReference<BoneRemapTableBlob>>
.TryGetFirstValueAtomic(rigToSkinnedMeshRemapTables.m_Writer.m_Buffer, h, out _, out _))
return;
// Make new remap table
var rv = AnimationUtils.MakeSkinnedMeshToRigRemapTable(sm, rigDef, Allocator.Persistent);
if (!rigToSkinnedMeshRemapTables.TryAdd(h, rv))
rv.Dispose();
}
}
//=================================================================================================================//
[BurstCompile]
[WithAll(typeof(ShouldUpdateBoundingBoxTag))]
partial struct UpdateSkinnedMeshBoundsJob: IJobEntity
{
[ReadOnly]
public ComponentLookup<RigDefinitionComponent> rigDefLookup;
[ReadOnly]
public NativeList<BoneTransform> worldBonePoses;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in SkinnedMeshRendererComponent asm, ref RenderBounds rbb)
{
var rigDefinition = rigDefLookup[asm.animatedRigEntity];
var animationData = RuntimeAnimationData.GetAnimationDataForRigRO(worldBonePoses, rigDefinition);
if (animationData.Length <= asm.rootBoneIndexInRig || asm.rootBoneIndexInRig < 0)
return;
// Skinned mesh root bone world pose
var rootPose = animationData[0];
var invRootPose = BoneTransform.Inverse(rootPose);
// Loop over all bones and calculate extents in root bone space
float3 minPt = float.MaxValue;
float3 maxPt = float.MinValue;
for (var i = asm.rootBoneIndexInRig + 1; i < animationData.Length; ++i)
{
var boneWorldPose = animationData[i];
var rootBoneSpaceBonePose = BoneTransform.Multiply(invRootPose, boneWorldPose);
minPt = math.min(minPt, rootBoneSpaceBonePose.pos);
maxPt = math.max(maxPt, rootBoneSpaceBonePose.pos);
}
var aabb = new AABB()
{
Center = (minPt + maxPt) * 0.5f,
Extents = (maxPt - minPt) * 0.5f,
};
// Slightly extend result aabb to 'emulate' skin. Without proper per-vertex skin hull calculation this is reasonable approximation
aabb.Extents *= 1.1f;
rbb.Value = aabb;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: faa2bfe9711f2db4ea5949b9e8cf55ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationApplicationSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,177 @@
using System.Runtime.CompilerServices;
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using FixedStringName = Unity.Collections.FixedString512Bytes;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public enum BindingType
{
Unknown,
Translation,
Quaternion,
EulerAngles,
HumanMuscle,
Scale
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct KeyFrame
{
public float v;
public float inTan, outTan;
public float time;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimationEventBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint nameHash;
public float time;
public float floatParam;
public int intParam;
public uint stringParamHash;
#if RUKHANKA_DEBUG_INFO
public BlobString stringParam;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct PerfectHashTableBlob
{
public BlobArray<uint2> pht;
public uint seed;
public unsafe int Query(uint v)
{
return Perfect2HashTable.Query(v, seed, (uint2*)pht.GetUnsafePtr(), pht.Length);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum TrackFrame
{
First,
Last,
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct Track
{
#if RUKHANKA_DEBUG_INFO
public FixedString64Bytes name;
#endif
public uint props;
public int2 keyFrameRange;
public Track(BindingType bt, uint channelIndex)
{
keyFrameRange = 0;
props = 0;
#if RUKHANKA_DEBUG_INFO
name = default;
#endif
bindingType = bt;
this.channelIndex = channelIndex;
}
public BindingType bindingType
{
get => (BindingType)(props & 0xf);
set => props = (uint)value | props & 0xfffffff0;
}
public uint channelIndex
{
get => props >> 4 & 3;
set => props = value << 4 | props & 0xffffffcf;
}
// Zero out last 4 bits, to force Unknown binding type for such tracks
public static uint CalculateHash(uint h) => h & 0xfffffff0;
public static uint CalculateHash(in FixedStringName h) => CalculateHash(h.CalculateHash32());
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if RUKHANKA_DEBUG_INFO
public struct TrackGroupInfo
{
public FixedString128Bytes name;
public uint hash;
}
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct TrackSet
{
public BlobArray<KeyFrame> keyframes;
public BlobArray<Track> tracks;
public BlobArray<int> trackGroups;
public PerfectHashTableBlob trackGroupPHT;
#if RUKHANKA_DEBUG_INFO
public BlobArray<TrackGroupInfo> trackGroupDebugInfo;
#endif
public int GetTrackGroupIndex(uint boneHash) => trackGroupPHT.Query(boneHash);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimationClipBlob: GenericAssetBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
public string Name() => name.ToString();
public float bakingTime;
public float BakingTime() => bakingTime;
#endif
public Hash128 hash;
public Hash128 Hash() => hash;
public TrackSet clipTracks;
public TrackSet additiveReferencePoseFrame;
public BlobArray<AnimationEventBlob> events;
public uint flags;
public float cycleOffset;
public float length;
public bool looped { get => GetFlag(1); set => SetFlag(1, value); }
public bool loopPoseBlend { get => GetFlag(2); set => SetFlag(2, value); }
public uint maxTrackKeyframeLength;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void SetFlag(int index, bool value)
{
var v = 1u << index;
var mask = ~v;
var valueBits = math.select(0, v, value);
flags = flags & mask | valueBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
bool GetFlag(int index)
{
var v = 1u << index;
return (flags & v) != 0;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ee0714c0498362a4fb8592fc69214070
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationClipBlob.cs
uploadId: 897522
@@ -0,0 +1,35 @@
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct RigDefinitionComponent: IComponentData, IEnableableComponent
{
public BlobAssetReference<RigDefinitionBlob> rigBlob;
public bool applyRootMotion;
// Dynamic per-frame data
internal DynamicFrameData dynamicFrameData;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct DynamicFrameData
{
public int bonePoseOffset;
public int boneFlagsOffset;
public int rigBoneCount;
public static DynamicFrameData MakeInvalid()
{
return new DynamicFrameData()
{
boneFlagsOffset = -1,
bonePoseOffset = -1,
rigBoneCount = -1,
};
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 70db13716bccec641b9cf402fdbe0821
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationEngineComponents.cs
uploadId: 897522
@@ -0,0 +1,76 @@
using Rukhanka.WaybackMachine;
using Unity.Burst;
using Unity.Entities;
using Unity.Jobs;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[UpdateBefore(typeof(AnimationProcessSystem))]
public partial struct AnimationEventEmitSystem: ISystem
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle EmitAnimationEvents(ref SystemState ss, JobHandle dependsOn)
{
var dt = SystemAPI.Time.DeltaTime;
var emitAnimationEventsJob = new EmitAnimationEventsJob()
{
deltaTime = dt
};
var jh = emitAnimationEventsJob.ScheduleParallel(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle MakeProcessedAnimationsSnapshot(ref SystemState ss, JobHandle dependsOn)
{
var makeProcessedAnimationsSnapshotJob = new MakeProcessedAnimationsSnapshotJob() { };
var jh = makeProcessedAnimationsSnapshotJob.ScheduleParallel(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle CopyEventsForWaybackMachineDuringRecording(ref SystemState ss, JobHandle dependsOn)
{
if (!SystemAPI.TryGetSingletonRW<RecordComponent>(out var rcd))
return dependsOn;
var copyEventsToWaybackMachineJob = new CopyAnimationEventsToWaybackMachineRecordingJob()
{
outEvents = rcd.ValueRW.wbData.Value.emittedAnimationEvents
};
var jh = copyEventsToWaybackMachineJob.Schedule(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
// Emit animation events based on current and previously processed animations
var emitAnimationEventsJH = EmitAnimationEvents(ref ss, ss.Dependency);
// Make a snapshot of current frame animations, to use it in next frame as previously processed jobs
var makeProcessedAnimationsSnapshotJH = MakeProcessedAnimationsSnapshot(ref ss, emitAnimationEventsJH);
// Copy animation events into shadow buffer for wayback machine if recording requested
// Why to copy? Wayback machine recordings are performed in fixed-step update loop. If game frame time is more
// then recording fixed time, then some events will be missed from recording because each simulation (game)
// frame event buffer is cleared in AnimationEventEmitSystem
var copyEventsToWaybackMachineBufJH = CopyEventsForWaybackMachineDuringRecording(ref ss, emitAnimationEventsJH);
var combinedJH = JobHandle.CombineDependencies(copyEventsToWaybackMachineBufJH, makeProcessedAnimationsSnapshotJH);
ss.Dependency = combinedJH;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c6f4459336e6b6a44bc192d28fceb8ce
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationEventEmitSystem.cs
uploadId: 897522
@@ -0,0 +1,195 @@
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());
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3350396351b897c49b7e5d5e4e629bcb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationEventEmitSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,262 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Transforms;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[UpdateAfter(typeof(AnimationCullingSystem))]
public partial struct AnimationProcessSystem: ISystem
{
EntityQuery animatedObjectQuery;
NativeList<int2> bonePosesOffsetsArr;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
InitializeRuntimeData(ref ss);
bonePosesOffsetsArr = new (Allocator.Persistent);
var eqb0 = new EntityQueryBuilder(Allocator.Temp)
.WithAll<RigDefinitionComponent, AnimationToProcessComponent>()
.WithDisabled<GPUAnimationEngineTag>();
animatedObjectQuery = ss.GetEntityQuery(eqb0);
ss.RequireForUpdate(animatedObjectQuery);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnDestroy(ref SystemState ss)
{
bonePosesOffsetsArr.Dispose();
if (SystemAPI.TryGetSingleton<RuntimeAnimationData>(out var rad))
{
rad.Dispose();
ss.EntityManager.DestroyEntity(SystemAPI.GetSingletonEntity<RuntimeAnimationData>());
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void InitializeRuntimeData(ref SystemState ss)
{
var rad = RuntimeAnimationData.MakeDefault();
ss.EntityManager.CreateSingleton(rad, "Rukhanka.RuntimeAnimationData");
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle PrepareComputationData(ref SystemState ss, NativeArray<int> chunkBaseEntityIndices, ref RuntimeAnimationData runtimeData, NativeList<Entity> entitiesArr, JobHandle dependsOn)
{
var rigDefinitionTypeHandle = SystemAPI.GetComponentTypeHandle<RigDefinitionComponent>(true);
var cullAnimationsTagComponentLookup = SystemAPI.GetComponentLookup<CullAnimationsTag>(true);
var rigDefTypeHandle = SystemAPI.GetComponentTypeHandle<RigDefinitionComponent>();
// Calculate bone offsets per entity
var calcBoneOffsetsJob = new CalculateBoneOffsetsJob()
{
chunkBaseEntityIndices = chunkBaseEntityIndices,
bonePosesOffsets = bonePosesOffsetsArr,
rigDefinitionTypeHandle = rigDefinitionTypeHandle,
cullAnimationsTagLookup = cullAnimationsTagComponentLookup,
entities = entitiesArr,
};
var calcBoneOffsetsJobJH = calcBoneOffsetsJob.ScheduleParallel(animatedObjectQuery, dependsOn);
// Do prefix sum to calculate absolute offsets
var prefixSumJob = new DoPrefixSumJob()
{
boneOffsets = bonePosesOffsetsArr
};
var prefixSumJH = prefixSumJob.Schedule(calcBoneOffsetsJobJH);
// Resize data buffers depending on current workload
var resizeDataBuffersJob = new ResizeDataBuffersJob()
{
boneOffsets = bonePosesOffsetsArr,
runtimeData = runtimeData
};
var resizeDataBuffersJH = resizeDataBuffersJob.Schedule(prefixSumJH);
// Fill boneToEntityArr with proper values
var boneToEntityArrFillJob = new CalculatePerBoneInfoJob()
{
bonePosesOffsets = bonePosesOffsetsArr,
boneToEntityIndices = runtimeData.boneToEntityArr,
chunkBaseEntityIndices = chunkBaseEntityIndices,
rigDefinitionTypeHandle = rigDefTypeHandle
};
var boneToEntityJH = boneToEntityArrFillJob.ScheduleParallel(animatedObjectQuery, resizeDataBuffersJH);
return boneToEntityJH;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle AnimationCalculation(ref SystemState ss, NativeList<Entity> entitiesArr, in RuntimeAnimationData runtimeData, JobHandle dependsOn)
{
var animationToProcessBufferLookup = SystemAPI.GetBufferLookup<AnimationToProcessComponent>(true);
var rootMotionAnimationStateBufferLookupRW = SystemAPI.GetBufferLookup<RootMotionAnimationStateComponent>();
var rigDefsArr = animatedObjectQuery.ToComponentDataListAsync<RigDefinitionComponent>(ss.WorldUpdateAllocator, dependsOn, out var rigDefsLookupJH);
var computeAnimationsJob = new ComputeBoneAnimationJob()
{
animationsToProcessLookup = animationToProcessBufferLookup,
entityArr = entitiesArr,
rigDefs = rigDefsArr,
boneTransformFlagsArr = runtimeData.boneTransformFlagsHolderArr,
animatedBonesBuffer = runtimeData.animatedBonesBuffer,
boneToEntityArr = runtimeData.boneToEntityArr,
rootMotionAnimStateBufferLookup = rootMotionAnimationStateBufferLookupRW,
};
var jh = computeAnimationsJob.Schedule(runtimeData.animatedBonesBuffer, 16, rigDefsLookupJH);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle ProcessAnimatorParameterCurves(ref SystemState ss, JobHandle dependsOn)
{
var genericCurveProcessJob = new ProcessAnimatorParameterCurveJob();
var jh = genericCurveProcessJob.ScheduleParallel(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle CopyEntityBonesToAnimationTransforms(ref SystemState ss, ref RuntimeAnimationData runtimeData, JobHandle dependsOn)
{
var rigDefinitionLookup = SystemAPI.GetComponentLookup<RigDefinitionComponent>(true);
var gpuAnimationEngineTagLookup = SystemAPI.GetComponentLookup<GPUAnimationEngineTag>(true);
var parentComponentLookup = SystemAPI.GetComponentLookup<Parent>();
var ptmComponentLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true);
// Now take available entity transforms as ref poses overrides
var copyEntityBoneTransforms = new CopyEntityBoneTransformsToAnimationBuffer()
{
rigDefComponentLookup = rigDefinitionLookup,
boneTransformFlags = runtimeData.boneTransformFlagsHolderArr,
animatedBoneTransforms = runtimeData.animatedBonesBuffer,
parentComponentLookup = parentComponentLookup,
gpuAnimationEngineTagLookup = gpuAnimationEngineTagLookup,
ptmComponentLookup = ptmComponentLookup,
};
var jh = copyEntityBoneTransforms.ScheduleParallel(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle MakeAbsoluteBoneTransforms(ref SystemState ss, in RuntimeAnimationData runtimeData, JobHandle dependsOn)
{
var rigDefTypeHandle = SystemAPI.GetComponentTypeHandle<RigDefinitionComponent>(true);
var makeAbsTransformsJob = new MakeAbsoluteTransformsJob()
{
localBoneTransforms = runtimeData.animatedBonesBuffer,
worldBoneTransforms = runtimeData.worldSpaceBonesBuffer,
boneTransformFlags = runtimeData.boneTransformFlagsHolderArr,
rigDefTypeHandle = rigDefTypeHandle
};
var query = SystemAPI.QueryBuilder()
.WithAll<RigDefinitionComponent>()
.WithNone<GPUAnimationEngineTag>()
.Build();
var jh = makeAbsTransformsJob.ScheduleParallel(query, dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle ComputeRootMotion(ref SystemState ss, in RuntimeAnimationData runtimeData, JobHandle dependsOn)
{
var computeRootMotionJob = new ComputeRootMotionJob()
{
animatedBonePoses = runtimeData.animatedBonesBuffer,
deltaTime = SystemAPI.Time.DeltaTime,
parentLookup = SystemAPI.GetComponentLookup<Parent>(true),
ptmLookup = SystemAPI.GetComponentLookup<PostTransformMatrix>(true),
localTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(true),
};
var jh = computeRootMotionJob.ScheduleParallel(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle AnimateBlendShapeWeights(ref SystemState ss, JobHandle dependsOn)
{
var animateBlendWeightsJob = new AnimateBlendShapeWeightsJob()
{
animationToProcessLookup = SystemAPI.GetBufferLookup<AnimationToProcessComponent>(true),
cullAnimationsLookup = SystemAPI.GetComponentLookup<CullAnimationsTag>(true)
};
var jh = animateBlendWeightsJob.ScheduleParallel(dependsOn);
return jh;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
ref var runtimeData = ref SystemAPI.GetSingletonRW<RuntimeAnimationData>().ValueRW;
var entityCount = animatedObjectQuery.CalculateEntityCount();
if (entityCount == 0) return;
bonePosesOffsetsArr.Resize(entityCount + 1, NativeArrayOptions.UninitializedMemory);
var chunkBaseEntityIndices = animatedObjectQuery.CalculateBaseEntityIndexArrayAsync(ss.WorldUpdateAllocator, ss.Dependency, out var baseIndexCalcJH);
var entitiesArr = animatedObjectQuery.ToEntityListAsync(ss.WorldUpdateAllocator, ss.Dependency, out var entityArrJH);
var combinedJH = JobHandle.CombineDependencies(baseIndexCalcJH, entityArrJH);
// Define array with bone pose offsets for calculated bone poses
var calcBoneOffsetsJH = PrepareComputationData(ref ss, chunkBaseEntityIndices, ref runtimeData, entitiesArr, combinedJH);
// Animate blend shape weights
var animateBlendShapeWeights = AnimateBlendShapeWeights(ref ss, calcBoneOffsetsJH);
// Curves that control controller parameters
var parameterControlByCurvesJob = ProcessAnimatorParameterCurves(ref ss, calcBoneOffsetsJH);
var combinedJH2 = JobHandle.CombineDependencies(parameterControlByCurvesJob, animateBlendShapeWeights);
// Spawn jobs for animation calculation
var computeAnimationJH = AnimationCalculation(ref ss, entitiesArr, runtimeData, combinedJH2);
// Copy entities poses into animation buffer for non-animated parts
var copyEntityTransformsIntoAnimationBufferJH = CopyEntityBonesToAnimationTransforms(ref ss, ref runtimeData, computeAnimationJH);
// Compute root motion
var rootMotionJH = ComputeRootMotion(ref ss, runtimeData, copyEntityTransformsIntoAnimationBufferJH);
// Make world space transforms out of local bone poses
var makeAbsTransformsJH = MakeAbsoluteBoneTransforms(ref ss, runtimeData, rootMotionJH);
ss.Dependency = makeAbsTransformsJH;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: d2a720ee00702074395f1ce17309a70f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationProcessSystem.cs
uploadId: 897522
@@ -0,0 +1,131 @@
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
using FixedStringName = Unity.Collections.FixedString512Bytes;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[InternalBufferCapacity(4)]
[ChunkSerializable]
public struct AnimationToProcessComponent: IBufferElementData
{
public float weight;
public float time;
public BlobAssetReference<AnimationClipBlob> animation;
public BlobAssetReference<AvatarMaskBlob> avatarMask;
public AnimationBlendingMode blendMode;
public float layerWeight;
public int layerIndex;
public uint motionId;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorEntityRefComponent: IComponentData
{
public int boneIndexInAnimationRig;
public Entity animatorEntity;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if RUKHANKA_WITH_NETCODE
[GhostComponent(PrefabType = GhostPrefabType.Client)]
#endif
public struct SkinnedMeshRendererComponent: IComponentData
{
public uint nameHash;
public Entity animatedRigEntity;
public int rootBoneIndexInRig;
public BlobAssetReference<SkinnedMeshInfoBlob> smrInfoBlob;
public bool IsGPUAnimator(in ComponentLookup<GPUAnimationEngineTag> gpuAnimationEngineTagLookup)
=> AnimationUtils.IsGPUAnimator(animatedRigEntity, gpuAnimationEngineTagLookup);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if RUKHANKA_WITH_NETCODE
[GhostComponent(PrefabType = GhostPrefabType.Client)]
#endif
public struct ShouldUpdateBoundingBoxTag: IComponentData, IEnableableComponent { }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct RootMotionAnimationStateComponent: IBufferElementData, IEnableableComponent
{
public uint uniqueMotionId;
public BoneTransform animationState;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct RootMotionVelocityComponent: IComponentData
{
public bool removeBuiltinEntityMovement;
public float3 worldVelocity;
public float3 deltaPos;
public quaternion deltaRot;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[InternalBufferCapacity(4)]
public struct AnimationEventComponent: IBufferElementData, IEnableableComponent
{
public AnimationEventComponent(ref AnimationEventBlob aeb)
{
nameHash = aeb.nameHash;
floatParam = aeb.floatParam;
intParam = aeb.intParam;
stringParamHash = aeb.stringParamHash;
#if RUKHANKA_DEBUG_INFO
name = "";
if (aeb.name.Length > 0)
aeb.name.CopyToWithTruncate(ref name);
stringParam = "";
if (aeb.stringParam.Length > 0)
aeb.stringParam.CopyToWithTruncate(ref stringParam);
#endif
}
#if RUKHANKA_DEBUG_INFO
public FixedString32Bytes name;
public FixedString32Bytes stringParam;
#endif
public uint nameHash;
public float floatParam;
public int intParam;
public uint stringParamHash;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[ChunkSerializable]
public struct PreviousProcessedAnimationComponent: IBufferElementData
{
public uint motionId;
public float animationTime;
public BlobAssetReference<AnimationClipBlob> animation;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Define some special bone names
public static class SpecialBones
{
// Special bone hashes need to be recalculated in case of hashing function change!
// I cannot add hash computation code here, because burst will fail to compile
public static readonly string UnnamedRootBoneName = "RUKHANKA_UnnamedRootBone";
public static readonly string AnimatorTypeName = "RUKHANKA_Animator";
public static readonly uint AnimatorTypeNameHash = 0xde00343e;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bce484afb6b804048b94e9f522c6ca5c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationProcessSystemComponents.cs
uploadId: 897522
@@ -0,0 +1,130 @@
using System;
using Unity.Burst.CompilerServices;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
//=================================================================================================================//
namespace Rukhanka
{
partial struct AnimationProcessSystem
{
struct LayerInfo
{
public int index;
public float weight;
public AnimationBlendingMode blendMode;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static float ComputeAnimatedProperty(float animatedValue, in NativeArray<AnimationToProcessComponent> animations, uint trackHash, uint propHash)
{
var layerValue = 0.0f;
var rv = animatedValue;
var layerHasAnimation = false;
LayerInfo layerInfo = default;
for (int l = 0; l < animations.Length; ++l)
{
var atp = animations[l];
if (Hint.Unlikely(atp.animation == BlobAssetReference<AnimationClipBlob>.Null || atp.weight == 0 || atp.layerWeight == 0))
continue;
var curLayerInfo = GetLayerInfoFromAnimation(atp);
// Apply layer value
if (layerInfo.index != curLayerInfo.index)
{
if (layerHasAnimation)
rv = ApplyLayerValue(rv, layerValue, layerInfo);
layerValue = 0;
layerHasAnimation = false;
}
layerInfo = curLayerInfo;
ref var trackSet = ref atp.animation.Value.clipTracks;
var trackGroupIndex = trackSet.trackGroupPHT.Query(trackHash);
if (trackGroupIndex < 0)
continue;
var animTime = ComputeBoneAnimationJob.NormalizeAnimationTime(atp.time, ref atp.animation.Value);
var trackRange = new int2(trackSet.trackGroups[trackGroupIndex], trackSet.trackGroups[trackGroupIndex + 1]);
for (var k = trackRange.x; k < trackRange.y; ++k)
{
var track = trackSet.tracks[k];
if (track.props == propHash)
{
var curveValue = SampleTrack(ref trackSet, k, atp, animTime.x);
if (atp.animation.Value.loopPoseBlend)
curveValue -= CalculateTrackLoopValue(ref trackSet, k, atp, animTime.y);
layerValue += curveValue * atp.weight;
layerHasAnimation = true;
break;
}
}
}
if (layerHasAnimation)
rv = ApplyLayerValue(rv, layerValue, layerInfo);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static float ApplyLayerValue(float currentValue, float layerValue, in LayerInfo layerInfo)
{
var rv = currentValue;
if (Hint.Likely(layerInfo.blendMode == AnimationBlendingMode.Override))
{
rv = math.lerp(rv, layerValue, layerInfo.weight);
}
else
{
rv += layerValue * layerInfo.weight;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static float SampleTrack(ref TrackSet trackSet, int trackIndex, in AnimationToProcessComponent atp, float animTime)
{
var curveValue = BlobCurve.SampleAnimationCurve(ref trackSet, trackIndex, animTime);
// Make additive animation if requested
if (atp.blendMode == AnimationBlendingMode.Additive)
{
var additiveValue = BlobCurve.SampleAnimationCurve(ref trackSet, trackIndex, 0);
curveValue -= additiveValue;
}
return curveValue;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static float CalculateTrackLoopValue(ref TrackSet trackSet, int trackIndex, in AnimationToProcessComponent atp, float normalizedTime)
{
var startV = SampleTrack(ref trackSet, trackIndex, atp, 0);
var endV = SampleTrack(ref trackSet, trackIndex, atp, atp.animation.Value.length);
var rv = (endV - startV) * normalizedTime;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static LayerInfo GetLayerInfoFromAnimation(in AnimationToProcessComponent atp)
{
var rv = new LayerInfo()
{
weight = atp.layerWeight,
blendMode = atp.blendMode,
index = atp.layerIndex
};
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4d9e19b3f73cd174b97a4a499d857d09
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationProcessSystem_Common.cs
uploadId: 897522
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b0a1354f9dc3a6b48bfc81641c1a6e7f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationProcessSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,243 @@
using System;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Assertions;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimationStream: IDisposable
{
public DynamicFrameData rigFrameData;
public RuntimeAnimationData runtimeData;
public BlobAssetReference<RigDefinitionBlob> rigBlob;
public NativeBitArray worldPoseDirtyFlags;
/////////////////////////////////////////////////////////////////////////////////
public static AnimationStream Create(RuntimeAnimationData rd, in RigDefinitionComponent rdc)
{
var rv = new AnimationStream()
{
rigFrameData = rdc.dynamicFrameData,
runtimeData = rd,
rigBlob = rdc.rigBlob,
worldPoseDirtyFlags = new NativeBitArray(rdc.dynamicFrameData.rigBoneCount, Allocator.Temp)
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
RebuildOutdatedBonePoses(-1);
}
/////////////////////////////////////////////////////////////////////////////////
public BoneTransform GetLocalPose(int boneIndex)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return BoneTransform.Identity();
return runtimeData.animatedBonesBuffer[rigFrameData.bonePoseOffset + boneIndex];
}
/////////////////////////////////////////////////////////////////////////////////
public float3 GetLocalPosition(int boneIndex) => GetLocalPose(boneIndex).pos;
public quaternion GetLocalRotation(int boneIndex) => GetLocalPose(boneIndex).rot;
/////////////////////////////////////////////////////////////////////////////////
public BoneTransform GetWorldPose(int boneIndex)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return BoneTransform.Identity();
var isWorldPoseDirty = worldPoseDirtyFlags.IsSet(boneIndex);
if (isWorldPoseDirty)
RebuildOutdatedBonePoses(boneIndex);
return runtimeData.worldSpaceBonesBuffer[rigFrameData.bonePoseOffset + boneIndex];
}
/////////////////////////////////////////////////////////////////////////////////
public float3 GetWorldPosition(int boneIndex) => GetWorldPose(boneIndex).pos;
public quaternion GetWorldRotation(int boneIndex) => GetWorldPose(boneIndex).rot;
/////////////////////////////////////////////////////////////////////////////////
BoneTransform GetParentBoneWorldPose(int boneIndex)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return BoneTransform.Identity();
var parentBoneIndex = rigBlob.Value.bones[boneIndex].parentBoneIndex;
var parentWorldPose = BoneTransform.Identity();
if (parentBoneIndex >= 0)
{
if (worldPoseDirtyFlags.IsSet(parentBoneIndex))
RebuildOutdatedBonePoses(parentBoneIndex);
parentWorldPose = runtimeData.worldSpaceBonesBuffer[parentBoneIndex + rigFrameData.bonePoseOffset];
}
return parentWorldPose;
}
/////////////////////////////////////////////////////////////////////////////////
public void SetWorldPose(int boneIndex, in BoneTransform bt)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return;
var absBoneIndex = rigFrameData.bonePoseOffset + boneIndex;
runtimeData.worldSpaceBonesBuffer[absBoneIndex] = bt;
var parentWorldPose = GetParentBoneWorldPose(boneIndex);
ref var boneLocalPose = ref runtimeData.animatedBonesBuffer.ElementAt(absBoneIndex);
boneLocalPose = BoneTransform.Multiply(BoneTransform.Inverse(parentWorldPose), bt);
MarkChildrenWorldPosesAsDirty(boneIndex);
}
/////////////////////////////////////////////////////////////////////////////////
public void SetWorldPosition(int boneIndex, float3 pos)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return;
var curPose = GetWorldPose(boneIndex);
curPose.pos = pos;
SetWorldPose(boneIndex, curPose);
}
/////////////////////////////////////////////////////////////////////////////////
public void SetWorldRotation(int boneIndex, quaternion rot)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return;
var absBoneIndex = rigFrameData.bonePoseOffset + boneIndex;
ref var boneWorldPose = ref runtimeData.worldSpaceBonesBuffer.ElementAt(absBoneIndex);
boneWorldPose.rot = rot;
var parentWorldRot = GetParentBoneWorldPose(boneIndex).rot;
ref var boneLocalPose = ref runtimeData.animatedBonesBuffer.ElementAt(absBoneIndex);
boneLocalPose.rot = math.mul(math.inverse(parentWorldRot), boneWorldPose.rot);
MarkChildrenWorldPosesAsDirty(boneIndex);
}
/////////////////////////////////////////////////////////////////////////////////
public void SetLocalPose(int boneIndex, in BoneTransform bt)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return;
var absBoneIndex = rigFrameData.bonePoseOffset + boneIndex;
runtimeData.animatedBonesBuffer[absBoneIndex] = bt;
MarkChildrenWorldPosesAsDirty(boneIndex);
worldPoseDirtyFlags.Set(boneIndex, true);
}
/////////////////////////////////////////////////////////////////////////////////
public void SetLocalPosition(int boneIndex, float3 pos)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return;
var absBoneIndex = rigFrameData.bonePoseOffset + boneIndex;
ref var curPose = ref runtimeData.animatedBonesBuffer.ElementAt(absBoneIndex);
curPose.pos = pos;
MarkChildrenWorldPosesAsDirty(boneIndex);
worldPoseDirtyFlags.Set(boneIndex, true);
}
/////////////////////////////////////////////////////////////////////////////////
public void SetLocalRotation(int boneIndex, quaternion rot)
{
if (boneIndex >= rigFrameData.rigBoneCount)
return;
var absBoneIndex = rigFrameData.bonePoseOffset + boneIndex;
ref var curPose = ref runtimeData.animatedBonesBuffer.ElementAt(absBoneIndex);
curPose.rot = rot;
MarkChildrenWorldPosesAsDirty(boneIndex);
worldPoseDirtyFlags.Set(boneIndex, true);
}
/////////////////////////////////////////////////////////////////////////////////
void MarkChildrenWorldPosesAsDirty(int rootBoneIndex)
{
for (var i = rootBoneIndex + 1; i < rigFrameData.rigBoneCount; ++i)
{
ref var bone = ref rigBlob.Value.bones[i];
if (bone.parentBoneIndex == rootBoneIndex)
{
worldPoseDirtyFlags.Set(i, true);
MarkChildrenWorldPosesAsDirty(i);
}
}
}
/////////////////////////////////////////////////////////////////////////////////
void RebuildOutdatedBonePoses(int interestedBoneIndex)
{
if (rigFrameData.rigBoneCount < 0)
return;
var endBoneIndex = math.select(rigFrameData.rigBoneCount - 1, interestedBoneIndex, interestedBoneIndex >= 0);
endBoneIndex = math.min(endBoneIndex, rigFrameData.rigBoneCount);
for (var i = 0; i <= endBoneIndex; ++i)
{
var isWorldPoseDirty = worldPoseDirtyFlags.IsSet(i);
if (!isWorldPoseDirty)
continue;
var absBoneIndex = rigFrameData.bonePoseOffset + i;
ref var rigBone = ref rigBlob.Value.bones[i];
var boneLocalPose = runtimeData.animatedBonesBuffer[absBoneIndex];
var parentBoneWorldPose = BoneTransform.Identity();
if (rigBone.parentBoneIndex >= 0)
{
parentBoneWorldPose = runtimeData.worldSpaceBonesBuffer[rigFrameData.bonePoseOffset + rigBone.parentBoneIndex];
}
var worldPose = BoneTransform.Multiply(parentBoneWorldPose, boneLocalPose);
runtimeData.worldSpaceBonesBuffer[absBoneIndex] = worldPose;
}
worldPoseDirtyFlags.SetBits(0, false, endBoneIndex + 1);
}
/////////////////////////////////////////////////////////////////////////////////
public AnimationTransformFlags GetAnimationTransformFlagsRO()
{
return AnimationTransformFlags.CreateFromBufferRO(runtimeData.boneTransformFlagsHolderArr, rigFrameData.boneFlagsOffset, rigFrameData.rigBoneCount);
}
/////////////////////////////////////////////////////////////////////////////////
public AnimationTransformFlags GetAnimationTransformFlagsRW()
{
return AnimationTransformFlags.CreateFromBufferRW(runtimeData.boneTransformFlagsHolderArr, rigFrameData.boneFlagsOffset, rigFrameData.rigBoneCount);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e30795c9f15acc849bbee0264be3ccf0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationStream.cs
uploadId: 897522
@@ -0,0 +1,80 @@
using System;
using System.Runtime.CompilerServices;
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimationTransformFlags
{
UnsafeBitArray transformFlags;
/////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsTranslationSet(int index) => transformFlags.IsSet(index * 4 + 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsRotationSet(int index) => transformFlags.IsSet(index * 4 + 1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsScaleSet(int index) => transformFlags.IsSet(index * 4 + 2);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsAbsoluteTransform(int index) => transformFlags.IsSet(index * 4 + 3);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetTranslationFlag(int index) => transformFlags.SetBitThreadSafe(index * 4 + 0);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetRotationFlag(int index) => transformFlags.SetBitThreadSafe(index * 4 + 1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetScaleFlag(int index) => transformFlags.SetBitThreadSafe(index * 4 + 2);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetAbsoluteTransformFlag(int index) => transformFlags.SetBitThreadSafe(index * 4 + 3);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ResetAllFlags() => transformFlags.Clear();
/////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
unsafe static UnsafeBitArray GetRigTransformFlagsInternal(void* ptr, int bufLenInBytes, int ulongOffset, int boneCount)
{
// Each bone contains 4 bit flags - TRS and is bone already in absolute transform
var sizeInUlongs = (boneCount * 4 >> 6) + 1;
var sizeInBytes = sizeInUlongs * 8;
var startPtr = (ulong*)ptr + ulongOffset;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
if ((byte*)startPtr + sizeInBytes > (byte*)ptr + bufLenInBytes)
{
throw new InvalidOperationException($"Buffer range error! Offset and/or count exceed buffer space!");
}
#endif
var rv = new UnsafeBitArray(startPtr, sizeInUlongs * 8, Allocator.None);
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe AnimationTransformFlags CreateFromBufferRW(in NativeList<ulong> buf, int bufElementOffset, int boneCount)
{
var rwPtr = buf.GetUnsafePtr();
var rv = new AnimationTransformFlags() { transformFlags = GetRigTransformFlagsInternal(rwPtr, buf.Length * sizeof(ulong), bufElementOffset, boneCount) };
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe AnimationTransformFlags CreateFromBufferRO(in NativeList<ulong> buf, int bufElementOffset, int boneCount)
{
var roPtr = buf.GetUnsafeReadOnlyPtr();
var rv = new AnimationTransformFlags() { transformFlags = GetRigTransformFlagsInternal(roPtr, buf.Length * sizeof(ulong), bufElementOffset, boneCount) };
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ad302fcd955fb504bb4d7a4fffbf5c22
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AnimationTransformFlags.cs
uploadId: 897522
@@ -0,0 +1,42 @@
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AvatarMaskBlob: GenericAssetBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
public string Name() => name.ToString();
public BlobArray<BlobString> includedBoneNames;
public float bakingTime;
public float BakingTime() => bakingTime;
#endif
public Hash128 hash;
public Hash128 Hash() => hash;
public BlobArray<uint> includedBoneMask;
public uint humanBodyPartsAvatarMask;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static (int, uint) GetUintIndexAndMask(int boneIndex)
{
int uintIndex = boneIndex >> 5; // boneIndex / 32
int byteOffsetInUint = boneIndex & 0x1f; // boneIndex % 32;
uint boneMask = 1u << byteOffsetInUint;
return (uintIndex, boneMask);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool IsBoneIncluded(int boneIndex)
{
var (uintIndex, mask) = GetUintIndexAndMask(boneIndex);
var rv = (includedBoneMask[uintIndex] & mask) != 0;
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3e9e4993401af9e458a46c80dcacceaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/AvatarMaskBlob.cs
uploadId: 897522
@@ -0,0 +1,109 @@
using System;
using System.Runtime.CompilerServices;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public static class BlobCurve
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static public float EvaluateBezierCurve(KeyFrame f0, KeyFrame f1, float l)
{
float dt = f1.time - f0.time;
float m0 = f0.outTan * dt;
float m1 = f1.inTan * dt;
float t2 = l * l;
float t3 = t2 * l;
float a = 2 * t3 - 3 * t2 + 1;
float b = t3 - 2 * t2 + l;
float c = t3 - t2;
float d = -2 * t3 + 3 * t2;
float rv = a * f0.v + b * m0 + c * m1 + d * f1.v;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe float SampleAnimationCurve(ref BlobArray<KeyFrame> kf, float time)
{
var arr = new ReadOnlySpan<KeyFrame>(kf.GetUnsafePtr(), kf.Length);
return SampleAnimationCurveBinarySearch(arr, time);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe float SampleAnimationCurve(ref TrackSet ts, int trackIndex, float time)
{
var track = ts.tracks[trackIndex];
var kfRange = new ReadOnlySpan<KeyFrame>((KeyFrame*)ts.keyframes.GetUnsafePtr() + track.keyFrameRange.x, track.keyFrameRange.y);
return SampleAnimationCurveBinarySearch(kfRange, time);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float SampleAnimationCurveBinarySearch(in ReadOnlySpan<KeyFrame> kf, float time)
{
var startIndex = 0;
var endIndex = kf.Length;
var less = true;
var greater = true;
KeyFrame frame0 = default, frame1 = default;
if (kf.Length < 3)
return SampleAnimationCurveLinearSearch(kf, time);
while(endIndex - startIndex >= 1 && (less || greater) && endIndex > 1)
{
var middleIndex = (endIndex + startIndex) / 2;
frame1 = kf[middleIndex];
frame0 = kf[middleIndex - 1];
less = time < frame0.time;
greater = time > frame1.time;
startIndex = math.select(startIndex, middleIndex + 1, greater);
endIndex = math.select(endIndex, middleIndex, less);
}
if (less)
return kf[0].v;
if (greater)
return kf[^1].v;
float f = (time - frame0.time) / (frame1.time - frame0.time);
return EvaluateBezierCurve(frame0, frame1, f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float SampleAnimationCurveLinearSearch(in ReadOnlySpan<KeyFrame> kf, float time)
{
for (int i = 0; i < kf.Length; ++i)
{
var frame1 = kf[i];
if (frame1.time >= time)
{
if (i == 0)
return kf[i].v;
var frame0 = kf[i - 1];
float f = (time - frame0.time) / (frame1.time - frame0.time);
return EvaluateBezierCurve(frame0, frame1, f);
}
}
return kf[^1].v;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e1e6e7021f2c35f4c9c8257fbc6ae4f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/BlobCurve.cs
uploadId: 897522
@@ -0,0 +1,120 @@
using Unity.Mathematics;
using Unity.Transforms;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct BoneTransform
{
public float3 pos;
public quaternion rot;
public float3 scale;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public BoneTransform(LocalTransform lt)
{
pos = lt.Position;
rot = lt.Rotation;
scale = lt.Scale;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public BoneTransform(LocalTransform lt, in PostTransformMatrix ptm)
{
pos = lt.Position;
rot = lt.Rotation;
scale = lt.Scale * new float3(ptm.Value[0][0], ptm.Value[1][1], ptm.Value[2][2]);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BoneTransform Identity()
{
return new BoneTransform() { pos = 0, rot = quaternion.identity, scale = 1 };
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public float4x4 ToFloat4x4()
{
return float4x4.TRS(pos, rot, scale);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Multiply child with parent
public static BoneTransform Multiply(in BoneTransform parent, in BoneTransform child)
{
var rv = new BoneTransform();
rv.pos = math.mul(parent.rot, child.pos * parent.scale) + parent.pos;
rv.rot = math.mul(parent.rot, child.rot);
rv.scale = parent.scale * child.scale;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BoneTransform Inverse(in BoneTransform bt)
{
var rv = new BoneTransform();
rv.rot = math.inverse(bt.rot);
rv.scale = math.rcp(bt.scale);
rv.pos = math.mul(rv.rot, -bt.pos * rv.scale);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BoneTransform Scale(in BoneTransform bt, float3 scale)
{
var rv = new BoneTransform()
{
pos = bt.pos * scale.x,
rot = bt.rot.value * scale.y,
scale = bt.scale * scale.z,
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BoneTransform Add(in BoneTransform p0, in BoneTransform p1)
{
var rv = new BoneTransform()
{
pos = p0.pos + p1.pos,
rot = p0.rot.value + p1.rot.value,
scale = p0.scale + p1.scale
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BoneTransform Lerp(in BoneTransform bt0, in BoneTransform bt1, float f)
{
var rv = new BoneTransform()
{
pos = math.lerp(bt0.pos, bt1.pos, f),
rot = math.slerp(bt0.rot.value, bt1.rot.value, f),
scale = math.lerp(bt0.scale, bt1.scale, f)
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
public static float3 TransformPoint(BoneTransform bt, float3 v)
{
float3 rv = math.rotate(bt.rot, v * bt.scale) + bt.pos;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public LocalTransform ToLocalTransformComponent() => new LocalTransform() { Position = pos, Rotation = rot, Scale = scale.x };
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 4d9a73be1128e564fa48a1770a1149bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/BoneTransform.cs
uploadId: 897522
@@ -0,0 +1,65 @@
using Unity.Mathematics;
using UnityEngine;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public static class HumanStaticData
{
static readonly float bodyDefaultMass = 82.5f;
public static readonly float[] bodyPartsMasses =
{
12 / bodyDefaultMass,
10 / bodyDefaultMass,
10 / bodyDefaultMass,
4 / bodyDefaultMass,
4 / bodyDefaultMass,
0.8f / bodyDefaultMass,
0.8f / bodyDefaultMass,
2.5f / bodyDefaultMass,
12 / bodyDefaultMass,
12 / bodyDefaultMass,
1 / bodyDefaultMass,
4 / bodyDefaultMass,
0.5f / bodyDefaultMass,
0.5f / bodyDefaultMass,
2 / bodyDefaultMass,
2 / bodyDefaultMass,
1.5f / bodyDefaultMass,
1.5f / bodyDefaultMass,
0.5f / bodyDefaultMass,
0.5f / bodyDefaultMass,
0.2f / bodyDefaultMass,
0.2f / bodyDefaultMass,
};
public static readonly int4[] massIndicesTable =
{
new ((int)HumanBodyBones.LeftUpperLeg, (int)HumanBodyBones.RightUpperLeg, (int)HumanBodyBones.Spine, -1),
new ((int)HumanBodyBones.LeftUpperLeg, (int)HumanBodyBones.LeftLowerLeg, -1, -1),
new ((int)HumanBodyBones.RightUpperLeg, (int)HumanBodyBones.RightLowerLeg, -1, -1),
new ((int)HumanBodyBones.LeftLowerLeg, (int)HumanBodyBones.LeftFoot, -1, -1),
new ((int)HumanBodyBones.RightLowerLeg, (int)HumanBodyBones.RightFoot, -1, -1),
new ((int)HumanBodyBones.LeftFoot, -1, -1, -1),
new ((int)HumanBodyBones.RightFoot, -1, -1, -1),
new ((int)HumanBodyBones.Spine, (int)HumanBodyBones.Chest, -1, -1),
new ((int)HumanBodyBones.UpperChest, (int)HumanBodyBones.Chest, -1, -1),
new ((int)HumanBodyBones.UpperChest, (int)HumanBodyBones.Neck, (int)HumanBodyBones.LeftShoulder, (int)HumanBodyBones.RightShoulder),
new ((int)HumanBodyBones.Neck, (int)HumanBodyBones.Head, -1, 1),
new ((int)HumanBodyBones.Head, -1, -1, 1),
new ((int)HumanBodyBones.LeftShoulder, (int)HumanBodyBones.LeftUpperArm, -1, 1),
new ((int)HumanBodyBones.RightShoulder, (int)HumanBodyBones.RightUpperArm, -1, 1),
new ((int)HumanBodyBones.LeftLowerArm, (int)HumanBodyBones.LeftUpperArm, -1, 1),
new ((int)HumanBodyBones.RightLowerArm, (int)HumanBodyBones.RightUpperArm, -1, 1),
new ((int)HumanBodyBones.LeftLowerArm, (int)HumanBodyBones.LeftHand, -1, 1),
new ((int)HumanBodyBones.RightLowerArm, (int)HumanBodyBones.RightHand, -1, 1),
new ((int)HumanBodyBones.LeftHand, -1, -1, 1),
new ((int)HumanBodyBones.RightHand, -1, -1, 1),
new ((int)HumanBodyBones.LeftToes, -1, -1, 1),
new ((int)HumanBodyBones.RightToes, -1, -1, 1),
};
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f04734850528aef44aaaff3219c1f72e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/HumanStaticData.cs
uploadId: 897522
@@ -0,0 +1,106 @@
using System;
using System.Runtime.CompilerServices;
using Rukhanka.Toolbox;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct RuntimeAnimationData: IComponentData
{
internal NativeList<BoneTransform> animatedBonesBuffer;
internal NativeList<BoneTransform> worldSpaceBonesBuffer;
internal NativeList<int3> boneToEntityArr;
internal NativeList<ulong> boneTransformFlagsHolderArr;
/////////////////////////////////////////////////////////////////////////////////
public static RuntimeAnimationData MakeDefault()
{
var rv = new RuntimeAnimationData()
{
animatedBonesBuffer = new (Allocator.Persistent),
worldSpaceBonesBuffer = new (Allocator.Persistent),
boneToEntityArr = new (Allocator.Persistent),
boneTransformFlagsHolderArr = new (Allocator.Persistent),
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
animatedBonesBuffer.Dispose();
worldSpaceBonesBuffer.Dispose();
boneToEntityArr.Dispose();
boneTransformFlagsHolderArr.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<BoneTransform> GetAnimationDataForRigRO(in NativeList<BoneTransform> animatedBonesBuffer, int offset, int length)
{
var rv = animatedBonesBuffer.GetReadOnlySpan(offset, length);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<BoneTransform> GetAnimationDataForRigRW(in NativeList<BoneTransform> animatedBonesBuffer, int offset, int length)
{
var rv = animatedBonesBuffer.GetSpan(offset, length);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ReadOnlySpan<BoneTransform> GetAnimationDataForRigRO
(
in NativeList<BoneTransform> animatedBonesBuffer,
in RigDefinitionComponent rigDefinition
)
{
return GetAnimationDataForRigRO(animatedBonesBuffer, rigDefinition.dynamicFrameData.bonePoseOffset, rigDefinition.dynamicFrameData.rigBoneCount);
}
///////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<BoneTransform> GetAnimationDataForRigRW
(
in NativeList<BoneTransform> animatedBonesBuffer,
in RigDefinitionComponent rigDefinition
)
{
return GetAnimationDataForRigRW(animatedBonesBuffer, rigDefinition.dynamicFrameData.bonePoseOffset, rigDefinition.dynamicFrameData.rigBoneCount);
}
///////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AnimationTransformFlags GetAnimationTransformFlagsRO(in NativeList<int3> boneToEntityArr, in NativeList<ulong> boneTransformFlagsArr, int globalBoneIndex, int boneCount)
{
var boneInfo = boneToEntityArr[globalBoneIndex];
var rv = AnimationTransformFlags.CreateFromBufferRO(boneTransformFlagsArr, boneInfo.z, boneCount);
return rv;
}
///////////////////////////////////////////////////////////////////////////////////////////
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static AnimationTransformFlags GetAnimationTransformFlagsRW(in NativeList<int3> boneToEntityArr, in NativeList<ulong> boneTransformFlagsArr, int globalBoneIndex, int boneCount)
{
var boneInfo = boneToEntityArr[globalBoneIndex];
var rv = AnimationTransformFlags.CreateFromBufferRW(boneTransformFlagsArr, boneInfo.z, boneCount);
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 51c1d5baca6feef46ad93355850eb2e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/RuntimeAnimationDataSingleton.cs
uploadId: 897522
@@ -0,0 +1,58 @@
using Hash128 = Unity.Entities.Hash128;
using Unity.Mathematics;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct SkinnedMeshBoneInfo
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public float4x4 bindPose;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct BlendShapeInfo
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct SkinnedMeshInfoBlob: GenericAssetBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString skeletonName;
public string Name() => skeletonName.ToString();
public float bakingTime;
public float BakingTime() => bakingTime;
#endif
public Hash128 hash;
public Hash128 Hash() => hash;
public BlobArray<SkinnedMeshBoneInfo> bones;
public BlobArray<BlendShapeInfo> blendShapes;
// Each weight index is bone count for a vertex (lower 8 bits) and start bone weight data index (upper 24 bits)
// Bone weights index need to be used to index into Mesh.GetAllBoneWeights().
public BlobArray<uint> boneWeightsIndices;
public int meshBoneWeightsCount;
public int meshBlendShapesCount;
public int meshVerticesCount;
public static uint GetBoneWeightCount(uint packedBoneWeightsValue) => packedBoneWeightsValue & 0xff;
public static uint GetBoneWeightIndexOffset(uint packedBoneWeightsValue) => packedBoneWeightsValue >> 8;
public static uint PackBoneCountAndOffset(byte boneCount, uint offset)
{
var rv = boneCount | (offset << 8);
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f390f1fc5933c314e8a444549220162a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/AnimationEngine/SkinnedMeshBlob.cs
uploadId: 897522