Tries to pack rectangles as tightly as possible. : Rectangle « 2D Graphics GUI « Java






Tries to pack rectangles as tightly as possible.

   
/*
 * Copyright (c) 2007, Ryan McNally All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met: Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following
 * disclaimer. Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution. Neither the name of the <ORGANIZATION> nor
 * the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

//package com.ryanm.droid.rugl.util;

import java.util.List;

/**
 * Tries to pack rectangles as tightly as possible. An implementation of the
 * algorithm described at http://www.blackpawn.com/texts/lightmaps/default.html
 * 
 * @author ryanm
 * @param <P>
 *            The type of items to be held
 */
public class RectanglePacker<P> {

  /**
   * Determines the outcome of a rectangle-fitting test
   * 
   * @author ryanm
   */
  private static enum Fit {
    /**
     * Indicates that the rectangle did not fit
     */
    FAIL,
    /**
     * Indicates that the rectangle fitted perfectly
     */
    PERFECT,
    /**
     * Indicates that the rectangle fitted with room to spare
     */
    FIT
  };

  private Node root;

  /**
   * The border to leave around rectangles
   */
  private int border = 0;

  /**
   * Builds a new {@link RectanglePacker}
   * 
   * @param width
   *            The width of the space available to pack into
   * @param height
   *            The height of the space available to pack into
   * @param border
   *            The border to preserve between packed items
   */
  public RectanglePacker(int width, int height, int border) {
    root = new Node(new Rectangle(0, 0, width, height));
    this.border = border;
  }

  /**
   * Builds a list of all {@link Rectangle}s in the tree, for debugging
   * purposes
   * 
   * @param rectangles
   *            The list to add the tree's {@link Rectangle}s to
   */
  public void inspectRectangles(List<Rectangle> rectangles) {
    root.getRectangles(rectangles);
  }

  /**
   * Finds the {@link Rectangle} where an item is stored
   * 
   * @param item
   *            The item to search for
   * @return The {@link Rectangle} where that item resides, or null if not
   *         found
   */
  public Rectangle findRectangle(P item) {
    return root.findRectange(item);
  }

  /**
   * Clears the packer of all items
   */
  public void clear() {
    root = new Node(root.rect);
  }

  /**
   * Attempts to pack an item of the supplied dimensions
   * 
   * @param width
   *            The width of the item
   * @param height
   *            The height of the item
   * @param o
   *            The item to pack
   * @return The packed location, or null if it will not fit.
   */
  public Rectangle insert(int width, int height, P o) {
    Node n = root.insert(width + 2 * border, height + 2 * border, o);

    if (n != null) {
      Rectangle r = new Rectangle(n.rect.x + border, n.rect.y + border,
          n.rect.width - 2 * border, n.rect.height - 2 * border);
      return r;
    } else {
      return null;
    }
  }

  /**
   * Removes an item from the tree, consolidating the space if possible. The
   * space can easily become fragmented, so don't rely on this to work as
   * cleverly as you would like.
   * 
   * @param o
   *            the item to remove
   * @return <code>true</code> if the item was found, false otherwise
   */
  public boolean remove(P o) {
    return root.remove(o);
  }

  /**
   * Gets the width of this packer
   * 
   * @return the width of this packer
   */
  public int getWidth() {
    return root.rect.width;
  }

  /**
   * Gets the height of this packer
   * 
   * @return The height of this packer
   */
  public int getHeight() {
    return root.rect.height;
  }

  private class Node {
    private Rectangle rect;

    private P occupier = null;

    private Node left = null;

    private Node right = null;

    private Node(Rectangle r) {
      this.rect = r;
    }

    private Rectangle findRectange(P item) {
      if (isLeaf()) {
        if (item == occupier) {
          return rect;
        } else {
          return null;
        }
      } else {
        Rectangle l = left.findRectange(item);

        if (l != null) {
          return l;
        } else {
          return right.findRectange(item);
        }
      }
    }

    private Node insert(int width, int height, P o) {
      if (!isLeaf()) {
        Node r = left.insert(width, height, o);

        if (r == null) {
          r = right.insert(width, height, o);
        }

        return r;
      } else {
        if (occupier != null) {
          return null;
        }

        Fit fit = fits(width, height);

        switch (fit) {
        case FAIL:
          return null;
        case PERFECT:
          occupier = o;
          return this;
        case FIT:
          split(width, height);
          break;
        }

        return left.insert(width, height, o);
      }
    }

    private boolean isLeaf() {
      return left == null;
    }

    /**
     * Determines if this node contains an item, even many levels below
     * 
     * @return <code>true</code> if this node or any of it's descendants
     *         holds an item
     */
    private boolean isOccupied() {
      return occupier != null || !isLeaf();
    }

    /**
     * Removes an item, and consolidates the tree if possible
     * 
     * @param o
     *            the item to remove
     * @return <code>true</code> if the item was found, <code>false</code>
     *         otherwise
     */
    private boolean remove(P o) {
      if (isLeaf()) {
        if (occupier == o) {
          occupier = null;

          return true;
        }
        return false;
      } else {
        boolean found = left.remove(o);
        if (!found) {
          found = right.remove(o);
        }

        if (found) {
          if (!left.isOccupied() && !right.isOccupied()) {
            left = null;
            right = null;
          }
        }

        return found;
      }
    }

    private void split(int width, int height) {
      int dw = rect.width - width;
      int dh = rect.height - height;

      assert dw >= 0;
      assert dh >= 0;

      Rectangle r, l;
      if (dw > dh) {
        l = new Rectangle(rect.x, rect.y, width, rect.height);

        r = new Rectangle(l.x + width, rect.y, rect.width - width,
            rect.height);
      } else {
        l = new Rectangle(rect.x, rect.y, rect.width, height);

        r = new Rectangle(rect.x, l.y + height, rect.width, rect.height
            - height);
      }

      left = new Node(l);
      right = new Node(r);
    }

    private Fit fits(int width, int height) {
      if (width <= rect.width && height <= rect.height) {
        if (width == rect.width && height == rect.height) {
          return Fit.PERFECT;
        } else {
          return Fit.FIT;
        }
      }

      return Fit.FAIL;
    }

    private void getRectangles(List<Rectangle> rectangles) {
      rectangles.add(rect);

      if (!isLeaf()) {
        left.getRectangles(rectangles);
        right.getRectangles(rectangles);
      }
    }
  }

  /**
   * Yet another Rectangle class. Only here to remove dependencies on
   * awt/lwjgl/etc
   * 
   * @author ryanm
   */
  public static class Rectangle {
    /**
     * 
     */
    public final int x;

    /**
     * 
     */
    public final int y;

    /**
     * 
     */
    public final int width;

    /**
     * 
     */
    public final int height;

    private Rectangle(int x, int y, int width, int height) {
      this.x = x;
      this.y = y;
      this.width = width;
      this.height = height;
    }

    private Rectangle(Rectangle r) {
      this.x = r.x;
      this.y = r.y;
      this.width = r.width;
      this.height = r.height;
    }

    @Override
    public String toString() {
      return "[ " + x + ", " + y + ", " + width + ", " + height + " ]";
    }
  }
}

   
    
    
  








Related examples in the same category

1.Contract Rectangle2D
2.Expand Rectangle2D
3.Get point for dragging from a Rectangle
4.Draw string along a Rectangle