493 lines
13 KiB
C++
493 lines
13 KiB
C++
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "map2dif plus/lmapPacker.h"
|
|
#include "platform/platformAssert.h"
|
|
#include "math/mPoint.h"
|
|
|
|
/* for FN_CDECL defs */
|
|
#include "platform/types.h"
|
|
|
|
const U32 SheetManager::csm_sheetSize = 256;
|
|
|
|
SheetManager::SheetManager()
|
|
{
|
|
m_currSheet = -1;
|
|
m_currX = 0;
|
|
m_currY = 0;
|
|
m_lowestY = 0;
|
|
}
|
|
|
|
SheetManager::~SheetManager()
|
|
{
|
|
for (U32 i = 0; i < m_sheets.size(); i++)
|
|
{
|
|
delete m_sheets[i].pData;
|
|
m_sheets[i].pData = NULL;
|
|
}
|
|
|
|
m_currSheet = -1;
|
|
m_currX = 0;
|
|
m_currY = 0;
|
|
m_lowestY = 0;
|
|
}
|
|
|
|
void SheetManager::begin()
|
|
{
|
|
numPixels = 0;
|
|
numSheetPixels = 0;
|
|
}
|
|
|
|
void SheetManager::end()
|
|
{
|
|
repackBlock();
|
|
|
|
// dPrintf("\n\n"
|
|
// " Total Pixels: %d\n"
|
|
// " Total SheetP: %d\n"
|
|
// " Efficiency: %g\n\n", numPixels, numSheetPixels, F32(numPixels)/F32(numSheetPixels));
|
|
|
|
m_currY = m_lowestY = csm_sheetSize + 1;
|
|
m_currSheet = -1;
|
|
}
|
|
|
|
U32 SheetManager::enterLightMap(const GBitmap* lm)
|
|
{
|
|
U32 width = lm->getWidth() + (SG_LIGHTMAP_BORDER_SIZE * 2);
|
|
U32 height = lm->getHeight() + (SG_LIGHTMAP_BORDER_SIZE * 2);
|
|
|
|
numPixels += width * height;
|
|
|
|
if (m_currSheet < 0)
|
|
{
|
|
// Must initialize the sheets...
|
|
setupNewSheet();
|
|
}
|
|
|
|
if (m_currX + width >= csm_sheetSize)
|
|
{
|
|
// Carriage return...
|
|
m_currX = 0;
|
|
m_currY = m_lowestY;
|
|
}
|
|
|
|
if (m_currY + height >= csm_sheetSize)
|
|
{
|
|
// new sheet needed
|
|
setupNewSheet();
|
|
}
|
|
|
|
m_lightMaps.increment();
|
|
m_lightMaps.last().sheetId = m_currSheet;
|
|
m_lightMaps.last().x = m_currX;
|
|
m_lightMaps.last().y = m_currY;
|
|
m_lightMaps.last().width = width;
|
|
m_lightMaps.last().height = height;
|
|
|
|
// And place the lightmap...
|
|
//
|
|
AssertFatal(lm->bytesPerPixel == m_sheets[m_currSheet].pData->bytesPerPixel, "Um, bad mismatch of bitmap types...");
|
|
|
|
|
|
U32 y, b;
|
|
U32 pixoffset = lm->bytesPerPixel;
|
|
U32 borderwidth = SG_LIGHTMAP_BORDER_SIZE * 2;
|
|
U32 nonborderpixlen = (width - borderwidth) * pixoffset;
|
|
|
|
U32 lmheightindex = lm->getHeight() - 1;
|
|
U32 lmborderheightindex = lmheightindex + SG_LIGHTMAP_BORDER_SIZE;
|
|
U32 borderpixlen = pixoffset * SG_LIGHTMAP_BORDER_SIZE;
|
|
|
|
for(y=0; y<height; y++)
|
|
{
|
|
U8 *srun, *drun;
|
|
|
|
if(y < SG_LIGHTMAP_BORDER_SIZE)
|
|
srun = (U8 *)lm->getAddress(0, 0);
|
|
else if(y > lmborderheightindex)
|
|
srun = (U8 *)lm->getAddress(0, lmheightindex);
|
|
else
|
|
srun = (U8 *)lm->getAddress(0, (y - SG_LIGHTMAP_BORDER_SIZE));
|
|
|
|
drun = (U8 *)m_sheets[m_currSheet].pData->getAddress(m_currX, (m_currY + y));
|
|
|
|
dMemcpy(&drun[borderpixlen], srun, nonborderpixlen);
|
|
|
|
U8 *ss, *se;
|
|
ss = srun;
|
|
se = &srun[(nonborderpixlen - pixoffset)];
|
|
|
|
for(b=0; b<SG_LIGHTMAP_BORDER_SIZE; b++)
|
|
{
|
|
U32 i = b * pixoffset;
|
|
drun[i] = ss[0];
|
|
drun[i+1] = ss[1];
|
|
drun[i+2] = ss[2];
|
|
|
|
i = (lm->getWidth() + SG_LIGHTMAP_BORDER_SIZE + b) * pixoffset;
|
|
drun[i] = se[0];
|
|
drun[i+1] = se[1];
|
|
drun[i+2] = se[2];
|
|
}
|
|
}
|
|
|
|
m_currX += width;
|
|
if (m_currY + height > m_lowestY)
|
|
m_lowestY = m_currY + height;
|
|
|
|
return m_lightMaps.size() - 1;
|
|
}
|
|
|
|
const SheetManager::LightMapEntry &SheetManager::getLightmap(const S32 in_lightMapIndex) const
|
|
{
|
|
AssertFatal(U32(in_lightMapIndex) < m_lightMaps.size(), "Out of bounds lightmap");
|
|
|
|
return m_lightMaps[in_lightMapIndex];
|
|
}
|
|
|
|
SheetManager::LightMapEntry &SheetManager::getLightmapNC(const S32 in_lightMapIndex)
|
|
{
|
|
AssertFatal(U32(in_lightMapIndex) < m_lightMaps.size(), "Out of bounds lightmap");
|
|
|
|
return m_lightMaps[in_lightMapIndex];
|
|
}
|
|
|
|
|
|
void SheetManager::setupNewSheet()
|
|
{
|
|
m_sheets.increment();
|
|
m_currSheet = m_sheets.size() - 1;
|
|
|
|
m_sheets.last().pData = new GBitmap(csm_sheetSize, csm_sheetSize);
|
|
for (U32 y = 0; y < m_sheets.last().pData->getHeight(); y++)
|
|
{
|
|
for (U32 x = 0; x < m_sheets.last().pData->getWidth(); x++)
|
|
{
|
|
U8* pDst = m_sheets.last().pData->getAddress(x, y);
|
|
pDst[0] = 0xFF;
|
|
pDst[1] = 0x00;
|
|
pDst[2] = 0xFF;
|
|
}
|
|
}
|
|
|
|
|
|
m_currX = 0;
|
|
m_currY = 0;
|
|
m_lowestY = 0;
|
|
}
|
|
|
|
void SheetManager::repackBlock()
|
|
{
|
|
if (m_lightMaps.size() == 0)
|
|
return;
|
|
|
|
U32 currLightMapStart = 0;
|
|
U32 currLightMapEnd = m_lightMaps.size() - 1;
|
|
|
|
S32 first = 0;
|
|
U32 num = m_sheets.size();
|
|
|
|
// OK, so at this point, we have a block of sheets, and a block of lightmaps on
|
|
// that sheet. What we want to do is loop on the lightmaps until we have none
|
|
// left, and pack them into new sheets that are as small as possible.
|
|
//
|
|
do
|
|
{
|
|
const U32 numSizes = 16;
|
|
U32 sizes[numSizes][2] = {
|
|
{32, 32}, // 1024
|
|
{32, 64}, // 2048
|
|
{64, 32}, // 2048
|
|
{64, 64}, // 4096
|
|
{32, 128}, // 4096
|
|
{128, 32}, // 4096
|
|
{64, 128}, // 8192
|
|
{128, 64}, // 8192
|
|
{32, 256}, // 8192
|
|
{256, 32}, // 8192
|
|
{128, 128}, // 16384
|
|
{64, 256}, // 16384
|
|
{256, 64}, // 16384
|
|
{128, 256}, // 32768
|
|
{256, 128}, // 32768
|
|
{256, 256} // 65536
|
|
};
|
|
// const U32 numSizes = 4;
|
|
// U32 sizes[numSizes][2] = {
|
|
// {32, 32}, // 1024
|
|
// {64, 64}, // 4096
|
|
// {128, 128}, // 16384
|
|
// {256, 256} // 65536
|
|
// };
|
|
|
|
bool success = false;
|
|
for (U32 i = 0; i < numSizes; i++)
|
|
{
|
|
if (doesFit(currLightMapStart, currLightMapEnd, sizes[i][0], sizes[i][1]) == true)
|
|
{
|
|
// Everything left fits into a 32
|
|
//
|
|
numSheetPixels += sizes[i][0] * sizes[i][1];
|
|
repackSection(currLightMapStart, currLightMapEnd, sizes[i][0], sizes[i][1]);
|
|
currLightMapStart = currLightMapEnd;
|
|
success = true;
|
|
break;
|
|
}
|
|
}
|
|
if (success == false)
|
|
{
|
|
// BSearch for the max we can get into a 256, then keep going...
|
|
//
|
|
U32 searchStart = currLightMapStart;
|
|
U32 searchEnd = currLightMapEnd - 1;
|
|
|
|
while (searchStart != searchEnd)
|
|
{
|
|
U32 probe = (searchStart + searchEnd) >> 1;
|
|
|
|
if (doesFit(currLightMapStart, probe, 256, 256) == true)
|
|
{
|
|
if (searchStart != probe)
|
|
searchStart = probe;
|
|
else
|
|
searchEnd = searchStart;
|
|
}
|
|
else
|
|
{
|
|
if (searchEnd != probe)
|
|
searchEnd = probe;
|
|
else
|
|
searchEnd = searchStart;
|
|
}
|
|
}
|
|
|
|
numSheetPixels += 256*256;
|
|
repackSection(currLightMapStart, searchStart, 256, 256, true);
|
|
currLightMapStart = searchStart + 1;
|
|
}
|
|
} while (currLightMapStart < currLightMapEnd);
|
|
|
|
// All the sheets from the same block id are contigous, so all we have to do is
|
|
// decrement the sheetIds of the affected lightmaps...
|
|
//
|
|
for (U32 i = 0; i < m_lightMaps.size(); i++)
|
|
{
|
|
if (m_lightMaps[i].sheetId >= (first + num))
|
|
{
|
|
m_lightMaps[i].sheetId -= num;
|
|
}
|
|
}
|
|
|
|
for (U32 i = num; i != 0; i--)
|
|
{
|
|
SheetEntry& rEntry = m_sheets[first];
|
|
|
|
delete rEntry.pData;
|
|
rEntry.pData = NULL;
|
|
|
|
m_sheets.erase(first);
|
|
}
|
|
}
|
|
|
|
|
|
S32 FN_CDECL compY(const void* p1, const void* p2)
|
|
{
|
|
const Point2I* point1 = (const Point2I*)p1;
|
|
const Point2I* point2 = (const Point2I*)p2;
|
|
|
|
if (point1->y != point2->y)
|
|
return point2->y - point1->y;
|
|
else
|
|
return point2->x - point1->x;
|
|
}
|
|
|
|
|
|
SheetManager* g_pManager = NULL;
|
|
|
|
S32 FN_CDECL compMapY(const void* in_p1, const void* in_p2)
|
|
{
|
|
const S32* p1 = (const S32*)in_p1;
|
|
const S32* p2 = (const S32*)in_p2;
|
|
|
|
const SheetManager::LightMapEntry& rEntry1 = g_pManager->getLightmap(*p1);
|
|
const SheetManager::LightMapEntry& rEntry2 = g_pManager->getLightmap(*p2);
|
|
|
|
if (rEntry1.height != rEntry2.height)
|
|
return rEntry2.height - rEntry1.height;
|
|
else
|
|
return rEntry2.width - rEntry1.width;
|
|
}
|
|
|
|
void
|
|
SheetManager::repackSection(const S32 in_start,
|
|
const S32 in_end,
|
|
const U32 in_sizeX,
|
|
const U32 in_sizeY,
|
|
const bool overflow)
|
|
{
|
|
Vector<S32> sheetIndices;
|
|
for (S32 i = in_start; i <= in_end; i++)
|
|
sheetIndices.push_back(i);
|
|
|
|
g_pManager = this;
|
|
dQsort(sheetIndices.address(), sheetIndices.size(), sizeof(S32), compMapY);
|
|
g_pManager = NULL;
|
|
|
|
// Ok, we now have the list of entries that we'll be entering, in the correct
|
|
// order. Go for it! Create the "right-sized" sheet, and pack.
|
|
//
|
|
m_sheets.increment();
|
|
|
|
m_sheets.last().pData = new GBitmap(in_sizeX, in_sizeY);
|
|
for (U32 y = 0; y < m_sheets.last().pData->getHeight(); y++)
|
|
{
|
|
for (U32 x = 0; x < m_sheets.last().pData->getWidth(); x++)
|
|
{
|
|
U8* pDst = m_sheets.last().pData->getAddress(x, y);
|
|
|
|
if (overflow == false)
|
|
{
|
|
pDst[0] = 0xFF;
|
|
pDst[1] = 0x00;
|
|
pDst[2] = 0xFF;
|
|
}
|
|
else
|
|
{
|
|
pDst[0] = 0x00;
|
|
pDst[1] = 0xFF;
|
|
pDst[2] = 0x00;
|
|
}
|
|
}
|
|
}
|
|
|
|
U32 currX = 0;
|
|
U32 currY = 0;
|
|
U32 lowestY = 0;
|
|
|
|
while (sheetIndices.size() != 0)
|
|
{
|
|
LightMapEntry& rEntry = getLightmapNC(sheetIndices[0]);
|
|
|
|
S32 lastOfHeight = 0;
|
|
for (U32 j = 1; j < sheetIndices.size(); j++)
|
|
{
|
|
LightMapEntry& rEntryTest = getLightmapNC(sheetIndices[j]);
|
|
if (rEntryTest.height != rEntry.height)
|
|
break;
|
|
|
|
lastOfHeight = j;
|
|
}
|
|
|
|
S32 insert = -1;
|
|
for (S32 k = 0; k <= lastOfHeight; k++)
|
|
{
|
|
LightMapEntry& rEntryTest = getLightmapNC(sheetIndices[k]);
|
|
if (currX + rEntryTest.width < in_sizeX)
|
|
{
|
|
insert = k;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (insert == -1)
|
|
{
|
|
// CR
|
|
currY = lowestY;
|
|
currX = 0;
|
|
insert = 0;
|
|
}
|
|
LightMapEntry* pInsert = &getLightmapNC(sheetIndices[insert]);
|
|
AssertFatal(currY + pInsert->height < in_sizeY, "Error, too many lmaps for this size");
|
|
|
|
for (S32 y = 0; y < pInsert->height; y++)
|
|
{
|
|
const U8* pSrc = m_sheets[pInsert->sheetId].pData->getAddress(pInsert->x, (pInsert->y + y));
|
|
U8* pDst = m_sheets.last().pData->getAddress(currX, (currY + y));
|
|
|
|
dMemcpy(pDst, pSrc, pInsert->width * m_sheets[pInsert->sheetId].pData->bytesPerPixel);
|
|
}
|
|
|
|
pInsert->sheetId = m_sheets.size() - 1;
|
|
pInsert->x = currX;
|
|
pInsert->y = currY;
|
|
AssertFatal(pInsert->x + pInsert->width <= m_sheets.last().pData->getWidth(), "very bad");
|
|
AssertFatal(pInsert->y + pInsert->height <= m_sheets.last().pData->getHeight(), "also bad");
|
|
|
|
currX += pInsert->width;
|
|
if (currY + pInsert->height > lowestY)
|
|
lowestY = currY + pInsert->height;
|
|
|
|
sheetIndices.erase(insert);
|
|
}
|
|
}
|
|
|
|
|
|
bool SheetManager::doesFit(const S32 startMap,
|
|
const S32 endMap,
|
|
const U32 sizeX,
|
|
const U32 sizeY) const
|
|
{
|
|
Vector<Point2I> mapSizes;
|
|
mapSizes.setSize(endMap - startMap + 1);
|
|
|
|
for (S32 i = startMap; i <= endMap; i++)
|
|
{
|
|
mapSizes[i - startMap].x = m_lightMaps[i].width;
|
|
mapSizes[i - startMap].y = m_lightMaps[i].height;
|
|
|
|
if (m_lightMaps[i].width > sizeX ||
|
|
m_lightMaps[i].height > sizeY)
|
|
return false;
|
|
}
|
|
|
|
dQsort(mapSizes.address(), mapSizes.size(), sizeof(Point2I), compY);
|
|
|
|
U32 currX = 0;
|
|
U32 currY = 0;
|
|
U32 lowestY = 0;
|
|
|
|
while (mapSizes.size() != 0) {
|
|
S32 lastOfHeight = 0;
|
|
for (U32 j = 1; j < mapSizes.size(); j++) {
|
|
if (mapSizes[j].y != mapSizes[0].y)
|
|
break;
|
|
|
|
lastOfHeight = j;
|
|
}
|
|
|
|
S32 insert = -1;
|
|
for (S32 k = 0; k <= lastOfHeight; k++)
|
|
{
|
|
if (currX + mapSizes[k].x < sizeX)
|
|
{
|
|
insert = k;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (insert == -1)
|
|
{
|
|
// CR
|
|
currX = 0;
|
|
currY = lowestY;
|
|
insert = 0;
|
|
}
|
|
|
|
if (currY + mapSizes[insert].y >= sizeY)
|
|
{
|
|
// Failure.
|
|
return false;
|
|
}
|
|
|
|
currX += mapSizes[insert].x;
|
|
if (currY + mapSizes[insert].y > lowestY)
|
|
lowestY = currY + mapSizes[insert].y;
|
|
|
|
mapSizes.erase(insert);
|
|
}
|
|
|
|
return true;
|
|
}
|