Package com.badlogic.gdx.tools.imagepacker

Source Code of com.badlogic.gdx.tools.imagepacker.MaxRectsPacker$BinarySearch

/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/

package com.badlogic.gdx.tools.imagepacker;

import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.tools.imagepacker.TexturePacker2.Page;
import com.badlogic.gdx.tools.imagepacker.TexturePacker2.Rect;
import com.badlogic.gdx.tools.imagepacker.TexturePacker2.Settings;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Sort;

import java.util.Comparator;

/** Packs pages of images using the maximal rectangles bin packing algorithm by Jukka Jylänki. A brute force binary search is used
* to pack into the smallest bin possible.
* @author Nathan Sweet */
public class MaxRectsPacker {
  private RectComparator rectComparator = new RectComparator();
  private FreeRectChoiceHeuristic[] methods = FreeRectChoiceHeuristic.values();
  private MaxRects maxRects = new MaxRects();
  Settings settings;
  private Sort sort = new Sort();

  public MaxRectsPacker (Settings settings) {
    this.settings = settings;
    if (settings.minWidth > settings.maxWidth) throw new RuntimeException("Page min width cannot be higher than max width.");
    if (settings.minHeight > settings.maxHeight)
      throw new RuntimeException("Page min height cannot be higher than max height.");
  }

  public Array<Page> pack (Array<Rect> inputRects) {
    for (int i = 0, nn = inputRects.size; i < nn; i++) {
      Rect rect = inputRects.get(i);
      rect.width += settings.paddingX;
      rect.height += settings.paddingY;
    }

    if (settings.fast) {
      if (settings.rotation) {
        // Sort by longest side if rotation is enabled.
        sort.sort(inputRects, new Comparator<Rect>() {
          public int compare (Rect o1, Rect o2) {
            int n1 = o1.width > o1.height ? o1.width : o1.height;
            int n2 = o2.width > o2.height ? o2.width : o2.height;
            return n2 - n1;
          }
        });
      } else {
        // Sort only by width (largest to smallest) if rotation is disabled.
        sort.sort(inputRects, new Comparator<Rect>() {
          public int compare (Rect o1, Rect o2) {
            return o2.width - o1.width;
          }
        });
      }
    }

    Array<Page> pages = new Array();
    while (inputRects.size > 0) {
      Page result = packPage(inputRects);
      pages.add(result);
      inputRects = result.remainingRects;
    }
    return pages;
  }

  private Page packPage (Array<Rect> inputRects) {
    int edgePaddingX = 0, edgePaddingY = 0;
    if (!settings.duplicatePadding) { // if duplicatePadding, edges get only half padding.
      edgePaddingX = settings.paddingX;
      edgePaddingY = settings.paddingY;
    }
    // Find min size.
    int minWidth = Integer.MAX_VALUE;
    int minHeight = Integer.MAX_VALUE;
    for (int i = 0, nn = inputRects.size; i < nn; i++) {
      Rect rect = inputRects.get(i);
      minWidth = Math.min(minWidth, rect.width);
      minHeight = Math.min(minHeight, rect.height);
      if (settings.rotation) {
        if ((rect.width > settings.maxWidth || rect.height > settings.maxHeight)
          && (rect.width > settings.maxHeight || rect.height > settings.maxWidth)) {
          throw new RuntimeException("Image does not fit with max page size " + settings.maxWidth + "x" + settings.maxHeight
            + " and padding " + settings.paddingX + "," + settings.paddingY + ": " + rect);
        }
      } else {
        if (rect.width > settings.maxWidth) {
          throw new RuntimeException("Image does not fit with max page width " + settings.maxWidth + " and paddingX "
            + settings.paddingX + ": " + rect);
        }
        if (rect.height > settings.maxHeight && (!settings.rotation || rect.width > settings.maxHeight)) {
          throw new RuntimeException("Image does not fit in max page height " + settings.maxHeight + " and paddingY "
            + settings.paddingY + ": " + rect);
        }
      }
    }
    minWidth = Math.max(minWidth, settings.minWidth);
    minHeight = Math.max(minHeight, settings.minHeight);

    System.out.print("Packing");

    // Find the minimal page size that fits all rects.
    BinarySearch widthSearch = new BinarySearch(minWidth, settings.maxWidth, settings.fast ? 25 : 15, settings.pot);
    BinarySearch heightSearch = new BinarySearch(minHeight, settings.maxHeight, settings.fast ? 25 : 15, settings.pot);
    int width = widthSearch.reset(), height = heightSearch.reset(), i = 0;
    Page bestResult = null;
    while (true) {
      Page bestWidthResult = null;
      while (width != -1) {
        Page result = packAtSize(true, width - edgePaddingX, height - edgePaddingY, inputRects);
        if (++i % 70 == 0) System.out.println();
        System.out.print(".");
        bestWidthResult = getBest(bestWidthResult, result);
        width = widthSearch.next(result == null);
      }
      bestResult = getBest(bestResult, bestWidthResult);
      height = heightSearch.next(bestWidthResult == null);
      if (height == -1) break;
      width = widthSearch.reset();
    }
    System.out.println();

    // Rects don't fit on one page. Fill a whole page and return.
    if (bestResult == null)
      bestResult = packAtSize(false, settings.maxWidth - edgePaddingX, settings.maxHeight - edgePaddingY, inputRects);

    sort.sort(bestResult.outputRects, rectComparator);

    return bestResult;
  }

  /** @param fully If true, the only results that pack all rects will be considered. If false, all results are considered, not all
   *           rects may be packed. */
  private Page packAtSize (boolean fully, int width, int height, Array<Rect> inputRects) {
    Page bestResult = null;
    for (int i = 0, n = methods.length; i < n; i++) {
      maxRects.init(width, height);
      Page result;
      if (!settings.fast) {
        result = maxRects.pack(inputRects, methods[i]);
      } else {
        Array<Rect> remaining = new Array();
        for (int ii = 0, nn = inputRects.size; ii < nn; ii++) {
          Rect rect = inputRects.get(ii);
          if (maxRects.insert(rect, methods[i]) == null) {
            while (ii < nn)
              remaining.add(inputRects.get(ii++));
          }
        }
        result = maxRects.getResult();
        result.remainingRects = remaining;
      }
      if (fully && result.remainingRects.size > 0) continue;
      if (result.outputRects.size == 0) continue;
      bestResult = getBest(bestResult, result);
    }
    return bestResult;
  }

  private Page getBest (Page result1, Page result2) {
    if (result1 == null) return result2;
    if (result2 == null) return result1;
    return result1.occupancy > result2.occupancy ? result1 : result2;
  }

  static class BinarySearch {
    int min, max, fuzziness, low, high, current;
    boolean pot;

    public BinarySearch (int min, int max, int fuzziness, boolean pot) {
      this.pot = pot;
      this.fuzziness = pot ? 0 : fuzziness;
      this.min = pot ? (int)(Math.log(MathUtils.nextPowerOfTwo(min)) / Math.log(2)) : min;
      this.max = pot ? (int)(Math.log(MathUtils.nextPowerOfTwo(max)) / Math.log(2)) : max;
    }

    public int reset () {
      low = min;
      high = max;
      current = (low + high) >>> 1;
      return pot ? (int)Math.pow(2, current) : current;
    }

    public int next (boolean result) {
      if (low >= high) return -1;
      if (result)
        low = current + 1;
      else
        high = current - 1;
      current = (low + high) >>> 1;
      if (Math.abs(low - high) < fuzziness) return -1;
      return pot ? (int)Math.pow(2, current) : current;
    }
  }

  /** Maximal rectangles bin packing algorithm. Adapted from this C++ public domain source:
   * http://clb.demon.fi/projects/even-more-rectangle-bin-packing
   * @author Jukka Jyl�nki
   * @author Nathan Sweet */
  class MaxRects {
    private int binWidth;
    private int binHeight;
    private final Array<Rect> usedRectangles = new Array();
    private final Array<Rect> freeRectangles = new Array();

    public void init (int width, int height) {
      binWidth = width;
      binHeight = height;

      usedRectangles.clear();
      freeRectangles.clear();
      Rect n = new Rect();
      n.x = 0;
      n.y = 0;
      n.width = width;
      n.height = height;
      freeRectangles.add(n);
    }

    /** Packs a single image. Order is defined externally. */
    public Rect insert (Rect rect, FreeRectChoiceHeuristic method) {
      Rect newNode = scoreRect(rect, method);
      if (newNode.height == 0) return null;

      int numRectanglesToProcess = freeRectangles.size;
      for (int i = 0; i < numRectanglesToProcess; ++i) {
        if (splitFreeNode(freeRectangles.get(i), newNode)) {
          freeRectangles.removeIndex(i);
          --i;
          --numRectanglesToProcess;
        }
      }

      pruneFreeList();

      Rect bestNode = new Rect();
      bestNode.set(rect);
      bestNode.score1 = newNode.score1;
      bestNode.score2 = newNode.score2;
      bestNode.x = newNode.x;
      bestNode.y = newNode.y;
      bestNode.width = newNode.width;
      bestNode.height = newNode.height;
      bestNode.rotated = newNode.rotated;

      usedRectangles.add(bestNode);
      return bestNode;
    }

    /** For each rectangle, packs each one then chooses the best and packs that. Slow! */
    public Page pack (Array<Rect> rects, FreeRectChoiceHeuristic method) {
      rects = new Array(rects);
      while (rects.size > 0) {
        int bestRectIndex = -1;
        Rect bestNode = new Rect();
        bestNode.score1 = Integer.MAX_VALUE;
        bestNode.score2 = Integer.MAX_VALUE;

        // Find the next rectangle that packs best.
        for (int i = 0; i < rects.size; i++) {
          Rect newNode = scoreRect(rects.get(i), method);
          if (newNode.score1 < bestNode.score1 || (newNode.score1 == bestNode.score1 && newNode.score2 < bestNode.score2)) {
            bestNode.set(rects.get(i));
            bestNode.score1 = newNode.score1;
            bestNode.score2 = newNode.score2;
            bestNode.x = newNode.x;
            bestNode.y = newNode.y;
            bestNode.width = newNode.width;
            bestNode.height = newNode.height;
            bestNode.rotated = newNode.rotated;
            bestRectIndex = i;
          }
        }

        if (bestRectIndex == -1) break;

        placeRect(bestNode);
        rects.removeIndex(bestRectIndex);
      }

      Page result = getResult();
      result.remainingRects = rects;
      return result;
    }

    public Page getResult () {
      int w = 0, h = 0;
      for (int i = 0; i < usedRectangles.size; i++) {
        Rect rect = usedRectangles.get(i);
        w = Math.max(w, rect.x + rect.width);
        h = Math.max(h, rect.y + rect.height);
      }
      Page result = new Page();
      result.outputRects = new Array(usedRectangles);
      result.occupancy = getOccupancy();
      result.width = w;
      result.height = h;
      return result;
    }

    private void placeRect (Rect node) {
      int numRectanglesToProcess = freeRectangles.size;
      for (int i = 0; i < numRectanglesToProcess; i++) {
        if (splitFreeNode(freeRectangles.get(i), node)) {
          freeRectangles.removeIndex(i);
          --i;
          --numRectanglesToProcess;
        }
      }

      pruneFreeList();

      usedRectangles.add(node);
    }

    private Rect scoreRect (Rect rect, FreeRectChoiceHeuristic method) {
      int width = rect.width;
      int height = rect.height;
      int rotatedWidth = height - settings.paddingY + settings.paddingX;
      int rotatedHeight = width - settings.paddingX + settings.paddingY;
      boolean rotate = rect.canRotate && settings.rotation;

      Rect newNode = null;
      switch (method) {
      case BestShortSideFit:
        newNode = findPositionForNewNodeBestShortSideFit(width, height, rotatedWidth, rotatedHeight, rotate);
        break;
      case BottomLeftRule:
        newNode = findPositionForNewNodeBottomLeft(width, height, rotatedWidth, rotatedHeight, rotate);
        break;
      case ContactPointRule:
        newNode = findPositionForNewNodeContactPoint(width, height, rotatedWidth, rotatedHeight, rotate);
        newNode.score1 = -newNode.score1; // Reverse since we are minimizing, but for contact point score bigger is better.
        break;
      case BestLongSideFit:
        newNode = findPositionForNewNodeBestLongSideFit(width, height, rotatedWidth, rotatedHeight, rotate);
        break;
      case BestAreaFit:
        newNode = findPositionForNewNodeBestAreaFit(width, height, rotatedWidth, rotatedHeight, rotate);
        break;
      }

      // Cannot fit the current rectangle.
      if (newNode.height == 0) {
        newNode.score1 = Integer.MAX_VALUE;
        newNode.score2 = Integer.MAX_VALUE;
      }

      return newNode;
    }

    // / Computes the ratio of used surface area.
    private float getOccupancy () {
      int usedSurfaceArea = 0;
      for (int i = 0; i < usedRectangles.size; i++)
        usedSurfaceArea += usedRectangles.get(i).width * usedRectangles.get(i).height;
      return (float)usedSurfaceArea / (binWidth * binHeight);
    }

    private Rect findPositionForNewNodeBottomLeft (int width, int height, int rotatedWidth, int rotatedHeight, boolean rotate) {
      Rect bestNode = new Rect();

      bestNode.score1 = Integer.MAX_VALUE; // best y, score2 is best x

      for (int i = 0; i < freeRectangles.size; i++) {
        // Try to place the rectangle in upright (non-rotated) orientation.
        if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
          int topSideY = freeRectangles.get(i).y + height;
          if (topSideY < bestNode.score1 || (topSideY == bestNode.score1 && freeRectangles.get(i).x < bestNode.score2)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = width;
            bestNode.height = height;
            bestNode.score1 = topSideY;
            bestNode.score2 = freeRectangles.get(i).x;
            bestNode.rotated = false;
          }
        }
        if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
          int topSideY = freeRectangles.get(i).y + rotatedHeight;
          if (topSideY < bestNode.score1 || (topSideY == bestNode.score1 && freeRectangles.get(i).x < bestNode.score2)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = rotatedWidth;
            bestNode.height = rotatedHeight;
            bestNode.score1 = topSideY;
            bestNode.score2 = freeRectangles.get(i).x;
            bestNode.rotated = true;
          }
        }
      }
      return bestNode;
    }

    private Rect findPositionForNewNodeBestShortSideFit (int width, int height, int rotatedWidth, int rotatedHeight,
      boolean rotate) {
      Rect bestNode = new Rect();
      bestNode.score1 = Integer.MAX_VALUE;

      for (int i = 0; i < freeRectangles.size; i++) {
        // Try to place the rectangle in upright (non-rotated) orientation.
        if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
          int leftoverHoriz = Math.abs(freeRectangles.get(i).width - width);
          int leftoverVert = Math.abs(freeRectangles.get(i).height - height);
          int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
          int longSideFit = Math.max(leftoverHoriz, leftoverVert);

          if (shortSideFit < bestNode.score1 || (shortSideFit == bestNode.score1 && longSideFit < bestNode.score2)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = width;
            bestNode.height = height;
            bestNode.score1 = shortSideFit;
            bestNode.score2 = longSideFit;
            bestNode.rotated = false;
          }
        }

        if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
          int flippedLeftoverHoriz = Math.abs(freeRectangles.get(i).width - rotatedWidth);
          int flippedLeftoverVert = Math.abs(freeRectangles.get(i).height - rotatedHeight);
          int flippedShortSideFit = Math.min(flippedLeftoverHoriz, flippedLeftoverVert);
          int flippedLongSideFit = Math.max(flippedLeftoverHoriz, flippedLeftoverVert);

          if (flippedShortSideFit < bestNode.score1
            || (flippedShortSideFit == bestNode.score1 && flippedLongSideFit < bestNode.score2)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = rotatedWidth;
            bestNode.height = rotatedHeight;
            bestNode.score1 = flippedShortSideFit;
            bestNode.score2 = flippedLongSideFit;
            bestNode.rotated = true;
          }
        }
      }

      return bestNode;
    }

    private Rect findPositionForNewNodeBestLongSideFit (int width, int height, int rotatedWidth, int rotatedHeight,
      boolean rotate) {
      Rect bestNode = new Rect();

      bestNode.score2 = Integer.MAX_VALUE;

      for (int i = 0; i < freeRectangles.size; i++) {
        // Try to place the rectangle in upright (non-rotated) orientation.
        if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
          int leftoverHoriz = Math.abs(freeRectangles.get(i).width - width);
          int leftoverVert = Math.abs(freeRectangles.get(i).height - height);
          int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
          int longSideFit = Math.max(leftoverHoriz, leftoverVert);

          if (longSideFit < bestNode.score2 || (longSideFit == bestNode.score2 && shortSideFit < bestNode.score1)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = width;
            bestNode.height = height;
            bestNode.score1 = shortSideFit;
            bestNode.score2 = longSideFit;
            bestNode.rotated = false;
          }
        }

        if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
          int leftoverHoriz = Math.abs(freeRectangles.get(i).width - rotatedWidth);
          int leftoverVert = Math.abs(freeRectangles.get(i).height - rotatedHeight);
          int shortSideFit = Math.min(leftoverHoriz, leftoverVert);
          int longSideFit = Math.max(leftoverHoriz, leftoverVert);

          if (longSideFit < bestNode.score2 || (longSideFit == bestNode.score2 && shortSideFit < bestNode.score1)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = rotatedWidth;
            bestNode.height = rotatedHeight;
            bestNode.score1 = shortSideFit;
            bestNode.score2 = longSideFit;
            bestNode.rotated = true;
          }
        }
      }
      return bestNode;
    }

    private Rect findPositionForNewNodeBestAreaFit (int width, int height, int rotatedWidth, int rotatedHeight, boolean rotate) {
      Rect bestNode = new Rect();

      bestNode.score1 = Integer.MAX_VALUE; // best area fit, score2 is best short side fit

      for (int i = 0; i < freeRectangles.size; i++) {
        int areaFit = freeRectangles.get(i).width * freeRectangles.get(i).height - width * height;

        // Try to place the rectangle in upright (non-rotated) orientation.
        if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
          int leftoverHoriz = Math.abs(freeRectangles.get(i).width - width);
          int leftoverVert = Math.abs(freeRectangles.get(i).height - height);
          int shortSideFit = Math.min(leftoverHoriz, leftoverVert);

          if (areaFit < bestNode.score1 || (areaFit == bestNode.score1 && shortSideFit < bestNode.score2)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = width;
            bestNode.height = height;
            bestNode.score2 = shortSideFit;
            bestNode.score1 = areaFit;
            bestNode.rotated = false;
          }
        }

        if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
          int leftoverHoriz = Math.abs(freeRectangles.get(i).width - rotatedWidth);
          int leftoverVert = Math.abs(freeRectangles.get(i).height - rotatedHeight);
          int shortSideFit = Math.min(leftoverHoriz, leftoverVert);

          if (areaFit < bestNode.score1 || (areaFit == bestNode.score1 && shortSideFit < bestNode.score2)) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = rotatedWidth;
            bestNode.height = rotatedHeight;
            bestNode.score2 = shortSideFit;
            bestNode.score1 = areaFit;
            bestNode.rotated = true;
          }
        }
      }
      return bestNode;
    }

    // / Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise.
    private int commonIntervalLength (int i1start, int i1end, int i2start, int i2end) {
      if (i1end < i2start || i2end < i1start) return 0;
      return Math.min(i1end, i2end) - Math.max(i1start, i2start);
    }

    private int contactPointScoreNode (int x, int y, int width, int height) {
      int score = 0;

      if (x == 0 || x + width == binWidth) score += height;
      if (y == 0 || y + height == binHeight) score += width;

      for (int i = 0; i < usedRectangles.size; i++) {
        if (usedRectangles.get(i).x == x + width || usedRectangles.get(i).x + usedRectangles.get(i).width == x)
          score += commonIntervalLength(usedRectangles.get(i).y, usedRectangles.get(i).y + usedRectangles.get(i).height, y,
            y + height);
        if (usedRectangles.get(i).y == y + height || usedRectangles.get(i).y + usedRectangles.get(i).height == y)
          score += commonIntervalLength(usedRectangles.get(i).x, usedRectangles.get(i).x + usedRectangles.get(i).width, x, x
            + width);
      }
      return score;
    }

    private Rect findPositionForNewNodeContactPoint (int width, int height, int rotatedWidth, int rotatedHeight, boolean rotate) {
      Rect bestNode = new Rect();

      bestNode.score1 = -1; // best contact score

      for (int i = 0; i < freeRectangles.size; i++) {
        // Try to place the rectangle in upright (non-rotated) orientation.
        if (freeRectangles.get(i).width >= width && freeRectangles.get(i).height >= height) {
          int score = contactPointScoreNode(freeRectangles.get(i).x, freeRectangles.get(i).y, width, height);
          if (score > bestNode.score1) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = width;
            bestNode.height = height;
            bestNode.score1 = score;
            bestNode.rotated = false;
          }
        }
        if (rotate && freeRectangles.get(i).width >= rotatedWidth && freeRectangles.get(i).height >= rotatedHeight) {
          // This was width,height -- bug fixed?
          int score = contactPointScoreNode(freeRectangles.get(i).x, freeRectangles.get(i).y, rotatedWidth, rotatedHeight);
          if (score > bestNode.score1) {
            bestNode.x = freeRectangles.get(i).x;
            bestNode.y = freeRectangles.get(i).y;
            bestNode.width = rotatedWidth;
            bestNode.height = rotatedHeight;
            bestNode.score1 = score;
            bestNode.rotated = true;
          }
        }
      }
      return bestNode;
    }

    private boolean splitFreeNode (Rect freeNode, Rect usedNode) {
      // Test with SAT if the rectangles even intersect.
      if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x
        || usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y) return false;

      if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) {
        // New node at the top side of the used node.
        if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) {
          Rect newNode = new Rect(freeNode);
          newNode.height = usedNode.y - newNode.y;
          freeRectangles.add(newNode);
        }

        // New node at the bottom side of the used node.
        if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) {
          Rect newNode = new Rect(freeNode);
          newNode.y = usedNode.y + usedNode.height;
          newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height);
          freeRectangles.add(newNode);
        }
      }

      if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) {
        // New node at the left side of the used node.
        if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) {
          Rect newNode = new Rect(freeNode);
          newNode.width = usedNode.x - newNode.x;
          freeRectangles.add(newNode);
        }

        // New node at the right side of the used node.
        if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) {
          Rect newNode = new Rect(freeNode);
          newNode.x = usedNode.x + usedNode.width;
          newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width);
          freeRectangles.add(newNode);
        }
      }

      return true;
    }

    private void pruneFreeList () {
      /*
       * /// Would be nice to do something like this, to avoid a Theta(n^2) loop through each pair. /// But unfortunately it
       * doesn't quite cut it, since we also want to detect containment. /// Perhaps there's another way to do this faster than
       * Theta(n^2).
       *
       * if (freeRectangles.size > 0) clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size, NodeSortCmp);
       *
       * for(int i = 0; i < freeRectangles.size-1; i++) if (freeRectangles[i].x == freeRectangles[i+1].x && freeRectangles[i].y
       * == freeRectangles[i+1].y && freeRectangles[i].width == freeRectangles[i+1].width && freeRectangles[i].height ==
       * freeRectangles[i+1].height) { freeRectangles.erase(freeRectangles.begin() + i); --i; }
       */

      // / Go through each pair and remove any rectangle that is redundant.
      for (int i = 0; i < freeRectangles.size; i++)
        for (int j = i + 1; j < freeRectangles.size; ++j) {
          if (isContainedIn(freeRectangles.get(i), freeRectangles.get(j))) {
            freeRectangles.removeIndex(i);
            --i;
            break;
          }
          if (isContainedIn(freeRectangles.get(j), freeRectangles.get(i))) {
            freeRectangles.removeIndex(j);
            --j;
          }
        }
    }

    private boolean isContainedIn (Rect a, Rect b) {
      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;
    }
  }

  static public enum FreeRectChoiceHeuristic {
    // BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best.
    BestShortSideFit,
    // BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best.
    BestLongSideFit,
    // BAF: Positions the rectangle into the smallest free rect into which it fits.
    BestAreaFit,
    // BL: Does the Tetris placement.
    BottomLeftRule,
    // CP: Choosest the placement where the rectangle touches other rects as much as possible.
    ContactPointRule
  };

  class RectComparator implements Comparator<Rect> {
    public int compare (Rect o1, Rect o2) {
      return Rect.getAtlasName(o1.name, settings.flattenPaths).compareTo(Rect.getAtlasName(o2.name, settings.flattenPaths));
    }
  }
}
TOP

Related Classes of com.badlogic.gdx.tools.imagepacker.MaxRectsPacker$BinarySearch

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.