개발/Unity

유니티 - 라인 렌더러를 이용한 단진자 속도의 시각화

피로물든딸기 2023. 4. 3. 12:19
반응형

Unity 전체 링크

 

참고

- 쿼터니언으로 구현한 단진자의 운동
- 쿼터니언으로 구현한 단진자의 운동 확장
- 독립된 단진자의 운동

- 런타임에 단진자 운동 설정하기

- 라인 렌더러를 이용한 단진자 속도의 시각화

- 라인 렌더러로 간단한 화살표 만들기

- 운동하는 단진자의 로프를 끊기

 

이제 단진자의 속도를 라인 렌더러로 표현해보자.

속도가 클수록 라인의 길이가 길어진다.


속도의 크기

 

속도의 크기는 에너지 보존 법칙을 이용해서 구할 수 있다.

역학적 에너지는 운동 에너지(K)위치 에너지(U)의 합이다.

 

 

단진자가 처음 시작되는 경우에는 속도가 0이기 때문에 K = 0이 되고, 총 에너지는 다음과 같다.

 

 

여기서 h는 this.transform.position.y가 된다.

 

이후 h → h'가 된다면, 운동에너지를 다음과 같이 구할 수 있다.

 

 

따라서 속도의 크기는 다음과 같다.

 


속도의 방향

 

속도의 크기는 위치에 따라 알 수 있지만, 속도의 방향은 다를 수 있다.

아래와 같이 같은 위치에 있는 진자라도 위로 올라가는 중인지, 아래로 떨어지는 중인지에 따라 방향이 달라진다.

하지만 속도의 방향이 pivot → 진자의 방향에 대해 90도가 되는 것은 확실하다.

 

단진자는 4개의 상태로 나눌 수 있다.

 

1. 오른쪽으로 이동하면서 공이 내려가는 경우

2. 오른쪽으로 이동하면서 공이 올라가는 경우

3. 왼쪽으로 이동하면서 공이 내려가는 경우

4. 왼쪽으로 이동하면서 공이 올라가는 경우

 

위 4가지 상태가 반복된다.

 

단진자의 각도를 구할 때 사용한 LerpAngle에서 Mathf.Sin 내부에 있는 값이 이 주기를 결정한다.

    float angle 
        = Mathf.LerpAngle(startAngle, endAngle, (Mathf.Sin(startRot - Mathf.PI / 2) + 1.0f) / 2.0f);

 

따라서 현재의 상태는 4가지로 나눌 수 있고, 0 ~ 1이면 + 방향, 2 ~ 3이면 - 방향이 되도록 만든다.

    int getCurrentState()
    {
        float hPi = Mathf.PI / 2.0f;
        return (int)(startRot / hPi) % 4;
    }

    float getNormalDirection()
    {
        return getCurrentState() < 2 ? 1.0f : -1.0f;
    }

 

이후 속도의 크기 v를 위에 말한 공식으로 구한 후, 방향 verocity를 90도 회전시키고 위의 공식대로 방향을 곱한다.

    float kinetic = gravity * (totalEnergy - this.transform.position.y);
    float v = Mathf.Sqrt(2 * kinetic); // k = 1/2 m v^2 -> v = sqrt(2k/m)
    Vector3 verocity 
        = (Quaternion.AngleAxis(90, startPlaneNormal) * vPO).normalized * getNormalDirection();

 

라인 렌더러로 표시하면 다음과 같이 나타낼 수 있다.

    lrs[1].SetPosition(0, this.transform.position);
    lrs[1].SetPosition(1, this.transform.position + verocity * v);

 

시각적으로 보기 편하게 gravity는 1.0f로 설정하였다. 

float gravity = 1.0f;

 

전체 코드는 다음과 같다.

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

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

    float gravity = 1.0f;

    float startAngle, endAngle;
    Vector3 startPlaneNormal;

    Vector3 startPO;
    float distance;

    public GameObject lineRenderer;
    LineRenderer[] lrs;

    float startRot = 0.0f;

    bool isDragging;

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

        lr.positionCount = 2;
    }

    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 OnMouseDown()
    {
        isDragging = true;
    }

    void OnMouseDrag()
    {
        float distance = Camera.main.WorldToScreenPoint(transform.position).z;

        Vector3 mousePos = new Vector3(Input.mousePosition.x, Input.mousePosition.y, distance);
        Vector3 objPos = Camera.main.ScreenToWorldPoint(mousePos);

        objPos.x = 0;
        this.transform.position = objPos;
    }

    void OnMouseUp()
    {
        initSettings();
        isDragging = false;
    }

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

        startAngle = 0.0f; endAngle = angle * 2;

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

        startPlaneNormal = normal;

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

        startPO = (this.transform.position - pivot.position).normalized;
        distance = Vector3.Distance(this.transform.position, pivot.position);

        startRot = 0.0f;

        totalEnergy = this.transform.position.y; // Potential Energy = mgh
    }

    void Start()
    {
        initSettings();

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

        setLineRenderer(lrs[1], Color.red);     
    }

    int getCurrentState()
    {
        float hPi = Mathf.PI / 2.0f;
        return (int)(startRot / hPi) % 4;
    }

    float getNormalDirection()
    {
        return getCurrentState() < 2 ? 1.0f : -1.0f;
    }

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

    void FixedUpdate()
    {
        if (isDragging) return;

        startRot += (Time.fixedDeltaTime * speed);

        float angle 
            = Mathf.LerpAngle(startAngle, endAngle, (Mathf.Sin(startRot - Mathf.PI / 2) + 1.0f) / 2.0f);

        Vector3 vPO 
            = Quaternion.AngleAxis(angle, startPlaneNormal) * startPO;

        this.transform.position 
            = pivot.transform.position + (vPO).normalized * distance;

        float kinetic = gravity * (totalEnergy - this.transform.position.y);
        float v = Mathf.Sqrt(2 * kinetic); // k = 1/2 m v^2 -> v = sqrt(2k/m)
        Vector3 verocity 
            = (Quaternion.AngleAxis(90, startPlaneNormal) * vPO).normalized * getNormalDirection();

        lrs[1].SetPosition(0, this.transform.position);
        lrs[1].SetPosition(1, this.transform.position + verocity * v);
    }
}

 

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

VelocityWithLineRenderer.unitypackage
0.03MB

반응형