Beispiel #1
0
void MeshX::BuildSkinnedMesh()
{
	ID3DXMesh* oldMesh = allocMeshHierarchy.GetMeshList().front()->MeshData.pMesh;

	//compute normal
	D3DVERTEXELEMENT9 elements[MAX_FVF_DECL_SIZE];
	oldMesh->GetDeclaration(elements);

	ID3DXMesh* tempMesh = 0;
	ID3DXMesh* tempOpMesh = 0;
	oldMesh->CloneMesh(D3DXMESH_SYSTEMMEM, elements, pDevice, &tempMesh);
	 
	if( !HasNormals(tempMesh) )
		D3DXComputeNormals(tempMesh, 0);

	//optimize the mesh
	DWORD* adj = new DWORD[tempMesh->GetNumFaces()*3];
	ID3DXBuffer* remap = 0;
	tempMesh->GenerateAdjacency(0.00001f, adj);
	tempMesh->Optimize(D3DXMESH_SYSTEMMEM | D3DXMESHOPT_VERTEXCACHE | 
		D3DXMESHOPT_ATTRSORT, adj, 0, 0, &remap, &tempOpMesh);

	SafeRelease(tempMesh);

	// In the .X file (specifically the array DWORD vertexIndices[nWeights]
	// data member of the SkinWeights template) each bone has an array of
	// indices which identify the vertices of the mesh that the bone influences.
	// Because we have just rearranged the vertices (from optimizing), the vertex 
	// indices of a bone are obviously incorrect (i.e., they index to vertices the bone
	// does not influence since we moved vertices around).  In order to update a bone's 
	// vertex indices to the vertices the bone _does_ influence, we simply need to specify
	// where we remapped the vertices to, so that the vertex indices can be updated to 
	// match.  This is done with the ID3DXSkinInfo::Remap method.
	skinInfo->Remap(tempOpMesh->GetNumVertices(), 
		(DWORD*)remap->GetBufferPointer());
	SafeRelease(remap); // Done with remap info.

	DWORD        numBoneComboEntries = 0;
	ID3DXBuffer* boneComboTable      = 0;
	DWORD maxVertInfluences;
	skinInfo->ConvertToIndexedBlendedMesh(tempOpMesh, 0,  
		128, 0, 0, 0, 0, &maxVertInfluences,
		&numBoneComboEntries, &boneComboTable, &this->mesh);

	SafeRelease(tempOpMesh);
	SafeRelease(boneComboTable);
	delete[] adj;
}
Beispiel #2
0
void LoadXFile(const std::string & filename, ID3DXMesh** meshOut, std::vector<Mtrl> & mtrls, std::vector<IDirect3DTexture9*> & texs)
{
	ID3DXMesh* meshSys				= 0;
	ID3DXBuffer * adjBuffer			= 0;
	ID3DXBuffer * mtrlBuffer		= 0;
	DWORD numMtrls					= 0;

    //step 1. Load the x file into system memory
    HR(D3DXLoadMeshFromX(filename.c_str(), D3DXMESH_SYSTEMMEM, gd3dDevice, &adjBuffer, &mtrlBuffer, 0, &numMtrls, &meshSys));

    //step 2. look into MAX_FVF_DECL_SIZE
    D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
    HR(meshSys->GetDeclaration(elems));

    bool hasNormals = false;
	D3DVERTEXELEMENT9 term = D3DDECL_END();
    for(int i = 0; i < MAX_FVF_DECL_SIZE; ++i)
    {
        //did we reach end?
        if(elems[i].Stream == 0xff)
            break;

        if(elems[i].Type == D3DDECLTYPE_FLOAT3 && elems[i].Usage == D3DDECLUSAGE_NORMAL && elems[i].UsageIndex == 0)
        {
            hasNormals = true;
            break;
        }
    }

    //step 3.
    D3DVERTEXELEMENT9 elements[64];
    UINT numElements = 0;
    VertexPNT::Decl->GetDeclaration(elements, &numElements);
   
    ID3DXMesh * temp = 0;
    //HR(meshSys->CloneMesh(D3DXMESH_SYSTEMMEM, elements, gd3dDevice, &temp));
	 HR(meshSys->CloneMesh(D3DXMESH_32BIT, elements, gd3dDevice, &temp));
	ReleaseCOM(meshSys);
	meshSys = temp;

    //step 4
    if( hasNormals == false )
        HR(D3DXComputeNormals(meshSys, 0));

    //step 5
    HR(meshSys->Optimize(D3DXMESH_MANAGED | D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT
        | D3DXMESHOPT_VERTEXCACHE, (DWORD*)adjBuffer->GetBufferPointer(), 0, 0, 0, meshOut));

    ReleaseCOM(meshSys); //Done w/ system mesh
    ReleaseCOM(adjBuffer); //done with buffer

    //step 6: get the materials and load the textures
    if(mtrlBuffer != 0 && numMtrls != 0)
    {
		D3DXMATERIAL *d3dxMtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();
		
		for(DWORD i = 0; i < numMtrls; ++i)
		{
			//save the ith material.  MatD3D ambient
			//doesnt have a default value, so I'm setting
			//it to be the same as the diffuse
			Mtrl m;
			m.ambient = d3dxMtrls[i].MatD3D.Diffuse;
			m.diffuse = d3dxMtrls[i].MatD3D.Diffuse;
			m.spec = d3dxMtrls[i].MatD3D.Specular;
			m.specPower = d3dxMtrls[i].MatD3D.Power;
			mtrls.push_back(m);

			//check if the ith material has an associative texture
			if(d3dxMtrls[i].pTextureFilename != 0)
			{
				//yes, load the texture for the ith subset
				IDirect3DTexture9* tex = 0;
				char *texFN = d3dxMtrls[i].pTextureFilename;
				HR(D3DXCreateTextureFromFile(gd3dDevice, texFN, &tex));

				//save the loaded texure
				texs.push_back(tex);
			}
			else
			{
				//no texture
				texs.push_back( 0 );
			}
		}
	}

	ReleaseCOM(mtrlBuffer); // done with the buffer
}
Beispiel #3
0
void LoadXFile(
			   const std::string& filename, 
			   ID3DXMesh** meshOut,
			   std::vector<Mtrl>& mtrls, 
			   std::vector<IDirect3DTexture9*>& texs)
{
	// Step 1: Load the .x file from file into a system memory mesh.

	ID3DXMesh* meshSys      = 0;
	ID3DXBuffer* adjBuffer  = 0;
	ID3DXBuffer* mtrlBuffer = 0;
	DWORD numMtrls          = 0;

	HR(D3DXLoadMeshFromX(filename.c_str(), D3DXMESH_SYSTEMMEM, gd3dDevice,
		&adjBuffer,	&mtrlBuffer, 0, &numMtrls, &meshSys));


	// Step 2: Find out if the mesh already has normal info?

	D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
	HR(meshSys->GetDeclaration(elems));

	bool hasNormals = false;
	D3DVERTEXELEMENT9 term = D3DDECL_END();
	for(int i = 0; i < MAX_FVF_DECL_SIZE; ++i)
	{
		// Did we reach D3DDECL_END() {0xFF,0,D3DDECLTYPE_UNUSED, 0,0,0}?
		if(elems[i].Stream == 0xff )
			break;

		if( elems[i].Type == D3DDECLTYPE_FLOAT3 &&
			elems[i].Usage == D3DDECLUSAGE_NORMAL &&
			elems[i].UsageIndex == 0 )
		{
			hasNormals = true;
			break;
		}
	}


	// Step 3: Change vertex format to VertexPNT.

	D3DVERTEXELEMENT9 elements[64];
	UINT numElements = 0;
	VertexPNT::Decl->GetDeclaration(elements, &numElements);

	ID3DXMesh* temp = 0;
	HR(meshSys->CloneMesh(D3DXMESH_SYSTEMMEM, 
		elements, gd3dDevice, &temp));
	ReleaseCOM(meshSys);
	meshSys = temp;


	// Step 4: If the mesh did not have normals, generate them.

	if( hasNormals == false)
		HR(D3DXComputeNormals(meshSys, 0));


	// Step 5: Optimize the mesh.

	HR(meshSys->Optimize(D3DXMESH_MANAGED | 
		D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, 
		(DWORD*)adjBuffer->GetBufferPointer(), 0, 0, 0, meshOut));
	ReleaseCOM(meshSys); // Done w/ system mesh.
	ReleaseCOM(adjBuffer); // Done with buffer.

	// Step 6: Extract the materials and load the textures.

	if( mtrlBuffer != 0 && numMtrls != 0 )
	{
		D3DXMATERIAL* d3dxmtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();

		for(DWORD i = 0; i < numMtrls; ++i)
		{
			// Save the ith material.  Note that the MatD3D property does not have an ambient
			// value set when its loaded, so just set it to the diffuse value.
			Mtrl m;
			m.ambient   = d3dxmtrls[i].MatD3D.Diffuse;
			m.diffuse   = d3dxmtrls[i].MatD3D.Diffuse;
			m.spec      = d3dxmtrls[i].MatD3D.Specular;
			m.specPower = d3dxmtrls[i].MatD3D.Power;
			mtrls.push_back( m );

			// Check if the ith material has an associative texture
			if( d3dxmtrls[i].pTextureFilename != 0 )
			{
				// Yes, load the texture for the ith subset
				IDirect3DTexture9* tex = 0;
				char* texFN = d3dxmtrls[i].pTextureFilename;
				HR(D3DXCreateTextureFromFile(gd3dDevice, texFN, &tex));

				// Save the loaded texture
				texs.push_back( tex );
			}
			else
			{
				// No texture for the ith subset
				texs.push_back( 0 );
			}
		}
	}
	ReleaseCOM(mtrlBuffer); // done w/ buffer
}
Beispiel #4
0
void SkinnedMesh::buildSkinnedMesh(ID3DXMesh* mesh)
{
    //====================================================================
    // First add a normal component and 2D texture coordinates component.

    D3DVERTEXELEMENT9 elements[64];
    UINT numElements = 0;
    VertexPNT::Decl->GetDeclaration(elements, &numElements);

    ID3DXMesh* tempMesh = 0;
    HR(mesh->CloneMesh(D3DXMESH_SYSTEMMEM, elements, gd3dDevice, &tempMesh));

    if( !hasNormals(tempMesh) )
        HR(D3DXComputeNormals(tempMesh, 0));

    //====================================================================
    // Optimize the mesh; in particular, the vertex cache.
    DWORD* adj = new DWORD[tempMesh->GetNumFaces()*3];
    ID3DXBuffer* remap = 0;
    HR(tempMesh->GenerateAdjacency(EPSILON, adj));
    ID3DXMesh* optimizedTempMesh = 0;
    HR(tempMesh->Optimize(D3DXMESH_SYSTEMMEM | D3DXMESHOPT_VERTEXCACHE |
                          D3DXMESHOPT_ATTRSORT, adj, 0, 0, &remap, &optimizedTempMesh));

    ReleaseCOM(tempMesh); // Done w/ this mesh.
    delete[] adj;         // Done with buffer.

    // In the .X file (specifically the array DWORD vertexIndices[nWeights]
    // data member of the SkinWeights template) each bone has an array of
    // indices which identify the vertices of the mesh that the bone influences.
    // Because we have just rearranged the vertices (from optimizing), the vertex
    // indices of a bone are obviously incorrect (i.e., they index to vertices the bone
    // does not influence since we moved vertices around).  In order to update a bone's
    // vertex indices to the vertices the bone _does_ influence, we simply need to specify
    // where we remapped the vertices to, so that the vertex indices can be updated to
    // match.  This is done with the ID3DXSkinInfo::Remap method.
    HR(mSkinInfo->Remap(optimizedTempMesh->GetNumVertices(),
                        (DWORD*)remap->GetBufferPointer()));
    ReleaseCOM(remap); // Done with remap info.

    //====================================================================
    // The vertex format of the source mesh does not include vertex weights
    // nor bone index data, which are both needed for vertex blending.
    // Therefore, we must convert the source mesh to an "indexed-blended-mesh,"
    // which does have the necessary data.

    DWORD        numBoneComboEntries = 0;
    ID3DXBuffer* boneComboTable      = 0;
    HR(mSkinInfo->ConvertToIndexedBlendedMesh(optimizedTempMesh, D3DXMESH_MANAGED | D3DXMESH_WRITEONLY,
            MAX_NUM_BONES_SUPPORTED, 0, 0, 0, 0, &mMaxVertInfluences,
            &numBoneComboEntries, &boneComboTable, &mSkinnedMesh));

    ReleaseCOM(optimizedTempMesh); // Done with tempMesh.
    ReleaseCOM(boneComboTable); // Don't need bone table.

#if defined(DEBUG) | defined(_DEBUG)
    // Output to the debug output the vertex declaration of the mesh at this point.
    // This is for insight only to see what exactly ConvertToIndexedBlendedMesh
    // does to the vertex declaration.
    D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
    HR(mSkinnedMesh->GetDeclaration(elems));

    OutputDebugString("\nVertex Format After ConvertToIndexedBlendedMesh\n");
    int i = 0;
    while( elems[i].Stream != 0xff ) // While not D3DDECL_END()
    {
        if( elems[i].Type == D3DDECLTYPE_FLOAT1)
            OutputDebugString("Type = D3DDECLTYPE_FLOAT1; ");
        if( elems[i].Type == D3DDECLTYPE_FLOAT2)
            OutputDebugString("Type = D3DDECLTYPE_FLOAT2; ");
        if( elems[i].Type == D3DDECLTYPE_FLOAT3)
            OutputDebugString("Type = D3DDECLTYPE_FLOAT3; ");
        if( elems[i].Type == D3DDECLTYPE_UBYTE4)
            OutputDebugString("Type = D3DDECLTYPE_UBYTE4; ");

        if( elems[i].Usage == D3DDECLUSAGE_POSITION)
            OutputDebugString("Usage = D3DDECLUSAGE_POSITION\n");
        if( elems[i].Usage == D3DDECLUSAGE_BLENDWEIGHT)
            OutputDebugString("Usage = D3DDECLUSAGE_BLENDWEIGHT\n");
        if( elems[i].Usage == D3DDECLUSAGE_BLENDINDICES)
            OutputDebugString("Usage = D3DDECLUSAGE_BLENDINDICES\n");
        if( elems[i].Usage == D3DDECLUSAGE_NORMAL)
            OutputDebugString("Usage = D3DDECLUSAGE_NORMAL\n");
        if( elems[i].Usage == D3DDECLUSAGE_TEXCOORD)
            OutputDebugString("Usage = D3DDECLUSAGE_TEXCOORD\n");
        ++i;
    }
#endif
}
Beispiel #5
0
void XManager::loadXFile(IDirect3DDevice9* dev, const std::string& filename, ID3DXMesh** meshOut, std::vector<Material>* mtrls, std::vector<IDirect3DTexture9*>* texs) {
	// Step 1: Load the .x file from file into a system memory mesh.

	ID3DXMesh* meshSys      = 0;
	ID3DXBuffer* adjBuffer  = 0;
	ID3DXBuffer* mtrlBuffer = 0;
	DWORD numMtrls          = 0;

	HR(D3DXLoadMeshFromX(filename.c_str(), D3DXMESH_SYSTEMMEM, dev,
		&adjBuffer,	&mtrlBuffer, 0, &numMtrls, &meshSys));


	// Step 2: Find out if the mesh already has normal info?

	D3DVERTEXELEMENT9 elems[MAX_FVF_DECL_SIZE];
	HR(meshSys->GetDeclaration(elems));
	
	bool hasNormals = false;
	D3DVERTEXELEMENT9 term = D3DDECL_END();
	for(int i = 0; i < MAX_FVF_DECL_SIZE; ++i)
	{
		// Did we reach D3DDECL_END() {0xFF,0,D3DDECLTYPE_UNUSED, 0,0,0}?
		if(elems[i].Stream == 0xff )
			break;

		if( elems[i].Type == D3DDECLTYPE_FLOAT3 &&
			elems[i].Usage == D3DDECLUSAGE_NORMAL &&
			elems[i].UsageIndex == 0 )
		{
			hasNormals = true;
			break;
		}
	}


	// Step 3: Change vertex format to CustomVertex3NormalUV

	D3DVERTEXELEMENT9 elements[64];
	UINT numElements = 0;
	CustomVertex3NormalUV::decl->GetDeclaration(elements, &numElements);

	ID3DXMesh* temp = 0;
	HR(meshSys->CloneMesh(D3DXMESH_SYSTEMMEM, 
		elements, dev, &temp));
	SAFERELEASECOM(meshSys);
	meshSys = temp;


	// Step 4: If the mesh did not have normals, generate them.

	if( hasNormals == false)
		HR(D3DXComputeNormals(meshSys, 0));


	// Step 5: Optimize the mesh.

	HR(meshSys->Optimize(D3DXMESH_MANAGED | 
		D3DXMESHOPT_COMPACT | D3DXMESHOPT_ATTRSORT | D3DXMESHOPT_VERTEXCACHE, 
		(DWORD*)adjBuffer->GetBufferPointer(), 0, 0, 0, meshOut));
	SAFERELEASECOM(meshSys); // Done w/ system mesh.
	SAFERELEASECOM(adjBuffer); // Done with buffer.

	// Step 6: Extract the materials and load the textures.

	if( mtrlBuffer != 0 && numMtrls != 0 )
	{
		D3DXMATERIAL* d3dxmtrls = (D3DXMATERIAL*)mtrlBuffer->GetBufferPointer();

		for(DWORD i = 0; i < numMtrls; ++i)
		{
			// Save the ith material.  Note that the MatD3D property does not have an ambient
			// value set when its loaded, so just set it to the diffuse value.
			Material m;
			m.ambient   = d3dxmtrls[i].MatD3D.Diffuse;
			m.diffuse   = d3dxmtrls[i].MatD3D.Diffuse;
			m.spec      = d3dxmtrls[i].MatD3D.Specular;
			m.specPower = d3dxmtrls[i].MatD3D.Power;
			mtrls->push_back( m );

			// Check if the ith material has an associative texture
			if( d3dxmtrls[i].pTextureFilename != 0 )
			{
				IDirect3DTexture9* tex = 0;
				std::string s(d3dxmtrls[i].pTextureFilename);
				const char folder[] = "./texture/";
				unsigned int pos = s.find_last_of('\/');
				std::string newFileName(folder);

				// std::string::npos gets returned if no / was found
				if(pos != std::string::npos) {
					//std::string sub = s.substr(pos,s.size());

					if(strcmp(s.substr(pos+1,s.size()).c_str(),"Watcher.tga") == 0) {
						newFileName.append("Watcher_Black.tga");
					}
					else {
						newFileName.append(s.substr(pos+1,s.size()));
					}
				}
				else {
					
					newFileName.append(s);
					
				}
				// Save the loaded texture
				HR(D3DXCreateTextureFromFile(dev, newFileName.c_str(), &tex));
				texs->push_back( tex );
			}
			else
			{
Beispiel #6
0
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
    WNDCLASS wc;

    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = (WNDPROC) MainWindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "xtocmod";
    if (RegisterClass(&wc) == 0)
    {
	MessageBox(NULL,
                   "Failed to register the window class.", "Fatal Error",
                   MB_OK | MB_ICONERROR);
	return NULL;
    }

    DWORD windowStyle = (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
                         WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX);
    g_mainWindow = CreateWindow("xtocmod",
                                "xtocmod",
                                windowStyle,
                                CW_USEDEFAULT,
                                CW_USEDEFAULT,
                                300, 300,
                                NULL,
                                NULL,
                                hInstance,
                                NULL);
    if (g_mainWindow == NULL)
    {
        MessageBox(NULL,
                   "Error creating application window.", "Fatal Error",
                   MB_OK | MB_ICONERROR);
    }

    //ShowWindow(g_mainWindow, SW_SHOW);
    SetForegroundWindow(g_mainWindow);
    SetFocus(g_mainWindow);

    // Initialize D3D
    g_d3d = Direct3DCreate9(D3D_SDK_VERSION);
    if (g_d3d == NULL)
    {
        ShowD3DErrorMessage("Initializing D3D", 0);
        return 1;
    }

    D3DPRESENT_PARAMETERS presentParams;
    ZeroMemory(&presentParams, sizeof(presentParams));
    presentParams.Windowed = TRUE;
    presentParams.SwapEffect = D3DSWAPEFFECT_COPY;
#if 0
    presentParams.BackBufferWidth = 300;
    presentParams.BackBufferHeight = 300;
    presentParams.BackBufferCount = 1;
    presentParams.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
    presentParams.Windowed = TRUE;
#endif
    
    HRESULT hr = g_d3d->CreateDevice(D3DADAPTER_DEFAULT,
                                     D3DDEVTYPE_HAL,
                                     g_mainWindow,
                                     D3DCREATE_HARDWARE_VERTEXPROCESSING,
                                     &presentParams,
                                     &g_d3dDev);
    if (FAILED(hr))
    {
        ShowD3DErrorMessage("Creating D3D device", hr);
        //return 1;
    }

    string inputFilename(lpCmdLine);
    string outputFilename(inputFilename, 0, inputFilename.rfind('.'));
    outputFilename += ".cmod";

    ID3DXMesh* mesh = NULL;
    ID3DXBuffer* adjacency = NULL;
    ID3DXBuffer* materialBuf = NULL;
    ID3DXBuffer* effects = NULL;
    DWORD numMaterials;
    
    hr = D3DXLoadMeshFromX(inputFilename.c_str(),
                           0,
                           g_d3dDev,
                           &adjacency,
                           &materialBuf,
                           &effects,
                           &numMaterials,
                           &mesh);
    if (FAILED(hr))
    {
        ShowD3DErrorMessage("Loading mesh from X file", hr);
        return 1;
    }


    DWORD numVertices = mesh->GetNumVertices();
    DWORD numFaces = mesh->GetNumFaces();

    cout << "vertices: " << numVertices << '\n';
    cout << "faces: " << numFaces << '\n';

    cout << "adjacency buffer size: " << adjacency->GetBufferSize() << '\n';

    ofstream meshfile(outputFilename.c_str());

    // Output the header
    meshfile << "#celmodel__ascii\n\n";

    cout << "numMaterials=" << numMaterials << '\n';
    D3DXMATERIAL* materials = reinterpret_cast<D3DXMATERIAL*>(materialBuf->GetBufferPointer());
    for (DWORD mat = 0; mat < numMaterials; mat++)
    {
        meshfile << "material\n";
        meshfile << "diffuse " << materials[mat].MatD3D.Diffuse << '\n';
        //meshfile << "emissive " << materials[mat].MatD3D.Emissive << '\n';
        meshfile << "specular " << materials[mat].MatD3D.Specular << '\n';
        meshfile << "specpower " << materials[mat].MatD3D.Power << '\n';
        meshfile << "opacity " << materials[mat].MatD3D.Diffuse.a << '\n';
        meshfile << "end_material\n\n";
    }

    // Vertex format
    D3DVERTEXELEMENT9 declElements[MAX_FVF_DECL_SIZE];
    hr = mesh->GetDeclaration(declElements);
    if (FAILED(hr))
    {
        ShowD3DErrorMessage("Checking vertex declaration", hr);
        return 1;
    }

    DWORD stride = D3DXGetDeclVertexSize(declElements, 0);

    VertexAttribute vertexMap[VertexAttribute::MaxAttribute];
    CreateVertexAttributeMap(declElements, vertexMap);

    meshfile << "mesh\n\n";

    DumpVertexDescription(vertexMap, meshfile);

    ID3DXMesh* optMesh = NULL;
    ID3DXBuffer* vertexRemap = NULL;
    DWORD* faceRemap = new DWORD[numFaces];
    DWORD* optAdjacency = new DWORD[numFaces * 3];
    hr = mesh->Optimize(D3DXMESHOPT_COMPACT | D3DXMESHOPT_STRIPREORDER,
                        //D3DXMESHOPT_VERTEXCACHE |
                        reinterpret_cast<DWORD*>(adjacency->GetBufferPointer()),
                        optAdjacency,
                        faceRemap,
                        &vertexRemap,
                        &optMesh);
    if (FAILED(hr))
    {
        ShowD3DErrorMessage("Optimize failed: ", hr);
        return 1;
    }
    
    // Attribute table
    DWORD attribTableSize = 0;
    hr = optMesh->GetAttributeTable(NULL, &attribTableSize);
    if (FAILED(hr))
    {
        ShowD3DErrorMessage("Querying attribute table size", hr);
        return 1;
    }

    D3DXATTRIBUTERANGE* attribTable = NULL;
    if (attribTableSize > 0)
    {
        attribTable = new D3DXATTRIBUTERANGE[attribTableSize];
        hr = optMesh->GetAttributeTable(attribTable, &attribTableSize);
        if (FAILED(hr))
        {
            ShowD3DErrorMessage("Getting attribute table", hr);
            return 1;
        }
    }

    cout << "Attribute table size: " << attribTableSize << '\n';
    if (attribTableSize == 1)
    {
        cout << "Attribute id: " << attribTable[0].AttribId << '\n';
    }

    if (!DumpMeshVertices(optMesh, vertexMap, stride, meshfile))
        return 1;
    
    // output the indices
    for (DWORD attr = 0; attr < attribTableSize; attr++)
    {
        StripifyMeshSubset(optMesh, attr, meshfile);
    }
    meshfile << "\nend_mesh\n";

#if 0
    IDirect3DIndexBuffer9* indices = NULL;
    hr = mesh->GetIndexBuffer(&indices);
#endif

#if 0
    // No message loop required for this app
    MSG msg;
    GetMessage(&msg, NULL, 0u, 0u);
    while (msg.message != WM_QUIT)
    {
        GetMessage(&msg, NULL, 0u, 0u);
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
#endif

    return 0;
}