2018년 11월 3일 토요일

시간 복잡도와(Time complexity) 빅오 표기법

■ 시간 복잡도란?

입력 갯수(혹은 크기)에 따른 연산의 수(반복의 수 )를 함수화 시킨 것을 시간 복잡도라고한다. 정의에서 나와있듯 함수의 결과 값이 아닌, 함수 자체를 의미하기 때문에 시간 복잡도는 절대적인 시간을 측정하는 기준이 아니다.

    int N = 10;
 
    int ret = 0;
 
    // Case 0
    for (int i = 0; i < N; i++)
    {
        ret += i;
    }
 
    // Case 1
    for (int i = 0; i < N; i++)
    {
        // somthing task about I/O;
    }
예를들어 위와같이 두가지 케이스가 있다고 했을때, 둘의 시간복잡도는 동일하기 N 이지만
일반적으로 입출력(I/O) 작업이 더하기 보다는 느리기 때문에 Case1의 수행시간이 더 오래걸릴 것이다.

시간 복잡도라는 것은 입력 갯수에 따른 시간의 변화율을 알아보기위한 표기법 정도로 받아들이면 좋을 것 같다. 

■ 선형 시간

위의 Case0 이 선형시간에 해당한다. 입력갯수에 따라 동일하게 늘어나는 경우를 선형 시간이 걸린다고 한다.

    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < 100; ++j)
        {
            // Something task 
        }
    }
예를 들어 이런식으로 반복문안에 매번 100번의 반복문들 돌린다해도 이때도 선형시간이 걸렸다고한다. 왜냐하면 100*N 번의 반복 수행을하는 것이고 이것또한 선형적이기 때문이다.

■ 다항 시간

Colored By Color Scripter
    for (int i = 0; i < N; ++i)
    {
        for (int j = 0; j < N; ++j)
        {
            // Something task 
        }
    }

위와 같이 N의 m제곱인 형태(여기선 2제곱)을 다항시간이 걸린다고한다. 여기서도 마찬가지로 K*N 이나 N+K 처럼 상수 곱과 더하기는 무시한다.

■ 선형 이하 시간 




■ 지수 시간



2018년 8월 7일 화요일

캣멀-롬 스플라인( Catmull-rom splines)

■ 내가 모르는 것

- 왜 2차 곡선이 아니라 3차 곡선(Cubic curve)을 쓰는가?
- 왜 캣멀-롬 스플라인에서는 1/2란 상수값을 쓰는가?

위 두가지는 내가 모르는 것들이다. 그러니 이 포스트에서는 위 두가지에 대한 답을 찾지 말기바란다.. 사실 나머지도 제대로 아는게 아니라 대략 이런 것이구나 정도까지만 설명할수 있을거같다.

■ 곡선과 스플라인

곡선(Curve)이란 말그대로 휘어진 선을 의미한다. 2차 곡선.. 3차곡선.. 그런것들 말이다. 그리고 스플라인이란 제어점들이란 걸 통해 만들어진 곡선을 의미한다.



위 그림 같은 것을 스플라인이라고한다. 그리고 여기서 다룰 곡선은 3차곡선인데, 3차 곡선이란것은 4개의 상수 데이터와 변수로 이루어져있고 결국 스플라인이란 그 4개의 상수 데이터를 제어점이라는 것으로부터 다루는 것이기 때문에 우선 우리는 3차 곡선의 개념을 알아야한다.( 여기서는 에르밋 곡선 )

■ 에르밋 곡선( Hermite curves )

캣멀-롬 스플라인은 에르밋 곡선이란 것에 기반한다. 에르밋 곡선이란 것은 3차 곡선을 2개의 점과(진입점과 종료점) 2개의 접선(진입점과 종료점에서의 접선)으로 나타낸 방식이고 캣멀-롬 스플라인은 그 2개의 접선을 주어진 4개의 제어점으로부터 적절히 구해내는 방식이다. 3차 함수는 아래와 같다.



식에서 보이듯, t는 변하는 값이고( 보간으로 쓸때는 일반적으로 시간값이 된다 ) a,b,c 그리고 D는 곡선의 모습을 결정짓는 어떠한 상수 값(벡터들과 점)들이다.  그리고 위 식을 미분하면



위와 같은 도함수가 구해지는 것을 알수가있다. 짐입점(t = 0)을 P_0 종료점( t = 1)을 P_1 그리고 진입점에서의 접선을 P'_0 종료점에서의 접선을 P'_1 라고 놓는다면,



이렇게 각각 함수와 도함수에 0과 1을 집어넣었을때 위와 같은 결과가 나오는것을 알수있다. 그림으로 보자면,



이러하다. ( 당연하지만 0~1의 범위를 가지는 t는 정규화된 값이다 ) 위 식으로부터 a와 b에 대한 식을 세워보면



와 같이 나올것이다. ( 첫번째와 세번째 식에서 값이 정해진 D와 c를 두번째와 네번째 식에 넣고 잘 정리하면 위의 식이 나온다 ) 이제 a,b,c 그리고 D에 대한 모든 값들은 점 P_0와 점 P_1 그리고 접선 P'_0 과 P'_1 로 완벽히 대체했다. 그래서 다시 3차 함수에 이 값들을 집어넣으면



위와 같이 되고 이것을또 잘 정리하면



결국 위의 공식이 나오게되는데 이것이 에르밋 곡선이다. 결과에서 보이듯 진입점과 종료점 그리고 각각 그에 대응하는 접선 두개만 안다면은 변수 t에서의 특정 점을 우리는 알수가있다.

■ 에르밋 곡선의 행렬

미지수 행렬을 T, 스칼라 행렬을 M, 점과 벡터 행렬을 G라고 놓는다면

 

위와 같이 나온다.

■ 캣멀-롬 스플라인

위의  P'_i 와 P'_i+1 을



이렇게 구하면은 이게 캣멀-롬 스플라인인데, 위에서 말했듯이 왜 상수 1/2를 써서 이렇게 구하는지느 모르겠다. 어쨋든 캣멀-롬 스플라인은 보다시피 4개의 점이 필요하고 4개의 점중  i-1 과 i+2은 접선값을 구하는데 쓰이고 그 접선으로부터 i 와 i+1 사이에 곡선이 생기는것을 확인할수가 있다.


2018년 8월 5일 일요일

에이스타( A-Star )

■ 설명

에이스타에 대한 자세한 설명은

http://egloos.zum.com/cozycoz/v/9748811

위 블로그에 자세히 잘 나와있으니 참고하기 바라고, 여기서는 내 소스코드나 올리고 그냥 조금의 설명이나 하려고한다. 구동 가능하고 쉬운 에이스타 코드를 찾는 많은 이들을 위하여..

■ 에이스타

void Astar(const Position & start, const Position & end,
    const int & tileCX, const int & tileCY, TILE * map)
{
    list<Node*> openList;
    list<Node*> closeList;
 
    Node* startNode = CreateNode(start, 0, 0, nullptr);
    openList.push_back(startNode);
 
    Node* endNode = nullptr;
 
    while (!endNode)
    {
        // 오픈리스트가 비었다면 더이상 찾을 길이없다 판단하고 종료한다
        if (openList.empty())
            break;
 
        Node* parentNode = nullptr;
 
        // 부모노드가 될 최소 비용 노드를 찾는다.
        openList.sort([&](Node* node1, Node* node2)
        {
            int F1 = node1->G + node1->H;
            int F2 = node2->G + node2->H;
 
            if (F1 < F2)
            {
                return true;
            }
 
            return false;
        });
 
        parentNode = openList.front();
        openList.pop_front();
 
        closeList.push_back(parentNode);
 
        for (int y = parentNode->pos.y - 1; y < parentNode->pos.y + 2; ++y)
        {
            for (int x = parentNode->pos.x - 1; x < parentNode->pos.x + 2; ++x)
            {
                int idx = GetIdx(Position(x, y), tileCX, tileCY);
 
                // 범위밖, 못가는 길, 닫힌 목록에 있을때의 예외처리
                if (idx == -1)
                    continue;
                if (map[idx] == OP_OBSTACLE)
                    continue;
                if (FindNodeByPos(Position(x, y), closeList))
                    continue;
 
                // 목표지점에 도달했다면 기록하고 종료한다.
                if (x == end.x && y == end.y)
                {
                    endNode = CreateNode(Position(x, y), 0, 0, parentNode);
                    break;
                }
                
                Node* openNode = FindNodeByPos(Position(x, y), openList);
 
                // 현재 위치가 열린목록에 있다면 g값을 갱신시키고 더 작은지 검사한다.
                if (openNode)
                {
                    int G = GetG(parentNode->G, parentNode->pos, Position(x, y));
 
                    if (G < openNode->G)
                    {
                        openNode->G = G;
                        openNode->parent = parentNode;
                    }
                }
                // 아니라면 노드를 생성해 비용들을 산출하고 열린목록에 넣는다
                else
                {
                    int G = GetG(parentNode->G, parentNode->pos, Position(x, y));
                    int H = GetH(Position(x, y), end);
 
                    Node* node = CreateNode(Position(x, y), G, H, parentNode);
 
                    openList.push_back(node);
                }
            }
        }
    }
 
    // 엔드노드가 존재한다면 길을 찾은것으로 판단하고 기록한다
    Node* node = endNode;
 
    while (node)
    {
        Position pos = node->pos;
 
        int idx = GetIdx(pos, tileCX, tileCY);
 
        if (map[idx] != OP_START && map[idx] != OP_END)
        {
            map[idx] = OP_PATH;
        }
 
        node = node->parent;
    }
 
    ReleaseList(&openList);
    ReleaseList(&closeList);
}

에이스타를 처리하는 코드는 이게 전부다. 순서는 대략 이러하다.

1. 열린목록에서 최소 F 비용을 가진 노드를 찾아 부모노드로 설정하고 닫힌목록에 넣는다.

2. 1에서 설정된 부모노드의 주변을 탐색한다. 범위 밖이거나, 못가는 길이거나, 닫힌 목록에있는 노드는 제외한다.

3. 2 과정에서 찾은 타겟 노드가 만약 열린목록에 존재한다면 G값을 현재 부모노드로 부터 산출해보고 만약 새로 산출한 G값이 더 저렴하다면, 타겟 노드의 G값과 부모노드를 새로 산출한 G와 현재 부모로 대체한다

4. 만약 열린목록에 존재하지 않는다면, 새로운 노드를 생성해 비용들을 산출하고 열린목록에 집어넣는다.

5. 위 과정중에 열린 목록에 존재하지 않는다면 경로가 존재하지 않는것으로 판단하고 종료하고, 목표지점에 도달했다면 경로가 존재하는 것으로 판단하고 기록한다.

소스코드도 길지않고 과정도 어렵지않아 매우 쉽게 이해 가능할것이다. 자세한건 아래 프로젝트 파일을 참고하기 바란다.

■ 소스코드

2018년 7월 11일 수요일

좌표 변환 행렬 ( 정규직교기저에서의 )

■ 정규 직교 기저란?



정규직교 기저란 위의 벡터 v1, v2 처럼 길이가 1이고(정규) 서로 수직인(직교) 벡터들을 정규직교 기저라한다. 그리고 여기서는 정규 직교 기저를 x축 y축 같은 하나의 좌표축으로 볼것이고, 그래서 그 좌표계가 변했을때 원래의 x축 y축의서의 어떠한 점 P는 변환 좌표축 x'과 y'축에서 어떻게 표현되고 그것을 어떻게 구할수있을지를 살펴볼것이다.

■ 정규 직교 기저와 좌표

우리가 일반적으로 쓰는 2차원의 데카르트 좌표계에서 어떠한 점 P( 2, 1) 이 있다고 해보자. 그리고 x, y축이 각각 v1(1, 0), v2(0, 1)이란 정규 직교 기저로 이루어져있다고 해보면,



이렇게 말이다, 사실 점 P는



이렇게 좌표축 v1, v2의 스칼라 곱 형태로 표현될수 있다는 것을 확인할수가 있다. 그리고 그 스칼라값 1과 2가 v1과 v2를 축으로가지는 좌표계에서 (x, y) 인것도 확인할수가있다. 이것은 이 좌표축이 변했을때도 마찬가지이다.



이런식으로 v1, v2가 반시계 방향으로 90도 회전하여 위와같이 v'1, v'2 가 되었다고 해보자. v1, v2에서 좌표 점 P는 위와같이 v'1, v'2 에서는 P'이 되는것을 확인할수가있다. 그리고 그것을 식으로 써보면



결국 위와같다는것을 확인할수가있다. 즉 변한된 좌표축( 회전된 )에서의 새로운 점 P'를 구하는것은 결국 표준 좌표축 에서의 점 P가 어떻게 새로운 좌표축의 합과 스칼라곱으로 표현되는지를 찾는 문제가된다.

* 표준 좌표축이란 용여가 맞는지는 모르겠지만, (1, 0), (0, 1) .. 같은 좌표축에서 기본적은 좌표축을  여기서는 표준 좌표축 혹은 표준 직교 기저라하겠다. 

■ 정규 직교 기저와 정사영

합과 스칼라곱으로 표현된다는 것은 결국 새로운 좌표축에 곱해질 어떤 스칼라 값을 찾냐의 문제인데, 어떻게 이 스칼라 값을 찾을수있을까?



이 그림을 잘보도록하자. 정규 직교 기저란 결국 '직교' 이기때문에, 어떠한 점 P의 원소들은( 여기서는 x, y ) 각 축에 정사영된 결과라는것을 확인할수가있다. 그리고 동시에 정규직교 기저는 길이가 1인 '정규'이기 때문에, 각각의 원소를 x, y라 놓는다면, 정사영 공식에 따라



이런식으로 표현될수 있음을 확인할수가있다. 즉 우리는 변환하려는 표준 좌표축에서의 점 P와 변환된 좌표축 v'1과 v'2.. v'n ( 혹은 원래의 좌표축들 ) 만 안다면 변환된 점 P'을 구할수있다는 것이다.

■ 좌표 변환 행렬

... 시간이 없다 나중에 쓴다.

2018년 7월 10일 화요일

마찰력

■ 마찰력이란?

마찰력이란 두 물체 사이에서 물체의 운동을 방해하는 힘을 마찰력이라고한다. 물체를 민다던지, 뭔가에 접촉해서 문질러본다던지 일상생활에서도 많은 마찰력을 경험해봤을 것이다. 그리고 그런 경험속에서, 내가 어떤 물체를 운동시키는 방향과 수직되는 힘에따라( 보통 무게가 무겁다던지 ) 물체의 가속도가 달라지는것을 알게 모르게 느꼇을것이다.

■ 마찰에서 고려할 데이터들

마찰이라는 것을 구현하기 위해서는 아래의 데이터들을 고려해야한다.

1. 움직이려는 방향의 F

말그대로 어떤 물체에 준 힘을 뜻한다. 나머지 아래의 데이터는 최종적으로 이 데이터를
뽑기위해 필요하다. 힘 F가 최종적으로 얼마고, 물체가 가진 질량을 안다면 우리는 가속도 a를 구할수있다.

2. 정지 최대 마찰력 F_smax와 운동 마찰력 F_k

어떠한 물체에 힘을 주어 밀어본 경험을 떠올려보자. 분명히 물체는 어느정도의 힘까지는 움직이지 않다가, 그 어느 정도 이상의 힘을주면 물체가 술술 움직이는것을 경혐해봤을 것이다. 그 경험에서 알수있는 사실은 아래와 같다.

가. 정지마찰력 F_s가 존재한다.

어느정도의 이상의 힘을 주지않은이상 물체는 움직이지않는다. 즉 그말은 어느정도의 힘까지는 그것과 똑같은 크기지만 방향이 반대인 힘인 F_s가 같은 물체에 적용되고있음을 의미한다.( F와 F_s 가 적용되는 물체는 같은 물체이기때문에 질량이 같다, 고로 물체가 가속되지않는다면 힘도 같은것이다 )

나. 정지 최대마찰력 F_smax 가 존재한다.

어느정도 이상의 힘을 주면 물체는 움직인다. 즉 F_s 란 정지마찰력은 어느정도의 한계가 존재한다는 것을 알수가있다.

다. 운동 마찰력 F_k는 존재하고 F_k 는 F_smax보다 작다.

물체에 적용되는 힘이 F_smax 를 넘어서, 미끌어지는 상태에 가면 물체가 더 적은 힘으로도 움직이게 되는것을 알수가있다. 즉 여기서 미끌어지는 상태에서는 F_s가 아닌 다른 운동마찰력 F_k라는 것이 존재하고 F_smax 보다 작다는 것을 알수가있다.
( 존재한다는 표현보다는 .. 그냥 그런 힘이 있다고 볼수 있다 라는게 맞지않나 싶다 )

3. 수직항력 N














일반적으로 물체를 땅바닦에 놓는다고해서 물체가 땅바닦 아래로 가라앉지는 않는다. 즉 물체가 바닦으로 향하는 힘에의해( 여기서는 중력 ) 물체가 가라앉지 않는다는것은, 그 물체에는똑같은 크기지만 방향이 반대인 수직항력 N이란 것이 가해지고있다는 것이다. 그리고 경험에서 느꼇듯 이 N이 클수록 정지 최대 마찰력 F_smax과 운동마찰력 F_k가 비례해서 크다라는 것을 알수있다.

4. 정지마찰계수 M_s 와 운동마찰계수 M_k

N이란 것과 F_smax, F_k 가 비례하는 것은 사실이나 1:1 비례 관계는 아닐수있다. 예를어 같은 크기를 가진 얼음 육면체와, 고무 육면체가 아스팔트 바닦위에 있다고하자. 어느게 더 잘 움직이겠는가? 혹은 같은 크기의 고무 육면체가 하나는 얼음바닦 위에있고, 하나는 아스팔트 위에있다면 어느게 더 잘움직이겠는가?

그래서 여기서 마찰계수란게 필요하다. 마찰계수란거는 위의 육면체와 바닦처럼 두 물체의 표면사이에 관계를 나타내는 실험적인 값이다. 고무와 얼음은 이렇게 움직이네..이것의 마찰계수는 1.0.. 얼을과 얼을음 이렇게 움직이네 이것의 마찰계수는 0.2 .. 뭐 이런식의 계수라는것인데 사실 코딩할때는 그냥 적절한 수를 집어넣으면된다. 어쨋든 이 마찰계수를 이용하여

F_smax = M_s * N
F_k = M_k * N

이렇게 F_smax와 F_k 에 대한 식을 세울수가있다. 마찰계수 F_smax가 1이라면 N과 F_smax는  1:1 관계일것이고 아니면 아닐것이다.






쭉 설명을 했는데.. 사실 나도 물리를 잘 모른다. 맞는 소리 하는지도 모르겠고..  하지만 저 데이터들을 잘 이용해서 마찰력이란걸 게임에서 구현할수는 있을거 같다.



2018년 7월 7일 토요일

등가속 운동 방정식들

■ 등가속 운동이란?

가속이란 시간당 속도의 변화율을 의미를한다. 당연하게도 가속도는 변할수도 혹은 일정할수도있는데, 이 일정할 때를 등가속 운동이라고한다. 그리고 이 등가속 운동의 특징으로부터 몇가지 유용한 방정식들을 얻을수가있다.

■ 미지의 데이터와 알고있는 데이터

s = x - x0 : 변위 
v0 : 처음 속도
v : 현재 속도 
t : 시간 
a : 가속도

아래에서 설명할 방정식은 위의 다섯가지 물리량에서 세가지는 알고있고 한가지는 모를수있는 상태에서 알고있는 세가지로 나머지 하나를 구하는 식으로 구성된다.

■ 방정식들


각각의 식에 대한 모를수 있는 미지수는 이렇다.

1. x-x0
2. v
3. t
4. a
5. v0

그리고 3~5번의 공식들은 1~2번의 공식들로부터 유도된다. 그러므로 1~2번의 공식들을 먼저 살펴보도록하자.

[1] * 모르는 미지수 : x-x0

1번 공식을 이해하기 위해서는, 가속도가 일정할때는 결국 순간가속도와 평균가속도가 같다는 것이다. 이는 

이런 시간과 속도에 대한 등가속운동 그래프를 보면 쉽게 이해가 갈것이다. 속도의 증가율이 일정하기 때문에, 특정 구간에서나 특정 t에서나 결국 기울기는 같다. 그러므로 가속도를 평균가속도와 같이


이런식으로 놓을수가 있다. 그런데 여기서의 t는 등가속 운동을 한 시점부터 지나간 시간이므로, t0 는 사실 0이다. 그렇기 때문에,


결국 1번의 방정식이 나오게 된다.

[2] * 모르는 미지수 : v

속도를 적분하면 변위(x-x0)이 나오는데, 이는 근본적으로



위와 같은 그래프가 있을때, A+B처럼 그래프에서 특정 구간의 넓이를 구하는것과 같다. 그런데 현재 운동은 등가속운동이기 때문에 위의 그래프에서 보다시피 결국 직사각형과 삼각형의 합이 넓이가 되는것을 알수가있다. 그래서 변위 x-x0은 결국



와 같은것을 확일할수있다. 그런데 여기서 v-v0란 속도의 변위는 결국 등가속도 운동에서의 가속도에 시간을 곱한값인 at와 같다. 그러므로,


결국 2번의 식이 나오게된다.

[3] * 모르는 미지수 : t

여기서부터는 1번과 2번의 식으로부터 모를수있는 미지수를 배제하여 식을 구성할것이다.
3번의 식에서 모를수있는 미지수는 t이다.




1번식에 근거하여 t는 이렇게 나온다. 그럼 이 식을 2번의 t에 넣어보도록하면


결국 3번의 식이 나온다. 나머지 4, 5도 비슷한 요령으로 하면되니 알아서들 하자.