package org.anddev.andengine.opengl.texture.atlas.buildable.builder; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import org.anddev.andengine.opengl.texture.atlas.ITextureAtlas; import org.anddev.andengine.opengl.texture.atlas.buildable.BuildableTextureAtlas.TextureAtlasSourceWithWithLocationCallback; import org.anddev.andengine.opengl.texture.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 BlackPawnTextureBuilder<T extends ITextureAtlasSource, A extends ITextureAtlas<T>> implements ITextureBuilder<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().getWidth() - pTextureAtlasSourceWithWithLocationCallbackA.getTextureAtlasSource().getWidth(); if(deltaWidth != 0) { return deltaWidth; } else { return pTextureAtlasSourceWithWithLocationCallbackB.getTextureAtlasSource().getHeight() - pTextureAtlasSourceWithWithLocationCallbackA.getTextureAtlasSource().getHeight(); } } }; // =========================================================== // Fields // =========================================================== private final int mTextureAtlasSourceSpacing; // =========================================================== // Constructors // =========================================================== public BlackPawnTextureBuilder(final int pTextureAtlasSourceSpacing) { this.mTextureAtlasSourceSpacing = pTextureAtlasSourceSpacing; } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void pack(final A pTextureAtlas, final ArrayList<TextureAtlasSourceWithWithLocationCallback<T>> pTextureAtlasSourcesWithLocationCallback) throws TextureAtlasSourcePackingException { Collections.sort(pTextureAtlasSourcesWithLocationCallback, TEXTURESOURCE_COMPARATOR); final Node root = new Node(new Rect(0, 0, pTextureAtlas.getWidth(), pTextureAtlas.getHeight())); final int textureSourceCount = pTextureAtlasSourcesWithLocationCallback.size(); for(int i = 0; i < textureSourceCount; i++) { final TextureAtlasSourceWithWithLocationCallback<T> textureSourceWithLocationCallback = pTextureAtlasSourcesWithLocationCallback.get(i); final T textureSource = textureSourceWithLocationCallback.getTextureAtlasSource(); final Node inserted = root.insert(textureSource, pTextureAtlas.getWidth(), pTextureAtlas.getHeight(), this.mTextureAtlasSourceSpacing); if(inserted == null) { throw new TextureAtlasSourcePackingException("Could not pack: " + textureSource.toString()); } pTextureAtlas.addTextureAtlasSource(textureSource, inserted.mRect.mLeft, inserted.mRect.mTop); textureSourceWithLocationCallback.getCallback().onCallback(textureSource); } } // =========================================================== // 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 pTextureSpacing) throws IllegalArgumentException { if(this.mChildA != null && this.mChildB != null) { final Node newNode = this.mChildA.insert(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureSpacing); if(newNode != null){ return newNode; } else { return this.mChildB.insert(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureSpacing); } } else { if(this.mTextureAtlasSource != null) { return null; } final int textureSourceWidth = pTextureAtlasSource.getWidth(); final int textureSourceHeight = pTextureAtlasSource.getHeight(); final int rectWidth = this.mRect.getWidth(); final int rectHeight = this.mRect.getHeight(); if(textureSourceWidth > rectWidth || textureSourceHeight > rectHeight) { return null; } final int textureSourceWidthWithSpacing = textureSourceWidth + pTextureSpacing; final int textureSourceHeightWithSpacing = textureSourceHeight + pTextureSpacing; 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, pTextureSpacing, 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, pTextureSpacing, rectWidth - textureSourceWidthWithSpacing, rectHeight - textureSourceHeight); } } else if(textureSourceWidthWithSpacing > rectWidth || textureSourceHeightWithSpacing > rectHeight) { return null; } else { return this.createChildren(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureSpacing, rectWidth - textureSourceWidthWithSpacing, rectHeight - textureSourceHeightWithSpacing); } } } private Node createChildren(final ITextureAtlasSource pTextureAtlasSource, final int pTextureWidth, final int pTextureHeight, final int pTextureSpacing, 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.getWidth() + pTextureSpacing, rect.getHeight() ); this.mChildB = new Node( rect.getLeft() + (pTextureAtlasSource.getWidth() + pTextureSpacing), rect.getTop(), rect.getWidth() - (pTextureAtlasSource.getWidth() + pTextureSpacing), rect.getHeight() ); } else { /* Split using a horizontal axis. */ this.mChildA = new Node( rect.getLeft(), rect.getTop(), rect.getWidth(), pTextureAtlasSource.getHeight() + pTextureSpacing ); this.mChildB = new Node( rect.getLeft(), rect.getTop() + (pTextureAtlasSource.getHeight() + pTextureSpacing), rect.getWidth(), rect.getHeight() - (pTextureAtlasSource.getHeight() + pTextureSpacing) ); } return this.mChildA.insert(pTextureAtlasSource, pTextureWidth, pTextureHeight, pTextureSpacing); } // =========================================================== // Inner and Anonymous Classes // =========================================================== } }