gdx-studio
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 | } |