From 3a0e408b046dcdad3e204a7a9288fe142a2577be Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 11:31:13 +0100 Subject: [PATCH 01/13] Clean up 1.14 build --- BlueMapBukkit/src/main/resources/plugin.yml | 1 + build.gradle | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/BlueMapBukkit/src/main/resources/plugin.yml b/BlueMapBukkit/src/main/resources/plugin.yml index 6c8fbe10..b04550eb 100644 --- a/BlueMapBukkit/src/main/resources/plugin.yml +++ b/BlueMapBukkit/src/main/resources/plugin.yml @@ -2,6 +2,7 @@ name: BlueMap description: "A 3d-map of your Minecraft worlds view-able in your browser using three.js (WebGL)" main: de.bluecolored.bluemap.bukkit.BukkitPlugin version: 0.2.1 +api-version: 1.14.4 author: "Blue (TBlueF / Lukas Rieger)" authors: [Blue (TBlueF / Lukas Rieger)] website: "https://github.com/BlueMap-Minecraft" diff --git a/build.gradle b/build.gradle index f79939c6..8a18b431 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ allprojects { dependencies { compile project(':BlueMapCLI') compile project(':BlueMapBukkit') - compile project(':BlueMapSponge') + //compile project(':BlueMapSponge') } assemble.dependsOn shadowJar { From 611109ea9194527d537bf2f5ade8c3b886fda787 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 11:34:01 +0100 Subject: [PATCH 02/13] Fix CLI skipping unrendered maps - Fixes #8 --- .../src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index 2b55dfb2..1415cde4 100644 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -166,8 +166,7 @@ public void renderMaps() throws IOException { } if (tiles.isEmpty()) { - Logger.global.logInfo("Render finished!"); - return; + continue; } Logger.global.logInfo("Starting Render..."); From ee384322d748e8938e4daabb901d5240c3e00acb Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 12:47:06 +0100 Subject: [PATCH 03/13] Also search in parent folder for a level.dat --- .../java/de/bluecolored/bluemap/core/mca/MCAWorld.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index e020c3c3..2cbb696a 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -357,7 +357,15 @@ private Path getMCAFilePath(Vector2i region) { public static MCAWorld load(Path worldFolder, UUID uuid, BlockIdMapper blockIdMapper, BlockPropertiesMapper blockPropertiesMapper, BiomeMapper biomeIdMapper) throws IOException { try { - CompoundTag level = (CompoundTag) NBTUtil.readTag(worldFolder.resolve("level.dat").toFile()); + File levelFile = new File(worldFolder.toFile(), "level.dat"); + if (!levelFile.exists()) { + levelFile = new File(worldFolder.toFile().getParentFile(), "level.dat"); + } + if (!levelFile.exists()) { + throw new FileNotFoundException("Could not find a level.dat file for this world!"); + } + + CompoundTag level = (CompoundTag) NBTUtil.readTag(levelFile); CompoundTag levelData = level.getCompoundTag("Data"); String name = levelData.getString("LevelName"); From a75f2382227d771fae530d6e94306c0df841f00c Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 12:59:23 +0100 Subject: [PATCH 04/13] No need to double check :D --- .../main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java index 2cbb696a..09019451 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -360,9 +360,9 @@ public static MCAWorld load(Path worldFolder, UUID uuid, BlockIdMapper blockIdMa File levelFile = new File(worldFolder.toFile(), "level.dat"); if (!levelFile.exists()) { levelFile = new File(worldFolder.toFile().getParentFile(), "level.dat"); - } - if (!levelFile.exists()) { - throw new FileNotFoundException("Could not find a level.dat file for this world!"); + if (!levelFile.exists()) { + throw new FileNotFoundException("Could not find a level.dat file for this world!"); + } } CompoundTag level = (CompoundTag) NBTUtil.readTag(levelFile); From 2bc1f2dace89619c46e7b65d1a3660841ce263cc Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 13:43:32 +0100 Subject: [PATCH 05/13] Add etag handling and cache-control headers to webserver - closes #17 --- .../core/web/BlueMapWebRequestHandler.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java index ddd3116a..1f91a663 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.Set; import java.util.TimeZone; +import java.util.concurrent.TimeUnit; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -160,9 +161,21 @@ private HttpResponse generateResponse(HttpRequest request) { } catch (IllegalArgumentException e){} } - + //check ETag + String eTag = Long.toHexString(file.length()) + Integer.toHexString(file.hashCode()) + Long.toHexString(lastModified); + Set etagStringSet = request.getHeader("If-None-Match"); + if (!etagStringSet.isEmpty()){ + if(etagStringSet.iterator().next().equals(eTag)) { + return new HttpResponse(HttpStatusCode.NOT_MODIFIED); + } + } + + //create response HttpResponse response = new HttpResponse(HttpStatusCode.OK); + response.addHeader("ETag", eTag); if (lastModified > 0) response.addHeader("Last-Modified", timestampToString(lastModified)); + response.addHeader("Cache-Control", "public"); + response.addHeader("Cache-Control", "max-age=" + TimeUnit.HOURS.toSeconds(1)); //add content type header String filetype = file.getName().toString(); From 3b5393202f30c61696ee6e133042e83d7acdb4d7 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 14:34:20 +0100 Subject: [PATCH 06/13] Add configuration if gzip-compression should be used --- .../src/main/resources/bluemap-bukkit.conf | 6 ++++++ .../src/main/resources/bluemap-cli.conf | 6 ++++++ .../bluemap/core/config/MainConfig.java | 9 +++++++++ .../bluemap/core/render/RenderSettings.java | 7 +++++++ .../core/render/hires/HiresModelManager.java | 20 +++++++++++-------- .../src/main/resources/bluemap-sponge.conf | 6 ++++++ 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf b/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf index 34d64029..a0c813f4 100644 --- a/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf +++ b/BlueMapBukkit/src/main/resources/bluemap-bukkit.conf @@ -109,6 +109,12 @@ maps: [ # Default is enabled renderEdges: true + # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. + # Files will be only 5% as big with compression! + # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. + # This is much better than disabling the compression. + useCompression: true + # HIRES is the high-resolution render of the map. Where you see every block. hires { # Defines the size of one map-tile in blocks. diff --git a/BlueMapCLI/src/main/resources/bluemap-cli.conf b/BlueMapCLI/src/main/resources/bluemap-cli.conf index 3ae7ec80..8034fdf1 100644 --- a/BlueMapCLI/src/main/resources/bluemap-cli.conf +++ b/BlueMapCLI/src/main/resources/bluemap-cli.conf @@ -108,6 +108,12 @@ maps: [ # Default is enabled renderEdges: true + # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. + # Files will be only 5% as big with compression! + # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. + # This is much better than disabling the compression. + useCompression: true + # HIRES is the high-resolution render of the map. Where you see every block. hires { # Defines the size of one map-tile in blocks. diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java index cc421be9..e7fa381e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/MainConfig.java @@ -201,6 +201,8 @@ public class MapConfig implements RenderSettings { private Vector3i min, max; private boolean renderEdges; + private boolean useGzip; + private int hiresTileSize; private float hiresViewDistance; @@ -231,6 +233,8 @@ private MapConfig(ConfigurationNode node) throws IOException { this.max = new Vector3i(maxX, maxY, maxZ); this.renderEdges = node.getNode("renderEdges").getBoolean(true); + + this.renderEdges = node.getNode("useCompression").getBoolean(true); this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32); this.hiresViewDistance = node.getNode("hires", "viewDistance").getFloat(4.5f); @@ -310,6 +314,11 @@ public boolean isRenderEdges() { return renderEdges; } + @Override + public boolean useGzipCompression() { + return useGzip; + } + } private void checkOutdated(ConfigurationNode node) throws OutdatedConfigException { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java index bc50278b..026d324c 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java @@ -78,6 +78,13 @@ default boolean isRenderEdges() { return true; } + /** + * If gzip compression will be used to compress the generated files + */ + default boolean useGzipCompression() { + return true; + } + default RenderSettings copy() { return new StaticRenderSettings( getAmbientOcclusionStrenght(), diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java index 137308dc..a70b4fdf 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; @@ -58,11 +59,13 @@ public class HiresModelManager { private ExecutorService savingExecutor; + private boolean useGzip; + public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Vector2i tileSize, ExecutorService savingExecutor) { - this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileSize, new Vector2i(2, 2), savingExecutor); + this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileSize, new Vector2i(2, 2), savingExecutor, renderSettings.useGzipCompression()); } - public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i tileSize, Vector2i gridOrigin, ExecutorService savingExecutor) { + public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i tileSize, Vector2i gridOrigin, ExecutorService savingExecutor, boolean useGzip) { this.fileRoot = fileRoot; this.renderer = renderer; @@ -70,6 +73,7 @@ public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i ti this.gridOrigin = gridOrigin; this.savingExecutor = savingExecutor; + this.useGzip = useGzip; } /** @@ -87,7 +91,7 @@ private void save(final HiresModel model) { } private void save(HiresModel model, String modelJson){ - File file = getFile(model.getTile()); + File file = getFile(model.getTile(), useGzip); try { if (!file.exists()){ @@ -95,9 +99,9 @@ private void save(HiresModel model, String modelJson){ file.createNewFile(); } - FileOutputStream fos = new FileOutputStream(file); - GZIPOutputStream zos = new GZIPOutputStream(fos); - OutputStreamWriter osw = new OutputStreamWriter(zos, StandardCharsets.UTF_8); + OutputStream os = new FileOutputStream(file); + if (useGzip) os = new GZIPOutputStream(os); + OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); try ( PrintWriter pw = new PrintWriter(osw); ){ @@ -185,8 +189,8 @@ public Vector2i posToTile(Vector3d pos){ /** * Returns the file for a tile */ - public File getFile(Vector2i tilePos){ - return FileUtils.coordsToFile(fileRoot, tilePos, "json.gz"); + public File getFile(Vector2i tilePos, boolean gzip){ + return FileUtils.coordsToFile(fileRoot, tilePos, "json" + (gzip ? ".gz" : "")); } } diff --git a/BlueMapSponge/src/main/resources/bluemap-sponge.conf b/BlueMapSponge/src/main/resources/bluemap-sponge.conf index ecf32b40..a12c4bf6 100644 --- a/BlueMapSponge/src/main/resources/bluemap-sponge.conf +++ b/BlueMapSponge/src/main/resources/bluemap-sponge.conf @@ -104,6 +104,12 @@ maps: [ # Default is enabled renderEdges: true + # With this set to true, the generated files for this world are compressed using gzip to save A LOT of space. + # Files will be only 5% as big with compression! + # Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly. + # This is much better than disabling the compression. + useCompression: true + # HIRES is the high-resolution render of the map. Where you see every block. hires { # Defines the size of one map-tile in blocks. From 5ea1fbb4c5f1492e75b77fdcf65a03e31426299b Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 16:04:46 +0100 Subject: [PATCH 07/13] Little styling additions and QoL, also only load hires-tiles when zoomed in - closes #16 --- .gitignore | 3 +++ .../src/main/webroot/js/libs/BlueMap.js | 26 ++++++++++++++----- .../src/main/webroot/js/libs/Controls.js | 18 +++++++------ .../main/webroot/js/libs/modules/Compass.js | 4 +-- .../src/main/webroot/js/libs/modules/Info.js | 4 +-- .../main/webroot/js/libs/modules/MapMenu.js | 4 +-- .../main/webroot/js/libs/modules/Settings.js | 14 +++++----- .../main/webroot/style/modules/alertbox.scss | 4 --- .../main/webroot/style/modules/mapmenu.scss | 1 + .../main/webroot/style/modules/position.scss | 13 +++++++++- 10 files changed, 58 insertions(+), 33 deletions(-) diff --git a/.gitignore b/.gitignore index a2cea087..4cf4afab 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,9 @@ bin/* .project */.project +.idea +*/.idea + node_modules/ package-lock.json diff --git a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js index 552417c8..97176aed 100644 --- a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js +++ b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js @@ -205,7 +205,9 @@ export default class BlueMap { setTimeout(this.update, 1000); this.lowresTileManager.setPosition(this.controls.targetPosition); - this.hiresTileManager.setPosition(this.controls.targetPosition); + if (this.camera.position.y < 400) { + this.hiresTileManager.setPosition(this.controls.targetPosition); + } this.locationHash = '#' + this.map @@ -239,7 +241,7 @@ export default class BlueMap { this.renderer.clearDepth(); this.renderer.render(this.hiresScene, this.camera, this.renderer.getRenderTarget(), false); } - } + }; handleContainerResize = () => { this.camera.aspect = this.element.clientWidth / this.element.clientHeight; @@ -254,7 +256,7 @@ export default class BlueMap { .css('height', this.element.clientHeight); this.updateFrame = true; - } + }; async loadSettings() { return new Promise(resolve => { @@ -455,20 +457,29 @@ export default class BlueMap { // ###### UI ###### - alert(content) { + toggleAlert(id, content) { let alertBox = $('#alert-box'); if (alertBox.length === 0){ alertBox = $('
').appendTo(this.element); } let displayAlert = () => { - let alert = $(``).appendTo(alertBox); + let alert = $(``).appendTo(alertBox); alert.find('.alert-close-button').click(() => { alert.fadeOut(200, () => alert.remove()); }); alert.fadeIn(200); }; + let sameAlert = alertBox.find(`.alert[data-alert-id=${id}]`); + if (sameAlert.length > 0){ + alertBox.fadeOut(200, () => { + alertBox.html(''); + alertBox.show(); + }); + return; + } + let oldAlerts = alertBox.find('.alert'); if (oldAlerts.length > 0){ alertBox.fadeOut(200, () => { @@ -476,8 +487,9 @@ export default class BlueMap { alertBox.show(); displayAlert(); }); - } else { - displayAlert(); + return; } + + displayAlert(); } } diff --git a/BlueMapCore/src/main/webroot/js/libs/Controls.js b/BlueMapCore/src/main/webroot/js/libs/Controls.js index 6705af6a..49609440 100644 --- a/BlueMapCore/src/main/webroot/js/libs/Controls.js +++ b/BlueMapCore/src/main/webroot/js/libs/Controls.js @@ -103,7 +103,7 @@ export default class Controls { }, false); window.addEventListener('mousemove', this.onMouseMove, false); canvas.addEventListener('mousedown', this.onMouseDown, false); - canvas.addEventListener('mouseup', this.onMouseUp, false); + window.addEventListener('mouseup', this.onMouseUp, false); //this is on the window instead of the canvas, so if we drag out of the canvas and release the mouse it actually gets released canvas.addEventListener('wheel', this.onMouseWheel, { passive: true }); window.addEventListener('keydown', this.onKeyDown, false); window.addEventListener('keyup', this.onKeyUp, false); @@ -197,7 +197,7 @@ export default class Controls { this.minHeight = intersects[0].point.y; } } - }; + } updateMouseMoves = () => { this.deltaMouse.set(this.lastMouse.x - this.mouse.x, this.lastMouse.y - this.mouse.y); @@ -249,7 +249,7 @@ export default class Controls { if (this.targetDistance < this.settings.zoom.min) this.targetDistance = this.settings.zoom.min; if (this.targetDistance > this.settings.zoom.max) this.targetDistance = this.settings.zoom.max; - } + }; onMouseMove = event => { this.mouse.set(event.clientX, event.clientY); @@ -257,11 +257,13 @@ export default class Controls { if (this.state !== Controls.STATES.NONE){ event.preventDefault(); } - } + }; onMouseDown = event => { if (this.state !== Controls.STATES.NONE) return; + $(":focus").blur(); + switch (event.button) { case Controls.KEYS.MOVE : this.state = Controls.STATES.MOVE; @@ -272,7 +274,7 @@ export default class Controls { event.preventDefault(); break; } - } + }; onMouseUp = event => { if (this.state === Controls.STATES.NONE) return; @@ -285,13 +287,13 @@ export default class Controls { if (this.state === Controls.STATES.ORBIT) this.state = Controls.STATES.NONE; break; } - } + }; onKeyDown = event => { this.keyStates[event.keyCode] = true; - } + }; onKeyUp = event => { this.keyStates[event.keyCode] = false; - } + }; } diff --git a/BlueMapCore/src/main/webroot/js/libs/modules/Compass.js b/BlueMapCore/src/main/webroot/js/libs/modules/Compass.js index 4121be0c..02b3e099 100644 --- a/BlueMapCore/src/main/webroot/js/libs/modules/Compass.js +++ b/BlueMapCore/src/main/webroot/js/libs/modules/Compass.js @@ -42,12 +42,12 @@ export default class Compass { onBlueMapUpdateFrame = () => { this.needle.css('transform', `rotate(${this.blueMap.controls.direction}rad)`); - } + }; onClick = () => { this.blueMap.controls.targetDirection = 0; this.blueMap.controls.direction = this.blueMap.controls.direction % (Math.PI * 2); if (this.blueMap.controls.direction < -Math.PI) this.blueMap.controls.direction += Math.PI * 2; if (this.blueMap.controls.direction > Math.PI) this.blueMap.controls.direction -= Math.PI * 2; - } + }; } diff --git a/BlueMapCore/src/main/webroot/js/libs/modules/Info.js b/BlueMapCore/src/main/webroot/js/libs/modules/Info.js index b380d866..ae3fb6b1 100644 --- a/BlueMapCore/src/main/webroot/js/libs/modules/Info.js +++ b/BlueMapCore/src/main/webroot/js/libs/modules/Info.js @@ -36,7 +36,7 @@ export default class Info { } onClick = () => { - this.blueMap.alert( + this.blueMap.toggleAlert('bluemap-info', '

Info

' + 'Visit BlueMap on GitHub!
' + 'BlueMap works best with Chrome.
' + @@ -45,5 +45,5 @@ export default class Info { 'Rightclick-drag with your mouse to rotate your view.
' + 'Scroll to zoom.
' ); - } + }; } diff --git a/BlueMapCore/src/main/webroot/js/libs/modules/MapMenu.js b/BlueMapCore/src/main/webroot/js/libs/modules/MapMenu.js index 483eff69..9dc2864b 100644 --- a/BlueMapCore/src/main/webroot/js/libs/modules/MapMenu.js +++ b/BlueMapCore/src/main/webroot/js/libs/modules/MapMenu.js @@ -53,11 +53,11 @@ export default class MapMenu { onMapClick = event => { const map = $(event.target).attr('map'); this.bluemap.changeMap(map); - } + }; onBlueMapMapChange = () => { this.maplist.find('li').show(); this.maplist.find('li[map=' + this.bluemap.map + ']').hide(); this.element.find('.selection').html(this.bluemap.settings[this.bluemap.map].name); - } + }; } diff --git a/BlueMapCore/src/main/webroot/js/libs/modules/Settings.js b/BlueMapCore/src/main/webroot/js/libs/modules/Settings.js index 1d76db08..c2c73c42 100644 --- a/BlueMapCore/src/main/webroot/js/libs/modules/Settings.js +++ b/BlueMapCore/src/main/webroot/js/libs/modules/Settings.js @@ -43,13 +43,13 @@ export default class Settings { this.elementQuality = $( '' ).prependTo(this.elementMenu); - this.elementQuality.find('li[quality]').click(this.onQualityClick); + this.elementQuality.find('li[data-quality]').click(this.onQualityClick); this.elementRenderDistance = $('').prependTo(this.elementMenu); this.init(); @@ -84,10 +84,10 @@ export default class Settings { onQualityClick = (event) => { const target = event.target const desc = $(target).html(); - this.blueMap.quality = parseFloat($(target).attr('quality')); + this.blueMap.quality = parseFloat($(target).attr('data-quality')); this.elementQuality.find('li').show(); - this.elementQuality.find(`li[quality="${this.blueMap.quality}"]`).hide(); + this.elementQuality.find(`li[data-quality="${this.blueMap.quality}"]`).hide(); this.elementQuality.find('.selection > span').html(desc); @@ -104,7 +104,7 @@ export default class Settings { this.elementMenu.animate({ width: 'toggle' }, 200); - } + }; pctToRenderDistance(value, defaultValue) { let max = defaultValue * 5; diff --git a/BlueMapCore/src/main/webroot/style/modules/alertbox.scss b/BlueMapCore/src/main/webroot/style/modules/alertbox.scss index 6a3132ce..201566e3 100644 --- a/BlueMapCore/src/main/webroot/style/modules/alertbox.scss +++ b/BlueMapCore/src/main/webroot/style/modules/alertbox.scss @@ -45,10 +45,6 @@ padding: 10px; .alert-close-button { - /*position: absolute; - top: 5px; - right: 5px; - */ margin: -10px -10px 0px 0px; padding: 0 0 5px 5px; float: right; diff --git a/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss b/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss index 594fe1b5..e0bef99e 100644 --- a/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss +++ b/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss @@ -1,5 +1,6 @@ #bluemap-mapmenu { width: 200px; + cursor: pointer; .selection, .dropdown li { padding-left: 10px; diff --git a/BlueMapCore/src/main/webroot/style/modules/position.scss b/BlueMapCore/src/main/webroot/style/modules/position.scss index e6a6702f..053a00ba 100644 --- a/BlueMapCore/src/main/webroot/style/modules/position.scss +++ b/BlueMapCore/src/main/webroot/style/modules/position.scss @@ -8,7 +8,18 @@ outline: none; background: transparent; padding: 0 5px 0 25px; - font-family: inherit; + font: inherit; + color: inherit; + + // remove number spinner firefox + -moz-appearance:textfield; + + // remove number spinner webkit + &::-webkit-inner-spin-button, + &::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; + } } &[data-pos]::before { From aaaaf7e18adfdda2abe7d4bcd28867a28e5cfed9 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 16:42:34 +0100 Subject: [PATCH 08/13] Also use config with lowres model compression --- .../bluecolored/bluemap/cli/BlueMapCLI.java | 3 +- .../bluemap/common/plugin/Plugin.java | 3 +- .../core/render/lowres/LowresModel.java | 9 +++--- .../render/lowres/LowresModelManager.java | 32 +++++++++++-------- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index 1415cde4..d6bca0e8 100644 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -116,7 +116,8 @@ public void renderMaps() throws IOException { LowresModelManager lowresModelManager = new LowresModelManager( config.getWebDataPath().resolve(mapConfig.getId()).resolve("lowres"), new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()), - new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()) + new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()), + mapConfig.useGzipCompression() ); TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager); diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java index 4a2e4064..f990f61a 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/Plugin.java @@ -190,7 +190,8 @@ public synchronized void load() throws IOException, ParseResourceException { LowresModelManager lowresModelManager = new LowresModelManager( config.getWebDataPath().resolve(id).resolve("lowres"), new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()), - new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()) + new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()), + mapConfig.useGzipCompression() ); TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java index a1b73144..a0be9f90 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; @@ -95,7 +96,7 @@ public void update(Vector2i point, float height, Vector3f color){ * Saves this model to its file * @param force if this is false, the model is only saved if it has any changes */ - public void save(File file, boolean force) throws IOException { + public void save(File file, boolean force, boolean useGzip) throws IOException { if (!force && !hasUnsavedChanges) return; this.hasUnsavedChanges = false; @@ -118,9 +119,9 @@ public void save(File file, boolean force) throws IOException { throw new IOException("Failed to get write-access to file: " + file, e); } - FileOutputStream fos = new FileOutputStream(file); - GZIPOutputStream zos = new GZIPOutputStream(fos); - OutputStreamWriter osw = new OutputStreamWriter(zos, StandardCharsets.UTF_8); + OutputStream os = new FileOutputStream(file); + if (useGzip) os = new GZIPOutputStream(os); + OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); try ( PrintWriter pw = new PrintWriter(osw); ){ diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java index d09d71c2..800bc4aa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java @@ -27,6 +27,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.ArrayList; @@ -58,14 +59,18 @@ public class LowresModelManager { private Vector2i pointsPerHiresTile; private Map models; + + private boolean useGzip; - public LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHiresTile) { + public LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHiresTile, boolean useGzip) { this.fileRoot = fileRoot; this.gridSize = gridSize; this.pointsPerHiresTile = pointsPerHiresTile; models = new ConcurrentHashMap<>(); + + this.useGzip = useGzip; } /** @@ -166,13 +171,13 @@ public void update(UUID world, Vector2i point, float height, Vector3f color) thr /** * Returns the file for a tile */ - public File getFile(Vector2i tile){ - return FileUtils.coordsToFile(fileRoot, tile, "json.gz"); + public File getFile(Vector2i tile, boolean useGzip){ + return FileUtils.coordsToFile(fileRoot, tile, "json" + (useGzip ? ".gz" : "")); } private LowresModel getModel(UUID world, Vector2i tile) throws IOException { - File modelFile = getFile(tile); + File modelFile = getFile(tile, useGzip); CachedModel model = models.get(modelFile); if (model == null){ @@ -181,11 +186,10 @@ private LowresModel getModel(UUID world, Vector2i tile) throws IOException { if (model == null){ if (modelFile.exists()){ - FileInputStream fis = new FileInputStream(modelFile); - try( - GZIPInputStream zis = new GZIPInputStream(fis); - ){ - String json = IOUtils.toString(zis, StandardCharsets.UTF_8); + InputStream is = new FileInputStream(modelFile); + if (useGzip) is = new GZIPInputStream(is); + try { + String json = IOUtils.toString(is, StandardCharsets.UTF_8); try { model = new CachedModel(world, tile, BufferGeometry.fromJson(json)); @@ -194,6 +198,8 @@ private LowresModel getModel(UUID world, Vector2i tile) throws IOException { //gridFile.renameTo(gridFile.toPath().getParent().resolve(gridFile.getName() + ".broken").toFile()); modelFile.delete(); } + } finally { + is.close(); } } @@ -238,10 +244,10 @@ public synchronized void tidyUpModelCache() { } private synchronized void saveAndRemoveModel(CachedModel model) { - File modelFile = getFile(model.getTile()); + File modelFile = getFile(model.getTile(), useGzip); models.remove(modelFile); try { - model.save(modelFile, false); + model.save(modelFile, false, useGzip); //logger.logDebug("Saved and unloaded lowres tile: " + model.getTile()); } catch (IOException ex) { Logger.global.logError("Failed to save and unload lowres-model: " + modelFile, ex); @@ -249,9 +255,9 @@ private synchronized void saveAndRemoveModel(CachedModel model) { } private void saveModel(CachedModel model) { - File modelFile = getFile(model.getTile()); + File modelFile = getFile(model.getTile(), useGzip); try { - model.save(modelFile, false); + model.save(modelFile, false, useGzip); //logger.logDebug("Saved lowres tile: " + model.getTile()); } catch (IOException ex) { Logger.global.logError("Failed to save lowres-model: " + modelFile, ex); From 7cca8e428f2a304870eb3999d1c0ed34cfd63f95 Mon Sep 17 00:00:00 2001 From: Katrix Date: Sat, 18 Jan 2020 19:30:44 +0100 Subject: [PATCH 09/13] Limit raycast objects to the current tile --- .../src/main/webroot/js/libs/BlueMap.js | 3 +++ .../src/main/webroot/js/libs/Controls.js | 22 ++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js index 97176aed..c890fb90 100644 --- a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js +++ b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js @@ -80,6 +80,8 @@ export default class BlueMap { this.controls = new Controls(this.camera, this.element, this.hiresScene); this.loadSettings().then(async () => { + this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']); + this.lowresTileManager = new TileManager( this, this.settings[this.map]['lowres']['viewDistance'], @@ -120,6 +122,7 @@ export default class BlueMap { this.lowresTileManager.close(); this.map = map; + this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']); this.controls.resetPosition(); this.lowresTileManager = new TileManager( diff --git a/BlueMapCore/src/main/webroot/js/libs/Controls.js b/BlueMapCore/src/main/webroot/js/libs/Controls.js index 49609440..e0bb3efe 100644 --- a/BlueMapCore/src/main/webroot/js/libs/Controls.js +++ b/BlueMapCore/src/main/webroot/js/libs/Controls.js @@ -113,6 +113,10 @@ export default class Controls { this.camera.updateProjectionMatrix(); } + setTileSize(tileSize) { + this.tileSize = tileSize; + } + resetPosition() { this.position = new Vector3(0, 70, 0); this.targetPosition = new Vector3(0, 70, 0); @@ -174,13 +178,25 @@ export default class Controls { } updateHeights() { - //TODO: this can be performance-improved by only intersecting the correct tile? + function between(n, min, max) { + return n >= min && n < max; + } + + let inTile = (pos, thisPos) => { + return between(pos.x, thisPos.x - this.tileSize.x, thisPos.x) && + between(pos.z, thisPos.z - this.tileSize.z, thisPos.z); + }; + + let tileChildren = (targetPos) => { + return this.heightScene.children.filter(child => inTile(child.position, targetPos)) + }; let rayStart = new Vector3(this.targetPosition.x, 300, this.targetPosition.z); this.raycaster.set(rayStart, this.rayDirection); this.raycaster.near = 1; this.raycaster.far = 300; - let intersects = this.raycaster.intersectObjects(this.heightScene.children); + let intersects = this.raycaster.intersectObjects(tileChildren(this.targetPosition)); + if (intersects.length > 0){ this.minHeight = intersects[0].point.y; //this.targetPosition.y = this.minHeight; @@ -191,7 +207,7 @@ export default class Controls { rayStart.set(this.camera.position.x, 300, this.camera.position.z); this.raycaster.set(rayStart, this.rayDirection); intersects.length = 0; - intersects = this.raycaster.intersectObjects(this.heightScene.children); + intersects = this.raycaster.intersectObjects(tileChildren(this.camera.position)); if (intersects.length > 0){ if (intersects[0].point.y > this.minHeight){ this.minHeight = intersects[0].point.y; From c5485e665767af3250ee3965725b2a15cba100a4 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 18 Jan 2020 23:33:38 +0100 Subject: [PATCH 10/13] Add touch controls --- BlueMapCore/package.json | 3 +- BlueMapCore/src/main/webroot/index.html | 1 + .../src/main/webroot/js/libs/BlueMap.js | 32 +++-- .../src/main/webroot/js/libs/Controls.js | 116 +++++++++++++++++- BlueMapCore/webpack.config.js | 1 + 5 files changed, 138 insertions(+), 15 deletions(-) diff --git a/BlueMapCore/package.json b/BlueMapCore/package.json index 512f0515..9788f551 100644 --- a/BlueMapCore/package.json +++ b/BlueMapCore/package.json @@ -17,7 +17,8 @@ "homepage": "https://github.com/BlueMap-Minecraft/BlueMap#readme", "dependencies": { "jquery": "^3.4.1", - "three": "^0.94.0" + "three": "^0.94.0", + "hammerjs": "^2.0.8" }, "devDependencies": { "css-loader": "^3.4.2", diff --git a/BlueMapCore/src/main/webroot/index.html b/BlueMapCore/src/main/webroot/index.html index d818108c..07fb8406 100644 --- a/BlueMapCore/src/main/webroot/index.html +++ b/BlueMapCore/src/main/webroot/index.html @@ -2,6 +2,7 @@ + BlueMap diff --git a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js index c890fb90..9cbb6532 100644 --- a/BlueMapCore/src/main/webroot/js/libs/BlueMap.js +++ b/BlueMapCore/src/main/webroot/js/libs/BlueMap.js @@ -69,6 +69,7 @@ export default class BlueMap { this.dataRoot = dataRoot; this.loadingNoticeElement = $('
loading...
').appendTo($(this.element)); + window.onerror = this.onLoadError; this.fileLoader = new FileLoader(); this.blobLoader = new FileLoader(); @@ -105,7 +106,7 @@ export default class BlueMap { this.initModules(); this.start(); - }); + }).catch(error => this.onLoadError(error.toString())); } initModules() { @@ -363,7 +364,6 @@ export default class BlueMap { return new Promise(resolve => { this.fileLoader.load(this.dataRoot + 'textures.json', textures => { textures = JSON.parse(textures); - let materials = []; for (let i = 0; i < textures['textures'].length; i++) { let t = textures['textures'][i]; @@ -399,7 +399,6 @@ export default class BlueMap { } this.hiresMaterial = materials; - resolve(); }); }); @@ -458,6 +457,17 @@ export default class BlueMap { }) } + onLoadError = (message, url, line, col) => { + this.loadingNoticeElement.remove(); + + this.toggleAlert(undefined, ` +
+

Error

+

${message}

+
+ `); + }; + // ###### UI ###### toggleAlert(id, content) { @@ -474,13 +484,15 @@ export default class BlueMap { alert.fadeIn(200); }; - let sameAlert = alertBox.find(`.alert[data-alert-id=${id}]`); - if (sameAlert.length > 0){ - alertBox.fadeOut(200, () => { - alertBox.html(''); - alertBox.show(); - }); - return; + if (id !== undefined) { + let sameAlert = alertBox.find(`.alert[data-alert-id=${id}]`); + if (sameAlert.length > 0) { + alertBox.fadeOut(200, () => { + alertBox.html(''); + alertBox.show(); + }); + return; + } } let oldAlerts = alertBox.find('.alert'); diff --git a/BlueMapCore/src/main/webroot/js/libs/Controls.js b/BlueMapCore/src/main/webroot/js/libs/Controls.js index e0bb3efe..2eef441d 100644 --- a/BlueMapCore/src/main/webroot/js/libs/Controls.js +++ b/BlueMapCore/src/main/webroot/js/libs/Controls.js @@ -30,6 +30,7 @@ import { Vector3, MOUSE } from 'three'; +import Hammer from 'hammerjs'; import { Vector2_ZERO } from './utils.js'; @@ -94,10 +95,15 @@ export default class Controls { this.cameraPosDelta = new Vector3(0, 0, 0); this.moveDelta = new Vector2(0, 0); - this.keyStates = {} + this.touchStart = new Vector2(0, 0); + this.touchDelta = new Vector2(0, 0); + + this.keyStates = {}; this.state = Controls.STATES.NONE; let canvas = $(this.element).find('canvas').get(0); + + // mouse events window.addEventListener('contextmenu', event => { event.preventDefault(); }, false); @@ -108,6 +114,37 @@ export default class Controls { window.addEventListener('keydown', this.onKeyDown, false); window.addEventListener('keyup', this.onKeyUp, false); + // touch events + this.hammer = new Hammer.Manager(canvas); + let touchMove = new Hammer.Pan({ event: 'move', direction: Hammer.DIRECTION_ALL, threshold: 0 }); + let touchTilt = new Hammer.Pan({ event: 'tilt', direction: Hammer.DIRECTION_VERTICAL, pointers: 2, threshold: 0 }); + let touchRotate = new Hammer.Rotate({ event: 'rotate', pointers: 2, threshold: 10 }); + let touchZoom = new Hammer.Pinch({ event: 'zoom', pointers: 2, threshold: 0 }); + + touchTilt.recognizeWith(touchRotate); + touchTilt.recognizeWith(touchZoom); + touchRotate.recognizeWith(touchZoom); + + this.hammer.add( touchMove ); + this.hammer.add( touchTilt ); + this.hammer.add( touchRotate ); + this.hammer.add( touchZoom ); + + this.hammer.on('movestart', this.onTouchDown); + this.hammer.on('movemove', this.onTouchMove); + this.hammer.on('moveend', this.onTouchUp); + this.hammer.on('movecancel', this.onTouchUp); + this.hammer.on('tiltstart', this.onTouchTiltDown); + this.hammer.on('tiltmove', this.onTouchTiltMove); + this.hammer.on('tiltend', this.onTouchTiltUp); + this.hammer.on('tiltcancel', this.onTouchTiltUp); + this.hammer.on('rotatestart', this.onTouchRotateDown); + this.hammer.on('rotatemove', this.onTouchRotateMove); + this.hammer.on('rotateend', this.onTouchRotateUp); + this.hammer.on('rotatecancel', this.onTouchRotateUp); + this.hammer.on('zoomstart', this.onTouchZoomDown); + this.hammer.on('zoommove', this.onTouchZoomMove); + this.camera.position.set(0, 1000, 0); this.camera.lookAt(this.position); this.camera.updateProjectionMatrix(); @@ -218,9 +255,6 @@ export default class Controls { updateMouseMoves = () => { this.deltaMouse.set(this.lastMouse.x - this.mouse.x, this.lastMouse.y - this.mouse.y); - this.moveDelta.x = 0; - this.moveDelta.y = 0; - if (this.keyStates[Controls.KEYS.UP]){ this.moveDelta.y -= 20; } @@ -254,6 +288,9 @@ export default class Controls { } this.lastMouse.copy(this.mouse); + + this.moveDelta.x = 0; + this.moveDelta.y = 0; }; onMouseWheel = event => { @@ -305,6 +342,77 @@ export default class Controls { } }; + onTouchDown = event => { + this.touchStart.x = this.targetPosition.x; + this.touchStart.y = this.targetPosition.z; + this.state = Controls.STATES.MOVE; + }; + + onTouchMove = event => { + if (this.state !== Controls.STATES.MOVE) return; + + this.touchDelta.x = event.deltaX; + this.touchDelta.y = event.deltaY; + + if (this.touchDelta.x !== 0 || this.touchDelta.y !== 0) { + this.touchDelta.rotateAround(Vector2_ZERO, -this.direction); + + this.targetPosition.x = this.touchStart.x - (this.touchDelta.x * this.distance / this.element.clientHeight * this.settings.move.speed); + this.targetPosition.z = this.touchStart.y - (this.touchDelta.y * this.distance / this.element.clientHeight * this.settings.move.speed); + } + }; + + onTouchUp = event => { + this.state = Controls.STATES.NONE; + }; + + onTouchTiltDown = event => { + this.touchTiltStart = this.targetAngle; + this.state = Controls.STATES.ORBIT; + }; + + onTouchTiltMove = event => { + if (this.state !== Controls.STATES.ORBIT) return; + + this.targetAngle = this.touchTiltStart - (event.deltaY / this.element.clientHeight * Math.PI); + }; + + onTouchTiltUp = event => { + this.state = Controls.STATES.NONE; + }; + + onTouchRotateDown = event => { + this.lastTouchRotation = event.rotation; + this.state = Controls.STATES.ORBIT; + }; + + onTouchRotateMove = event => { + if (this.state !== Controls.STATES.ORBIT) return; + + let delta = event.rotation - this.lastTouchRotation; + this.lastTouchRotation = event.rotation; + if (delta > 180) delta -= 360; + if (delta < -180) delta += 360; + + this.targetDirection += (delta * (Math.PI / 180)) * 1.4; + }; + + onTouchRotateUp = event => { + this.state = Controls.STATES.NONE; + }; + + + onTouchZoomDown = event => { + this.touchZoomStart = this.targetDistance; + }; + + onTouchZoomMove = event => { + this.targetDistance = this.touchZoomStart / event.scale; + + if (this.targetDistance < this.settings.zoom.min) this.targetDistance = this.settings.zoom.min; + if (this.targetDistance > this.settings.zoom.max) this.targetDistance = this.settings.zoom.max; + }; + onKeyDown = event => { this.keyStates[event.keyCode] = true; }; diff --git a/BlueMapCore/webpack.config.js b/BlueMapCore/webpack.config.js index e2b68645..0e271fa9 100644 --- a/BlueMapCore/webpack.config.js +++ b/BlueMapCore/webpack.config.js @@ -23,6 +23,7 @@ module.exports = { compress: true, port: 8080, hot: true, + host: "192.168.178.22" }, plugins: [ new MiniCssExtractPlugin({ From 5dc59b454b9908453073402648547f50034fafd1 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sun, 19 Jan 2020 00:26:43 +0100 Subject: [PATCH 11/13] Do some quick mobile stylings --- .../src/main/webroot/js/libs/Controls.js | 7 +++ .../main/webroot/style/modules/compass.scss | 9 +++- .../src/main/webroot/style/modules/info.scss | 4 +- .../main/webroot/style/modules/mapmenu.scss | 2 +- .../main/webroot/style/modules/position.scss | 2 +- .../main/webroot/style/modules/settings.scss | 11 +++-- BlueMapCore/src/main/webroot/style/style.scss | 47 ++++++++++++++++--- 7 files changed, 67 insertions(+), 15 deletions(-) diff --git a/BlueMapCore/src/main/webroot/js/libs/Controls.js b/BlueMapCore/src/main/webroot/js/libs/Controls.js index 2eef441d..da71cfb4 100644 --- a/BlueMapCore/src/main/webroot/js/libs/Controls.js +++ b/BlueMapCore/src/main/webroot/js/libs/Controls.js @@ -343,12 +343,17 @@ export default class Controls { }; onTouchDown = event => { + if (event.pointerType === "mouse") return; + + $(":focus").blur(); + this.touchStart.x = this.targetPosition.x; this.touchStart.y = this.targetPosition.z; this.state = Controls.STATES.MOVE; }; onTouchMove = event => { + if (event.pointerType === "mouse") return; if (this.state !== Controls.STATES.MOVE) return; this.touchDelta.x = event.deltaX; @@ -363,6 +368,8 @@ export default class Controls { }; onTouchUp = event => { + if (event.pointerType === "mouse") return; + this.state = Controls.STATES.NONE; }; diff --git a/BlueMapCore/src/main/webroot/style/modules/compass.scss b/BlueMapCore/src/main/webroot/style/modules/compass.scss index 8713e9d5..bfd715d7 100644 --- a/BlueMapCore/src/main/webroot/style/modules/compass.scss +++ b/BlueMapCore/src/main/webroot/style/modules/compass.scss @@ -1,6 +1,11 @@ #bluemap-compass { - width: 30px; - height: 30px; + width: 2rem; + height: 2rem; + + #bluemap-compass-needle { + width: 100%; + height: 100%; + } &:hover #bluemap-compass-needle { filter: invert(1); diff --git a/BlueMapCore/src/main/webroot/style/modules/info.scss b/BlueMapCore/src/main/webroot/style/modules/info.scss index 692f5b59..68b01549 100644 --- a/BlueMapCore/src/main/webroot/style/modules/info.scss +++ b/BlueMapCore/src/main/webroot/style/modules/info.scss @@ -1,6 +1,6 @@ #bluemap-info { - width: 30px; - height: 30px; + width: 2rem; + height: 2rem; text-align: center; &::after { diff --git a/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss b/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss index e0bef99e..5b5d68af 100644 --- a/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss +++ b/BlueMapCore/src/main/webroot/style/modules/mapmenu.scss @@ -1,5 +1,5 @@ #bluemap-mapmenu { - width: 200px; + width: 15rem; cursor: pointer; .selection, .dropdown li { diff --git a/BlueMapCore/src/main/webroot/style/modules/position.scss b/BlueMapCore/src/main/webroot/style/modules/position.scss index 053a00ba..35acb84d 100644 --- a/BlueMapCore/src/main/webroot/style/modules/position.scss +++ b/BlueMapCore/src/main/webroot/style/modules/position.scss @@ -2,7 +2,7 @@ position: relative; input { - width: 60px; + width: 4rem; height: 100%; border: none; outline: none; diff --git a/BlueMapCore/src/main/webroot/style/modules/settings.scss b/BlueMapCore/src/main/webroot/style/modules/settings.scss index 7353e447..8d934126 100644 --- a/BlueMapCore/src/main/webroot/style/modules/settings.scss +++ b/BlueMapCore/src/main/webroot/style/modules/settings.scss @@ -1,6 +1,11 @@ #bluemap-settings { - width: 30px; - height: 30px; + width: 2rem; + height: 2rem; + + > img { + width: 100%; + height: 100%; + } } #bluemap-settings.active:not(:hover) { @@ -18,7 +23,7 @@ #bluemap-settings-quality { width: 150px; - height: 30px; + height: 2rem; } diff --git a/BlueMapCore/src/main/webroot/style/style.scss b/BlueMapCore/src/main/webroot/style/style.scss index 99b09d94..d4a723bc 100644 --- a/BlueMapCore/src/main/webroot/style/style.scss +++ b/BlueMapCore/src/main/webroot/style/style.scss @@ -10,17 +10,21 @@ html, body { padding: 0; font-size: 15px; - line-height: 15px; + line-height: 1rem; font-family: Verdana,Helvetica,Arial,sans-serif; color: #333333; background-color: #dddddd; + + @media (max-width: 900px) { + font-size: 17px; + } } .box { color: #333333; background-color: white; - box-shadow: 0px 1px 4px 0px rgba(50, 50, 50, 0.8); + box-shadow: 0 1px 4px 0 rgba(50, 50, 50, 0.8); } .button { @@ -37,7 +41,7 @@ html, body { background-color: white; position: relative; - transition: all 0.3s; + transition: background-color 0.3s; } .dropdown-container:hover { @@ -53,7 +57,7 @@ html, body { overflow: hidden; transition: all 0.3s; - max-height: 0px; + max-height: 0; } .dropdown-container:hover > .dropdown { @@ -104,9 +108,15 @@ html, body { top: 10px; right: 10px; - line-height: 30px; + line-height: 2rem; display: flex; + + @media (max-width: 900px) { + display: none; + } + + } #bluemap-topright > *:not(:last-child) { @@ -118,9 +128,34 @@ html, body { top: 10px; left: 10px; - line-height: 30px; + line-height: 2rem; + + white-space: nowrap; display: flex; + + @media (max-width: 900px) { + top: 0; + left: 0; + width: 100%; + + >:last-child { + flex-grow: 1; + } + } + + @media (max-width: 500px) { + flex-wrap: wrap; + + > :not(:first-child) { + flex-grow: 1; + } + + >:last-child { + width: 100%; + border-top: solid 1px #dddddd; + } + } } #bluemap-topleft > *:not(:last-child) { From 0b8624d54e69f75fdeba9538796a66bb182577a7 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sun, 19 Jan 2020 01:51:30 +0100 Subject: [PATCH 12/13] Make CLI use some classes from common and remove the obsolete ones --- BlueMapCLI/build.gradle | 2 +- .../bluecolored/bluemap/cli/BlueMapCLI.java | 52 +++++- .../de/bluecolored/bluemap/cli/MapType.java | 91 ---------- .../bluecolored/bluemap/cli/RenderTask.java | 171 ------------------ 4 files changed, 51 insertions(+), 265 deletions(-) delete mode 100644 BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/MapType.java delete mode 100644 BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderTask.java diff --git a/BlueMapCLI/build.gradle b/BlueMapCLI/build.gradle index b17d6fa5..0820d22c 100644 --- a/BlueMapCLI/build.gradle +++ b/BlueMapCLI/build.gradle @@ -1,4 +1,4 @@ dependencies { compile group: 'commons-cli', name: 'commons-cli', version: '1.4' - compile project(':BlueMapCore') + compile project(':BlueMapCommon') } diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java index d6bca0e8..881d4a21 100644 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -45,10 +45,15 @@ import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.time.DurationFormatUtils; +import com.flowpowered.math.GenericMath; import com.flowpowered.math.vector.Vector2i; import com.google.common.base.Preconditions; +import de.bluecolored.bluemap.common.MapType; +import de.bluecolored.bluemap.common.RenderManager; +import de.bluecolored.bluemap.common.RenderTask; import de.bluecolored.bluemap.core.config.ConfigManager; import de.bluecolored.bluemap.core.config.MainConfig; import de.bluecolored.bluemap.core.config.MainConfig.MapConfig; @@ -147,6 +152,9 @@ public void renderMaps() throws IOException { File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile(); resourcePack.saveTextureFile(textureExportFile); + RenderManager renderManager = new RenderManager(config.getRenderThreadCount()); + renderManager.start(); + for (MapType map : maps.values()) { Logger.global.logInfo("Rendering map '" + map.getId() + "' ..."); Logger.global.logInfo("Collecting tiles to render..."); @@ -173,8 +181,46 @@ public void renderMaps() throws IOException { Logger.global.logInfo("Starting Render..."); long starttime = System.currentTimeMillis(); - RenderTask task = new RenderTask(map, tiles, config.getRenderThreadCount()); - task.render(); + RenderTask task = new RenderTask("Map-Render: " + map.getName(), map); + task.addTiles(tiles); + task.optimizeQueue(); + + renderManager.addRenderTask(task); + + long lastLogUpdate = System.currentTimeMillis(); + long lastSave = lastLogUpdate; + + while(!task.isFinished()) { + try { + Thread.sleep(200); + } catch (InterruptedException e) {} + + long now = System.currentTimeMillis(); + + if (lastLogUpdate < now - 10000) { // print update all 10 seconds + lastLogUpdate = now; + long time = task.getActiveTime(); + + String durationString = DurationFormatUtils.formatDurationWords(time, true, true); + int tileCount = task.getRemainingTileCount() + task.getRenderedTileCount(); + double pct = (double)task.getRenderedTileCount() / (double) tileCount; + + long ert = (long)((time / pct) * (1d - pct)); + String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); + + double tps = task.getRenderedTileCount() / (time / 1000.0); + + Logger.global.logInfo("Rendered " + task.getRenderedTileCount() + " of " + tileCount + " tiles in " + durationString + " | " + GenericMath.round(tps, 3) + " tiles/s"); + Logger.global.logInfo(GenericMath.round(pct * 100, 3) + "% | Estimated remaining time: " + ertDurationString); + } + + if (lastSave < now - 5 * 60000) { // save every 5 minutes + lastSave = now; + map.getTileRenderer().save(); + } + } + + map.getTileRenderer().save(); try { webSettings.set(starttime, map.getId(), "last-render"); @@ -184,6 +230,8 @@ public void renderMaps() throws IOException { } } + renderManager.stop(); + Logger.global.logInfo("Waiting for all threads to quit..."); if (!ForkJoinPool.commonPool().awaitQuiescence(30, TimeUnit.SECONDS)) { Logger.global.logWarning("Some save-threads are taking very long to exit (>30s), they will be ignored."); diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/MapType.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/MapType.java deleted file mode 100644 index 501a79d3..00000000 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/MapType.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.cli; - -import java.io.IOException; - -import com.flowpowered.math.vector.Vector2i; -import com.google.common.base.Preconditions; - -import de.bluecolored.bluemap.core.render.TileRenderer; -import de.bluecolored.bluemap.core.render.WorldTile; -import de.bluecolored.bluemap.core.world.World; - -public class MapType { - - private final String id; - private String name; - private World world; - private TileRenderer tileRenderer; - - public MapType(String id, String name, World world, TileRenderer tileRenderer) { - Preconditions.checkNotNull(id); - Preconditions.checkNotNull(name); - Preconditions.checkNotNull(world); - Preconditions.checkNotNull(tileRenderer); - - this.id = id; - this.name = name; - this.world = world; - this.tileRenderer = tileRenderer; - } - - public String getId() { - return id; - } - - public String getName() { - return name; - } - - public World getWorld() { - return world; - } - - public TileRenderer getTileRenderer() { - return tileRenderer; - } - - public void renderTile(Vector2i tile) throws IOException { - getTileRenderer().render(new WorldTile(getWorld(), tile)); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj != null && obj instanceof MapType) { - MapType that = (MapType) obj; - - return this.id.equals(that.id); - } - - return false; - } - -} diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderTask.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderTask.java deleted file mode 100644 index 5ae8ec89..00000000 --- a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderTask.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * This file is part of BlueMap, licensed under the MIT License (MIT). - * - * Copyright (c) Blue (Lukas Rieger) - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package de.bluecolored.bluemap.cli; - -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; - -import org.apache.commons.lang3.time.DurationFormatUtils; - -import com.flowpowered.math.GenericMath; -import com.flowpowered.math.vector.Vector2d; -import com.flowpowered.math.vector.Vector2i; - -import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.render.TileRenderer; -import de.bluecolored.bluemap.core.render.WorldTile; -import de.bluecolored.bluemap.core.world.World; - -public class RenderTask { - - private World world; - private TileRenderer tileRenderer; - private Deque tilesToRender; - - private int tileCount; - private long startTime = -1; - private int renderedTiles = 0; - - private Thread[] threads; - - public RenderTask(MapType map, Collection tilesToRender, int threadCount) { - this.world = map.getWorld(); - this.tileRenderer = map.getTileRenderer(); - - //Sort the chunks to opimize the chunk-cache usage of MCAWorld and generate the world in a nicer order, so you can see the first results early in the web-map during render - Vector2d sortGridSize = new Vector2d(20, 20).div(tileRenderer.getHiresModelManager().getTileSize().toDouble().div(16)).ceil().max(1, 1); //Find a good grid size to match the MCAWorlds chunk-cache size of 500 - ArrayList sortedTiles = new ArrayList<>(tilesToRender); - sortedTiles.sort((v1, v2) -> { - Vector2i v1SortGridPos = v1.toDouble().div(sortGridSize).floor().toInt(); - Vector2i v2SortGridPos = v2.toDouble().div(sortGridSize).floor().toInt(); - - if (v1SortGridPos != v2SortGridPos){ - int v1Dist = v1SortGridPos.distanceSquared(Vector2i.ZERO); - int v2Dist = v2SortGridPos.distanceSquared(Vector2i.ZERO); - - if (v1Dist < v2Dist) return -1; - if (v1Dist > v2Dist) return 1; - - if (v1SortGridPos.getY() < v2SortGridPos.getY()) return -1; - if (v1SortGridPos.getY() > v2SortGridPos.getY()) return 1; - if (v1SortGridPos.getX() < v2SortGridPos.getX()) return -1; - if (v1SortGridPos.getX() > v2SortGridPos.getX()) return 1; - } - - if (v1.getY() < v2.getY()) return -1; - if (v1.getY() > v2.getY()) return 1; - if (v1.getX() < v2.getX()) return -1; - if (v1.getX() > v2.getX()) return 1; - - return 0; - }); - - this.tilesToRender = new ArrayDeque<>(sortedTiles); - - this.tileCount = this.tilesToRender.size(); - this.threads = new Thread[threadCount]; - - - } - - public void render() { - this.startTime = System.currentTimeMillis(); - - for (int i = 0; i < threads.length; i++) { - if (threads[i] != null) threads[i].interrupt(); - - threads[i] = new Thread(this::renderThread); - threads[i].start(); - } - - long lastLogUpdate = startTime; - long lastSave = startTime; - - while (!Thread.interrupted()) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - break; - } - - boolean stillRendering = false; - for (Thread t : threads) { - if (t.isAlive()) { - stillRendering = true; - break; - } - } - if (!stillRendering) break; - - long now = System.currentTimeMillis(); - if (lastLogUpdate < now - 10000) { // print update all 10 seconds - lastLogUpdate = now; - - long time = now - startTime; - String durationString = DurationFormatUtils.formatDurationWords(time, true, true); - double pct = (double)renderedTiles / (double)tileCount; - - long ert = (long)((time / pct) * (1d - pct)); - String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true); - - double tps = renderedTiles / (time / 1000.0); - - Logger.global.logInfo("Rendered " + renderedTiles + " of " + tileCount + " tiles in " + durationString + " | " + GenericMath.round(tps, 3) + " tiles/s"); - Logger.global.logInfo(GenericMath.round(pct * 100, 3) + "% | Estimated remaining time: " + ertDurationString); - } - - if (lastSave < now - 5 * 60000) { // save every 5 minutes - lastSave = now; - tileRenderer.save(); - } - } - - tileRenderer.save(); - } - - private void renderThread() { - Vector2i tilePos; - - while (!Thread.interrupted()) { - synchronized (tilesToRender) { - if (tilesToRender.isEmpty()) break; - tilePos = tilesToRender.poll(); - } - - WorldTile tile = new WorldTile(world, tilePos); - try { - tileRenderer.render(tile); - } catch (IOException e) { - Logger.global.logError("Failed to render tile " + tilePos, e); - } - - renderedTiles++; - } - } - -} From eb45eb9b15743c920e923ec4c3810509a830b2cd Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sun, 19 Jan 2020 02:01:21 +0100 Subject: [PATCH 13/13] Whoops, this was not meant to be committed --- BlueMapCore/webpack.config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/BlueMapCore/webpack.config.js b/BlueMapCore/webpack.config.js index 0e271fa9..d62578f6 100644 --- a/BlueMapCore/webpack.config.js +++ b/BlueMapCore/webpack.config.js @@ -22,8 +22,7 @@ module.exports = { contentBase: WORLD_DATA_PATH, compress: true, port: 8080, - hot: true, - host: "192.168.178.22" + hot: true }, plugins: [ new MiniCssExtractPlugin({