1 module Engine.Atlas;
2 
3 
4 import Engine.math;
5 import std.math;
6 import std.stdio;
7 import Engine.Texture;
8 
9 class Atlas {
10 	Texture texture;
11 	
12 	this(int width, int height) {
13 
14 	}
15 
16 
17 }
18 
19 
20 
21 class Image {
22 	ubyte[] Pixels;
23 	int Width;
24 	int Height;
25 	PixelType Type;
26 
27 
28 
29 	this(ubyte[] pixels, int width, int height, PixelType type) {
30 		this.Pixels = pixels;
31 		this.Width = width;
32 		this.Height = height;
33 		this.Type = type;
34 	}
35 
36 
37 	enum PixelType {
38 		RGBA,
39 		RGB,
40 		BGR,
41 		BGRA,
42 		Gray,
43 		Alpha,
44 	}
45 }
46 
47 
48 class MaxRectsBinPack {
49 	int width;
50 	int height;
51 	int padding;
52 
53 	recti[] freeRectangles;
54 	recti[] usedRectangles;
55 
56 	this(int width, int height, int padding) {
57 		this.width = width;
58 		this.height = height;
59 		this.padding = padding;
60 		freeRectangles ~= recti(width,height);
61 	}
62 
63 	void Reset() {
64 		freeRectangles.length = 1;
65 		usedRectangles.length = 0;
66 		freeRectangles[0] = recti(width,height);
67 	}
68 
69 	void placeRect(recti r) {
70 		for (auto i = 0; i < freeRectangles.length; i++) {
71 			if (SplitFreeNode(freeRectangles[i], r)) {
72 				freeRectangles[i] = freeRectangles[freeRectangles.length-1];
73 				freeRectangles.length--;
74 				i--;
75 			}
76 		}
77 		PruneFreeList();
78 		usedRectangles ~= r;
79 	}
80 
81 
82 	/// Computes the ratio of used surface area.
83 	float Occupancy()  {
84 		ulong usedSurfaceArea = 0;
85 		foreach (ref r; usedRectangles) {
86 			usedSurfaceArea += r.Dx() * r.Dy();
87 		}
88 		return (cast(double)(usedSurfaceArea) / cast(double)(this.width*this.height));
89 	}
90 
91 	bool Insert(recti rect, out recti result) {
92 		int a,b;
93 		auto r = this.FindPositionForNewNodeBestShortSideFit(rect.Dx()+this.padding, rect.Dy()+this.padding,a,b);
94 		if (r.Dx() == 0) {
95 			return false;
96 		}
97 
98 		this.placeRect(r);
99 	
100 		r.max.x -= this.padding;
101 		r.max.y -= this.padding;
102 
103 		return true;
104 	}
105 
106 	recti[] InsertArray(recti[] rects)   {
107 		auto r = new recti[rects.length];
108 		auto numRects = rects.length;
109 		while (numRects != 0) {
110 			auto bestScore1 = int.max;
111 			auto bestScore2 = int.max;
112 			auto bestRectIndex = -1;
113 			recti bestNode;
114 
115 			foreach (int i,ref recti rect; rects) {
116 				if (r[i] != recti.Zero) {
117 					continue;
118 				}	
119 				int score1, score2;
120 				auto newNode = this.FindPositionForNewNodeBestShortSideFit(rect.Dx()+this.padding, rect.Dy()+this.padding, score1, score2);
121 				if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) {
122 					bestScore1 = score1;
123 					bestScore2 = score2;
124 					bestNode = newNode;
125 					bestRectIndex = i;
126 				}
127 			}
128 
129 			if (bestRectIndex == -1) {
130 				return null;
131 			} else {
132 				placeRect(bestNode);
133 				bestNode.max.x -= padding;
134 				bestNode.max.y -= padding;
135 				r[bestRectIndex] = bestNode;
136 				numRects--;
137 			}
138 		}
139 		return r;
140 	}
141 
142 	void PruneFreeList() {
143 		/*
144 		///  Would be nice to do something like this, to avoid a Theta(n^2) loop through each pair.
145 		///  But unfortunately it doesn't quite cut it, since we also want to detect containment.
146 		///  Perhaps there's another way to do this faster than Theta(n^2).
147 
148 		if (freeRectangles.size() > 0)
149 		clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size(), NodeSortCmp);
150 
151 		for(size_t i = 0; i < freeRectangles.size()-1; ++i)
152 		if (freeRectangles[i].x == freeRectangles[i+1].x &&
153 		freeRectangles[i].y == freeRectangles[i+1].y &&
154 		freeRectangles[i].width == freeRectangles[i+1].width &&
155 		freeRectangles[i].height == freeRectangles[i+1].height)
156 		{
157 		freeRectangles.erase(freeRectangles.begin() + i);
158 		--i;
159 		}
160 		*/
161 
162 		/// Go through each pair and remove any rectangle that is redundant.
163 		for (auto i = 0; i < freeRectangles.length; i++) {
164 			for (auto j = i + 1; j < freeRectangles.length; j++) {
165 				if (freeRectangles[i].In(this.freeRectangles[j])) {
166 					freeRectangles[i] = freeRectangles[freeRectangles.length-1];
167 					//freeRectangles = freeRectangles[0..i] ~ freeRectangles[i+1..$];
168 					freeRectangles.length--;
169 					
170 					i--;
171 					break;
172 				}
173 				if (this.freeRectangles[j].In(this.freeRectangles[i])) {
174 					freeRectangles[j] = freeRectangles[freeRectangles.length-1];
175 					//freeRectangles = freeRectangles[0..j] ~ freeRectangles[j+1..$];
176 					freeRectangles.length--;
177 					j--;
178 				}
179 			}
180 		}
181 	}
182 
183 	recti FindPositionForNewNodeBestShortSideFit(int width,int height, out int bestShortSideFit, out int bestLongSideFit) {
184 		bestShortSideFit = int.max;
185 		bestLongSideFit = int.max;
186 		recti bestNode;
187 		foreach (ref r; freeRectangles) {
188 				auto rW = r.Dx();
189 				auto rH = r.Dy();
190 				// Try to place the rectangle in upright (non-flipped) orientation.
191 				if (rW >= width && rH >= height) {
192 						auto leftoverHoriz = abs(rW - width);
193 						auto leftoverVert = abs(rH - height);
194 						auto shortSideFit = min(leftoverHoriz, leftoverVert);
195 						auto longSideFit = max(leftoverHoriz, leftoverVert);
196 
197 						if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit)) {
198 								bestNode.min = r.min;
199 								bestNode.max = vec2i(bestNode.min.x + width, bestNode.min.y + height);
200 								bestShortSideFit = shortSideFit;
201 								bestLongSideFit = longSideFit;
202 						}
203 				}
204 
205 			/* Disable rotation
206 			if (rW >= height && rH >= width)
207 			{
208 				auto flippedLeftoverHoriz = abs(rW - height);
209 				auto flippedLeftoverVert = abs(rH - width);
210 				auto flippedShortSideFit = min(flippedLeftoverHoriz, flippedLeftoverVert);
211 				auto flippedLongSideFit = max(flippedLeftoverHoriz, flippedLeftoverVert);
212 
213 				if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit)) {
214 					bestNode.min = vec2i(freeRectangles[i].x, freeRectangles[i].y);
215 					bestNode.max = vec2i(freeRectangles[i].x + height,  freeRectangles[i].y + width);
216 					bestShortSideFit = flippedShortSideFit;
217 					bestLongSideFit = flippedLongSideFit;
218 				}
219 			}
220 			*/
221 		}
222 		return bestNode;
223 	}
224 
225 	bool SplitFreeNode(recti freeNode,recti usedNode )  {
226 		// Test with SAT if the rectangles even intersect.
227 		if (usedNode.min.x >= freeNode.max.x || usedNode.max.x <= freeNode.min.x ||
228 			usedNode.min.y >= freeNode.max.y || usedNode.max.y <= freeNode.min.y) {
229 				return false;
230 		}
231 
232 		if (usedNode.min.x < freeNode.max.x && usedNode.max.x > freeNode.min.x) {
233 			// New node at the top side of the used node.
234 			if (usedNode.min.y > freeNode.min.y && usedNode.min.y < freeNode.max.y) {
235 					auto newNode = freeNode;
236 					newNode.max.y = usedNode.min.y;
237 					this.freeRectangles ~= newNode;
238 			}
239 
240 			// New node at the bottom side of the used node.
241 			if (usedNode.max.y < freeNode.max.y) {
242 					auto newNode = freeNode;
243 					newNode.min.y = usedNode.max.y;
244 					this.freeRectangles ~= newNode;
245 			}
246 		}
247 
248 		if (usedNode.min.y < freeNode.max.y && usedNode.max.y > freeNode.min.y) {
249 			// New node at the left side of the used node.
250 			if (usedNode.min.x > freeNode.min.x && usedNode.min.x < freeNode.max.x) {
251 					auto newNode = freeNode;
252 					newNode.max.x = usedNode.min.x;
253 					this.freeRectangles ~= newNode;
254 			}
255 
256 			// New node at the right side of the used node.
257 			if (usedNode.max.x < freeNode.max.x) {
258 					auto newNode = freeNode;
259 					newNode.min.x = usedNode.max.x;
260 					this.freeRectangles ~= newNode;
261 			}
262 		}
263 
264 		return true;
265 	}
266 
267 	static vec2i FindOptimalSizeFast(long totalSize)  {
268 		long ww = 1, hh = 1;
269 		auto sw = true;
270 		while (ww*hh < totalSize) {
271 			if (sw) {
272 				hh *= 2;
273 			} else {
274 				ww *= 2;
275 			}
276 			sw = !sw;
277 		}
278 		return vec2i(cast(int)(ww),cast(int)(hh));
279 	}
280 
281 	/*
282 	This needs to be smarter, but it does work great for images like fonts
283 	*/
284 	static vec2i FindOptimalSize(int tries ,recti[] rects, int padding)  {
285 		long totalSize = 0;
286 		foreach (ref rect; rects) {
287 			totalSize += (rect.Dx() * rect.Dy());
288 		}
289 
290 		auto size = FindOptimalSizeFast(totalSize);
291 		auto sw = true;
292 		if (size.x < size.y) {
293 			sw = false;
294 		}
295 		auto bin = new MaxRectsBinPack(size.x, size.y, padding);
296 		for (auto i = 0; i < tries; i++) {
297 			auto rs = bin.InsertArray(rects);
298 			if (rs != null) {
299 				return size;
300 			}
301 			if (sw) {
302 				size.y *= 2;
303 				sw = !sw;
304 			} else {
305 				size.x *= 2;
306 				sw = !sw;
307 			}	
308 			bin.Reset();
309 			bin.width = size.x;
310 			bin.height = size.y;
311 		}
312 
313 		return vec2i(0,0);
314 	}
315 }
316