Example #1
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;
}
Example #2
0
VCNNode* D3DConverter::ConvertMesh(const std::wstring& name, LPD3DXMESHCONTAINER baseMeshContainer,
	D3DXFRAME* frameRoot, ID3DXAnimationController* animController, LPDIRECT3DDEVICE9 device)
{
	MultiAnimMC* meshContainer = static_cast<MultiAnimMC*>(baseMeshContainer);
	ID3DXMesh* systemMesh = meshContainer->MeshData.pMesh;

	// Load vertex caches
	//
	DWORD meshFVF = systemMesh->GetFVF();
	size_t vertexCount = systemMesh->GetNumVertices();
	const DWORD stride = D3DXGetFVFVertexSize( meshFVF );
	const DWORD normalStride = D3DXGetFVFVertexSize( D3DFVF_NORMAL );
	const DWORD diffuseStride = D3DXGetFVFVertexSize( D3DFVF_DIFFUSE );
	const DWORD textureStride = D3DXGetFVFVertexSize( D3DFVF_TEX1 );

	std::vector<VCNFloat> vtPositionBuffer( vertexCount * kCacheStrides[VT_POSITION] );
	std::vector<VCNFloat> vtBlendWeights( vertexCount * kCacheStrides[VT_BLENDWEIGHTS] ); //TODO Verify the size of this shit
	std::vector<DWORD> vtBlendIndices( vertexCount * kCacheStrides[VT_BLENDINDICES] ); //TODO Verify the size of this shit

	std::vector<VCNFloat> vtNormalBuffer( vertexCount * kCacheStrides[VT_LIGHTING] );
	std::vector<VCNFloat> vtTextureBuffer( vertexCount * kCacheStrides[VT_DIFFUSE_TEX_COORDS] );

	VCNFloat* vtPositionBuf = &vtPositionBuffer[0];
	VCNFloat* vtBlendWeightBuf = &vtBlendWeights[0];
	DWORD* vtBlendIndicesBuf = &vtBlendIndices[0];

	VCNFloat* vtNormalBuf = &vtNormalBuffer[0];
	VCNFloat* vtTextureBuf = &vtTextureBuffer[0];

	BYTE* vbptr = NULL;
	BYTE* vblineptr = NULL;
	systemMesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*)&vblineptr);

	DWORD positionBlendAndIndicesStride = GetPositionStride(meshFVF);

	for(VCNUInt i = 0; i < vertexCount; ++i)
	{
		vbptr = vblineptr;

		if ( ContainsPositionInformation(meshFVF) )
		{
			// Read position
			float* posData = (float*)vbptr;
			*vtPositionBuf = posData[0]; vtPositionBuf++;
			*vtPositionBuf = posData[1]; vtPositionBuf++;
			*vtPositionBuf = posData[2]; vtPositionBuf++;

			if (ContainsBlending(meshFVF))
			{
				// Get blend weights
				size_t blendCount = (positionBlendAndIndicesStride / 4) - 3 - 1; // -3 to remove xyz, -1 to remove indices which come after
				for(size_t i = 0; i < blendCount; ++i)
				{
					*vtBlendWeightBuf = posData[3 + i]; vtBlendWeightBuf++;
				}
				vtBlendWeightBuf += 4 - blendCount; //each item is an array of 4 floats

				// Get blend indices
				// TODO SKIN Check the format we have to send this data as.
				if ( ContainsFlag(meshFVF, D3DFVF_LASTBETA_UBYTE4) )
				{
					*vtBlendIndicesBuf = ((DWORD*)vbptr)[3 + blendCount]; vtBlendIndicesBuf++;
				}
			}

			vbptr += positionBlendAndIndicesStride;
		}
		else
		{
			VCN_ASSERT_FAIL( VCNTXT("Mesh FVF not supported (no vertex position) [FVF = %d, stride = %d]"), meshFVF, stride );
		}

		// Read normal
		if ( ContainsFlag(meshFVF, D3DFVF_NORMAL) )
		{
			D3DXVECTOR3* normal = (D3DXVECTOR3*)(vbptr);
			*vtNormalBuf = normal->x; vtNormalBuf++;
			*vtNormalBuf = normal->y; vtNormalBuf++;
			*vtNormalBuf = normal->z; vtNormalBuf++;

			// Set default diffuse color
			std::fill(vtNormalBuf, vtNormalBuf+3, 1.0f); vtNormalBuf += 3;

			vbptr += normalStride;
		}
		else
		{
			VCN_ASSERT_FAIL( VCNTXT("Mesh FVF not supported (no normals) [FVF = %d, stride = %d]"), meshFVF, stride );
		}

		if ( ContainsFlag(meshFVF, D3DFVF_DIFFUSE) ) vbptr += diffuseStride;

		// Read texcoords
		// the check with D3DFVF_TEX0 is pretty useless as it's always true... the flag value is 0...
		if ( ContainsFlag(meshFVF, D3DFVF_TEX0) || ContainsFlag(meshFVF, D3DFVF_TEX1) )
		{
			float* texCoords = (float*)(vbptr);
			*vtTextureBuf = texCoords[0]; vtTextureBuf++;
			*vtTextureBuf = texCoords[1]; vtTextureBuf++;

			vbptr += textureStride;
		}
		else
		{
			VCN_ASSERT_FAIL( VCNTXT("Mesh FVF not supported (no texture coordinates) [FVF = %d, stride = %d]"), meshFVF, stride );
		}

		vblineptr += stride;
	}
	systemMesh->UnlockVertexBuffer();

	VCND3D9* renderer = VCNRenderCore::GetInstance()->Cast<VCND3D9>();

	// Generate cache resources that will be bind to Vicuna's meshes
	VCNResID positionCache = renderer->CreateCache(VT_POSITION, &vtPositionBuffer[0], vertexCount * kCacheStrides[VT_POSITION]);
	VCNResID lightingCache = renderer->CreateCache(VT_LIGHTING, &vtNormalBuffer[0], vertexCount * kCacheStrides[VT_LIGHTING]);
	VCNResID textureCache = renderer->CreateCache(VT_DIFFUSE_TEX_COORDS, &vtTextureBuffer[0], vertexCount * kCacheStrides[VT_DIFFUSE_TEX_COORDS]);

	VCNResID blendWeightCache = renderer->CreateCache(VT_BLENDWEIGHTS, &vtBlendWeights[0], vertexCount * kCacheStrides[VT_BLENDWEIGHTS]);
	VCNResID blendIndiceCache = renderer->CreateCache(VT_BLENDINDICES, &vtBlendIndices[0], vertexCount * kCacheStrides[VT_BLENDINDICES]);

	// Get model faces
	//
	VCNUShort* ibptr = 0;
	std::vector<VCNUShort> indices( systemMesh->GetNumFaces() * 3 );
	systemMesh->LockIndexBuffer(D3DLOCK_READONLY, (LPVOID*)&ibptr);
	for(VCNUInt i = 0; i < systemMesh->GetNumFaces(); i++)
	{
		indices[(i * 3) + 0] = *(ibptr++);
		indices[(i * 3) + 1] = *(ibptr++);
		indices[(i * 3) + 2] = *(ibptr++);
	}
	systemMesh->UnlockIndexBuffer();

	// Load materials
	//
	std::vector<VCNResID> materialIDS;
	D3DXMATERIAL* d3dxMaterials = meshContainer->pMaterials;
	for (DWORD i = 0; i < meshContainer->NumMaterials; ++i)
	{
		VCNResID materialID = kInvalidResID;

		// Create the texture if it exists - it may not
		if ( d3dxMaterials[i].pTextureFilename )
		{
			VCNResID textureID = kInvalidResID;

			VCNString texturePath = VCNTXT("Textures/");
			texturePath += VCN_A2W(d3dxMaterials[i].pTextureFilename);

			// Check if the texture is already loaded
			VCND3D9Texture* resTexture = VCNResourceCore::GetInstance()->GetResource<VCND3D9Texture>(texturePath);
			if ( !resTexture )
			{
				textureID = VCNMaterialCore::GetInstance()->CreateTexture(texturePath);
				VCN_ASSERT_MSG( textureID != kInvalidResID, VCNTXT("Can't load texture %s"), texturePath.c_str() );
			}
			else
			{
				textureID = resTexture->GetResourceID();
			}

			VCNMaterial* material = new VCNMaterial();
			const VCNString materialName = StringBuilder() << name << VCNTXT("_material_") << i;
			material->SetName( materialName );
			VCNColor ambient = VCNColor((const VCNFloat*)&d3dxMaterials[i].MatD3D.Ambient);
			ambient.a = 1.0f;
			ambient += VCNColor(0.5f, 0.5f, 0.5f, 0);

			material->SetAmbientColor( ambient );
			material->SetDiffuseColor( VCNColor((const VCNFloat*)&d3dxMaterials[i].MatD3D.Diffuse) );
			material->SetSpecularColor( VCNColor((const VCNFloat*)&d3dxMaterials[i].MatD3D.Specular) );
			material->SetSpecularPower( d3dxMaterials[i].MatD3D.Power );

			VCNEffectParamSet& params = material->GetEffectParamSet();
			params.SetEffectID( eidSkinned );
			params.AddResource( VCNTXT("DiffuseTexture"), textureID );

			// Add material as a resource.
			materialID = VCNResourceCore::GetInstance()->AddResource( material->GetName(), material );
		}

		materialIDS.push_back( materialID );
	}

	// Get the model attribute table with which we will instantiate has many mesh.
	//
	DWORD attribTableSize;
	std::vector<D3DXATTRIBUTERANGE> attribTable;
	HRESULT hr = systemMesh->GetAttributeTable( 0, &attribTableSize );
	if ( FAILED(hr) )
		return 0;
	attribTable.resize( attribTableSize );
	hr = systemMesh->GetAttributeTable( &attribTable[0], &attribTableSize );
	if ( FAILED(hr) )
		return 0;

	// Set the root node
	VCNNode* rootNode = attribTableSize > 1 ?  VCNNodeCore::GetInstance()->CreateNode<VCNNode>() : 
		VCNNodeCore::GetInstance()->CreateNode<VCNRenderNode>();

	rootNode->SetTag( StringBuilder() << name << VCNTXT("_Root") );

	// For each attribute, we get the material texture
	for (DWORD i = 0; i < attribTableSize; ++i)
	{
		VCNRenderNode* partNode = attribTableSize == 1 ? 
			safe_pointer_cast<VCNRenderNode*>( rootNode ) : 
		VCNNodeCore::GetInstance()->CreateNode<VCNRenderNode>();

		const VCNString partNodeName = StringBuilder() << name << VCNTXT("_Part_") << i;
		partNode->SetTag( partNodeName );

		VCNMesh* partMesh = new VCNMesh();
		partMesh->SetCacheID(VT_POSITION, positionCache); //SKIN do this for blend weights and blend indices
		partMesh->SetCacheID(VT_LIGHTING, lightingCache);
		partMesh->SetCacheID(VT_DIFFUSE_TEX_COORDS, textureCache);
		partMesh->SetCacheID(VT_BLENDWEIGHTS, blendWeightCache);
		partMesh->SetCacheID(VT_BLENDINDICES, blendIndiceCache);
		partMesh->SetPrimitiveType(PT_TRIANGLELIST);

		partMesh->SetBoneInfluenceCount( meshContainer->m_dwMaxNumFaceInfls );
		size_t numBones = meshContainer->pSkinInfo == nullptr ? 0 : meshContainer->pSkinInfo->GetNumBones();
		if (numBones > 0)
		{
			auto offsets = std::vector<Matrix4>(numBones);
			std::transform( std::begin(meshContainer->m_amxBoneOffsets), std::end(meshContainer->m_amxBoneOffsets), std::begin(offsets), [](const D3DXMATRIX& mat)
			{
				return Matrix4( (VCNFloat*)mat.m );
			});

			partMesh->SetBoneOffsets( std::move(offsets) );

			LPD3DXBONECOMBINATION boneCombination = reinterpret_cast<LPD3DXBONECOMBINATION>( 
				meshContainer->m_pBufBoneCombos->GetBufferPointer() );

			size_t numPaletteEntries = meshContainer->m_dwNumPaletteEntries;

			std::vector<size_t> matriceIndexes;

			for(size_t paletteIndex = 0; paletteIndex < numPaletteEntries; ++paletteIndex)
			{
				size_t matIndex = boneCombination[i].BoneId[paletteIndex];
				if ( matIndex == std::numeric_limits<size_t>::max())
					continue;

				matriceIndexes.push_back(matIndex);
			}

			partMesh->SetMatrixPaletteIndexes(matriceIndexes);
		}

		const DWORD partFaceCount = attribTable[i].FaceCount;
		const void* partFaceBufferStart = &indices[attribTable[i].FaceStart * 3];
		const VCNResID indexCacheID = renderer->CreateCache(VT_INDEX, partFaceBufferStart, partFaceCount * 3 * kCacheStrides[VT_INDEX]);
		partMesh->SetFaceCount( attribTable[i].FaceCount );
		partMesh->SetFaceCache( indexCacheID );

		// Compute bounding sphere
		float radius;
		D3DXVECTOR3 center;
		D3DXComputeBoundingSphere( (D3DXVECTOR3*)(&vtPositionBuffer[0] + attribTable[i].VertexStart * 3), 
			attribTable[i].VertexCount, stride, &center, &radius );
		VCNSphere modelBoundSphere( radius, V2V<Vector3>(center) );
		partMesh->SetBoundingSphere( modelBoundSphere );

		// Add mesh resource
		const VCNString partMeshName = StringBuilder() << name << VCNTXT("_part_") << i;
		const VCNResID partMeshID = VCNResourceCore::GetInstance()->AddResource( partMeshName, partMesh );

		// Set model part node attributes
		partNode->SetMeshID( partMeshID );
		if (animController && numBones > 0)
		{
			partNode->AddComponent( new VCND3DAnimator(partMeshID, animController, frameRoot, meshContainer->m_apmxBonePointers) );
		}

		size_t index = attribTable[i].AttribId;
		index = index >= materialIDS.size() ? materialIDS.size() - 1 : index;

		partNode->SetMaterialID( materialIDS[index] );

		// Add children to root
		if ( attribTableSize > 1 )
		{
			rootNode->AttachChild( partNode->GetNodeID() );
		}
	}

	return rootNode;
}
bool CMeshBundle::loadMesh( const CResourceId& id, const CResourceId& fullName, CMesh& mesh ) const
{
	// try to load with D3DX
	// obsolete case: .X files
	if( CStringHelper::endsWith( fullName.getUniqueName(), ".x" ) || CStringHelper::endsWith( fullName.getUniqueName(), ".X" ) ) {
		ID3DXBuffer* adjancency = NULL;
		ID3DXBuffer* material = NULL;
		ID3DXBuffer* effects = NULL;
		DWORD matCount;
		ID3DXMesh* dxmesh = NULL;

		HRESULT hres = D3DXLoadMeshFromX(
			fullName.getUniqueName().c_str(),
			D3DXMESH_SYSTEMMEM,
			&CD3DDevice::getInstance().getDevice(),
			&adjancency,
			&material,
			&effects,
			&matCount,
			&dxmesh );
		if( !SUCCEEDED( hres ) )
			return false;
		assert( dxmesh );

		if( adjancency )
			adjancency->Release();
		if( material )
			material->Release();
		if( effects )
			effects->Release();

		//
		// init our mesh

		assert( !mesh.isCreated() );
		// HACK - very limited
		int formatFlags = 0;
		DWORD dxFormat = dxmesh->GetFVF();
		if( dxFormat & D3DFVF_XYZ )
			formatFlags |= CVertexFormat::V_POSITION;
		if( dxFormat & D3DFVF_NORMAL )
			formatFlags |= CVertexFormat::V_NORMAL;
		if( dxFormat & D3DFVF_TEX1 )
			formatFlags |= CVertexFormat::V_UV0_2D;
		CVertexFormat vertFormat( formatFlags );
		// HACK
		int indexStride = 2;

		CD3DVertexDecl* vertDecl = RGET_VDECL( CVertexDesc( vertFormat ) );
		mesh.createResource( dxmesh->GetNumVertices(), dxmesh->GetNumFaces()*3, vertFormat, indexStride, *vertDecl, CMesh::BUF_STATIC );

		//
		// now, copy data into our mesh

		void *dxvb, *dxib;
		dxmesh->LockVertexBuffer( 0, &dxvb );
		dxmesh->LockIndexBuffer( 0, &dxib );
		void* myvb = mesh.lockVBWrite();
		void* myib = mesh.lockIBWrite();

		memcpy( myvb, dxvb, mesh.getVertexCount() * mesh.getVertexStride() );
		memcpy( myib, dxib, mesh.getIndexCount() * mesh.getIndexStride() );
		
		dxmesh->UnlockVertexBuffer();
		dxmesh->UnlockIndexBuffer();
		mesh.unlockVBWrite();
		mesh.unlockIBWrite();

		//
		// create groups

		int ngroups;
		dxmesh->GetAttributeTable( 0, (DWORD*)&ngroups );
		D3DXATTRIBUTERANGE *attrs = new D3DXATTRIBUTERANGE[ngroups];
		dxmesh->GetAttributeTable( attrs, (DWORD*)&ngroups );
		for( int i = 0; i < ngroups; ++i ) {
			const D3DXATTRIBUTERANGE& a = attrs[i];
			mesh.addGroup( CMesh::CGroup( a.VertexStart, a.VertexCount, a.FaceStart, a.FaceCount ) );
		}
		delete[] attrs;

		// release d3dx mesh
		dxmesh->Release();

	} else {

		// our own format
		assert( !mesh.isCreated() );
		bool ok = CMeshSerializer::loadMeshFromFile( fullName.getUniqueName().c_str(), mesh );
		if( !ok )
			return false;
	}
	mesh.computeAABBs();
	CONSOLE.write( "mesh loaded '" + id.getUniqueName() + "'" );
	return true;
}