From 69baafe5974dec75adc02862e29cf5101336eb7a Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 8 Jul 2011 22:40:40 -0500 Subject: [PATCH] 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; + } }