// NeL - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
// lightmap_optimizer
// ------------------
// the goal is to regroup lightmap of a level into lightmap with a higher level
#include "nel/misc/common.h"
#include "nel/misc/file.h"
#include "nel/misc/bitmap.h"
#include "nel/misc/log.h"
#include "nel/misc/path.h"
#include "nel/misc/uv.h"
//#include "windows.h"
#include
#include
// ---------------------------------------------------------------------------
using namespace std;
using namespace NLMISC;
// ***************************************************************************
//char sExeDir[MAX_PATH];
std::string sExeDir;
NLMISC::CApplicationContext _ApplicationContext;
void outString (const string &sText)
{
std::string sCurDir = CPath::getCurrentPath();
CPath::setCurrentPath(sExeDir.c_str());
//char sCurDir[MAX_PATH];
//GetCurrentDirectory (MAX_PATH, sCurDir);
//SetCurrentDirectory (sExeDir);
NLMISC::createDebug ();
NLMISC::InfoLog->displayRaw(sText.c_str());
//SetCurrentDirectory (sCurDir);
CPath::setCurrentPath(sCurDir.c_str());
}
// ***************************************************************************
// test every 4 pixels for 2 reason: DXTC and speed
const uint32 posStep= 4;
// ***************************************************************************
// Try all position to put pSrc in pDst
bool tryAllPos (NLMISC::CBitmap *pSrc, NLMISC::CBitmap *pDst, sint32 &x, sint32 &y)
{
uint32 i, j;
CObjectVector &rSrcPix = pSrc->getPixels();
CObjectVector &rDstPix = pDst->getPixels();
// Recalculate real size of the source (without padding to power of 2)
uint32 nSrcWidth = pSrc->getWidth(), nSrcHeight = pSrc->getHeight();
if (nSrcWidth > pDst->getWidth() ) return false;
if (nSrcHeight > pDst->getHeight() ) return false;
// For all position test if the Src plane can be put in
for (j = 0; j <= (pDst->getHeight() - nSrcHeight); j+= posStep)
for (i = 0; i <= (pDst->getWidth() - nSrcWidth); i+= posStep)
{
x = i; y = j;
uint32 a, b;
bool bCanPut = true;
for (b = 0; b < nSrcHeight; ++b)
{
for (a = 0; a < nSrcWidth; ++a)
{
if (rDstPix[4*((x+a)+(y+b)*pDst->getWidth())+3] != 0)
{
bCanPut = false;
break;
}
}
if (bCanPut == false)
break;
}
if (bCanPut)
return true;
}
return false;
}
// ***************************************************************************
void putPixel(uint8 *dst, uint8 *src, bool alphaTransfert)
{
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
if (alphaTransfert)
dst[3] = src[3];
else
dst[3] = 255;
}
// ***************************************************************************
bool putIn (NLMISC::CBitmap *pSrc, NLMISC::CBitmap *pDst, sint32 x, sint32 y, bool alphaTransfert=true)
{
uint32 a, b;
uint8 *rSrcPix = &pSrc->getPixels()[0];
uint8 *rDstPix = &pDst->getPixels()[0];
uint wSrc= pSrc->getWidth();
uint hSrc= pSrc->getHeight();
for (b = 0; b < hSrc; ++b)
for (a = 0; a < wSrc; ++a)
{
if (rDstPix[4*((x+a)+(y+b)*pDst->getWidth())+3] != 0)
return false;
// write
putPixel(rDstPix + 4*((x+a)+(y+b)*pDst->getWidth()), rSrcPix+ 4*(a+b*pSrc->getWidth()), alphaTransfert);
}
// DXTC compression optim: fill last column block and last row block of 4 pixels with block color (don't let black or undefined)
uint wSrc4= 4*((wSrc+3)/4);
uint hSrc4= 4*((hSrc+3)/4);
// expand on W
if(wSrcgetWidth()), rDstPix + 4*((x+wSrc-1)+(y+b)*pDst->getWidth()), alphaTransfert);
}
}
}
// expand on H
if(hSrcgetWidth()), rDstPix + 4*((x+a)+(y+hSrc-1)*pDst->getWidth()), alphaTransfert);
}
}
}
return true;
}
// ***************************************************************************
string getBaseName (const string &fullname)
{
string sTmp2;
string::size_type pos = fullname.rfind('_');
if (pos != string::npos)
sTmp2 = fullname.substr(0, pos+1);
return sTmp2;
}
// ***************************************************************************
// resize the bitmap to the next power of 2 and preserve content
void enlargeCanvas (NLMISC::CBitmap &b)
{
sint32 nNewWidth = b.getWidth(), nNewHeight = b.getHeight();
if (nNewWidth > nNewHeight)
nNewHeight *= 2;
else
nNewWidth *= 2;
NLMISC::CBitmap b2;
b2.resize (nNewWidth, nNewHeight, NLMISC::CBitmap::RGBA);
CObjectVector &rPixelBitmap = b2.getPixels(0);
for (sint32 i = 0; i < nNewWidth*nNewHeight*4; ++i)
rPixelBitmap[i] = 0;
putIn (&b, &b2, 0, 0);
b = b2;
}
// ***************************************************************************
// main
// ***************************************************************************
int main(int nNbArg, char **ppArgs)
{
//GetCurrentDirectory (MAX_PATH, sExeDir);
sExeDir = CPath::getCurrentPath();
if (nNbArg < 3)
{
outString ("ERROR : Wrong number of arguments\n");
outString ("USAGE : build_interface [-s] [path_maps2] [path_maps3] ....\n");
outString (" -s : build a subset of an existing interface definition while preserving the existing texture ids,");
outString (" to support freeing up VRAM by switching to the subset without rebuilding the entire interface\n");
return -1;
}
// build as a subset of existing interface
bool buildSubset = false;
string existingUVfilename;
list inputDirs;
for ( uint i=1; (sint)i AllMapNames;
list::iterator it = inputDirs.begin();
list::iterator itEnd = inputDirs.end();
while( it != itEnd )
{
string sDir = *it++;
if( !CFile::isDirectory(sDir) )
{
outString (string("ERROR : directory ") + sDir + " does not exist\n");
return -1;
}
CPath::getPathContent(sDir, false, false, true, AllMapNames);
}
vector AllMaps;
sint32 i, j;
// Load all maps
sint32 mapSize = (sint32)AllMapNames.size();
AllMaps.resize( mapSize );
for( i = 0; i < mapSize; ++i )
{
try
{
NLMISC::CBitmap *pBtmp = new NLMISC::CBitmap;
NLMISC::CIFile inFile;
inFile.open( AllMapNames[i] );
pBtmp->load(inFile);
AllMaps[i] = pBtmp;
}
catch (NLMISC::Exception &e)
{
outString (string("ERROR :") + e.what());
return -1;
}
}
// Sort all maps by decreasing size
for (i = 0; i < mapSize-1; ++i)
for (j = i+1; j < mapSize; ++j)
{
NLMISC::CBitmap *pBI = AllMaps[i];
NLMISC::CBitmap *pBJ = AllMaps[j];
if ((pBI->getWidth()*pBI->getHeight()) < (pBJ->getWidth()*pBJ->getHeight()))
{
NLMISC::CBitmap *pBTmp = AllMaps[i];
AllMaps[i] = AllMaps[j];
AllMaps[j] = pBTmp;
string sTmp = AllMapNames[i];
AllMapNames[i] = AllMapNames[j];
AllMapNames[j] = sTmp;
}
}
// Place all maps into the global texture
NLMISC::CBitmap GlobalTexture, GlobalMask;
GlobalTexture.resize (1, 1, NLMISC::CBitmap::RGBA);
GlobalMask.resize (1, 1, NLMISC::CBitmap::RGBA);
CObjectVector &rPixelBitmap = GlobalTexture.getPixels(0);
rPixelBitmap[0] = rPixelBitmap[1] = rPixelBitmap[2] = rPixelBitmap[3] = 0;
CObjectVector &rPixelMask = GlobalMask.getPixels(0);
rPixelMask[0] = rPixelMask[1] = rPixelMask[2] = rPixelMask[3] = 0;
vector UVMin, UVMax;
UVMin.resize (mapSize, NLMISC::CUV(0.0f, 0.0f));
UVMax.resize (mapSize, NLMISC::CUV(0.0f, 0.0f));
for (i = 0; i < mapSize; ++i)
{
sint32 x, y;
while (!tryAllPos(AllMaps[i], &GlobalMask, x, y))
{
// Enlarge global texture
enlargeCanvas (GlobalTexture);
enlargeCanvas (GlobalMask);
}
putIn (AllMaps[i], &GlobalTexture, x, y);
putIn (AllMaps[i], &GlobalMask, x, y, false);
UVMin[i].U = (float)x;
UVMin[i].V = (float)y;
UVMax[i].U = (float)x+AllMaps[i]->getWidth();
UVMax[i].V = (float)y+AllMaps[i]->getHeight();
/* // Do not remove this is useful for debugging
{
NLMISC::COFile outTga;
string fmtName = ppArgs[1];
if (fmtName.rfind('.') == string::npos)
fmtName += ".tga";
if (outTga.open(fmtName))
{
GlobalTexture.writeTGA (outTga, 32);
outTga.close();
}
}
{
NLMISC::COFile outTga;
string fmtName = ppArgs[1];
if (fmtName.rfind('.') == string::npos)
fmtName += "_msk.tga";
else
fmtName = fmtName.substr(0,fmtName.rfind('.')) + "_msk.tga";
if (outTga.open(fmtName))
{
GlobalMask.writeTGA (outTga, 32);
outTga.close();
}
}*/
}
// Convert UV from pixel to ratio
for (i = 0; i < mapSize; ++i)
{
UVMin[i].U = UVMin[i].U / (float)GlobalTexture.getWidth();
UVMin[i].V = UVMin[i].V / (float)GlobalTexture.getHeight();
UVMax[i].U = UVMax[i].U / (float)GlobalTexture.getWidth();
UVMax[i].V = UVMax[i].V / (float)GlobalTexture.getHeight();
}
// Write global texture file
//SetCurrentDirectory (sExeDir);
CPath::setCurrentPath(sExeDir.c_str());
NLMISC::COFile outTga;
if (fmtName.rfind('.') == string::npos)
fmtName += ".tga";
if (outTga.open(fmtName))
{
std::string ext;
if (toLower(fmtName).find(".png") != string::npos)
{
ext = "png";
GlobalTexture.writePNG (outTga, 32);
}
else
{
ext = "tga";
GlobalTexture.writeTGA (outTga, 32);
}
outTga.close();
outString (toString("Writing %s file : %s\n", ext.c_str(), fmtName.c_str()));
}
else
{
outString (string("ERROR: Cannot write tga file : ") + fmtName + "\n");
}
// Write UV text file
if( !buildSubset )
{
fmtName = fmtName.substr(0, fmtName.rfind('.'));
fmtName += ".txt";
FILE *f = fopen (fmtName.c_str(), "wt");
if (f != NULL)
{
for (i = 0; i < mapSize; ++i)
{
// get the string whitout path
string fileName= CFile::getFilename(AllMapNames[i]);
fprintf (f, "%s %.12f %.12f %.12f %.12f\n", fileName.c_str(), UVMin[i].U, UVMin[i].V,
UVMax[i].U, UVMax[i].V);
}
fclose (f);
outString (string("Writing UV file : ") + fmtName + "\n");
}
else
{
outString (string("ERROR: Cannot write UV file : ") + fmtName + "\n");
}
}
else // build as a subset
{
// Load existing uv file
CIFile iFile;
string filename = CPath::lookup (existingUVfilename, false);
if( (filename == "") || (!iFile.open(filename)) )
{
outString (string("ERROR : could not open file ") + existingUVfilename + "\n");
return -1;
}
// Write subset UV text file
fmtName = fmtName.substr(0, fmtName.rfind('.'));
fmtName += ".txt";
FILE *f = fopen (fmtName.c_str(), "wt");
if (f == NULL)
{
outString (string("ERROR: Cannot write UV file : ") + fmtName + "\n");
// fclose (iFile);
return -1;
}
char bufTmp[256], tgaName[256];
string sTGAname;
float uvMinU, uvMinV, uvMaxU, uvMaxV;
while (!iFile.eof())
{
iFile.getline (bufTmp, 256);
if (sscanf (bufTmp, "%s %f %f %f %f", tgaName, &uvMinU, &uvMinV, &uvMaxU, &uvMaxV) != 5)
{
nlwarning("Can't parse %s", bufTmp);
continue;
}
sTGAname = toLower(string(tgaName));
string findTGAName;
for (i = 0; i < mapSize; ++i)
{
// get the string whitout path
findTGAName = toLower(CFile::getFilename(AllMapNames[i]));
if( findTGAName == sTGAname )
break;
}
if( i == mapSize )
{
// not present in subset: offset existing uv's to (0,0), preserving size
fprintf (f, "%s %.12f %.12f %.12f %.12f\n", sTGAname.c_str(), 0.0f, 0.0f, uvMaxU - uvMinU, uvMaxV - uvMinV);
}
else
{
// present in subset: use new uv's
fprintf (f, "%s %.12f %.12f %.12f %.12f\n", sTGAname.c_str(), UVMin[i].U, UVMin[i].V,
UVMax[i].U, UVMax[i].V);
}
}
// fclose (iFile);
fclose (f);
outString (string("Writing UV file : ") + fmtName + "\n");
}
return 0;
}