Archive for the ‘mesh file format’ tag
X to OpenCTM Converter

The OpenCTM file format for compressed triangle meshes looks very promising. To celebrate my gigasecond birthday, I wrote a .x to .ctm converter that exports positions and normals. It currently discards texture coordinates, but it shouldn’t be hard to extend it. I can’t guarantee that this tool is super robust, but it worked well enough for the test cases that I ran across.
I simply used ID3DXMesh to load in the .x file and the OpenCTM API to write out the .ctm file. Unfortunately, the usage of ID3DXMesh makes this tool Windows-only.
It’s a command line tool that works like this:
Usage: x2ctm [sourcefile] [sourcefile] ...
To form a destination filename, it simply replaces the existing file extension with .ctm and preserves the folder path.
At a high level, here’s how it works: (if you want, skip to the downloads section)
void ExportCTM(ID3DXMesh* pMesh, const char* destFile)
{
// 1. Find where the positions, normals, and texture coordinates live.
// 2. Check that we support the format of the data.
// 3. Obtain vertex & index counts from the D3D mesh; allocate memory for the CTM mesh.
// 4. Lock down the verts and pluck out the positions and normals.
// 5. Lock down the indices and convert them to unsigned 32-bit integers.
// 6. Instance the OpenCTM mesh and save it to disk.
// 7. Free the OpenCTM buffers.
}
Here’s the actual code for the above summary:
void ExportCTM(ID3DXMesh* pMesh, const char* destFile)
{
// Find where the positions, normals, and texture coordinates live.
const WORD MISSING_ATTRIBUTE = 0xffff;
WORD positionsOffset = MISSING_ATTRIBUTE;
WORD normalsOffset = MISSING_ATTRIBUTE;
WORD texCoordsOffset = MISSING_ATTRIBUTE;
D3DVERTEXELEMENT9 vertexLayout[MAX_FVF_DECL_SIZE];
D3DVERTEXELEMENT9 endMarker = D3DDECL_END();
pMesh->GetDeclaration(vertexLayout);
D3DVERTEXELEMENT9* pVertexLayout = &vertexLayout[0];
for (int attrib = 0; attrib < MAX_FVF_DECL_SIZE; ++attrib, pVertexLayout++)
{
if (0 == memcmp(&vertexLayout[attrib], &endMarker, sizeof(endMarker)))
{
break;
}
if (pVertexLayout->Stream != 0)
{
continue;
}
if (pVertexLayout->Usage == D3DDECLUSAGE_POSITION &&
pVertexLayout->Type == D3DDECLTYPE_FLOAT3)
{
cout << "Contains positions." << endl;
positionsOffset = pVertexLayout->Offset;
}
if (pVertexLayout->Usage == D3DDECLUSAGE_NORMAL &&
pVertexLayout->Type == D3DDECLTYPE_FLOAT3)
{
cout << "Contains normals." << endl;
normalsOffset = pVertexLayout->Offset;
}
if (pVertexLayout->Usage == D3DDECLUSAGE_TEXCOORD &&
pVertexLayout->Type == D3DDECLTYPE_FLOAT2)
{
cout << "Contains texture coordinates." << endl;
texCoordsOffset = pVertexLayout->Offset;
}
}
// Check that we support the format of the data.
if (positionsOffset == MISSING_ATTRIBUTE)
{
cerr << "Unsupported Vertex Declaration in " << destFile << endl;
exit(1);
}
// Obtain vertex & index counts from the D3D mesh; allocate memory for the CTM mesh.
DWORD dwVertexCount = pMesh->GetNumVertices();
DWORD dwTriangleCount = pMesh->GetNumFaces();
DWORD dwIndexCount = dwTriangleCount * 3;
CTMfloat* pCtmVertices = new CTMfloat[3* dwVertexCount];
CTMuint* pCtmIndices = new CTMuint[dwIndexCount];
CTMfloat* pCtmNormals = (normalsOffset == 0xffff) ? 0 : new CTMfloat[3 * dwVertexCount];
// Lock down the verts and pluck out the positions and normals.
{
void* pData = 0;
pMesh->LockVertexBuffer(D3DLOCK_READONLY , &pData);
if (positionsOffset != MISSING_ATTRIBUTE)
{
unsigned char* pSource = ((unsigned char*) pData) + positionsOffset;
unsigned char* pDest = (unsigned char* ) pCtmVertices;
DWORD dwSourceStride = pMesh->GetNumBytesPerVertex();
DWORD dwDestStride = sizeof(CTMfloat) * 3;
for (DWORD dwVertex = 0; dwVertex < dwVertexCount; ++dwVertex)
{
memcpy(pDest, pSource, dwDestStride);
pSource += dwSourceStride;
pDest += dwDestStride;
}
}
if (normalsOffset != MISSING_ATTRIBUTE)
{
unsigned char* pSource = ((unsigned char*) pData) + normalsOffset;
unsigned char* pDest = (unsigned char* ) pCtmNormals;
DWORD dwSourceStride = pMesh->GetNumBytesPerVertex();
DWORD dwDestStride = sizeof(CTMfloat) * 3;
for (DWORD dwVertex = 0; dwVertex < dwVertexCount; ++dwVertex)
{
memcpy(pDest, pSource, dwDestStride);
pSource += dwSourceStride;
pDest += dwDestStride;
}
}
pMesh->UnlockVertexBuffer();
}
// Lock down the indices and convert them to unsigned 32-bit integers.
{
void* pData = 0;
pMesh->LockIndexBuffer(D3DLOCK_READONLY , &pData);
DWORD dwOptions = pMesh->GetOptions();
DWORD dwSourceStride = (dwOptions & D3DXMESH_32BIT) ? 4 : 2;
unsigned char* pSource = (unsigned char*) pData;
unsigned char* pDest = (unsigned char*) pCtmIndices;
DWORD dwDestStride = sizeof(CTMuint);
for (DWORD dwIndex = 0; dwIndex < dwIndexCount; ++dwIndex)
{
memset(pDest, 0, dwDestStride);
memcpy(pDest, pSource, dwSourceStride);
pSource += dwSourceStride;
pDest += dwDestStride;
}
pMesh->UnlockIndexBuffer();
}
// Instance the OpenCTM mesh and save it to disk.
CTMcontext context = ctmNewContext(CTM_EXPORT);
ctmDefineMesh(context,
pCtmVertices,
dwVertexCount,
pCtmIndices,
dwTriangleCount,
pCtmNormals);
ctmSave(context, destFile);
ctmFreeContext(context);
// Free the OpenCTM buffers.
delete [] pCtmVertices;
delete [] pCtmIndices;
delete [] pCtmNormals;
}
Downloads
I wrote this tool with Visual Studio 2010. The code is covered by the WTFPL license.