유니티 - 라인 렌더러를 이용한 단진자 속도의 시각화
참고
- 쿼터니언으로 구현한 단진자의 운동
- 쿼터니언으로 구현한 단진자의 운동 확장
- 독립된 단진자의 운동
- 라인 렌더러를 이용한 단진자 속도의 시각화
이제 단진자의 속도를 라인 렌더러로 표현해보자.
속도가 클수록 라인의 길이가 길어진다.
속도의 크기
속도의 크기는 에너지 보존 법칙을 이용해서 구할 수 있다.
역학적 에너지는 운동 에너지(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에서 확인 가능하다.
Unity Plus:
Unity Pro:
Unity 프리미엄 학습: