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", 노드이름, 인덱스 번호 순으로 출력하고 있습니다.

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



BOOL AsciiExp::MakeNameTable(INode* node, int* idx, int indentLevel) 
{

        // Stop recursing if the user pressed Cancel 
        if (ip->GetCancel())
                return FALSE;

        TSTR indent = GetIndent(indentLevel);

        {
                ObjectState os = node->EvalWorldState(0); 

                // The obj member of ObjectState is the actual object we will export.
                if (os. obj) {

                        switch(os. obj->SuperClassID()) {
                        case GEOMOBJECT_CLASS_ID:  
 fprintf(pStream,"%s \"%s\" %d\n""*NAME_INDEX",  FixupName(node->GetName()),*idx); 
                                indentLevel++;
                                (*idx)++;
                                break;
                        }
                }
        }

        for (int c = 0; c < node->NumberOfChildren(); c++) {
                if (!MakeNameTable(node->GetChildNode(c), idx,indentLevel))
                        return FALSE;
                (*idx)++;
        }

        return TRUE;
}


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

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

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

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

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

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

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

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

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

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

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

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

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


        int numChildren = ip->GetRootNode()->NumberOfChildren();
        int idxNum = 0;

        fprintf(pStream,"%s {\n""*NAME_TABLE" );
        //name table
        for (int idx=0; idx<numChildren; idx++) {
        if (ip->GetCancel())
                break;
        MakeNameTable(ip->GetRootNode()->GetChildNode(idx), &idxNum, 0);
        idxNum++;
        }

        fprintf(pStream,"}\n");

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

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

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

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

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

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

 퍼가실땐 블러그에 방명록에 글 하나만 남겨주세요^^ http://oneil.cafe24.com