Refactor HDMap configuration - add shaders, perspectives

This commit is contained in:
Mike Primm 2011-07-08 22:40:40 -05:00
parent 7e5865a899
commit 69baafe597
19 changed files with 1375 additions and 769 deletions

View File

@ -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

View File

@ -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<DynmapChunk> requiredChunks = mt.getRequiredChunks(tile);
MapChunkCache cache = createMapChunkCache(world, requiredChunks, mt.isBlockTypeDataNeeded(),
mt.isHightestBlockYDataNeeded(), mt.isBiomeDataNeeded(),
mt.isRawBiomeDataNeeded());
List<DynmapChunk> 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<MapTile> invalitateListener = new Event.Listener<MapTile>() {
@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.<MapType>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<World, File> worldTileDirectories = new HashMap<World, File>();
public File getTileFile(MapTile tile) {
World world = tile.getWorld();

View File

@ -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<DynmapChunk> 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; }
}

View File

@ -8,8 +8,6 @@ import org.dynmap.utils.MapChunkCache;
import org.json.simple.JSONObject;
public abstract class MapType {
public Event<MapTile> onTileInvalidated = new Event<MapTile>();
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,

View File

@ -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<DynmapChunk> 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

View File

@ -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);
}
}

View File

@ -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<HDShader> shaders = configuration.<HDShader>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<MapTile> tiles = new HashSet<MapTile>();
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<MapTile> 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<DynmapChunk> 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<DynmapChunk> chunks = new ArrayList<DynmapChunk>();
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<String> baseZoomFilePrefixes() {
ArrayList<String> s = new ArrayList<String>();
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);
}
}

View File

@ -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<String, HDShader> shaders = new HashMap<String, HDShader>();
public HashMap<String, HDPerspective> perspectives = new HashMap<String, HDPerspective>();
public HashSet<HDMap> maps = new HashSet<HDMap>();
public HashMap<String, ArrayList<HDMap>> maps_by_world_perspective = new HashMap<String, ArrayList<HDMap>>();
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<HDShaderState> shaders = new ArrayList<HDShaderState>();
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<String, boolean[]> cached_data_flags_by_world_perspective = new HashMap<String, boolean[]>();
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;
}
}

View File

@ -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<DynmapChunk> getRequiredChunks() {
return perspective.getRequiredChunks(this);
}
public MapTile[] getAdjecentTiles() {
return perspective.getAdjecentTiles(this);
}
}

View File

@ -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<DynmapChunk> 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);
}

View File

@ -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
*/

View File

@ -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);
}

View File

@ -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
*/

View File

@ -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<MapTile> tiles = new HashSet<MapTile>();
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<MapTile> 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<DynmapChunk> 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<DynmapChunk> chunks = new ArrayList<DynmapChunk>();
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());
}
}

View File

@ -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);

View File

@ -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

View File

@ -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<DynmapChunk> getRequiredChunks() {
return map.getRequiredChunks(this);
}
public MapTile[] getAdjecentTiles() {
return map.getAdjecentTiles(this);
}
}

View File

@ -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<DynmapChunk> getRequiredChunks() {
return null;
}
@Override
public MapTile[] getAdjecentTiles() {
return null;
}
}

View File

@ -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;
}
}