/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package org.pepsoft.worldpainter; import org.jetbrains.annotations.NotNull; import org.pepsoft.util.swing.TileListener; import org.pepsoft.worldpainter.biomeschemes.CustomBiomeManager; import org.pepsoft.worldpainter.layers.Layer; import org.pepsoft.worldpainter.layers.NotPresent; import org.pepsoft.worldpainter.layers.renderers.VoidRenderer; import java.awt.*; import java.awt.image.BufferedImage; import java.util.*; import java.util.List; import java.util.stream.Collectors; import static org.pepsoft.minecraft.Constants.*; import static org.pepsoft.worldpainter.Constants.DIM_NORMAL; import static org.pepsoft.worldpainter.Constants.TILE_SIZE; /** * * @author pepijn */ public class WPTileProvider implements org.pepsoft.util.swing.TileProvider, Dimension.Listener, Tile.Listener { public WPTileProvider(Dimension dimension, ColourScheme colourScheme, BiomeScheme biomeScheme, CustomBiomeManager customBiomeManager, Collection<Layer> hiddenLayers, boolean contourLines, int contourSeparation, TileRenderer.LightOrigin lightOrigin, boolean showBorder, org.pepsoft.util.swing.TileProvider surroundingTileProvider, boolean active) { tileProvider = dimension; this.colourScheme = colourScheme; this.biomeScheme = biomeScheme; this.hiddenLayers = (hiddenLayers != null) ? new HashSet<>(hiddenLayers) : null; this.contourLines = contourLines; this.contourSeparation = contourSeparation; this.lightOrigin = lightOrigin; this.active = active; this.customBiomeManager = customBiomeManager; this.surroundingTileProvider = surroundingTileProvider; this.showBorder = showBorder; tileRendererRef = createNewTileRendererRef(); } public WPTileProvider(TileProvider tileProvider, ColourScheme colourScheme, BiomeScheme biomeScheme, CustomBiomeManager customBiomeManager, Collection<Layer> hiddenLayers, boolean contourLines, int contourSeparation, TileRenderer.LightOrigin lightOrigin, boolean showBorder, org.pepsoft.util.swing.TileProvider surroundingTileProvider) { this.tileProvider = tileProvider; this.colourScheme = colourScheme; this.biomeScheme = biomeScheme; this.hiddenLayers = (hiddenLayers != null) ? new HashSet<>(hiddenLayers) : null; this.contourLines = contourLines; this.contourSeparation = contourSeparation; this.lightOrigin = lightOrigin; active = false; this.customBiomeManager = customBiomeManager; this.surroundingTileProvider = surroundingTileProvider; this.showBorder = showBorder; tileRendererRef = createNewTileRendererRef(); } public synchronized void addHiddenLayer(Layer layer) { hiddenLayers.add(layer); tileRendererRef = createNewTileRendererRef(); } public synchronized void removeHiddenLayer(Layer layer) { hiddenLayers.remove(layer); tileRendererRef = createNewTileRendererRef(); } @Override public int getTileSize() { return TILE_SIZE; } @Override public boolean isTilePresent(int x, int y) { if (zoom == 0) { return getUnzoomedTileType(x, y) != TileType.SURROUNDS || ((surroundingTileProvider != null) && surroundingTileProvider.isTilePresent(x, y)); } else { final int scale = 1 << -zoom; for (int dx = 0; dx < scale; dx++) { for (int dy = 0; dy < scale; dy++) { switch (getUnzoomedTileType(x * scale + dx, y * scale + dy)) { case WORLD: case BORDER: case WALL: return true; case SURROUNDS: if ((surroundingTileProvider != null) && surroundingTileProvider.isTilePresent(x, y)) { return true; } break; } } } return false; } } @Override public boolean paintTile(final Image tileImage, final int x, final int y, final int imageX, final int imageY) { try { if (zoom == 0) { return paintUnzoomedTile(tileImage, x, y, imageX, imageY); } else { Graphics2D g2 = (Graphics2D) tileImage.getGraphics(); g2.setComposite(AlphaComposite.Src); try { Boolean surroundingTileImageAvailable = null; BufferedImage surroundingTileImage = null; final Color waterColour = new Color(colourScheme.getColour(BLK_WATER)); final Color lavaColour = new Color(colourScheme.getColour(BLK_LAVA)); final Color voidColour = new Color(0x00ffffff & VoidRenderer.getColour(), true); final Color bedrockColour = new Color(colourScheme.getColour(BLK_BEDROCK)); final int scale = 1 << -zoom; final int subSize = TILE_SIZE / scale; for (int dx = 0; dx < scale; dx++) { for (int dy = 0; dy < scale; dy++) { TileType tileType = getUnzoomedTileType(x * scale + dx, y * scale + dy); switch (tileType) { case WORLD: Tile tile = tileProvider.getTile(x * scale + dx, y * scale + dy); if (tile.hasLayer(NotPresent.INSTANCE)) { // The tile may have chunks marked "not // present", which the tile renderer // will skip, so make sure a background // is painted to appear in those // locations if (surroundingTileProvider != null) { if (surroundingTileImageAvailable == null) { surroundingTileImage = new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB); surroundingTileImageAvailable = surroundingTileProvider.paintTile(surroundingTileImage, x, y, 0, 0); } if (surroundingTileImageAvailable) { g2.drawImage(surroundingTileImage, imageX + dx * subSize, imageY + dy * subSize, imageX + (dx + 1) * subSize, imageY + (dy + 1) * subSize, imageX + dx * subSize, imageY + dy * subSize, imageX + (dx + 1) * subSize, imageY + (dy + 1) * subSize, null); } } if (! Boolean.TRUE.equals(surroundingTileImageAvailable)) { g2.setColor(voidColour); g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, subSize); } } TileRenderer tileRenderer = tileRendererRef.get(); tileRenderer.setTile(tile); tileRenderer.renderTile(tileImage, dx * subSize, dy * subSize); break; case BORDER: Color colour; switch (((Dimension) tileProvider).getBorder()) { case WATER: case ENDLESS_WATER: colour = waterColour; break; case LAVA: case ENDLESS_LAVA: colour = lavaColour; break; case VOID: case ENDLESS_VOID: colour = voidColour; break; default: throw new InternalError(); } g2.setColor(colour); g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, subSize); // Draw border lines g2.setColor(Color.BLACK); g2.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] {4.0f, 4.0f}, 0.0f)); if (tileProvider.isTilePresent(x * scale + dx, y * scale + dy - 1)) { g2.drawLine(imageX + dx * subSize , imageY + dy * subSize , imageX + (dx + 1) * subSize - 1, imageY + dy * subSize); } if (tileProvider.isTilePresent(x * scale + dx + 1, y * scale + dy)) { g2.drawLine(imageX + (dx + 1) * subSize - 1, imageY + dy * subSize , imageX + (dx + 1) * subSize - 1, imageY + (dy + 1) * subSize - 1); } if (tileProvider.isTilePresent(x * scale + dx, y * scale + dy + 1)) { g2.drawLine(imageX + dx * subSize , imageY + (dy + 1) * subSize - 1, imageX + (dx + 1) * subSize - 1, imageY + (dy + 1) * subSize - 1); } if (tileProvider.isTilePresent(x * scale + dx - 1, y * scale + dy)) { g2.drawLine(imageX + dx * subSize , imageY + dy * subSize , imageX + dx * subSize , imageY + (dy + 1) * subSize - 1); } break; case SURROUNDS: case WALL: if (surroundingTileProvider != null) { if (surroundingTileImageAvailable == null) { surroundingTileImage = new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB); surroundingTileImageAvailable = surroundingTileProvider.paintTile(surroundingTileImage, x, y, 0, 0); } if (surroundingTileImageAvailable) { g2.drawImage(surroundingTileImage, imageX + dx * subSize, imageY + dy * subSize, imageX + (dx + 1) * subSize, imageY + (dy + 1) * subSize, imageX + dx * subSize, imageY + dy * subSize, imageX + (dx + 1) * subSize, imageY + (dy + 1) * subSize, null); } } if (! Boolean.TRUE.equals(surroundingTileImageAvailable)) { g2.setColor(voidColour); g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, subSize); } if (tileType == TileType.WALL) { g2.setColor(bedrockColour); TileType neighbourType = getUnzoomedTileType(x * scale + dx, y * scale + dy - 1); int wallWidth = Math.max(subSize / 8, 1); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, subSize, wallWidth); } neighbourType = getUnzoomedTileType(x * scale + dx + 1, y * scale + dy); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(imageX + (dx + 1) * subSize - wallWidth, imageY + dy * subSize, wallWidth, subSize); } neighbourType = getUnzoomedTileType(x * scale + dx, y * scale + dy + 1); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(imageX + dx * subSize, imageY + (dy + 1) * subSize - wallWidth, subSize, wallWidth); } neighbourType = getUnzoomedTileType(x * scale + dx - 1, y * scale + dy); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(imageX + dx * subSize, imageY + dy * subSize, wallWidth, subSize); } } break; } } } } finally { g2.dispose(); } return true; } } catch (Throwable e) { logger.error("Exception while generating image for tile at {}, {}", x, y, e); return false; } } @Override public int getTilePriority(int x, int y) { if (zoom == 0) { return (getUnzoomedTileType(x, y) == TileType.WORLD) ? 1 : 0; } else { final int scale = 1 << -zoom; for (int dx = 0; dx < scale; dx++) { for (int dy = 0; dy < scale; dy++) { if (getUnzoomedTileType(x * scale + dx, y * scale + dy) == TileType.WORLD) { return 1; } } } return 0; } } @Override public Rectangle getExtent() { Rectangle sourceExtent = tileProvider.getExtent(); if (sourceExtent != null) { if (zoom == 0) { return sourceExtent; } else if (zoom < 0) { return new Rectangle(sourceExtent.x >> -zoom, sourceExtent.y >> -zoom, sourceExtent.width >> -zoom, sourceExtent.height >> -zoom); } else { return new Rectangle(sourceExtent.x << zoom, sourceExtent.y << zoom, sourceExtent.width << zoom, sourceExtent.height << zoom); } } else { return null; } } @Override public void addTileListener(TileListener tileListener) { if (active && listeners.isEmpty()) { ((Dimension) tileProvider).addDimensionListener(this); for (Tile tile: ((Dimension) tileProvider).getTiles()) { tile.addListener(this); } } if (! listeners.contains(tileListener)) { listeners.add(tileListener); } } @Override public void removeTileListener(TileListener tileListener) { listeners.remove(tileListener); if (active && listeners.isEmpty()) { for (Tile tile: ((Dimension) tileProvider).getTiles()) { tile.removeListener(this); } ((Dimension) tileProvider).removeDimensionListener(this); } } @Override public boolean isZoomSupported() { return true; } @Override public int getZoom() { return zoom; } @Override public void setZoom(int zoom) { if (zoom != this.zoom) { if (zoom > 0) { throw new UnsupportedOperationException("Zooming in not supported"); } this.zoom = zoom; tileRendererRef = createNewTileRendererRef(); if (surroundingTileProvider != null) { surroundingTileProvider.setZoom(zoom); } } } // Dimension.Listener @Override public void tilesAdded(Dimension dimension, Set<Tile> tiles) { for (Tile tile: tiles) { tile.addListener(this); } fireTilesChangedIncludeBorder(tiles); } @Override public void tilesRemoved(Dimension dimension, Set<Tile> tiles) { for (Tile tile: tiles) { tile.removeListener(this); } fireTilesChangedIncludeBorder(tiles); } // Tile.Listener @Override public void heightMapChanged(Tile tile) { fireTileChanged(tile); } @Override public void terrainChanged(Tile tile) { fireTileChanged(tile); } @Override public void waterLevelChanged(Tile tile) { fireTileChanged(tile); } @Override public void layerDataChanged(Tile tile, Set<Layer> changedLayers) { fireTileChanged(tile); } @Override public void allBitLayerDataChanged(Tile tile) { fireTileChanged(tile); } @Override public void allNonBitlayerDataChanged(Tile tile) { fireTileChanged(tile); } @Override public void seedsChanged(Tile tile) { fireTileChanged(tile); } private TileType getUnzoomedTileType(int x, int y) { if (tileProvider.isTilePresent(x, y)) { return TileType.WORLD; } else if (showBorder && (tileProvider instanceof Dimension)) { Dimension dimension = (Dimension) tileProvider; if (dimension.isBorderTile(x, y)) { return TileType.BORDER; } else if (dimension.isBedrockWall() && ((dimension.getBorder() != null) ? (dimension.isBorderTile(x - 1, y) || dimension.isBorderTile(x, y - 1) || dimension.isBorderTile(x + 1, y) || dimension.isBorderTile(x, y + 1)) : (tileProvider.isTilePresent(x - 1, y) || tileProvider.isTilePresent(x, y - 1) || tileProvider.isTilePresent(x + 1, y) || tileProvider.isTilePresent(x, y + 1)))) { return TileType.WALL; } } return TileType.SURROUNDS; } private boolean paintUnzoomedTile(final Image tileImage, final int x, final int y, final int dx, final int dy) { TileType tileType = getUnzoomedTileType(x, y); switch (tileType) { case WORLD: Tile tile = tileProvider.getTile(x, y); if (tile.hasLayer(NotPresent.INSTANCE) && (surroundingTileProvider != null)) { if (! surroundingTileProvider.paintTile(tileImage, x, y, dx, dy)) { Graphics2D g2 = (Graphics2D) tileImage.getGraphics(); try { g2.setComposite(AlphaComposite.Src); g2.setColor(new Color(VoidRenderer.getColour())); g2.fillRect(dx, dy, TILE_SIZE, TILE_SIZE); } finally { g2.dispose(); } } } TileRenderer tileRenderer = tileRendererRef.get(); tileRenderer.setTile(tile); tileRenderer.renderTile(tileImage, dx, dy); return true; case BORDER: int colour; switch (((Dimension) tileProvider).getBorder()) { case WATER: case ENDLESS_WATER: colour = 0xff000000 | colourScheme.getColour(BLK_WATER); break; case LAVA: case ENDLESS_LAVA: colour = 0xff000000 | colourScheme.getColour(BLK_LAVA); break; case VOID: case ENDLESS_VOID: colour = 0x00ffffff & VoidRenderer.getColour(); break; default: throw new InternalError(); } Graphics2D g2 = (Graphics2D) tileImage.getGraphics(); try { g2.setColor(new Color(colour, true)); g2.setComposite(AlphaComposite.Src); g2.fillRect(dx, dy, TILE_SIZE, TILE_SIZE); // Draw border lines g2.setColor(Color.BLACK); g2.setStroke(new BasicStroke(2, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0.0f, new float[] {4.0f, 4.0f}, 0.0f)); if (tileProvider.isTilePresent(x, y - 1)) { g2.drawLine(dx + 1, dy + 1, dx + TILE_SIZE - 1, dy + 1); } if (tileProvider.isTilePresent(x + 1, y)) { g2.drawLine(dx + TILE_SIZE - 1, dy + 1, dx + TILE_SIZE - 1, dy + TILE_SIZE - 1); } if (tileProvider.isTilePresent(x, y + 1)) { g2.drawLine(dx + 1, dy + TILE_SIZE - 1, dx + TILE_SIZE - 1, dy + TILE_SIZE - 1); } if (tileProvider.isTilePresent(x - 1, y)) { g2.drawLine(dx + 1, dy + 1, dx + 1, dy + TILE_SIZE - 1); } } finally { g2.dispose(); } return true; case WALL: boolean backgroundPainted = false; if (surroundingTileProvider != null) { backgroundPainted = surroundingTileProvider.paintTile(tileImage, x, y, dx, dy); } g2 = (Graphics2D) tileImage.getGraphics(); try { if (! backgroundPainted) { // A surrounding tile provider would have completely // filled the image, but if there isn't one or it didn't // work we have to make sure of that ourselves g2.setColor(new Color(0x00ffffff & VoidRenderer.getColour(), true)); g2.setComposite(AlphaComposite.Src); g2.fillRect(dx, dy, TILE_SIZE, TILE_SIZE); } g2.setColor(new Color(colourScheme.getColour(BLK_BEDROCK))); TileType neighbourType = getUnzoomedTileType(x, y - 1); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(dx, dy, TILE_SIZE, 16); } neighbourType = getUnzoomedTileType(x + 1, y); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(dx + TILE_SIZE - 16, dy, 16, TILE_SIZE); } neighbourType = getUnzoomedTileType(x, y + 1); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(dx, dy + TILE_SIZE - 16, TILE_SIZE, 16); } neighbourType = getUnzoomedTileType(x - 1, y); if ((neighbourType == TileType.WORLD) || (neighbourType == TileType.BORDER)) { g2.fillRect(dx, dy, 16, TILE_SIZE); } } finally { g2.dispose(); } return true; case SURROUNDS: if (surroundingTileProvider != null) { return surroundingTileProvider.paintTile(tileImage, x, y, dx, dy); } return false; default: throw new InternalError(); } } private void fireTileChanged(Tile tile) { Point coords = getTileCoordinates(tile); for (TileListener listener: listeners) { listener.tileChanged(this, coords.x, coords.y); } } private void fireTilesChangedIncludeBorder(Set<Tile> tiles) { if (showBorder && (tileProvider instanceof Dimension) && (((Dimension) tileProvider).getDim() == DIM_NORMAL) && (((Dimension) tileProvider).getBorder() != null) && (! ((Dimension) tileProvider).getBorder().isEndless())) { final Set<Point> coordSet = new HashSet<>(); for (Tile tile: tiles) { final int tileX = tile.getX(), tileY = tile.getY(), borderSize = ((Dimension) tileProvider).getBorderSize(); for (int dx = -borderSize; dx <= borderSize; dx++) { for (int dy = -borderSize; dy <= borderSize; dy++) { coordSet.add(getTileCoordinates(tileX + dx, tileY + dy)); } } } for (TileListener listener: listeners) { listener.tilesChanged(this, coordSet); } } else { Set<Point> coords = tiles.stream().map(this::getTileCoordinates).collect(Collectors.toSet()); for (TileListener listener: listeners) { listener.tilesChanged(this, coords); } } } /** * Convert the actual tile coordinates to zoom-corrected (tile provider * coordinate system) coordinates. * * @param tile The tile of which to convert the coordinates. * @return The coordinates of the tile in the tile provider coordinate * system (corrected for zoom). */ private Point getTileCoordinates(Tile tile) { return getTileCoordinates(tile.getX(), tile.getY()); } /** * Convert the actual tile coordinates to zoom-corrected (tile provider * coordinate system) coordinates. * * @param tileX The X tile coordinate to convert. * @param tileY The Y tile coordinate to convert. * @return The coordinates of the tile in the tile provider coordinate * system (corrected for zoom). */ private Point getTileCoordinates(final int tileX, final int tileY) { if (zoom == 0) { return new Point(tileX, tileY); } else if (zoom < 0) { return new Point(tileX >> -zoom, tileY >> -zoom); } else { return new Point(tileX << zoom, tileY << zoom); } } @NotNull private ThreadLocal<TileRenderer> createNewTileRendererRef() { return new ThreadLocal<TileRenderer>() { @Override protected TileRenderer initialValue() { TileRenderer tileRenderer = new TileRenderer(tileProvider, colourScheme, biomeScheme, customBiomeManager, zoom); synchronized (WPTileProvider.this) { if (hiddenLayers != null) { tileRenderer.addHiddenLayers(hiddenLayers); } } tileRenderer.setContourLines(contourLines); tileRenderer.setContourSeparation(contourSeparation); tileRenderer.setLightOrigin(lightOrigin); return tileRenderer; } }; } private final TileProvider tileProvider; private final ColourScheme colourScheme; private final BiomeScheme biomeScheme; private final Set<Layer> hiddenLayers; private final boolean contourLines, active, showBorder; private final int contourSeparation; private final TileRenderer.LightOrigin lightOrigin; private final List<TileListener> listeners = new ArrayList<>(); private final CustomBiomeManager customBiomeManager; private final org.pepsoft.util.swing.TileProvider surroundingTileProvider; private int zoom = 0; private volatile ThreadLocal<TileRenderer> tileRendererRef; private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(WPTileProvider.class); private enum TileType { /** * The tile is part of the WorldPainter world. */ WORLD, /** * The tile is part of the WorldPainter border. */ BORDER, /** * The tile contains no WorldPainter-generated chunks. */ SURROUNDS, /** * The tile is outside the WorldPainter world and border but does * contain part of a wall. */ WALL} }