diff --git a/src/main/java/org/dynmap/DynmapWorld.java b/src/main/java/org/dynmap/DynmapWorld.java index 8933a129..708b9e4c 100644 --- a/src/main/java/org/dynmap/DynmapWorld.java +++ b/src/main/java/org/dynmap/DynmapWorld.java @@ -1,12 +1,25 @@ package org.dynmap; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import org.bukkit.World; import org.bukkit.Location; +import org.dynmap.debug.Debug; +import org.dynmap.kzedmap.KzedMap; +import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; +import org.dynmap.utils.FileLockManager; import org.dynmap.utils.MapChunkCache; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.HashMap; + +import javax.imageio.ImageIO; + public class DynmapWorld { public World world; public List maps = new ArrayList(); @@ -19,4 +32,198 @@ public class DynmapWorld { public boolean sendposition; public boolean sendhealth; public boolean bigworld; /* If true, deeper directory hierarchy */ + public int extrazoomoutlevels; /* Number of additional zoom out levels to generate */ + + private static class DirFilter implements FilenameFilter { + public boolean accept(File f, String n) { + if(!n.equals("..") && !n.equals(".")) { + File fn = new File(f, n); + return fn.isDirectory(); + } + return false; + } + } + + private static class PNGFileFilter implements FilenameFilter { + String prefix; + public PNGFileFilter(String pre) { prefix = pre; } + public boolean accept(File f, String n) { + if(n.endsWith(".png") && n.startsWith(prefix)) { + File fn = new File(f, n); + return fn.isFile(); + } + return false; + } + } + + public void freshenZoomOutFiles(File tilepath) { + for(int i = 0; i < extrazoomoutlevels; i++) { + freshenZoomOutFilesByLevel(tilepath, i); + } + } + + public void freshenZoomOutFilesByLevel(File tilepath, int zoomlevel) { + Log.info("freshenZoomOutFiles(" + tilepath.getPath() + "," + zoomlevel + ")"); + File worldpath = new File(tilepath, world.getName()); /* Make path to our world */ + if(worldpath.exists() == false) /* Quit if not found */ + return; + HashMap maptab = new HashMap(); + /* Build table of file prefixes and step sizes */ + for(MapType mt : maps) { + List pfx = mt.baseZoomFilePrefixes(); + Integer step = mt.baseZoomFileStepSize(); + for(String p : pfx) { + maptab.put(p, step); + } + } + if(bigworld) { /* If big world, next directories are map name specific */ + DirFilter df = new DirFilter(); + for(String pfx : maptab.keySet()) { /* Walk thrugh prefixes, as directories */ + File dname = new File(worldpath, pfx); + /* Now, go through subdirectories under this one, and process them */ + String[] subdir = dname.list(df); + for(String s : subdir) { + File sdname = new File(dname, s); + /* Each middle tier directory is redundant - just go through them */ + String[] ssubdir = sdname.list(df); + for(String ss : ssubdir) { + File ssdname = new File(sdname, ss); + processZoomDirectory(ssdname, maptab.get(pfx), "", zoomlevel); + } + } + } + } + else { /* Else, classic file layout */ + for(String pfx : maptab.keySet()) { /* Walk thrugh prefixes, as directories */ + processZoomDirectory(worldpath, maptab.get(pfx), pfx + "_", zoomlevel); + } + } + } + + + private static class ProcessTileRec { + File zf; + int x, z; + } + private void processZoomDirectory(File dir, int stepsize, String prefix, int zoomlevel) { + Log.info("processZoomDirectory(" + dir.getPath() + "," + stepsize + "," + prefix + "," + zoomlevel + ")"); + String zoomprefix = "zzzzzzzzzzzzzzzzzzzz".substring(0, zoomlevel); + HashMap toprocess = new HashMap(); + if(prefix.equals("")) { + if(zoomlevel > 0) + prefix = zoomprefix + "_"; + } + else + prefix = zoomprefix + prefix; + zoomlevel++; + String[] files = dir.list(new PNGFileFilter(prefix)); + for(String fn : files) { + /* Build file object */ + File f = new File(dir, fn); + /* Parse filename to predict zoomed out file */ + fn = fn.substring(0, fn.lastIndexOf('.')); /* Strip off extension */ + String[] tok = fn.split("_"); /* Split by underscores */ + int x = 0; + int z = 0; + boolean parsed = false; + if(tok.length >= 2) { + try { + x = Integer.parseInt(tok[tok.length-2]); + z = Integer.parseInt(tok[tok.length-1]); + parsed = true; + } catch (NumberFormatException nfx) { + } + } + if(!parsed) + continue; + if(x >= 0) + x = x - (x % (stepsize << zoomlevel)); + else + x = x + (x % (stepsize << zoomlevel)); + if(z >= 0) + z = z - (z % (stepsize << zoomlevel)); + else + z = z + (z % (stepsize << zoomlevel)); + File zf; + if(prefix.equals("")) + zf = new File(dir, "z_" + x + "_" + z + ".png"); + else + zf = new File(dir, "z" + prefix + x + "_" + z + ".png"); + /* If zoom file exists and is older than our file, nothing to do */ + if(zf.exists() && (zf.lastModified() >= f.lastModified())) { + continue; + } + String zfpath = zf.getPath(); + if(!toprocess.containsKey(zfpath)) { + ProcessTileRec rec = new ProcessTileRec(); + rec.zf = zf; + rec.x = x; + rec.z = z; + toprocess.put(zfpath, rec); + } + } + /* Do processing */ + for(ProcessTileRec s : toprocess.values()) { + processZoomTile(dir, s.zf, s.x, s.z, stepsize << (zoomlevel-1), prefix); + } + } + private void processZoomTile(File dir, File zf, int tx, int tz, int stepsize, String prefix) { + Log.info("processZoomFile(" + dir.getPath() + "," + zf.getPath() + "," + tx + "," + tz + "," + stepsize + "," + prefix); + int width = 128, height = 128; + BufferedImage zIm = null; + KzedBufferedImage kzIm = null; + int[] argb = new int[width*height]; + + /* create image buffer */ + kzIm = KzedMap.allocateBufferedImage(width, height); + zIm = kzIm.buf_img; + + for(int i = 0; i < 4; i++) { + File f = new File(dir, prefix + (tx + stepsize*(i>>1)) + "_" + (tz + stepsize*(i&1)) + ".png"); + if(f.exists()) { + BufferedImage im = null; + try { + im = ImageIO.read(f); + } catch (IOException e) { + } catch (IndexOutOfBoundsException e) { + } + if(im != null) { + im.getRGB(0, 0, width, height, argb, 0, width); /* Read data */ + im.flush(); + /* Do binlinear scale to 64x64 */ + Color c1 = new Color(); + for(int y = 0; y < height; y += 2) { + for(int x = 0; x < width; x += 2) { + int red = 0; + int green = 0; + int blue = 0; + int alpha = 0; + for(int yy = y; yy < y+2; yy++) { + for(int xx = x; xx < x+2; xx++) { + c1.setARGB(argb[(yy*width)+xx]); + red += c1.getRed(); + green += c1.getGreen(); + blue += c1.getBlue(); + alpha += c1.getAlpha(); + } + } + c1.setRGBA(red>>2, green>>2, blue>>2, alpha>>2); + argb[(y*width/2) + (x/2)] = c1.getARGB(); + } + } + /* blit scaled rendered tile onto zoom-out tile */ + zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width); + } + } + } + try { + FileLockManager.imageIOWrite(zIm, "png", zf); + Debug.debug("Saved zoom-out tile at " + zf.getName()); + } catch (IOException e) { + Debug.error("Failed to save zoom-out tile: " + zf.getName(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save zoom-out tile (NullPointerException): " + zf.getName(), e); + } + KzedMap.freeBufferedImage(kzIm); + } } diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index be548053..2036e25b 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -261,6 +261,17 @@ public class MapManager { } } + private class DoZoomOutProcessing implements Runnable { + public void run() { + Log.info("DoZoomOutProcessing started"); + for(DynmapWorld w : worlds) { + w.freshenZoomOutFiles(plug_in.tilesDirectory); + } + renderpool.schedule(this, 60000, TimeUnit.MILLISECONDS); + Log.info("DoZoomOutProcessing finished"); + } + } + public MapManager(DynmapPlugin plugin, ConfigurationNode configuration) { plug_in = plugin; mapman = this; @@ -288,6 +299,7 @@ public class MapManager { scheduler.scheduleSyncRepeatingTask(plugin, new CheckWorldTimes(), 5*20, 5*20); /* Check very 5 seconds */ scheduler.scheduleSyncRepeatingTask(plugin, new ProcessChunkLoads(), 1, 1); /* Chunk loader task */ + } void renderFullWorld(Location l, CommandSender sender) { @@ -343,6 +355,8 @@ public class MapManager { dynmapWorld.sendposition = worldConfiguration.getBoolean("sendposition", true); dynmapWorld.sendhealth = worldConfiguration.getBoolean("sendhealth", true); dynmapWorld.bigworld = worldConfiguration.getBoolean("bigworld", false); + dynmapWorld.extrazoomoutlevels = worldConfiguration.getInteger("extrazoomout", 0); + if(loclist != null) { for(ConfigurationNode loc : loclist) { Location lx = new Location(w, loc.getDouble("x", 0), loc.getDouble("y", 64), loc.getDouble("z", 0)); @@ -422,6 +436,7 @@ public class MapManager { public void startRendering() { tileQueue.start(); renderpool = new DynmapScheduledThreadPoolExecutor(); + renderpool.schedule(new DoZoomOutProcessing(), 60000, TimeUnit.MILLISECONDS); } public void stopRendering() { diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index e5c0c979..f8cff80f 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -27,5 +27,14 @@ public abstract class MapType { public boolean isHightestBlockYDataNeeded() { return false; } public boolean isRawBiomeDataNeeded() { return false; } public boolean isBlockTypeDataNeeded() { return true; } - + + public abstract List baseZoomFilePrefixes(); + public abstract int baseZoomFileStepSize(); + public enum ZoomStepDirection { + POSITIVE_X_Y, + NEGATIVE_X_Y, + POSITIVE_X_NEGATIVE_Y, + NEGATIVE_X_POSITIVE_Y + } + public abstract ZoomStepDirection zoomFileStepDirection(); } diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index f97e9add..980c916b 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -22,8 +22,10 @@ import org.dynmap.MapManager; import org.dynmap.TileHashManager; import org.dynmap.MapTile; import org.dynmap.MapType; +import org.dynmap.MapType.ZoomStepDirection; import org.dynmap.debug.Debug; import org.dynmap.kzedmap.KzedMap; +import org.dynmap.kzedmap.MapTileRenderer; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; import org.dynmap.utils.FileLockManager; import org.dynmap.utils.MapChunkCache; @@ -406,6 +408,17 @@ public class FlatMap extends MapType { return prefix; } + public List baseZoomFilePrefixes() { + ArrayList s = new ArrayList(); + s.add(getName() + "_128"); + if(night_and_day) + s.add(getName()+"_day_128"); + return s; + } + + public int baseZoomFileStepSize() { return 1; } + + public ZoomStepDirection zoomFileStepDirection() { return ZoomStepDirection.POSITIVE_X_Y; } public static class FlatMapTile extends MapTile { FlatMap map; @@ -427,7 +440,7 @@ public class FlatMap extends MapType { public String getFilename() { if(fname == null) { if(world.bigworld) - fname = map.prefix + "/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + size + "_" + -(y+1) + "_" + x + ".png"; + fname = map.prefix + "_" + size + "/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + -(y+1) + "_" + x + ".png"; else fname = map.prefix + "_" + size + "_" + -(y+1) + "_" + x + ".png"; } @@ -437,7 +450,7 @@ public class FlatMap extends MapType { public String getDayFilename() { if(fname_day == null) { if(world.bigworld) - fname_day = map.prefix + "_day/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + size + "_" + -(y+1) + "_" + x + ".png"; + fname_day = map.prefix + "_day_" + size + "/" + ((-(y+1))>>5) + "_" + (x>>5) + "/" + -(y+1) + "_" + x + ".png"; else fname_day = map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png"; } diff --git a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java index 67a02e71..7b540440 100644 --- a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java @@ -11,6 +11,8 @@ public class CaveTileRenderer extends DefaultTileRenderer { super(configuration); } + public boolean isNightAndDayEnabled() { return false; } + @Override protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, MapIterator mapiter) { diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index d5c068f7..07c01d12 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -48,6 +48,8 @@ public class DefaultTileRenderer implements MapTileRenderer { return name; } + public boolean isNightAndDayEnabled() { return night_and_day; } + public DefaultTileRenderer(ConfigurationNode configuration) { this.configuration = configuration; name = (String) configuration.get("prefix"); diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 4963edb4..d13aa2bd 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -17,6 +17,7 @@ import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; +import org.dynmap.MapType.ZoomStepDirection; import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; import java.awt.image.DataBufferInt; @@ -326,6 +327,20 @@ public class KzedMap extends MapType { return false; } + public List baseZoomFilePrefixes() { + ArrayList s = new ArrayList(); + for(MapTileRenderer r : renderers) { + s.add("z" + r.getName()); + if(r.isNightAndDayEnabled()) + s.add("z" + r.getName() + "_day"); + } + return s; + } + + public int baseZoomFileStepSize() { return zTileWidth; } + + public ZoomStepDirection zoomFileStepDirection() { return ZoomStepDirection.NEGATIVE_X_POSITIVE_Y; } + public String getName() { return "KzedMap"; } diff --git a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java index 09390df7..01b1270d 100644 --- a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java @@ -15,4 +15,5 @@ public interface MapTileRenderer { boolean isBiomeDataNeeded(); boolean isRawBiomeDataNeeded(); + boolean isNightAndDayEnabled(); } diff --git a/web/js/flatmap.js b/web/js/flatmap.js index 63d51d52..ef68cb59 100644 --- a/web/js/flatmap.js +++ b/web/js/flatmap.js @@ -30,8 +30,8 @@ FlatMapType.prototype = $.extend(new DynMapType(), { dnprefix = '_day'; if(this.dynmap.world.bigworld) - tileName = this.prefix + dnprefix + '/' + (coord.x >> 5) + '_' + (coord.y >> 5) + - '/128_' + coord.x + '_' + coord.y + '.png'; + tileName = this.prefix + dnprefix + '_128/' + (coord.x >> 5) + '_' + (coord.y >> 5) + + '/' + coord.x + '_' + coord.y + '.png'; else tileName = this.prefix + dnprefix + '_128_' + coord.x + '_' + coord.y + '.png'; imgSize = Math.pow(2, 7+zoom);