mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-12-27 11:07:38 +01:00
HD renderer prototype
This commit is contained in:
parent
33206e089f
commit
630759c87b
45
src/main/java/org/dynmap/hdmap/DummyHDRenderer.java
Normal file
45
src/main/java/org/dynmap/hdmap/DummyHDRenderer.java
Normal file
@ -0,0 +1,45 @@
|
||||
package org.dynmap.hdmap;
|
||||
|
||||
import static org.dynmap.JSONUtils.a;
|
||||
import static org.dynmap.JSONUtils.s;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.dynmap.ColorScheme;
|
||||
import org.dynmap.ConfigurationNode;
|
||||
import org.dynmap.Log;
|
||||
import org.dynmap.kzedmap.KzedMapTile;
|
||||
import org.dynmap.kzedmap.DefaultTileRenderer.BiomeColorOption;
|
||||
import org.dynmap.utils.MapChunkCache;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public class DummyHDRenderer implements HDMapTileRenderer {
|
||||
private ConfigurationNode configuration;
|
||||
private String name;
|
||||
|
||||
public DummyHDRenderer(ConfigurationNode configuration) {
|
||||
this.configuration = configuration;
|
||||
name = (String) configuration.get("prefix");
|
||||
}
|
||||
public boolean isBiomeDataNeeded() { return false; }
|
||||
public boolean isRawBiomeDataNeeded() { return false; };
|
||||
public boolean isNightAndDayEnabled() { return false; }
|
||||
public String getName() { return name; }
|
||||
|
||||
public boolean render(MapChunkCache cache, HDMapTile tile, File outputFile) {
|
||||
Log.info("DummyHDRenderer(" + tile + ", " + outputFile.getPath());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildClientConfiguration(JSONObject worldObject) {
|
||||
ConfigurationNode c = configuration;
|
||||
JSONObject o = new JSONObject();
|
||||
s(o, "type", "HDMapType");
|
||||
s(o, "name", c.getString("name"));
|
||||
s(o, "title", c.getString("title"));
|
||||
s(o, "icon", c.getString("icon"));
|
||||
s(o, "prefix", c.getString("prefix"));
|
||||
a(worldObject, "maps", o);
|
||||
}
|
||||
}
|
566
src/main/java/org/dynmap/hdmap/HDMap.java
Normal file
566
src/main/java/org/dynmap/hdmap/HDMap.java
Normal file
@ -0,0 +1,566 @@
|
||||
package org.dynmap.hdmap;
|
||||
|
||||
import org.dynmap.DynmapWorld;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.World.Environment;
|
||||
import org.dynmap.Client;
|
||||
import org.dynmap.Color;
|
||||
import org.dynmap.ColorScheme;
|
||||
import org.dynmap.ConfigurationNode;
|
||||
import org.dynmap.DynmapChunk;
|
||||
import org.dynmap.Log;
|
||||
import org.dynmap.MapManager;
|
||||
import org.dynmap.MapTile;
|
||||
import org.dynmap.MapType;
|
||||
import org.dynmap.TileHashManager;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.flat.FlatMap.FlatMapTile;
|
||||
import org.dynmap.kzedmap.KzedMap.KzedBufferedImage;
|
||||
import org.dynmap.kzedmap.KzedMap;
|
||||
import org.dynmap.kzedmap.MapTileRenderer;
|
||||
import org.dynmap.utils.FileLockManager;
|
||||
import org.dynmap.utils.MapChunkCache;
|
||||
import org.dynmap.utils.MapIterator;
|
||||
import org.dynmap.utils.Matrix3D;
|
||||
import org.dynmap.utils.Vector3D;
|
||||
import org.json.simple.JSONObject;
|
||||
import java.awt.image.DataBufferInt;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.Raster;
|
||||
|
||||
public class HDMap extends MapType {
|
||||
/* View angles */
|
||||
public double azimuth; /* Angle in degrees from looking north (0), east (90), south (180), or west (270) */
|
||||
public double inclination; /* Angle in degrees from horizontal (0) to vertical (90) */
|
||||
public double scale; /* Scale - tile pixel widths per block */
|
||||
public ColorScheme colorScheme;
|
||||
/* Coordinate space for tiles consists of a plane (X, Y), corresponding to the projection of each tile on to the
|
||||
* plane of the bottom of the world (X positive to the right, Y positive to the top), with Z+ corresponding to the
|
||||
* height above this plane on a vector towards the viewer). Logically, this makes the parallelogram representing the
|
||||
* space contributing to the tile have consistent tile-space X,Y coordinate pairs for both the top and bottom faces
|
||||
* Note that this is a classic right-hand coordinate system, while minecraft's world coordinates are left handed
|
||||
* (X+ is south, Y+ is up, Z+ is east).
|
||||
*/
|
||||
/* Transformation matrix for taking coordinate in world-space (x, y, z) and finding coordinate in tile space (x, y, z) */
|
||||
private Matrix3D world_to_map;
|
||||
private Matrix3D map_to_world;
|
||||
|
||||
/* dimensions of a map tile */
|
||||
public static final int tileWidth = 128;
|
||||
public static final int tileHeight = 128;
|
||||
|
||||
/* Maximum and minimum inclinations */
|
||||
public static final double MAX_INCLINATION = 90.0;
|
||||
public static final double MIN_INCLINATION = 20.0;
|
||||
|
||||
/* Maximum and minimum scale */
|
||||
public static final double MAX_SCALE = 64;
|
||||
public static final double MIN_SCALE = 1;
|
||||
|
||||
private HDMapTileRenderer renderers[];
|
||||
|
||||
public HDMap(ConfigurationNode configuration) {
|
||||
colorScheme = ColorScheme.getScheme(configuration.getString("colorscheme", "default"));
|
||||
|
||||
azimuth = configuration.getDouble("azimuth", 135.0); /* Get azimuth (default to classic kzed POV */
|
||||
inclination = configuration.getDouble("inclination", 60.0);
|
||||
if(inclination > MAX_INCLINATION) inclination = MAX_INCLINATION;
|
||||
if(inclination < MIN_INCLINATION) inclination = MIN_INCLINATION;
|
||||
scale = configuration.getDouble("scale", MIN_SCALE);
|
||||
if(scale < MIN_SCALE) scale = MIN_SCALE;
|
||||
if(scale > MAX_SCALE) scale = MAX_SCALE;
|
||||
Log.info("azimuth=" + azimuth + ", inclination=" + inclination + ", scale=" + scale);
|
||||
|
||||
/* Generate transform matrix for world-to-tile coordinate mapping */
|
||||
/* First, need to fix basic coordinate mismatches before rotation - we want zero azimuth to have north to top
|
||||
* (world -X -> tile +Y) and east to right (world -Z to tile +X), with height being up (world +Y -> tile +Z)
|
||||
*/
|
||||
Matrix3D transform = new Matrix3D(0.0, 0.0, -1.0, -1.0, 0.0, 0.0, 0.0, 1.0, 0.0);
|
||||
/* Next, rotate world counterclockwise around Z axis by azumuth angle */
|
||||
transform.rotateXY(180-azimuth);
|
||||
/* Next, rotate world by (90-inclination) degrees clockwise around +X axis */
|
||||
transform.rotateYZ(90.0-inclination);
|
||||
/* Finally, shear along Z axis to normalize Z to be height above map plane */
|
||||
transform.shearZ(0, Math.tan(Math.toRadians(90.0-inclination)));
|
||||
/* And scale Z to be same scale as world coordinates, and scale X and Y based on setting */
|
||||
transform.scale(scale, scale, Math.sin(Math.toRadians(inclination)));
|
||||
world_to_map = transform;
|
||||
/* Now, generate map to world tranform, by doing opposite actions in reverse order */
|
||||
transform = new Matrix3D();
|
||||
transform.scale(1.0/scale, 1.0/scale, 1/Math.sin(Math.toRadians(inclination)));
|
||||
transform.shearZ(0, -Math.tan(Math.toRadians(90.0-inclination)));
|
||||
transform.rotateYZ(-(90.0-inclination));
|
||||
transform.rotateXY(-180+azimuth);
|
||||
Matrix3D coordswap = new Matrix3D(0.0, -1.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0);
|
||||
transform.multiply(coordswap);
|
||||
map_to_world = transform;
|
||||
|
||||
Log.verboseinfo("Loading renderers for map '" + getClass().toString() + "'...");
|
||||
List<HDMapTileRenderer> renderers = configuration.<HDMapTileRenderer>createInstances("renderers", new Class<?>[0], new Object[0]);
|
||||
this.renderers = new HDMapTileRenderer[renderers.size()];
|
||||
renderers.toArray(this.renderers);
|
||||
Log.verboseinfo("Loaded " + renderers.size() + " renderers for map '" + getClass().toString() + "'.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapTile[] getTiles(Location loc) {
|
||||
DynmapWorld world = MapManager.mapman.getWorld(loc.getWorld().getName());
|
||||
HashSet<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, t.renderer, x, y - 1),
|
||||
new HDMapTile(w, this, t.renderer, x + 1, y),
|
||||
new HDMapTile(w, this, t.renderer, x, y + 1),
|
||||
new HDMapTile(w, this, t.renderer, x - 1, y) };
|
||||
}
|
||||
|
||||
public void addTile(HashSet<MapTile> tiles, DynmapWorld world, int tx, int ty) {
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
tiles.add(new HDMapTile(world, this, renderers[i], tx, ty));
|
||||
}
|
||||
}
|
||||
|
||||
public void invalidateTile(MapTile tile) {
|
||||
}
|
||||
|
||||
private static class Rectangle {
|
||||
double r0x, r0z; /* Coord of corner of rectangle */
|
||||
double s1x, s1z; /* Side vector for one edge */
|
||||
double s2x, s2z; /* Side vector for other edge */
|
||||
public Rectangle(Vector3D v1, Vector3D v2, Vector3D v3) {
|
||||
r0x = v1.x;
|
||||
r0z = v1.z;
|
||||
s1x = v2.x - v1.x;
|
||||
s1z = v2.z - v1.z;
|
||||
s2x = v3.x - v1.x;
|
||||
s2z = v3.z - v1.z;
|
||||
}
|
||||
public Rectangle() {
|
||||
}
|
||||
public void setSquare(double rx, double rz, double s) {
|
||||
this.r0x = rx;
|
||||
this.r0z = rz;
|
||||
this.s1x = s;
|
||||
this.s1z = 0;
|
||||
this.s2x = 0;
|
||||
this.s2z = s;
|
||||
}
|
||||
double getX(int idx) {
|
||||
return r0x + (((idx & 1) == 0)?0:s1x) + (((idx & 2) != 0)?0:s2x);
|
||||
}
|
||||
double getZ(int idx) {
|
||||
return r0z + (((idx & 1) == 0)?0:s1z) + (((idx & 2) != 0)?0:s2z);
|
||||
}
|
||||
/**
|
||||
* Test for overlap of projection of one vector on to anoter
|
||||
*/
|
||||
boolean testoverlap(double rx, double rz, double sx, double sz, Rectangle r) {
|
||||
double rmin_dot_s0 = Double.MAX_VALUE;
|
||||
double rmax_dot_s0 = Double.MIN_VALUE;
|
||||
/* Project each point from rectangle on to vector: find lowest and highest */
|
||||
for(int i = 0; i < 4; i++) {
|
||||
double r_x = r.getX(i) - rx; /* Get relative positon of second vector start to origin */
|
||||
double r_z = r.getZ(i) - rz;
|
||||
double r_dot_s0 = r_x*sx + r_z*sz; /* Projection of start of vector */
|
||||
if(r_dot_s0 < rmin_dot_s0) rmin_dot_s0 = r_dot_s0;
|
||||
if(r_dot_s0 > rmax_dot_s0) rmax_dot_s0 = r_dot_s0;
|
||||
}
|
||||
/* Compute dot products */
|
||||
double s0_dot_s0 = sx*sx + sz*sz; /* End of our side */
|
||||
if((rmax_dot_s0 < 0.0) || (rmin_dot_s0 > s0_dot_s0))
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Test if two rectangles intersect
|
||||
* Based on separating axis theorem
|
||||
*/
|
||||
boolean testRectangleIntesectsRectangle(Rectangle r) {
|
||||
/* Test if projection of each edge of one rectangle on to each edge of the other yields overlap */
|
||||
if(testoverlap(r0x, r0z, s1x, s1z, r) && testoverlap(r0x, r0z, s2x, s2z, r) &&
|
||||
testoverlap(r0x+s1x, r0z+s1z, s2x, s2z, r) && testoverlap(r0x+s2x, r0z+s2z, s1x, s1z, r) &&
|
||||
r.testoverlap(r.r0x, r.r0z, r.s1x, r.s1z, this) && r.testoverlap(r.r0x, r.r0z, r.s2x, r.s2z, this) &&
|
||||
r.testoverlap(r.r0x+r.s1x, r.r0z+r.s1z, r.s2x, r.s2z, this) && r.testoverlap(r.r0x+r.s2x, r.r0z+r.s2z, r.s1x, r.s1z, this)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public String toString() {
|
||||
return "{ " + r0x + "," + r0z + "}x{" + (r0x+s1x) + ","+ + (r0z+s1z) + "}x{" + (r0x+s2x) + "," + (r0z+s2z) + "}";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<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, MapTile tile, File outputFile) {
|
||||
HDMapTile t = (HDMapTile) tile;
|
||||
World w = t.getWorld();
|
||||
boolean rendered = false;
|
||||
Color rslt = new Color();
|
||||
int[] pixel = new int[4];
|
||||
KzedBufferedImage im = KzedMap.allocateBufferedImage(tileWidth, tileHeight);
|
||||
int[] argb_buf = im.argb_buf;
|
||||
|
||||
MapIterator mapiter = cache.getIterator(0, 0, 0);
|
||||
Vector3D top = new Vector3D();
|
||||
Vector3D bottom = new Vector3D();
|
||||
double xbase = t.tx * tileWidth;
|
||||
double ybase = t.ty * tileHeight;
|
||||
boolean odd = false;
|
||||
for(int x = 0; x < tileWidth; x++) {
|
||||
for(int y = 0; y < tileHeight; y++) {
|
||||
top.x = bottom.x = xbase + x + 0.5; /* Start at center of pixel at Y=127.5, bottom at Y=-0.5 */
|
||||
top.y = bottom.y = ybase + y + 0.5;
|
||||
top.z = 127.5; bottom.z = -0.5;
|
||||
map_to_world.transform(top); /* Transform to world coordinates */
|
||||
map_to_world.transform(bottom);
|
||||
raytrace(cache, mapiter, top, bottom, rslt, odd);
|
||||
argb_buf[(tileHeight-y-1)*tileWidth + x] = rslt.getARGB();
|
||||
rendered = true;
|
||||
odd = !odd;
|
||||
}
|
||||
odd = !odd;
|
||||
}
|
||||
/* Test to see if we're unchanged from older tile */
|
||||
TileHashManager hashman = MapManager.mapman.hashman;
|
||||
long crc = hashman.calculateTileHash(argb_buf);
|
||||
boolean tile_update = false;
|
||||
FileLockManager.getWriteLock(outputFile);
|
||||
try {
|
||||
if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), null, t.tx, t.ty))) {
|
||||
/* Wrap buffer as buffered image */
|
||||
Debug.debug("saving image " + outputFile.getPath());
|
||||
if(!outputFile.getParentFile().exists())
|
||||
outputFile.getParentFile().mkdirs();
|
||||
try {
|
||||
FileLockManager.imageIOWrite(im.buf_img, "png", outputFile);
|
||||
} catch (IOException e) {
|
||||
Debug.error("Failed to save image: " + outputFile.getPath(), e);
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e);
|
||||
}
|
||||
MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename()));
|
||||
hashman.updateHashCode(tile.getKey(), null, t.tx, t.ty, crc);
|
||||
tile.getDynmapWorld().enqueueZoomOutUpdate(outputFile);
|
||||
tile_update = true;
|
||||
}
|
||||
else {
|
||||
Debug.debug("skipping image " + outputFile.getPath() + " - hash match");
|
||||
}
|
||||
} finally {
|
||||
FileLockManager.releaseWriteLock(outputFile);
|
||||
KzedMap.freeBufferedImage(im);
|
||||
}
|
||||
MapManager.mapman.updateStatistics(tile, null, true, tile_update, !rendered);
|
||||
return rendered;
|
||||
}
|
||||
|
||||
public enum BlockStep {
|
||||
X_PLUS,
|
||||
Y_PLUS,
|
||||
Z_PLUS,
|
||||
X_MINUS,
|
||||
Y_MINUS,
|
||||
Z_MINUS
|
||||
};
|
||||
|
||||
/**
|
||||
* Trace ray, based on "Voxel Tranversal along a 3D line"
|
||||
*/
|
||||
private void raytrace(MapChunkCache cache, MapIterator mapiter, Vector3D top, Vector3D bottom, Color rslt, boolean odd) {
|
||||
/* Compute total delta on each axis */
|
||||
double dx = Math.abs(bottom.x - top.x);
|
||||
double dy = Math.abs(bottom.y - top.y);
|
||||
double dz = Math.abs(bottom.z - top.z);
|
||||
/* Initial block coord */
|
||||
int x = (int) (Math.floor(top.x));
|
||||
int y = (int) (Math.floor(top.y));
|
||||
int z = (int) (Math.floor(top.z));
|
||||
/* Compute parametric step (dt) per step on each axis */
|
||||
double dt_dx = 1.0 / dx;
|
||||
double dt_dy = 1.0 / dy;
|
||||
double dt_dz = 1.0 / dz;
|
||||
/* Initialize parametric value to 0 (and we're stepping towards 1) */
|
||||
double t = 0;
|
||||
/* Compute number of steps and increments for each */
|
||||
int n = 1;
|
||||
int x_inc, y_inc, z_inc;
|
||||
|
||||
double t_next_y, t_next_x, t_next_z;
|
||||
/* If perpendicular to X axis */
|
||||
if (dx == 0) {
|
||||
x_inc = 0;
|
||||
t_next_x = Double.MAX_VALUE;
|
||||
}
|
||||
/* If bottom is right of top */
|
||||
else if (bottom.x > top.x) {
|
||||
x_inc = 1;
|
||||
n += (int) (Math.floor(bottom.x)) - x;
|
||||
t_next_x = (Math.floor(top.x) + 1 - top.x) * dt_dx;
|
||||
}
|
||||
/* Top is right of bottom */
|
||||
else {
|
||||
x_inc = -1;
|
||||
n += x - (int) (Math.floor(bottom.x));
|
||||
t_next_x = (top.x - Math.floor(top.x)) * dt_dx;
|
||||
}
|
||||
/* If perpendicular to Y axis */
|
||||
if (dy == 0) {
|
||||
y_inc = 0;
|
||||
t_next_y = Double.MAX_VALUE;
|
||||
}
|
||||
/* If bottom is above top */
|
||||
else if (bottom.y > top.y) {
|
||||
y_inc = 1;
|
||||
n += (int) (Math.floor(bottom.y)) - y;
|
||||
t_next_y = (Math.floor(top.y) + 1 - top.y) * dt_dy;
|
||||
}
|
||||
/* If top is above bottom */
|
||||
else {
|
||||
y_inc = -1;
|
||||
n += y - (int) (Math.floor(bottom.y));
|
||||
t_next_y = (top.y - Math.floor(top.y)) * dt_dy;
|
||||
}
|
||||
/* If perpendicular to Z axis */
|
||||
if (dz == 0) {
|
||||
z_inc = 0;
|
||||
t_next_z = Double.MAX_VALUE;
|
||||
}
|
||||
/* If bottom right of top */
|
||||
else if (bottom.z > top.z) {
|
||||
z_inc = 1;
|
||||
n += (int) (Math.floor(bottom.z)) - z;
|
||||
t_next_z = (Math.floor(top.z) + 1 - top.z) * dt_dz;
|
||||
}
|
||||
/* If bottom left of top */
|
||||
else {
|
||||
z_inc = -1;
|
||||
n += z - (int) (Math.floor(bottom.z));
|
||||
t_next_z = (top.z - Math.floor(top.z)) * dt_dz;
|
||||
}
|
||||
/* Walk through scene */
|
||||
rslt.setTransparent();
|
||||
BlockStep laststep = BlockStep.Y_MINUS; /* Last step is down into map */
|
||||
mapiter.initialize(x, y, z);
|
||||
for (; n > 0; --n) {
|
||||
int blocktype = mapiter.getBlockTypeID();
|
||||
if(blocktype != 0) {
|
||||
Color[] clr = colorScheme.colors[blocktype];
|
||||
if(clr != null) {
|
||||
if(laststep == BlockStep.Y_MINUS)
|
||||
rslt.setColor(odd?clr[0]:clr[2]);
|
||||
else if((laststep == BlockStep.X_PLUS) || (laststep == BlockStep.X_MINUS))
|
||||
rslt.setColor(clr[1]);
|
||||
else
|
||||
rslt.setColor(clr[3]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
/* If X step is next best */
|
||||
if((t_next_x <= t_next_y) && (t_next_x <= t_next_z)) {
|
||||
x += x_inc;
|
||||
t = t_next_x;
|
||||
t_next_x += dt_dx;
|
||||
if(x_inc > 0) {
|
||||
laststep = BlockStep.X_PLUS;
|
||||
mapiter.incrementX();
|
||||
}
|
||||
else {
|
||||
laststep = BlockStep.X_MINUS;
|
||||
mapiter.decrementX();
|
||||
}
|
||||
}
|
||||
/* If Y step is next best */
|
||||
else if((t_next_y <= t_next_x) && (t_next_y <= t_next_z)) {
|
||||
y += y_inc;
|
||||
t = t_next_y;
|
||||
t_next_y += dt_dy;
|
||||
if(y_inc > 0) {
|
||||
laststep = BlockStep.Y_PLUS;
|
||||
mapiter.incrementY();
|
||||
if(mapiter.getY() > 127)
|
||||
return;
|
||||
}
|
||||
else {
|
||||
laststep = BlockStep.Y_MINUS;
|
||||
mapiter.decrementY();
|
||||
if(mapiter.getY() < 0)
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* Else, Z step is next best */
|
||||
else {
|
||||
z += z_inc;
|
||||
t = t_next_z;
|
||||
t_next_z += dt_dz;
|
||||
if(z_inc > 0) {
|
||||
laststep = BlockStep.Z_PLUS;
|
||||
mapiter.incrementZ();
|
||||
}
|
||||
else {
|
||||
laststep = BlockStep.Z_MINUS;
|
||||
mapiter.decrementZ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBiomeDataNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRawBiomeDataNeeded() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> baseZoomFilePrefixes() {
|
||||
ArrayList<String> s = new ArrayList<String>();
|
||||
for(HDMapTileRenderer r : renderers) {
|
||||
s.add(r.getName());
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public int baseZoomFileStepSize() { return 1; }
|
||||
|
||||
private static final int[] stepseq = { 3, 1, 2, 0 };
|
||||
|
||||
public int[] zoomFileStepSequence() { return stepseq; }
|
||||
|
||||
/* How many bits of coordinate are shifted off to make big world directory name */
|
||||
public int getBigWorldShift() { return 5; }
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "HDMap";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildClientConfiguration(JSONObject worldObject) {
|
||||
for(HDMapTileRenderer renderer : renderers) {
|
||||
renderer.buildClientConfiguration(worldObject);
|
||||
}
|
||||
}
|
||||
}
|
58
src/main/java/org/dynmap/hdmap/HDMapTile.java
Normal file
58
src/main/java/org/dynmap/hdmap/HDMapTile.java
Normal file
@ -0,0 +1,58 @@
|
||||
package org.dynmap.hdmap;
|
||||
|
||||
import org.dynmap.DynmapWorld;
|
||||
import java.io.File;
|
||||
import org.dynmap.MapTile;
|
||||
|
||||
public class HDMapTile extends MapTile {
|
||||
public HDMap map;
|
||||
public HDMapTileRenderer renderer;
|
||||
public int tx, ty; /* Tile X and Tile Y are in tile coordinates (pixels/tile-size) */
|
||||
private String fname;
|
||||
|
||||
public HDMapTile(DynmapWorld world, HDMap map, HDMapTileRenderer renderer, int tx, int ty) {
|
||||
super(world, map);
|
||||
this.map = map;
|
||||
this.renderer = renderer;
|
||||
this.tx = tx;
|
||||
this.ty = ty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilename() {
|
||||
if(fname == null) {
|
||||
fname = renderer.getName() + "/" + (tx >> 5) + '_' + (ty >> 5) + '/' + tx + "_" + ty + ".png";
|
||||
}
|
||||
return fname;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDayFilename() {
|
||||
return getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getFilename().hashCode() ^ getWorld().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof HDMapTile) {
|
||||
return equals((HDMapTile) obj);
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
public boolean equals(HDMapTile o) {
|
||||
return o.tx == tx && o.ty == ty && o.renderer == o.renderer && o.getWorld().equals(getWorld());
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return getWorld().getName() + "." + renderer.getName();
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getWorld().getName() + ":" + getFilename();
|
||||
}
|
||||
}
|
19
src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java
Normal file
19
src/main/java/org/dynmap/hdmap/HDMapTileRenderer.java
Normal file
@ -0,0 +1,19 @@
|
||||
package org.dynmap.hdmap;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.dynmap.utils.MapChunkCache;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
public interface HDMapTileRenderer {
|
||||
String getName();
|
||||
|
||||
boolean render(MapChunkCache cache, HDMapTile tile, File outputFile);
|
||||
|
||||
void buildClientConfiguration(JSONObject worldObject);
|
||||
|
||||
boolean isBiomeDataNeeded();
|
||||
boolean isRawBiomeDataNeeded();
|
||||
boolean isNightAndDayEnabled();
|
||||
}
|
122
src/main/java/org/dynmap/utils/Matrix3D.java
Normal file
122
src/main/java/org/dynmap/utils/Matrix3D.java
Normal file
@ -0,0 +1,122 @@
|
||||
package org.dynmap.utils;
|
||||
|
||||
/**
|
||||
* Basic 3D matrix math class - prevent dependency on Java 3D for this
|
||||
*/
|
||||
public class Matrix3D {
|
||||
private double m11, m12, m13, m21, m22, m23, m31, m32, m33;
|
||||
/**
|
||||
* Construct identity matrix
|
||||
*/
|
||||
public Matrix3D() {
|
||||
m11 = m22 = m33 = 1.0;
|
||||
m12 = m13 = m21 = m23 = m31 = m32 = 0.0;
|
||||
}
|
||||
/**
|
||||
* Construct matrix with given parms
|
||||
*
|
||||
* @param m11 - first cell of first row
|
||||
* @param m12 - second cell of first row
|
||||
* @param m13 - third cell of first row
|
||||
* @param m21 - first cell of second row
|
||||
* @param m22 - second cell of second row
|
||||
* @param m23 - third cell of second row
|
||||
* @param m31 - first cell of third row
|
||||
* @param m32 - second cell of third row
|
||||
* @param m33 - third cell of third row
|
||||
*/
|
||||
public Matrix3D(double m11, double m12, double m13, double m21, double m22, double m23, double m31, double m32, double m33) {
|
||||
this.m11 = m11; this.m12 = m12; this.m13 = m13;
|
||||
this.m21 = m21; this.m22 = m22; this.m23 = m23;
|
||||
this.m31 = m31; this.m32 = m32; this.m33 = m33;
|
||||
}
|
||||
/**
|
||||
* Multiply matrix by another matrix (this = mat * this), and store result in self
|
||||
* @param mat
|
||||
*/
|
||||
public void multiply(Matrix3D mat) {
|
||||
double new_m11 = mat.m11*m11 + mat.m12*m21 + mat.m13*m31;
|
||||
double new_m12 = mat.m11*m12 + mat.m12*m22 + mat.m13*m32;
|
||||
double new_m13 = mat.m11*m13 + mat.m12*m23 + mat.m13*m33;
|
||||
double new_m21 = mat.m21*m11 + mat.m22*m21 + mat.m23*m31;
|
||||
double new_m22 = mat.m21*m12 + mat.m22*m22 + mat.m23*m32;
|
||||
double new_m23 = mat.m21*m13 + mat.m22*m23 + mat.m23*m33;
|
||||
double new_m31 = mat.m31*m11 + mat.m32*m21 + mat.m33*m31;
|
||||
double new_m32 = mat.m31*m12 + mat.m32*m22 + mat.m33*m32;
|
||||
double new_m33 = mat.m31*m13 + mat.m32*m23 + mat.m33*m33;
|
||||
m11 = new_m11; m12 = new_m12; m13 = new_m13;
|
||||
m21 = new_m21; m22 = new_m22; m23 = new_m23;
|
||||
m31 = new_m31; m32 = new_m32; m33 = new_m33;
|
||||
}
|
||||
/**
|
||||
* Scale each coordinate by given values
|
||||
*
|
||||
* @param s1
|
||||
* @param s2
|
||||
* @param s3
|
||||
*/
|
||||
public void scale(double s1, double s2, double s3) {
|
||||
Matrix3D scalemat = new Matrix3D(s1, 0, 0, 0, s2, 0, 0, 0, s3);
|
||||
multiply(scalemat);
|
||||
}
|
||||
/**
|
||||
* Rotate XY clockwise around +Z axis
|
||||
* @param rot_deg - degrees of rotation
|
||||
*/
|
||||
public void rotateXY(double rot_deg) {
|
||||
double rot_rad = Math.toRadians(rot_deg);
|
||||
double sin_rot = Math.sin(rot_rad);
|
||||
double cos_rot = Math.cos(rot_rad);
|
||||
Matrix3D rotmat = new Matrix3D(cos_rot, sin_rot, 0, -sin_rot, cos_rot, 0, 0, 0, 1);
|
||||
multiply(rotmat);
|
||||
}
|
||||
/**
|
||||
* Rotate YZ clockwise around +X axis
|
||||
* @param rot_deg - degrees of rotation
|
||||
*/
|
||||
public void rotateYZ(double rot_deg) {
|
||||
double rot_rad = Math.toRadians(rot_deg);
|
||||
double sin_rot = Math.sin(rot_rad);
|
||||
double cos_rot = Math.cos(rot_rad);
|
||||
Matrix3D rotmat = new Matrix3D(1, 0, 0, 0, cos_rot, sin_rot, 0, -sin_rot, cos_rot);
|
||||
multiply(rotmat);
|
||||
}
|
||||
/**
|
||||
* Shear along Z axis by factor of X and Y
|
||||
*/
|
||||
public void shearZ(double x_fact, double y_fact) {
|
||||
Matrix3D shearmat = new Matrix3D(1, 0, 0, 0, 1, 0, x_fact, y_fact, 1);
|
||||
multiply(shearmat);
|
||||
}
|
||||
/**
|
||||
* Transform a given vector using the matrix
|
||||
*/
|
||||
public final void transform(double[] v) {
|
||||
double v1 = m11*v[0] + m12*v[1] + m13*v[2];
|
||||
double v2 = m21*v[0] + m22*v[1] + m23*v[2];
|
||||
double v3 = m31*v[0] + m32*v[1] + m33*v[2];
|
||||
v[0] = v1; v[1] = v2; v[2] = v3;
|
||||
}
|
||||
/**
|
||||
* Transform a given vector using the matrix
|
||||
*/
|
||||
public final void transform(Vector3D v) {
|
||||
double v1 = m11*v.x + m12*v.y + m13*v.z;
|
||||
double v2 = m21*v.x + m22*v.y + m23*v.z;
|
||||
double v3 = m31*v.x + m32*v.y + m33*v.z;
|
||||
v.x = v1; v.y = v2; v.z = v3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a given vector using the matrix - put result in provided output vector
|
||||
*/
|
||||
public final void transform(Vector3D v, Vector3D outv) {
|
||||
outv.x = m11*v.x + m12*v.y + m13*v.z;
|
||||
outv.y = m21*v.x + m22*v.y + m23*v.z;
|
||||
outv.z = m31*v.x + m32*v.y + m33*v.z;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[ [" + m11 + " " + m12 + " " + m13 + "] [" + m21 + " " + m22 + " " + m23 + "] [" + m31 + " " + m32 + " " + m33 + "] ]";
|
||||
}
|
||||
}
|
16
src/main/java/org/dynmap/utils/Vector3D.java
Normal file
16
src/main/java/org/dynmap/utils/Vector3D.java
Normal file
@ -0,0 +1,16 @@
|
||||
package org.dynmap.utils;
|
||||
import org.bukkit.Location;
|
||||
/**
|
||||
* Simple vector class
|
||||
*/
|
||||
public class Vector3D {
|
||||
public double x, y, z;
|
||||
|
||||
public Vector3D() { x = y = z = 0.0; }
|
||||
|
||||
public void setFromLocation(Location l) { x = l.getX(); y = l.getY(); z = l.getZ(); }
|
||||
|
||||
public String toString() {
|
||||
return "{ " + x + ", " + y + ", " + z + " }";
|
||||
}
|
||||
}
|
@ -26,6 +26,7 @@
|
||||
<script type="text/javascript" src="js/custommarker.js"></script>
|
||||
<script type="text/javascript" src="js/minecraft.js"></script>
|
||||
<script type="text/javascript" src="js/map.js"></script>
|
||||
<script type="text/javascript" src="js/hdmap.js"></script>
|
||||
<script type="text/javascript" src="js/kzedmaps.js"></script>
|
||||
<script type="text/javascript" src="js/flatmap.js"></script>
|
||||
<script type="text/javascript" src="config.js"></script>
|
||||
|
78
web/js/hdmap.js
Normal file
78
web/js/hdmap.js
Normal file
@ -0,0 +1,78 @@
|
||||
function HDProjection() {}
|
||||
HDProjection.prototype = {
|
||||
extrazoom: 0,
|
||||
fromLatLngToPoint: function(latLng) {
|
||||
return new google.maps.Point(latLng.lng()*config.tileWidth,latLng.lat()*config.tileHeight);
|
||||
},
|
||||
fromPointToLatLng: function(point) {
|
||||
return new google.maps.LatLng(point.y/config.tileHeight, point.x/config.tileWidth);
|
||||
},
|
||||
fromWorldToLatLng: function(x, y, z) {
|
||||
return new google.maps.LatLng(-z / config.tileWidth / (1 << this.extrazoom), x / config.tileHeight / (1 << this.extrazoom));
|
||||
}
|
||||
};
|
||||
|
||||
function HDMapType(configuration) {
|
||||
$.extend(this, configuration); }
|
||||
HDMapType.prototype = $.extend(new DynMapType(), {
|
||||
constructor: HDMapType,
|
||||
projection: new HDProjection(),
|
||||
tileSize: new google.maps.Size(128.0, 128.0),
|
||||
minZoom: 0,
|
||||
maxZoom: 3,
|
||||
prefix: null,
|
||||
getTile: function(coord, zoom, doc) {
|
||||
var tileSize = 128;
|
||||
var imgSize;
|
||||
var tileName;
|
||||
|
||||
var extrazoom = this.dynmap.world.extrazoomout;
|
||||
if(zoom < extrazoom) {
|
||||
var scale = 1 << (extrazoom-zoom);
|
||||
var zprefix = "zzzzzzzzzzzz".substring(0, extrazoom-zoom);
|
||||
tileName = this.prefix + '/' + ((scale*coord.x) >> 5) + '_' + ((-scale*coord.y) >> 5) +
|
||||
'/' + zprefix + "_" + (scale*coord.x) + '_' + (-scale*coord.y) + '.png';
|
||||
imgSize = 128;
|
||||
}
|
||||
else {
|
||||
tileName = this.prefix + '/' + (coord.x >> 5) + '_' + ((-coord.y) >> 5) +
|
||||
'/' + coord.x + '_' + (-coord.y) + '.png';
|
||||
imgSize = Math.pow(2, 7+zoom-extrazoom);
|
||||
}
|
||||
var tile = $('<div/>')
|
||||
.addClass('tile')
|
||||
.css({
|
||||
width: tileSize + 'px',
|
||||
height: tileSize + 'px'
|
||||
});
|
||||
var img = $('<img/>')
|
||||
.attr('src', this.dynmap.getTileUrl(tileName))
|
||||
.error(function() { img.hide(); })
|
||||
.bind('load', function() { img.show(); })
|
||||
.css({
|
||||
width: imgSize +'px',
|
||||
height: imgSize + 'px',
|
||||
borderStyle: 'none'
|
||||
})
|
||||
.hide()
|
||||
.appendTo(tile);
|
||||
this.dynmap.registerTile(this, tileName, img);
|
||||
//this.dynmap.unregisterTile(this, tileName);
|
||||
return tile.get(0);
|
||||
},
|
||||
updateTileSize: function(zoom) {
|
||||
var size;
|
||||
var extrazoom = this.dynmap.world.extrazoomout;
|
||||
this.projection.extrazoom = extrazoom;
|
||||
this.maxZoom = 3 + extrazoom;
|
||||
if (zoom <= extrazoom) {
|
||||
size = 128;
|
||||
}
|
||||
else {
|
||||
size = Math.pow(2, 7+zoom-extrazoom);
|
||||
}
|
||||
this.tileSize = new google.maps.Size(size, size);
|
||||
}
|
||||
});
|
||||
|
||||
maptypes.HDMapType = function(configuration) { return new HDMapType(configuration); };
|
Loading…
Reference in New Issue
Block a user