public class

BlackPawnTextureAtlasBuilder

extends Object
implements ITextureAtlasBuilder<T extends ITextureAtlasSource, A extends ITextureAtlas<T>>
package org.andengine.opengl.texture.atlas.buildable.builder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

import org.andengine.opengl.texture.atlas.ITextureAtlas;
import org.andengine.opengl.texture.atlas.buildable.BuildableTextureAtlas.TextureAtlasSourceWithWithLocationCallback;
import org.andengine.opengl.texture.atlas.source.ITextureAtlasSource;

/**
 * (c) 2010 Nicolas Gramlich 
 * (c) 2011 Zynga Inc.
 * 
 * @author Nicolas Gramlich
 * @author Jim Scott (BlackPawn)
 * @since 16:03:01 - 12.08.2010
 * @see http://www.blackpawn.com/texts/lightmaps/default.html
 */
public class BlackPawnTextureAtlasBuilder<T extends ITextureAtlasSource, A extends ITextureAtlas<T>> implements ITextureAtlasBuilder<T, A> {
	// ===========================================================
	// Constants
	// ===========================================================

	private static final Comparator<TextureAtlasSourceWithWithLocationCallback<?>> TEXTURESOURCE_COMPARATOR = new Comparator<TextureAtlasSourceWithWithLocationCallback<?>>() {
		@Override
		public int compare(final TextureAtlasSourceWithWithLocationCallback<?> pTextureAtlasSourceWithWithLocationCallbackA, final TextureAtlasSourceWithWithLocationCallback<?> pTextureAtlasSourceWithWithLocationCallbackB) {
			final int deltaWidth = pTextureAtlasSourceWithWithLocationCallbackB.getTextureAtlasSource().getTextureWidth() - pTextureAtlasSourceWithWithLocationCallbackA.getTextureAtlasSource().getTextureWidth();
			if(deltaWidth != 0) {
				return deltaWidth;
			} else {
				return pTextureAtlasSourceWithWithLocationCallbackB.getTextureAtlasSource().getTextureHeight() - pTextureAtlasSourceWithWithLocationCallbackA.getTextureAtlasSource().getTextureHeight();
			}
		}
	};

	// ===========================================================
	// Fields
	// ===========================================================

	private final int mTextureAtlasBorderSpacing;
	private final int mTextureAtlasSourceSpacing;
	private final int mTextureAtlasSourcePadding;

	// ===========================================================
	// Constructors
	// ===========================================================

	/**
	 * @param pTextureAtlasBorderSpacing the minimum spacing between the border of the texture and the {@link ITextureAtlasSource}s. 
	 * @param pTextureAtlasSourceSpacing the spacing between the different {@link ITextureAtlasSource}s.
	 * @param pTextureAtlasSourcePadding the transparent padding around each {@link ITextureAtlasSource} (prevents texture bleeding).
	 */
	public BlackPawnTextureAtlasBuilder(final int pTextureAtlasBorderSpacing, final int pTextureAtlasSourceSpacing, final int pTextureAtlasSourcePadding) {
		this.mTextureAtlasBorderSpacing = pTextureAtlasBorderSpacing;
		this.mTextureAtlasSourceSpacing = pTextureAtlasSourceSpacing;
		this.mTextureAtlasSourcePadding = pTextureAtlasSourcePadding;
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public void build(final A pTextureAtlas, final ArrayList<TextureAtlasSourceWithWithLocationCallback<T>> pTextureAtlasSourcesWithLocationCallback) throws TextureAtlasBuilderException {
		Collections.sort(pTextureAtlasSourcesWithLocationCallback, TEXTURESOURCE_COMPARATOR);

		final int rootX = 0;
		final int rootY = 0;
		final int rootWidth = pTextureAtlas.getWidth() - 2 * this.mTextureAtlasBorderSpacing;
		final int rootHeight = pTextureAtlas.getHeight() - 2 * this.mTextureAtlasBorderSpacing;
		final Node root = new Node(new Rect(rootX, rootY, rootWidth, rootHeight));

		final int textureSourceCount = pTextureAtlasSourcesWithLocationCallback.size();

		for(int i = 0; i < textureSourceCount; i++) {
			final TextureAtlasSourceWithWithLocationCallback<T> textureSourceWithLocationCallback = pTextureAtlasSourcesWithLocationCallback.get(i);
			final T textureAtlasSource = textureSourceWithLocationCallback.getTextureAtlasSource();

			final Node inserted = root.insert(textureAtlasSource, rootWidth, rootHeight, this.mTextureAtlasSourceSpacing, this.mTextureAtlasSourcePadding);

			if(inserted == null) {
				throw new TextureAtlasBuilderException("Could not build: '" + textureAtlasSource.toString() + "' into: '" + pTextureAtlas.getClass().getSimpleName() + "'.");
			}

			final int textureAtlasSourceLeft = inserted.mRect.mLeft + this.mTextureAtlasBorderSpacing + this.mTextureAtlasSourcePadding;
			final int textureAtlasSourceTop = inserted.mRect.mTop + this.mTextureAtlasBorderSpacing + this.mTextureAtlasSourcePadding;
			if(this.mTextureAtlasSourcePadding == 0) {
				pTextureAtlas.addTextureAtlasSource(textureAtlasSource, textureAtlasSourceLeft, textureAtlasSourceTop);
			} else {
				pTextureAtlas.addTextureAtlasSource(textureAtlasSource, textureAtlasSourceLeft, textureAtlasSourceTop, this.mTextureAtlasSourcePadding);
			}

			textureSourceWithLocationCallback.getCallback().onCallback(textureAtlasSource);
		}
	}

	// ===========================================================
	// Methods
	// ===========================================================

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================

	protected static class Rect {
		// ===========================================================
		// Constants
		// ===========================================================

		// ===========================================================
		// Fields
		// ===========================================================

		private final int mLeft;
		private final int mTop;
		private final int mWidth;
		private final int mHeight;

		// ===========================================================
		// Constructors
		// ===========================================================

		public Rect(final int pLeft, final int pTop, final int pWidth, final int pHeight) {
			this.mLeft = pLeft;
			this.mTop = pTop;
			this.mWidth = pWidth;
			this.mHeight = pHeight;
		}

		// ===========================================================
		// Getter & Setter
		// ===========================================================

		public int getWidth() {
			return this.mWidth;
		}

		public int getHeight() {
			return this.mHeight;
		}

		public int getLeft() {
			return this.mLeft;
		}

		public int getTop() {
			return this.mTop;
		}

		public int getRight() {
			return this.mLeft + this.mWidth;
		}

		public int getBottom() {
			return this.mTop + this.mHeight;
		}

		// ===========================================================
		// Methods for/from SuperClass/Interfaces
		// ===========================================================

		@Override
		public String toString() {
			return "@: " + this.mLeft + "/" + this.mTop + " * " + this.mWidth + "x" + this.mHeight;
		}

		// ===========================================================
		// Methods
		// ===========================================================

		// ===========================================================
		// Inner and Anonymous Classes
		// ===========================================================
	}

	protected static class Node {
		// ===========================================================
		// Constants
		// ===========================================================

		// ===========================================================
		// Fields
		// ===========================================================

		private Node mChildA;
		private Node mChildB;
		private final Rect mRect;
		private ITextureAtlasSource mTextureAtlasSource;

		// ===========================================================
		// Constructors
		// ===========================================================

		public Node(final int pLeft, final int pTop, final int pWidth, final int pHeight) {
			this(new Rect(pLeft, pTop, pWidth, pHeight));
		}

		public Node(final Rect pRect) {
			this.mRect = pRect;
		}

		// ===========================================================
		// Getter & Setter
		// ===========================================================

		public Rect getRect() {
			return this.mRect;
		}

		public Node getChildA() {
			return this.mChildA;
		}

		public Node getChildB() {
			return this.mChildB;
		}

		// ===========================================================
		// Methods for/from SuperClass/Interfaces
		// ===========================================================

		// ===========================================================
		// Methods
		// ===========================================================

		public Node insert(final ITextureAtlasSource pTextureAtlasSource, final int pTextureWidth, final int pTextureHeight, final int pTextureAtlasSourceSpacing, final int pTextureAtlasSourcePadding) throws IllegalArgumentException {
			if(this.mChildA != null && this.mChildB != null) {
				final Node newNode = this.mChildA.insert(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureAtlasSourceSpacing, pTextureAtlasSourcePadding);
				if(newNode != null){
					return newNode;
				} else {
					return this.mChildB.insert(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureAtlasSourceSpacing, pTextureAtlasSourcePadding);
				}
			} else {
				if(this.mTextureAtlasSource != null) {
					return null;
				}

				final int textureSourceWidth = pTextureAtlasSource.getTextureWidth() + 2 * pTextureAtlasSourcePadding;
				final int textureSourceHeight = pTextureAtlasSource.getTextureHeight() + 2 * pTextureAtlasSourcePadding;

				final int rectWidth = this.mRect.getWidth();
				final int rectHeight = this.mRect.getHeight();

				if(textureSourceWidth > rectWidth || textureSourceHeight > rectHeight) {
					return null;
				}

				final int textureSourceWidthWithSpacing = textureSourceWidth + pTextureAtlasSourceSpacing;
				final int textureSourceHeightWithSpacing = textureSourceHeight + pTextureAtlasSourceSpacing;

				final int rectLeft = this.mRect.getLeft();
				final int rectTop = this.mRect.getTop();

				final boolean fitToBottomWithoutSpacing = textureSourceHeight == rectHeight && rectTop + textureSourceHeight == pTextureHeight;
				final boolean fitToRightWithoutSpacing = textureSourceWidth == rectWidth && rectLeft + textureSourceWidth == pTextureWidth;

				if(textureSourceWidthWithSpacing == rectWidth){
					if(textureSourceHeightWithSpacing == rectHeight) { /* Normal case with padding. */
						this.mTextureAtlasSource = pTextureAtlasSource;
						return this;
					} else if(fitToBottomWithoutSpacing) { /* Bottom edge of the BitmapTexture. */
						this.mTextureAtlasSource = pTextureAtlasSource;
						return this;
					}
				}

				if(fitToRightWithoutSpacing) { /* Right edge of the BitmapTexture. */
					if(textureSourceHeightWithSpacing == rectHeight) {
						this.mTextureAtlasSource = pTextureAtlasSource;
						return this;
					} else if(fitToBottomWithoutSpacing) { /* Bottom edge of the BitmapTexture. */
						this.mTextureAtlasSource = pTextureAtlasSource;
						return this;
					} else if(textureSourceHeightWithSpacing > rectHeight) {
						return null;
					} else {
						return this.createChildren(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureAtlasSourceSpacing, pTextureAtlasSourcePadding, rectWidth - textureSourceWidth, rectHeight - textureSourceHeightWithSpacing);
					}
				}

				if(fitToBottomWithoutSpacing) {
					if(textureSourceWidthWithSpacing == rectWidth) {
						this.mTextureAtlasSource = pTextureAtlasSource;
						return this;
					} else if(textureSourceWidthWithSpacing > rectWidth) {
						return null;
					} else {
						return this.createChildren(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureAtlasSourceSpacing, pTextureAtlasSourcePadding, rectWidth - textureSourceWidthWithSpacing, rectHeight - textureSourceHeight);
					}
				} else if(textureSourceWidthWithSpacing > rectWidth || textureSourceHeightWithSpacing > rectHeight) {
					return null;
				} else {
					return this.createChildren(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureAtlasSourceSpacing, pTextureAtlasSourcePadding, rectWidth - textureSourceWidthWithSpacing, rectHeight - textureSourceHeightWithSpacing);
				}
			}
		}

		private Node createChildren(final ITextureAtlasSource pTextureAtlasSource, final int pTextureWidth, final int pTextureHeight, final int pTextureAtlasSourceSpacing, final int pTextureAtlasSourcePadding, final int pDeltaWidth, final int pDeltaHeight) {
			final Rect rect = this.mRect;

			if(pDeltaWidth >= pDeltaHeight) {
				/* Split using a vertical axis. */
				this.mChildA = new Node(
						rect.getLeft(),
						rect.getTop(),
						pTextureAtlasSource.getTextureWidth() + pTextureAtlasSourceSpacing + 2 * pTextureAtlasSourcePadding,
						rect.getHeight()
				);

				this.mChildB = new Node(
						rect.getLeft() + (pTextureAtlasSource.getTextureWidth() + pTextureAtlasSourceSpacing + 2 * pTextureAtlasSourcePadding),
						rect.getTop(),
						rect.getWidth() - (pTextureAtlasSource.getTextureWidth() + pTextureAtlasSourceSpacing + 2 * pTextureAtlasSourcePadding),
						rect.getHeight()
				);
			} else {
				/* Split using a horizontal axis. */
				this.mChildA = new Node(
						rect.getLeft(),
						rect.getTop(),
						rect.getWidth(),
						pTextureAtlasSource.getTextureHeight() + pTextureAtlasSourceSpacing + 2 * pTextureAtlasSourcePadding
				);

				this.mChildB = new Node(
						rect.getLeft(),
						rect.getTop() + (pTextureAtlasSource.getTextureHeight() + pTextureAtlasSourceSpacing + 2 * pTextureAtlasSourcePadding),
						rect.getWidth(),
						rect.getHeight() - (pTextureAtlasSource.getTextureHeight() + pTextureAtlasSourceSpacing + 2 * pTextureAtlasSourcePadding)
				);
			}

			return this.mChildA.insert(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureAtlasSourceSpacing, pTextureAtlasSourcePadding);
		}

		// ===========================================================
		// Inner and Anonymous Classes
		// ===========================================================
	}
}