원문 : http://www.braynzarsoft.net/viewtutorial/q16390-28-skeletal-animation-based-on-the-md5-format
저번 강좌에 이어서 MD5 모델을 애니메이팅을 해보도록 하겠습니다. MD5 포맷은 애니메이션을 하기 위해 뼈대 시스템(더 정확히는 관절)을 사용하는데 그래서 어떻게 "md5anim" 파일을 읽고 우리의 모델에 적용할 수 있는지에 대해 배워보도록 할 것입니다. 뼈대 시스템은 애니메이션의 모든 프레임에 대해 기본적으로 새 모델을 가지는 키프레임 애니메이션을 저장하는 것보다 메모리를 적게 먹어서 아주 좋습니다. 관절 시스템(우리가 사용할)은 각 프레임에서 본(뼈)의 위치와 방향만 저장합니다. 본 시스템은 또 "랙돌(rag-doll, 보통 게임에서 굴러다니는 시체같은거에 적용시킴)" 물리를 적용하기 원할때 완전 좋습니다. 이번 강좌가 끝나면 어떤 모델이든 뼈대 애니메이션을 로드할 수 있고 실행 동안 여러분만의 애니메이션이나 랙돌 효과를 만들 수 있습니다.
Introduction
md5 강좌의 두번째 부분입니다. 저번 강좌에서 md5 모델 로딩하고 뼈대 시스템을 이용하여 정점 위치와 법선을 계산에 관해 다루었었습니다. 이번 강좌에서는 md5 애니메이션 파일인 "md5anim" 로드하고 파일에 저장된 데이터를 사용하여 모델을 움직이는것을 어떻게 하는지에 대해 배울 것입니다.
Animating the Model (Using the Joint Structure)
저번 시간에 뼈대 애니메이션을 간단하게 언급했습니다. 그래서 이제 이번 시간에는 모델을 애니메이팅 하깅 위해 뼈대 구조체를 (관절) 어떻게 사용할지에 집중할 것입니다. 천천히 하면 다 할 수 있습니다.
먼저 애니메이션 파일을 (.md5anim) 로드할 것인데 밑에서 다룰 것입니다. 그 다음에 애니메이션에서 현재 시간에 따른 뼈대를 계산해야합니다. 두 프레임사이 (이미 지나간 프레임과 구성중인 프레임) 현재 시간에 따른 두 뼈대의 "보간"에 의해 할 수 있습니다. 보간된 뼈대를 구한 후에 weight를 이용하여 정점 위치와 법선을 다시 계산해야 합니다. 마지막으로 정점 위치와 법선을 (법선과 같은 방식으로 tangent 와 bitangent를 구할 수 있습니다.) 구한 후 우리의 버텍스 버퍼에 갱신해주어야 합니다. 기본적으로 방금 얘기한 4단계와 같으며 첫번째 단계(애니메이션 로드)는 짧게 다뤄지며 그래서 2단계 보간된 뼈대 만들기로 넘어갑니다. Creating the Interplated Skeleton 애니메이션을 로드할때 모든 프레임에 대한 뼈대를 만듭니다. 애니메이션의 현재 프레임을 얻기 위해 두 프레임(예를들어 프레임1 과 프레임 2)을 "보간"할 수 있습니다. 물론 프레임당 보간된 뼈대를 생성하지 않고 애니메이션의 프레임들을 사용할 수 있지만 그렇게 하면 "뚝뚝끊겨"보일 수 있고 게임이 느려지면 날으는 총알이 여러개로 보이는 것처럼 매우 부드럽지 못한 애니메이션 될 것입니다. 또 애니메이션 파일은 초당 애니메이션이 실행되는 프레임이 얼마인지 표시합니다. 우리가 로딩하는 모델은 초당 30 프레임을 사용합니다. 현재 우리가 있는 프레임을 얻기 위해 현재 애니메이션 시간을(애니메이션 시작 이후 시간) 프레임 레이트와 곱할 수 있습니다. 이 계산은 "5.3667" 같은 실수(부동 소수점 수)로 계산됩니다. 이 "5.3667"의 경우 우리는 현재 프레임 "5"에 있으므로 가장 가까운 whole number(자연수와 0을 갖는 수, 정수에서 음수집합을 뺀 수)로 내림하기 위해 floorf() 함수를 사용할 수 있으며 이 경우 5가 됩니다. 바로 이게 우리가 frame0에 저장할 것이며 보간할 두 프레임 뼈대의 첫번째가 될 것입니다. frame0에 간단하게 "1"을 더함으로 두번째 뼈대 프레임을 얻을 수 있으며 frame1이 되는데 방금 예에서 "6"이 될 것입니다. 이제 보간된 뼈대를 구하기 위해 보간시킬 두 프레임 뼈대를 얻어 우리의 모델을 업데이트할 수 있게 되었지만 두 뼈대를 보간할때 각각의 것이 얼마나 사용될지를 알기 위해 0 과 1 ("0"은 frame0만, "0.5"는 frame0 반과 frame1 반, "1"은 완전히 frame1을 보간에 사용)사이 값이 필요합니다. 어떤 값을 우리가 보간 요소로 사용할 수 있을까? 아마도 간단한 질문일 것이지만 바로 위에서 현재 있는 프레임을 구할때 얻은 값의 나머지를 사용할 것이며 방금 예에서는 "0.3667"이 될 것입니다. (위의 계산 값에 frame0를 빼서 구합니다.) 이제 보간을 해봅시다. 이제 실제 보간된 뼈대를 구할 것입니다. 정말로 매우 간단합니다. 모델의 각 관절을 돌며 (애니메이션들은 단지 위치와 방향만 다른 같은 관절을 사용하기 때문에) 관절들의 위치와 방향을 보간합니다. 위치를 업데이트 하기 위해 다음과 같은 방정식을 사용할 수 있습니다.
interpolatedJoint.position = joint0.pos + (interpolation * (joint1.pos - joint0.pos))
두 프레임의 관절 사이 보간된 방향을 구하기 위해 구면 선형 보간 혹은 "Slerp"라 부르는 테크닉을 사용할 것입니다. 편리하게도 slerp이 가능한 xna math 함수가 있어서 다음 줄과 같이 보간된 방향을 얻을 수 있습니다. (xna를 쓰지 않는 관계로 공부겸 직접 만들어 사용하자)
interpolatedJoint.orientation = XMQuaternionSlerp(joint0.orientation, joint1.orientation, interpolation))
위 과정을 모든 관절에 대해 수행하면 보간된 뼈대를 얻습니다.
Updating the Vertex Position and Normal 다음으로 할 일은 정점과 법선을 보간된 뼈대를 이용하여 갱신하는 것입니다. 기본적으로 저번 강좌에서 정점 위치를 구했던 방법으로 할 수 있습니다. 각 정점에 대해 루프를 돌고 그 루프에서 각 정점의 weight 위치들에 대해 루프를 돕니다. 먼저 관절 방향을 이용하여 관절을 축으로 다음과 같은 방정식으로 weight를 회전시킵니다.
weightPos = XMQuaternionMultiply(XMQuaternionMultiply(jointOrientation, weightPos), jointOrientationConjugate))
위 식은 우리에게 관절 공간상의 weight 위치를 줄 것입니다. 이제 weight를 모델 공간상의 관절 위칠 옮겨야 합니다. weight 위치에 관절 위치를 더하는 것으로 쉽게 할 수 있습니다. 마지막으로 weight 위치를 가중치와 곱한 뒤 정점 최종 위치에 더합니다.
또 법선도 계산할 것입니다. 아직 설명하지는 않았지만 weight의 법선(md5mesh 파일을 로드하는 함수를 수정해서 얻음)을 이용할 것입니다. weight 위치를 관절 방향을 이용하여 회전시켰던 것처럼 같은 방법으로 weight 법선을 회전시킬 것입니다. 법선은 위치를 가지지 않기 때문에 (벡터라서) 법선에 관절 위치를 더할 필요는 없습니다. 그러고는 weight 법선과 가중치를 곱하고 정점의 최종 법선에 더합니다.
Updating Direct3D's Buffers 마지막으로 할 일은 버텍스 버퍼를 갱신하는 것입니다. 3가지 방법이 있습니다. (D3D11_USAGE_STAGING, D3D11_USAGE_DYNAMIC, D3D11_USAGE_DEFAULT중에 하나를 설정하는 것. D3D11_USAGE_IMMUTABLE 타입은 갱신이 불가합니다.)
첫번째 방법은 D3D11_USAGE_STAGING 버퍼로 D3D11_USAGE_DEFAULT buffer를 사용하는 것입니다. 스테이징 버퍼는 GPU에 보내진 버퍼로부터 정보를 얻는데 사용되는데 디폴트 버퍼는 기본적으로 스테이징 버퍼의 복사이기 때문입니다. 버퍼를 갱신하기 위해 먼저 ID3D11DeviceContext::Map와 ID3D11DeviceContext::Unmap를 이용하여 스테이징 버퍼를 갱신할 것입니다. 그러고는 스테이징 버퍼의 내용을 ID3D11DeviceContext::CopyResource를 이용하여 디폴트 버퍼로 복사할 것입니다. Map 메소드는 D3D11_MAPPED_SUBRESOURCE 객체를 반환하는데 객체의 pData 멤버는 버퍼내 데이터의 시작에 대한 포인터입니다. 그 다음에 memcpy() 함수로 버퍼내 정점 (혹은 여러분이 원하는 어떤 데이터든지) 을 저장할 수 있습니다. 다음처럼 할 수 있습니다.
D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
ID3D11DeviceContext->Map(stagingVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);
memcpy(mappedVertBuff.pData, &vertices[0], (sizeof(Vertex) * vertices.size()));
ID3D11DeviceContext->Unmap(stagingVertexBuffer, 0);
ID3D11DeviceContext->UpdateSubresource( defaultVertexBuffer, 0, NULL, &stagingVertexBuffer, 0, 0 );
두번째 방법은 스테이징 버퍼없이 그냥 디폴트 버퍼를 갱신하는 것입니다. 한줄이면 되며 ID3D11DeviceContext::CopyResource 메소드를 사용합니다. 다음 라인처럼 디폴트 버퍼를 갱신할 수 있습니다.
ID3D11DeviceContext->UpdateSubresource( defaultVertexBuffer, 0, NULL, &vertices[0], 0, 0 );
마지막 방법은 D3D11_USAGE_DYNAMIC 방식으로 만들어진 버퍼에 대한 것입니다. 이것은 우리가 사용할 방법인데 빠른 업데이트를 위해 만들어 졌기 때문입니다. (비록 GPU에 의해 느리게 읽어지지만, 동적 버퍼 정적 버퍼에 대해 이전 강좌(md5mesh 로드)에서 언급하였다) 스테이징 버퍼를 했던 것 처럼 Map 과 Unmap을 이용하여 버퍼를 갱신하 수 있습니다. 다음과 같이
D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
ID3D11DeviceContext->Map(dynamicVertexBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);
memcpy(mappedVertBuff.pData, &vertices[0], (sizeof(Vertex) * vertices.size()));
ID3D11DeviceContext->Unmap(dynamicVertexBuffer, 0);
첫번째 두번째 방법 스테이징 버퍼와 디폴트 버퍼를 사용하는 것은 동적 버퍼에 비해 매우 느리게 갱신되어서 버퍼가 프레임당 한번 이하로 갱신된다면 첫 두 방법으로 버퍼를 갱신하는 것이 좋습니다. 디폴트 버퍼는 GPU에 의해 빠르게 읽어지고 느리게 갱신됩니다. 동적 버퍼는 GPU에 의해 느리게 읽어지지만 훨씬 빠르게 갱신됩니다.
The .md5anim Format
.md5anim 포맷은 기본적으로 5개의 섹션으로 분할되어 있는데 헤더, hierarchy, 바운드, 기본프레임, 프레임들 입니다. 헤더는 파일과 애니메이션에 관한 정보를 가지고 있는데 fps(frames per second), 파일 버전, 관절과 프레임 총 개수 등 입니다. hierarchy는 모델에 사용된 관절의 리스트입니다. hierarchy 섹션에서 관절은 .md5mesh 파일의 수와 부모 자식 관계와 일치해야 합니다. hierarchy 다음은 바운드입니다. 이 섹션은 mesh의 각 프레임에 대해 축 일치 바운딩 박스(AABB)를 정의합니다. AABB는 충돌검출이나 픽킹 같은 빠르지만 부정확한 계산에 사용됩니다. 바운드 다음은 기본프레임으로 디폴트 위치에 있을때 각 관절의 방향과 위치를 저장합니다. 모든 프레임들은 이 기본프레임 혹은 기본 뼈대를 시작으로 지어집니다. 마지막으로 프레임들이 있습니다. 이 섹션은 프레임당 하나씩 있으며 각 섹션은 관절이 어떻게 이동되고 회전될지를 설명하는부동소수점값(실수)의 리스트를 포함합니다. "MD5Version" 이 문자열 다음에는 파일의 버전을 설명하는 숫자가 옵니다. 우리의 로더는 버전 10에 특화되어 있습니다. (실제 로더 체크는 안하지만) 제가 다운로드한 모든 md5 모델은 (사실 많지는 않습니다.) 버전 10이지만 다른 버전에 대한 정보를 아실 수 있습니다.
MD5Version 10
commandline ""
numFrames 46
"numJoints" 모델의 관절 개수이며 관절은 hierarchy 섹션에 있습니다. 이 숫자는 .md5mesh 파일의 숫자와 일치해야 합니다.
numJoints 31
"frameRate" frame rate는 초당 몇 프레임으로 애니메이션이 실행되는지 입니다. 파일이 의도하는 같은 스피드로 부드럽고 지속적인 애니메이션을 유지하도록 확실히 하기 위해 이 값을 우리의 프로그램에서 고려해야 합니다.
frameRate 30
"numAnimatedComponents" 이 수는 컴포넌트 혹은 각 프레임 섹션의 부동소수점 수 객수 입니다. 각 컴포넌트들은 기본프레임 관절 위치 혹은 방향의 컴포넌트중 하나를 대체할 것입니다. 이는 나중에 설명할 것입니다.
numAnimatedComponents 186
"hierarchy" 관절 설명의 시작부분입니다. 관절 설명은 다음 라인 ("hierarchy {" 다음) 부터 시작하여 닫는 괄호 ("}")에 도달할때 까지 갑니다.
관절의 개수와 부모 인덱스와 관절 이름은 반드시 .md5mesh 파일의 것과 일치해야 합니다. 만약 일치하지 않으면 예산치 못한 애니메이션 결과를 가질 수 있습니다. "hierarchy {" 뒤 각 라인은 새 관절입니다. 각 라인은 두 따옴표 사이 문자열로 시작하는데 문자열은 각 관절의 이름입니다. 관절 이름 다음은 부모 인덱스 입니다. 부모 인덱스가 "-1" 일때 해당 관절은 hierarchy의 가장 위에 위치하며 부모를 가지지 않음을 의미합니다. 그 다음 숫자는 나중에 프레임 섹션에서 관절의 어떤 부분(위치와 방향의 x, y, z)이 갱신될지를 설명하는 플래그 입니다. 마지막 값은 시작 인덱스 입니다. 시작 인덱스는 프레임 데이터에서 특정 관절 갱신을 시작하는 값입니다. 밑에 코드 보시면 알 수 있듯이 "bip01"의 시작 인덱스는 "0"인데 프레임 데이터 에서 가장 첫번째 값으로 시작하며 갱신될 것을 의미합니다. 플래그 섹션은 관절을 어떻게 갱신할지 그리고 프레임 데이터에서 시작 인덱스로부터 얼마나 많은 값을 사용할지를 정의할 것 입니다. (1~6 사이, 3개는 위치 3개는 방향)
hierarchy {
"Bip01" -1 63 0
...
}
"bounds" 바운드는 각 프레임에 대한 축 정렬 바운딩 박스입니다. "bounds"뒤 각 라인은 프레임들의 AABB 인데 x, y, z 축별로 가장 크고 가장 작은 값을 의미하는 최대, 최소 점으로 구성되어 있습니다. 괄호 안 첫번째 실수 3개는 최소점을 나타내고 두번째 괄호의 실수 3개는 최대점을 나타냅니다.
( -30.4137325286 -23.4689254760 -42.4965248107 ) ( 35.7306137084 6.3094730377 48.7827911376 )
"baseframe" 애니메이션에서 각 관절의 기본 위치입니다. 이 값은 .md5mesh에 묘사된 bind-pose와 같을 필요는 없습니다. 각 프레임은 이 기본프레임으로부터 프레임 뼈대를 생성하는데 모든 프레임이 모든 관절 위치와 회전을 나타내지 않기때문입니다. 그 이유는 기본프레임이 0이나 다른 어떤 값으로 채워질 수 있으며 이 경우 모든 관절의 위치나 방향은 모든 프레임에서 갱신되개 때문입니다. "baseframe" 뒤 각 라인은 대응하는 관절 위치와 방향을 나태내는데 첫번째 실수 3개는 위치를 그다음 3개는 방향을 나타냅니다.
( 0.5196304321 1.7362534999 4.6482505798 ) ( 0.0000000000 0.0000000000 0.7071063041 )
"frame" .md5anim 파일에서 살펴 볼 마지막 섹션은 프레임 섹션입니다. 애니메이션의 각 프레임은 섹션을 하나씩 가지며 각 섹션은 실수 리스트로 이루어져 있습니다. 실수의 개수는 헤더의 "numAnimatedComponents"에 정의되어 있습니다. 각 실수는 기본프레임 과절의 위치와 방향값의 x, y, z를 대체할 것입니다. 관절(위치나 방향의 x, y, z)의 어떤 값 어떤 요소인지는 관절 플래그와 시작 인덱스에 의해 결정됩니다.
0.5196304321 1.7362534999 4.6482505798 0.0000000000 0.0000000000 0.7071063041
Animating the Normals
모든 정점에 대한 법선을 계산하는 것은 상당히 느릴 수 있습니다. 그래서 우리의 모델을 애니메이팅할때를 위한 방법이 따로 있으며 그래서 모든 프레임에 대해 법선을 계산하지 않아도 됩니다. 우리가 하는 방법은 .md5mesh 파일로부터 원래 모델을 로드할때 관절 공간상의 (각 weight에 대해) 법선을 계산하는 것입니다. (이번 강좌에서 이 작업을 하기위해 함수를 수정했습니다.) 관절 공간의 법선을 구한 뒤 관절 방향을 기인한 각 프레임에 대한 법선을 쉽게 회전시킬 수 있습니다. 마치 관절 방향을 이용하여 weight 위치를 회전시킨것과 똑같이 단, 법선을 재위치하지 않아도 됩니다. 관절 공간상의 법선을 계산할때 먼저 각 정점에 대해 보통 모델에 대해 하듯이 법선을 계산합니다. 그러면 정점 법선을 사용할 수 있고 관절 방향의 역으로 회전시킬 수 있습니다. (LoadMd5Model 함수의 내용이며 왜 역방향으로 회전시키는지 불분) 정점에 대해 각 weight에 대해 돌며 이 weight가 바인딩된 관절로 이 계산을 수행합니다.
Right/Left Handed Coordinat Systems
여러분은 아마도 제가 .md5mesh와 .md5anim을 로딩할때 "z" 와 "y" 값을 바꾸는 것을 알아차리실 겁니다. 왜냐하면 파일이 오른손 좌표계로 출력되는 반면에 directx는 왼손 좌표계를 사용하기 때문입니다. 이미 모델이 왼손 좌표계로 이루어져 있는 경우 데이터 로딩시 y와 z 값을 다시 바꾸시면 됩니다.
New Structures
여기 여러개의 새로운 구조체들이 있습니다. 보다시피 첫번쨰는 .md5anim 파일의 바운드 섹션에서 로드된 바운딩 박스를 위한 것 입니다. 다음 구조체는 프레임 데이터 구조체입니다. 이 구조체는 어떤 프레임인지 확인하는 숫자와 프레임 데이터인 실수 배열을 가질 것입니다. 이 실수값들은 각 프레임에 대한 기본프레임 뼈대를 갱신하는데 사용될 것입니다. 다음 새 구조체는 관절을 위한 것입니다. 이 구조체는 다른 관절 구조체(md5mesh때 생성한)와는 다른 데이터를 가지는데 각 애니메이션의 관절은 (md5mesh 파일은 하나 이상의 md5anim 파일을 가질 수 있기 때문에) 애니메이션 마다 다를 수 있습니다. 그래서 이 구조체는 플래그나 (프레임마다 관절의 어떤 요소가 업데이트 될지) 시작 인덱스같은 (업데이트를 시작하는 프레임 데이터 열에서 첫번째 값(frameID)) 애니메이션 전용 데이터를 가집니다. 다음은 ModelAnimation 구조체 입니다. 나중에 이 구조체의 벡터(STL)가 Model3D 구조체의 멤버로 있는것을 보실 겁니다. 그 이유는 각 모델은 하나 이상의 애니메이션을 가질수 있기 때문입니다. 어쨌든 이 구조체는 애니메이션에 따른 (각 프레임의 뼈대 포즈) 정보를 저장합니다. 멤버는 이미 위에서 설명했거나 제 설명없이도 이해하기 충분히 쉽습니다. 하지만 마지막 멤버인 frameSkeleton은 설명할 것입니다. 각 뼈대는 관절의 벡터(stl)로 정의되어 있고 애니메이션에서 각 프레임 별로 단일 뼈대를 가집니다. 첫번째 벡터(stl)는 애니메이션의 각 프레임을 위함이고 그중 한 요소 내부 벡터(stl)은 뼈대의 관절들의 벡터(stl)입니다. (std::vector<std::vector<Joint>> frameSkeleton 에 대한 설명이다.) 그러면 나중에 이 벡터(stl)의 멤버로부터 "frameSkeleton[frame][joint]" 이런식으로 관절 하나를 호출할 수 있으며 아니면 간단하게 "frameSkeleton[frame]" 이런식으로 프레임을 통째로 가져올 수 있습니다.
struct BoundingBox
{
XMFLOAT3 min;
XMFLOAT3 max;
};
struct FrameData
{
int frameID;
std::vector<float> frameData;
};
struct AnimJointInfo
{
std::wstring name;
int parentID;
int flags;
int startIndex;
};
struct ModelAnimation
{
int numFrames;
int numJoints;
int frameRate;
int numAnimatedComponents;
float frameTime;
float totalAnimTime;
float currAnimTime;
std::vector<AnimJointInfo> jointInfo;
std::vector<BoundingBox> frameBounds;
std::vector<Joint> baseFrameJoints;
std::vector<FrameData> frameData;
std::vector<std::vector<Joint>> frameSkeleton;
};
Updated Weight Structure
법선을 추가하기 위해 weight 구조체를 수정하였습니다. 그 이유는 법선을 관절 공간에 정의한다면 모든 프레임에 대해 모든 법선을 다시 계산해야하는 대신 애니메이션의 진전으로써 쉽게 바꿀 수 있습니다.
struct Weight
{
int jointID;
float bias;
XMFLOAT3 pos;
///////////////**************new**************////////////////////
XMFLOAT3 normal;
///////////////**************new**************////////////////////
};
Updated Model3D Structure
여기서 볼 수 있듯이 애니메이션의 배열을 가지기 위해 Model3D 구조체를 수정하였습니다.
struct Model3D
{
int numSubsets;
int numJoints;
std::vector<Joint> joints;
std::vector<ModelSubset> subsets;
///////////////**************new**************////////////////////
std::vector<ModelAnimation> animations;
///////////////**************new**************////////////////////
};
Two New Functions
새로 추가된 함수들입니다. 첫번째 것은 .md5anim 파일에서 애니메이션을 로드하고 두번째 것은 애니메이션에 넘겨진 시간에 따라 애니메이션이 사용할 버텍스 버퍼를 갱신합니다.
bool LoadMD5Anim(std::wstring filename, Model3D& MD5Model);
void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation);
The DetectInput() Function
키 검출 함수가 바뀌었습니다. "R"이 추가되었습니다. 이 키를 누를때 애니메이션이 갱신될 것입니다. "timeFactor" 라는 실수도 추가하였는데 이걸로 애니메이션 영역에서 빠르게 하거나 느리게 할 수 있습니다. 여러분이 정말로 게임 전반에서 속도를 조절하고 싶으면 이 time factor를 많은 영역에 두어야 합니다. (모델 뿐만 아니라 다른 것도 속도 조절을 하고 싶다면 전역 변수이거나 클래스 관계에서 상위단계에 위치시켜 코드 전반에 걸쳐 적용할 수 있어야 한다는 의미같습니다. ㅠ)
void DetectInput(double time)
{
DIMOUSESTATE mouseCurrState;
BYTE keyboardState[256];
DIKeyboard->Acquire();
DIMouse->Acquire();
DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState);
DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState);
if(keyboardState[DIK_ESCAPE] & 0x80)
PostMessage(hwnd, WM_DESTROY, 0, 0);
float speed = 10.0f * time;
if(keyboardState[DIK_A] & 0x80)
{
moveLeftRight -= speed;
}
if(keyboardState[DIK_D] & 0x80)
{
moveLeftRight += speed;
}
if(keyboardState[DIK_W] & 0x80)
{
moveBackForward += speed;
}
if(keyboardState[DIK_S] & 0x80)
{
moveBackForward -= speed;
}
///////////////**************new**************////////////////////
if(keyboardState[DIK_R] & 0X80)
{
float timeFactor = 1.0f; // You can speed up or slow down time by changing this
UpdateMD5Model(NewMD5Model, time*timeFactor, 0);
}
///////////////**************new**************////////////////////
if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY))
{
camYaw += mouseLastState.lX * 0.001f;
camPitch += mouseCurrState.lY * 0.001f;
mouseLastState = mouseCurrState;
}
UpdateCamera();
return;
}
The LoadMD5Anim() Function
여기서 애니메이션을 로드할 것입니다. 또 애니메이션의 모든 프레임에 대한 뼈대를 생성할 것입니다. 함수를 부분 부분 살펴 보도록 하겠습니다.
bool LoadMD5Anim(std::wstring filename, Model3D& MD5Model)
{
ModelAnimation tempAnim; // Temp animation to later store in our model's animation array
std::wifstream fileIn (filename.c_str()); // Open file
std::wstring checkString; // Stores the next string from our file
if(fileIn) // Check if the file was opened
{
while(fileIn) // Loop until the end of the file is reached
{
fileIn >> checkString; // Get next string from file
if ( checkString == L"MD5Version" ) // Get MD5 version (this function supports version 10)
{
fileIn >> checkString;
/*MessageBox(0, checkString.c_str(), //display message
L"MD5Version", MB_OK);*/
}
else if ( checkString == L"commandline" )
{
std::getline(fileIn, checkString); // Ignore the rest of this line
}
else if ( checkString == L"numFrames" )
{
fileIn >> tempAnim.numFrames; // Store number of frames in this animation
}
else if ( checkString == L"numJoints" )
{
fileIn >> tempAnim.numJoints; // Store number of joints (must match .md5mesh)
}
else if ( checkString == L"frameRate" )
{
fileIn >> tempAnim.frameRate; // Store animation's frame rate (frames per second)
}
else if ( checkString == L"numAnimatedComponents" )
{
fileIn >> tempAnim.numAnimatedComponents; // Number of components in each frame section
}
else if ( checkString == L"hierarchy" )
{
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numJoints; i++) // Load in each joint
{
AnimJointInfo tempJoint;
fileIn >> tempJoint.name; // Get joints name
// Sometimes the names might contain spaces. If that is the case, we need to continue
// to read the name until we get to the closing " (quotation marks)
if(tempJoint.name[tempJoint.name.size()-1] != '"')
{
wchar_t checkChar;
bool jointNameFound = false;
while(!jointNameFound)
{
checkChar = fileIn.get();
if(checkChar == '"')
jointNameFound = true;
tempJoint.name += checkChar;
}
}
// Remove the quotation marks from joints name
tempJoint.name.erase(0, 1);
tempJoint.name.erase(tempJoint.name.size()-1, 1);
fileIn >> tempJoint.parentID; // Get joints parent ID
fileIn >> tempJoint.flags; // Get flags
fileIn >> tempJoint.startIndex; // Get joints start index
// Make sure the joint exists in the model, and the parent ID's match up
// because the bind pose (md5mesh) joint hierarchy and the animations (md5anim)
// joint hierarchy must match up
bool jointMatchFound = false;
for(int k = 0; k < MD5Model.numJoints; k++)
{
if(MD5Model.joints[k].name == tempJoint.name)
{
if(MD5Model.joints[k].parentID == tempJoint.parentID)
{
jointMatchFound = true;
tempAnim.jointInfo.push_back(tempJoint);
}
}
}
if(!jointMatchFound) // If the skeleton system does not match up, return false
return false; // You might want to add an error message here
std::getline(fileIn, checkString); // Skip rest of this line
}
}
else if ( checkString == L"bounds" ) // Load in the AABB for each animation
{
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numFrames; i++)
{
BoundingBox tempBB;
fileIn >> checkString; // Skip "("
fileIn >> tempBB.min.x >> tempBB.min.z >> tempBB.min.y;
fileIn >> checkString >> checkString; // Skip ") ("
fileIn >> tempBB.max.x >> tempBB.max.z >> tempBB.max.y;
fileIn >> checkString; // Skip ")"
tempAnim.frameBounds.push_back(tempBB);
}
}
else if ( checkString == L"baseframe" ) // This is the default position for the animation
{ // All frames will build their skeletons off this
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numJoints; i++)
{
Joint tempBFJ;
fileIn >> checkString; // Skip "("
fileIn >> tempBFJ.pos.x >> tempBFJ.pos.z >> tempBFJ.pos.y;
fileIn >> checkString >> checkString; // Skip ") ("
fileIn >> tempBFJ.orientation.x >> tempBFJ.orientation.z >> tempBFJ.orientation.y;
fileIn >> checkString; // Skip ")"
tempAnim.baseFrameJoints.push_back(tempBFJ);
}
}
else if ( checkString == L"frame" ) // Load in each frames skeleton (the parts of each joint that changed from the base frame)
{
FrameData tempFrame;
fileIn >> tempFrame.frameID; // Get the frame ID
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numAnimatedComponents; i++)
{
float tempData;
fileIn >> tempData; // Get the data
tempFrame.frameData.push_back(tempData);
}
tempAnim.frameData.push_back(tempFrame);
///*** build the frame skeleton ***///
std::vector tempSkeleton;
for(int i = 0; i < tempAnim.jointInfo.size(); i++)
{
int k = 0; // Keep track of position in frameData array
// Start the frames joint with the base frame's joint
Joint tempFrameJoint = tempAnim.baseFrameJoints[i];
tempFrameJoint.parentID = tempAnim.jointInfo[i].parentID;
// Notice how I have been flipping y and z. this is because some modeling programs such as
// 3ds max (which is what I use) use a right handed coordinate system. Because of this, we
// need to flip the y and z axes. If your having problems loading some models, it's possible
// the model was created in a left hand coordinate system. in that case, just reflip all the
// y and z axes in our md5 mesh and anim loader.
if(tempAnim.jointInfo[i].flags & 1) // pos.x ( 000001 )
tempFrameJoint.pos.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 2) // pos.y ( 000010 )
tempFrameJoint.pos.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 4) // pos.z ( 000100 )
tempFrameJoint.pos.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 8) // orientation.x ( 001000 )
tempFrameJoint.orientation.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 16) // orientation.y ( 010000 )
tempFrameJoint.orientation.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 32) // orientation.z ( 100000 )
tempFrameJoint.orientation.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
// Compute the quaternions w
float t = 1.0f - ( tempFrameJoint.orientation.x * tempFrameJoint.orientation.x )
- ( tempFrameJoint.orientation.y * tempFrameJoint.orientation.y )
- ( tempFrameJoint.orientation.z * tempFrameJoint.orientation.z );
if ( t < 0.0f )
{
tempFrameJoint.orientation.w = 0.0f;
}
else
{
tempFrameJoint.orientation.w = -sqrtf(t);
}
// Now, if the upper arm of your skeleton moves, you need to also move the lower part of your arm, and then the hands, and then finally the fingers (possibly weapon or tool too)
// This is where joint hierarchy comes in. We start at the top of the hierarchy, and move down to each joints child, rotating and translating them based on their parents rotation
// and translation. We can assume that by the time we get to the child, the parent has already been rotated and transformed based of it's parent. We can assume this because
// the child should never come before the parent in the files we loaded in.
if(tempFrameJoint.parentID >= 0)
{
Joint parentJoint = tempSkeleton[tempFrameJoint.parentID];
// Turn the XMFLOAT3 and 4's into vectors for easier computation
XMVECTOR parentJointOrientation = XMVectorSet(parentJoint.orientation.x, parentJoint.orientation.y, parentJoint.orientation.z, parentJoint.orientation.w);
XMVECTOR tempJointPos = XMVectorSet(tempFrameJoint.pos.x, tempFrameJoint.pos.y, tempFrameJoint.pos.z, 0.0f);
XMVECTOR parentOrientationConjugate = XMVectorSet(-parentJoint.orientation.x, -parentJoint.orientation.y, -parentJoint.orientation.z, parentJoint.orientation.w);
// Calculate current joints position relative to its parents position
XMFLOAT3 rotatedPos;
XMStoreFloat3(&rotatedPos, XMQuaternionMultiply(XMQuaternionMultiply(parentJointOrientation, tempJointPos), parentOrientationConjugate));
// Translate the joint to model space by adding the parent joint's pos to it
tempFrameJoint.pos.x = rotatedPos.x + parentJoint.pos.x;
tempFrameJoint.pos.y = rotatedPos.y + parentJoint.pos.y;
tempFrameJoint.pos.z = rotatedPos.z + parentJoint.pos.z;
// Currently the joint is oriented in its parent joints space, we now need to orient it in
// model space by multiplying the two orientations together (parentOrientation * childOrientation) <- In that order
XMVECTOR tempJointOrient = XMVectorSet(tempFrameJoint.orientation.x, tempFrameJoint.orientation.y, tempFrameJoint.orientation.z, tempFrameJoint.orientation.w);
tempJointOrient = XMQuaternionMultiply(parentJointOrientation, tempJointOrient);
// Normalize the orienation quaternion
tempJointOrient = XMQuaternionNormalize(tempJointOrient);
XMStoreFloat4(&tempFrameJoint.orientation, tempJointOrient);
}
// Store the joint into our temporary frame skeleton
tempSkeleton.push_back(tempFrameJoint);
}
// Push back our newly created frame skeleton into the animation's frameSkeleton array
tempAnim.frameSkeleton.push_back(tempSkeleton);
fileIn >> checkString; // Skip closing bracket "}"
}
}
// Calculate and store some usefull animation data
tempAnim.frameTime = 1.0f / tempAnim.frameRate; // Set the time per frame
tempAnim.totalAnimTime = tempAnim.numFrames * tempAnim.frameTime; // Set the total time the animation takes
tempAnim.currAnimTime = 0.0f; // Set the current time to zero
MD5Model.animations.push_back(tempAnim); // Push back the animation into our model object
}
else // If the file was not loaded
{
SwapChain->SetFullscreenState(false, NULL); // Make sure we are out of fullscreen
// create message
std::wstring message = L"Could not open: ";
message += filename;
MessageBox(0, message.c_str(), // display message
L"Error", MB_OK);
return false;
}
return true;
}
Opening the File and Reading the Header Info
저번 강좌에서 설명할 것은 또 설명하는데 시간을 많이 할애하고 싶지 않기 때문에 여러분이 최소한 저번 강좌를 읽었다면 어떻게 동작하는지 대충 아실 겁니다.
bool LoadMD5Anim(std::wstring filename, Model3D& MD5Model)
{
ModelAnimation tempAnim; // Temp animation to later store in our model's animation array
std::wifstream fileIn (filename.c_str()); // Open file
std::wstring checkString; // Stores the next string from our file
if(fileIn) // Check if the file was opened
{
while(fileIn) // Loop until the end of the file is reached
{
fileIn >> checkString; // Get next string from file
if ( checkString == L"MD5Version" ) // Get MD5 version (this function supports version 10)
{
fileIn >> checkString;
/*MessageBox(0, checkString.c_str(), //display message
L"MD5Version", MB_OK);*/
}
else if ( checkString == L"commandline" )
{
std::getline(fileIn, checkString); // Ignore the rest of this line
}
else if ( checkString == L"numFrames" )
{
fileIn >> tempAnim.numFrames; // Store number of frames in this animation
}
else if ( checkString == L"numJoints" )
{
fileIn >> tempAnim.numJoints; // Store number of joints (must match .md5mesh)
}
else if ( checkString == L"frameRate" )
{
fileIn >> tempAnim.frameRate; // Store animation's frame rate (frames per second)
}
else if ( checkString == L"numAnimatedComponents" )
{
fileIn >> tempAnim.numAnimatedComponents; // Number of components in each frame section
}
...
}
else // If the file was not loaded
{
SwapChain->SetFullscreenState(false, NULL); // Make sure we are out of fullscreen
// create message
std::wstring message = L"Could not open: ";
message += filename;
MessageBox(0, message.c_str(), // display message
L"Error", MB_OK);
return false;
}
return true;
}
Load the Joint Hierarchy
다음은 hierarchy를 로드합니다. 위에서 제가 얻급하였듯이 이 hierarchy가 .md5mesh 파일의 것과 일치하는지 확인합니다. 그렇지 않다면 false를 리턴합니다.
else if ( checkString == L"hierarchy" )
{
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numJoints; i++) // Load in each joint
{
AnimJointInfo tempJoint;
fileIn >> tempJoint.name; // Get joints name
// Sometimes the names might contain spaces. If that is the case, we need to continue
// to read the name until we get to the closing " (quotation marks)
if(tempJoint.name[tempJoint.name.size()-1] != '"')
{
wchar_t checkChar;
bool jointNameFound = false;
while(!jointNameFound)
{
checkChar = fileIn.get();
if(checkChar == '"')
jointNameFound = true;
tempJoint.name += checkChar;
}
}
// Remove the quotation marks from joints name
tempJoint.name.erase(0, 1);
tempJoint.name.erase(tempJoint.name.size()-1, 1);
fileIn >> tempJoint.parentID; // Get joints parent ID
fileIn >> tempJoint.flags; // Get flags
fileIn >> tempJoint.startIndex; // Get joints start index
// Make sure the joint exists in the model, and the parent ID's match up
// because the bind pose (md5mesh) joint hierarchy and the animations (md5anim)
// joint hierarchy must match up
bool jointMatchFound = false;
for(int k = 0; k < MD5Model.numJoints; k++)
{
if(MD5Model.joints[k].name == tempJoint.name)
{
if(MD5Model.joints[k].parentID == tempJoint.parentID)
{
jointMatchFound = true;
tempAnim.jointInfo.push_back(tempJoint);
}
}
}
if(!jointMatchFound) // If the skeleton system does not match up, return false
return false; // You might want to add an error message here
std::getline(fileIn, checkString); // Skip rest of this line
}
}
Load Each Frames Bounding Box (AABB)
이제 각 프레임의 바운딩 박스를 로드하는데 바로 최소, 최대 점입니다. 이 강좌에서는 바운딩 박스를 사용하지 않을 것이지만 필요해 보이는 어떤 목적 가장 먼저 생각나는 것은 충돌 검출 같은 목적에 사용될 수 있습니다.
else if ( checkString == L"bounds" ) // Load in the AABB for each animation
{
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numFrames; i++)
{
BoundingBox tempBB;
fileIn >> checkString; // Skip "("
fileIn >> tempBB.min.x >> tempBB.min.z >> tempBB.min.y;
fileIn >> checkString >> checkString; // Skip ") ("
fileIn >> tempBB.max.x >> tempBB.max.z >> tempBB.max.y;
fileIn >> checkString; // Skip ")"
tempAnim.frameBounds.push_back(tempBB);
}
}
Loading the Baseframe
여기 기본프레임을 로드하는데 다시말하지만 이것에 관해 모든 프레임 뼈대들이 이 기본프레임의 뼈대로 만든다는 것 외에 할 말은 없습니다.
else if ( checkString == L"baseframe" ) // This is the default position for the animation
{ // All frames will build their skeletons off this
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numJoints; i++)
{
Joint tempBFJ;
fileIn >> checkString; // Skip "("
fileIn >> tempBFJ.pos.x >> tempBFJ.pos.z >> tempBFJ.pos.y;
fileIn >> checkString >> checkString; // Skip ") ("
fileIn >> tempBFJ.orientation.x >> tempBFJ.orientation.z >> tempBFJ.orientation.y;
fileIn >> checkString; // Skip ")"
tempAnim.baseFrameJoints.push_back(tempBFJ);
}
}
Loading the Frame
여기는 프레임 섹션입니다. 프레임 섹션은 실수값(개수는 헤더의 numAnimatedComponents에 나와있습니다.)들의 배열을 가집니다.
else if ( checkString == L"frame" ) // Load in each frames skeleton (the parts of each joint that changed from the base frame)
{
FrameData tempFrame;
fileIn >> tempFrame.frameID; // Get the frame ID
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numAnimatedComponents; i++)
{
float tempData;
fileIn >> tempData; // Get the data
tempFrame.frameData.push_back(tempData);
}
tempAnim.frameData.push_back(tempFrame);
Creating the Frame Skeleton
데이터에 대한 실수값들의 묶을을 가지는 프레임을 로드했고 이제 이 데이터를 이용하여 프레임 뼈대를 생성할 수 있습니다. "flag"와 덧붙여 시작 인덱스가 등장하는 곳입니다. 플래그을 설명하는 것으로 시작 해 보겠습니다. 플래그는 비트에 관한 숫자(0 과 1)로 더 정확하게 64의 비트 숫자(6비트)로 관절의 어느 부분이 갱신될지를 나타냅니다. 관절 위치의 x 값이 갱신될 여부를 나타내는 첫번째 비트 부터 관절 방향의 z 값인 마지막 비트순으로 정렬됩니다. 예를 들어 "1" 또는 (000001) 은 관절 위치의 x가 프레임 데이터 배열의 실수 값으로 대체될지를 나타냅니다. "7" 또는 (000111) 은 관절 위치의 모든 x, y, z 가 프레임 데이터의 다음 3개 값들로 갱신되는 것을 나타냅니다. "63" 또는 (111111) 은 위치와 방향 둘 다 x, y, z값이 갱신되는 것을 의미합니다. 관절의 시작 인덱스는 프레임 데이터 배열의 어디서 사직할지 즉 관절을 업데이트하는데 사용할 시작 값을 의미합니다. 관절에 대해 가능한 여섯 플래그를 각각 확인하며 설정되어 있으면 위치나 방향의 x, y, z를 대체하고 "k"를 증가시키는데 관절의 시작 인덱스로부터 프레임 데이터 배열에서 우리의 위치를 추적하고있습니다. 이걸 이해 하셨다면 그리 어렵지 않다는 것을 아실겁니다. 제가 단지 "장황"해짐 없이 설명한 것 같지 않은 것 같습니다. (장황하게 설명 했다는) 기본프레임으로부터 프레임 뼈대의 관절들을 갱신한 후에 방향 사원수의 "w" 값을 관절의 부모 위치와 방향을 고려한 프레임 관절을 갱신하기 전에 계산합니다.
///*** build the frame skeleton ***///
std::vector<Joint> tempSkeleton;
for(int i = 0; i < tempAnim.jointInfo.size(); i++)
{
int k = 0; // Keep track of position in frameData array
// Start the frames joint with the base frame's joint
Joint tempFrameJoint = tempAnim.baseFrameJoints[i];
tempFrameJoint.parentID = tempAnim.jointInfo[i].parentID;
// Notice how I have been flipping y and z. this is because some modeling programs such as
// 3ds max (which is what I use) use a right handed coordinate system. Because of this, we
// need to flip the y and z axes. If your having problems loading some models, it's possible
// the model was created in a left hand coordinate system. in that case, just reflip all the
// y and z axes in our md5 mesh and anim loader.
if(tempAnim.jointInfo[i].flags & 1) // pos.x ( 000001 )
tempFrameJoint.pos.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 2) // pos.y ( 000010 )
tempFrameJoint.pos.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 4) // pos.z ( 000100 )
tempFrameJoint.pos.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 8) // orientation.x ( 001000 )
tempFrameJoint.orientation.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 16) // orientation.y ( 010000 )
tempFrameJoint.orientation.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 32) // orientation.z ( 100000 )
tempFrameJoint.orientation.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
// Compute the quaternions w
float t = 1.0f - ( tempFrameJoint.orientation.x * tempFrameJoint.orientation.x )
- ( tempFrameJoint.orientation.y * tempFrameJoint.orientation.y )
- ( tempFrameJoint.orientation.z * tempFrameJoint.orientation.z );
if ( t < 0.0f )
{
tempFrameJoint.orientation.w = 0.0f;
}
else
{
tempFrameJoint.orientation.w = -sqrtf(t);
}
Update Frame Skeleton Joints Based On Parent's Position and Orientation
다음은 위에서 말했던 부로를 기인하여 관절들을 업데이트해 보도록 하겠습니다. hierarchy 리스트에서 추정컨데 부모전에 자식이 먼저 오지 않음으로 부모보다 먼저 자식이 처리되지 않을 것을 알기때문에 그런 경우가 있다면 지금 우리가 하려는 것이 무의미합니다. (부모 관절이 먼저 오는 것이 전제이다.) 각 관절에 대해 돌며 부모 관절을 찾습니다. (부모가 있다면, 탑 관절은 부모가 없기 때문에) 그러고는 부모 방향으로 관절의 위치를 회전시킵니다. 그 다음에 모델 공간에 위치하기 위해 부모 위치를 자식 위치에 더해야 합니다. 그 다음에는 부모 관절에 맞게 자식 관절에 새 방향을 주어야 합니다. 이것을 하기 위해서는 간단하게 함께 곱하면 됩니다. (부모 * 자식 (둘다사원수로))
// Now, if the upper arm of your skeleton moves, you need to also move the lower part of your arm, and then the hands, and then finally the fingers (possibly weapon or tool too)
// This is where joint hierarchy comes in. We start at the top of the hierarchy, and move down to each joints child, rotating and translating them based on their parents rotation
// and translation. We can assume that by the time we get to the child, the parent has already been rotated and transformed based of it's parent. We can assume this because
// the child should never come before the parent in the files we loaded in.
if(tempFrameJoint.parentID >= 0)
{
Joint parentJoint = tempSkeleton[tempFrameJoint.parentID];
// Turn the XMFLOAT3 and 4's into vectors for easier computation
XMVECTOR parentJointOrientation = XMVectorSet(parentJoint.orientation.x, parentJoint.orientation.y, parentJoint.orientation.z, parentJoint.orientation.w);
XMVECTOR tempJointPos = XMVectorSet(tempFrameJoint.pos.x, tempFrameJoint.pos.y, tempFrameJoint.pos.z, 0.0f);
XMVECTOR parentOrientationConjugate = XMVectorSet(-parentJoint.orientation.x, -parentJoint.orientation.y, -parentJoint.orientation.z, parentJoint.orientation.w);
// Calculate current joints position relative to its parents position
XMFLOAT3 rotatedPos;
XMStoreFloat3(&rotatedPos, XMQuaternionMultiply(XMQuaternionMultiply(parentJointOrientation, tempJointPos), parentOrientationConjugate));
// Translate the joint to model space by adding the parent joint's pos to it
tempFrameJoint.pos.x = rotatedPos.x + parentJoint.pos.x;
tempFrameJoint.pos.y = rotatedPos.y + parentJoint.pos.y;
tempFrameJoint.pos.z = rotatedPos.z + parentJoint.pos.z;
// Currently the joint is oriented in its parent joints space, we now need to orient it in
// model space by multiplying the two orientations together (parentOrientation * childOrientation) <- In that order
XMVECTOR tempJointOrient = XMVectorSet(tempFrameJoint.orientation.x, tempFrameJoint.orientation.y, tempFrameJoint.orientation.z, tempFrameJoint.orientation.w);
tempJointOrient = XMQuaternionMultiply(parentJointOrientation, tempJointOrient);
// Normalize the orienation quaternion
tempJointOrient = XMQuaternionNormalize(tempJointOrient);
XMStoreFloat4(&tempFrameJoint.orientation, tempJointOrient);
}
// Store the joint into our temporary frame skeleton
tempSkeleton.push_back(tempFrameJoint);
}
// Push back our newly created frame skeleton into the animation's frameSkeleton array
tempAnim.frameSkeleton.push_back(tempSkeleton);
fileIn >> checkString; // Skip closing bracket "}"
}
}
Calculating Some Frame Stuff
각 프레임의 시간 길이와 애니메이션 총 시간을 알아야 해서 빠르게 계산합니다. 또 확실하게 최근 애니메이션 시간도 0으로 설정합니다. 다 하면 temp 애니메이션을 우리의 애니메이션 벡터(stl)에 푸쉬하고 함수를 성공적으로 종료합니다.
// Calculate and store some usefull animation data
tempAnim.frameTime = 1.0f / tempAnim.frameRate; // Set the time per frame
tempAnim.totalAnimTime = tempAnim.numFrames * tempAnim.frameTime; // Set the total time the animation takes
tempAnim.currAnimTime = 0.0f; // Set the current time to zero
MD5Model.animations.push_back(tempAnim); // Push back the animation into our model object
The UpdateMD5Model() Function
이 함수는 애니메이션을 사용하여 우리의 모델을 갱신하기 원할 때마다 호출 됩니다. 이 함수도 똑같이 부분 부분 살펴 볼 것입니다.
void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation)
{
MD5Model.animations[animation].currAnimTime += deltaTime; // Update the current animation time
if(MD5Model.animations[animation].currAnimTime > MD5Model.animations[animation].totalAnimTime)
MD5Model.animations[animation].currAnimTime = 0.0f;
// Which frame are we on
float currentFrame = MD5Model.animations[animation].currAnimTime * MD5Model.animations[animation].frameRate;
int frame0 = floorf( currentFrame );
int frame1 = frame0 + 1;
// Make sure we don't go over the number of frames
if(frame0 == MD5Model.animations[animation].numFrames-1)
frame1 = 0;
float interpolation = currentFrame - frame0; // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor
std::vector<Joint> interpolatedSkeleton; // Create a frame skeleton to store the interpolated skeletons in
// Compute the interpolated skeleton
for( int i = 0; i < MD5Model.animations[animation].numJoints; i++)
{
Joint tempJoint;
Joint joint0 = MD5Model.animations[animation].frameSkeleton[frame0][i]; // Get the i'th joint of frame0's skeleton
Joint joint1 = MD5Model.animations[animation].frameSkeleton[frame1][i]; // Get the i'th joint of frame1's skeleton
tempJoint.parentID = joint0.parentID; // Set the tempJoints parent id
// Turn the two quaternions into XMVECTORs for easy computations
XMVECTOR joint0Orient = XMVectorSet(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w);
XMVECTOR joint1Orient = XMVectorSet(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w);
// Interpolate positions
tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x));
tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y));
tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z));
// Interpolate orientations using spherical interpolation (Slerp)
XMStoreFloat4(&tempJoint.orientation, XMQuaternionSlerp(joint0Orient, joint1Orient, interpolation));
interpolatedSkeleton.push_back(tempJoint); // Push the joint back into our interpolated skeleton
}
for ( int k = 0; k < MD5Model.numSubsets; k++)
{
for ( int i = 0; i < MD5Model.subsets[k].vertices.size(); ++i )
{
Vertex tempVert = MD5Model.subsets[k].vertices[i];
tempVert.pos = XMFLOAT3(0, 0, 0); // Make sure the vertex's pos is cleared first
tempVert.normal = XMFLOAT3(0,0,0); // Clear vertices normal
// Sum up the joints and weights information to get vertex's position and normal
for ( int j = 0; j < tempVert.WeightCount; ++j )
{
Weight tempWeight = MD5Model.subsets[k].weights[tempVert.StartWeight + j];
Joint tempJoint = interpolatedSkeleton[tempWeight.jointID];
// Convert joint orientation and weight pos to vectors for easier computation
XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);
// We will need to use the conjugate of the joint orientation quaternion
XMVECTOR tempJointOrientationConjugate = XMQuaternionInverse(tempJointOrientation);
// Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
// We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
XMFLOAT3 rotatedPoint;
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));
// Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;
// Compute the normals for this frames skeleton using the weight normals from before
// We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate)
XMVECTOR tempWeightNormal = XMVectorSet(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f);
// Rotate the normal
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightNormal), tempJointOrientationConjugate));
// Add to vertices normal and ake weight bias into account
tempVert.normal.x -= rotatedPoint.x * tempWeight.bias;
tempVert.normal.y -= rotatedPoint.y * tempWeight.bias;
tempVert.normal.z -= rotatedPoint.z * tempWeight.bias;
}
MD5Model.subsets[k].positions[i] = tempVert.pos; // Store the vertices position in the position vector instead of straight into the vertex vector
MD5Model.subsets[k].vertices[i].normal = tempVert.normal; // Store the vertices normal
XMStoreFloat3(&MD5Model.subsets[k].vertices[i].normal, XMVector3Normalize(XMLoadFloat3(&MD5Model.subsets[k].vertices[i].normal)));
}
// Put the positions into the vertices for this subset
for(int i = 0; i < MD5Model.subsets[k].vertices.size(); i++)
{
MD5Model.subsets[k].vertices[i].pos = MD5Model.subsets[k].positions[i];
}
// Update the subsets vertex buffer
// First lock the buffer
D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
hr = d3d11DevCon->Map(MD5Model.subsets[k].vertBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);
// Copy the data into the vertex buffer.
memcpy(mappedVertBuff.pData, &MD5Model.subsets[k].vertices[0], (sizeof(Vertex) * MD5Model.subsets[k].vertices.size()));
d3d11DevCon->Unmap(MD5Model.subsets[k].vertBuff, 0);
// The line below is another way to update a buffer. You will use this when you want to update a buffer less
// than once per frame, since the GPU reads will be faster (the buffer was created as a DEFAULT buffer instead
// of a DYNAMIC buffer), and the CPU writes will be slower. You can try both methods to find out which one is faster
// for you. if you want to use the line below, you will have to create the buffer with D3D11_USAGE_DEFAULT instead
// of D3D11_USAGE_DYNAMIC
//d3d11DevCon->UpdateSubresource( MD5Model.subsets[k].vertBuff, 0, NULL, &MD5Model.subsets[k].vertices[0], 0, 0 );
}
}
Which Frames to Use
최근 애니메이션 시간을 갱신하는것으로 함수를 시작합니다. 만약 최근 애니메이션 시간이 전체 애니메이션 시간 보다 크면 최근 애니메이션 시간을 0으로 하여 애니메이션을 재시작합니다. 그 다음 우리가 있는 프레임이 뭔지 알아냅니다. 최근 애니메이션 시간에 frameRate를 곱하는 것으로 할 수 있습니다. 이 결과 값을 가장 가까운 whole number(음이 아닌 정수)로 내림하여 현재 있는 프레임을 얻고 여기에 1을 더하여 다음 프레임을 구합니다. 아주 부드러운 애니메이션을 유지하도록 현재 프레임 뼈대를 구하기 위해 두 프레임을 보간할 것입니다. 이 두개를 보간할때 보간 요소가 필요한데 framerate와 곱한 현재 애니메이션 시간의 나머지를 구함으로 얻을 수 있습니다. (eg. currentFrameTime = 5.3667, currentFrame = 5, interpolationFactor = 0.3667).
void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation)
{
MD5Model.animations[animation].currAnimTime += deltaTime; // Update the current animation time
if(MD5Model.animations[animation].currAnimTime > MD5Model.animations[animation].totalAnimTime)
MD5Model.animations[animation].currAnimTime = 0.0f;
// Which frame are we on
float currentFrame = MD5Model.animations[animation].currAnimTime * MD5Model.animations[animation].frameRate;
int frame0 = floorf( currentFrame );
int frame1 = frame0 + 1;
// Make sure we don't go over the number of frames
if(frame0 == MD5Model.animations[animation].numFrames-1)
frame1 = 0;
float interpolation = currentFrame - frame0; // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor
std::vector<Joint> interpolatedSkeleton; // Create a frame skeleton to store the interpolated skeletons in
Computing the Interpolated Skeleton
이제 보간된 뼈대를 구할 수 있습니다. 구하는건 정말 상당히 쉽습니다. 여러분은 코드에서 우리가 관절의 보간된 위치를 구하는데 사용하는 방정식과 밑에 구면 선형 보간(Slerp)이라는 테크닉을 관절의 방향을 표현하는 두 사원수를 보간하기 위해 사용하는 것을 보실 수 있습니다. XMQuaternionSlerp()라는 함수로 보간 요소에 따른 두 사원수를 보간하도록 사용할 수 있습니다.
// Compute the interpolated skeleton
for( int i = 0; i < MD5Model.animations[animation].numJoints; i++)
{
Joint tempJoint;
Joint joint0 = MD5Model.animations[animation].frameSkeleton[frame0][i]; // Get the i'th joint of frame0's skeleton
Joint joint1 = MD5Model.animations[animation].frameSkeleton[frame1][i]; // Get the i'th joint of frame1's skeleton
tempJoint.parentID = joint0.parentID; // Set the tempJoints parent id
// Turn the two quaternions into XMVECTORs for easy computations
XMVECTOR joint0Orient = XMVectorSet(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w);
XMVECTOR joint1Orient = XMVectorSet(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w);
// Interpolate positions
tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x));
tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y));
tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z));
// Interpolate orientations using spherical interpolation (Slerp)
XMStoreFloat4(&tempJoint.orientation, XMQuaternionSlerp(joint0Orient, joint1Orient, interpolation));
interpolatedSkeleton.push_back(tempJoint); // Push the joint back into our interpolated skeleton
}
Calculate the Vertex Position and Normal
이제 모델의 각 subset을 돌며 또 각 정점에 대해 루프를 돌고 마지막으로 각 정점의 weight들에 대해 돕니다. 정점 위치를 계산하기 위해 저번 강좌에서 우리의 bind-pose 모델을 만들때 했던 것을 똑같이 합니다. 정점 위치를 계산한 다음 법선을 계산합니다. 먼저 weight의 법선을 구하는 것으로 법선을 계산할 수 있습니다. weight의 법선을 구하기 위해 관절 방향을 축으로 법선을 회전시킵니다. weight 법선을 구한 뒤 가중치와 곱하고 정점 법선에 더합니다.
for ( int k = 0; k < MD5Model.numSubsets; k++)
{
for ( int i = 0; i < MD5Model.subsets[k].vertices.size(); ++i )
{
Vertex tempVert = MD5Model.subsets[k].vertices[i];
tempVert.pos = XMFLOAT3(0, 0, 0); // Make sure the vertex's pos is cleared first
tempVert.normal = XMFLOAT3(0,0,0); // Clear vertices normal
// Sum up the joints and weights information to get vertex's position and normal
for ( int j = 0; j < tempVert.WeightCount; ++j )
{
Weight tempWeight = MD5Model.subsets[k].weights[tempVert.StartWeight + j];
Joint tempJoint = interpolatedSkeleton[tempWeight.jointID];
// Convert joint orientation and weight pos to vectors for easier computation
XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);
// We will need to use the conjugate of the joint orientation quaternion
XMVECTOR tempJointOrientationConjugate = XMQuaternionInverse(tempJointOrientation);
// Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
// We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
XMFLOAT3 rotatedPoint;
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));
// Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;
// Compute the normals for this frames skeleton using the weight normals from before
// We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate)
XMVECTOR tempWeightNormal = XMVectorSet(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f);
// Rotate the normal
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightNormal), tempJointOrientationConjugate));
// Add to vertices normal and ake weight bias into account
tempVert.normal.x -= rotatedPoint.x * tempWeight.bias;
tempVert.normal.y -= rotatedPoint.y * tempWeight.bias;
tempVert.normal.z -= rotatedPoint.z * tempWeight.bias;
}
MD5Model.subsets[k].positions[i] = tempVert.pos; // Store the vertices position in the position vector instead of straight into the vertex vector
MD5Model.subsets[k].vertices[i].normal = tempVert.normal; // Store the vertices normal
XMStoreFloat3(&MD5Model.subsets[k].vertices[i].normal, XMVector3Normalize(XMLoadFloat3(&MD5Model.subsets[k].vertices[i].normal)));
}
Updating the Buffer
이 함수에서 마지막으로 할 것은 정점 버퍼를 갱신하는 것입니다. 저번 강좌에서 여러분이 기억하실지 모르겠지만 동적 정점 버퍼를 만들었습니다. directx 에서 동적 버퍼를 갱신하기 위해 먼저 버퍼를 잠그고 원하는 내용을 버퍼에 복사합니다. map 함수는 버퍼에서 데이터의 시작을 가리키는 포인터로 D3D11_MAPPED_SUBRESOURCE의 멤버인 pData가 있는 채워진 D3D11_MAPPED_SUBRESOURCE 객체를 반환합니다. 포인터를 우리의 정점 배열을 버퍼에 카피하도록 사용할 수 있으며 그리고는 마지막으로 버퍼를 언매핑합니다. 주의할 점은 벡터(stl)를 버퍼에 카피할때 반드시 이 포인터를 사용해야 하는데 이 포인터가 여러분이 카피를 시작하기 원하는 곳을 첫번째 요소([0])로 가리키고 있습니다. 그 이유는 벡터의 시작을 직접적으로 가리키는 포인터가 벡터나 어떤 것을 나타내는 추가적인 것의 묶음을 가르킬 것이기 때문입니다.
코드에 참고로 두었지만 디폴트 버퍼를 사용한다면 UpdateSubresource 함수를 사용하여 버퍼를 갱신할 수 있습니다만 이런 류의 갱신은 정적 버퍼를 사용하는 것보다 훨씬 느려서 자주 사용되진 않습니다.
// Put the positions into the vertices for this subset
for(int i = 0; i < MD5Model.subsets[k].vertices.size(); i++)
{
MD5Model.subsets[k].vertices[i].pos = MD5Model.subsets[k].positions[i];
}
// Update the subsets vertex buffer
// First lock the buffer
D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
hr = d3d11DevCon->Map(MD5Model.subsets[k].vertBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);
// Copy the data into the vertex buffer.
memcpy(mappedVertBuff.pData, &MD5Model.subsets[k].vertices[0], (sizeof(Vertex) * MD5Model.subsets[k].vertices.size()));
d3d11DevCon->Unmap(MD5Model.subsets[k].vertBuff, 0);
// The line below is another way to update a buffer. You will use this when you want to update a buffer less
// than once per frame, since the GPU reads will be faster (the buffer was created as a DEFAULT buffer instead
// of a DYNAMIC buffer), and the CPU writes will be slower. You can try both methods to find out which one is faster
// for you. if you want to use the line below, you will have to create the buffer with D3D11_USAGE_DEFAULT instead
// of D3D11_USAGE_DYNAMIC
//d3d11DevCon->UpdateSubresource( MD5Model.subsets[k].vertBuff, 0, NULL, &MD5Model.subsets[k].vertices[0], 0, 0 );
}
}
Call the LoadMD5Model() Function
이제 남은 것은 우리의 모델 애니메이션을 로드하는 함수를 호출하는 것입니다!
if(!LoadMD5Anim(L"boy.md5anim", NewMD5Model))
return false;
최소한 여러분에게 유용한 정보였기를 바랍니다! 즐기세요!
Exercise:
1. 늘어난 모델들에 대해 로드된 애니메이션을 사용할 수 있도록 (같은 관절 hierarchy를 가지기 때문에) 구조체들을 갱신해보세요. 2. 모델 녀석이 한 자리에서만 뛰게하지말고 원을 그리거나 여러분의 패턴대로 달리게 해보세요! 3. 생각하는 것을 알려주세요! (질문?) 4. 짱 좋은 하루 보내세요! 최종 코드입니다. main.cpp
//Include and link appropriate libraries and headers//
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")
#pragma comment (lib, "D3D10_1.lib")
#pragma comment (lib, "DXGI.lib")
#pragma comment (lib, "D2D1.lib")
#pragma comment (lib, "dwrite.lib")
#pragma comment (lib, "dinput8.lib")
#pragma comment (lib, "dxguid.lib")
#include <windows.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
#include <xnamath.h>
#include <D3D10_1.h>
#include <DXGI.h>
#include <D2D1.h>
#include <sstream>
#include <dwrite.h>
#include <dinput.h>
#include <vector>
#include <fstream>
#include <istream>
//Global Declarations - Interfaces//
IDXGISwapChain* SwapChain;
ID3D11Device* d3d11Device;
ID3D11DeviceContext* d3d11DevCon;
ID3D11RenderTargetView* renderTargetView;
ID3D11DepthStencilView* depthStencilView;
ID3D11Texture2D* depthStencilBuffer;
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D11PixelShader* D2D_PS;
ID3D10Blob* D2D_PS_Buffer;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;
ID3D11Buffer* cbPerObjectBuffer;
ID3D11BlendState* d2dTransparency;
ID3D11RasterizerState* CCWcullMode;
ID3D11RasterizerState* CWcullMode;
ID3D11SamplerState* CubesTexSamplerState;
ID3D11Buffer* cbPerFrameBuffer;
ID3D10Device1 *d3d101Device;
IDXGIKeyedMutex *keyedMutex11;
IDXGIKeyedMutex *keyedMutex10;
ID2D1RenderTarget *D2DRenderTarget;
ID2D1SolidColorBrush *Brush;
ID3D11Texture2D *BackBuffer11;
ID3D11Texture2D *sharedTex11;
ID3D11Buffer *d2dVertBuffer;
ID3D11Buffer *d2dIndexBuffer;
ID3D11ShaderResourceView *d2dTexture;
IDWriteFactory *DWriteFactory;
IDWriteTextFormat *TextFormat;
IDirectInputDevice8* DIKeyboard;
IDirectInputDevice8* DIMouse;
ID3D11Buffer* sphereIndexBuffer;
ID3D11Buffer* sphereVertBuffer;
ID3D11VertexShader* SKYMAP_VS;
ID3D11PixelShader* SKYMAP_PS;
ID3D10Blob* SKYMAP_VS_Buffer;
ID3D10Blob* SKYMAP_PS_Buffer;
ID3D11ShaderResourceView* smrv;
ID3D11DepthStencilState* DSLessEqual;
ID3D11RasterizerState* RSCullNone;
ID3D11BlendState* Transparency;
//Mesh variables. Each loaded mesh will need its own set of these
ID3D11Buffer* meshVertBuff;
ID3D11Buffer* meshIndexBuff;
XMMATRIX meshWorld;
int meshSubsets = 0;
std::vector<int> meshSubsetIndexStart;
std::vector<int> meshSubsetTexture;
//Textures and material variables, used for all mesh's loaded
std::vector<ID3D11ShaderResourceView*> meshSRV;
std::vector<std::wstring> textureNameArray;
std::wstring printText;
//Global Declarations - Others//
LPCTSTR WndClassName = L"firstwindow";
HWND hwnd = NULL;
HRESULT hr;
int Width = 300;
int Height = 300;
DIMOUSESTATE mouseLastState;
LPDIRECTINPUT8 DirectInput;
float rotx = 0;
float rotz = 0;
float scaleX = 1.0f;
float scaleY = 1.0f;
XMMATRIX Rotationx;
XMMATRIX Rotationz;
XMMATRIX Rotationy;
XMMATRIX WVP;
XMMATRIX camView;
XMMATRIX camProjection;
XMMATRIX d2dWorld;
XMVECTOR camPosition;
XMVECTOR camTarget;
XMVECTOR camUp;
XMVECTOR DefaultForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f);
XMVECTOR DefaultRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f);
XMVECTOR camForward = XMVectorSet(0.0f,0.0f,1.0f, 0.0f);
XMVECTOR camRight = XMVectorSet(1.0f,0.0f,0.0f, 0.0f);
XMMATRIX camRotationMatrix;
float moveLeftRight = 0.0f;
float moveBackForward = 0.0f;
float camYaw = 0.0f;
float camPitch = 0.0f;
int NumSphereVertices;
int NumSphereFaces;
XMMATRIX sphereWorld;
XMMATRIX Rotation;
XMMATRIX Scale;
XMMATRIX Translation;
float rot = 0.01f;
double countsPerSecond = 0.0;
__int64 CounterStart = 0;
int frameCount = 0;
int fps = 0;
__int64 frameTimeOld = 0;
double frameTime;
//Function Prototypes//
bool InitializeDirect3d11App(HINSTANCE hInstance);
void CleanUp();
bool InitScene();
void DrawScene();
bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter);
void InitD2DScreenTexture();
void UpdateScene(double time);
void UpdateCamera();
void RenderText(std::wstring text, int inInt);
void StartTimer();
double GetTime();
double GetFrameTime();
bool InitializeWindow(HINSTANCE hInstance,
int ShowWnd,
int width, int height,
bool windowed);
int messageloop();
bool InitDirectInput(HINSTANCE hInstance);
void DetectInput(double time);
void CreateSphere(int LatLines, int LongLines);
LRESULT CALLBACK WndProc(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);
//Create effects constant buffer's structure//
struct cbPerObject
{
XMMATRIX WVP;
XMMATRIX World;
//These will be used for the pixel shader
XMFLOAT4 difColor;
BOOL hasTexture;
//Because of HLSL structure packing, we will use windows BOOL
//instead of bool because HLSL packs things into 4 bytes, and
//bool is only one byte, where BOOL is 4 bytes
BOOL hasNormMap;
};
cbPerObject cbPerObj;
//Create material structure
struct SurfaceMaterial
{
std::wstring matName;
XMFLOAT4 difColor;
int texArrayIndex;
int normMapTexArrayIndex;
bool hasNormMap;
bool hasTexture;
bool transparent;
};
std::vector<SurfaceMaterial> material;
//Define LoadObjModel function after we create surfaceMaterial structure
bool LoadObjModel(std::wstring filename, //.obj filename
ID3D11Buffer** vertBuff, //mesh vertex buffer
ID3D11Buffer** indexBuff, //mesh index buffer
std::vector<int>& subsetIndexStart, //start index of each subset
std::vector<int>& subsetMaterialArray, //index value of material for each subset
std::vector<SurfaceMaterial>& material, //vector of material structures
int& subsetCount, //Number of subsets in mesh
bool isRHCoordSys, //true if model was created in right hand coord system
bool computeNormals); //true to compute the normals, false to use the files normals
struct Light
{
Light()
{
ZeroMemory(this, sizeof(Light));
}
XMFLOAT3 pos;
float range;
XMFLOAT3 dir;
float cone;
XMFLOAT3 att;
float pad2;
XMFLOAT4 ambient;
XMFLOAT4 diffuse;
};
Light light;
struct cbPerFrame
{
Light light;
};
cbPerFrame constbuffPerFrame;
struct Vertex //Overloaded Vertex Structure
{
Vertex(){}
Vertex(float x, float y, float z,
float u, float v,
float nx, float ny, float nz,
float tx, float ty, float tz)
: pos(x,y,z), texCoord(u, v), normal(nx, ny, nz),
tangent(tx, ty, tz){}
XMFLOAT3 pos;
XMFLOAT2 texCoord;
XMFLOAT3 normal;
XMFLOAT3 tangent;
XMFLOAT3 biTangent;
// Will not be sent to shader
int StartWeight;
int WeightCount;
};
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0},
{ "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
UINT numElements = ARRAYSIZE(layout);
struct Joint
{
std::wstring name;
int parentID;
XMFLOAT3 pos;
XMFLOAT4 orientation;
};
///////////////**************new**************////////////////////
struct BoundingBox
{
XMFLOAT3 min;
XMFLOAT3 max;
};
struct FrameData
{
int frameID;
std::vector<float> frameData;
};
struct AnimJointInfo
{
std::wstring name;
int parentID;
int flags;
int startIndex;
};
struct ModelAnimation
{
int numFrames;
int numJoints;
int frameRate;
int numAnimatedComponents;
float frameTime;
float totalAnimTime;
float currAnimTime;
std::vector<AnimJointInfo> jointInfo;
std::vector<BoundingBox> frameBounds;
std::vector<Joint> baseFrameJoints;
std::vector<FrameData> frameData;
std::vector<std::vector<Joint>> frameSkeleton;
};
///////////////**************new**************////////////////////
struct Weight
{
int jointID;
float bias;
XMFLOAT3 pos;
///////////////**************new**************////////////////////
XMFLOAT3 normal;
///////////////**************new**************////////////////////
};
struct ModelSubset
{
int texArrayIndex;
int numTriangles;
std::vector<Vertex> vertices;
std::vector<XMFLOAT3> jointSpaceNormals;
std::vector<DWORD> indices;
std::vector<Weight> weights;
std::vector<XMFLOAT3> positions;
ID3D11Buffer* vertBuff;
ID3D11Buffer* indexBuff;
};
struct Model3D
{
int numSubsets;
int numJoints;
std::vector<Joint> joints;
std::vector<ModelSubset> subsets;
///////////////**************new**************////////////////////
std::vector<ModelAnimation> animations;
///////////////**************new**************////////////////////
};
XMMATRIX smilesWorld;
Model3D NewMD5Model;
//LoadMD5Model() function prototype
bool LoadMD5Model(std::wstring filename,
Model3D& MD5Model,
std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
std::vector<std::wstring> texFileNameArray);
///////////////**************new**************////////////////////
bool LoadMD5Anim(std::wstring filename, Model3D& MD5Model);
void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation);
///////////////**************new**************////////////////////
int WINAPI WinMain(HINSTANCE hInstance, //Main windows function
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd)
{
if(!InitializeWindow(hInstance, nShowCmd, Width, Height, true))
{
MessageBox(0, L"Window Initialization - Failed",
L"Error", MB_OK);
return 0;
}
if(!InitializeDirect3d11App(hInstance)) //Initialize Direct3D
{
MessageBox(0, L"Direct3D Initialization - Failed",
L"Error", MB_OK);
return 0;
}
if(!InitScene()) //Initialize our scene
{
MessageBox(0, L"Scene Initialization - Failed",
L"Error", MB_OK);
return 0;
}
if(!InitDirectInput(hInstance))
{
MessageBox(0, L"Direct Input Initialization - Failed",
L"Error", MB_OK);
return 0;
}
messageloop();
CleanUp();
return 0;
}
bool InitializeWindow(HINSTANCE hInstance,
int ShowWnd,
int width, int height,
bool windowed)
{
typedef struct _WNDCLASS {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS;
WNDCLASSEX wc;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = NULL;
wc.cbWndExtra = NULL;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = WndClassName;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Error registering class",
L"Error", MB_OK | MB_ICONERROR);
return 1;
}
hwnd = CreateWindowEx(
NULL,
WndClassName,
L"Lesson 4 - Begin Drawing",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
width, height,
NULL,
NULL,
hInstance,
NULL
);
if (!hwnd)
{
MessageBox(NULL, L"Error creating window",
L"Error", MB_OK | MB_ICONERROR);
return 1;
}
ShowWindow(hwnd, ShowWnd);
UpdateWindow(hwnd);
return true;
}
bool InitializeDirect3d11App(HINSTANCE hInstance)
{
//Describe our SwapChain Buffer
DXGI_MODE_DESC bufferDesc;
ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));
bufferDesc.Width = Width;
bufferDesc.Height = Height;
bufferDesc.RefreshRate.Numerator = 60;
bufferDesc.RefreshRate.Denominator = 1;
bufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
//Describe our SwapChain
DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));
swapChainDesc.BufferDesc = bufferDesc;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.OutputWindow = hwnd;
///////////////**************new**************////////////////////
swapChainDesc.Windowed = true;
///////////////**************new**************////////////////////
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
// Create DXGI factory to enumerate adapters///////////////////////////////////////////////////////////////////////////
IDXGIFactory1 *DXGIFactory;
HRESULT hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&DXGIFactory);
// Use the first adapter
IDXGIAdapter1 *Adapter;
hr = DXGIFactory->EnumAdapters1(0, &Adapter);
DXGIFactory->Release();
//Create our Direct3D 11 Device and SwapChain//////////////////////////////////////////////////////////////////////////
hr = D3D11CreateDeviceAndSwapChain(Adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
NULL, NULL, D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);
//Initialize Direct2D, Direct3D 10.1, DirectWrite
InitD2D_D3D101_DWrite(Adapter);
//Release the Adapter interface
Adapter->Release();
//Create our BackBuffer and Render Target
hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer11 );
hr = d3d11Device->CreateRenderTargetView( BackBuffer11, NULL, &renderTargetView );
//Describe our Depth/Stencil Buffer
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = Width;
depthStencilDesc.Height = Height;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
//Create the Depth/Stencil View
d3d11Device->CreateTexture2D(&depthStencilDesc, NULL, &depthStencilBuffer);
d3d11Device->CreateDepthStencilView(depthStencilBuffer, NULL, &depthStencilView);
return true;
}
bool InitD2D_D3D101_DWrite(IDXGIAdapter1 *Adapter)
{
//Create our Direc3D 10.1 Device///////////////////////////////////////////////////////////////////////////////////////
hr = D3D10CreateDevice1(Adapter, D3D10_DRIVER_TYPE_HARDWARE, NULL,D3D10_CREATE_DEVICE_BGRA_SUPPORT,
D3D10_FEATURE_LEVEL_9_3, D3D10_1_SDK_VERSION, &d3d101Device );
//Create Shared Texture that Direct3D 10.1 will render on//////////////////////////////////////////////////////////////
D3D11_TEXTURE2D_DESC sharedTexDesc;
ZeroMemory(&sharedTexDesc, sizeof(sharedTexDesc));
sharedTexDesc.Width = Width;
sharedTexDesc.Height = Height;
sharedTexDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
sharedTexDesc.MipLevels = 1;
sharedTexDesc.ArraySize = 1;
sharedTexDesc.SampleDesc.Count = 1;
sharedTexDesc.Usage = D3D11_USAGE_DEFAULT;
sharedTexDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
sharedTexDesc.MiscFlags = D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX;
hr = d3d11Device->CreateTexture2D(&sharedTexDesc, NULL, &sharedTex11);
// Get the keyed mutex for the shared texture (for D3D11)///////////////////////////////////////////////////////////////
hr = sharedTex11->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex11);
// Get the shared handle needed to open the shared texture in D3D10.1///////////////////////////////////////////////////
IDXGIResource *sharedResource10;
HANDLE sharedHandle10;
hr = sharedTex11->QueryInterface(__uuidof(IDXGIResource), (void**)&sharedResource10);
hr = sharedResource10->GetSharedHandle(&sharedHandle10);
sharedResource10->Release();
// Open the surface for the shared texture in D3D10.1///////////////////////////////////////////////////////////////////
IDXGISurface1 *sharedSurface10;
hr = d3d101Device->OpenSharedResource(sharedHandle10, __uuidof(IDXGISurface1), (void**)(&sharedSurface10));
hr = sharedSurface10->QueryInterface(__uuidof(IDXGIKeyedMutex), (void**)&keyedMutex10);
// Create D2D factory///////////////////////////////////////////////////////////////////////////////////////////////////
ID2D1Factory *D2DFactory;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), (void**)&D2DFactory);
D2D1_RENDER_TARGET_PROPERTIES renderTargetProperties;
ZeroMemory(&renderTargetProperties, sizeof(renderTargetProperties));
renderTargetProperties.type = D2D1_RENDER_TARGET_TYPE_HARDWARE;
renderTargetProperties.pixelFormat = D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED);
hr = D2DFactory->CreateDxgiSurfaceRenderTarget(sharedSurface10, &renderTargetProperties, &D2DRenderTarget);
sharedSurface10->Release();
D2DFactory->Release();
// Create a solid color brush to draw something with
hr = D2DRenderTarget->CreateSolidColorBrush(D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f), &Brush);
//DirectWrite///////////////////////////////////////////////////////////////////////////////////////////////////////////
hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&DWriteFactory));
hr = DWriteFactory->CreateTextFormat(
L"Script",
NULL,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
24.0f,
L"en-us",
&TextFormat
);
hr = TextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
hr = TextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
d3d101Device->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);
return true;
}
bool InitDirectInput(HINSTANCE hInstance)
{
hr = DirectInput8Create(hInstance,
DIRECTINPUT_VERSION,
IID_IDirectInput8,
(void**)&DirectInput,
NULL);
hr = DirectInput->CreateDevice(GUID_SysKeyboard,
&DIKeyboard,
NULL);
hr = DirectInput->CreateDevice(GUID_SysMouse,
&DIMouse,
NULL);
hr = DIKeyboard->SetDataFormat(&c_dfDIKeyboard);
hr = DIKeyboard->SetCooperativeLevel(hwnd, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);
hr = DIMouse->SetDataFormat(&c_dfDIMouse);
hr = DIMouse->SetCooperativeLevel(hwnd, DISCL_NONEXCLUSIVE | DISCL_NOWINKEY | DISCL_FOREGROUND);
return true;
}
void UpdateCamera()
{
camRotationMatrix = XMMatrixRotationRollPitchYaw(camPitch, camYaw, 0);
camTarget = XMVector3TransformCoord(DefaultForward, camRotationMatrix );
camTarget = XMVector3Normalize(camTarget);
XMMATRIX RotateYTempMatrix;
RotateYTempMatrix = XMMatrixRotationY(camYaw);
// Walk
//camRight = XMVector3TransformCoord(DefaultRight, RotateYTempMatrix);
//camUp = XMVector3TransformCoord(camUp, RotateYTempMatrix);
//camForward = XMVector3TransformCoord(DefaultForward, RotateYTempMatrix);
// Free Cam
camRight = XMVector3TransformCoord(DefaultRight, camRotationMatrix);
camForward = XMVector3TransformCoord(DefaultForward, camRotationMatrix);
camUp = XMVector3Cross(camForward, camRight);
camPosition += moveLeftRight*camRight;
camPosition += moveBackForward*camForward;
moveLeftRight = 0.0f;
moveBackForward = 0.0f;
camTarget = camPosition + camTarget;
camView = XMMatrixLookAtLH( camPosition, camTarget, camUp );
}
void DetectInput(double time)
{
DIMOUSESTATE mouseCurrState;
BYTE keyboardState[256];
DIKeyboard->Acquire();
DIMouse->Acquire();
DIMouse->GetDeviceState(sizeof(DIMOUSESTATE), &mouseCurrState);
DIKeyboard->GetDeviceState(sizeof(keyboardState),(LPVOID)&keyboardState);
if(keyboardState[DIK_ESCAPE] & 0x80)
PostMessage(hwnd, WM_DESTROY, 0, 0);
float speed = 10.0f * time;
if(keyboardState[DIK_A] & 0x80)
{
moveLeftRight -= speed;
}
if(keyboardState[DIK_D] & 0x80)
{
moveLeftRight += speed;
}
if(keyboardState[DIK_W] & 0x80)
{
moveBackForward += speed;
}
if(keyboardState[DIK_S] & 0x80)
{
moveBackForward -= speed;
}
///////////////**************new**************////////////////////
if(keyboardState[DIK_R] & 0X80)
{
float timeFactor = 1.0f; // You can speed up or slow down time by changing this
UpdateMD5Model(NewMD5Model, time*timeFactor, 0);
}
///////////////**************new**************////////////////////
if((mouseCurrState.lX != mouseLastState.lX) || (mouseCurrState.lY != mouseLastState.lY))
{
camYaw += mouseLastState.lX * 0.001f;
camPitch += mouseCurrState.lY * 0.001f;
mouseLastState = mouseCurrState;
}
UpdateCamera();
return;
}
void CleanUp()
{
SwapChain->SetFullscreenState(false, NULL);
PostMessage(hwnd, WM_DESTROY, 0, 0);
//Release the COM Objects we created
SwapChain->Release();
d3d11Device->Release();
d3d11DevCon->Release();
renderTargetView->Release();
VS->Release();
PS->Release();
VS_Buffer->Release();
PS_Buffer->Release();
vertLayout->Release();
depthStencilView->Release();
depthStencilBuffer->Release();
cbPerObjectBuffer->Release();
Transparency->Release();
CCWcullMode->Release();
CWcullMode->Release();
d3d101Device->Release();
keyedMutex11->Release();
keyedMutex10->Release();
D2DRenderTarget->Release();
Brush->Release();
BackBuffer11->Release();
sharedTex11->Release();
DWriteFactory->Release();
TextFormat->Release();
d2dTexture->Release();
cbPerFrameBuffer->Release();
DIKeyboard->Unacquire();
DIMouse->Unacquire();
DirectInput->Release();
sphereIndexBuffer->Release();
sphereVertBuffer->Release();
SKYMAP_VS->Release();
SKYMAP_PS->Release();
SKYMAP_VS_Buffer->Release();
SKYMAP_PS_Buffer->Release();
smrv->Release();
DSLessEqual->Release();
RSCullNone->Release();
meshVertBuff->Release();
meshIndexBuff->Release();
for(int i = 0; i < NewMD5Model.numSubsets; i++)
{
NewMD5Model.subsets[i].indexBuff->Release();
NewMD5Model.subsets[i].vertBuff->Release();
}
}
///////////////**************new**************////////////////////
bool LoadMD5Anim(std::wstring filename, Model3D& MD5Model)
{
ModelAnimation tempAnim; // Temp animation to later store in our model's animation array
std::wifstream fileIn (filename.c_str()); // Open file
std::wstring checkString; // Stores the next string from our file
if(fileIn) // Check if the file was opened
{
while(fileIn) // Loop until the end of the file is reached
{
fileIn >> checkString; // Get next string from file
if ( checkString == L"MD5Version" ) // Get MD5 version (this function supports version 10)
{
fileIn >> checkString;
/*MessageBox(0, checkString.c_str(), //display message
L"MD5Version", MB_OK);*/
}
else if ( checkString == L"commandline" )
{
std::getline(fileIn, checkString); // Ignore the rest of this line
}
else if ( checkString == L"numFrames" )
{
fileIn >> tempAnim.numFrames; // Store number of frames in this animation
}
else if ( checkString == L"numJoints" )
{
fileIn >> tempAnim.numJoints; // Store number of joints (must match .md5mesh)
}
else if ( checkString == L"frameRate" )
{
fileIn >> tempAnim.frameRate; // Store animation's frame rate (frames per second)
}
else if ( checkString == L"numAnimatedComponents" )
{
fileIn >> tempAnim.numAnimatedComponents; // Number of components in each frame section
}
else if ( checkString == L"hierarchy" )
{
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numJoints; i++) // Load in each joint
{
AnimJointInfo tempJoint;
fileIn >> tempJoint.name; // Get joints name
// Sometimes the names might contain spaces. If that is the case, we need to continue
// to read the name until we get to the closing " (quotation marks)
if(tempJoint.name[tempJoint.name.size()-1] != '"')
{
wchar_t checkChar;
bool jointNameFound = false;
while(!jointNameFound)
{
checkChar = fileIn.get();
if(checkChar == '"')
jointNameFound = true;
tempJoint.name += checkChar;
}
}
// Remove the quotation marks from joints name
tempJoint.name.erase(0, 1);
tempJoint.name.erase(tempJoint.name.size()-1, 1);
fileIn >> tempJoint.parentID; // Get joints parent ID
fileIn >> tempJoint.flags; // Get flags
fileIn >> tempJoint.startIndex; // Get joints start index
// Make sure the joint exists in the model, and the parent ID's match up
// because the bind pose (md5mesh) joint hierarchy and the animations (md5anim)
// joint hierarchy must match up
bool jointMatchFound = false;
for(int k = 0; k < MD5Model.numJoints; k++)
{
if(MD5Model.joints[k].name == tempJoint.name)
{
if(MD5Model.joints[k].parentID == tempJoint.parentID)
{
jointMatchFound = true;
tempAnim.jointInfo.push_back(tempJoint);
}
}
}
if(!jointMatchFound) // If the skeleton system does not match up, return false
return false; // You might want to add an error message here
std::getline(fileIn, checkString); // Skip rest of this line
}
}
else if ( checkString == L"bounds" ) // Load in the AABB for each animation
{
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numFrames; i++)
{
BoundingBox tempBB;
fileIn >> checkString; // Skip "("
fileIn >> tempBB.min.x >> tempBB.min.z >> tempBB.min.y;
fileIn >> checkString >> checkString; // Skip ") ("
fileIn >> tempBB.max.x >> tempBB.max.z >> tempBB.max.y;
fileIn >> checkString; // Skip ")"
tempAnim.frameBounds.push_back(tempBB);
}
}
else if ( checkString == L"baseframe" ) // This is the default position for the animation
{ // All frames will build their skeletons off this
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numJoints; i++)
{
Joint tempBFJ;
fileIn >> checkString; // Skip "("
fileIn >> tempBFJ.pos.x >> tempBFJ.pos.z >> tempBFJ.pos.y;
fileIn >> checkString >> checkString; // Skip ") ("
fileIn >> tempBFJ.orientation.x >> tempBFJ.orientation.z >> tempBFJ.orientation.y;
fileIn >> checkString; // Skip ")"
tempAnim.baseFrameJoints.push_back(tempBFJ);
}
}
else if ( checkString == L"frame" ) // Load in each frames skeleton (the parts of each joint that changed from the base frame)
{
FrameData tempFrame;
fileIn >> tempFrame.frameID; // Get the frame ID
fileIn >> checkString; // Skip opening bracket "{"
for(int i = 0; i < tempAnim.numAnimatedComponents; i++)
{
float tempData;
fileIn >> tempData; // Get the data
tempFrame.frameData.push_back(tempData);
}
tempAnim.frameData.push_back(tempFrame);
///*** build the frame skeleton ***///
std::vector<Joint> tempSkeleton;
for(int i = 0; i < tempAnim.jointInfo.size(); i++)
{
int k = 0; // Keep track of position in frameData array
// Start the frames joint with the base frame's joint
Joint tempFrameJoint = tempAnim.baseFrameJoints[i];
tempFrameJoint.parentID = tempAnim.jointInfo[i].parentID;
// Notice how I have been flipping y and z. this is because some modeling programs such as
// 3ds max (which is what I use) use a right handed coordinate system. Because of this, we
// need to flip the y and z axes. If your having problems loading some models, it's possible
// the model was created in a left hand coordinate system. in that case, just reflip all the
// y and z axes in our md5 mesh and anim loader.
if(tempAnim.jointInfo[i].flags & 1) // pos.x ( 000001 )
tempFrameJoint.pos.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 2) // pos.y ( 000010 )
tempFrameJoint.pos.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 4) // pos.z ( 000100 )
tempFrameJoint.pos.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 8) // orientation.x ( 001000 )
tempFrameJoint.orientation.x = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 16) // orientation.y ( 010000 )
tempFrameJoint.orientation.z = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
if(tempAnim.jointInfo[i].flags & 32) // orientation.z ( 100000 )
tempFrameJoint.orientation.y = tempFrame.frameData[tempAnim.jointInfo[i].startIndex + k++];
// Compute the quaternions w
float t = 1.0f - ( tempFrameJoint.orientation.x * tempFrameJoint.orientation.x )
- ( tempFrameJoint.orientation.y * tempFrameJoint.orientation.y )
- ( tempFrameJoint.orientation.z * tempFrameJoint.orientation.z );
if ( t < 0.0f )
{
tempFrameJoint.orientation.w = 0.0f;
}
else
{
tempFrameJoint.orientation.w = -sqrtf(t);
}
// Now, if the upper arm of your skeleton moves, you need to also move the lower part of your arm, and then the hands, and then finally the fingers (possibly weapon or tool too)
// This is where joint hierarchy comes in. We start at the top of the hierarchy, and move down to each joints child, rotating and translating them based on their parents rotation
// and translation. We can assume that by the time we get to the child, the parent has already been rotated and transformed based of it's parent. We can assume this because
// the child should never come before the parent in the files we loaded in.
if(tempFrameJoint.parentID >= 0)
{
Joint parentJoint = tempSkeleton[tempFrameJoint.parentID];
// Turn the XMFLOAT3 and 4's into vectors for easier computation
XMVECTOR parentJointOrientation = XMVectorSet(parentJoint.orientation.x, parentJoint.orientation.y, parentJoint.orientation.z, parentJoint.orientation.w);
XMVECTOR tempJointPos = XMVectorSet(tempFrameJoint.pos.x, tempFrameJoint.pos.y, tempFrameJoint.pos.z, 0.0f);
XMVECTOR parentOrientationConjugate = XMVectorSet(-parentJoint.orientation.x, -parentJoint.orientation.y, -parentJoint.orientation.z, parentJoint.orientation.w);
// Calculate current joints position relative to its parents position
XMFLOAT3 rotatedPos;
XMStoreFloat3(&rotatedPos, XMQuaternionMultiply(XMQuaternionMultiply(parentJointOrientation, tempJointPos), parentOrientationConjugate));
// Translate the joint to model space by adding the parent joint's pos to it
tempFrameJoint.pos.x = rotatedPos.x + parentJoint.pos.x;
tempFrameJoint.pos.y = rotatedPos.y + parentJoint.pos.y;
tempFrameJoint.pos.z = rotatedPos.z + parentJoint.pos.z;
// Currently the joint is oriented in its parent joints space, we now need to orient it in
// model space by multiplying the two orientations together (parentOrientation * childOrientation) <- In that order
XMVECTOR tempJointOrient = XMVectorSet(tempFrameJoint.orientation.x, tempFrameJoint.orientation.y, tempFrameJoint.orientation.z, tempFrameJoint.orientation.w);
tempJointOrient = XMQuaternionMultiply(parentJointOrientation, tempJointOrient);
// Normalize the orienation quaternion
tempJointOrient = XMQuaternionNormalize(tempJointOrient);
XMStoreFloat4(&tempFrameJoint.orientation, tempJointOrient);
}
// Store the joint into our temporary frame skeleton
tempSkeleton.push_back(tempFrameJoint);
}
// Push back our newly created frame skeleton into the animation's frameSkeleton array
tempAnim.frameSkeleton.push_back(tempSkeleton);
fileIn >> checkString; // Skip closing bracket "}"
}
}
// Calculate and store some usefull animation data
tempAnim.frameTime = 1.0f / tempAnim.frameRate; // Set the time per frame
tempAnim.totalAnimTime = tempAnim.numFrames * tempAnim.frameTime; // Set the total time the animation takes
tempAnim.currAnimTime = 0.0f; // Set the current time to zero
MD5Model.animations.push_back(tempAnim); // Push back the animation into our model object
}
else // If the file was not loaded
{
SwapChain->SetFullscreenState(false, NULL); // Make sure we are out of fullscreen
// create message
std::wstring message = L"Could not open: ";
message += filename;
MessageBox(0, message.c_str(), // display message
L"Error", MB_OK);
return false;
}
return true;
}
void UpdateMD5Model(Model3D& MD5Model, float deltaTime, int animation)
{
MD5Model.animations[animation].currAnimTime += deltaTime; // Update the current animation time
if(MD5Model.animations[animation].currAnimTime > MD5Model.animations[animation].totalAnimTime)
MD5Model.animations[animation].currAnimTime = 0.0f;
// Which frame are we on
float currentFrame = MD5Model.animations[animation].currAnimTime * MD5Model.animations[animation].frameRate;
int frame0 = floorf( currentFrame );
int frame1 = frame0 + 1;
// Make sure we don't go over the number of frames
if(frame0 == MD5Model.animations[animation].numFrames-1)
frame1 = 0;
float interpolation = currentFrame - frame0; // Get the remainder (in time) between frame0 and frame1 to use as interpolation factor
std::vector<Joint> interpolatedSkeleton; // Create a frame skeleton to store the interpolated skeletons in
// Compute the interpolated skeleton
for( int i = 0; i < MD5Model.animations[animation].numJoints; i++)
{
Joint tempJoint;
Joint joint0 = MD5Model.animations[animation].frameSkeleton[frame0][i]; // Get the i'th joint of frame0's skeleton
Joint joint1 = MD5Model.animations[animation].frameSkeleton[frame1][i]; // Get the i'th joint of frame1's skeleton
tempJoint.parentID = joint0.parentID; // Set the tempJoints parent id
// Turn the two quaternions into XMVECTORs for easy computations
XMVECTOR joint0Orient = XMVectorSet(joint0.orientation.x, joint0.orientation.y, joint0.orientation.z, joint0.orientation.w);
XMVECTOR joint1Orient = XMVectorSet(joint1.orientation.x, joint1.orientation.y, joint1.orientation.z, joint1.orientation.w);
// Interpolate positions
tempJoint.pos.x = joint0.pos.x + (interpolation * (joint1.pos.x - joint0.pos.x));
tempJoint.pos.y = joint0.pos.y + (interpolation * (joint1.pos.y - joint0.pos.y));
tempJoint.pos.z = joint0.pos.z + (interpolation * (joint1.pos.z - joint0.pos.z));
// Interpolate orientations using spherical interpolation (Slerp)
XMStoreFloat4(&tempJoint.orientation, XMQuaternionSlerp(joint0Orient, joint1Orient, interpolation));
interpolatedSkeleton.push_back(tempJoint); // Push the joint back into our interpolated skeleton
}
for ( int k = 0; k < MD5Model.numSubsets; k++)
{
for ( int i = 0; i < MD5Model.subsets[k].vertices.size(); ++i )
{
Vertex tempVert = MD5Model.subsets[k].vertices[i];
tempVert.pos = XMFLOAT3(0, 0, 0); // Make sure the vertex's pos is cleared first
tempVert.normal = XMFLOAT3(0,0,0); // Clear vertices normal
// Sum up the joints and weights information to get vertex's position and normal
for ( int j = 0; j < tempVert.WeightCount; ++j )
{
Weight tempWeight = MD5Model.subsets[k].weights[tempVert.StartWeight + j];
Joint tempJoint = interpolatedSkeleton[tempWeight.jointID];
// Convert joint orientation and weight pos to vectors for easier computation
XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);
// We will need to use the conjugate of the joint orientation quaternion
XMVECTOR tempJointOrientationConjugate = XMQuaternionInverse(tempJointOrientation);
// Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
// We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
XMFLOAT3 rotatedPoint;
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));
// Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;
// Compute the normals for this frames skeleton using the weight normals from before
// We can comput the normals the same way we compute the vertices position, only we don't have to translate them (just rotate)
XMVECTOR tempWeightNormal = XMVectorSet(tempWeight.normal.x, tempWeight.normal.y, tempWeight.normal.z, 0.0f);
// Rotate the normal
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightNormal), tempJointOrientationConjugate));
// Add to vertices normal and ake weight bias into account
tempVert.normal.x -= rotatedPoint.x * tempWeight.bias;
tempVert.normal.y -= rotatedPoint.y * tempWeight.bias;
tempVert.normal.z -= rotatedPoint.z * tempWeight.bias;
}
MD5Model.subsets[k].positions[i] = tempVert.pos; // Store the vertices position in the position vector instead of straight into the vertex vector
MD5Model.subsets[k].vertices[i].normal = tempVert.normal; // Store the vertices normal
XMStoreFloat3(&MD5Model.subsets[k].vertices[i].normal, XMVector3Normalize(XMLoadFloat3(&MD5Model.subsets[k].vertices[i].normal)));
}
// Put the positions into the vertices for this subset
for(int i = 0; i < MD5Model.subsets[k].vertices.size(); i++)
{
MD5Model.subsets[k].vertices[i].pos = MD5Model.subsets[k].positions[i];
}
// Update the subsets vertex buffer
// First lock the buffer
D3D11_MAPPED_SUBRESOURCE mappedVertBuff;
hr = d3d11DevCon->Map(MD5Model.subsets[k].vertBuff, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedVertBuff);
// Copy the data into the vertex buffer.
memcpy(mappedVertBuff.pData, &MD5Model.subsets[k].vertices[0], (sizeof(Vertex) * MD5Model.subsets[k].vertices.size()));
d3d11DevCon->Unmap(MD5Model.subsets[k].vertBuff, 0);
// The line below is another way to update a buffer. You will use this when you want to update a buffer less
// than once per frame, since the GPU reads will be faster (the buffer was created as a DEFAULT buffer instead
// of a DYNAMIC buffer), and the CPU writes will be slower. You can try both methods to find out which one is faster
// for you. if you want to use the line below, you will have to create the buffer with D3D11_USAGE_DEFAULT instead
// of D3D11_USAGE_DYNAMIC
//d3d11DevCon->UpdateSubresource( MD5Model.subsets[k].vertBuff, 0, NULL, &MD5Model.subsets[k].vertices[0], 0, 0 );
}
}
///////////////**************new**************////////////////////
bool LoadMD5Model(std::wstring filename,
Model3D& MD5Model,
std::vector<ID3D11ShaderResourceView*>& shaderResourceViewArray,
std::vector<std::wstring> texFileNameArray)
{
std::wifstream fileIn (filename.c_str()); // Open file
std::wstring checkString; // Stores the next string from our file
if(fileIn) // Check if the file was opened
{
while(fileIn) // Loop until the end of the file is reached
{
fileIn >> checkString; // Get next string from file
if(checkString == L"MD5Version") // Get MD5 version (this function supports version 10)
{
/*fileIn >> checkString;
MessageBox(0, checkString.c_str(), //display message
L"MD5Version", MB_OK);*/
}
else if ( checkString == L"commandline" )
{
std::getline(fileIn, checkString); // Ignore the rest of this line
}
else if ( checkString == L"numJoints" )
{
fileIn >> MD5Model.numJoints; // Store number of joints
}
else if ( checkString == L"numMeshes" )
{
fileIn >> MD5Model.numSubsets; // Store number of meshes or subsets which we will call them
}
else if ( checkString == L"joints" )
{
Joint tempJoint;
fileIn >> checkString; // Skip the "{"
for(int i = 0; i < MD5Model.numJoints; i++)
{
fileIn >> tempJoint.name; // Store joints name
// Sometimes the names might contain spaces. If that is the case, we need to continue
// to read the name until we get to the closing " (quotation marks)
if(tempJoint.name[tempJoint.name.size()-1] != '"')
{
wchar_t checkChar;
bool jointNameFound = false;
while(!jointNameFound)
{
checkChar = fileIn.get();
if(checkChar == '"')
jointNameFound = true;
tempJoint.name += checkChar;
}
}
fileIn >> tempJoint.parentID; // Store Parent joint's ID
fileIn >> checkString; // Skip the "("
// Store position of this joint (swap y and z axis if model was made in RH Coord Sys)
fileIn >> tempJoint.pos.x >> tempJoint.pos.z >> tempJoint.pos.y;
fileIn >> checkString >> checkString; // Skip the ")" and "("
// Store orientation of this joint
fileIn >> tempJoint.orientation.x >> tempJoint.orientation.z >> tempJoint.orientation.y;
// Remove the quotation marks from joints name
tempJoint.name.erase(0, 1);
tempJoint.name.erase(tempJoint.name.size()-1, 1);
// Compute the w axis of the quaternion (The MD5 model uses a 3D vector to describe the
// direction the bone is facing. However, we need to turn this into a quaternion, and the way
// quaternions work, is the xyz values describe the axis of rotation, while the w is a value
// between 0 and 1 which describes the angle of rotation)
float t = 1.0f - ( tempJoint.orientation.x * tempJoint.orientation.x )
- ( tempJoint.orientation.y * tempJoint.orientation.y )
- ( tempJoint.orientation.z * tempJoint.orientation.z );
if ( t < 0.0f )
{
tempJoint.orientation.w = 0.0f;
}
else
{
tempJoint.orientation.w = -sqrtf(t);
}
std::getline(fileIn, checkString); // Skip rest of this line
MD5Model.joints.push_back(tempJoint); // Store the joint into this models joint vector
}
fileIn >> checkString; // Skip the "}"
}
else if ( checkString == L"mesh")
{
ModelSubset subset;
int numVerts, numTris, numWeights;
fileIn >> checkString; // Skip the "{"
fileIn >> checkString;
while ( checkString != L"}" ) // Read until '}'
{
// In this lesson, for the sake of simplicity, we will assume a textures filename is givin here.
// Usually though, the name of a material (stored in a material library. Think back to the lesson on
// loading .obj files, where the material library was contained in the file .mtl) is givin. Let this
// be an exercise to load the material from a material library such as obj's .mtl file, instead of
// just the texture like we will do here.
if(checkString == L"shader") // Load the texture or material
{
std::wstring fileNamePath;
fileIn >> fileNamePath; // Get texture's filename
// Take spaces into account if filename or material name has a space in it
if(fileNamePath[fileNamePath.size()-1] != '"')
{
wchar_t checkChar;
bool fileNameFound = false;
while(!fileNameFound)
{
checkChar = fileIn.get();
if(checkChar == '"')
fileNameFound = true;
fileNamePath += checkChar;
}
}
// Remove the quotation marks from texture path
fileNamePath.erase(0, 1);
fileNamePath.erase(fileNamePath.size()-1, 1);
//check if this texture has already been loaded
bool alreadyLoaded = false;
for(int i = 0; i < texFileNameArray.size(); ++i)
{
if(fileNamePath == texFileNameArray[i])
{
alreadyLoaded = true;
subset.texArrayIndex = i;
}
}
//if the texture is not already loaded, load it now
if(!alreadyLoaded)
{
ID3D11ShaderResourceView* tempMeshSRV;
hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
NULL, NULL, &tempMeshSRV, NULL );
if(SUCCEEDED(hr))
{
texFileNameArray.push_back(fileNamePath.c_str());
subset.texArrayIndex = shaderResourceViewArray.size();
shaderResourceViewArray.push_back(tempMeshSRV);
}
else
{
MessageBox(0, fileNamePath.c_str(), //display message
L"Could Not Open:", MB_OK);
return false;
}
}
std::getline(fileIn, checkString); // Skip rest of this line
}
else if ( checkString == L"numverts")
{
fileIn >> numVerts; // Store number of vertices
std::getline(fileIn, checkString); // Skip rest of this line
for(int i = 0; i < numVerts; i++)
{
Vertex tempVert;
fileIn >> checkString // Skip "vert # ("
>> checkString
>> checkString;
fileIn >> tempVert.texCoord.x // Store tex coords
>> tempVert.texCoord.y;
fileIn >> checkString; // Skip ")"
fileIn >> tempVert.StartWeight; // Index of first weight this vert will be weighted to
fileIn >> tempVert.WeightCount; // Number of weights for this vertex
std::getline(fileIn, checkString); // Skip rest of this line
subset.vertices.push_back(tempVert); // Push back this vertex into subsets vertex vector
}
}
else if ( checkString == L"numtris")
{
fileIn >> numTris;
subset.numTriangles = numTris;
std::getline(fileIn, checkString); // Skip rest of this line
for(int i = 0; i < numTris; i++) // Loop through each triangle
{
DWORD tempIndex;
fileIn >> checkString; // Skip "tri"
fileIn >> checkString; // Skip tri counter
for(int k = 0; k < 3; k++) // Store the 3 indices
{
fileIn >> tempIndex;
subset.indices.push_back(tempIndex);
}
std::getline(fileIn, checkString); // Skip rest of this line
}
}
else if ( checkString == L"numweights")
{
fileIn >> numWeights;
std::getline(fileIn, checkString); // Skip rest of this line
for(int i = 0; i < numWeights; i++)
{
Weight tempWeight;
fileIn >> checkString >> checkString; // Skip "weight #"
fileIn >> tempWeight.jointID; // Store weight's joint ID
fileIn >> tempWeight.bias; // Store weight's influence over a vertex
fileIn >> checkString; // Skip "("
fileIn >> tempWeight.pos.x // Store weight's pos in joint's local space
>> tempWeight.pos.z
>> tempWeight.pos.y;
std::getline(fileIn, checkString); // Skip rest of this line
subset.weights.push_back(tempWeight); // Push back tempWeight into subsets Weight array
}
}
else
std::getline(fileIn, checkString); // Skip anything else
fileIn >> checkString; // Skip "}"
}
//*** find each vertex's position using the joints and weights ***//
for ( int i = 0; i < subset.vertices.size(); ++i )
{
Vertex tempVert = subset.vertices[i];
tempVert.pos = XMFLOAT3(0, 0, 0); // Make sure the vertex's pos is cleared first
// Sum up the joints and weights information to get vertex's position
for ( int j = 0; j < tempVert.WeightCount; ++j )
{
Weight tempWeight = subset.weights[tempVert.StartWeight + j];
Joint tempJoint = MD5Model.joints[tempWeight.jointID];
// Convert joint orientation and weight pos to vectors for easier computation
// When converting a 3d vector to a quaternion, you should put 0 for "w", and
// When converting a quaternion to a 3d vector, you can just ignore the "w"
XMVECTOR tempJointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
XMVECTOR tempWeightPos = XMVectorSet(tempWeight.pos.x, tempWeight.pos.y, tempWeight.pos.z, 0.0f);
// We will need to use the conjugate of the joint orientation quaternion
// To get the conjugate of a quaternion, all you have to do is inverse the x, y, and z
XMVECTOR tempJointOrientationConjugate = XMVectorSet(-tempJoint.orientation.x, -tempJoint.orientation.y, -tempJoint.orientation.z, tempJoint.orientation.w);
// Calculate vertex position (in joint space, eg. rotate the point around (0,0,0)) for this weight using the joint orientation quaternion and its conjugate
// We can rotate a point using a quaternion with the equation "rotatedPoint = quaternion * point * quaternionConjugate"
XMFLOAT3 rotatedPoint;
XMStoreFloat3(&rotatedPoint, XMQuaternionMultiply(XMQuaternionMultiply(tempJointOrientation, tempWeightPos), tempJointOrientationConjugate));
// Now move the verices position from joint space (0,0,0) to the joints position in world space, taking the weights bias into account
// The weight bias is used because multiple weights might have an effect on the vertices final position. Each weight is attached to one joint.
tempVert.pos.x += ( tempJoint.pos.x + rotatedPoint.x ) * tempWeight.bias;
tempVert.pos.y += ( tempJoint.pos.y + rotatedPoint.y ) * tempWeight.bias;
tempVert.pos.z += ( tempJoint.pos.z + rotatedPoint.z ) * tempWeight.bias;
// Basically what has happened above, is we have taken the weights position relative to the joints position
// we then rotate the weights position (so that the weight is actually being rotated around (0, 0, 0) in world space) using
// the quaternion describing the joints rotation. We have stored this rotated point in rotatedPoint, which we then add to
// the joints position (because we rotated the weight's position around (0,0,0) in world space, and now need to translate it
// so that it appears to have been rotated around the joints position). Finally we multiply the answer with the weights bias,
// or how much control the weight has over the final vertices position. All weight's bias effecting a single vertex's position
// must add up to 1.
}
subset.positions.push_back(tempVert.pos); // Store the vertices position in the position vector instead of straight into the vertex vector
// since we can use the positions vector for certain things like collision detection or picking
// without having to work with the entire vertex structure.
}
// Put the positions into the vertices for this subset
for(int i = 0; i < subset.vertices.size(); i++)
{
subset.vertices[i].pos = subset.positions[i];
}
//*** Calculate vertex normals using normal averaging ***///
std::vector<XMFLOAT3> tempNormal;
//normalized and unnormalized normals
XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);
//Used to get vectors (sides) from the position of the verts
float vecX, vecY, vecZ;
//Two edges of our triangle
XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
//Compute face normals
for(int i = 0; i < subset.numTriangles; ++i)
{
//Get the vector describing one edge of our triangle (edge 0,2)
vecX = subset.vertices[subset.indices[(i*3)]].pos.x - subset.vertices[subset.indices[(i*3)+2]].pos.x;
vecY = subset.vertices[subset.indices[(i*3)]].pos.y - subset.vertices[subset.indices[(i*3)+2]].pos.y;
vecZ = subset.vertices[subset.indices[(i*3)]].pos.z - subset.vertices[subset.indices[(i*3)+2]].pos.z;
edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our first edge
//Get the vector describing another edge of our triangle (edge 2,1)
vecX = subset.vertices[subset.indices[(i*3)+2]].pos.x - subset.vertices[subset.indices[(i*3)+1]].pos.x;
vecY = subset.vertices[subset.indices[(i*3)+2]].pos.y - subset.vertices[subset.indices[(i*3)+1]].pos.y;
vecZ = subset.vertices[subset.indices[(i*3)+2]].pos.z - subset.vertices[subset.indices[(i*3)+1]].pos.z;
edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our second edge
//Cross multiply the two edge vectors to get the un-normalized face normal
XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));
tempNormal.push_back(unnormalized);
}
//Compute vertex normals (normal Averaging)
XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
int facesUsing = 0;
float tX, tY, tZ; //temp axis variables
//Go through each vertex
for(int i = 0; i < subset.vertices.size(); ++i)
{
//Check which triangles use this vertex
for(int j = 0; j < subset.numTriangles; ++j)
{
if(subset.indices[j*3] == i ||
subset.indices[(j*3)+1] == i ||
subset.indices[(j*3)+2] == i)
{
tX = XMVectorGetX(normalSum) + tempNormal[j].x;
tY = XMVectorGetY(normalSum) + tempNormal[j].y;
tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;
normalSum = XMVectorSet(tX, tY, tZ, 0.0f); //If a face is using the vertex, add the unormalized face normal to the normalSum
facesUsing++;
}
}
//Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
normalSum = normalSum / facesUsing;
//Normalize the normalSum vector
normalSum = XMVector3Normalize(normalSum);
//Store the normal and tangent in our current vertex
subset.vertices[i].normal.x = -XMVectorGetX(normalSum);
subset.vertices[i].normal.y = -XMVectorGetY(normalSum);
subset.vertices[i].normal.z = -XMVectorGetZ(normalSum);
///////////////**************new**************////////////////////
// Create the joint space normal for easy normal calculations in animation
Vertex tempVert = subset.vertices[i]; // Get the current vertex
subset.jointSpaceNormals.push_back(XMFLOAT3(0,0,0)); // Push back a blank normal
XMVECTOR normal = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f); // Clear normal
for ( int k = 0; k < tempVert.WeightCount; k++) // Loop through each of the vertices weights
{
Joint tempJoint = MD5Model.joints[subset.weights[tempVert.StartWeight + k].jointID]; // Get the joints orientation
XMVECTOR jointOrientation = XMVectorSet(tempJoint.orientation.x, tempJoint.orientation.y, tempJoint.orientation.z, tempJoint.orientation.w);
// Calculate normal based off joints orientation (turn into joint space)
normal = XMQuaternionMultiply(XMQuaternionMultiply(XMQuaternionInverse(jointOrientation), normalSum), jointOrientation);
XMStoreFloat3(&subset.weights[tempVert.StartWeight + k].normal, XMVector3Normalize(normal)); // Store the normalized quaternion into our weights normal
}
///////////////**************new**************////////////////////
//Clear normalSum, facesUsing for next vertex
normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
facesUsing = 0;
}
// Create index buffer
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * subset.numTriangles * 3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = &subset.indices[0];
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &subset.indexBuff);
//Create Vertex Buffer
D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );
vertexBufferDesc.Usage = D3D11_USAGE_DYNAMIC; // We will be updating this buffer, so we must set as dynamic
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * subset.vertices.size();
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // Give CPU power to write to buffer
vertexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = &subset.vertices[0];
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &subset.vertBuff);
// Push back the temp subset into the models subset vector
MD5Model.subsets.push_back(subset);
}
}
}
else
{
SwapChain->SetFullscreenState(false, NULL); // Make sure we are out of fullscreen
// create message
std::wstring message = L"Could not open: ";
message += filename;
MessageBox(0, message.c_str(), // display message
L"Error", MB_OK);
return false;
}
return true;
}
bool LoadObjModel(std::wstring filename,
ID3D11Buffer** vertBuff,
ID3D11Buffer** indexBuff,
std::vector<int>& subsetIndexStart,
std::vector<int>& subsetMaterialArray,
std::vector<SurfaceMaterial>& material,
int& subsetCount,
bool isRHCoordSys,
bool computeNormals)
{
HRESULT hr = 0;
std::wifstream fileIn (filename.c_str()); //Open file
std::wstring meshMatLib; //String to hold our obj material library filename
//Arrays to store our model's information
std::vector<DWORD> indices;
std::vector<XMFLOAT3> vertPos;
std::vector<XMFLOAT3> vertNorm;
std::vector<XMFLOAT2> vertTexCoord;
std::vector<std::wstring> meshMaterials;
//Vertex definition indices
std::vector<int> vertPosIndex;
std::vector<int> vertNormIndex;
std::vector<int> vertTCIndex;
//Make sure we have a default if no tex coords or normals are defined
bool hasTexCoord = false;
bool hasNorm = false;
//Temp variables to store into vectors
std::wstring meshMaterialsTemp;
int vertPosIndexTemp;
int vertNormIndexTemp;
int vertTCIndexTemp;
wchar_t checkChar; //The variable we will use to store one char from file at a time
std::wstring face; //Holds the string containing our face vertices
int vIndex = 0; //Keep track of our vertex index count
int triangleCount = 0; //Total Triangles
int totalVerts = 0;
int meshTriangles = 0;
//Check to see if the file was opened
if (fileIn)
{
while(fileIn)
{
checkChar = fileIn.get(); //Get next char
switch (checkChar)
{
case '#':
checkChar = fileIn.get();
while(checkChar != '\n')
checkChar = fileIn.get();
break;
case 'v': //Get Vertex Descriptions
checkChar = fileIn.get();
if(checkChar == ' ') //v - vert position
{
float vz, vy, vx;
fileIn >> vx >> vy >> vz; //Store the next three types
if(isRHCoordSys) //If model is from an RH Coord System
vertPos.push_back(XMFLOAT3( vx, vy, vz * -1.0f)); //Invert the Z axis
else
vertPos.push_back(XMFLOAT3( vx, vy, vz));
}
if(checkChar == 't') //vt - vert tex coords
{
float vtcu, vtcv;
fileIn >> vtcu >> vtcv; //Store next two types
if(isRHCoordSys) //If model is from an RH Coord System
vertTexCoord.push_back(XMFLOAT2(vtcu, 1.0f-vtcv)); //Reverse the "v" axis
else
vertTexCoord.push_back(XMFLOAT2(vtcu, vtcv));
hasTexCoord = true; //We know the model uses texture coords
}
//Since we compute the normals later, we don't need to check for normals
//In the file, but i'll do it here anyway
if(checkChar == 'n') //vn - vert normal
{
float vnx, vny, vnz;
fileIn >> vnx >> vny >> vnz; //Store next three types
if(isRHCoordSys) //If model is from an RH Coord System
vertNorm.push_back(XMFLOAT3( vnx, vny, vnz * -1.0f )); //Invert the Z axis
else
vertNorm.push_back(XMFLOAT3( vnx, vny, vnz ));
hasNorm = true; //We know the model defines normals
}
break;
//New group (Subset)
case 'g': //g - defines a group
checkChar = fileIn.get();
if(checkChar == ' ')
{
subsetIndexStart.push_back(vIndex); //Start index for this subset
subsetCount++;
}
break;
//Get Face Index
case 'f': //f - defines the faces
checkChar = fileIn.get();
if(checkChar == ' ')
{
face = L"";
std::wstring VertDef; //Holds one vertex definition at a time
triangleCount = 0;
checkChar = fileIn.get();
while(checkChar != '\n')
{
face += checkChar; //Add the char to our face string
checkChar = fileIn.get(); //Get the next Character
if(checkChar == ' ') //If its a space...
triangleCount++; //Increase our triangle count
}
//Check for space at the end of our face string
if(face[face.length()-1] == ' ')
triangleCount--; //Each space adds to our triangle count
triangleCount -= 1; //Ever vertex in the face AFTER the first two are new faces
std::wstringstream ss(face);
if(face.length() > 0)
{
int firstVIndex, lastVIndex; //Holds the first and last vertice's index
for(int i = 0; i < 3; ++i) //First three vertices (first triangle)
{
ss >> VertDef; //Get vertex definition (vPos/vTexCoord/vNorm)
std::wstring vertPart;
int whichPart = 0; //(vPos, vTexCoord, or vNorm)
//Parse this string
for(int j = 0; j < VertDef.length(); ++j)
{
if(VertDef[j] != '/') //If there is no divider "/", add a char to our vertPart
vertPart += VertDef[j];
//If the current char is a divider "/", or its the last character in the string
if(VertDef[j] == '/' || j == VertDef.length()-1)
{
std::wistringstream wstringToInt(vertPart); //Used to convert wstring to int
if(whichPart == 0) //If vPos
{
wstringToInt >> vertPosIndexTemp;
vertPosIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1
//Check to see if the vert pos was the only thing specified
if(j == VertDef.length()-1)
{
vertNormIndexTemp = 0;
vertTCIndexTemp = 0;
}
}
else if(whichPart == 1) //If vTexCoord
{
if(vertPart != L"") //Check to see if there even is a tex coord
{
wstringToInt >> vertTCIndexTemp;
vertTCIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1
}
else //If there is no tex coord, make a default
vertTCIndexTemp = 0;
//If the cur. char is the second to last in the string, then
//there must be no normal, so set a default normal
if(j == VertDef.length()-1)
vertNormIndexTemp = 0;
}
else if(whichPart == 2) //If vNorm
{
std::wistringstream wstringToInt(vertPart);
wstringToInt >> vertNormIndexTemp;
vertNormIndexTemp -= 1; //subtract one since c++ arrays start with 0, and obj start with 1
}
vertPart = L""; //Get ready for next vertex part
whichPart++; //Move on to next vertex part
}
}
//Check to make sure there is at least one subset
if(subsetCount == 0)
{
subsetIndexStart.push_back(vIndex); //Start index for this subset
subsetCount++;
}
//Avoid duplicate vertices
bool vertAlreadyExists = false;
if(totalVerts >= 3) //Make sure we at least have one triangle to check
{
//Loop through all the vertices
for(int iCheck = 0; iCheck < totalVerts; ++iCheck)
{
//If the vertex position and texture coordinate in memory are the same
//As the vertex position and texture coordinate we just now got out
//of the obj file, we will set this faces vertex index to the vertex's
//index value in memory. This makes sure we don't create duplicate vertices
if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists)
{
if(vertTCIndexTemp == vertTCIndex[iCheck])
{
indices.push_back(iCheck); //Set index for this vertex
vertAlreadyExists = true; //If we've made it here, the vertex already exists
}
}
}
}
//If this vertex is not already in our vertex arrays, put it there
if(!vertAlreadyExists)
{
vertPosIndex.push_back(vertPosIndexTemp);
vertTCIndex.push_back(vertTCIndexTemp);
vertNormIndex.push_back(vertNormIndexTemp);
totalVerts++; //We created a new vertex
indices.push_back(totalVerts-1); //Set index for this vertex
}
//If this is the very first vertex in the face, we need to
//make sure the rest of the triangles use this vertex
if(i == 0)
{
firstVIndex = indices[vIndex]; //The first vertex index of this FACE
}
//If this was the last vertex in the first triangle, we will make sure
//the next triangle uses this one (eg. tri1(1,2,3) tri2(1,3,4) tri3(1,4,5))
if(i == 2)
{
lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE
}
vIndex++; //Increment index count
}
meshTriangles++; //One triangle down
//If there are more than three vertices in the face definition, we need to make sure
//we convert the face to triangles. We created our first triangle above, now we will
//create a new triangle for every new vertex in the face, using the very first vertex
//of the face, and the last vertex from the triangle before the current triangle
for(int l = 0; l < triangleCount-1; ++l) //Loop through the next vertices to create new triangles
{
//First vertex of this triangle (the very first vertex of the face too)
indices.push_back(firstVIndex); //Set index for this vertex
vIndex++;
//Second Vertex of this triangle (the last vertex used in the tri before this one)
indices.push_back(lastVIndex); //Set index for this vertex
vIndex++;
//Get the third vertex for this triangle
ss >> VertDef;
std::wstring vertPart;
int whichPart = 0;
//Parse this string (same as above)
for(int j = 0; j < VertDef.length(); ++j)
{
if(VertDef[j] != '/')
vertPart += VertDef[j];
if(VertDef[j] == '/' || j == VertDef.length()-1)
{
std::wistringstream wstringToInt(vertPart);
if(whichPart == 0)
{
wstringToInt >> vertPosIndexTemp;
vertPosIndexTemp -= 1;
//Check to see if the vert pos was the only thing specified
if(j == VertDef.length()-1)
{
vertTCIndexTemp = 0;
vertNormIndexTemp = 0;
}
}
else if(whichPart == 1)
{
if(vertPart != L"")
{
wstringToInt >> vertTCIndexTemp;
vertTCIndexTemp -= 1;
}
else
vertTCIndexTemp = 0;
if(j == VertDef.length()-1)
vertNormIndexTemp = 0;
}
else if(whichPart == 2)
{
std::wistringstream wstringToInt(vertPart);
wstringToInt >> vertNormIndexTemp;
vertNormIndexTemp -= 1;
}
vertPart = L"";
whichPart++;
}
}
//Check for duplicate vertices
bool vertAlreadyExists = false;
if(totalVerts >= 3) //Make sure we at least have one triangle to check
{
for(int iCheck = 0; iCheck < totalVerts; ++iCheck)
{
if(vertPosIndexTemp == vertPosIndex[iCheck] && !vertAlreadyExists)
{
if(vertTCIndexTemp == vertTCIndex[iCheck])
{
indices.push_back(iCheck); //Set index for this vertex
vertAlreadyExists = true; //If we've made it here, the vertex already exists
}
}
}
}
if(!vertAlreadyExists)
{
vertPosIndex.push_back(vertPosIndexTemp);
vertTCIndex.push_back(vertTCIndexTemp);
vertNormIndex.push_back(vertNormIndexTemp);
totalVerts++; //New vertex created, add to total verts
indices.push_back(totalVerts-1); //Set index for this vertex
}
//Set the second vertex for the next triangle to the last vertex we got
lastVIndex = indices[vIndex]; //The last vertex index of this TRIANGLE
meshTriangles++; //New triangle defined
vIndex++;
}
}
}
break;
case 'm': //mtllib - material library filename
checkChar = fileIn.get();
if(checkChar == 't')
{
checkChar = fileIn.get();
if(checkChar == 'l')
{
checkChar = fileIn.get();
if(checkChar == 'l')
{
checkChar = fileIn.get();
if(checkChar == 'i')
{
checkChar = fileIn.get();
if(checkChar == 'b')
{
checkChar = fileIn.get();
if(checkChar == ' ')
{
//Store the material libraries file name
fileIn >> meshMatLib;
}
}
}
}
}
}
break;
case 'u': //usemtl - which material to use
checkChar = fileIn.get();
if(checkChar == 's')
{
checkChar = fileIn.get();
if(checkChar == 'e')
{
checkChar = fileIn.get();
if(checkChar == 'm')
{
checkChar = fileIn.get();
if(checkChar == 't')
{
checkChar = fileIn.get();
if(checkChar == 'l')
{
checkChar = fileIn.get();
if(checkChar == ' ')
{
meshMaterialsTemp = L""; //Make sure this is cleared
fileIn >> meshMaterialsTemp; //Get next type (string)
meshMaterials.push_back(meshMaterialsTemp);
}
}
}
}
}
}
break;
default:
break;
}
}
}
else //If we could not open the file
{
SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen
//create message
std::wstring message = L"Could not open: ";
message += filename;
MessageBox(0, message.c_str(), //display message
L"Error", MB_OK);
return false;
}
subsetIndexStart.push_back(vIndex); //There won't be another index start after our last subset, so set it here
//sometimes "g" is defined at the very top of the file, then again before the first group of faces.
//This makes sure the first subset does not conatain "0" indices.
if(subsetIndexStart[1] == 0)
{
subsetIndexStart.erase(subsetIndexStart.begin()+1);
meshSubsets--;
}
//Make sure we have a default for the tex coord and normal
//if one or both are not specified
if(!hasNorm)
vertNorm.push_back(XMFLOAT3(0.0f, 0.0f, 0.0f));
if(!hasTexCoord)
vertTexCoord.push_back(XMFLOAT2(0.0f, 0.0f));
//Close the obj file, and open the mtl file
fileIn.close();
fileIn.open(meshMatLib.c_str());
std::wstring lastStringRead;
int matCount = material.size(); //total materials
//kdset - If our diffuse color was not set, we can use the ambient color (which is usually the same)
//If the diffuse color WAS set, then we don't need to set our diffuse color to ambient
bool kdset = false;
if (fileIn)
{
while(fileIn)
{
checkChar = fileIn.get(); //Get next char
switch (checkChar)
{
//Check for comment
case '#':
checkChar = fileIn.get();
while(checkChar != '\n')
checkChar = fileIn.get();
break;
//Set diffuse color
case 'K':
checkChar = fileIn.get();
if(checkChar == 'd') //Diffuse Color
{
checkChar = fileIn.get(); //remove space
fileIn >> material[matCount-1].difColor.x;
fileIn >> material[matCount-1].difColor.y;
fileIn >> material[matCount-1].difColor.z;
kdset = true;
}
//Ambient Color (We'll store it in diffuse if there isn't a diffuse already)
if(checkChar == 'a')
{
checkChar = fileIn.get(); //remove space
if(!kdset)
{
fileIn >> material[matCount-1].difColor.x;
fileIn >> material[matCount-1].difColor.y;
fileIn >> material[matCount-1].difColor.z;
}
}
break;
//Check for transparency
case 'T':
checkChar = fileIn.get();
if(checkChar == 'r')
{
checkChar = fileIn.get(); //remove space
float Transparency;
fileIn >> Transparency;
material[matCount-1].difColor.w = Transparency;
if(Transparency > 0.0f)
material[matCount-1].transparent = true;
}
break;
//Some obj files specify d for transparency
case 'd':
checkChar = fileIn.get();
if(checkChar == ' ')
{
float Transparency;
fileIn >> Transparency;
//'d' - 0 being most transparent, and 1 being opaque, opposite of Tr
Transparency = 1.0f - Transparency;
material[matCount-1].difColor.w = Transparency;
if(Transparency > 0.0f)
material[matCount-1].transparent = true;
}
break;
//Get the diffuse map (texture)
case 'm':
checkChar = fileIn.get();
if(checkChar == 'a')
{
checkChar = fileIn.get();
if(checkChar == 'p')
{
checkChar = fileIn.get();
if(checkChar == '_')
{
//map_Kd - Diffuse map
checkChar = fileIn.get();
if(checkChar == 'K')
{
checkChar = fileIn.get();
if(checkChar == 'd')
{
std::wstring fileNamePath;
fileIn.get(); //Remove whitespace between map_Kd and file
//Get the file path - We read the pathname char by char since
//pathnames can sometimes contain spaces, so we will read until
//we find the file extension
bool texFilePathEnd = false;
while(!texFilePathEnd)
{
checkChar = fileIn.get();
fileNamePath += checkChar;
if(checkChar == '.')
{
for(int i = 0; i < 3; ++i)
fileNamePath += fileIn.get();
texFilePathEnd = true;
}
}
//check if this texture has already been loaded
bool alreadyLoaded = false;
for(int i = 0; i < textureNameArray.size(); ++i)
{
if(fileNamePath == textureNameArray[i])
{
alreadyLoaded = true;
material[matCount-1].texArrayIndex = i;
material[matCount-1].hasTexture = true;
}
}
//if the texture is not already loaded, load it now
if(!alreadyLoaded)
{
ID3D11ShaderResourceView* tempMeshSRV;
hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
NULL, NULL, &tempMeshSRV, NULL );
if(SUCCEEDED(hr))
{
textureNameArray.push_back(fileNamePath.c_str());
material[matCount-1].texArrayIndex = meshSRV.size();
meshSRV.push_back(tempMeshSRV);
material[matCount-1].hasTexture = true;
}
}
}
}
//map_d - alpha map
else if(checkChar == 'd')
{
//Alpha maps are usually the same as the diffuse map
//So we will assume that for now by only enabling
//transparency for this material, as we will already
//be using the alpha channel in the diffuse map
material[matCount-1].transparent = true;
}
//map_bump - bump map (we're usinga normal map though)
else if(checkChar == 'b')
{
checkChar = fileIn.get();
if(checkChar == 'u')
{
checkChar = fileIn.get();
if(checkChar == 'm')
{
checkChar = fileIn.get();
if(checkChar == 'p')
{
std::wstring fileNamePath;
fileIn.get(); //Remove whitespace between map_bump and file
//Get the file path - We read the pathname char by char since
//pathnames can sometimes contain spaces, so we will read until
//we find the file extension
bool texFilePathEnd = false;
while(!texFilePathEnd)
{
checkChar = fileIn.get();
fileNamePath += checkChar;
if(checkChar == '.')
{
for(int i = 0; i < 3; ++i)
fileNamePath += fileIn.get();
texFilePathEnd = true;
}
}
//check if this texture has already been loaded
bool alreadyLoaded = false;
for(int i = 0; i < textureNameArray.size(); ++i)
{
if(fileNamePath == textureNameArray[i])
{
alreadyLoaded = true;
material[matCount-1].normMapTexArrayIndex = i;
material[matCount-1].hasNormMap = true;
}
}
//if the texture is not already loaded, load it now
if(!alreadyLoaded)
{
ID3D11ShaderResourceView* tempMeshSRV;
hr = D3DX11CreateShaderResourceViewFromFile( d3d11Device, fileNamePath.c_str(),
NULL, NULL, &tempMeshSRV, NULL );
if(SUCCEEDED(hr))
{
textureNameArray.push_back(fileNamePath.c_str());
material[matCount-1].normMapTexArrayIndex = meshSRV.size();
meshSRV.push_back(tempMeshSRV);
material[matCount-1].hasNormMap = true;
}
}
}
}
}
}
}
}
}
break;
case 'n': //newmtl - Declare new material
checkChar = fileIn.get();
if(checkChar == 'e')
{
checkChar = fileIn.get();
if(checkChar == 'w')
{
checkChar = fileIn.get();
if(checkChar == 'm')
{
checkChar = fileIn.get();
if(checkChar == 't')
{
checkChar = fileIn.get();
if(checkChar == 'l')
{
checkChar = fileIn.get();
if(checkChar == ' ')
{
//New material, set its defaults
SurfaceMaterial tempMat;
material.push_back(tempMat);
fileIn >> material[matCount].matName;
material[matCount].transparent = false;
material[matCount].hasTexture = false;
material[matCount].hasNormMap = false;
material[matCount].normMapTexArrayIndex = 0;
material[matCount].texArrayIndex = 0;
matCount++;
kdset = false;
}
}
}
}
}
}
break;
default:
break;
}
}
}
else
{
SwapChain->SetFullscreenState(false, NULL); //Make sure we are out of fullscreen
std::wstring message = L"Could not open: ";
message += meshMatLib;
MessageBox(0, message.c_str(),
L"Error", MB_OK);
return false;
}
//Set the subsets material to the index value
//of the its material in our material array
for(int i = 0; i < meshSubsets; ++i)
{
bool hasMat = false;
for(int j = 0; j < material.size(); ++j)
{
if(meshMaterials[i] == material[j].matName)
{
subsetMaterialArray.push_back(j);
hasMat = true;
}
}
if(!hasMat)
subsetMaterialArray.push_back(0); //Use first material in array
}
std::vector<Vertex> vertices;
Vertex tempVert;
//Create our vertices using the information we got
//from the file and store them in a vector
for(int j = 0 ; j < totalVerts; ++j)
{
tempVert.pos = vertPos[vertPosIndex[j]];
tempVert.normal = vertNorm[vertNormIndex[j]];
tempVert.texCoord = vertTexCoord[vertTCIndex[j]];
vertices.push_back(tempVert);
}
//////////////////////Compute Normals///////////////////////////
//If computeNormals was set to true then we will create our own
//normals, if it was set to false we will use the obj files normals
if(computeNormals)
{
std::vector<XMFLOAT3> tempNormal;
//normalized and unnormalized normals
XMFLOAT3 unnormalized = XMFLOAT3(0.0f, 0.0f, 0.0f);
//tangent stuff
std::vector<XMFLOAT3> tempTangent;
XMFLOAT3 tangent = XMFLOAT3(0.0f, 0.0f, 0.0f);
float tcU1, tcV1, tcU2, tcV2;
//Used to get vectors (sides) from the position of the verts
float vecX, vecY, vecZ;
//Two edges of our triangle
XMVECTOR edge1 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
XMVECTOR edge2 = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
//Compute face normals
//And Tangents
for(int i = 0; i < meshTriangles; ++i)
{
//Get the vector describing one edge of our triangle (edge 0,2)
vecX = vertices[indices[(i*3)]].pos.x - vertices[indices[(i*3)+2]].pos.x;
vecY = vertices[indices[(i*3)]].pos.y - vertices[indices[(i*3)+2]].pos.y;
vecZ = vertices[indices[(i*3)]].pos.z - vertices[indices[(i*3)+2]].pos.z;
edge1 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our first edge
//Get the vector describing another edge of our triangle (edge 2,1)
vecX = vertices[indices[(i*3)+2]].pos.x - vertices[indices[(i*3)+1]].pos.x;
vecY = vertices[indices[(i*3)+2]].pos.y - vertices[indices[(i*3)+1]].pos.y;
vecZ = vertices[indices[(i*3)+2]].pos.z - vertices[indices[(i*3)+1]].pos.z;
edge2 = XMVectorSet(vecX, vecY, vecZ, 0.0f); //Create our second edge
//Cross multiply the two edge vectors to get the un-normalized face normal
XMStoreFloat3(&unnormalized, XMVector3Cross(edge1, edge2));
tempNormal.push_back(unnormalized);
//Find first texture coordinate edge 2d vector
tcU1 = vertices[indices[(i*3)]].texCoord.x - vertices[indices[(i*3)+2]].texCoord.x;
tcV1 = vertices[indices[(i*3)]].texCoord.y - vertices[indices[(i*3)+2]].texCoord.y;
//Find second texture coordinate edge 2d vector
tcU2 = vertices[indices[(i*3)+2]].texCoord.x - vertices[indices[(i*3)+1]].texCoord.x;
tcV2 = vertices[indices[(i*3)+2]].texCoord.y - vertices[indices[(i*3)+1]].texCoord.y;
//Find tangent using both tex coord edges and position edges
tangent.x = (tcV1 * XMVectorGetX(edge1) - tcV2 * XMVectorGetX(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
tangent.y = (tcV1 * XMVectorGetY(edge1) - tcV2 * XMVectorGetY(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
tangent.z = (tcV1 * XMVectorGetZ(edge1) - tcV2 * XMVectorGetZ(edge2)) * (1.0f / (tcU1 * tcV2 - tcU2 * tcV1));
tempTangent.push_back(tangent);
}
//Compute vertex normals (normal Averaging)
XMVECTOR normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
XMVECTOR tangentSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
int facesUsing = 0;
float tX, tY, tZ; //temp axis variables
//Go through each vertex
for(int i = 0; i < totalVerts; ++i)
{
//Check which triangles use this vertex
for(int j = 0; j < meshTriangles; ++j)
{
if(indices[j*3] == i ||
indices[(j*3)+1] == i ||
indices[(j*3)+2] == i)
{
tX = XMVectorGetX(normalSum) + tempNormal[j].x;
tY = XMVectorGetY(normalSum) + tempNormal[j].y;
tZ = XMVectorGetZ(normalSum) + tempNormal[j].z;
normalSum = XMVectorSet(tX, tY, tZ, 0.0f); //If a face is using the vertex, add the unormalized face normal to the normalSum
//We can reuse tX, tY, tZ to sum up tangents
tX = XMVectorGetX(tangentSum) + tempTangent[j].x;
tY = XMVectorGetY(tangentSum) + tempTangent[j].y;
tZ = XMVectorGetZ(tangentSum) + tempTangent[j].z;
tangentSum = XMVectorSet(tX, tY, tZ, 0.0f); //sum up face tangents using this vertex
facesUsing++;
}
}
//Get the actual normal by dividing the normalSum by the number of faces sharing the vertex
normalSum = normalSum / facesUsing;
tangentSum = tangentSum / facesUsing;
//Normalize the normalSum vector and tangent
normalSum = XMVector3Normalize(normalSum);
tangentSum = XMVector3Normalize(tangentSum);
//Store the normal and tangent in our current vertex
vertices[i].normal.x = XMVectorGetX(normalSum);
vertices[i].normal.y = XMVectorGetY(normalSum);
vertices[i].normal.z = XMVectorGetZ(normalSum);
vertices[i].tangent.x = XMVectorGetX(tangentSum);
vertices[i].tangent.y = XMVectorGetY(tangentSum);
vertices[i].tangent.z = XMVectorGetZ(tangentSum);
//Clear normalSum, tangentSum and facesUsing for next vertex
normalSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
tangentSum = XMVectorSet(0.0f, 0.0f, 0.0f, 0.0f);
facesUsing = 0;
}
}
//Create index buffer
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * meshTriangles*3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = &indices[0];
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, indexBuff);
//Create Vertex Buffer
D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * totalVerts;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = &vertices[0];
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, vertBuff);
return true;
}
void CreateSphere(int LatLines, int LongLines)
{
NumSphereVertices = ((LatLines-2) * LongLines) + 2;
NumSphereFaces = ((LatLines-3)*(LongLines)*2) + (LongLines*2);
float sphereYaw = 0.0f;
float spherePitch = 0.0f;
std::vector<Vertex> vertices(NumSphereVertices);
XMVECTOR currVertPos = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f);
vertices[0].pos.x = 0.0f;
vertices[0].pos.y = 0.0f;
vertices[0].pos.z = 1.0f;
for(DWORD i = 0; i < LatLines-2; ++i)
{
spherePitch = (i+1) * (3.14f/(LatLines-1));
Rotationx = XMMatrixRotationX(spherePitch);
for(DWORD j = 0; j < LongLines; ++j)
{
sphereYaw = j * (6.28f/(LongLines));
Rotationy = XMMatrixRotationZ(sphereYaw);
currVertPos = XMVector3TransformNormal( XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f), (Rotationx * Rotationy) );
currVertPos = XMVector3Normalize( currVertPos );
vertices[i*LongLines+j+1].pos.x = XMVectorGetX(currVertPos);
vertices[i*LongLines+j+1].pos.y = XMVectorGetY(currVertPos);
vertices[i*LongLines+j+1].pos.z = XMVectorGetZ(currVertPos);
}
}
vertices[NumSphereVertices-1].pos.x = 0.0f;
vertices[NumSphereVertices-1].pos.y = 0.0f;
vertices[NumSphereVertices-1].pos.z = -1.0f;
D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * NumSphereVertices;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = &vertices[0];
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &sphereVertBuffer);
std::vector<DWORD> indices(NumSphereFaces * 3);
int k = 0;
for(DWORD l = 0; l < LongLines-1; ++l)
{
indices[k] = 0;
indices[k+1] = l+1;
indices[k+2] = l+2;
k += 3;
}
indices[k] = 0;
indices[k+1] = LongLines;
indices[k+2] = 1;
k += 3;
for(DWORD i = 0; i < LatLines-3; ++i)
{
for(DWORD j = 0; j < LongLines-1; ++j)
{
indices[k] = i*LongLines+j+1;
indices[k+1] = i*LongLines+j+2;
indices[k+2] = (i+1)*LongLines+j+1;
indices[k+3] = (i+1)*LongLines+j+1;
indices[k+4] = i*LongLines+j+2;
indices[k+5] = (i+1)*LongLines+j+2;
k += 6; // next quad
}
indices[k] = (i*LongLines)+LongLines;
indices[k+1] = (i*LongLines)+1;
indices[k+2] = ((i+1)*LongLines)+LongLines;
indices[k+3] = ((i+1)*LongLines)+LongLines;
indices[k+4] = (i*LongLines)+1;
indices[k+5] = ((i+1)*LongLines)+1;
k += 6;
}
for(DWORD l = 0; l < LongLines-1; ++l)
{
indices[k] = NumSphereVertices-1;
indices[k+1] = (NumSphereVertices-1)-(l+1);
indices[k+2] = (NumSphereVertices-1)-(l+2);
k += 3;
}
indices[k] = NumSphereVertices-1;
indices[k+1] = (NumSphereVertices-1)-LongLines;
indices[k+2] = NumSphereVertices-2;
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * NumSphereFaces * 3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = &indices[0];
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &sphereIndexBuffer);
}
void InitD2DScreenTexture()
{
//Create the vertex buffer
Vertex v[] =
{
// Front Face
Vertex(-1.0f, -1.0f, -1.0f, 0.0f, 1.0f,-1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
Vertex(-1.0f, 1.0f, -1.0f, 0.0f, 0.0f,-1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
Vertex( 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
Vertex( 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f),
};
DWORD indices[] = {
// Front Face
0, 1, 2,
0, 2, 3,
};
D3D11_BUFFER_DESC indexBufferDesc;
ZeroMemory( &indexBufferDesc, sizeof(indexBufferDesc) );
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
indexBufferDesc.ByteWidth = sizeof(DWORD) * 2 * 3;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA iinitData;
iinitData.pSysMem = indices;
d3d11Device->CreateBuffer(&indexBufferDesc, &iinitData, &d2dIndexBuffer);
D3D11_BUFFER_DESC vertexBufferDesc;
ZeroMemory( &vertexBufferDesc, sizeof(vertexBufferDesc) );
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
vertexBufferDesc.ByteWidth = sizeof( Vertex ) * 4;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA vertexBufferData;
ZeroMemory( &vertexBufferData, sizeof(vertexBufferData) );
vertexBufferData.pSysMem = v;
hr = d3d11Device->CreateBuffer( &vertexBufferDesc, &vertexBufferData, &d2dVertBuffer);
//Create A shader resource view from the texture D2D will render to,
//So we can use it to texture a square which overlays our scene
d3d11Device->CreateShaderResourceView(sharedTex11, NULL, &d2dTexture);
}
bool InitScene()
{
InitD2DScreenTexture();
CreateSphere(10, 10);
if(!LoadObjModel(L"ground.obj", &meshVertBuff, &meshIndexBuff, meshSubsetIndexStart, meshSubsetTexture, material, meshSubsets, true, true))
return false;
if(!LoadMD5Model(L"boy.md5mesh", NewMD5Model, meshSRV, textureNameArray))
return false;
///////////////**************new**************////////////////////
if(!LoadMD5Anim(L"boy.md5anim", NewMD5Model))
return false;
///////////////**************new**************////////////////////
//Compile Shaders from shader file
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "VS", "vs_4_0", 0, 0, 0, &VS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "PS", "ps_4_0", 0, 0, 0, &PS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "D2D_PS", "ps_4_0", 0, 0, 0, &D2D_PS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_VS", "vs_4_0", 0, 0, 0, &SKYMAP_VS_Buffer, 0, 0);
hr = D3DX11CompileFromFile(L"Effects.fx", 0, 0, "SKYMAP_PS", "ps_4_0", 0, 0, 0, &SKYMAP_PS_Buffer, 0, 0);
//Create the Shader Objects
hr = d3d11Device->CreateVertexShader(VS_Buffer->GetBufferPointer(), VS_Buffer->GetBufferSize(), NULL, &VS);
hr = d3d11Device->CreatePixelShader(PS_Buffer->GetBufferPointer(), PS_Buffer->GetBufferSize(), NULL, &PS);
hr = d3d11Device->CreatePixelShader(D2D_PS_Buffer->GetBufferPointer(), D2D_PS_Buffer->GetBufferSize(), NULL, &D2D_PS);
hr = d3d11Device->CreateVertexShader(SKYMAP_VS_Buffer->GetBufferPointer(), SKYMAP_VS_Buffer->GetBufferSize(), NULL, &SKYMAP_VS);
hr = d3d11Device->CreatePixelShader(SKYMAP_PS_Buffer->GetBufferPointer(), SKYMAP_PS_Buffer->GetBufferSize(), NULL, &SKYMAP_PS);
//Set Vertex and Pixel Shaders
d3d11DevCon->VSSetShader(VS, 0, 0);
d3d11DevCon->PSSetShader(PS, 0, 0);
light.pos = XMFLOAT3(0.0f, 7.0f, 0.0f);
light.dir = XMFLOAT3(-0.5f, 0.75f, -0.5f);
light.range = 1000.0f;
light.cone = 12.0f;
light.att = XMFLOAT3(0.4f, 0.02f, 0.000f);
light.ambient = XMFLOAT4(0.2f, 0.2f, 0.2f, 1.0f);
light.diffuse = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
//Create the Input Layout
hr = d3d11Device->CreateInputLayout( layout, numElements, VS_Buffer->GetBufferPointer(),
VS_Buffer->GetBufferSize(), &vertLayout );
//Set the Input Layout
d3d11DevCon->IASetInputLayout( vertLayout );
//Set Primitive Topology
d3d11DevCon->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
//Create the Viewport
D3D11_VIEWPORT viewport;
ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
viewport.TopLeftX = 0;
viewport.TopLeftY = 0;
viewport.Width = Width;
viewport.Height = Height;
viewport.MinDepth = 0.0f;
viewport.MaxDepth = 1.0f;
//Set the Viewport
d3d11DevCon->RSSetViewports(1, &viewport);
//Create the buffer to send to the cbuffer in effect file
D3D11_BUFFER_DESC cbbd;
ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));
cbbd.Usage = D3D11_USAGE_DEFAULT;
cbbd.ByteWidth = sizeof(cbPerObject);
cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbbd.CPUAccessFlags = 0;
cbbd.MiscFlags = 0;
hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerObjectBuffer);
//Create the buffer to send to the cbuffer per frame in effect file
ZeroMemory(&cbbd, sizeof(D3D11_BUFFER_DESC));
cbbd.Usage = D3D11_USAGE_DEFAULT;
cbbd.ByteWidth = sizeof(cbPerFrame);
cbbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbbd.CPUAccessFlags = 0;
cbbd.MiscFlags = 0;
hr = d3d11Device->CreateBuffer(&cbbd, NULL, &cbPerFrameBuffer);
//Camera information
camPosition = XMVectorSet( 0.0f, 5.0f, -8.0f, 0.0f );
camTarget = XMVectorSet( 0.0f, 0.5f, 0.0f, 0.0f );
camUp = XMVectorSet( 0.0f, 1.0f, 0.0f, 0.0f );
//Set the View matrix
camView = XMMatrixLookAtLH( camPosition, camTarget, camUp );
//Set the Projection matrix
camProjection = XMMatrixPerspectiveFovLH( 0.4f*3.14f, (float)Width/Height, 1.0f, 1000.0f);
D3D11_BLEND_DESC blendDesc;
ZeroMemory( &blendDesc, sizeof(blendDesc) );
D3D11_RENDER_TARGET_BLEND_DESC rtbd;
ZeroMemory( &rtbd, sizeof(rtbd) );
rtbd.BlendEnable = true;
rtbd.SrcBlend = D3D11_BLEND_SRC_COLOR;
rtbd.DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
rtbd.BlendOp = D3D11_BLEND_OP_ADD;
rtbd.SrcBlendAlpha = D3D11_BLEND_ONE;
rtbd.DestBlendAlpha = D3D11_BLEND_ZERO;
rtbd.BlendOpAlpha = D3D11_BLEND_OP_ADD;
rtbd.RenderTargetWriteMask = D3D10_COLOR_WRITE_ENABLE_ALL;
blendDesc.AlphaToCoverageEnable = false;
blendDesc.RenderTarget[0] = rtbd;
d3d11Device->CreateBlendState(&blendDesc, &d2dTransparency);
ZeroMemory( &rtbd, sizeof(rtbd) );
rtbd.BlendEnable = true;
rtbd.SrcBlend = D3D11_BLEND_INV_SRC_ALPHA;
rtbd.DestBlend = D3D11_BLEND_SRC_ALPHA;
rtbd.BlendOp = D3D11_BLEND_OP_ADD;
rtbd.SrcBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA;
rtbd.DestBlendAlpha = D3D11_BLEND_SRC_ALPHA;
rtbd.BlendOpAlpha = D3D11_BLEND_OP_ADD;
rtbd.RenderTargetWriteMask = D3D10_COLOR_WRITE_ENABLE_ALL;
blendDesc.AlphaToCoverageEnable = false;
blendDesc.RenderTarget[0] = rtbd;
d3d11Device->CreateBlendState(&blendDesc, &Transparency);
///Load Skymap's cube texture///
D3DX11_IMAGE_LOAD_INFO loadSMInfo;
loadSMInfo.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;
ID3D11Texture2D* SMTexture = 0;
hr = D3DX11CreateTextureFromFile(d3d11Device, L"skymap.dds",
&loadSMInfo, 0, (ID3D11Resource**)&SMTexture, 0);
D3D11_TEXTURE2D_DESC SMTextureDesc;
SMTexture->GetDesc(&SMTextureDesc);
D3D11_SHADER_RESOURCE_VIEW_DESC SMViewDesc;
SMViewDesc.Format = SMTextureDesc.Format;
SMViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
SMViewDesc.TextureCube.MipLevels = SMTextureDesc.MipLevels;
SMViewDesc.TextureCube.MostDetailedMip = 0;
hr = d3d11Device->CreateShaderResourceView(SMTexture, &SMViewDesc, &smrv);
// Describe the Sample State
D3D11_SAMPLER_DESC sampDesc;
ZeroMemory( &sampDesc, sizeof(sampDesc) );
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
//Create the Sample State
hr = d3d11Device->CreateSamplerState( &sampDesc, &CubesTexSamplerState );
D3D11_RASTERIZER_DESC cmdesc;
ZeroMemory(&cmdesc, sizeof(D3D11_RASTERIZER_DESC));
cmdesc.FillMode = D3D11_FILL_SOLID;
cmdesc.CullMode = D3D11_CULL_BACK;
cmdesc.FrontCounterClockwise = true;
hr = d3d11Device->CreateRasterizerState(&cmdesc, &CCWcullMode);
cmdesc.FrontCounterClockwise = false;
hr = d3d11Device->CreateRasterizerState(&cmdesc, &CWcullMode);
cmdesc.CullMode = D3D11_CULL_NONE;
//cmdesc.FillMode = D3D11_FILL_WIREFRAME;
hr = d3d11Device->CreateRasterizerState(&cmdesc, &RSCullNone);
D3D11_DEPTH_STENCIL_DESC dssDesc;
ZeroMemory(&dssDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));
dssDesc.DepthEnable = true;
dssDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
dssDesc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL;
d3d11Device->CreateDepthStencilState(&dssDesc, &DSLessEqual);
return true;
}
void StartTimer()
{
LARGE_INTEGER frequencyCount;
QueryPerformanceFrequency(&frequencyCount);
countsPerSecond = double(frequencyCount.QuadPart);
QueryPerformanceCounter(&frequencyCount);
CounterStart = frequencyCount.QuadPart;
}
double GetTime()
{
LARGE_INTEGER currentTime;
QueryPerformanceCounter(¤tTime);
return double(currentTime.QuadPart-CounterStart)/countsPerSecond;
}
double GetFrameTime()
{
LARGE_INTEGER currentTime;
__int64 tickCount;
QueryPerformanceCounter(¤tTime);
tickCount = currentTime.QuadPart-frameTimeOld;
frameTimeOld = currentTime.QuadPart;
if(tickCount < 0.0f)
tickCount = 0.0f;
return float(tickCount)/countsPerSecond;
}
void UpdateScene(double time)
{
//Reset sphereWorld
sphereWorld = XMMatrixIdentity();
//Define sphereWorld's world space matrix
Scale = XMMatrixScaling( 5.0f, 5.0f, 5.0f );
//Make sure the sphere is always centered around camera
Translation = XMMatrixTranslation( XMVectorGetX(camPosition), XMVectorGetY(camPosition), XMVectorGetZ(camPosition) );
//Set sphereWorld's world space using the transformations
sphereWorld = Scale * Translation;
//the loaded models world space
meshWorld = XMMatrixIdentity();
Rotation = XMMatrixRotationY(3.14f);
Scale = XMMatrixScaling( 1.0f, 1.0f, 1.0f );
Translation = XMMatrixTranslation( 0.0f, 0.0f, 0.0f );
meshWorld = Rotation * Scale * Translation;
Scale = XMMatrixScaling( 0.04f, 0.04f, 0.04f ); // The model is a bit too large for our scene, so make it smaller
Translation = XMMatrixTranslation( 0.0f, 3.0f, 0.0f);
smilesWorld = Scale * Translation;
}
void RenderText(std::wstring text, int inInt)
{
d3d11DevCon->PSSetShader(D2D_PS, 0, 0);
//Release the D3D 11 Device
keyedMutex11->ReleaseSync(0);
//Use D3D10.1 device
keyedMutex10->AcquireSync(0, 5);
//Draw D2D content
D2DRenderTarget->BeginDraw();
//Clear D2D Background
D2DRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f));
//Create our string
std::wostringstream printString;
printString << text << inInt;
printText = printString.str();
//Set the Font Color
D2D1_COLOR_F FontColor = D2D1::ColorF(1.0f, 1.0f, 1.0f, 1.0f);
//Set the brush color D2D will use to draw with
Brush->SetColor(FontColor);
//Create the D2D Render Area
D2D1_RECT_F layoutRect = D2D1::RectF(0, 0, Width, Height);
//Draw the Text
D2DRenderTarget->DrawText(
printText.c_str(),
wcslen(printText.c_str()),
TextFormat,
layoutRect,
Brush
);
D2DRenderTarget->EndDraw();
//Release the D3D10.1 Device
keyedMutex10->ReleaseSync(1);
//Use the D3D11 Device
keyedMutex11->AcquireSync(1, 5);
//Use the shader resource representing the direct2d render target
//to texture a square which is rendered in screen space so it
//overlays on top of our entire scene. We use alpha blending so
//that the entire background of the D2D render target is "invisible",
//And only the stuff we draw with D2D will be visible (the text)
//Set the blend state for D2D render target texture objects
d3d11DevCon->OMSetBlendState(d2dTransparency, NULL, 0xffffffff);
//Set the d2d Index buffer
d3d11DevCon->IASetIndexBuffer( d2dIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
//Set the d2d vertex buffer
UINT stride = sizeof( Vertex );
UINT offset = 0;
d3d11DevCon->IASetVertexBuffers( 0, 1, &d2dVertBuffer, &stride, &offset );
WVP = XMMatrixIdentity();
cbPerObj.WVP = XMMatrixTranspose(WVP);
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
d3d11DevCon->PSSetShaderResources( 0, 1, &d2dTexture );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
d3d11DevCon->RSSetState(CWcullMode);
d3d11DevCon->DrawIndexed( 6, 0, 0 );
}
void DrawScene()
{
//Clear our render target and depth/stencil view
float bgColor[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);
d3d11DevCon->ClearDepthStencilView(depthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0);
constbuffPerFrame.light = light;
d3d11DevCon->UpdateSubresource( cbPerFrameBuffer, 0, NULL, &constbuffPerFrame, 0, 0 );
d3d11DevCon->PSSetConstantBuffers(0, 1, &cbPerFrameBuffer);
//Set our Render Target
d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, depthStencilView );
//Set the default blend state (no blending) for opaque objects
d3d11DevCon->OMSetBlendState(0, 0, 0xffffffff);
//Set Vertex and Pixel Shaders
d3d11DevCon->VSSetShader(VS, 0, 0);
d3d11DevCon->PSSetShader(PS, 0, 0);
UINT stride = sizeof( Vertex );
UINT offset = 0;
///***Draw MD5 Model***///
for(int i = 0; i < NewMD5Model.numSubsets; i ++)
{
//Set the grounds index buffer
d3d11DevCon->IASetIndexBuffer( NewMD5Model.subsets[i].indexBuff, DXGI_FORMAT_R32_UINT, 0);
//Set the grounds vertex buffer
d3d11DevCon->IASetVertexBuffers( 0, 1, &NewMD5Model.subsets[i].vertBuff, &stride, &offset );
//Set the WVP matrix and send it to the constant buffer in effect file
WVP = smilesWorld * camView * camProjection;
cbPerObj.WVP = XMMatrixTranspose(WVP);
cbPerObj.World = XMMatrixTranspose(smilesWorld);
cbPerObj.hasTexture = true; // We'll assume all md5 subsets have textures
cbPerObj.hasNormMap = false; // We'll also assume md5 models have no normal map (easy to change later though)
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[NewMD5Model.subsets[i].texArrayIndex] );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
d3d11DevCon->RSSetState(RSCullNone);
d3d11DevCon->DrawIndexed( NewMD5Model.subsets[i].indices.size(), 0, 0 );
}
/////Draw our model's NON-transparent subsets/////
for(int i = 0; i < meshSubsets; ++i)
{
//Set the grounds index buffer
d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0);
//Set the grounds vertex buffer
d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset );
//Set the WVP matrix and send it to the constant buffer in effect file
WVP = meshWorld * camView * camProjection;
cbPerObj.WVP = XMMatrixTranspose(WVP);
cbPerObj.World = XMMatrixTranspose(meshWorld);
cbPerObj.difColor = material[meshSubsetTexture[i]].difColor;
cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture;
cbPerObj.hasNormMap = material[meshSubsetTexture[i]].hasNormMap;
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
if(material[meshSubsetTexture[i]].hasTexture)
d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] );
if(material[meshSubsetTexture[i]].hasNormMap)
d3d11DevCon->PSSetShaderResources( 1, 1, &meshSRV[material[meshSubsetTexture[i]].normMapTexArrayIndex] );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
d3d11DevCon->RSSetState(RSCullNone);
int indexStart = meshSubsetIndexStart[i];
int indexDrawAmount = meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i];
if(!material[meshSubsetTexture[i]].transparent)
d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 );
}
/////Draw the Sky's Sphere//////
//Set the spheres index buffer
d3d11DevCon->IASetIndexBuffer( sphereIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
//Set the spheres vertex buffer
d3d11DevCon->IASetVertexBuffers( 0, 1, &sphereVertBuffer, &stride, &offset );
//Set the WVP matrix and send it to the constant buffer in effect file
WVP = sphereWorld * camView * camProjection;
cbPerObj.WVP = XMMatrixTranspose(WVP);
cbPerObj.World = XMMatrixTranspose(sphereWorld);
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
//Send our skymap resource view to pixel shader
d3d11DevCon->PSSetShaderResources( 0, 1, &smrv );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
//Set the new VS and PS shaders
d3d11DevCon->VSSetShader(SKYMAP_VS, 0, 0);
d3d11DevCon->PSSetShader(SKYMAP_PS, 0, 0);
//Set the new depth/stencil and RS states
d3d11DevCon->OMSetDepthStencilState(DSLessEqual, 0);
d3d11DevCon->RSSetState(RSCullNone);
d3d11DevCon->DrawIndexed( NumSphereFaces * 3, 0, 0 );
//Set the default VS, PS shaders and depth/stencil state
d3d11DevCon->VSSetShader(VS, 0, 0);
d3d11DevCon->PSSetShader(PS, 0, 0);
d3d11DevCon->OMSetDepthStencilState(NULL, 0);
/////Draw our model's TRANSPARENT subsets now/////
//Set our blend state
d3d11DevCon->OMSetBlendState(Transparency, NULL, 0xffffffff);
for(int i = 0; i < meshSubsets; ++i)
{
//Set the grounds index buffer
d3d11DevCon->IASetIndexBuffer( meshIndexBuff, DXGI_FORMAT_R32_UINT, 0);
//Set the grounds vertex buffer
d3d11DevCon->IASetVertexBuffers( 0, 1, &meshVertBuff, &stride, &offset );
//Set the WVP matrix and send it to the constant buffer in effect file
WVP = meshWorld * camView * camProjection;
cbPerObj.WVP = XMMatrixTranspose(WVP);
cbPerObj.World = XMMatrixTranspose(meshWorld);
cbPerObj.difColor = material[meshSubsetTexture[i]].difColor;
cbPerObj.hasTexture = material[meshSubsetTexture[i]].hasTexture;
cbPerObj.hasNormMap = material[meshSubsetTexture[i]].hasNormMap;
d3d11DevCon->UpdateSubresource( cbPerObjectBuffer, 0, NULL, &cbPerObj, 0, 0 );
d3d11DevCon->VSSetConstantBuffers( 0, 1, &cbPerObjectBuffer );
d3d11DevCon->PSSetConstantBuffers( 1, 1, &cbPerObjectBuffer );
if(material[meshSubsetTexture[i]].hasTexture)
d3d11DevCon->PSSetShaderResources( 0, 1, &meshSRV[material[meshSubsetTexture[i]].texArrayIndex] );
if(material[meshSubsetTexture[i]].hasNormMap)
d3d11DevCon->PSSetShaderResources( 1, 1, &meshSRV[material[meshSubsetTexture[i]].normMapTexArrayIndex] );
d3d11DevCon->PSSetSamplers( 0, 1, &CubesTexSamplerState );
d3d11DevCon->RSSetState(RSCullNone);
int indexStart = meshSubsetIndexStart[i];
int indexDrawAmount = meshSubsetIndexStart[i+1] - meshSubsetIndexStart[i];
if(material[meshSubsetTexture[i]].transparent)
d3d11DevCon->DrawIndexed( indexDrawAmount, indexStart, 0 );
}
RenderText(L"FPS: ", fps);
//Present the backbuffer to the screen
SwapChain->Present(0, 0);
}
int messageloop(){
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
while(true)
{
BOOL PeekMessageL(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else{
// run game code
frameCount++;
if(GetTime() > 1.0f)
{
fps = frameCount;
frameCount = 0;
StartTimer();
}
frameTime = GetFrameTime();
DetectInput(frameTime);
UpdateScene(frameTime);
DrawScene();
}
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
switch( msg )
{
case WM_KEYDOWN:
if( wParam == VK_ESCAPE ){
DestroyWindow(hwnd);
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd,
msg,
wParam,
lParam);
}
struct Light
{
float3 pos;
float range;
float3 dir;
float cone;
float3 att;
float4 ambient;
float4 diffuse;
};
cbuffer cbPerFrame
{
Light light;
};
cbuffer cbPerObject
{
float4x4 WVP;
float4x4 World;
float4 difColor;
bool hasTexture;
bool hasNormMap;
};
Texture2D ObjTexture;
Texture2D ObjNormMap;
SamplerState ObjSamplerState;
TextureCube SkyMap;
struct VS_OUTPUT
{
float4 Pos : SV_POSITION;
float4 worldPos : POSITION;
float2 TexCoord : TEXCOORD;
float3 normal : NORMAL;
float3 tangent : TANGENT;
};
struct SKYMAP_VS_OUTPUT //output structure for skymap vertex shader
{
float4 Pos : SV_POSITION;
float3 texCoord : TEXCOORD;
};
VS_OUTPUT VS(float4 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL, float3 tangent : TANGENT)
{
VS_OUTPUT output;
output.Pos = mul(inPos, WVP);
output.worldPos = mul(inPos, World);
output.normal = mul(normal, World);
output.tangent = mul(tangent, World);
output.TexCoord = inTexCoord;
return output;
}
SKYMAP_VS_OUTPUT SKYMAP_VS(float3 inPos : POSITION, float2 inTexCoord : TEXCOORD, float3 normal : NORMAL, float3 tangent : TANGENT)
{
SKYMAP_VS_OUTPUT output = (SKYMAP_VS_OUTPUT)0;
//Set Pos to xyww instead of xyzw, so that z will always be 1 (furthest from camera)
output.Pos = mul(float4(inPos, 1.0f), WVP).xyww;
output.texCoord = inPos;
return output;
}
float4 PS(VS_OUTPUT input) : SV_TARGET
{
input.normal = normalize(input.normal);
//Set diffuse color of material
float4 diffuse = difColor;
//If material has a diffuse texture map, set it now
if(hasTexture == true)
diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord );
//If material has a normal map, we can set it now
if(hasNormMap == true)
{
//Load normal from normal map
float4 normalMap = ObjNormMap.Sample( ObjSamplerState, input.TexCoord );
//Change normal map range from [0, 1] to [-1, 1]
normalMap = (2.0f*normalMap) - 1.0f;
//Make sure tangent is completely orthogonal to normal
input.tangent = normalize(input.tangent - dot(input.tangent, input.normal)*input.normal);
//Create the biTangent
float3 biTangent = cross(input.normal, input.tangent);
//Create the "Texture Space"
float3x3 texSpace = float3x3(input.tangent, biTangent, input.normal);
//Convert normal from normal map to texture space and store in input.normal
input.normal = normalize(mul(normalMap, texSpace));
}
float3 finalColor;
finalColor = diffuse * light.ambient;
finalColor += saturate(dot(light.dir, input.normal) * light.diffuse * diffuse);
return float4(finalColor, diffuse.a);
}
float4 SKYMAP_PS(SKYMAP_VS_OUTPUT input) : SV_Target
{
return SkyMap.Sample(ObjSamplerState, input.texCoord);
}
float4 D2D_PS(VS_OUTPUT input) : SV_TARGET
{
float4 diffuse = ObjTexture.Sample( ObjSamplerState, input.TexCoord );
return diffuse;
}
'프로그래밍 > directx' 카테고리의 다른 글
DirectX 11 Tutorials - 12 : 폰트 엔진 (0) | 2016.06.15 |
---|---|
DirectX 11 Tutorials - 11 : 2D 렌더링 (0) | 2016.06.12 |
DirectX 11 Tutorials - MD5 모델 로딩 (0) | 2016.05.28 |
DirectX 11 Tutorials - 10 : 반사 조명 (0) | 2016.05.14 |
DirectX 11 Tutorials - 9 : 주변 조명 (0) | 2016.05.09 |