diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java new file mode 100644 index 00000000..324a470b --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -0,0 +1,297 @@ +package org.dynmap.hdmap; + +import static org.dynmap.JSONUtils.a; +import static org.dynmap.JSONUtils.s; + +import java.io.File; +import java.util.HashSet; + +import org.bukkit.block.Biome; +import org.dynmap.Color; +import org.dynmap.ColorScheme; +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.dynmap.hdmap.HDMap.BlockStep; +import org.dynmap.kzedmap.KzedMapTile; +import org.dynmap.kzedmap.DefaultTileRenderer.BiomeColorOption; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; +import org.json.simple.JSONObject; + +public class DefaultHDShader implements HDShader { + private ConfigurationNode configuration; + private String name; + protected ColorScheme colorScheme; + + protected HashSet highlightBlocks = new HashSet(); + protected Color highlightColor = new Color(255, 0, 0); + + protected int shadowscale[]; /* index=skylight level, value = 256 * scaling value */ + protected int lightscale[]; /* scale skylight level (light = lightscale[skylight] */ + protected boolean night_and_day; /* If true, render both day (prefix+'-day') and night (prefix) tiles */ + protected boolean transparency; /* Is transparency support active? */ + public enum BiomeColorOption { + NONE, BIOME, TEMPERATURE, RAINFALL + } + protected BiomeColorOption biomecolored = BiomeColorOption.NONE; /* Use biome for coloring */ + + public DefaultHDShader(ConfigurationNode configuration) { + this.configuration = configuration; + name = (String) configuration.get("prefix"); + double shadowweight = configuration.getDouble("shadowstrength", 0.0); + if(shadowweight > 0.0) { + shadowscale = new int[16]; + shadowscale[15] = 256; + /* Normal brightness weight in MC is a 20% relative dropoff per step */ + for(int i = 14; i >= 0; i--) { + double v = shadowscale[i+1] * (1.0 - (0.2 * shadowweight)); + shadowscale[i] = (int)v; + if(shadowscale[i] > 256) shadowscale[i] = 256; + if(shadowscale[i] < 0) shadowscale[i] = 0; + } + } + int v = configuration.getInteger("ambientlight", -1); + if(v >= 0) { + lightscale = new int[16]; + for(int i = 0; i < 16; i++) { + if(i < (15-v)) + lightscale[i] = 0; + else + lightscale[i] = i - (15-v); + } + } + colorScheme = ColorScheme.getScheme((String)configuration.get("colorscheme")); + night_and_day = configuration.getBoolean("night-and-day", false); + transparency = configuration.getBoolean("transparency", true); /* Default on */ + String biomeopt = configuration.getString("biomecolored", "none"); + if(biomeopt.equals("biome")) { + biomecolored = BiomeColorOption.BIOME; + } + else if(biomeopt.equals("temperature")) { + biomecolored = BiomeColorOption.TEMPERATURE; + } + else if(biomeopt.equals("rainfall")) { + biomecolored = BiomeColorOption.RAINFALL; + } + else { + biomecolored = BiomeColorOption.NONE; + } + } + + public boolean isBiomeDataNeeded() { return biomecolored == BiomeColorOption.BIOME; } + public boolean isRawBiomeDataNeeded() { return (biomecolored == BiomeColorOption.RAINFALL) || (biomecolored == BiomeColorOption.TEMPERATURE); }; + public boolean isNightAndDayEnabled() { return night_and_day; } + public boolean isSkyLightLevelNeeded() { return (lightscale != null); } + public boolean isEmittedLightLevelNeeded() { return (shadowscale != null); } + + public String getName() { return name; } + + private class OurRendererState implements HDShaderState { + private Color color = new Color(); + private Color daycolor; + protected MapIterator mapiter; + private int seqy; /* For dither */ + private Color tmpcolor = new Color(); + private Color tmpdaycolor = new Color(); + + private OurRendererState(MapIterator mapiter) { + this.mapiter = mapiter; + if(night_and_day) { + daycolor = new Color(); + } + } + /** + * Reset renderer state for new ray + */ + public void reset(int x, int y) { + color.setTransparent(); + if(daycolor != null) + daycolor.setTransparent(); + if(((x+y) & 0x01) == 0x01) + seqy = 0; + else + seqy = 2; + } + protected Color[] getBlockColors(int blocktype, int blockdata) { + if((blockdata != 0) && (colorScheme.datacolors[blocktype] != null)) + return colorScheme.datacolors[blocktype][blockdata]; + else + return colorScheme.colors[blocktype]; + } + /** + * Process next ray step - called for each block on route + * @param blocktype - block type of current block + * @param blockdata - data nibble of current block + * @param skylightlevel - sky light level of previous block (surface on current block) + * @param emittedlightlevel - emitted light level of previous block (surface on current block) + * @param laststep - direction of last step + * @return true if ray is done, false if ray needs to continue + */ + public boolean processBlock(int blocktype, int blockdata, int skylightlevel, int emittedlightlevel, HDMap.BlockStep laststep) { + if(blocktype == 0) + return false; + Color[] colors = getBlockColors(blocktype, blockdata); + + if (colors != null) { + int seq; + /* Figure out which color to use */ + if((laststep == BlockStep.X_PLUS) || (laststep == BlockStep.X_MINUS)) + seq = 1; + else if((laststep == BlockStep.Z_PLUS) || (laststep == BlockStep.Z_MINUS)) + seq = 3; + else + seq = seqy; + + Color c = colors[seq]; + if (c.getAlpha() > 0) { + /* Handle light level, if needed */ + int lightlevel = 15, lightlevel_day = 15; + if(shadowscale != null) { + lightlevel = lightlevel_day = skylightlevel; + if(lightscale != null) + lightlevel = lightscale[lightlevel]; + if((lightlevel < 15) || (lightlevel_day < 15)) { + int emitted = emittedlightlevel; + lightlevel = Math.max(emitted, lightlevel); + lightlevel_day = Math.max(emitted, lightlevel_day); + } + } + /* Figure out our color, with lighting if needed */ + tmpcolor.setColor(c); + if(lightlevel < 15) { + shadowColor(tmpcolor, lightlevel); + } + if(daycolor != null) { + if(lightlevel_day == lightlevel) { + tmpdaycolor.setColor(tmpcolor); + } + else { + tmpdaycolor.setColor(c); + if(lightlevel_day < 15) { + shadowColor(tmpdaycolor, lightlevel_day); + } + } + } + /* Blend color with accumulated color (weighted by alpha) */ + if(!transparency) { /* No transparency support */ + color.setARGB(tmpcolor.getARGB() | 0xFF000000); + if(daycolor != null) + daycolor.setARGB(tmpdaycolor.getARGB() | 0xFF000000); + return true; /* We're done */ + } + /* If no previous color contribution, use new color */ + else if(color.isTransparent()) { + color.setColor(tmpcolor); + if(daycolor != null) + daycolor.setColor(tmpdaycolor); + return (color.getAlpha() == 255); + } + /* Else, blend and generate new alpha */ + else { + int alpha = color.getAlpha(); + int alpha2 = tmpcolor.getAlpha() * (255-alpha) / 255; + color.setRGBA((tmpcolor.getRed()*alpha2 + color.getRed()*alpha) / 255, + (tmpcolor.getGreen()*alpha2 + color.getGreen()*alpha) / 255, + (tmpcolor.getBlue()*alpha2 + color.getBlue()*alpha) / 255, alpha+alpha2); + if(daycolor != null) + daycolor.setRGBA((tmpdaycolor.getRed()*alpha2 + daycolor.getRed()*alpha) / 255, + (tmpdaycolor.getGreen()*alpha2 + daycolor.getGreen()*alpha) / 255, + (tmpdaycolor.getBlue()*alpha2 + daycolor.getBlue()*alpha) / 255, alpha+alpha2); + return (alpha+alpha2) >= 254; /* If only one short, no meaningful contribution left */ + } + } + } + return false; + } + /** + * Ray ended - used to report that ray has exited map (called if renderer has not reported complete) + */ + public void rayFinished() { + } + /** + * Get result color - get resulting color for ray + * @param c - object to store color value in + * @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer + */ + public void getRayColor(Color c, int index) { + if(index == 0) + c.setColor(color); + else if((index == 1) && (daycolor != null)) + c.setColor(daycolor); + } + /** + * Clean up state object - called after last ray completed + */ + public void cleanup() { + } + + private final void shadowColor(Color c, int lightlevel) { + int scale = shadowscale[lightlevel]; + if(scale < 256) + c.setRGBA((c.getRed() * scale) >> 8, (c.getGreen() * scale) >> 8, + (c.getBlue() * scale) >> 8, c.getAlpha()); + } + } + + private class OurBiomeRendererState extends OurRendererState { + private OurBiomeRendererState(MapIterator mapiter) { + super(mapiter); + } + protected Color[] getBlockColors(int blocktype, int blockdata) { + Biome bio = mapiter.getBiome(); + if(bio != null) + return colorScheme.biomecolors[bio.ordinal()]; + return null; + } + } + + private class OurBiomeRainfallRendererState extends OurRendererState { + private OurBiomeRainfallRendererState(MapIterator mapiter) { + super(mapiter); + } + protected Color[] getBlockColors(int blocktype, int blockdata) { + return colorScheme.getRainColor(mapiter.getRawBiomeRainfall()); + } + } + + private class OurBiomeTempRendererState extends OurRendererState { + private OurBiomeTempRendererState(MapIterator mapiter) { + super(mapiter); + } + protected Color[] getBlockColors(int blocktype, int blockdata) { + return colorScheme.getTempColor(mapiter.getRawBiomeTemperature()); + } + } + /** + * Get renderer state object for use rendering a tile + * @param map - map being rendered + * @param cache - chunk cache containing data for tile to be rendered + * @param mapiter - iterator used when traversing rays in tile + * @return state object to use for all rays in tile + */ + public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter) { + switch(biomecolored) { + case NONE: + return new OurRendererState(mapiter); + case BIOME: + return new OurBiomeRendererState(mapiter); + case RAINFALL: + return new OurBiomeRainfallRendererState(mapiter); + case TEMPERATURE: + return new OurBiomeTempRendererState(mapiter); + } + return null; + } + + @Override + public void buildClientConfiguration(JSONObject worldObject) { + ConfigurationNode c = configuration; + JSONObject o = new JSONObject(); + s(o, "type", "HDMapType"); + s(o, "name", c.getString("name")); + s(o, "title", c.getString("title")); + s(o, "icon", c.getString("icon")); + s(o, "prefix", c.getString("prefix")); + a(worldObject, "maps", o); + } +} diff --git a/src/main/java/org/dynmap/hdmap/DummyHDRenderer.java b/src/main/java/org/dynmap/hdmap/DummyHDRenderer.java deleted file mode 100644 index e741e630..00000000 --- a/src/main/java/org/dynmap/hdmap/DummyHDRenderer.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.dynmap.hdmap; - -import static org.dynmap.JSONUtils.a; -import static org.dynmap.JSONUtils.s; - -import java.io.File; - -import org.dynmap.ColorScheme; -import org.dynmap.ConfigurationNode; -import org.dynmap.Log; -import org.dynmap.kzedmap.KzedMapTile; -import org.dynmap.kzedmap.DefaultTileRenderer.BiomeColorOption; -import org.dynmap.utils.MapChunkCache; -import org.json.simple.JSONObject; - -public class DummyHDRenderer implements HDMapTileRenderer { - private ConfigurationNode configuration; - private String name; - - public DummyHDRenderer(ConfigurationNode configuration) { - this.configuration = configuration; - name = (String) configuration.get("prefix"); - } - public boolean isBiomeDataNeeded() { return false; } - public boolean isRawBiomeDataNeeded() { return false; }; - public boolean isNightAndDayEnabled() { return false; } - public String getName() { return name; } - - public boolean render(MapChunkCache cache, HDMapTile tile, File outputFile) { - Log.info("DummyHDRenderer(" + tile + ", " + outputFile.getPath()); - return false; - } - - @Override - public void buildClientConfiguration(JSONObject worldObject) { - ConfigurationNode c = configuration; - JSONObject o = new JSONObject(); - s(o, "type", "HDMapType"); - s(o, "name", c.getString("name")); - s(o, "title", c.getString("title")); - s(o, "icon", c.getString("icon")); - s(o, "prefix", c.getString("prefix")); - a(worldObject, "maps", o); - } -} diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index 754c50af..cadb45c5 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -49,7 +49,16 @@ public class HDMap extends MapType { public double azimuth; /* Angle in degrees from looking north (0), east (90), south (180), or west (270) */ public double inclination; /* Angle in degrees from horizontal (0) to vertical (90) */ public double scale; /* Scale - tile pixel widths per block */ - public ColorScheme colorScheme; + /* Represents last step of movement of the ray */ + public enum BlockStep { + X_PLUS, + Y_PLUS, + Z_PLUS, + X_MINUS, + Y_MINUS, + Z_MINUS + }; + /* Coordinate space for tiles consists of a plane (X, Y), corresponding to the projection of each tile on to the * plane of the bottom of the world (X positive to the right, Y positive to the top), with Z+ corresponding to the * height above this plane on a vector towards the viewer). Logically, this makes the parallelogram representing the @@ -73,11 +82,13 @@ public class HDMap extends MapType { public static final double MAX_SCALE = 64; public static final double MIN_SCALE = 1; - private HDMapTileRenderer renderers[]; + private HDShader shaders[]; + private boolean need_skylightlevel = false; + private boolean need_emittedlightlevel = false; + private boolean need_biomedata = false; + private boolean need_rawbiomedata = false; public HDMap(ConfigurationNode configuration) { - colorScheme = ColorScheme.getScheme(configuration.getString("colorscheme", "default")); - azimuth = configuration.getDouble("azimuth", 135.0); /* Get azimuth (default to classic kzed POV */ inclination = configuration.getDouble("inclination", 60.0); if(inclination > MAX_INCLINATION) inclination = MAX_INCLINATION; @@ -111,11 +122,21 @@ public class HDMap extends MapType { transform.multiply(coordswap); map_to_world = transform; - Log.verboseinfo("Loading renderers for map '" + getClass().toString() + "'..."); - List renderers = configuration.createInstances("renderers", new Class[0], new Object[0]); - this.renderers = new HDMapTileRenderer[renderers.size()]; - renderers.toArray(this.renderers); - Log.verboseinfo("Loaded " + renderers.size() + " renderers for map '" + getClass().toString() + "'."); + Log.verboseinfo("Loading shaders for map '" + getClass().toString() + "'..."); + List shaders = configuration.createInstances("shaders", new Class[0], new Object[0]); + this.shaders = new HDShader[shaders.size()]; + shaders.toArray(this.shaders); + Log.verboseinfo("Loaded " + shaders.size() + " shaders for map '" + getClass().toString() + "'."); + for(HDShader shader : shaders) { + if(shader.isBiomeDataNeeded()) + need_biomedata = true; + if(shader.isEmittedLightLevelNeeded()) + need_emittedlightlevel = true; + if(shader.isSkyLightLevelNeeded()) + need_skylightlevel = true; + if(shader.isRawBiomeDataNeeded()) + need_rawbiomedata = true; + } } @Override @@ -156,16 +177,14 @@ public class HDMap extends MapType { int x = t.tx; int y = t.ty; return new MapTile[] { - new HDMapTile(w, this, t.renderer, x, y - 1), - new HDMapTile(w, this, t.renderer, x + 1, y), - new HDMapTile(w, this, t.renderer, x, y + 1), - new HDMapTile(w, this, t.renderer, x - 1, y) }; + new HDMapTile(w, this, x, y - 1), + new HDMapTile(w, this, x + 1, y), + new HDMapTile(w, this, x, y + 1), + new HDMapTile(w, this, x - 1, y) }; } public void addTile(HashSet tiles, DynmapWorld world, int tx, int ty) { - for (int i = 0; i < renderers.length; i++) { - tiles.add(new HDMapTile(world, this, renderers[i], tx, ty)); - } + tiles.add(new HDMapTile(world, this, tx, ty)); } public void invalidateTile(MapTile tile) { @@ -312,82 +331,118 @@ public class HDMap extends MapType { } @Override - public boolean render(MapChunkCache cache, MapTile tile, File outputFile) { + public boolean render(MapChunkCache cache, MapTile tile, File bogus) { HDMapTile t = (HDMapTile) tile; World w = t.getWorld(); - boolean rendered = false; Color rslt = new Color(); - int[] pixel = new int[4]; - KzedBufferedImage im = KzedMap.allocateBufferedImage(tileWidth, tileHeight); - int[] argb_buf = im.argb_buf; - MapIterator mapiter = cache.getIterator(0, 0, 0); + /* Build shader state object for each shader */ + HDShaderState[] shaderstate = new HDShaderState[shaders.length]; + for(int i = 0; i < shaders.length; i++) { + shaderstate[i] = shaders[i].getStateInstance(this, cache, mapiter); + if(shaders[i].isEmittedLightLevelNeeded()) + need_emittedlightlevel = true; + if(shaders[i].isSkyLightLevelNeeded()) + need_skylightlevel = true; + } + /* Create buffered image for each */ + KzedBufferedImage im[] = new KzedBufferedImage[shaders.length]; + KzedBufferedImage dayim[] = new KzedBufferedImage[shaders.length]; + int[][] argb_buf = new int[shaders.length][]; + int[][] day_argb_buf = new int[shaders.length][]; + for(int i = 0; i < shaders.length; i++) { + im[i] = KzedMap.allocateBufferedImage(tileWidth, tileHeight); + argb_buf[i] = im[i].argb_buf; + if(shaders[i].isNightAndDayEnabled()) { + dayim[i] = KzedMap.allocateBufferedImage(tileWidth, tileHeight); + day_argb_buf[i] = dayim[i].argb_buf; + } + } + Vector3D top = new Vector3D(); Vector3D bottom = new Vector3D(); double xbase = t.tx * tileWidth; double ybase = t.ty * tileHeight; - boolean odd = false; + boolean shaderdone[] = new boolean[shaders.length]; + boolean rendered[] = new boolean[shaders.length]; for(int x = 0; x < tileWidth; x++) { for(int y = 0; y < tileHeight; y++) { top.x = bottom.x = xbase + x + 0.5; /* Start at center of pixel at Y=127.5, bottom at Y=-0.5 */ top.y = bottom.y = ybase + y + 0.5; top.z = 127.5; bottom.z = -0.5; map_to_world.transform(top); /* Transform to world coordinates */ - map_to_world.transform(bottom); - raytrace(cache, mapiter, top, bottom, rslt, odd); - argb_buf[(tileHeight-y-1)*tileWidth + x] = rslt.getARGB(); - rendered = true; - odd = !odd; + map_to_world.transform(bottom); + for(int i = 0; i < shaders.length; i++) { + shaderstate[i].reset(x, y); + } + raytrace(cache, mapiter, top, bottom, shaderstate, shaderdone); + for(int i = 0; i < shaders.length; i++) { + if(shaderdone[i] == false) { + shaderstate[i].rayFinished(); + } + else { + shaderdone[i] = false; + rendered[i] = true; + } + shaderstate[i].getRayColor(rslt, 0); + argb_buf[i][(tileHeight-y-1)*tileWidth + x] = rslt.getARGB(); + if(day_argb_buf[i] != null) { + shaderstate[i].getRayColor(rslt, 1); + day_argb_buf[i][(tileHeight-y-1)*tileWidth + x] = rslt.getARGB(); + } + } } - odd = !odd; } + + boolean renderedone = false; /* Test to see if we're unchanged from older tile */ TileHashManager hashman = MapManager.mapman.hashman; - long crc = hashman.calculateTileHash(argb_buf); - boolean tile_update = false; - FileLockManager.getWriteLock(outputFile); - try { - if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), null, t.tx, t.ty))) { - /* Wrap buffer as buffered image */ - Debug.debug("saving image " + outputFile.getPath()); - if(!outputFile.getParentFile().exists()) - outputFile.getParentFile().mkdirs(); + for(int i = 0; i < shaders.length; i++) { + long crc = hashman.calculateTileHash(argb_buf[i]); + boolean tile_update = false; + String shadername = shaders[i].getName(); + if(rendered[i]) { + File f = new File(t.getDynmapWorld().worldtilepath, t.getFilename(shadername)); + FileLockManager.getWriteLock(f); try { - FileLockManager.imageIOWrite(im.buf_img, "png", outputFile); - } catch (IOException e) { - Debug.error("Failed to save image: " + outputFile.getPath(), e); - } catch (java.lang.NullPointerException e) { - Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e); + if((!f.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), shadername, t.tx, t.ty))) { + /* Wrap buffer as buffered image */ + Debug.debug("saving image " + f.getPath()); + if(!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + try { + FileLockManager.imageIOWrite(im[i].buf_img, "png", f); + } catch (IOException e) { + Debug.error("Failed to save image: " + f.getPath(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save image (NullPointerException): " + f.getPath(), e); + } + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(f.getPath())); + hashman.updateHashCode(tile.getKey(), shadername, t.tx, t.ty, crc); + tile.getDynmapWorld().enqueueZoomOutUpdate(f); + tile_update = true; + } + else { + Debug.debug("skipping image " + f.getPath() + " - hash match"); + } + } finally { + FileLockManager.releaseWriteLock(f); + renderedone = true; + KzedMap.freeBufferedImage(im[i]); + if(dayim[i] != null) + KzedMap.freeBufferedImage(dayim[i]); } - MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename())); - hashman.updateHashCode(tile.getKey(), null, t.tx, t.ty, crc); - tile.getDynmapWorld().enqueueZoomOutUpdate(outputFile); - tile_update = true; } - else { - Debug.debug("skipping image " + outputFile.getPath() + " - hash match"); - } - } finally { - FileLockManager.releaseWriteLock(outputFile); - KzedMap.freeBufferedImage(im); + MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); } - MapManager.mapman.updateStatistics(tile, null, true, tile_update, !rendered); - return rendered; + return renderedone; } - - public enum BlockStep { - X_PLUS, - Y_PLUS, - Z_PLUS, - X_MINUS, - Y_MINUS, - Z_MINUS - }; - + /** * Trace ray, based on "Voxel Tranversal along a 3D line" */ - private void raytrace(MapChunkCache cache, MapIterator mapiter, Vector3D top, Vector3D bottom, Color rslt, boolean odd) { + private void raytrace(MapChunkCache cache, MapIterator mapiter, Vector3D top, Vector3D bottom, + HDShaderState[] shaderstate, boolean[] shaderdone) { /* Compute total delta on each axis */ double dx = Math.abs(bottom.x - top.x); double dy = Math.abs(bottom.y - top.y); @@ -459,23 +514,28 @@ public class HDMap extends MapType { t_next_z = (top.z - Math.floor(top.z)) * dt_dz; } /* Walk through scene */ - rslt.setTransparent(); BlockStep laststep = BlockStep.Y_MINUS; /* Last step is down into map */ mapiter.initialize(x, y, z); + int sky_lightlevel = 15; + int emitted_lightlevel = 15; for (; n > 0; --n) { int blocktype = mapiter.getBlockTypeID(); if(blocktype != 0) { - Color[] clr = colorScheme.colors[blocktype]; - if(clr != null) { - if(laststep == BlockStep.Y_MINUS) - rslt.setColor(odd?clr[0]:clr[2]); - else if((laststep == BlockStep.X_PLUS) || (laststep == BlockStep.X_MINUS)) - rslt.setColor(clr[1]); - else - rslt.setColor(clr[3]); + int blockdata = mapiter.getBlockData(); + boolean done = true; + for(int i = 0; i < shaderstate.length; i++) { + if(!shaderdone[i]) + shaderdone[i] = shaderstate[i].processBlock(blocktype, blockdata, sky_lightlevel, emitted_lightlevel, laststep); + done = done && shaderdone[i]; } - return; + /* If all are done, we're out */ + if(done) + return; } + if(need_skylightlevel) + sky_lightlevel = mapiter.getBlockSkyLight(); + if(need_emittedlightlevel) + emitted_lightlevel = mapiter.getBlockEmittedLight(); /* If X step is next best */ if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) { x += x_inc; @@ -527,19 +587,21 @@ public class HDMap extends MapType { @Override public boolean isBiomeDataNeeded() { - return false; + return need_biomedata; } @Override public boolean isRawBiomeDataNeeded() { - return false; + return need_rawbiomedata; } @Override public List baseZoomFilePrefixes() { ArrayList s = new ArrayList(); - for(HDMapTileRenderer r : renderers) { + for(HDShader r : shaders) { s.add(r.getName()); + if(r.isNightAndDayEnabled()) + s.add(r.getName() + "_day"); } return s; } @@ -562,8 +624,8 @@ public class HDMap extends MapType { @Override public void buildClientConfiguration(JSONObject worldObject) { - for(HDMapTileRenderer renderer : renderers) { - renderer.buildClientConfiguration(worldObject); + for(HDShader shader : shaders) { + shader.buildClientConfiguration(worldObject); } } } diff --git a/src/main/java/org/dynmap/hdmap/HDMapTile.java b/src/main/java/org/dynmap/hdmap/HDMapTile.java index f3d5cc75..220ce011 100644 --- a/src/main/java/org/dynmap/hdmap/HDMapTile.java +++ b/src/main/java/org/dynmap/hdmap/HDMapTile.java @@ -6,31 +6,33 @@ import org.dynmap.MapTile; public class HDMapTile extends MapTile { public HDMap map; - public HDMapTileRenderer renderer; public int tx, ty; /* Tile X and Tile Y are in tile coordinates (pixels/tile-size) */ - private String fname; - public HDMapTile(DynmapWorld world, HDMap map, HDMapTileRenderer renderer, int tx, int ty) { + public HDMapTile(DynmapWorld world, HDMap map, int tx, int ty) { super(world, map); this.map = map; - this.renderer = renderer; this.tx = tx; this.ty = ty; } @Override public String getFilename() { - if(fname == null) { - fname = renderer.getName() + "/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; - } - return fname; + return getFilename("hdmap"); + } + + public String getFilename(String shader) { + return shader + "/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; } @Override public String getDayFilename() { - return getFilename(); + return getDayFilename("hdmap"); } + public String getDayFilename(String shader) { + return shader + "_day/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; + } + @Override public int hashCode() { return getFilename().hashCode() ^ getWorld().hashCode(); @@ -45,11 +47,11 @@ public class HDMapTile extends MapTile { } public boolean equals(HDMapTile o) { - return o.tx == tx && o.ty == ty && o.renderer == o.renderer && o.getWorld().equals(getWorld()); + return o.tx == tx && o.ty == ty && o.getWorld().equals(getWorld()); } public String getKey() { - return getWorld().getName() + "." + renderer.getName(); + return getWorld().getName() + ".hdmap"; } public String toString() { diff --git a/src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java b/src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java deleted file mode 100644 index 0957c51b..00000000 --- a/src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.dynmap.hdmap; - -import java.io.File; - -import org.dynmap.utils.MapChunkCache; - -import org.json.simple.JSONObject; - -public interface HDMapTileRenderer { - String getName(); - - boolean render(MapChunkCache cache, HDMapTile tile, File outputFile); - - void buildClientConfiguration(JSONObject worldObject); - - boolean isBiomeDataNeeded(); - boolean isRawBiomeDataNeeded(); - boolean isNightAndDayEnabled(); -} diff --git a/src/main/java/org/dynmap/hdmap/HDShader.java b/src/main/java/org/dynmap/hdmap/HDShader.java new file mode 100644 index 00000000..d6f60086 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDShader.java @@ -0,0 +1,34 @@ +package org.dynmap.hdmap; + +import java.io.File; + +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; + +import org.json.simple.JSONObject; + +public interface HDShader { + /* Get renderer name */ + String getName(); + /** + * Get renderer state object for use rendering a tile + * @param map - map being rendered + * @param cache - chunk cache containing data for tile to be rendered + * @param mapiter - iterator used when traversing rays in tile + * @return state object to use for all rays in tile + */ + HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter); + /* Build client configuration for this render instance */ + void buildClientConfiguration(JSONObject worldObject); + /* Test if Biome Data is needed for this renderer */ + boolean isBiomeDataNeeded(); + /* Test if raw biome temperature/rainfall data is needed */ + boolean isRawBiomeDataNeeded(); + /* Test if night/day is enabled for this renderer */ + boolean isNightAndDayEnabled(); + /* Test if sky light level needed */ + boolean isSkyLightLevelNeeded(); + /* Test if emitted light level needed */ + boolean isEmittedLightLevelNeeded(); + +} diff --git a/src/main/java/org/dynmap/hdmap/HDShaderState.java b/src/main/java/org/dynmap/hdmap/HDShaderState.java new file mode 100644 index 00000000..add426eb --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDShaderState.java @@ -0,0 +1,40 @@ +package org.dynmap.hdmap; + +import org.dynmap.Color; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; + +/** + * This interface is used to define the operational state of a renderer during raytracing + * All method should be considered performance critical + */ +public interface HDShaderState { + /** + * Reset renderer state for new ray - passes in pixel coordinate for ray + */ + void reset(int x, int y); + /** + * Process next ray step - called for each block on route + * @param blocktype - block type of current block + * @param blockdata - data nibble of current block + * @param skylightlevel - sky light level of previous block (surface on current block) + * @param emittedlightlevel - emitted light level of previous block (surface on current block) + * @param laststep - direction of last step + * @return true if ray is done, false if ray needs to continue + */ + boolean processBlock(int blocktype, int blockdata, int skylightlevel, int emittedlightlevel, HDMap.BlockStep laststep); + /** + * Ray ended - used to report that ray has exited map (called if renderer has not reported complete) + */ + void rayFinished(); + /** + * Get result color - get resulting color for ray + * @param c - object to store color value in + * @param index - index of color to request (renderer specific - 0=default, 1=day for night/day renderer + */ + void getRayColor(Color c, int index); + /** + * Clean up state object - called after last ray completed + */ + void cleanup(); +}