'좌표계'에 해당되는 글 1건

  1. 2009.07.20 삼각함수를 이용한 좌표계 계산 (4)
삼각함수를 이용하여 좌표계를 계산하는 로직을 정리했다.
리펙토링 건덕지가 무척 많아 솔직히 손을 많이 대야 겠지만,
그래도 나름대로 작성한 부분이 있어 정리한다.

만든 내용을 정리하자면, 사각형 두개를 그린 뒤, 해당 사각형의 중심좌표를 구한다.
그리고 그 중심좌표들을 양끝으로 하는 선을 긋는다.
이 때, 선과 사각형이 만나는 좌표를 구하는 공식을 만들어봤다.


이 작업의 시작은 시작점과 끝점으로 연결된 직선을 빗변으로한 삼각형이라고 생각한 뒤,
각도를 구해야 한다. 삼각형의 특정 부위의 값만 구하면 각도 구하는 방법이 무척많다.
이 때 바로 등장하는게 삼각함수 였던 것이다.

맨 먼저 가정 부터 시작한다.
2개의 사각형이 있고, 좌표계에서 위치와 크기를 정확히 알고 있다는 데서 시작한다.
사각형은 총 4개의 점에서 출발하는데, 반드시 직각사각형이며,
각 좌표 값은 직관적 계산을 통해 얻어 올 수 있어야 한다. (한 점과 너비, 높이만 알면 사각형이 만들어진다.)
아래의 그림의 사각형은 특정좌표 값을 이미 가지고 있다는 가정에서 출발한다.
(컴퓨터로 그래픽 객체를 다루어보았다면 쉽게 알 수 있다.)

각 좌표나 값들이 정의되었으면, 이제 해야 하는 것은 각도를 구하는 것이다.
구해야 되는 각도는 radian 이라고 하는 삼각함수 계산용 각도다.
(일반 각도랑 틀림 : 참고 -> 위키피아 라디안 )



이 라디안을 구하기 위해서는 아크 탄젠트라고 불리는 삼각함수를 써야 한다.
보통 탄젠트 = 높이 / 밑변 으로 계산된다. 이 때 탄젠트에 걸친 각도 값을 구하기 위해서는 그의 역함수인
아크 탄젠트를 써야 한다. 즉 arctan(높이/밑변) 하면 radian 값이 나오게 된다.


자 이제 각도를 구했으니, 실제 목적인 사각형과 직선과 만나는 위치에 대한 좌표를 구해야 한다.
최소한 좌표 중 X 좌표는 구할 수 있다. 사각형의 중심점에서 사각형 너비/2 를 빼면 된다.
그러면 X 좌표가 나온다. 그러나, Y 좌표는 그냥 찍으면 모를까, 정확한 값은 나오지 않는다.
이 때 실제적인 삼각함수를 적용한다.

여기서 우리가 아는 값은 알고 싶은 좌표의 X 좌표. 그리고 각도(Radian 값이다.)이다.
이 두개의 값을 이용해서 뽑는 방법을 찾아보자.


먼저 알고 싶어하는 Y 좌표를 길이로 생각하자. 중심점을 기준으로 일정 양만큼 올라가면,
Y 좌표가 나오게 된다. 이 때 일정 양을 b' 라고 한다. 즉 좌표 값이 아닌 길이로 생각하는 것이다.

마찬가지로 중심점을 기준으로 X 좌표간의 차이를 a' 라고 한다.

잘 보면, 삼각형이 다시 만들어진다. a'가 밑변, b'가 높이인 형태의 삼각형인 것이다.
이를 tangent를 이용해서 값을 구하는 것이다.

tangent의 공식을 보면 이렇다.
tan(radian) = 높이 / 밑변.

이 중 우리가 구해야 할 값이 높이라고 할 때, 그 값을 b' 라고 하면,

tan(radian) = b' / 밑변.

이 값들을 우리가 알고 있는 값들로 치환하면,

tan(R < -이건 앞의 아크 탄젠트로 구했다.) = b' / a'

이걸 양변에 a'를 곱하면,

a' * tan(R) = b'

끝.

그래서 구한 b' 값을 중심점 좌표의 Y에서 b' 만큼 빼거나 더하면 된다.


앗.. 여기서 중요한 점.
b' 값을 더하거나 빼야 한다고 했는데, 그 기준이 무척 중요하다.
언제 빼고 언제 더해야 할까?

사각형을 수평으로 반으로 나눈 다음, 직선이 위를 바라보면 뺀다.
역으로 아래를 바라보면 더하면 되는 것이다.
(이 부분은 수학적으로 판단하기 보다, 컴퓨터 그래픽 좌표계로 판단하면 쉽다.)

아래의 그림은 위의 공식을 이용해 계산된 결과 값이다.
까만색 상자에서 출발해서 흰색 상자에서 끝난다.
직선은 각 상자의 중심점을 연결했다.
위의 계산식의 결과로 나온 점을 찍어보면,
직선과 검은색 상자가 만나는 부분이 빨강색 점처럼 나타나게 된다.






protected override void OnPaint(PaintEventArgs e)
{
    base.OnPaint(e);
    Graphics gc = e.Graphics;

    Pen pen = new Pen(Color.Green, 1.5f);

    int nDirection = -1;

    float nWidth = 150f;
    float nHeight = 80f;

    PointF posBox1 = new PointF(30f, 40f);
    PointF posBox2 = new PointF(330f, 110f);

    gc.DrawRectangle(Pens.Black, posBox1.X, posBox1.Y, nWidth, nHeight);
    gc.DrawRectangle(Pens.Ivory, posBox2.X, posBox2.Y, nWidth, nHeight);

    PointF centerBox1 = GetCenter(posBox1.X, posBox1.Y, nWidth, nHeight);
    PointF centerBox2 = GetCenter(posBox2.X, posBox2.Y, nWidth, nHeight);

    nDirection = GetDirection(centerBox1, centerBox2);

    PointF newPos = GetSourcePos(centerBox1, centerBox2, nWidth, nHeight, nDirection);

    gc.DrawRectangle(Pens.Red, newPos.X - 2, newPos.Y - 2, 4, 4);

    gc.DrawLine(Pens.Salmon, centerBox1, centerBox2);


    pen.Dispose();

}

PointF GetCenter(float X, float Y, float Width, float Height)
{
    PointF point = new PointF(X + Width / 2, Y + Height / 2);
    return point;
}

int GetDirection(PointF pos1, PointF pos2)
{
    // Direction Number.
    //
    //            0
    //         7     1
    //       6         2
    //         5     3
    //            4
    //
    // Direction을 판단 할 수 없는 경우 ( 같은 좌표위에 있는 경우 ) -1을 돌림.


    if (pos1.X == pos2.X)
    {
        if (pos1.Y > pos2.Y)
            return 0;   // North
        if (pos1.Y < pos2.Y)
            return 4;   // South
    }
    if (pos1.Y == pos2.Y)
    {
        if (pos1.X < pos2.X)
            return 2;   // East
        if (pos1.X > pos2.X)
            return 6;   // West
    }
    if (pos1.X < pos2.X)
    {
        if (pos1.Y > pos2.Y)   
            return 1;   // North-East
        if (pos1.Y < pos2.Y)
            return 3;   // South-East
    }
    if (pos1.X > pos2.X)
    {
        if (pos1.Y < pos2.Y)
            return 5;   // North-West
        if (pos1.Y > pos2.Y)
            return 7;   // South-West
    }
    return -1;
}

float GetDeltaY(float a, float b, float nWidth)
{
    double rad = Math.Atan((double)(b / a));
    float result = (float)((nWidth / 2) * Math.Tan(rad));
    return result;
}

PointF GetSourcePos(PointF srcCenter, PointF tgtCenter, float Width, float Height, int nDirection)
{
    PointF pos = new PointF(srcCenter.X, srcCenter.Y);
    float a = Math.Abs(tgtCenter.X - srcCenter.X);
    float b = Math.Abs(srcCenter.Y - tgtCenter.Y);
   
    switch (nDirection)
    {
        case 0:
            pos.Y -= Height / 2;
            break;
        case 1:
            pos.X += Width /2;
            pos.Y -= GetDeltaY(a,b, Width);
            break;
        case 2:
            pos.X += Width / 2;
            break;
        case 3:
            pos.X += Width / 2;
            pos.Y += GetDeltaY(a, b, Width);
            break;
        case 4:
            pos.Y += Height / 2;
            break;
        case 5:
            pos.X -= Width / 2;
            pos.Y += GetDeltaY(a, b, Width);
            break;
        case 6:
            pos.X -= Width / 2;
            break;
        case 7:
            pos.X -= Width / 2;
            pos.Y -= GetDeltaY(a, b, Width);
            break;
        default:
            break;
    }
    return pos;
}
PS. 예전에 삼각함수 따위는 어디다 써~ 하면서 내팽겨 쳤는데, 아주 곤혹스러운 상태다.
(특히 그래픽 할때.). 결국 삼각함수 책을 하나 사서 봤는데 은근 도움이 많이 되었다.
그래도 한 때 배운 가락이 있어 기억 되살리면서 하나씩 조립해보고 있다.
(삼각함수에 대한 값은 컴퓨터가 알아서 계산 해주니깐 공식만 잘 알면 된다. )

나중에 그래픽 관련(게임을 만들든 뭘 하든) 프로그래밍을 할 생각이면 삼각함수 기초 부터 한번 다시 살펴봐야 할 것 같다.
신고
Posted by 하인도


티스토리 툴바