Rewrite the CLI to use a config file

This commit is contained in:
Blue (Lukas Rieger) 2019-11-23 20:28:44 +01:00
parent fa32a90138
commit fee5d7ec46
6 changed files with 468 additions and 430 deletions

View File

@ -26,12 +26,13 @@ package de.bluecolored.bluemap.cli;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ForkJoinPool;
@ -47,223 +48,184 @@ import org.apache.commons.cli.ParseException;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.base.Preconditions;
import de.bluecolored.bluemap.core.config.Configuration;
import de.bluecolored.bluemap.core.config.Configuration.MapConfig;
import de.bluecolored.bluemap.core.config.ConfigurationFile;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.render.StaticRenderSettings;
import de.bluecolored.bluemap.core.metrics.Metrics;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.web.BlueMapWebRequestHandler;
import de.bluecolored.bluemap.core.web.WebFilesManager;
import de.bluecolored.bluemap.core.web.BlueMapWebServer;
import de.bluecolored.bluemap.core.web.WebSettings;
import de.bluecolored.bluemap.core.webserver.WebServer;
import de.bluecolored.bluemap.core.world.World;
public class BlueMapCLI {
private ConfigurationFile configFile;
private Configuration config;
private ResourcePack resourcePack;
private boolean forceRender;
private File webroot = new File("web");
private File dataPath = new File(webroot, "data");
private File extraResourceFile = null;
private int threadCount;
private String mapId = null;
private String mapName = null;
private int highresTileSize = 32;
private int lowresTileSize = 50;
private int samplesPerHighresTile = 4;
private float highresViewDistance = 6f;
private float lowresViewDistance = 5f;
private boolean excludeFacesWithoutSunlight = true;
private float ambientOcclusion = 0.25f;
private float lighting = 0.8f;
private int sliceY = Integer.MAX_VALUE;
private int maxY = Integer.MAX_VALUE;
private int minY = 0;
private int port = 8100;
private int maxConnections = 100;
private InetAddress bindAdress = null;
public BlueMapCLI() {
threadCount = Runtime.getRuntime().availableProcessors();
public BlueMapCLI(ConfigurationFile configFile, boolean forceRender) {
this.configFile = configFile;
this.config = configFile.getConfig();
this.forceRender = forceRender;
this.resourcePack = null;
}
public void renderMap(File mapPath, boolean updateOnly) throws IOException, NoSuchResourceException {
dataPath.mkdirs();
if (!mapPath.exists() || !mapPath.isDirectory()) {
throw new IOException("Save folder '" + mapPath + "' does not exist or is not a directory!");
}
Logger.global.logInfo("Reading world...");
World world = MCAWorld.load(mapPath.toPath(), UUID.randomUUID());
public void renderMaps() throws IOException, NoSuchResourceException {
Preconditions.checkNotNull(resourcePack);
if (mapName == null) {
mapName = world.getName();
}
config.getWebDataPath().toFile().mkdirs();
if (mapId == null) {
mapId = mapPath.getName().toLowerCase();
}
Map<String, MapType> maps = new HashMap<>();
Logger.global.logInfo("Starting Render:"
+ "\n map: " + mapPath.getAbsolutePath()
+ "\n map-id: " + mapId
+ "\n map-name: " + mapName
+ "\n thread-count: " + threadCount
+ "\n data-path: " + dataPath.getAbsolutePath()
+ "\n render-all: " + !excludeFacesWithoutSunlight
+ "\n ambient-occlusion: " + ambientOcclusion
+ "\n lighting: " + lighting
+ "\n sliceY: " + (sliceY < Integer.MAX_VALUE ? sliceY : "-")
+ "\n maxY: " + (maxY < Integer.MAX_VALUE ? maxY : "-")
+ "\n minY: " + (minY > 0 ? minY : "-")
+ "\n hr-tilesize: " + highresTileSize
+ "\n lr-tilesize: " + lowresTileSize
+ "\n lr-resolution: " + samplesPerHighresTile
+ "\n hr-viewdistance: " + highresViewDistance
+ "\n lr-viewdistance: " + lowresViewDistance
);
Logger.global.logInfo("Loading Resources...");
ResourcePack resourcePack = loadResources();
Logger.global.logInfo("Initializing renderer...");
HiresModelManager hiresModelManager = new HiresModelManager(
dataPath.toPath().resolve("hires").resolve(mapId),
resourcePack,
new Vector2i(highresTileSize, highresTileSize),
ForkJoinPool.commonPool()
);
LowresModelManager lowresModelManager = new LowresModelManager(
dataPath.toPath().resolve("lowres").resolve(mapId),
new Vector2i(lowresTileSize, lowresTileSize),
new Vector2i(samplesPerHighresTile, samplesPerHighresTile)
);
TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager, new StaticRenderSettings(
ambientOcclusion,
excludeFacesWithoutSunlight,
lighting,
maxY,
minY,
sliceY
));
File webSettingsFile = new File(dataPath, "settings.json");
Logger.global.logInfo("Writing '" + webSettingsFile.getAbsolutePath() + "'...");
WebSettings webSettings = new WebSettings(webSettingsFile);
webSettings.setName(mapName, mapId);
webSettings.setFrom(tileRenderer, mapId);
webSettings.setHiresViewDistance(highresViewDistance, mapId);
webSettings.setLowresViewDistance(lowresViewDistance, mapId);
webSettings.save();
Logger.global.logInfo("Collecting tiles to render...");
Collection<Vector2i> chunks;
if (updateOnly) {
long lastRender = webSettings.getLong(mapId, "last-render");
chunks = world.getChunkList(lastRender);
} else {
chunks = world.getChunkList();
}
Set<Vector2i> tiles = new HashSet<>();
for (Vector2i chunk : chunks) {
Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16);
tiles.add(hiresModelManager.posToTile(minBlockPos));
tiles.add(hiresModelManager.posToTile(minBlockPos.add(0, 0, 15)));
tiles.add(hiresModelManager.posToTile(minBlockPos.add(15, 0, 0)));
tiles.add(hiresModelManager.posToTile(minBlockPos.add(15, 0, 15)));
}
Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)");
if (tiles.isEmpty()) {
Logger.global.logInfo("Render finished!");
return;
}
Logger.global.logInfo("Starting Render...");
long starttime = System.currentTimeMillis();
RenderManager renderManager = new RenderManager(world, tileRenderer, tiles, threadCount);
renderManager.start(() -> {
Logger.global.logInfo("Waiting for 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.");
for (MapConfig mapConfig : config.getMapConfigs()) {
File mapPath = new File(mapConfig.getWorldPath());
if (!mapPath.exists() || !mapPath.isDirectory()) {
throw new IOException("Save folder '" + mapPath + "' does not exist or is not a directory!");
}
Logger.global.logInfo("Preparing renderer for map '" + mapConfig.getId() + "' ...");
World world = MCAWorld.load(mapPath.toPath(), UUID.randomUUID());
HiresModelManager hiresModelManager = new HiresModelManager(
config.getWebDataPath().resolve("hires").resolve(mapConfig.getId()),
resourcePack,
new Vector2i(mapConfig.getHiresTileSize(), mapConfig.getHiresTileSize()),
ForkJoinPool.commonPool()
);
LowresModelManager lowresModelManager = new LowresModelManager(
config.getWebDataPath().resolve("lowres").resolve(mapConfig.getId()),
new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()),
new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile())
);
TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager, mapConfig);
MapType mapType = new MapType(mapConfig.getId(), mapConfig.getName(), world, tileRenderer);
maps.put(mapConfig.getId(), mapType);
}
Logger.global.logInfo("Writing settings.json ...");
WebSettings webSettings = new WebSettings(config.getWebDataPath().resolve("settings.json").toFile());
for (MapType map : maps.values()) {
webSettings.setName(map.getName(), map.getId());
webSettings.setFrom(map.getTileRenderer(), map.getId());
}
for (MapConfig map : config.getMapConfigs()) {
webSettings.setHiresViewDistance(map.getHiresViewDistance(), map.getId());
webSettings.setLowresViewDistance(map.getLowresViewDistance(), map.getId());
}
webSettings.save();
for (MapType map : maps.values()) {
Logger.global.logInfo("Rendering map '" + map.getId() + "' ...");
Logger.global.logInfo("Collecting tiles to render...");
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();
Set<Vector2i> tiles = new HashSet<>();
for (Vector2i chunk : chunks) {
Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16);
tiles.add(hiresModelManager.posToTile(minBlockPos));
tiles.add(hiresModelManager.posToTile(minBlockPos.add(0, 0, 15)));
tiles.add(hiresModelManager.posToTile(minBlockPos.add(15, 0, 0)));
tiles.add(hiresModelManager.posToTile(minBlockPos.add(15, 0, 15)));
}
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()) {
Logger.global.logInfo("Render finished!");
return;
}
Logger.global.logInfo("Starting Render...");
long starttime = System.currentTimeMillis();
RenderTask task = new RenderTask(map, tiles, config.getRenderThreadCount());
task.render();
try {
webSettings.set(starttime, mapId, "last-render");
webSettings.set(starttime, map.getId(), "last-render");
webSettings.save();
} catch (IOException e) {
Logger.global.logError("Failed to update web-settings!", e);
}
Logger.global.logInfo("Render finished!");
});
}
public void updateWebFiles() throws IOException {
webroot.mkdirs();
}
Logger.global.logInfo("Creating webfiles in " + webroot.getAbsolutePath());
WebFilesManager webFilesManager = new WebFilesManager(webroot.toPath());
webFilesManager.updateFiles();
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.");
}
Logger.global.logInfo("Render finished!");
}
public void startWebserver() throws UnknownHostException {
if (bindAdress == null) bindAdress = InetAddress.getLocalHost();
Logger.global.logInfo("Starting webserver:"
+ "\n address: " + this.bindAdress.toString() + ""
+ "\n port: " + this.port
+ "\n max connections: " + this.maxConnections
+ "\n webroot: " + this.webroot.getAbsolutePath()
);
WebServer webserver = new WebServer(
this.port,
this.maxConnections,
this.bindAdress,
new BlueMapWebRequestHandler(this.webroot.toPath())
);
public void startWebserver() throws IOException {
Logger.global.logInfo("Starting webserver...");
BlueMapWebServer webserver = new BlueMapWebServer(config);
webserver.updateWebfiles();
webserver.start();
}
private ResourcePack loadResources() throws IOException, NoSuchResourceException {
File defaultResourceFile;
try {
defaultResourceFile = File.createTempFile("res", ".zip");
defaultResourceFile.delete();
} catch (IOException e) {
throw new IOException("Failed to create temporary resource file!", e);
}
try {
ResourcePack.downloadDefaultResource(defaultResourceFile);
} catch (IOException e) {
throw new IOException("Failed to create default resources!", e);
private boolean loadResources() throws IOException, NoSuchResourceException {
File defaultResourceFile = config.getDataPath().resolve("minecraft-client-" + ResourcePack.MINECRAFT_CLIENT_VERSION + ".jar").toFile();
File textureExportFile = config.getWebDataPath().resolve("textures.json").toFile();
if (!defaultResourceFile.exists()) {
if (!handleMissingResources(defaultResourceFile)) return false;
}
List<File> resourcePacks = new ArrayList<>();
resourcePacks.add(defaultResourceFile);
if (this.extraResourceFile != null) resourcePacks.add(extraResourceFile);
//find more resource packs
File resourcePackFolder = configFile.getFile().toPath().resolveSibling("resourcepacks").toFile();
resourcePackFolder.mkdirs();
File[] resourcePacks = resourcePackFolder.listFiles();
Arrays.sort(resourcePacks);
ResourcePack resourcePack = new ResourcePack(resourcePacks, new File(dataPath, "textures.json"));
List<File> resources = new ArrayList<>(resourcePacks.length + 1);
resources.add(defaultResourceFile);
for (File file : resourcePacks) resources.add(file);
defaultResourceFile.delete();
resourcePack = new ResourcePack(resources, textureExportFile);
return resourcePack;
return true;
}
private boolean handleMissingResources(File resourceFile) {
if (config.isDownloadAccepted()) {
try {
Logger.global.logInfo("Downloading " + ResourcePack.MINECRAFT_CLIENT_URL + " to " + resourceFile + " ...");
ResourcePack.downloadDefaultResource(resourceFile);
return true;
} catch (IOException e) {
Logger.global.logError("Failed to download resources!", e);
return false;
}
} else {
Logger.global.logWarning("BlueMap is missing important resources!");
Logger.global.logWarning("You need to accept the download of the required files in order of BlueMap to work!");
Logger.global.logWarning("Please check: " + configFile.getFile() + " and try again!");
return false;
}
}
public static void main(String[] args) throws IOException, NoSuchResourceException {
@ -271,253 +233,73 @@ public class BlueMapCLI {
try {
CommandLine cmd = parser.parse(BlueMapCLI.createOptions(), args, false);
//help
if (cmd.hasOption("h")) {
BlueMapCLI.printHelp();
return;
}
boolean executed = false;
BlueMapCLI bluemapcli = new BlueMapCLI();
if (cmd.hasOption("o")) bluemapcli.dataPath = new File(cmd.getOptionValue("o"));
if (cmd.hasOption("r")) bluemapcli.extraResourceFile = new File(cmd.getOptionValue("r"));
if (cmd.hasOption("t")) bluemapcli.threadCount = Integer.parseInt(cmd.getOptionValue("t"));
if (cmd.hasOption("d")) bluemapcli.webroot = new File(cmd.getOptionValue("d"));
if (cmd.hasOption("i")) bluemapcli.bindAdress = InetAddress.getByName(cmd.getOptionValue("i"));
bluemapcli.port = Integer.parseInt(cmd.getOptionValue("p", Integer.toString(bluemapcli.port)));
bluemapcli.maxConnections = Integer.parseInt(cmd.getOptionValue("connections", Integer.toString(bluemapcli.maxConnections)));
bluemapcli.mapName = cmd.getOptionValue("n", bluemapcli.mapName);
bluemapcli.mapId = cmd.getOptionValue("id", bluemapcli.mapId);
bluemapcli.ambientOcclusion = Float.parseFloat(cmd.getOptionValue("ao", Float.toString(bluemapcli.ambientOcclusion)));
bluemapcli.lighting = Float.parseFloat(cmd.getOptionValue("lighting", Float.toString(bluemapcli.lighting)));
bluemapcli.sliceY = Integer.parseInt(cmd.getOptionValue("y-slice", Integer.toString(bluemapcli.sliceY)));
bluemapcli.maxY = Integer.parseInt(cmd.getOptionValue("y-max", Integer.toString(bluemapcli.maxY)));
bluemapcli.minY = Integer.parseInt(cmd.getOptionValue("y-min", Integer.toString(bluemapcli.minY)));
bluemapcli.highresTileSize = Integer.parseInt(cmd.getOptionValue("hr-tilesize", Integer.toString(bluemapcli.highresTileSize)));
bluemapcli.highresViewDistance = Float.parseFloat(cmd.getOptionValue("hr-viewdist", Float.toString(bluemapcli.highresViewDistance)));
bluemapcli.lowresTileSize = Integer.parseInt(cmd.getOptionValue("lr-tilesize", Integer.toString(bluemapcli.lowresTileSize)));
bluemapcli.samplesPerHighresTile = Integer.parseInt(cmd.getOptionValue("lr-resolution", Integer.toString(bluemapcli.samplesPerHighresTile)));
bluemapcli.lowresViewDistance = Float.parseFloat(cmd.getOptionValue("lr-viewdist", Float.toString(bluemapcli.lowresViewDistance)));
//load config
File configFile = new File("bluemap.conf").getAbsoluteFile();
if (cmd.hasOption("c")) {
bluemapcli.updateWebFiles();
executed = true;
configFile = new File(cmd.getOptionValue("c"));
configFile.getParentFile().mkdirs();
}
if (cmd.hasOption("s")) {
bluemapcli.startWebserver();
executed = true;
boolean configCreated = !configFile.exists();
ConfigurationFile config = ConfigurationFile.loadOrCreate(configFile, BlueMapCLI.class.getResource("/bluemap-cli.conf"));
if (configCreated) {
Logger.global.logInfo("No config file found! Created an example config here: " + configFile);
return;
}
if (cmd.hasOption("w")) {
bluemapcli.renderMap(new File(cmd.getOptionValue("w")), !cmd.hasOption("f"));
executed = true;
BlueMapCLI bluemap = new BlueMapCLI(config, cmd.hasOption("f"));
if (config.getConfig().isWebserverEnabled()) {
//start webserver
bluemap.startWebserver();
}
if (executed) return;
if (!config.getConfig().getMapConfigs().isEmpty()) {
//load resources
if (bluemap.loadResources()) {
//metrics
if (config.getConfig().isMetricsEnabled()) Metrics.sendReportAsync("CLI");
//render maps
bluemap.renderMaps();
//since we don't need it any more, free some memory
bluemap.resourcePack = null;
}
}
} catch (ParseException e) {
Logger.global.logError("Failed to parse provided arguments!", e);
} catch (NumberFormatException e) {
Logger.global.logError("One argument expected a number but got the wrong format!", e);
BlueMapCLI.printHelp();
return;
}
BlueMapCLI.printHelp();
}
private static Options createOptions() {
Options options = new Options();
options.addOption("h", "help", false, "Displays this message");
options.addOption(
Option.builder("o")
.longOpt("out")
.hasArg()
.argName("directory-path")
.desc("Defines the render-output directory. Default is '<webroot>/data' (See option -d)")
.build()
);
options.addOption(
Option.builder("d")
.longOpt("dir")
.hasArg()
.argName("directory-path")
.desc("Defines the webroot directory. Default is './web'")
.build()
);
options.addOption("s", "webserver", false, "Starts the integrated webserver");
options.addOption(
Option.builder("c")
.longOpt("create-web")
.desc("The webfiles will be (re)created, existing web-files in the webroot will be replaced!")
.build()
);
options.addOption(
Option.builder("i")
.longOpt("ip")
.hasArg()
.argName("ip-adress")
.desc("Specifies the IP adress the webserver will use")
.build()
);
options.addOption(
Option.builder("p")
.longOpt("port")
.hasArg()
.argName("port")
.desc("Specifies the port the webserver will use. Default is 8100")
.build()
);
options.addOption(
Option.builder()
.longOpt("connections")
.hasArg()
.argName("count")
.desc("Sets the maximum count of simultaneous client-connections that the webserver will allow. Default is 100")
.build()
);
options.addOption(
Option.builder("w")
.longOpt("world")
.hasArg()
.argName("directory-path")
.desc("Defines the world-save folder that will be rendered")
.build()
);
options.addOption(
Option.builder("f")
.longOpt("force-render")
.desc("Rerenders all tiles even if there are no changes since the last render")
.build()
);
options.addOption(
Option.builder("r")
.longOpt("resource")
.hasArg()
.argName("file")
.desc("Defines the resourcepack that will be used to render the map")
.build()
);
options.addOption(
Option.builder("t")
.longOpt("threads")
.hasArg()
.argName("thread-count")
.desc("Defines the number of threads that will be used to render the map. Default is the number of system cores")
.build()
);
options.addOption(
Option.builder("I")
.longOpt("id")
.hasArg()
.argName("id")
.desc("The id of the world. Default is the name of the world-folder")
.build()
);
options.addOption(
Option.builder("n")
.longOpt("name")
.hasArg()
.argName("name")
.desc("The name of the world. Default is the world-name defined in the level.dat")
.build()
);
options.addOption(
Option.builder()
.longOpt("render-all")
.desc("Also renders blocks that are normally omitted due to a sunlight value of 0. Enabling this can cause a big performance impact in the web-viewer, but it might fix some cases where blocks are missing.")
.build()
);
options.addOption(
Option.builder("ao")
.longOpt("ambient-occlusion")
.hasArg()
.argName("value")
.desc("The strength of ambient-occlusion baked into the model (a value between 0 and 1). Default is 0.25")
.build()
);
options.addOption(
Option.builder("l")
.longOpt("lighting")
.hasArg()
.argName("value")
.desc("The max strength of shadows baked into the model (a value between 0 and 1 where 0 is fully bright (no lighting) and 1 is max lighting-contrast). Default is 0.8")
.build()
);
options.addOption(
Option.builder("ys")
.longOpt("y-slice")
.hasArg()
.argName("value")
.desc("Using this, BlueMap pretends that every Block above the defined value is AIR. Default is disabled")
.build()
);
options.addOption(
Option.builder("yM")
.longOpt("y-max")
.hasArg()
.argName("value")
.desc("Blocks above this height will not be rendered. Default is no limit")
.build()
);
options.addOption(
Option.builder("ym")
.longOpt("y-min")
.hasArg()
.argName("value")
.desc("Blocks below this height will not be rendered. Default is no limit")
.build()
);
options.addOption(
Option.builder()
.longOpt("hr-tilesize")
.hasArg()
.argName("value")
.desc("Defines the size of one map-tile in blocks. If you change this value, the lowres values might need adjustment as well! Default is 32")
.build()
);
options.addOption(
Option.builder()
.longOpt("hr-viewdist")
.hasArg()
.argName("value")
.desc("The View-Distance for hires tiles on the web-map (the value is the radius in tiles). Default is 6")
.build()
);
options.addOption(
Option.builder()
.longOpt("lr-tilesize")
.hasArg()
.argName("value")
.desc("Defines the size of one lowres-map-tile in grid-points. Default is 50")
.build()
);
options.addOption(
Option.builder()
.longOpt("lr-resolution")
.hasArg()
.argName("value")
.desc("Defines resolution of the lowres model. E.g. If the hires.tileSize is 32, a value of 4 means that every 8*8 blocks will be summarized by one point on the lowres map. Calculation: 32 / 4 = 8! You have to use values that result in an integer if you use the above calculation! Default is 4")
.build()
);
options.addOption(
Option.builder()
.longOpt("lr-viewdist")
.hasArg()
.argName("value")
.desc("The View-Distance for lowres tiles on the web-map (the value is the radius in tiles). Default is 5")
.build()
);
Option.builder("c")
.longOpt("config")
.hasArg()
.argName("config-file")
.desc("Sets path of the configuration file to use")
.build()
);
options.addOption("f", "force-render", false, "Forces rendering everything, instead of only rendering chunks that have been modified since the last render");
return options;
}
@ -534,7 +316,7 @@ public class BlueMapCLI {
if (file.isFile()) {
try {
filename = "./" + new File(".").toPath().relativize(file.toPath()).toString();
filename = "." + File.separator + new File("").getAbsoluteFile().toPath().relativize(file.toPath()).toString();
} catch (IllegalArgumentException ex) {
filename = file.getAbsolutePath();
}
@ -543,13 +325,7 @@ public class BlueMapCLI {
String command = "java -jar " + filename;
formatter.printHelp(command + " [options]", "\nOptions:", createOptions(), "\n"
+ "Examples:\n\n"
+ command + " -w ./world/\n"
+ " -> Renders the whole world to ./web/data/\n\n"
+ command + " -csi localhost\n"
+ " -> Creates all neccesary web-files in ./web/ and starts the webserver. (Open http://localhost:8100/ in your browser)"
);
formatter.printHelp(command + " [options]", "\nOptions:", createOptions(), "");
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.ChunkNotGeneratedException;
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, ChunkNotGeneratedException {
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

@ -42,7 +42,7 @@ import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class RenderManager extends Thread {
public class RenderTask {
private World world;
private TileRenderer tileRenderer;
@ -54,11 +54,9 @@ public class RenderManager extends Thread {
private Thread[] threads;
private Runnable onFinished;
public RenderManager(World world, TileRenderer tileRenderer, Collection<Vector2i> tilesToRender, int threadCount) {
this.world = world;
this.tileRenderer = tileRenderer;
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
@ -96,14 +94,7 @@ public class RenderManager extends Thread {
}
public synchronized void start(Runnable onFinished) {
this.onFinished = onFinished;
start();
}
@Override
public void run() {
public void render() {
this.startTime = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
@ -154,8 +145,6 @@ public class RenderManager extends Thread {
}
tileRenderer.save();
onFinished.run();
}
private void renderThread() {

View File

@ -0,0 +1,168 @@
## ##
## BlueMap ##
## ##
## by Blue (Lukas Rieger) ##
## http://bluecolored.de/ ##
## ##
# !! Don't change this !!
# This is used to detect version-changes in the configuration
# and update configuration correctly.
version: "%version%"
# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula),
# you confirm that you own a license to Minecraft (Java Edition)
# and you agree that BlueMap will download and use this file for you: %minecraft-client-url%
# (Alternatively you can download the file yourself and store it here: <data>/minecraft-client-%minecraft-client-version%.jar)
# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compilant with mojang's EULA.
# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.)
# %datetime-iso%
accept-download: false
# 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%"}
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.
data: "data"
web {
# With this setting you can enable the integrated web-server.
enabled: false
# The webroot of the website that displays the map.
webroot: "web"
# The IP-Adress that the webserver binds to.
# If this setting is commented out, bluemap tries to find the default ip-adress of your system.
# If you only want to access it locally use "localhost".
#ip: "localhost"
#ip: "127.0.0.1"
# The port that the webserver listenes to.
# Default is 8100
port: 8100
# Max number of simultaneous connections that the webserver allows
# Default is 100
maxConnectionCount: 100
# Unncomment this to override the path where bluemap stores the data-files.
# Default is "<webroot>/data"
#web-data: "path/to/data/folder"
}
# This changes the amount of threads that BlueMap will use to render the maps.
# A higher value can improve render-speed but could impact performance on the host machine.
# This should be always below or equal to the number of available processor-cores.
# If this value is commented out BlueMap tries to find the optimal thread count to max out render-performance
#renderThreadCount: 2
# This is an array with multiple configured maps.
# You can define multiple maps, for different worlds with different render-settings here
maps: [
{
# The id of this map
# Should only contain word-charactes: [a-zA-Z0-9_]
id: "world"
# The name of this map
# This defines the display name of this map, you can change this at any time
# Default is the id of this map
name: "World"
# The path to the save-folder of the world to render
world: "world"
# 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.
# Default is false
renderCaves: false
# AmbientOcclusion adds soft shadows into corners, which gives the map a much better look.
# This has only a small impact on render-time and has no impact on the web-performance of the map.
# The value defines the strength of the shading, a value of 0 disables ambientOcclusion.
# Default is 0.25
ambientOcclusion: 0.25
# Lighting uses the light-data in minecraft to shade each block-face.
# If this is enabled, caves and inside buildings without torches will be darker.
# The value defines the strength of the shading and a value of 0 disables lighting (every block will be fully lit).
# Default is 0.8
lighting: 0.8
# Using this, BlueMap pretends that every Block above the defined value is AIR.
# Default is disabled
#sliceY: 90
# With the below values you can just not render blocks at certain heights.
# This can be used to ignore the nethers ceiling.
# Default is no min or max y value
#minY: 50
#maxY: 126
# HIRES is the high-resolution render of the map. Where you see every block.
hires {
# Defines the size of one map-tile in blocks.
# If you change this value, the lowres values might need adjustment as well!
# Default is 32
tileSize: 32
# The View-Distance for hires tiles on the web-map (the value is the radius in tiles)
# Default is 3.5
viewDistance: 3.5
}
# LOWRES is the low-resolution render of the map. THats the model that you see if you zoom far out to get an overview.
lowres {
# Defines resolution of the lowres model. E.g. If the hires.tileSize is 32, a value of 4 means that every 8*8 blocks will be summarized by one point on the lowres map.
# Calculation: 32 / 4 = 8
# You can only use values that result in an integer if you use the above calculation!
# Default is 4
pointsPerHiresTile: 4
# Defines the size of one lowres-map-tile in points.
# Default is 50
pointsPerLowresTile: 50
# The View-Distance for lowres tiles on the web-map (the value is the radius in tiles)
# Default is 4
viewDistance: 4
}
}
# Here another example for the End-Map
# Things we dont want to change from default we can just omit
{
id: "end"
name: "End"
world: "world/DIM1"
# In the end is no light, so we need to enable this or we don't see anything.
renderCaves: true
# Same here, we don't want a dark map. But not completely disabled, so we see the effect of e.g torches.
lighting: 0.4
}
# Here another example for the Nether-Map
{
id: "nether"
name: "Nether"
world: "world/DIM-1"
renderCaves: true
lighting: 0.6
# We slice the whole world at y:90 so evrery block above 90 will be air.
# This way we dont render the nethers ceiling.
sliceY: 90
# Instead of slicing we also could do this, that would look like an x-ray view through the ceiling.
#maxY: 126
}
]

View File

@ -43,6 +43,7 @@ public class Configuration implements WebServerConfig {
private String version;
private boolean downloadAccepted = false;
private boolean metricsEnabled = false;
private boolean webserverEnabled = true;
private int webserverPort = 8100;
@ -61,6 +62,7 @@ private String version;
public Configuration(ConfigurationNode node) throws IOException {
version = node.getNode("version").getString("-");
downloadAccepted = node.getNode("accept-download").getBoolean(false);
metricsEnabled = node.getNode("metrics").getBoolean(false);
dataPath = toFolder(node.getNode("data").getString("data"));
@ -157,6 +159,10 @@ private String version;
return downloadAccepted;
}
public boolean isMetricsEnabled() {
return metricsEnabled;
}
public int getRenderThreadCount() {
return renderThreadCount;
}

View File

@ -52,16 +52,23 @@ public class ConfigurationFile {
CONFIG_PLACEHOLDERS.add(new Placeholder("minecraft-client-version", ResourcePack.MINECRAFT_CLIENT_VERSION));
}
private File configFile;
private Configuration config;
private ConfigurationFile(File configFile) throws IOException {
this.configFile = configFile;
ConfigurationLoader<CommentedConfigurationNode> configLoader = HoconConfigurationLoader.builder()
.setFile(configFile)
.build();
CommentedConfigurationNode rootNode = configLoader.load();
config = new Configuration(rootNode);
this.config = new Configuration(rootNode);
}
public File getFile() {
return configFile;
}
public Configuration getConfig() {