dev.log2009. 2. 1. 23:29
회사에서는 1주일마다 주간회의때 주제 하나씩을 잡아서 간략한 설명형식의 세미나를 한다.
지금까지 다뤄왔던 주제는 다음과 같다.
  • STL 컨테이너
  • 시간복잡도
  • 컴파일 & 링크
  • 프로세스와 쓰레드
  • 비동기 환경에서의 문제해결
  • 메모리 할당자
  • 커스텀 자료구조 설계
  • lua 스크립트 언어
  • GUI 설계변경
  • 곡선표현
  • 물리연산
  • 소프트웨어 설계 방법론
  • 애니메이션 기법
  • 커스텀 데이터 매니징
  • CPU 아키텍쳐
  • 회전변환
  • 개발 프로세스 리뷰

다는 생각이 안나지만, 대략 이러한 종류의 주제들로 약 8개월동안 진행해 왔다. 단순계산으로 24가지 주제를 다뤄온 셈인데, 이제부터라도 기록을 남기기 위해 포스팅을 해 놓아야 겠다는 생각이 든다.

지난주의 주제는 충돌.
충돌계산의 핵심은 매개변수(parametric form) 방정식이다.
좌표평면에서 x절편 A, y절편 B를 가지는 직선의 방정식은 일반적으로 다음과 같이 쓰곤 한다.
y = -(B/A)x + B
이때 A' = -B/A 로 정의하면 간략히 다음과 같이 쓸 수 있다.
y = A'x + B
이와 같은 형태는 표현은 간단하지만, 직선이 y축과 평행한 경우는 절대로 표현이 불가능하다. 따라서, 평면에서 임의의 직선을 표현하는 좀 더 나은 방식은 음함수 형태로, 다음과 같이 쓰는 것이다.
Bx + Ay - AB = 0
이와 같은 형태는 평면상에서 임의의 직선을 표현할 수는 있지만, 특정한 조건을 만족하는 점을 계산하려면 (이를테면, 두 직선의 교점) 여러차례의 이항과 소거를 거쳐야 하는 단점이 있다. 또한 3차원 이상의 공간상에서의 직선은 이와 같은 형태로 표현이 불가능하다는 결점이 있다.
매개변수 방정식에는 위와 같은 결점이 없다. 위의 2차원 평면상의 직선은, 매개변수 t에 대하여, 다음과 같이 쓸 수 있다.
x = -At + A
y = Bt
이는 다시, 벡터 방정식으로 a=(A,0), b=(b,0)으로 정의하면, 직선 l은, 다음 식으로 표현된다.
l = (b-a)t + a
이러한 형태의 표현은 3차원 이상의 공간에서도 일반적으로 성립하며, 표기도 간단하고, 매개변수 t의 범위를 제한함으로써 선분이나 반직선등을 용이하게 표현할 수 있다. 또한 한가지 변수 t만 결정하면 직선상의 한 점을 지정할 수 있으므로, 풀이도 비교적 용이하다. 기본적인 충돌 계산은, 충돌 대상이 되는 도형을 매개변수 형태로 표현하는 것으로 시작한다.

보통 충돌 시스템을 구현할 때에는 간단한 기하도형을 몇가지 정해놓고 시작하는데, 일반적으로 다음과 같은 도형들이 충돌계산에 사용되곤 한다.
  • 직선
  • 선분
  • 캡슐
  • 실린더
  • AABB
  • OBB
  • 삼각형

대략 9개 정도의 도형이 널리 이용되는데, 이들에 대한 구현을 모두 하려면, 각 도형이 다른 도형, 혹은 같은 도형과 충돌할 수 있으므로, 45 가지 경우를 구현해야 한다. 이에 대한 설명을 모두 하는 것은 길고 시간이 오래걸리므로, 여기에서는 직선-구, 직선-삼각형의 경우만 다룬다.

일반적으로 직선 l은, 직선의 방향벡터 d와 직선위의 한점을 가리키는 위치벡터 p가 주어질 때, 실매개변수 t에 대하여 다음 형태로 쓸 수 있다.
l = dt + p
또한 일반적인 구 s는, 중심 c와 반지름 r만으로 완전하게 정의할 수 있으므로, 구의 방정식은 벡터 형태로 다음과 같이 쓸 수 있다.
|s - c| = r
이와 같은 형태는 계산이 곤란하므로, 내적 형태로 다음과 같이 쓸 수 있다.
(s-c)·(s-c) = r*r
이때, 구 s와 직선 l의 교차점은 s = l 로 놓고 풀어 직선의 매개변수 t를 결정하는 작업이다. 구의 방정식에서 s = l을 대입하고 전개하면, t에 대한 2차방정식을 구할 수 있으므로, 이 2차방정식의 근으로 교점을 결정할 수 있다. 이 방정식이 2개의 실근을 가지면 직선은 구를 관통하며, 1개의 중근을 가지면, 직선은 구와 접하고, 허근을 가지면 직선은 구와 만나지 않는다.

3차원 공간상에서 삼각형과 직선의 충돌은 먼저, 삼각형을 매개변수 형태로 표현하는 것으로 시작한다. 삼각형 ABC에서, 벡터CA를 a, 벡터CB를 b라고하고, 점C의 위치벡터를 c라고 재명명하면, 삼각형 내부의 영역 q는, 두 매개변수 u,v에 대하여 다음과 같이 표현된다.
q = au + bv + c (u >= 0, v >= 0, 0 <= u+v <= 1 )
여기서 삼각형과 직선의 교점을 구하는 것은, 직선 l이 주어졌을 때, q = l로 놓고 풀어 직선의 매개변수 t와 삼각형의 매개변수 u,v를 구하는 문제로 바뀐다. l을 대입하면, 풀어야할 방정식이 나온다.
dt + p = au + bv + c
매개변수에 대한 항을 이항하면, 다음과 같다.
dt - au -bv = c-p
표현을 간단히 하기 위해 a' = -a, b' = -b, o = c-p 로 치환하면, 다음과 같이 된다.
dt + a'u + b'v = o
이는 3차원 벡터 (t,u,v)에 대한 전형적인 선형변환이므로, 행렬 형태로 바꾸면 다음과 같다.
dx
a'x
b'x
×
t =
ox
dy
a'y
b'y
u
oy
dz
a'z
b'z
v
oz
이때 행렬 (d, a', b')을 M이라고 쓰고, 벡터 (t,u,v)를 s라고 다시 쓰면,
Ms = o
곧, M의 역행렬을 M'이라고 하면
s= M'o
따라서, M의 역행렬을 구하고 벡터 o를 계한하여 둘을 곱하면 세 매개변수 t, u, v를 모두 결정할 수 있다. 유일한 해가 존재하고, u,v가 삼각형의 매개변수 조건을 만족하면, 직선 l과 삼각형 q는 교차한다.
이때, M의 역행렬을 구할 수 없는 경우는 직선과 삼각형이 평행하거나 삼각형이 직선을 포함하는 경우이므로, 이때는 삼각형의 세변과 직선을 직선-직선 간의 충돌 계산으로 교차 여부를 검사해야 한다.

Posted by uhm
dev.log2009. 1. 28. 23:38
ice 프로젝트를 시작하려다보니, 기초 수학 클래스의 성능을 측정해야만 했다. 그래서, 옛날 R모 게임을 만들던 시절에 만져본 언리얼엔진2의 스탯 수집방식을 좇아서 비슷하게 만들어 보기로 했다. 기본 방식은 수집할 스탯의 이름과 타입을 등록하여 인덱스를 지정한 후 수집할 구역의 시작과 끝을 지정하는 것. 여기에 시간 통계를 위한 API콜은 별도의 레이어로 분리하면 대략적인 틀이 나온다.
class syscore
{
public:
    static int64 clock()
    {
        int64 begin;
        QueryPerformanceCounter( (LARGE_INTEGER*)&begin );
        return begin;
    }
    static int64 clockpersec()
    {
        int64 freq;
        QueryPerformanceCounter( (LARGE_INTEGER*)&freq );
        return freq;
    }
};

class stat_collector
{
public:
    stat_collector() { _freq = (float)syscore::clockpersec(); }
    void flush();
    uint32 enroll( const string& name, uint type );
    void start( uint index );
    void stop( uint index );

    float mean_stat( uint index );
    float inst_stat( uint index );
    float total_stat( uint index );
    int   sample_count( uint index );

    class block_stat
    {
    public:
        block_stat( stat_collector& collector, uint index )
            :_collector( &collector ), _index( index )
        {
            _collector->start( _index );
        }
        ~block_stat()
        {
            _collector->stop( _index );
        }
    private:
        stat_collector* _collector;
        uint            _index;
    };

    enum stat_type
    {
        STAT_TIME,
        STAT_COUNT,
    };

private:

    struct stat_item
    {
        stat_item( const string& name, uint type )
            : _count(0), _name(name), _type(type)
        {}

        uint   _count;
        string _name;
        uint   _type;
    };
    std::vector<sint64> _prv_stats;
    std::vector<sint64> _cur_stats;
    std::vector<stat_item> _stat_info;
    float _freq;
};

void stat_collector::flush()
{
    //_prv_stats = _cur_stats;
}

uint32 stat_collector::enroll( const string& name, uint type )
{
    assert( _prv_stats.size() == _cur_stats.size() );
    assert( _prv_stats.size() == _names.size()  );
    uint32 index = _cur_stats.size();
    _prv_stats.push_back( 0 );
    _cur_stats.push_back( 0 );
    _stat_info.push_back( stat_item( name, type ) );

    return index;
}

void stat_collector::start( uint index )
{
    _prv_stats[index] = _cur_stats[index];

    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        _cur_stats[index] -= syscore::clock();;
        break;
    case STAT_COUNT:
        break;
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
    };
}

void stat_collector::stop( uint index )
{
    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        _cur_stats[index] += syscore::clock();
        break;
    case STAT_COUNT:
        _cur_stats[index]++;
        break;
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
    }

    _stat_info[index]._count++;
}

float stat_collector::mean_stat( uint index )
{
    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        return _cur_stats[index]/(_freq * _stat_info[index]._count);
    case STAT_COUNT:
        return (float)_cur_stats[index]/_stat_info[index]._count;
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
        return 0;
    }
}


float stat_collector::inst_stat( uint index )
{
    switch ( _stat_info[index]._type )
    {
    case STAT_TIME:
        return (_cur_stats[index]-_prv_stats[index])/_freq;
    case STAT_COUNT:
        return (float)_cur_stats[index]-_prv_stats[index];
    default:
        assert( "unkown stat type!!!!!!!!!" && 0 );
        return 0;
    }
}

네이밍 컨벤션은.. C/C++ 표준 위원회의 스타일을 따라가 봤다.
사용법은,
#define TEST_LIST() \
    TEST( l = c.length() )                   \
    TEST( c = vector_t::cross( a, b ) ) \
    TEST( l = vector_t::dot( b, c ) )      \

stat_collector _stats;

template <class vector_t>
void test_run( const char* filename, uint stat_index )
{
    static vector_t a, b, c;

    std::ofstream log( filename, ios::app );
#define TEST( expression ) #expression "\n"
    log << "=======================================" << endl;
    log << TEST_LIST();
    log << "---------------------------------------" << endl;
#undef TEST
    for ( int count = 0; count < 10; ++count )
    {
        vector_t::scalar_type x = (float)rand();
        vector_t::scalar_type y = (float)rand();
        vector_t::scalar_type z = (float)rand();
        volatile vector_t::scalar_type l = 0;
        a = vector_t( x, y, 0 );
        b = vector_t( -y, x, 0 );
        c = vector_t( x, y, z );

        _stats.start( stat_index );
        for ( int i = 0; i < 10000000; ++i )
        {
#define TEST( expression ) expression;
            TEST_LIST();
#undef TEST
        }
        _stats.stop( stat_index );
        log << _stats.inst_stat(stat_index) << std::endl;
    }
    log << "======== " << _stats.mean_stat(stat_index) << " sec/sample ==========" << std::endl;
}

int WINAPI WinMain( HINSTANCE , HINSTANCE, char* , int )
{
    uint imp_stat, exp_stat;
    imp_stat = _stats.enroll( _t("imp"), stat_collector::STAT_TIME );
    exp_stat = _stats.enroll( _t("exp"), stat_collector::STAT_TIME );
    test_run<vector3def>( "log_imp.txt", imp_stat );
    test_run<vector3sse>( "log_exp.txt", exp_stat );

    return 0;
}

이름과 타입을 등록하고, 측정할 구간을 start/end로 지정하면 끝. TEST_LIST는 테스트 대상이 어떤 코드였는지에 대한 로그를 남기기 위한 매크로.

부족한 부분은 나중에 수정하자.

Posted by uhm
dev.log2009. 1. 27. 23:02
설도 지났으니, ice 개발 계획을 본격적으로 잡아야 겠다;는 생각이 들었다.
전체적으로는 각 기능간 커플링을 없도록 제작하여 기능별로 독립적으로 사용할 수 있도록
작업하는 것이 목적. 제네릭 프로그래밍을 엔진 레벨에서 적용해 보는 것도 이 프로젝트의
목적중 하나.
  1. 벡터/행렬 클래스
    - 템플릿 기반으로 SSE 특수화 구현
  2. 드로잉
    - OGL/DX 공용 드로잉, 다중 쓰레드 구현
  3. 맥스 플럭인/익스포터
    - 맥스 뷰포트로 렌더링, 바이너리/텍스트 포맷으로 저장
  4. 장면 트리
    - octree/BSP
  5. 애니메이션
    - 버텍스 스키닝/애니메이션블렌딩, 익스포터 제작
  6. 셰이더
    - 맥스의 머티리얼과 대응하는 개념으로 제작, 맥스 셰이더 플럭인 제작
  7. 에디터
    - height field/BSP+PVS, 독립 어플리케이션으로 seamlessness 구현.
  8. 파티클
    - 맥스 플럭인 제작
  9. 물리 시뮬레이션
    - 충돌/IK/강체역학/마찰/스프링+댐퍼, 맥스 플럭인
  10. 그림자
    - 부드러운 경계
Posted by uhm