3 Copyright (C) 2010 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 TextureSource::TextureSource(IrrlichtDevice *device):
25 m_main_atlas_image(NULL),
26 m_main_atlas_texture(NULL)
30 m_atlaspointer_cache_mutex.Init();
32 m_main_thread = get_current_thread_id();
34 // Add a NULL AtlasPointer as the first index, named ""
35 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
38 // Build main texture atlas
42 TextureSource::~TextureSource()
46 void TextureSource::processQueue()
51 if(m_get_texture_queue.size() > 0)
53 GetRequest<std::string, u32, u8, u8>
54 request = m_get_texture_queue.pop();
56 dstream<<"INFO: TextureSource::processQueue(): "
57 <<"got texture request with "
58 <<"name="<<request.key
61 GetResult<std::string, u32, u8, u8>
63 result.key = request.key;
64 result.callers = request.callers;
65 result.item = getTextureIdDirect(request.key);
67 request.dest->push_back(result);
71 u32 TextureSource::getTextureId(const std::string &name)
73 //dstream<<"INFO: getTextureId(): name="<<name<<std::endl;
77 See if texture already exists
79 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
80 core::map<std::string, u32>::Node *n;
81 n = m_name_to_id.find(name);
91 if(get_current_thread_id() == m_main_thread)
93 return getTextureIdDirect(name);
97 dstream<<"INFO: getTextureId(): Queued: name="<<name<<std::endl;
99 // We're gonna ask the result to be put into here
100 ResultQueue<std::string, u32, u8, u8> result_queue;
102 // Throw a request in
103 m_get_texture_queue.add(name, 0, 0, &result_queue);
105 dstream<<"INFO: Waiting for texture from main thread, name="
110 // Wait result for a second
111 GetResult<std::string, u32, u8, u8>
112 result = result_queue.pop_front(1000);
114 // Check that at least something worked OK
115 assert(result.key == name);
119 catch(ItemNotFoundException &e)
121 dstream<<"WARNING: Waiting for texture timed out."<<std::endl;
126 dstream<<"WARNING: getTextureId(): Failed"<<std::endl;
131 // Draw a progress bar on the image
132 void make_progressbar(float value, video::IImage *image);
135 Generate image based on a string like "stone.png" or "[crack0".
136 if baseimg is NULL, it is created. Otherwise stuff is made on it.
138 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
139 video::IVideoDriver* driver);
142 Generates an image from a full string like
143 "stone.png^mineral_coal.png^[crack0".
145 This is used by buildMainAtlas().
147 video::IImage* generate_image_from_scratch(std::string name,
148 video::IVideoDriver* driver);
151 This method generates all the textures
153 u32 TextureSource::getTextureIdDirect(const std::string &name)
155 dstream<<"INFO: getTextureIdDirect(): name="<<name<<std::endl;
157 // Empty name means texture 0
160 dstream<<"INFO: getTextureIdDirect(): name is empty"<<std::endl;
165 Calling only allowed from main thread
167 if(get_current_thread_id() != m_main_thread)
169 dstream<<"ERROR: TextureSource::getTextureIdDirect() "
170 "called not from main thread"<<std::endl;
175 See if texture already exists
178 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
180 core::map<std::string, u32>::Node *n;
181 n = m_name_to_id.find(name);
184 dstream<<"INFO: getTextureIdDirect(): name="<<name
185 <<" found in cache"<<std::endl;
186 return n->getValue();
190 dstream<<"INFO: getTextureIdDirect(): name="<<name
191 <<" NOT found in cache. Creating it."<<std::endl;
197 char separator = '^';
200 This is set to the id of the base image.
201 If left 0, there is no base image and a completely new image
204 u32 base_image_id = 0;
206 // Find last meta separator in name
207 s32 last_separator_position = -1;
208 for(s32 i=name.size()-1; i>=0; i--)
210 if(name[i] == separator)
212 last_separator_position = i;
217 If separator was found, construct the base name and make the
218 base image using a recursive call
220 std::string base_image_name;
221 if(last_separator_position != -1)
223 // Construct base name
224 base_image_name = name.substr(0, last_separator_position);
225 dstream<<"INFO: getTextureIdDirect(): Calling itself recursively"
226 " to get base image, name="<<base_image_name<<std::endl;
227 base_image_id = getTextureIdDirect(base_image_name);
230 dstream<<"base_image_id="<<base_image_id<<std::endl;
232 video::IVideoDriver* driver = m_device->getVideoDriver();
235 video::ITexture *t = NULL;
238 An image will be built from files and then converted into a texture.
240 video::IImage *baseimg = NULL;
242 // If a base image was found, copy it to baseimg
243 if(base_image_id != 0)
245 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
247 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
249 video::IImage *image = ap.atlas_img;
251 core::dimension2d<u32> dim = ap.intsize;
253 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
255 core::position2d<s32> pos_to(0,0);
256 core::position2d<s32> pos_from = ap.intpos;
260 v2s32(0,0), // position in target
261 core::rect<s32>(pos_from, dim) // from
264 dstream<<"INFO: getTextureIdDirect(): Loaded \""
265 <<base_image_name<<"\" from image cache"
270 Parse out the last part of the name of the image and act
274 std::string last_part_of_name = name.substr(last_separator_position+1);
275 dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
277 // Generate image according to part of name
278 if(generate_image(last_part_of_name, baseimg, driver) == false)
280 dstream<<"INFO: getTextureIdDirect(): "
281 "failed to generate \""<<last_part_of_name<<"\""
286 // If no resulting image, return NULL
289 dstream<<"WARNING: getTextureIdDirect(): baseimg is NULL (attempted to"
290 " create texture \""<<name<<"\""<<std::endl;
294 // Create texture from resulting image
295 t = driver->addTexture(name.c_str(), baseimg);
302 Add texture to caches
305 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
307 u32 id = m_atlaspointer_cache.size();
313 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg->getDimension());
314 m_atlaspointer_cache.push_back(nap);
315 m_name_to_id.insert(name, id);
317 dstream<<"INFO: getTextureIdDirect(): name="<<name
318 <<": succesfully returning id="<<id<<std::endl;
323 std::string TextureSource::getTextureName(u32 id)
325 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
327 if(id >= m_atlaspointer_cache.size())
329 dstream<<"WARNING: TextureSource::getTextureName(): id="<<id
330 <<" >= m_atlaspointer_cache.size()="
331 <<m_atlaspointer_cache.size()<<std::endl;
335 return m_atlaspointer_cache[id].name;
339 AtlasPointer TextureSource::getTexture(u32 id)
341 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
343 if(id >= m_atlaspointer_cache.size())
344 return AtlasPointer(0, NULL);
346 return m_atlaspointer_cache[id].a;
349 void TextureSource::buildMainAtlas()
351 dstream<<"TextureSource::buildMainAtlas()"<<std::endl;
353 //return; // Disable (for testing)
355 video::IVideoDriver* driver = m_device->getVideoDriver();
358 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
360 // Create an image of the right size
361 core::dimension2d<u32> atlas_dim(1024,1024);
362 video::IImage *atlas_img =
363 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
366 A list of stuff to add. This should contain as much of the
367 stuff shown in game as possible, to minimize texture changes.
370 core::array<std::string> sourcelist;
372 sourcelist.push_back("stone.png");
373 sourcelist.push_back("mud.png");
374 sourcelist.push_back("sand.png");
375 sourcelist.push_back("grass.png");
376 sourcelist.push_back("mud.png");
377 sourcelist.push_back("tree.png");
378 sourcelist.push_back("tree_top.png");
379 sourcelist.push_back("water.png");
380 sourcelist.push_back("leaves.png");
381 sourcelist.push_back("mud.png^grass_side.png");
383 sourcelist.push_back("stone.png^mineral_coal.png");
384 sourcelist.push_back("stone.png^mineral_iron.png");
385 sourcelist.push_back("mud.png^mineral_coal.png");
386 sourcelist.push_back("mud.png^mineral_iron.png");
387 sourcelist.push_back("sand.png^mineral_coal.png");
388 sourcelist.push_back("sand.png^mineral_iron.png");
391 First pass: generate almost everything
393 core::position2d<s32> pos_in_atlas(0,0);
394 for(u32 i=0; i<sourcelist.size(); i++)
396 std::string name = sourcelist[i];
398 /*video::IImage *img = driver->createImageFromFile(
399 porting::getDataPath(name.c_str()).c_str());
403 core::dimension2d<u32> dim = img->getDimension();
404 // Make a copy with the right color format
405 video::IImage *img2 =
406 driver->createImage(video::ECF_A8R8G8B8, dim);
410 // Generate image by name
411 video::IImage *img2 = generate_image_from_scratch(name, driver);
412 core::dimension2d<u32> dim = img2->getDimension();
414 // Tile it a few times in the X direction
415 u16 xwise_tiling = 16;
416 for(u32 j=0; j<xwise_tiling; j++)
418 // Copy the copy to the atlas
419 img2->copyToWithAlpha(atlas_img,
420 pos_in_atlas + v2s32(j*dim.Width,0),
421 core::rect<s32>(v2s32(0,0), dim),
422 video::SColor(255,255,255,255),
429 Add texture to caches
433 u32 id = m_atlaspointer_cache.size();
435 // Create AtlasPointer
437 ap.atlas = NULL; // Set on the second pass
438 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
439 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
440 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
441 (float)dim.Width/(float)atlas_dim.Height);
442 ap.tiled = xwise_tiling;
444 // Create SourceAtlasPointer and add to containers
445 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
446 m_atlaspointer_cache.push_back(nap);
447 m_name_to_id.insert(name, id);
449 // Increment position
450 pos_in_atlas.Y += dim.Height;
456 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
460 Second pass: set texture pointer in generated AtlasPointers
462 for(u32 i=0; i<sourcelist.size(); i++)
464 std::string name = sourcelist[i];
465 u32 id = m_name_to_id[name];
466 m_atlaspointer_cache[id].a.atlas = t;
470 Write image to file so that it can be inspected
472 driver->writeImageToFile(atlas_img,
473 porting::getDataPath("main_atlas.png").c_str());
476 video::IImage* generate_image_from_scratch(std::string name,
477 video::IVideoDriver* driver)
479 dstream<<"INFO: generate_image_from_scratch(): "
480 "name="<<name<<std::endl;
486 video::IImage *baseimg = NULL;
488 char separator = '^';
490 // Find last meta separator in name
491 s32 last_separator_position = -1;
492 for(s32 i=name.size()-1; i>=0; i--)
494 if(name[i] == separator)
496 last_separator_position = i;
501 /*dstream<<"INFO: generate_image_from_scratch(): "
502 <<"last_separator_position="<<last_separator_position
506 If separator was found, construct the base name and make the
507 base image using a recursive call
509 std::string base_image_name;
510 if(last_separator_position != -1)
512 // Construct base name
513 base_image_name = name.substr(0, last_separator_position);
514 dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
515 " to get base image, name="<<base_image_name<<std::endl;
516 baseimg = generate_image_from_scratch(base_image_name, driver);
520 Parse out the last part of the name of the image and act
524 std::string last_part_of_name = name.substr(last_separator_position+1);
525 dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
527 // Generate image according to part of name
528 if(generate_image(last_part_of_name, baseimg, driver) == false)
530 dstream<<"INFO: generate_image_from_scratch(): "
531 "failed to generate \""<<last_part_of_name<<"\""
539 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
540 video::IVideoDriver* driver)
542 // Stuff starting with [ are special commands
543 if(part_of_name[0] != '[')
545 // A normal texture; load it from a file
546 std::string path = porting::getDataPath(part_of_name.c_str());
547 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
550 video::IImage *image = driver->createImageFromFile(path.c_str());
554 dstream<<"WARNING: Could not load image \""<<part_of_name
555 <<"\" from path \""<<path<<"\""
556 <<" while building texture"<<std::endl;
560 // If base image is NULL, load as base.
563 dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
565 Copy it this way to get an alpha channel.
566 Otherwise images with alpha cannot be blitted on
567 images that don't have alpha in the original file.
569 core::dimension2d<u32> dim = image->getDimension();
570 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
571 image->copyTo(baseimg);
574 // Else blit on base.
577 dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
578 // Size of the copied area
579 core::dimension2d<u32> dim = image->getDimension();
580 //core::dimension2d<u32> dim(16,16);
581 // Position to copy the blitted to in the base image
582 core::position2d<s32> pos_to(0,0);
583 // Position to copy the blitted from in the blitted image
584 core::position2d<s32> pos_from(0,0);
586 image->copyToWithAlpha(baseimg, pos_to,
587 core::rect<s32>(pos_from, dim),
588 video::SColor(255,255,255,255),
596 // A special texture modification
598 dstream<<"INFO: getTextureIdDirect(): generating special "
599 <<"modification \""<<part_of_name<<"\""
602 if(part_of_name.substr(0,6) == "[crack")
606 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
607 <<"for part_of_name="<<part_of_name
608 <<", cancelling."<<std::endl;
612 u16 progression = stoi(part_of_name.substr(6));
613 // Size of the base image
614 core::dimension2d<u32> dim_base = baseimg->getDimension();
615 // Crack will be drawn at this size
617 // Size of the crack image
618 core::dimension2d<u32> dim_crack(cracksize,cracksize);
619 // Position to copy the crack from in the crack image
620 core::position2d<s32> pos_other(0, 16 * progression);
622 video::IImage *crackimage = driver->createImageFromFile(
623 porting::getDataPath("crack.png").c_str());
627 /*crackimage->copyToWithAlpha(baseimg, v2s32(0,0),
628 core::rect<s32>(pos_other, dim_base),
629 video::SColor(255,255,255,255),
632 for(u32 y0=0; y0<dim_base.Height/dim_crack.Height; y0++)
633 for(u32 x0=0; x0<dim_base.Width/dim_crack.Width; x0++)
635 // Position to copy the crack to in the base image
636 core::position2d<s32> pos_base(x0*cracksize, y0*cracksize);
637 crackimage->copyToWithAlpha(baseimg, pos_base,
638 core::rect<s32>(pos_other, dim_crack),
639 video::SColor(255,255,255,255),
646 else if(part_of_name.substr(0,8) == "[combine")
648 // "[combine:16x128:0,0=stone.png:0,16=grass.png"
649 Strfnd sf(part_of_name);
651 u32 w0 = stoi(sf.next("x"));
652 u32 h0 = stoi(sf.next(":"));
653 dstream<<"INFO: combined w="<<w0<<" h="<<h0<<std::endl;
654 core::dimension2d<u32> dim(w0,h0);
655 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
656 while(sf.atend() == false)
658 u32 x = stoi(sf.next(","));
659 u32 y = stoi(sf.next("="));
660 std::string filename = sf.next(":");
661 dstream<<"INFO: Adding \""<<filename
662 <<"\" to combined ("<<x<<","<<y<<")"
664 video::IImage *img = driver->createImageFromFile(
665 porting::getDataPath(filename.c_str()).c_str());
668 core::dimension2d<u32> dim = img->getDimension();
669 dstream<<"INFO: Size "<<dim.Width
670 <<"x"<<dim.Height<<std::endl;
671 core::position2d<s32> pos_base(x, y);
672 video::IImage *img2 =
673 driver->createImage(video::ECF_A8R8G8B8, dim);
676 img2->copyToWithAlpha(baseimg, pos_base,
677 core::rect<s32>(v2s32(0,0), dim),
678 video::SColor(255,255,255,255),
684 dstream<<"WARNING: img==NULL"<<std::endl;
688 else if(part_of_name.substr(0,12) == "[progressbar")
692 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
693 <<"for part_of_name="<<part_of_name
694 <<", cancelling."<<std::endl;
698 float value = stof(part_of_name.substr(12));
699 make_progressbar(value, baseimg);
701 // "[noalpha:filename.png"
702 // Use an image without it's alpha channel
703 else if(part_of_name.substr(0,8) == "[noalpha")
707 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
708 <<"for part_of_name="<<part_of_name
709 <<", cancelling."<<std::endl;
713 std::string filename = part_of_name.substr(9);
715 std::string path = porting::getDataPath(filename.c_str());
717 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
720 video::IImage *image = driver->createImageFromFile(path.c_str());
724 dstream<<"WARNING: getTextureIdDirect(): Loading path \""
725 <<path<<"\" failed"<<std::endl;
729 core::dimension2d<u32> dim = image->getDimension();
730 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
733 for(u32 y=0; y<dim.Height; y++)
734 for(u32 x=0; x<dim.Width; x++)
736 video::SColor c = image->getPixel(x,y);
738 image->setPixel(x,y,c);
741 image->copyTo(baseimg);
748 dstream<<"WARNING: getTextureIdDirect(): Invalid "
749 " modification: \""<<part_of_name<<"\""<<std::endl;
756 void make_progressbar(float value, video::IImage *image)
761 core::dimension2d<u32> size = image->getDimension();
766 u32 barwidth = size.Width - barpad_x*2;
767 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
769 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
771 video::SColor active(255,255,0,0);
772 video::SColor inactive(255,0,0,0);
773 for(u32 x0=0; x0<barwidth; x0++)
780 u32 x = x0 + barpos.X;
781 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
783 image->setPixel(x,y, *c);