정점 익스포트

SceneExport 상속

3ds max Plugin Wizard에 의해  생성된 코드는 다음과 같다.
SceneExport를 상속 받은 클래스로, maxProject::DoExport()에서 익스포트 처리를 한다.

class maxProject : public SceneExport

{

 public:

     static HWND hParams;

 

     int              ExtCount();                 // Number of extensions supported

     const TCHAR *    Ext(int n);                 // Extension #n (i.e. "3DS")

     const TCHAR *    LongDesc();                 // Long ASCII description (i.e. "Autodesk 3D Studio File")

     const TCHAR *    ShortDesc();                // Short ASCII description (i.e. "3D Studio")

     const TCHAR *    AuthorName();               // ASCII Author name

     const TCHAR *    CopyrightMessage();         // ASCII Copyright message

     const TCHAR *    OtherMessage1();            // Other message #1

     const TCHAR *    OtherMessage2();            // Other message #2

     unsigned int     Version();                  // Version number * 100 (i.e. v3.01 = 301)

     void             ShowAbout(HWND hWnd);       // Show DLL's "About..." box

 

     BOOL SupportsOptions(int ext, DWORD options);

     int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts=FALSE, DWORD options=0);

};

ClassDesc2 상속

ClassDesc2를 상속한 maxProjectClassDesc  클래스는 maxProject 클래스가 맥스에서 생성될때,
어떤 클래스인지 맥스에게 정보를 알려준다.  
SCENE_EXPORT_CLASS_ID는 플러그인의 고유 ID를 나타낸다.
Create함수에 의해 실제 플러그인의 클래스가 생성된다.

class maxProjectClassDesc : public ClassDesc2

{

public:

    int            IsPublic() { return TRUE; }

    void *            Create(BOOL loading = FALSE) { return new maxProject(); }

    const TCHAR *    ClassName() { return GetString(IDS_CLASS_NAME); }

    SClass_ID        SuperClassID() { return SCENE_EXPORT_CLASS_ID; }

    Class_ID        ClassID() { return maxProject_CLASS_ID; }

    const TCHAR*    Category() { return GetString(IDS_CATEGORY); }

 

    // returns fixed parsable name (scripter-visible name)

    const TCHAR*    InternalName() { return _T("maxProject"); }   

 

    HINSTANCE        HInstance() { return hInstance; }    // returns owning module handle

 

};

Plugin-in Calss Export

maxProject.def( dll export function 정의)

LIBRARY maxProject.dle

EXPORTS

   LibDescription       @1       PRIVATE

   LibNumberClasses     @2       PRIVATE

   LibClassDesc         @3       PRIVATE

   LibVersion           @4       PRIVATE

SECTIONS

   .data READ WRITE

plug-in class를 export하기 위한 함수

Export 네임과 확장자 지정

3ds 맥스 Export 대화 상자를 열면 Export 할려는 파일 포맷에 대한 짧은 설명과 파일 확장자가 나온다.
ASE 포맷의 경우는 ASCII Scene Export (*.ASE) 로 표시 된다.
My Format (*.MYF)로 출력 할려면 다음의 메쏘드를 채운다.

//확장자 출력

const TCHAR *maxProject::Ext(int n)

{       

    //TODO: Return the 'i-th' file name extension (i.e. "3DS").

    return _T("myf");

}

 

//짧은 설명 표시

const TCHAR *maxProject::ShortDesc()

{           

    //TODO: Return short ASCII description (i.e. "Targa")

    return _T("My Format");

}

Node Collect

맥스에서 Export를 시작하면 maxProject::DoExport()가 호출된다.
,int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)
DoExport 메쏘드가 호출되면, 파일이름, 익스포터 인터페이스, 맥스 인터페이스등을 얻어 올수가 있다.
맥스에서 개체는 INode로 이루어져 있다.
노드 분류( Bone, Dummy, Mesh등)는 노드를 다 모으고 난 후 한다.

INode를 모으는 방법은 2가지가 있다.

--- Interface::GetRootNode()를 이용하는 방법
INode* Interface::GetSelNode( 0 )     0번째 자식의 노드를 돌려준다.
INode* Interface::GetRootNode()      루트 노드를 찾는다.   

void maxProject::PreProcess(INode* node, int& nodeCount)

{

    nodeCount++;

 

    // Add the nodes material to out material list

    // Null entries are ignored when added...

    mtlList.AddMtl(node->GetMtl());

 

    // For each child of this node, we recurse into ourselves

    // and increment the counter until no more children are found.

    for (int c = 0; c < node->NumberOfChildren(); c++) {

        PreProcess(node->GetChildNode(c), nodeCount);

    }

}

ASE Export 소스를 살펴 보면, PreProcess()에 루트 노드로 재귀 호출하면, nodeCount가 증가되고,
mtlList에 추가된다.

--- Interface::GetRootNode()를 이용하는 방법

ExpInterface::theScene->EnumTree(ITreeEnumProc*) 를 통한 callback 메쏘드를 호출하는 방법이 있다.

//ITreeEnumProc 맥스의 원형이다.

class ITreeEnumProc

{

   public:

      virtual int callback( INode *node )=0;

};

 

//MyEnumProc는 ITreeEnumProc를 상속한다.

class MyEnumProc: public ITreeEnumProc

{

public:

    int callback( INode *node )

    {

        return 0;

    }

};

정점 익스포트

익스포트 되는 정점은 ASE 익스포트 샘플의 코드를 가져 왔기 때문에 괄호도 맞지 않고, 실용성도 없다.
단 버텍스의 정점을 출력한다는 목적을 위해, 코드는 단순화 시켰다.
DoExport() 실행 --> ExportGeometryObject() 실행 --> TraverseNode() 실행

GetDepthString()은 문자열의 들여다 쓰기를 문자열로 가지고 있다.
float t = 0.0f이다. f는 time으로, Animation 되는 데이터 익스포트시에는 시간이 필요하지만,
정적인 데이터를 익스포트 할 때는 시간을 0.0f으로 한다.

IsGroupHead()는 맥스의 메뉴에서 Group / Group으로 구성된 Node이다.
여기서는 박스하나만 만들어서 테스트 하기 때문에, 그룹이 없다.

void maxProject::ExportGeometryObject(FILE* pFile, Interface* pInterface)

{

    // 예외 처리

    //if(0 == (EXPORTOPT_MESH & m_dwExportOption))

    //    return ;

 

    // 인터페이스의 루트 노드를 획득

    INode* pRoot = pInterface->GetRootNode();

 

    // 루트 노드의 자식 개수를 획득

    int nChlildren = pRoot->NumberOfChildren();

 

    // 자식노드의 개수만큼

    for(int i = 0; i < nChlildren; ++i)

    {

        // 실제 자식 노드 획득

        INode* pChild = pRoot->GetChildNode(i);

 

        // 각 노드를 순회한다.

        TraverseNode(pFile, pChild, 0);

    }

}

 

 

void maxProject::TraverseNode(FILE* pFile, INode* pNode, int nDepth)

{

    float t = 0.0f;  //시간 설정

 

    // 깊이 문자열

    TSTR tstrDepth = GetDepthString(nDepth);

    Matrix3 tm = pNode->GetObjTMAfterWSM(t);  //Node로부터 Word Matrix를 읽어온다.

 

 

    // 이 노드가 그룹의 헤드인지를 검사.

    if(TRUE == pNode->IsGroupHead())

    {

        fprintf(pFile, "%s*GROUP \"%s\" {\n", tstrDepth.data(), FixupName(pNode->GetName()));

        ++nDepth;

    }

 

    const ObjectState objState = pNode->EvalWorldState(t);

    if (!objState.obj || objState.obj->SuperClassID() != GEOMOBJECT_CLASS_ID)

    {

        return; // Safety net. This shouldn't happen.

    }

 

    // 실제 오브젝트를 획득한다.

    Object* pObj = objState.obj;

 

    if(NULL != pObj)

    {

        // 오브젝트의 상위 클래스 아이디를 확인하고 기하 오브젝트만 처리한다.

        switch(pObj->SuperClassID())

        {

        // 기하 오브젝트이면

        case GEOMOBJECT_CLASS_ID:

            {

                // 기하 오브젝트를 덤프

                fprintf(pFile, "%s\t%s\n", tstrDepth.data(), FixupName(pNode->GetName()));

                BOOL needDel;

                TriObject* tri = GetTriObjectFromNode(pNode, t, needDel);

                if (!tri)

                    return;

 

                Mesh* mesh = &tri->GetMesh();

                fprintf(pFile, "%s%s {\n", tstrDepth.data(),  _T("*MESH"));

                fprintf(pFile, "%s\t%s %d\n", tstrDepth.data(), _T("*TIMEVALUE"), t);

                fprintf(pFile, "%s\t%s %d\n",tstrDepth.data(), _T("*MESH_NUMVERTEX"), mesh->getNumVerts());

                fprintf(pFile, "%s\t%s %d\n",tstrDepth.data(), _T("*MESH_NUMFACES"), mesh->getNumFaces());

 

                // Export the vertices

                fprintf(pFile,"%s\t%s {\n",tstrDepth.data(), _T("*MESH_VERTEX_LIST"));

                for (int i=0; i < mesh->getNumVerts(); i++)

                {

                    //Point3 v = tm * Inverse(node->GetNodeTM(t)) * mesh->verts[i];

                    Point3 v = tm * mesh->verts[i];

                    fprintf(pFile, "%s\t\t%s %4d\t%s\n",tstrDepth.data(), _T("*MESH_VERTEX"), i, Format(v));

                }

                fprintf(pFile,"%s\t}\n",tstrDepth.data()); // End vertex list

            }

            break;

 

        case CAMERA_CLASS_ID:  //카메라

        case LIGHT_CLASS_ID:   //광원

        case SHAPE_CLASS_ID:   //도형

        case HELPER_CLASS_ID:  //헬퍼

        default :

            break;

        }

    }

 

    //자식 처리

    int nChlildren = pNode->NumberOfChildren();

    for(int i = 0; i < nChlildren; ++i)

    {

        INode* pChild = pNode->GetChildNode(i);

        TraverseNode(pFile, pChild, nDepth);

    }

 

    // 그룹 종료

    if(TRUE == pNode->IsGroupHead())

    {

        fprintf(pFile, "%s}\n", tstrDepth.data());

        --nDepth;

    }

}

위의 소스를 간단히 정리하면 다음과 같다.

//Node로부터 Word Matrix를 읽어온다.

Matrix3 tm = pNode->GetObjTMAfterWSM(t);

//오브젝트를 얻어온다.

const ObjectState objState = pNode->EvalWorldState(t);

//노드를 TriObject로 변환한다. TriObject가 아니면 NULL을 리턴한다.

TriObject* tri = GetTriObjectFromNode(pNode, t, needDel);

//TriObject의 메쉬를 얻어 온다.

Mesh* mesh = &tri->GetMesh();

 

//메쉬의 버텍수 갯수를 리턴한다.

fprintf(pFile, "%s\t%s %d\n",tstrDepth.data(), _T("*MESH_NUMVERTEX"), mesh->getNumVerts());

//메쉬의 면갯수(삼각형의 수)를 리턴한다.

fprintf(pFile, "%s\t%s %d\n",tstrDepth.data(), _T("*MESH_NUMFACES"), mesh->getNumFaces());

 

//버텍수 갯수 만큼 버텍스를 출력한다.

for (int i=0; i < mesh->getNumVerts(); i++)

{

    //월드 메트릭스와 버텍스를 곱하여 월드 좌표를 구한다.

    Point3 v = tm * mesh->verts[i];  

    fprintf(pFile, "%s\t\t%s %4d\t%s\n",tstrDepth.data(), _T("*MESH_VERTEX"), i, Format(v));

}

다음장에는 출력된 버텍스를 XML로 출력해 보겠다.

참고:

Pivot : Object의 중심점

Local Transform Matrix(LTM) : 부모 Node와 Pivot과의 상대적인 Transform Matrix이다.
                                        부모를 기준으로한 Transform Matrix이다.

Node Transform Matrix : 원점을 기준으로 World Transform Matrix이다.

Local TM   X   Parent Node TM = Node TM

Local TM = Node TM   X   Inverse( Parent Node TM )


Tab 클래스

선언은 다음과 같다.
template <class T> class Tab

제네릭(generic) 테이블 클래스로 std::vector와 비슷하지만, 메모리를 할당하지 않는 클래스만
사용하는 것이 좋다.  Tab<float>의 경우는 좋은 반면, Tab<TSTR>의 경우는 생성/소멸이 둘 다
호출 된다는 보장이 없기 때문에, Tab<TSTR*>처럼 사용 해야 한다.
 

참조:
이글은 아래 링크에서 퍼온것으로 일부 편집한글입니다.
http://kin3d.tistory.com/166

http://blog.naver.com/lifeisforu/80023643246

도서: 한국게임 산업 개발원의 3D DATA EXPORT

맥스 SDK의 ase Export 예제도 참고 한다.

C:\Program Files\Autodesk\3ds Max 9 SDK\maxsdk\samples\import_export\asciiexp
C:\Program Files\Autodesk\3ds Max 9 SDK\maxsdk\howto\objects\pervertexdata\testexporter