namespace UnityEngine.Animations.Rigging
{
///
/// The OverrideTransform job.
///
[Unity.Burst.BurstCompile]
public struct OverrideTransformJob : IWeightedAnimationJob
{
///
/// The override space controls how the override source Transform
/// is copied unto constrained Transform.
///
public enum Space
{
/// Copy override world TR components into world TR components of the constrained Transform.
World = 0,
/// Copy override local TR components into local TR components of the constrained Transform.
Local = 1,
/// Add override local TR components to local TR components of the constrained Transform.
Pivot = 2
}
/// The Transform handle for the constrained object Transform.
public ReadWriteTransformHandle driven;
/// The Transform handle for the override source object Transform.
public ReadOnlyTransformHandle source;
/// Cached inverse local TR used in space switching calculations.
public AffineTransform sourceInvLocalBindTx;
/// Cached source to world coordinates quaternion. Used when Space is set to World.
public Quaternion sourceToWorldRot;
/// Cached source to local coordinates quaternion. Used when Space is set to Local.
public Quaternion sourceToLocalRot;
/// Cached source to pivot coordinates quaternion. Used when Space is set to Pivot.
public Quaternion sourceToPivotRot;
/// CacheIndex to the override space.
///
public CacheIndex spaceIdx;
/// CacheIndex to source to space quaternion used in current space.
///
public CacheIndex sourceToCurrSpaceRotIdx;
/// The override position.
public Vector3Property position;
/// The override rotation.
public Vector3Property rotation;
/// The weight for which override position has an effect on the constrained Transform. This is a value in between 0 and 1.
public FloatProperty positionWeight;
/// The weight for which override rotation has an effect on the constrained Transform. This is a value in between 0 and 1.
public FloatProperty rotationWeight;
/// Cache for static properties in the job.
public AnimationJobCache cache;
///
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)
{
AffineTransform overrideTx;
if (source.IsValid(stream))
{
source.GetLocalTRS(stream, out Vector3 srcLPos, out Quaternion srcLRot, out _);
var sourceLocalTx = new AffineTransform(srcLPos, srcLRot);
var sourceToSpaceRot = cache.Get(sourceToCurrSpaceRotIdx);
overrideTx = Quaternion.Inverse(sourceToSpaceRot) * (sourceInvLocalBindTx * sourceLocalTx) * sourceToSpaceRot;
}
else
overrideTx = new AffineTransform(position.Get(stream), Quaternion.Euler(rotation.Get(stream)));
Space overrideSpace = (Space)cache.GetRaw(spaceIdx);
var posW = positionWeight.Get(stream) * w;
var rotW = rotationWeight.Get(stream) * w;
switch (overrideSpace)
{
case Space.World:
{
driven.GetGlobalTR(stream, out Vector3 drivenWPos, out Quaternion drivenWRot);
driven.SetGlobalTR(
stream,
Vector3.Lerp(drivenWPos, overrideTx.translation, posW),
Quaternion.Lerp(drivenWRot, overrideTx.rotation, rotW)
);
}
break;
case Space.Local:
{
driven.GetLocalTRS(stream, out Vector3 drivenLPos, out Quaternion drivenLRot, out Vector3 drivenLScale);
driven.SetLocalTRS(
stream,
Vector3.Lerp(drivenLPos, overrideTx.translation, posW),
Quaternion.Lerp(drivenLRot, overrideTx.rotation, rotW),
drivenLScale
);
}
break;
case Space.Pivot:
{
driven.GetLocalTRS(stream, out Vector3 drivenLPos, out Quaternion drivenLRot, out Vector3 drivenLScale);
var drivenLocalTx = new AffineTransform(drivenLPos, drivenLRot);
overrideTx = drivenLocalTx * overrideTx;
driven.SetLocalTRS(
stream,
Vector3.Lerp(drivenLocalTx.translation, overrideTx.translation, posW),
Quaternion.Lerp(drivenLocalTx.rotation, overrideTx.rotation, rotW),
drivenLScale
);
}
break;
default:
break;
}
}
else
AnimationRuntimeUtils.PassThrough(stream, driven);
}
internal void UpdateSpace(int space)
{
if ((int)cache.GetRaw(spaceIdx) == space)
return;
cache.SetRaw(space, spaceIdx);
Space currSpace = (Space)space;
if (currSpace == Space.Pivot)
cache.Set(sourceToPivotRot, sourceToCurrSpaceRotIdx);
else if (currSpace == Space.Local)
cache.Set(sourceToLocalRot, sourceToCurrSpaceRotIdx);
else
cache.Set(sourceToWorldRot, sourceToCurrSpaceRotIdx);
}
}
///
/// This interface defines the data mapping for OverrideTransform.
///
public interface IOverrideTransformData
{
/// The Transform affected by the constraint source Transform.
Transform constrainedObject { get; }
/// The override source Transform.
Transform sourceObject { get; }
/// The override space.
int space { get; }
/// The path to the position weight property in the constraint component.
string positionWeightFloatProperty { get; }
/// The path to the rotation weight property in the constraint component.
string rotationWeightFloatProperty { get; }
/// The path to the override position property in the constraint component.
string positionVector3Property { get; }
/// The path to the override rotation property in the constraint component.
string rotationVector3Property { get; }
}
///
/// The OverrideTransform job binder.
///
/// The constraint data type
public class OverrideTransformJobBinder : AnimationJobBinder
where T : struct, IAnimationJobData, IOverrideTransformData
{
///
public override OverrideTransformJob Create(Animator animator, ref T data, Component component)
{
var job = new OverrideTransformJob();
var cacheBuilder = new AnimationJobCacheBuilder();
job.driven = ReadWriteTransformHandle.Bind(animator, data.constrainedObject);
if (data.sourceObject != null)
{
// Cache source to possible space rotation offsets (world, local and pivot)
// at bind time so we can switch dynamically between them at runtime.
job.source = ReadOnlyTransformHandle.Bind(animator, data.sourceObject);
var sourceLocalTx = new AffineTransform(data.sourceObject.localPosition, data.sourceObject.localRotation);
job.sourceInvLocalBindTx = sourceLocalTx.Inverse();
var sourceWorldTx = new AffineTransform(data.sourceObject.position, data.sourceObject.rotation);
var drivenWorldTx = new AffineTransform(data.constrainedObject.position, data.constrainedObject.rotation);
job.sourceToWorldRot = sourceWorldTx.Inverse().rotation;
job.sourceToPivotRot = sourceWorldTx.InverseMul(drivenWorldTx).rotation;
var drivenParent = data.constrainedObject.parent;
if (drivenParent != null)
{
var drivenParentWorldTx = new AffineTransform(drivenParent.position, drivenParent.rotation);
job.sourceToLocalRot = sourceWorldTx.InverseMul(drivenParentWorldTx).rotation;
}
else
job.sourceToLocalRot = job.sourceToPivotRot;
}
job.spaceIdx = cacheBuilder.Add(data.space);
if (data.space == (int)OverrideTransformJob.Space.Pivot)
job.sourceToCurrSpaceRotIdx = cacheBuilder.Add(job.sourceToPivotRot);
else if (data.space == (int)OverrideTransformJob.Space.Local)
job.sourceToCurrSpaceRotIdx = cacheBuilder.Add(job.sourceToLocalRot);
else
job.sourceToCurrSpaceRotIdx = cacheBuilder.Add(job.sourceToWorldRot);
job.position = Vector3Property.Bind(animator, component, data.positionVector3Property);
job.rotation = Vector3Property.Bind(animator, component, data.rotationVector3Property);
job.positionWeight = FloatProperty.Bind(animator, component, data.positionWeightFloatProperty);
job.rotationWeight = FloatProperty.Bind(animator, component, data.rotationWeightFloatProperty);
job.cache = cacheBuilder.Build();
return job;
}
///
public override void Destroy(OverrideTransformJob job)
{
job.cache.Dispose();
}
///
public override void Update(OverrideTransformJob job, ref T data)
{
job.UpdateSpace(data.space);
}
}
}