using Unity.Collections; namespace UnityEngine.Animations.Rigging { /// /// The MultiPosition constraint job. /// [Unity.Burst.BurstCompile] public struct MultiPositionConstraintJob : 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; /// The post-translation offset applied to the constrained object. public Vector3Property drivenOffset; /// 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; /// 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 axesMask; /// 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; var currentWPos = driven.GetPosition(stream); var drivenPos = currentWPos; Vector3 accumPos = currentWPos; for (int i = 0; i < sourceTransforms.Length; ++i) { var normalizedWeight = weightBuffer[i] * weightScale; if (normalizedWeight < k_Epsilon) continue; ReadOnlyTransformHandle sourceTransform = sourceTransforms[i]; accumPos += (sourceTransform.GetPosition(stream) + sourceOffsets[i] - currentWPos) * normalizedWeight; // Required to update handles with binding info. sourceTransforms[i] = sourceTransform; } var parentTx = AffineTransform.identity; // Convert accumPos and drivenPos to local space if (drivenParent.IsValid(stream)) { drivenParent.GetGlobalTR(stream, out Vector3 parentWPos, out Quaternion parentWRot); parentTx = new AffineTransform(parentWPos, parentWRot); accumPos = parentTx.InverseTransform(accumPos); drivenPos = parentTx.InverseTransform(drivenPos); } if (Vector3.Dot(axesMask, axesMask) < 3f) accumPos = AnimationRuntimeUtils.Lerp(drivenPos, accumPos, axesMask); // Convert accumPos back to world space accumPos = parentTx * (accumPos + drivenOffset.Get(stream)); driven.SetPosition(stream, Vector3.Lerp(currentWPos, accumPos, w)); } else AnimationRuntimeUtils.PassThrough(stream, driven); } } /// /// This interface defines the data mapping for the MultiPosition constraint. /// public interface IMultiPositionConstraintData { /// The Transform affected by the constraint Source Transforms. Transform constrainedObject { get; } /// /// The list of Transforms that influence the constrained Transform position. /// 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 maintainOffset { get; } /// The path to the offset property in the constraint component. string offsetVector3Property { get; } /// The path to the source objects property in the constraint component. string sourceObjectsProperty { get; } /// Toggles whether the constrained transform will translate along the X axis. bool constrainedXAxis { get; } /// Toggles whether the constrained transform will translate along the Y axis. bool constrainedYAxis { get; } /// Toggles whether the constrained transform will translate along the Z axis. bool constrainedZAxis { get; } } /// /// The MultiPosition constraint job binder. /// /// The constraint data type public class MultiPositionConstraintJobBinder : AnimationJobBinder where T : struct, IAnimationJobData, IMultiPositionConstraintData { /// public override MultiPositionConstraintJob Create(Animator animator, ref T data, Component component) { var job = new MultiPositionConstraintJob(); job.driven = ReadWriteTransformHandle.Bind(animator, data.constrainedObject); job.drivenParent = ReadOnlyTransformHandle.Bind(animator, data.constrainedObject.parent); job.drivenOffset = Vector3Property.Bind(animator, component, data.offsetVector3Property); 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); Vector3 drivenPos = data.constrainedObject.position; for (int i = 0; i < sourceObjects.Count; ++i) { job.sourceOffsets[i] = data.maintainOffset ? (drivenPos - sourceObjects[i].transform.position) : Vector3.zero; } job.axesMask = new Vector3( System.Convert.ToSingle(data.constrainedXAxis), System.Convert.ToSingle(data.constrainedYAxis), System.Convert.ToSingle(data.constrainedZAxis) ); return job; } /// public override void Destroy(MultiPositionConstraintJob job) { job.sourceTransforms.Dispose(); job.sourceWeights.Dispose(); job.sourceOffsets.Dispose(); job.weightBuffer.Dispose(); } } }