Three js 에서 애니메이션 재생시 애니메이션 한개마다 FBX 파일을 로드, 처리하는건 불필요하다고 판단되어


3D Max에서 JD 파일로 모델을 애니메이션 정보와 함께 출력했다.


JD 파일을 이용해서 씬에 출력 및 애니메이션을 재생해보자.


JDLoader.min.js


JDLoader 파일을 먼저 index에 추가, 로드 시킨다. (검색해도 찾을수 있음)


jd파일을 json로드한뒤 parse 하면 다양한 정보들이 담겨서 나오는데 해당 데이터를 담아보자.



static jdModel( data )
{
let meshes = [];
let mixers = [];
let actions = [];

for( let i = 0; i < data.objects.length; ++i )
{
if( data.objects[i].type == "Mesh" || data.objects[i].type == "SkinnedMesh" )
{
let mesh = null;
let matArray = this.createMaterials( data );
if ( data.objects[i].type == "SkinnedMesh")
{
mesh = new THREE.SkinnedMesh( data.objects[i].geometry, matArray );
}
else
{
mesh = new THREE.Mesh( data.objects[i].geometry, matArray );
}
meshes.push( mesh );

if( mesh && mesh.geometry.animations )
{
for(let j = 0, length = mesh.geometry.animations.length; j < length; j++)
{
let mixer = new THREE.AnimationMixer( mesh );
mixers.push( mixer );
let action = mixer.clipAction( mesh.geometry.animations[j] );
action.play();
actions.push( action );
}
}
}
else if ( data.objects[i].type == "Line" )
{
let jd_color = data.objects[i].jd_object.color;
let color1 = new THREE.Color( jd_color[0] / 255, jd_color[1] / 255, jd_color[2] / 255 );
let material = new THREE.LineBasicMaterial({ color: color1 }); //{ color: new THREE.Color( 0xff0000 ) }
let line = new THREE.Line( data.objects[i].geometry, material );
// scene.add( line );

if( line.geometry.animations )
{
for( let j = 0, length = mesh.geometry.animations.length; j < length; j++ )
{
let mixer = new THREE.AnimationMixer( line );
mixers.push( mixer );
let action = mixer.clipAction( line.geometry.animations[j] );
action.play();
actions.push( actions );
}
}
}
}

let object = {};
object.meshes = meshes;
object.mixers = mixers;
object.actions = actions;
return object;
}

static createMaterials( data )
{
let matArray = [];
for( let i = 0; i < data.materials.length; ++i )
{
let mat = new THREE.MeshPhongMaterial({});
mat.copy( data.materials[i] );
matArray.push( mat );
}

return matArray;
}

Static 함수로 처리했다.


기존의 JDModel 로드 방법에 일부 스크립트가 추가되었는데

반환되는 object내부에

meshes , mixers, actions가 담겨서 나오게 된다.


이 함수를 통해 오브젝트를 받은뒤

meshes에 들어있는 모든 오브젝트를 Scene에 등록해준다.


this.object = new THREE.Group();

let model = StaticMethodCall.jdModel( data );

for(let i = 0, length = model.meshes.length; i < length; i++)
this.object.add( model.meshes[i] );

Group을 하나 만든뒤 씬에 등록하고 그 자식으로 넣어주는 방법이 편하다.


mixer는 애니메이션을 담당하므로 update함수에서 지속적으로 호출한다.

this.mixers[0].update( delta );

애니메이션을 재생, 정지 하고 싶을때는 Actions를 이용한다.

this.actions[1].stop();
this.actions[0].play();


여러 책들은 번거롭게 일일히 점을 입력해서 배열방식으로 삽입하는데

이 글에서는 이미 벡터에 점과 나머지 정보가 담겨있는 상태에서 메쉬에 집어 넣어보자.


시작하기전 LPD3DXMESH == ID3DXMesh* 이다. 이는 정의에서도 확인할 수 있다.


1. 모든 점(Point)들은 std::vector<ST_PNT_VERTEX> vecTotalVertex에 담겨있다.

->ST_PNT_VERTEX는 구조체로 D3DXVECTOR3 p (점), D3DXVECTOR3 n (법선벡터), D3DXVECTOR2 t (텍스쳐)의 정보를 가지고 있다.

ST_PNT_VERTEX::FVF == D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 이다.

이와 비슷한 구조체를 여러분들은 이미 만들어서 사용중일거다.

ST_PNT_VERTEX대신에 여러분들이 사용중인 구조체의 이름을 적어주면 된다.

2. 모든 인덱스(Index)는 std::vector<WORD> vecIndex에 담겨있다.

3. 모든 속성(Attribute)은 std::vector<DWORD> vecAttribute에 담겨있다.


자 그럼 이제 MESH를 생성해보자.


LPD3DXMESH pMesh = NULL;


D3DXCreateMeshFVF(vecTotalVertex.size() / 3,    Mesh가 가지고 있는 모든 면의 개수, 현재 여기서는 삼각형으로 모든 면이 구현되기 때문에 이렇게 계산

vecTotalVertex.size(),        Mesh가 가지고 있는 모든 점의 개수

D3DXMESH_MANAGED,    생성 옵션, 관리 메모리 풀 내에 보관되도록 설정했다.

ST_PNT_VERTEX::FVF,        구조체의 FVF값, 현재 여기서는 D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 값이 들어가고 있다.

g_pD3DDevice,                DirectX Device를 넣어주면 된다. 여기서는 싱글톤으로 관리되는 디바이스를 넣어줬다.

&pMesh);                        방금 생성한 Mesh의 주소를 넣어주면 된다.


현재 모든 데이터가 파일에서 파싱되어 벡터에 저장되어 있기 때문에 벡터의 정보를 그대로 메쉬에 넣어보자


ST_PNT_VERTEX* vertex;                                            다른 구조체 이름으로 정의되었다면 해당 구조체 포인터를 만든다.

pMesh->LockVertexBuffer(0, (void**)&vertex);                Mesh에 점을 복사하기 위해서 메모리를 잠근다.

memcpy(vertex, &vecTotalVertex[0], vecTotalVertex.size() * sizeof(ST_PNT_VERTEX));

메모리에 벡터의 처음부터 끝까지 복사해서 집어 넣는다. ST_PNT_VERTEX대신에 여러분들의 구조체 이름을 적으면 된다.

pMesh->UnlockVertexBuffer();                                    점을 다 입력했다면 무조건 잠금을 해제한다.


WORD* index = 0;                                                          인덱스 버퍼에 접근하기 위한 WORD포인터

pMesh->LockIndexBuffer(0, (void**)&index);                         인덱스 버퍼를 잠근다.

memcpy(index, &vecIndex[0], vecIndex.size() * sizeof(WORD));   메모리에 벡터의 처음부터 끝까지 복사해서 집어넣는다. 인덱스는 무조건 WORD이다.

pMesh->UnlockIndexBuffer();                                            인덱스를 다 입력했다면 무조건 잠금을 해제한다.


DWORD* attributeBuffer = 0;                                속성버퍼에 접근하기위한 DWORD포인터

pMesh->LockAttributeBuffer(0, &attributeBuffer);      속성버퍼를 잠근다.

memcpy(attributeBuffer, &vecAttribute[0], vecAttribute.size() * sizeof(DWORD)); 

       메모리에 벡터의 처음부터 끝까지 복사해서 집어넣는다. 속성 인덱스는 무조건 DWORD 이다.

pMesh->UnlockAttributeBuffer                             속성을 다 입력했다면 무조건 잠금을 해제한다.


이와 같은 과정을 거치면 벡터에 저장된 모든 데이터를 Mesh에 삽입할 수 있다.

정방행렬 == 정사각행렬 == 가로줄 개수와 (행) 세로줄 개수 (열)이 같은 행렬


항등행렬 : Identity

A행렬 X 항등행렬 = A행렬이 나오게 하는 행렬

항등행렬은 정방행렬일때만 가능하다.

전치행렬 : Transpose

원래 행렬에서 행과 열을 서로 바꿔주는것

행렬식 : Determinant

행렬식의 값이 0이라면 역행렬이 나올수 없고 행렬식의 값이 !0(0이아니라면)이면 유일한 해를 가진다.

라플라스 전개를 통한 정의 (재귀함수)를 이용해 구할 수 있다.


소행렬식 : Minor

각 차수를 제외하고 나머지를 행렬로 만든것

   1 2 3           a11의 소행렬식은             a22의 소행렬식은

   4 5 6   ->             5 6              ->              1 3

   7 8 9                   8 9                               7 9


여인자 : Cofactor

거기에 1 또는 -1을 값으로 하는 계수를 곱한것.





여기서 det == determinent , C == Cofactor

수반행렬 : adjoint

그 행렬의 전치행렬에서 각 원소의 복소켤레를 취한것

->모든 항의 Cofactor(여인자)를 구하고 행렬을 Transpose(전치) 한 행렬



역행렬 : Inverse

A행렬 X A역행렬 = 항등행렬

lAl는 A의 Determinant값 , C는 Cofactor , 차수위치에 있는 T는 Transpose



+ Recent posts