1/*******************************************************************************
2 * Copyright 2011 See AUTHORS file.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *   http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16
17package com.badlogic.gdx.tools.texturepacker;
18
19import java.util.Comparator;
20
21import com.badlogic.gdx.math.MathUtils;
22import com.badlogic.gdx.tools.texturepacker.TexturePacker.Packer;
23import com.badlogic.gdx.tools.texturepacker.TexturePacker.Page;
24import com.badlogic.gdx.tools.texturepacker.TexturePacker.Rect;
25import com.badlogic.gdx.tools.texturepacker.TexturePacker.Settings;
26import com.badlogic.gdx.utils.Array;
27import com.badlogic.gdx.utils.Sort;
28
29/** Packs pages of images using the maximal rectangles bin packing algorithm by Jukka Jylänki. A brute force binary search is
30 * used to pack into the smallest bin possible.
31 * @author Nathan Sweet */
32public class MaxRectsPacker implements Packer {
33	private RectComparator rectComparator = new RectComparator();
34	private FreeRectChoiceHeuristic[] methods = FreeRectChoiceHeuristic.values();
35	private MaxRects maxRects = new MaxRects();
36	Settings settings;
37	private Sort sort = new Sort();
38
39	public MaxRectsPacker (Settings settings) {
40		this.settings = settings;
41		if (settings.minWidth > settings.maxWidth) throw new RuntimeException("Page min width cannot be higher than max width.");
42		if (settings.minHeight > settings.maxHeight)
43			throw new RuntimeException("Page min height cannot be higher than max height.");
44	}
45
46	public Array<Page> pack (Array<Rect> inputRects) {
47		for (int i = 0, nn = inputRects.size; i < nn; i++) {
48			Rect rect = inputRects.get(i);
49			rect.width += settings.paddingX;
50			rect.height += settings.paddingY;
51		}
52
53		if (settings.fast) {
54			if (settings.rotation) {
55				// Sort by longest side if rotation is enabled.
56				sort.sort(inputRects, new Comparator<Rect>() {
57					public int compare (Rect o1, Rect o2) {
58						int n1 = o1.width > o1.height ? o1.width : o1.height;
59						int n2 = o2.width > o2.height ? o2.width : o2.height;
60						return n2 - n1;
61					}
62				});
63			} else {
64				// Sort only by width (largest to smallest) if rotation is disabled.
65				sort.sort(inputRects, new Comparator<Rect>() {
66					public int compare (Rect o1, Rect o2) {
67						return o2.width - o1.width;
68					}
69				});
70			}
71		}
72
73		Array<Page> pages = new Array();
74		while (inputRects.size > 0)
75
76		{
77			Page result = packPage(inputRects);
78			pages.add(result);
79			inputRects = result.remainingRects;
80		}
81		return pages;
82
83	}
84
85	private Page packPage (Array<Rect> inputRects) {
86		int paddingX = settings.paddingX, paddingY = settings.paddingY;
87		float maxWidth = settings.maxWidth, maxHeight = settings.maxHeight;
88		int edgePaddingX = 0, edgePaddingY = 0;
89		if (settings.edgePadding) {
90			if (settings.duplicatePadding) { // If duplicatePadding, edges get only half padding.
91				maxWidth -= paddingX;
92				maxHeight -= paddingY;
93			} else {
94				maxWidth -= paddingX * 2;
95				maxHeight -= paddingY * 2;
96				edgePaddingX = paddingX;
97				edgePaddingY = paddingY;
98			}
99		}
100
101		// Find min size.
102		int minWidth = Integer.MAX_VALUE, minHeight = Integer.MAX_VALUE;
103		for (int i = 0, nn = inputRects.size; i < nn; i++) {
104			Rect rect = inputRects.get(i);
105			minWidth = Math.min(minWidth, rect.width);
106			minHeight = Math.min(minHeight, rect.height);
107			float width = rect.width - paddingX, height = rect.height - paddingY;
108			if (settings.rotation) {
109				if ((width > maxWidth || height > maxHeight) && (width > maxHeight || height > maxWidth)) {
110					String paddingMessage = (edgePaddingX > 0 || edgePaddingY > 0) ? (" and edge padding " + paddingX + "," + paddingY)
111						: "";
112					throw new RuntimeException("Image does not fit with max page size " + settings.maxWidth + "x" + settings.maxHeight
113						+ paddingMessage + ": " + rect.name + "[" + width + "," + height + "]");
114				}
115			} else {
116				if (width > maxWidth) {
117					String paddingMessage = edgePaddingX > 0 ? (" and X edge padding " + paddingX) : "";
118					throw new RuntimeException("Image does not fit with max page width " + settings.maxWidth + paddingMessage + ": "
119						+ rect.name + "[" + width + "," + height + "]");
120				}
121				if (height > maxHeight && (!settings.rotation || width > maxHeight)) {
122					String paddingMessage = edgePaddingY > 0 ? (" and Y edge padding " + paddingY) : "";
123					throw new RuntimeException("Image does not fit in max page height " + settings.maxHeight + paddingMessage + ": "
124						+ rect.name + "[" + width + "," + height + "]");
125				}
126			}
127		}
128		minWidth = Math.max(minWidth, settings.minWidth);
129		minHeight = Math.max(minHeight, settings.minHeight);
130
131		if (!settings.silent) System.out.print("Packing");
132
133		// Find the minimal page size that fits all rects.
134		Page bestResult = null;
135		if (settings.square) {
136			int minSize = Math.max(minWidth, minHeight);
137			int maxSize = Math.min(settings.maxWidth, settings.maxHeight);
138			BinarySearch sizeSearch = new BinarySearch(minSize, maxSize, settings.fast ? 25 : 15, settings.pot);
139			int size = sizeSearch.reset(), i = 0;
140			while (size != -1) {
141				Page result = packAtSize(true, size - edgePaddingX, size - edgePaddingY, inputRects);
142				if (!settings.silent) {
143					if (++i % 70 == 0) System.out.println();
144					System.out.print(".");
145				}
146				bestResult = getBest(bestResult, result);
147				size = sizeSearch.next(result == null);
148			}
149			if (!settings.silent) System.out.println();
150			// Rects don't fit on one page. Fill a whole page and return.
151			if (bestResult == null) bestResult = packAtSize(false, maxSize - edgePaddingX, maxSize - edgePaddingY, inputRects);
152			sort.sort(bestResult.outputRects, rectComparator);
153			bestResult.width = Math.max(bestResult.width, bestResult.height);
154			bestResult.height = Math.max(bestResult.width, bestResult.height);
155			return bestResult;
156		} else {
157			BinarySearch widthSearch = new BinarySearch(minWidth, settings.maxWidth, settings.fast ? 25 : 15, settings.pot);
158			BinarySearch heightSearch = new BinarySearch(minHeight, settings.maxHeight, settings.fast ? 25 : 15, settings.pot);
159			int width = widthSearch.reset(), i = 0;
160			int height = settings.square ? width : heightSearch.reset();
161			while (true) {
162				Page bestWidthResult = null;
163				while (width != -1) {
164					Page result = packAtSize(true, width - edgePaddingX, height - edgePaddingY, inputRects);
165					if (!settings.silent) {
166						if (++i % 70 == 0) System.out.println();
167						System.out.print(".");
168					}
169					bestWidthResult = getBest(bestWidthResult, result);
170					width = widthSearch.next(result == null);
171					if (settings.square) height = width;
172				}
173				bestResult = getBest(bestResult, bestWidthResult);
174				if (settings.square) break;
175				height = heightSearch.next(bestWidthResult == null);
176				if (height == -1) break;
177				width = widthSearch.reset();
178			}
179			if (!settings.silent) System.out.println();
180			// Rects don't fit on one page. Fill a whole page and return.
181			if (bestResult == null)
182				bestResult = packAtSize(false, settings.maxWidth - edgePaddingX, settings.maxHeight - edgePaddingY, inputRects);
183			sort.sort(bestResult.outputRects, rectComparator);
184			return bestResult;
185		}
186	}
187
188	/** @param fully If true, the only results that pack all rects will be considered. If false, all results are considered, not
189	 *           all rects may be packed. */
190	private Page packAtSize (boolean fully, int width, int height, Array<Rect> inputRects) {
191		Page bestResult = null;
192		for (int i = 0, n = methods.length; i < n; i++) {
193			maxRects.init(width, height);
194			Page result;
195			if (!settings.fast) {
196				result = maxRects.pack(inputRects, methods[i]);
197			} else {
198				Array<Rect> remaining = new Array();
199				for (int ii = 0, nn = inputRects.size; ii < nn; ii++) {
200					Rect rect = inputRects.get(ii);
201					if (maxRects.insert(rect, methods[i]) == null) {
202						while (ii < nn)
203							remaining.add(inputRects.get(ii++));
204					}
205				}
206				result = maxRects.getResult();
207				result.remainingRects = remaining;
208			}
209			if (fully && result.remainingRects.size > 0) continue;
210			if (result.outputRects.size == 0) continue;
211			bestResult = getBest(bestResult, result);
212		}
213		return bestResult;
214	}
215
216	private Page getBest (Page result1, Page result2) {
217		if (result1 == null) return result2;
218		if (result2 == null) return result1;
219		return result1.occupancy > result2.occupancy ? result1 : result2;
220	}
221
222	static class BinarySearch {
223		int min, max, fuzziness, low, high, current;
224		boolean pot;
225
226		public BinarySearch (int min, int max, int fuzziness, boolean pot) {
227			this.pot = pot;
228			this.fuzziness = pot ? 0 : fuzziness;
229			this.min = pot ? (int)(Math.log(MathUtils.nextPowerOfTwo(min)) / Math.log(2)) : min;
230			this.max = pot ? (int)(Math.log(MathUtils.nextPowerOfTwo(max)) / Math.log(2)) : max;
231		}
232
233		public int reset () {
234			low = min;
235			high = max;
236			current = (low + high) >>> 1;
237			return pot ? (int)Math.pow(2, current) : current;
238		}
239
240		public int next (boolean result) {
241			if (low >= high) return -1;
242			if (result)
243				low = current + 1;
244			else
245				high = current - 1;
246			current = (low + high) >>> 1;
247			if (Math.abs(low - high) < fuzziness) return -1;
248			return pot ? (int)Math.pow(2, current) : current;
249		}
250	}
251
252	/** Maximal rectangles bin packing algorithm. Adapted from this C++ public domain source:
253	 * http://clb.demon.fi/projects/even-more-rectangle-bin-packing
254	 * @author Jukka Jyl�nki
255	 * @author Nathan Sweet */
256	class MaxRects {
257		private int binWidth;
258		private int binHeight;
259		private final Array<Rect> usedRectangles = new Array();
260		private final Array<Rect> freeRectangles = new Array();
261
262		public void init (int width, int height) {
263			binWidth = width;
264			binHeight = height;
265
266			usedRectangles.clear();
267			freeRectangles.clear();
268			Rect n = new Rect();
269			n.x = 0;
270			n.y = 0;
271			n.width = width;
272			n.height = height;
273			freeRectangles.add(n);
274		}
275
276		/** Packs a single image. Order is defined externally. */
277		public Rect insert (Rect rect, FreeRectChoiceHeuristic method) {
278			Rect newNode = scoreRect(rect, method);
279			if (newNode.height == 0) return null;
280
281			int numRectanglesToProcess = freeRectangles.size;
282			for (int i = 0; i < numRectanglesToProcess; ++i) {
283				if (splitFreeNode(freeRectangles.get(i), newNode)) {
284					freeRectangles.removeIndex(i);
285					--i;
286					--numRectanglesToProcess;
287				}
288			}
289
290			pruneFreeList();
291
292			Rect bestNode = new Rect();
293			bestNode.set(rect);
294			bestNode.score1 = newNode.score1;
295			bestNode.score2 = newNode.score2;
296			bestNode.x = newNode.x;
297			bestNode.y = newNode.y;
298			bestNode.width = newNode.width;
299			bestNode.height = newNode.height;
300			bestNode.rotated = newNode.rotated;
301
302			usedRectangles.add(bestNode);
303			return bestNode;
304		}
305
306		/** For each rectangle, packs each one then chooses the best and packs that. Slow! */
307		public Page pack (Array<Rect> rects, FreeRectChoiceHeuristic method) {
308			rects = new Array(rects);
309			while (rects.size > 0) {
310				int bestRectIndex = -1;
311				Rect bestNode = new Rect();
312				bestNode.score1 = Integer.MAX_VALUE;
313				bestNode.score2 = Integer.MAX_VALUE;
314
315				// Find the next rectangle that packs best.
316				for (int i = 0; i < rects.size; i++) {
317					Rect newNode = scoreRect(rects.get(i), method);
318					if (newNode.score1 < bestNode.score1 || (newNode.score1 == bestNode.score1 && newNode.score2 < bestNode.score2)) {
319						bestNode.set(rects.get(i));
320						bestNode.score1 = newNode.score1;
321						bestNode.score2 = newNode.score2;
322						bestNode.x = newNode.x;
323						bestNode.y = newNode.y;
324						bestNode.width = newNode.width;
325						bestNode.height = newNode.height;
326						bestNode.rotated = newNode.rotated;
327						bestRectIndex = i;
328					}
329				}
330
331				if (bestRectIndex == -1) break;
332
333				placeRect(bestNode);
334				rects.removeIndex(bestRectIndex);
335			}
336
337			Page result = getResult();
338			result.remainingRects = rects;
339			return result;
340		}
341
342		public Page getResult () {
343			int w = 0, h = 0;
344			for (int i = 0; i < usedRectangles.size; i++) {
345				Rect rect = usedRectangles.get(i);
346				w = Math.max(w, rect.x + rect.width);
347				h = Math.max(h, rect.y + rect.height);
348			}
349			Page result = new Page();
350			result.outputRects = new Array(usedRectangles);
351			result.occupancy = getOccupancy();
352			result.width = w;
353			result.height = h;
354			return result;
355		}
356
357		private void placeRect (Rect node) {
358			int numRectanglesToProcess = freeRectangles.size;
359			for (int i = 0; i < numRectanglesToProcess; i++) {
360				if (splitFreeNode(freeRectangles.get(i), node)) {
361					freeRectangles.removeIndex(i);
362					--i;
363					--numRectanglesToProcess;
364				}
365			}
366
367			pruneFreeList();
368
369			usedRectangles.add(node);
370		}
371
372		private Rect scoreRect (Rect rect, FreeRectChoiceHeuristic method) {
373			int width = rect.width;
374			int height = rect.height;
375			int rotatedWidth = height - settings.paddingY + settings.paddingX;
376			int rotatedHeight = width - settings.paddingX + settings.paddingY;
377			boolean rotate = rect.canRotate && settings.rotation;
378
379			Rect newNode = null;
380			switch (method) {
381			case BestShortSideFit:
382				newNode = findPositionForNewNodeBestShortSideFit(width, height, rotatedWidth, rotatedHeight, rotate);
383				break;
384			case BottomLeftRule:
385				newNode = findPositionForNewNodeBottomLeft(width, height, rotatedWidth, rotatedHeight, rotate);
386				break;
387			case ContactPointRule:
388				newNode = findPositionForNewNodeContactPoint(width, height, rotatedWidth, rotatedHeight, rotate);
389				newNode.score1 = -newNode.score1; // Reverse since we are minimizing, but for contact point score bigger is better.
390				break;
391			case BestLongSideFit:
392				newNode = findPositionForNewNodeBestLongSideFit(width, height, rotatedWidth, rotatedHeight, rotate);
393				break;
394			case BestAreaFit:
395				newNode = findPositionForNewNodeBestAreaFit(width, height, rotatedWidth, rotatedHeight, rotate);
396				break;
397			}
398
399			// Cannot fit the current rectangle.
400			if (newNode.height == 0) {
401				newNode.score1 = Integer.MAX_VALUE;
402				newNode.score2 = Integer.MAX_VALUE;
403			}
404
405			return newNode;
406		}
407
408		// / Computes the ratio of used surface area.
409		private float getOccupancy () {
410			int usedSurfaceArea = 0;
411			for (int i = 0; i < usedRectangles.size; i++)
412				usedSurfaceArea += usedRectangles.get(i).width * usedRectangles.get(i).height;
413			return (float)usedSurfaceArea / (binWidth * binHeight);
414		}
415
416		private Rect findPositionForNewNodeBottomLeft (int width, int height, int rotatedWidth, int rotatedHeight, boolean rotate) {
417			Rect bestNode = new Rect();
418
419			bestNode.score1 = Integer.MAX_VALUE; // best y, score2 is best x
420
421			for (int i = 0; i < freeRectangles.size; i++) {
422				// Try to place the rectangle in upright (non-rotated) orientation.
423				if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
424					int topSideY = freeRectangles.get(i).y + height;
425					if (topSideY < bestNode.score1 || (topSideY == bestNode.score1 && freeRectangles.get(i).x < bestNode.score2)) {
426						bestNode.x = freeRectangles.get(i).x;
427						bestNode.y = freeRectangles.get(i).y;
428						bestNode.width = width;
429						bestNode.height = height;
430						bestNode.score1 = topSideY;
431						bestNode.score2 = freeRectangles.get(i).x;
432						bestNode.rotated = false;
433					}
434				}
435				if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
436					int topSideY = freeRectangles.get(i).y + rotatedHeight;
437					if (topSideY < bestNode.score1 || (topSideY == bestNode.score1 && freeRectangles.get(i).x < bestNode.score2)) {
438						bestNode.x = freeRectangles.get(i).x;
439						bestNode.y = freeRectangles.get(i).y;
440						bestNode.width = rotatedWidth;
441						bestNode.height = rotatedHeight;
442						bestNode.score1 = topSideY;
443						bestNode.score2 = freeRectangles.get(i).x;
444						bestNode.rotated = true;
445					}
446				}
447			}
448			return bestNode;
449		}
450
451		private Rect findPositionForNewNodeBestShortSideFit (int width, int height, int rotatedWidth, int rotatedHeight,
452			boolean rotate) {
453			Rect bestNode = new Rect();
454			bestNode.score1 = Integer.MAX_VALUE;
455
456			for (int i = 0; i < freeRectangles.size; i++) {
457				// Try to place the rectangle in upright (non-rotated) orientation.
458				if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
459					int leftoverHoriz = Math.abs(freeRectangles.get(i).width - width);
460					int leftoverVert = Math.abs(freeRectangles.get(i).height - height);
461					int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
462					int longSideFit = Math.max(leftoverHoriz, leftoverVert);
463
464					if (shortSideFit < bestNode.score1 || (shortSideFit == bestNode.score1 && longSideFit < bestNode.score2)) {
465						bestNode.x = freeRectangles.get(i).x;
466						bestNode.y = freeRectangles.get(i).y;
467						bestNode.width = width;
468						bestNode.height = height;
469						bestNode.score1 = shortSideFit;
470						bestNode.score2 = longSideFit;
471						bestNode.rotated = false;
472					}
473				}
474
475				if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
476					int flippedLeftoverHoriz = Math.abs(freeRectangles.get(i).width - rotatedWidth);
477					int flippedLeftoverVert = Math.abs(freeRectangles.get(i).height - rotatedHeight);
478					int flippedShortSideFit = Math.min(flippedLeftoverHoriz, flippedLeftoverVert);
479					int flippedLongSideFit = Math.max(flippedLeftoverHoriz, flippedLeftoverVert);
480
481					if (flippedShortSideFit < bestNode.score1
482						|| (flippedShortSideFit == bestNode.score1 && flippedLongSideFit < bestNode.score2)) {
483						bestNode.x = freeRectangles.get(i).x;
484						bestNode.y = freeRectangles.get(i).y;
485						bestNode.width = rotatedWidth;
486						bestNode.height = rotatedHeight;
487						bestNode.score1 = flippedShortSideFit;
488						bestNode.score2 = flippedLongSideFit;
489						bestNode.rotated = true;
490					}
491				}
492			}
493
494			return bestNode;
495		}
496
497		private Rect findPositionForNewNodeBestLongSideFit (int width, int height, int rotatedWidth, int rotatedHeight,
498			boolean rotate) {
499			Rect bestNode = new Rect();
500
501			bestNode.score2 = Integer.MAX_VALUE;
502
503			for (int i = 0; i < freeRectangles.size; i++) {
504				// Try to place the rectangle in upright (non-rotated) orientation.
505				if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
506					int leftoverHoriz = Math.abs(freeRectangles.get(i).width - width);
507					int leftoverVert = Math.abs(freeRectangles.get(i).height - height);
508					int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
509					int longSideFit = Math.max(leftoverHoriz, leftoverVert);
510
511					if (longSideFit < bestNode.score2 || (longSideFit == bestNode.score2 && shortSideFit < bestNode.score1)) {
512						bestNode.x = freeRectangles.get(i).x;
513						bestNode.y = freeRectangles.get(i).y;
514						bestNode.width = width;
515						bestNode.height = height;
516						bestNode.score1 = shortSideFit;
517						bestNode.score2 = longSideFit;
518						bestNode.rotated = false;
519					}
520				}
521
522				if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
523					int leftoverHoriz = Math.abs(freeRectangles.get(i).width - rotatedWidth);
524					int leftoverVert = Math.abs(freeRectangles.get(i).height - rotatedHeight);
525					int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
526					int longSideFit = Math.max(leftoverHoriz, leftoverVert);
527
528					if (longSideFit < bestNode.score2 || (longSideFit == bestNode.score2 && shortSideFit < bestNode.score1)) {
529						bestNode.x = freeRectangles.get(i).x;
530						bestNode.y = freeRectangles.get(i).y;
531						bestNode.width = rotatedWidth;
532						bestNode.height = rotatedHeight;
533						bestNode.score1 = shortSideFit;
534						bestNode.score2 = longSideFit;
535						bestNode.rotated = true;
536					}
537				}
538			}
539			return bestNode;
540		}
541
542		private Rect findPositionForNewNodeBestAreaFit (int width, int height, int rotatedWidth, int rotatedHeight,
543			boolean rotate) {
544			Rect bestNode = new Rect();
545
546			bestNode.score1 = Integer.MAX_VALUE; // best area fit, score2 is best short side fit
547
548			for (int i = 0; i < freeRectangles.size; i++) {
549				int areaFit = freeRectangles.get(i).width * freeRectangles.get(i).height - width * height;
550
551				// Try to place the rectangle in upright (non-rotated) orientation.
552				if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
553					int leftoverHoriz = Math.abs(freeRectangles.get(i).width - width);
554					int leftoverVert = Math.abs(freeRectangles.get(i).height - height);
555					int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
556
557					if (areaFit < bestNode.score1 || (areaFit == bestNode.score1 && shortSideFit < bestNode.score2)) {
558						bestNode.x = freeRectangles.get(i).x;
559						bestNode.y = freeRectangles.get(i).y;
560						bestNode.width = width;
561						bestNode.height = height;
562						bestNode.score2 = shortSideFit;
563						bestNode.score1 = areaFit;
564						bestNode.rotated = false;
565					}
566				}
567
568				if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
569					int leftoverHoriz = Math.abs(freeRectangles.get(i).width - rotatedWidth);
570					int leftoverVert = Math.abs(freeRectangles.get(i).height - rotatedHeight);
571					int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
572
573					if (areaFit < bestNode.score1 || (areaFit == bestNode.score1 && shortSideFit < bestNode.score2)) {
574						bestNode.x = freeRectangles.get(i).x;
575						bestNode.y = freeRectangles.get(i).y;
576						bestNode.width = rotatedWidth;
577						bestNode.height = rotatedHeight;
578						bestNode.score2 = shortSideFit;
579						bestNode.score1 = areaFit;
580						bestNode.rotated = true;
581					}
582				}
583			}
584			return bestNode;
585		}
586
587		// / Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise.
588		private int commonIntervalLength (int i1start, int i1end, int i2start, int i2end) {
589			if (i1end < i2start || i2end < i1start) return 0;
590			return Math.min(i1end, i2end) - Math.max(i1start, i2start);
591		}
592
593		private int contactPointScoreNode (int x, int y, int width, int height) {
594			int score = 0;
595
596			if (x == 0 || x + width == binWidth) score += height;
597			if (y == 0 || y + height == binHeight) score += width;
598
599			Array<Rect> usedRectangles = this.usedRectangles;
600			for (int i = 0, n = usedRectangles.size; i < n; i++) {
601				Rect rect = usedRectangles.get(i);
602				if (rect.x == x + width || rect.x + rect.width == x)
603					score += commonIntervalLength(rect.y, rect.y + rect.height, y, y + height);
604				if (rect.y == y + height || rect.y + rect.height == y)
605					score += commonIntervalLength(rect.x, rect.x + rect.width, x, x + width);
606			}
607			return score;
608		}
609
610		private Rect findPositionForNewNodeContactPoint (int width, int height, int rotatedWidth, int rotatedHeight,
611			boolean rotate) {
612
613			Rect bestNode = new Rect();
614			bestNode.score1 = -1; // best contact score
615
616			Array<Rect> freeRectangles = this.freeRectangles;
617			for (int i = 0, n = freeRectangles.size; i < n; i++) {
618				// Try to place the rectangle in upright (non-rotated) orientation.
619				Rect free = freeRectangles.get(i);
620				if (free.width >= width && free.height >= height) {
621					int score = contactPointScoreNode(free.x, free.y, width, height);
622					if (score > bestNode.score1) {
623						bestNode.x = free.x;
624						bestNode.y = free.y;
625						bestNode.width = width;
626						bestNode.height = height;
627						bestNode.score1 = score;
628						bestNode.rotated = false;
629					}
630				}
631				if (rotate && free.width >= rotatedWidth && free.height >= rotatedHeight) {
632					int score = contactPointScoreNode(free.x, free.y, rotatedWidth, rotatedHeight);
633					if (score > bestNode.score1) {
634						bestNode.x = free.x;
635						bestNode.y = free.y;
636						bestNode.width = rotatedWidth;
637						bestNode.height = rotatedHeight;
638						bestNode.score1 = score;
639						bestNode.rotated = true;
640					}
641				}
642			}
643			return bestNode;
644		}
645
646		private boolean splitFreeNode (Rect freeNode, Rect usedNode) {
647			// Test with SAT if the rectangles even intersect.
648			if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x
649				|| usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y) return false;
650
651			if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
652				// New node at the top side of the used node.
653				if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
654					Rect newNode = new Rect(freeNode);
655					newNode.height = usedNode.y - newNode.y;
656					freeRectangles.add(newNode);
657				}
658
659				// New node at the bottom side of the used node.
660				if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
661					Rect newNode = new Rect(freeNode);
662					newNode.y = usedNode.y + usedNode.height;
663					newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
664					freeRectangles.add(newNode);
665				}
666			}
667
668			if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
669				// New node at the left side of the used node.
670				if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
671					Rect newNode = new Rect(freeNode);
672					newNode.width = usedNode.x - newNode.x;
673					freeRectangles.add(newNode);
674				}
675
676				// New node at the right side of the used node.
677				if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
678					Rect newNode = new Rect(freeNode);
679					newNode.x = usedNode.x + usedNode.width;
680					newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
681					freeRectangles.add(newNode);
682				}
683			}
684
685			return true;
686		}
687
688		private void pruneFreeList () {
689			/*
690			 * /// Would be nice to do something like this, to avoid a Theta(n^2) loop through each pair. /// But unfortunately it
691			 * doesn't quite cut it, since we also want to detect containment. /// Perhaps there's another way to do this faster than
692			 * Theta(n^2).
693			 *
694			 * if (freeRectangles.size > 0) clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size, NodeSortCmp);
695			 *
696			 * for(int i = 0; i < freeRectangles.size-1; i++) if (freeRectangles[i].x == freeRectangles[i+1].x && freeRectangles[i].y
697			 * == freeRectangles[i+1].y && freeRectangles[i].width == freeRectangles[i+1].width && freeRectangles[i].height ==
698			 * freeRectangles[i+1].height) { freeRectangles.erase(freeRectangles.begin() + i); --i; }
699			 */
700
701			// Go through each pair and remove any rectangle that is redundant.
702			Array<Rect> freeRectangles = this.freeRectangles;
703			for (int i = 0, n = freeRectangles.size; i < n; i++)
704				for (int j = i + 1; j < n; ++j) {
705					Rect rect1 = freeRectangles.get(i);
706					Rect rect2 = freeRectangles.get(j);
707					if (isContainedIn(rect1, rect2)) {
708						freeRectangles.removeIndex(i);
709						--i;
710						--n;
711						break;
712					}
713					if (isContainedIn(rect2, rect1)) {
714						freeRectangles.removeIndex(j);
715						--j;
716						--n;
717					}
718				}
719		}
720
721		private boolean isContainedIn (Rect a, Rect b) {
722			return a.x >= b.x && a.y >= b.y && a.x + a.width <= b.x + b.width && a.y + a.height <= b.y + b.height;
723		}
724	}
725
726	static public enum FreeRectChoiceHeuristic {
727		// BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
728		BestShortSideFit,
729		// BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
730		BestLongSideFit,
731		// BAF: Positions the rectangle into the smallest free rect into which it fits.
732		BestAreaFit,
733		// BL: Does the Tetris placement.
734		BottomLeftRule,
735		// CP: Choosest the placement where the rectangle touches other rects as much as possible.
736		ContactPointRule
737	};
738
739	class RectComparator implements Comparator<Rect> {
740		public int compare (Rect o1, Rect o2) {
741			return Rect.getAtlasName(o1.name, settings.flattenPaths).compareTo(Rect.getAtlasName(o2.name, settings.flattenPaths));
742		}
743	}
744}
745