root - joint01 - joint02 - foot 에서
root - joint01 - foot을 Two Bone IK Constraint에 적용
(joint02는 건너뜀)
foot의 의 앞뒤 이동은 꽤나 자연스러움
하지만, foot을 위로 들어 올리면 조금 부자연스럽게 느껴짐
joint02 부분이 foot 높이에 따라 미세하게나마 접히거나 펴졌으면 좋겠음
(이왕 하는 김에 앞뒤 이동할 때도 관절이 접히거나 펴지도록)
시작했을 때 정해진 값(방향이나 길이)대로 적용됨
실행 도중에 상태를 갱신하지 못 하는 것 같음
매 프레임마다 상태를 갱신하는 Two Bone IK Constraint를 구현하자
새로운 joint0의 위치는 root와 foot, hint에 의해 결정됨
우선, 사전에 셋팅 해야할 값이 4개가 있음 (tip = foot)
r0 : root - joint01 사이의 뼈의 길이
r1 : joint01 -> foot 사이의 가상의 뼈 길이
rootTojoint01ToLocal : root에 대한 초기 joint01로의 로컬 방향. (관절축?)
joint01ToTipToLocal : 초기 joint01에 대한 초기 foot로의 로컬 방향
// 초기 값 셋팅
public void UpdateCondition()
{
r0 = (root.position - joint01.position).magnitude;
r1 = (joint01.position - tip.position).magnitude;
rootTojoint01ToLocal = root.InverseTransformDirection(joint01.position - root.position);
joint01ToTipToLocal = joint01.InverseTransformDirection(tip.position - joint01.position);
}
// 관절의 로컬 방향(축)을 특정 월드 위치를 바라보는 방향으로 회전
void LookTargetDir(Transform toRotateJoint, Vector3 jointAxisLocalDir, Vector3 toLookTargetPositionToWorld)
{
Vector3 targetDirToLocal = toRotateJoint.InverseTransformDirection(toLookTargetPositionToWorld - toRotateJoint.position);
Quaternion rotation = Quaternion.FromToRotation(jointAxisLocalDir, targetDirToLocal);
toRotateJoint.rotation *= rotation;
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyTwoBoneIK : MonoBehaviour
{
public Transform root;
public Transform joint01;
public Transform joint02;
public Transform tip;
public Transform target;
public float r0;
public float r1;
public Transform hint;
public Vector3 rootTojoint01ToLocalDir;
public Vector3 joint01ToTipToLocalDir;
void Awake()
{
UpdateCondition();
}
private void FixedUpdate()
{
Solve();
}
void Solve()
{
UpdateCondition();
Vector3 d = target.position - root.position;
float squaredR0 = r0 * r0;
float squaredR1 = r1 * r1;
float x = (d.sqrMagnitude + squaredR0 - squaredR1) / 2f / d.magnitude;
Debug.DrawRay(root.position, d.normalized * x, Color.red, 0.1f, true);
Vector3 rootToHint = hint.position - root.position;
Vector3 v = rootToHint;
// 벡터 d에 수직인 벡터 v
Vector3.OrthoNormalize(ref d, ref v);
Vector3 k = root.position + d * x;
float h = Mathf.Sqrt(squaredR0 - x * x);
Debug.DrawRay(k, v * h, Color.blue);
Vector3 newJoint01Position = k + v * h;
Vector3 targetDir = (newJoint01Position - root.position).normalized;
Debug.DrawRay(root.position, targetDir * r0, Color.yellow, 0.1f, true);
LookTargetDir(root, rootTojoint01ToLocalDir, newJoint01Position);
LookTargetDir(joint01, joint01ToTipToLocalDir, target.position);
}
void LookTargetDir(Transform toRotateJoint, Vector3 jointAxisLocalDir, Vector3 toLookTargetPositionToWorld)
{
Vector3 targetDirToLocal = toRotateJoint.InverseTransformDirection(toLookTargetPositionToWorld - toRotateJoint.position);
Quaternion rotation = Quaternion.FromToRotation(jointAxisLocalDir, targetDirToLocal);
toRotateJoint.rotation *= rotation;
}
public void UpdateCondition()
{
r0 = (root.position - joint01.position).magnitude;
r1 = (joint01.position - tip.position).magnitude;
rootTojoint01ToLocalDir = root.InverseTransformDirection(joint01.position - root.position).normalized;
joint01ToTipToLocalDir = joint01.InverseTransformDirection(tip.position - joint01.position).normalized;
}
}
(이름이 joint02을 joint1로...)
아직 target이 많이 멀어지는 경우를 처리하지 못 함
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MyTwoBoneIK : MonoBehaviour
{
public Transform root;
public Transform joint01;
public Transform joint02;
public Transform tip;
public Transform target;
public float r0;
public float r1;
//public float r2;
//public float r3;
public Transform joint01Hint;
public Transform joint02Hint;
public Vector3 rootTojoint01ToLocalDir;
public Vector3 joint01ToTipToLocalDir;
//public Vector3 joint01ToJoint02LocalDir;
//public Vector3 joint02ToTipLocalDir;
/// <summary>
/// root->tip까지의 초기 벡터
/// </summary>
public Vector3 initDiffVectorRootToTip;
/// <summary>
/// joint02의 초기 회전
/// </summary>
public Quaternion initJoint02LocalRotation;
public Transform body;
void Awake()
{
initDiffVectorRootToTip = body.InverseTransformPoint(tip.position - root.position);
initJoint02LocalRotation = joint02.localRotation;
UpdateCondition();
}
private void FixedUpdate()
{
RotateJoint01();
UpdateCondition();
Solve(
root, r0, rootTojoint01ToLocalDir,
joint01, r1, joint01ToTipToLocalDir,
joint01Hint, target.position
);
return;
}
void Solve(
Transform rootJoint, float rootBoneLength, Vector3 rootBoneLocalDir,
Transform midJoint, float midBoneToTipLength, Vector3 midBoneToTipLocalDir,
Transform jointHint, Vector3 targetWorldPosition
)
{
Vector3 d = targetWorldPosition - rootJoint.position;
float squaredR0 = rootBoneLength * rootBoneLength;
float squaredR1 = midBoneToTipLength * midBoneToTipLength;
float x = (d.sqrMagnitude + squaredR0 - squaredR1) / 2f / d.magnitude;
Debug.DrawRay(rootJoint.position, d.normalized * x, Color.red, 0.1f, true);
Vector3 rootToHint = jointHint.position - rootJoint.position;
Vector3 v = rootToHint;
// 벡터 d에 수직인 벡터 v
Vector3.OrthoNormalize(ref d, ref v);
Vector3 k = rootJoint.position + d * x;
float h = Mathf.Sqrt(squaredR0 - x * x);
Debug.DrawRay(k, v * h, Color.blue);
Vector3 newJoint01Position = k + v * h;
Vector3 targetDir = (newJoint01Position - rootJoint.position).normalized;
Debug.DrawRay(rootJoint.position, targetDir * r0, Color.yellow, 0.1f, true);
LookTargetDir(rootJoint, rootBoneLocalDir, newJoint01Position);
LookTargetDir(midJoint, midBoneToTipLocalDir, targetWorldPosition);
}
void LookTargetDir(Transform toRotateJoint, Vector3 jointAxisLocalDir, Vector3 toLookTargetPositionToWorld)
{
Vector3 targetDirToLocal = toRotateJoint.InverseTransformDirection(toLookTargetPositionToWorld - toRotateJoint.position);
Quaternion rotation = Quaternion.FromToRotation(jointAxisLocalDir, targetDirToLocal);
toRotateJoint.rotation *= rotation;
}
public void UpdateCondition()
{
r0 = (root.position - joint01.position).magnitude;
r1 = (joint01.position - tip.position).magnitude;
//r2 = (joint02.position - tip.position).magnitude;
//r3 = (tip.position - joint02.position).magnitude;
rootTojoint01ToLocalDir = root.InverseTransformDirection(joint01.position - root.position).normalized;
joint01ToTipToLocalDir = joint01.InverseTransformDirection(tip.position - joint01.position).normalized;
//joint01ToJoint02LocalDir = joint01.InverseTransformDirection(joint02.position - joint01.position).normalized;
//joint02ToTipLocalDir = joint02.InverseTransformDirection(tip.position - joint02.position).normalized;
}
void RotateJoint01()
{
Vector3 diff = body.InverseTransformPoint(target.position - root.position);
float ratio = diff.sqrMagnitude / initDiffVectorRootToTip.sqrMagnitude;
if (ratio < 1f)
{
ratio *= ratio;
}
Vector3 angles = initJoint02LocalRotation.eulerAngles;
angles.z /= ratio;
joint02.localRotation = Quaternion.Euler(angles);
}
}