Merge branch 'master' into mc/1.12

This commit is contained in:
Blue (Lukas Rieger) 2020-01-19 02:03:52 +01:00
commit 4a04746794
33 changed files with 428 additions and 357 deletions

3
.gitignore vendored
View File

@ -20,6 +20,9 @@ bin/*
.project .project
*/.project */.project
.idea
*/.idea
node_modules/ node_modules/
package-lock.json package-lock.json

View File

@ -109,6 +109,12 @@ maps: [
# Default is enabled # Default is enabled
renderEdges: true 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 is the high-resolution render of the map. Where you see every block.
hires { hires {
# Defines the size of one map-tile in blocks. # Defines the size of one map-tile in blocks.

View File

@ -1,4 +1,4 @@
dependencies { dependencies {
compile group: 'commons-cli', name: 'commons-cli', version: '1.4' compile group: 'commons-cli', name: 'commons-cli', version: '1.4'
compile project(':BlueMapCore') compile project(':BlueMapCommon')
} }

View File

@ -45,10 +45,15 @@
import org.apache.commons.cli.Options; import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException; import org.apache.commons.cli.ParseException;
import org.apache.commons.io.FileUtils; 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.flowpowered.math.vector.Vector2i;
import com.google.common.base.Preconditions; 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.ConfigManager;
import de.bluecolored.bluemap.core.config.MainConfig; import de.bluecolored.bluemap.core.config.MainConfig;
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig; import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
@ -116,7 +121,8 @@ public void renderMaps() throws IOException {
LowresModelManager lowresModelManager = new LowresModelManager( LowresModelManager lowresModelManager = new LowresModelManager(
config.getWebDataPath().resolve(mapConfig.getId()).resolve("lowres"), config.getWebDataPath().resolve(mapConfig.getId()).resolve("lowres"),
new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()), 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); TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager);
@ -146,6 +152,9 @@ public void renderMaps() throws IOException {
File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile(); File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile();
resourcePack.saveTextureFile(textureExportFile); resourcePack.saveTextureFile(textureExportFile);
RenderManager renderManager = new RenderManager(config.getRenderThreadCount());
renderManager.start();
for (MapType map : maps.values()) { for (MapType map : maps.values()) {
Logger.global.logInfo("Rendering map '" + map.getId() + "' ..."); Logger.global.logInfo("Rendering map '" + map.getId() + "' ...");
Logger.global.logInfo("Collecting tiles to render..."); Logger.global.logInfo("Collecting tiles to render...");
@ -166,15 +175,52 @@ public void renderMaps() throws IOException {
} }
if (tiles.isEmpty()) { if (tiles.isEmpty()) {
Logger.global.logInfo("Render finished!"); continue;
return;
} }
Logger.global.logInfo("Starting Render..."); Logger.global.logInfo("Starting Render...");
long starttime = System.currentTimeMillis(); long starttime = System.currentTimeMillis();
RenderTask task = new RenderTask(map, tiles, config.getRenderThreadCount()); RenderTask task = new RenderTask("Map-Render: " + map.getName(), map);
task.render(); 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 { try {
webSettings.set(starttime, map.getId(), "last-render"); 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..."); Logger.global.logInfo("Waiting for all threads to quit...");
if (!ForkJoinPool.commonPool().awaitQuiescence(30, TimeUnit.SECONDS)) { if (!ForkJoinPool.commonPool().awaitQuiescence(30, TimeUnit.SECONDS)) {
Logger.global.logWarning("Some save-threads are taking very long to exit (>30s), they will be ignored."); Logger.global.logWarning("Some save-threads are taking very long to exit (>30s), they will be ignored.");

View File

@ -1,91 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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;
}
}

View File

@ -1,171 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* 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<Vector2i> tilesToRender;
private int tileCount;
private long startTime = -1;
private int renderedTiles = 0;
private Thread[] threads;
public RenderTask(MapType map, Collection<Vector2i> 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<Vector2i> 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++;
}
}
}

View File

@ -108,6 +108,12 @@ maps: [
# Default is enabled # Default is enabled
renderEdges: true 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 is the high-resolution render of the map. Where you see every block.
hires { hires {
# Defines the size of one map-tile in blocks. # Defines the size of one map-tile in blocks.

View File

@ -190,7 +190,8 @@ public synchronized void load() throws IOException, ParseResourceException {
LowresModelManager lowresModelManager = new LowresModelManager( LowresModelManager lowresModelManager = new LowresModelManager(
config.getWebDataPath().resolve(id).resolve("lowres"), config.getWebDataPath().resolve(id).resolve("lowres"),
new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()), 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); TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager);

View File

@ -17,7 +17,8 @@
"homepage": "https://github.com/BlueMap-Minecraft/BlueMap#readme", "homepage": "https://github.com/BlueMap-Minecraft/BlueMap#readme",
"dependencies": { "dependencies": {
"jquery": "^3.4.1", "jquery": "^3.4.1",
"three": "^0.94.0" "three": "^0.94.0",
"hammerjs": "^2.0.8"
}, },
"devDependencies": { "devDependencies": {
"css-loader": "^3.4.2", "css-loader": "^3.4.2",

View File

@ -201,6 +201,8 @@ public class MapConfig implements RenderSettings {
private Vector3i min, max; private Vector3i min, max;
private boolean renderEdges; private boolean renderEdges;
private boolean useGzip;
private int hiresTileSize; private int hiresTileSize;
private float hiresViewDistance; private float hiresViewDistance;
@ -231,6 +233,8 @@ private MapConfig(ConfigurationNode node) throws IOException {
this.max = new Vector3i(maxX, maxY, maxZ); this.max = new Vector3i(maxX, maxY, maxZ);
this.renderEdges = node.getNode("renderEdges").getBoolean(true); this.renderEdges = node.getNode("renderEdges").getBoolean(true);
this.renderEdges = node.getNode("useCompression").getBoolean(true);
this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32); this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32);
this.hiresViewDistance = node.getNode("hires", "viewDistance").getFloat(4.5f); this.hiresViewDistance = node.getNode("hires", "viewDistance").getFloat(4.5f);
@ -310,6 +314,11 @@ public boolean isRenderEdges() {
return renderEdges; return renderEdges;
} }
@Override
public boolean useGzipCompression() {
return useGzip;
}
} }
private void checkOutdated(ConfigurationNode node) throws OutdatedConfigException { private void checkOutdated(ConfigurationNode node) throws OutdatedConfigException {

View File

@ -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 { public static MCAWorld load(Path worldFolder, UUID uuid, BlockIdMapper blockIdMapper, BlockPropertiesMapper blockPropertiesMapper, BiomeMapper biomeIdMapper) throws IOException {
try { 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"); CompoundTag levelData = level.getCompoundTag("Data");
String name = levelData.getString("LevelName"); String name = levelData.getString("LevelName");

View File

@ -78,6 +78,13 @@ default boolean isRenderEdges() {
return true; return true;
} }
/**
* If gzip compression will be used to compress the generated files
*/
default boolean useGzipCompression() {
return true;
}
default RenderSettings copy() { default RenderSettings copy() {
return new StaticRenderSettings( return new StaticRenderSettings(
getAmbientOcclusionStrenght(), getAmbientOcclusionStrenght(),

View File

@ -27,6 +27,7 @@
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -58,11 +59,13 @@ public class HiresModelManager {
private ExecutorService savingExecutor; private ExecutorService savingExecutor;
private boolean useGzip;
public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Vector2i tileSize, ExecutorService savingExecutor) { 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.fileRoot = fileRoot;
this.renderer = renderer; this.renderer = renderer;
@ -70,6 +73,7 @@ public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i ti
this.gridOrigin = gridOrigin; this.gridOrigin = gridOrigin;
this.savingExecutor = savingExecutor; this.savingExecutor = savingExecutor;
this.useGzip = useGzip;
} }
/** /**
@ -87,7 +91,7 @@ private void save(final HiresModel model) {
} }
private void save(HiresModel model, String modelJson){ private void save(HiresModel model, String modelJson){
File file = getFile(model.getTile()); File file = getFile(model.getTile(), useGzip);
try { try {
if (!file.exists()){ if (!file.exists()){
@ -95,9 +99,9 @@ private void save(HiresModel model, String modelJson){
file.createNewFile(); file.createNewFile();
} }
FileOutputStream fos = new FileOutputStream(file); OutputStream os = new FileOutputStream(file);
GZIPOutputStream zos = new GZIPOutputStream(fos); if (useGzip) os = new GZIPOutputStream(os);
OutputStreamWriter osw = new OutputStreamWriter(zos, StandardCharsets.UTF_8); OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
try ( try (
PrintWriter pw = new PrintWriter(osw); PrintWriter pw = new PrintWriter(osw);
){ ){
@ -185,8 +189,8 @@ public Vector2i posToTile(Vector3d pos){
/** /**
* Returns the file for a tile * Returns the file for a tile
*/ */
public File getFile(Vector2i tilePos){ public File getFile(Vector2i tilePos, boolean gzip){
return FileUtils.coordsToFile(fileRoot, tilePos, "json.gz"); return FileUtils.coordsToFile(fileRoot, tilePos, "json" + (gzip ? ".gz" : ""));
} }
} }

View File

@ -27,6 +27,7 @@
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter; import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -95,7 +96,7 @@ public void update(Vector2i point, float height, Vector3f color){
* Saves this model to its file * Saves this model to its file
* @param force if this is false, the model is only saved if it has any changes * @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; if (!force && !hasUnsavedChanges) return;
this.hasUnsavedChanges = false; 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); throw new IOException("Failed to get write-access to file: " + file, e);
} }
FileOutputStream fos = new FileOutputStream(file); OutputStream os = new FileOutputStream(file);
GZIPOutputStream zos = new GZIPOutputStream(fos); if (useGzip) os = new GZIPOutputStream(os);
OutputStreamWriter osw = new OutputStreamWriter(zos, StandardCharsets.UTF_8); OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
try ( try (
PrintWriter pw = new PrintWriter(osw); PrintWriter pw = new PrintWriter(osw);
){ ){

View File

@ -27,6 +27,7 @@
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
@ -58,14 +59,18 @@ public class LowresModelManager {
private Vector2i pointsPerHiresTile; private Vector2i pointsPerHiresTile;
private Map<File, CachedModel> models; private Map<File, CachedModel> 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.fileRoot = fileRoot;
this.gridSize = gridSize; this.gridSize = gridSize;
this.pointsPerHiresTile = pointsPerHiresTile; this.pointsPerHiresTile = pointsPerHiresTile;
models = new ConcurrentHashMap<>(); 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 * Returns the file for a tile
*/ */
public File getFile(Vector2i tile){ public File getFile(Vector2i tile, boolean useGzip){
return FileUtils.coordsToFile(fileRoot, tile, "json.gz"); return FileUtils.coordsToFile(fileRoot, tile, "json" + (useGzip ? ".gz" : ""));
} }
private LowresModel getModel(UUID world, Vector2i tile) throws IOException { private LowresModel getModel(UUID world, Vector2i tile) throws IOException {
File modelFile = getFile(tile); File modelFile = getFile(tile, useGzip);
CachedModel model = models.get(modelFile); CachedModel model = models.get(modelFile);
if (model == null){ if (model == null){
@ -181,11 +186,10 @@ private LowresModel getModel(UUID world, Vector2i tile) throws IOException {
if (model == null){ if (model == null){
if (modelFile.exists()){ if (modelFile.exists()){
FileInputStream fis = new FileInputStream(modelFile); InputStream is = new FileInputStream(modelFile);
try( if (useGzip) is = new GZIPInputStream(is);
GZIPInputStream zis = new GZIPInputStream(fis); try {
){ String json = IOUtils.toString(is, StandardCharsets.UTF_8);
String json = IOUtils.toString(zis, StandardCharsets.UTF_8);
try { try {
model = new CachedModel(world, tile, BufferGeometry.fromJson(json)); 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()); //gridFile.renameTo(gridFile.toPath().getParent().resolve(gridFile.getName() + ".broken").toFile());
modelFile.delete(); modelFile.delete();
} }
} finally {
is.close();
} }
} }
@ -238,10 +244,10 @@ public synchronized void tidyUpModelCache() {
} }
private synchronized void saveAndRemoveModel(CachedModel model) { private synchronized void saveAndRemoveModel(CachedModel model) {
File modelFile = getFile(model.getTile()); File modelFile = getFile(model.getTile(), useGzip);
models.remove(modelFile); models.remove(modelFile);
try { try {
model.save(modelFile, false); model.save(modelFile, false, useGzip);
//logger.logDebug("Saved and unloaded lowres tile: " + model.getTile()); //logger.logDebug("Saved and unloaded lowres tile: " + model.getTile());
} catch (IOException ex) { } catch (IOException ex) {
Logger.global.logError("Failed to save and unload lowres-model: " + modelFile, 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) { private void saveModel(CachedModel model) {
File modelFile = getFile(model.getTile()); File modelFile = getFile(model.getTile(), useGzip);
try { try {
model.save(modelFile, false); model.save(modelFile, false, useGzip);
//logger.logDebug("Saved lowres tile: " + model.getTile()); //logger.logDebug("Saved lowres tile: " + model.getTile());
} catch (IOException ex) { } catch (IOException ex) {
Logger.global.logError("Failed to save lowres-model: " + modelFile, ex); Logger.global.logError("Failed to save lowres-model: " + modelFile, ex);

View File

@ -38,6 +38,7 @@
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@ -160,9 +161,21 @@ private HttpResponse generateResponse(HttpRequest request) {
} catch (IllegalArgumentException e){} } catch (IllegalArgumentException e){}
} }
//check ETag
String eTag = Long.toHexString(file.length()) + Integer.toHexString(file.hashCode()) + Long.toHexString(lastModified);
Set<String> 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); HttpResponse response = new HttpResponse(HttpStatusCode.OK);
response.addHeader("ETag", eTag);
if (lastModified > 0) response.addHeader("Last-Modified", timestampToString(lastModified)); 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 //add content type header
String filetype = file.getName().toString(); String filetype = file.getName().toString();

View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
<title>BlueMap</title> <title>BlueMap</title>
</head> </head>
<body> <body>

View File

@ -69,6 +69,7 @@ export default class BlueMap {
this.dataRoot = dataRoot; this.dataRoot = dataRoot;
this.loadingNoticeElement = $('<div id="bluemap-loading" class="box">loading...</div>').appendTo($(this.element)); this.loadingNoticeElement = $('<div id="bluemap-loading" class="box">loading...</div>').appendTo($(this.element));
window.onerror = this.onLoadError;
this.fileLoader = new FileLoader(); this.fileLoader = new FileLoader();
this.blobLoader = new FileLoader(); this.blobLoader = new FileLoader();
@ -80,6 +81,8 @@ export default class BlueMap {
this.controls = new Controls(this.camera, this.element, this.hiresScene); this.controls = new Controls(this.camera, this.element, this.hiresScene);
this.loadSettings().then(async () => { this.loadSettings().then(async () => {
this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']);
this.lowresTileManager = new TileManager( this.lowresTileManager = new TileManager(
this, this,
this.settings[this.map]['lowres']['viewDistance'], this.settings[this.map]['lowres']['viewDistance'],
@ -103,7 +106,7 @@ export default class BlueMap {
this.initModules(); this.initModules();
this.start(); this.start();
}); }).catch(error => this.onLoadError(error.toString()));
} }
initModules() { initModules() {
@ -120,6 +123,7 @@ export default class BlueMap {
this.lowresTileManager.close(); this.lowresTileManager.close();
this.map = map; this.map = map;
this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']);
this.controls.resetPosition(); this.controls.resetPosition();
this.lowresTileManager = new TileManager( this.lowresTileManager = new TileManager(
@ -205,7 +209,9 @@ export default class BlueMap {
setTimeout(this.update, 1000); setTimeout(this.update, 1000);
this.lowresTileManager.setPosition(this.controls.targetPosition); 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.locationHash =
'#' + this.map '#' + this.map
@ -239,7 +245,7 @@ export default class BlueMap {
this.renderer.clearDepth(); this.renderer.clearDepth();
this.renderer.render(this.hiresScene, this.camera, this.renderer.getRenderTarget(), false); this.renderer.render(this.hiresScene, this.camera, this.renderer.getRenderTarget(), false);
} }
} };
handleContainerResize = () => { handleContainerResize = () => {
this.camera.aspect = this.element.clientWidth / this.element.clientHeight; this.camera.aspect = this.element.clientWidth / this.element.clientHeight;
@ -254,7 +260,7 @@ export default class BlueMap {
.css('height', this.element.clientHeight); .css('height', this.element.clientHeight);
this.updateFrame = true; this.updateFrame = true;
} };
async loadSettings() { async loadSettings() {
return new Promise(resolve => { return new Promise(resolve => {
@ -358,7 +364,6 @@ export default class BlueMap {
return new Promise(resolve => { return new Promise(resolve => {
this.fileLoader.load(this.dataRoot + 'textures.json', textures => { this.fileLoader.load(this.dataRoot + 'textures.json', textures => {
textures = JSON.parse(textures); textures = JSON.parse(textures);
let materials = []; let materials = [];
for (let i = 0; i < textures['textures'].length; i++) { for (let i = 0; i < textures['textures'].length; i++) {
let t = textures['textures'][i]; let t = textures['textures'][i];
@ -394,7 +399,6 @@ export default class BlueMap {
} }
this.hiresMaterial = materials; this.hiresMaterial = materials;
resolve(); resolve();
}); });
}); });
@ -453,22 +457,44 @@ export default class BlueMap {
}) })
} }
onLoadError = (message, url, line, col) => {
this.loadingNoticeElement.remove();
this.toggleAlert(undefined, `
<div style="max-width: 500px">
<h1>Error</h1>
<p style="color: red; font-family: monospace">${message}</p>
</div>
`);
};
// ###### UI ###### // ###### UI ######
alert(content) { toggleAlert(id, content) {
let alertBox = $('#alert-box'); let alertBox = $('#alert-box');
if (alertBox.length === 0){ if (alertBox.length === 0){
alertBox = $('<div id="alert-box"></div>').appendTo(this.element); alertBox = $('<div id="alert-box"></div>').appendTo(this.element);
} }
let displayAlert = () => { let displayAlert = () => {
let alert = $(`<div class="alert box" style="display: none;"><div class="alert-close-button"></div>${content}</div>`).appendTo(alertBox); let alert = $(`<div class="alert box" data-alert-id="${id}" style="display: none;"><div class="alert-close-button"></div>${content}</div>`).appendTo(alertBox);
alert.find('.alert-close-button').click(() => { alert.find('.alert-close-button').click(() => {
alert.fadeOut(200, () => alert.remove()); alert.fadeOut(200, () => alert.remove());
}); });
alert.fadeIn(200); alert.fadeIn(200);
}; };
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'); let oldAlerts = alertBox.find('.alert');
if (oldAlerts.length > 0){ if (oldAlerts.length > 0){
alertBox.fadeOut(200, () => { alertBox.fadeOut(200, () => {
@ -476,8 +502,9 @@ export default class BlueMap {
alertBox.show(); alertBox.show();
displayAlert(); displayAlert();
}); });
} else { return;
displayAlert();
} }
displayAlert();
} }
} }

View File

@ -30,6 +30,7 @@ import {
Vector3, Vector3,
MOUSE MOUSE
} from 'three'; } from 'three';
import Hammer from 'hammerjs';
import { Vector2_ZERO } from './utils.js'; import { Vector2_ZERO } from './utils.js';
@ -94,25 +95,65 @@ export default class Controls {
this.cameraPosDelta = new Vector3(0, 0, 0); this.cameraPosDelta = new Vector3(0, 0, 0);
this.moveDelta = new Vector2(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; this.state = Controls.STATES.NONE;
let canvas = $(this.element).find('canvas').get(0); let canvas = $(this.element).find('canvas').get(0);
// mouse events
window.addEventListener('contextmenu', event => { window.addEventListener('contextmenu', event => {
event.preventDefault(); event.preventDefault();
}, false); }, false);
window.addEventListener('mousemove', this.onMouseMove, false); window.addEventListener('mousemove', this.onMouseMove, false);
canvas.addEventListener('mousedown', this.onMouseDown, 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 }); canvas.addEventListener('wheel', this.onMouseWheel, { passive: true });
window.addEventListener('keydown', this.onKeyDown, false); window.addEventListener('keydown', this.onKeyDown, false);
window.addEventListener('keyup', this.onKeyUp, 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.position.set(0, 1000, 0);
this.camera.lookAt(this.position); this.camera.lookAt(this.position);
this.camera.updateProjectionMatrix(); this.camera.updateProjectionMatrix();
} }
setTileSize(tileSize) {
this.tileSize = tileSize;
}
resetPosition() { resetPosition() {
this.position = new Vector3(0, 70, 0); this.position = new Vector3(0, 70, 0);
this.targetPosition = new Vector3(0, 70, 0); this.targetPosition = new Vector3(0, 70, 0);
@ -174,13 +215,25 @@ export default class Controls {
} }
updateHeights() { 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); let rayStart = new Vector3(this.targetPosition.x, 300, this.targetPosition.z);
this.raycaster.set(rayStart, this.rayDirection); this.raycaster.set(rayStart, this.rayDirection);
this.raycaster.near = 1; this.raycaster.near = 1;
this.raycaster.far = 300; this.raycaster.far = 300;
let intersects = this.raycaster.intersectObjects(this.heightScene.children); let intersects = this.raycaster.intersectObjects(tileChildren(this.targetPosition));
if (intersects.length > 0){ if (intersects.length > 0){
this.minHeight = intersects[0].point.y; this.minHeight = intersects[0].point.y;
//this.targetPosition.y = this.minHeight; //this.targetPosition.y = this.minHeight;
@ -191,20 +244,17 @@ export default class Controls {
rayStart.set(this.camera.position.x, 300, this.camera.position.z); rayStart.set(this.camera.position.x, 300, this.camera.position.z);
this.raycaster.set(rayStart, this.rayDirection); this.raycaster.set(rayStart, this.rayDirection);
intersects.length = 0; intersects.length = 0;
intersects = this.raycaster.intersectObjects(this.heightScene.children); intersects = this.raycaster.intersectObjects(tileChildren(this.camera.position));
if (intersects.length > 0){ if (intersects.length > 0){
if (intersects[0].point.y > this.minHeight){ if (intersects[0].point.y > this.minHeight){
this.minHeight = intersects[0].point.y; this.minHeight = intersects[0].point.y;
} }
} }
}; }
updateMouseMoves = () => { updateMouseMoves = () => {
this.deltaMouse.set(this.lastMouse.x - this.mouse.x, this.lastMouse.y - this.mouse.y); 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]){ if (this.keyStates[Controls.KEYS.UP]){
this.moveDelta.y -= 20; this.moveDelta.y -= 20;
} }
@ -238,6 +288,9 @@ export default class Controls {
} }
this.lastMouse.copy(this.mouse); this.lastMouse.copy(this.mouse);
this.moveDelta.x = 0;
this.moveDelta.y = 0;
}; };
onMouseWheel = event => { onMouseWheel = event => {
@ -249,7 +302,7 @@ export default class Controls {
if (this.targetDistance < this.settings.zoom.min) this.targetDistance = this.settings.zoom.min; 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; if (this.targetDistance > this.settings.zoom.max) this.targetDistance = this.settings.zoom.max;
} };
onMouseMove = event => { onMouseMove = event => {
this.mouse.set(event.clientX, event.clientY); this.mouse.set(event.clientX, event.clientY);
@ -257,11 +310,13 @@ export default class Controls {
if (this.state !== Controls.STATES.NONE){ if (this.state !== Controls.STATES.NONE){
event.preventDefault(); event.preventDefault();
} }
} };
onMouseDown = event => { onMouseDown = event => {
if (this.state !== Controls.STATES.NONE) return; if (this.state !== Controls.STATES.NONE) return;
$(":focus").blur();
switch (event.button) { switch (event.button) {
case Controls.KEYS.MOVE : case Controls.KEYS.MOVE :
this.state = Controls.STATES.MOVE; this.state = Controls.STATES.MOVE;
@ -272,7 +327,7 @@ export default class Controls {
event.preventDefault(); event.preventDefault();
break; break;
} }
} };
onMouseUp = event => { onMouseUp = event => {
if (this.state === Controls.STATES.NONE) return; if (this.state === Controls.STATES.NONE) return;
@ -285,13 +340,91 @@ export default class Controls {
if (this.state === Controls.STATES.ORBIT) this.state = Controls.STATES.NONE; if (this.state === Controls.STATES.ORBIT) this.state = Controls.STATES.NONE;
break; break;
} }
} };
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;
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 => {
if (event.pointerType === "mouse") return;
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 => { onKeyDown = event => {
this.keyStates[event.keyCode] = true; this.keyStates[event.keyCode] = true;
} };
onKeyUp = event => { onKeyUp = event => {
this.keyStates[event.keyCode] = false; this.keyStates[event.keyCode] = false;
} };
} }

View File

@ -42,12 +42,12 @@ export default class Compass {
onBlueMapUpdateFrame = () => { onBlueMapUpdateFrame = () => {
this.needle.css('transform', `rotate(${this.blueMap.controls.direction}rad)`); this.needle.css('transform', `rotate(${this.blueMap.controls.direction}rad)`);
} };
onClick = () => { onClick = () => {
this.blueMap.controls.targetDirection = 0; this.blueMap.controls.targetDirection = 0;
this.blueMap.controls.direction = this.blueMap.controls.direction % (Math.PI * 2); 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;
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;
} };
} }

View File

@ -36,7 +36,7 @@ export default class Info {
} }
onClick = () => { onClick = () => {
this.blueMap.alert( this.blueMap.toggleAlert('bluemap-info',
'<h1>Info</h1>' + '<h1>Info</h1>' +
'Visit BlueMap on <a href="https://github.com/BlueMap-Minecraft">GitHub</a>!<br>' + 'Visit BlueMap on <a href="https://github.com/BlueMap-Minecraft">GitHub</a>!<br>' +
'BlueMap works best with <a href="https://www.google.com/chrome/">Chrome</a>.<br>' + 'BlueMap works best with <a href="https://www.google.com/chrome/">Chrome</a>.<br>' +
@ -45,5 +45,5 @@ export default class Info {
'Rightclick-drag with your mouse to rotate your view.<br>' + 'Rightclick-drag with your mouse to rotate your view.<br>' +
'Scroll to zoom.<br>' 'Scroll to zoom.<br>'
); );
} };
} }

View File

@ -53,11 +53,11 @@ export default class MapMenu {
onMapClick = event => { onMapClick = event => {
const map = $(event.target).attr('map'); const map = $(event.target).attr('map');
this.bluemap.changeMap(map); this.bluemap.changeMap(map);
} };
onBlueMapMapChange = () => { onBlueMapMapChange = () => {
this.maplist.find('li').show(); this.maplist.find('li').show();
this.maplist.find('li[map=' + this.bluemap.map + ']').hide(); this.maplist.find('li[map=' + this.bluemap.map + ']').hide();
this.element.find('.selection').html(this.bluemap.settings[this.bluemap.map].name); this.element.find('.selection').html(this.bluemap.settings[this.bluemap.map].name);
} };
} }

View File

@ -43,13 +43,13 @@ export default class Settings {
this.elementQuality = $( this.elementQuality = $(
'<div id="bluemap-settings-quality" class="dropdown-container"><span class="selection">Quality: <span>Normal</span></span><div class="dropdown"><ul>' + '<div id="bluemap-settings-quality" class="dropdown-container"><span class="selection">Quality: <span>Normal</span></span><div class="dropdown"><ul>' +
'<li quality="2">High</li>' + '<li data-quality="2">High</li>' +
'<li quality="1" style="display: none">Normal</li>' + '<li data-quality="1" style="display: none">Normal</li>' +
'<li quality="0.75">Fast</li>' + '<li data-quality="0.75">Fast</li>' +
'</ul></div></div>' '</ul></div></div>'
).prependTo(this.elementMenu); ).prependTo(this.elementMenu);
this.elementQuality.find('li[quality]').click(this.onQualityClick); this.elementQuality.find('li[data-quality]').click(this.onQualityClick);
this.elementRenderDistance = $('<div id="bluemap-settings-render-distance" class="dropdown-container"></div>').prependTo(this.elementMenu); this.elementRenderDistance = $('<div id="bluemap-settings-render-distance" class="dropdown-container"></div>').prependTo(this.elementMenu);
this.init(); this.init();
@ -84,10 +84,10 @@ export default class Settings {
onQualityClick = (event) => { onQualityClick = (event) => {
const target = event.target const target = event.target
const desc = $(target).html(); 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').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); this.elementQuality.find('.selection > span').html(desc);
@ -104,7 +104,7 @@ export default class Settings {
this.elementMenu.animate({ this.elementMenu.animate({
width: 'toggle' width: 'toggle'
}, 200); }, 200);
} };
pctToRenderDistance(value, defaultValue) { pctToRenderDistance(value, defaultValue) {
let max = defaultValue * 5; let max = defaultValue * 5;

View File

@ -45,10 +45,6 @@
padding: 10px; padding: 10px;
.alert-close-button { .alert-close-button {
/*position: absolute;
top: 5px;
right: 5px;
*/
margin: -10px -10px 0px 0px; margin: -10px -10px 0px 0px;
padding: 0 0 5px 5px; padding: 0 0 5px 5px;
float: right; float: right;

View File

@ -1,6 +1,11 @@
#bluemap-compass { #bluemap-compass {
width: 30px; width: 2rem;
height: 30px; height: 2rem;
#bluemap-compass-needle {
width: 100%;
height: 100%;
}
&:hover #bluemap-compass-needle { &:hover #bluemap-compass-needle {
filter: invert(1); filter: invert(1);

View File

@ -1,6 +1,6 @@
#bluemap-info { #bluemap-info {
width: 30px; width: 2rem;
height: 30px; height: 2rem;
text-align: center; text-align: center;
&::after { &::after {

View File

@ -1,5 +1,6 @@
#bluemap-mapmenu { #bluemap-mapmenu {
width: 200px; width: 15rem;
cursor: pointer;
.selection, .dropdown li { .selection, .dropdown li {
padding-left: 10px; padding-left: 10px;

View File

@ -2,13 +2,24 @@
position: relative; position: relative;
input { input {
width: 60px; width: 4rem;
height: 100%; height: 100%;
border: none; border: none;
outline: none; outline: none;
background: transparent; background: transparent;
padding: 0 5px 0 25px; 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 { &[data-pos]::before {

View File

@ -1,6 +1,11 @@
#bluemap-settings { #bluemap-settings {
width: 30px; width: 2rem;
height: 30px; height: 2rem;
> img {
width: 100%;
height: 100%;
}
} }
#bluemap-settings.active:not(:hover) { #bluemap-settings.active:not(:hover) {
@ -18,7 +23,7 @@
#bluemap-settings-quality { #bluemap-settings-quality {
width: 150px; width: 150px;
height: 30px; height: 2rem;
} }

View File

@ -10,17 +10,21 @@ html, body {
padding: 0; padding: 0;
font-size: 15px; font-size: 15px;
line-height: 15px; line-height: 1rem;
font-family: Verdana,Helvetica,Arial,sans-serif; font-family: Verdana,Helvetica,Arial,sans-serif;
color: #333333; color: #333333;
background-color: #dddddd; background-color: #dddddd;
@media (max-width: 900px) {
font-size: 17px;
}
} }
.box { .box {
color: #333333; color: #333333;
background-color: white; 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 { .button {
@ -37,7 +41,7 @@ html, body {
background-color: white; background-color: white;
position: relative; position: relative;
transition: all 0.3s; transition: background-color 0.3s;
} }
.dropdown-container:hover { .dropdown-container:hover {
@ -53,7 +57,7 @@ html, body {
overflow: hidden; overflow: hidden;
transition: all 0.3s; transition: all 0.3s;
max-height: 0px; max-height: 0;
} }
.dropdown-container:hover > .dropdown { .dropdown-container:hover > .dropdown {
@ -104,9 +108,15 @@ html, body {
top: 10px; top: 10px;
right: 10px; right: 10px;
line-height: 30px; line-height: 2rem;
display: flex; display: flex;
@media (max-width: 900px) {
display: none;
}
} }
#bluemap-topright > *:not(:last-child) { #bluemap-topright > *:not(:last-child) {
@ -118,9 +128,34 @@ html, body {
top: 10px; top: 10px;
left: 10px; left: 10px;
line-height: 30px; line-height: 2rem;
white-space: nowrap;
display: flex; 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) { #bluemap-topleft > *:not(:last-child) {

View File

@ -22,7 +22,7 @@ module.exports = {
contentBase: WORLD_DATA_PATH, contentBase: WORLD_DATA_PATH,
compress: true, compress: true,
port: 8080, port: 8080,
hot: true, hot: true
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({

View File

@ -104,6 +104,12 @@ maps: [
# Default is enabled # Default is enabled
renderEdges: true 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 is the high-resolution render of the map. Where you see every block.
hires { hires {
# Defines the size of one map-tile in blocks. # Defines the size of one map-tile in blocks.

View File

@ -29,7 +29,7 @@ allprojects {
dependencies { dependencies {
compile project(':BlueMapCLI') compile project(':BlueMapCLI')
compile project(':BlueMapBukkit') compile project(':BlueMapBukkit')
compile project(':BlueMapSponge') //compile project(':BlueMapSponge')
} }
assemble.dependsOn shadowJar { assemble.dependsOn shadowJar {