Skip to content

Instantly share code, notes, and snippets.

@hiroshi-nishiura
Created July 29, 2022 02:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hiroshi-nishiura/9a696e2f5ba28fc82e410f8c16dc09c9 to your computer and use it in GitHub Desktop.
Save hiroshi-nishiura/9a696e2f5ba28fc82e410f8c16dc09c9 to your computer and use it in GitHub Desktop.
Hand IK Applier for Unity
using System;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;
public struct IKTarget
{
public Vector3 pos;
public Quaternion rot;
public Vector3 hint;
}
public struct HandIKJob : IAnimationJob
{
public IKTarget left;
public IKTarget right;
public PropertySceneHandle leftWeightHandle;
public PropertySceneHandle rightWeightHandle;
public void ProcessRootMotion(AnimationStream stream){}
public void ProcessAnimation(AnimationStream stream)
{
AnimationHumanStream humanStream = stream.AsHuman();
left.pos = humanStream.GetGoalPosition(AvatarIKGoal.LeftHand);
left.rot = humanStream.GetGoalRotation(AvatarIKGoal.LeftHand);
left.hint = humanStream.GetHintPosition(AvatarIKHint.LeftElbow);
right.pos = humanStream.GetGoalPosition(AvatarIKGoal.RightHand);
right.rot = humanStream.GetGoalRotation(AvatarIKGoal.RightHand);
right.hint = humanStream.GetHintPosition(AvatarIKHint.RightElbow);
float weightL = leftWeightHandle.GetFloat(stream);
float weightR = rightWeightHandle.GetFloat(stream);
if(weightL>0)
{
humanStream.SetGoalPosition(AvatarIKGoal.LeftHand, left.pos);
humanStream.SetGoalRotation(AvatarIKGoal.LeftHand, left.rot);
humanStream.SetHintPosition(AvatarIKHint.LeftElbow, left.hint);
humanStream.SetGoalWeightPosition(AvatarIKGoal.LeftHand, weightL);
humanStream.SetGoalWeightRotation(AvatarIKGoal.LeftHand, weightL);
humanStream.SetHintWeightPosition(AvatarIKHint.LeftElbow, weightL);
}
if(weightR>0)
{
humanStream.SetGoalPosition(AvatarIKGoal.RightHand, right.pos);
humanStream.SetGoalRotation(AvatarIKGoal.RightHand, right.rot);
humanStream.SetHintPosition(AvatarIKHint.RightElbow, right.hint);
humanStream.SetGoalWeightPosition(AvatarIKGoal.RightHand, weightR);
humanStream.SetGoalWeightRotation(AvatarIKGoal.RightHand, weightR);
humanStream.SetHintWeightPosition(AvatarIKHint.RightElbow, weightR);
}
if(weightL>0 || weightR>0) humanStream.SolveIK();
}
}
public class HandIKApplier : MonoBehaviour
{
public bool _drawGizmo = false;
public PlayableDirector _director;
[Range(0,1)]
public float _leftWeight;
[Range(0,1)]
public float _rightWeight;
Animator _animator;
AnimationScriptPlayable _playable;
IKTarget _left;
IKTarget _right;
void OnEnable()
{
for(Transform current = this.transform; _animator==null && current!=null; current = current.parent)
_animator = current.GetComponent<Animator>();
if(_animator==null) throw new System.Exception("Animator has not in the parent");
if(_director && _director.playableGraph.IsValid()) CreateJob(_director.playableGraph);
}
void OnDisable()
{
if (_playable.IsValid()) _playable.Destroy();
}
void CreateJob(PlayableGraph graph) // PlayableDirector's graph
{
for(int i=0; i<graph.GetOutputCountByType<AnimationPlayableOutput>(); i++)
{
AnimationPlayableOutput output = (AnimationPlayableOutput) graph.GetOutputByType<AnimationPlayableOutput>(i);
if(output.GetTarget() == _animator && output.IsOutputValid())
{
var job = new HandIKJob()
{
leftWeightHandle = _animator.BindSceneProperty(transform, typeof(HandIKApplier), "_leftWeight"),
rightWeightHandle = _animator.BindSceneProperty(transform, typeof(HandIKApplier), "_rightWeight")
};
_playable = AnimationScriptPlayable.Create(graph, job, 1);
_playable.SetInputWeight(0, 1);
var timeline = output.GetSourcePlayable();
var source = timeline.GetInput(i);
graph.Disconnect(timeline, i);
graph.Connect(source, 0, _playable, 0);
graph.Connect(_playable, 0, timeline, i);
break;
}
}
//Debug.Log("CreateJob : " + _animator + " // " + _playable);
}
void OnAnimatorIK()// Animation Controller events
{
_animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, _leftWeight);
_animator.SetIKRotationWeight(AvatarIKGoal.LeftHand, _leftWeight);
_animator.SetIKHintPositionWeight(AvatarIKHint.LeftElbow, _leftWeight);
_animator.SetIKPositionWeight(AvatarIKGoal.RightHand, _rightWeight);
_animator.SetIKRotationWeight(AvatarIKGoal.RightHand, _rightWeight);
_animator.SetIKHintPositionWeight(AvatarIKHint.RightElbow, _rightWeight);
}
void Update()
{
if(_director && _director.playableGraph.IsValid() && !_playable.IsValid()) CreateJob(_director.playableGraph);
}
void LateUpdate()
{
if (_playable.IsValid())
{
var job = _playable.GetJobData<HandIKJob>();
_left = job.left;
_right = job.right;
}
}
#if UNITY_EDITOR
void OnDrawGizmosSelected()
{
if (_drawGizmo && _playable.IsValid())
{
Vector3 scale = new Vector3(1,1,1);
Gizmos.color = Color.magenta;
Gizmos.matrix = Matrix4x4.TRS(_left.pos, _left.rot, scale);
Gizmos.DrawWireSphere(Vector3.zero, 0.05f);
Gizmos.matrix = Matrix4x4.TRS(_right.pos, _right.rot, scale);
Gizmos.DrawWireSphere(Vector3.zero, 0.05f);
Gizmos.matrix = Matrix4x4.identity;
Gizmos.DrawWireSphere(_left.hint, 0.05f);
Gizmos.DrawWireSphere(_right.hint, 0.05f);
}
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment