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 + " ]"; } } }
1. | Contract Rectangle2D | ||
2. | Expand Rectangle2D | ||
3. | Get point for dragging from a Rectangle | ||
4. | Draw string along a Rectangle |