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("grass_footsteps.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");
390 // Padding to disallow texture bleeding
394 First pass: generate almost everything
396 core::position2d<s32> pos_in_atlas(0,0);
398 pos_in_atlas.Y += padding;
400 for(u32 i=0; i<sourcelist.size(); i++)
402 std::string name = sourcelist[i];
404 /*video::IImage *img = driver->createImageFromFile(
405 porting::getDataPath(name.c_str()).c_str());
409 core::dimension2d<u32> dim = img->getDimension();
410 // Make a copy with the right color format
411 video::IImage *img2 =
412 driver->createImage(video::ECF_A8R8G8B8, dim);
416 // Generate image by name
417 video::IImage *img2 = generate_image_from_scratch(name, driver);
418 core::dimension2d<u32> dim = img2->getDimension();
420 // Tile it a few times in the X direction
421 u16 xwise_tiling = 16;
422 for(u32 j=0; j<xwise_tiling; j++)
424 // Copy the copy to the atlas
425 img2->copyToWithAlpha(atlas_img,
426 pos_in_atlas + v2s32(j*dim.Width,0),
427 core::rect<s32>(v2s32(0,0), dim),
428 video::SColor(255,255,255,255),
432 // Copy the borders a few times to disallow texture bleeding
433 for(u32 side=0; side<2; side++) // top and bottom
434 for(s32 y0=0; y0<padding; y0++)
435 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
441 dst_y = y0 + pos_in_atlas.Y + dim.Height;
442 src_y = pos_in_atlas.Y + dim.Height - 1;
446 dst_y = -y0 + pos_in_atlas.Y-1;
447 src_y = pos_in_atlas.Y;
449 s32 x = x0 + pos_in_atlas.X * dim.Width;
450 video::SColor c = atlas_img->getPixel(x, src_y);
451 atlas_img->setPixel(x,dst_y,c);
457 Add texture to caches
461 u32 id = m_atlaspointer_cache.size();
463 // Create AtlasPointer
465 ap.atlas = NULL; // Set on the second pass
466 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
467 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
468 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
469 (float)dim.Width/(float)atlas_dim.Height);
470 ap.tiled = xwise_tiling;
472 // Create SourceAtlasPointer and add to containers
473 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
474 m_atlaspointer_cache.push_back(nap);
475 m_name_to_id.insert(name, id);
477 // Increment position
478 pos_in_atlas.Y += dim.Height + padding * 2;
484 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
488 Second pass: set texture pointer in generated AtlasPointers
490 for(u32 i=0; i<sourcelist.size(); i++)
492 std::string name = sourcelist[i];
493 u32 id = m_name_to_id[name];
494 m_atlaspointer_cache[id].a.atlas = t;
498 Write image to file so that it can be inspected
500 /*driver->writeImageToFile(atlas_img,
501 porting::getDataPath("main_atlas.png").c_str());*/
504 video::IImage* generate_image_from_scratch(std::string name,
505 video::IVideoDriver* driver)
507 dstream<<"INFO: generate_image_from_scratch(): "
508 "name="<<name<<std::endl;
514 video::IImage *baseimg = NULL;
516 char separator = '^';
518 // Find last meta separator in name
519 s32 last_separator_position = -1;
520 for(s32 i=name.size()-1; i>=0; i--)
522 if(name[i] == separator)
524 last_separator_position = i;
529 /*dstream<<"INFO: generate_image_from_scratch(): "
530 <<"last_separator_position="<<last_separator_position
534 If separator was found, construct the base name and make the
535 base image using a recursive call
537 std::string base_image_name;
538 if(last_separator_position != -1)
540 // Construct base name
541 base_image_name = name.substr(0, last_separator_position);
542 dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
543 " to get base image, name="<<base_image_name<<std::endl;
544 baseimg = generate_image_from_scratch(base_image_name, driver);
548 Parse out the last part of the name of the image and act
552 std::string last_part_of_name = name.substr(last_separator_position+1);
553 dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
555 // Generate image according to part of name
556 if(generate_image(last_part_of_name, baseimg, driver) == false)
558 dstream<<"INFO: generate_image_from_scratch(): "
559 "failed to generate \""<<last_part_of_name<<"\""
567 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
568 video::IVideoDriver* driver)
570 // Stuff starting with [ are special commands
571 if(part_of_name[0] != '[')
573 // A normal texture; load it from a file
574 std::string path = porting::getDataPath(part_of_name.c_str());
575 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
578 video::IImage *image = driver->createImageFromFile(path.c_str());
582 dstream<<"WARNING: Could not load image \""<<part_of_name
583 <<"\" from path \""<<path<<"\""
584 <<" while building texture"<<std::endl;
588 // If base image is NULL, load as base.
591 dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
593 Copy it this way to get an alpha channel.
594 Otherwise images with alpha cannot be blitted on
595 images that don't have alpha in the original file.
597 core::dimension2d<u32> dim = image->getDimension();
598 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
599 image->copyTo(baseimg);
602 // Else blit on base.
605 dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
606 // Size of the copied area
607 core::dimension2d<u32> dim = image->getDimension();
608 //core::dimension2d<u32> dim(16,16);
609 // Position to copy the blitted to in the base image
610 core::position2d<s32> pos_to(0,0);
611 // Position to copy the blitted from in the blitted image
612 core::position2d<s32> pos_from(0,0);
614 image->copyToWithAlpha(baseimg, pos_to,
615 core::rect<s32>(pos_from, dim),
616 video::SColor(255,255,255,255),
624 // A special texture modification
626 dstream<<"INFO: getTextureIdDirect(): generating special "
627 <<"modification \""<<part_of_name<<"\""
631 This is the simplest of all; it just adds stuff to the
632 name so that a separate texture is created.
634 It is used to make textures for stuff that doesn't want
635 to implement getting the texture from a bigger texture
638 if(part_of_name == "[forcesingle")
643 Adds a cracking texture
645 else if(part_of_name.substr(0,6) == "[crack")
649 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
650 <<"for part_of_name="<<part_of_name
651 <<", cancelling."<<std::endl;
655 u16 progression = stoi(part_of_name.substr(6));
656 // Size of the base image
657 core::dimension2d<u32> dim_base = baseimg->getDimension();
658 // Crack will be drawn at this size
660 // Size of the crack image
661 core::dimension2d<u32> dim_crack(cracksize,cracksize);
662 // Position to copy the crack from in the crack image
663 core::position2d<s32> pos_other(0, 16 * progression);
665 video::IImage *crackimage = driver->createImageFromFile(
666 porting::getDataPath("crack.png").c_str());
670 /*crackimage->copyToWithAlpha(baseimg, v2s32(0,0),
671 core::rect<s32>(pos_other, dim_base),
672 video::SColor(255,255,255,255),
675 for(u32 y0=0; y0<dim_base.Height/dim_crack.Height; y0++)
676 for(u32 x0=0; x0<dim_base.Width/dim_crack.Width; x0++)
678 // Position to copy the crack to in the base image
679 core::position2d<s32> pos_base(x0*cracksize, y0*cracksize);
680 crackimage->copyToWithAlpha(baseimg, pos_base,
681 core::rect<s32>(pos_other, dim_crack),
682 video::SColor(255,255,255,255),
690 [combine:WxH:X,Y=filename:X,Y=filename2
691 Creates a bigger texture from an amount of smaller ones
693 else if(part_of_name.substr(0,8) == "[combine")
695 Strfnd sf(part_of_name);
697 u32 w0 = stoi(sf.next("x"));
698 u32 h0 = stoi(sf.next(":"));
699 dstream<<"INFO: combined w="<<w0<<" h="<<h0<<std::endl;
700 core::dimension2d<u32> dim(w0,h0);
701 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
702 while(sf.atend() == false)
704 u32 x = stoi(sf.next(","));
705 u32 y = stoi(sf.next("="));
706 std::string filename = sf.next(":");
707 dstream<<"INFO: Adding \""<<filename
708 <<"\" to combined ("<<x<<","<<y<<")"
710 video::IImage *img = driver->createImageFromFile(
711 porting::getDataPath(filename.c_str()).c_str());
714 core::dimension2d<u32> dim = img->getDimension();
715 dstream<<"INFO: Size "<<dim.Width
716 <<"x"<<dim.Height<<std::endl;
717 core::position2d<s32> pos_base(x, y);
718 video::IImage *img2 =
719 driver->createImage(video::ECF_A8R8G8B8, dim);
722 img2->copyToWithAlpha(baseimg, pos_base,
723 core::rect<s32>(v2s32(0,0), dim),
724 video::SColor(255,255,255,255),
730 dstream<<"WARNING: img==NULL"<<std::endl;
736 Adds a progress bar, 0.0 <= N <= 1.0
738 else if(part_of_name.substr(0,12) == "[progressbar")
742 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
743 <<"for part_of_name="<<part_of_name
744 <<", cancelling."<<std::endl;
748 float value = stof(part_of_name.substr(12));
749 make_progressbar(value, baseimg);
752 "[noalpha:filename.png"
753 Use an image without it's alpha channel.
754 Used for the leaves texture when in old leaves mode, so
755 that the transparent parts don't look completely black
756 when simple alpha channel is used for rendering.
758 else if(part_of_name.substr(0,8) == "[noalpha")
762 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
763 <<"for part_of_name="<<part_of_name
764 <<", cancelling."<<std::endl;
768 std::string filename = part_of_name.substr(9);
770 std::string path = porting::getDataPath(filename.c_str());
772 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
775 video::IImage *image = driver->createImageFromFile(path.c_str());
779 dstream<<"WARNING: getTextureIdDirect(): Loading path \""
780 <<path<<"\" failed"<<std::endl;
784 core::dimension2d<u32> dim = image->getDimension();
785 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
788 for(u32 y=0; y<dim.Height; y++)
789 for(u32 x=0; x<dim.Width; x++)
791 video::SColor c = image->getPixel(x,y);
793 image->setPixel(x,y,c);
796 image->copyTo(baseimg);
802 [inventorycube{topimage{leftimage{rightimage
803 In every subimage, replace ^ with &.
804 Create an "inventory cube".
805 NOTE: This should be used only on its own.
806 Example (a grass block (not actually used in game):
807 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
809 else if(part_of_name.substr(0,14) == "[inventorycube")
813 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
814 <<"for part_of_name="<<part_of_name
815 <<", cancelling."<<std::endl;
819 // This is just a placeholder
821 str_replace_char(part_of_name, '&', '^');
822 Strfnd sf(part_of_name);
824 std::string imagename_top = sf.next("{");
825 std::string imagename_left = sf.next("{");
826 std::string imagename_right = sf.next("{");
828 baseimg = generate_image_from_scratch(
829 imagename_top, driver);
833 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
835 dstream<<"WARNING: getTextureIdDirect(): EVDF_RENDER_TO_TARGET"
836 " not supported"<<std::endl;
842 dstream<<"INFO: inventorycube w="<<w0<<" h="<<h0<<std::endl;
843 core::dimension2d<u32> dim(w0,h0);
845 //baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
847 video::IImage *img_top = generate_image_from_scratch(
848 imagename_top, driver);
849 video::IImage *img_left = generate_image_from_scratch(
850 imagename_left, driver);
851 video::IImage *img_right = generate_image_from_scratch(
852 imagename_right, driver);
854 // Render target texture
855 video::ITexture *rtt = NULL;
856 std::string rtt_name = part_of_name + "_RTT";
858 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str());
873 dstream<<"WARNING: getTextureIdDirect(): Invalid "
874 " modification: \""<<part_of_name<<"\""<<std::endl;
881 void make_progressbar(float value, video::IImage *image)
886 core::dimension2d<u32> size = image->getDimension();
891 u32 barwidth = size.Width - barpad_x*2;
892 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
894 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
896 video::SColor active(255,255,0,0);
897 video::SColor inactive(255,0,0,0);
898 for(u32 x0=0; x0<barwidth; x0++)
905 u32 x = x0 + barpos.X;
906 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
908 image->setPixel(x,y, *c);