using Unity.Collections; namespace UnityEngine.Animations.Rigging { /// /// The MultiParent constraint job. /// [Unity.Burst.BurstCompile] public struct MultiParentConstraintJob : IWeightedAnimationJob { const float k_Epsilon = 1e-5f; /// The Transform handle for the constrained object Transform. public ReadWriteTransformHandle driven; /// The Transform handle for the constrained object parent Transform. public ReadOnlyTransformHandle drivenParent; /// List of Transform handles for the source objects. public NativeArray sourceTransforms; /// List of weights for the source objects. public NativeArray sourceWeights; /// List of offsets to apply to source rotations if maintainOffset is enabled. public NativeArray sourceOffsets; /// Buffer used to store weights during job execution. public NativeArray weightBuffer; /// Position axes mask. Position will apply on the local axis for a value of 1.0, and will be kept as is for a value of 0.0. public Vector3 positionAxesMask; /// Rotation axes mask. Rotation will apply on the local axis for a value of 1.0, and will be kept as is for a value of 0.0. public Vector3 rotationAxesMask; /// The main weight given to the constraint. This is a value in between 0 and 1. 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) { AnimationStreamHandleUtility.ReadFloats(stream, sourceWeights, weightBuffer); float sumWeights = AnimationRuntimeUtils.Sum(weightBuffer); if (sumWeights < k_Epsilon) { AnimationRuntimeUtils.PassThrough(stream, driven); return; } float weightScale = sumWeights > 1f ? 1f / sumWeights : 1f; float accumWeights = 0f; var accumTx = new AffineTransform(Vector3.zero, QuaternionExt.zero); for (int i = 0; i < sourceTransforms.Length; ++i) { ReadOnlyTransformHandle sourceTransform = sourceTransforms[i]; var normalizedWeight = weightBuffer[i] * weightScale; if (normalizedWeight < k_Epsilon) continue; sourceTransform.GetGlobalTR(stream, out Vector3 srcWPos, out Quaternion srcWRot); var sourceTx = new AffineTransform(srcWPos, srcWRot); sourceTx *= sourceOffsets[i]; accumTx.translation += sourceTx.translation * normalizedWeight; accumTx.rotation = QuaternionExt.Add(accumTx.rotation, QuaternionExt.Scale(sourceTx.rotation, normalizedWeight)); // Required to update handles with binding info. sourceTransforms[i] = sourceTransform; accumWeights += normalizedWeight; } driven.GetGlobalTR(stream, out Vector3 currentWPos, out Quaternion currentWRot); var drivenTx = new AffineTransform(currentWPos, currentWRot); accumTx.rotation = QuaternionExt.NormalizeSafe(accumTx.rotation); if (accumWeights < 1f) { accumTx.translation += currentWPos * (1f - accumWeights); accumTx.rotation = Quaternion.Lerp(currentWRot, accumTx.rotation, accumWeights); } var parentTx = AffineTransform.identity; // Convert accumTx and drivenTx to local space if (drivenParent.IsValid(stream)) { drivenParent.GetGlobalTR(stream, out Vector3 parentWPos, out Quaternion parentWRot); parentTx = new AffineTransform(parentWPos, parentWRot); accumTx = parentTx.InverseMul(accumTx); drivenTx = parentTx.InverseMul(drivenTx); } if (Vector3.Dot(positionAxesMask, positionAxesMask) < 3f) accumTx.translation = AnimationRuntimeUtils.Lerp(drivenTx.translation, accumTx.translation, positionAxesMask); if (Vector3.Dot(rotationAxesMask, rotationAxesMask) < 3f) accumTx.rotation = Quaternion.Euler(AnimationRuntimeUtils.Lerp(drivenTx.rotation.eulerAngles, accumTx.rotation.eulerAngles, rotationAxesMask)); // Convert accumTx back to world space accumTx = parentTx * accumTx; driven.SetGlobalTR( stream, Vector3.Lerp(currentWPos, accumTx.translation, w), Quaternion.Lerp(currentWRot, accumTx.rotation, w) ); } else AnimationRuntimeUtils.PassThrough(stream, driven); } } /// /// This interface defines the data mapping for the MultiParent constraint. /// public interface IMultiParentConstraintData { /// The Transform affected by the constraint Source Transforms. Transform constrainedObject { get; } /// /// The list of Transforms that influence the constrained Transform rotation. /// Each source has a weight from 0 to 1. /// WeightedTransformArray sourceObjects { get; } /// This is used to maintain the current position offset from the constrained GameObject to the source GameObjects. bool maintainPositionOffset { get; } /// This is used to maintain the current rotation offset from the constrained GameObject to the source GameObjects. bool maintainRotationOffset { get; } /// Toggles whether the constrained transform will translate along the X axis. bool constrainedPositionXAxis { get; } /// Toggles whether the constrained transform will translate along the Y axis. bool constrainedPositionYAxis { get; } /// Toggles whether the constrained transform will translate along the Z axis. bool constrainedPositionZAxis { get; } /// Toggles whether the constrained transform will rotate along the X axis. bool constrainedRotationXAxis { get; } /// Toggles whether the constrained transform will rotate along the Y axis. bool constrainedRotationYAxis { get; } /// Toggles whether the constrained transform will rotate along the Z axis. bool constrainedRotationZAxis { get; } /// The path to the source objects property in the constraint component. string sourceObjectsProperty { get; } } /// /// The MultiParent constraint job binder. /// /// The constraint data type public class MultiParentConstraintJobBinder : AnimationJobBinder where T : struct, IAnimationJobData, IMultiParentConstraintData { /// public override MultiParentConstraintJob Create(Animator animator, ref T data, Component component) { var job = new MultiParentConstraintJob(); job.driven = ReadWriteTransformHandle.Bind(animator, data.constrainedObject); job.drivenParent = ReadOnlyTransformHandle.Bind(animator, data.constrainedObject.parent); WeightedTransformArray sourceObjects = data.sourceObjects; WeightedTransformArrayBinder.BindReadOnlyTransforms(animator, component, sourceObjects, out job.sourceTransforms); WeightedTransformArrayBinder.BindWeights(animator, component, sourceObjects, data.sourceObjectsProperty, out job.sourceWeights); job.sourceOffsets = new NativeArray(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); job.weightBuffer = new NativeArray(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); var drivenTx = new AffineTransform(data.constrainedObject.position, data.constrainedObject.rotation); for (int i = 0; i < sourceObjects.Count; ++i) { var sourceTransform = sourceObjects[i].transform; var srcTx = new AffineTransform(sourceTransform.position, sourceTransform.rotation); var srcOffset = AffineTransform.identity; var tmp = srcTx.InverseMul(drivenTx); if (data.maintainPositionOffset) srcOffset.translation = tmp.translation; if (data.maintainRotationOffset) srcOffset.rotation = tmp.rotation; job.sourceOffsets[i] = srcOffset; } job.positionAxesMask = new Vector3( System.Convert.ToSingle(data.constrainedPositionXAxis), System.Convert.ToSingle(data.constrainedPositionYAxis), System.Convert.ToSingle(data.constrainedPositionZAxis) ); job.rotationAxesMask = new Vector3( System.Convert.ToSingle(data.constrainedRotationXAxis), System.Convert.ToSingle(data.constrainedRotationYAxis), System.Convert.ToSingle(data.constrainedRotationZAxis) ); return job; } /// public override void Destroy(MultiParentConstraintJob job) { job.sourceTransforms.Dispose(); job.sourceWeights.Dispose(); job.sourceOffsets.Dispose(); job.weightBuffer.Dispose(); } } }