using System; using Unity.Collections; namespace UnityEngine.Animations.Rigging { /// /// The MultiAim constraint job. /// [Unity.Burst.BurstCompile] public struct MultiAimConstraintJob : IWeightedAnimationJob { const float k_Epsilon = 1e-5f; /// /// Specifies how the world up vector used by the Multi-Aim constraint is defined. /// public enum WorldUpType { /// Neither defines nor uses a world up vector. None, /// Uses and defines the world up vector as the Unity Scene up vector (the Y axis). SceneUp, /// Uses and defines the world up vector as a vector from the constrained object, in the direction of the up object. ObjectUp, /// Uses and defines the world up vector as relative to the local space of the object. ObjectRotationUp, /// Uses and defines the world up vector as a vector specified by the user. Vector }; /// 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-rotation 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; /// Local aim axis of the constrained object Transform. public Vector3 aimAxis; /// Local up axis of the constrained object Transform. public Vector3 upAxis; /// /// Specifies which mode to use to keep the upward direction of the constrained Object. /// public WorldUpType worldUpType; /// /// A static vector in world coordinates that is the general upward direction. This is used when World Up Type is set to WorldUpType.Vector. /// public Vector3 worldUpAxis; /// /// The Transform handle for the world up object. This is used when World Up Type is set to WorldUpType.ObjectUp or WorldUpType.ObjectRotationUp. /// public ReadOnlyTransformHandle worldUpObject; /// 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; /// Minimum rotation value. public FloatProperty minLimit; /// Maximum rotation value. public FloatProperty maxLimit; /// 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; } var weightScale = sumWeights > 1f ? 1f / sumWeights : 1f; var drivenParentInvRot = Quaternion.Inverse(drivenParent.GetRotation(stream)); var drivenParentInvWorldMatrix = Matrix4x4.Inverse(drivenParent.GetLocalToWorldMatrix(stream)); var accumWeights = 0f; var accumDeltaRot = QuaternionExt.zero; var drivenWPos = driven.GetPosition(stream); var drivenLRot = driven.GetLocalRotation(stream); var worldUpVector = ComputeWorldUpVector(stream); var upVector = AnimationRuntimeUtils.Select(Vector3.zero, upAxis, axesMask); var hasMasks = Vector3.Dot(axesMask, axesMask) < 3f; var hasUpAxisCorrection = worldUpType != WorldUpType.None && Vector3.Dot(upVector, upVector) > k_Epsilon; var minMaxAngles = new Vector2(minLimit.Get(stream), maxLimit.Get(stream)); for (int i = 0; i < sourceTransforms.Length; ++i) { var normalizedWeight = weightBuffer[i] * weightScale; if (normalizedWeight < k_Epsilon) continue; var sourceTransform = sourceTransforms[i]; var fromDir = drivenLRot * aimAxis; var toDir = drivenParentInvWorldMatrix.MultiplyVector(sourceTransform.GetPosition(stream) - drivenWPos); if (toDir.sqrMagnitude < k_Epsilon) continue; var crossDir = Vector3.Cross(fromDir, toDir).normalized; if (hasMasks) { crossDir = AnimationRuntimeUtils.Select(Vector3.zero, crossDir, axesMask).normalized; if (Vector3.Dot(crossDir, crossDir) > k_Epsilon) { fromDir = AnimationRuntimeUtils.ProjectOnPlane(fromDir, crossDir); toDir = AnimationRuntimeUtils.ProjectOnPlane(toDir, crossDir); } else { toDir = fromDir; } } var rotToSource = Quaternion.AngleAxis( Mathf.Clamp(Vector3.Angle(fromDir, toDir), minMaxAngles.x, minMaxAngles.y), crossDir ); if (hasUpAxisCorrection) { var wupProject = Vector3.Cross(Vector3.Cross(drivenParentInvRot * worldUpVector, toDir).normalized, toDir).normalized; var rupProject = Vector3.Cross(Vector3.Cross(rotToSource * drivenLRot * upVector, toDir).normalized, toDir).normalized; rotToSource = QuaternionExt.FromToRotation(rupProject, wupProject) * rotToSource; } accumDeltaRot = QuaternionExt.Add( accumDeltaRot, QuaternionExt.Scale(sourceOffsets[i] * rotToSource, normalizedWeight) ); // Required to update handles with binding info. sourceTransforms[i] = sourceTransform; accumWeights += normalizedWeight; } accumDeltaRot = QuaternionExt.NormalizeSafe(accumDeltaRot); if (accumWeights < 1f) accumDeltaRot = Quaternion.Lerp(Quaternion.identity, accumDeltaRot, accumWeights); var newRot = accumDeltaRot * drivenLRot; if (hasMasks) newRot = Quaternion.Euler(AnimationRuntimeUtils.Select(drivenLRot.eulerAngles, newRot.eulerAngles, axesMask)); var offset = drivenOffset.Get(stream); if (Vector3.Dot(offset, offset) > 0f) newRot *= Quaternion.Euler(offset); driven.SetLocalRotation(stream, Quaternion.Lerp(drivenLRot, newRot, w)); } else AnimationRuntimeUtils.PassThrough(stream, driven); } Vector3 ComputeWorldUpVector(AnimationStream stream) { var result = Vector3.up; switch (worldUpType) { case WorldUpType.None: result = Vector3.zero; break; case WorldUpType.SceneUp: // the scene Up vector and the World Up vector are the same thing break; case WorldUpType.ObjectUp: { // the target's Up vector points to the up object var referencePos = Vector3.zero; if (worldUpObject.IsValid(stream)) referencePos = worldUpObject.GetPosition(stream); var targetPos = driven.GetPosition(stream); result = (referencePos - targetPos).normalized; break; } case WorldUpType.ObjectRotationUp: { var upRotation = Quaternion.identity; if (worldUpObject.IsValid(stream)) upRotation = worldUpObject.GetRotation(stream); // if no object is specified, the up vector is defined relative to the scene world space result = upRotation * worldUpAxis; break; } case WorldUpType.Vector: result = worldUpAxis; break; default: break; } return result; } } /// /// This interface defines the data mapping for the MultiAim constraint. /// public interface IMultiAimConstraintData { /// The Transform affected by the constraint Source Transforms. Transform constrainedObject { get; } /// /// The list of Transforms that influence the constrained Transform orientation. /// Each source has a weight from 0 to 1. /// WeightedTransformArray sourceObjects { get; } /// /// This is used to maintain the current rotation offset from the constrained GameObject to the source GameObjects. /// bool maintainOffset { get; } /// Specifies the local aim axis of the constrained Transform to use in order to orient itself to the Source Transforms. Vector3 aimAxis { get; } /// Specified the local up axis of the constrained Transform to use in order to orient itself to the Source Transforms. Vector3 upAxis { get; } /// /// Specifies which mode to use to keep the upward direction of the constrained Object. /// /// int worldUpType { get; } /// /// A static vector in world coordinates that is the general upward direction. This is used when World Up Type is set to WorldUpType.Vector. /// Vector3 worldUpAxis { get; } /// /// The Transform used to calculate the upward direction. This is used when World Up Type is set to WorldUpType.ObjectUp or WorldUpType.ObjectRotationUp. /// Transform worldUpObject { get; } /// Toggles whether the constrained Transform will rotate along the X axis. bool constrainedXAxis { get; } /// Toggles whether the constrained Transform will rotate along the Y axis. bool constrainedYAxis { get; } /// Toggles whether the constrained Transform will rotate along the Z axis. bool constrainedZAxis { get; } /// The path to the offset property in the constraint component. string offsetVector3Property { get; } /// The path to the minimum limit property in the constraint component. string minLimitFloatProperty { get; } /// The path to the maximum limit property in the constraint component. string maxLimitFloatProperty { get; } /// The path to the source objects property in the constraint component. string sourceObjectsProperty { get; } } /// /// The MultiAim constraint job binder. /// /// The constraint data type public class MultiAimConstraintJobBinder : AnimationJobBinder where T : struct, IAnimationJobData, IMultiAimConstraintData { /// public override MultiAimConstraintJob Create(Animator animator, ref T data, Component component) { var job = new MultiAimConstraintJob(); job.driven = ReadWriteTransformHandle.Bind(animator, data.constrainedObject); job.drivenParent = ReadOnlyTransformHandle.Bind(animator, data.constrainedObject.parent); job.aimAxis = data.aimAxis; job.upAxis = data.upAxis; job.worldUpType = (MultiAimConstraintJob.WorldUpType)data.worldUpType; job.worldUpAxis = data.worldUpAxis; if (data.worldUpObject != null) job.worldUpObject = ReadOnlyTransformHandle.Bind(animator, data.worldUpObject); 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); for (int i = 0; i < sourceObjects.Count; ++i) { if (data.maintainOffset) { var constrainedAim = data.constrainedObject.rotation * data.aimAxis; job.sourceOffsets[i] = QuaternionExt.FromToRotation( sourceObjects[i].transform.position - data.constrainedObject.position, constrainedAim ); } else job.sourceOffsets[i] = Quaternion.identity; } job.minLimit = FloatProperty.Bind(animator, component, data.minLimitFloatProperty); job.maxLimit = FloatProperty.Bind(animator, component, data.maxLimitFloatProperty); job.drivenOffset = Vector3Property.Bind(animator, component, data.offsetVector3Property); 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(MultiAimConstraintJob job) { job.sourceTransforms.Dispose(); job.sourceWeights.Dispose(); job.sourceOffsets.Dispose(); job.weightBuffer.Dispose(); } } }