mirror of https://github.com/webbukkit/dynmap.git
549 lines
17 KiB
Java
549 lines
17 KiB
Java
package org.dynmap.markers.impl;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import org.dynmap.ConfigurationNode;
|
|
import org.dynmap.DynmapWorld;
|
|
import org.dynmap.hdmap.HDPerspective;
|
|
import org.dynmap.markers.CircleMarker;
|
|
import org.dynmap.markers.EnterExitMarker;
|
|
import org.dynmap.markers.MarkerSet;
|
|
import org.dynmap.markers.EnterExitMarker.EnterExitText;
|
|
import org.dynmap.markers.impl.MarkerAPIImpl.MarkerUpdate;
|
|
import org.dynmap.utils.Vector3D;
|
|
|
|
class CircleMarkerImpl implements CircleMarker, EnterExitMarker {
|
|
private String markerid;
|
|
private String label;
|
|
private boolean markup;
|
|
private String desc;
|
|
private MarkerSetImpl markerset;
|
|
private String world;
|
|
private String normalized_world;
|
|
private boolean ispersistent;
|
|
private double x;
|
|
private double y;
|
|
private double z;
|
|
private double xr;
|
|
private double zr;
|
|
private int lineweight = 3;
|
|
private double lineopacity = 0.8;
|
|
private int linecolor = 0xFF0000;
|
|
private double fillopacity = 0.35;
|
|
private int fillcolor = 0xFF0000;
|
|
private boolean boostflag = false;
|
|
private int minzoom = -1;
|
|
private int maxzoom = -1;
|
|
private EnterExitText greeting;
|
|
private EnterExitText farewell;
|
|
|
|
private static class BoundingBox {
|
|
double xmin, xmax;
|
|
double ymin, ymax;
|
|
double xp[];
|
|
double yp[];
|
|
}
|
|
private Map<String, BoundingBox> bb_cache = null;
|
|
|
|
/**
|
|
* Create circle marker
|
|
* @param id - marker ID
|
|
* @param lbl - label
|
|
* @param markup - if true, label is HTML markup
|
|
* @param world - world id
|
|
* @param x - x center
|
|
* @param y - y center
|
|
* @param z - z center
|
|
* @param xr - radius on X axis
|
|
* @param zr - radius on Z axis
|
|
* @param persistent - true if persistent
|
|
* @param set - marker set
|
|
*/
|
|
CircleMarkerImpl(String id, String lbl, boolean markup, String world, double x, double y, double z, double xr, double zr, boolean persistent, MarkerSetImpl set) {
|
|
markerid = id;
|
|
if(lbl != null)
|
|
label = lbl;
|
|
else
|
|
label = id;
|
|
this.markup = markup;
|
|
this.x = x; this.y = y; this.z = z;
|
|
this.xr = xr; this.zr = zr;
|
|
this.world = world;
|
|
this.normalized_world = DynmapWorld.normalizeWorldName(world);
|
|
this.desc = null;
|
|
this.minzoom = -1;
|
|
this.maxzoom = -1;
|
|
ispersistent = persistent;
|
|
markerset = set;
|
|
}
|
|
/**
|
|
* Make bare area marker - used for persistence load
|
|
* @param id - marker ID
|
|
* @param set - marker set
|
|
*/
|
|
CircleMarkerImpl(String id, MarkerSetImpl set) {
|
|
markerid = id;
|
|
markerset = set;
|
|
label = id;
|
|
markup = false;
|
|
desc = null;
|
|
world = normalized_world = "world";
|
|
this.minzoom = -1;
|
|
this.maxzoom = -1;
|
|
x = z = 0;
|
|
y = 64;
|
|
xr = zr = 0;
|
|
}
|
|
/**
|
|
* Load marker from configuration node
|
|
* @param node - configuration node
|
|
*/
|
|
boolean loadPersistentData(ConfigurationNode node) {
|
|
label = node.getString("label", markerid);
|
|
markup = node.getBoolean("markup", false);
|
|
world = node.getString("world", "world");
|
|
normalized_world = DynmapWorld.normalizeWorldName(world);
|
|
x = node.getDouble("x", 0);
|
|
y = node.getDouble("y", 64);
|
|
z = node.getDouble("z", 0);
|
|
xr = node.getDouble("xr", 0);
|
|
zr = node.getDouble("zr", 0);
|
|
desc = node.getString("desc", null);
|
|
lineweight = node.getInteger("strokeWeight", -1);
|
|
if(lineweight == -1) { /* Handle typo-saved value */
|
|
lineweight = node.getInteger("stokeWeight", 3);
|
|
}
|
|
lineopacity = node.getDouble("strokeOpacity", 0.8);
|
|
linecolor = node.getInteger("strokeColor", 0xFF0000);
|
|
fillopacity = node.getDouble("fillOpacity", 0.35);
|
|
fillcolor = node.getInteger("fillColor", 0xFF0000);
|
|
boostflag = node.getBoolean("boostFlag", false);
|
|
minzoom = node.getInteger("minzoom", -1);
|
|
maxzoom = node.getInteger("maxzoom", -1);
|
|
String gt = node.getString("greeting", null);
|
|
String gst = node.getString("greetingsub", null);
|
|
if ((gt != null) || (gst != null)) {
|
|
greeting = new EnterExitText();
|
|
greeting.title = gt;
|
|
greeting.subtitle = gst;
|
|
}
|
|
String ft = node.getString("farewell", null);
|
|
String fst = node.getString("farewellsub", null);
|
|
if ((ft != null) || (fst != null)) {
|
|
farewell = new EnterExitText();
|
|
farewell.title = ft;
|
|
farewell.subtitle = fst;
|
|
}
|
|
|
|
ispersistent = true; /* Loaded from config, so must be */
|
|
|
|
return true;
|
|
}
|
|
|
|
void cleanup() {
|
|
markerset = null;
|
|
bb_cache = null;
|
|
}
|
|
|
|
@Override
|
|
public String getUniqueMarkerID() {
|
|
if (markerset != null) {
|
|
return markerset + ":circle:" + markerid;
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getMarkerID() {
|
|
return markerid;
|
|
}
|
|
|
|
@Override
|
|
public MarkerSet getMarkerSet() {
|
|
return markerset;
|
|
}
|
|
|
|
@Override
|
|
public void deleteMarker() {
|
|
if(markerset == null) return;
|
|
markerset.removeCircleMarker(this); /* Remove from our marker set (notified by set) */
|
|
cleanup();
|
|
}
|
|
|
|
@Override
|
|
public boolean isPersistentMarker() {
|
|
return ispersistent;
|
|
}
|
|
|
|
@Override
|
|
public String getLabel() {
|
|
return label;
|
|
}
|
|
|
|
@Override
|
|
public void setLabel(String lbl) {
|
|
setLabel(lbl, false);
|
|
}
|
|
|
|
@Override
|
|
public void setLabel(String lbl, boolean markup) {
|
|
label = lbl;
|
|
this.markup = markup;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
|
|
/**
|
|
* Get configuration node to be saved
|
|
* @return node
|
|
*/
|
|
Map<String, Object> getPersistentData() {
|
|
if(!ispersistent) /* Nothing if not persistent */
|
|
return null;
|
|
HashMap<String, Object> node = new HashMap<String, Object>();
|
|
node.put("label", label);
|
|
node.put("markup", markup);
|
|
node.put("x", x);
|
|
node.put("y", y);
|
|
node.put("z", z);
|
|
node.put("xr", xr);
|
|
node.put("zr", zr);
|
|
node.put("world", world);
|
|
if(desc != null)
|
|
node.put("desc", desc);
|
|
node.put("strokeWeight", lineweight);
|
|
node.put("strokeOpacity", lineopacity);
|
|
node.put("strokeColor", linecolor);
|
|
node.put("fillOpacity", fillopacity);
|
|
node.put("fillColor", fillcolor);
|
|
if(boostflag) {
|
|
node.put("boostFlag", true);
|
|
}
|
|
if (minzoom >= 0) {
|
|
node.put("minzoom", minzoom);
|
|
}
|
|
if (maxzoom >= 0) {
|
|
node.put("maxzoom", maxzoom);
|
|
}
|
|
if (greeting != null) {
|
|
if (greeting.title != null) {
|
|
node.put("greeting", greeting.title);
|
|
}
|
|
if (greeting.subtitle != null) {
|
|
node.put("greetingsub", greeting.subtitle);
|
|
}
|
|
}
|
|
if (farewell != null) {
|
|
if (farewell.title != null) {
|
|
node.put("farewell", farewell.title);
|
|
}
|
|
if (farewell.subtitle != null) {
|
|
node.put("farewellsub", farewell.subtitle);
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
@Override
|
|
public String getWorld() {
|
|
return world;
|
|
}
|
|
@Override
|
|
public String getNormalizedWorld() {
|
|
return normalized_world;
|
|
}
|
|
@Override
|
|
public boolean isLabelMarkup() {
|
|
return markup;
|
|
}
|
|
@Override
|
|
public void setDescription(String desc) {
|
|
if((this.desc == null) || (this.desc.equals(desc) == false)) {
|
|
this.desc = desc;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
}
|
|
/**
|
|
* Get marker description
|
|
* @return descrption
|
|
*/
|
|
public String getDescription() {
|
|
return this.desc;
|
|
}
|
|
@Override
|
|
public void setLineStyle(int weight, double opacity, int color) {
|
|
if((weight != lineweight) || (opacity != lineopacity) || (color != linecolor)) {
|
|
lineweight = weight;
|
|
lineopacity = opacity;
|
|
linecolor = color;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
}
|
|
@Override
|
|
public int getLineWeight() {
|
|
return lineweight;
|
|
}
|
|
@Override
|
|
public double getLineOpacity() {
|
|
return lineopacity;
|
|
}
|
|
@Override
|
|
public int getLineColor() {
|
|
return linecolor;
|
|
}
|
|
@Override
|
|
public void setFillStyle(double opacity, int color) {
|
|
if((opacity != fillopacity) || (color != fillcolor)) {
|
|
fillopacity = opacity;
|
|
fillcolor = color;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
}
|
|
@Override
|
|
public double getFillOpacity() {
|
|
return fillopacity;
|
|
}
|
|
@Override
|
|
public int getFillColor() {
|
|
return fillcolor;
|
|
}
|
|
@Override
|
|
public double getCenterX() {
|
|
return x;
|
|
}
|
|
@Override
|
|
public double getCenterY() {
|
|
return y;
|
|
}
|
|
@Override
|
|
public double getCenterZ() {
|
|
return z;
|
|
}
|
|
@Override
|
|
public void setCenter(String worldid, double x, double y, double z) {
|
|
boolean updated = false;
|
|
if(!worldid.equals(world)) {
|
|
world = worldid;
|
|
normalized_world = DynmapWorld.normalizeWorldName(world);
|
|
updated = true;
|
|
}
|
|
if(this.x != x) {
|
|
this.x = x;
|
|
updated = true;
|
|
}
|
|
if(this.y != y) {
|
|
this.y = y;
|
|
updated = true;
|
|
}
|
|
if(this.z != z) {
|
|
this.z = z;
|
|
updated = true;
|
|
}
|
|
if(updated) {
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
bb_cache = null;
|
|
}
|
|
}
|
|
@Override
|
|
public double getRadiusX() {
|
|
return xr;
|
|
}
|
|
@Override
|
|
public double getRadiusZ() {
|
|
return zr;
|
|
}
|
|
@Override
|
|
public void setRadius(double xr, double zr) {
|
|
if((this.xr != xr) || (this.zr != zr)) {
|
|
this.xr = xr;
|
|
this.zr = zr;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
bb_cache = null;
|
|
}
|
|
}
|
|
@Override
|
|
public void setMarkerSet(MarkerSet newset) {
|
|
if(markerset != null) {
|
|
markerset.removeCircleMarker(this); /* Remove from our marker set (notified by set) */
|
|
}
|
|
markerset = (MarkerSetImpl)newset;
|
|
markerset.insertCircleMarker(this);
|
|
}
|
|
@Override
|
|
public void setBoostFlag(boolean bflag) {
|
|
if (this.boostflag != bflag) {
|
|
this.boostflag = bflag;
|
|
if (markerset != null) {
|
|
setMarkerSet(markerset);
|
|
}
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
}
|
|
@Override
|
|
public boolean getBoostFlag() {
|
|
return boostflag;
|
|
}
|
|
|
|
final boolean testTileForBoostMarkers(DynmapWorld w, HDPerspective perspective, double tile_x, double tile_y, double tile_dim) {
|
|
Map<String, BoundingBox> bbc = bb_cache;
|
|
if(bbc == null) {
|
|
bbc = new ConcurrentHashMap<String, BoundingBox>();
|
|
}
|
|
BoundingBox bb = bbc.get(perspective.getName());
|
|
if (bb == null) { // No cached bounding box, so generate it
|
|
bb = new BoundingBox();
|
|
Vector3D v = new Vector3D();
|
|
Vector3D v2 = new Vector3D();
|
|
bb.xmin = Double.MAX_VALUE;
|
|
bb.xmax = -Double.MAX_VALUE;
|
|
bb.ymin = Double.MAX_VALUE;
|
|
bb.ymax = -Double.MAX_VALUE;
|
|
int cnt = 16; // Just do 16 points for now
|
|
bb.xp = new double[cnt];
|
|
bb.yp = new double[cnt];
|
|
for(int i = 0; i < cnt; i++) {
|
|
v.x = this.x + (this.xr * Math.cos(2.0*Math.PI*i/cnt));
|
|
v.y = this.y;
|
|
v.z = this.z + (this.zr * Math.sin(2.0*Math.PI*i/cnt));
|
|
perspective.transformWorldToMapCoord(v, v2); // Transform to map coord
|
|
if(v2.x < bb.xmin) bb.xmin = v2.x;
|
|
if(v2.y < bb.ymin) bb.ymin = v2.y;
|
|
if(v2.x > bb.xmax) bb.xmax = v2.x;
|
|
if(v2.y > bb.ymax) bb.ymax = v2.y;
|
|
bb.xp[i] = v2.x;
|
|
bb.yp[i] = v2.y;
|
|
}
|
|
//Log.info("x=" + bb.xmin + " - " + bb.xmax + ", y=" + bb.ymin + " - " + bb.ymax);
|
|
bbc.put(perspective.getName(), bb);
|
|
bb_cache = bbc;
|
|
}
|
|
final double tile_x2 = tile_x + tile_dim;
|
|
final double tile_y2 = tile_y + tile_dim;
|
|
if ((bb.xmin > tile_x2) || (bb.xmax < tile_x) || (bb.ymin > tile_y2) || (bb.ymax < tile_y)) {
|
|
//Log.info("tile: " + tile_x + " / " + tile_y + " - miss");
|
|
return false;
|
|
}
|
|
final int cnt = bb.xp.length;
|
|
final double[] px = bb.xp;
|
|
final double[] py = bb.yp;
|
|
/* Now see if tile square intersects polygon - start with seeing if any point inside */
|
|
if(MarkerImpl.testPointInPolygon(tile_x, tile_y, px, py)) {
|
|
return true; // If tile corner inside, we intersect
|
|
}
|
|
if(MarkerImpl.testPointInPolygon(tile_x2, tile_y, px, py)) {
|
|
return true; // If tile corner inside, we intersect
|
|
}
|
|
if(MarkerImpl.testPointInPolygon(tile_x, tile_y2, px, py)) {
|
|
return true; // If tile corner inside, we intersect
|
|
}
|
|
if(MarkerImpl.testPointInPolygon(tile_x2, tile_y2, px, py)) {
|
|
return true; // If tile corner inside, we intersect
|
|
}
|
|
/* Test if any polygon corners are inside square */
|
|
for(int i = 0; i < cnt; i++) {
|
|
if((px[i] >= tile_x) && (px[i] <= tile_x2) && (py[i] >= tile_y) && (py[i] <= tile_y2)) {
|
|
return true; // If poly corner inside tile, we intersect
|
|
}
|
|
}
|
|
// Otherwise, only intersects if at least one edge crosses
|
|
//for (int i = 0, j = cnt-1; i < cnt; j = i++) {
|
|
// // Test for X=tile_x side
|
|
// if ((px[i] < tile_x) && (px[j] >= tile_x) && ()
|
|
// }
|
|
//Log.info("tile: " + tile_x + " / " + tile_y + " - hit");
|
|
return false;
|
|
}
|
|
@Override
|
|
public int getMinZoom() {
|
|
return minzoom;
|
|
}
|
|
@Override
|
|
public void setMinZoom(int zoom) {
|
|
if (zoom < 0) zoom = -1;
|
|
if (this.minzoom == zoom) return;
|
|
this.minzoom = zoom;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
@Override
|
|
public int getMaxZoom() {
|
|
return maxzoom;
|
|
}
|
|
@Override
|
|
public void setMaxZoom(int zoom) {
|
|
if (zoom < 0) zoom = -1;
|
|
if (this.maxzoom == zoom) return;
|
|
this.maxzoom = zoom;
|
|
MarkerAPIImpl.circleMarkerUpdated(this, MarkerUpdate.UPDATED);
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
@Override
|
|
public EnterExitText getGreetingText() {
|
|
return greeting;
|
|
}
|
|
@Override
|
|
public EnterExitText getFarewellText() {
|
|
return farewell;
|
|
}
|
|
@Override
|
|
public void setGreetingText(String title, String subtitle) {
|
|
if ((title != null) || (subtitle != null)) {
|
|
greeting = new EnterExitText();
|
|
greeting.title = title;
|
|
greeting.subtitle = subtitle;
|
|
}
|
|
else {
|
|
greeting = null;
|
|
}
|
|
if (markerset != null) {
|
|
setMarkerSet(markerset);
|
|
}
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
@Override
|
|
public void setFarewellText(String title, String subtitle) {
|
|
if ((title != null) || (subtitle != null)) {
|
|
farewell = new EnterExitText();
|
|
farewell.title = title;
|
|
farewell.subtitle = subtitle;
|
|
}
|
|
else {
|
|
farewell = null;
|
|
}
|
|
if (markerset != null) {
|
|
setMarkerSet(markerset);
|
|
}
|
|
if(ispersistent)
|
|
MarkerAPIImpl.saveMarkers();
|
|
}
|
|
@Override
|
|
public boolean testIfPointWithinMarker(String worldid, double x, double y, double z) {
|
|
// Wrong world
|
|
if (!worldid.equals(this.world)) {
|
|
return false;
|
|
}
|
|
// Test if inside ellipse
|
|
double dx = ((x - this.x) * (x - this.x)) / (xr * xr);
|
|
double dz = ((z - this.z) * (z - this.z)) / (zr * zr);
|
|
return (dx + dz) <= 1.0;
|
|
}
|
|
}
|