Merge branch 'master' into mc/1.12

This commit is contained in:
Blue (Lukas Rieger) 2020-01-21 21:15:51 +01:00
commit 9f79488e75
13 changed files with 270 additions and 145 deletions

View File

@ -104,7 +104,7 @@ private UUID getUUIDForWorldSync (File worldFolder) throws IOException {
if (worldFolder.equals(world.getWorldFolder().getCanonicalFile())) return world.getUID();
}
throw new IOException("There is no world with this folder loaded: " + worldFolder.getCanonicalPath());
throw new IOException("There is no world with this folder loaded: " + worldFolder.getPath());
}
@Override

View File

@ -24,7 +24,7 @@ renderThreadCount: -2
# If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/
# This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :)
# An example report looks like this: {"implementation":"CLI","version":"%version%"}
# An example report looks like this: {"implementation":"bukkit","version":"%version%"}
metrics: true
# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later.
@ -75,6 +75,10 @@ maps: [
# The path to the save-folder of the world to render
world: "world"
# The position on the world where the map will be centered if you open it.
# This defaults to the world-spawn if you don't set it.
#startPos: [500, -820]
# If this is false, BlueMap tries to omit all blocks that are not visible from above-ground.
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.

View File

@ -24,8 +24,14 @@
*/
package de.bluecolored.bluemap.cli;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
@ -36,6 +42,8 @@
import java.util.UUID;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
@ -136,15 +144,14 @@ public void renderMaps() throws IOException {
webSettings.setAllEnabled(false);
for (MapType map : maps.values()) {
webSettings.setEnabled(true, map.getId());
webSettings.setName(map.getName(), map.getId());
webSettings.setFrom(map.getTileRenderer(), map.getId());
webSettings.setFrom(map.getWorld(), map.getId());
}
int ordinal = 0;
for (MapConfig map : config.getMapConfigs()) {
if (!maps.containsKey(map.getId())) continue; //don't add not loaded maps
webSettings.setOrdinal(ordinal++, map.getId());
webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId());
webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId());
webSettings.setFrom(map, map.getId());
}
webSettings.save();
@ -153,86 +160,118 @@ public void renderMaps() throws IOException {
resourcePack.saveTextureFile(textureExportFile);
RenderManager renderManager = new RenderManager(config.getRenderThreadCount());
File rmstate = new File(configFolder, "rmstate");
if (rmstate.exists()) {
try (
InputStream in = new GZIPInputStream(new FileInputStream(rmstate));
DataInputStream din = new DataInputStream(in);
){
renderManager.readState(din, maps.values());
Logger.global.logInfo("Found unfinished render, continuing ... (If you want to start a new render, delete the this file: " + rmstate.getCanonicalPath());
} catch (IOException ex) {
Logger.global.logError("Failed to read saved render-state! Remove the file " + rmstate.getCanonicalPath() + " to start a new render.", ex);
}
} else {
for (MapType map : maps.values()) {
Logger.global.logInfo("Creating render-task for map '" + map.getId() + "' ...");
Logger.global.logInfo("Collecting tiles ...");
Collection<Vector2i> chunks;
if (!forceRender) {
long lastRender = webSettings.getLong(map.getId(), "last-render");
chunks = map.getWorld().getChunkList(lastRender);
} else {
chunks = map.getWorld().getChunkList();
}
HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager();
Collection<Vector2i> tiles = hiresModelManager.getTilesForChunks(chunks);
Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)");
if (!forceRender && chunks.size() == 0) {
Logger.global.logInfo("(This is normal if nothing has changed in the world since the last render. Use -f on the command-line to force a render of all chunks)");
}
if (tiles.isEmpty()) {
continue;
}
RenderTask task = new RenderTask(map.getName(), map);
task.addTiles(tiles);
task.optimizeQueue();
renderManager.addRenderTask(task);
}
}
Logger.global.logInfo("Starting render ...");
renderManager.start();
for (MapType map : maps.values()) {
Logger.global.logInfo("Rendering map '" + map.getId() + "' ...");
Logger.global.logInfo("Collecting tiles to render...");
long startTime = System.currentTimeMillis();
Collection<Vector2i> chunks;
if (!forceRender) {
long lastRender = webSettings.getLong(map.getId(), "last-render");
chunks = map.getWorld().getChunkList(lastRender);
} else {
chunks = map.getWorld().getChunkList();
}
HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager();
Collection<Vector2i> tiles = hiresModelManager.getTilesForChunks(chunks);
Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)");
if (!forceRender && chunks.size() == 0) {
Logger.global.logInfo("(This is normal if nothing has changed in the world since the last render. Use -f on the command-line to force a render of all chunks)");
}
if (tiles.isEmpty()) {
continue;
}
Logger.global.logInfo("Starting Render...");
long starttime = System.currentTimeMillis();
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();
long lastLogUpdate = startTime;
long lastSave = startTime;
while(renderManager.getRenderTaskCount() != 0) {
try {
webSettings.set(starttime, map.getId(), "last-render");
webSettings.save();
} catch (IOException e) {
Logger.global.logError("Failed to update web-settings!", e);
Thread.sleep(200);
} catch (InterruptedException e) {}
long now = System.currentTimeMillis();
if (lastLogUpdate < now - 10000) { // print update all 10 seconds
RenderTask currentTask = renderManager.getCurrentRenderTask();
lastLogUpdate = now;
long time = currentTask.getActiveTime();
String durationString = DurationFormatUtils.formatDurationWords(time, true, true);
int tileCount = currentTask.getRemainingTileCount() + currentTask.getRenderedTileCount();
double pct = (double)currentTask.getRenderedTileCount() / (double) tileCount;
long ert = (long)((time / pct) * (1d - pct));
String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true);
double tps = currentTask.getRenderedTileCount() / (time / 1000.0);
Logger.global.logInfo("Rendering map '" + currentTask.getName() + "':");
Logger.global.logInfo("Rendered " + currentTask.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 - 1 * 60000) { // save every minute
RenderTask currentTask = renderManager.getCurrentRenderTask();
lastSave = now;
currentTask.getMapType().getTileRenderer().save();
try (
OutputStream os = new GZIPOutputStream(new FileOutputStream(rmstate));
DataOutputStream dos = new DataOutputStream(os);
){
renderManager.writeState(dos);
} catch (IOException ex) {
Logger.global.logError("Failed to save render-state!", ex);
}
}
}
renderManager.stop();
Logger.global.logInfo("Waiting for all threads to quit...");
//render finished, so remove render state file
rmstate.delete();
for (MapType map : maps.values()) {
webSettings.set(startTime, map.getId(), "last-render");
}
try {
webSettings.save();
} catch (IOException e) {
Logger.global.logError("Failed to update web-settings!", e);
}
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.");
}
@ -241,14 +280,14 @@ public void renderMaps() throws IOException {
}
public void startWebserver() throws IOException {
Logger.global.logInfo("Starting webserver...");
Logger.global.logInfo("Starting webserver ...");
BlueMapWebServer webserver = new BlueMapWebServer(configManager.getMainConfig());
webserver.start();
}
private boolean loadResources() throws IOException, ParseResourceException {
Logger.global.logInfo("Loading resources...");
Logger.global.logInfo("Loading resources ...");
MainConfig config = configManager.getMainConfig();

View File

@ -24,7 +24,7 @@ renderThreadCount: 0
# If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/
# This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :)
# An example report looks like this: {"implementation":"CLI","version":"%version%"}
# An example report looks like this: {"implementation":"cli","version":"%version%"}
metrics: true
# The folder where bluemap saves data-files it needs during runtime
@ -74,6 +74,10 @@ maps: [
# The path to the save-folder of the world to render
world: "world"
# The position on the world where the map will be centered if you open it.
# This defaults to the world-spawn if you don't set it.
#startPos: [500, -820]
# If this is false, BlueMap tries to omit all blocks that are not visible from above-ground.
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.

View File

@ -127,8 +127,10 @@ private void renderThread() {
RenderTask task = renderTasks.peek();
if (task != null) {
ticket = task.poll();
if (task.isFinished()) renderTasks.poll();
task.getMapType().getTileRenderer().save();
if (task.isFinished()) {
renderTasks.poll();
task.getMapType().getTileRenderer().save();
}
}
}
}
@ -155,7 +157,17 @@ public int getQueueSize() {
* Returns a copy of the deque with the render tasks in order as array
*/
public RenderTask[] getRenderTasks(){
return renderTasks.toArray(new RenderTask[renderTasks.size()]);
synchronized (renderTasks) {
return renderTasks.toArray(new RenderTask[renderTasks.size()]);
}
}
public int getRenderTaskCount(){
return renderTasks.size();
}
public RenderTask getCurrentRenderTask() {
return renderTasks.peek();
}
public boolean isRunning() {

View File

@ -69,6 +69,7 @@ public class Plugin {
private RenderManager renderManager;
private BlueMapWebServer webServer;
private Thread periodicalSaveThread;
private Thread metricsThread;
private boolean loaded = false;
@ -218,6 +219,23 @@ public synchronized void load() throws IOException, ParseResourceException {
Logger.global.logError("Failed to load render-manager state!", ex);
}
//create periodical-save thread
periodicalSaveThread = new Thread(() -> {
try {
while (true) {
Thread.sleep(TimeUnit.MINUTES.toMillis(5));
try {
saveRenderManagerState();
} catch (IOException ex) {
Logger.global.logError("Failed to save render-manager state!", ex);
}
}
} catch (InterruptedException ex){
return;
}
});
periodicalSaveThread.start();
//start map updater
this.updateHandler = new MapUpdateHandler();
serverInterface.registerListener(updateHandler);
@ -232,15 +250,14 @@ public synchronized void load() throws IOException, ParseResourceException {
webSettings.setAllEnabled(false);
for (MapType map : maps.values()) {
webSettings.setEnabled(true, map.getId());
webSettings.setName(map.getName(), map.getId());
webSettings.setFrom(map.getTileRenderer(), map.getId());
webSettings.setFrom(map.getWorld(), map.getId());
}
int ordinal = 0;
for (MapConfig map : config.getMapConfigs()) {
if (!maps.containsKey(map.getId())) continue; //don't add not loaded maps
webSettings.setOrdinal(ordinal++, map.getId());
webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId());
webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId());
webSettings.setFrom(map, map.getId());
}
webSettings.save();
@ -278,6 +295,9 @@ public synchronized void unload() {
if (metricsThread != null) metricsThread.interrupt();
metricsThread = null;
if (periodicalSaveThread != null) periodicalSaveThread.interrupt();
periodicalSaveThread = null;
//stop services
if (renderManager != null) renderManager.stop();
if (webServer != null) webServer.close();
@ -286,14 +306,7 @@ public synchronized void unload() {
if (updateHandler != null) updateHandler.flushTileBuffer(); //first write all buffered tiles to the render manager to save them too
if (renderManager != null) {
try {
File saveFile = config.getDataPath().resolve("rmstate").toFile();
saveFile.getParentFile().mkdirs();
if (saveFile.exists()) saveFile.delete();
saveFile.createNewFile();
try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) {
renderManager.writeState(out);
}
saveRenderManagerState();
} catch (IOException ex) {
Logger.global.logError("Failed to save render-manager state!", ex);
}
@ -316,6 +329,17 @@ public synchronized void unload() {
loaded = false;
}
public void saveRenderManagerState() throws IOException {
File saveFile = config.getDataPath().resolve("rmstate").toFile();
saveFile.getParentFile().mkdirs();
if (saveFile.exists()) saveFile.delete();
saveFile.createNewFile();
try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) {
renderManager.writeState(out);
}
}
public synchronized void reload() throws IOException, ParseResourceException {
unload();
load();

View File

@ -32,6 +32,7 @@
import java.util.ArrayList;
import java.util.List;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.base.Preconditions;
@ -194,6 +195,8 @@ public class MapConfig implements RenderSettings {
private String name;
private String world;
private Vector2i startPos;
private boolean renderCaves;
private float ambientOcclusion;
private float lighting;
@ -219,6 +222,8 @@ private MapConfig(ConfigurationNode node) throws IOException {
this.world = node.getNode("world").getString("");
if (world.isEmpty()) throw new IOException("Invalid configuration: Node maps[?].world is not defined");
if (!node.getNode("startPos").isVirtual()) this.startPos = ConfigUtils.readVector2i(node.getNode("startPos"));
this.renderCaves = node.getNode("renderCaves").getBoolean(false);
this.ambientOcclusion = node.getNode("ambientOcclusion").getFloat(0.25f);
this.lighting = node.getNode("lighting").getFloat(0.8f);
@ -234,7 +239,7 @@ private MapConfig(ConfigurationNode node) throws IOException {
this.renderEdges = node.getNode("renderEdges").getBoolean(true);
this.renderEdges = node.getNode("useCompression").getBoolean(true);
this.useGzip = node.getNode("useCompression").getBoolean(true);
this.hiresTileSize = node.getNode("hires", "tileSize").getInt(32);
this.hiresViewDistance = node.getNode("hires", "viewDistance").getFloat(4.5f);
@ -260,6 +265,10 @@ public String getWorldPath() {
return world;
}
public Vector2i getStartPos() {
return startPos;
}
public boolean isRenderCaves() {
return renderCaves;
}

View File

@ -84,8 +84,7 @@ public HiresModel render(WorldTile tile, AABB region) {
e.addSuppressed(e2);
blockModel = new BlockStateModel();
}
Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")");
//Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")");
}
blockModel.translate(new Vector3f(x, y, z).sub(modelMin.toFloat()));

View File

@ -66,7 +66,7 @@ public Collection<TransformedBlockModelResource> getModels(BlockState blockState
Variant allMatch = null;
for (Variant variant : variants) {
if (variant.condition.matches(blockState)) {
if (variant.condition instanceof All) { //only use "all" conditioned if nothing else matched
if (variant.condition instanceof All) { //only use "all" condition if nothing else matched
if (allMatch == null) allMatch = variant;
continue;
}
@ -106,16 +106,21 @@ private Variant() {
}
public TransformedBlockModelResource getModel(Vector3i pos) {
if (models.isEmpty()) throw new IllegalStateException("A variant must have at least one model!");
double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; // random based on position
for (Weighted<TransformedBlockModelResource> w : models) {
selection -= w.weight;
if (selection < 0)
return w.value;
if (selection <= 0) return w.value;
}
throw new RuntimeException("This line should never be reached!");
}
public void checkValid() throws ParseResourceException {
if (models.isEmpty()) throw new ParseResourceException("A variant must have at least one model!");
}
public void updateTotalWeight() {
totalWeight = 0d;
for (Weighted<?> w : models) {
@ -181,10 +186,11 @@ public BlockStateResource build(String blockstateFile) throws IOException {
variant.models = loadModels(transformedModelNode, blockstateFile, null);
variant.updateTotalWeight();
variant.checkValid();
blockState.variants.add(variant);
} catch (Exception ex) {
Logger.global.logWarning("Failed to parse a variant of " + blockstateFile + ": " + ex);
} catch (Throwable t) {
Logger.global.logWarning("Failed to parse a variant of " + blockstateFile + ": " + t);
}
}
@ -199,10 +205,11 @@ public BlockStateResource build(String blockstateFile) throws IOException {
variant.models = loadModels(partNode.getNode("apply"), blockstateFile, null);
variant.updateTotalWeight();
variant.checkValid();
blockState.multipart.add(variant);
} catch (Exception ex) {
Logger.global.logWarning("Failed to parse a multipart-part of " + blockstateFile + ": " + ex);
} catch (Throwable t) {
Logger.global.logWarning("Failed to parse a multipart-part of " + blockstateFile + ": " + t);
}
}
@ -364,7 +371,14 @@ private BlockStateResource buildForge(ConfigurationNode config, String blockstat
}
variant.updateTotalWeight();
blockState.variants.add(variant);
try {
variant.checkValid();
blockState.variants.add(variant);
} catch (ParseResourceException ex) {
Logger.global.logWarning("Failed to parse a variant (forge/property) of " + blockstateFile + ": " + ex);
}
}
//create default straight variant
@ -390,7 +404,14 @@ private BlockStateResource buildForge(ConfigurationNode config, String blockstat
}
variant.updateTotalWeight();
blockState.variants.add(variant);
try {
variant.checkValid();
blockState.variants.add(variant);
} catch (ParseResourceException ex) {
Logger.global.logWarning("Failed to parse a variant (forge/straight) of " + blockstateFile + ": " + ex);
}
}
return blockState;

View File

@ -31,7 +31,9 @@
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.world.World;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
@ -129,12 +131,22 @@ public void setFrom(TileRenderer tileRenderer, String mapId) {
set(pointSize.getY() / 2, mapId, "lowres", "translate", "z");
}
public void setHiresViewDistance(float hiresViewDistance, String mapId) {
set(hiresViewDistance, mapId, "hires", "viewDistance");
public void setFrom(World world, String mapId) {
set(world.getSpawnPoint().getX(), mapId, "startPos", "x");
set(world.getSpawnPoint().getZ(), mapId, "startPos", "z");
}
public void setLowresViewDistance(float lowresViewDistance, String mapId) {
set(lowresViewDistance, mapId, "lowres", "viewDistance");
public void setFrom(MapConfig mapConfig, String mapId) {
Vector2i startPos = mapConfig.getStartPos();
if (startPos != null) {
set(startPos.getX(), mapId, "startPos", "x");
set(startPos.getY(), mapId, "startPos", "z");
}
set(mapConfig.getLowresViewDistance(), mapId, "lowres", "viewDistance");
set(mapConfig.getHiresViewDistance(), mapId, "hires", "viewDistance");
setName(mapConfig.getName(), mapId);
}
public void setOrdinal(int ordinal, String mapId) {

View File

@ -81,32 +81,17 @@ 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'],
this.loadLowresTile,
this.lowresScene,
this.settings[this.map]['lowres']['tileSize'],
{x: 0, z: 0}
);
this.hiresTileManager = new TileManager(
this,
this.settings[this.map]['hires']['viewDistance'],
this.loadHiresTile,
this.hiresScene,
this.settings[this.map]['hires']['tileSize'],
{x: 0, z: 0}
);
await this.loadHiresMaterial();
await this.loadLowresMaterial();
this.changeMap(this.map);
this.initModules();
this.start();
}).catch(error => this.onLoadError(error.toString()));
}).catch(error => {
this.onLoadError(error.toString())
console.error(error);
});
}
initModules() {
@ -119,12 +104,20 @@ export default class BlueMap {
}
changeMap(map) {
this.hiresTileManager.close();
this.lowresTileManager.close();
if (this.hiresTileManager !== undefined) this.hiresTileManager.close();
if (this.lowresTileManager !== undefined) this.lowresTileManager.close();
this.map = map;
let startPos = {
x: this.settings[this.map]["startPos"]["x"],
z: this.settings[this.map]["startPos"]["z"]
};
this.controls.setTileSize(this.settings[this.map]['hires']['tileSize']);
this.controls.resetPosition();
this.controls.targetPosition.set(startPos.x, this.controls.targetPosition.y, startPos.z);
this.controls.position.copy(this.controls.targetPosition);
this.lowresTileManager = new TileManager(
this,
@ -132,7 +125,7 @@ export default class BlueMap {
this.loadLowresTile,
this.lowresScene,
this.settings[this.map]['lowres']['tileSize'],
{x: 0, z: 0}
startPos
);
this.hiresTileManager = new TileManager(
@ -141,7 +134,7 @@ export default class BlueMap {
this.loadHiresTile,
this.hiresScene,
this.settings[this.map]['hires']['tileSize'],
{x: 0, z: 0}
startPos
);
this.lowresTileManager.update();
@ -214,14 +207,17 @@ export default class BlueMap {
}
this.locationHash =
'#' + this.map
+ ':' + Math.floor(this.controls.targetPosition.x)
+ ':' + Math.floor(this.controls.targetPosition.z)
+ ':' + Math.round(this.controls.targetDirection * 100) / 100
+ ':' + Math.round(this.controls.targetDistance * 100) / 100
+ ':' + Math.ceil(this.controls.targetAngle * 100) / 100
+ ':' + Math.floor(this.controls.targetPosition.y);
history.replaceState(undefined, undefined, this.locationHash);
'#' + this.map
+ ':' + Math.floor(this.controls.targetPosition.x)
+ ':' + Math.floor(this.controls.targetPosition.z)
+ ':' + Math.round(this.controls.targetDirection * 100) / 100
+ ':' + Math.round(this.controls.targetDistance * 100) / 100
+ ':' + Math.ceil(this.controls.targetAngle * 100) / 100
+ ':' + Math.floor(this.controls.targetPosition.y);
// only update hash when changed
if (window.location.hash !== this.locationHash) {
history.replaceState(undefined, undefined, this.locationHash);
}
};
render = () => {

View File

@ -37,7 +37,8 @@ export default class TileManager {
this.scene = scene;
this.tileSize = new Vector2(tileSize.x, tileSize.z);
this.tile = new Vector2(position.x, position.z);
this.tile = new Vector2(0, 0);
this.tile.set(position.x, position.z).divide(this.tileSize).floor();
this.lastTile = this.tile.clone();
this.closed = false;

View File

@ -70,6 +70,10 @@ maps: [
# The path to the save-folder of the world to render
world: "world"
# The position on the world where the map will be centered if you open it.
# This defaults to the world-spawn if you don't set it.
#startPos: [500, -820]
# If this is false, BlueMap tries to omit all blocks that are not visible from above-ground.
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.