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,8 @@
fileFormatVersion: 2
guid: 76a64ddea3967624a8916c6160d19ed8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
@@ -0,0 +1,27 @@
using Unity.Entities;
using Unity.Transforms;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateBefore(typeof(TransformSystemGroup))]
#if RUKHANKA_WITH_NETCODE
[UpdateAfter(typeof(PredictedSimulationSystemGroup))]
#else
[WorldSystemFilter(WorldSystemFilterFlags.LocalSimulation | WorldSystemFilterFlags.ClientSimulation)]
#endif
public partial class RukhankaAnimationSystemGroup: ComponentSystemGroup { }
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if RUKHANKA_WITH_NETCODE
[UpdateInGroup(typeof(PredictedSimulationSystemGroup))]
public partial class RukhankaPredictedAnimationSystemGroup: ComponentSystemGroup { }
#endif
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 27dcde57ce4d3c242be17a4d0ff56e71
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/AnimationSystemGroup.cs
uploadId: 897522
@@ -0,0 +1,52 @@
using Unity.Collections;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public static class AnimationUtils
{
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;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BlobAssetReference<BoneRemapTableBlob> MakeSkinnedMeshToRigRemapTable(in SkinnedMeshRendererComponent sm, in RigDefinitionComponent rigDef, Allocator allocator)
{
var bb = new BlobBuilder(Allocator.Temp);
ref var brt = ref bb.ConstructRoot<BoneRemapTableBlob>();
var bba = bb.Allocate(ref brt.remapIndices, sm.smrInfoBlob.Value.bones.Length);
for (int i = 0; i < bba.Length; ++i)
{
bba[i] = -1;
ref var rb = ref sm.smrInfoBlob.Value.bones[i];
var rbHash = rb.hash;
for (int j = 0; j < rigDef.rigBlob.Value.bones.Length; ++j)
{
ref var bn = ref rigDef.rigBlob.Value.bones[j];
var bnHash = bn.hash;
if (bnHash == rbHash)
{
bba[i] = j;
}
}
}
var rv = bb.CreateBlobAssetReference<BoneRemapTableBlob>(allocator);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static bool IsGPUAnimator(Entity animatedRigEntity, in ComponentLookup<GPUAnimationEngineTag> gpuAnimationEngineTagLookup)
{
return animatedRigEntity != Entity.Null && gpuAnimationEngineTagLookup.IsComponentEnabled(animatedRigEntity);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c733bc1447e67644bb4bb6324086e012
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/AnimationUtils.cs
uploadId: 897522
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c2eaa811ea5636741a61036f9d5d7dc8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,217 @@
using Unity.Entities;
using Unity.Mathematics;
using System.Runtime.InteropServices;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct ControllerBlob: 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 BlobArray<LayerBlob> layers;
public BlobArray<ParameterBlob> parameters;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum ControllerParameterType
{
Int,
Float,
Bool,
Trigger
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[StructLayout(LayoutKind.Explicit)]
public struct ParameterValue
{
[FieldOffset(0)]
public float floatValue;
[FieldOffset(0)]
public int intValue;
[FieldOffset(0)]
public bool boolValue;
public static implicit operator ParameterValue(float f) => new ParameterValue() { floatValue = f };
public static implicit operator ParameterValue(int i) => new ParameterValue() { intValue = i };
public static implicit operator ParameterValue(bool b) => new ParameterValue() { boolValue = b };
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ParameterBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public ParameterValue defaultValue;
public ControllerParameterType type;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum AnimationBlendingMode
{
Override = 0,
Additive = 1
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public enum AnimatorConditionMode
{
If = 1,
IfNot = 2,
Greater = 3,
Less = 4,
Equals = 6,
NotEqual = 7
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct LayerBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public int defaultStateIndex;
public float initialWeight;
public int syncedLayerIndex;
// syncedTiming acts as a bool (v != 0) for layers that have syncedLayerIndex >= 0. Base layer state speed also
// need to be adjusted, so syncedTiming acts as index to synced layer for weight lerp computation
public int syncedTiming;
public AnimationBlendingMode blendingMode;
public BlobArray<StateBlob> states;
public Hash128 avatarMaskBlobHash;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct TransitionBlob
{
// Just a copy of UnityEditor.Animations.TransitionInterruptionSource
// I cannot use original enum because it is in editor assembly
public enum InterruptionSource
{
None,
Source,
Destination,
SourceThenDestination,
DestinationThenSource,
}
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public BlobArray<ConditionBlob> conditions;
public int targetStateId;
public float duration;
public float exitTime;
public float offset;
public bool hasExitTime;
public bool hasFixedDuration;
public InterruptionSource interruptionSource;
public bool orderedInterruption;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ConditionBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public int paramIdx;
public ParameterValue threshold;
public AnimatorConditionMode conditionMode;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct MotionBlob
{
public enum Type
{
None,
AnimationClip,
BlendTree1D,
BlendTree2DSimpleDirectional,
BlendTree2DFreeformDirectional,
BlendTree2DFreeformCartesian,
BlendTreeDirect
}
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public uint hash;
public Type type;
public int animationIndex;
public BlendTreeBlob blendTree;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ChildMotionBlob
{
public MotionBlob motion;
public float threshold;
public float timeScale;
public bool mirror;
public float2 position2D;
public int directBlendParameterIndex;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct BlendTreeBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
#endif
public int blendParameterIndex;
public int blendParameterYIndex;
public bool normalizeBlendValues;
public BlobArray<ChildMotionBlob> motions;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct StateBlob
{
#if RUKHANKA_DEBUG_INFO
public BlobString name;
public BlobString tag;
#endif
public uint hash;
public uint tagHash;
public float speed;
public int speedMultiplierParameterIndex;
public int timeParameterIndex;
public float cycleOffset;
public int cycleOffsetParameterIndex;
public BlobArray<TransitionBlob> transitions;
public MotionBlob motion;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct ControllerAnimationsBlob
{
public BlobArray<Hash128> animations;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: bb7e72e901a18aa4ea9c4806acac4167
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/AnimatorController/AnimatorControllerBlob.cs
uploadId: 897522
@@ -0,0 +1,111 @@
using Rukhanka;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
using UnityEngine;
using static Rukhanka.AnimatorControllerSystemJobs;
/////////////////////////////////////////////////////////////////////////////////////////////////////
[assembly: RegisterGenericSystemType(typeof(AnimatorControllerSystem<PredictedAnimatorControllerQuery>))]
[assembly: RegisterGenericSystemType(typeof(AnimatorControllerSystem<AnimatorControllerQuery>))]
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
public partial struct AnimatorControllerSystem<T>: ISystem where T: AnimatorControllerQueryCreator, new()
{
EntityQuery animatorControllerQuery;
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
var queryCreator = new T();
animatorControllerQuery = queryCreator.CreateQuery(ref ss);
ss.RequireForUpdate(animatorControllerQuery);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var dt = SystemAPI.Time.DeltaTime;
var controllerLayersBufferHandle = SystemAPI.GetBufferTypeHandle<AnimatorControllerLayerComponent>();
var controllerParametersBufferHandle = SystemAPI.GetBufferTypeHandle<AnimatorControllerParameterComponent>();
var animatorOverrideAnimationsLookup = SystemAPI.GetComponentLookup<AnimatorOverrideAnimations>(true);
var entityTypeHandle = SystemAPI.GetEntityTypeHandle();
var controllerEventsBufferLookup = SystemAPI.GetBufferLookup<AnimatorControllerEventComponent>();
var animDBSingleton = SystemAPI.GetSingleton<BlobDatabaseSingleton>();
var internalDataSingletonQuery = SystemAPI.QueryBuilder().WithAllRW<InternalAnimatorDataSingleton>().Build();
var internalAnimatorData = FillAnimationsFromControllerSystem.GetInternalDataSingleton(internalDataSingletonQuery, ref ss);
var stateMachineProcessJob = new StateMachineProcessJob()
{
controllerLayersBufferHandle = controllerLayersBufferHandle,
controllerParametersBufferHandle = controllerParametersBufferHandle,
dt = dt,
entityTypeHandle = entityTypeHandle,
controllerEventsBufferLookup = controllerEventsBufferLookup,
animationDatabase = animDBSingleton.animations,
animatorOverrideAnimationLookup = animatorOverrideAnimationsLookup,
animatorOverrideAnimationsMap = internalAnimatorData.animatorOverrideAnimationsMap.AsParallelWriter()
};
ss.Dependency = stateMachineProcessJob.ScheduleParallel(animatorControllerQuery, ss.Dependency);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public interface AnimatorControllerQueryCreator
{
EntityQuery CreateQuery(ref SystemState ss);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct PredictedAnimatorControllerQuery: AnimatorControllerQueryCreator
{
public EntityQuery CreateQuery(ref SystemState ss)
{
var eqBuilder0 = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithAll<Simulate, PredictedGhost>()
#endif
;
var animatorControllerQuery = ss.GetEntityQuery(eqBuilder0);
return animatorControllerQuery;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerQuery: AnimatorControllerQueryCreator
{
public EntityQuery CreateQuery(ref SystemState ss)
{
var eqBuilder0 = new EntityQueryBuilder(Allocator.Temp)
.WithAllRW<AnimatorControllerLayerComponent>()
#if RUKHANKA_WITH_NETCODE
.WithNone<GhostInstance>()
#endif
;
var animatorControllerQuery = ss.GetEntityQuery(eqBuilder0);
return animatorControllerQuery;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: ecdf4d0de0c58ed4489dca4b8c9ab490
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/AnimatorController/AnimatorControllerSystem.cs
uploadId: 897522
@@ -0,0 +1,89 @@
using Unity.Collections;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimatorControllerLayerComponent: IBufferElementData, IEnableableComponent
{
public BlobAssetReference<ControllerBlob> controller;
public BlobAssetReference<ControllerAnimationsBlob> animations;
public int layerIndex;
public float weight;
public float speed;
public RuntimeAnimatorData rtd;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerParameterComponent: IBufferElementData
{
#if RUKHANKA_DEBUG_INFO
public FixedString64Bytes name;
#endif
public uint hash;
public ControllerParameterType type;
public ParameterValue value;
public float FloatValue
{
get => value.floatValue;
set => this.value.floatValue = value;
}
public int IntValue
{
get => value.intValue;
set => this.value.intValue= value;
}
public bool BoolValue
{
get => value.boolValue;
set => this.value.boolValue = value;
}
public void SetTrigger()
{
value.boolValue = true;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerParameterIndexTableComponent: IComponentData
{
public BlobAssetReference<PerfectHashTableBlob> value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorControllerEventComponent : IBufferElementData, IEnableableComponent
{
public enum EventType
{
StateEnter,
StateExit,
StateUpdate
}
public EventType eventType;
public int layerId;
public int stateId;
public float timeInState;
#if RUKHANKA_DEBUG_INFO
public FixedString32Bytes stateName;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public struct AnimatorOverrideAnimations: IComponentData, IEnableableComponent
{
public BlobAssetReference<ControllerAnimationsBlob> value;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1026c97737e562f4c816544af6ac420f
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/AnimatorController/AnimatorControllerSystemComponents.cs
uploadId: 897522
@@ -0,0 +1,740 @@
using System.Runtime.CompilerServices;
using Rukhanka.Toolbox;
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;
using Hash128 = Unity.Entities.Hash128;
[assembly: InternalsVisibleTo("Rukhanka.Tests")]
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimatorControllerSystemJobs
{
[BurstCompile]
public struct StateMachineProcessJob: IJobChunk
{
public float dt;
public BufferTypeHandle<AnimatorControllerLayerComponent> controllerLayersBufferHandle;
public BufferTypeHandle<AnimatorControllerParameterComponent> controllerParametersBufferHandle;
public EntityTypeHandle entityTypeHandle;
[NativeDisableParallelForRestriction]
public BufferLookup<AnimatorControllerEventComponent> controllerEventsBufferLookup;
[ReadOnly]
public NativeHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> animationDatabase;
[ReadOnly]
public ComponentLookup<AnimatorOverrideAnimations> animatorOverrideAnimationLookup;
public NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>>.ParallelWriter animatorOverrideAnimationsMap;
BlobAssetReference<ControllerAnimationsBlob> controllerAnimationsBlob;
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var layerBuffers = chunk.GetBufferAccessor(ref controllerLayersBufferHandle);
var parameterBuffers = chunk.GetBufferAccessor(ref controllerParametersBufferHandle);
var entities = chunk.GetNativeArray(entityTypeHandle);
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
while (cee.NextEntityIndex(out var i))
{
var layers = layerBuffers[i];
var parameters = parameterBuffers.Length > 0 ? parameterBuffers[i].AsNativeArray() : default;
var e = entities[i];
DynamicBuffer<AnimatorControllerEventComponent> controllerEventsBuffer = default;
if (controllerEventsBufferLookup.HasBuffer(e) && controllerEventsBufferLookup.IsBufferEnabled(e))
controllerEventsBuffer = controllerEventsBufferLookup[e];
ExecuteSingle(layers, parameters, ref controllerEventsBuffer, e);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void ExecuteSingle
(
in DynamicBuffer<AnimatorControllerLayerComponent> aclc,
in NativeArray<AnimatorControllerParameterComponent> acpc,
ref DynamicBuffer<AnimatorControllerEventComponent> events,
Entity entity
)
{
if (events.IsCreated)
events.Clear();
var numIntsForBitMemory = BitFieldN.CalculateUIntsCountForGivenBitCount(acpc.Length);
var triggersToResetMem = stackalloc uint[numIntsForBitMemory];
var triggersToReset = new BitFieldN(triggersToResetMem, numIntsForBitMemory);
var startIndex = 0;
for (int i = 0; i < aclc.Length; ++i)
{
ref var acc = ref aclc.ElementAt(i);
// Save controller animations blob asset reference in class variable, because passing it inside almost all functions will bloat signatures significantly
controllerAnimationsBlob = FillAnimationsFromControllerSystem.GetControllerAnimationsBlob
(entity, animatorOverrideAnimationLookup, acc.animations, animatorOverrideAnimationsMap);
ProcessLayer(ref acc.controller.Value, acc.layerIndex, acpc, aclc, ref events, triggersToReset);
if (events.IsCreated)
{
EmitStateUpdateEvents(ref events, acc, startIndex);
startIndex = events.Length;
}
}
// Reset affected triggers
ResetTriggers(acpc, triggersToReset);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void ResetTriggers(NativeArray<AnimatorControllerParameterComponent> acpc, BitFieldN triggersToReset)
{
if (!triggersToReset.TestAny())
return;
for (var i = 0; i < acpc.Length; ++i)
{
var p = acpc[i];
if (p.type == ControllerParameterType.Trigger && triggersToReset.IsSet(i))
{
p.BoolValue = false;
acpc[i] = p;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
RuntimeAnimatorData.StateData InitControllerStateData(ref RuntimeAnimatorData rtd, int stateID)
{
var rv = rtd.MakeDefaultState();
rv.id = stateID;
rv.normalizedDuration = 0;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void TryExitTransition(ref AnimatorControllerLayerComponent acc, ref DynamicBuffer<AnimatorControllerEventComponent> events)
{
if (acc.rtd.activeTransition.id < 0)
return;
if (CheckTransitionExitConditions(acc.rtd.activeTransition))
{
// Add state exit event
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateExit, acc, acc.rtd.srcState.id, acc.rtd.srcState.normalizedDuration);
acc.rtd.srcState = acc.rtd.dstState;
acc.rtd.ClearStateSnapshots();
acc.rtd.dstState = acc.rtd.MakeDefaultState();
acc.rtd.activeTransition = acc.rtd.MakeDefaultTransition();
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
int GetValidTransitionForCurrentFrame
(
ref BlobArray<TransitionBlob> transitions,
float normalizedStateDuration,
float srcStateDurationFrameDelta,
NativeArray<AnimatorControllerParameterComponent> runtimeParams,
BitFieldN triggersToReset
)
{
var isTransitionFits = false;
int i = 0;
for (; i < transitions.Length && !isTransitionFits; ++i)
{
ref var t = ref transitions[i];
isTransitionFits =
CheckTransitionEnterExitTimeCondition(ref t, normalizedStateDuration, srcStateDurationFrameDelta) &&
CheckTransitionEnterConditions(ref t, runtimeParams, triggersToReset);
}
return isTransitionFits ? i - 1 : -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void TryEnterTransition
(
in DynamicBuffer<AnimatorControllerLayerComponent> aclc,
ref ControllerBlob controllerBlob,
int layerIndex,
NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float srcStateDurationFrameDelta,
float curStateDuration,
ref DynamicBuffer<AnimatorControllerEventComponent> events,
BitFieldN triggersToReset
)
{
ref var acc = ref aclc.ElementAt(layerIndex);
ref var layer = ref controllerBlob.layers[layerIndex];
ref var currentState = ref layer.states[acc.rtd.srcState.id];
if (acc.rtd.activeTransition.id >= 0)
{
TryTransitionInterruption(ref layer, ref acc.rtd, srcStateDurationFrameDelta, runtimeParams, triggersToReset);
return;
}
var newTransitionIndex = GetValidTransitionForCurrentFrame(ref currentState.transitions, acc.rtd.srcState.normalizedDuration, srcStateDurationFrameDelta, runtimeParams, triggersToReset);
if (newTransitionIndex < 0)
return;
ref var t = ref currentState.transitions[newTransitionIndex];
var timeShouldBeInTransition = GetTimeInSecondsShouldBeInTransition(ref t, acc.rtd.srcState.normalizedDuration, curStateDuration, srcStateDurationFrameDelta);
acc.rtd.activeTransition.id = newTransitionIndex;
acc.rtd.activeTransition.length = GetTransitionLength(ref t);
acc.rtd.activeTransition.normalizedDuration = timeShouldBeInTransition / CalculateTransitionDuration(acc.rtd.activeTransition, curStateDuration);
var dstStateDur = CalculateStateDuration(layerIndex, t.targetStateId, ref controllerBlob, aclc.AsNativeArray(), runtimeParams);
acc.rtd.dstState = InitControllerStateData(ref acc.rtd, t.targetStateId);
acc.rtd.dstState.normalizedDuration += timeShouldBeInTransition / dstStateDur + t.offset;
// Add state enter event
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateEnter, acc, acc.rtd.dstState.id, acc.rtd.dstState.normalizedDuration);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void EmitEvent
(
ref DynamicBuffer<AnimatorControllerEventComponent> events,
AnimatorControllerEventComponent.EventType eventType,
AnimatorControllerLayerComponent aclc,
int stateId,
float stateDuration
)
{
if (!events.IsCreated)
return;
var evt = new AnimatorControllerEventComponent()
{
eventType = eventType,
stateId = stateId,
layerId = aclc.layerIndex,
timeInState = stateDuration,
};
#if RUKHANKA_DEBUG_INFO
aclc.controller.Value.layers[aclc.layerIndex].states[stateId].name.CopyToWithTruncate(ref evt.stateName);
#endif
events.Add(evt);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void EmitStateUpdateEvents(ref DynamicBuffer<AnimatorControllerEventComponent> events, in AnimatorControllerLayerComponent acc, int startIndex)
{
if (!events.IsCreated)
return;
var srcStateEnterExit = false;
var dstStateEnterExit = false;
for (var i = startIndex; i < events.Length; ++i)
{
var e = events[i];
if (acc.rtd.srcState.id >= 0 && e.stateId == acc.rtd.srcState.id)
srcStateEnterExit = true;
if (acc.rtd.dstState.id >= 0 && e.stateId == acc.rtd.dstState.id)
dstStateEnterExit = true;
}
if (acc.rtd.srcState.id >= 0 && !srcStateEnterExit)
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateUpdate, acc, acc.rtd.srcState.id, acc.rtd.srcState.normalizedDuration);
if (acc.rtd.dstState.id >= 0 && !dstStateEnterExit)
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateUpdate, acc, acc.rtd.dstState.id, acc.rtd.dstState.normalizedDuration);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void ProcessLayer
(
ref ControllerBlob c,
int layerIndex,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
DynamicBuffer<AnimatorControllerLayerComponent> aclc,
ref DynamicBuffer<AnimatorControllerEventComponent> events,
BitFieldN triggersToReset
)
{
ref var layer = ref c.layers[layerIndex];
ref var acc = ref aclc.ElementAt(layerIndex);
var currentStateID = acc.rtd.srcState.id;
if (currentStateID < 0)
currentStateID = layer.defaultStateIndex;
// Adjust delta time according to the layer animator speed
var layerDeltaTime = dt * acc.speed;
var curStateDuration = CalculateStateDuration(layerIndex, currentStateID, ref c, aclc.AsNativeArray(), runtimeParams);
if (Hint.Unlikely(acc.rtd.srcState.id < 0))
{
acc.rtd.srcState = InitControllerStateData(ref acc.rtd, layer.defaultStateIndex);
EmitEvent(ref events, AnimatorControllerEventComponent.EventType.StateEnter, acc, acc.rtd.srcState.id, acc.rtd.srcState.normalizedDuration);
}
var srcStateDurationFrameDelta = CalculateStateFrameDeltaSafe(layerDeltaTime, curStateDuration);
acc.rtd.srcState.normalizedDuration += srcStateDurationFrameDelta;
if (acc.rtd.dstState.id >= 0)
{
var dstStateDuration = CalculateStateDuration(layerIndex, acc.rtd.dstState.id, ref c, aclc.AsNativeArray(), runtimeParams);
acc.rtd.dstState.normalizedDuration += CalculateStateFrameDeltaSafe(layerDeltaTime, dstStateDuration);
}
if (acc.rtd.activeTransition.id >= 0)
{
var transitionDuration = CalculateTransitionDuration(acc.rtd.activeTransition, curStateDuration);
acc.rtd.activeTransition.normalizedDuration += layerDeltaTime / transitionDuration;
}
TryExitTransition(ref acc, ref events);
TryEnterTransition(aclc, ref c, layerIndex, runtimeParams, srcStateDurationFrameDelta, curStateDuration, ref events, triggersToReset);
// Check transition exit conditions one more time in case of Enter->Exit sequence appeared in single frame
TryExitTransition(ref acc, ref events);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateStateFrameDeltaSafe(float dt, float stateDuration)
{
var rv = dt / stateDuration;
rv = math.select(0, rv, math.isfinite(rv));
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateMotionDuration
(
ref MotionBlob mb,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float weight
)
{
if (weight == 0) return 0;
switch (mb.type)
{
case MotionBlob.Type.None:
return 1;
case MotionBlob.Type.AnimationClip:
var animationHash = controllerAnimationsBlob.Value.animations[mb.animationIndex];
var animBlob = BlobDatabaseSingleton.GetBlobAsset(animationHash, animationDatabase);
if (animBlob != BlobAssetReference<AnimationClipBlob>.Null)
return animBlob.Value.length * weight;
return 1;
}
var childMotions = ScriptedAnimator.GetChildMotionsList(ref mb, runtimeParams);
var rv = CalculateBlendTreeMotionDuration(childMotions, ref mb.blendTree.motions, runtimeParams, weight);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateBlendTreeMotionDuration
(
NativeList<ScriptedAnimator.MotionIndexAndWeight> miwArr,
ref BlobArray<ChildMotionBlob> motions,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float weight
)
{
if (!miwArr.IsCreated || miwArr.IsEmpty)
return 1;
var weightSum = 0.0f;
for (int i = 0; i < miwArr.Length; ++i)
weightSum += miwArr[i].weight;
// If total weight less then 1, normalize weights
if (Hint.Unlikely(weightSum < 1))
{
for (int i = 0; i < miwArr.Length; ++i)
{
var miw = miwArr[i];
miw.weight = miw.weight / weightSum;
miwArr[i] = miw;
}
}
var rv = 0.0f;
for (int i = 0; i < miwArr.Length; ++i)
{
var miw = miwArr[i];
ref var m = ref motions[miw.motionIndex];
rv += CalculateMotionDuration(ref m.motion, runtimeParams, weight * miw.weight) / m.timeScale;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Negative value means that length is a normalized value from source state length
float GetTransitionLength(ref TransitionBlob tb)
{
return math.select(-tb.duration, tb.duration, tb.hasFixedDuration);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateTransitionDuration(in RuntimeAnimatorData.TransitionData trd, float curStateDuration)
{
var rv = math.abs(trd.length);
if (trd.length < 0)
{
rv *= curStateDuration;
}
return math.max(rv, 0.0001f);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float CalculateStateDuration
(
int layerIndex,
int stateId,
ref ControllerBlob controllerBlob,
in NativeArray<AnimatorControllerLayerComponent> aclc,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams
)
{
ref var layer = ref controllerBlob.layers[layerIndex];
ref var sb = ref layer.states[stateId];
var motionDuration = CalculateMotionDuration(ref sb.motion, runtimeParams, 1);
// In case of layer sync option is enabled, adjust state duration with respect to "timing" property
if (Hint.Unlikely(layer.syncedLayerIndex >= 0))
{
// Override controller must be exact copy of current
ref var baseState = ref controllerBlob.layers[layer.syncedLayerIndex].states[stateId];
var baseMotionDuration = CalculateMotionDuration(ref baseState.motion, runtimeParams, 1);
var weightedMotionDuration = math.lerp(baseMotionDuration, motionDuration, aclc[layerIndex].weight);
motionDuration = math.select(baseMotionDuration, weightedMotionDuration, layer.syncedTiming > 0);
}
else if (Hint.Unlikely(layer.syncedTiming >= 0))
{
ref var syncedState = ref controllerBlob.layers[layer.syncedTiming].states[stateId];
var syncedStateDuration = CalculateMotionDuration(ref syncedState.motion, runtimeParams, 1);
motionDuration = math.lerp(motionDuration, syncedStateDuration, aclc[layer.syncedTiming].weight);
}
var speedMultiplier = 1.0f;
if (sb.speedMultiplierParameterIndex >= 0)
{
speedMultiplier = runtimeParams[sb.speedMultiplierParameterIndex].FloatValue;
}
var rv = motionDuration / (sb.speed * speedMultiplier);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
internal static float GetLoopAwareTransitionExitTime(float exitTime, float normalizedDuration, float speedSign)
{
var rv = exitTime;
if (exitTime <= 1.0f)
{
// Unity animator logic and documentation mismatch. Documentation says that exit time loop condition should be when transition exitTime less then 1, but in practice it will loop when exitTime is less or equal(!) to 1.
exitTime = math.min(exitTime, 0.9999f);
var snd = normalizedDuration * speedSign;
var f = math.frac(snd);
rv += (int)snd;
if (f > exitTime)
rv += 1;
}
return rv * speedSign;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float GetTimeInSecondsShouldBeInTransition(ref TransitionBlob tb, float normalizedStateDuration, float curStateDuration, float frameDT)
{
if (!tb.hasExitTime) return 0;
// This should be always less then curStateRTD.normalizedDuration
var loopAwareExitTime = GetLoopAwareTransitionExitTime(tb.exitTime, normalizedStateDuration - frameDT, math.sign(frameDT));
var loopDelta = normalizedStateDuration - loopAwareExitTime;
var rv = loopDelta * curStateDuration;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckTransitionEnterExitTimeCondition(ref TransitionBlob tb, float normalizedStateDuration, float srcStateDurationFrameDelta)
{
var noNormalConditions = tb.conditions.Length == 0;
if (!tb.hasExitTime) return !noNormalConditions;
var l0 = normalizedStateDuration - srcStateDurationFrameDelta;
var l1 = normalizedStateDuration;
var speedSign = math.select(-1, 1, l0 < l1);
var loopAwareExitTime = GetLoopAwareTransitionExitTime(tb.exitTime, l0, speedSign);
if (speedSign < 0)
(l0, l1) = (l1, l0);
var rv = loopAwareExitTime > l0 && loopAwareExitTime <= l1;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckIntCondition(in AnimatorControllerParameterComponent param, ref ConditionBlob c)
{
var rv = true;
switch (c.conditionMode)
{
case AnimatorConditionMode.Equals:
if (param.IntValue != c.threshold.intValue) rv = false;
break;
case AnimatorConditionMode.Greater:
if (param.IntValue <= c.threshold.intValue) rv = false;
break;
case AnimatorConditionMode.Less:
if (param.IntValue >= c.threshold.intValue) rv = false;
break;
case AnimatorConditionMode.NotEqual:
if (param.IntValue == c.threshold.intValue) rv = false;
break;
default:
Debug.LogError($"Unsupported condition type for int parameter value!");
break;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckFloatCondition(in AnimatorControllerParameterComponent param, ref ConditionBlob c)
{
var rv = true;
switch (c.conditionMode)
{
case AnimatorConditionMode.Greater:
if (param.FloatValue <= c.threshold.floatValue) rv = false;
break;
case AnimatorConditionMode.Less:
if (param.FloatValue >= c.threshold.floatValue) rv = false;
break;
default:
Debug.LogError($"Unsupported condition type for int parameter value!");
break;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckBoolCondition(in AnimatorControllerParameterComponent param, ref ConditionBlob c)
{
var rv = true;
switch (c.conditionMode)
{
case AnimatorConditionMode.If:
rv = param.BoolValue;
break;
case AnimatorConditionMode.IfNot:
rv = !param.BoolValue;
break;
default:
Debug.LogError($"Unsupported condition type for int parameter value!");
break;
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void MarkTriggersToReset(ref TransitionBlob tb, BitFieldN triggersToReset)
{
for (int i = 0; i < tb.conditions.Length; ++i)
{
ref var c = ref tb.conditions[i];
// Mark all transition parameters as "need to be reset". We will check actual parameter type later, after all layers processing
triggersToReset.Set(c.paramIdx, true);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckTransitionEnterConditions(ref TransitionBlob tb, NativeArray<AnimatorControllerParameterComponent> runtimeParams, BitFieldN triggersToReset)
{
if (tb.conditions.Length == 0)
return true;
var rv = true;
var hasTriggers = false;
for (int i = 0; i < tb.conditions.Length && rv; ++i)
{
ref var c = ref tb.conditions[i];
var param = runtimeParams[c.paramIdx];
switch (param.type)
{
case ControllerParameterType.Float:
rv = CheckFloatCondition(param, ref c);
break;
case ControllerParameterType.Int:
rv = CheckIntCondition(param, ref c);
break;
case ControllerParameterType.Bool:
rv = CheckBoolCondition(param, ref c);
break;
case ControllerParameterType.Trigger:
rv = CheckBoolCondition(param, ref c);
hasTriggers = true;
break;
}
}
if (hasTriggers && rv)
MarkTriggersToReset(ref tb, triggersToReset);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
bool CheckTransitionExitConditions(RuntimeAnimatorData.TransitionData transitionRuntimeData)
{
return transitionRuntimeData.normalizedDuration >= 1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void TryTransitionInterruption
(
ref LayerBlob layerBlob,
ref RuntimeAnimatorData rtd,
float srcStateDurationFrameDelta,
NativeArray<AnimatorControllerParameterComponent> runtimeParams,
BitFieldN triggersToReset
)
{
ref var srcStateTransitions = ref layerBlob.states[rtd.srcState.id].transitions;
var transitionIndex = rtd.activeTransition.id;
// Transition index could be greater than number of actual transitions when 'ScriptedAnimator.CrossFade' is used
if (transitionIndex >= srcStateTransitions.Length)
return;
// We are in transition right now. Perform transition interruption logic
ref var activeTransition = ref srcStateTransitions[transitionIndex];
// Can't interrupt, return
if (Hint.Likely(activeTransition.interruptionSource == TransitionBlob.InterruptionSource.None))
return;
// Make copy of triggers to reset flags because I don't know if there are valid interruption transitions yet
var triggersToResetMem = stackalloc uint[triggersToReset.SizeInInts()];
var triggersClone = triggersToReset.Clone(triggersToResetMem);
var transitionIndexCandidateSrc = -1;
var transitionIndexCandidateDst = -1;
var canBeInterrupted = false;
if
(
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.Source ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.SourceThenDestination ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.DestinationThenSource
)
{
transitionIndexCandidateSrc = GetValidTransitionForCurrentFrame(ref srcStateTransitions, rtd.srcState.normalizedDuration, srcStateDurationFrameDelta, runtimeParams, triggersClone);
canBeInterrupted |=
transitionIndexCandidateSrc >= 0 &&
(activeTransition.orderedInterruption ? transitionIndexCandidateSrc < transitionIndex : transitionIndexCandidateSrc != transitionIndex);
}
ref var dstStateTransitions = ref layerBlob.states[rtd.dstState.id].transitions;
if
(
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.Destination ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.SourceThenDestination ||
activeTransition.interruptionSource == TransitionBlob.InterruptionSource.DestinationThenSource
)
{
transitionIndexCandidateDst = GetValidTransitionForCurrentFrame(ref dstStateTransitions, rtd.dstState.normalizedDuration, srcStateDurationFrameDelta, runtimeParams, triggersClone);
canBeInterrupted |= transitionIndexCandidateDst >= 0;
}
if (!canBeInterrupted)
return;
// There is valid interruption
// Select interrupting transition
var interruptingTransitionID = -1;
TransitionBlob* transitionBlobsPtr = null;
switch (activeTransition.interruptionSource)
{
case TransitionBlob.InterruptionSource.Source:
interruptingTransitionID = transitionIndexCandidateSrc;
transitionBlobsPtr = (TransitionBlob*)srcStateTransitions.GetUnsafePtr();
break;
case TransitionBlob.InterruptionSource.Destination:
interruptingTransitionID = transitionIndexCandidateDst;
transitionBlobsPtr = (TransitionBlob*)dstStateTransitions.GetUnsafePtr();
break;
case TransitionBlob.InterruptionSource.SourceThenDestination:
interruptingTransitionID = math.select(transitionIndexCandidateDst, transitionIndexCandidateSrc, transitionIndexCandidateSrc >= 0);
transitionBlobsPtr = transitionIndexCandidateSrc >= 0 ? (TransitionBlob*)srcStateTransitions.GetUnsafePtr() : (TransitionBlob*)dstStateTransitions.GetUnsafePtr();
break;
case TransitionBlob.InterruptionSource.DestinationThenSource:
interruptingTransitionID = math.select(transitionIndexCandidateSrc, transitionIndexCandidateDst, transitionIndexCandidateDst >= 0);
transitionBlobsPtr = transitionIndexCandidateDst >= 0 ? (TransitionBlob*)dstStateTransitions.GetUnsafePtr() : (TransitionBlob*)srcStateTransitions.GetUnsafePtr();
break;
default:
// This should not happen
BurstAssert.IsTrue(false, "Transition interruption invalid code path");
return;
}
ref var newTransition = ref transitionBlobsPtr[interruptingTransitionID];
// Copy triggers back to original bitset
triggersToReset.CopyFrom(triggersClone);
// If state snapshot array is empty, then push both destination state and source state
// If there are some state snapshots in array, the these snapshots are our "source state" already, so we need to add only destination state
if (rtd.srcStateSnapshots.IsEmpty)
rtd.PushStateSnapshot(rtd.srcState.id, 1, rtd.srcState.normalizedDuration, rtd.srcState.motionId);
rtd.PushStateSnapshot(rtd.dstState.id, rtd.activeTransition.normalizedDuration, rtd.dstState.normalizedDuration, rtd.dstState.motionId);
rtd.srcState.normalizedDuration = 0;
// If interrupting transition belongs to next state, configure srcState accordingly
if (transitionBlobsPtr == dstStateTransitions.GetUnsafePtr())
rtd.srcState.id = rtd.dstState.id;
rtd.activeTransition.id = interruptingTransitionID;
rtd.activeTransition.length = GetTransitionLength(ref newTransition);
rtd.activeTransition.normalizedDuration = 0;
rtd.dstState = InitControllerStateData(ref rtd, newTransition.targetStateId);
rtd.dstState.normalizedDuration = newTransition.offset;
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 66f7113131a9adf41bd19f34c3f9434c
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/AnimatorController/AnimatorControllerSystem_Job.cs
uploadId: 897522
@@ -0,0 +1,182 @@
using Unity.Entities;
using FixedStringName = Unity.Collections.FixedString512Bytes;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct AnimatorParametersAspect
{
public AnimatorControllerParameterIndexTableComponent indexTable;
public DynamicBuffer<AnimatorControllerParameterComponent> parametersArr;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public AnimatorParametersAspect(DynamicBuffer<AnimatorControllerParameterComponent> parametersArr, AnimatorControllerParameterIndexTableComponent indexTable)
{
this.parametersArr = parametersArr;
this.indexTable = indexTable;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public float GetFloatParameter(FastAnimatorParameter fp) => GetParameterValue(fp).floatValue;
public int GetIntParameter(FastAnimatorParameter fp) => GetParameterValue(fp).intValue;
public bool GetBoolParameter(FastAnimatorParameter fp) => GetParameterValue(fp).boolValue;
public float GetFloatParameter(uint h) => GetParameterValue(h).floatValue;
public int GetIntParameter(uint h) => GetParameterValue(h).intValue;
public bool GetBoolParameter(uint h) => GetParameterValue(h).boolValue;
public float GetFloatParameter(FixedStringName n) => GetParameterValue(n).floatValue;
public int GetIntParameter(FixedStringName n) => GetParameterValue(n).intValue;
public bool GetBoolParameter(FixedStringName n) => GetParameterValue(n).boolValue;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValue(FastAnimatorParameter fp)
{
ParameterValue rv;
if (indexTable.value.IsCreated)
fp.GetRuntimeParameterData(indexTable.value, parametersArr, out rv);
else
fp.GetRuntimeParameterData(parametersArr, out rv);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValue(uint parameterHash)
{
var fp = new FastAnimatorParameter()
{
hash = parameterHash,
};
return GetParameterValue(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValue(FixedStringName parameterName)
{
var fp = new FastAnimatorParameter(parameterName);
return GetParameterValue(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public ParameterValue GetParameterValueByIndex(int paramIndex)
{
return parametersArr.ElementAt(paramIndex).value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValue(FastAnimatorParameter fp, ParameterValue value)
{
if (indexTable.value.IsCreated)
fp.SetRuntimeParameterData(indexTable.value, parametersArr, value);
else
fp.SetRuntimeParameterData(parametersArr, value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetTrigger(FastAnimatorParameter fp)
{
SetParameterValue(fp, new ParameterValue() { boolValue = true });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void ResetTrigger(FastAnimatorParameter fp)
{
SetParameterValue(fp, new ParameterValue() { boolValue = false });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetIntParameter(FastAnimatorParameter fp, int v)
{
SetParameterValue(fp, new ParameterValue() { intValue = v });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetFloatParameter(FastAnimatorParameter fp, float v)
{
SetParameterValue(fp, new ParameterValue() { floatValue = v });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetBoolParameter(FastAnimatorParameter fp, bool v)
{
SetParameterValue(fp, new ParameterValue() { boolValue = v });
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValueByIndex(int paramIndex, ParameterValue value)
{
parametersArr.ElementAt(paramIndex).value = value;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValue(uint parameterHash, ParameterValue value)
{
var fp = new FastAnimatorParameter()
{
hash = parameterHash,
};
SetParameterValue(fp, value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void SetParameterValue(FixedStringName parameterName, ParameterValue value)
{
var fp = new FastAnimatorParameter(parameterName);
SetParameterValue(fp, value);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public int GetParameterIndex(FastAnimatorParameter fp)
{
var index = indexTable.value.IsCreated ?
fp.GetRuntimeParameterIndex(indexTable.value, parametersArr) :
fp.GetRuntimeParameterIndex(parametersArr);
return index;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool HasParameter(FastAnimatorParameter fp)
{
return GetParameterIndex(fp) != -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool HasParameter(uint parameterHash)
{
var fp = new FastAnimatorParameter()
{
hash = parameterHash,
};
return HasParameter(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool HasParameter(FixedStringName parameterName)
{
var fp = new FastAnimatorParameter(parameterName);
return HasParameter(fp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public int ParametersCount() => parametersArr.Length;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 20aa9d2bf9a03ba45be985d8bc55cdd7
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/AnimatorController/AnimatorParameterAspect.cs
uploadId: 897522
@@ -0,0 +1,105 @@
using Rukhanka.Toolbox;
using Unity.Entities;
using FixedStringName = Unity.Collections.FixedString512Bytes;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public readonly struct AnimatorStateQueryAspect
{
readonly DynamicBuffer<AnimatorControllerLayerComponent> layersArr;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public AnimatorStateQueryAspect(DynamicBuffer<AnimatorControllerLayerComponent> layers)
{
layersArr = layers;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public struct RuntimeStateInfo
{
#if RUKHANKA_DEBUG_INFO
public FixedStringName name;
#endif
public uint hash;
public float normalizedTime;
}
public struct RuntimeTransitionInfo
{
#if RUKHANKA_DEBUG_INFO
public FixedStringName name;
#endif
public uint hash;
public float normalizedTime;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public RuntimeStateInfo GetLayerCurrentStateInfo(int layerIndex)
{
if (layersArr.Length <= layerIndex)
return default;
var layerRuntimeData = layersArr[layerIndex];
ref var layerBlob = ref layerRuntimeData.controller.Value.layers[layerIndex];
var curStateID = layerRuntimeData.rtd.srcState.id;
if (curStateID < 0 || curStateID >= layerBlob.states.Length)
return default;
var rv = new RuntimeStateInfo()
{
#if RUKHANKA_DEBUG_INFO
name = layerBlob.states[curStateID].name.ToFixedString(),
#endif
hash = layerBlob.states[curStateID].hash,
normalizedTime = layerRuntimeData.rtd.srcState.normalizedDuration,
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public RuntimeTransitionInfo GetLayerCurrentTransitionInfo(int layerIndex)
{
if (layersArr.Length <= layerIndex)
return default;
var layerRuntimeData = layersArr[layerIndex];
ref var layerBlob = ref layerRuntimeData.controller.Value.layers[layerIndex];
var curTransitionID = layerRuntimeData.rtd.activeTransition.id;
var curStateID = layerRuntimeData.rtd.srcState.id;
if (curTransitionID < 0 || curStateID < 0 || curStateID >= layerBlob.states.Length)
return default;
var rv = new RuntimeTransitionInfo()
{
#if RUKHANKA_DEBUG_INFO
name = layerBlob.states[curStateID].transitions[curTransitionID].name.ToFixedString(),
#endif
hash = layerBlob.states[curStateID].transitions[curTransitionID].hash,
normalizedTime = layerRuntimeData.rtd.activeTransition.normalizedDuration
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool IsInTransition(int layerIndex)
{
if (layersArr.Length <= layerIndex)
return default;
var layerRuntimeData = layersArr[layerIndex];
var rv = layerRuntimeData.rtd.activeTransition.id >= 0;
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e4d517a04d48e334796e296fb7c2d66e
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/AnimatorController/AnimatorStateQueryAspect.cs
uploadId: 897522
@@ -0,0 +1,159 @@
using Unity.Entities;
using FixedStringName = Unity.Collections.FixedString512Bytes;
using UnityEngine;
using System;
using Rukhanka.Toolbox;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct FastAnimatorParameter
{
#if RUKHANKA_DEBUG_INFO
public FixedStringName paramName;
#endif
public uint hash;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public FastAnimatorParameter(FixedStringName name)
{
hash = name.CalculateHash32();
#if RUKHANKA_DEBUG_INFO
paramName = name;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public FastAnimatorParameter(uint hash)
{
this.hash = hash;
#if RUKHANKA_DEBUG_INFO
paramName = default;
#endif
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool GetRuntimeParameterDataInternal(int paramIdx, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, out ParameterValue outData)
{
bool isValid = paramIdx >= 0;
if (isValid)
{
outData = runtimeParameters[paramIdx].value;
}
else
{
outData = default;
#if RUKHANKA_DEBUG_INFO
Debug.LogError($"Could find animator parameter with name {paramName} in hash table! Returning default value!");
#endif
}
return isValid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool GetRuntimeParameterData(BlobAssetReference<PerfectHashTableBlob> pt, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, out ParameterValue outData)
{
var paramIdx = GetRuntimeParameterIndex(pt, runtimeParameters);
return GetRuntimeParameterDataInternal(paramIdx, runtimeParameters, out outData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool GetRuntimeParameterData(DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, out ParameterValue outData)
{
var paramIdx = GetRuntimeParameterIndex(runtimeParameters);
return GetRuntimeParameterDataInternal(paramIdx, runtimeParameters, out outData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bool SetRuntimeParameterDataInternal(int paramIdx, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, in ParameterValue paramData)
{
bool isValid = paramIdx >= 0;
if (isValid)
{
var p = runtimeParameters[paramIdx];
p.value = paramData;
runtimeParameters[paramIdx] = p;
}
#if RUKHANKA_DEBUG_INFO
else
{
Debug.LogError($"Could find animator parameter with name {paramName} in hash table! Setting value is failed!");
}
#endif
return isValid;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool SetRuntimeParameterData(BlobAssetReference<PerfectHashTableBlob> pt, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, in ParameterValue paramData)
{
var paramIdx = GetRuntimeParameterIndex(pt, runtimeParameters);
return SetRuntimeParameterDataInternal(paramIdx, runtimeParameters, paramData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool SetRuntimeParameterData(DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters, in ParameterValue paramData)
{
var paramIdx = GetRuntimeParameterIndex(runtimeParameters);
return SetRuntimeParameterDataInternal(paramIdx, runtimeParameters, paramData);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public bool SetTrigger(BlobAssetReference<PerfectHashTableBlob> pt, DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters) => SetRuntimeParameterData(pt, runtimeParameters, new ParameterValue() { boolValue = true });
public bool SetTrigger(DynamicBuffer<AnimatorControllerParameterComponent> runtimeParameters) => SetRuntimeParameterData(runtimeParameters, new ParameterValue() { boolValue = true });
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Linear search variant
public static int GetRuntimeParameterIndex(uint hash, in ReadOnlySpan<AnimatorControllerParameterComponent> parameters)
{
for (int i = 0; i < parameters.Length; ++i)
{
var p = parameters[i];
if (p.hash == hash)
return i;
}
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Perfect hash table variant
public static int GetRuntimeParameterIndex(uint hash, in BlobAssetReference<PerfectHashTableBlob> pt, in ReadOnlySpan<AnimatorControllerParameterComponent> parameters)
{
var paramIdx = pt.Value.Query(hash);
if (paramIdx < 0)
return -1;
return paramIdx;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe int GetRuntimeParameterIndex(in BlobAssetReference<PerfectHashTableBlob> pt, in DynamicBuffer<AnimatorControllerParameterComponent> acpc)
{
var span = new ReadOnlySpan<AnimatorControllerParameterComponent>(acpc.GetUnsafeReadOnlyPtr(), acpc.Length);
return GetRuntimeParameterIndex(hash, pt, span);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public unsafe int GetRuntimeParameterIndex(in DynamicBuffer<AnimatorControllerParameterComponent> acpc)
{
var span = new ReadOnlySpan<AnimatorControllerParameterComponent>(acpc.GetUnsafeReadOnlyPtr(), acpc.Length);
return GetRuntimeParameterIndex(hash, span);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fa18070f79c037d47b59593d7d6cc720
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/AnimatorController/FastAnimatorParameter.cs
uploadId: 897522
@@ -0,0 +1,133 @@
using Rukhanka.WaybackMachine;
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Entities;
using Unity.Jobs;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[DisableAutoCreation]
[BurstCompile]
[UpdateAfter(typeof(AnimatorControllerSystem<AnimatorControllerQuery>))]
public partial struct FillAnimationsFromControllerSystem: ISystem
{
EntityQuery fillAnimationsBufferQuery;
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
fillAnimationsBufferQuery = SystemAPI.QueryBuilder()
.WithAll<AnimatorControllerLayerComponent, AnimationToProcessComponent>()
.Build();
ss.RequireForUpdate(fillAnimationsBufferQuery);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnDestroy(ref SystemState ss)
{
if (SystemAPI.TryGetSingletonRW<InternalAnimatorDataSingleton>(out var internalDataSingleton))
internalDataSingleton.ValueRW.Dispose();
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
var entityTypeHandle = SystemAPI.GetEntityTypeHandle();
var controllerLayersBufferHandleRO = SystemAPI.GetBufferTypeHandle<AnimatorControllerLayerComponent>(true);
var controllerParametersBufferHandleRO = SystemAPI.GetBufferTypeHandle<AnimatorControllerParameterComponent>(true);
var animatorOverrideAnimationsLookup = SystemAPI.GetComponentLookup<AnimatorOverrideAnimations>(true);
var animationToProcessBufferHandle = SystemAPI.GetBufferTypeHandle<AnimationToProcessComponent>();
var animDBSingleton = SystemAPI.GetSingleton<BlobDatabaseSingleton>();
var internalDataSingletonQuery = SystemAPI.QueryBuilder().WithAllRW<InternalAnimatorDataSingleton>().Build();
var internalAnimatorData = GetInternalDataSingleton(internalDataSingletonQuery, ref ss);
var fillAnimationsBufferJob = new FillAnimationsBufferJob()
{
controllerLayersBufferHandle = controllerLayersBufferHandleRO,
controllerParametersBufferHandle = controllerParametersBufferHandleRO,
animationToProcessBufferHandle = animationToProcessBufferHandle,
animatorOverrideAnimationLookup = animatorOverrideAnimationsLookup,
entityTypeHandle = entityTypeHandle,
animationDatabase = animDBSingleton.animations,
avatarMaskDatabase = animDBSingleton.avatarMasks,
animatorOverrideAnimationsMap = internalAnimatorData.animatorOverrideAnimationsMap.AsParallelWriter()
};
ss.Dependency = fillAnimationsBufferJob.ScheduleParallel(fillAnimationsBufferQuery, ss.Dependency);
ss.Dependency = CopyEventsForWaybackMachineDuringRecording(ref ss, ss.Dependency);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
internal static InternalAnimatorDataSingleton GetInternalDataSingleton(EntityQuery singletonQuery, ref SystemState ss)
{
if (singletonQuery.TryGetSingletonRW<InternalAnimatorDataSingleton>(out var internalAnimatorData))
return internalAnimatorData.ValueRW;
var iads = InternalAnimatorDataSingleton.MakeDefault();
ss.EntityManager.CreateSingleton(iads, "Rukhanka.InternalAnimatorDataSingleton");
return iads;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
internal static unsafe BlobAssetReference<ControllerAnimationsBlob> GetControllerAnimationsBlob
(
Entity e,
ComponentLookup<AnimatorOverrideAnimations> animatorOverrideAnimationLookup,
BlobAssetReference<ControllerAnimationsBlob> cab,
NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>>.ParallelWriter animatorOverrideAnimationsMap
)
{
if (animatorOverrideAnimationLookup.TryGetComponent(e, out var animationOverrides) && animatorOverrideAnimationLookup.IsComponentEnabled(e))
{
// Try cache first
var combinedHash = animationOverrides.value.GetHashCode() ^ cab.GetHashCode();
if (UnsafeParallelHashMapBase<int, BlobAssetReference<ControllerAnimationsBlob>>
.TryGetFirstValueAtomic(animatorOverrideAnimationsMap.m_Writer.m_Buffer, combinedHash, out var rv, out _))
return rv;
// Merge controller animations and override animations
var bb = new BlobBuilder(Allocator.Temp);
ref var mergedBlobAsset = ref bb.ConstructRoot<ControllerAnimationsBlob>();
var animsArr = bb.Allocate(ref mergedBlobAsset.animations, cab.Value.animations.Length);
for (var i = 0; i < animsArr.Length; ++i)
{
var overrideAnim = animationOverrides.value.Value.animations[i];
animsArr[i] = overrideAnim.IsValid ? overrideAnim : cab.Value.animations[i];
}
rv = bb.CreateBlobAssetReference<ControllerAnimationsBlob>(Allocator.Persistent);
animatorOverrideAnimationsMap.TryAdd(combinedHash, rv);
return rv;
}
return cab;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
JobHandle CopyEventsForWaybackMachineDuringRecording(ref SystemState ss, JobHandle dependsOn)
{
if (!SystemAPI.TryGetSingletonRW<RecordComponent>(out var rcd))
return dependsOn;
var copyEventsToWaybackMachineJob = new CopyAnimatorEventsToWaybackMachineRecordingJob()
{
outEvents = rcd.ValueRW.wbData.Value.emittedAnimatorEvents
};
var jh = copyEventsToWaybackMachineJob.Schedule(dependsOn);
return jh;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 8142c7ce247b2d4449bacfa4ece227cc
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/AnimatorController/FillAnimationsFromControllerSystem.cs
uploadId: 897522
@@ -0,0 +1,271 @@
using System;
using Unity.Burst;
using Unity.Burst.CompilerServices;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using static Rukhanka.AnimatorControllerSystemJobs;
using Rukhanka.WaybackMachine;
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial struct FillAnimationsFromControllerSystem
{
[BurstCompile]
struct FillAnimationsBufferJob: IJobChunk
{
[ReadOnly]
public BufferTypeHandle<AnimatorControllerLayerComponent> controllerLayersBufferHandle;
[ReadOnly]
public BufferTypeHandle<AnimatorControllerParameterComponent> controllerParametersBufferHandle;
[ReadOnly]
public ComponentLookup<AnimatorOverrideAnimations> animatorOverrideAnimationLookup;
[ReadOnly]
public EntityTypeHandle entityTypeHandle;
[ReadOnly]
public NativeHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> animationDatabase;
[ReadOnly]
public NativeHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>> avatarMaskDatabase;
public BufferTypeHandle<AnimationToProcessComponent> animationToProcessBufferHandle;
public NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>>.ParallelWriter animatorOverrideAnimationsMap;
BlobAssetReference<ControllerAnimationsBlob> controllerAnimationsBlob;
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var layerBuffers = chunk.GetBufferAccessor(ref controllerLayersBufferHandle);
var parameterBuffers = chunk.GetBufferAccessor(ref controllerParametersBufferHandle);
var animationsToProcessBuffers = chunk.GetBufferAccessor(ref animationToProcessBufferHandle);
var entities = chunk.GetNativeArray(entityTypeHandle);
var cee = new ChunkEntityEnumerator(useEnabledMask, chunkEnabledMask, chunk.Count);
while (cee.NextEntityIndex(out var i))
{
var layers = layerBuffers[i].AsNativeArray();
var parameters = parameterBuffers.Length > 0 ? parameterBuffers[i].AsNativeArray() : default;
var e = entities[i];
var animsBuf = animationsToProcessBuffers[i];
AddAnimationsForEntity(ref animsBuf, layers, parameters, e);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void AnimationsPostSetup(Span<AnimationToProcessComponent> animations, ref LayerBlob lb, int layerIndex, float weightMultiplier, float layerWeight)
{
// Set blending mode and adjust animations weight according to layer weight
for (int k = 0; k < animations.Length; ++k)
{
var a = animations[k];
a.blendMode = lb.blendingMode;
a.layerWeight = layerWeight;
a.layerIndex = layerIndex;
a.weight *= weightMultiplier;
a.avatarMask = BlobDatabaseSingleton.GetBlobAsset(lb.avatarMaskBlobHash, avatarMaskDatabase);
animations[k] = a;
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
unsafe void AddAnimationsForEntity
(
ref DynamicBuffer<AnimationToProcessComponent> animations,
in NativeArray<AnimatorControllerLayerComponent> aclc,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
Entity entity
)
{
if (entity == Entity.Null)
return;
animations.Clear();
// Need to skip zero weight layers
for (int i = 0; i < aclc.Length; ++i)
{
var animationCurIndex = animations.Length;
var l = aclc[i];
controllerAnimationsBlob = GetControllerAnimationsBlob(entity, animatorOverrideAnimationLookup, l.animations, animatorOverrideAnimationsMap);
var cb = l.controller;
ref var lb = ref cb.Value.layers[i];
if (l.weight == 0 || l.rtd.srcState.id < 0)
continue;
ref var srcState0Blob = ref lb.states[l.rtd.srcState.id];
var srcStateWeight = 1.0f;
var dstStateWeight = 0.0f;
if (l.rtd.activeTransition.id >= 0)
{
dstStateWeight = l.rtd.activeTransition.normalizedDuration;
srcStateWeight = (1 - dstStateWeight);
}
var srcStateTime = GetDurationTime(ref srcState0Blob, runtimeParams, l.rtd.srcState.normalizedDuration);
var dstStateAnimCount = 0;
if (l.rtd.dstState.id >= 0)
{
ref var dstStateBlob = ref lb.states[l.rtd.dstState.id];
var dstStateTime = GetDurationTime(ref dstStateBlob, runtimeParams, l.rtd.dstState.normalizedDuration);
dstStateAnimCount = AddMotionForEntity(ref animations, ref dstStateBlob.motion, runtimeParams, 1, dstStateTime, l.rtd.dstState.motionId);
}
var srcStateAnimCount = 0;
// No state snapshots - no transition interruption process
// Default state motion processing
if (Hint.Likely(l.rtd.srcStateSnapshots.Length == 0))
{
ref var srcStateBlob = ref lb.states[l.rtd.srcState.id];
srcStateAnimCount += AddMotionForEntity(ref animations, ref srcStateBlob.motion, runtimeParams, 1, srcStateTime, l.rtd.srcState.motionId);
}
// Transition interruption motions from state snapshots
for (var k = l.rtd.srcStateSnapshots.length - 1; k >= 0; --k)
{
var stateData = l.rtd.srcStateSnapshots[k];
ref var srcStateBlob = ref lb.states[stateData.id];
srcStateAnimCount += AddMotionForEntity(ref animations, ref srcStateBlob.motion, runtimeParams, stateData.weight, stateData.normalizedTime, stateData.motionId);
}
var animStartPtr = (AnimationToProcessComponent*)animations.GetUnsafePtr() + animationCurIndex;
var dstAnimsSpan = new Span<AnimationToProcessComponent>(animStartPtr, dstStateAnimCount);
var srcAnimsSpan = new Span<AnimationToProcessComponent>(animStartPtr + dstStateAnimCount, srcStateAnimCount);
var dstLayerMultiplier = math.select(dstStateWeight, 1, srcStateAnimCount > 0);
var srcLayerMultiplier = math.select(srcStateWeight, 1, dstStateAnimCount > 0);
dstStateWeight = math.select(1, dstStateWeight, srcStateAnimCount > 0);
srcStateWeight = math.select(1, srcStateWeight, dstStateAnimCount > 0);
AnimationsPostSetup(dstAnimsSpan, ref lb, i, dstStateWeight, dstLayerMultiplier * l.weight);
AnimationsPostSetup(srcAnimsSpan, ref lb, i, srcStateWeight, srcLayerMultiplier * l.weight);
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void AddAnimationForEntity
(
ref DynamicBuffer<AnimationToProcessComponent> outAnims,
ref MotionBlob mb,
float weight,
float normalizedStateTime,
uint motionId
)
{
var atp = new AnimationToProcessComponent();
var animationHash = controllerAnimationsBlob.Value.animations[mb.animationIndex];
atp.animation = BlobDatabaseSingleton.GetBlobAsset(animationHash, animationDatabase);
atp.weight = weight;
atp.time = normalizedStateTime;
atp.motionId = mb.hash + motionId;
outAnims.Add(atp);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
void AddMotionsFromBlendtree
(
in NativeList<ScriptedAnimator.MotionIndexAndWeight> miws,
ref DynamicBuffer<AnimationToProcessComponent> outAnims,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
ref BlobArray<ChildMotionBlob> motions,
float weight,
float normalizedStateTime,
uint motionId
)
{
for (int i = 0; i < miws.Length; ++i)
{
var miw = miws[i];
ref var m = ref motions[miw.motionIndex];
var finalWeight = weight * miw.weight;
if (finalWeight > 0)
AddMotionForEntity(ref outAnims, ref m.motion, runtimeParams, finalWeight, normalizedStateTime, (uint)(i + motionId));
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
int AddMotionForEntity
(
ref DynamicBuffer<AnimationToProcessComponent> outAnims,
ref MotionBlob mb,
in NativeArray<AnimatorControllerParameterComponent> runtimeParams,
float weight,
float normalizedStateTime,
uint motionId
)
{
var startLen = outAnims.Length;
switch (mb.type)
{
case MotionBlob.Type.None:
break;
case MotionBlob.Type.AnimationClip:
AddAnimationForEntity(ref outAnims, ref mb, weight, normalizedStateTime, motionId);
break;
}
var childMotions = ScriptedAnimator.GetChildMotionsList(ref mb, runtimeParams);
if (childMotions.IsCreated)
{
AddMotionsFromBlendtree(childMotions, ref outAnims, runtimeParams, ref mb.blendTree.motions, weight, normalizedStateTime, motionId);
}
return outAnims.Length - startLen;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
float GetDurationTime(ref StateBlob sb, in NativeArray<AnimatorControllerParameterComponent> runtimeParams, float normalizedDuration)
{
var timeDuration = normalizedDuration;
if (sb.timeParameterIndex >= 0)
{
timeDuration = runtimeParams[sb.timeParameterIndex].FloatValue;
}
var stateCycleOffset = sb.cycleOffset;
if (sb.cycleOffsetParameterIndex >= 0)
{
stateCycleOffset = runtimeParams[sb.cycleOffsetParameterIndex].FloatValue;
}
timeDuration += stateCycleOffset;
return timeDuration;
}
}
//=================================================================================================================//
[BurstCompile]
[WithAll(typeof(RecordComponent))]
partial struct CopyAnimatorEventsToWaybackMachineRecordingJob: IJobEntity
{
public NativeList<AnimatorControllerEventComponent> outEvents;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Execute(in DynamicBuffer<AnimatorControllerEventComponent> acec)
{
outEvents.AddRange(acec.AsNativeArray());
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: f9daf9e1f7afc80459aad91d4b092a7e
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/AnimatorController/FillAnimationsFromControllerSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,33 @@
using Unity.Collections;
using Unity.Entities;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct InternalAnimatorDataSingleton: IComponentData
{
internal NativeParallelHashMap<int, BlobAssetReference<ControllerAnimationsBlob>> animatorOverrideAnimationsMap;
/////////////////////////////////////////////////////////////////////////////////
public static InternalAnimatorDataSingleton MakeDefault()
{
var rv = new InternalAnimatorDataSingleton()
{
animatorOverrideAnimationsMap = new (0xff, Allocator.Persistent)
};
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
public void Dispose()
{
foreach (var kv in animatorOverrideAnimationsMap)
kv.Value.Dispose();
animatorOverrideAnimationsMap.Dispose();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c7c1e038272b2944aab7454e9960315e
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/AnimatorController/InternalAnimatorDataSingleton.cs
uploadId: 897522
@@ -0,0 +1,114 @@
using Unity.Collections;
#if RUKHANKA_WITH_NETCODE
using Unity.NetCode;
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct RuntimeAnimatorData
{
public struct StateSnapshot
{
public int id;
public uint motionId;
public float weight;
public float normalizedTime;
}
//-------------------------------------------------------------------------------------------------//
public struct StateData
{
public int id;
public uint motionId;
public float normalizedDuration;
}
//-------------------------------------------------------------------------------------------------//
public struct TransitionData
{
public int id;
public float normalizedDuration;
public float length;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public StateData MakeDefaultState() => new StateData() { id = -1, normalizedDuration = 0, motionId = GetNextMotionID() };
public TransitionData MakeDefaultTransition() => new TransitionData() { id = -1, length = 0, normalizedDuration = 0 };
/////////////////////////////////////////////////////////////////////////////////////////////////////
uint GetNextMotionID()
{
motionIdCounter += 0xff;
return motionIdCounter;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public uint motionIdCounter;
public StateData srcState;
public StateData dstState;
public TransitionData activeTransition;
#if RUKHANKA_WITH_NETCODE
[GhostField(SendData = false)]
#endif
public FixedList64Bytes<StateSnapshot> srcStateSnapshots;
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void ClearStateSnapshots() { srcStateSnapshots.Clear(); }
/////////////////////////////////////////////////////////////////////////////////////////////////////
public void PushStateSnapshot(int stateID, float weight, float normalizedTime, uint motionId)
{
// If we are out of free space prune snapshot with the lowest weight
if (srcStateSnapshots.length == srcStateSnapshots.Capacity)
{
var minWeight = 1.0f;
var minWeightIndex = 0;
for (var i = 0; i < srcStateSnapshots.length; ++i)
{
var w = srcStateSnapshots[i].weight;
if (minWeight > w)
{
minWeight = w;
minWeightIndex = i;
}
}
if (minWeightIndex != srcStateSnapshots.length - 1)
srcStateSnapshots[minWeightIndex] = srcStateSnapshots[^1];
srcStateSnapshots.Length -= 1;
}
// Scale existing weights
for (var i = 0; i < srcStateSnapshots.length; ++i)
{
ref var sn = ref srcStateSnapshots.ElementAt(i);
sn.weight *= 1 - weight;
}
var ss = new StateSnapshot() { id = stateID, weight = weight, normalizedTime = normalizedTime, motionId = motionId };
srcStateSnapshots.Add(ss);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
public static RuntimeAnimatorData MakeDefault()
{
var rv = new RuntimeAnimatorData();
rv.srcState = rv.MakeDefaultState();
rv.dstState = rv.MakeDefaultState();
rv.activeTransition = rv.MakeDefaultTransition();
return rv;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: da31f172d48d3614dac5465f62e8efe0
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/AnimatorController/RuntimeAnimatorData.cs
uploadId: 897522
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c8738974cb5aaa14ba8720e321ce7b49
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,46 @@
using System;
using Unity.Collections;
using Unity.Entities;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[assembly: RegisterGenericComponentType(typeof(Rukhanka.NewBlobAssetDatabaseRecord<Rukhanka.AnimationClipBlob>))]
[assembly: RegisterGenericComponentType(typeof(Rukhanka.NewBlobAssetDatabaseRecord<Rukhanka.AvatarMaskBlob>))]
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public struct BlobDatabaseSingleton: IComponentData
{
public NativeHashMap<Hash128, BlobAssetReference<AnimationClipBlob>> animations;
public NativeHashMap<Hash128, BlobAssetReference<AvatarMaskBlob>> avatarMasks;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public static BlobAssetReference<T> GetBlobAsset<T>(Hash128 blobHash, NativeHashMap<Hash128, BlobAssetReference<T>> blobDatabase) where T: unmanaged
{
if (!blobDatabase.TryGetValue(blobHash, out var bar))
return default;
return bar;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public BlobAssetReference<AvatarMaskBlob> GetAvatarMaskBlob(Hash128 blobHash) => GetBlobAsset(blobHash, avatarMasks);
public BlobAssetReference<AnimationClipBlob> GetAnimationClipBlob(Hash128 blobHash) => GetBlobAsset(blobHash, animations);
}
//=================================================================================================================//
public struct NewBlobAssetDatabaseRecord<T>: IBufferElementData where T: unmanaged
{
public Hash128 hash;
public BlobAssetReference<T> value;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3af0196747a230b45a6e72e1dbec5523
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/BlobDatabase/BlobDatabase.cs
uploadId: 897522
@@ -0,0 +1,88 @@
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[UpdateInGroup(typeof(InitializationSystemGroup), OrderLast = true)]
public partial struct BlobDatabaseUpdateSystem: ISystem
{
EntityQuery newBlobAssetsQuery;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnCreate(ref SystemState ss)
{
CreateDBSingleton(ref ss);
newBlobAssetsQuery = SystemAPI.QueryBuilder()
.WithAny
<
NewBlobAssetDatabaseRecord<AnimationClipBlob>,
NewBlobAssetDatabaseRecord<AvatarMaskBlob>
>()
.WithOptions(EntityQueryOptions.IncludePrefab)
.Build();
ss.RequireForUpdate(newBlobAssetsQuery);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateDBSingleton(ref SystemState ss)
{
var blobDB = new BlobDatabaseSingleton()
{
animations = new (128, Allocator.Persistent),
avatarMasks = new (128, Allocator.Persistent),
};
ss.EntityManager.CreateSingleton(blobDB, "Rukhanka.BlobDatabaseSingleton");
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnUpdate(ref SystemState ss)
{
if (!SystemAPI.TryGetSingletonRW<BlobDatabaseSingleton>(out var db))
return;
var ecbs = SystemAPI.GetSingleton<BeginSimulationEntityCommandBufferSystem.Singleton>();
var ecb = ecbs.CreateCommandBuffer(ss.WorldUnmanaged);
ProcessNewBlobs(ref ss, ref db.ValueRW.avatarMasks, ecb);
ProcessNewBlobs(ref ss, ref db.ValueRW.animations, ecb);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void ProcessNewBlobs<T>(ref SystemState ss, ref NativeHashMap<Hash128, BlobAssetReference<T>> db, EntityCommandBuffer ecb) where T: unmanaged
{
var componentTypeHandle = ss.EntityManager.GetBufferTypeHandle<NewBlobAssetDatabaseRecord<T>>(true);
var processNewBlobsJob = new ProcessNewBlobsJob<T>()
{
db = db,
componentTypeHandle = componentTypeHandle
};
ss.Dependency = processNewBlobsJob.Schedule(newBlobAssetsQuery, ss.Dependency);
ecb.RemoveComponent<NewBlobAssetDatabaseRecord<T>>(newBlobAssetsQuery, EntityQueryCaptureMode.AtPlayback);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[BurstCompile]
public void OnDestroy(ref SystemState ss)
{
if (SystemAPI.TryGetSingletonRW<BlobDatabaseSingleton>(out var animDB))
{
animDB.ValueRW.animations.Dispose();
animDB.ValueRW.avatarMasks.Dispose();
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1b5bab6a76d92554cba1f6cafa248b48
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/BlobDatabase/BlobDatabaseUpdateSystem.cs
uploadId: 897522
@@ -0,0 +1,44 @@
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Hash128 = Unity.Entities.Hash128;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[assembly: RegisterGenericJobType(typeof(Rukhanka.BlobDatabaseUpdateSystem.ProcessNewBlobsJob<Rukhanka.AvatarMaskBlob>))]
[assembly: RegisterGenericJobType(typeof(Rukhanka.BlobDatabaseUpdateSystem.ProcessNewBlobsJob<Rukhanka.AnimationClipBlob>))]
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
public partial struct BlobDatabaseUpdateSystem
{
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
internal struct ProcessNewBlobsJob<T>: IJobChunk where T: unmanaged
{
public BufferTypeHandle<NewBlobAssetDatabaseRecord<T>> componentTypeHandle;
public NativeHashMap<Hash128, BlobAssetReference<T>> db;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
{
var ba = chunk.GetBufferAccessor(ref componentTypeHandle);
for (var i = 0; i < chunk.Count && ba.Length > 0; ++i)
{
var newBlobsArr = ba[i];
for (int j = 0; j < newBlobsArr.Length; ++j)
{
var a = newBlobsArr[j];
db[a.hash] = a.value;
}
}
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b900a60865904dc429164cdcb5c6f9dc
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/BlobDatabase/BlobDatabaseUpdateSystem_Jobs.cs
uploadId: 897522
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9f666a7d3068fc54c958786db43c176d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f49e1b09fc72bc744991b3bc2ff4e89f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
#ifndef DEBUG_HLSL_
#define DEBUG_HLSL_
///////////////////////////////////////////////////////////////////////////////////////////
#include "Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/DebugMarkers.cs.hlsl"
///////////////////////////////////////////////////////////////////////////////////////////
#ifdef RUKHANKA_SHADER_DEBUG
#define CHECK_MIN_MAX_RANGE(debugMarker, v, minValue, maxValue) if ((v) < (minValue) || (v) >= (maxValue)) InterlockedAdd(debugLoggerCB[debugMarker], 1);
// Vulkan reports size in elements (ints) rather then bytes as in D3D11/12
#ifdef SHADER_API_VULKAN
#define CHECK_RAW_BUFFER_OUT_OF_BOUNDS(debugMarker, index, count, buffer) \
{ \
uint bufferSize = 0; \
buffer.GetDimensions(bufferSize); \
bufferSize *= 4; \
if ((int)(index) < 0 || (int)((index) + (count)) > (int)bufferSize) InterlockedAdd(debugLoggerCB[debugMarker], 1); \
}
#else
#define CHECK_RAW_BUFFER_OUT_OF_BOUNDS(debugMarker, index, count, buffer) \
{ \
uint bufferSize = 0; \
buffer.GetDimensions(bufferSize); \
if ((int)(index) < 0 || (int)((index) + (count)) > (int)bufferSize) InterlockedAdd(debugLoggerCB[debugMarker], 1); \
}
#endif
#define CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(debugMarker, index, buffer) \
{ \
uint numStructs = 0; \
uint stride = 0; \
buffer.GetDimensions(numStructs, stride); \
if ((int)(index) < 0 || (int)(index) >= (int)numStructs) InterlockedAdd(debugLoggerCB[debugMarker], 1); \
}
RWStructuredBuffer<uint> debugLoggerCB;
#else
#define CHECK_MIN_MAX_RANGE(debugMarker, v, minValue, maxValue)
#define CHECK_RAW_BUFFER_OUT_OF_BOUNDS(debugMarker, index, count, buffer)
#define CHECK_STRUCTURED_BUFFER_OUT_OF_BOUNDS(debugMarker, index, buffer)
#endif
///////////////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ba72f5fdfaa93d349a06294d14840691
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/Debug.hlsl
uploadId: 897522
@@ -0,0 +1,48 @@
using UnityEngine.Rendering;
/////////////////////////////////////////////////////////////////////////////////
namespace Rukhanka
{
[GenerateHLSL]
public enum RukhankaDebugMarkers
{
Deformation_CopyMeshData,
Deformation_CopyBlendShapes,
Deformation_CreatePerVertexDeformationWorkload,
Deformation_SkinnedMeshVertex_Write,
Deformation_SkinnedMeshVertex_Read,
Deformation_DeformedVertex_Read,
Deformation_BoneInfluence_Read,
Deformation_DeformedVertex_Write,
Deformation_FrameSkinMatrices_Read,
Deformation_PerVertexWorkload_Read,
Deformation_FrameDeformedVertex_Read,
GPUAnimator_MakeRigSpaceBoneTransforms_AnimatedBoneWorkload_Read,
GPUAnimator_MakeRigSpaceBoneTransforms_AnimationJobs_Read,
GPUAnimator_MakeRigSpaceBoneTransforms_BoneLocalTransforms_Read0,
GPUAnimator_MakeRigSpaceBoneTransforms_BoneLocalTransforms_Read1,
GPUAnimator_MakeRigSpaceBoneTransforms_OutBoneTransforms_Write,
GPUAnimator_ComputeSkinMatrices_SkinMatrixWorkload_Read,
GPUAnimator_ComputeSkinMatrices_RigSpaceBoneTransforms_Read,
GPUAnimator_ComputeSkinMatrices_OutSkinMatrices_Write,
GPUAnimator_ProcessAnimations_AnimatedBoneWorkload_Read,
GPUAnimator_ProcessAnimations_AnimationJobs_Read,
GPUAnimator_ProcessAnimations_OutAnimatedBones_Write,
GPUAnimator_GenericAvatar_AvatarMaskBuffer_Read,
GPUAnimator_HumanoidAvatar_AvatarMaskBuffer_Read,
GPUAnimator_HumanRotationData_Read,
GPUAnimator_QueryPerfectHashTable_AnimationClips_Read0,
GPUAnimator_QueryPerfectHashTable_AnimationClips_Read1,
GPUAnimator_KeyFrame_Read,
GPUAnimator_Track_Read,
GPUAnimator_TrackSet_Read,
GPUAnimator_AnimationClip_Read,
GPUAnimator_SkinnedMeshBone_Read,
GPUAnimator_RigDefinition_Read,
GPUAnimator_RigBone_Read,
GPUAnimator_GPUAttachment_RigSpaceBoneTransforms_Read,
Total
}
}
@@ -0,0 +1,48 @@
//
// This file was automatically generated. Please don't edit by hand. Execute Editor command [ Edit > Rendering > Generate Shader Includes ] instead
//
#ifndef DEBUGMARKERS_CS_HLSL
#define DEBUGMARKERS_CS_HLSL
//
// Rukhanka.RukhankaDebugMarkers: static fields
//
#define RUKHANKADEBUGMARKERS_DEFORMATION_COPY_MESH_DATA (0)
#define RUKHANKADEBUGMARKERS_DEFORMATION_COPY_BLEND_SHAPES (1)
#define RUKHANKADEBUGMARKERS_DEFORMATION_CREATE_PER_VERTEX_DEFORMATION_WORKLOAD (2)
#define RUKHANKADEBUGMARKERS_DEFORMATION_SKINNED_MESH_VERTEX_WRITE (3)
#define RUKHANKADEBUGMARKERS_DEFORMATION_SKINNED_MESH_VERTEX_READ (4)
#define RUKHANKADEBUGMARKERS_DEFORMATION_DEFORMED_VERTEX_READ (5)
#define RUKHANKADEBUGMARKERS_DEFORMATION_BONE_INFLUENCE_READ (6)
#define RUKHANKADEBUGMARKERS_DEFORMATION_DEFORMED_VERTEX_WRITE (7)
#define RUKHANKADEBUGMARKERS_DEFORMATION_FRAME_SKIN_MATRICES_READ (8)
#define RUKHANKADEBUGMARKERS_DEFORMATION_PER_VERTEX_WORKLOAD_READ (9)
#define RUKHANKADEBUGMARKERS_DEFORMATION_FRAME_DEFORMED_VERTEX_READ (10)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_ANIMATED_BONE_WORKLOAD_READ (11)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_ANIMATION_JOBS_READ (12)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_BONE_LOCAL_TRANSFORMS_READ0 (13)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_BONE_LOCAL_TRANSFORMS_READ1 (14)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_MAKE_RIG_SPACE_BONE_TRANSFORMS_OUT_BONE_TRANSFORMS_WRITE (15)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_COMPUTE_SKIN_MATRICES_SKIN_MATRIX_WORKLOAD_READ (16)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_COMPUTE_SKIN_MATRICES_RIG_SPACE_BONE_TRANSFORMS_READ (17)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_COMPUTE_SKIN_MATRICES_OUT_SKIN_MATRICES_WRITE (18)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_PROCESS_ANIMATIONS_ANIMATED_BONE_WORKLOAD_READ (19)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_PROCESS_ANIMATIONS_ANIMATION_JOBS_READ (20)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_PROCESS_ANIMATIONS_OUT_ANIMATED_BONES_WRITE (21)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_GENERIC_AVATAR_AVATAR_MASK_BUFFER_READ (22)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_HUMANOID_AVATAR_AVATAR_MASK_BUFFER_READ (23)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_HUMAN_ROTATION_DATA_READ (24)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_QUERY_PERFECT_HASH_TABLE_ANIMATION_CLIPS_READ0 (25)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_QUERY_PERFECT_HASH_TABLE_ANIMATION_CLIPS_READ1 (26)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_KEY_FRAME_READ (27)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_TRACK_READ (28)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_TRACK_SET_READ (29)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_ANIMATION_CLIP_READ (30)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_SKINNED_MESH_BONE_READ (31)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_RIG_DEFINITION_READ (32)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_RIG_BONE_READ (33)
#define RUKHANKADEBUGMARKERS_GPUANIMATOR_GPUATTACHMENT_RIG_SPACE_BONE_TRANSFORMS_READ (34)
#define RUKHANKADEBUGMARKERS_TOTAL (35)
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8835b540c29a9e24780f45cd69428b92
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/DebugMarkers.cs.hlsl
uploadId: 897522
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: cdbd14dcc81008542b7c6fca79ba7111
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/Common/Shaders/DebugMarkers.cs
uploadId: 897522
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5b2404e6da098614aa9939f5120e09eb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,54 @@
#ifndef ANIMATION_CLIP_HLSL_
#define ANIMATION_CLIP_HLSL_
/////////////////////////////////////////////////////////////////////////////////
#include "TrackSet.hlsl"
/////////////////////////////////////////////////////////////////////////////////
struct AnimationClip
{
uint4 hash;
TrackSet clipTracks;
TrackSet additiveReferencePoseTracks;
uint flags;
float cycleOffset;
float length;
static const uint size = (4 + 1 + 1 + 1) * 4 + TrackSet::size * 2;
/////////////////////////////////////////////////////////////////////////////////
bool IsLooped() { return GetFlag(1); }
bool LoopPoseBlend() { return GetFlag(2); }
/////////////////////////////////////////////////////////////////////////////////
bool GetFlag(int index)
{
uint v = 1u << index;
return (flags & v) != 0;
}
/////////////////////////////////////////////////////////////////////////////////
static AnimationClip ReadFromRawBuffer(ByteAddressBuffer b, int byteAddress)
{
AnimationClip rv = (AnimationClip)0;
rv.hash = b.Load4(byteAddress);
rv.clipTracks = TrackSet::ReadFromRawBuffer(b, byteAddress + 16);
rv.additiveReferencePoseTracks = TrackSet::ReadFromRawBuffer(b, byteAddress + 16 + TrackSet::size);
rv.flags = b.Load(byteAddress + 16 + TrackSet::size * 2);
rv.cycleOffset = asfloat(b.Load(byteAddress + 20 + TrackSet::size * 2));
rv.length = asfloat(b.Load(byteAddress + 24 + TrackSet::size * 2));
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_ANIMATION_CLIP_READ, byteAddress, size, b);
return rv;
}
};
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 73e65cfe5708b1346962e42ce655aad5
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/AnimationClip.hlsl
uploadId: 897522
@@ -0,0 +1,24 @@
#ifndef ANIMATION_TO_PROCESS_HLSL_
#define ANIMATION_TO_PROCESS_HLSL_
/////////////////////////////////////////////////////////////////////////////////
#define BLEND_MODE_OVERRIDE 0
#define BLEND_MODE_ADDITIVE 1
/////////////////////////////////////////////////////////////////////////////////
struct AnimationToProcess
{
int animationClipAddress;
float weight;
float time;
int blendMode;
float layerWeight;
int layerIndex;
int avatarMaskDataOffset;
};
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ff3f32e2710712542bbe95b14f0535c3
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/AnimationToProcess.hlsl
uploadId: 897522
@@ -0,0 +1,53 @@
#ifndef AVATAR_MASK_HLSL_
#define AVATAR_MASK_HLSL_
/////////////////////////////////////////////////////////////////////////////////
// uint
ByteAddressBuffer avatarMasksBuffer;
/////////////////////////////////////////////////////////////////////////////////
bool IsBoneInGenericAvatarMask(int avatarMaskDataOffset, int boneIndex)
{
int uintIndex = boneIndex >> 5; // boneIndex / 32
int byteOffsetInUint = boneIndex & 0x1f; // boneIndex % 32
uint boneMask = 1u << byteOffsetInUint;
// First uint in mask is human body parts mask, skip it
uint absMaskDataIndex = avatarMaskDataOffset + 1 + uintIndex;
uint avatarMask = avatarMasksBuffer.Load(absMaskDataIndex * 4);
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_GENERIC_AVATAR_AVATAR_MASK_BUFFER_READ, absMaskDataIndex * 4, 4, avatarMasksBuffer);
return (avatarMask & boneMask) != 0;
}
/////////////////////////////////////////////////////////////////////////////////
bool IsBoneInHumanAvatarMask(int avatarMaskDataOffset, int humanAvatarMaskBodyPart)
{
uint absMaskDataIndex = avatarMaskDataOffset;
uint humanAvatarMask = avatarMasksBuffer.Load(absMaskDataIndex * 4);
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_HUMANOID_AVATAR_AVATAR_MASK_BUFFER_READ, absMaskDataIndex * 4, 4, avatarMasksBuffer);
return (humanAvatarMask & 1 << humanAvatarMaskBodyPart) != 0;
}
/////////////////////////////////////////////////////////////////////////////////
bool IsBoneInAvatarMask(int avatarMaskDataOffset, int humanAvatarMaskBodyPart, int boneIndex)
{
if (avatarMaskDataOffset < 0)
return true;
bool rv = humanAvatarMaskBodyPart < 0 ?
IsBoneInGenericAvatarMask(avatarMaskDataOffset, boneIndex) :
IsBoneInHumanAvatarMask(avatarMaskDataOffset, humanAvatarMaskBodyPart);
return rv;
}
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 6c18bdd7b329d1b40889497acd238107
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/AvatarMask.hlsl
uploadId: 897522
@@ -0,0 +1,119 @@
#ifndef BONE_TRANFORM_HLSL_
#define BONE_TRANFORM_HLSL_
/////////////////////////////////////////////////////////////////////////////////
#include "Quaternion.hlsl"
/////////////////////////////////////////////////////////////////////////////////
struct BoneTransform
{
float3 pos;
Quaternion rot;
float3 scale;
static const uint size = (3 + 4 + 3) * 4;
/////////////////////////////////////////////////////////////////////////////////
static BoneTransform Identity()
{
BoneTransform rv;
rv.pos = 0;
rv.rot.value = float4(0, 0, 0, 1);
rv.scale = 1;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static BoneTransform Zero()
{
BoneTransform rv = (BoneTransform)0;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static BoneTransform FromMatrix(float3x4 m)
{
BoneTransform rv;
rv.rot = Quaternion::FromMatrix((float3x3)m);
rv.pos = m._m03_m13_m23;
float3 u = m._m00_m10_m20;
float3 v = m._m01_m11_m21;
float3 w = m._m02_m12_m22;
rv.scale = float3(length(u), length(v), length(w));
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static BoneTransform Multiply(BoneTransform parent, BoneTransform child)
{
BoneTransform rv;
rv.pos = Quaternion::Rotate(parent.rot, child.pos * parent.scale) + parent.pos;
rv.rot = Quaternion::Multiply(parent.rot, child.rot);
rv.scale = parent.scale * child.scale;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static BoneTransform Scale(BoneTransform v, float3 scale)
{
v.pos *= scale.x;
v.rot.value *= scale.y;
v.scale *= scale.z;
return v;
}
/////////////////////////////////////////////////////////////////////////////////
float4x4 ToFloat4x4()
{
float3x3 rotMat = rot.ToRotationMatrix();
float4 c0 = float4(rotMat._11_21_31 * scale, pos.x);
float4 c1 = float4(rotMat._12_22_32 * scale, pos.y);
float4 c2 = float4(rotMat._13_23_33 * scale, pos.z);
float4 c3 = float4(0, 0, 0, 1);
float4x4 rv = float4x4(c0, c1, c2, c3);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static BoneTransform Inverse(BoneTransform v)
{
BoneTransform rv;
rv.rot = Quaternion::Inverse(v.rot);
rv.scale = rcp(v.scale);
rv.pos = Quaternion::Rotate(rv.rot, -v.pos * rv.scale);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static float3 TransformPoint(BoneTransform bt, float3 v)
{
float3 rv = Quaternion::Rotate(bt.rot, v * bt.scale) + bt.pos;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static float3 TransformDirection(BoneTransform bt, float3 v)
{
float3 rv = Quaternion::Rotate(bt.rot, v);
return rv;
}
};
/////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 3d24b7cf754800d4f80fd374dc9639ac
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/BoneTransform.hlsl
uploadId: 897522
@@ -0,0 +1,150 @@
#ifndef DUAL_QUATERNION_HLSL_
#define DUAL_QUATERBION_HLSL_
/////////////////////////////////////////////////////////////////////////////////
#include "Quaternion.hlsl"
/////////////////////////////////////////////////////////////////////////////////
struct DualQuaternion
{
Quaternion qReal, qDual;
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Identity()
{
DualQuaternion rv;
rv.qReal.value = float4(0, 0, 0, 1);
rv.qDual.value = float4(0, 0, 0, 0);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Construct(Quaternion r, Quaternion d)
{
DualQuaternion rv;
rv.qReal = Quaternion::Normalize(r);
rv.qDual = d;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Construct(float3 t, Quaternion r)
{
DualQuaternion rv;
rv.qReal = Quaternion::Normalize(r);
Quaternion qt;
qt.value = float4(t, 0);
rv.qDual = Quaternion::Scale(Quaternion::Multiply(qt, rv.qReal), 0.5f);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Normalize(DualQuaternion q)
{
DualQuaternion rv = q;
float m = length(q.qReal.value);
float rcpM = rcp(m);
rv.qReal = Quaternion::Scale(q.qReal, rcpM);
rv.qDual = Quaternion::Scale(q.qDual, rcpM);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Multiply(DualQuaternion l, DualQuaternion r)
{
DualQuaternion rv;
rv.qReal = Quaternion::Multiply(l.qReal, r.qReal);
Quaternion qDual0 = Quaternion::Multiply(l.qDual, r.qReal);
Quaternion qDual1 = Quaternion::Multiply(l.qReal, r.qDual);
rv.qDual.value = qDual0.value + qDual1.value;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Add(DualQuaternion l, DualQuaternion r)
{
DualQuaternion rv;
rv.qReal.value = l.qReal.value + r.qReal.value;
rv.qDual.value = l.qDual.value + r.qDual.value;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static DualQuaternion Scale(DualQuaternion q, float s)
{
DualQuaternion rv;
rv.qReal = Quaternion::Scale(q.qReal, s);
rv.qDual = Quaternion::Scale(q.qDual, s);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
BoneTransform GetBoneTransform()
{
BoneTransform rv;
Quaternion t0 = Quaternion::Scale(qDual, 2);
Quaternion t1 = Quaternion::Conjugate(qReal);
Quaternion t = Quaternion::Multiply(t0, t1);
rv.pos = t.value.xyz;
rv.rot = qReal;
rv.scale = 1;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
float4x4 ToTransformMatrix()
{
DualQuaternion q = DualQuaternion::Normalize(this);
float4x4 m = float4x4
(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
float4 r = q.qReal.value;
// Extract rotation
m._11 = r.w * r.w + r.x * r.x - r.y * r.y - r.z * r.z;
m._12 = 2 * r.x * r.y + 2 * r.w * r.z;
m._13 = 2 * r.x * r.z - 2 * r.w * r.y;
m._21 = 2 * r.x * r.y - 2 * r.w * r.z;
m._22 = r.w * r.w + r.y * r.y - r.x * r.x - r.z * r.z;
m._23 = 2 * r.y * r.z + 2 * r.w * r.x;
m._31 = 2 * r.x * r.z + 2 * r.w * r.y;
m._32 = 2 * r.y * r.z - 2 * r.w * r.x;
m._33 = r.w * r.w + r.z * r.z - r.x * r.x - r.y * r.y;
// Extract translation
Quaternion t0 = Quaternion::Scale(q.qDual, 2);
Quaternion t = Quaternion::Multiply(t0, Quaternion::Conjugate(q.qReal));
m._41 = t.value.x;
m._42 = t.value.y;
m._43 = t.value.z;
return m;
}
};
/////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 8edf7d0e28b62ca4094ee053fe36a5b0
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/DualQuaternion.hlsl
uploadId: 897522
@@ -0,0 +1,35 @@
#ifndef HUMAN_ROTATION_DATA_HLSL_
#define HUMAN_ROTATION_DATA_HLSL_
/////////////////////////////////////////////////////////////////////////////////
struct HumanRotationData
{
float3 minMuscleAngles, maxMuscleAngles;
Quaternion preRot, postRot;
float3 sign;
static const uint size = (3 + 3 + 4 + 4 + 3) * 4;
static HumanRotationData ReadFromRawBuffer(ByteAddressBuffer b, int index)
{
int byteAddress = index * size;
HumanRotationData rv = (HumanRotationData)0;
rv.minMuscleAngles = asfloat(b.Load3(byteAddress + 0));
rv.maxMuscleAngles = asfloat(b.Load3(byteAddress + 12));
rv.preRot.value = asfloat(b.Load4(byteAddress + 24));
rv.postRot.value = asfloat(b.Load4(byteAddress + 40));
rv.sign = asfloat(b.Load3(byteAddress + 56));
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_HUMAN_ROTATION_DATA_READ, byteAddress, size, b);
return rv;
}
};
/////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 9d46c9cd9b4c75d4188bf5e6914efc46
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/HumanRotationData.hlsl
uploadId: 897522
@@ -0,0 +1,36 @@
#ifndef KEYFRAME_HLSL_
#define KEYFRAME_HLSL_
/////////////////////////////////////////////////////////////////////////////////
struct KeyFrame
{
float v;
float inTan;
float outTan;
float time;
static const uint size = 4 * 4;
static KeyFrame ReadFromRawBuffer(ByteAddressBuffer b, int baseAddress, int index)
{
int addr = baseAddress + index * size;
float4 v = asfloat(b.Load4(addr));
KeyFrame rv = (KeyFrame)0;
rv.v = v.x;
rv.inTan = v.y;
rv.outTan = v.z;
rv.time = v.w;
//CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_KEY_FRAME_READ, addr, size, b);
return rv;
}
};
/////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: ecfd91f2fbf55b847880b642d5da4cc8
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/KeyFrame.hlsl
uploadId: 897522
@@ -0,0 +1,64 @@
#ifndef PERFECT_HASH_TABLE_HLSL_
#define PERFECT_HASH_TABLE_HLSL_
/////////////////////////////////////////////////////////////////////////////////
// High-Order Half of 64-Bit Product
// Ref "Hackers Delight" 8-2
uint MultiplyHighUnsigned(uint u, uint v)
{
uint u0 = u & 0xffff;
uint u1 = u >> 16;
uint v0 = v & 0xffff;
uint v1 = v >> 16;
uint w0 = u0 * v0;
uint t = u1 * v0 + (w0 >> 16);
uint w1 = t & 0xFFFF;
uint w2 = t >> 16;
w1 = u0 * v1 + w1;
return u1 * v1 + w2 + (w1 >> 16);
}
/////////////////////////////////////////////////////////////////////////////////
uint HashLemer(uint v, uint seed)
{
seed = v ^ seed;
uint tmpH = MultiplyHighUnsigned(seed, 0x4a39b70d);
uint tmpL = seed * 0x4a39b70d;
uint m1 = tmpH ^ tmpL;
tmpH = MultiplyHighUnsigned(m1, 0x12fad5c9);
tmpL = m1 * 0x12fad5c9;
uint m2 = tmpH ^ tmpL;
return m2;
}
/////////////////////////////////////////////////////////////////////////////////
uint GetValueIndex(uint v, uint seed, uint modMask)
{
uint hv = HashLemer(v, seed);
uint rv = hv & modMask;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
int QueryPerfectHashTable(uint v, uint seed, uint phtAddress, uint sizeMask)
{
uint index = GetValueIndex(v, seed, sizeMask);
uint byteAddress = phtAddress + index * 4 * 2;
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_QUERY_PERFECT_HASH_TABLE_ANIMATION_CLIPS_READ0, byteAddress, 8, animationClips);
uint2 phtv = animationClips.Load2(byteAddress);
if (phtv.x == v)
return (int)(phtv.y & 0xffff);
uint nextIndex = phtv.y >> 16;
uint byteAddress2 = phtAddress + nextIndex * 4 * 2;
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_QUERY_PERFECT_HASH_TABLE_ANIMATION_CLIPS_READ1, byteAddress2, 8, animationClips);
uint2 phtv2 = animationClips.Load2(byteAddress2);
if (phtv2.x == v)
return (int)(phtv2.y & 0xffff);
return -1;
}
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: cd4cb6713dc57034da2b5a2cda9cf73f
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/PerfectHashTable.hlsl
uploadId: 897522
@@ -0,0 +1,216 @@
#ifndef QUATERNION_HLSL_
#define QUATERNION_HLSL_
/////////////////////////////////////////////////////////////////////////////////
struct Quaternion
{
float4 value;
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Identity()
{
Quaternion rv;
rv.value = float4(0, 0, 0, 1);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static float3 Rotate(Quaternion q, float3 v)
{
float3 t = 2 * cross(q.value.xyz, v);
float3 rv = v + q.value.w * t + cross(q.value.xyz, t);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Multiply(Quaternion a, Quaternion b)
{
Quaternion rv;
rv.value = float4(a.value.wwww * b.value + (a.value.xyzx * b.value.wwwx + a.value.yzxy * b.value.zxyy) * float4(1.0f, 1.0f, 1.0f, -1.0f) - a.value.zxyz * b.value.yzxz);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion ShortestRotation(Quaternion from, Quaternion to)
{
Quaternion rv;
uint4 sign = asuint(dot(from.value, to.value)) & 0x80000000;
rv.value = asfloat(sign ^ asuint(to.value));
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion AxisAngle(float3 axis, float angle)
{
float sina, cosa;
sincos(0.5f * angle, sina, cosa);
Quaternion rv;
rv.value = float4(axis * sina, cosa);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Normalize(Quaternion q)
{
float4 x = q.value;
Quaternion rv;
rv.value = rsqrt(dot(x, x)) * x;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Inverse(Quaternion q)
{
float4 a = q.value;
Quaternion rv;
rv.value = rcp(dot(a, a)) * a * float4(-1.0f, -1.0f, -1.0f, 1.0f);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Conjugate(Quaternion q)
{
Quaternion rv;
rv.value = q.value * float4(-1, -1, -1, 1);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion NormalizeSafe(Quaternion q)
{
float FLT_MIN_NORMAL = 1.175494351e-38F;
float len = dot(q.value, q.value);
Quaternion rv;
rv.value = float4(0, 0, 0, 1);
if (len > FLT_MIN_NORMAL)
{
rv.value = q.value * rsqrt(len);
}
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion FromMatrix(float3x3 m)
{
Quaternion rv;
float3 u = m._m00_m10_m20;
float3 v = m._m01_m11_m21;
float3 w = m._m02_m12_m22;
uint u_sign = asuint(u.x) & 0x80000000;
float t = v.y + asfloat(asuint(w.z) ^ u_sign);
uint1 u_mask1 = (int)u_sign >> 31;
uint1 t_mask1 = asint(t) >> 31;
uint4 u_mask = u_mask1.xxxx;
uint4 t_mask = t_mask1.xxxx;
uint4 u_mask_inv = uint4(~u_mask1.x, ~u_mask1.x, ~u_mask1.x, ~u_mask1.x);
uint4 t_mask_inv = uint4(~t_mask1.x, ~t_mask1.x, ~t_mask1.x, ~t_mask1.x);
float tr = 1.0f + abs(u.x);
uint4 sign_flips = uint4(0x00000000, 0x80000000, 0x80000000, 0x80000000) ^ (u_mask & uint4(0x00000000, 0x80000000, 0x00000000, 0x80000000)) ^ (t_mask & uint4(0x80000000, 0x80000000, 0x80000000, 0x00000000));
rv.value = float4(tr, u.y, w.x, v.z) + asfloat(asuint(float4(t, v.x, u.z, w.y)) ^ sign_flips); // +---, +++-, ++-+, +-++
rv.value = asfloat((asuint(rv.value) & u_mask_inv) | (asuint(rv.value.zwxy) & u_mask));
rv.value = asfloat((asuint(rv.value.wzyx) & t_mask_inv) | (asuint(rv.value) & t_mask));
rv.value = normalize(rv.value);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
float3x3 ToRotationMatrix()
{
float4 value2 = value * 2;
uint3 npn = uint3(0x80000000, 0x00000000, 0x80000000);
uint3 nnp = uint3(0x80000000, 0x80000000, 0x00000000);
uint3 pnn = uint3(0x00000000, 0x80000000, 0x80000000);
float3 c0 = value2.y * asfloat(asuint(value.yxw) ^ npn) - value2.z * asfloat(asuint(value.zwx) ^ pnn) + float3(1, 0, 0);
float3 c1 = value2.z * asfloat(asuint(value.wzy) ^ nnp) - value2.x * asfloat(asuint(value.yxw) ^ npn) + float3(0, 1, 0);
float3 c2 = value2.x * asfloat(asuint(value.zwx) ^ pnn) - value2.y * asfloat(asuint(value.wzy) ^ nnp) + float3(0, 0, 1);
float3x3 rv = float3x3(c0, c1, c2);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion EulerXYZ(float3 eulerAngles)
{
float3 sinV, cosV;
sincos(0.5f * eulerAngles, sinV, cosV);
Quaternion rv;
rv.value = float4(sinV.xyz, cosV.x) * cosV.yxxy * cosV.zzyz + sinV.yxxy * sinV.zzyz * float4(cosV.xyz, sinV.x) * float4(-1.0f, 1.0f, -1.0f, 1.0f);
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static float4 ChangeSign(float4 a, float4 b)
{
return asfloat(asuint(a) ^ asuint(b) & 0x80000000);
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Scale(Quaternion q, float s)
{
Quaternion rv;
rv.value = q.value * s;
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Nlerp(Quaternion a, Quaternion b, float t)
{
Quaternion rv;
rv.value = normalize(a.value + t * (ChangeSign(b.value, dot(a.value, b.value)) - a.value));
return rv;
}
/////////////////////////////////////////////////////////////////////////////////
static Quaternion Slerp(Quaternion a, Quaternion b, float t)
{
float dt = dot(a.value, b.value);
if (dt < 0)
{
dt = -dt;
b.value = -b.value;
}
if (dt < 0.9995f)
{
float angle = acos(dt);
float s = rsqrt(1 - dt * dt);
float w1 = sin(angle * (1 - t)) * s;
float w2 = sin(angle * t) * s;
Quaternion rv;
rv.value = a.value * w1 + b.value * w2;
return rv;
}
return Nlerp(a, b, t);
}
};
/////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: 4e63b0f0c621a4b47ab8c2ddfa5439b5
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/Quaternion.hlsl
uploadId: 897522
@@ -0,0 +1,37 @@
#ifndef RIG_BONE_HLSL_
#define RIG_BONE_HLSL_
/////////////////////////////////////////////////////////////////////////////////
struct RigBone
{
uint hash;
int parentBoneIndex;
BoneTransform refPose;
int humanBodyPart;
static const uint size = (1 + 1 + 1) * 4 + BoneTransform::size;
static RigBone ReadFromRawBuffer(ByteAddressBuffer b, int index)
{
int byteAddress = index * size;
RigBone rv = (RigBone)0;
rv.hash = b.Load(byteAddress);
rv.parentBoneIndex = b.Load(byteAddress + 4);
rv.refPose.pos = asfloat(b.Load3(byteAddress + 8));
rv.refPose.rot.value = asfloat(b.Load4(byteAddress + 20));
rv.refPose.scale = asfloat(b.Load3(byteAddress + 36));
rv.humanBodyPart = b.Load(byteAddress + 48);
CHECK_RAW_BUFFER_OUT_OF_BOUNDS(RUKHANKADEBUGMARKERS_GPUANIMATOR_RIG_BONE_READ, byteAddress, size, b);
return rv;
}
};
/////////////////////////////////////////////////////////////////////////////////
#endif
@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: a4b682d628d7a5f4ab07b948d0360648
ShaderIncludeImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 298480
packageName: Rukhanka Animation System 2
packageVersion: 2.9.0
assetPath: Packages/com.rukhanka.animation/Rukhanka.Runtime/Common/Shaders/GPUStructures/RigBone.hlsl
uploadId: 897522

Some files were not shown because too many files have changed in this diff Show More