gdx-studio

#libgdx#java#desktop

git clone https://git.pyrossh.dev/gdx-studio

An IDE for creating Games using libgdx and Java supported on all platforms Android, iOS, Desktop


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