arctan을 활용한 각도 구하기

Dave Lee·2021년 3월 23일
2

캔버스나 뷰에서 이런저런 장난을 치다 보면, 두 점 사이의 각도를 구해야 할 때가 있다. 이럴 때는 삼각함수 중 arctan을 이용해보자.

arctan

삼각함수중 tan을 이용하면 특정한 각도에서의 세로/가로의 비를 구할 수 있다. 이를 역으로 계산해주는 함수가 arctan이다.

그렇지만 그래프에서 알 수 있듯이, -90°~90° 사이의 각만 계산되므로 별도의처리를 좀 더 해주어야 -180°~180° 사이의 각을 얻을 수 있다.

위와 같은 컴퓨터 화면에서 θ(이하 a)를 계산하는 함수를 만들어보자. 윗쪽을 0° 라고 하고, 시계방향이 증가하는 방향이라고 정의하자. a주변의 작은 삼각형을 생각해 볼 때, 화살표 방향에 대해 높이는 x2-x1 이지만 밑변은 y1-y2 이다.

let a;
a = Math.atan((x2-x1)/(y1-y2));

별도 처리 각도

그렇지만 대충 보아도 y1==y2일 경우 위 문장은 오류가 난다. -90°,90°,180°는 따로 처리해주자.

let a;
if(y1 == y2) {
	if(x2<x1) {
    	a = -90;
    } else {
    	a = 90;
    }
} else if(x1==x2 && y2>y1) {
	a = 180;
} else {
	a = Math.atan((x2-x1)/(y1-y2));
}

이렇게 하면 각도의 범위중 -90°~90°, 180° 가 해결되었다.

90°초과, -90°미만의 각도

나머지 범위에 해당하는 각 경우를 살펴보자.

+90°를 넘어가는 경우에, 가령 150°의 경우에 (x2-x1)/(y1-y2) 의 비는 -30° 일 때와 동일하고, 구분하는 방법은 y2>y1 인지만 확인하면 된다. 따라서 이 경우 +180° 에 arctan 값(-30°)을 더하면 된다.

마찬가지로 -90°를 넘어가는 경우에, 가령 -150°의 경우에 (x2-x1)/(y1-y2) 의 비는 +30° 일 때와 동일하고, 구분하는 방법은 y2>y1 인지만 확인하면 된다. 따라서 이 경우 -180° 에 arctan 값(+30°)을 더하면 된다.

함수는 다음과 같이 된다.

let a;
if(y1 == y2) {
	if(x2<x1) {
    	a = -90;
    } else {
    	a = 90;
    }
} else if(x1==x2 && y2>y1) {
	a = 180;
} else {
	a = Math.atan((x2-x1)/(y1-y2));
    if(y2>y1 && x2>x1) {
    	a = 180 + a;
    } else if(y2>y1 && x2<x1) {
    	a = -180 + a;
    }
}

radian

아직 할 일이 남아 있다. 자바스크립트를 포함한 대부분의 프로그래밍 언어/라이브러리에서 삼각함수를 처리할 때 다루는 각도는 degree(°)단위가 아니라 rad 단위이다. 1rad는 반지름의 길이와 호의길이가 같은 부채꼴의 중심각 크기이다. (대략 57.3°)

180°크기의 반원 피자가 있다고 하자. 우리는 이 피자를 잘라서 위와 같은 피자 조각을 3개하고 조금 더 얻을 수 있을 것이다. 좀 더 정확히는 3개하고 0.14개 정도. 따라서 s rad와 t degree 사이에는 다음과 같은 식이 성립한다.

180° : 3.14 = t : s
t = s × 180 / 3.14
또는 t = s × 180 / Math.PI

함수형태로 완성시켜보자.

function getAngle(x1,y1,x2,y2) {
    let a;
    if(y1 == y2) {
        if(x2<x1) {
            a = -90;
        } else {
            a = 90;
        }
    } else if(x1==x2 && y2>y1) {
        a = 180;
    } else {
        const rad = Math.atan((x2-x1)/(y1-y2));
        a = rad * 180 / Math.PI;
        
        if(y2>y1 && x2>x1) {
            a = 180 + a;
        } else if(y2>y1 && x2<x1) {
            a = -180 + a;
        }
    }
    return a;
}

test code

간단한 테스터를 달아보자.

index.html

<!DOCTYPE html>
<html>
  <head>
    <script src="index.js"></script>
    <style>
      #test { border: 1px solid #000; width:400px; height:400px; }
    </style>
  </head>
  <body>
    <div id="test"></div>
  </body>
</html>

index.js

function getAngle(x1,y1,x2,y2) {
  let a;
  if(y1 == y2) {
      if(x2<x1) {
          a = -90;
      } else {
          a = 90;
      }
  } else if(x1==x2 && y2>y1) {
      a = 180;
  } else {
      const rad = Math.atan((x2-x1)/(y1-y2));
      a = rad * 180 / Math.PI;
      
      if(y2>y1 && x2>x1) {
          a = 180 + a;
      } else if(y2>y1 && x2<x1) {
          a = -180 + a;
      }
  }
  return a;
}

document.addEventListener("DOMContentLoaded", () => {
  const testField = document.querySelector("#test");
  testField.addEventListener('mousemove', e => {
    const angle = getAngle(200, 200, e.offsetX, e.offsetY);
    console.log(angle);      
  })
})

실행결과는 다음과 같다.

profile
developer

0개의 댓글