이득우의 게임 수학 이번에는 캐릭터를 제작하기 위한 트랜스폼의 계층 구조에 대해 알아본다. 실습해 본 예제에서는 Left와 Right 본이 서로 연결되지 않고 독립적으로 움직였지만, 일반적으로 캐릭터를 구성하는 본은 모두 유기적으로 연결되어 있으며, 부모-자식 관계의 계층 구조가 형성되어 있다. 손가락이 움직이지 않아도 팔목이 움직이면 손가락의 최종 위치가 달라지듯이, 게임 캐릭터도 부모-자식의 계층 구조를 가져야 사람의 움직임을 구현할 수 있다. 부모-자식 계층 관계에서 부모와 자식 트랜스폼이 가져야 하는 규칙을 정리하면 다음과 같다. 부모가 움직이면 그만큼 자식도 움직이지만, 자식의 움직임에 부모는 영향을 받지 않는다. 부모는 여러 자식을 가질 수 있지만, 자식은 하나의 부모만 갖는다. 계층 구조에서..
Level 2. 멀쩡한 사각형 대각선을 일차 함수로 생각해 int 형변환으로 소숫점을 날리면서 더하면 된다. #include #define f(x) int(a * (x) + b) using namespace std; long long solution(int w, int h) { long long answer = 0; double b = h, a = -(b / w); for (int x = 1; x
이득우의 게임 수학 게임에서 캐릭터 애니메이션은 가상의 뼈대인 본(Bone)을 캐릭터 메시에 심은 후, 해당 본의 움직임에 맞춰 캐릭터 메시가 변형되는 스켈레탈 애니메이션(Skeletal Animation) 방식을 사용한다. 스켈레탈 애니메이션을 구현하려면 기존의 메시 체계를 확장해 본을 추가해야 한다. 본은 단순히 이동만 하는 것이 아니고 회전하거나 크기도 변경되므로, 본의 정보는 트랜스폼에서 관리한다. 게임 오브젝트뿐만 아니라 본에서도 트랜스폼이 필요하고, 책의 예제인 CK소프트렌더러에서 제공하는 트랜스폼 구조체는 다음과 같다. // 책의 예제인 CK소프트렌더러에 정의된 트랜스폼 구조체 struct Transform { public: Transform() = default; Transform(cons..
Level 2. 행렬 테두리 회전하기 GetBorder()라는 테두리를 이루는 원소들의 포인터를 저장하는 함수를 만들고, rotate() 함수를 활용해 배열을 1칸 밀어 해결했다. #include #include #include using namespace std; vector GetBorder(vector& nxn, vector& query) { int x1 = query[0], y1 = query[1]; int x2 = query[2], y2 = query[3]; int r = x1, c = y1; int n = 0; vector v((x2 - x1 + y2 - y1) * 2); while (c < y2) v[n++] = &nxn[r][c++]; while (r < x2) v[n++] = &nxn[r..
이득우의 게임 수학 사원수는 로드리게스 회전과 동일한 축-각 방식을 사용해 3차원 공간의 회전을 표현한다. 사원수를 사용하는 경우의 장점은 다음과 같다. 1. 오일러 각과 쉽게 변환이 가능하며, 회전 행렬 제작이 용이하다. 2. 행렬과 상호 변환이 가능하다. 3. 임의의 축에 대한 회전 표현이 가능하기 때문에 짐벌락 현상을 방지할 수 있다. 4. 임의의 축에 대한 회전 보간 값을 구할 수 있다. 5. 4개의 숫자로 표현하기 때문에 저장 공간을 효율적으로 쓸 수 있다. 회전 방식별 장단점은 다음과 같다. 오일러 각 행렬 사원수 저장 공간 3 9 4 짐벌락 현상 없음 X O O 회전 보간 한 기저 축에 대해서만 가능 X 임의의 축에 가능 직관성 O X X 다른 회전 방식에 비해 사원수의 장점은 회전 보간이 ..
Level 0. 수열과 구간 쿼리 4 i를 1씩 증가시키며 매번 모듈러 연산을 하는 것보다는, s보다 크거나 같은 가장 작은 k를 먼저 구해 놓고 k씩 e까지 더하는 게 더 효율적이다. #include #include using namespace std; vector solution(vector arr, vector queries) { for (vector& query : queries) for (int i = (query[0] + query[2] - 1) / query[2] * query[2]; i