/* UVATlas.cpp Written by Matthew Fisher UVAtlas is used to break a mesh up into regions topologically equivalent to a disk, and pack these regions into a planar texture. */ #ifdef USE_D3D9 UVAtlas::UVAtlas() { _GutterHelper = NULL; } UVAtlas::~UVAtlas() { FreeMemory(); } void UVAtlas::FreeMemory() { _VertexRemapArray.FreeMemory(); _Cells.FreeMemory(); if(_GutterHelper) { _GutterHelper->Release(); _GutterHelper = NULL; } } void UVAtlas::MakeDescriptiveBitmap(Bitmap &Bmp) { Bmp.Allocate(_Cells.Cols(), _Cells.Rows()); for(UINT y = 0; y < Bmp.Height(); y++) { for(UINT x = 0; x < Bmp.Width(); x++) { const UVAtlasCell &curCell = _Cells(y, x); RGBColor &C = Bmp[y][x]; switch(curCell.TextelClass) { case 0: C = RGBColor::Black; break; case 1: C = RGBColor(Utility::Hash32(curCell.FaceIndex) % 256, Utility::Hash32(curCell.FaceIndex * 765 + 424) % 256, Utility::Hash32(curCell.FaceIndex * 431 + 861) % 256); break; case 2: case 4: C = RGBColor::Magenta; break; } } } } void UVAtlas::ApplyToTexture(LPDIRECT3DTEXTURE9 pTexture) { Assert(_GutterHelper != NULL, "Gutter helper not allocated"); _GutterHelper->ApplyGuttersTex(pTexture); } struct UVAtlasCallbackContext { UINT LastValue; }; HRESULT __stdcall UVAtlasCallback(FLOAT fPercentDone, LPVOID lpUserContext) { UVAtlasCallbackContext *Context = (UVAtlasCallbackContext *)lpUserContext; UINT CurValue = UINT(fPercentDone * 100.0f); if(CurValue > Context->LastValue) { if(Context->LastValue == 0) { Console::WriteLine(String("Percent done: ") + String(fPercentDone * 100.0f) + String("%")); } else { Console::OverwriteLine(String("Percent done: ") + String(fPercentDone * 100.0f) + String("%")); } Context->LastValue = CurValue; } return S_OK; } void UVAtlas::Create(const BaseMesh &MeshIn, BaseMesh &MeshOut, UINT TextureWidth, UINT TextureHeight, bool UseProvidedTexCoords, const String &FastLoadDirectory) { Console::WriteLine(String("Generating atlas for ") + FastLoadDirectory + String(", ") + String(MeshIn.VertexCount()) + String(" vertices")); Assert(MeshIn.VertexCount() > 0 && MeshIn.IndexCount() > 0, "UVAtlas called on empty mesh"); FreeMemory(); const UINT MaxCharts = 0; const float AllowedStretch = 0.25f; const float GutterSize = 3.0f; const float Tolerance = 0.0f; float MaxStretch; UINT NumCharts; D3D9Mesh MeshInCopy(MeshIn); LPD3DXMESH MeshOutCopy = NULL; bool FreeMeshOutCopy = false; D3D9Mesh PartitionedMesh(MeshIn.GetGD()); if(UseProvidedTexCoords) { MeshOutCopy = MeshInCopy.GetMesh(); MeshOut = MeshInCopy; } else { DWORD Hash = Utility::Hash32((BYTE *)MeshInCopy.Indices(), sizeof(DWORD) * MeshInCopy.IndexCount()) + Utility::Hash32((BYTE *)MeshInCopy.Vertices(), sizeof(MeshVertex) * MeshInCopy.VertexCount()) + Utility::Hash32(TextureWidth) + Utility::Hash32(TextureHeight); String DataFilename = FastLoadDirectory + String(Hash) + String(".dat"); String MeshFilename = FastLoadDirectory + String(Hash) + String(".x"); bool LoadFromFile = Utility::FileExists(DataFilename); if(LoadFromFile) { PartitionedMesh.LoadFromXFile(MeshFilename); MeshOutCopy = PartitionedMesh.GetMesh(); InputDataStream Stream; Stream.LoadFromFile(DataFilename); UINT Length; Stream >> Length; _VertexRemapArray.Allocate(Length); Stream.ReadData((BYTE *)_VertexRemapArray.CArray(), _VertexRemapArray.Length() * sizeof(UINT)); } else { FreeMeshOutCopy = true; DWORD *pAdjacency = new DWORD[MeshIn.IndexCount()]; //FLOAT *pfIMTArray; LPD3DXBUFFER FacePartitioningBuffer = NULL, VertexRemapArrayBuffer = NULL; D3DValidate(MeshInCopy.GetMesh()->GenerateAdjacency(Tolerance, pAdjacency), "GenerateAdjacency"); /*pFalseEdges = new DWORD[MeshIn.IndexCount()]; for(UINT i = 0; i < MeshIn.IndexCount(); i++) { pFalseEdges[i] = -1; }*/ UVAtlasCallbackContext Context; Context.LastValue = 0; D3DAlwaysValidate(D3DXUVAtlasCreate( MeshInCopy.GetMesh(), MaxCharts, AllowedStretch, TextureWidth, TextureHeight, GutterSize, 0, pAdjacency, NULL, NULL, UVAtlasCallback, 0.0001f, &Context, D3DXUVATLAS_DEFAULT, &MeshOutCopy, &FacePartitioningBuffer, &VertexRemapArrayBuffer, &MaxStretch, &NumCharts ), "D3DXUVAtlasCreate"); delete[] pAdjacency; //delete[] pFalseEdges; _VertexRemapArray.Allocate(MeshOutCopy->GetNumVertices()); memcpy(_VertexRemapArray.CArray(), VertexRemapArrayBuffer->GetBufferPointer(), VertexRemapArrayBuffer->GetBufferSize()); FacePartitioningBuffer->Release(); VertexRemapArrayBuffer->Release(); PartitionedMesh.LoadMesh(MeshOutCopy); if(FastLoadDirectory.Length() > 0) { PartitionedMesh.SaveToXFile(MeshFilename); OutputDataStream Stream; Stream << _VertexRemapArray.Length(); Stream.WriteData(_VertexRemapArray); Stream.SaveToFile(DataFilename); } } MeshOut = PartitionedMesh; } D3DAlwaysValidate(D3DXCreateTextureGutterHelper(TextureWidth, TextureHeight, MeshOutCopy, GutterSize / 2.0f, &_GutterHelper), "D3DXCreateTextureGutterHelper"); if(FreeMeshOutCopy) { MeshOutCopy->Release(); } Grid BarycentricCoordGrid(TextureHeight, TextureWidth); Grid TextelClassGrid(TextureHeight, TextureWidth); Grid FaceIndexGrid(TextureHeight, TextureWidth); D3DAlwaysValidate(_GutterHelper->GetBaryMap((D3DXVECTOR2 *)(BarycentricCoordGrid.CArray())), "GetBaryMap"); D3DAlwaysValidate(_GutterHelper->GetGutterMap(TextelClassGrid.CArray()), "GetGutterMap"); D3DAlwaysValidate(_GutterHelper->GetFaceMap(FaceIndexGrid.CArray()), "GetFaceMap"); _Cells.Allocate(TextureHeight, TextureWidth); for(UINT y = 0; y < TextureHeight; y++) { for(UINT x = 0; x < TextureWidth; x++) { UVAtlasCell &CurCell = _Cells(y, x); CurCell.BarycentricCoord = BarycentricCoordGrid(y, x); CurCell.TextelClass = TextelClassGrid(y, x); CurCell.FaceIndex = FaceIndexGrid(y, x); } } } #endif