수학이나 과학 쪽에 그래픽화 할 것들이 많은 것 같다. 내가 이과라서 그런 걸 수도 있겠지만... 이번에는 사이클로이드를 그려보기로 했다. 옛날 수학 시간에 지오지브라라는 툴을 이용해 그래프를 그리고 시뮬레이션 하는 시간이 있었던 것 같은데 그런 느낌으로 만들어 봤다.
사이클로이드를 그리기 위해 굴러가는 원과 그 위의 한 점을 그려야 한다. 미끄러짐 없이 구르는 원의 중심과 한 점의 좌표는 아래와 같이 구할 수 있다.
const center = [55 + Vx * t, GROUND - RADIUS];
const theta = (Vx * t) / RADIUS - Math.PI / 2;
const point = [
center[0] + LENGTH * Math.cos(theta),
center[1] + LENGTH * Math.sin(theta),
];
원의 중심은 등속 직선 운동을 하고 그 위의 점은 중심을 기준으로 등속 원운동을 한다. 일반적으로 동경이 증가하는 방향은 반시계 방향이나 canvas는 y축이 반대이기 때문에 시계 방향이다. 원이 오른쪽으로 굴러갈 때 회전 방향과 일치한다. 출발점에서 점이 위에서 시작하도록 하기 위해 만큼 빼주었다.
canvas에 그려야 할 요소를 정리하면 지평선, 원, 점, 반지름, 사이클로이드가 있다. 이 중 앞의 네 개는 선으로 간단히 그릴 수 있다.
const drawGround = () => {
context.beginPath();
context.moveTo(0, GROUND);
context.lineTo(400, GROUND);
context.closePath();
context.stroke();
};
const drawCircle = () => {
context.beginPath();
context.arc(center[0], center[1], RADIUS, 0, 2 * Math.PI, true);
context.closePath();
context.stroke();
};
const drawPoint = () => {
context.beginPath();
context.arc(point[0], point[1], playing ? 2 : 6, 0, 2 * Math.PI, true);
context.closePath();
context.fill();
};
const drawRadius = () => {
context.beginPath();
context.moveTo(center[0], center[1]);
context.lineTo(point[0], point[1]);
context.closePath();
context.stroke();
};
사이클로이드는 원이 굴러가는 것에 맞춰 서서히 궤적이 나타나도록 할 것이기 때문에 지나온 점의 좌표를 배열로 저장해두고 누적해서 그리도록 하였다.
points.push(point);
const drawPath = () => {
if (points) {
for (let index = 1; index < points.length; index++) {
context.beginPath();
context.moveTo(points[index - 1][0], points[index - 1][1]);
context.lineTo(points[index][0], points[index][1]);
context.closePath();
context.stroke();
}
}
};
원의 반지름은 고정으로 두고 점과 중심 사이의 거리를 직접 움직일 수 있도록 하였다. 마우스 이벤트로 현재 점 근처를 클릭하고 드래그하면 거리가 바뀌도록 하였고, 그럴 때마다 canvas를 새로 그리도록 했다.
const handleCanvasMouseDown = (event) => {
const point = [55, GROUND - RADIUS - LENGTH];
const distance =
((point[0] - event.offsetX) ** 2 + (point[1] - event.offsetY) ** 2) **
0.5;
if (distance <= 6) {
isClicking = true;
}
};
const handleCanvasMouseMove = (event) => {
if (isClicking && points.length <= 1) {
const distance = GROUND - RADIUS - event.offsetY;
LENGTH = distance;
initiateCanvas();
drawCanvas();
}
};
그리고 애니메이션을 조작할 수 있도록 재생 버튼과 정지 버튼을 만들었다.
중간 단계가 훨씬 깔끔하고 예뻤는데 기능을 넣으면서 구려진 것 같아 슬프다..