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

303 lines
11 KiB
C#

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