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 // =========================================================== } }