본문 바로가기
개발/Unity

유니티 - 쿼터니언으로 구현한 단진자의 운동 확장 (Expanded Simple Pendulum with Unity Quaternion)

by 피로물든딸기 2023. 3. 29.
반응형

Unity 전체 링크

 

참고

- 세 점을 지나는 평면 구하기

- 쿼터니언으로 구현한 단진자의 운동
- 쿼터니언으로 구현한 단진자의 운동 확장
- 독립된 단진자의 운동
- 런타임에 단진자 운동 설정하기

 

쿼터니언으로 구현한 단진자의 운동의 경우

투명한 Plane을 추가해서 그림으로 보면 아래와 같은 상황이다. (x = 0으로 고정)

 

Plane의 설정은 현재 아래와 같다.

 

마찬가지로 pivot과 object의 좌표도 x = 0이다.

이제 object의 x 좌표를 변경해보자.

현재의 코드대로라면 아래와 같이 x 축이 고정된 채로 부자연스럽게 단진자 운동을 하게 된다.

 

x = 0이라고 가정한 코드이기 때문에 수정이 필요하다

오브젝트가 어디에 있던지 간에 자연스럽게 움직일 수 있도록 해보자.


평면 추가

 

먼저 원할한 디버깅을 위해 Pivot 아래에 UnderPivot이라는 임의의 오브젝트를 추가한다.

UnderPivot은 Pivot과 (x, z) 좌표는 같고, y 좌표만 다르다.

 

Oscillator가 어디에 있던, 단진자는 Pivot, UnderPivot, Oscillator 세 점을 지나는 평면에서 움직이게 된다.

세 점을 지나는 평면 구하기를 참고하여 아래의 코드를 추가해보자.

    Vector3 getNormal(Vector3 a, Vector3 b, Vector3 c)
    {
        Plane p = new Plane(a, b, c);
        return p.normal;
    }

    void setPlane(Vector3 a, Vector3 b, Vector3 c)
    {
        plane.transform.position = a;
        plane.transform.up = getNormal(a, b, c);
    }

    void Start()
    {   
		...
        setPlane(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);
    }

 

처음에는 세 오브젝트가 모두 평면에 존재한다.

 

하지만, 단진자는 평면에서 움직이지 않는다.


코드 확장

 

문제가 되는 코드는 getRotateQuaternion 함수다.

angle을 보면 new Vector3에서 x축에만 값이 들어간다.

    Quaternion getRotateQuaternion(float angle)
    {
        Quaternion current = Quaternion.Euler(new Vector3(0, 0, 0));
        Quaternion quaternion = Quaternion.Euler(new Vector3(angle, 0, 0));

        return current * quaternion;
    }

 

이것을 노멀 벡터를 기준으로 회전하도록 변경하면 된다.

AngleAxis를 이용하면 노멀 벡터를 기준으로 angle만큼 회전할 수 있다.

    Quaternion getRotateQuaternion(float angle, Vector3 normal)
    {
        Quaternion current = Quaternion.Euler(new Vector3(0, 0, 0));
        Quaternion quaternion = Quaternion.AngleAxis(angle, normal);

        return current * quaternion;
    }

 

normal은 pivot의 아래, pivot, 진자를 기준으로 만들었다.

반대로 만들 경우, start/endQt의 normal에 -를 곱해야한다.

        Vector3 normal 
            = getNormal(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);

        setPlane(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);

        startQt = getRotateQuaternion(0, normal);
        endQt = getRotateQuaternion(2 * angle, normal);

 

전체 코드는 다음과 같다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ExpandedSimpleHarmonicOscillator : MonoBehaviour
{
    public Transform pivot;
    public float speed = 2.0f;
    public GameObject plane;

    Quaternion startQt, endQt;

    public GameObject lineRenderer;
    LineRenderer[] lrs;

    float startRot = 0.0f;

    void setLineRenderer(LineRenderer lr, Color color)
    {
        lr.startWidth = lr.endWidth = .1f;
        lr.material.color = color;

        lr.positionCount = 2;
    }

    Quaternion getRotateQuaternion(float angle, Vector3 normal)
    {
        Quaternion current = Quaternion.Euler(new Vector3(0, 0, 0));
        Quaternion quaternion = Quaternion.AngleAxis(angle, normal);

        return current * quaternion;
    }

    Vector3 getNormal(Vector3 a, Vector3 b, Vector3 c)
    {
        Plane p = new Plane(a, b, c);
        return p.normal;
    }

    void setPlane(Vector3 a, Vector3 b, Vector3 c)
    {
        plane.transform.position = a;
        plane.transform.up = getNormal(a, b, c);
    }

    void Start()
    {   
        float angle = Vector3.Angle(this.transform.position - pivot.position, Vector3.down);

        Vector3 normal 
            = getNormal(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);

        setPlane(pivot.position + Vector3.down, pivot.transform.position, this.transform.position);

        startQt = getRotateQuaternion(0, normal);
        endQt = getRotateQuaternion(2 * angle, normal);

        lrs = lineRenderer.GetComponentsInChildren<LineRenderer>();
        setLineRenderer(lrs[0], Color.blue);
        lrs[0].SetPosition(0, pivot.position);
    }

    void Update()
    {
        lrs[0].SetPosition(1, this.transform.position);
    }

    void FixedUpdate()
    {
        startRot += (Time.fixedDeltaTime * speed);
        pivot.rotation
            = Quaternion.Lerp(startQt, endQt, (Mathf.Sin(startRot - Mathf.PI / 2) + 1.0f) / 2.0f);
    }
}

 

이제 진자가 어디에 있더라도 자연스럽에 움직인다.

 

위의 실행결과는 아래의 unitypackage에서 확인 가능하다.

ExpandedSimpleHarmonicOscillator.unitypackage
0.03MB

 

Unity Plus:

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Unity Pro:

 

Unity Pro

The complete solutions for professionals to create and operate.

unity.com

 

Unity 프리미엄 학습:

 

Unity Learn

Advance your Unity skills with live sessions and over 750 hours of on-demand learning content designed for creators at every skill level.

unity.com

반응형

댓글