first commit
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// CacheIndex is used in AnimationJobCache to recover the index to the cached data.
|
||||
/// </summary>
|
||||
public struct CacheIndex
|
||||
{
|
||||
internal int idx;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AnimationJobCache can be used in animation jobs to store values that
|
||||
/// can be updated through the AnimationJobCache during the Update loop without
|
||||
/// rebuilding the job.
|
||||
/// </summary>
|
||||
public struct AnimationJobCache : System.IDisposable
|
||||
{
|
||||
NativeArray<float> m_Data;
|
||||
|
||||
internal AnimationJobCache(float[] data)
|
||||
{
|
||||
m_Data = new NativeArray<float>(data, Allocator.Persistent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose of the AnimationJobCache memory.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
m_Data.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets raw float data at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">CacheIndex value.</param>
|
||||
/// <param name="offset">Offset to the CacheIndex.</param>
|
||||
/// <returns>The raw float data.</returns>
|
||||
public float GetRaw(CacheIndex index, int offset = 0)
|
||||
{
|
||||
return m_Data[index.idx + offset];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets raw float data at specified index.
|
||||
/// </summary>
|
||||
/// <param name="val">Raw float data.</param>
|
||||
/// <param name="index">CacheIndex value.</param>
|
||||
/// <param name="offset">Offset to the CacheIndex.</param>
|
||||
public void SetRaw(float val, CacheIndex index, int offset = 0)
|
||||
{
|
||||
m_Data[index.idx + offset] = val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets value at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">CacheIndex value.</param>
|
||||
/// <param name="offset">Offset to the CacheIndex.</param>
|
||||
/// <typeparam name="T">The value type.</typeparam>
|
||||
/// <returns></returns>
|
||||
unsafe public T Get<T>(CacheIndex index, int offset = 0) where T : unmanaged
|
||||
{
|
||||
int size = UnsafeUtility.SizeOf<T>();
|
||||
int stride = size / UnsafeUtility.SizeOf<float>();
|
||||
|
||||
T val = default(T);
|
||||
UnsafeUtility.MemCpy(&val, (float*)m_Data.GetUnsafeReadOnlyPtr() + index.idx + offset * stride, size);
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets value at specified index.
|
||||
/// </summary>
|
||||
/// <param name="val">Value.</param>
|
||||
/// <param name="index">CacheIndex value.</param>
|
||||
/// <param name="offset">Offset to the CacheIndex.</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
unsafe public void Set<T>(T val, CacheIndex index, int offset = 0) where T : unmanaged
|
||||
{
|
||||
int size = UnsafeUtility.SizeOf<T>();
|
||||
int stride = size / UnsafeUtility.SizeOf<float>();
|
||||
|
||||
UnsafeUtility.MemCpy((float*)m_Data.GetUnsafePtr() + index.idx + offset * stride, &val, size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets an array of values at specified index.
|
||||
/// </summary>
|
||||
/// <param name="v">Array of values.</param>
|
||||
/// <param name="index">CacheIndex value.</param>
|
||||
/// <param name="offset">Offset to the CacheIndex.</param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
unsafe public void SetArray<T>(T[] v, CacheIndex index, int offset = 0) where T : unmanaged
|
||||
{
|
||||
int size = UnsafeUtility.SizeOf<T>();
|
||||
int stride = size / UnsafeUtility.SizeOf<float>();
|
||||
|
||||
fixed (void* ptr = v)
|
||||
{
|
||||
UnsafeUtility.MemCpy((float*)m_Data.GetUnsafePtr() + index.idx + offset * stride, ptr, size * v.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AnimationJobCacheBuilder can be used to create a new AnimationJobCache object.
|
||||
/// </summary>
|
||||
public class AnimationJobCacheBuilder
|
||||
{
|
||||
List<float> m_Data;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
public AnimationJobCacheBuilder()
|
||||
{
|
||||
m_Data = new List<float>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a float value to the AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <param name="v">Float value.</param>
|
||||
/// <returns>CacheIndex that refers to the new value.</returns>
|
||||
public CacheIndex Add(float v)
|
||||
{
|
||||
m_Data.Add(v);
|
||||
return new CacheIndex { idx = m_Data.Count - 1 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Vector2 value to the AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <param name="v">Vector2 value.</param>
|
||||
/// <returns>CacheIndex that refers to the new value.</returns>
|
||||
public CacheIndex Add(Vector2 v)
|
||||
{
|
||||
m_Data.Add(v.x);
|
||||
m_Data.Add(v.y);
|
||||
return new CacheIndex { idx = m_Data.Count - 2 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Vector3 value to the AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <param name="v">Vector3 value.</param>
|
||||
/// <returns>CacheIndex that refers to the new value.</returns>
|
||||
public CacheIndex Add(Vector3 v)
|
||||
{
|
||||
m_Data.Add(v.x);
|
||||
m_Data.Add(v.y);
|
||||
m_Data.Add(v.z);
|
||||
return new CacheIndex { idx = m_Data.Count - 3 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Vector4 value to the AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <param name="v">Vector4 value.</param>
|
||||
/// <returns>CacheIndex that refers to the new value.</returns>
|
||||
public CacheIndex Add(Vector4 v)
|
||||
{
|
||||
m_Data.Add(v.x);
|
||||
m_Data.Add(v.y);
|
||||
m_Data.Add(v.z);
|
||||
m_Data.Add(v.w);
|
||||
return new CacheIndex { idx = m_Data.Count - 4 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a Quaternion value to the AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <param name="v">Quaternion value.</param>
|
||||
/// <returns>CacheIndex that refers to the new value.</returns>
|
||||
public CacheIndex Add(Quaternion v)
|
||||
{
|
||||
return Add(new Vector4(v.x, v.y, v.z, v.w));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a AffineTransform value to the AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <param name="tx">AffineTransform value.</param>
|
||||
/// <returns>CacheIndex that refers to the new value.</returns>
|
||||
public CacheIndex Add(AffineTransform tx)
|
||||
{
|
||||
Add(tx.translation);
|
||||
Add(tx.rotation);
|
||||
return new CacheIndex { idx = m_Data.Count - 7 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allocates uninitialized chunk of specified size in the AnimationJobCacheBuilder.
|
||||
/// </summary>
|
||||
/// <param name="size">Size of chunk to allocate.</param>
|
||||
/// <returns>CacheIndex that refers to the allocated chunk.</returns>
|
||||
public CacheIndex AllocateChunk(int size)
|
||||
{
|
||||
m_Data.AddRange(new float[size]);
|
||||
return new CacheIndex { idx = m_Data.Count - size };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets value in AnimationJobCacheBuilder at specified index.
|
||||
/// </summary>
|
||||
/// <param name="index">CacheIndex value.</param>
|
||||
/// <param name="offset">Offset to the CacheIndex.</param>
|
||||
/// <param name="value">float data.</param>
|
||||
public void SetValue(CacheIndex index, int offset, float value)
|
||||
{
|
||||
if (index.idx + offset < m_Data.Count)
|
||||
m_Data[index.idx + offset] = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new AnimationJobCache.
|
||||
/// </summary>
|
||||
/// <returns>AnimationJobCache object with newly set values.</returns>
|
||||
public AnimationJobCache Build() => new AnimationJobCache(m_Data.ToArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 81d27177925c7894da0eb85baee3d530
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,156 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The Blend constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct BlendConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
private const int k_BlendTranslationMask = 1 << 0;
|
||||
private const int k_BlendRotationMask = 1 << 1;
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for sourceA Transform.</summary>
|
||||
public ReadOnlyTransformHandle sourceA;
|
||||
/// <summary>The Transform handle for sourceB Transform.</summary>
|
||||
public ReadOnlyTransformHandle sourceB;
|
||||
/// <summary>TR offset to apply to sourceA if maintainOffset is enabled.</summary>
|
||||
public AffineTransform sourceAOffset;
|
||||
/// <summary>TR offset to apply to sourceB if maintainOffset is enabled.</summary>
|
||||
public AffineTransform sourceBOffset;
|
||||
|
||||
/// <summary>Toggles whether to blend position in the job.</summary>
|
||||
public BoolProperty blendPosition;
|
||||
/// <summary>Toggles whether to blend rotation in the job.</summary>
|
||||
public BoolProperty blendRotation;
|
||||
/// <summary>
|
||||
/// Specifies the weight with which to blend position.
|
||||
/// A weight of zero will result in the position of sourceA, while a weight of one will result in the position of sourceB.
|
||||
/// </summary>
|
||||
public FloatProperty positionWeight;
|
||||
/// <summary>
|
||||
/// Specifies the weight with which to blend rotation.
|
||||
/// A weight of zero will result in the rotation of sourceA, while a weight of one will result in the rotation of sourceB.
|
||||
/// </summary>
|
||||
public FloatProperty rotationWeight;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
if (w > 0f)
|
||||
{
|
||||
if (blendPosition.Get(stream))
|
||||
{
|
||||
Vector3 posBlend = Vector3.Lerp(
|
||||
sourceA.GetPosition(stream) + sourceAOffset.translation,
|
||||
sourceB.GetPosition(stream) + sourceBOffset.translation,
|
||||
positionWeight.Get(stream)
|
||||
);
|
||||
driven.SetPosition(stream, Vector3.Lerp(driven.GetPosition(stream), posBlend, w));
|
||||
}
|
||||
else
|
||||
driven.SetLocalPosition(stream, driven.GetLocalPosition(stream));
|
||||
|
||||
if (blendRotation.Get(stream))
|
||||
{
|
||||
Quaternion rotBlend = Quaternion.Lerp(
|
||||
sourceA.GetRotation(stream) * sourceAOffset.rotation,
|
||||
sourceB.GetRotation(stream) * sourceBOffset.rotation,
|
||||
rotationWeight.Get(stream)
|
||||
);
|
||||
driven.SetRotation(stream, Quaternion.Lerp(driven.GetRotation(stream), rotBlend, w));
|
||||
}
|
||||
else
|
||||
driven.SetLocalRotation(stream, driven.GetLocalRotation(stream));
|
||||
}
|
||||
else
|
||||
AnimationRuntimeUtils.PassThrough(stream, driven);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the Blend constraint.
|
||||
/// </summary>
|
||||
public interface IBlendConstraintData
|
||||
{
|
||||
/// <summary>The Transform affected by the two source Transforms.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
/// <summary>First Transform in blend.</summary>
|
||||
Transform sourceObjectA { get; }
|
||||
/// <summary>Second Transform in blend.</summary>
|
||||
Transform sourceObjectB { get; }
|
||||
|
||||
/// <summary>This is used to maintain the current position offset from the constrained GameObject to the source GameObjects.</summary>
|
||||
bool maintainPositionOffsets { get; }
|
||||
/// <summary>This is used to maintain the current rotation offset from the constrained GameObject to the source GameObjects.</summary>
|
||||
bool maintainRotationOffsets { get; }
|
||||
|
||||
/// <summary>The path to the blend position property in the constraint component.</summary>
|
||||
string blendPositionBoolProperty { get; }
|
||||
/// <summary>The path to the blend rotation property in the constraint component.</summary>
|
||||
string blendRotationBoolProperty { get; }
|
||||
/// <summary>The path to the position weight property in the constraint component.</summary>
|
||||
string positionWeightFloatProperty { get; }
|
||||
/// <summary>The path to the rotation weight property in the constraint component.</summary>
|
||||
string rotationWeightFloatProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Blend constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class BlendConstraintJobBinder<T> : AnimationJobBinder<BlendConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IBlendConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override BlendConstraintJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new BlendConstraintJob();
|
||||
|
||||
job.driven = ReadWriteTransformHandle.Bind(animator, data.constrainedObject);
|
||||
job.sourceA = ReadOnlyTransformHandle.Bind(animator, data.sourceObjectA);
|
||||
job.sourceB = ReadOnlyTransformHandle.Bind(animator, data.sourceObjectB);
|
||||
|
||||
job.sourceAOffset = job.sourceBOffset = AffineTransform.identity;
|
||||
if (data.maintainPositionOffsets)
|
||||
{
|
||||
var drivenPos = data.constrainedObject.position;
|
||||
job.sourceAOffset.translation = drivenPos - data.sourceObjectA.position;
|
||||
job.sourceBOffset.translation = drivenPos - data.sourceObjectB.position;
|
||||
}
|
||||
|
||||
if (data.maintainRotationOffsets)
|
||||
{
|
||||
var drivenRot = data.constrainedObject.rotation;
|
||||
job.sourceAOffset.rotation = Quaternion.Inverse(data.sourceObjectA.rotation) * drivenRot;
|
||||
job.sourceBOffset.rotation = Quaternion.Inverse(data.sourceObjectB.rotation) * drivenRot;
|
||||
}
|
||||
|
||||
job.blendPosition = BoolProperty.Bind(animator, component, data.blendPositionBoolProperty);
|
||||
job.blendRotation = BoolProperty.Bind(animator, component, data.blendRotationBoolProperty);
|
||||
job.positionWeight = FloatProperty.Bind(animator, component, data.positionWeightFloatProperty);
|
||||
job.rotationWeight = FloatProperty.Bind(animator, component, data.rotationWeightFloatProperty);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(BlendConstraintJob job)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6aab915008afb774bbdc597691ed5d5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,188 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The ChainIK constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct ChainIKConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
/// <summary>An array of Transform handles that represents the Transform chain.</summary>
|
||||
public NativeArray<ReadWriteTransformHandle> chain;
|
||||
/// <summary>The Transform handle for the target Transform.</summary>
|
||||
public ReadOnlyTransformHandle target;
|
||||
|
||||
/// <summary>The offset applied to the target transform if maintainTargetPositionOffset or maintainTargetRotationOffset is enabled.</summary>
|
||||
public AffineTransform targetOffset;
|
||||
|
||||
/// <summary>An array of length in between Transforms in the chain.</summary>
|
||||
public NativeArray<float> linkLengths;
|
||||
|
||||
/// <summary>An array of positions for Transforms in the chain.</summary>
|
||||
public NativeArray<Vector3> linkPositions;
|
||||
|
||||
/// <summary>The weight for which ChainIK target has an effect on chain (up to tip Transform). This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty chainRotationWeight;
|
||||
/// <summary>The weight for which ChainIK target has and effect on tip Transform. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty tipRotationWeight;
|
||||
|
||||
/// <summary>CacheIndex to ChainIK tolerance value.</summary>
|
||||
/// <seealso cref="AnimationJobCache"/>
|
||||
public CacheIndex toleranceIdx;
|
||||
/// <summary>CacheIndex to ChainIK maxIterations value.</summary>
|
||||
/// <seealso cref="AnimationJobCache"/>
|
||||
public CacheIndex maxIterationsIdx;
|
||||
/// <summary>Cache for static properties in the job.</summary>
|
||||
public AnimationJobCache cache;
|
||||
|
||||
/// <summary>The maximum distance the Transform chain can reach.</summary>
|
||||
public float maxReach;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
if (w > 0f)
|
||||
{
|
||||
for (int i = 0; i < chain.Length; ++i)
|
||||
{
|
||||
var handle = chain[i];
|
||||
linkPositions[i] = handle.GetPosition(stream);
|
||||
chain[i] = handle;
|
||||
}
|
||||
|
||||
int tipIndex = chain.Length - 1;
|
||||
if (AnimationRuntimeUtils.SolveFABRIK(ref linkPositions, ref linkLengths, target.GetPosition(stream) + targetOffset.translation,
|
||||
cache.GetRaw(toleranceIdx), maxReach, (int)cache.GetRaw(maxIterationsIdx)))
|
||||
{
|
||||
var chainRWeight = chainRotationWeight.Get(stream) * w;
|
||||
for (int i = 0; i < tipIndex; ++i)
|
||||
{
|
||||
var prevDir = chain[i + 1].GetPosition(stream) - chain[i].GetPosition(stream);
|
||||
var newDir = linkPositions[i + 1] - linkPositions[i];
|
||||
var rot = chain[i].GetRotation(stream);
|
||||
chain[i].SetRotation(stream, Quaternion.Lerp(rot, QuaternionExt.FromToRotation(prevDir, newDir) * rot, chainRWeight));
|
||||
}
|
||||
}
|
||||
|
||||
chain[tipIndex].SetRotation(
|
||||
stream,
|
||||
Quaternion.Lerp(
|
||||
chain[tipIndex].GetRotation(stream),
|
||||
target.GetRotation(stream) * targetOffset.rotation,
|
||||
tipRotationWeight.Get(stream) * w
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < chain.Length; ++i)
|
||||
AnimationRuntimeUtils.PassThrough(stream, chain[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the ChainIK constraint.
|
||||
/// </summary>
|
||||
public interface IChainIKConstraintData
|
||||
{
|
||||
/// <summary>The root Transform of the ChainIK hierarchy.</summary>
|
||||
Transform root { get; }
|
||||
/// <summary>The tip Transform of the ChainIK hierarchy. The tip needs to be a descendant/child of the root Transform.</summary>
|
||||
Transform tip { get; }
|
||||
/// <summary>The ChainIK target Transform.</summary>
|
||||
Transform target { get; }
|
||||
|
||||
/// <summary>The maximum number of iterations allowed for the ChainIK algorithm to converge to a solution.</summary>
|
||||
int maxIterations { get; }
|
||||
/// <summary>
|
||||
/// The allowed distance between the tip and target Transform positions.
|
||||
/// When the distance is smaller than the tolerance, the algorithm has converged on a solution and will stop.
|
||||
/// </summary>
|
||||
float tolerance { get; }
|
||||
/// <summary>This is used to maintain the current position offset from the tip Transform to target Transform.</summary>
|
||||
bool maintainTargetPositionOffset { get; }
|
||||
/// <summary>This is used to maintain the current rotation offset from the tip Transform to target Transform.</summary>
|
||||
bool maintainTargetRotationOffset { get; }
|
||||
|
||||
/// <summary>The path to the chain rotation weight property in the constraint component.</summary>
|
||||
string chainRotationWeightFloatProperty { get; }
|
||||
/// <summary>The path to the tip rotation weight property in the constraint component.</summary>
|
||||
string tipRotationWeightFloatProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The ChainIK constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class ChainIKConstraintJobBinder<T> : AnimationJobBinder<ChainIKConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IChainIKConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override ChainIKConstraintJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
Transform[] chain = ConstraintsUtils.ExtractChain(data.root, data.tip);
|
||||
|
||||
var job = new ChainIKConstraintJob();
|
||||
job.chain = new NativeArray<ReadWriteTransformHandle>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.linkLengths = new NativeArray<float>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.linkPositions = new NativeArray<Vector3>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.maxReach = 0f;
|
||||
|
||||
int tipIndex = chain.Length - 1;
|
||||
for (int i = 0; i < chain.Length; ++i)
|
||||
{
|
||||
job.chain[i] = ReadWriteTransformHandle.Bind(animator, chain[i]);
|
||||
job.linkLengths[i] = (i != tipIndex) ? Vector3.Distance(chain[i].position, chain[i + 1].position) : 0f;
|
||||
job.maxReach += job.linkLengths[i];
|
||||
}
|
||||
|
||||
job.target = ReadOnlyTransformHandle.Bind(animator, data.target);
|
||||
job.targetOffset = AffineTransform.identity;
|
||||
if (data.maintainTargetPositionOffset)
|
||||
job.targetOffset.translation = data.tip.position - data.target.position;
|
||||
if (data.maintainTargetRotationOffset)
|
||||
job.targetOffset.rotation = Quaternion.Inverse(data.target.rotation) * data.tip.rotation;
|
||||
|
||||
job.chainRotationWeight = FloatProperty.Bind(animator, component, data.chainRotationWeightFloatProperty);
|
||||
job.tipRotationWeight = FloatProperty.Bind(animator, component, data.tipRotationWeightFloatProperty);
|
||||
|
||||
var cacheBuilder = new AnimationJobCacheBuilder();
|
||||
job.maxIterationsIdx = cacheBuilder.Add(data.maxIterations);
|
||||
job.toleranceIdx = cacheBuilder.Add(data.tolerance);
|
||||
job.cache = cacheBuilder.Build();
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(ChainIKConstraintJob job)
|
||||
{
|
||||
job.chain.Dispose();
|
||||
job.linkLengths.Dispose();
|
||||
job.linkPositions.Dispose();
|
||||
job.cache.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(ChainIKConstraintJob job, ref T data)
|
||||
{
|
||||
job.cache.SetRaw(data.maxIterations, job.maxIterationsIdx);
|
||||
job.cache.SetRaw(data.tolerance, job.toleranceIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43ef7c2783a6019459e739a74712bf50
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,160 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The DampedTransform job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct DampedTransformJob : IWeightedAnimationJob
|
||||
{
|
||||
const float k_FixedDt = 0.01667f; // 60Hz simulation step
|
||||
const float k_DampFactor = 40f;
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for the source object Transform.</summary>
|
||||
public ReadOnlyTransformHandle source;
|
||||
|
||||
/// <summary>Initial TR offset from source to constrained object.</summary>
|
||||
public AffineTransform localBindTx;
|
||||
|
||||
/// <summary>Aim axis used to adjust constrained object rotation if maintainAim is enabled.</summary>
|
||||
public Vector3 aimBindAxis;
|
||||
|
||||
/// <summary>Previous frame driven Transform TR components.</summary>
|
||||
public AffineTransform prevDrivenTx;
|
||||
|
||||
/// <summary>
|
||||
/// Defines how much of constrained object position follows source object position.
|
||||
/// constrained position will closely follow source object when set to 0, and will
|
||||
/// not move when set to 1.
|
||||
/// </summary>
|
||||
public FloatProperty dampPosition;
|
||||
/// <summary>
|
||||
/// Defines how much of constrained object rotation follows source object rotation.
|
||||
/// constrained rotation will closely follow source object when set to 0, and will
|
||||
/// not move when set to 1.
|
||||
/// </summary>
|
||||
public FloatProperty dampRotation;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
float streamDt = Mathf.Abs(stream.deltaTime);
|
||||
driven.GetGlobalTR(stream, out Vector3 drivenPos, out Quaternion drivenRot);
|
||||
|
||||
if (w > 0f && streamDt > 0f)
|
||||
{
|
||||
source.GetGlobalTR(stream, out Vector3 sourcePos, out Quaternion sourceRot);
|
||||
var sourceTx = new AffineTransform(sourcePos, sourceRot);
|
||||
var targetTx = sourceTx * localBindTx;
|
||||
targetTx.translation = Vector3.Lerp(drivenPos, targetTx.translation, w);
|
||||
targetTx.rotation = Quaternion.Lerp(drivenRot, targetTx.rotation, w);
|
||||
|
||||
var dampPosW = AnimationRuntimeUtils.Square(1f - dampPosition.Get(stream));
|
||||
var dampRotW = AnimationRuntimeUtils.Square(1f - dampRotation.Get(stream));
|
||||
bool doAimAjustements = Vector3.Dot(aimBindAxis, aimBindAxis) > 0f;
|
||||
|
||||
while (streamDt > 0f)
|
||||
{
|
||||
float factoredDt = k_DampFactor * Mathf.Min(k_FixedDt, streamDt);
|
||||
|
||||
prevDrivenTx.translation +=
|
||||
(targetTx.translation - prevDrivenTx.translation) * dampPosW * factoredDt;
|
||||
|
||||
prevDrivenTx.rotation *= Quaternion.Lerp(
|
||||
Quaternion.identity,
|
||||
Quaternion.Inverse(prevDrivenTx.rotation) * targetTx.rotation,
|
||||
dampRotW * factoredDt
|
||||
);
|
||||
|
||||
if (doAimAjustements)
|
||||
{
|
||||
var fromDir = prevDrivenTx.rotation * aimBindAxis;
|
||||
var toDir = sourceTx.translation - prevDrivenTx.translation;
|
||||
prevDrivenTx.rotation =
|
||||
Quaternion.AngleAxis(Vector3.Angle(fromDir, toDir), Vector3.Cross(fromDir, toDir).normalized) * prevDrivenTx.rotation;
|
||||
}
|
||||
|
||||
streamDt -= k_FixedDt;
|
||||
}
|
||||
|
||||
driven.SetGlobalTR(stream, prevDrivenTx.translation, prevDrivenTx.rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
prevDrivenTx.Set(drivenPos, drivenRot);
|
||||
AnimationRuntimeUtils.PassThrough(stream, driven);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for DampedTransform.
|
||||
/// </summary>
|
||||
public interface IDampedTransformData
|
||||
{
|
||||
/// <summary>The Transform affected by the constraint Source Transform.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
/// <summary>The source Transform.</summary>
|
||||
Transform sourceObject { get; }
|
||||
|
||||
/// <summary>Toggles whether damping will enforces aim.</summary>
|
||||
bool maintainAim { get; }
|
||||
|
||||
/// <summary>The path to the damp position weight property in the constraint component.</summary>
|
||||
string dampPositionFloatProperty { get; }
|
||||
/// <summary>The path to the damp rotation weight property in the constraint component.</summary>
|
||||
string dampRotationFloatProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The DampedTransform job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class DampedTransformJobBinder<T> : AnimationJobBinder<DampedTransformJob, T>
|
||||
where T : struct, IAnimationJobData, IDampedTransformData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override DampedTransformJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new DampedTransformJob();
|
||||
|
||||
job.driven = ReadWriteTransformHandle.Bind(animator, data.constrainedObject);
|
||||
job.source = ReadOnlyTransformHandle.Bind(animator, data.sourceObject);
|
||||
|
||||
var drivenTx = new AffineTransform(data.constrainedObject.position, data.constrainedObject.rotation);
|
||||
var sourceTx = new AffineTransform(data.sourceObject.position, data.sourceObject.rotation);
|
||||
|
||||
job.localBindTx = sourceTx.InverseMul(drivenTx);
|
||||
job.prevDrivenTx = drivenTx;
|
||||
|
||||
job.dampPosition = FloatProperty.Bind(animator, component, data.dampPositionFloatProperty);
|
||||
job.dampRotation = FloatProperty.Bind(animator, component, data.dampRotationFloatProperty);
|
||||
|
||||
if (data.maintainAim && AnimationRuntimeUtils.SqrDistance(data.constrainedObject.position, data.sourceObject.position) > 0f)
|
||||
job.aimBindAxis = Quaternion.Inverse(data.constrainedObject.rotation) * (sourceTx.translation - drivenTx.translation).normalized;
|
||||
else
|
||||
job.aimBindAxis = Vector3.zero;
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(DampedTransformJob job)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 88f002c8d5442ea4ea02ed982c62e8f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,511 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for animatable property handles used to read and write
|
||||
/// values in the AnimationStream.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The animatable value type</typeparam>
|
||||
public interface IAnimatableProperty<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The property value.</returns>
|
||||
T Get(AnimationStream stream);
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="value">The new property value.</param>
|
||||
void Set(AnimationStream stream, T value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct BoolProperty : IAnimatableProperty<bool>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BoolProperty handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a BoolProperty handle that represents the new binding.</returns>
|
||||
public static BoolProperty Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
return new BoolProperty()
|
||||
{
|
||||
value = animator.BindStreamProperty(component.transform, component.GetType(), name)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a BoolProperty handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="property">The name of the property.</param>
|
||||
/// <returns>Returns a BoolProperty handle that represents the new binding.</returns>
|
||||
public static BoolProperty BindCustom(Animator animator, string property)
|
||||
{
|
||||
return new BoolProperty
|
||||
{
|
||||
value = animator.BindCustomStreamProperty(property, CustomStreamPropertyType.Bool)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The boolean property value.</returns>
|
||||
public bool Get(AnimationStream stream) => value.GetBool(stream);
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="v">The new boolean property value.</param>
|
||||
public void Set(AnimationStream stream, bool v) => value.SetBool(stream, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Integer property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct IntProperty : IAnimatableProperty<int>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a IntProperty handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a IntProperty handle that represents the new binding.</returns>
|
||||
public static IntProperty Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
return new IntProperty()
|
||||
{
|
||||
value = animator.BindStreamProperty(component.transform, component.GetType(), name)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a IntProperty handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="property">The name of the property.</param>
|
||||
/// <returns>Returns a IntProperty handle that represents the new binding.</returns>
|
||||
public static IntProperty BindCustom(Animator animator, string property)
|
||||
{
|
||||
return new IntProperty
|
||||
{
|
||||
value = animator.BindCustomStreamProperty(property, CustomStreamPropertyType.Int)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The integer property value.</returns>
|
||||
public int Get(AnimationStream stream) => value.GetInt(stream);
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="v">The new integer property value.</param>
|
||||
public void Set(AnimationStream stream, int v) => value.SetInt(stream, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Float property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct FloatProperty : IAnimatableProperty<float>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle value;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a FloatProperty handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a FloatProperty handle that represents the new binding.</returns>
|
||||
public static FloatProperty Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
return new FloatProperty()
|
||||
{
|
||||
value = animator.BindStreamProperty(component.transform, component.GetType(), name)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a FloatProperty handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="property">The name of the property.</param>
|
||||
/// <returns>Returns a FloatProperty handle that represents the new binding.</returns>
|
||||
public static FloatProperty BindCustom(Animator animator, string property)
|
||||
{
|
||||
return new FloatProperty
|
||||
{
|
||||
value = animator.BindCustomStreamProperty(property, CustomStreamPropertyType.Float)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The float property value.</returns>
|
||||
public float Get(AnimationStream stream) => value.GetFloat(stream);
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="v">The new float property value.</param>
|
||||
public void Set(AnimationStream stream, float v) => value.SetFloat(stream, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vector2 property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct Vector2Property : IAnimatableProperty<Vector2>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used for the X component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle x;
|
||||
/// <summary>The PropertyStreamHandle used for the Y component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle y;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector2Property handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a Vector2Property handle that represents the new binding.</returns>
|
||||
public static Vector2Property Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
var type = component.GetType();
|
||||
return new Vector2Property
|
||||
{
|
||||
x = animator.BindStreamProperty(component.transform, type, name + ".x"),
|
||||
y = animator.BindStreamProperty(component.transform, type, name + ".y")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector2Property handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="name">The name of the property.</param>
|
||||
/// <returns>Returns a Vector2Property handle that represents the new binding.</returns>
|
||||
public static Vector2Property BindCustom(Animator animator, string name)
|
||||
{
|
||||
return new Vector2Property
|
||||
{
|
||||
x = animator.BindCustomStreamProperty(name + ".x", CustomStreamPropertyType.Float),
|
||||
y = animator.BindCustomStreamProperty(name + ".y", CustomStreamPropertyType.Float)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The Vector2 property value.</returns>
|
||||
public Vector2 Get(AnimationStream stream) =>
|
||||
new Vector2(x.GetFloat(stream), y.GetFloat(stream));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="value">The new Vector2 property value.</param>
|
||||
public void Set(AnimationStream stream, Vector2 value)
|
||||
{
|
||||
x.SetFloat(stream, value.x);
|
||||
y.SetFloat(stream, value.y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vector3 property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct Vector3Property : IAnimatableProperty<Vector3>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used for the X component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle x;
|
||||
/// <summary>The PropertyStreamHandle used for the Y component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle y;
|
||||
/// <summary>The PropertyStreamHandle used for the Z component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle z;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector3Property handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a Vector3Property handle that represents the new binding.</returns>
|
||||
public static Vector3Property Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
var type = component.GetType();
|
||||
return new Vector3Property
|
||||
{
|
||||
x = animator.BindStreamProperty(component.transform, type, name + ".x"),
|
||||
y = animator.BindStreamProperty(component.transform, type, name + ".y"),
|
||||
z = animator.BindStreamProperty(component.transform, type, name + ".z")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector3Property handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="name">The name of the property.</param>
|
||||
/// <returns>Returns a Vector3Property handle that represents the new binding.</returns>
|
||||
public static Vector3Property BindCustom(Animator animator, string name)
|
||||
{
|
||||
return new Vector3Property
|
||||
{
|
||||
x = animator.BindCustomStreamProperty(name + ".x", CustomStreamPropertyType.Float),
|
||||
y = animator.BindCustomStreamProperty(name + ".y", CustomStreamPropertyType.Float),
|
||||
z = animator.BindCustomStreamProperty(name + ".z", CustomStreamPropertyType.Float)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The Vector3 property value.</returns>
|
||||
public Vector3 Get(AnimationStream stream) =>
|
||||
new Vector3(x.GetFloat(stream), y.GetFloat(stream), z.GetFloat(stream));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="value">The new Vector3 property value.</param>
|
||||
public void Set(AnimationStream stream, Vector3 value)
|
||||
{
|
||||
x.SetFloat(stream, value.x);
|
||||
y.SetFloat(stream, value.y);
|
||||
z.SetFloat(stream, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vector3Int property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct Vector3IntProperty : IAnimatableProperty<Vector3Int>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used for the X component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle x;
|
||||
/// <summary>The PropertyStreamHandle used for the Y component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle y;
|
||||
/// <summary>The PropertyStreamHandle used for the Z component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle z;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector3IntProperty handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a Vector3IntProperty handle that represents the new binding.</returns>
|
||||
public static Vector3IntProperty Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
var type = component.GetType();
|
||||
return new Vector3IntProperty
|
||||
{
|
||||
x = animator.BindStreamProperty(component.transform, type, name + ".x"),
|
||||
y = animator.BindStreamProperty(component.transform, type, name + ".y"),
|
||||
z = animator.BindStreamProperty(component.transform, type, name + ".z")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector3IntProperty handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="name">The name of the property.</param>
|
||||
/// <returns>Returns a Vector3IntProperty handle that represents the new binding.</returns>
|
||||
public static Vector3IntProperty BindCustom(Animator animator, string name)
|
||||
{
|
||||
return new Vector3IntProperty
|
||||
{
|
||||
x = animator.BindCustomStreamProperty(name + ".x", CustomStreamPropertyType.Int),
|
||||
y = animator.BindCustomStreamProperty(name + ".y", CustomStreamPropertyType.Int),
|
||||
z = animator.BindCustomStreamProperty(name + ".z", CustomStreamPropertyType.Int)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The Vector3Int property value.</returns>
|
||||
public Vector3Int Get(AnimationStream stream) =>
|
||||
new Vector3Int(x.GetInt(stream), y.GetInt(stream), z.GetInt(stream));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="value">The new Vector3Int property value.</param>
|
||||
public void Set(AnimationStream stream, Vector3Int value)
|
||||
{
|
||||
x.SetInt(stream, value.x);
|
||||
y.SetInt(stream, value.y);
|
||||
z.SetInt(stream, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vector3Bool property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct Vector3BoolProperty : IAnimatableProperty<Vector3Bool>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used for the X component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle x;
|
||||
/// <summary>The PropertyStreamHandle used for the Y component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle y;
|
||||
/// <summary>The PropertyStreamHandle used for the Z component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle z;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector3BoolProperty handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a Vector3BoolProperty handle that represents the new binding.</returns>
|
||||
public static Vector3BoolProperty Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
var type = component.GetType();
|
||||
return new Vector3BoolProperty
|
||||
{
|
||||
x = animator.BindStreamProperty(component.transform, type, name + ".x"),
|
||||
y = animator.BindStreamProperty(component.transform, type, name + ".y"),
|
||||
z = animator.BindStreamProperty(component.transform, type, name + ".z")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector3BoolProperty handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="name">The name of the property.</param>
|
||||
/// <returns>Returns a Vector3BoolProperty handle that represents the new binding.</returns>
|
||||
public static Vector3BoolProperty BindCustom(Animator animator, string name)
|
||||
{
|
||||
return new Vector3BoolProperty
|
||||
{
|
||||
x = animator.BindCustomStreamProperty(name + ".x", CustomStreamPropertyType.Bool),
|
||||
y = animator.BindCustomStreamProperty(name + ".y", CustomStreamPropertyType.Bool),
|
||||
z = animator.BindCustomStreamProperty(name + ".z", CustomStreamPropertyType.Bool)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The Vector3Bool property value.</returns>
|
||||
public Vector3Bool Get(AnimationStream stream) =>
|
||||
new Vector3Bool(x.GetBool(stream), y.GetBool(stream), z.GetBool(stream));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="value">The new Vector3Bool property value.</param>
|
||||
public void Set(AnimationStream stream, Vector3Bool value)
|
||||
{
|
||||
x.SetBool(stream, value.x);
|
||||
y.SetBool(stream, value.y);
|
||||
z.SetBool(stream, value.z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vector4 property handle used to read and write values in the AnimationStream.
|
||||
/// </summary>
|
||||
public struct Vector4Property : IAnimatableProperty<Vector4>
|
||||
{
|
||||
/// <summary>The PropertyStreamHandle used for the X component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle x;
|
||||
/// <summary>The PropertyStreamHandle used for the Y component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle y;
|
||||
/// <summary>The PropertyStreamHandle used for the Z component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle z;
|
||||
/// <summary>The PropertyStreamHandle used for the X component in the AnimationStream.</summary>
|
||||
public PropertyStreamHandle w;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector4Property handle representing a property binding on a Component.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The Component owning the parameter.</param>
|
||||
/// <param name="name">The property name</param>
|
||||
/// <returns>Returns a Vector4Property handle that represents the new binding.</returns>
|
||||
public static Vector4Property Bind(Animator animator, Component component, string name)
|
||||
{
|
||||
var type = component.GetType();
|
||||
return new Vector4Property
|
||||
{
|
||||
x = animator.BindStreamProperty(component.transform, type, name + ".x"),
|
||||
y = animator.BindStreamProperty(component.transform, type, name + ".y"),
|
||||
z = animator.BindStreamProperty(component.transform, type, name + ".z"),
|
||||
w = animator.BindStreamProperty(component.transform, type, name + ".w")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Vector4Property handle for a custom property in the AnimationStream to pass extra data to downstream animation jobs in the graph.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="name">The name of the property.</param>
|
||||
/// <returns>Returns a Vector4Property handle that represents the new binding.</returns>
|
||||
public static Vector4Property BindCustom(Animator animator, string name)
|
||||
{
|
||||
return new Vector4Property
|
||||
{
|
||||
x = animator.BindCustomStreamProperty(name + ".x", CustomStreamPropertyType.Float),
|
||||
y = animator.BindCustomStreamProperty(name + ".y", CustomStreamPropertyType.Float),
|
||||
z = animator.BindCustomStreamProperty(name + ".z", CustomStreamPropertyType.Float),
|
||||
w = animator.BindCustomStreamProperty(name + ".w", CustomStreamPropertyType.Float)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the property value from a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The Vector4 property value.</returns>
|
||||
public Vector4 Get(AnimationStream stream) =>
|
||||
new Vector4(x.GetFloat(stream), y.GetFloat(stream), z.GetFloat(stream), w.GetFloat(stream));
|
||||
|
||||
/// <summary>
|
||||
/// Sets the property value into a stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="value">The new Vector4 property value.</param>
|
||||
public void Set(AnimationStream stream, Vector4 value)
|
||||
{
|
||||
x.SetFloat(stream, value.x);
|
||||
y.SetFloat(stream, value.y);
|
||||
z.SetFloat(stream, value.z);
|
||||
w.SetFloat(stream, value.w);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae72ed6fe0e30e142b831b12b8cd9b3f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// This is the base interface for all animation job binders.
|
||||
/// </summary>
|
||||
public interface IAnimationJobBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the animation job.
|
||||
/// </summary>
|
||||
/// <param name="animator">The animated hierarchy Animator component.</param>
|
||||
/// <param name="data">The constraint data.</param>
|
||||
/// <param name="component">The constraint component.</param>
|
||||
/// <returns>Returns a new job interface.</returns>
|
||||
IAnimationJob Create(Animator animator, IAnimationJobData data, Component component = null);
|
||||
/// <summary>
|
||||
/// Destroys the animation job.
|
||||
/// </summary>
|
||||
/// <param name="job">The animation job to destroy.</param>
|
||||
void Destroy(IAnimationJob job);
|
||||
/// <summary>
|
||||
/// Updates the animation job.
|
||||
/// </summary>
|
||||
/// <param name="job">The animation job to update.</param>
|
||||
/// <param name="data">The constraint data.</param>
|
||||
void Update(IAnimationJob job, IAnimationJobData data);
|
||||
/// <summary>
|
||||
/// Creates an AnimationScriptPlayable with the specified animation job.
|
||||
/// </summary>
|
||||
/// <param name="graph">The PlayableGraph that will own the AnimationScriptPlayable.</param>
|
||||
/// <param name="job">The animation job to use in the AnimationScriptPlayable.</param>
|
||||
/// <returns>Returns a new AnimationScriptPlayable.</returns>
|
||||
AnimationScriptPlayable CreatePlayable(PlayableGraph graph, IAnimationJob job);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The is the base class for all animation job binders.
|
||||
/// </summary>
|
||||
/// <typeparam name="TJob">The constraint job.</typeparam>
|
||||
/// <typeparam name="TData">The constraint data.</typeparam>
|
||||
public abstract class AnimationJobBinder<TJob, TData> : IAnimationJobBinder
|
||||
where TJob : struct, IAnimationJob
|
||||
where TData : struct, IAnimationJobData
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the animation job.
|
||||
/// </summary>
|
||||
/// <param name="animator">The animated hierarchy Animator component.</param>
|
||||
/// <param name="data">The constraint data.</param>
|
||||
/// <param name="component">The constraint component.</param>
|
||||
/// <returns>Returns a new job interface.</returns>
|
||||
public abstract TJob Create(Animator animator, ref TData data, Component component);
|
||||
/// <summary>
|
||||
/// Destroys the animation job.
|
||||
/// </summary>
|
||||
/// <param name="job">The animation job to destroy.</param>
|
||||
public abstract void Destroy(TJob job);
|
||||
/// <summary>
|
||||
/// Updates the animation job.
|
||||
/// </summary>
|
||||
/// <param name="job">The animation job to update.</param>
|
||||
/// <param name="data">The constraint data.</param>
|
||||
public virtual void Update(TJob job, ref TData data) {}
|
||||
|
||||
/// <inheritdoc />
|
||||
IAnimationJob IAnimationJobBinder.Create(Animator animator, IAnimationJobData data, Component component)
|
||||
{
|
||||
Debug.Assert(data is TData);
|
||||
TData tData = (TData)data;
|
||||
return Create(animator, ref tData, component);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
void IAnimationJobBinder.Destroy(IAnimationJob job)
|
||||
{
|
||||
Debug.Assert(job is TJob);
|
||||
Destroy((TJob)job);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
void IAnimationJobBinder.Update(IAnimationJob job, IAnimationJobData data)
|
||||
{
|
||||
Debug.Assert(data is TData && job is TJob);
|
||||
TData tData = (TData)data;
|
||||
Update((TJob)job, ref tData);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
AnimationScriptPlayable IAnimationJobBinder.CreatePlayable(PlayableGraph graph, IAnimationJob job)
|
||||
{
|
||||
Debug.Assert(job is TJob);
|
||||
return AnimationScriptPlayable.Create(graph, (TJob)job);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bef79d42046e7414a8a36ee9d28430ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface is used to represent all constraint data structs.
|
||||
/// </summary>
|
||||
public interface IAnimationJobData
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves the data valid state.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if data can be successfully used in a constraint. Returns false otherwise.</returns>
|
||||
bool IsValid();
|
||||
/// <summary>
|
||||
/// Resets values to defaults.
|
||||
/// </summary>
|
||||
void SetDefaultValues();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef3cacef35acee14290c1dae6963fed8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface represents an animation job with a weight value.
|
||||
/// </summary>
|
||||
public interface IWeightedAnimationJob : IAnimationJob
|
||||
{
|
||||
/// <summary>The main weight given to the constraint. This is a value in between 0 and 1.</summary>
|
||||
FloatProperty jobWeight { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6b0b0d5c8df2df4aaeca18077358f75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,355 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The MultiAim constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct MultiAimConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
const float k_Epsilon = 1e-5f;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies how the world up vector used by the Multi-Aim constraint is defined.
|
||||
/// </summary>
|
||||
public enum WorldUpType
|
||||
{
|
||||
/// <summary>Neither defines nor uses a world up vector.</summary>
|
||||
None,
|
||||
/// <summary>Uses and defines the world up vector as the Unity Scene up vector (the Y axis).</summary>
|
||||
SceneUp,
|
||||
/// <summary>Uses and defines the world up vector as a vector from the constrained object, in the direction of the up object.</summary>
|
||||
ObjectUp,
|
||||
/// <summary>Uses and defines the world up vector as relative to the local space of the object.</summary>
|
||||
ObjectRotationUp,
|
||||
/// <summary>Uses and defines the world up vector as a vector specified by the user.</summary>
|
||||
Vector
|
||||
};
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for the constrained object parent Transform.</summary>
|
||||
public ReadOnlyTransformHandle drivenParent;
|
||||
/// <summary>The post-rotation offset applied to the constrained object.</summary>
|
||||
public Vector3Property drivenOffset;
|
||||
|
||||
/// <summary>List of Transform handles for the source objects.</summary>
|
||||
public NativeArray<ReadOnlyTransformHandle> sourceTransforms;
|
||||
/// <summary>List of weights for the source objects.</summary>
|
||||
public NativeArray<PropertyStreamHandle> sourceWeights;
|
||||
/// <summary>List of offsets to apply to source rotations if maintainOffset is enabled.</summary>
|
||||
public NativeArray<Quaternion> sourceOffsets;
|
||||
|
||||
/// <summary>Buffer used to store weights during job execution.</summary>
|
||||
public NativeArray<float> weightBuffer;
|
||||
|
||||
/// <summary>Local aim axis of the constrained object Transform.</summary>
|
||||
public Vector3 aimAxis;
|
||||
/// <summary>Local up axis of the constrained object Transform.</summary>
|
||||
public Vector3 upAxis;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which mode to use to keep the upward direction of the constrained Object.
|
||||
/// </summary>
|
||||
public WorldUpType worldUpType;
|
||||
/// <summary>
|
||||
/// A static vector in world coordinates that is the general upward direction. This is used when World Up Type is set to WorldUpType.Vector.
|
||||
/// </summary>
|
||||
public Vector3 worldUpAxis;
|
||||
/// <summary>
|
||||
/// The Transform handle for the world up object. This is used when World Up Type is set to WorldUpType.ObjectUp or WorldUpType.ObjectRotationUp.
|
||||
/// </summary>
|
||||
public ReadOnlyTransformHandle worldUpObject;
|
||||
|
||||
/// <summary>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.</summary>
|
||||
public Vector3 axesMask;
|
||||
|
||||
/// <summary>Minimum rotation value.</summary>
|
||||
public FloatProperty minLimit;
|
||||
/// <summary>Maximum rotation value.</summary>
|
||||
public FloatProperty maxLimit;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the MultiAim constraint.
|
||||
/// </summary>
|
||||
public interface IMultiAimConstraintData
|
||||
{
|
||||
/// <summary>The Transform affected by the constraint Source Transforms.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
/// <summary>
|
||||
/// The list of Transforms that influence the constrained Transform orientation.
|
||||
/// Each source has a weight from 0 to 1.
|
||||
/// </summary>
|
||||
WeightedTransformArray sourceObjects { get; }
|
||||
/// <summary>
|
||||
/// This is used to maintain the current rotation offset from the constrained GameObject to the source GameObjects.
|
||||
/// </summary>
|
||||
bool maintainOffset { get; }
|
||||
|
||||
/// <summary>Specifies the local aim axis of the constrained Transform to use in order to orient itself to the Source Transforms.</summary>
|
||||
Vector3 aimAxis { get; }
|
||||
/// <summary>Specified the local up axis of the constrained Transform to use in order to orient itself to the Source Transforms.</summary>
|
||||
Vector3 upAxis { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Specifies which mode to use to keep the upward direction of the constrained Object.
|
||||
/// </summary>
|
||||
/// <seealso cref="MultiAimConstraintJob.WorldUpType"/>
|
||||
int worldUpType { get; }
|
||||
/// <summary>
|
||||
/// A static vector in world coordinates that is the general upward direction. This is used when World Up Type is set to WorldUpType.Vector.
|
||||
/// </summary>
|
||||
Vector3 worldUpAxis { get; }
|
||||
/// <summary>
|
||||
/// The Transform used to calculate the upward direction. This is used when World Up Type is set to WorldUpType.ObjectUp or WorldUpType.ObjectRotationUp.
|
||||
/// </summary>
|
||||
Transform worldUpObject { get; }
|
||||
|
||||
/// <summary>Toggles whether the constrained Transform will rotate along the X axis.</summary>
|
||||
bool constrainedXAxis { get; }
|
||||
/// <summary>Toggles whether the constrained Transform will rotate along the Y axis.</summary>
|
||||
bool constrainedYAxis { get; }
|
||||
/// <summary>Toggles whether the constrained Transform will rotate along the Z axis.</summary>
|
||||
bool constrainedZAxis { get; }
|
||||
|
||||
/// <summary>The path to the offset property in the constraint component.</summary>
|
||||
string offsetVector3Property { get; }
|
||||
/// <summary>The path to the minimum limit property in the constraint component.</summary>
|
||||
string minLimitFloatProperty { get; }
|
||||
/// <summary>The path to the maximum limit property in the constraint component.</summary>
|
||||
string maxLimitFloatProperty { get; }
|
||||
/// <summary>The path to the source objects property in the constraint component.</summary>
|
||||
string sourceObjectsProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MultiAim constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class MultiAimConstraintJobBinder<T> : AnimationJobBinder<MultiAimConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IMultiAimConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Quaternion>(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
job.weightBuffer = new NativeArray<float>(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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(MultiAimConstraintJob job)
|
||||
{
|
||||
job.sourceTransforms.Dispose();
|
||||
job.sourceWeights.Dispose();
|
||||
job.sourceOffsets.Dispose();
|
||||
job.weightBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b3fcaeaf10650b4ba5972b838169673
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,222 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The MultiParent constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct MultiParentConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
const float k_Epsilon = 1e-5f;
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for the constrained object parent Transform.</summary>
|
||||
public ReadOnlyTransformHandle drivenParent;
|
||||
|
||||
/// <summary>List of Transform handles for the source objects.</summary>
|
||||
public NativeArray<ReadOnlyTransformHandle> sourceTransforms;
|
||||
/// <summary>List of weights for the source objects.</summary>
|
||||
public NativeArray<PropertyStreamHandle> sourceWeights;
|
||||
/// <summary>List of offsets to apply to source rotations if maintainOffset is enabled.</summary>
|
||||
public NativeArray<AffineTransform> sourceOffsets;
|
||||
|
||||
/// <summary>Buffer used to store weights during job execution.</summary>
|
||||
public NativeArray<float> weightBuffer;
|
||||
|
||||
/// <summary>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.</summary>
|
||||
public Vector3 positionAxesMask;
|
||||
/// <summary>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.</summary>
|
||||
public Vector3 rotationAxesMask;
|
||||
|
||||
/// <summary>The main weight given to the constraint. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the MultiParent constraint.
|
||||
/// </summary>
|
||||
public interface IMultiParentConstraintData
|
||||
{
|
||||
/// <summary>The Transform affected by the constraint Source Transforms.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
/// <summary>
|
||||
/// The list of Transforms that influence the constrained Transform rotation.
|
||||
/// Each source has a weight from 0 to 1.
|
||||
/// </summary>
|
||||
WeightedTransformArray sourceObjects { get; }
|
||||
/// <summary>This is used to maintain the current position offset from the constrained GameObject to the source GameObjects.</summary>
|
||||
bool maintainPositionOffset { get; }
|
||||
/// <summary>This is used to maintain the current rotation offset from the constrained GameObject to the source GameObjects.</summary>
|
||||
bool maintainRotationOffset { get; }
|
||||
|
||||
/// <summary>Toggles whether the constrained transform will translate along the X axis.</summary>
|
||||
bool constrainedPositionXAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will translate along the Y axis.</summary>
|
||||
bool constrainedPositionYAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will translate along the Z axis.</summary>
|
||||
bool constrainedPositionZAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will rotate along the X axis.</summary>
|
||||
bool constrainedRotationXAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will rotate along the Y axis.</summary>
|
||||
bool constrainedRotationYAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will rotate along the Z axis.</summary>
|
||||
bool constrainedRotationZAxis { get; }
|
||||
|
||||
/// <summary>The path to the source objects property in the constraint component.</summary>
|
||||
string sourceObjectsProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MultiParent constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class MultiParentConstraintJobBinder<T> : AnimationJobBinder<MultiParentConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IMultiParentConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<AffineTransform>(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
job.weightBuffer = new NativeArray<float>(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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(MultiParentConstraintJob job)
|
||||
{
|
||||
job.sourceTransforms.Dispose();
|
||||
job.sourceWeights.Dispose();
|
||||
job.sourceOffsets.Dispose();
|
||||
job.weightBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ad0821068505234982932fc083e74bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,180 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The MultiPosition constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct MultiPositionConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
const float k_Epsilon = 1e-5f;
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for the constrained object parent Transform.</summary>
|
||||
public ReadOnlyTransformHandle drivenParent;
|
||||
/// <summary>The post-translation offset applied to the constrained object.</summary>
|
||||
public Vector3Property drivenOffset;
|
||||
|
||||
/// <summary>List of Transform handles for the source objects.</summary>
|
||||
public NativeArray<ReadOnlyTransformHandle> sourceTransforms;
|
||||
/// <summary>List of weights for the source objects.</summary>
|
||||
public NativeArray<PropertyStreamHandle> sourceWeights;
|
||||
/// <summary>List of offsets to apply to source rotations if maintainOffset is enabled.</summary>
|
||||
public NativeArray<Vector3> sourceOffsets;
|
||||
|
||||
/// <summary>Buffer used to store weights during job execution.</summary>
|
||||
public NativeArray<float> weightBuffer;
|
||||
|
||||
/// <summary>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.</summary>
|
||||
public Vector3 axesMask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the MultiPosition constraint.
|
||||
/// </summary>
|
||||
public interface IMultiPositionConstraintData
|
||||
{
|
||||
/// <summary>The Transform affected by the constraint Source Transforms.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
/// <summary>
|
||||
/// The list of Transforms that influence the constrained Transform position.
|
||||
/// Each source has a weight from 0 to 1.
|
||||
/// </summary>
|
||||
WeightedTransformArray sourceObjects { get; }
|
||||
/// <summary>This is used to maintain the current position offset from the constrained GameObject to the source GameObjects.</summary>
|
||||
bool maintainOffset { get; }
|
||||
|
||||
/// <summary>The path to the offset property in the constraint component.</summary>
|
||||
string offsetVector3Property { get; }
|
||||
/// <summary>The path to the source objects property in the constraint component.</summary>
|
||||
string sourceObjectsProperty { get; }
|
||||
|
||||
/// <summary>Toggles whether the constrained transform will translate along the X axis.</summary>
|
||||
bool constrainedXAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will translate along the Y axis.</summary>
|
||||
bool constrainedYAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will translate along the Z axis.</summary>
|
||||
bool constrainedZAxis { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MultiPosition constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class MultiPositionConstraintJobBinder<T> : AnimationJobBinder<MultiPositionConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IMultiPositionConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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<Vector3>(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
job.weightBuffer = new NativeArray<float>(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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(MultiPositionConstraintJob job)
|
||||
{
|
||||
job.sourceTransforms.Dispose();
|
||||
job.sourceWeights.Dispose();
|
||||
job.sourceOffsets.Dispose();
|
||||
job.weightBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 819c022146d742641b3cc4ec97fdae03
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The MultiReferential constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct MultiReferentialConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
/// <summary>The driver index.</summary>
|
||||
public IntProperty driver;
|
||||
/// <summary>The list of Transforms that are affected by the specified driver.</summary>
|
||||
public NativeArray<ReadWriteTransformHandle> sources;
|
||||
/// <summary>Cache of AffineTransform representing the source objects initial positions.</summary>
|
||||
public NativeArray<AffineTransform> sourceBindTx;
|
||||
/// <summary>List of AffineTransform to apply to driven source objects.</summary>
|
||||
public NativeArray<AffineTransform> offsetTx;
|
||||
|
||||
private int m_PrevDriverIdx;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
if (w > 0f)
|
||||
{
|
||||
var driverIdx = driver.Get(stream);
|
||||
if (driverIdx != m_PrevDriverIdx)
|
||||
UpdateOffsets(driverIdx);
|
||||
|
||||
sources[driverIdx].GetGlobalTR(stream, out Vector3 driverWPos, out Quaternion driverWRot);
|
||||
var driverTx = new AffineTransform(driverWPos, driverWRot);
|
||||
|
||||
int offset = 0;
|
||||
for (int i = 0; i < sources.Length; ++i)
|
||||
{
|
||||
if (i == driverIdx)
|
||||
continue;
|
||||
|
||||
var tx = driverTx * offsetTx[offset];
|
||||
|
||||
var src = sources[i];
|
||||
src.GetGlobalTR(stream, out Vector3 srcWPos, out Quaternion srcWRot);
|
||||
src.SetGlobalTR(stream, Vector3.Lerp(srcWPos, tx.translation, w), Quaternion.Lerp(srcWRot, tx.rotation, w));
|
||||
offset++;
|
||||
|
||||
sources[i] = src;
|
||||
}
|
||||
|
||||
AnimationRuntimeUtils.PassThrough(stream, sources[driverIdx]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < sources.Length; ++i)
|
||||
AnimationRuntimeUtils.PassThrough(stream, sources[i]);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateOffsets(int driver)
|
||||
{
|
||||
driver = Mathf.Clamp(driver, 0, sources.Length - 1);
|
||||
|
||||
int offset = 0;
|
||||
var invDriverTx = sourceBindTx[driver].Inverse();
|
||||
for (int i = 0; i < sourceBindTx.Length; ++i)
|
||||
{
|
||||
if (i == driver)
|
||||
continue;
|
||||
|
||||
offsetTx[offset] = invDriverTx * sourceBindTx[i];
|
||||
offset++;
|
||||
}
|
||||
|
||||
m_PrevDriverIdx = driver;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the MultiReferential constraint.
|
||||
/// </summary>
|
||||
public interface IMultiReferentialConstraintData
|
||||
{
|
||||
/// <summary>The driver index. This is a value in between 0 and the number of sourceObjects.</summary>
|
||||
int driverValue { get; }
|
||||
/// <summary>The path to the driver property in the constraint component.</summary>
|
||||
string driverIntProperty { get; }
|
||||
/// <summary>The list of Transforms that can act as drivers for the constrained Transform.</summary>
|
||||
Transform[] sourceObjects { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MultiReferential constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class MultiReferentialConstraintJobBinder<T> : AnimationJobBinder<MultiReferentialConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IMultiReferentialConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override MultiReferentialConstraintJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new MultiReferentialConstraintJob();
|
||||
|
||||
var sources = data.sourceObjects;
|
||||
job.driver = IntProperty.Bind(animator, component, data.driverIntProperty);
|
||||
job.sources = new NativeArray<ReadWriteTransformHandle>(sources.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.sourceBindTx = new NativeArray<AffineTransform>(sources.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.offsetTx = new NativeArray<AffineTransform>(sources.Length - 1, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int i = 0; i < sources.Length; ++i)
|
||||
{
|
||||
job.sources[i] = ReadWriteTransformHandle.Bind(animator, sources[i].transform);
|
||||
job.sourceBindTx[i] = new AffineTransform(sources[i].position, sources[i].rotation);
|
||||
}
|
||||
|
||||
job.UpdateOffsets(data.driverValue);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(MultiReferentialConstraintJob job)
|
||||
{
|
||||
job.sources.Dispose();
|
||||
job.sourceBindTx.Dispose();
|
||||
job.offsetTx.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d4d5179cf211d24d9f152ce8fd9c880
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,180 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The MultiRotation constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct MultiRotationConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
const float k_Epsilon = 1e-5f;
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for the constrained object parent Transform.</summary>
|
||||
public ReadOnlyTransformHandle drivenParent;
|
||||
/// <summary>The post-rotation offset applied to the constrained object.</summary>
|
||||
public Vector3Property drivenOffset;
|
||||
|
||||
/// <summary>List of Transform handles for the source objects.</summary>
|
||||
public NativeArray<ReadOnlyTransformHandle> sourceTransforms;
|
||||
/// <summary>List of weights for the source objects.</summary>
|
||||
public NativeArray<PropertyStreamHandle> sourceWeights;
|
||||
/// <summary>List of offsets to apply to source rotations if maintainOffset is enabled.</summary>
|
||||
public NativeArray<Quaternion> sourceOffsets;
|
||||
|
||||
/// <summary>Buffer used to store weights during job execution.</summary>
|
||||
public NativeArray<float> weightBuffer;
|
||||
|
||||
/// <summary>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.</summary>
|
||||
public Vector3 axesMask;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
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;
|
||||
Quaternion accumRot = QuaternionExt.zero;
|
||||
for (int i = 0; i < sourceTransforms.Length; ++i)
|
||||
{
|
||||
var normalizedWeight = weightBuffer[i] * weightScale;
|
||||
if (normalizedWeight < k_Epsilon)
|
||||
continue;
|
||||
|
||||
ReadOnlyTransformHandle sourceTransform = sourceTransforms[i];
|
||||
accumRot = QuaternionExt.Add(accumRot, QuaternionExt.Scale(sourceTransform.GetRotation(stream) * sourceOffsets[i], normalizedWeight));
|
||||
|
||||
// Required to update handles with binding info.
|
||||
sourceTransforms[i] = sourceTransform;
|
||||
accumWeights += normalizedWeight;
|
||||
}
|
||||
|
||||
accumRot = QuaternionExt.NormalizeSafe(accumRot);
|
||||
if (accumWeights < 1f)
|
||||
accumRot = Quaternion.Lerp(driven.GetRotation(stream), accumRot, accumWeights);
|
||||
|
||||
// Convert accumRot to local space
|
||||
if (drivenParent.IsValid(stream))
|
||||
accumRot = Quaternion.Inverse(drivenParent.GetRotation(stream)) * accumRot;
|
||||
|
||||
Quaternion currentLRot = driven.GetLocalRotation(stream);
|
||||
if (Vector3.Dot(axesMask, axesMask) < 3f)
|
||||
accumRot = Quaternion.Euler(AnimationRuntimeUtils.Lerp(currentLRot.eulerAngles, accumRot.eulerAngles, axesMask));
|
||||
|
||||
var offset = drivenOffset.Get(stream);
|
||||
if (Vector3.Dot(offset, offset) > 0f)
|
||||
accumRot *= Quaternion.Euler(offset);
|
||||
|
||||
driven.SetLocalRotation(stream, Quaternion.Lerp(currentLRot, accumRot, w));
|
||||
}
|
||||
else
|
||||
AnimationRuntimeUtils.PassThrough(stream, driven);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the MultiRotation constraint.
|
||||
/// </summary>
|
||||
public interface IMultiRotationConstraintData
|
||||
{
|
||||
/// <summary>The Transform affected by the constraint Source Transforms.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of Transforms that influence the constrained Transform rotation.
|
||||
/// Each source has a weight from 0 to 1.
|
||||
/// </summary>
|
||||
WeightedTransformArray sourceObjects { get; }
|
||||
/// <summary>This is used to maintain the current rotation offset from the constrained GameObject to the source GameObjects.</summary>
|
||||
bool maintainOffset { get; }
|
||||
|
||||
/// <summary>The path to the offset property in the constraint component.</summary>
|
||||
string offsetVector3Property { get; }
|
||||
/// <summary>The path to the source objects property in the constraint component.</summary>
|
||||
string sourceObjectsProperty { get; }
|
||||
|
||||
/// <summary>Toggles whether the constrained transform will rotate along the X axis.</summary>
|
||||
bool constrainedXAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will rotate along the Y axis.</summary>
|
||||
bool constrainedYAxis { get; }
|
||||
/// <summary>Toggles whether the constrained transform will rotate along the Z axis.</summary>
|
||||
bool constrainedZAxis { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The MultiRotation constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class MultiRotationConstraintJobBinder<T> : AnimationJobBinder<MultiRotationConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, IMultiRotationConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override MultiRotationConstraintJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new MultiRotationConstraintJob();
|
||||
|
||||
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<Quaternion>(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
job.weightBuffer = new NativeArray<float>(sourceObjects.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
Quaternion drivenRot = data.constrainedObject.rotation;
|
||||
for (int i = 0; i < sourceObjects.Count; ++i)
|
||||
{
|
||||
job.sourceOffsets[i] = data.maintainOffset ?
|
||||
(Quaternion.Inverse(sourceObjects[i].transform.rotation) * drivenRot) : Quaternion.identity;
|
||||
}
|
||||
|
||||
job.axesMask = new Vector3(
|
||||
System.Convert.ToSingle(data.constrainedXAxis),
|
||||
System.Convert.ToSingle(data.constrainedYAxis),
|
||||
System.Convert.ToSingle(data.constrainedZAxis)
|
||||
);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(MultiRotationConstraintJob job)
|
||||
{
|
||||
job.sourceTransforms.Dispose();
|
||||
job.sourceWeights.Dispose();
|
||||
job.sourceOffsets.Dispose();
|
||||
job.weightBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e65922aac9b8f54c99855336aea7f75
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,241 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The OverrideTransform job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct OverrideTransformJob : IWeightedAnimationJob
|
||||
{
|
||||
/// <summary>
|
||||
/// The override space controls how the override source Transform
|
||||
/// is copied unto constrained Transform.
|
||||
/// </summary>
|
||||
public enum Space
|
||||
{
|
||||
/// <summary>Copy override world TR components into world TR components of the constrained Transform.</summary>
|
||||
World = 0,
|
||||
/// <summary>Copy override local TR components into local TR components of the constrained Transform.</summary>
|
||||
Local = 1,
|
||||
/// <summary>Add override local TR components to local TR components of the constrained Transform. </summary>
|
||||
Pivot = 2
|
||||
}
|
||||
|
||||
/// <summary>The Transform handle for the constrained object Transform.</summary>
|
||||
public ReadWriteTransformHandle driven;
|
||||
/// <summary>The Transform handle for the override source object Transform.</summary>
|
||||
public ReadOnlyTransformHandle source;
|
||||
/// <summary>Cached inverse local TR used in space switching calculations.</summary>
|
||||
public AffineTransform sourceInvLocalBindTx;
|
||||
|
||||
/// <summary>Cached source to world coordinates quaternion. Used when Space is set to World.</summary>
|
||||
public Quaternion sourceToWorldRot;
|
||||
/// <summary>Cached source to local coordinates quaternion. Used when Space is set to Local.</summary>
|
||||
public Quaternion sourceToLocalRot;
|
||||
/// <summary>Cached source to pivot coordinates quaternion. Used when Space is set to Pivot.</summary>
|
||||
public Quaternion sourceToPivotRot;
|
||||
|
||||
/// <summary>CacheIndex to the override space.</summary>
|
||||
/// <seealso cref="AnimationJobCache"/>
|
||||
public CacheIndex spaceIdx;
|
||||
/// <summary>CacheIndex to source to space quaternion used in current space.</summary>
|
||||
/// <seealso cref="AnimationJobCache"/>
|
||||
public CacheIndex sourceToCurrSpaceRotIdx;
|
||||
|
||||
/// <summary>The override position.</summary>
|
||||
public Vector3Property position;
|
||||
/// <summary>The override rotation.</summary>
|
||||
public Vector3Property rotation;
|
||||
/// <summary>The weight for which override position has an effect on the constrained Transform. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty positionWeight;
|
||||
/// <summary>The weight for which override rotation has an effect on the constrained Transform. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty rotationWeight;
|
||||
|
||||
/// <summary>Cache for static properties in the job.</summary>
|
||||
public AnimationJobCache cache;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
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<Quaternion>(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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for OverrideTransform.
|
||||
/// </summary>
|
||||
public interface IOverrideTransformData
|
||||
{
|
||||
/// <summary>The Transform affected by the constraint source Transform.</summary>
|
||||
Transform constrainedObject { get; }
|
||||
/// <summary>The override source Transform.</summary>
|
||||
Transform sourceObject { get; }
|
||||
/// <summary>The override space.</summary>
|
||||
int space { get; }
|
||||
|
||||
/// <summary>The path to the position weight property in the constraint component.</summary>
|
||||
string positionWeightFloatProperty { get; }
|
||||
/// <summary>The path to the rotation weight property in the constraint component.</summary>
|
||||
string rotationWeightFloatProperty { get; }
|
||||
/// <summary>The path to the override position property in the constraint component.</summary>
|
||||
string positionVector3Property { get; }
|
||||
/// <summary>The path to the override rotation property in the constraint component.</summary>
|
||||
string rotationVector3Property { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The OverrideTransform job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class OverrideTransformJobBinder<T> : AnimationJobBinder<OverrideTransformJob, T>
|
||||
where T : struct, IAnimationJobData, IOverrideTransformData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
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;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(OverrideTransformJob job)
|
||||
{
|
||||
job.cache.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Update(OverrideTransformJob job, ref T data)
|
||||
{
|
||||
job.UpdateSpace(data.space);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adadb93c5c0fa494797401ddcc0c1922
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,250 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
using TransformSyncer = RigSyncSceneToStreamJob.TransformSyncer;
|
||||
using PropertySyncer = RigSyncSceneToStreamJob.PropertySyncer;
|
||||
|
||||
[Unity.Burst.BurstCompile]
|
||||
internal struct RigSyncSceneToStreamJob : IAnimationJob
|
||||
{
|
||||
public struct TransformSyncer : System.IDisposable
|
||||
{
|
||||
public NativeArray<TransformSceneHandle> sceneHandles;
|
||||
public NativeArray<TransformStreamHandle> streamHandles;
|
||||
|
||||
public static TransformSyncer Create(int size)
|
||||
{
|
||||
return new TransformSyncer() {
|
||||
sceneHandles = new NativeArray<TransformSceneHandle>(size, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
streamHandles = new NativeArray<TransformStreamHandle>(size, Allocator.Persistent, NativeArrayOptions.UninitializedMemory)
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (sceneHandles.IsCreated)
|
||||
sceneHandles.Dispose();
|
||||
|
||||
if (streamHandles.IsCreated)
|
||||
streamHandles.Dispose();
|
||||
}
|
||||
|
||||
public void BindAt(int index, Animator animator, Transform transform)
|
||||
{
|
||||
sceneHandles[index] = animator.BindSceneTransform(transform);
|
||||
streamHandles[index] = animator.BindStreamTransform(transform);
|
||||
}
|
||||
|
||||
public void Sync(ref AnimationStream stream)
|
||||
{
|
||||
for (int i = 0, count = sceneHandles.Length; i < count; ++i)
|
||||
{
|
||||
var sceneHandle = sceneHandles[i];
|
||||
if (!sceneHandle.IsValid(stream))
|
||||
continue;
|
||||
|
||||
var streamHandle = streamHandles[i];
|
||||
sceneHandle.GetLocalTRS(stream, out Vector3 scenePos, out Quaternion sceneRot, out Vector3 sceneScale);
|
||||
streamHandle.SetLocalTRS(stream, scenePos, sceneRot, sceneScale, true);
|
||||
|
||||
streamHandles[i] = streamHandle;
|
||||
sceneHandles[i] = sceneHandle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct PropertySyncer : System.IDisposable
|
||||
{
|
||||
public NativeArray<PropertySceneHandle> sceneHandles;
|
||||
public NativeArray<PropertyStreamHandle> streamHandles;
|
||||
public NativeArray<float> buffer;
|
||||
|
||||
public static PropertySyncer Create(int size)
|
||||
{
|
||||
return new PropertySyncer() {
|
||||
sceneHandles = new NativeArray<PropertySceneHandle>(size, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
streamHandles = new NativeArray<PropertyStreamHandle>(size, Allocator.Persistent, NativeArrayOptions.UninitializedMemory),
|
||||
buffer = new NativeArray<float>(size, Allocator.Persistent, NativeArrayOptions.UninitializedMemory)
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (sceneHandles.IsCreated)
|
||||
sceneHandles.Dispose();
|
||||
|
||||
if (streamHandles.IsCreated)
|
||||
streamHandles.Dispose();
|
||||
|
||||
if (buffer.IsCreated)
|
||||
buffer.Dispose();
|
||||
}
|
||||
|
||||
public void BindAt(int index, Animator animator, Component component, string property)
|
||||
{
|
||||
sceneHandles[index] = animator.BindSceneProperty(component.transform, component.GetType(), property);
|
||||
streamHandles[index] = animator.BindStreamProperty(component.transform, component.GetType(), property);
|
||||
}
|
||||
|
||||
public void Sync(ref AnimationStream stream)
|
||||
{
|
||||
AnimationSceneHandleUtility.ReadFloats(stream, sceneHandles, buffer);
|
||||
AnimationStreamHandleUtility.WriteFloats(stream, streamHandles, buffer, true);
|
||||
}
|
||||
|
||||
public NativeArray<float> StreamValues(ref AnimationStream stream)
|
||||
{
|
||||
AnimationStreamHandleUtility.ReadFloats(stream, streamHandles, buffer);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
public TransformSyncer transformSyncer;
|
||||
public PropertySyncer propertySyncer;
|
||||
public PropertySyncer rigWeightSyncer;
|
||||
public PropertySyncer constraintWeightSyncer;
|
||||
|
||||
public NativeArray<float> rigStates;
|
||||
public NativeArray<int> rigConstraintEndIdx;
|
||||
|
||||
public NativeArray<PropertyStreamHandle> modulatedConstraintWeights;
|
||||
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
transformSyncer.Sync(ref stream);
|
||||
propertySyncer.Sync(ref stream);
|
||||
rigWeightSyncer.Sync(ref stream);
|
||||
constraintWeightSyncer.Sync(ref stream);
|
||||
|
||||
var currRigWeights = rigWeightSyncer.StreamValues(ref stream);
|
||||
var currConstraintWeights = constraintWeightSyncer.StreamValues(ref stream);
|
||||
|
||||
int rigIndex = 0;
|
||||
for (int i = 0, count = currConstraintWeights.Length; i < count; ++i)
|
||||
{
|
||||
if (i >= rigConstraintEndIdx[rigIndex])
|
||||
rigIndex++;
|
||||
|
||||
currConstraintWeights[i] *= (currRigWeights[rigIndex] * rigStates[rigIndex]);
|
||||
}
|
||||
|
||||
AnimationStreamHandleUtility.WriteFloats(stream, modulatedConstraintWeights, currConstraintWeights, false);
|
||||
}
|
||||
}
|
||||
|
||||
internal struct SyncableProperties
|
||||
{
|
||||
public RigProperties rig;
|
||||
public ConstraintProperties[] constraints;
|
||||
}
|
||||
|
||||
internal interface IRigSyncSceneToStreamData
|
||||
{
|
||||
Transform[] syncableTransforms { get; }
|
||||
SyncableProperties[] syncableProperties { get; }
|
||||
|
||||
bool[] rigStates { get; }
|
||||
}
|
||||
|
||||
internal class RigSyncSceneToStreamJobBinder<T> : AnimationJobBinder<RigSyncSceneToStreamJob, T>
|
||||
where T : struct, IAnimationJobData, IRigSyncSceneToStreamData
|
||||
{
|
||||
internal static string[] s_PropertyElementNames = new string[] {".x", ".y", ".z", ".w"};
|
||||
|
||||
public override RigSyncSceneToStreamJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new RigSyncSceneToStreamJob();
|
||||
|
||||
var transforms = data.syncableTransforms;
|
||||
if (transforms != null)
|
||||
{
|
||||
job.transformSyncer = TransformSyncer.Create(transforms.Length);
|
||||
for (int i = 0; i < transforms.Length; ++i)
|
||||
job.transformSyncer.BindAt(i, animator, transforms[i]);
|
||||
}
|
||||
|
||||
var properties = data.syncableProperties;
|
||||
if (properties != null)
|
||||
{
|
||||
int rigCount = properties.Length, constraintCount = 0, propertyCount = 0;
|
||||
for (int i = 0; i < properties.Length; ++i)
|
||||
{
|
||||
constraintCount += properties[i].constraints.Length;
|
||||
for (int j = 0; j < properties[i].constraints.Length; ++j)
|
||||
for (int k = 0; k < properties[i].constraints[j].properties.Length; ++k)
|
||||
propertyCount += properties[i].constraints[j].properties[k].descriptor.size;
|
||||
}
|
||||
|
||||
job.propertySyncer = PropertySyncer.Create(propertyCount);
|
||||
job.rigWeightSyncer = PropertySyncer.Create(rigCount);
|
||||
job.constraintWeightSyncer = PropertySyncer.Create(constraintCount);
|
||||
job.rigStates = new NativeArray<float>(rigCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.rigConstraintEndIdx = new NativeArray<int>(rigCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.modulatedConstraintWeights = new NativeArray<PropertyStreamHandle>(constraintCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
int constraintIdx = 0, propertyIdx = 0;
|
||||
for (int i = 0; i < properties.Length; ++i)
|
||||
{
|
||||
job.rigWeightSyncer.BindAt(i, animator, properties[i].rig.component, RigProperties.s_Weight);
|
||||
job.rigStates[i] = data.rigStates[i] ? 1f : 0f;
|
||||
|
||||
var constraints = properties[i].constraints;
|
||||
for (int j = 0; j < constraints.Length; ++j)
|
||||
{
|
||||
ref var constraint = ref constraints[j];
|
||||
|
||||
job.constraintWeightSyncer.BindAt(constraintIdx, animator, constraint.component, ConstraintProperties.s_Weight);
|
||||
job.modulatedConstraintWeights[constraintIdx++] = animator.BindCustomStreamProperty(
|
||||
ConstraintsUtils.ConstructCustomPropertyName(constraint.component, ConstraintProperties.s_Weight),
|
||||
CustomStreamPropertyType.Float
|
||||
);
|
||||
|
||||
for (int k = 0; k < constraint.properties.Length; ++k)
|
||||
{
|
||||
ref var property = ref constraint.properties[k];
|
||||
if (property.descriptor.size == 1)
|
||||
job.propertySyncer.BindAt(propertyIdx++, animator, constraint.component, property.name);
|
||||
else
|
||||
{
|
||||
Debug.Assert(property.descriptor.size <= 4);
|
||||
for (int l = 0; l < property.descriptor.size; ++l)
|
||||
job.propertySyncer.BindAt(propertyIdx++, animator, constraint.component, property.name + s_PropertyElementNames[l]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job.rigConstraintEndIdx[i] = constraintIdx;
|
||||
}
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
public override void Destroy(RigSyncSceneToStreamJob job)
|
||||
{
|
||||
job.transformSyncer.Dispose();
|
||||
job.propertySyncer.Dispose();
|
||||
job.rigWeightSyncer.Dispose();
|
||||
job.constraintWeightSyncer.Dispose();
|
||||
|
||||
if (job.rigStates.IsCreated)
|
||||
job.rigStates.Dispose();
|
||||
|
||||
if (job.rigConstraintEndIdx.IsCreated)
|
||||
job.rigConstraintEndIdx.Dispose();
|
||||
|
||||
if (job.modulatedConstraintWeights.IsCreated)
|
||||
job.modulatedConstraintWeights.Dispose();
|
||||
}
|
||||
|
||||
public override void Update(RigSyncSceneToStreamJob job, ref T data)
|
||||
{
|
||||
int count = Mathf.Min(job.rigStates.Length, data.rigStates.Length);
|
||||
for (int i = 0; i < count; ++i)
|
||||
job.rigStates[i] = data.rigStates[i] ? 1f : 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e244834d2385d54382ec1664e76601c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// Read/write handle on a Transform component used in Animation C# Jobs.
|
||||
/// </summary>
|
||||
public struct ReadWriteTransformHandle
|
||||
{
|
||||
TransformStreamHandle m_Handle;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The position of the transform relative to the parent.</returns>
|
||||
public Vector3 GetLocalPosition(AnimationStream stream) => m_Handle.GetLocalPosition(stream);
|
||||
/// <summary>
|
||||
/// Gets the rotation of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The rotation of the transform relative to the parent.</returns>
|
||||
public Quaternion GetLocalRotation(AnimationStream stream) => m_Handle.GetLocalRotation(stream);
|
||||
/// <summary>
|
||||
/// Gets the scale of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The scale of the transform relative to the parent.</returns>
|
||||
public Vector3 GetLocalScale(AnimationStream stream) => m_Handle.GetLocalScale(stream);
|
||||
/// <summary>
|
||||
/// Gets the position, rotation and scale of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform relative to the parent.</param>
|
||||
/// <param name="rotation">The rotation of the transform relative to the parent.</param>
|
||||
/// <param name="scale">The scale of the transform relative to the parent.</param>
|
||||
public void GetLocalTRS(AnimationStream stream, out Vector3 position, out Quaternion rotation, out Vector3 scale) =>
|
||||
m_Handle.GetLocalTRS(stream, out position, out rotation, out scale);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform relative to the parent.</param>
|
||||
public void SetLocalPosition(AnimationStream stream, Vector3 position) => m_Handle.SetLocalPosition(stream, position);
|
||||
/// <summary>
|
||||
/// Sets the rotation of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="rotation">The rotation of the transform relative to the parent.</param>
|
||||
public void SetLocalRotation(AnimationStream stream, Quaternion rotation) => m_Handle.SetLocalRotation(stream, rotation);
|
||||
/// <summary>
|
||||
/// Sets the scale of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="scale">The scale of the transform relative to the parent.</param>
|
||||
public void SetLocalScale(AnimationStream stream, Vector3 scale) => m_Handle.SetLocalScale(stream, scale);
|
||||
/// <summary>
|
||||
/// Sets the position, rotation and scale of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform relative to the parent.</param>
|
||||
/// <param name="rotation">The rotation of the transform relative to the parent.</param>
|
||||
/// <param name="scale">The scale of the transform relative to the parent.</param>
|
||||
/// <param name="useMask">Set to true to write the specified parameters if the matching stream parameters have not already been modified.</param>
|
||||
public void SetLocalTRS(AnimationStream stream, Vector3 position, Quaternion rotation, Vector3 scale, bool useMask = false) =>
|
||||
m_Handle.SetLocalTRS(stream, position, rotation, scale, useMask);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The position of the transform in world space.</returns>
|
||||
public Vector3 GetPosition(AnimationStream stream) => m_Handle.GetPosition(stream);
|
||||
/// <summary>
|
||||
/// Gets the rotation of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The rotation of the transform in world space.</returns>
|
||||
public Quaternion GetRotation(AnimationStream stream) => m_Handle.GetRotation(stream);
|
||||
/// <summary>
|
||||
/// Gets the position and scaled rotation of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform in world space.</param>
|
||||
/// <param name="rotation">The rotation of the transform in world space.</param>
|
||||
public void GetGlobalTR(AnimationStream stream, out Vector3 position, out Quaternion rotation) =>
|
||||
m_Handle.GetGlobalTR(stream, out position, out rotation);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the position of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform in world space.</param>
|
||||
public void SetPosition(AnimationStream stream, Vector3 position) => m_Handle.SetPosition(stream, position);
|
||||
/// <summary>
|
||||
/// Sets the rotation of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="rotation"> The rotation of the transform in world space.</param>
|
||||
public void SetRotation(AnimationStream stream, Quaternion rotation) => m_Handle.SetRotation(stream, rotation);
|
||||
/// <summary>
|
||||
/// Sets the position and rotation of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform in world space.</param>
|
||||
/// <param name="rotation">The rotation of the transform in world space.</param>
|
||||
/// <param name="useMask">Set to true to write the specified parameters if the matching stream parameters have not already been modified.</param>
|
||||
public void SetGlobalTR(AnimationStream stream, Vector3 position, Quaternion rotation, bool useMask = false) =>
|
||||
m_Handle.SetGlobalTR(stream, position, rotation, useMask);
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this handle is resolved.
|
||||
/// A ReadWriteTransformHandle is resolved if it is valid, if it has the same bindings version than the one in the stream, and if it is bound to the transform in the stream.
|
||||
/// A ReadWriteTransformHandle can become unresolved if the animator bindings have changed or if the transform had been destroyed.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReadWriteTransformHandle.Resolve"/>
|
||||
/// <seealso cref="ReadWriteTransformHandle.IsValid"/>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>Returns true if the handle is resolved, false otherwise.</returns>
|
||||
public bool IsResolved(AnimationStream stream) => m_Handle.IsResolved(stream);
|
||||
/// <summary>
|
||||
/// Returns whether this is a valid handle.
|
||||
/// A ReadWriteTransformHandle may be invalid if, for example, you didn't use the correct function to create it.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReadWriteTransformHandle.Bind"/>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>Returns whether this is a valid handle.</returns>
|
||||
public bool IsValid(AnimationStream stream) => m_Handle.IsValid(stream);
|
||||
/// <summary>
|
||||
/// Bind this handle with an animated values from the AnimationStream.
|
||||
/// Handles are lazily resolved as they're accessed, but in order to prevent unwanted CPU spikes, this method allows to resolve handles in a deterministic way.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReadWriteTransformHandle.IsResolved"/>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
public void Resolve(AnimationStream stream) => m_Handle.Resolve(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Create a ReadWriteTransformHandle representing the new binding between the Animator and a Transform already bound to the Animator.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="transform">The Transform to bind.</param>
|
||||
/// <returns>Returns the ReadWriteTransformHandle that represents the new binding.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown if transform is null.</exception>
|
||||
/// <exception cref="InvalidOperationException">Thrown if transform is not a child in the Animator hierarchy.</exception>
|
||||
public static ReadWriteTransformHandle Bind(Animator animator, Transform transform)
|
||||
{
|
||||
ReadWriteTransformHandle handle = new ReadWriteTransformHandle();
|
||||
if (transform == null)
|
||||
throw new ArgumentNullException(nameof(transform));
|
||||
|
||||
if (!transform.IsChildOf(animator.avatarRoot))
|
||||
throw new InvalidOperationException($"Transform '{transform.name}' is not a child of the Animator hierarchy, and cannot be written to.");
|
||||
|
||||
handle.m_Handle = animator.BindStreamTransform(transform);
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read-only handle on a Transform component used in Animation C# Jobs.
|
||||
/// </summary>
|
||||
public struct ReadOnlyTransformHandle
|
||||
{
|
||||
TransformStreamHandle m_StreamHandle;
|
||||
TransformSceneHandle m_SceneHandle;
|
||||
byte m_InStream;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The position of the transform relative to the parent.</returns>
|
||||
public Vector3 GetLocalPosition(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetLocalPosition(stream) : m_SceneHandle.GetLocalPosition(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The rotation of the transform relative to the parent.</returns>
|
||||
public Quaternion GetLocalRotation(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetLocalRotation(stream) : m_SceneHandle.GetLocalRotation(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the scale of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The scale of the transform relative to the parent.</returns>
|
||||
public Vector3 GetLocalScale(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetLocalScale(stream) : m_SceneHandle.GetLocalScale(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position, rotation and scale of the transform relative to the parent.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform relative to the parent.</param>
|
||||
/// <param name="rotation">The rotation of the transform relative to the parent.</param>
|
||||
/// <param name="scale">The scale of the transform relative to the parent.</param>
|
||||
public void GetLocalTRS(AnimationStream stream, out Vector3 position, out Quaternion rotation, out Vector3 scale)
|
||||
{
|
||||
if (m_InStream == 1)
|
||||
m_StreamHandle.GetLocalTRS(stream, out position, out rotation, out scale);
|
||||
else
|
||||
m_SceneHandle.GetLocalTRS(stream, out position, out rotation, out scale);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the matrix of the transform in local space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The matrix of the transform in local space.</returns>
|
||||
public Matrix4x4 GetLocalToParentMatrix(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetLocalToParentMatrix(stream) : m_SceneHandle.GetLocalToParentMatrix(stream);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The position of the transform in world space.</returns>
|
||||
public Vector3 GetPosition(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetPosition(stream) : m_SceneHandle.GetPosition(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rotation of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The rotation of the transform in world space.</returns>
|
||||
public Quaternion GetRotation(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetRotation(stream) : m_SceneHandle.GetRotation(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position and scaled rotation of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <param name="position">The position of the transform in world space.</param>
|
||||
/// <param name="rotation">The rotation of the transform in world space.</param>
|
||||
public void GetGlobalTR(AnimationStream stream, out Vector3 position, out Quaternion rotation)
|
||||
{
|
||||
if (m_InStream == 1)
|
||||
m_StreamHandle.GetGlobalTR(stream, out position, out rotation);
|
||||
else
|
||||
m_SceneHandle.GetGlobalTR(stream, out position, out rotation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the matrix of the transform in world space.
|
||||
/// </summary>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>The matrix of the transform in world space.</returns>
|
||||
public Matrix4x4 GetLocalToWorldMatrix(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.GetLocalToWorldMatrix(stream) : m_SceneHandle.GetLocalToWorldMatrix(stream);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this handle is resolved.
|
||||
/// A ReadOnlyTransformHandle is resolved if it is valid, if it has the same bindings version than the one in the stream, and if it is bound to the transform in the stream.
|
||||
/// A ReadOnlyTransformHandle can become unresolved if the animator bindings have changed or if the transform had been destroyed.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReadWriteTransformHandle.Resolve"/>
|
||||
/// <seealso cref="ReadWriteTransformHandle.IsValid"/>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>Returns true if the handle is resolved, false otherwise.</returns>
|
||||
public bool IsResolved(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.IsResolved(stream) : true;
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether this is a valid handle.
|
||||
/// A ReadOnlyTransformHandle may be invalid if, for example, you didn't use the correct function to create it.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReadWriteTransformHandle.Bind"/>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
/// <returns>Returns whether this is a valid handle.</returns>
|
||||
public bool IsValid(AnimationStream stream) =>
|
||||
m_InStream == 1 ? m_StreamHandle.IsValid(stream) : m_SceneHandle.IsValid(stream);
|
||||
|
||||
/// <summary>
|
||||
/// Bind this handle with an animated values from the AnimationStream.
|
||||
/// Handles are lazily resolved as they're accessed, but in order to prevent unwanted CPU spikes, this method allows to resolve handles in a deterministic way.
|
||||
/// </summary>
|
||||
/// <seealso cref="ReadWriteTransformHandle.IsResolved"/>
|
||||
/// <param name="stream">The AnimationStream that holds the animated values.</param>
|
||||
public void Resolve(AnimationStream stream)
|
||||
{
|
||||
if (m_InStream == 1)
|
||||
m_StreamHandle.Resolve(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a ReadOnlyTransformHandle representing the new binding between the Animator and a Transform already bound to the Animator.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="transform">The Transform to bind.</param>
|
||||
/// <returns>Returns the ReadOnlyTransformHandle that represents the new binding.</returns>
|
||||
/// <exception cref="ArgumentNullException">Thrown if transform is null.</exception>
|
||||
public static ReadOnlyTransformHandle Bind(Animator animator, Transform transform)
|
||||
{
|
||||
ReadOnlyTransformHandle handle = new ReadOnlyTransformHandle();
|
||||
if (transform == null)
|
||||
throw new ArgumentNullException(nameof(transform));
|
||||
|
||||
handle.m_InStream = (byte)(transform.IsChildOf(animator.avatarRoot) ? 1 : 0);
|
||||
if (handle.m_InStream == 1)
|
||||
handle.m_StreamHandle = animator.BindStreamTransform(transform);
|
||||
else
|
||||
handle.m_SceneHandle = animator.BindSceneTransform(transform);
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85da06385921c524c9e558da8da655c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,155 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The TwistChain constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct TwistChainConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
/// <summary>The Transform handle for the root target Transform.</summary>
|
||||
public ReadWriteTransformHandle rootTarget;
|
||||
/// <summary>The Transform handle for the tip target Transform.</summary>
|
||||
public ReadWriteTransformHandle tipTarget;
|
||||
|
||||
/// <summary>An array of Transform handles that represents the Transform chain.</summary>
|
||||
public NativeArray<ReadWriteTransformHandle> chain;
|
||||
|
||||
/// <summary>An array of interpolant values used to reevaluate the weights.</summary>
|
||||
public NativeArray<float> steps;
|
||||
/// <summary>An array of weight values used to adjust how twist is distributed along the chain.</summary>
|
||||
public NativeArray<float> weights;
|
||||
/// <summary>An array of rotation offsets to maintain the chain initial shape.</summary>
|
||||
public NativeArray<Quaternion> rotations;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
if (w > 0f)
|
||||
{
|
||||
// Retrieve root and tip rotation.
|
||||
Quaternion rootRotation = rootTarget.GetRotation(stream);
|
||||
Quaternion tipRotation = tipTarget.GetRotation(stream);
|
||||
|
||||
// Interpolate rotation on chain.
|
||||
chain[0].SetRotation(stream, Quaternion.Lerp(chain[0].GetRotation(stream), rootRotation, w));
|
||||
for (int i = 1; i < chain.Length - 1; ++i)
|
||||
{
|
||||
chain[i].SetRotation(stream, Quaternion.Lerp(chain[i].GetRotation(stream), rotations[i] * Quaternion.Lerp(rootRotation, tipRotation, weights[i]), w));
|
||||
}
|
||||
chain[chain.Length - 1].SetRotation(stream, Quaternion.Lerp(chain[chain.Length - 1].GetRotation(stream), tipRotation, w));
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// Update position of tip handle for easier visualization.
|
||||
rootTarget.SetPosition(stream, chain[0].GetPosition(stream));
|
||||
tipTarget.SetPosition(stream, chain[chain.Length - 1].GetPosition(stream));
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < chain.Length; ++i)
|
||||
AnimationRuntimeUtils.PassThrough(stream, chain[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the TwistChain constraint.
|
||||
/// </summary>
|
||||
public interface ITwistChainConstraintData
|
||||
{
|
||||
/// <summary>The root Transform of the TwistChain hierarchy.</summary>
|
||||
Transform root { get; }
|
||||
/// <summary>The tip Transform of the TwistChain hierarchy.</summary>
|
||||
Transform tip { get; }
|
||||
|
||||
/// <summary>The TwistChain root target Transform.</summary>
|
||||
Transform rootTarget { get; }
|
||||
/// <summary>The TwistChain tip target Transform.</summary>
|
||||
Transform tipTarget { get; }
|
||||
|
||||
/// <summary>Curve with weight values used to adjust how twist is distributed along the chain.</summary>
|
||||
AnimationCurve curve { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TwistChain constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class TwistChainConstraintJobBinder<T> : AnimationJobBinder<TwistChainConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, ITwistChainConstraintData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TwistChainConstraintJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
// Retrieve chain in-between root and tip transforms.
|
||||
Transform[] chain = ConstraintsUtils.ExtractChain(data.root, data.tip);
|
||||
|
||||
// Extract steps from chain.
|
||||
float[] steps = ConstraintsUtils.ExtractSteps(chain);
|
||||
|
||||
// Build Job.
|
||||
var job = new TwistChainConstraintJob();
|
||||
job.chain = new NativeArray<ReadWriteTransformHandle>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.steps = new NativeArray<float>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.weights = new NativeArray<float>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.rotations = new NativeArray<Quaternion>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
job.rootTarget = ReadWriteTransformHandle.Bind(animator, data.rootTarget);
|
||||
job.tipTarget = ReadWriteTransformHandle.Bind(animator, data.tipTarget);
|
||||
|
||||
// Set values in NativeArray.
|
||||
for (int i = 0; i < chain.Length; ++i)
|
||||
{
|
||||
job.chain[i] = ReadWriteTransformHandle.Bind(animator, chain[i]);
|
||||
job.steps[i] = steps[i];
|
||||
job.weights[i] = Mathf.Clamp01(data.curve.Evaluate(steps[i]));
|
||||
}
|
||||
|
||||
job.rotations[0] = Quaternion.identity;
|
||||
job.rotations[chain.Length - 1] = Quaternion.identity;
|
||||
for (int i = 1; i < chain.Length - 1; ++i)
|
||||
{
|
||||
job.rotations[i] = Quaternion.Inverse(Quaternion.Lerp(chain[0].rotation, chain[chain.Length - 1].rotation, job.weights[i])) * chain[i].rotation;
|
||||
}
|
||||
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(TwistChainConstraintJob job)
|
||||
{
|
||||
job.chain.Dispose();
|
||||
job.weights.Dispose();
|
||||
job.steps.Dispose();
|
||||
job.rotations.Dispose();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <inheritdoc />
|
||||
public override void Update(TwistChainConstraintJob job, ref T data)
|
||||
{
|
||||
// Update weights based on curve.
|
||||
for (int i = 0; i < job.steps.Length; ++i)
|
||||
{
|
||||
job.weights[i] = Mathf.Clamp01(data.curve.Evaluate(job.steps[i]));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e6ac5cbead2d48f8b8e5e3911f53ced
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,141 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The TwistCorrection job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct TwistCorrectionJob : IWeightedAnimationJob
|
||||
{
|
||||
/// <summary>The Transform handle for the source object Transform.</summary>
|
||||
public ReadOnlyTransformHandle source;
|
||||
/// <summary>Cached inverse local rotation for source Transform.</summary>
|
||||
public Quaternion sourceInverseBindRotation;
|
||||
/// <summary>The local twist axis</summary>
|
||||
public Vector3 axisMask;
|
||||
|
||||
/// <summary>List of Transform handles for the twist nodes.</summary>
|
||||
public NativeArray<ReadWriteTransformHandle> twistTransforms;
|
||||
/// <summary>List of weights for the twist nodes.</summary>
|
||||
public NativeArray<PropertyStreamHandle> twistWeights;
|
||||
/// <summary>List of cached local rotation for twist nodes.</summary>
|
||||
public NativeArray<Quaternion> twistBindRotations;
|
||||
|
||||
/// <summary>Buffer used to store weights during job execution.</summary>
|
||||
public NativeArray<float> weightBuffer;
|
||||
|
||||
/// <inheritdoc />
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
if (w > 0f)
|
||||
{
|
||||
if (twistTransforms.Length == 0)
|
||||
return;
|
||||
|
||||
AnimationStreamHandleUtility.ReadFloats(stream, twistWeights, weightBuffer);
|
||||
|
||||
Quaternion twistRot = TwistRotation(axisMask, sourceInverseBindRotation * source.GetLocalRotation(stream));
|
||||
Quaternion invTwistRot = Quaternion.Inverse(twistRot);
|
||||
for (int i = 0; i < twistTransforms.Length; ++i)
|
||||
{
|
||||
ReadWriteTransformHandle twistTransform = twistTransforms[i];
|
||||
|
||||
float twistWeight = Mathf.Clamp(weightBuffer[i], -1f, 1f);
|
||||
Quaternion rot = Quaternion.Lerp(Quaternion.identity, Mathf.Sign(twistWeight) < 0f ? invTwistRot : twistRot, Mathf.Abs(twistWeight));
|
||||
twistTransform.SetLocalRotation(stream, Quaternion.Lerp(twistBindRotations[i], rot, w));
|
||||
|
||||
// Required to update handles with binding info.
|
||||
twistTransforms[i] = twistTransform;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < twistTransforms.Length; ++i)
|
||||
AnimationRuntimeUtils.PassThrough(stream, twistTransforms[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static Quaternion TwistRotation(Vector3 axis, Quaternion rot)
|
||||
{
|
||||
return new Quaternion(axis.x * rot.x, axis.y * rot.y, axis.z * rot.z, rot.w);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for TwistCorrection.
|
||||
/// </summary>
|
||||
public interface ITwistCorrectionData
|
||||
{
|
||||
/// <summary>The source Transform that influences the twist nodes.</summary>
|
||||
Transform source { get; }
|
||||
/// <summary>
|
||||
/// The list of Transforms on which to apply twist corrections.
|
||||
/// Each twist node has a weight that ranges from -1 to 1 to control
|
||||
/// how closely a twist node follows source rotation (from 0 to 1),
|
||||
/// or opposite rotation (from -1 to 0).
|
||||
/// </summary>
|
||||
WeightedTransformArray twistNodes { get; }
|
||||
/// <summary>The local twist axis of the source Transform on which to evaluate twist rotation.</summary>
|
||||
Vector3 twistAxis { get; }
|
||||
/// <summary>The path to the twist nodes property in the constraint component.</summary>
|
||||
string twistNodesProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TwistCorrection job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class TwistCorrectionJobBinder<T> : AnimationJobBinder<TwistCorrectionJob, T>
|
||||
where T : struct, IAnimationJobData, ITwistCorrectionData
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override TwistCorrectionJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new TwistCorrectionJob();
|
||||
|
||||
job.source = ReadOnlyTransformHandle.Bind(animator, data.source);
|
||||
job.sourceInverseBindRotation = Quaternion.Inverse(data.source.localRotation);
|
||||
job.axisMask = data.twistAxis;
|
||||
|
||||
WeightedTransformArray twistNodes = data.twistNodes;
|
||||
|
||||
WeightedTransformArrayBinder.BindReadWriteTransforms(animator, component, twistNodes, out job.twistTransforms);
|
||||
WeightedTransformArrayBinder.BindWeights(animator, component, twistNodes, data.twistNodesProperty, out job.twistWeights);
|
||||
|
||||
job.weightBuffer = new NativeArray<float>(twistNodes.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
job.twistBindRotations = new NativeArray<Quaternion>(twistNodes.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int i = 0; i < twistNodes.Count; ++i)
|
||||
{
|
||||
var sourceTransform = twistNodes[i].transform;
|
||||
job.twistBindRotations[i] = sourceTransform.localRotation;
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Destroy(TwistCorrectionJob job)
|
||||
{
|
||||
job.twistTransforms.Dispose();
|
||||
job.twistWeights.Dispose();
|
||||
job.twistBindRotations.Dispose();
|
||||
job.weightBuffer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d160fc2718684f4c823027eda103445
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,142 @@
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// The TwoBoneIK constraint job.
|
||||
/// </summary>
|
||||
[Unity.Burst.BurstCompile]
|
||||
public struct TwoBoneIKConstraintJob : IWeightedAnimationJob
|
||||
{
|
||||
/// <summary>The transform handle for the root transform.</summary>
|
||||
public ReadWriteTransformHandle root;
|
||||
/// <summary>The transform handle for the mid transform.</summary>
|
||||
public ReadWriteTransformHandle mid;
|
||||
/// <summary>The transform handle for the tip transform.</summary>
|
||||
public ReadWriteTransformHandle tip;
|
||||
|
||||
/// <summary>The transform handle for the hint transform.</summary>
|
||||
public ReadOnlyTransformHandle hint;
|
||||
/// <summary>The transform handle for the target transform.</summary>
|
||||
public ReadOnlyTransformHandle target;
|
||||
|
||||
/// <summary>The offset applied to the target transform if maintainTargetPositionOffset or maintainTargetRotationOffset is enabled.</summary>
|
||||
public AffineTransform targetOffset;
|
||||
|
||||
/// <summary>The weight for which target position has an effect on IK calculations. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty targetPositionWeight;
|
||||
/// <summary>The weight for which target rotation has an effect on IK calculations. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty targetRotationWeight;
|
||||
/// <summary>The weight for which hint transform has an effect on IK calculations. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty hintWeight;
|
||||
|
||||
/// <summary>The main weight given to the constraint. This is a value in between 0 and 1.</summary>
|
||||
public FloatProperty jobWeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the root motion.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessRootMotion(AnimationStream stream) { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines what to do when processing the animation.
|
||||
/// </summary>
|
||||
/// <param name="stream">The animation stream to work on.</param>
|
||||
public void ProcessAnimation(AnimationStream stream)
|
||||
{
|
||||
float w = jobWeight.Get(stream);
|
||||
if (w > 0f)
|
||||
{
|
||||
AnimationRuntimeUtils.SolveTwoBoneIK(
|
||||
stream, root, mid, tip, target, hint,
|
||||
targetPositionWeight.Get(stream) * w,
|
||||
targetRotationWeight.Get(stream) * w,
|
||||
hintWeight.Get(stream) * w,
|
||||
targetOffset
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationRuntimeUtils.PassThrough(stream, root);
|
||||
AnimationRuntimeUtils.PassThrough(stream, mid);
|
||||
AnimationRuntimeUtils.PassThrough(stream, tip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines the data mapping for the TwoBoneIK constraint.
|
||||
/// </summary>
|
||||
public interface ITwoBoneIKConstraintData
|
||||
{
|
||||
/// <summary>The root transform of the two bones hierarchy.</summary>
|
||||
Transform root { get; }
|
||||
/// <summary>The mid transform of the two bones hierarchy.</summary>
|
||||
Transform mid { get; }
|
||||
/// <summary>The tip transform of the two bones hierarchy.</summary>
|
||||
Transform tip { get; }
|
||||
/// <summary>The IK target transform.</summary>
|
||||
Transform target { get; }
|
||||
/// <summary>The IK hint transform.</summary>
|
||||
Transform hint { get; }
|
||||
|
||||
/// <summary>This is used to maintain the offset of the tip position to the target position.</summary>
|
||||
bool maintainTargetPositionOffset { get; }
|
||||
/// <summary>This is used to maintain the offset of the tip rotation to the target rotation.</summary>
|
||||
bool maintainTargetRotationOffset { get; }
|
||||
|
||||
/// <summary>The path to the target position weight property in the constraint component.</summary>
|
||||
string targetPositionWeightFloatProperty { get; }
|
||||
/// <summary>The path to the target rotation weight property in the constraint component.</summary>
|
||||
string targetRotationWeightFloatProperty { get; }
|
||||
/// <summary>The path to the hint weight property in the constraint component.</summary>
|
||||
string hintWeightFloatProperty { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The TwoBoneIK constraint job binder.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The constraint data type</typeparam>
|
||||
public class TwoBoneIKConstraintJobBinder<T> : AnimationJobBinder<TwoBoneIKConstraintJob, T>
|
||||
where T : struct, IAnimationJobData, ITwoBoneIKConstraintData
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates the animation job.
|
||||
/// </summary>
|
||||
/// <param name="animator">The animated hierarchy Animator component.</param>
|
||||
/// <param name="data">The constraint data.</param>
|
||||
/// <param name="component">The constraint component.</param>
|
||||
/// <returns>Returns a new job interface.</returns>
|
||||
public override TwoBoneIKConstraintJob Create(Animator animator, ref T data, Component component)
|
||||
{
|
||||
var job = new TwoBoneIKConstraintJob();
|
||||
|
||||
job.root = ReadWriteTransformHandle.Bind(animator, data.root);
|
||||
job.mid = ReadWriteTransformHandle.Bind(animator, data.mid);
|
||||
job.tip = ReadWriteTransformHandle.Bind(animator, data.tip);
|
||||
job.target = ReadOnlyTransformHandle.Bind(animator, data.target);
|
||||
|
||||
if (data.hint != null)
|
||||
job.hint = ReadOnlyTransformHandle.Bind(animator, data.hint);
|
||||
|
||||
job.targetOffset = AffineTransform.identity;
|
||||
if (data.maintainTargetPositionOffset)
|
||||
job.targetOffset.translation = data.tip.position - data.target.position;
|
||||
if (data.maintainTargetRotationOffset)
|
||||
job.targetOffset.rotation = Quaternion.Inverse(data.target.rotation) * data.tip.rotation;
|
||||
|
||||
job.targetPositionWeight = FloatProperty.Bind(animator, component, data.targetPositionWeightFloatProperty);
|
||||
job.targetRotationWeight = FloatProperty.Bind(animator, component, data.targetRotationWeightFloatProperty);
|
||||
job.hintWeight = FloatProperty.Bind(animator, component, data.hintWeightFloatProperty);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys the animation job.
|
||||
/// </summary>
|
||||
/// <param name="job">The animation job to destroy.</param>
|
||||
public override void Destroy(TwoBoneIKConstraintJob job)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c984ab44d4265e41be0681eb0a999b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace UnityEngine.Animations.Rigging
|
||||
{
|
||||
/// <summary>
|
||||
/// This class is used to create Animation C# jobs handles for WeightedTransformArray.
|
||||
/// </summary>
|
||||
public class WeightedTransformArrayBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an array of ReadOnlyTransformHandles representing the new bindings between the Animator and the Transforms in a WeightedTransformArray.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The component owning the WeightedTransformArray property.</param>
|
||||
/// <param name="weightedTransformArray">The WeightedTransformArray property.</param>
|
||||
/// <param name="transforms">The resulting array of ReadOnlyTransformHandles.</param>
|
||||
public static void BindReadOnlyTransforms(Animator animator, Component component, WeightedTransformArray weightedTransformArray, out NativeArray<ReadOnlyTransformHandle> transforms)
|
||||
{
|
||||
transforms = new NativeArray<ReadOnlyTransformHandle>(weightedTransformArray.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int index = 0; index < weightedTransformArray.Count; ++index)
|
||||
{
|
||||
transforms[index] = ReadOnlyTransformHandle.Bind(animator, weightedTransformArray[index].transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an array of ReadWriteTransformHandles representing the new bindings between the Animator and the Transforms in a WeightedTransformArray.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The component owning the WeightedTransformArray property.</param>
|
||||
/// <param name="weightedTransformArray">The WeightedTransformArray property.</param>
|
||||
/// <param name="transforms">The resulting array of ReadWriteTransformHandles.</param>
|
||||
public static void BindReadWriteTransforms(Animator animator, Component component, WeightedTransformArray weightedTransformArray, out NativeArray<ReadWriteTransformHandle> transforms)
|
||||
{
|
||||
transforms = new NativeArray<ReadWriteTransformHandle>(weightedTransformArray.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int index = 0; index < weightedTransformArray.Count; ++index)
|
||||
{
|
||||
transforms[index] = ReadWriteTransformHandle.Bind(animator, weightedTransformArray[index].transform);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an array of PropertyStreamHandle representing the new bindings between the Animator and the weights in a WeightedTransformArray.
|
||||
/// </summary>
|
||||
/// <param name="animator">The Animator on which to bind the new handle.</param>
|
||||
/// <param name="component">The component owning the WeightedTransformArray property.</param>
|
||||
/// <param name="weightedTransformArray">The WeightedTransformArray property.</param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="weights"></param>
|
||||
public static void BindWeights(Animator animator, Component component, WeightedTransformArray weightedTransformArray, string name, out NativeArray<PropertyStreamHandle> weights)
|
||||
{
|
||||
weights = new NativeArray<PropertyStreamHandle>(weightedTransformArray.Count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int index = 0; index < weightedTransformArray.Count; ++index)
|
||||
{
|
||||
weights[index] = animator.BindStreamProperty(component.transform, component.GetType(), name + ".m_Item" + index + ".weight");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26fe63952ce3041dfa37f9c05626256d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user