package hunternif.mc.atlas.client; import hunternif.mc.atlas.client.SubTile.Part; import hunternif.mc.atlas.client.SubTile.Shape; import hunternif.mc.atlas.core.ITileStorage; import hunternif.mc.atlas.core.Tile; import hunternif.mc.atlas.util.Rect; import java.util.Iterator; /** * Iterates through a tile storage for the purpose of rendering their textures. * Returned is an array of 4 {@link SubTile}s which constitute a whole * {@link Tile}. * The SubTile objects are generated on the fly and not retained in memory. * May return null! * @author Hunternif */ public class TileRenderIterator implements Iterator<SubTileQuartet> { private final ITileStorage tiles; /** How many chunks a tile spans. Used for viewing the map at a scale below * the threshold at which the tile texture is of minimum size and no longer * scales down. Can't be less than 1. */ private int step = 1; public void setStep(int step) { if (step >= 1) { this.step = step; } } /** The scope of iteration. */ private final Rect scope = new Rect(); public void setScope(int minX, int minY, int maxX, int maxY) { scope.set(minX, minY, maxX, maxY); chunkX = minX; chunkY = minY; } public void setScope(Rect scope){ this.scope.set(scope); chunkX = scope.minX; chunkY = scope.minY; } /** * The group of adjacent tiles used for traversing the storage. * <pre> * a b * c d e f * g h i j * k l * </pre> * 'i' is at (x, y). * The returned array of subtiles represents the corner 'd-e-h-i' */ private Tile a, b, c, d, e, f, g, h, i, j, k, l; /** Shortcuts for the quartet. */ private final SubTile _d = new SubTile(Part.BOTTOM_RIGHT), _e = new SubTile(Part.BOTTOM_LEFT), _h = new SubTile(Part.TOP_RIGHT), _i = new SubTile(Part.TOP_LEFT); private final SubTileQuartet quartet = new SubTileQuartet(_d, _e, _h, _i); /** Current index into the tile storage, which presumably has every tile spanning exactly 1 chunk. */ private int chunkX, chunkY; /** Current index into the grid of subtiles, starting at (-1, -1). */ private int subtileX = -1, subtileY = -1; public TileRenderIterator(ITileStorage tiles) { this.tiles = tiles; setScope(tiles.getScope()); } @Override public boolean hasNext() { return chunkX >= scope.minX && chunkX <= scope.maxX + 1 && chunkY >= scope.minY && chunkY <= scope.maxY + 1; } @Override public SubTileQuartet next() { a = b; b = tiles.getTile(chunkX, chunkY - step * 2); c = d; d = e; e = f; f = tiles.getTile(chunkX + step, chunkY - step); g = h; h = i; i = j; j = tiles.getTile(chunkX + step, chunkY); k = l; l = tiles.getTile(chunkX, chunkY + step); quartet.setCoords(subtileX, subtileY); _d.tile = d; _e.tile = e; _h.tile = h; _i.tile = i; // At first assume all convex: for (SubTile subtile : quartet) { subtile.shape = Shape.CONVEX; } // Connect horizontally: if (shouldStitchToHorizontally(d, e)) { stitchHorizontally(_d); } if (shouldStitchToHorizontally(e, d)) { stitchHorizontally(_e); } if (shouldStitchToHorizontally(h, i)) { stitchHorizontally(_h); } if (shouldStitchToHorizontally(i, h)) { stitchHorizontally(_i); } // Connect vertically: if (shouldStitchToVertically(d, h)) { stitchVertically(_d); if (_d.shape == Shape.CONCAVE && shouldStitchTo(d, i)) { _d.shape = Shape.FULL; } } if (shouldStitchToVertically(h, d)) { stitchVertically(_h); if (_h.shape == Shape.CONCAVE && shouldStitchTo(h, e)) { _h.shape = Shape.FULL; } } if (shouldStitchToVertically(e, i)) { stitchVertically(_e); if (_e.shape == Shape.CONCAVE && shouldStitchTo(e, h)) { _e.shape = Shape.FULL; } } if (shouldStitchToVertically(i, e)) { stitchVertically(_i); if (_i.shape == Shape.CONCAVE && shouldStitchTo(i, d)) { _i.shape = Shape.FULL; } } // For any convex subtile check for single-object: if (_d.shape == Shape.CONVEX && !shouldStitchToVertically(d, a) && !shouldStitchToHorizontally(d, c)) { _d.shape = Shape.SINGLE_OBJECT; } if (_e.shape == Shape.CONVEX && !shouldStitchToVertically(e, b) && !shouldStitchToHorizontally(e, f)) { _e.shape = Shape.SINGLE_OBJECT; } if (_h.shape == Shape.CONVEX && !shouldStitchToHorizontally(h, g) && !shouldStitchToVertically(h, k)) { _h.shape = Shape.SINGLE_OBJECT; } if (_i.shape == Shape.CONVEX && !shouldStitchToHorizontally(i, j) && !shouldStitchToVertically(i, l)) { _i.shape = Shape.SINGLE_OBJECT; } chunkX += step; subtileX += 2; if (chunkX > scope.maxX + 1) { chunkX = scope.minX; subtileX = -1; chunkY += step; subtileY += 2; a = null; b = null; c = null; d = null; e = null; f = tiles.getTile(chunkX, chunkY - step); g = null; h = null; i = null; j = tiles.getTile(chunkX, chunkY); k = null; l = null; } return quartet; } /** Whether the first tile should be stitched to the 2nd (in any direction) * (but the opposite is not always true!) */ private static boolean shouldStitchTo(Tile tile, Tile to) { if (tile == null) return false; TextureSet set = BiomeTextureMap.instance().getTextureSet(tile); TextureSet toSet = BiomeTextureMap.instance().getTextureSet(to); return set != null && set.shouldStitchTo(toSet); } /** Whether the first tile should be stitched to the 2nd along the X axis * (but the opposite is not always true!) */ private static boolean shouldStitchToHorizontally(Tile tile, Tile to) { if (tile == null) return false; TextureSet set = BiomeTextureMap.instance().getTextureSet(tile); TextureSet toSet = BiomeTextureMap.instance().getTextureSet(to); return set != null && set.shouldStitchToHorizontally(toSet); } /** Whether the first tile should be stitched to the 2nd along the Z axis * (but the opposite is not always true!) */ private static boolean shouldStitchToVertically(Tile tile, Tile to) { if (tile == null) return false; TextureSet set = BiomeTextureMap.instance().getTextureSet(tile); TextureSet toSet = BiomeTextureMap.instance().getTextureSet(to); return set != null && set.shouldStitchToVertically(toSet); } /** Change the shape of the subtile in order to stitch it vertically * to another subtile. It doesn't matter if it's top or bottom. */ private static void stitchVertically(SubTile subtile) { if (subtile.shape == Shape.HORIZONTAL) subtile.shape = Shape.CONCAVE; if (subtile.shape == Shape.CONVEX) subtile.shape = Shape.VERTICAL; } /** Change the shape of the subtile in order to stitch it horizontally * to another subtile. It doesn't matter if it's left or right. */ private static void stitchHorizontally(SubTile subtile) { if (subtile.shape == Shape.VERTICAL) subtile.shape = Shape.CONCAVE; if (subtile.shape == Shape.CONVEX) subtile.shape = Shape.HORIZONTAL; } @Override public void remove() { throw new UnsupportedOperationException("cannot remove subtiles from tile storage"); } }