곤충 다리 - Three Bone

jkjkbk·2023년 5월 24일
0

Unity

목록 보기
8/16

1. Two Bone IK Constraint 이용해보기


root - joint01 - joint02 - foot 에서

root - joint01 - foot을 Two Bone IK Constraint에 적용
(joint02는 건너뜀)

foot의 의 앞뒤 이동은 꽤나 자연스러움

하지만, foot을 위로 들어 올리면 조금 부자연스럽게 느껴짐

joint02 부분이 foot 높이에 따라 미세하게나마 접히거나 펴졌으면 좋겠음
(이왕 하는 김에 앞뒤 이동할 때도 관절이 접히거나 펴지도록)


실행 중에 joint02를 접으면 어떻게 될까?

시작했을 때 정해진 값(방향이나 길이)대로 적용됨
실행 도중에 상태를 갱신하지 못 하는 것 같음

매 프레임마다 상태를 갱신하는 Two Bone IK Constraint를 구현하자

2. 새 Two Bone IK Constraint 구현하기

2.1 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;
}

MyTwoBoneIK 전체 코드

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;
    }
}

실행 결과

Two Bone IK Constraint 구현 완료. Joint02의 변화가 적용됨

(이름이 joint02을 joint1로...)

아직 target이 많이 멀어지는 경우를 처리하지 못 함

2.2 Root에 대한 Foot의 상대 위치에 따라 Joint02 회전 조절

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);
    }
}

실행 결과

0개의 댓글