Created
September 20, 2024 15:06
-
-
Save keenanwoodall/e6cb8b849c655c8b22e6ff52579e6b12 to your computer and use it in GitHub Desktop.
Animation Rigging CCDIK
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CCDIKConstraint : RigConstraint<CCDIKConstraint.Job, CCDIKConstraint.Data, CCDIKConstraint.Binder> | |
{ | |
public struct Job : IWeightedAnimationJob | |
{ | |
public FloatProperty jobWeight { get; set; } | |
public ReadOnlyTransformHandle target; | |
public NativeArray<ReadWriteTransformHandle> chain; | |
public NativeArray<Vector3> bindPositions; | |
public NativeArray<Quaternion> bindRotations; | |
public NativeArray<float> weights; | |
public NativeArray<float> steps; | |
public IntProperty maxIterations; | |
public FloatProperty errorDistance; | |
public FloatProperty tipRotationWeight; | |
public void ProcessRootMotion(AnimationStream stream){} | |
public void ProcessAnimation(AnimationStream stream) | |
{ | |
var totalWeight = jobWeight.Get(stream); | |
if (totalWeight <= 0f) | |
return; | |
// Reset chain transforms to the local pos/rots they had when first bound. | |
for (int i = 0; i < chain.Length; i++) | |
{ | |
var handle = chain[i]; | |
handle.SetLocalPosition(stream, bindPositions[i]); | |
handle.SetLocalRotation(stream, bindRotations[i]); | |
} | |
var targetPosition = target.GetPosition(stream); | |
var iterationCount = 0; | |
var sqrDist = 0f; | |
do | |
{ | |
// CCDIK: Iterate over each joint in the chain and rotate it so that the line between it and the tip is | |
// aligned with the target position. | |
for (int i = 0; i < chain.Length; i++) | |
{ | |
var weight = weights[i]; | |
var tipPosition = chain[0].GetPosition(stream); | |
var jointPosition = chain[i].GetPosition(stream); | |
var jointRotation = chain[i].GetRotation(stream); | |
var correctiveRotation = CorrectiveBoneRotation(tipPosition, jointPosition, jointRotation, targetPosition); | |
correctiveRotation = Quaternion.Slerp(Quaternion.identity, correctiveRotation, totalWeight * weight); | |
chain[i].SetRotation(stream, correctiveRotation * jointRotation); | |
} | |
sqrDist = (chain[0].GetPosition(stream) - targetPosition).sqrMagnitude; | |
iterationCount++; | |
} | |
while (sqrDist > errorDistance.Get(stream) && iterationCount < maxIterations.Get(stream)); | |
chain[0].SetRotation(stream, Quaternion.Lerp(target.GetRotation(stream), chain[0].GetRotation(stream), totalWeight * tipRotationWeight.Get(stream))); | |
} | |
public Quaternion CorrectiveBoneRotation(Vector3 tipPosition, Vector3 jointPosition, Quaternion jointRotation, Vector3 targetPosition) | |
{ | |
var jointToTip = tipPosition - jointPosition; | |
var jointToTarget = targetPosition - jointPosition; | |
return Quaternion.FromToRotation(jointToTip, jointToTarget); | |
} | |
} | |
[System.Serializable] | |
public struct Data : IAnimationJobData | |
{ | |
[SyncSceneToStream] | |
public Transform root; | |
[SyncSceneToStream] | |
public Transform tip; | |
[SyncSceneToStream] | |
public Transform target; | |
public AnimationCurve weight; | |
[Range(0f, 1f), SyncSceneToStream] | |
public float tipRotationWeight; | |
[SyncSceneToStream] | |
public int maxIterations; | |
[SyncSceneToStream] | |
public float errorDistance; | |
public bool IsValid() => root && tip && target && maxIterations > 0; | |
public void SetDefaultValues() | |
{ | |
weight = AnimationCurve.Linear(0f, 1f, 1f, 0f); | |
tipRotationWeight = 0f; | |
maxIterations = 15; | |
errorDistance = 0.001f; | |
} | |
} | |
public class Binder : AnimationJobBinder<Job, Data> | |
{ | |
public override Job Create(Animator animator, ref Data data, Component component) | |
{ | |
var chain = ConstraintsUtils.ExtractChain(data.root, data.tip); | |
Array.Reverse(chain); | |
var steps = ConstraintsUtils.ExtractSteps(chain); | |
var nativeChain = new NativeArray<ReadWriteTransformHandle>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); | |
var nativeBindPositions = new NativeArray<Vector3>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); | |
var nativeBindRotations = new NativeArray<Quaternion>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); | |
var nativeWeights = new NativeArray<float>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); | |
var nativeSteps = new NativeArray<float>(chain.Length, Allocator.Persistent, NativeArrayOptions.UninitializedMemory); | |
for (int i = 0; i < chain.Length; i++) | |
{ | |
var joint = chain[i]; | |
var t = i / (chain.Length - 1f); | |
nativeChain[i] = ReadWriteTransformHandle.Bind(animator, joint); | |
nativeBindPositions[i] = joint.localPosition; | |
nativeBindRotations[i] = joint.localRotation; | |
nativeSteps[i] = steps[i]; | |
nativeWeights[i] = data.weight.Evaluate(t); | |
} | |
var job = new Job | |
{ | |
chain = nativeChain, | |
bindPositions = nativeBindPositions, | |
bindRotations = nativeBindRotations, | |
weights = nativeWeights, | |
steps = nativeSteps, | |
target = ReadOnlyTransformHandle.Bind(animator, data.target), | |
maxIterations = IntProperty.Bind(animator, component, ConstraintsUtils.ConstructConstraintDataPropertyName(nameof(Data.maxIterations))), | |
errorDistance = FloatProperty.Bind(animator, component, ConstraintsUtils.ConstructConstraintDataPropertyName(nameof(Data.errorDistance))), | |
tipRotationWeight = FloatProperty.Bind(animator, component, ConstraintsUtils.ConstructConstraintDataPropertyName(nameof(Data.tipRotationWeight))), | |
}; | |
return job; | |
} | |
public override void Destroy(Job job) | |
{ | |
job.chain.Dispose(); | |
job.bindPositions.Dispose(); | |
job.bindRotations.Dispose(); | |
job.steps.Dispose(); | |
job.weights.Dispose(); | |
} | |
public override void Update(Job job, ref Data data) | |
{ | |
for (int i = 0; i < job.chain.Length; i++) | |
{ | |
var t = i / (job.chain.Length - 1f); | |
job.weights[i] = data.weight.Evaluate(t); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment