[토이] Cycloid

EEuglena·2023년 11월 14일
0

토이 프로젝트

목록 보기
6/6

아이디어

수학이나 과학 쪽에 그래픽화 할 것들이 많은 것 같다. 내가 이과라서 그런 걸 수도 있겠지만... 이번에는 사이클로이드를 그려보기로 했다. 옛날 수학 시간에 지오지브라라는 툴을 이용해 그래프를 그리고 시뮬레이션 하는 시간이 있었던 것 같은데 그런 느낌으로 만들어 봤다.

구현 1. 수식으로 변환

사이클로이드를 그리기 위해 굴러가는 원과 그 위의 한 점을 그려야 한다. 미끄러짐 없이 구르는 원의 중심과 한 점의 좌표는 아래와 같이 구할 수 있다.

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축이 반대이기 때문에 시계 방향이다. 원이 오른쪽으로 굴러갈 때 회전 방향과 일치한다. 출발점에서 점이 위에서 시작하도록 하기 위해 π2\pi \over 2만큼 빼주었다.

구현 2. 요소 그리기

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

구현 3. 상호작용

원의 반지름은 고정으로 두고 점과 중심 사이의 거리를 직접 움직일 수 있도록 하였다. 마우스 이벤트로 현재 점 근처를 클릭하고 드래그하면 거리가 바뀌도록 하였고, 그럴 때마다 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();
	}
};

그리고 애니메이션을 조작할 수 있도록 재생 버튼과 정지 버튼을 만들었다.

결과물

중간 단계가 훨씬 깔끔하고 예뻤는데 기능을 넣으면서 구려진 것 같아 슬프다..

코드는 깃허브에

깃허브 링크

0개의 댓글