virtual void createMesh( CMesh& mesh ) {
		assert( !mesh.isCreated() );

		int nverts = mParticleCount * 4;
		int ntris = mParticleCount * 2;

		CVertexFormat vformat(
			CVertexFormat::V_POSITION |
			CVertexFormat::V_NORMAL |
			CVertexFormat::COLOR_MASK
		);
		
		mesh.createResource( nverts, ntris*3, vformat, 2, *RGET_VDECL(vformat), CMesh::BUF_STATIC );

		unsigned short* pib = (unsigned short*)mesh.lockIBWrite();
		assert( pib );
		TVertex* pvb = (TVertex*)mesh.lockVBWrite();
		assert( pvb );

		for( int i = 0; i < mParticleCount; ++i ) {
			// IB
			pib[0] = i*4+0;
			pib[1] = i*4+1;
			pib[2] = i*4+2;
			pib[3] = i*4+0;
			pib[4] = i*4+2;
			pib[5] = i*4+3;
			pib += 6;
			// VB
			const SVector3& p = mParticles[i];
			D3DCOLOR color = mParticleColor[i];
			float ao = gRandom.getFloat();
			pvb[0].p = p;	pvb[0].n.x = -0.5f;	pvb[0].n.y = -0.5f;	pvb[0].n.z = ao; pvb[0].diffuse = color;
			pvb[1].p = p;	pvb[1].n.x =  0.5f;	pvb[1].n.y = -0.5f;	pvb[1].n.z = ao; pvb[1].diffuse = color;
			pvb[2].p = p;	pvb[2].n.x =  0.5f;	pvb[2].n.y =  0.5f;	pvb[2].n.z = ao; pvb[2].diffuse = color;
			pvb[3].p = p;	pvb[3].n.x = -0.5f;	pvb[3].n.y =  0.5f;	pvb[3].n.z = ao; pvb[3].diffuse = color;
			pvb += 4;
		}

		mesh.unlockIBWrite();
		mesh.unlockVBWrite();

		mesh.computeAABBs();
	}
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;
}