Skip to content

Commit

Permalink
2.0 release. Tiles are now packed using maximum rectangles algorithm.
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Cashman committed Feb 10, 2013
1 parent c8a6bf1 commit 3179950
Show file tree
Hide file tree
Showing 5 changed files with 491 additions and 40 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.tilepacker</groupId>
<artifactId>tilepacker-core</artifactId>
<version>1.1.1</version>
<version>2.0</version>
<packaging>jar</packaging>
<name>tilepacker</name>
<url>http://www.tilepacker.org</url>
Expand Down
150 changes: 150 additions & 0 deletions src/main/java/org/tilepacker/core/Rectangle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/**
* Copyright 2013 Thomas Cashman
*/
package org.tilepacker.core;

import java.util.ArrayList;
import java.util.List;

import org.newdawn.slick.SpriteSheet;

/**
* Represents available rectangle space for maxrects algorithm
*
* @author Thomas Cashman
*/
public class Rectangle {
private int x, y, width, height;
private Tile[][] tiles;

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

public void addTiles(SpriteSheet sheet) {
tiles = new Tile[sheet.getHorizontalCount()][sheet.getVerticalCount()];
for (int x = 0; x < sheet.getHorizontalCount(); x++) {
for (int y = 0; y < sheet.getVerticalCount(); y++) {
tiles[x][y] = new Tile(sheet.getSubImage(x, y));
}
}

width = sheet.getHorizontalCount();
height = sheet.getVerticalCount();
}

public static Rectangle and(Rectangle rect1, Rectangle rect2) {
if(!rect1.intersects(rect2)) {
return null;
}

int x = Math.max(rect1.getX(), rect2.getX());
int y = Math.max(rect1.getY(), rect2.getY());

int maxX = Math.min(rect1.getMaxX(), rect2.getMaxX());
int maxY = Math.min(rect1.getMaxY(), rect2.getMaxY());

return new Rectangle(x, y, (maxX - x) + 1, (maxY - y) + 1);
}

public static List<Rectangle> subtract(Rectangle rect, Rectangle innerRect) {
List<Rectangle> result = new ArrayList<Rectangle>();

int leftMinX = Math.min(rect.getX(), innerRect.getX());
int leftMaxX = Math.max(rect.getX(), innerRect.getX());

int topMinY = Math.min(rect.getY(), innerRect.getY());
int topMaxY = Math.max(rect.getY(), innerRect.getY());

int rightMinX = Math.min(rect.getMaxX(), innerRect.getMaxX());
int rightMaxX = Math.max(rect.getMaxX(), innerRect.getMaxX());

int bottomMinY = Math.min(rect.getMaxY(), innerRect.getMaxY());
int bottomMaxY = Math.max(rect.getMaxY(), innerRect.getMaxY());

if (leftMinX != leftMaxX) {
/* There's a rectangle to the left */
result.add(new Rectangle(leftMinX, topMinY, leftMaxX - leftMinX,
(bottomMaxY - topMinY) + 1));
}

if (topMinY != topMaxY) {
/* There's a rectangle to the top */
result.add(new Rectangle(leftMinX, topMinY,
(rightMaxX - leftMinX) + 1, topMaxY - topMinY));
}

if (rightMinX != rightMaxX) {
/* There's a rectangle to the right */
result.add(new Rectangle(rightMinX + 1, topMinY,
rightMaxX - rightMinX, (bottomMaxY - topMinY) + 1));
}

if (bottomMinY != bottomMaxY) {
/* There's a rectangle to the bottom */
result.add(new Rectangle(leftMinX, bottomMinY + 1,
(rightMaxX - leftMinX) + 1, bottomMaxY - bottomMinY));
}
return result;
}

public boolean canContain(SpriteSheet sheet) {
if (sheet.getHorizontalCount() > width) {
return false;
}
if (sheet.getVerticalCount() > height) {
return false;
}
return true;
}

public boolean intersects(Rectangle rect) {
if (rect.getX() > getMaxX()) {
return false;
} else if (rect.getY() > getMaxY()) {
return false;
} else if (rect.getMaxX() < getX()) {
return false;
} else if (rect.getMaxY() < getY()) {
return false;
}
return true;
}

public boolean equals(Rectangle rectangle) {
return getX() == rectangle.getX() && getY() == rectangle.getY()
&& getWidth() == rectangle.getWidth()
&& getHeight() == rectangle.getHeight();
}

public Tile[][] getTiles() {
return tiles;
}

public int getX() {
return x;
}

public int getY() {
return y;
}

public int getWidth() {
return width;
}

public int getHeight() {
return height;
}

public int getMaxX() {
return x + (width - 1);
}

public int getMaxY() {
return y + (height - 1);
}
}
52 changes: 36 additions & 16 deletions src/main/java/org/tilepacker/core/TilePacker.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.cli.CommandLine;
Expand All @@ -36,7 +37,7 @@ public class TilePacker extends BasicGame {
public static String SOURCE_DIRECTORY = "";
public static String TARGET_DIRECTORY = "";

private File[] inputFiles;
private List<String> inputFiles;
private List<Tileset> tilesets;

/**
Expand All @@ -50,25 +51,44 @@ public TilePacker() {
@Override
public void init(GameContainer gc) throws SlickException {
File sourceDirectory = new File(SOURCE_DIRECTORY);
inputFiles = sourceDirectory.listFiles();
Tileset tileset = new Tileset();
for(int i = 0; i < inputFiles.length; i++) {
if(inputFiles[i].getAbsolutePath().endsWith("." + FORMAT.toLowerCase())) {
System.out.println("INFO: Reading " + inputFiles[i].getAbsolutePath());
SpriteSheet spriteSheet = new SpriteSheet(inputFiles[i].getAbsolutePath(), Tile.WIDTH, Tile.HEIGHT);
for(int x = 0; x < spriteSheet.getHorizontalCount(); x++) {
for(int y = 0; y < spriteSheet.getVerticalCount(); y++) {
Tile tile = new Tile(spriteSheet.getSubImage(x, y));
if(!tileset.add(tile)) {
tilesets.add(tileset);
tileset = new Tileset();
tileset.add(tile);
}
File [] files = sourceDirectory.listFiles();

inputFiles = new ArrayList<String>();

for(int i = 0; i < files.length; i++) {
inputFiles.add(files[i].getAbsolutePath());
}

Collections.sort(inputFiles);

tilesets.add(new Tileset());

for(int i = 0; i < inputFiles.size(); i++) {
String path = inputFiles.get(i);
if(path.endsWith("." + FORMAT.toLowerCase())) {
System.out.println("INFO: Reading " + path);
SpriteSheet spriteSheet = new SpriteSheet(path, Tile.WIDTH, Tile.HEIGHT);

boolean added = false;
for(int j = 0; j < tilesets.size(); j++) {
Tileset tileset = tilesets.get(j);

if(tileset.add(spriteSheet)) {
added = true;
break;
}
}

if(!added) {
Tileset tileset = new Tileset();
if(!tileset.add(spriteSheet)) {
System.err.println("ERROR: Image too large - " + path);
System.exit(0);
}
tilesets.add(tileset);
}
}
}
tilesets.add(tileset);
}

@Override
Expand Down
85 changes: 62 additions & 23 deletions src/main/java/org/tilepacker/core/Tileset.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.imageout.ImageOut;

import sun.security.krb5.internal.PAData;

/**
* Stores a tileset
*
Expand All @@ -30,13 +29,18 @@
public class Tileset {
public static int MAX_WIDTH;
public static int MAX_HEIGHT;
private List<Tile> tiles;
private List<Rectangle> availableRectangles;
private List<Rectangle> usedRectangles;

/**
* Constructor
*/
public Tileset() {
tiles = new ArrayList<Tile>();
availableRectangles = new ArrayList<Rectangle>();
usedRectangles = new ArrayList<Rectangle>();

availableRectangles.add(new Rectangle(0, 0, getMaximumWidthInTiles(),
getMaxiumumHeightInTiles()));
}

/**
Expand All @@ -46,19 +50,51 @@ public Tileset() {
* The {@link Tile} to be added
* @return True on success, false if the tileset is full
*/
public boolean add(Tile tile) {
if (tiles.size() + 1 > (getMaximumWidthInTiles() * getMaxiumumHeightInTiles()))
return false;
tiles.add(tile);
return true;
public boolean add(SpriteSheet sheet) {
for (int i = availableRectangles.size() - 1; i >= 0
&& i < availableRectangles.size(); i--) {
Rectangle rect = availableRectangles.get(i);
if (rect.canContain(sheet)) {
Rectangle copyRect = new Rectangle(rect.getX(), rect.getY(),
rect.getWidth(), rect.getHeight());

availableRectangles.remove(i);
rect.addTiles(sheet);
usedRectangles.add(rect);
availableRectangles.add(copyRect);

List<Rectangle> remaining = new ArrayList<Rectangle>();

for (int j = availableRectangles.size() - 1; j >= 0; j--) {
Rectangle otherRect = availableRectangles.get(j);
if (rect.intersects(otherRect)) {
Rectangle andRect = Rectangle.and(rect, otherRect);
remaining.addAll(Rectangle.subtract(otherRect, andRect));
availableRectangles.remove(j);
}
}

for(int j = remaining.size() - 1; j >= 0; j--) {
for(int k = 0; k < availableRectangles.size(); k++) {
if(availableRectangles.get(k).equals(remaining.get(j))) {
remaining.remove(j);
break;
}
}
}
availableRectangles.addAll(remaining);
return true;
}
}
return false;
}

/**
* Returns the maximum width in tiles
*
* @return
*/
public int getMaximumWidthInTiles() {
public static int getMaximumWidthInTiles() {
return MAX_WIDTH / (Tile.WIDTH + (Tile.PADDING * 2));
}

Expand All @@ -67,7 +103,7 @@ public int getMaximumWidthInTiles() {
*
* @return
*/
public int getMaxiumumHeightInTiles() {
public static int getMaxiumumHeightInTiles() {
return MAX_HEIGHT / (Tile.HEIGHT + (Tile.PADDING * 2));
}

Expand All @@ -87,18 +123,21 @@ public void save(String destinationFile, String format)
g.setColor(Color.magenta);
g.fillRect(0, 0, MAX_WIDTH, MAX_HEIGHT);
g.setAntiAlias(true);
for (int i = 0; i < tiles.size(); i++) {
Tile tile = tiles.get(i);

if (tile != null) {
int y = i / (MAX_WIDTH / (Tile.WIDTH + (Tile.PADDING * 2)));
int x = i
- (y * (MAX_WIDTH / (Tile.WIDTH + (Tile.PADDING * 2))));

g.drawImage(
tile.getTileImage(),
((x * (Tile.WIDTH + (Tile.PADDING * 2))) + Tile.PADDING),
((y * (Tile.HEIGHT + (Tile.PADDING * 2))) + Tile.PADDING));
for (int i = 0; i < usedRectangles.size(); i++) {
Rectangle rectangle = usedRectangles.get(i);

for (int x = 0; x < rectangle.getWidth(); x++) {
for (int y = 0; y < rectangle.getHeight(); y++) {
int tileX = rectangle.getX() + x;
int tileY = rectangle.getY() + y;

Tile tile = rectangle.getTiles()[x][y];

g.drawImage(
tile.getTileImage(),
((tileX * (Tile.WIDTH + (Tile.PADDING * 2))) + Tile.PADDING),
((tileY * (Tile.HEIGHT + (Tile.PADDING * 2))) + Tile.PADDING));
}
}
}
g.flush();
Expand Down
Loading

0 comments on commit 3179950

Please sign in to comment.