설명 - 플러그인을 개발할때 컴파일 후 매번 max를 키고 끄는 작업이 필요합니다. 이것을 이용하면 그런 불편을 제거 할 수 있습니다.

1. 랩퍼 파일을 다운 받습니다.

2. 압축을 해제 합니다.

3. .Net 2005 로 프로젝트를 변환합니다.

4. 스크린샷1 - 상자안에 라이브러리를 max sdk 가 설치된 폴더의 라이브러리로 교체합니다.

( 그냥 파일 추가로 하시면 됩니다. )

사용자 삽입 이미지

5. 스크린샷2 - 프로젝트 속성에서 C/C++ 일반 -> 추가포함 디렉터리역시 sdk가 깔린 위치로 변경합니다.

사용자 삽입 이미지

6. wrapperexp.cpp 파일에서 char *wrappedDll = "~"; 라고 되있는 곳을 자신의 랩퍼될 파일의 플러그인이 있는 곳과 이름으로 변경합니다.
주의점은 당연히 max의 plugin폴더가 아닌 곳으로 노출시켜야 하겠지요.

7. 스크린샷2 - C/C++ 아래쪽에 링커 부분이 있는데 여기에 랩퍼플러그인의 출력파일 폴더를 지정 할 수 있습니다. sdk폴더가 아닌 설치된 max/plugins 폴더로 변경해 주시면 됩니다.

8. 설정이 끝난후 자신의 플러그인의 출력폴더를 변경하고, max를 실행하면 콘솔화면이 잠시 출력된 뒤에 익스포트를 해보면 다음 화면과 같이 Wrapper(자신의 플러그인)이 나옵니다.

사용자 삽입 이미지



PluginWrappers.Zip



Leave a comment


파서를 직접 제작하신 분이라면, 문득 스키닝매쉬가 가지고 있는 뼈대 행렬을 구지 포함시켜야 하는가?

라는 생각을 한것은 아마도 저뿐만이 아닐겁니다. 에니메이션 행렬과 뼈대 행렬의 곱셈을 제거할 수 없

는가?... 결론을 내리자면, 제외 불가능입니다. 단순히 생각해서 뼈대행렬의 역행렬을 정점에 가중치로

곱해주는 방법을 떠올릴 수 있습니다. 버텍스의 영향을 주는 뼈가 한가지라면 이것은 물론 가능합니다.

( "스티칭" 에 경우입니다 ) 머리, 다리, 팔 등을 모두 토막내서 가지고 있다가 인형을 맞추면 모습을 복

원할 수 있습니다.. 그러나 한정점에 4개의 뼈가 영향을 줄때는( "스키닝" ) 이것을 억지로 한가지로 모

으는 일에 비유를 할 수 있습니다. 캔 로봇을 납작하게 찌그려트려서 부분부분 부위를 연결해도 그전의

모양은 나오지 않겠죠. 당연한 이치지만, 의심많은 제가 오랜시간 직접실험 해본결과 알아낸 결과입니다

ㅠ_ㅠ;

스키닝 매쉬를 읽어들일때 본래의 뼈대행렬정보(매트릭스, 혹은 벡터와 쿼터니온)를 포함시켜야 하고 에

니메이션 행렬곱셈 정점당(복수행렬곱)이 불가피 합니다. 이에 오프셋정점? 을 사용하는 방법을 생각해볼

수 있습니다. v1 * 'world, v2 * 'world, v3 * 'world, v4 * 'world 를 모두 미리 계산 해놓고 랜더링시에는

정점당 행렬곱셈 4번을 줄일수 있다는 얘깁니다. 이것은 매우 큰 이득임이 틀림 없습니다. 행렬이 16개의

float을 가지고 있지만, 오프셋벡터는 12개의 float이고 언뜻 보면 용량에서도 이득인듯 보이지만, 행렬은

뼈대갯수 예를들어 50개정도 만 있으면 되지만 오프셋정점은 정점마다 4개의 벡터가 필요함으로 저장공간이

너무 많이 필요하게 됩니다.


최근의 애플리케이션의 관점에서 보면 몇번의 실행연산을 추가하여 용량상의 이득을 보는 것이 최적화 하려다

실행파일크기 증가로 속도저하를 유발하는 것보다는 낳을 것이지만, 퍼포먼스를 중요시하는 게임같은 분야에서

는 충분히 토론과 연구분야가 될것입니다.


Leave a comment

ASE 익스포터 (플러그인) 수정 - 2. 몇가지 수정 & 피지크, 스킨 얻기



요즘들어 표준문서, 웹표준에 각광받고 있는 XML과 맥스 스크립트가 주요 이슈 인것 같습니다. 하지만

ASE파일도 장단점이 있는데, 가장 큰 장점은 파서를 구현하기가 쉽다는데 있고 아이러니 하게도 익스포터

를 수정하는 것은 쉽지 않고, 수정시마다 매번 컴파일후 테스트가 큰 단점이 될것 같습니다.


다음의 수정 사항들은, 파서에서 가공할 수 있는 데이터를 익스포터에서 처리해 줌으로써 최적화된 데이터

를 좀더 편하고 빠르게 처리할 수 있도록 합니다. 파서의 소스를 더욱 간결하게 만들 수 있지만, 기존의 파서

로는 더이상 ASE파일을 읽어 들일 수 없게 됩니다. 따라서 ASE인 ASCII Exporter 파일의 속성은 변함이 없

지만, 기존의 ASE파일 보다는 사용자 정의 파일에 가깝게 됩니다. 이러한 최적화가 끝난 후 데이터를 바이

너리로 변환해서 출력한다던가, 에니메이션 구조에 적합한 데이터로 변환하여 출력 한다면 여러분만의 모델

파일이 완성이 될것입니다. ( ASE파일은 분명히 TXT기반 이기 때문에 항시 공개되어 있습니다. 분명, 개발이

끝난 부분이라면 바이너리 출력으로 변환 하는것이 나을 것입니다. )



1. 메쉬의 정점정보를 로컬 좌표로 변환


01: // 변환함수
02: 
03: void AsciiExp::ExportMesh(INode* node, TimeValue t, int indentLevel)  
04: 
05: // 변환위치
06: 
07: for (i=0; igetNumVerts(); i++)
08: {
09:     Point3 v = mesh->verts[i]; // using node local space coordinate
10: 
11:     fprintf(pStream, "%s\t\t%s %4d\t%s\n",
12: 
13:     indent.data(), D_MESH_VERTEX, i, Format(v)); 
14: }
15:  
16: 
17: // 변환할 내용 
18: 
19: Point3 v = tm * mesh->verts[i]; 이부분을 Point3 v = mesh->verts[i];
20:  
21: 



출력을 위한 3D Max는 오브젝트를 화면에 출력하기 위해 최적화된 정보를 보여줍니다. 하지만 우리의 목

적은 바로 자유로운 움직임을 가능하게 하는 게임에서 쓸 목적을 가지고 있습니다. 간단히 TM을 빼버려서

로컬좌표로 출력되게 합니다. (스키닝을 위한 변환)



2. 노드에서 Local TM 가져오기


01: // 변환함수
02:
03: void AsciiExp::ExportNodeTM(INode* node, int indentLevel)
04:
05:
06: // 삽입내용
07:
08: INode *parent;
09: Matrix3 parentTM, nodeTM, localTM;
10: nodeTM = node->GetNodeTM(GetStaticFrame());
11: parent = node->GetParentNode();
12: parentTM = parent->GetNodeTM(GetStaticFrame());
13: localTM = nodeTM*Inverse(parentTM); //부모 tm의 역행렬을 곱해서 local tm을 얻는다.



Local TM = TM * Inv(ParentTM) 입니다, 즉 나중에 이 Local TM을 얻으려면 부모의 역행렬을 가져와 계

산해야 하기 때문에 계산의 편의성을 위해 Local TM을 함께 얻어 오는것이 좋습니다. DumpLocalMatrix3

함수를 변경하셔서 Local,World 모두를 얻어옵니다.




3. Rotate Sample에서 Quaternion값 변경


0
1: // 변환함수
02:
03: void AsciiExp::DumpRotSample(INode* node, int indentLevel) 
04:
05: // 변환위치
06:
07: Quat q = ap.q / prevQ;
08:
09: prevQ = ap.q;
10:
11:
12: // 변환할 내용
13:
14: Quat q = ap.q;로 고칩니다.
15:



Quaternion값이 변화량만 되있기 때문에 이것을 축적된 형태로 변환 합니다. 이것으로 간단하게 변환방법을

알아봤습니다. 부수적으로 좌표계 변환처리와 필요없는 정보의 제외등의 작업 만 한다면 깔끔한 모델파일이

출력된 것을 보실 수 있습니다.



*. Export Physique / Skin Data


Physique Modifier 정보를 찾는 함수,

01: Modifier* AsciiExp::FindPhysiqueModifier(INode *pNode)
02: {
03:   // Get object from node. Abort if no object.
04:   Object* ObjectPtr = pNode->GetObjectRef();
05: 
06: 
07:   if (!ObjectPtr) return NULL;
08: 
09:   // Is derived object ?
10:   while (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID && ObjectPtr)
11:   {
12:     // Yes -> Cast.
13:     IDerivedObject *DerivedObjectPtr = (IDerivedObject *)(ObjectPtr);
14: 
15:     // Iterate over all entries of the modifier stack.
16:     int ModStackIndex = 0;
17:     while (ModStackIndex < DerivedObjectPtr->NumModifiers())
18:     {
19:       // Get current modifier.
20:       Modifier* ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
21: 
22:       // Is this Physique ?
23:       if (ModifierPtr->ClassID() == Class_ID(PHYSIQUE_CLASS_ID_A, 
PHYSIQUE_CLASS_ID_B)) 24: { 25: // Yes -> Exit. 26: return ModifierPtr; 27: } 28: 29: // Next modifier stack entry. 30: ModStackIndex++; 31: } 32: ObjectPtr = DerivedObjectPtr->GetObjRef(); 33: } 34: 35: // Not found. 36: return NULL; 37: }



Skin Modifier 정보를 찾는 함수,


01: Modifier* AsciiExp::FindSkinModifier(INode *pNode)
02: {
03:   // Get object from node. Abort if no object.
04:   Object* ObjectPtr = pNode->GetObjectRef();
05: 
06: 
07:   if (!ObjectPtr) return NULL;
08: 
09:   // Is derived object ?
10:   while (ObjectPtr->SuperClassID() == GEN_DERIVOB_CLASS_ID && ObjectPtr)
11:   {
12:     // Yes -> Cast.
13:     IDerivedObject *DerivedObjectPtr = (IDerivedObject *)(ObjectPtr);
14: 
15:     // Iterate over all entries of the modifier stack.
16:     int ModStackIndex = 0;
17:     while (ModStackIndex < DerivedObjectPtr->NumModifiers())
18:     {
19:       // Get current modifier.
20:       Modifier* ModifierPtr = DerivedObjectPtr->GetModifier(ModStackIndex);
21: 
22:       // Is this Skin ?
23:       if (ModifierPtr->ClassID() == SKIN_CLASSID)
24:       {
25:       // Yes -> Exit.
26:         return ModifierPtr;
27:       }
28: 
29:       // Next modifier stack entry.
30:       ModStackIndex++;
31:     }
32:     ObjectPtr = DerivedObjectPtr->GetObjRef();
33:   }
34: 
35:   // Not found.
36:   return NULL;
37: }


PhysiqueData를 익스포트 하는 함수,



001:
void AsciiExp::ExportPhysiqueData(INode* node, Modifier* mod)
002: {
003:   //get a pointer to the export interface
004:   IPhysiqueExport *phyExport = (IPhysiqueExport *)mod->GetInterface(I_PHYEXPORT); 
005:
006:   //get the physique version number. 
007:   //If the version number is > 30 you may have floating bones
008:   int ver = phyExport->Version();
009:
010:   //get a pointer to the export context interface
011:   IPhyContextExport *mcExport = (IPhyContextExport *)phyExport->GetContextInterface(node);
012:
013:   //convert to rigid for time independent vertex assignment
014:   mcExport->ConvertToRigid(true);
015:
016:   //allow blending to export multi-link assignments
017:   mcExport->AllowBlending(true);
018:
019:   int x = 0;
020:   float normalizedWeight;
021:   Point3 offsetVector;
022:
023:   //these are the different types of rigid vertices supported
024:   IPhyBlendedRigidVertex *rb_vtx;
025:   IPhyRigidVertex *r_vtx;
026:
027:
028:   //get the vertex count from the export interface
029:   int numverts = mcExport->GetNumberVertices(); // 노드의 버텍스 수
030:
031:   fprintf(pStream,"\t%s {\n" , "*PHYSIQUE"); // 피지크 정보 출력 시작
032:
033:   //gather the vertex-link assignment data
034:   for (int i = 0; i<numverts; i++)
035:   {
036:     //keep record of the total bone weight in order to normalize the flaoting bone weights later
037:     float totalWeight = 0.0f, weight = 0.0f;
038:     TSTR nodeName;
039:
040:     //Get the hierarchial vertex interface for this vertex
041:     //IPhyVertexExport* vi = mcExport->GetVertexInterface(i, HIERARCHIAL_VERTEX);
042:     IPhyVertexExport* vi = (IPhyVertexExport*)mcExport->GetVertexInterface(i);
043:     if (vi){
044:
045:       //check the vertex type and process accordingly
046:       int type = vi->GetVertexType();
047:       switch (type)
048:       {
049:         //we have a vertex assigned to more than one link
050:       case RIGID_BLENDED_TYPE:     // 블랜드 타입
051:         //fprintf(pStream, "\t\t%s\t%d {\n", "*BLENDED_RIGID", i);
052:
053:         //type-cast the node to the proper class 
054:         rb_vtx = (IPhyBlendedRigidVertex*)vi;
055:
056:         //iterate through all links this vertex is assigned to
057:         for (x = 0; x<rb_vtx->GetNumberNodes(); x++)
058:         {
059:           //Get the node and its name for export
060:           nodeName = rb_vtx->GetNode(x)->GetName();
061:
062:           //Get the returned normalized weight for export. 
063:           //Use the weight reference to keep track of the total weight.
064:                   //normalizedWeight = rb_vtx->GetWeight(x, &weight);
065:           normalizedWeight = rb_vtx->GetWeight(x);
066:           if( normalizedWeight < 0.000001 ) continue ;
067:           totalWeight += weight;
068:
069:           //Get the offset vector for export
070:           offsetVector = rb_vtx->GetOffsetVector(x);
071:          
072:           // 수정
073:           fprintf(pStream, "\t\t%s %d %s %0.6f %s \"%s\"\n",
074:             "*VERTEX ID", i, "Weight", normalizedWeight, "Bone", nodeName);
075:         }
076:         break;
077:
078:         // we have a vertex assigned to a single link
079:       case RIGID_TYPE:     // 논블랜드 타입
080:         {
081:           //type-cast the node to the proper class
082:           r_vtx = (IPhyRigidVertex*)vi;
083:
084:           //get the node and its name for export
085:           nodeName = r_vtx->GetNode()->GetName();
086:
087:           //the weight is 1.0f since it is not blended
088:           float NormalizedWeight = 1.0f;
089:           //add this vertex weight to the running total
090:           totalWeight += 1.0f;
091:
092:           //get the offset vector for export
093:           offsetVector = r_vtx->GetOffsetVector();
094:
095:           // 수정
096:           fprintf(pStream, "\t\t%s %d %s %0.6f %s \"%s\"\n",
097:             "*VERTEX ID", i, "Weight", NormalizedWeight, "Bone", nodeName);
098:         }
099:         break;
100:
101:         //Should not make it here since assignments were converted to Rigid. 
102:         //Should be one of the above two types
103:       default:
104:         break;
105:       }
106:       //release the vertex interface
107:       mcExport->ReleaseVertexInterface(vi);
108:     }
109:
110:   }
111:
112:   fprintf(pStream, "\t}\n");  // 피지크 정보 출력 끝
113:
114:   //release the context interface
115:   phyExport->ReleaseContextInterface(mcExport);
116:
117:   //Release the physique interface
118:   mod->ReleaseInterface(I_PHYINTERFACE, phyExport);
119: }



SkinData를 익스포트 하는 함수.


01: void AsciiExp::ExportSkinData(INode* node, Modifier* mod)
02: {
03:   ISkin *skin = (ISkin *)mod->GetInterface(I_SKIN);
04: 
05:   ISkinContextData *skin_data = 
06:           (ISkinContextData *)skin->GetContextInterface(node);
07: 
08: //  int numverts = skin_data->GetNumPoints( ) ;
09: 
10:   Object *obj = node->EvalWorldState(ip->GetTime()).obj;
11: 
12:   TriObject *tri = (TriObject*)obj->ConvertToType(ip->GetTime(),
13:           Class_ID(TRIOBJ_CLASS_ID,0));
14:   Mesh* mesh = &tri->GetMesh();
15: 
16:   int numverts = mesh->numVerts ;
17:   
18:   fprintf(pStream,"\t%s {\n" , "*SKIN"); // 스킨 정보 출력 시작
19: 
20:   for (int i = 0; i<numverts; i++)
21:   {
22:     float totalWeight = 0.0f, weight = 0.0f;
23:     TSTR nodeName;
24: 
25:     int numWeights = skin_data->GetNumAssignedBones(i);
26: 
27:     for(int j = 0 ; j < numWeights ; j ++ )
28:     {
29:       INode * pBone = skin->GetBone( skin_data->GetAssignedBone( i, j ) );
30:       weight = skin_data->GetBoneWeight( i, j );
31:       if( weight < 0.000001 ) continue ;
32:       nodeName = pBone->GetName();
33: 
34:       fprintf(pStream, "\t\t%s %d %s %0.6f %s \"%s\"\n", 
35:                   "*VERTEX ID", i, "Weight", weight, "Bone", nodeName); 
36:     }
37:   }
38:   fprintf(pStream, "\t}\n");  // 스킨 정보 출력 끝
39:   mod->ReleaseInterface( I_SKIN, skin );
40: }

Leave a comment

ASE 익스포터 (플러그인) 수정 - 1.Index Table 정보 추가하기



  ASE의 장점은 뛰어난 가독성과 TXT형식으로 편집의 용의성에 있다고 할 수 있습니다.

그래서 3D모델을 프로그래밍 하는 프로그래머와 모델러 모두 파일의 분석이 쉽고

교육적인 면에서 뛰어난것 같습니다. 그래서 저는 그것에 매력을 느끼고 ASE 파싱

( 파일을 실제 게임 프로그램에서 쓸수 있게 가공하는 것 )을 선택해서 게임에서

좀더 실무적으로 쓸 수 있는 방법을 모색하고 있었습니다.

( 참고 문서는 김병철님의 자료를 많이 참고 했습니다. )

하지만 기본 3D MAX 익스포터로 제공되는 ASE파일은 실제 게임에서 가져다 쓰기에는

몇몇 불편한 점들이 있습니다.

여기서는 그것을 보완하는 방법중 한가지로 Index Table을 추가하는 것을 설명하고자 합니다.

'Index Table'이란 제가 편한대로 붙인 이름이고 정확히는 ASE파일에 있는 SCENE정보 처럼

파싱을 위해 Geometric Object(보통 하나의 메쉬)를 번호를 매겨서 나열한 정보입니다.


*NAME_TABLE {
*NAME_INDEX "Bip01_Pelvis" 0
*NAME_INDEX "Bip01_Spine" 1
*NAME_INDEX "Bip01_Spine1" 2
*NAME_INDEX "Man" 3
}

다음과 같은 형식으로 만들 것 입니다. 이것을 왜 만들어야 하는가? 에 대해선 기존의

ASE형식의 파일을 살펴보고 개선할 점에대해서 언급을 해보도록 하겠습니다.

기존에 ASE형식은 각 Object를 식별할수 있는 정보로 문자열을 사용 하고 있습니다.

*GEOMOBJECT {
*NODE_NAME "Bip01_Spine"
*NODE_PARENT "Bip01_Pelvis"

... 이와 같은 형식으로 되어 있습니다.

그렇다면 파싱할때 인덱스를 따로 작성해주지 않는다면, 파싱이 끝난후 이 문자열을 들고 있어

야 하며 계층형 구조를 가지고 있는 본 에니메이션 같은경우 매 프레임마다 문자열을 비교해야

합니다. 그렇다면 문자열 최소 64자의 메모리가 낭비가 되며 매 프레임마다 문자열 비교라는

오버 헤드가 생깁니다. 그리고 문자열 처리는 잠재적인 문제점이 있을 수 있습니다. 그것을

실 게임 데이터처리까지 들고 가는것 보다 파싱을 하는 파서에서 끝내는 것이 나을 것입니다.

물론 아예 ASE파일을 수정하여

*GEOMOBJECT {
*NODE_INDEX 9
*NODE_PARENT_INDEX 1

... 이같은 인덱스 형식으로 아예 수정하는 방법도 있습니다만, 본래의 가독성을 위해서 테이블을

따로 만들어 두는게 더 좋을 듯합니다. ( SMD파일도 이러한 테이블이 존재합니다. )

에니메이션 파일의 독립화나 또 다른 큰 이유도 있지만, 일단 익스포터 수정을 해보도록 합니다.

익스포터 ( 3D MAX 플러그인 ) 을 수정하기 위해선


3D MAX SDK 설치 가 필요하며,

maxsdk\samples\impexp\asciiexp 프로젝트를 이용 하시면 됩니다 (.NET 7.1 사용)

샘플에 있는 소스파일은 3D 맥스에 설치되어 있는 Ascii expoter ( ASE 플러그인 )과 동일하며

sdk 라이브러리 등을 포함시키고 컴파일을 Hybrid로 변경하는 절차가 필요합니다.


수정할 곳은 AsciiExp클래스에서 대부분 이루어 지고 대부분 쉽게 분석 하실 수 있습니다.


이중에 인덱스 테이블을 만드는데 참고할 함수는 바로 nodeEnum()함수 입니다.

이 nodeEnum() 함수를 복사해서 클론 함수를 만든 후 이름과 내용을 약간 수정해 줍니다.

저는 함수 이름을 MakeNameTable이라고 지었습니다. ;)

한가지 함수인자로 새롭게 받는 것은 int 포인터형의 idx입니다. 이것은 전역변수 성격의

카운터 이고 쉽게 idx++로 인덱스 번호를 매깁니다.

fprintf쪽을 보시면 "*NAME_INDEX", 노드이름, 인덱스 번호 순으로 출력하고 있습니다.

중요한 것이 바로 밑에 있습니다. 바로 해당노드의 자식을 재귀적으로 호출하는 것입니다.

01: BOOL AsciiExp::MakeNameTable(INode* node, int* idx, int indentLevel)  
02: { 
03: 
04:         // Stop recursing if the user pressed Cancel  
05:         if (ip->GetCancel()) 
06:                 return FALSE; 
07: 
08:         TSTR indent = GetIndent(indentLevel); 
09: 
10:         { 
11:                 ObjectState os = node->EvalWorldState(0);  
12: 
13:                 if (os. obj) { 
14: 
15:                         switch(os. obj->SuperClassID()) { 
16:                         case GEOMOBJECT_CLASS_ID:   
17:                         fprintf(pStream,"%s \"%s\" %d\n", "*NAME_INDEX",  
18:                         FixupName(node->GetName()),*idx);  
19:                                 indentLevel++; 
20:                                 (*idx)++; 
21:                                 break; 
22:                         } 
23:                 } 
24:         } 
25: 
26:         for (int c = 0; c < node->NumberOfChildren(); c++) { 
27:                 if (!MakeNameTable(node->GetChildNode(c), idx,indentLevel)) 
28:                         return FALSE; 
29:                 (*idx)++; 
30:         } 
31: 
32:         return TRUE; 
33: }


이것의 좋은점은 인덱스를 순서대로 매길때 패턴이 생깁니다. 다른 얘기를 좀더 설명 해 드리면,

모델에서 계층형 구조에서 하나의 노드는 단 하나의 부모를 가지고 있습니다. 그에 반해 자식은

수가 많을 수 있습니다. 이 계층형 구조는 에니메이션을 할때 중요한 개념이며 한가지 룰이 생기

는데, 바로 자식이 선조 부모보다 먼저 처리되면 제대로된 에니메이션을 할 수가 없습니다.

어떠한 경우에도 자식은 부모보다 먼저 처리하면 않되지만, 형제들이나 다른 계층의 노드끼리는

그 순서가 상관 없습니다. 그로인해 바로 인덱스 번호가 계층적으로 출력이 되는 것입니다.

( 사실, 별다른 처리없이 파서에서 순서대로 처리해줘도 계층적으로 구현이 됩니다. )


에니메이션에서 트리구조를 제대로 구현 하려면 하나의 노드가 하나이상의 자식을 가리키고 있는

컨테이너를 가지고 있어야 하며 에니메이션은 최상의 부모로부터 자식으로 재귀적으로 호출되야

계층적인 에니메이션이 실행됩니다.

하지만 이 인덱스 번호로 배열을 구성하면 그럴필요 없이 for문 하나로 계층적인 에니메이션이

가능하게 되어 여러가지 이득을 볼 수 있습니다.

다시 DoExport함수를 수정해 봅시다. DoExport는 전체적으로 순서대로 출력시키는 함수를 호출하는

엔트리 성격의 함수입니다.


01: int numChildren = ip->GetRootNode()->NumberOfChildren(); 
02: int idxNum = 0; 
03: 
04: fprintf(pStream,"%s {\n", "*NAME_TABLE" ); 
05: //name table 
06: for (int idx=0; idx<numChildren; idx++) { 
07: if (ip->GetCancel()) 
08:         break; 
09: MakeNameTable(ip->GetRootNode()->GetChildNode(idx), &idxNum, 0); 
10: idxNum++; 
11: } 
12: 
13: fprintf(pStream,"}\n"); 

이와 같은 구문을 적절한 곳에 삽입하면 제대로된 출력을 보실수 있습니다.

저같은 경우는, SCENE정보 출력을 하는 ExportGlobalInfo()함수 아래에 작성했습니다.

마지막으로 AsciiExp.h파일에 AsciiExp 클래스안에

BOOL MakeNameTable( INode* node, int* idx, int indentLevel );

추가하고 컴파일후 Max plugin폴더에 복사해 넣으시면 끝입니다.

(만일의 상황을 대비해서 기존의 플러그인은 백업해 놓는것이 좋을것 같습니다.)

퍼가실땐, 출처와 방명록을 필히 남겨주세요^^


Leave a comment


Recent Posts

  1. [참고자료] 모니터 해상도 도표
  2. [런타임에러] Expression: map/set it..
  3. *. 플러그인 랩퍼 사용하기 ( 3DS MAX..
  4. *. 스키닝 오프셋 벡터 [Offset Vecto..
  5. November 2007 DirectX SDK News

Recent Comments

  1. 관리자만 볼 수 있는 댓글입니다. 06/24

Recent Trackbacks

Calendar

«   2009/11   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          

Bookmarks

  1. Cafe24
  2. ForTheProgramming
  3. Tatter Tools
  4. 근석이
  5. 진호네

Site Stats

TOTAL 14477 HIT
TODAY 4 HIT
YESTERDAY 16 HIT
Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.