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<<"\""
630 if(part_of_name.substr(0,6) == "[crack")
634 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
635 <<"for part_of_name="<<part_of_name
636 <<", cancelling."<<std::endl;
640 u16 progression = stoi(part_of_name.substr(6));
641 // Size of the base image
642 core::dimension2d<u32> dim_base = baseimg->getDimension();
643 // Crack will be drawn at this size
645 // Size of the crack image
646 core::dimension2d<u32> dim_crack(cracksize,cracksize);
647 // Position to copy the crack from in the crack image
648 core::position2d<s32> pos_other(0, 16 * progression);
650 video::IImage *crackimage = driver->createImageFromFile(
651 porting::getDataPath("crack.png").c_str());
655 /*crackimage->copyToWithAlpha(baseimg, v2s32(0,0),
656 core::rect<s32>(pos_other, dim_base),
657 video::SColor(255,255,255,255),
660 for(u32 y0=0; y0<dim_base.Height/dim_crack.Height; y0++)
661 for(u32 x0=0; x0<dim_base.Width/dim_crack.Width; x0++)
663 // Position to copy the crack to in the base image
664 core::position2d<s32> pos_base(x0*cracksize, y0*cracksize);
665 crackimage->copyToWithAlpha(baseimg, pos_base,
666 core::rect<s32>(pos_other, dim_crack),
667 video::SColor(255,255,255,255),
674 else if(part_of_name.substr(0,8) == "[combine")
676 // "[combine:16x128:0,0=stone.png:0,16=grass.png"
677 Strfnd sf(part_of_name);
679 u32 w0 = stoi(sf.next("x"));
680 u32 h0 = stoi(sf.next(":"));
681 dstream<<"INFO: combined w="<<w0<<" h="<<h0<<std::endl;
682 core::dimension2d<u32> dim(w0,h0);
683 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
684 while(sf.atend() == false)
686 u32 x = stoi(sf.next(","));
687 u32 y = stoi(sf.next("="));
688 std::string filename = sf.next(":");
689 dstream<<"INFO: Adding \""<<filename
690 <<"\" to combined ("<<x<<","<<y<<")"
692 video::IImage *img = driver->createImageFromFile(
693 porting::getDataPath(filename.c_str()).c_str());
696 core::dimension2d<u32> dim = img->getDimension();
697 dstream<<"INFO: Size "<<dim.Width
698 <<"x"<<dim.Height<<std::endl;
699 core::position2d<s32> pos_base(x, y);
700 video::IImage *img2 =
701 driver->createImage(video::ECF_A8R8G8B8, dim);
704 img2->copyToWithAlpha(baseimg, pos_base,
705 core::rect<s32>(v2s32(0,0), dim),
706 video::SColor(255,255,255,255),
712 dstream<<"WARNING: img==NULL"<<std::endl;
716 else if(part_of_name.substr(0,12) == "[progressbar")
720 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
721 <<"for part_of_name="<<part_of_name
722 <<", cancelling."<<std::endl;
726 float value = stof(part_of_name.substr(12));
727 make_progressbar(value, baseimg);
729 // "[noalpha:filename.png"
730 // Use an image without it's alpha channel
731 else if(part_of_name.substr(0,8) == "[noalpha")
735 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
736 <<"for part_of_name="<<part_of_name
737 <<", cancelling."<<std::endl;
741 std::string filename = part_of_name.substr(9);
743 std::string path = porting::getDataPath(filename.c_str());
745 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
748 video::IImage *image = driver->createImageFromFile(path.c_str());
752 dstream<<"WARNING: getTextureIdDirect(): Loading path \""
753 <<path<<"\" failed"<<std::endl;
757 core::dimension2d<u32> dim = image->getDimension();
758 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
761 for(u32 y=0; y<dim.Height; y++)
762 for(u32 x=0; x<dim.Width; x++)
764 video::SColor c = image->getPixel(x,y);
766 image->setPixel(x,y,c);
769 image->copyTo(baseimg);
776 dstream<<"WARNING: getTextureIdDirect(): Invalid "
777 " modification: \""<<part_of_name<<"\""<<std::endl;
784 void make_progressbar(float value, video::IImage *image)
789 core::dimension2d<u32> size = image->getDimension();
794 u32 barwidth = size.Width - barpad_x*2;
795 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
797 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
799 video::SColor active(255,255,0,0);
800 video::SColor inactive(255,0,0,0);
801 for(u32 x0=0; x0<barwidth; x0++)
808 u32 x = x0 + barpos.X;
809 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
811 image->setPixel(x,y, *c);