From 60448dda095d01f3269d5e7fef20d4e42d963cd9 Mon Sep 17 00:00:00 2001 From: fescen9 Date: Wed, 8 Dec 2010 07:18:12 +0000 Subject: [PATCH] Merge of trunk version 9-19 --- Cache.java | 114 ++++++++++++++++++++++++++++++ MapManager.java | 151 +++++++++++++++++++++++++++++++++++++-- MapTile.java | 101 +++++++++++++++++++++++++-- WebServerRequest.java | 4 +- doc/README | 3 + misc/CacheTest.java | 72 +++++++++++++++++++ readme.txt | 1 + web/map.js | 159 +++++++++++++++++++----------------------- 8 files changed, 506 insertions(+), 99 deletions(-) create mode 100644 Cache.java create mode 100644 doc/README create mode 100644 misc/CacheTest.java diff --git a/Cache.java b/Cache.java new file mode 100644 index 00000000..5a4e13ea --- /dev/null +++ b/Cache.java @@ -0,0 +1,114 @@ +import java.util.HashMap; + +public class Cache +{ + private final int size; + private int len; + + private CacheNode head; + private CacheNode tail; + + private class CacheNode + { + public CacheNode prev; + public CacheNode next; + public K key; + public V value; + + public CacheNode(K key, V value) + { + this.key = key; + this.value = value; + prev = null; + next = null; + } + + public void unlink() + { + if(prev == null) { + head = next; + } else { + prev.next = next; + } + + if(next == null) { + tail = prev; + } else { + next.prev = prev; + } + + prev = null; + next = null; + + len --; + } + + public void append() + { + if(tail == null) { + head = this; + tail = this; + } else { + tail.next = this; + prev = tail; + tail = this; + } + + len ++; + } + } + + private HashMap map; + + public Cache(int size) + { + this.size = size; + len = 0; + + head = null; + tail = null; + + map = new HashMap(); + } + + /* returns value for key, if key exists in the cache + * otherwise null */ + public V get(K key) + { + CacheNode n = map.get(key); + if(n == null) + return null; + return n.value; + } + + /* puts a new key-value pair in the cache + * if the key existed already, the value is updated, and the old value is returned + * if the key didn't exist, it is added; the oldest value (now pushed out of the + * cache) may be returned, or null if the cache isn't yet full */ + public V put(K key, V value) + { + CacheNode n = map.get(key); + if(n == null) { + V ret = null; + + if(len >= size) { + CacheNode first = head; + first.unlink(); + map.remove(first.key); + ret = first.value; + } + + CacheNode add = new CacheNode(key, value); + add.append(); + map.put(key, add); + + return ret; + } else { + n.unlink(); + V old = n.value; + n.value = value; + n.append(); + return old; + } + } +} diff --git a/MapManager.java b/MapManager.java index 135760d6..74ab6362 100644 --- a/MapManager.java +++ b/MapManager.java @@ -1,15 +1,13 @@ -import java.awt.Color; -import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; @@ -19,6 +17,15 @@ import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; +import java.awt.image.BufferedImage; + +import java.io.File; +import java.io.IOException; + +import java.awt.*; +import java.awt.image.*; +import javax.imageio.ImageIO; + public class MapManager extends Thread { protected static final Logger log = Logger.getLogger("Minecraft"); @@ -26,6 +33,11 @@ public class MapManager extends Thread { public static final int tileWidth = 128; public static final int tileHeight = 128; + /* (logical!) dimensions of a zoomed out map tile + * must be twice the size of the normal tile */ + public static final int zTileWidth = 256; + public static final int zTileHeight = 256; + /* lock for our data structures */ public static final Object lock = new Object(); @@ -73,6 +85,12 @@ public class MapManager extends Thread { /* hashmap of markers */ public HashMap markers = null; + /* cache this many zoomed-out tiles */ + public static final int zoomCacheSize = 64; + + /* zoomed-out tile cache */ + public Cache zoomCache; + public void debug(String msg) { if(debugPlayer == null) return; @@ -100,6 +118,7 @@ public class MapManager extends Thread { tileStore = new HashMap(); staleTiles = new LinkedList(); tileUpdates = new LinkedList(); + zoomCache = new Cache(zoomCacheSize); markers = new HashMap(); } @@ -122,6 +141,25 @@ public class MapManager extends Thread { return y - (y % tileHeight); } + /* zoomed-out tile X for tile position x */ + static int ztilex(int x) + { + if(x < 0) + return x + x % zTileWidth; + else + return x - (x % zTileWidth); + } + + /* zoomed-out tile Y for tile position y */ + static int ztiley(int y) + { + if(y < 0) + return y + y % zTileHeight; + //return y - (zTileHeight + (y % zTileHeight)); + else + return y - (y % zTileHeight); + } + /* initialize and start map manager */ public void startManager() { @@ -346,7 +384,8 @@ public class MapManager extends Thread { MapTile t = tileStore.get(key); if(t == null) { /* no maptile exists, need to create one */ - t = new MapTile(px, py); + + t = new MapTile(px, py, ztilex(px), ztiley(py)); tileStore.put(key, t); return t; } else { @@ -407,6 +446,110 @@ public class MapManager extends Thread { open.add(getTileByPosition(t.px, t.py - tileHeight)); } } + + /* regenerate all zoom tiles, starting at position */ + public void regenerateZoom(int x, int y, int z) + { + int dx = x - anchorx; + int dy = y - anchory; + int dz = z - anchorz; + int px = dx + dz; + int py = dx - dz - dy; + + int zpx = ztilex(tilex(px)); + int zpy = ztiley(tiley(py)); + + class Pair { + public int x; + public int y; + public Pair(int x, int y) + { + this.x = x; + this.y = y; + } + + public int hashCode() + { + return (x << 16) ^ y; + } + + public boolean equals(Pair o) + { + return x == o.x && y == o.y; + } + } + + HashSet visited = new HashSet(); + HashSet open = new HashSet(); + } + + /* regenerate zoom-out tile + * returns number of valid subtiles */ + public int regenZoomTile(int zpx, int zpy) + { + int px1 = (zpx >= 0) ? zpx + tileWidth : zpx - zTileWidth; + int py1 = (zpy >= 0) ? zpy : zpy - zTileHeight; + int px2 = px1 - tileWidth; + int py2 = py1 + tileHeight; + + MapTile t1 = getTileByPosition(px1, py1); + MapTile t2 = getTileByPosition(px2, py1); + MapTile t3 = getTileByPosition(px1, py2); + MapTile t4 = getTileByPosition(px2, py2); + + BufferedImage im1 = t1.loadTile(this); + BufferedImage im2 = t2.loadTile(this); + BufferedImage im3 = t3.loadTile(this); + BufferedImage im4 = t4.loadTile(this); + + BufferedImage zIm = new BufferedImage(MapManager.tileWidth, MapManager.tileHeight, BufferedImage.TYPE_INT_RGB); + WritableRaster zr = zIm.getRaster(); + Graphics2D g2 = zIm.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + + int scw = tileWidth / 2; + int sch = tileHeight / 2; + + int good = 0; + + if(im1 != null) { + g2.drawImage(im1, 0, 0, scw, sch, null); + good ++; + } + + if(im2 != null) { + g2.drawImage(im2, scw, 0, scw, sch, null); + good ++; + } + + if(im3 != null) { + g2.drawImage(im3, 0, sch, scw, sch, null); + good ++; + } + + if(im4 != null) { + g2.drawImage(im4, scw, sch, scw, sch, null); + good ++; + } + + if(good == 0) { + return 0; + } + + String zPath = t1.getZoomPath(this); + /* save zoom-out tile */ + try { + File file = new File(zPath); + ImageIO.write(zIm, "png", file); + log.info("regenZoomTile saved zoom-out tile at " + zPath); + } catch(IOException e) { + log.log(Level.SEVERE, "Failed to save zoom-out tile: " + zPath, e); + } catch(java.lang.NullPointerException e) { + log.log(Level.SEVERE, "Failed to save zoom-out tile (NullPointerException): " + zPath, e); + } + + return good; + } /* adds a marker to the map */ public boolean addMapMarker(Player player, String name, double px, double py, double pz) diff --git a/MapTile.java b/MapTile.java index 0717d1c3..878a4b90 100644 --- a/MapTile.java +++ b/MapTile.java @@ -15,6 +15,9 @@ public class MapTile { /* projection position */ public int px, py; + /* projection position of zoom-out tile */ + public int zpx, zpy; + /* minecraft space origin */ public int mx, my, mz; @@ -22,10 +25,12 @@ public class MapTile { boolean stale = false; /* create new MapTile */ - public MapTile(int px, int py) + public MapTile(int px, int py, int zpx, int zpy) { this.px = px; this.py = py; + this.zpx = zpx; + this.zpy = zpy; mx = MapManager.anchorx + px / 2 + py / 2; my = MapManager.anchory; @@ -118,21 +123,105 @@ public class MapTile { String path = getPath(mgr); File file = new File(path); ImageIO.write(im, "png", file); - } catch(java.lang.NullPointerException e) { - // IOException is not enough, a NullPointerException often occurs due to this issue - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5034864 - log.log(Level.SEVERE, "Failed to save tile (NullPointerException): " + getPath(mgr), e); } catch(IOException e) { log.log(Level.SEVERE, "Failed to save tile: " + getPath(mgr), e); + } catch(java.lang.NullPointerException e) { + log.log(Level.SEVERE, "Failed to save tile (NullPointerException): " + getPath(mgr), e); + } + + /* now update zoom-out tile */ + String zPath = getZoomPath(mgr); + BufferedImage zIm = mgr.zoomCache.get(zPath); + + if(zIm == null) { + /* zoom-out tile doesn't exist - try to load it from disk */ + + mgr.debug("Trying to load zoom-out tile: " + zPath); + + try { + File file = new File(zPath); + zIm = ImageIO.read(file); + } catch(IOException e) { + } + + + if(zIm == null) { + mgr.debug("Failed to load zoom-out tile: " + zPath); + + /* create new one */ + /* TODO: we might use existing tiles that we could load + * to fill the zoomed out tile in... */ + zIm = new BufferedImage(MapManager.tileWidth, MapManager.tileHeight, BufferedImage.TYPE_INT_RGB); + } else { + mgr.debug("Loaded zoom-out tile from " + zPath); + } + } else { + mgr.debug("Using zoom-out tile from cache: " + zPath); + } + + /* update zoom-out tile */ + + /* scaled size */ + int scw = mgr.tileWidth / 2; + int sch = mgr.tileHeight / 2; + + /* origin in zoomed-out tile */ + int ox = scw; + int oy = 0; + + if(zpx != px) ox = 0; + if(zpy != py) oy = sch; + + /* blit scaled rendered tile onto zoom-out tile */ + WritableRaster zr = zIm.getRaster(); + Graphics2D g2 = zIm.createGraphics(); + g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + g2.drawImage(im, ox, oy, scw, sch, null); + + /* update zoom-out tile cache */ + BufferedImage oldIm = mgr.zoomCache.put(zPath, zIm); + if(oldIm != null && oldIm != zIm) { + oldIm.flush(); + } + + /* save zoom-out tile */ + try { + File file = new File(zPath); + ImageIO.write(zIm, "png", file); + mgr.debug("saved zoom-out tile at " + zPath); + + //log.info("Saved tile: " + path); + } catch(IOException e) { + log.log(Level.SEVERE, "Failed to save zoom-out tile: " + zPath, e); + } catch(java.lang.NullPointerException e) { + log.log(Level.SEVERE, "Failed to save zoom-out tile (NullPointerException): " + zPath, e); } } /* generate a path name for this map tile */ - String getPath(MapManager mgr) + public String getPath(MapManager mgr) { return mgr.tilepath + "t_" + px + "_" + py + ".png"; } + /* generate a path name for the zoomed-out tile */ + public String getZoomPath(MapManager mgr) + { + return mgr.tilepath + "zt_" + zpx + "_" + zpy + ".png"; + } + + /* try to load already generated image */ + public BufferedImage loadTile(MapManager mgr) + { + try { + File file = new File(getPath(mgr)); + return ImageIO.read(file); + } catch(IOException e) { + } + + return null; + } + /* cast a ray into the map */ private Color scan(MapManager mgr, int x, int y, int z, int seq) { diff --git a/WebServerRequest.java b/WebServerRequest.java index bdaaac00..fe52fd0c 100644 --- a/WebServerRequest.java +++ b/WebServerRequest.java @@ -5,6 +5,8 @@ import java.util.*; import java.util.logging.Logger; public class WebServerRequest extends Thread { + protected static final Logger log = Logger.getLogger("Minecraft"); + private Socket sock; private MapManager mgr; @@ -86,7 +88,7 @@ public class WebServerRequest extends Thread { synchronized(mgr.lock) { for(TileUpdate tu : mgr.tileUpdates) { if(tu.at >= cutoff) { - sb.append(tu.tile.px + "_" + tu.tile.py + "\n"); + sb.append(tu.tile.px + "_" + tu.tile.py + " " + tu.tile.zpx + "_" + tu.tile.zpy + "\n"); } } } diff --git a/doc/README b/doc/README new file mode 100644 index 00000000..f7e2284c --- /dev/null +++ b/doc/README @@ -0,0 +1,3 @@ + +Placeholder for documentation and tutorials! + diff --git a/misc/CacheTest.java b/misc/CacheTest.java new file mode 100644 index 00000000..cf9dac52 --- /dev/null +++ b/misc/CacheTest.java @@ -0,0 +1,72 @@ +public class CacheTest +{ + Cache cache; + + void add(String key, Integer val) + { + Integer old = cache.put(key, val); + if(old == null) { + System.out.println("cache.put(" + key + ", " + val + ") == null"); + } else { + System.out.println("cache.put(" + key + ", " + val + ") == " + old); + } + } + + void get(String key) + { + Integer v = cache.get(key); + if(v == null) { + System.out.println("cache.get(" + key + ") == null"); + } else { + System.out.println("cache.get(" + key + ") == " + v); + } + } + + public void test() + { + cache = new Cache(5); + + add("a", 1); + add("b", 2); + add("c", 3); + add("d", 4); + add("e", 5); + add("f", 6); + add("g", 7); + add("h", 8); + add("i", 9); + add("j", 10); + + get("a"); + get("b"); + get("c"); + get("d"); + get("e"); + get("f"); + get("g"); + get("h"); + get("i"); + get("j"); + + add("g", 11); + add("i", 12); + + get("f"); + get("g"); + get("h"); + get("i"); + get("j"); + + add("k", 13); + add("l", 14); + add("m", 15); + add("n", 16); + add("o", 17); + } + + public static void main(String[] args) + { + CacheTest k = new CacheTest(); + k.test(); + } +} diff --git a/readme.txt b/readme.txt index 2394eeef..04950052 100644 --- a/readme.txt +++ b/readme.txt @@ -5,6 +5,7 @@ Commands /map_regen - regenerate entire map (currently buggy) /map_debug - send map debugging messages /map_nodebug - disable map debugging messages +/map_regenzoom - regenerates zoom-out tiles /addmarker [name] - adds a named marker to the map /removemarker [name] - removes a named marker to the map diff --git a/web/map.js b/web/map.js index 0657a989..98c3f053 100644 --- a/web/map.js +++ b/web/map.js @@ -451,23 +451,30 @@ function makeRequest(url, func, type, fail, post, contenttype) tileWidth: 128, tileHeight: 128, updateRate: setup.updateRate, - zoomSize: [ 32, 128, 256 ] + zoomSize: [ 128, 128, 256 ] }; - function MCMapProjection() { - } - - MCMapProjection.prototype.fromLatLngToPoint = function(latLng) { - var x = (latLng.lng() * config.tileWidth)|0; - var y = (latLng.lat() * config.tileHeight)|0; - return new google.maps.Point(x, y); - }; - - MCMapProjection.prototype.fromPointToLatLng = function(point) { - var lng = point.x / config.tileWidth; - var lat = point.y / config.tileHeight; - return new google.maps.LatLng(lat, lng); - }; + function MCMapProjection() { + } + + MCMapProjection.prototype.fromLatLngToPoint = function(latLng) { + var x = (latLng.lng() * config.tileWidth)|0; + var y = (latLng.lat() * config.tileHeight)|0; + + if(map.zoom == 0) { + x += config.tileWidth / 2; + } + return new google.maps.Point(x, y); + }; + + MCMapProjection.prototype.fromPointToLatLng = function(point) { + var x = point.x; + if(map.zoom == 0) + x -= config.tileWidth / 2; + var lng = x / config.tileWidth; + var lat = point.y / config.tileHeight; + return new google.maps.LatLng(lat, lng); + }; function fromWorldToLatLng(x, y, z) { @@ -492,11 +499,11 @@ function makeRequest(url, func, type, fail, post, contenttype) function tileUrl(tile, always) { if(always) { var now = new Date(); - return config.tileUrl + 't_' + tile + '.png?' + now.getTime(); + return config.tileUrl + tile + '.png?' + now.getTime(); } else if(tile in lastSeen) { - return config.tileUrl + 't_' + tile + '.png?' + lastSeen[tile]; + return config.tileUrl + tile + '.png?' + lastSeen[tile]; } else { - return config.tileUrl + 't_' + tile + '.png?0'; + return config.tileUrl + tile + '.png?0'; } } @@ -517,7 +524,7 @@ function makeRequest(url, func, type, fail, post, contenttype) } mcMapType.prototype.tileSize = new google.maps.Size(config.tileWidth, config.tileHeight); - mcMapType.prototype.minZoom = 1; + mcMapType.prototype.minZoom = 0; mcMapType.prototype.maxZoom = 2; mcMapType.prototype.getTile = function(coord, zoom, doc) { var img = doc.createElement('IMG'); @@ -528,7 +535,11 @@ function makeRequest(url, func, type, fail, post, contenttype) img.style.height = config.zoomSize[zoom] + 'px'; img.style.borderStyle = 'none'; - var tilename = (- coord.x * config.tileWidth) + '_' + coord.y * config.tileHeight; + if(zoom > 0) { + var tilename = "t_" + (- coord.x * config.tileWidth) + '_' + coord.y * config.tileHeight; + } else { + var tilename = "zt_" + (- coord.x * config.tileWidth * 2) + '_' + coord.y * config.tileHeight * 2; + } tileDict[tilename] = img; var url = tileUrl(tilename); @@ -594,79 +605,50 @@ function makeRequest(url, func, type, fail, post, contenttype) markers[p[0]] = marker; } } else if(p.length == 6) { - if (p[1] == 'warp') + var image = 'sign.png'; + if (p[1] == 'marker') { - if(p[0] in markers) { - var m = markers[p[0]]; - - if (showWarps == false) { - m.setMap(null); - continue; - } - else if (m.map == null) { - m.setMap(map); - } - - var converted = fromWorldToLatLng(p[3], p[4], p[5]); - m.setPosition(converted); - } else { - if (showWarps == false) { - continue; - } - - var converted = fromWorldToLatLng(p[3], p[4], p[5]); - var marker = new MarkerWithLabel({ - position: converted, - map: map, - labelContent: p[0], - labelAnchor: new google.maps.Point(-14, 10), - labelClass: "labels", - clickable: false, - flat: true, - icon: new google.maps.MarkerImage('watch.png', new google.maps.Size(28, 28), new google.maps.Point(0, 0), new google.maps.Point(14, 14)) - }); - - markers[p[0]] = marker; - } + image = 'watch.png'; } - else if (p[1] == 'marker') - { - if(p[0] in markers) { - var m = markers[p[0]]; - if (showMarkers == false) { - m.setMap(null); - continue; - } - else if (m.map == null) { - m.setMap(map); - } + if(p[0] in markers) { + var m = markers[p[0]]; - var converted = fromWorldToLatLng(p[3], p[4], p[5]); - m.setPosition(converted); - } else { - if (showMarkers == false) { - continue; - } - - var converted = fromWorldToLatLng(p[3], p[4], p[5]); - var marker = new MarkerWithLabel({ - position: converted, - map: map, - labelContent: p[0], - labelAnchor: new google.maps.Point(-14, 10), - labelClass: "labels", - clickable: false, - flat: true, - icon: new google.maps.MarkerImage('sign.png', new google.maps.Size(28, 28), new google.maps.Point(0, 0), new google.maps.Point(14, 14)) - }); - - markers[p[0]] = marker; + if (showWarps == false) { + m.setMap(null); + continue; } + else if (m.map == null) { + m.setMap(map); + } + + var converted = fromWorldToLatLng(p[3], p[4], p[5]); + m.setPosition(converted); + } else { + if (showWarps == false) { + continue; + } + + var converted = fromWorldToLatLng(p[3], p[4], p[5]); + var marker = new MarkerWithLabel({ + position: converted, + map: map, + labelContent: p[0], + labelAnchor: new google.maps.Point(-14, 10), + labelClass: "labels", + clickable: false, + flat: true, + icon: new google.maps.MarkerImage(image, new google.maps.Size(28, 28), new google.maps.Point(0, 0), new google.maps.Point(14, 14)) + }); + + markers[p[0]] = marker; } - } else if(p.length == 1) { - lastSeen[p[0]] = lasttimestamp; - imgSubst(p[0]); + } else if(p.length == 2) { + lastSeen['t_' + p[0]] = lasttimestamp; + lastSeen['zt_' + p[1]] = lasttimestamp; + + imgSubst('t_' + p[0]); + imgSubst('zt_' + p[1]); } } @@ -699,7 +681,8 @@ function makeRequest(url, func, type, fail, post, contenttype) scaleControl: false, mapTypeControl: false, streetViewControl: false, - mapTypeId: 'mcmap' + mapTypeId: 'mcmap', + backgroundColor: '#000' }; map = new google.maps.Map(document.getElementById("mcmap"), mapOptions); mapType = new mcMapType();