Update lua versions
[ryzomcore.git] / nel / tools / 3d / build_interface / main.cpp
blob1d3c518b978f6ea4380b5f733b25a91dbcc21993
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 // lightmap_optimizer
21 // ------------------
22 // the goal is to regroup lightmap of a level into lightmap with a higher level
24 #include "nel/misc/common.h"
25 #include "nel/misc/file.h"
26 #include "nel/misc/bitmap.h"
27 #include "nel/misc/log.h"
28 #include "nel/misc/path.h"
29 #include "nel/misc/uv.h"
30 #include "nel/misc/cmd_args.h"
32 #include <vector>
33 #include <string>
35 // ---------------------------------------------------------------------------
37 using namespace std;
38 using namespace NLMISC;
40 // ***************************************************************************
41 void outString(const string &sText)
43 printf("%s\n", sText.c_str());
46 // ***************************************************************************
47 // test every 4 pixels for 2 reason: DXTC and speed
48 const uint32 posStep= 4;
50 // ***************************************************************************
51 // Try all position to put pSrc in pDst
52 bool tryAllPos(NLMISC::CBitmap *pSrc, NLMISC::CBitmap *pDst, sint32 &x, sint32 &y)
54 uint32 i, j;
55 CObjectVector<uint8> &rSrcPix = pSrc->getPixels();
56 CObjectVector<uint8> &rDstPix = pDst->getPixels();
58 // Recalculate real size of the source (without padding to power of 2)
59 uint32 nSrcWidth = pSrc->getWidth(), nSrcHeight = pSrc->getHeight();
61 if (nSrcWidth > pDst->getWidth() ) return false;
62 if (nSrcHeight > pDst->getHeight() ) return false;
64 // For all position test if the Src plane can be put in
65 for (j = 0; j <= (pDst->getHeight() - nSrcHeight); j+= posStep)
66 for (i = 0; i <= (pDst->getWidth() - nSrcWidth); i+= posStep)
68 x = i; y = j;
70 uint32 a, b;
71 bool bCanPut = true;
72 for (b = 0; b < nSrcHeight; ++b)
74 for (a = 0; a < nSrcWidth; ++a)
76 if (rDstPix[4*((x+a)+(y+b)*pDst->getWidth())+3] != 0)
78 bCanPut = false;
79 break;
82 if (bCanPut == false)
83 break;
85 if (bCanPut)
86 return true;
88 return false;
91 // ***************************************************************************
92 void putPixel(uint8 *dst, uint8 *src, bool alphaTransfert)
94 dst[0] = src[0];
95 dst[1] = src[1];
96 dst[2] = src[2];
97 if (alphaTransfert)
98 dst[3] = src[3];
99 else
100 dst[3] = 255;
103 // ***************************************************************************
104 bool putIn(NLMISC::CBitmap *pSrc, NLMISC::CBitmap *pDst, sint32 x, sint32 y, bool alphaTransfert=true)
106 uint8 *rSrcPix = &pSrc->getPixels()[0];
107 uint8 *rDstPix = &pDst->getPixels()[0];
109 uint wSrc= pSrc->getWidth();
110 uint hSrc= pSrc->getHeight();
111 for (uint b = 0; b < hSrc; ++b)
112 for (uint a = 0; a < wSrc; ++a)
114 if (rDstPix[4*((x+a)+(y+b)*pDst->getWidth())+3] != 0)
115 return false;
117 // write
118 putPixel(rDstPix + 4*((x+a)+(y+b)*pDst->getWidth()), rSrcPix+ 4*(a+b*pSrc->getWidth()), alphaTransfert);
121 // DXTC compression optim: fill last column block and last row block of 4 pixels with block color (don't let black or undefined)
122 uint wSrc4= 4*((wSrc+3)/4);
123 uint hSrc4= 4*((hSrc+3)/4);
124 // expand on W
125 if(wSrc<wSrc4)
127 for(uint a=wSrc;a<wSrc4;a++)
129 for(uint b=0;b<hSrc4;b++)
131 putPixel(rDstPix + 4*((x+a)+(y+b)*pDst->getWidth()), rDstPix + 4*((x+wSrc-1)+(y+b)*pDst->getWidth()), alphaTransfert);
135 // expand on H
136 if(hSrc<hSrc4)
138 for(uint b=hSrc;b<hSrc4;b++)
140 for(uint a=0;a<wSrc4;a++)
142 putPixel(rDstPix + 4*((x+a)+(y+b)*pDst->getWidth()), rDstPix + 4*((x+a)+(y+hSrc-1)*pDst->getWidth()), alphaTransfert);
147 return true;
150 // ***************************************************************************
151 string getBaseName(const string &fullname)
153 string basename;
154 string::size_type pos = fullname.rfind('_');
155 if (pos != string::npos) basename = fullname.substr(0, pos+1);
156 return basename;
159 // ***************************************************************************
160 // resize the bitmap to the next power of 2 and preserve content
161 void enlargeCanvas(NLMISC::CBitmap &b)
163 sint32 nNewWidth = b.getWidth(), nNewHeight = b.getHeight();
164 if (nNewWidth > nNewHeight)
165 nNewHeight *= 2;
166 else
167 nNewWidth *= 2;
169 NLMISC::CBitmap b2;
170 b2.resize (nNewWidth, nNewHeight, NLMISC::CBitmap::RGBA);
172 CObjectVector<uint8> &rPixelBitmap = b2.getPixels(0);
173 for (sint32 i = 0; i < nNewWidth*nNewHeight*4; ++i)
174 rPixelBitmap[i] = 0;
176 putIn (&b, &b2, 0, 0);
177 b = b2;
180 bool writeFileDependingOnFilename(const std::string &filename, CBitmap &bitmap)
182 NLMISC::COFile out;
184 if (out.open(filename))
186 if (toLowerAscii(filename).find(".png") != string::npos)
188 bitmap.writePNG(out, 32);
190 else
192 bitmap.writeTGA(out, 32);
195 out.close();
197 return true;
200 return false;
204 // ***************************************************************************
205 // main
206 // ***************************************************************************
207 int main(int argc, char **argv)
209 CApplicationContext applicationContext;
211 // Parse Command Line.
212 NLMISC::CCmdArgs args;
214 args.setDescription("Build a huge interface texture from several small elements to optimize video memory usage.");
215 args.addArg("f", "format", "format", "Output format (png or tga)");
216 args.addArg("s", "subset", "existing_uv_txt_name", "Build a subset of an existing interface definition while preserving the existing texture ids, to support freeing up VRAM by switching to the subset without rebuilding the entire interface.");
217 args.addArg("x", "extract", "", "Extract all interface elements from <output_filename> to <input_path>.");
218 args.addAdditionalArg("output_filename", "PNG or TGA file to generate", true);
219 args.addAdditionalArg("input_path", "Path that containts interfaces elements", false);
220 args.addArg("", "no-border", "", "Disable border duplication. Enabled by default");
222 if (!args.parse(argc, argv)) return 1;
224 // build as a subset of existing interface
225 bool buildSubset = false;
226 string existingUVfilename;
228 if (args.haveArg("s"))
230 buildSubset = true;
231 existingUVfilename = args.getArg("s").front();
235 uint borderSize = 1;
236 if (args.haveLongArg("no-border"))
238 borderSize = 0;
241 // extract all interface elements
242 bool extractElements = args.haveArg("x");
244 // output format
245 std::string outputFormat;
247 if (args.haveArg("f"))
249 outputFormat = args.getArg("f").front();
251 if (outputFormat != "png" && outputFormat != "tga")
253 outString(toString("ERROR: Format %s not supported, only png and tga formats are", outputFormat.c_str()));
254 return -1;
258 std::vector<std::string> inputDirs = args.getAdditionalArg("input_path");
260 string fmtName = args.getAdditionalArg("output_filename").front();
262 // append PNG extension if no one provided
263 if (fmtName.rfind('.') == string::npos) fmtName += "." + (outputFormat.empty() ? "png":outputFormat);
265 if (extractElements)
267 if (inputDirs.empty())
269 outString(toString("ERROR: No input directories specified"));
270 return -1;
273 // name of UV file
274 existingUVfilename = fmtName.substr(0, fmtName.rfind('.'));
275 existingUVfilename += ".txt";
277 // Load existing UV file
278 CIFile iFile;
279 string filename = CPath::lookup(existingUVfilename, false);
281 if (filename.empty() || !iFile.open(filename))
283 outString(toString("ERROR: Unable to open %s", existingUVfilename.c_str()));
284 return -1;
287 // Load existing bitmap file
288 CIFile bitmapFile;
290 if (!bitmapFile.open(fmtName))
292 outString(toString("ERROR: Unable to open %s", fmtName.c_str()));
293 return -1;
296 // load bitmap
297 CBitmap textureBitmap;
298 uint8 colors = textureBitmap.load(bitmapFile);
300 // file already loaded in memory, close it
301 bitmapFile.close();
303 if (colors != 32)
305 outString(toString("ERROR: %s is not a RGBA bitmap", existingUVfilename.c_str()));
306 return -1;
309 // make sure transparent pixels are black
310 textureBitmap.makeTransparentPixelsBlack();
312 float textureWidth = (float)textureBitmap.getWidth();
313 float textureHeight = (float)textureBitmap.getHeight();
315 char bufTmp[256], tgaName[256];
316 string sTGAname;
317 float uvMinU, uvMinV, uvMaxU, uvMaxV;
318 while (!iFile.eof())
320 iFile.getline(bufTmp, 256);
322 if (sscanf(bufTmp, "%s %f %f %f %f", tgaName, &uvMinU, &uvMinV, &uvMaxU, &uvMaxV) != 5)
324 nlwarning("Can't parse %s", bufTmp);
325 continue;
328 float xf = uvMinU * textureWidth;
329 float yf = uvMinV * textureHeight;
330 float widthf = (uvMaxU - uvMinU) * textureWidth;
331 float heightf = (uvMaxV - uvMinV) * textureHeight;
333 uint x = (uint)xf;
334 uint y = (uint)yf;
335 uint width = (uint)widthf;
336 uint height = (uint)heightf;
338 if ((float)x != xf || (float)y != yf || (float)width != widthf || (float)height != heightf)
340 nlwarning("Wrong round");
343 if (width && height)
345 // create bitmap
346 CBitmap bitmap;
347 bitmap.resize(width, height);
348 bitmap.blit(textureBitmap, x, y, width, height, 0, 0);
350 sTGAname = inputDirs.front() + "/" + tgaName;
352 // force specific format instead of using original one
353 if (!outputFormat.empty())
355 sTGAname = sTGAname.substr(0, sTGAname.rfind('.'));
356 sTGAname += "." + outputFormat;
359 // write the file
360 if (writeFileDependingOnFilename(sTGAname, bitmap))
362 outString(toString("Writing file %s", sTGAname.c_str()));
364 else
366 outString(toString("Unable to writing file %s", sTGAname.c_str()));
369 else
371 outString(toString("Bitmap with wrong size"));
375 return 0;
378 vector<string> AllMapNames;
379 vector<string>::iterator it = inputDirs.begin(), itEnd = inputDirs.end();
381 while( it != itEnd )
383 string sDir = *it++;
385 if( !CFile::isDirectory(sDir) )
387 outString(toString("ERROR: directory %s does not exist", sDir.c_str()));
388 return -1;
391 CPath::getPathContent(sDir, false, false, true, AllMapNames);
394 vector<NLMISC::CBitmap*> AllMaps;
395 sint32 j;
397 // Load all maps
398 sint32 mapSize = (sint32)AllMapNames.size();
399 AllMaps.resize( mapSize );
400 for(sint i = 0; i < mapSize; ++i )
402 NLMISC::CBitmap *pBtmp = NULL;
406 pBtmp = new NLMISC::CBitmap;
407 NLMISC::CIFile inFile;
409 if (!inFile.open(AllMapNames[i])) throw NLMISC::Exception(toString("Unable to open %s", AllMapNames[i].c_str()));
411 uint8 colors = pBtmp->load(inFile);
413 if (!colors) throw NLMISC::Exception(toString("%s is not a bitmap", AllMapNames[i].c_str()));
415 if (pBtmp->getPixelFormat() != CBitmap::RGBA)
417 outString(toString("Converting %s to RGBA (32 bits), originally using %u bits...", AllMapNames[i].c_str(), (uint)colors));
418 pBtmp->convertToType(CBitmap::RGBA);
421 // duplicate icon border
422 if (borderSize > 0)
424 NLMISC::CBitmap *tmp = new NLMISC::CBitmap;
425 tmp->resize(pBtmp->getWidth(), pBtmp->getHeight());
426 tmp->blit(pBtmp, 0, 0);
427 // corners
428 tmp->resample(tmp->getWidth() + borderSize * 2, tmp->getHeight() + borderSize * 2);
429 // top, bottom
430 tmp->blit(pBtmp, borderSize, 0);
431 tmp->blit(pBtmp, borderSize, borderSize*2);
432 // left, right
433 tmp->blit(pBtmp, 0, borderSize);
434 tmp->blit(pBtmp, borderSize*2, borderSize);
435 // center
436 tmp->blit(pBtmp, borderSize, borderSize);
438 delete pBtmp;
439 pBtmp = tmp;
442 AllMaps[i] = pBtmp;
444 catch (const NLMISC::Exception &e)
446 if (pBtmp) delete pBtmp;
448 outString(toString("ERROR : %s", e.what()));
449 return -1;
453 // Sort all maps by decreasing size
454 for (sint i = 0; i < mapSize-1; ++i)
455 for (j = i+1; j < mapSize; ++j)
457 NLMISC::CBitmap *pBI = AllMaps[i];
458 NLMISC::CBitmap *pBJ = AllMaps[j];
459 if ((pBI->getWidth()*pBI->getHeight()) < (pBJ->getWidth()*pBJ->getHeight()))
461 NLMISC::CBitmap *pBTmp = AllMaps[i];
462 AllMaps[i] = AllMaps[j];
463 AllMaps[j] = pBTmp;
465 string sTmp = AllMapNames[i];
466 AllMapNames[i] = AllMapNames[j];
467 AllMapNames[j] = sTmp;
471 // Place all maps into the global texture
472 NLMISC::CBitmap GlobalTexture, GlobalMask;
473 GlobalTexture.resize (1, 1, NLMISC::CBitmap::RGBA);
474 GlobalMask.resize (1, 1, NLMISC::CBitmap::RGBA);
475 CObjectVector<uint8> &rPixelBitmap = GlobalTexture.getPixels(0);
476 rPixelBitmap[0] = rPixelBitmap[1] = rPixelBitmap[2] = rPixelBitmap[3] = 0;
477 CObjectVector<uint8> &rPixelMask = GlobalMask.getPixels(0);
478 rPixelMask[0] = rPixelMask[1] = rPixelMask[2] = rPixelMask[3] = 0;
479 vector<NLMISC::CUV> UVMin, UVMax;
480 UVMin.resize (mapSize, NLMISC::CUV(0.0f, 0.0f));
481 UVMax.resize (mapSize, NLMISC::CUV(0.0f, 0.0f));
483 for (sint i = 0; i < mapSize; ++i)
485 sint32 x, y;
486 while (!tryAllPos(AllMaps[i], &GlobalMask, x, y))
488 // Enlarge global texture
489 enlargeCanvas (GlobalTexture);
490 enlargeCanvas (GlobalMask);
493 putIn (AllMaps[i], &GlobalTexture, x, y);
494 putIn (AllMaps[i], &GlobalMask, x, y, false);
496 UVMin[i].U = (float)x + borderSize;
497 UVMin[i].V = (float)y + borderSize;
498 UVMax[i].U = (float)x + AllMaps[i]->getWidth() - borderSize;
499 UVMax[i].V = (float)y + AllMaps[i]->getHeight() - borderSize;
501 #if 0
502 // Do not remove this is useful for debugging
503 writeFileDependingOnFilename(fmtName.substr(0, fmtName.rfind('.')) + "_txt.png", GlobalTexture);
504 writeFileDependingOnFilename(fmtName.substr(0, fmtName.rfind('.')) + "_msk.png", GlobalMask);
505 #endif
508 // Convert UV from pixel to ratio
509 for (sint i = 0; i < mapSize; ++i)
511 UVMin[i].U = UVMin[i].U / (float)GlobalTexture.getWidth();
512 UVMin[i].V = UVMin[i].V / (float)GlobalTexture.getHeight();
513 UVMax[i].U = UVMax[i].U / (float)GlobalTexture.getWidth();
514 UVMax[i].V = UVMax[i].V / (float)GlobalTexture.getHeight();
517 // make sure transparent pixels are black
518 GlobalTexture.makeTransparentPixelsBlack();
520 // Write global texture file
521 if (writeFileDependingOnFilename(fmtName, GlobalTexture))
523 outString(toString("Writing %s", fmtName.c_str()));
525 else
527 outString(toString("ERROR: Unable to write %s", fmtName.c_str()));
530 // Write UV text file
531 if( !buildSubset )
533 fmtName = fmtName.substr(0, fmtName.rfind('.'));
534 fmtName += ".txt";
535 FILE *f = nlfopen(fmtName, "wb");
536 if (f != NULL)
538 for (sint i = 0; i < mapSize; ++i)
540 // get the string whitout path
541 string fileName = CFile::getFilename(AllMapNames[i]);
542 fprintf (f, "%s %.12f %.12f %.12f %.12f\n", fileName.c_str(), UVMin[i].U, UVMin[i].V, UVMax[i].U, UVMax[i].V);
545 fclose (f);
547 outString(toString("Writing UV file %s", fmtName.c_str()));
549 else
551 outString(toString("ERROR: Cannot write UV file %s", fmtName.c_str()));
554 else // build as a subset
556 // Load existing uv file
557 CIFile iFile;
558 string filename = CPath::lookup (existingUVfilename, false);
560 if( filename.empty() || !iFile.open(filename) )
562 outString(toString("ERROR: Unable to open %s", existingUVfilename.c_str()));
563 return -1;
566 // Write subset UV text file
567 fmtName = fmtName.substr(0, fmtName.rfind('.'));
568 fmtName += ".txt";
569 FILE *f = nlfopen(fmtName, "wb");
571 if (f == NULL)
573 outString(toString("ERROR: Unable to write UV file %s", fmtName.c_str()));
574 return -1;
577 char bufTmp[256], tgaName[256];
578 string sTGAname;
579 float uvMinU, uvMinV, uvMaxU, uvMaxV;
580 while (!iFile.eof())
582 iFile.getline (bufTmp, 256);
583 if (sscanf (bufTmp, "%s %f %f %f %f", tgaName, &uvMinU, &uvMinV, &uvMaxU, &uvMaxV) != 5)
585 nlwarning("Can't parse %s", bufTmp);
586 continue;
589 sTGAname = toLowerAscii(string(tgaName));
591 // search position of extension
592 std::string tgaExt = CFile::getExtension(sTGAname);
594 // remove extension
595 sTGAname = CFile::getFilenameWithoutExtension(sTGAname);
597 sint i;
599 string findTGAName;
600 for (i = 0; i < mapSize; ++i)
602 // get the string whitout path
603 findTGAName = toLowerAscii(CFile::getFilenameWithoutExtension(AllMapNames[i]));
604 if( findTGAName == sTGAname )
605 break;
608 // append extension
609 sTGAname += "." + tgaExt;
611 if( i == mapSize )
613 // not present in subset: offset existing uv's to (0,0), preserving size
614 fprintf (f, "%s %.12f %.12f %.12f %.12f\n", sTGAname.c_str(), 0.0f, 0.0f, uvMaxU - uvMinU, uvMaxV - uvMinV);
616 else
618 // present in subset: use new uv's
619 fprintf (f, "%s %.12f %.12f %.12f %.12f\n", sTGAname.c_str(), UVMin[i].U, UVMin[i].V, UVMax[i].U, UVMax[i].V);
622 fclose (f);
623 outString(toString("Writing UV file: %s", fmtName.c_str()));
626 return 0;