using Unity.Collections; namespace UnityEngine.Animations.Rigging { /// /// The ChainIK constraint job. /// [Unity.Burst.BurstCompile] public struct ChainIKConstraintJob : IWeightedAnimationJob { /// An array of Transform handles that represents the Transform chain. public NativeArray chain; /// The Transform handle for the target Transform. public ReadOnlyTransformHandle target; /// The offset applied to the target transform if maintainTargetPositionOffset or maintainTargetRotationOffset is enabled. public AffineTransform targetOffset; /// An array of length in between Transforms in the chain. public NativeArray linkLengths; /// An array of positions for Transforms in the chain. public NativeArray linkPositions; /// The weight for which ChainIK target has an effect on chain (up to tip Transform). This is a value in between 0 and 1. public FloatProperty chainRotationWeight; /// The weight for which ChainIK target has and effect on tip Transform. This is a value in between 0 and 1. public FloatProperty tipRotationWeight; /// CacheIndex to ChainIK tolerance value. /// public CacheIndex toleranceIdx; /// CacheIndex to ChainIK maxIterations value. /// public CacheIndex maxIterationsIdx; /// Cache for static properties in the job. public AnimationJobCache cache; /// The maximum distance the Transform chain can reach. public float maxReach; /// public FloatProperty jobWeight { get; set; } /// /// Defines what to do when processing the root motion. /// /// The animation stream to work on. public void ProcessRootMotion(AnimationStream stream) { } /// /// Defines what to do when processing the animation. /// /// The animation stream to work on. public void ProcessAnimation(AnimationStream stream) { float w = jobWeight.Get(stream); if (w > 0f) { for (int i = 0; i < chain.Length; ++i) { var handle = chain[i]; linkPositions[i] = handle.GetPosition(stream); chain[i] = handle; } int tipIndex = chain.Length - 1; if (AnimationRuntimeUtils.SolveFABRIK(ref linkPositions, ref linkLengths, target.GetPosition(stream) + targetOffset.translation, cache.GetRaw(toleranceIdx), maxReach, (int)cache.GetRaw(maxIterationsIdx))) { var chainRWeight = chainRotationWeight.Get(stream) * w; for (int i = 0; i < tipIndex; ++i) { var prevDir = chain[i + 1].GetPosition(stream) - chain[i].GetPosition(stream); var newDir = linkPositions[i + 1] - linkPositions[i]; var rot = chain[i].GetRotation(stream); chain[i].SetRotation(stream, Quaternion.Lerp(rot, QuaternionExt.FromToRotation(prevDir, newDir) * rot, chainRWeight)); } } chain[tipIndex].SetRotation( stream, Quaternion.Lerp( chain[tipIndex].GetRotation(stream), target.GetRotation(stream) * targetOffset.rotation, tipRotationWeight.Get(stream) * w ) ); } else { for (int i = 0; i < chain.Length; ++i) AnimationRuntimeUtils.PassThrough(stream, chain[i]); } } } /// /// This interface defines the data mapping for the ChainIK constraint. /// public interface IChainIKConstraintData { /// The root Transform of the ChainIK hierarchy. Transform root { get; } /// The tip Transform of the ChainIK hierarchy. The tip needs to be a descendant/child of the root Transform. Transform tip { get; } /// The ChainIK target Transform. Transform target { get; } /// The maximum number of iterations allowed for the ChainIK algorithm to converge to a solution. int maxIterations { get; } /// /// The allowed distance between the tip and target Transform positions. /// When the distance is smaller than the tolerance, the algorithm has converged on a solution and will stop. /// float tolerance { get; } /// This is used to maintain the current position offset from the tip Transform to target Transform. bool maintainTargetPositionOffset { get; } /// This is used to maintain the current rotation offset from the tip Transform to target Transform. bool maintainTargetRotationOffset { get; } /// The path to the chain rotation weight property in the constraint component. string chainRotationWeightFloatProperty { get; } /// The path to the tip rotation weight property in the constraint component. string tipRotationWeightFloatProperty { get; } } /// /// The ChainIK constraint job binder. /// /// The constraint data type public class ChainIKConstraintJobBinder : AnimationJobBinder where T : struct, IAnimationJobData, IChainIKConstraintData { /// public override ChainIKConstraintJob Create(Animator animator, ref T data, Component component) { Transform[] chain = ConstraintsUtils.ExtractChain(data.root, data.tip); var job = new ChainIKConstraintJob(); job.chain = new NativeArray(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); job.linkLengths = new NativeArray(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); job.linkPositions = new NativeArray(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); job.maxReach = 0f; int tipIndex = chain.Length - 1; for (int i = 0; i < chain.Length; ++i) { job.chain[i] = ReadWriteTransformHandle.Bind(animator, chain[i]); job.linkLengths[i] = (i != tipIndex) ? Vector3.Distance(chain[i].position, chain[i + 1].position) : 0f; job.maxReach += job.linkLengths[i]; } job.target = ReadOnlyTransformHandle.Bind(animator, data.target); job.targetOffset = AffineTransform.identity; if (data.maintainTargetPositionOffset) job.targetOffset.translation = data.tip.position - data.target.position; if (data.maintainTargetRotationOffset) job.targetOffset.rotation = Quaternion.Inverse(data.target.rotation) * data.tip.rotation; job.chainRotationWeight = FloatProperty.Bind(animator, component, data.chainRotationWeightFloatProperty); job.tipRotationWeight = FloatProperty.Bind(animator, component, data.tipRotationWeightFloatProperty); var cacheBuilder = new AnimationJobCacheBuilder(); job.maxIterationsIdx = cacheBuilder.Add(data.maxIterations); job.toleranceIdx = cacheBuilder.Add(data.tolerance); job.cache = cacheBuilder.Build(); return job; } /// public override void Destroy(ChainIKConstraintJob job) { job.chain.Dispose(); job.linkLengths.Dispose(); job.linkPositions.Dispose(); job.cache.Dispose(); } /// public override void Update(ChainIKConstraintJob job, ref T data) { job.cache.SetRaw(data.maxIterations, job.maxIterationsIdx); job.cache.SetRaw(data.tolerance, job.toleranceIdx); } } }