From 630759c87be44732beb88fde0e39ef0a11d5c88e Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Mon, 4 Jul 2011 09:28:06 -0500 Subject: [PATCH 01/14] HD renderer prototype --- .../org/dynmap/hdmap/DummyHDRenderer.java | 45 ++ src/main/java/org/dynmap/hdmap/HDMap.java | 566 ++++++++++++++++++ src/main/java/org/dynmap/hdmap/HDMapTile.java | 58 ++ .../org/dynmap/hdmap/HDMapTileRenderer.java | 19 + src/main/java/org/dynmap/utils/Matrix3D.java | 122 ++++ src/main/java/org/dynmap/utils/Vector3D.java | 16 + web/index.html | 1 + web/js/hdmap.js | 78 +++ 8 files changed, 905 insertions(+) create mode 100644 src/main/java/org/dynmap/hdmap/DummyHDRenderer.java create mode 100644 src/main/java/org/dynmap/hdmap/HDMap.java create mode 100644 src/main/java/org/dynmap/hdmap/HDMapTile.java create mode 100644 src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java create mode 100644 src/main/java/org/dynmap/utils/Matrix3D.java create mode 100644 src/main/java/org/dynmap/utils/Vector3D.java create mode 100644 web/js/hdmap.js diff --git a/src/main/java/org/dynmap/hdmap/DummyHDRenderer.java b/src/main/java/org/dynmap/hdmap/DummyHDRenderer.java new file mode 100644 index 00000000..e741e630 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/DummyHDRenderer.java @@ -0,0 +1,45 @@ +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 new file mode 100644 index 00000000..183a3482 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -0,0 +1,566 @@ +package org.dynmap.hdmap; + +import org.dynmap.DynmapWorld; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.World.Environment; +import org.dynmap.Client; +import org.dynmap.Color; +import org.dynmap.ColorScheme; +import org.dynmap.ConfigurationNode; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.MapManager; +import org.dynmap.MapTile; +import org.dynmap.MapType; +import org.dynmap.TileHashManager; +import org.dynmap.debug.Debug; +import org.dynmap.flat.FlatMap.FlatMapTile; +import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; +import org.dynmap.kzedmap.KzedMap; +import org.dynmap.kzedmap.MapTileRenderer; +import org.dynmap.utils.FileLockManager; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; +import org.dynmap.utils.Matrix3D; +import org.dynmap.utils.Vector3D; +import org.json.simple.JSONObject; +import java.awt.image.DataBufferInt; +import java.awt.image.DataBuffer; +import java.awt.image.WritableRaster; +import java.awt.image.ColorModel; +import java.awt.image.Raster; + +public class HDMap extends MapType { + /* View angles */ + 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; + /* 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 + * space contributing to the tile have consistent tile-space X,Y coordinate pairs for both the top and bottom faces + * Note that this is a classic right-hand coordinate system, while minecraft's world coordinates are left handed + * (X+ is south, Y+ is up, Z+ is east). + */ + /* Transformation matrix for taking coordinate in world-space (x, y, z) and finding coordinate in tile space (x, y, z) */ + private Matrix3D world_to_map; + private Matrix3D map_to_world; + + /* dimensions of a map tile */ + public static final int tileWidth = 128; + public static final int tileHeight = 128; + + /* Maximum and minimum inclinations */ + public static final double MAX_INCLINATION = 90.0; + public static final double MIN_INCLINATION = 20.0; + + /* Maximum and minimum scale */ + public static final double MAX_SCALE = 64; + public static final double MIN_SCALE = 1; + + private HDMapTileRenderer renderers[]; + + 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; + if(inclination < MIN_INCLINATION) inclination = MIN_INCLINATION; + scale = configuration.getDouble("scale", MIN_SCALE); + if(scale < MIN_SCALE) scale = MIN_SCALE; + if(scale > MAX_SCALE) scale = MAX_SCALE; + Log.info("azimuth=" + azimuth + ", inclination=" + inclination + ", scale=" + scale); + + /* Generate transform matrix for world-to-tile coordinate mapping */ + /* First, need to fix basic coordinate mismatches before rotation - we want zero azimuth to have north to top + * (world -X -> tile +Y) and east to right (world -Z to tile +X), with height being up (world +Y -> tile +Z) + */ + Matrix3D transform = new Matrix3D(0.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0); + /* Next, rotate world counterclockwise around Z axis by azumuth angle */ + transform.rotateXY(180-azimuth); + /* Next, rotate world by (90-inclination) degrees clockwise around +X axis */ + transform.rotateYZ(90.0-inclination); + /* Finally, shear along Z axis to normalize Z to be height above map plane */ + transform.shearZ(0, Math.tan(Math.toRadians(90.0-inclination))); + /* And scale Z to be same scale as world coordinates, and scale X and Y based on setting */ + transform.scale(scale, scale, Math.sin(Math.toRadians(inclination))); + world_to_map = transform; + /* Now, generate map to world tranform, by doing opposite actions in reverse order */ + transform = new Matrix3D(); + transform.scale(1.0/scale, 1.0/scale, 1/Math.sin(Math.toRadians(inclination))); + transform.shearZ(0, -Math.tan(Math.toRadians(90.0-inclination))); + transform.rotateYZ(-(90.0-inclination)); + transform.rotateXY(-180+azimuth); + Matrix3D coordswap = new Matrix3D(0.0, -1.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0); + 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() + "'."); + } + + @Override + public MapTile[] getTiles(Location loc) { + DynmapWorld world = MapManager.mapman.getWorld(loc.getWorld().getName()); + HashSet tiles = new HashSet(); + Vector3D block = new Vector3D(); + block.setFromLocation(loc); /* Get coordinate for block */ + Vector3D corner = new Vector3D(); + /* Loop through corners of the cube */ + for(int i = 0; i < 2; i++) { + double inity = block.y; + for(int j = 0; j < 2; j++) { + double initz = block.z; + for(int k = 0; k < 2; k++) { + world_to_map.transform(block, corner); /* Get map coordinate of corner */ + addTile(tiles, world, (int)Math.floor(corner.x/tileWidth), (int)Math.floor(corner.y/tileHeight)); + + block.z += 1; + } + block.z = initz; + block.y += 1; + } + block.y = inity; + block.x += 1; + } + MapTile[] result = tiles.toArray(new MapTile[tiles.size()]); + Log.info("processed update for " + loc); + for(MapTile mt : result) + Log.info("need to render " + mt); + return result; + } + + @Override + public MapTile[] getAdjecentTiles(MapTile tile) { + HDMapTile t = (HDMapTile) tile; + DynmapWorld w = t.getDynmapWorld(); + 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) }; + } + + 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)); + } + } + + public void invalidateTile(MapTile tile) { + } + + private static class Rectangle { + double r0x, r0z; /* Coord of corner of rectangle */ + double s1x, s1z; /* Side vector for one edge */ + double s2x, s2z; /* Side vector for other edge */ + public Rectangle(Vector3D v1, Vector3D v2, Vector3D v3) { + r0x = v1.x; + r0z = v1.z; + s1x = v2.x - v1.x; + s1z = v2.z - v1.z; + s2x = v3.x - v1.x; + s2z = v3.z - v1.z; + } + public Rectangle() { + } + public void setSquare(double rx, double rz, double s) { + this.r0x = rx; + this.r0z = rz; + this.s1x = s; + this.s1z = 0; + this.s2x = 0; + this.s2z = s; + } + double getX(int idx) { + return r0x + (((idx & 1) == 0)?0:s1x) + (((idx & 2) != 0)?0:s2x); + } + double getZ(int idx) { + return r0z + (((idx & 1) == 0)?0:s1z) + (((idx & 2) != 0)?0:s2z); + } + /** + * Test for overlap of projection of one vector on to anoter + */ + boolean testoverlap(double rx, double rz, double sx, double sz, Rectangle r) { + double rmin_dot_s0 = Double.MAX_VALUE; + double rmax_dot_s0 = Double.MIN_VALUE; + /* Project each point from rectangle on to vector: find lowest and highest */ + for(int i = 0; i < 4; i++) { + double r_x = r.getX(i) - rx; /* Get relative positon of second vector start to origin */ + double r_z = r.getZ(i) - rz; + double r_dot_s0 = r_x*sx + r_z*sz; /* Projection of start of vector */ + if(r_dot_s0 < rmin_dot_s0) rmin_dot_s0 = r_dot_s0; + if(r_dot_s0 > rmax_dot_s0) rmax_dot_s0 = r_dot_s0; + } + /* Compute dot products */ + double s0_dot_s0 = sx*sx + sz*sz; /* End of our side */ + if((rmax_dot_s0 < 0.0) || (rmin_dot_s0 > s0_dot_s0)) + return false; + else + return true; + } + /** + * Test if two rectangles intersect + * Based on separating axis theorem + */ + boolean testRectangleIntesectsRectangle(Rectangle r) { + /* Test if projection of each edge of one rectangle on to each edge of the other yields overlap */ + if(testoverlap(r0x, r0z, s1x, s1z, r) && testoverlap(r0x, r0z, s2x, s2z, r) && + testoverlap(r0x+s1x, r0z+s1z, s2x, s2z, r) && testoverlap(r0x+s2x, r0z+s2z, s1x, s1z, r) && + r.testoverlap(r.r0x, r.r0z, r.s1x, r.s1z, this) && r.testoverlap(r.r0x, r.r0z, r.s2x, r.s2z, this) && + r.testoverlap(r.r0x+r.s1x, r.r0z+r.s1z, r.s2x, r.s2z, this) && r.testoverlap(r.r0x+r.s2x, r.r0z+r.s2z, r.s1x, r.s1z, this)) { + return true; + } + else { + return false; + } + } + public String toString() { + return "{ " + r0x + "," + r0z + "}x{" + (r0x+s1x) + ","+ + (r0z+s1z) + "}x{" + (r0x+s2x) + "," + (r0z+s2z) + "}"; + } + } + + @Override + public List getRequiredChunks(MapTile tile) { + if (!(tile instanceof HDMapTile)) + return Collections.emptyList(); + + HDMapTile t = (HDMapTile) tile; + int min_chunk_x = Integer.MAX_VALUE; + int max_chunk_x = Integer.MIN_VALUE; + int min_chunk_z = Integer.MAX_VALUE; + int max_chunk_z = Integer.MIN_VALUE; + + /* Make corners for volume: 0 = bottom-lower-left, 1 = top-lower-left, 2=bottom-upper-left, 3=top-upper-left + * 4 = bottom-lower-right, 5 = top-lower-right, 6 = bottom-upper-right, 7 = top-upper-right */ + Vector3D corners[] = new Vector3D[8]; + int[] chunk_x = new int[8]; + int[] chunk_z = new int[8]; + for(int x = t.tx, idx = 0; x <= (t.tx+1); x++) { + for(int y = t.ty; y <= (t.ty+1); y++) { + for(int z = 0; z <= 1; z++) { + corners[idx] = new Vector3D(); + corners[idx].x = x*tileWidth; corners[idx].y = y*tileHeight; corners[idx].z = z*128; + map_to_world.transform(corners[idx]); + /* Compute chunk coordinates of corner */ + chunk_x[idx] = (int)Math.floor(corners[idx].x / 16); + chunk_z[idx] = (int)Math.floor(corners[idx].z / 16); + /* Compute min/max of chunk coordinates */ + if(min_chunk_x > chunk_x[idx]) min_chunk_x = chunk_x[idx]; + if(max_chunk_x < chunk_x[idx]) max_chunk_x = chunk_x[idx]; + if(min_chunk_z > chunk_z[idx]) min_chunk_z = chunk_z[idx]; + if(max_chunk_z < chunk_z[idx]) max_chunk_z = chunk_z[idx]; + idx++; + } + } + } + /* Make rectangles of X-Z projection of each side of the tile volume, 0 = top, 1 = bottom, 2 = left, 3 = right, + * 4 = upper, 5 = lower */ + Rectangle rect[] = new Rectangle[6]; + rect[0] = new Rectangle(corners[1], corners[3], corners[5]); + rect[1] = new Rectangle(corners[0], corners[2], corners[4]); + rect[2] = new Rectangle(corners[0], corners[1], corners[2]); + rect[3] = new Rectangle(corners[4], corners[5], corners[6]); + rect[4] = new Rectangle(corners[2], corners[3], corners[6]); + rect[5] = new Rectangle(corners[0], corners[1], corners[4]); + + /* Now, need to walk through the min/max range to see which chunks are actually needed */ + ArrayList chunks = new ArrayList(); + Rectangle chunkrect = new Rectangle(); + int misscnt = 0; + for(int x = min_chunk_x; x <= max_chunk_x; x++) { + for(int z = min_chunk_z; z <= max_chunk_z; z++) { + chunkrect.setSquare(x*16, z*16, 16); + boolean hit = false; + /* Check to see if square of chunk intersects any of our rectangle sides */ + for(int rctidx = 0; (!hit) && (rctidx < rect.length); rctidx++) { + if(chunkrect.testRectangleIntesectsRectangle(rect[rctidx])) { + hit = true; + } + } + if(hit) { + DynmapChunk chunk = new DynmapChunk(x, z); + chunks.add(chunk); + } + else { + misscnt++; + } + } + } + return chunks; + } + + @Override + public boolean render(MapChunkCache cache, MapTile tile, File outputFile) { + 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); + Vector3D top = new Vector3D(); + Vector3D bottom = new Vector3D(); + double xbase = t.tx * tileWidth; + double ybase = t.ty * tileHeight; + boolean odd = false; + 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; + } + odd = !odd; + } + /* 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(); + 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); + } + 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, null, true, tile_update, !rendered); + return rendered; + } + + 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) { + /* Compute total delta on each axis */ + double dx = Math.abs(bottom.x - top.x); + double dy = Math.abs(bottom.y - top.y); + double dz = Math.abs(bottom.z - top.z); + /* Initial block coord */ + int x = (int) (Math.floor(top.x)); + int y = (int) (Math.floor(top.y)); + int z = (int) (Math.floor(top.z)); + /* Compute parametric step (dt) per step on each axis */ + double dt_dx = 1.0 / dx; + double dt_dy = 1.0 / dy; + double dt_dz = 1.0 / dz; + /* Initialize parametric value to 0 (and we're stepping towards 1) */ + double t = 0; + /* Compute number of steps and increments for each */ + int n = 1; + int x_inc, y_inc, z_inc; + + double t_next_y, t_next_x, t_next_z; + /* If perpendicular to X axis */ + if (dx == 0) { + x_inc = 0; + t_next_x = Double.MAX_VALUE; + } + /* If bottom is right of top */ + else if (bottom.x > top.x) { + x_inc = 1; + n += (int) (Math.floor(bottom.x)) - x; + t_next_x = (Math.floor(top.x) + 1 - top.x) * dt_dx; + } + /* Top is right of bottom */ + else { + x_inc = -1; + n += x - (int) (Math.floor(bottom.x)); + t_next_x = (top.x - Math.floor(top.x)) * dt_dx; + } + /* If perpendicular to Y axis */ + if (dy == 0) { + y_inc = 0; + t_next_y = Double.MAX_VALUE; + } + /* If bottom is above top */ + else if (bottom.y > top.y) { + y_inc = 1; + n += (int) (Math.floor(bottom.y)) - y; + t_next_y = (Math.floor(top.y) + 1 - top.y) * dt_dy; + } + /* If top is above bottom */ + else { + y_inc = -1; + n += y - (int) (Math.floor(bottom.y)); + t_next_y = (top.y - Math.floor(top.y)) * dt_dy; + } + /* If perpendicular to Z axis */ + if (dz == 0) { + z_inc = 0; + t_next_z = Double.MAX_VALUE; + } + /* If bottom right of top */ + else if (bottom.z > top.z) { + z_inc = 1; + n += (int) (Math.floor(bottom.z)) - z; + t_next_z = (Math.floor(top.z) + 1 - top.z) * dt_dz; + } + /* If bottom left of top */ + else { + z_inc = -1; + n += z - (int) (Math.floor(bottom.z)); + 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); + 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]); + } + return; + } + /* If X step is next best */ + if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) { + x += x_inc; + t = t_next_x; + t_next_x += dt_dx; + if(x_inc > 0) { + laststep = BlockStep.X_PLUS; + mapiter.incrementX(); + } + else { + laststep = BlockStep.X_MINUS; + mapiter.decrementX(); + } + } + /* If Y step is next best */ + else if((t_next_y <= t_next_x) && (t_next_y <= t_next_z)) { + y += y_inc; + t = t_next_y; + t_next_y += dt_dy; + if(y_inc > 0) { + laststep = BlockStep.Y_PLUS; + mapiter.incrementY(); + if(mapiter.getY() > 127) + return; + } + else { + laststep = BlockStep.Y_MINUS; + mapiter.decrementY(); + if(mapiter.getY() < 0) + return; + } + } + /* Else, Z step is next best */ + else { + z += z_inc; + t = t_next_z; + t_next_z += dt_dz; + if(z_inc > 0) { + laststep = BlockStep.Z_PLUS; + mapiter.incrementZ(); + } + else { + laststep = BlockStep.Z_MINUS; + mapiter.decrementZ(); + } + } + } + } + + @Override + public boolean isBiomeDataNeeded() { + return false; + } + + @Override + public boolean isRawBiomeDataNeeded() { + return false; + } + + @Override + public List baseZoomFilePrefixes() { + ArrayList s = new ArrayList(); + for(HDMapTileRenderer r : renderers) { + s.add(r.getName()); + } + return s; + } + + public int baseZoomFileStepSize() { return 1; } + + private static final int[] stepseq = { 3, 1, 2, 0 }; + + public int[] zoomFileStepSequence() { return stepseq; } + + /* How many bits of coordinate are shifted off to make big world directory name */ + public int getBigWorldShift() { return 5; } + + @Override + public String getName() { + return "HDMap"; + } + + @Override + public void buildClientConfiguration(JSONObject worldObject) { + for(HDMapTileRenderer renderer : renderers) { + renderer.buildClientConfiguration(worldObject); + } + } +} diff --git a/src/main/java/org/dynmap/hdmap/HDMapTile.java b/src/main/java/org/dynmap/hdmap/HDMapTile.java new file mode 100644 index 00000000..f3d5cc75 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDMapTile.java @@ -0,0 +1,58 @@ +package org.dynmap.hdmap; + +import org.dynmap.DynmapWorld; +import java.io.File; +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) { + 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; + } + + @Override + public String getDayFilename() { + return getFilename(); + } + + @Override + public int hashCode() { + return getFilename().hashCode() ^ getWorld().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof HDMapTile) { + return equals((HDMapTile) obj); + } + return super.equals(obj); + } + + public boolean equals(HDMapTile o) { + return o.tx == tx && o.ty == ty && o.renderer == o.renderer && o.getWorld().equals(getWorld()); + } + + public String getKey() { + return getWorld().getName() + "." + renderer.getName(); + } + + public String toString() { + return getWorld().getName() + ":" + getFilename(); + } +} diff --git a/src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java b/src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java new file mode 100644 index 00000000..0957c51b --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java @@ -0,0 +1,19 @@ +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/utils/Matrix3D.java b/src/main/java/org/dynmap/utils/Matrix3D.java new file mode 100644 index 00000000..583f792c --- /dev/null +++ b/src/main/java/org/dynmap/utils/Matrix3D.java @@ -0,0 +1,122 @@ +package org.dynmap.utils; + +/** + * Basic 3D matrix math class - prevent dependency on Java 3D for this + */ +public class Matrix3D { + private double m11, m12, m13, m21, m22, m23, m31, m32, m33; + /** + * Construct identity matrix + */ + public Matrix3D() { + m11 = m22 = m33 = 1.0; + m12 = m13 = m21 = m23 = m31 = m32 = 0.0; + } + /** + * Construct matrix with given parms + * + * @param m11 - first cell of first row + * @param m12 - second cell of first row + * @param m13 - third cell of first row + * @param m21 - first cell of second row + * @param m22 - second cell of second row + * @param m23 - third cell of second row + * @param m31 - first cell of third row + * @param m32 - second cell of third row + * @param m33 - third cell of third row + */ + public Matrix3D(double m11, double m12, double m13, double m21, double m22, double m23, double m31, double m32, double m33) { + this.m11 = m11; this.m12 = m12; this.m13 = m13; + this.m21 = m21; this.m22 = m22; this.m23 = m23; + this.m31 = m31; this.m32 = m32; this.m33 = m33; + } + /** + * Multiply matrix by another matrix (this = mat * this), and store result in self + * @param mat + */ + public void multiply(Matrix3D mat) { + double new_m11 = mat.m11*m11 + mat.m12*m21 + mat.m13*m31; + double new_m12 = mat.m11*m12 + mat.m12*m22 + mat.m13*m32; + double new_m13 = mat.m11*m13 + mat.m12*m23 + mat.m13*m33; + double new_m21 = mat.m21*m11 + mat.m22*m21 + mat.m23*m31; + double new_m22 = mat.m21*m12 + mat.m22*m22 + mat.m23*m32; + double new_m23 = mat.m21*m13 + mat.m22*m23 + mat.m23*m33; + double new_m31 = mat.m31*m11 + mat.m32*m21 + mat.m33*m31; + double new_m32 = mat.m31*m12 + mat.m32*m22 + mat.m33*m32; + double new_m33 = mat.m31*m13 + mat.m32*m23 + mat.m33*m33; + m11 = new_m11; m12 = new_m12; m13 = new_m13; + m21 = new_m21; m22 = new_m22; m23 = new_m23; + m31 = new_m31; m32 = new_m32; m33 = new_m33; + } + /** + * Scale each coordinate by given values + * + * @param s1 + * @param s2 + * @param s3 + */ + public void scale(double s1, double s2, double s3) { + Matrix3D scalemat = new Matrix3D(s1, 0, 0, 0, s2, 0, 0, 0, s3); + multiply(scalemat); + } + /** + * Rotate XY clockwise around +Z axis + * @param rot_deg - degrees of rotation + */ + public void rotateXY(double rot_deg) { + double rot_rad = Math.toRadians(rot_deg); + double sin_rot = Math.sin(rot_rad); + double cos_rot = Math.cos(rot_rad); + Matrix3D rotmat = new Matrix3D(cos_rot, sin_rot, 0, -sin_rot, cos_rot, 0, 0, 0, 1); + multiply(rotmat); + } + /** + * Rotate YZ clockwise around +X axis + * @param rot_deg - degrees of rotation + */ + public void rotateYZ(double rot_deg) { + double rot_rad = Math.toRadians(rot_deg); + double sin_rot = Math.sin(rot_rad); + double cos_rot = Math.cos(rot_rad); + Matrix3D rotmat = new Matrix3D(1, 0, 0, 0, cos_rot, sin_rot, 0, -sin_rot, cos_rot); + multiply(rotmat); + } + /** + * Shear along Z axis by factor of X and Y + */ + public void shearZ(double x_fact, double y_fact) { + Matrix3D shearmat = new Matrix3D(1, 0, 0, 0, 1, 0, x_fact, y_fact, 1); + multiply(shearmat); + } + /** + * Transform a given vector using the matrix + */ + public final void transform(double[] v) { + double v1 = m11*v[0] + m12*v[1] + m13*v[2]; + double v2 = m21*v[0] + m22*v[1] + m23*v[2]; + double v3 = m31*v[0] + m32*v[1] + m33*v[2]; + v[0] = v1; v[1] = v2; v[2] = v3; + } + /** + * Transform a given vector using the matrix + */ + public final void transform(Vector3D v) { + double v1 = m11*v.x + m12*v.y + m13*v.z; + double v2 = m21*v.x + m22*v.y + m23*v.z; + double v3 = m31*v.x + m32*v.y + m33*v.z; + v.x = v1; v.y = v2; v.z = v3; + } + + /** + * Transform a given vector using the matrix - put result in provided output vector + */ + public final void transform(Vector3D v, Vector3D outv) { + outv.x = m11*v.x + m12*v.y + m13*v.z; + outv.y = m21*v.x + m22*v.y + m23*v.z; + outv.z = m31*v.x + m32*v.y + m33*v.z; + } + + public String toString() { + return "[ [" + m11 + " " + m12 + " " + m13 + "] [" + m21 + " " + m22 + " " + m23 + "] [" + m31 + " " + m32 + " " + m33 + "] ]"; + } +} diff --git a/src/main/java/org/dynmap/utils/Vector3D.java b/src/main/java/org/dynmap/utils/Vector3D.java new file mode 100644 index 00000000..1beebfcf --- /dev/null +++ b/src/main/java/org/dynmap/utils/Vector3D.java @@ -0,0 +1,16 @@ +package org.dynmap.utils; +import org.bukkit.Location; +/** + * Simple vector class + */ +public class Vector3D { + public double x, y, z; + + public Vector3D() { x = y = z = 0.0; } + + public void setFromLocation(Location l) { x = l.getX(); y = l.getY(); z = l.getZ(); } + + public String toString() { + return "{ " + x + ", " + y + ", " + z + " }"; + } +} diff --git a/web/index.html b/web/index.html index 4ad5a33e..a2d246f7 100644 --- a/web/index.html +++ b/web/index.html @@ -26,6 +26,7 @@ + diff --git a/web/js/hdmap.js b/web/js/hdmap.js new file mode 100644 index 00000000..09027303 --- /dev/null +++ b/web/js/hdmap.js @@ -0,0 +1,78 @@ +function HDProjection() {} +HDProjection.prototype = { + extrazoom: 0, + fromLatLngToPoint: function(latLng) { + return new google.maps.Point(latLng.lng()*config.tileWidth,latLng.lat()*config.tileHeight); + }, + fromPointToLatLng: function(point) { + return new google.maps.LatLng(point.y/config.tileHeight, point.x/config.tileWidth); + }, + fromWorldToLatLng: function(x, y, z) { + return new google.maps.LatLng(-z / config.tileWidth / (1 << this.extrazoom), x / config.tileHeight / (1 << this.extrazoom)); + } +}; + +function HDMapType(configuration) { + $.extend(this, configuration); } +HDMapType.prototype = $.extend(new DynMapType(), { + constructor: HDMapType, + projection: new HDProjection(), + tileSize: new google.maps.Size(128.0, 128.0), + minZoom: 0, + maxZoom: 3, + prefix: null, + getTile: function(coord, zoom, doc) { + var tileSize = 128; + var imgSize; + var tileName; + + var extrazoom = this.dynmap.world.extrazoomout; + if(zoom < extrazoom) { + var scale = 1 << (extrazoom-zoom); + var zprefix = "zzzzzzzzzzzz".substring(0, extrazoom-zoom); + tileName = this.prefix + '/' + ((scale*coord.x) >> 5) + '_' + ((-scale*coord.y) >> 5) + + '/' + zprefix + "_" + (scale*coord.x) + '_' + (-scale*coord.y) + '.png'; + imgSize = 128; + } + else { + tileName = this.prefix + '/' + (coord.x >> 5) + '_' + ((-coord.y) >> 5) + + '/' + coord.x + '_' + (-coord.y) + '.png'; + imgSize = Math.pow(2, 7+zoom-extrazoom); + } + var tile = $('
') + .addClass('tile') + .css({ + width: tileSize + 'px', + height: tileSize + 'px' + }); + var img = $('') + .attr('src', this.dynmap.getTileUrl(tileName)) + .error(function() { img.hide(); }) + .bind('load', function() { img.show(); }) + .css({ + width: imgSize +'px', + height: imgSize + 'px', + borderStyle: 'none' + }) + .hide() + .appendTo(tile); + this.dynmap.registerTile(this, tileName, img); + //this.dynmap.unregisterTile(this, tileName); + return tile.get(0); + }, + updateTileSize: function(zoom) { + var size; + var extrazoom = this.dynmap.world.extrazoomout; + this.projection.extrazoom = extrazoom; + this.maxZoom = 3 + extrazoom; + if (zoom <= extrazoom) { + size = 128; + } + else { + size = Math.pow(2, 7+zoom-extrazoom); + } + this.tileSize = new google.maps.Size(size, size); + } +}); + +maptypes.HDMapType = function(configuration) { return new HDMapType(configuration); }; \ No newline at end of file From 961eb1753686b0fbc911643fcbe820e46103b690 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Mon, 4 Jul 2011 10:18:05 -0500 Subject: [PATCH 02/14] Fix zoom-out coordinate consistency on HDMaps --- src/main/java/org/dynmap/DynmapWorld.java | 22 +++++++++++++++---- src/main/java/org/dynmap/MapType.java | 9 +++++++- src/main/java/org/dynmap/flat/FlatMap.java | 3 +++ src/main/java/org/dynmap/hdmap/HDMap.java | 3 +++ src/main/java/org/dynmap/kzedmap/KzedMap.java | 6 +++-- web/js/hdmap.js | 4 ++-- 6 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index c4e865d5..9c9097c5 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -107,6 +107,7 @@ public class DynmapWorld { int stepsize; int[] stepseq; boolean neg_step_x; + boolean neg_step_y; String baseprefix; int zoomlevel; String zoomprefix; @@ -152,15 +153,26 @@ public class DynmapWorld { int stepsize = mt.baseZoomFileStepSize(); int bigworldshift = mt.getBigWorldShift(); boolean neg_step_x = false; - if(stepsize < 0) { - stepsize = -stepsize; - neg_step_x = true; + boolean neg_step_y = false; + switch(mt.zoomFileMapStep()) { + case X_PLUS_Y_PLUS: + break; + case X_MINUS_Y_PLUS: + neg_step_x = true; + break; + case X_PLUS_Y_MINUS: + neg_step_y = true; + break; + case X_MINUS_Y_MINUS: + neg_step_x = neg_step_y = true; + break; } int[] stepseq = mt.zoomFileStepSequence(); for(String p : pfx) { PrefixData pd = new PrefixData(); pd.stepsize = stepsize; pd.neg_step_x = neg_step_x; + pd.neg_step_y = neg_step_y; pd.stepseq = stepseq; pd.baseprefix = p; pd.zoomlevel = zoomlevel; @@ -285,7 +297,9 @@ public class DynmapWorld { int[] argb = new int[width*height]; int step = pd.stepsize << pd.zoomlevel; int ztx = tx; + int zty = ty; tx = tx - (pd.neg_step_x?step:0); /* Adjust for negative step */ + ty = ty - (pd.neg_step_y?step:0); /* Adjust for negative step */ /* create image buffer */ kzIm = KzedMap.allocateBufferedImage(width, height); @@ -338,7 +352,7 @@ public class DynmapWorld { TileHashManager hashman = MapManager.mapman.hashman; long crc = hashman.calculateTileHash(kzIm.argb_buf); /* Get hash of tile */ int tilex = ztx/step/2; - int tiley = ty/step/2; + int tiley = zty/step/2; String key = world.getName()+".z"+pd.zoomprefix+pd.baseprefix; if((!zf.exists()) || (crc != MapManager.mapman.hashman.getImageHashCode(key, null, tilex, tiley))) { try { diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 0b51c1eb..b83f7551 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -27,7 +27,14 @@ public abstract class MapType { public boolean isHightestBlockYDataNeeded() { return false; } public boolean isRawBiomeDataNeeded() { return false; } public boolean isBlockTypeDataNeeded() { return true; } - + + public enum MapStep { + X_PLUS_Y_PLUS, + X_PLUS_Y_MINUS, + X_MINUS_Y_PLUS, + X_MINUS_Y_MINUS + } + public abstract MapStep zoomFileMapStep(); public abstract List baseZoomFilePrefixes(); public abstract int baseZoomFileStepSize(); /* How many bits of coordinate are shifted off to make big world directory name */ diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index 5792109a..a536c43d 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -22,6 +22,7 @@ import org.dynmap.MapManager; import org.dynmap.TileHashManager; import org.dynmap.MapTile; import org.dynmap.MapType; +import org.dynmap.MapType.MapStep; import org.dynmap.debug.Debug; import org.dynmap.kzedmap.KzedMap; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; @@ -426,6 +427,8 @@ public class FlatMap extends MapType { private static final int[] stepseq = { 1, 3, 0, 2 }; + public MapStep zoomFileMapStep() { return MapStep.X_PLUS_Y_PLUS; } + public int[] zoomFileStepSequence() { return stepseq; } /* How many bits of coordinate are shifted off to make big world directory name */ diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index 183a3482..754c50af 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -26,6 +26,7 @@ import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.TileHashManager; +import org.dynmap.MapType.MapStep; import org.dynmap.debug.Debug; import org.dynmap.flat.FlatMap.FlatMapTile; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; @@ -547,6 +548,8 @@ public class HDMap extends MapType { private static final int[] stepseq = { 3, 1, 2, 0 }; + public MapStep zoomFileMapStep() { return MapStep.X_PLUS_Y_MINUS; } + public int[] zoomFileStepSequence() { return stepseq; } /* How many bits of coordinate are shifted off to make big world directory name */ diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index e4858fc6..bf8f0486 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -17,6 +17,7 @@ import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; +import org.dynmap.MapType.MapStep; import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; import java.awt.image.DataBufferInt; @@ -335,8 +336,9 @@ public class KzedMap extends MapType { } return s; } - /* Return negative to flag negative X walk */ - public int baseZoomFileStepSize() { return -zTileWidth; } + public int baseZoomFileStepSize() { return zTileWidth; } + + public MapStep zoomFileMapStep() { return MapStep.X_MINUS_Y_PLUS; } private static final int[] stepseq = { 0, 2, 1, 3 }; diff --git a/web/js/hdmap.js b/web/js/hdmap.js index 09027303..7bc1b68a 100644 --- a/web/js/hdmap.js +++ b/web/js/hdmap.js @@ -2,10 +2,10 @@ function HDProjection() {} HDProjection.prototype = { extrazoom: 0, fromLatLngToPoint: function(latLng) { - return new google.maps.Point(latLng.lng()*config.tileWidth,latLng.lat()*config.tileHeight); + return new google.maps.Point(latLng.lng()*config.tileWidth, latLng.lat()*config.tileHeight); }, fromPointToLatLng: function(point) { - return new google.maps.LatLng(point.y/config.tileHeight, point.x/config.tileWidth); + return new google.maps.LatLng( point.y/config.tileHeight, point.x/config.tileWidth); }, fromWorldToLatLng: function(x, y, z) { return new google.maps.LatLng(-z / config.tileWidth / (1 << this.extrazoom), x / config.tileHeight / (1 << this.extrazoom)); From f3f871df3cfce3c12289c1ee4dae7a0b8dba4f5e Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Mon, 4 Jul 2011 22:34:30 -0500 Subject: [PATCH 03/14] Finish correction of zoom file coordinate correction for HDMap --- src/main/java/org/dynmap/DynmapWorld.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index 9c9097c5..e4d828eb 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -267,10 +267,12 @@ public class DynmapWorld { else x = x + (x % (2*step)); if(pd.neg_step_x) x = -x; + if(pd.neg_step_y) y = -y; if(y >= 0) y = y - (y % (2*step)); else y = y + (y % (2*step)); + if(pd.neg_step_y) y = -y; /* Make name of corresponding zoomed tile */ String zfname = makeFilePath(pd, x, y, true); File zf = new File(worldtilepath, zfname); From 1f2722b24991bbb2401f51567177ee8db9f48f71 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Tue, 5 Jul 2011 22:17:22 -0500 Subject: [PATCH 04/14] Get full existing shader function working on HDMap --- .../org/dynmap/hdmap/DefaultHDShader.java | 297 ++++++++++++++++++ .../org/dynmap/hdmap/DummyHDRenderer.java | 45 --- src/main/java/org/dynmap/hdmap/HDMap.java | 224 ++++++++----- src/main/java/org/dynmap/hdmap/HDMapTile.java | 24 +- .../org/dynmap/hdmap/HDMapTileRenderer.java | 19 -- src/main/java/org/dynmap/hdmap/HDShader.java | 34 ++ .../java/org/dynmap/hdmap/HDShaderState.java | 40 +++ 7 files changed, 527 insertions(+), 156 deletions(-) create mode 100644 src/main/java/org/dynmap/hdmap/DefaultHDShader.java delete mode 100644 src/main/java/org/dynmap/hdmap/DummyHDRenderer.java delete mode 100644 src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java create mode 100644 src/main/java/org/dynmap/hdmap/HDShader.java create mode 100644 src/main/java/org/dynmap/hdmap/HDShaderState.java 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(); +} From 225b348154c849eed5392f04f3215e02b168641f Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Tue, 5 Jul 2011 23:24:25 -0500 Subject: [PATCH 05/14] Get day/night workding with HDMap --- .../org/dynmap/hdmap/DefaultHDShader.java | 30 +++++++++------- src/main/java/org/dynmap/hdmap/HDMap.java | 36 +++++++++++++++++-- .../org/dynmap/utils/LegacyMapChunkCache.java | 6 ++++ .../java/org/dynmap/utils/MapIterator.java | 8 +++++ .../org/dynmap/utils/NewMapChunkCache.java | 6 ++++ web/js/hdmap.js | 8 +++-- 6 files changed, 77 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java index 324a470b..732f5703 100644 --- a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -90,9 +90,9 @@ public class DefaultHDShader implements HDShader { 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 int pixelodd; private OurRendererState(MapIterator mapiter) { this.mapiter = mapiter; @@ -107,10 +107,7 @@ public class DefaultHDShader implements HDShader { color.setTransparent(); if(daycolor != null) daycolor.setTransparent(); - if(((x+y) & 0x01) == 0x01) - seqy = 0; - else - seqy = 2; + pixelodd = x ^ y; } protected Color[] getBlockColors(int blocktype, int blockdata) { if((blockdata != 0) && (colorScheme.datacolors[blocktype] != null)) @@ -139,8 +136,10 @@ public class DefaultHDShader implements HDShader { seq = 1; else if((laststep == BlockStep.Z_PLUS) || (laststep == BlockStep.Z_MINUS)) seq = 3; + else if(((mapiter.getX() ^ mapiter.getZ() ^ pixelodd) & 0x01) == 0) + seq = 0; else - seq = seqy; + seq = 2; Color c = colors[seq]; if (c.getAlpha() > 0) { @@ -190,14 +189,15 @@ public class DefaultHDShader implements HDShader { 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); + int talpha = alpha + alpha2; + color.setRGBA((tmpcolor.getRed()*alpha2 + color.getRed()*alpha) / talpha, + (tmpcolor.getGreen()*alpha2 + color.getGreen()*alpha) / talpha, + (tmpcolor.getBlue()*alpha2 + color.getBlue()*alpha) / talpha, talpha); 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 */ + daycolor.setRGBA((tmpdaycolor.getRed()*alpha2 + daycolor.getRed()*alpha) / talpha, + (tmpdaycolor.getGreen()*alpha2 + daycolor.getGreen()*alpha) / talpha, + (tmpdaycolor.getBlue()*alpha2 + daycolor.getBlue()*alpha) / talpha, talpha); + return (talpha >= 254); /* If only one short, no meaningful contribution left */ } } } @@ -292,6 +292,10 @@ public class DefaultHDShader implements HDShader { s(o, "title", c.getString("title")); s(o, "icon", c.getString("icon")); s(o, "prefix", c.getString("prefix")); + s(o, "background", c.getString("background")); + s(o, "nightandday", c.getBoolean("night-and-day", false)); + s(o, "backgroundday", c.getString("backgroundday")); + s(o, "backgroundnight", c.getString("backgroundnight")); 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 cadb45c5..00cde6cb 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -429,11 +429,43 @@ public class HDMap extends MapType { FileLockManager.releaseWriteLock(f); renderedone = true; KzedMap.freeBufferedImage(im[i]); - if(dayim[i] != null) + } + MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); + /* Handle day image, if needed */ + if(dayim[i] != null) { + f = new File(t.getDynmapWorld().worldtilepath, t.getDayFilename(shadername)); + FileLockManager.getWriteLock(f); + shadername = shadername+"_day"; + tile_update = false; + try { + 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(dayim[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(dayim[i]); + } + MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); } } - MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); } return renderedone; } diff --git a/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java b/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java index c27c6012..30c86a5d 100644 --- a/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java +++ b/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java @@ -136,9 +136,15 @@ public class LegacyMapChunkCache implements MapChunkCache { public final void setY(int y) { this.y = y; } + public final int getX() { + return x; + } public final int getY() { return y; } + public final int getZ() { + return z; + } } /** diff --git a/src/main/java/org/dynmap/utils/MapIterator.java b/src/main/java/org/dynmap/utils/MapIterator.java index d8bba2da..9585df11 100644 --- a/src/main/java/org/dynmap/utils/MapIterator.java +++ b/src/main/java/org/dynmap/utils/MapIterator.java @@ -81,8 +81,16 @@ public interface MapIterator { * @param y */ void setY(int y); + /** + * Get X coordinate + */ + int getX(); /** * Get Y coordinate */ int getY(); + /** + * Get Z coordinate + */ + int getZ(); } diff --git a/src/main/java/org/dynmap/utils/NewMapChunkCache.java b/src/main/java/org/dynmap/utils/NewMapChunkCache.java index ca76b764..e4816e96 100644 --- a/src/main/java/org/dynmap/utils/NewMapChunkCache.java +++ b/src/main/java/org/dynmap/utils/NewMapChunkCache.java @@ -136,9 +136,15 @@ public class NewMapChunkCache implements MapChunkCache { public final void setY(int y) { this.y = y; } + public final int getX() { + return x; + } public final int getY() { return y; } + public final int getZ() { + return z; + } } /** diff --git a/web/js/hdmap.js b/web/js/hdmap.js index 7bc1b68a..5a922fbf 100644 --- a/web/js/hdmap.js +++ b/web/js/hdmap.js @@ -26,16 +26,20 @@ HDMapType.prototype = $.extend(new DynMapType(), { var imgSize; var tileName; + var dnprefix = ''; + if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].nightandday && this.dynmap.serverday) + dnprefix = '_day'; + var extrazoom = this.dynmap.world.extrazoomout; if(zoom < extrazoom) { var scale = 1 << (extrazoom-zoom); var zprefix = "zzzzzzzzzzzz".substring(0, extrazoom-zoom); - tileName = this.prefix + '/' + ((scale*coord.x) >> 5) + '_' + ((-scale*coord.y) >> 5) + + tileName = this.prefix + dnprefix + '/' + ((scale*coord.x) >> 5) + '_' + ((-scale*coord.y) >> 5) + '/' + zprefix + "_" + (scale*coord.x) + '_' + (-scale*coord.y) + '.png'; imgSize = 128; } else { - tileName = this.prefix + '/' + (coord.x >> 5) + '_' + ((-coord.y) >> 5) + + tileName = this.prefix + dnprefix + '/' + (coord.x >> 5) + '_' + ((-coord.y) >> 5) + '/' + coord.x + '_' + (-coord.y) + '.png'; imgSize = Math.pow(2, 7+zoom-extrazoom); } From 4f73d2cbd6b7cc5272d8d2d2b7a628186e4e6f69 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Thu, 7 Jul 2011 00:35:59 -0500 Subject: [PATCH 06/14] Clean up HDMap dither, fix tile update events --- src/main/java/org/dynmap/MapManager.java | 1 + src/main/java/org/dynmap/hdmap/DefaultHDShader.java | 11 ++++++----- src/main/java/org/dynmap/hdmap/HDMap.java | 12 +++++++----- src/main/java/org/dynmap/hdmap/HDShaderState.java | 5 ++--- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 41626997..7eadbed3 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -188,6 +188,7 @@ public class MapManager { found.clear(); rendered.clear(); rendercnt = 0; + timeaccum = 0; map_index++; /* Next map */ if(map_index >= world.maps.size()) { /* Last one done? */ sender.sendMessage("Full render of '" + world.world.getName() + "' finished."); diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java index 732f5703..a2b23af9 100644 --- a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -16,6 +16,7 @@ import org.dynmap.kzedmap.KzedMapTile; import org.dynmap.kzedmap.DefaultTileRenderer.BiomeColorOption; import org.dynmap.utils.MapChunkCache; import org.dynmap.utils.MapIterator; +import org.dynmap.utils.Vector3D; import org.json.simple.JSONObject; public class DefaultHDShader implements HDShader { @@ -103,11 +104,11 @@ public class DefaultHDShader implements HDShader { /** * Reset renderer state for new ray */ - public void reset(int x, int y) { + public void reset(int x, int y, Vector3D raystart, double scale) { color.setTransparent(); if(daycolor != null) daycolor.setTransparent(); - pixelodd = x ^ y; + pixelodd = (x & 0x3) + (y<<1); } protected Color[] getBlockColors(int blocktype, int blockdata) { if((blockdata != 0) && (colorScheme.datacolors[blocktype] != null)) @@ -136,10 +137,10 @@ public class DefaultHDShader implements HDShader { seq = 1; else if((laststep == BlockStep.Z_PLUS) || (laststep == BlockStep.Z_MINUS)) seq = 3; - else if(((mapiter.getX() ^ mapiter.getZ() ^ pixelodd) & 0x01) == 0) - seq = 0; - else + else if(((pixelodd + mapiter.getY()) & 0x03) == 0) seq = 2; + else + seq = 0; Color c = colors[seq]; if (c.getAlpha() > 0) { diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index 00cde6cb..5e7e3b10 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -373,7 +373,7 @@ public class HDMap extends MapType { map_to_world.transform(top); /* Transform to world coordinates */ map_to_world.transform(bottom); for(int i = 0; i < shaders.length; i++) { - shaderstate[i].reset(x, y); + shaderstate[i].reset(x, y, top, scale); } raytrace(cache, mapiter, top, bottom, shaderstate, shaderdone); for(int i = 0; i < shaders.length; i++) { @@ -402,7 +402,8 @@ public class HDMap extends MapType { boolean tile_update = false; String shadername = shaders[i].getName(); if(rendered[i]) { - File f = new File(t.getDynmapWorld().worldtilepath, t.getFilename(shadername)); + String fname = t.getFilename(shadername); + File f = new File(t.getDynmapWorld().worldtilepath, fname); FileLockManager.getWriteLock(f); try { if((!f.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), shadername, t.tx, t.ty))) { @@ -417,7 +418,7 @@ public class HDMap extends MapType { } 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())); + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(fname)); hashman.updateHashCode(tile.getKey(), shadername, t.tx, t.ty, crc); tile.getDynmapWorld().enqueueZoomOutUpdate(f); tile_update = true; @@ -433,7 +434,8 @@ public class HDMap extends MapType { MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); /* Handle day image, if needed */ if(dayim[i] != null) { - f = new File(t.getDynmapWorld().worldtilepath, t.getDayFilename(shadername)); + fname = t.getDayFilename(shadername); + f = new File(t.getDynmapWorld().worldtilepath, fname); FileLockManager.getWriteLock(f); shadername = shadername+"_day"; tile_update = false; @@ -450,7 +452,7 @@ public class HDMap extends MapType { } 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())); + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(fname)); hashman.updateHashCode(tile.getKey(), shadername, t.tx, t.ty, crc); tile.getDynmapWorld().enqueueZoomOutUpdate(f); tile_update = true; diff --git a/src/main/java/org/dynmap/hdmap/HDShaderState.java b/src/main/java/org/dynmap/hdmap/HDShaderState.java index add426eb..7befeece 100644 --- a/src/main/java/org/dynmap/hdmap/HDShaderState.java +++ b/src/main/java/org/dynmap/hdmap/HDShaderState.java @@ -1,8 +1,7 @@ package org.dynmap.hdmap; import org.dynmap.Color; -import org.dynmap.utils.MapChunkCache; -import org.dynmap.utils.MapIterator; +import org.dynmap.utils.Vector3D; /** * This interface is used to define the operational state of a renderer during raytracing @@ -12,7 +11,7 @@ public interface HDShaderState { /** * Reset renderer state for new ray - passes in pixel coordinate for ray */ - void reset(int x, int y); + void reset(int x, int y, Vector3D raystart, double scale); /** * Process next ray step - called for each block on route * @param blocktype - block type of current block From 56e5f6fbb38777a4f9be19140a62bfa2d44e8639 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Thu, 7 Jul 2011 08:41:31 -0500 Subject: [PATCH 07/14] Split perspective state out, to let shaders better interact with it --- .../org/dynmap/hdmap/DefaultHDShader.java | 23 ++- src/main/java/org/dynmap/hdmap/HDMap.java | 132 ++++++++++++------ .../org/dynmap/hdmap/HDPerspectiveState.java | 46 ++++++ .../java/org/dynmap/hdmap/HDShaderState.java | 11 +- 4 files changed, 147 insertions(+), 65 deletions(-) create mode 100644 src/main/java/org/dynmap/hdmap/HDPerspectiveState.java diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java index a2b23af9..dd9aa82f 100644 --- a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -104,35 +104,34 @@ public class DefaultHDShader implements HDShader { /** * Reset renderer state for new ray */ - public void reset(int x, int y, Vector3D raystart, double scale) { + public void reset(HDPerspectiveState ps) { color.setTransparent(); if(daycolor != null) daycolor.setTransparent(); - pixelodd = (x & 0x3) + (y<<1); + pixelodd = (ps.getPixelX() & 0x3) + (ps.getPixelY()<<1); } + 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) { + public boolean processBlock(HDPerspectiveState ps) { + int blocktype = ps.getBlockTypeID(); if(blocktype == 0) return false; - Color[] colors = getBlockColors(blocktype, blockdata); + Color[] colors = getBlockColors(blocktype, ps.getBlockData()); if (colors != null) { int seq; /* Figure out which color to use */ + HDMap.BlockStep laststep = ps.getLastBlockStep(); if((laststep == BlockStep.X_PLUS) || (laststep == BlockStep.X_MINUS)) seq = 1; else if((laststep == BlockStep.Z_PLUS) || (laststep == BlockStep.Z_MINUS)) @@ -147,11 +146,11 @@ public class DefaultHDShader implements HDShader { /* Handle light level, if needed */ int lightlevel = 15, lightlevel_day = 15; if(shadowscale != null) { - lightlevel = lightlevel_day = skylightlevel; + lightlevel = lightlevel_day = ps.getSkyLightLevel(); if(lightscale != null) lightlevel = lightscale[lightlevel]; if((lightlevel < 15) || (lightlevel_day < 15)) { - int emitted = emittedlightlevel; + int emitted = ps.getEmittedLightLevel(); lightlevel = Math.max(emitted, lightlevel); lightlevel_day = Math.max(emitted, lightlevel_day); } @@ -207,7 +206,7 @@ public class DefaultHDShader implements HDShader { /** * Ray ended - used to report that ray has exited map (called if renderer has not reported complete) */ - public void rayFinished() { + public void rayFinished(HDPerspectiveState ps) { } /** * Get result color - get resulting color for ray diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index 5e7e3b10..bf10b784 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -1,24 +1,17 @@ package org.dynmap.hdmap; import org.dynmap.DynmapWorld; -import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedList; import java.util.List; -import java.util.logging.Logger; import org.bukkit.Location; import org.bukkit.World; -import org.bukkit.World.Environment; import org.dynmap.Client; import org.dynmap.Color; -import org.dynmap.ColorScheme; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapChunk; import org.dynmap.Log; @@ -26,23 +19,15 @@ import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.TileHashManager; -import org.dynmap.MapType.MapStep; import org.dynmap.debug.Debug; -import org.dynmap.flat.FlatMap.FlatMapTile; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; import org.dynmap.kzedmap.KzedMap; -import org.dynmap.kzedmap.MapTileRenderer; import org.dynmap.utils.FileLockManager; import org.dynmap.utils.MapChunkCache; import org.dynmap.utils.MapIterator; import org.dynmap.utils.Matrix3D; import org.dynmap.utils.Vector3D; import org.json.simple.JSONObject; -import java.awt.image.DataBufferInt; -import java.awt.image.DataBuffer; -import java.awt.image.WritableRaster; -import java.awt.image.ColorModel; -import java.awt.image.Raster; public class HDMap extends MapType { /* View angles */ @@ -88,6 +73,57 @@ public class HDMap extends MapType { private boolean need_biomedata = false; private boolean need_rawbiomedata = false; + private class OurPerspectiveState implements HDPerspectiveState { + int skylightlevel = 15; + int emittedlightlevel = 0; + int blocktypeid = 0; + int blockdata = 0; + Vector3D top, bottom; + int px, py; + BlockStep laststep = BlockStep.Y_MINUS; + /** + * Get sky light level - only available if shader requested it + */ + public final int getSkyLightLevel() { return skylightlevel; } + /** + * Get emitted light level - only available if shader requested it + */ + public final int getEmittedLightLevel() { return emittedlightlevel; } + /** + * Get current block type ID + */ + public final int getBlockTypeID() { return blocktypeid; } + /** + * Get current block data + */ + public final int getBlockData() { return blockdata; } + /** + * Get direction of last block step + */ + public final BlockStep getLastBlockStep() { return laststep; } + /** + * Get perspective scale + */ + public final double getScale() { return scale; } + /** + * Get start of current ray, in world coordinates + */ + public final Vector3D getRayStart() { return top; } + /** + * Get end of current ray, in world coordinates + */ + public final Vector3D getRayEnd() { return bottom; } + /** + * Get pixel X coordinate + */ + public final int getPixelX() { return px; } + /** + * Get pixel Y coordinate + */ + public final int getPixelY() { return py; } + + } + public HDMap(ConfigurationNode configuration) { azimuth = configuration.getDouble("azimuth", 135.0); /* Get azimuth (default to classic kzed POV */ inclination = configuration.getDouble("inclination", 60.0); @@ -345,6 +381,9 @@ public class HDMap extends MapType { if(shaders[i].isSkyLightLevelNeeded()) need_skylightlevel = true; } + /* Create perspective state object */ + OurPerspectiveState ps = new OurPerspectiveState(); + /* Create buffered image for each */ KzedBufferedImage im[] = new KzedBufferedImage[shaders.length]; KzedBufferedImage dayim[] = new KzedBufferedImage[shaders.length]; @@ -359,26 +398,28 @@ public class HDMap extends MapType { } } - Vector3D top = new Vector3D(); - Vector3D bottom = new Vector3D(); + ps.top = new Vector3D(); + ps.bottom = new Vector3D(); double xbase = t.tx * tileWidth; double ybase = t.ty * tileHeight; boolean shaderdone[] = new boolean[shaders.length]; boolean rendered[] = new boolean[shaders.length]; for(int x = 0; x < tileWidth; x++) { + ps.px = 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); + ps.top.x = ps.bottom.x = xbase + x + 0.5; /* Start at center of pixel at Y=127.5, bottom at Y=-0.5 */ + ps.top.y = ps.bottom.y = ybase + y + 0.5; + ps.top.z = 127.5; ps.bottom.z = -0.5; + map_to_world.transform(ps.top); /* Transform to world coordinates */ + map_to_world.transform(ps.bottom); + ps.py = y; for(int i = 0; i < shaders.length; i++) { - shaderstate[i].reset(x, y, top, scale); + shaderstate[i].reset(ps); } - raytrace(cache, mapiter, top, bottom, shaderstate, shaderdone); + raytrace(cache, mapiter, ps, shaderstate, shaderdone); for(int i = 0; i < shaders.length; i++) { if(shaderdone[i] == false) { - shaderstate[i].rayFinished(); + shaderstate[i].rayFinished(ps); } else { shaderdone[i] = false; @@ -394,7 +435,7 @@ public class HDMap extends MapType { } } - boolean renderedone = false; + boolean renderone = false; /* Test to see if we're unchanged from older tile */ TileHashManager hashman = MapManager.mapman.hashman; for(int i = 0; i < shaders.length; i++) { @@ -402,6 +443,7 @@ public class HDMap extends MapType { boolean tile_update = false; String shadername = shaders[i].getName(); if(rendered[i]) { + renderone = true; String fname = t.getFilename(shadername); File f = new File(t.getDynmapWorld().worldtilepath, fname); FileLockManager.getWriteLock(f); @@ -428,7 +470,6 @@ public class HDMap extends MapType { } } finally { FileLockManager.releaseWriteLock(f); - renderedone = true; KzedMap.freeBufferedImage(im[i]); } MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); @@ -462,21 +503,22 @@ public class HDMap extends MapType { } } finally { FileLockManager.releaseWriteLock(f); - renderedone = true; KzedMap.freeBufferedImage(dayim[i]); } MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); } } } - return renderedone; + return renderone; } /** * Trace ray, based on "Voxel Tranversal along a 3D line" */ - private void raytrace(MapChunkCache cache, MapIterator mapiter, Vector3D top, Vector3D bottom, + private void raytrace(MapChunkCache cache, MapIterator mapiter, OurPerspectiveState ps, HDShaderState[] shaderstate, boolean[] shaderdone) { + Vector3D top = ps.top; + Vector3D bottom = ps.bottom; /* Compute total delta on each axis */ double dx = Math.abs(bottom.x - top.x); double dy = Math.abs(bottom.y - top.y); @@ -548,18 +590,18 @@ public class HDMap extends MapType { t_next_z = (top.z - Math.floor(top.z)) * dt_dz; } /* Walk through scene */ - BlockStep laststep = BlockStep.Y_MINUS; /* Last step is down into map */ + ps.laststep = BlockStep.Y_MINUS; /* Last step is down into map */ mapiter.initialize(x, y, z); - int sky_lightlevel = 15; - int emitted_lightlevel = 15; + ps.skylightlevel = 15; + ps.emittedlightlevel = 0; for (; n > 0; --n) { - int blocktype = mapiter.getBlockTypeID(); - if(blocktype != 0) { - int blockdata = mapiter.getBlockData(); + ps.blocktypeid = mapiter.getBlockTypeID(); + if(ps.blocktypeid != 0) { + ps.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); + shaderdone[i] = shaderstate[i].processBlock(ps); done = done && shaderdone[i]; } /* If all are done, we're out */ @@ -567,20 +609,20 @@ public class HDMap extends MapType { return; } if(need_skylightlevel) - sky_lightlevel = mapiter.getBlockSkyLight(); + ps.skylightlevel = mapiter.getBlockSkyLight(); if(need_emittedlightlevel) - emitted_lightlevel = mapiter.getBlockEmittedLight(); + ps.emittedlightlevel = mapiter.getBlockEmittedLight(); /* If X step is next best */ if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) { x += x_inc; t = t_next_x; t_next_x += dt_dx; if(x_inc > 0) { - laststep = BlockStep.X_PLUS; + ps.laststep = BlockStep.X_PLUS; mapiter.incrementX(); } else { - laststep = BlockStep.X_MINUS; + ps.laststep = BlockStep.X_MINUS; mapiter.decrementX(); } } @@ -590,13 +632,13 @@ public class HDMap extends MapType { t = t_next_y; t_next_y += dt_dy; if(y_inc > 0) { - laststep = BlockStep.Y_PLUS; + ps.laststep = BlockStep.Y_PLUS; mapiter.incrementY(); if(mapiter.getY() > 127) return; } else { - laststep = BlockStep.Y_MINUS; + ps.laststep = BlockStep.Y_MINUS; mapiter.decrementY(); if(mapiter.getY() < 0) return; @@ -608,11 +650,11 @@ public class HDMap extends MapType { t = t_next_z; t_next_z += dt_dz; if(z_inc > 0) { - laststep = BlockStep.Z_PLUS; + ps.laststep = BlockStep.Z_PLUS; mapiter.incrementZ(); } else { - laststep = BlockStep.Z_MINUS; + ps.laststep = BlockStep.Z_MINUS; mapiter.decrementZ(); } } diff --git a/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java b/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java new file mode 100644 index 00000000..9a91903e --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java @@ -0,0 +1,46 @@ +package org.dynmap.hdmap; + +import org.dynmap.utils.Vector3D; + +public interface HDPerspectiveState { + /** + * Get sky light level - only available if shader requested it + */ + int getSkyLightLevel(); + /** + * Get emitted light level - only available if shader requested it + */ + int getEmittedLightLevel(); + /** + * Get current block type ID + */ + int getBlockTypeID(); + /** + * Get current block data + */ + int getBlockData(); + /** + * Get direction of last block step + */ + HDMap.BlockStep getLastBlockStep(); + /** + * Get perspective scale + */ + double getScale(); + /** + * Get start of current ray, in world coordinates + */ + Vector3D getRayStart(); + /** + * Get end of current ray, in world coordinates + */ + Vector3D getRayEnd(); + /** + * Get pixel X coordinate + */ + int getPixelX(); + /** + * Get pixel Y coordinate + */ + int getPixelY(); +} diff --git a/src/main/java/org/dynmap/hdmap/HDShaderState.java b/src/main/java/org/dynmap/hdmap/HDShaderState.java index 7befeece..028ad62d 100644 --- a/src/main/java/org/dynmap/hdmap/HDShaderState.java +++ b/src/main/java/org/dynmap/hdmap/HDShaderState.java @@ -11,21 +11,16 @@ public interface HDShaderState { /** * Reset renderer state for new ray - passes in pixel coordinate for ray */ - void reset(int x, int y, Vector3D raystart, double scale); + void reset(HDPerspectiveState ps); /** * 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); + boolean processBlock(HDPerspectiveState ps); /** * Ray ended - used to report that ray has exited map (called if renderer has not reported complete) */ - void rayFinished(); + void rayFinished(HDPerspectiveState ps); /** * Get result color - get resulting color for ray * @param c - object to store color value in From 52f23f5e2dc9f1d03fcdef2d0225a53d6feb9ae0 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 8 Jul 2011 01:34:22 -0500 Subject: [PATCH 08/14] Start structuring for shaders and perspectives --- perspectives.txt | 1 + shaders.txt | 18 ++++++++++ src/main/assembly/package.xml | 4 ++- src/main/java/org/dynmap/DynmapPlugin.java | 11 ++++++- src/main/java/org/dynmap/MapManager.java | 11 +++++-- .../org/dynmap/hdmap/DefaultHDShader.java | 4 +-- .../java/org/dynmap/hdmap/HDMapManager.java | 33 +++++++++++++++++++ .../java/org/dynmap/hdmap/HDPerspective.java | 6 ++++ 8 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 perspectives.txt create mode 100644 shaders.txt create mode 100644 src/main/java/org/dynmap/hdmap/HDMapManager.java create mode 100644 src/main/java/org/dynmap/hdmap/HDPerspective.java diff --git a/perspectives.txt b/perspectives.txt new file mode 100644 index 00000000..22c4c82c --- /dev/null +++ b/perspectives.txt @@ -0,0 +1 @@ +perspectives: diff --git a/shaders.txt b/shaders.txt new file mode 100644 index 00000000..3e75bb75 --- /dev/null +++ b/shaders.txt @@ -0,0 +1,18 @@ +shaders: + - class: org.dynmap.hdmap.DefaultHDShader + name: classic + colorscheme: default + + - class: org.dynmap.hdmap.DefaultHDShader + name: night + colorscheme: default + ambientlight: 4 + shadowstrength: 1.0 + + - class: org.dynmap.hdmap.DefaultHDShader + name: daynight + colorscheme: default + ambientlight: 4 + shadowstrength: 1.0 + night-and-day: true + \ No newline at end of file diff --git a/src/main/assembly/package.xml b/src/main/assembly/package.xml index aa50719f..91a9506a 100644 --- a/src/main/assembly/package.xml +++ b/src/main/assembly/package.xml @@ -27,7 +27,9 @@ ${project.basedir} /dynmap/ - configuration.txt + configuration.txt + shaders.txt + perspectives.txt diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index d769b975..0bf23209 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -46,6 +46,8 @@ public class DynmapPlugin extends JavaPlugin { public MapManager mapManager = null; public PlayerList playerList; public ConfigurationNode configuration; + public ConfigurationNode shaderconfig; + public ConfigurationNode perspectiveconfig; public HashSet enabledTriggers = new HashSet(); public PermissionProvider permissions; public ComponentManager componentManager = new ComponentManager(); @@ -77,6 +79,13 @@ public class DynmapPlugin extends JavaPlugin { org.bukkit.util.config.Configuration bukkitConfiguration = new org.bukkit.util.config.Configuration(new File(this.getDataFolder(), "configuration.txt")); bukkitConfiguration.load(); configuration = new ConfigurationNode(bukkitConfiguration); + /* Load shaders and perspectives */ + org.bukkit.util.config.Configuration bukkitShaderConfig = new org.bukkit.util.config.Configuration(new File(this.getDataFolder(), "shaders.txt")); + bukkitShaderConfig.load(); + shaderconfig = new ConfigurationNode(bukkitShaderConfig); + org.bukkit.util.config.Configuration bukkitPerspectiveConfig = new org.bukkit.util.config.Configuration(new File(this.getDataFolder(), "perspectives.txt")); + bukkitPerspectiveConfig.load(); + perspectiveconfig = new ConfigurationNode(bukkitPerspectiveConfig); Log.verbose = configuration.getBoolean("verbose", true); @@ -90,7 +99,7 @@ public class DynmapPlugin extends JavaPlugin { playerList = new PlayerList(getServer(), getFile("hiddenplayers.txt"), configuration); playerList.load(); - mapManager = new MapManager(this, configuration); + mapManager = new MapManager(this, configuration, shaderconfig, perspectiveconfig); mapManager.startRendering(); loadWebserver(); diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 7eadbed3..556a1b8a 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -21,6 +21,8 @@ import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.command.CommandSender; import org.dynmap.DynmapWorld.AutoGenerateOption; import org.dynmap.debug.Debug; +import org.dynmap.hdmap.HDMapManager; +import org.dynmap.hdmap.HDShader; import org.dynmap.utils.LegacyMapChunkCache; import org.dynmap.utils.MapChunkCache; import org.dynmap.utils.NewMapChunkCache; @@ -48,7 +50,8 @@ public class MapManager { public static final Object lock = new Object(); public static MapManager mapman; /* Our singleton */ - + public HDMapManager hdmapman; + /* Thread pool for processing renders */ private DynmapScheduledThreadPoolExecutor renderpool; private static final int POOL_SIZE = 3; @@ -319,9 +322,13 @@ public class MapManager { } } - public MapManager(DynmapPlugin plugin, ConfigurationNode configuration) { + public MapManager(DynmapPlugin plugin, ConfigurationNode configuration, ConfigurationNode shadercfg, ConfigurationNode perspectivecfg) { plug_in = plugin; mapman = this; + /* Initialize HD map manager */ + hdmapman = new HDMapManager(); + hdmapman.loadHDShaders(shadercfg); + hdmapman.loadHDPerspectives(perspectivecfg); this.tileQueue = new AsynchronousQueue(new Handler() { @Override diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java index a2b23af9..8b69da56 100644 --- a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -38,7 +38,7 @@ public class DefaultHDShader implements HDShader { public DefaultHDShader(ConfigurationNode configuration) { this.configuration = configuration; - name = (String) configuration.get("prefix"); + name = (String) configuration.get("name"); double shadowweight = configuration.getDouble("shadowstrength", 0.0); if(shadowweight > 0.0) { shadowscale = new int[16]; @@ -61,7 +61,7 @@ public class DefaultHDShader implements HDShader { lightscale[i] = i - (15-v); } } - colorScheme = ColorScheme.getScheme((String)configuration.get("colorscheme")); + colorScheme = ColorScheme.getScheme(configuration.getString("colorscheme", "default")); night_and_day = configuration.getBoolean("night-and-day", false); transparency = configuration.getBoolean("transparency", true); /* Default on */ String biomeopt = configuration.getString("biomecolored", "none"); diff --git a/src/main/java/org/dynmap/hdmap/HDMapManager.java b/src/main/java/org/dynmap/hdmap/HDMapManager.java new file mode 100644 index 00000000..1850f3ed --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDMapManager.java @@ -0,0 +1,33 @@ +package org.dynmap.hdmap; + +import java.util.HashMap; + +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; + +public class HDMapManager { + public HashMap shaders = new HashMap(); + public HashMap perspectives = new HashMap(); + + public void loadHDShaders(ConfigurationNode shadercfg) { + Log.verboseinfo("Loading shaders..."); + for(HDShader shader : shadercfg.createInstances("shaders", new Class[0], new Object[0])) { + if(shaders.containsKey(shader.getName())) { + Log.severe("Duplicate shader name '" + shader.getName() + "' - shader ignored"); + } + shaders.put(shader.getName(), shader); + } + Log.info("Loaded " + shaders.size() + " shaders."); + } + + public void loadHDPerspectives(ConfigurationNode perspectivecfg) { + Log.verboseinfo("Loading perspectives..."); + for(HDPerspective perspective : perspectivecfg.createInstances("perspectives", new Class[0], new Object[0])) { + if(perspectives.containsKey(perspective.getName())) { + Log.severe("Duplicate perspective name '" + perspective.getName() + "' - perspective ignored"); + } + perspectives.put(perspective.getName(), perspective); + } + Log.info("Loaded " + perspectives.size() + " perspectives."); + } +} diff --git a/src/main/java/org/dynmap/hdmap/HDPerspective.java b/src/main/java/org/dynmap/hdmap/HDPerspective.java new file mode 100644 index 00000000..deebe50b --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDPerspective.java @@ -0,0 +1,6 @@ +package org.dynmap.hdmap; + +public interface HDPerspective { + /* Get name of perspective */ + String getName(); +} From 69baafe5974dec75adc02862e29cf5101336eb7a Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 8 Jul 2011 22:40:40 -0500 Subject: [PATCH 09/14] Refactor HDMap configuration - add shaders, perspectives --- perspectives.txt | 307 ++++++++ src/main/java/org/dynmap/MapManager.java | 39 +- src/main/java/org/dynmap/MapTile.java | 27 +- src/main/java/org/dynmap/MapType.java | 7 - src/main/java/org/dynmap/flat/FlatMap.java | 30 +- .../org/dynmap/hdmap/DefaultHDShader.java | 94 +-- src/main/java/org/dynmap/hdmap/HDMap.java | 711 ++---------------- .../java/org/dynmap/hdmap/HDMapManager.java | 81 ++ src/main/java/org/dynmap/hdmap/HDMapTile.java | 53 +- .../java/org/dynmap/hdmap/HDPerspective.java | 23 + .../org/dynmap/hdmap/HDPerspectiveState.java | 11 +- src/main/java/org/dynmap/hdmap/HDShader.java | 10 +- .../java/org/dynmap/hdmap/HDShaderState.java | 8 + .../org/dynmap/hdmap/IsoHDPerspective.java | 676 +++++++++++++++++ .../dynmap/kzedmap/DefaultTileRenderer.java | 3 +- src/main/java/org/dynmap/kzedmap/KzedMap.java | 4 - .../java/org/dynmap/kzedmap/KzedMapTile.java | 20 +- .../org/dynmap/kzedmap/KzedZoomedMapTile.java | 23 +- src/main/java/org/dynmap/utils/Matrix3D.java | 17 + 19 files changed, 1375 insertions(+), 769 deletions(-) create mode 100644 src/main/java/org/dynmap/hdmap/IsoHDPerspective.java diff --git a/perspectives.txt b/perspectives.txt index 22c4c82c..b7d96270 100644 --- a/perspectives.txt +++ b/perspectives.txt @@ -1 +1,308 @@ perspectives: + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_S_60_lowres + azimuth: 180 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_S_60_medres + azimuth: 180 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_S_60_hires + azimuth: 180 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SE_60_lowres + azimuth: 135 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SE_60_medres + azimuth: 135 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SE_60_hires + azimuth: 135 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_E_60_lowres + azimuth: 90 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_E_60_medres + azimuth: 90 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_E_60_hires + azimuth: 90 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NE_60_lowres + azimuth: 45 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NE_60_medres + azimuth: 45 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NE_60_hires + azimuth: 45 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_60_lowres + azimuth: 0 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_60_medres + azimuth: 0 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_60_hires + azimuth: 0 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NW_60_lowres + azimuth: 315 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NW_60_medres + azimuth: 315 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NW_60_hires + azimuth: 315 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_W_60_lowres + azimuth: 270 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_W_60_medres + azimuth: 270 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_W_60_hires + azimuth: 270 + inclination: 60 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SW_60_lowres + azimuth: 225 + inclination: 60 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SW_60_medres + azimuth: 225 + inclination: 60 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SW_60_hires + azimuth: 225 + inclination: 60 + scale: 16 + +# Low angle perspectives (30 degrees) + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_S_30_lowres + azimuth: 180 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_S_30_medres + azimuth: 180 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_S_30_hires + azimuth: 180 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SE_30_lowres + azimuth: 135 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SE_30_medres + azimuth: 135 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SE_30_hires + azimuth: 135 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_E_30_lowres + azimuth: 90 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_E_30_medres + azimuth: 90 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_E_30_hires + azimuth: 90 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NE_30_lowres + azimuth: 45 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NE_30_medres + azimuth: 45 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NE_30_hires + azimuth: 45 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_30_lowres + azimuth: 0 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_30_medres + azimuth: 0 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_30_hires + azimuth: 0 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NW_30_lowres + azimuth: 315 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NW_30_medres + azimuth: 315 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_NW_30_hires + azimuth: 315 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_W_30_lowres + azimuth: 270 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_W_30_medres + azimuth: 270 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_W_30_hires + azimuth: 270 + inclination: 30 + scale: 16 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SW_30_lowres + azimuth: 225 + inclination: 30 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SW_30_medres + azimuth: 225 + inclination: 30 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_SW_30_hires + azimuth: 225 + inclination: 30 + scale: 16 + +# Vertical perspectives (90 deg) + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_90_lowres + azimuth: 0 + inclination: 90 + scale: 4 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_90_medres + azimuth: 0 + inclination: 90 + scale: 8 + + - class: org.dynmap.hdmap.IsoHDPerspective + name: iso_N_90_hires + azimuth: 0 + inclination: 90 + scale: 16 diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 556a1b8a..d4fb68a1 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -22,7 +22,6 @@ import org.bukkit.command.CommandSender; import org.dynmap.DynmapWorld.AutoGenerateOption; import org.dynmap.debug.Debug; import org.dynmap.hdmap.HDMapManager; -import org.dynmap.hdmap.HDShader; import org.dynmap.utils.LegacyMapChunkCache; import org.dynmap.utils.MapChunkCache; import org.dynmap.utils.NewMapChunkCache; @@ -102,12 +101,11 @@ public class MapManager { } @Override public void execute(final Runnable r) { - final Runnable rr = r; try { super.execute(new Runnable() { public void run() { try { - r.run(); + r.run(); } catch (Exception x) { Log.severe("Exception during render job: " + r); x.printStackTrace(); @@ -225,21 +223,20 @@ public class MapManager { } World w = world.world; /* Fetch chunk cache from server thread */ - MapType mt = tile.getMap(); - List requiredChunks = mt.getRequiredChunks(tile); - MapChunkCache cache = createMapChunkCache(world, requiredChunks, mt.isBlockTypeDataNeeded(), - mt.isHightestBlockYDataNeeded(), mt.isBiomeDataNeeded(), - mt.isRawBiomeDataNeeded()); + List requiredChunks = tile.getRequiredChunks(); + MapChunkCache cache = createMapChunkCache(world, requiredChunks, tile.isBlockTypeDataNeeded(), + tile.isHightestBlockYDataNeeded(), tile.isBiomeDataNeeded(), + tile.isRawBiomeDataNeeded()); if(cache == null) { cleanup(); return; /* Cancelled/aborted */ } if(tile0 != null) { /* Single tile? */ if(cache.isEmpty() == false) - render(cache, tile); /* Just render */ + tile.render(cache); } else { - if ((cache.isEmpty() == false) && render(cache, tile)) { + if ((cache.isEmpty() == false) && tile.render(cache)) { found.remove(tile); rendered.add(tile); for (MapTile adjTile : map.getAdjecentTiles(tile)) { @@ -389,21 +386,14 @@ public class MapManager { return; } String worldName = w.getName(); - - Event.Listener invalitateListener = new Event.Listener() { - @Override - public void triggered(MapTile t) { - invalidateTile(t); - } - }; - + DynmapWorld dynmapWorld = new DynmapWorld(); dynmapWorld.world = w; dynmapWorld.configuration = worldConfiguration; Log.verboseinfo("Loading maps of world '" + worldName + "'..."); for(MapType map : worldConfiguration.createInstances("maps", new Class[0], new Object[0])) { - map.onTileInvalidated.addListener(invalitateListener); - dynmapWorld.maps.add(map); + if(map.getName() != null) + dynmapWorld.maps.add(map); } Log.info("Loaded " + dynmapWorld.maps.size() + " maps of world '" + worldName + "'."); @@ -414,7 +404,7 @@ public class MapManager { dynmapWorld.sendhealth = worldConfiguration.getBoolean("sendhealth", true); dynmapWorld.bigworld = worldConfiguration.getBoolean("bigworld", false); dynmapWorld.setExtraZoomOutLevels(worldConfiguration.getInteger("extrazoomout", 0)); - dynmapWorld.worldtilepath = new File(plug_in.tilesDirectory, w.getName()); + dynmapWorld.worldtilepath = new File(DynmapPlugin.tilesDirectory, w.getName()); if(loclist != null) { for(ConfigurationNode loc : loclist) { Location lx = new Location(w, loc.getDouble("x", 0), loc.getDouble("y", 64), loc.getDouble("z", 0)); @@ -516,13 +506,6 @@ public class MapManager { tileQueue.stop(); } - public boolean render(MapChunkCache cache, MapTile tile) { - boolean result = tile.getMap().render(cache, tile, getTileFile(tile)); - //Do update after async file write - - return result; - } - private HashMap worldTileDirectories = new HashMap(); public File getTileFile(MapTile tile) { World world = tile.getWorld(); diff --git a/src/main/java/org/dynmap/MapTile.java b/src/main/java/org/dynmap/MapTile.java index 04f63aea..1b7f6ee3 100644 --- a/src/main/java/org/dynmap/MapTile.java +++ b/src/main/java/org/dynmap/MapTile.java @@ -1,10 +1,18 @@ package org.dynmap; +import java.io.File; +import java.util.List; + import org.bukkit.World; +import org.dynmap.kzedmap.MapTileRenderer; +import org.dynmap.utils.MapChunkCache; public abstract class MapTile { protected DynmapWorld world; - private MapType map; + + public abstract boolean render(MapChunkCache cache); + public abstract List getRequiredChunks(); + public abstract MapTile[] getAdjecentTiles(); public World getWorld() { return world.world; @@ -14,17 +22,12 @@ public abstract class MapTile { return world; } - public MapType getMap() { - return map; - } - public abstract String getFilename(); public abstract String getDayFilename(); - public MapTile(DynmapWorld world, MapType map) { + public MapTile(DynmapWorld world) { this.world = world; - this.map = map; } @Override @@ -41,7 +44,11 @@ public abstract class MapTile { return super.equals(obj); } - public String getKey() { - return world.world.getName() + "." + map.getName(); - } + public abstract String getKey(); + + public boolean isBiomeDataNeeded() { return false; } + public boolean isHightestBlockYDataNeeded() { return false; } + public boolean isRawBiomeDataNeeded() { return false; } + public boolean isBlockTypeDataNeeded() { return true; } + } diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index b83f7551..74c1057d 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -8,8 +8,6 @@ import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; public abstract class MapType { - public Event onTileInvalidated = new Event(); - public abstract MapTile[] getTiles(Location l); public abstract MapTile[] getAdjecentTiles(MapTile tile); @@ -23,11 +21,6 @@ public abstract class MapType { public abstract String getName(); - public boolean isBiomeDataNeeded() { return false; } - public boolean isHightestBlockYDataNeeded() { return false; } - public boolean isRawBiomeDataNeeded() { return false; } - public boolean isBlockTypeDataNeeded() { return true; } - public enum MapStep { X_PLUS_Y_PLUS, X_PLUS_Y_MINUS, diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index a536c43d..3da0ce08 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -118,11 +118,6 @@ public class FlatMap extends MapType { return result; } - @Override - public boolean isHightestBlockYDataNeeded() { - return true; - } - @Override public boolean render(MapChunkCache cache, MapTile tile, File outputFile) { FlatMapTile t = (FlatMapTile) tile; @@ -443,7 +438,7 @@ public class FlatMap extends MapType { private String fname_day; public FlatMapTile(DynmapWorld world, FlatMap map, int x, int y, int size) { - super(world, map); + super(world); this.map = map; this.x = x; this.y = y; @@ -473,6 +468,29 @@ public class FlatMap extends MapType { public String toString() { return getWorld().getName() + ":" + getFilename(); } + + @Override + public boolean render(MapChunkCache cache) { + return map.render(cache, this, MapManager.mapman.getTileFile(this)); + } + + @Override + public List getRequiredChunks() { + return map.getRequiredChunks(this); + } + + @Override + public MapTile[] getAdjecentTiles() { + return map.getAdjecentTiles(this); + } + + @Override + public String getKey() { + return world.world.getName() + "." + map.getName(); + } + + public boolean isHightestBlockYDataNeeded() { return true; } + } @Override diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java index 0a28fc46..ca4a5067 100644 --- a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -1,26 +1,16 @@ 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.dynmap.utils.Vector3D; import org.json.simple.JSONObject; public class DefaultHDShader implements HDShader { - private ConfigurationNode configuration; private String name; protected ColorScheme colorScheme; @@ -37,7 +27,6 @@ public class DefaultHDShader implements HDShader { protected BiomeColorOption biomecolored = BiomeColorOption.NONE; /* Use biome for coloring */ public DefaultHDShader(ConfigurationNode configuration) { - this.configuration = configuration; name = (String) configuration.get("name"); double shadowweight = configuration.getDouble("shadowstrength", 0.0); if(shadowweight > 0.0) { @@ -84,6 +73,8 @@ public class DefaultHDShader implements HDShader { public boolean isNightAndDayEnabled() { return night_and_day; } public boolean isSkyLightLevelNeeded() { return (lightscale != null); } public boolean isEmittedLightLevelNeeded() { return (shadowscale != null); } + public boolean isHightestBlockYDataNeeded() { return false; } + public boolean isBlockTypeDataNeeded() { return true; } public String getName() { return name; } @@ -91,16 +82,31 @@ public class DefaultHDShader implements HDShader { private Color color = new Color(); private Color daycolor; protected MapIterator mapiter; + protected HDMap map; private Color tmpcolor = new Color(); private Color tmpdaycolor = new Color(); private int pixelodd; - private OurRendererState(MapIterator mapiter) { + private OurRendererState(MapIterator mapiter, HDMap map) { this.mapiter = mapiter; + this.map = map; if(night_and_day) { daycolor = new Color(); } } + /** + * Get our shader + */ + public HDShader getShader() { + return DefaultHDShader.this; + } + + /** + * Get our map + */ + public HDMap getMap() { + return map; + } /** * Reset renderer state for new ray */ @@ -131,16 +137,22 @@ public class DefaultHDShader implements HDShader { if (colors != null) { int seq; /* Figure out which color to use */ - HDMap.BlockStep laststep = ps.getLastBlockStep(); - if((laststep == BlockStep.X_PLUS) || (laststep == BlockStep.X_MINUS)) - seq = 1; - else if((laststep == BlockStep.Z_PLUS) || (laststep == BlockStep.Z_MINUS)) - seq = 3; - else if(((pixelodd + mapiter.getY()) & 0x03) == 0) - seq = 2; - else - seq = 0; - + switch(ps.getLastBlockStep()) { + case X_PLUS: + case X_MINUS: + seq = 1; + break; + case Z_PLUS: + case Z_MINUS: + seq = 3; + break; + default: + if(((pixelodd + mapiter.getY()) & 0x03) == 0) + seq = 2; + else + seq = 0; + break; + } Color c = colors[seq]; if (c.getAlpha() > 0) { /* Handle light level, if needed */ @@ -234,8 +246,8 @@ public class DefaultHDShader implements HDShader { } private class OurBiomeRendererState extends OurRendererState { - private OurBiomeRendererState(MapIterator mapiter) { - super(mapiter); + private OurBiomeRendererState(MapIterator mapiter, HDMap map) { + super(mapiter, map); } protected Color[] getBlockColors(int blocktype, int blockdata) { Biome bio = mapiter.getBiome(); @@ -246,8 +258,8 @@ public class DefaultHDShader implements HDShader { } private class OurBiomeRainfallRendererState extends OurRendererState { - private OurBiomeRainfallRendererState(MapIterator mapiter) { - super(mapiter); + private OurBiomeRainfallRendererState(MapIterator mapiter, HDMap map) { + super(mapiter, map); } protected Color[] getBlockColors(int blocktype, int blockdata) { return colorScheme.getRainColor(mapiter.getRawBiomeRainfall()); @@ -255,8 +267,8 @@ public class DefaultHDShader implements HDShader { } private class OurBiomeTempRendererState extends OurRendererState { - private OurBiomeTempRendererState(MapIterator mapiter) { - super(mapiter); + private OurBiomeTempRendererState(MapIterator mapiter, HDMap map) { + super(mapiter, map); } protected Color[] getBlockColors(int blocktype, int blockdata) { return colorScheme.getTempColor(mapiter.getRawBiomeTemperature()); @@ -272,30 +284,20 @@ public class DefaultHDShader implements HDShader { public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter) { switch(biomecolored) { case NONE: - return new OurRendererState(mapiter); + return new OurRendererState(mapiter, map); case BIOME: - return new OurBiomeRendererState(mapiter); + return new OurBiomeRendererState(mapiter, map); case RAINFALL: - return new OurBiomeRainfallRendererState(mapiter); + return new OurBiomeRainfallRendererState(mapiter, map); case TEMPERATURE: - return new OurBiomeTempRendererState(mapiter); + return new OurBiomeTempRendererState(mapiter, map); } 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")); - s(o, "background", c.getString("background")); - s(o, "nightandday", c.getBoolean("night-and-day", false)); - s(o, "backgroundday", c.getString("backgroundday")); - s(o, "backgroundnight", c.getString("backgroundnight")); - a(worldObject, "maps", o); + /* Add shader's contributions to JSON for map object */ + public void addClientConfiguration(JSONObject mapObject) { + s(mapObject, "shader", name); + s(mapObject, "nightandday", night_and_day); } } diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index bf10b784..1113b448 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -1,684 +1,84 @@ package org.dynmap.hdmap; -import org.dynmap.DynmapWorld; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; +import static org.dynmap.JSONUtils.a; +import static org.dynmap.JSONUtils.s; +import java.io.File; +import java.util.ArrayList; +import java.util.List; import org.bukkit.Location; -import org.bukkit.World; -import org.dynmap.Client; -import org.dynmap.Color; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapChunk; import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; -import org.dynmap.TileHashManager; -import org.dynmap.debug.Debug; -import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; -import org.dynmap.kzedmap.KzedMap; -import org.dynmap.utils.FileLockManager; import org.dynmap.utils.MapChunkCache; -import org.dynmap.utils.MapIterator; -import org.dynmap.utils.Matrix3D; -import org.dynmap.utils.Vector3D; import org.json.simple.JSONObject; public class HDMap extends MapType { - /* View angles */ - 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 */ - /* 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 - * space contributing to the tile have consistent tile-space X,Y coordinate pairs for both the top and bottom faces - * Note that this is a classic right-hand coordinate system, while minecraft's world coordinates are left handed - * (X+ is south, Y+ is up, Z+ is east). - */ - /* Transformation matrix for taking coordinate in world-space (x, y, z) and finding coordinate in tile space (x, y, z) */ - private Matrix3D world_to_map; - private Matrix3D map_to_world; - - /* dimensions of a map tile */ - public static final int tileWidth = 128; - public static final int tileHeight = 128; - - /* Maximum and minimum inclinations */ - public static final double MAX_INCLINATION = 90.0; - public static final double MIN_INCLINATION = 20.0; - - /* Maximum and minimum scale */ - public static final double MAX_SCALE = 64; - public static final double MIN_SCALE = 1; - - private HDShader shaders[]; - private boolean need_skylightlevel = false; - private boolean need_emittedlightlevel = false; - private boolean need_biomedata = false; - private boolean need_rawbiomedata = false; - - private class OurPerspectiveState implements HDPerspectiveState { - int skylightlevel = 15; - int emittedlightlevel = 0; - int blocktypeid = 0; - int blockdata = 0; - Vector3D top, bottom; - int px, py; - BlockStep laststep = BlockStep.Y_MINUS; - /** - * Get sky light level - only available if shader requested it - */ - public final int getSkyLightLevel() { return skylightlevel; } - /** - * Get emitted light level - only available if shader requested it - */ - public final int getEmittedLightLevel() { return emittedlightlevel; } - /** - * Get current block type ID - */ - public final int getBlockTypeID() { return blocktypeid; } - /** - * Get current block data - */ - public final int getBlockData() { return blockdata; } - /** - * Get direction of last block step - */ - public final BlockStep getLastBlockStep() { return laststep; } - /** - * Get perspective scale - */ - public final double getScale() { return scale; } - /** - * Get start of current ray, in world coordinates - */ - public final Vector3D getRayStart() { return top; } - /** - * Get end of current ray, in world coordinates - */ - public final Vector3D getRayEnd() { return bottom; } - /** - * Get pixel X coordinate - */ - public final int getPixelX() { return px; } - /** - * Get pixel Y coordinate - */ - public final int getPixelY() { return py; } - - } + private String name; + private String prefix; + private HDPerspective perspective; + private HDShader shader; + private ConfigurationNode configuration; public HDMap(ConfigurationNode configuration) { - 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; - if(inclination < MIN_INCLINATION) inclination = MIN_INCLINATION; - scale = configuration.getDouble("scale", MIN_SCALE); - if(scale < MIN_SCALE) scale = MIN_SCALE; - if(scale > MAX_SCALE) scale = MAX_SCALE; - Log.info("azimuth=" + azimuth + ", inclination=" + inclination + ", scale=" + scale); - - /* Generate transform matrix for world-to-tile coordinate mapping */ - /* First, need to fix basic coordinate mismatches before rotation - we want zero azimuth to have north to top - * (world -X -> tile +Y) and east to right (world -Z to tile +X), with height being up (world +Y -> tile +Z) - */ - Matrix3D transform = new Matrix3D(0.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0); - /* Next, rotate world counterclockwise around Z axis by azumuth angle */ - transform.rotateXY(180-azimuth); - /* Next, rotate world by (90-inclination) degrees clockwise around +X axis */ - transform.rotateYZ(90.0-inclination); - /* Finally, shear along Z axis to normalize Z to be height above map plane */ - transform.shearZ(0, Math.tan(Math.toRadians(90.0-inclination))); - /* And scale Z to be same scale as world coordinates, and scale X and Y based on setting */ - transform.scale(scale, scale, Math.sin(Math.toRadians(inclination))); - world_to_map = transform; - /* Now, generate map to world tranform, by doing opposite actions in reverse order */ - transform = new Matrix3D(); - transform.scale(1.0/scale, 1.0/scale, 1/Math.sin(Math.toRadians(inclination))); - transform.shearZ(0, -Math.tan(Math.toRadians(90.0-inclination))); - transform.rotateYZ(-(90.0-inclination)); - transform.rotateXY(-180+azimuth); - Matrix3D coordswap = new Matrix3D(0.0, -1.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0); - transform.multiply(coordswap); - map_to_world = transform; - - 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; + name = configuration.getString("name", null); + if(name == null) { + Log.severe("HDMap missing required attribute 'name' - disabled"); + return; } + String perspectiveid = configuration.getString("perspective", "default"); + perspective = MapManager.mapman.hdmapman.perspectives.get(perspectiveid); + if(perspective == null) { + Log.severe("HDMap '"+name+"' loading invalid perspective '" + perspectiveid + "' - map disabled"); + name = null; + return; + } + String shaderid = configuration.getString("shader", "default"); + shader = MapManager.mapman.hdmapman.shaders.get(shaderid); + if(shader == null) { + Log.severe("HDMap '"+name+"' loading invalid shader '" + shaderid + "' - map disabled"); + name = null; + return; + } + prefix = configuration.getString("prefix", name); + this.configuration = configuration; } + public HDShader getShader() { return shader; } + public HDPerspective getPerspective() { return perspective; } + @Override public MapTile[] getTiles(Location loc) { - DynmapWorld world = MapManager.mapman.getWorld(loc.getWorld().getName()); - HashSet tiles = new HashSet(); - Vector3D block = new Vector3D(); - block.setFromLocation(loc); /* Get coordinate for block */ - Vector3D corner = new Vector3D(); - /* Loop through corners of the cube */ - for(int i = 0; i < 2; i++) { - double inity = block.y; - for(int j = 0; j < 2; j++) { - double initz = block.z; - for(int k = 0; k < 2; k++) { - world_to_map.transform(block, corner); /* Get map coordinate of corner */ - addTile(tiles, world, (int)Math.floor(corner.x/tileWidth), (int)Math.floor(corner.y/tileHeight)); - - block.z += 1; - } - block.z = initz; - block.y += 1; - } - block.y = inity; - block.x += 1; - } - MapTile[] result = tiles.toArray(new MapTile[tiles.size()]); - Log.info("processed update for " + loc); - for(MapTile mt : result) - Log.info("need to render " + mt); - return result; + return perspective.getTiles(loc); } @Override public MapTile[] getAdjecentTiles(MapTile tile) { - HDMapTile t = (HDMapTile) tile; - DynmapWorld w = t.getDynmapWorld(); - int x = t.tx; - int y = t.ty; - return new MapTile[] { - 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) { - tiles.add(new HDMapTile(world, this, tx, ty)); - } - - public void invalidateTile(MapTile tile) { - } - - private static class Rectangle { - double r0x, r0z; /* Coord of corner of rectangle */ - double s1x, s1z; /* Side vector for one edge */ - double s2x, s2z; /* Side vector for other edge */ - public Rectangle(Vector3D v1, Vector3D v2, Vector3D v3) { - r0x = v1.x; - r0z = v1.z; - s1x = v2.x - v1.x; - s1z = v2.z - v1.z; - s2x = v3.x - v1.x; - s2z = v3.z - v1.z; - } - public Rectangle() { - } - public void setSquare(double rx, double rz, double s) { - this.r0x = rx; - this.r0z = rz; - this.s1x = s; - this.s1z = 0; - this.s2x = 0; - this.s2z = s; - } - double getX(int idx) { - return r0x + (((idx & 1) == 0)?0:s1x) + (((idx & 2) != 0)?0:s2x); - } - double getZ(int idx) { - return r0z + (((idx & 1) == 0)?0:s1z) + (((idx & 2) != 0)?0:s2z); - } - /** - * Test for overlap of projection of one vector on to anoter - */ - boolean testoverlap(double rx, double rz, double sx, double sz, Rectangle r) { - double rmin_dot_s0 = Double.MAX_VALUE; - double rmax_dot_s0 = Double.MIN_VALUE; - /* Project each point from rectangle on to vector: find lowest and highest */ - for(int i = 0; i < 4; i++) { - double r_x = r.getX(i) - rx; /* Get relative positon of second vector start to origin */ - double r_z = r.getZ(i) - rz; - double r_dot_s0 = r_x*sx + r_z*sz; /* Projection of start of vector */ - if(r_dot_s0 < rmin_dot_s0) rmin_dot_s0 = r_dot_s0; - if(r_dot_s0 > rmax_dot_s0) rmax_dot_s0 = r_dot_s0; - } - /* Compute dot products */ - double s0_dot_s0 = sx*sx + sz*sz; /* End of our side */ - if((rmax_dot_s0 < 0.0) || (rmin_dot_s0 > s0_dot_s0)) - return false; - else - return true; - } - /** - * Test if two rectangles intersect - * Based on separating axis theorem - */ - boolean testRectangleIntesectsRectangle(Rectangle r) { - /* Test if projection of each edge of one rectangle on to each edge of the other yields overlap */ - if(testoverlap(r0x, r0z, s1x, s1z, r) && testoverlap(r0x, r0z, s2x, s2z, r) && - testoverlap(r0x+s1x, r0z+s1z, s2x, s2z, r) && testoverlap(r0x+s2x, r0z+s2z, s1x, s1z, r) && - r.testoverlap(r.r0x, r.r0z, r.s1x, r.s1z, this) && r.testoverlap(r.r0x, r.r0z, r.s2x, r.s2z, this) && - r.testoverlap(r.r0x+r.s1x, r.r0z+r.s1z, r.s2x, r.s2z, this) && r.testoverlap(r.r0x+r.s2x, r.r0z+r.s2z, r.s1x, r.s1z, this)) { - return true; - } - else { - return false; - } - } - public String toString() { - return "{ " + r0x + "," + r0z + "}x{" + (r0x+s1x) + ","+ + (r0z+s1z) + "}x{" + (r0x+s2x) + "," + (r0z+s2z) + "}"; - } + return perspective.getAdjecentTiles(tile); } @Override public List getRequiredChunks(MapTile tile) { - if (!(tile instanceof HDMapTile)) - return Collections.emptyList(); - - HDMapTile t = (HDMapTile) tile; - int min_chunk_x = Integer.MAX_VALUE; - int max_chunk_x = Integer.MIN_VALUE; - int min_chunk_z = Integer.MAX_VALUE; - int max_chunk_z = Integer.MIN_VALUE; - - /* Make corners for volume: 0 = bottom-lower-left, 1 = top-lower-left, 2=bottom-upper-left, 3=top-upper-left - * 4 = bottom-lower-right, 5 = top-lower-right, 6 = bottom-upper-right, 7 = top-upper-right */ - Vector3D corners[] = new Vector3D[8]; - int[] chunk_x = new int[8]; - int[] chunk_z = new int[8]; - for(int x = t.tx, idx = 0; x <= (t.tx+1); x++) { - for(int y = t.ty; y <= (t.ty+1); y++) { - for(int z = 0; z <= 1; z++) { - corners[idx] = new Vector3D(); - corners[idx].x = x*tileWidth; corners[idx].y = y*tileHeight; corners[idx].z = z*128; - map_to_world.transform(corners[idx]); - /* Compute chunk coordinates of corner */ - chunk_x[idx] = (int)Math.floor(corners[idx].x / 16); - chunk_z[idx] = (int)Math.floor(corners[idx].z / 16); - /* Compute min/max of chunk coordinates */ - if(min_chunk_x > chunk_x[idx]) min_chunk_x = chunk_x[idx]; - if(max_chunk_x < chunk_x[idx]) max_chunk_x = chunk_x[idx]; - if(min_chunk_z > chunk_z[idx]) min_chunk_z = chunk_z[idx]; - if(max_chunk_z < chunk_z[idx]) max_chunk_z = chunk_z[idx]; - idx++; - } - } - } - /* Make rectangles of X-Z projection of each side of the tile volume, 0 = top, 1 = bottom, 2 = left, 3 = right, - * 4 = upper, 5 = lower */ - Rectangle rect[] = new Rectangle[6]; - rect[0] = new Rectangle(corners[1], corners[3], corners[5]); - rect[1] = new Rectangle(corners[0], corners[2], corners[4]); - rect[2] = new Rectangle(corners[0], corners[1], corners[2]); - rect[3] = new Rectangle(corners[4], corners[5], corners[6]); - rect[4] = new Rectangle(corners[2], corners[3], corners[6]); - rect[5] = new Rectangle(corners[0], corners[1], corners[4]); - - /* Now, need to walk through the min/max range to see which chunks are actually needed */ - ArrayList chunks = new ArrayList(); - Rectangle chunkrect = new Rectangle(); - int misscnt = 0; - for(int x = min_chunk_x; x <= max_chunk_x; x++) { - for(int z = min_chunk_z; z <= max_chunk_z; z++) { - chunkrect.setSquare(x*16, z*16, 16); - boolean hit = false; - /* Check to see if square of chunk intersects any of our rectangle sides */ - for(int rctidx = 0; (!hit) && (rctidx < rect.length); rctidx++) { - if(chunkrect.testRectangleIntesectsRectangle(rect[rctidx])) { - hit = true; - } - } - if(hit) { - DynmapChunk chunk = new DynmapChunk(x, z); - chunks.add(chunk); - } - else { - misscnt++; - } - } - } - return chunks; + return perspective.getRequiredChunks(tile); } @Override public boolean render(MapChunkCache cache, MapTile tile, File bogus) { - HDMapTile t = (HDMapTile) tile; - World w = t.getWorld(); - Color rslt = new Color(); - 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 perspective state object */ - OurPerspectiveState ps = new OurPerspectiveState(); - - /* 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; - } - } - - ps.top = new Vector3D(); - ps.bottom = new Vector3D(); - double xbase = t.tx * tileWidth; - double ybase = t.ty * tileHeight; - boolean shaderdone[] = new boolean[shaders.length]; - boolean rendered[] = new boolean[shaders.length]; - for(int x = 0; x < tileWidth; x++) { - ps.px = x; - for(int y = 0; y < tileHeight; y++) { - ps.top.x = ps.bottom.x = xbase + x + 0.5; /* Start at center of pixel at Y=127.5, bottom at Y=-0.5 */ - ps.top.y = ps.bottom.y = ybase + y + 0.5; - ps.top.z = 127.5; ps.bottom.z = -0.5; - map_to_world.transform(ps.top); /* Transform to world coordinates */ - map_to_world.transform(ps.bottom); - ps.py = y; - for(int i = 0; i < shaders.length; i++) { - shaderstate[i].reset(ps); - } - raytrace(cache, mapiter, ps, shaderstate, shaderdone); - for(int i = 0; i < shaders.length; i++) { - if(shaderdone[i] == false) { - shaderstate[i].rayFinished(ps); - } - 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(); - } - } - } - } - - boolean renderone = false; - /* Test to see if we're unchanged from older tile */ - TileHashManager hashman = MapManager.mapman.hashman; - 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]) { - renderone = true; - String fname = t.getFilename(shadername); - File f = new File(t.getDynmapWorld().worldtilepath, fname); - FileLockManager.getWriteLock(f); - try { - 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(fname)); - 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); - KzedMap.freeBufferedImage(im[i]); - } - MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); - /* Handle day image, if needed */ - if(dayim[i] != null) { - fname = t.getDayFilename(shadername); - f = new File(t.getDynmapWorld().worldtilepath, fname); - FileLockManager.getWriteLock(f); - shadername = shadername+"_day"; - tile_update = false; - try { - 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(dayim[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(fname)); - 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); - KzedMap.freeBufferedImage(dayim[i]); - } - MapManager.mapman.updateStatistics(tile, shadername, true, tile_update, !rendered[i]); - } - } - } - return renderone; + if(tile instanceof HDMapTile) + return perspective.render(cache, (HDMapTile)tile); + else + return false; } - - /** - * Trace ray, based on "Voxel Tranversal along a 3D line" - */ - private void raytrace(MapChunkCache cache, MapIterator mapiter, OurPerspectiveState ps, - HDShaderState[] shaderstate, boolean[] shaderdone) { - Vector3D top = ps.top; - Vector3D bottom = ps.bottom; - /* Compute total delta on each axis */ - double dx = Math.abs(bottom.x - top.x); - double dy = Math.abs(bottom.y - top.y); - double dz = Math.abs(bottom.z - top.z); - /* Initial block coord */ - int x = (int) (Math.floor(top.x)); - int y = (int) (Math.floor(top.y)); - int z = (int) (Math.floor(top.z)); - /* Compute parametric step (dt) per step on each axis */ - double dt_dx = 1.0 / dx; - double dt_dy = 1.0 / dy; - double dt_dz = 1.0 / dz; - /* Initialize parametric value to 0 (and we're stepping towards 1) */ - double t = 0; - /* Compute number of steps and increments for each */ - int n = 1; - int x_inc, y_inc, z_inc; - - double t_next_y, t_next_x, t_next_z; - /* If perpendicular to X axis */ - if (dx == 0) { - x_inc = 0; - t_next_x = Double.MAX_VALUE; - } - /* If bottom is right of top */ - else if (bottom.x > top.x) { - x_inc = 1; - n += (int) (Math.floor(bottom.x)) - x; - t_next_x = (Math.floor(top.x) + 1 - top.x) * dt_dx; - } - /* Top is right of bottom */ - else { - x_inc = -1; - n += x - (int) (Math.floor(bottom.x)); - t_next_x = (top.x - Math.floor(top.x)) * dt_dx; - } - /* If perpendicular to Y axis */ - if (dy == 0) { - y_inc = 0; - t_next_y = Double.MAX_VALUE; - } - /* If bottom is above top */ - else if (bottom.y > top.y) { - y_inc = 1; - n += (int) (Math.floor(bottom.y)) - y; - t_next_y = (Math.floor(top.y) + 1 - top.y) * dt_dy; - } - /* If top is above bottom */ - else { - y_inc = -1; - n += y - (int) (Math.floor(bottom.y)); - t_next_y = (top.y - Math.floor(top.y)) * dt_dy; - } - /* If perpendicular to Z axis */ - if (dz == 0) { - z_inc = 0; - t_next_z = Double.MAX_VALUE; - } - /* If bottom right of top */ - else if (bottom.z > top.z) { - z_inc = 1; - n += (int) (Math.floor(bottom.z)) - z; - t_next_z = (Math.floor(top.z) + 1 - top.z) * dt_dz; - } - /* If bottom left of top */ - else { - z_inc = -1; - n += z - (int) (Math.floor(bottom.z)); - t_next_z = (top.z - Math.floor(top.z)) * dt_dz; - } - /* Walk through scene */ - ps.laststep = BlockStep.Y_MINUS; /* Last step is down into map */ - mapiter.initialize(x, y, z); - ps.skylightlevel = 15; - ps.emittedlightlevel = 0; - for (; n > 0; --n) { - ps.blocktypeid = mapiter.getBlockTypeID(); - if(ps.blocktypeid != 0) { - ps.blockdata = mapiter.getBlockData(); - boolean done = true; - for(int i = 0; i < shaderstate.length; i++) { - if(!shaderdone[i]) - shaderdone[i] = shaderstate[i].processBlock(ps); - done = done && shaderdone[i]; - } - /* If all are done, we're out */ - if(done) - return; - } - if(need_skylightlevel) - ps.skylightlevel = mapiter.getBlockSkyLight(); - if(need_emittedlightlevel) - ps.emittedlightlevel = mapiter.getBlockEmittedLight(); - /* If X step is next best */ - if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) { - x += x_inc; - t = t_next_x; - t_next_x += dt_dx; - if(x_inc > 0) { - ps.laststep = BlockStep.X_PLUS; - mapiter.incrementX(); - } - else { - ps.laststep = BlockStep.X_MINUS; - mapiter.decrementX(); - } - } - /* If Y step is next best */ - else if((t_next_y <= t_next_x) && (t_next_y <= t_next_z)) { - y += y_inc; - t = t_next_y; - t_next_y += dt_dy; - if(y_inc > 0) { - ps.laststep = BlockStep.Y_PLUS; - mapiter.incrementY(); - if(mapiter.getY() > 127) - return; - } - else { - ps.laststep = BlockStep.Y_MINUS; - mapiter.decrementY(); - if(mapiter.getY() < 0) - return; - } - } - /* Else, Z step is next best */ - else { - z += z_inc; - t = t_next_z; - t_next_z += dt_dz; - if(z_inc > 0) { - ps.laststep = BlockStep.Z_PLUS; - mapiter.incrementZ(); - } - else { - ps.laststep = BlockStep.Z_MINUS; - mapiter.decrementZ(); - } - } - } - } - - @Override - public boolean isBiomeDataNeeded() { - return need_biomedata; - } - - @Override - public boolean isRawBiomeDataNeeded() { - return need_rawbiomedata; - } @Override public List baseZoomFilePrefixes() { ArrayList s = new ArrayList(); - for(HDShader r : shaders) { - s.add(r.getName()); - if(r.isNightAndDayEnabled()) - s.add(r.getName() + "_day"); - } + s.add(prefix); + if(shader.isNightAndDayEnabled()) + s.add(prefix + "_day"); return s; } @@ -695,13 +95,30 @@ public class HDMap extends MapType { @Override public String getName() { - return "HDMap"; + return name; + } + + public String getPrefix() { + return prefix; } @Override public void buildClientConfiguration(JSONObject worldObject) { - for(HDShader shader : shaders) { - shader.buildClientConfiguration(worldObject); - } + ConfigurationNode c = configuration; + JSONObject o = new JSONObject(); + s(o, "type", "HDMapType"); + s(o, "name", name); + s(o, "title", c.getString("title")); + s(o, "icon", c.getString("icon")); + s(o, "prefix", prefix); + s(o, "background", c.getString("background")); + s(o, "backgroundday", c.getString("backgroundday")); + s(o, "backgroundnight", c.getString("backgroundnight")); + + perspective.addClientConfiguration(o); + shader.addClientConfiguration(o); + + a(worldObject, "maps", o); + } } diff --git a/src/main/java/org/dynmap/hdmap/HDMapManager.java b/src/main/java/org/dynmap/hdmap/HDMapManager.java index 1850f3ed..9ae2c875 100644 --- a/src/main/java/org/dynmap/hdmap/HDMapManager.java +++ b/src/main/java/org/dynmap/hdmap/HDMapManager.java @@ -1,13 +1,27 @@ package org.dynmap.hdmap; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import org.bukkit.World; import org.dynmap.ConfigurationNode; +import org.dynmap.DynmapChunk; +import org.dynmap.DynmapWorld; import org.dynmap.Log; +import org.dynmap.MapManager; +import org.dynmap.MapTile; +import org.dynmap.MapType; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; public class HDMapManager { public HashMap shaders = new HashMap(); public HashMap perspectives = new HashMap(); + public HashSet maps = new HashSet(); + public HashMap> maps_by_world_perspective = new HashMap>(); public void loadHDShaders(ConfigurationNode shadercfg) { Log.verboseinfo("Loading shaders..."); @@ -30,4 +44,71 @@ public class HDMapManager { } Log.info("Loaded " + perspectives.size() + " perspectives."); } + + /** + * Initialize shader states for all shaders for given tile + */ + public HDShaderState[] getShaderStateForTile(HDMapTile tile, MapChunkCache cache, MapIterator mapiter) { + DynmapWorld w = MapManager.mapman.worldsLookup.get(tile.getWorld().getName()); + if(w == null) return new HDShaderState[0]; + ArrayList shaders = new ArrayList(); + for(MapType map : w.maps) { + if(map instanceof HDMap) { + HDMap hdmap = (HDMap)map; + if(hdmap.getPerspective() == tile.perspective) { + shaders.add(hdmap.getShader().getStateInstance(hdmap, cache, mapiter)); + } + } + } + return shaders.toArray(new HDShaderState[shaders.size()]); + } + + private static final int BIOMEDATAFLAG = 0; + private static final int HIGHESTZFLAG = 1; + private static final int RAWBIOMEFLAG = 2; + private static final int BLOCKTYPEFLAG = 3; + + public boolean isBiomeDataNeeded(HDMapTile t) { + return getCachedFlags(t)[BIOMEDATAFLAG]; + } + + public boolean isHightestBlockYDataNeeded(HDMapTile t) { + return getCachedFlags(t)[HIGHESTZFLAG]; + } + + public boolean isRawBiomeDataNeeded(HDMapTile t) { + return getCachedFlags(t)[RAWBIOMEFLAG]; + } + + public boolean isBlockTypeDataNeeded(HDMapTile t) { + return getCachedFlags(t)[BLOCKTYPEFLAG]; + } + + private HashMap cached_data_flags_by_world_perspective = new HashMap(); + + private boolean[] getCachedFlags(HDMapTile t) { + String w = t.getWorld().getName(); + String k = w + "/" + t.perspective.getName(); + boolean[] flags = cached_data_flags_by_world_perspective.get(k); + if(flags != null) + return flags; + flags = new boolean[4]; + cached_data_flags_by_world_perspective.put(k, flags); + DynmapWorld dw = MapManager.mapman.worldsLookup.get(w); + if(dw == null) return flags; + + for(MapType map : dw.maps) { + if(map instanceof HDMap) { + HDMap hdmap = (HDMap)map; + if(hdmap.getPerspective() == t.perspective) { + HDShader sh = hdmap.getShader(); + flags[BIOMEDATAFLAG] |= sh.isBiomeDataNeeded(); + flags[HIGHESTZFLAG] |= sh.isHightestBlockYDataNeeded(); + flags[RAWBIOMEFLAG] |= sh.isRawBiomeDataNeeded(); + flags[BLOCKTYPEFLAG] |= sh.isBlockTypeDataNeeded(); + } + } + } + return flags; + } } diff --git a/src/main/java/org/dynmap/hdmap/HDMapTile.java b/src/main/java/org/dynmap/hdmap/HDMapTile.java index 220ce011..53293e81 100644 --- a/src/main/java/org/dynmap/hdmap/HDMapTile.java +++ b/src/main/java/org/dynmap/hdmap/HDMapTile.java @@ -1,16 +1,20 @@ package org.dynmap.hdmap; +import org.dynmap.DynmapChunk; import org.dynmap.DynmapWorld; -import java.io.File; +import org.dynmap.MapManager; + +import java.util.List; import org.dynmap.MapTile; +import org.dynmap.utils.MapChunkCache; public class HDMapTile extends MapTile { - public HDMap map; + public HDPerspective perspective; public int tx, ty; /* Tile X and Tile Y are in tile coordinates (pixels/tile-size) */ - public HDMapTile(DynmapWorld world, HDMap map, int tx, int ty) { - super(world, map); - this.map = map; + public HDMapTile(DynmapWorld world, HDPerspective perspective, int tx, int ty) { + super(world); + this.perspective = perspective; this.tx = tx; this.ty = ty; } @@ -20,8 +24,8 @@ public class HDMapTile extends MapTile { return getFilename("hdmap"); } - public String getFilename(String shader) { - return shader + "/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; + public String getFilename(String prefix) { + return prefix + "/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; } @Override @@ -29,13 +33,13 @@ public class HDMapTile extends MapTile { return getDayFilename("hdmap"); } - public String getDayFilename(String shader) { - return shader + "_day/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; + public String getDayFilename(String prefix) { + return prefix + "_day/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png"; } @Override public int hashCode() { - return getFilename().hashCode() ^ getWorld().hashCode(); + return perspective.getName().hashCode() ^ getWorld().hashCode(); } @Override @@ -47,14 +51,39 @@ public class HDMapTile extends MapTile { } public boolean equals(HDMapTile o) { - return o.tx == tx && o.ty == ty && o.getWorld().equals(getWorld()); + return o.tx == tx && o.ty == ty && o.getWorld().equals(getWorld()) && (perspective.equals(o.perspective)); } public String getKey() { - return getWorld().getName() + ".hdmap"; + return getWorld().getName() + "." + perspective.getName(); } + @Override public String toString() { return getWorld().getName() + ":" + getFilename(); } + + @Override + public boolean isBiomeDataNeeded() { return MapManager.mapman.hdmapman.isBiomeDataNeeded(this); } + + @Override + public boolean isHightestBlockYDataNeeded() { return MapManager.mapman.hdmapman.isHightestBlockYDataNeeded(this); } + + @Override + public boolean isRawBiomeDataNeeded() { return MapManager.mapman.hdmapman.isRawBiomeDataNeeded(this); } + + @Override + public boolean isBlockTypeDataNeeded() { return MapManager.mapman.hdmapman.isBlockTypeDataNeeded(this); } + + public boolean render(MapChunkCache cache) { + return perspective.render(cache, this); + } + + public List getRequiredChunks() { + return perspective.getRequiredChunks(this); + } + + public MapTile[] getAdjecentTiles() { + return perspective.getAdjecentTiles(this); + } } diff --git a/src/main/java/org/dynmap/hdmap/HDPerspective.java b/src/main/java/org/dynmap/hdmap/HDPerspective.java index deebe50b..718acfcf 100644 --- a/src/main/java/org/dynmap/hdmap/HDPerspective.java +++ b/src/main/java/org/dynmap/hdmap/HDPerspective.java @@ -1,6 +1,29 @@ package org.dynmap.hdmap; +import java.util.List; + +import org.bukkit.Location; +import org.dynmap.DynmapChunk; +import org.dynmap.MapTile; +import org.dynmap.utils.MapChunkCache; +import org.json.simple.JSONObject; + public interface HDPerspective { /* Get name of perspective */ String getName(); + /* Get tiles invalidated by change at given location */ + MapTile[] getTiles(Location loc); + /* Get tiles adjacent to given tile */ + MapTile[] getAdjecentTiles(MapTile tile); + /* Get chunks needed for given tile */ + List getRequiredChunks(MapTile tile); + /* Render given tile */ + boolean render(MapChunkCache cache, HDMapTile tile); + + public boolean isBiomeDataNeeded(); + public boolean isHightestBlockYDataNeeded(); + public boolean isRawBiomeDataNeeded(); + public boolean isBlockTypeDataNeeded(); + + public void addClientConfiguration(JSONObject mapObject); } diff --git a/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java b/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java index 9a91903e..e51b2749 100644 --- a/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java +++ b/src/main/java/org/dynmap/hdmap/HDPerspectiveState.java @@ -3,6 +3,15 @@ package org.dynmap.hdmap; import org.dynmap.utils.Vector3D; public interface HDPerspectiveState { + /* Represents last step of movement of the ray */ + public enum BlockStep { + X_PLUS, + Y_PLUS, + Z_PLUS, + X_MINUS, + Y_MINUS, + Z_MINUS + }; /** * Get sky light level - only available if shader requested it */ @@ -22,7 +31,7 @@ public interface HDPerspectiveState { /** * Get direction of last block step */ - HDMap.BlockStep getLastBlockStep(); + BlockStep getLastBlockStep(); /** * Get perspective scale */ diff --git a/src/main/java/org/dynmap/hdmap/HDShader.java b/src/main/java/org/dynmap/hdmap/HDShader.java index d6f60086..be16442f 100644 --- a/src/main/java/org/dynmap/hdmap/HDShader.java +++ b/src/main/java/org/dynmap/hdmap/HDShader.java @@ -18,17 +18,21 @@ public interface HDShader { * @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 highest block Y data is needed */ + boolean isHightestBlockYDataNeeded(); + /* Tet if block type data needed */ + boolean isBlockTypeDataNeeded(); /* 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(); - + /* Add shader's contributions to JSON for map object */ + void addClientConfiguration(JSONObject mapObject); + } diff --git a/src/main/java/org/dynmap/hdmap/HDShaderState.java b/src/main/java/org/dynmap/hdmap/HDShaderState.java index 028ad62d..6a2e7fba 100644 --- a/src/main/java/org/dynmap/hdmap/HDShaderState.java +++ b/src/main/java/org/dynmap/hdmap/HDShaderState.java @@ -8,6 +8,14 @@ import org.dynmap.utils.Vector3D; * All method should be considered performance critical */ public interface HDShaderState { + /** + * Get our shader + */ + HDShader getShader(); + /** + * Get our map + */ + HDMap getMap(); /** * Reset renderer state for new ray - passes in pixel coordinate for ray */ diff --git a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java new file mode 100644 index 00000000..75a207a3 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java @@ -0,0 +1,676 @@ +package org.dynmap.hdmap; + +import static org.dynmap.JSONUtils.s; + +import org.dynmap.DynmapWorld; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +import org.bukkit.Location; +import org.bukkit.World; +import org.dynmap.Client; +import org.dynmap.Color; +import org.dynmap.ConfigurationNode; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; +import org.dynmap.MapManager; +import org.dynmap.MapTile; +import org.dynmap.TileHashManager; +import org.dynmap.debug.Debug; +import org.dynmap.hdmap.HDPerspectiveState.BlockStep; +import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; +import org.dynmap.kzedmap.KzedMap; +import org.dynmap.utils.FileLockManager; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; +import org.dynmap.utils.Matrix3D; +import org.dynmap.utils.Vector3D; +import org.json.simple.JSONObject; + +public class IsoHDPerspective implements HDPerspective { + private String name; + /* View angles */ + 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 */ + + /* 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 + * space contributing to the tile have consistent tile-space X,Y coordinate pairs for both the top and bottom faces + * Note that this is a classic right-hand coordinate system, while minecraft's world coordinates are left handed + * (X+ is south, Y+ is up, Z+ is east). + */ + /* Transformation matrix for taking coordinate in world-space (x, y, z) and finding coordinate in tile space (x, y, z) */ + private Matrix3D world_to_map; + private Matrix3D map_to_world; + + /* dimensions of a map tile */ + public static final int tileWidth = 128; + public static final int tileHeight = 128; + + /* Maximum and minimum inclinations */ + public static final double MAX_INCLINATION = 90.0; + public static final double MIN_INCLINATION = 20.0; + + /* Maximum and minimum scale */ + public static final double MAX_SCALE = 64; + public static final double MIN_SCALE = 1; + + private boolean need_skylightlevel = false; + private boolean need_emittedlightlevel = false; + private boolean need_biomedata = false; + private boolean need_rawbiomedata = false; + + private class OurPerspectiveState implements HDPerspectiveState { + int skylightlevel = 15; + int emittedlightlevel = 0; + int blocktypeid = 0; + int blockdata = 0; + Vector3D top, bottom; + int px, py; + BlockStep laststep = BlockStep.Y_MINUS; + /** + * Get sky light level - only available if shader requested it + */ + public final int getSkyLightLevel() { return skylightlevel; } + /** + * Get emitted light level - only available if shader requested it + */ + public final int getEmittedLightLevel() { return emittedlightlevel; } + /** + * Get current block type ID + */ + public final int getBlockTypeID() { return blocktypeid; } + /** + * Get current block data + */ + public final int getBlockData() { return blockdata; } + /** + * Get direction of last block step + */ + public final BlockStep getLastBlockStep() { return laststep; } + /** + * Get perspective scale + */ + public final double getScale() { return scale; } + /** + * Get start of current ray, in world coordinates + */ + public final Vector3D getRayStart() { return top; } + /** + * Get end of current ray, in world coordinates + */ + public final Vector3D getRayEnd() { return bottom; } + /** + * Get pixel X coordinate + */ + public final int getPixelX() { return px; } + /** + * Get pixel Y coordinate + */ + public final int getPixelY() { return py; } + + } + + public IsoHDPerspective(ConfigurationNode configuration) { + name = configuration.getString("name", null); + if(name == null) { + Log.severe("Perspective definition missing name - must be defined and unique"); + return; + } + 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; + if(inclination < MIN_INCLINATION) inclination = MIN_INCLINATION; + scale = configuration.getDouble("scale", MIN_SCALE); + if(scale < MIN_SCALE) scale = MIN_SCALE; + if(scale > MAX_SCALE) scale = MAX_SCALE; + Log.info("azimuth=" + azimuth + ", inclination=" + inclination + ", scale=" + scale); + + /* Generate transform matrix for world-to-tile coordinate mapping */ + /* First, need to fix basic coordinate mismatches before rotation - we want zero azimuth to have north to top + * (world -X -> tile +Y) and east to right (world -Z to tile +X), with height being up (world +Y -> tile +Z) + */ + Matrix3D transform = new Matrix3D(0.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0); + /* Next, rotate world counterclockwise around Z axis by azumuth angle */ + transform.rotateXY(180-azimuth); + /* Next, rotate world by (90-inclination) degrees clockwise around +X axis */ + transform.rotateYZ(90.0-inclination); + /* Finally, shear along Z axis to normalize Z to be height above map plane */ + transform.shearZ(0, Math.tan(Math.toRadians(90.0-inclination))); + /* And scale Z to be same scale as world coordinates, and scale X and Y based on setting */ + transform.scale(scale, scale, Math.sin(Math.toRadians(inclination))); + world_to_map = transform; + /* Now, generate map to world tranform, by doing opposite actions in reverse order */ + transform = new Matrix3D(); + transform.scale(1.0/scale, 1.0/scale, 1/Math.sin(Math.toRadians(inclination))); + transform.shearZ(0, -Math.tan(Math.toRadians(90.0-inclination))); + transform.rotateYZ(-(90.0-inclination)); + transform.rotateXY(-180+azimuth); + Matrix3D coordswap = new Matrix3D(0.0, -1.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0); + transform.multiply(coordswap); + map_to_world = transform; + } + + @Override + public MapTile[] getTiles(Location loc) { + DynmapWorld world = MapManager.mapman.getWorld(loc.getWorld().getName()); + HashSet tiles = new HashSet(); + Vector3D block = new Vector3D(); + block.setFromLocation(loc); /* Get coordinate for block */ + Vector3D corner = new Vector3D(); + /* Loop through corners of the cube */ + for(int i = 0; i < 2; i++) { + double inity = block.y; + for(int j = 0; j < 2; j++) { + double initz = block.z; + for(int k = 0; k < 2; k++) { + world_to_map.transform(block, corner); /* Get map coordinate of corner */ + addTile(tiles, world, (int)Math.floor(corner.x/tileWidth), (int)Math.floor(corner.y/tileHeight)); + + block.z += 1; + } + block.z = initz; + block.y += 1; + } + block.y = inity; + block.x += 1; + } + MapTile[] result = tiles.toArray(new MapTile[tiles.size()]); + Log.info("processed update for " + loc); + for(MapTile mt : result) + Log.info("need to render " + mt); + return result; + } + + @Override + public MapTile[] getAdjecentTiles(MapTile tile) { + HDMapTile t = (HDMapTile) tile; + DynmapWorld w = t.getDynmapWorld(); + int x = t.tx; + int y = t.ty; + return new MapTile[] { + 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) { + tiles.add(new HDMapTile(world, this, tx, ty)); + } + + private static class Rectangle { + double r0x, r0z; /* Coord of corner of rectangle */ + double s1x, s1z; /* Side vector for one edge */ + double s2x, s2z; /* Side vector for other edge */ + public Rectangle(Vector3D v1, Vector3D v2, Vector3D v3) { + r0x = v1.x; + r0z = v1.z; + s1x = v2.x - v1.x; + s1z = v2.z - v1.z; + s2x = v3.x - v1.x; + s2z = v3.z - v1.z; + } + public Rectangle() { + } + public void setSquare(double rx, double rz, double s) { + this.r0x = rx; + this.r0z = rz; + this.s1x = s; + this.s1z = 0; + this.s2x = 0; + this.s2z = s; + } + double getX(int idx) { + return r0x + (((idx & 1) == 0)?0:s1x) + (((idx & 2) != 0)?0:s2x); + } + double getZ(int idx) { + return r0z + (((idx & 1) == 0)?0:s1z) + (((idx & 2) != 0)?0:s2z); + } + /** + * Test for overlap of projection of one vector on to anoter + */ + boolean testoverlap(double rx, double rz, double sx, double sz, Rectangle r) { + double rmin_dot_s0 = Double.MAX_VALUE; + double rmax_dot_s0 = Double.MIN_VALUE; + /* Project each point from rectangle on to vector: find lowest and highest */ + for(int i = 0; i < 4; i++) { + double r_x = r.getX(i) - rx; /* Get relative positon of second vector start to origin */ + double r_z = r.getZ(i) - rz; + double r_dot_s0 = r_x*sx + r_z*sz; /* Projection of start of vector */ + if(r_dot_s0 < rmin_dot_s0) rmin_dot_s0 = r_dot_s0; + if(r_dot_s0 > rmax_dot_s0) rmax_dot_s0 = r_dot_s0; + } + /* Compute dot products */ + double s0_dot_s0 = sx*sx + sz*sz; /* End of our side */ + if((rmax_dot_s0 < 0.0) || (rmin_dot_s0 > s0_dot_s0)) + return false; + else + return true; + } + /** + * Test if two rectangles intersect + * Based on separating axis theorem + */ + boolean testRectangleIntesectsRectangle(Rectangle r) { + /* Test if projection of each edge of one rectangle on to each edge of the other yields overlap */ + if(testoverlap(r0x, r0z, s1x, s1z, r) && testoverlap(r0x, r0z, s2x, s2z, r) && + testoverlap(r0x+s1x, r0z+s1z, s2x, s2z, r) && testoverlap(r0x+s2x, r0z+s2z, s1x, s1z, r) && + r.testoverlap(r.r0x, r.r0z, r.s1x, r.s1z, this) && r.testoverlap(r.r0x, r.r0z, r.s2x, r.s2z, this) && + r.testoverlap(r.r0x+r.s1x, r.r0z+r.s1z, r.s2x, r.s2z, this) && r.testoverlap(r.r0x+r.s2x, r.r0z+r.s2z, r.s1x, r.s1z, this)) { + return true; + } + else { + return false; + } + } + public String toString() { + return "{ " + r0x + "," + r0z + "}x{" + (r0x+s1x) + ","+ + (r0z+s1z) + "}x{" + (r0x+s2x) + "," + (r0z+s2z) + "}"; + } + } + + @Override + public List getRequiredChunks(MapTile tile) { + if (!(tile instanceof HDMapTile)) + return Collections.emptyList(); + + HDMapTile t = (HDMapTile) tile; + int min_chunk_x = Integer.MAX_VALUE; + int max_chunk_x = Integer.MIN_VALUE; + int min_chunk_z = Integer.MAX_VALUE; + int max_chunk_z = Integer.MIN_VALUE; + + /* Make corners for volume: 0 = bottom-lower-left, 1 = top-lower-left, 2=bottom-upper-left, 3=top-upper-left + * 4 = bottom-lower-right, 5 = top-lower-right, 6 = bottom-upper-right, 7 = top-upper-right */ + Vector3D corners[] = new Vector3D[8]; + int[] chunk_x = new int[8]; + int[] chunk_z = new int[8]; + for(int x = t.tx, idx = 0; x <= (t.tx+1); x++) { + for(int y = t.ty; y <= (t.ty+1); y++) { + for(int z = 0; z <= 1; z++) { + corners[idx] = new Vector3D(); + corners[idx].x = x*tileWidth; corners[idx].y = y*tileHeight; corners[idx].z = z*128; + map_to_world.transform(corners[idx]); + /* Compute chunk coordinates of corner */ + chunk_x[idx] = (int)Math.floor(corners[idx].x / 16); + chunk_z[idx] = (int)Math.floor(corners[idx].z / 16); + /* Compute min/max of chunk coordinates */ + if(min_chunk_x > chunk_x[idx]) min_chunk_x = chunk_x[idx]; + if(max_chunk_x < chunk_x[idx]) max_chunk_x = chunk_x[idx]; + if(min_chunk_z > chunk_z[idx]) min_chunk_z = chunk_z[idx]; + if(max_chunk_z < chunk_z[idx]) max_chunk_z = chunk_z[idx]; + idx++; + } + } + } + /* Make rectangles of X-Z projection of each side of the tile volume, 0 = top, 1 = bottom, 2 = left, 3 = right, + * 4 = upper, 5 = lower */ + Rectangle rect[] = new Rectangle[6]; + rect[0] = new Rectangle(corners[1], corners[3], corners[5]); + rect[1] = new Rectangle(corners[0], corners[2], corners[4]); + rect[2] = new Rectangle(corners[0], corners[1], corners[2]); + rect[3] = new Rectangle(corners[4], corners[5], corners[6]); + rect[4] = new Rectangle(corners[2], corners[3], corners[6]); + rect[5] = new Rectangle(corners[0], corners[1], corners[4]); + + /* Now, need to walk through the min/max range to see which chunks are actually needed */ + ArrayList chunks = new ArrayList(); + Rectangle chunkrect = new Rectangle(); + int misscnt = 0; + for(int x = min_chunk_x; x <= max_chunk_x; x++) { + for(int z = min_chunk_z; z <= max_chunk_z; z++) { + chunkrect.setSquare(x*16, z*16, 16); + boolean hit = false; + /* Check to see if square of chunk intersects any of our rectangle sides */ + for(int rctidx = 0; (!hit) && (rctidx < rect.length); rctidx++) { + if(chunkrect.testRectangleIntesectsRectangle(rect[rctidx])) { + hit = true; + } + } + if(hit) { + DynmapChunk chunk = new DynmapChunk(x, z); + chunks.add(chunk); + } + else { + misscnt++; + } + } + } + return chunks; + } + + @Override + public boolean render(MapChunkCache cache, HDMapTile tile) { + Color rslt = new Color(); + MapIterator mapiter = cache.getIterator(0, 0, 0); + /* Build shader state object for each shader */ + HDShaderState[] shaderstate = MapManager.mapman.hdmapman.getShaderStateForTile(tile, cache, mapiter); + int numshaders = shaderstate.length; + if(numshaders == 0) + return false; + + /* Create buffered image for each */ + KzedBufferedImage im[] = new KzedBufferedImage[numshaders]; + KzedBufferedImage dayim[] = new KzedBufferedImage[numshaders]; + int[][] argb_buf = new int[numshaders][]; + int[][] day_argb_buf = new int[numshaders][]; + + for(int i = 0; i < numshaders; i++) { + HDShader shader = shaderstate[i].getShader(); + if(shader.isEmittedLightLevelNeeded()) + need_emittedlightlevel = true; + if(shader.isSkyLightLevelNeeded()) + need_skylightlevel = true; + im[i] = KzedMap.allocateBufferedImage(tileWidth, tileHeight); + argb_buf[i] = im[i].argb_buf; + if(shader.isNightAndDayEnabled()) { + dayim[i] = KzedMap.allocateBufferedImage(tileWidth, tileHeight); + day_argb_buf[i] = dayim[i].argb_buf; + } + } + + /* Create perspective state object */ + OurPerspectiveState ps = new OurPerspectiveState(); + + ps.top = new Vector3D(); + ps.bottom = new Vector3D(); + double xbase = tile.tx * tileWidth; + double ybase = tile.ty * tileHeight; + boolean shaderdone[] = new boolean[numshaders]; + boolean rendered[] = new boolean[numshaders]; + for(int x = 0; x < tileWidth; x++) { + ps.px = x; + for(int y = 0; y < tileHeight; y++) { + ps.top.x = ps.bottom.x = xbase + x + 0.5; /* Start at center of pixel at Y=127.5, bottom at Y=-0.5 */ + ps.top.y = ps.bottom.y = ybase + y + 0.5; + ps.top.z = 127.5; ps.bottom.z = -0.5; + map_to_world.transform(ps.top); /* Transform to world coordinates */ + map_to_world.transform(ps.bottom); + ps.py = y; + for(int i = 0; i < numshaders; i++) { + shaderstate[i].reset(ps); + } + raytrace(cache, mapiter, ps, shaderstate, shaderdone); + for(int i = 0; i < numshaders; i++) { + if(shaderdone[i] == false) { + shaderstate[i].rayFinished(ps); + } + 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(); + } + } + } + } + + boolean renderone = false; + /* Test to see if we're unchanged from older tile */ + TileHashManager hashman = MapManager.mapman.hashman; + for(int i = 0; i < numshaders; i++) { + long crc = hashman.calculateTileHash(argb_buf[i]); + boolean tile_update = false; + String prefix = shaderstate[i].getMap().getPrefix(); + if(rendered[i]) { + renderone = true; + String fname = tile.getFilename(prefix); + File f = new File(tile.getDynmapWorld().worldtilepath, fname); + FileLockManager.getWriteLock(f); + try { + if((!f.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), prefix, tile.tx, tile.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(fname)); + hashman.updateHashCode(tile.getKey(), prefix, tile.tx, tile.ty, crc); + tile.getDynmapWorld().enqueueZoomOutUpdate(f); + tile_update = true; + } + else { + Debug.debug("skipping image " + f.getPath() + " - hash match"); + } + } finally { + FileLockManager.releaseWriteLock(f); + KzedMap.freeBufferedImage(im[i]); + } + MapManager.mapman.updateStatistics(tile, prefix, true, tile_update, !rendered[i]); + /* Handle day image, if needed */ + if(dayim[i] != null) { + fname = tile.getDayFilename(prefix); + f = new File(tile.getDynmapWorld().worldtilepath, fname); + FileLockManager.getWriteLock(f); + prefix = prefix+"_day"; + tile_update = false; + try { + if((!f.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), prefix, tile.tx, tile.ty))) { + /* Wrap buffer as buffered image */ + Debug.debug("saving image " + f.getPath()); + if(!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + try { + FileLockManager.imageIOWrite(dayim[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(fname)); + hashman.updateHashCode(tile.getKey(), prefix, tile.tx, tile.ty, crc); + tile.getDynmapWorld().enqueueZoomOutUpdate(f); + tile_update = true; + } + else { + Debug.debug("skipping image " + f.getPath() + " - hash match"); + } + } finally { + FileLockManager.releaseWriteLock(f); + KzedMap.freeBufferedImage(dayim[i]); + } + MapManager.mapman.updateStatistics(tile, prefix, true, tile_update, !rendered[i]); + } + } + } + return renderone; + } + + /** + * Trace ray, based on "Voxel Tranversal along a 3D line" + */ + private void raytrace(MapChunkCache cache, MapIterator mapiter, OurPerspectiveState ps, + HDShaderState[] shaderstate, boolean[] shaderdone) { + Vector3D top = ps.top; + Vector3D bottom = ps.bottom; + /* Compute total delta on each axis */ + double dx = Math.abs(bottom.x - top.x); + double dy = Math.abs(bottom.y - top.y); + double dz = Math.abs(bottom.z - top.z); + /* Initial block coord */ + int x = (int) (Math.floor(top.x)); + int y = (int) (Math.floor(top.y)); + int z = (int) (Math.floor(top.z)); + /* Compute parametric step (dt) per step on each axis */ + double dt_dx = 1.0 / dx; + double dt_dy = 1.0 / dy; + double dt_dz = 1.0 / dz; + /* Initialize parametric value to 0 (and we're stepping towards 1) */ + double t = 0; + /* Compute number of steps and increments for each */ + int n = 1; + int x_inc, y_inc, z_inc; + + double t_next_y, t_next_x, t_next_z; + /* If perpendicular to X axis */ + if (dx == 0) { + x_inc = 0; + t_next_x = Double.MAX_VALUE; + } + /* If bottom is right of top */ + else if (bottom.x > top.x) { + x_inc = 1; + n += (int) (Math.floor(bottom.x)) - x; + t_next_x = (Math.floor(top.x) + 1 - top.x) * dt_dx; + } + /* Top is right of bottom */ + else { + x_inc = -1; + n += x - (int) (Math.floor(bottom.x)); + t_next_x = (top.x - Math.floor(top.x)) * dt_dx; + } + /* If perpendicular to Y axis */ + if (dy == 0) { + y_inc = 0; + t_next_y = Double.MAX_VALUE; + } + /* If bottom is above top */ + else if (bottom.y > top.y) { + y_inc = 1; + n += (int) (Math.floor(bottom.y)) - y; + t_next_y = (Math.floor(top.y) + 1 - top.y) * dt_dy; + } + /* If top is above bottom */ + else { + y_inc = -1; + n += y - (int) (Math.floor(bottom.y)); + t_next_y = (top.y - Math.floor(top.y)) * dt_dy; + } + /* If perpendicular to Z axis */ + if (dz == 0) { + z_inc = 0; + t_next_z = Double.MAX_VALUE; + } + /* If bottom right of top */ + else if (bottom.z > top.z) { + z_inc = 1; + n += (int) (Math.floor(bottom.z)) - z; + t_next_z = (Math.floor(top.z) + 1 - top.z) * dt_dz; + } + /* If bottom left of top */ + else { + z_inc = -1; + n += z - (int) (Math.floor(bottom.z)); + t_next_z = (top.z - Math.floor(top.z)) * dt_dz; + } + /* Walk through scene */ + ps.laststep = BlockStep.Y_MINUS; /* Last step is down into map */ + mapiter.initialize(x, y, z); + ps.skylightlevel = 15; + ps.emittedlightlevel = 0; + for (; n > 0; --n) { + ps.blocktypeid = mapiter.getBlockTypeID(); + if(ps.blocktypeid != 0) { + ps.blockdata = mapiter.getBlockData(); + boolean done = true; + for(int i = 0; i < shaderstate.length; i++) { + if(!shaderdone[i]) + shaderdone[i] = shaderstate[i].processBlock(ps); + done = done && shaderdone[i]; + } + /* If all are done, we're out */ + if(done) + return; + } + if(need_skylightlevel) + ps.skylightlevel = mapiter.getBlockSkyLight(); + if(need_emittedlightlevel) + ps.emittedlightlevel = mapiter.getBlockEmittedLight(); + /* If X step is next best */ + if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) { + x += x_inc; + t = t_next_x; + t_next_x += dt_dx; + if(x_inc > 0) { + ps.laststep = BlockStep.X_PLUS; + mapiter.incrementX(); + } + else { + ps.laststep = BlockStep.X_MINUS; + mapiter.decrementX(); + } + } + /* If Y step is next best */ + else if((t_next_y <= t_next_x) && (t_next_y <= t_next_z)) { + y += y_inc; + t = t_next_y; + t_next_y += dt_dy; + if(y_inc > 0) { + ps.laststep = BlockStep.Y_PLUS; + mapiter.incrementY(); + if(mapiter.getY() > 127) + return; + } + else { + ps.laststep = BlockStep.Y_MINUS; + mapiter.decrementY(); + if(mapiter.getY() < 0) + return; + } + } + /* Else, Z step is next best */ + else { + z += z_inc; + t = t_next_z; + t_next_z += dt_dz; + if(z_inc > 0) { + ps.laststep = BlockStep.Z_PLUS; + mapiter.incrementZ(); + } + else { + ps.laststep = BlockStep.Z_MINUS; + mapiter.decrementZ(); + } + } + } + } + + @Override + public boolean isBiomeDataNeeded() { + return need_biomedata; + } + + @Override + public boolean isRawBiomeDataNeeded() { + return need_rawbiomedata; + } + + public boolean isHightestBlockYDataNeeded() { + return false; + } + + public boolean isBlockTypeDataNeeded() { + return true; + } + + @Override + public String getName() { + return name; + } + + @Override + public void addClientConfiguration(JSONObject mapObject) { + s(mapObject, "perspective", name); + s(mapObject, "azimuth", azimuth); + s(mapObject, "inclination", inclination); + s(mapObject, "scale", scale); + s(mapObject, "worldtomap", world_to_map.toJSON()); + s(mapObject, "maptoworld", map_to_world.toJSON()); + } +} diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index 1bd97ece..ea2c1576 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -213,8 +213,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* Hand encoding and writing file off to MapManager */ - KzedZoomedMapTile zmtile = new KzedZoomedMapTile(tile.getDynmapWorld(), - (KzedMap) tile.getMap(), tile); + KzedZoomedMapTile zmtile = new KzedZoomedMapTile(tile.getDynmapWorld(), tile); File zoomFile = MapManager.mapman.getTileFile(zmtile); doFileWrites(outputFile, tile, im, im_day, zmtile, zoomFile, zim, zim_day, !isempty); diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index bf8f0486..59220ce7 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -140,10 +140,6 @@ public class KzedMap extends MapType { } } - public void invalidateTile(MapTile tile) { - onTileInvalidated.trigger(tile); - } - /** * Test if point x,z is inside rectangle with corner at r0x,r0z and with * size vectors s1x,s1z and s2x,s2z diff --git a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java index 5df90cdc..ea8e3e0c 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java @@ -1,8 +1,14 @@ package org.dynmap.kzedmap; +import org.dynmap.DynmapChunk; import org.dynmap.DynmapWorld; +import org.dynmap.MapManager; + import java.io.File; +import java.util.List; + import org.dynmap.MapTile; +import org.dynmap.utils.MapChunkCache; public class KzedMapTile extends MapTile { public KzedMap map; @@ -15,7 +21,7 @@ public class KzedMapTile extends MapTile { public File file = null; public KzedMapTile(DynmapWorld world, KzedMap map, MapTileRenderer renderer, int px, int py) { - super(world, map); + super(world); this.map = map; this.renderer = renderer; this.px = px; @@ -68,4 +74,16 @@ public class KzedMapTile extends MapTile { public String toString() { return getWorld().getName() + ":" + getFilename(); } + + public boolean render(MapChunkCache cache) { + return map.render(cache, this, MapManager.mapman.getTileFile(this)); + } + + public List getRequiredChunks() { + return map.getRequiredChunks(this); + } + + public MapTile[] getAdjecentTiles() { + return map.getAdjecentTiles(this); + } } diff --git a/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java index b7fec488..897d2038 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java @@ -1,7 +1,11 @@ package org.dynmap.kzedmap; +import java.util.List; + +import org.dynmap.DynmapChunk; import org.dynmap.DynmapWorld; import org.dynmap.MapTile; +import org.dynmap.utils.MapChunkCache; public class KzedZoomedMapTile extends MapTile { private String fname; @@ -33,8 +37,8 @@ public class KzedZoomedMapTile extends MapTile { public KzedMapTile originalTile; - public KzedZoomedMapTile(DynmapWorld world, KzedMap map, KzedMapTile original) { - super(world, map); + public KzedZoomedMapTile(DynmapWorld world, KzedMapTile original) { + super(world); this.originalTile = original; } @@ -79,4 +83,19 @@ public class KzedZoomedMapTile extends MapTile { return getWorld().getName() + ".z" + originalTile.renderer.getName(); } + @Override + public boolean render(MapChunkCache cache) { + return false; + } + + @Override + public List getRequiredChunks() { + return null; + } + + @Override + public MapTile[] getAdjecentTiles() { + return null; + } + } diff --git a/src/main/java/org/dynmap/utils/Matrix3D.java b/src/main/java/org/dynmap/utils/Matrix3D.java index 583f792c..06a23981 100644 --- a/src/main/java/org/dynmap/utils/Matrix3D.java +++ b/src/main/java/org/dynmap/utils/Matrix3D.java @@ -1,5 +1,7 @@ package org.dynmap.utils; +import org.json.simple.JSONArray; + /** * Basic 3D matrix math class - prevent dependency on Java 3D for this */ @@ -119,4 +121,19 @@ public class Matrix3D { public String toString() { return "[ [" + m11 + " " + m12 + " " + m13 + "] [" + m21 + " " + m22 + " " + m23 + "] [" + m31 + " " + m32 + " " + m33 + "] ]"; } + + @SuppressWarnings("unchecked") + public JSONArray toJSON() { + JSONArray array = new JSONArray(); + array.add(m11); + array.add(m12); + array.add(m13); + array.add(m21); + array.add(m22); + array.add(m23); + array.add(m31); + array.add(m32); + array.add(m33); + return array; + } } From a3c00a10267e67573075addd01ef54b05a4212ee Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sat, 9 Jul 2011 01:26:47 -0500 Subject: [PATCH 10/14] Split lighting and shading - add lightings.txt --- lightings.txt | 30 +++ shaders.txt | 48 +++-- src/main/assembly/package.xml | 3 +- src/main/java/org/dynmap/DynmapPlugin.java | 6 +- src/main/java/org/dynmap/MapManager.java | 4 +- .../org/dynmap/hdmap/DefaultHDLighting.java | 54 +++++ .../org/dynmap/hdmap/DefaultHDShader.java | 204 ++++++++---------- .../java/org/dynmap/hdmap/HDLighting.java | 29 +++ src/main/java/org/dynmap/hdmap/HDMap.java | 12 +- .../java/org/dynmap/hdmap/HDMapManager.java | 34 ++- src/main/java/org/dynmap/hdmap/HDShader.java | 4 +- .../java/org/dynmap/hdmap/HDShaderState.java | 4 + .../org/dynmap/hdmap/IsoHDPerspective.java | 7 +- .../org/dynmap/hdmap/ShadowHDLighting.java | 99 +++++++++ 14 files changed, 387 insertions(+), 151 deletions(-) create mode 100644 lightings.txt create mode 100644 src/main/java/org/dynmap/hdmap/DefaultHDLighting.java create mode 100644 src/main/java/org/dynmap/hdmap/HDLighting.java create mode 100644 src/main/java/org/dynmap/hdmap/ShadowHDLighting.java diff --git a/lightings.txt b/lightings.txt new file mode 100644 index 00000000..f8ec1899 --- /dev/null +++ b/lightings.txt @@ -0,0 +1,30 @@ +lightings: + # Default lighting - no effects, shadows, day/night + - class: org.dynmap.hdmap.DefaultHDLighting + name: default + # Shadows enabled day mode + - class: org.dynmap.hdmap.ShadowHDLighting + name: shadows + shadowstrength: 1.0 + # Night view (default moonight level is 4) + - class: org.dynmap.hdmap.ShadowHDLighting + name: night + shadowstrength: 1.0 + ambientlight: 4 + # A 'bright' night view (easier to see unlit landscape dimly) + - class: org.dynmap.hdmap.ShadowHDLighting + name: brightnight + shadowstrength: 1.0 + ambientlight: 8 + # Night and day view + - class: org.dynmap.hdmap.ShadowHDLighting + name: nightandday + shadowstrength: 1.0 + ambientlight: 4 + night-and-day: true + # 'Bright' Night and day view + - class: org.dynmap.hdmap.ShadowHDLighting + name: brightnightandday + shadowstrength: 1.0 + ambientlight: 8 + night-and-day: true diff --git a/shaders.txt b/shaders.txt index 3e75bb75..1d3148df 100644 --- a/shaders.txt +++ b/shaders.txt @@ -1,18 +1,36 @@ shaders: - - class: org.dynmap.hdmap.DefaultHDShader - name: classic - colorscheme: default + - class: org.dynmap.hdmap.DefaultHDShader + name: default + colorscheme: default - - class: org.dynmap.hdmap.DefaultHDShader - name: night - colorscheme: default - ambientlight: 4 - shadowstrength: 1.0 + - class: org.dynmap.hdmap.DefaultHDShader + name: ovocean + colorscheme: ovocean - - class: org.dynmap.hdmap.DefaultHDShader - name: daynight - colorscheme: default - ambientlight: 4 - shadowstrength: 1.0 - night-and-day: true - \ No newline at end of file + - class: org.dynmap.hdmap.DefaultHDShader + name: flames + colorscheme: flames + + - class: org.dynmap.hdmap.DefaultHDShader + name: sk89q + colorscheme: sk89q + + - class: org.dynmap.hdmap.DefaultHDShader + name: biome + biomecolored: biome + + - class: org.dynmap.hdmap.DefaultHDShader + name: temperature + biomecolored: temperature + + - class: org.dynmap.hdmap.DefaultHDShader + name: rainfall + biomecolored: rainfall + + - class: org.dynmap.hdmap.DefaultHDShader + name: no_transparency + colorscheme: default + transparency: false + + + \ No newline at end of file diff --git a/src/main/assembly/package.xml b/src/main/assembly/package.xml index 91a9506a..7e457682 100644 --- a/src/main/assembly/package.xml +++ b/src/main/assembly/package.xml @@ -29,7 +29,8 @@ configuration.txt shaders.txt - perspectives.txt + perspectives.txt + lightings.txt diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index 0bf23209..59a5df2a 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -48,6 +48,7 @@ public class DynmapPlugin extends JavaPlugin { public ConfigurationNode configuration; public ConfigurationNode shaderconfig; public ConfigurationNode perspectiveconfig; + public ConfigurationNode lightingsconfig; public HashSet enabledTriggers = new HashSet(); public PermissionProvider permissions; public ComponentManager componentManager = new ComponentManager(); @@ -86,6 +87,9 @@ public class DynmapPlugin extends JavaPlugin { org.bukkit.util.config.Configuration bukkitPerspectiveConfig = new org.bukkit.util.config.Configuration(new File(this.getDataFolder(), "perspectives.txt")); bukkitPerspectiveConfig.load(); perspectiveconfig = new ConfigurationNode(bukkitPerspectiveConfig); + org.bukkit.util.config.Configuration bukkitLightingsConfig = new org.bukkit.util.config.Configuration(new File(this.getDataFolder(), "lightings.txt")); + bukkitLightingsConfig.load(); + lightingsconfig = new ConfigurationNode(bukkitLightingsConfig); Log.verbose = configuration.getBoolean("verbose", true); @@ -99,7 +103,7 @@ public class DynmapPlugin extends JavaPlugin { playerList = new PlayerList(getServer(), getFile("hiddenplayers.txt"), configuration); playerList.load(); - mapManager = new MapManager(this, configuration, shaderconfig, perspectiveconfig); + mapManager = new MapManager(this, configuration, shaderconfig, perspectiveconfig, lightingsconfig); mapManager.startRendering(); loadWebserver(); diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index d4fb68a1..42a0c4fe 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -319,13 +319,15 @@ public class MapManager { } } - public MapManager(DynmapPlugin plugin, ConfigurationNode configuration, ConfigurationNode shadercfg, ConfigurationNode perspectivecfg) { + public MapManager(DynmapPlugin plugin, ConfigurationNode configuration, ConfigurationNode shadercfg, ConfigurationNode perspectivecfg, + ConfigurationNode lightingscfg) { plug_in = plugin; mapman = this; /* Initialize HD map manager */ hdmapman = new HDMapManager(); hdmapman.loadHDShaders(shadercfg); hdmapman.loadHDPerspectives(perspectivecfg); + hdmapman.loadHDLightings(lightingscfg); this.tileQueue = new AsynchronousQueue(new Handler() { @Override diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDLighting.java b/src/main/java/org/dynmap/hdmap/DefaultHDLighting.java new file mode 100644 index 00000000..92b2fb8b --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/DefaultHDLighting.java @@ -0,0 +1,54 @@ +package org.dynmap.hdmap; + +import java.util.HashSet; + +import org.dynmap.Color; +import org.dynmap.ColorScheme; +import org.dynmap.ConfigurationNode; +import org.dynmap.hdmap.DefaultHDShader.BiomeColorOption; +import org.json.simple.JSONObject; +import static org.dynmap.JSONUtils.s; + +public class DefaultHDLighting implements HDLighting { + private String name; + + public DefaultHDLighting(ConfigurationNode configuration) { + name = (String) configuration.get("name"); + } + + /* Get lighting name */ + public String getName() { return name; } + + /* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */ + public void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor) { + for(Color oc: outcolor) + oc.setColor(incolor); + } + + /* Test if Biome Data is needed for this renderer */ + public boolean isBiomeDataNeeded() { return false; } + + /* Test if raw biome temperature/rainfall data is needed */ + public boolean isRawBiomeDataNeeded() { return false; } + + /* Test if highest block Y data is needed */ + public boolean isHightestBlockYDataNeeded() { return false; } + + /* Tet if block type data needed */ + public boolean isBlockTypeDataNeeded() { return false; } + + /* Test if night/day is enabled for this renderer */ + public boolean isNightAndDayEnabled() { return false; } + + /* Test if sky light level needed */ + public boolean isSkyLightLevelNeeded() { return false; } + + /* Test if emitted light level needed */ + public boolean isEmittedLightLevelNeeded() { return false; } + + /* Add shader's contributions to JSON for map object */ + public void addClientConfiguration(JSONObject mapObject) { + s(mapObject, "lighting", name); + s(mapObject, "nightandday", isNightAndDayEnabled()); + } +} diff --git a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java index ca4a5067..b2461da0 100644 --- a/src/main/java/org/dynmap/hdmap/DefaultHDShader.java +++ b/src/main/java/org/dynmap/hdmap/DefaultHDShader.java @@ -1,7 +1,6 @@ package org.dynmap.hdmap; import static org.dynmap.JSONUtils.s; -import java.util.HashSet; import org.bukkit.block.Biome; import org.dynmap.Color; import org.dynmap.ColorScheme; @@ -14,12 +13,6 @@ public class DefaultHDShader implements HDShader { 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 @@ -28,30 +21,7 @@ public class DefaultHDShader implements HDShader { public DefaultHDShader(ConfigurationNode configuration) { name = (String) configuration.get("name"); - 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(configuration.getString("colorscheme", "default")); - 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")) { @@ -68,30 +38,60 @@ public class DefaultHDShader implements HDShader { } } - 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 boolean isHightestBlockYDataNeeded() { return false; } - public boolean isBlockTypeDataNeeded() { return true; } - - public String getName() { return name; } + @Override + public boolean isBiomeDataNeeded() { + return biomecolored == BiomeColorOption.BIOME; + } - private class OurRendererState implements HDShaderState { - private Color color = new Color(); - private Color daycolor; + @Override + public boolean isRawBiomeDataNeeded() { + return (biomecolored == BiomeColorOption.RAINFALL) || (biomecolored == BiomeColorOption.TEMPERATURE); + } + + @Override + public boolean isHightestBlockYDataNeeded() { + return false; + } + + @Override + public boolean isBlockTypeDataNeeded() { + return true; + } + + @Override + public boolean isSkyLightLevelNeeded() { + return false; + } + + @Override + public boolean isEmittedLightLevelNeeded() { + return false; + } + + @Override + public String getName() { + return name; + } + + private class OurShaderState implements HDShaderState { + private Color color[]; protected MapIterator mapiter; protected HDMap map; - private Color tmpcolor = new Color(); - private Color tmpdaycolor = new Color(); + private Color tmpcolor[]; private int pixelodd; + private HDLighting lighting; - private OurRendererState(MapIterator mapiter, HDMap map) { + private OurShaderState(MapIterator mapiter, HDMap map) { this.mapiter = mapiter; this.map = map; - if(night_and_day) { - daycolor = new Color(); + this.lighting = map.getLighting(); + if(lighting.isNightAndDayEnabled()) { + color = new Color[] { new Color(), new Color() }; + tmpcolor = new Color[] { new Color(), new Color() }; + } + else { + color = new Color[] { new Color() }; + tmpcolor = new Color[] { new Color() }; } } /** @@ -107,13 +107,18 @@ public class DefaultHDShader implements HDShader { public HDMap getMap() { return map; } + /** + * Get our lighting + */ + public HDLighting getLighting() { + return lighting; + } /** * Reset renderer state for new ray */ public void reset(HDPerspectiveState ps) { - color.setTransparent(); - if(daycolor != null) - daycolor.setTransparent(); + for(Color c: color) + c.setTransparent(); pixelodd = (ps.getPixelX() & 0x3) + (ps.getPixelY()<<1); } @@ -129,6 +134,7 @@ public class DefaultHDShader implements HDShader { * @return true if ray is done, false if ray needs to continue */ public boolean processBlock(HDPerspectiveState ps) { + int i; int blocktype = ps.getBlockTypeID(); if(blocktype == 0) return false; @@ -140,75 +146,44 @@ public class DefaultHDShader implements HDShader { switch(ps.getLastBlockStep()) { case X_PLUS: case X_MINUS: - seq = 1; + seq = 0; break; case Z_PLUS: case Z_MINUS: - seq = 3; + seq = 2; break; default: if(((pixelodd + mapiter.getY()) & 0x03) == 0) - seq = 2; + seq = 3; else - seq = 0; + seq = 1; break; } 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 = ps.getSkyLightLevel(); - if(lightscale != null) - lightlevel = lightscale[lightlevel]; - if((lightlevel < 15) || (lightlevel_day < 15)) { - int emitted = ps.getEmittedLightLevel(); - 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); - } - } - } + lighting.applyLighting(ps, this, c, tmpcolor); /* 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); + for(i = 0; i < color.length; i++) + color[i].setARGB(tmpcolor[i].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 if(color[0].isTransparent()) { + for(i = 0; i < color.length; i++) + color[i].setColor(tmpcolor[i]); + return (color[0].getAlpha() == 255); } /* Else, blend and generate new alpha */ else { - int alpha = color.getAlpha(); - int alpha2 = tmpcolor.getAlpha() * (255-alpha) / 255; + int alpha = color[0].getAlpha(); + int alpha2 = tmpcolor[0].getAlpha() * (255-alpha) / 255; int talpha = alpha + alpha2; - color.setRGBA((tmpcolor.getRed()*alpha2 + color.getRed()*alpha) / talpha, - (tmpcolor.getGreen()*alpha2 + color.getGreen()*alpha) / talpha, - (tmpcolor.getBlue()*alpha2 + color.getBlue()*alpha) / talpha, talpha); - if(daycolor != null) - daycolor.setRGBA((tmpdaycolor.getRed()*alpha2 + daycolor.getRed()*alpha) / talpha, - (tmpdaycolor.getGreen()*alpha2 + daycolor.getGreen()*alpha) / talpha, - (tmpdaycolor.getBlue()*alpha2 + daycolor.getBlue()*alpha) / talpha, talpha); + for(i = 0; i < color.length; i++) + color[i].setRGBA((tmpcolor[i].getRed()*alpha2 + color[i].getRed()*alpha) / talpha, + (tmpcolor[i].getGreen()*alpha2 + color[i].getGreen()*alpha) / talpha, + (tmpcolor[i].getBlue()*alpha2 + color[i].getBlue()*alpha) / talpha, talpha); return (talpha >= 254); /* If only one short, no meaningful contribution left */ } } @@ -226,27 +201,17 @@ public class DefaultHDShader implements HDShader { * @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); + c.setColor(color[index]); } /** * 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, HDMap map) { + private class OurBiomeShaderState extends OurShaderState { + private OurBiomeShaderState(MapIterator mapiter, HDMap map) { super(mapiter, map); } protected Color[] getBlockColors(int blocktype, int blockdata) { @@ -257,8 +222,8 @@ public class DefaultHDShader implements HDShader { } } - private class OurBiomeRainfallRendererState extends OurRendererState { - private OurBiomeRainfallRendererState(MapIterator mapiter, HDMap map) { + private class OurBiomeRainfallShaderState extends OurShaderState { + private OurBiomeRainfallShaderState(MapIterator mapiter, HDMap map) { super(mapiter, map); } protected Color[] getBlockColors(int blocktype, int blockdata) { @@ -266,8 +231,8 @@ public class DefaultHDShader implements HDShader { } } - private class OurBiomeTempRendererState extends OurRendererState { - private OurBiomeTempRendererState(MapIterator mapiter, HDMap map) { + private class OurBiomeTempShaderState extends OurShaderState { + private OurBiomeTempShaderState(MapIterator mapiter, HDMap map) { super(mapiter, map); } protected Color[] getBlockColors(int blocktype, int blockdata) { @@ -284,13 +249,13 @@ public class DefaultHDShader implements HDShader { public HDShaderState getStateInstance(HDMap map, MapChunkCache cache, MapIterator mapiter) { switch(biomecolored) { case NONE: - return new OurRendererState(mapiter, map); + return new OurShaderState(mapiter, map); case BIOME: - return new OurBiomeRendererState(mapiter, map); + return new OurBiomeShaderState(mapiter, map); case RAINFALL: - return new OurBiomeRainfallRendererState(mapiter, map); + return new OurBiomeRainfallShaderState(mapiter, map); case TEMPERATURE: - return new OurBiomeTempRendererState(mapiter, map); + return new OurBiomeTempShaderState(mapiter, map); } return null; } @@ -298,6 +263,5 @@ public class DefaultHDShader implements HDShader { /* Add shader's contributions to JSON for map object */ public void addClientConfiguration(JSONObject mapObject) { s(mapObject, "shader", name); - s(mapObject, "nightandday", night_and_day); } } diff --git a/src/main/java/org/dynmap/hdmap/HDLighting.java b/src/main/java/org/dynmap/hdmap/HDLighting.java new file mode 100644 index 00000000..11e572d8 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/HDLighting.java @@ -0,0 +1,29 @@ +package org.dynmap.hdmap; + +import org.dynmap.Color; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; +import org.json.simple.JSONObject; + +public interface HDLighting { + /* Get lighting name */ + String getName(); + /* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */ + void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor); + /* Test if Biome Data is needed for this renderer */ + boolean isBiomeDataNeeded(); + /* Test if raw biome temperature/rainfall data is needed */ + boolean isRawBiomeDataNeeded(); + /* Test if highest block Y data is needed */ + boolean isHightestBlockYDataNeeded(); + /* Tet if block type data needed */ + boolean isBlockTypeDataNeeded(); + /* 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(); + /* Add shader's contributions to JSON for map object */ + void addClientConfiguration(JSONObject mapObject); +} diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index 1113b448..0b8ff3d4 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -21,6 +21,7 @@ public class HDMap extends MapType { private String prefix; private HDPerspective perspective; private HDShader shader; + private HDLighting lighting; private ConfigurationNode configuration; public HDMap(ConfigurationNode configuration) { @@ -43,12 +44,20 @@ public class HDMap extends MapType { name = null; return; } + String lightingid = configuration.getString("lighting", "default"); + lighting = MapManager.mapman.hdmapman.lightings.get(lightingid); + if(lighting == null) { + Log.severe("HDMap '"+name+"' loading invalid lighting '" + lighting + "' - map disabled"); + name = null; + return; + } prefix = configuration.getString("prefix", name); this.configuration = configuration; } public HDShader getShader() { return shader; } public HDPerspective getPerspective() { return perspective; } + public HDLighting getLighting() { return lighting; } @Override public MapTile[] getTiles(Location loc) { @@ -77,7 +86,7 @@ public class HDMap extends MapType { public List baseZoomFilePrefixes() { ArrayList s = new ArrayList(); s.add(prefix); - if(shader.isNightAndDayEnabled()) + if(lighting.isNightAndDayEnabled()) s.add(prefix + "_day"); return s; } @@ -117,6 +126,7 @@ public class HDMap extends MapType { perspective.addClientConfiguration(o); shader.addClientConfiguration(o); + lighting.addClientConfiguration(o); a(worldObject, "maps", o); diff --git a/src/main/java/org/dynmap/hdmap/HDMapManager.java b/src/main/java/org/dynmap/hdmap/HDMapManager.java index 9ae2c875..590d76f3 100644 --- a/src/main/java/org/dynmap/hdmap/HDMapManager.java +++ b/src/main/java/org/dynmap/hdmap/HDMapManager.java @@ -20,16 +20,20 @@ import org.dynmap.utils.MapIterator; public class HDMapManager { public HashMap shaders = new HashMap(); public HashMap perspectives = new HashMap(); + public HashMap lightings = new HashMap(); public HashSet maps = new HashSet(); public HashMap> maps_by_world_perspective = new HashMap>(); public void loadHDShaders(ConfigurationNode shadercfg) { Log.verboseinfo("Loading shaders..."); for(HDShader shader : shadercfg.createInstances("shaders", new Class[0], new Object[0])) { + if(shader.getName() == null) continue; if(shaders.containsKey(shader.getName())) { Log.severe("Duplicate shader name '" + shader.getName() + "' - shader ignored"); } - shaders.put(shader.getName(), shader); + else { + shaders.put(shader.getName(), shader); + } } Log.info("Loaded " + shaders.size() + " shaders."); } @@ -37,14 +41,31 @@ public class HDMapManager { public void loadHDPerspectives(ConfigurationNode perspectivecfg) { Log.verboseinfo("Loading perspectives..."); for(HDPerspective perspective : perspectivecfg.createInstances("perspectives", new Class[0], new Object[0])) { + if(perspective.getName() == null) continue; if(perspectives.containsKey(perspective.getName())) { Log.severe("Duplicate perspective name '" + perspective.getName() + "' - perspective ignored"); } - perspectives.put(perspective.getName(), perspective); + else { + perspectives.put(perspective.getName(), perspective); + } } Log.info("Loaded " + perspectives.size() + " perspectives."); } + public void loadHDLightings(ConfigurationNode lightingcfg) { + Log.verboseinfo("Loading lightings..."); + for(HDLighting lighting : lightingcfg.createInstances("lightings", new Class[0], new Object[0])) { + if(lighting.getName() == null) continue; + if(lightings.containsKey(lighting.getName())) { + Log.severe("Duplicate lighting name '" + lighting.getName() + "' - lighting ignored"); + } + else { + lightings.put(lighting.getName(), lighting); + } + } + Log.info("Loaded " + lightings.size() + " lightings."); + } + /** * Initialize shader states for all shaders for given tile */ @@ -102,10 +123,11 @@ public class HDMapManager { HDMap hdmap = (HDMap)map; if(hdmap.getPerspective() == t.perspective) { HDShader sh = hdmap.getShader(); - flags[BIOMEDATAFLAG] |= sh.isBiomeDataNeeded(); - flags[HIGHESTZFLAG] |= sh.isHightestBlockYDataNeeded(); - flags[RAWBIOMEFLAG] |= sh.isRawBiomeDataNeeded(); - flags[BLOCKTYPEFLAG] |= sh.isBlockTypeDataNeeded(); + HDLighting lt = hdmap.getLighting(); + flags[BIOMEDATAFLAG] |= sh.isBiomeDataNeeded() | lt.isBiomeDataNeeded(); + flags[HIGHESTZFLAG] |= sh.isHightestBlockYDataNeeded() | lt.isHightestBlockYDataNeeded(); + flags[RAWBIOMEFLAG] |= sh.isRawBiomeDataNeeded() | lt.isRawBiomeDataNeeded(); + flags[BLOCKTYPEFLAG] |= sh.isBlockTypeDataNeeded() | lt.isBlockTypeDataNeeded(); } } } diff --git a/src/main/java/org/dynmap/hdmap/HDShader.java b/src/main/java/org/dynmap/hdmap/HDShader.java index be16442f..08ac8e0d 100644 --- a/src/main/java/org/dynmap/hdmap/HDShader.java +++ b/src/main/java/org/dynmap/hdmap/HDShader.java @@ -8,7 +8,7 @@ import org.dynmap.utils.MapIterator; import org.json.simple.JSONObject; public interface HDShader { - /* Get renderer name */ + /* Get shader name */ String getName(); /** * Get renderer state object for use rendering a tile @@ -26,8 +26,6 @@ public interface HDShader { boolean isHightestBlockYDataNeeded(); /* Tet if block type data needed */ boolean isBlockTypeDataNeeded(); - /* 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 */ diff --git a/src/main/java/org/dynmap/hdmap/HDShaderState.java b/src/main/java/org/dynmap/hdmap/HDShaderState.java index 6a2e7fba..151aeea4 100644 --- a/src/main/java/org/dynmap/hdmap/HDShaderState.java +++ b/src/main/java/org/dynmap/hdmap/HDShaderState.java @@ -12,6 +12,10 @@ public interface HDShaderState { * Get our shader */ HDShader getShader(); + /** + * Get our lighting + */ + HDLighting getLighting(); /** * Get our map */ diff --git a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java index 75a207a3..d4d96c1f 100644 --- a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java +++ b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java @@ -363,13 +363,14 @@ public class IsoHDPerspective implements HDPerspective { for(int i = 0; i < numshaders; i++) { HDShader shader = shaderstate[i].getShader(); - if(shader.isEmittedLightLevelNeeded()) + HDLighting lighting = shaderstate[i].getLighting(); + if(shader.isEmittedLightLevelNeeded() || lighting.isEmittedLightLevelNeeded()) need_emittedlightlevel = true; - if(shader.isSkyLightLevelNeeded()) + if(shader.isSkyLightLevelNeeded() || lighting.isSkyLightLevelNeeded()) need_skylightlevel = true; im[i] = KzedMap.allocateBufferedImage(tileWidth, tileHeight); argb_buf[i] = im[i].argb_buf; - if(shader.isNightAndDayEnabled()) { + if(lighting.isNightAndDayEnabled()) { dayim[i] = KzedMap.allocateBufferedImage(tileWidth, tileHeight); day_argb_buf[i] = dayim[i].argb_buf; } diff --git a/src/main/java/org/dynmap/hdmap/ShadowHDLighting.java b/src/main/java/org/dynmap/hdmap/ShadowHDLighting.java new file mode 100644 index 00000000..3f740280 --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/ShadowHDLighting.java @@ -0,0 +1,99 @@ +package org.dynmap.hdmap; + +import static org.dynmap.JSONUtils.s; + +import org.dynmap.Color; +import org.dynmap.ConfigurationNode; +import org.dynmap.Log; +import org.json.simple.JSONObject; + +public class ShadowHDLighting extends DefaultHDLighting { + + 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 */ + + public ShadowHDLighting(ConfigurationNode configuration) { + super(configuration); + 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) && (v < 15)) { + lightscale = new int[16]; + for(int i = 0; i < 16; i++) { + if(i < (15-v)) + lightscale[i] = 0; + else + lightscale[i] = i - (15-v); + } + } + night_and_day = configuration.getBoolean("night-and-day", false); + if(night_and_day) { + if(lightscale == null) { + Log.severe("night-and-day in lighting '" + getName() + "' requires ambientlight<15"); + night_and_day = false; + } + } + } + + /* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */ + public void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor) { + int lightlevel = 15, lightlevel_day = 15; + /* If processing for shadows, use sky light level as base lighting */ + if(shadowscale != null) { + lightlevel = lightlevel_day = ps.getSkyLightLevel(); + } + /* If ambient light, adjust base lighting for it */ + if(lightscale != null) + lightlevel = lightscale[lightlevel]; + /* If we're below max, see if emitted light helps */ + if((lightlevel < 15) || (lightlevel_day < 15)) { + int emitted = ps.getEmittedLightLevel(); + lightlevel = Math.max(emitted, lightlevel); + lightlevel_day = Math.max(emitted, lightlevel_day); + } + /* Figure out our color, with lighting if needed */ + outcolor[0].setColor(incolor); + if(lightlevel < 15) { + shadowColor(outcolor[0], lightlevel); + } + if(outcolor.length > 1) { + if(lightlevel_day == lightlevel) { + outcolor[1].setColor(outcolor[0]); + } + else { + outcolor[1].setColor(incolor); + if(lightlevel_day < 15) { + shadowColor(outcolor[1], lightlevel_day); + } + } + } + } + + 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()); + } + + /* Test if night/day is enabled for this renderer */ + public boolean isNightAndDayEnabled() { return night_and_day; } + + /* Test if sky light level needed */ + public boolean isSkyLightLevelNeeded() { return (shadowscale != null); } + + /* Test if emitted light level needed */ + public boolean isEmittedLightLevelNeeded() { return (shadowscale != null) || (lightscale != null); } + +} From 02fa9384acf24a3d5d04290490a9a8ea362d86ab Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sat, 9 Jul 2011 02:19:56 -0500 Subject: [PATCH 11/14] Add HD cave shader --- shaders.txt | 6 +- .../java/org/dynmap/hdmap/CaveHDShader.java | 184 ++++++++++++++++++ .../org/dynmap/hdmap/IsoHDPerspective.java | 4 +- 3 files changed, 190 insertions(+), 4 deletions(-) create mode 100644 src/main/java/org/dynmap/hdmap/CaveHDShader.java diff --git a/shaders.txt b/shaders.txt index 1d3148df..0754d1d6 100644 --- a/shaders.txt +++ b/shaders.txt @@ -31,6 +31,6 @@ shaders: name: no_transparency colorscheme: default transparency: false - - - \ No newline at end of file + + - class: org.dynmap.hdmap.CaveHDShader + name: cave diff --git a/src/main/java/org/dynmap/hdmap/CaveHDShader.java b/src/main/java/org/dynmap/hdmap/CaveHDShader.java new file mode 100644 index 00000000..437a3b1d --- /dev/null +++ b/src/main/java/org/dynmap/hdmap/CaveHDShader.java @@ -0,0 +1,184 @@ +package org.dynmap.hdmap; + +import static org.dynmap.JSONUtils.s; +import org.bukkit.block.Biome; +import org.dynmap.Color; +import org.dynmap.ColorScheme; +import org.dynmap.ConfigurationNode; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; +import org.json.simple.JSONObject; + +public class CaveHDShader implements HDShader { + private String name; + + + public CaveHDShader(ConfigurationNode configuration) { + name = (String) configuration.get("name"); + } + + @Override + public boolean isBiomeDataNeeded() { + return false; + } + + @Override + public boolean isRawBiomeDataNeeded() { + return false; + } + + @Override + public boolean isHightestBlockYDataNeeded() { + return false; + } + + @Override + public boolean isBlockTypeDataNeeded() { + return true; + } + + @Override + public boolean isSkyLightLevelNeeded() { + return false; + } + + @Override + public boolean isEmittedLightLevelNeeded() { + return false; + } + + @Override + public String getName() { + return name; + } + + private class OurShaderState implements HDShaderState { + private Color color; + protected MapIterator mapiter; + protected HDMap map; + private boolean air; + + private OurShaderState(MapIterator mapiter, HDMap map) { + this.mapiter = mapiter; + this.map = map; + this.color = new Color(); + } + /** + * Get our shader + */ + public HDShader getShader() { + return CaveHDShader.this; + } + + /** + * Get our map + */ + public HDMap getMap() { + return map; + } + + /** + * Get our lighting + */ + public HDLighting getLighting() { + return map.getLighting(); + } + + /** + * Reset renderer state for new ray + */ + public void reset(HDPerspectiveState ps) { + color.setTransparent(); + air = true; + } + + /** + * Process next ray step - called for each block on route + * @return true if ray is done, false if ray needs to continue + */ + public boolean processBlock(HDPerspectiveState ps) { + int blocktype = ps.getBlockTypeID(); + switch (blocktype) { + case 0: + case 20: + case 18: + case 17: + case 78: + case 79: + break; + default: + air = false; + return false; + } + if (!air) { + int cr, cg, cb; + int mult = 256; + + if (mapiter.getY() < 64) { + cr = 0; + cg = 64 + mapiter.getY() * 3; + cb = 255 - mapiter.getY() * 4; + } else { + cr = (mapiter.getY() - 64) * 4; + cg = 255; + cb = 0; + } + /* Figure out which color to use */ + switch(ps.getLastBlockStep()) { + case X_PLUS: + case X_MINUS: + mult = 224; + break; + case Z_PLUS: + case Z_MINUS: + mult = 256; + break; + default: + mult = 160; + break; + } + cr = cr * mult / 256; + cg = cg * mult / 256; + cb = cb * mult / 256; + + color.setRGBA(cr, cg, cb, 255); + return true; + } + return false; + } + /** + * Ray ended - used to report that ray has exited map (called if renderer has not reported complete) + */ + public void rayFinished(HDPerspectiveState ps) { + } + /** + * 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) { + c.setColor(color); + } + /** + * Clean up state object - called after last ray completed + */ + public void cleanup() { + } + } + + /** + * 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) { + return new OurShaderState(mapiter, map); + } + + /* Add shader's contributions to JSON for map object */ + public void addClientConfiguration(JSONObject mapObject) { + s(mapObject, "shader", name); + } +} diff --git a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java index d4d96c1f..4b766f50 100644 --- a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java +++ b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java @@ -575,9 +575,10 @@ public class IsoHDPerspective implements HDPerspective { mapiter.initialize(x, y, z); ps.skylightlevel = 15; ps.emittedlightlevel = 0; + boolean nonairhit = false; for (; n > 0; --n) { ps.blocktypeid = mapiter.getBlockTypeID(); - if(ps.blocktypeid != 0) { + if(nonairhit || (ps.blocktypeid != 0)) { ps.blockdata = mapiter.getBlockData(); boolean done = true; for(int i = 0; i < shaderstate.length; i++) { @@ -588,6 +589,7 @@ public class IsoHDPerspective implements HDPerspective { /* If all are done, we're out */ if(done) return; + nonairhit = true; } if(need_skylightlevel) ps.skylightlevel = mapiter.getBlockSkyLight(); From 898f4a67400f034f6acd3f4f0d14c39d82f952c0 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sat, 9 Jul 2011 15:51:32 -0500 Subject: [PATCH 12/14] Handle bigmap versus bigworld, allows HDMap to be big-map-style independent of bigworld setting. Add bigmap setting on KzedMap and FlatMap too. --- .../dynmap/ClientConfigurationComponent.java | 2 +- src/main/java/org/dynmap/DynmapWorld.java | 18 +++++++++--------- src/main/java/org/dynmap/MapType.java | 4 +++- src/main/java/org/dynmap/flat/FlatMap.java | 11 ++++++++++- src/main/java/org/dynmap/hdmap/HDMap.java | 9 +++++++-- .../dynmap/kzedmap/DefaultTileRenderer.java | 4 +++- src/main/java/org/dynmap/kzedmap/KzedMap.java | 12 ++++++++++-- .../java/org/dynmap/kzedmap/KzedMapTile.java | 4 ++-- .../org/dynmap/kzedmap/MapTileRenderer.java | 3 ++- web/js/flatmap.js | 4 ++-- web/js/kzedmaps.js | 4 ++-- 11 files changed, 51 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/dynmap/ClientConfigurationComponent.java b/src/main/java/org/dynmap/ClientConfigurationComponent.java index a342255a..3d2479aa 100644 --- a/src/main/java/org/dynmap/ClientConfigurationComponent.java +++ b/src/main/java/org/dynmap/ClientConfigurationComponent.java @@ -37,7 +37,7 @@ public class ClientConfigurationComponent extends Component { a(t, "worlds", wo); for(MapType mt : world.maps) { - mt.buildClientConfiguration(wo); + mt.buildClientConfiguration(wo, world); } } s(t, "defaultworld", c.getString("defaultworld", defaultWorld == null ? "world" : defaultWorld.world.getName())); diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index e4d828eb..c815b239 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -114,6 +114,7 @@ public class DynmapWorld { String fnprefix; String zfnprefix; int bigworldshift; + boolean isbigworld; } public void freshenZoomOutFilesByLevel(int zoomlevel) { @@ -123,10 +124,10 @@ public class DynmapWorld { return; HashMap maptab = buildPrefixData(zoomlevel); - if(bigworld) { /* If big world, next directories are map name specific */ - DirFilter df = new DirFilter(); - for(String pfx : maptab.keySet()) { /* Walk through prefixes, as directories */ - PrefixData pd = maptab.get(pfx); + DirFilter df = new DirFilter(); + for(String pfx : maptab.keySet()) { /* Walk through prefixes */ + PrefixData pd = maptab.get(pfx); + if(pd.isbigworld) { /* If big world, next directories are map name specific */ File dname = new File(worldtilepath, pfx); /* Now, go through subdirectories under this one, and process them */ String[] subdir = dname.list(df); @@ -136,9 +137,7 @@ public class DynmapWorld { cnt += processZoomDirectory(sdname, pd); } } - } - else { /* Else, classic file layout */ - for(String pfx : maptab.keySet()) { /* Walk through prefixes, as directories */ + else { /* Else, classic file layout */ cnt += processZoomDirectory(worldtilepath, maptab.get(pfx)); } } @@ -178,7 +177,8 @@ public class DynmapWorld { pd.zoomlevel = zoomlevel; pd.zoomprefix = "zzzzzzzzzzzz".substring(0, zoomlevel); pd.bigworldshift = bigworldshift; - if(bigworld) { + pd.isbigworld = mt.isBigWorldMap(this); + if(pd.isbigworld) { if(zoomlevel > 0) { pd.zoomprefix += "_"; pd.zfnprefix = "z" + pd.zoomprefix; @@ -206,7 +206,7 @@ public class DynmapWorld { } private String makeFilePath(PrefixData pd, int x, int y, boolean zoomed) { - if(bigworld) + if(pd.isbigworld) return pd.baseprefix + "/" + (x >> pd.bigworldshift) + "_" + (y >> pd.bigworldshift) + "/" + (zoomed?pd.zfnprefix:pd.fnprefix) + x + "_" + y + ".png"; else return (zoomed?pd.zfnprefix:pd.fnprefix) + "_" + x + "_" + y + ".png"; diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 74c1057d..55e58cfc 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -16,7 +16,7 @@ public abstract class MapType { public abstract boolean render(MapChunkCache cache, MapTile tile, File outputFile); - public void buildClientConfiguration(JSONObject worldObject) { + public void buildClientConfiguration(JSONObject worldObject, DynmapWorld w) { } public abstract String getName(); @@ -32,6 +32,8 @@ public abstract class MapType { public abstract int baseZoomFileStepSize(); /* How many bits of coordinate are shifted off to make big world directory name */ public abstract int getBigWorldShift(); + /* Returns true if big world file structure is in effect for this map */ + public abstract boolean isBigWorldMap(DynmapWorld w); /** * Step sequence for creating zoomed file: first index is top-left, second top-right, third bottom-left, forth bottom-right diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index 3da0ce08..1ada2645 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -42,6 +42,7 @@ public class FlatMap extends MapType { protected boolean transparency; private enum Texture { NONE, SMOOTH, DITHER }; private Texture textured = Texture.NONE; + private boolean isbigmap; public FlatMap(ConfigurationNode configuration) { this.configuration = configuration; @@ -81,6 +82,7 @@ public class FlatMap extends MapType { textured = Texture.DITHER; else textured = Texture.SMOOTH; + isbigmap = configuration.getBoolean("isbigmap", false); } @Override @@ -429,6 +431,12 @@ public class FlatMap extends MapType { /* How many bits of coordinate are shifted off to make big world directory name */ public int getBigWorldShift() { return 5; } + /* Returns true if big world file structure is in effect for this map */ + @Override + public boolean isBigWorldMap(DynmapWorld w) { + return w.bigworld || isbigmap; + } + public static class FlatMapTile extends MapTile { FlatMap map; public int x; @@ -494,7 +502,7 @@ public class FlatMap extends MapType { } @Override - public void buildClientConfiguration(JSONObject worldObject) { + public void buildClientConfiguration(JSONObject worldObject, DynmapWorld world) { ConfigurationNode c = configuration; JSONObject o = new JSONObject(); s(o, "type", "FlatMapType"); @@ -506,6 +514,7 @@ public class FlatMap extends MapType { s(o, "nightandday", c.getBoolean("night-and-day",false)); s(o, "backgroundday", c.getString("backgroundday")); s(o, "backgroundnight", c.getString("backgroundnight")); + s(o, "bigmap", this.isBigWorldMap(world)); 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 0b8ff3d4..b1f8b330 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -9,6 +9,7 @@ import java.util.List; import org.bukkit.Location; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapChunk; +import org.dynmap.DynmapWorld; import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.MapTile; @@ -102,6 +103,10 @@ public class HDMap extends MapType { /* How many bits of coordinate are shifted off to make big world directory name */ public int getBigWorldShift() { return 5; } + /* Returns true if big world file structure is in effect for this map */ + @Override + public boolean isBigWorldMap(DynmapWorld w) { return true; } /* We always use it on these maps */ + @Override public String getName() { return name; @@ -112,7 +117,7 @@ public class HDMap extends MapType { } @Override - public void buildClientConfiguration(JSONObject worldObject) { + public void buildClientConfiguration(JSONObject worldObject, DynmapWorld world) { ConfigurationNode c = configuration; JSONObject o = new JSONObject(); s(o, "type", "HDMapType"); @@ -123,7 +128,7 @@ public class HDMap extends MapType { s(o, "background", c.getString("background")); s(o, "backgroundday", c.getString("backgroundday")); s(o, "backgroundnight", c.getString("backgroundnight")); - + s(o, "bigmap", true); perspective.addClientConfiguration(o); shader.addClientConfiguration(o); lighting.addClientConfiguration(o); diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index ea2c1576..978abfbd 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -16,6 +16,7 @@ import org.dynmap.Client; import org.dynmap.Color; import org.dynmap.ColorScheme; import org.dynmap.ConfigurationNode; +import org.dynmap.DynmapWorld; import org.dynmap.MapManager; import org.dynmap.TileHashManager; import org.dynmap.debug.Debug; @@ -586,7 +587,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } @Override - public void buildClientConfiguration(JSONObject worldObject) { + public void buildClientConfiguration(JSONObject worldObject, DynmapWorld world, KzedMap map) { ConfigurationNode c = configuration; JSONObject o = new JSONObject(); s(o, "type", "KzedMapType"); @@ -598,6 +599,7 @@ public class DefaultTileRenderer implements MapTileRenderer { s(o, "nightandday", c.getBoolean("night-and-day", false)); s(o, "backgroundday", c.getString("backgroundday")); s(o, "backgroundnight", c.getString("backgroundnight")); + s(o, "bigmap", map.isBigWorldMap(world)); a(worldObject, "maps", o); } } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 59220ce7..5bc34e4c 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -47,6 +47,7 @@ public class KzedMap extends MapType { public static final int anchorz = 0; MapTileRenderer[] renderers; + private boolean isbigmap; /* BufferedImage with direct access to its ARGB-formatted data buffer */ public static class KzedBufferedImage { @@ -68,6 +69,7 @@ public class KzedMap extends MapType { this.renderers = new MapTileRenderer[renderers.size()]; renderers.toArray(this.renderers); Log.verboseinfo("Loaded " + renderers.size() + " renderers for map '" + getClass().toString() + "'."); + isbigmap = configuration.getBoolean("isbigmap", false); } @Override @@ -342,14 +344,20 @@ public class KzedMap extends MapType { /* How many bits of coordinate are shifted off to make big world directory name */ public int getBigWorldShift() { return 12; } + /* Returns true if big world file structure is in effect for this map */ + @Override + public boolean isBigWorldMap(DynmapWorld w) { + return w.bigworld || isbigmap; + } + public String getName() { return "KzedMap"; } @Override - public void buildClientConfiguration(JSONObject worldObject) { + public void buildClientConfiguration(JSONObject worldObject, DynmapWorld world) { for(MapTileRenderer renderer : renderers) { - renderer.buildClientConfiguration(worldObject); + renderer.buildClientConfiguration(worldObject, world, this); } } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java index ea8e3e0c..1070b202 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java @@ -31,7 +31,7 @@ public class KzedMapTile extends MapTile { @Override public String getFilename() { if(fname == null) { - if(world.bigworld) + if(map.isBigWorldMap(world)) fname = renderer.getName() + "/" + (px >> 12) + '_' + (py >> 12) + '/' + px + "_" + py + ".png"; else fname = renderer.getName() + "_" + px + "_" + py + ".png"; @@ -42,7 +42,7 @@ public class KzedMapTile extends MapTile { @Override public String getDayFilename() { if(fname_day == null) { - if(world.bigworld) + if(map.isBigWorldMap(world)) fname_day = renderer.getName() + "_day/" + (px >> 12) + '_' + (py >> 12) + '/' + px + "_" + py + ".png"; else fname_day = renderer.getName() + "_day_" + px + "_" + py + ".png"; diff --git a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java index 01b1270d..bccec2b9 100644 --- a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java @@ -2,6 +2,7 @@ package org.dynmap.kzedmap; import java.io.File; +import org.dynmap.DynmapWorld; import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; @@ -11,7 +12,7 @@ public interface MapTileRenderer { boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile); - void buildClientConfiguration(JSONObject worldObject); + void buildClientConfiguration(JSONObject worldObject, DynmapWorld w, KzedMap map); boolean isBiomeDataNeeded(); boolean isRawBiomeDataNeeded(); diff --git a/web/js/flatmap.js b/web/js/flatmap.js index 67633b50..7904f643 100644 --- a/web/js/flatmap.js +++ b/web/js/flatmap.js @@ -33,7 +33,7 @@ FlatMapType.prototype = $.extend(new DynMapType(), { if(zoom < extrazoom) { var scale = 1 << (extrazoom-zoom); var zprefix = "zzzzzzzzzzzz".substring(0, extrazoom-zoom); - if(this.dynmap.world.bigworld) + if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].bigmap) tileName = this.prefix + dnprefix + '_128/' + ((scale*coord.x) >> 5) + '_' + ((scale*coord.y) >> 5) + '/' + zprefix + "_" + (scale*coord.x) + '_' + (scale*coord.y) + '.png'; else @@ -41,7 +41,7 @@ FlatMapType.prototype = $.extend(new DynMapType(), { imgSize = 128; } else { - if(this.dynmap.world.bigworld) + if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].bigmap) tileName = this.prefix + dnprefix + '_128/' + (coord.x >> 5) + '_' + (coord.y >> 5) + '/' + coord.x + '_' + coord.y + '.png'; else diff --git a/web/js/kzedmaps.js b/web/js/kzedmaps.js index d5312715..7f3a816a 100644 --- a/web/js/kzedmaps.js +++ b/web/js/kzedmaps.js @@ -55,7 +55,7 @@ KzedMapType.prototype = $.extend(new DynMapType(), { tileSize = 128; imgSize = tileSize; var tilescale = 2 << (extrazoom-zoom); - if (this.dynmap.world.bigworld) { + if (this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].bigmap) { if(zoom < extrazoom) zpre = zpre + '_'; tileName = 'z' + this.prefix + dnprefix + '/' + ((-coord.x * tileSize*tilescale)>>12) + '_' + ((coord.y * tileSize*tilescale) >> 12) + '/' + zpre + @@ -69,7 +69,7 @@ KzedMapType.prototype = $.extend(new DynMapType(), { tileSize = 128; imgSize = Math.pow(2, 6+zoom-extrazoom); - if(this.dynmap.world.bigworld) { + if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].bigmap) { tileName = this.prefix + dnprefix + '/' + ((-coord.x*tileSize) >> 12) + '_' + ((coord.y*tileSize)>>12) + '/' + (-coord.x*tileSize) + '_' + (coord.y*tileSize) + '.png'; From 02329673b2f991a4241e7d6f923799afbf404d4e Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sat, 9 Jul 2011 17:01:40 -0500 Subject: [PATCH 13/14] Add adaptive zoomout levels for HDMaps, change zoomout processing to handle it properly --- src/main/java/org/dynmap/DynmapWorld.java | 51 ++++++++++++++----- src/main/java/org/dynmap/MapType.java | 3 +- src/main/java/org/dynmap/hdmap/HDMap.java | 15 ++++++ .../java/org/dynmap/hdmap/HDPerspective.java | 2 + .../org/dynmap/hdmap/IsoHDPerspective.java | 4 ++ web/js/hdmap.js | 13 ++--- 6 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index c815b239..c21dca2c 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -41,7 +41,8 @@ public class DynmapWorld { private int extrazoomoutlevels; /* Number of additional zoom out levels to generate */ public File worldtilepath; private Object lock = new Object(); - private HashSet zoomoutupdates[]; + @SuppressWarnings("unchecked") + private HashSet zoomoutupdates[] = new HashSet[0]; private boolean checkts = true; /* Check timestamps on first run with new configuration */ @SuppressWarnings("unchecked") @@ -59,15 +60,25 @@ public class DynmapWorld { } private void enqueueZoomOutUpdate(File f, int level) { - if(level >= extrazoomoutlevels) - return; synchronized(lock) { + if(level >= zoomoutupdates.length) { + @SuppressWarnings("unchecked") + HashSet new_zoomout[] = new HashSet[level+1]; + System.arraycopy(zoomoutupdates, 0, new_zoomout, 0, zoomoutupdates.length); + for(int i = 0; i < new_zoomout.length; i++) { + if(i < zoomoutupdates.length) + new_zoomout[i] = zoomoutupdates[i]; + else + new_zoomout[i] = new HashSet(); + } + zoomoutupdates = new_zoomout; + } zoomoutupdates[level].add(f.getPath()); } } private boolean popQueuedUpdate(File f, int level) { - if(level >= extrazoomoutlevels) + if(level >= zoomoutupdates.length) return false; synchronized(lock) { return zoomoutupdates[level].remove(f.getPath()); @@ -97,8 +108,15 @@ public class DynmapWorld { } public void freshenZoomOutFiles() { - for(int i = 0; i < extrazoomoutlevels; i++) { - freshenZoomOutFilesByLevel(i); + boolean done = false; + int last_done = 0; + for(int i = 0; (!done); i++) { + done = freshenZoomOutFilesByLevel(i); + last_done = i; + } + /* Purge updates for levels above what any map needs */ + for(int i = last_done; i < zoomoutupdates.length; i++) { + zoomoutupdates[i].clear(); } checkts = false; /* Just handle queued updates after first scan */ } @@ -114,20 +132,20 @@ public class DynmapWorld { String fnprefix; String zfnprefix; int bigworldshift; - boolean isbigworld; + boolean isbigmap; } - public void freshenZoomOutFilesByLevel(int zoomlevel) { + public boolean freshenZoomOutFilesByLevel(int zoomlevel) { int cnt = 0; Debug.debug("freshenZoomOutFiles(" + world.getName() + "," + zoomlevel + ")"); if(worldtilepath.exists() == false) /* Quit if not found */ - return; + return true; HashMap maptab = buildPrefixData(zoomlevel); DirFilter df = new DirFilter(); for(String pfx : maptab.keySet()) { /* Walk through prefixes */ PrefixData pd = maptab.get(pfx); - if(pd.isbigworld) { /* If big world, next directories are map name specific */ + if(pd.isbigmap) { /* If big world, next directories are map name specific */ File dname = new File(worldtilepath, pfx); /* Now, go through subdirectories under this one, and process them */ String[] subdir = dname.list(df); @@ -142,12 +160,17 @@ public class DynmapWorld { } } Debug.debug("freshenZoomOutFiles(" + world.getName() + "," + zoomlevel + ") - done (" + cnt + " updated files)"); + /* Return true when we have none left at the level */ + return (maptab.size() == 0); } private HashMap buildPrefixData(int zoomlevel) { HashMap maptab = new HashMap(); /* Build table of file prefixes and step sizes */ for(MapType mt : maps) { + /* If level is above top needed for this map, skip */ + if(zoomlevel > (this.extrazoomoutlevels + mt.getMapZoomOutLevels())) + continue; List pfx = mt.baseZoomFilePrefixes(); int stepsize = mt.baseZoomFileStepSize(); int bigworldshift = mt.getBigWorldShift(); @@ -175,10 +198,10 @@ public class DynmapWorld { pd.stepseq = stepseq; pd.baseprefix = p; pd.zoomlevel = zoomlevel; - pd.zoomprefix = "zzzzzzzzzzzz".substring(0, zoomlevel); + pd.zoomprefix = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz".substring(0, zoomlevel); pd.bigworldshift = bigworldshift; - pd.isbigworld = mt.isBigWorldMap(this); - if(pd.isbigworld) { + pd.isbigmap = mt.isBigWorldMap(this); + if(pd.isbigmap) { if(zoomlevel > 0) { pd.zoomprefix += "_"; pd.zfnprefix = "z" + pd.zoomprefix; @@ -206,7 +229,7 @@ public class DynmapWorld { } private String makeFilePath(PrefixData pd, int x, int y, boolean zoomed) { - if(pd.isbigworld) + if(pd.isbigmap) return pd.baseprefix + "/" + (x >> pd.bigworldshift) + "_" + (y >> pd.bigworldshift) + "/" + (zoomed?pd.zfnprefix:pd.fnprefix) + x + "_" + y + ".png"; else return (zoomed?pd.zfnprefix:pd.fnprefix) + "_" + x + "_" + y + ".png"; diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 55e58cfc..723dc5eb 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -34,7 +34,8 @@ public abstract class MapType { public abstract int getBigWorldShift(); /* Returns true if big world file structure is in effect for this map */ public abstract boolean isBigWorldMap(DynmapWorld w); - + /* Return number of zoom levels needed by this map (before extra levels from extrazoomout) */ + public int getMapZoomOutLevels() { return 0; } /** * Step sequence for creating zoomed file: first index is top-left, second top-right, third bottom-left, forth bottom-right * Values correspond to tile X,Y (0), X+step,Y (1), X,Y+step (2), X+step,Y+step (3) diff --git a/src/main/java/org/dynmap/hdmap/HDMap.java b/src/main/java/org/dynmap/hdmap/HDMap.java index b1f8b330..8ecd015f 100644 --- a/src/main/java/org/dynmap/hdmap/HDMap.java +++ b/src/main/java/org/dynmap/hdmap/HDMap.java @@ -24,6 +24,7 @@ public class HDMap extends MapType { private HDShader shader; private HDLighting lighting; private ConfigurationNode configuration; + private int mapzoomout; public HDMap(ConfigurationNode configuration) { name = configuration.getString("name", null); @@ -54,6 +55,14 @@ public class HDMap extends MapType { } prefix = configuration.getString("prefix", name); this.configuration = configuration; + + /* Compute extra zoom outs needed for this map */ + double scale = perspective.getScale(); + mapzoomout = 0; + while(scale >= 1.0) { + mapzoomout++; + scale = scale / 2.0; + } } public HDShader getShader() { return shader; } @@ -107,6 +116,11 @@ public class HDMap extends MapType { @Override public boolean isBigWorldMap(DynmapWorld w) { return true; } /* We always use it on these maps */ + /* Return number of zoom levels needed by this map (before extra levels from extrazoomout) */ + public int getMapZoomOutLevels() { + return mapzoomout; + } + @Override public String getName() { return name; @@ -129,6 +143,7 @@ public class HDMap extends MapType { s(o, "backgroundday", c.getString("backgroundday")); s(o, "backgroundnight", c.getString("backgroundnight")); s(o, "bigmap", true); + s(o, "mapzoomout", (world.getExtraZoomOutLevels()+mapzoomout)); perspective.addClientConfiguration(o); shader.addClientConfiguration(o); lighting.addClientConfiguration(o); diff --git a/src/main/java/org/dynmap/hdmap/HDPerspective.java b/src/main/java/org/dynmap/hdmap/HDPerspective.java index 718acfcf..af8b1d92 100644 --- a/src/main/java/org/dynmap/hdmap/HDPerspective.java +++ b/src/main/java/org/dynmap/hdmap/HDPerspective.java @@ -24,6 +24,8 @@ public interface HDPerspective { public boolean isHightestBlockYDataNeeded(); public boolean isRawBiomeDataNeeded(); public boolean isBlockTypeDataNeeded(); + + double getScale(); public void addClientConfiguration(JSONObject mapObject); } diff --git a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java index 4b766f50..7ac227ca 100644 --- a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java +++ b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java @@ -661,6 +661,10 @@ public class IsoHDPerspective implements HDPerspective { public boolean isBlockTypeDataNeeded() { return true; } + + public double getScale() { + return scale; + } @Override public String getName() { diff --git a/web/js/hdmap.js b/web/js/hdmap.js index 5a922fbf..2640daa9 100644 --- a/web/js/hdmap.js +++ b/web/js/hdmap.js @@ -19,7 +19,7 @@ HDMapType.prototype = $.extend(new DynMapType(), { projection: new HDProjection(), tileSize: new google.maps.Size(128.0, 128.0), minZoom: 0, - maxZoom: 3, + maxZoom: 2, prefix: null, getTile: function(coord, zoom, doc) { var tileSize = 128; @@ -27,13 +27,14 @@ HDMapType.prototype = $.extend(new DynMapType(), { var tileName; var dnprefix = ''; - if(this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId].nightandday && this.dynmap.serverday) + var map = this.dynmap.map.mapTypes[this.dynmap.map.mapTypeId]; + if(map.nightandday && this.dynmap.serverday) dnprefix = '_day'; - var extrazoom = this.dynmap.world.extrazoomout; + var extrazoom = map.mapzoomout; if(zoom < extrazoom) { var scale = 1 << (extrazoom-zoom); - var zprefix = "zzzzzzzzzzzz".substring(0, extrazoom-zoom); + var zprefix = "zzzzzzzzzzzzzzzzzzzzzz".substring(0, extrazoom-zoom); tileName = this.prefix + dnprefix + '/' + ((scale*coord.x) >> 5) + '_' + ((-scale*coord.y) >> 5) + '/' + zprefix + "_" + (scale*coord.x) + '_' + (-scale*coord.y) + '.png'; imgSize = 128; @@ -66,9 +67,9 @@ HDMapType.prototype = $.extend(new DynMapType(), { }, updateTileSize: function(zoom) { var size; - var extrazoom = this.dynmap.world.extrazoomout; + var extrazoom = this.mapzoomout; this.projection.extrazoom = extrazoom; - this.maxZoom = 3 + extrazoom; + this.maxZoom = 2 + extrazoom; if (zoom <= extrazoom) { size = 128; } From 874bf9da8d612b9efd7f5d5065440ee3fc77aeda Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sat, 9 Jul 2011 18:04:42 -0500 Subject: [PATCH 14/14] Fix marker/player world-to-map coordinate transform in hdmap.js --- web/js/hdmap.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/web/js/hdmap.js b/web/js/hdmap.js index 2640daa9..9e2cf632 100644 --- a/web/js/hdmap.js +++ b/web/js/hdmap.js @@ -1,6 +1,7 @@ function HDProjection() {} HDProjection.prototype = { extrazoom: 0, + worldtomap: null, fromLatLngToPoint: function(latLng) { return new google.maps.Point(latLng.lng()*config.tileWidth, latLng.lat()*config.tileHeight); }, @@ -8,7 +9,11 @@ HDProjection.prototype = { return new google.maps.LatLng( point.y/config.tileHeight, point.x/config.tileWidth); }, fromWorldToLatLng: function(x, y, z) { - return new google.maps.LatLng(-z / config.tileWidth / (1 << this.extrazoom), x / config.tileHeight / (1 << this.extrazoom)); + var wtp = this.worldtomap; + var xx = x*wtp[0] + y*wtp[1] + z*wtp[2]; + var yy = x*wtp[3] + y*wtp[4] + z*wtp[5]; + + return new google.maps.LatLng(-yy / config.tileHeight / (1 << this.extrazoom), xx / config.tileWidth / (1 << this.extrazoom)); } }; @@ -69,6 +74,7 @@ HDMapType.prototype = $.extend(new DynMapType(), { var size; var extrazoom = this.mapzoomout; this.projection.extrazoom = extrazoom; + this.projection.worldtomap = this.worldtomap; this.maxZoom = 2 + extrazoom; if (zoom <= extrazoom) { size = 128;