1 /**
2 Copyright: Copyright (c) 2013-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module voxelman.graphics.textureatlas;
8 
9 import voxelman.geometry.rectbinpacker;
10 import voxelman.graphics.bitmap;
11 import voxelman.graphics.color;
12 import voxelman.math;
13 
14 
15 class InsertException : Exception
16 {
17 	this(string msg, string file = __FILE__, size_t line = __LINE__)
18 	{
19 		super(msg, file, line);
20 	}
21 }
22 
23 /// Can be used to tightly store small images in big atlas, such as font glyps, or lightmaps etc.
24 class TextureAtlas
25 {
26 	public Bitmap bitmap;
27 	public bool autoGrow = true;
28 
29 private:
30 	uint _maxAtlasSize = 8192; // 2^13
31 	RectBinPacker binPacker;
32 
33 public:
34 
35 	this(in uint size)
36 	{
37 		binPacker = RectBinPacker(size, size);
38 		bitmap = new Bitmap(size, size);
39 	}
40 
41 	this(in uint width, in uint height)
42 	{
43 		binPacker = RectBinPacker(width, height);
44 		bitmap = new Bitmap(width, height);
45 	}
46 
47 	/// Returns: position of inserted node, or throws if not enough space
48 	ivec2 insert(ivec2 size, Color4ub color)
49 	{
50 		ivec2 pos = insert(size);
51 		bitmap.fillSubRect(irect(pos, size), color);
52 		return pos;
53 	}
54 
55 	/// ditto
56 	ivec2 insert(ivec2 size)
57 	{
58 		Node* node = binPacker.insert(size);
59 
60 		if (node is null) // There is no place to put new item.
61 		{
62 			if (autoGrow) // Atlas can grow.
63 			{
64 				if (bitmap.width >= bitmap.height) // Growing vertically.
65 				{
66 					binPacker = RectBinPacker(bitmap.width, bitmap.height, 0, bitmap.height);
67 					if (bitmap.height >= _maxAtlasSize)
68 						throw new InsertException("Texture atlas is full. Max atlas size reached");
69 					bitmap.resize(ivec2(bitmap.width, bitmap.height*2));
70 				}
71 				else // Growing horizontally.
72 				{
73 					binPacker = RectBinPacker(bitmap.width, bitmap.height, bitmap.width, 0);
74 					bitmap.resize(ivec2(bitmap.width*2, bitmap.height));
75 				}
76 
77 				node = binPacker.insert(size);
78 			}
79 			else
80 			{
81 				throw new InsertException("Texture atlas is full");
82 			}
83 		}
84 
85 		return ivec2(node.rect.x, node.rect.y);
86 	}
87 
88 	/// ditto
89 	ivec2 insert(in Bitmap source, in irect sourceSubRect)
90 	{
91 		ivec2 pos = insert(sourceSubRect.size);
92 		bitmap.putSubRect(source, sourceSubRect, pos);
93 		return pos;
94 	}
95 }