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 bonePosesOffsetsArr; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [BurstCompile] public void OnCreate(ref SystemState ss) { InitializeRuntimeData(ref ss); bonePosesOffsetsArr = new (Allocator.Persistent); var eqb0 = new EntityQueryBuilder(Allocator.Temp) .WithAll() .WithDisabled(); animatedObjectQuery = ss.GetEntityQuery(eqb0); ss.RequireForUpdate(animatedObjectQuery); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [BurstCompile] public void OnDestroy(ref SystemState ss) { bonePosesOffsetsArr.Dispose(); if (SystemAPI.TryGetSingleton(out var rad)) { rad.Dispose(); ss.EntityManager.DestroyEntity(SystemAPI.GetSingletonEntity()); } } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void InitializeRuntimeData(ref SystemState ss) { var rad = RuntimeAnimationData.MakeDefault(); ss.EntityManager.CreateSingleton(rad, "Rukhanka.RuntimeAnimationData"); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// JobHandle PrepareComputationData(ref SystemState ss, NativeArray chunkBaseEntityIndices, ref RuntimeAnimationData runtimeData, NativeList entitiesArr, JobHandle dependsOn) { var rigDefinitionTypeHandle = SystemAPI.GetComponentTypeHandle(true); var cullAnimationsTagComponentLookup = SystemAPI.GetComponentLookup(true); var rigDefTypeHandle = SystemAPI.GetComponentTypeHandle(); // 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 entitiesArr, in RuntimeAnimationData runtimeData, JobHandle dependsOn) { var animationToProcessBufferLookup = SystemAPI.GetBufferLookup(true); var rootMotionAnimationStateBufferLookupRW = SystemAPI.GetBufferLookup(); var rigDefsArr = animatedObjectQuery.ToComponentDataListAsync(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(true); var gpuAnimationEngineTagLookup = SystemAPI.GetComponentLookup(true); var parentComponentLookup = SystemAPI.GetComponentLookup(); var ptmComponentLookup = SystemAPI.GetComponentLookup(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(true); var makeAbsTransformsJob = new MakeAbsoluteTransformsJob() { localBoneTransforms = runtimeData.animatedBonesBuffer, worldBoneTransforms = runtimeData.worldSpaceBonesBuffer, boneTransformFlags = runtimeData.boneTransformFlagsHolderArr, rigDefTypeHandle = rigDefTypeHandle }; var query = SystemAPI.QueryBuilder() .WithAll() .WithNone() .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(true), ptmLookup = SystemAPI.GetComponentLookup(true), localTransformLookup = SystemAPI.GetComponentLookup(true), }; var jh = computeRootMotionJob.ScheduleParallel(dependsOn); return jh; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// JobHandle AnimateBlendShapeWeights(ref SystemState ss, JobHandle dependsOn) { var animateBlendWeightsJob = new AnimateBlendShapeWeightsJob() { animationToProcessLookup = SystemAPI.GetBufferLookup(true), cullAnimationsLookup = SystemAPI.GetComponentLookup(true) }; var jh = animateBlendWeightsJob.ScheduleParallel(dependsOn); return jh; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// [BurstCompile] public void OnUpdate(ref SystemState ss) { ref var runtimeData = ref SystemAPI.GetSingletonRW().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; } } }