mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-23 19:16:35 +01:00
Merge branch 'master' into mc/1.12
This commit is contained in:
commit
4a04746794
3
.gitignore
vendored
3
.gitignore
vendored
@ -20,6 +20,9 @@ bin/*
|
||||
.project
|
||||
*/.project
|
||||
|
||||
.idea
|
||||
*/.idea
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
|
||||
|
@ -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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
dependencies {
|
||||
compile group: 'commons-cli', name: 'commons-cli', version: '1.4'
|
||||
compile project(':BlueMapCore')
|
||||
compile project(':BlueMapCommon')
|
||||
}
|
||||
|
@ -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;
|
||||
@ -116,7 +121,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);
|
||||
@ -146,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...");
|
||||
@ -166,15 +175,52 @@ public void renderMaps() throws IOException {
|
||||
}
|
||||
|
||||
if (tiles.isEmpty()) {
|
||||
Logger.global.logInfo("Render finished!");
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
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.");
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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");
|
||||
|
@ -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(),
|
||||
|
@ -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" : ""));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
){
|
||||
|
@ -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<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.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);
|
||||
|
@ -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<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);
|
||||
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();
|
||||
|
@ -2,6 +2,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>BlueMap</title>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -69,6 +69,7 @@ export default class BlueMap {
|
||||
this.dataRoot = dataRoot;
|
||||
|
||||
this.loadingNoticeElement = $('<div id="bluemap-loading" class="box">loading...</div>').appendTo($(this.element));
|
||||
window.onerror = this.onLoadError;
|
||||
|
||||
this.fileLoader = 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.loadSettings().then(async () => {
|
||||
this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']);
|
||||
|
||||
this.lowresTileManager = new TileManager(
|
||||
this,
|
||||
this.settings[this.map]['lowres']['viewDistance'],
|
||||
@ -103,7 +106,7 @@ export default class BlueMap {
|
||||
|
||||
this.initModules();
|
||||
this.start();
|
||||
});
|
||||
}).catch(error => this.onLoadError(error.toString()));
|
||||
}
|
||||
|
||||
initModules() {
|
||||
@ -120,6 +123,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(
|
||||
@ -205,7 +209,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 +245,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 +260,7 @@ export default class BlueMap {
|
||||
.css('height', this.element.clientHeight);
|
||||
|
||||
this.updateFrame = true;
|
||||
}
|
||||
};
|
||||
|
||||
async loadSettings() {
|
||||
return new Promise(resolve => {
|
||||
@ -358,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];
|
||||
@ -394,7 +399,6 @@ export default class BlueMap {
|
||||
}
|
||||
|
||||
this.hiresMaterial = materials;
|
||||
|
||||
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 ######
|
||||
|
||||
alert(content) {
|
||||
toggleAlert(id, content) {
|
||||
let alertBox = $('#alert-box');
|
||||
if (alertBox.length === 0){
|
||||
alertBox = $('<div id="alert-box"></div>').appendTo(this.element);
|
||||
}
|
||||
|
||||
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.fadeOut(200, () => alert.remove());
|
||||
});
|
||||
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');
|
||||
if (oldAlerts.length > 0){
|
||||
alertBox.fadeOut(200, () => {
|
||||
@ -476,8 +502,9 @@ export default class BlueMap {
|
||||
alertBox.show();
|
||||
displayAlert();
|
||||
});
|
||||
} else {
|
||||
displayAlert();
|
||||
return;
|
||||
}
|
||||
|
||||
displayAlert();
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
Vector3,
|
||||
MOUSE
|
||||
} from 'three';
|
||||
import Hammer from 'hammerjs';
|
||||
|
||||
import { Vector2_ZERO } from './utils.js';
|
||||
|
||||
@ -94,25 +95,65 @@ 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);
|
||||
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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
setTileSize(tileSize) {
|
||||
this.tileSize = tileSize;
|
||||
}
|
||||
|
||||
resetPosition() {
|
||||
this.position = new Vector3(0, 70, 0);
|
||||
this.targetPosition = new Vector3(0, 70, 0);
|
||||
@ -174,13 +215,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,20 +244,17 @@ 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -238,6 +288,9 @@ export default class Controls {
|
||||
}
|
||||
|
||||
this.lastMouse.copy(this.mouse);
|
||||
|
||||
this.moveDelta.x = 0;
|
||||
this.moveDelta.y = 0;
|
||||
};
|
||||
|
||||
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.max) this.targetDistance = this.settings.zoom.max;
|
||||
}
|
||||
};
|
||||
|
||||
onMouseMove = event => {
|
||||
this.mouse.set(event.clientX, event.clientY);
|
||||
@ -257,11 +310,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 +327,7 @@ export default class Controls {
|
||||
event.preventDefault();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMouseUp = event => {
|
||||
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;
|
||||
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 => {
|
||||
this.keyStates[event.keyCode] = true;
|
||||
}
|
||||
};
|
||||
|
||||
onKeyUp = event => {
|
||||
this.keyStates[event.keyCode] = false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ export default class Info {
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
this.blueMap.alert(
|
||||
this.blueMap.toggleAlert('bluemap-info',
|
||||
'<h1>Info</h1>' +
|
||||
'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>' +
|
||||
@ -45,5 +45,5 @@ export default class Info {
|
||||
'Rightclick-drag with your mouse to rotate your view.<br>' +
|
||||
'Scroll to zoom.<br>'
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -43,13 +43,13 @@ export default class Settings {
|
||||
|
||||
this.elementQuality = $(
|
||||
'<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 quality="1" style="display: none">Normal</li>' +
|
||||
'<li quality="0.75">Fast</li>' +
|
||||
'<li data-quality="2">High</li>' +
|
||||
'<li data-quality="1" style="display: none">Normal</li>' +
|
||||
'<li data-quality="0.75">Fast</li>' +
|
||||
'</ul></div></div>'
|
||||
).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.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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -1,6 +1,6 @@
|
||||
#bluemap-info {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
text-align: center;
|
||||
|
||||
&::after {
|
||||
|
@ -1,5 +1,6 @@
|
||||
#bluemap-mapmenu {
|
||||
width: 200px;
|
||||
width: 15rem;
|
||||
cursor: pointer;
|
||||
|
||||
.selection, .dropdown li {
|
||||
padding-left: 10px;
|
||||
|
@ -2,13 +2,24 @@
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
width: 60px;
|
||||
width: 4rem;
|
||||
height: 100%;
|
||||
border: none;
|
||||
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 {
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -22,7 +22,7 @@ module.exports = {
|
||||
contentBase: WORLD_DATA_PATH,
|
||||
compress: true,
|
||||
port: 8080,
|
||||
hot: true,
|
||||
hot: true
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
|
@ -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.
|
||||
|
@ -29,7 +29,7 @@ allprojects {
|
||||
dependencies {
|
||||
compile project(':BlueMapCLI')
|
||||
compile project(':BlueMapBukkit')
|
||||
compile project(':BlueMapSponge')
|
||||
//compile project(':BlueMapSponge')
|
||||
}
|
||||
|
||||
assemble.dependsOn shadowJar {
|
||||
|
Loading…
Reference in New Issue
Block a user