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();
}
}
}