Initial commit: Merging Projects BlueMapCore, BlueMapCLI and BlueMapSponge into one

This commit is contained in:
Blue (Lukas Rieger) 2019-11-02 17:23:48 +01:00
commit 49ae2cf08f
125 changed files with 20404 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
.gradle/
.gradle/*
*/.gradle/*
build/
build/*
*/build/*
.settings/
.settings/*
*/.settings/*
bin/
bin/*
*/bin/*
.classpath
*/.classpath
.project
*/.project
# exclude generated resource
src/main/resources/webroot.zip

4
BlueMapCLI/build.gradle Normal file
View File

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

View File

@ -0,0 +1,555 @@
/*
* 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.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
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.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.WebSettings;
import de.bluecolored.bluemap.core.webserver.WebServer;
import de.bluecolored.bluemap.core.world.World;
public class BlueMapCLI {
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 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());
if (mapName == null) {
mapName = world.getName();
}
if (mapId == null) {
mapId = mapPath.getName().toLowerCase();
}
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.");
}
try {
webSettings.set(starttime, mapId, "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();
}
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())
);
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.createDefaultResource(defaultResourceFile);
} catch (IOException e) {
throw new IOException("Failed to create default resources!", e);
}
List<File> resourcePacks = new ArrayList<>();
resourcePacks.add(defaultResourceFile);
if (this.extraResourceFile != null) resourcePacks.add(extraResourceFile);
ResourcePack resourcePack = new ResourcePack(resourcePacks, new File(dataPath, "textures.json"));
defaultResourceFile.delete();
return resourcePack;
}
public static void main(String[] args) throws IOException, NoSuchResourceException {
CommandLineParser parser = new DefaultParser();
try {
CommandLine cmd = parser.parse(BlueMapCLI.createOptions(), args, false);
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)));
if (cmd.hasOption("c")) {
bluemapcli.updateWebFiles();
executed = true;
}
if (cmd.hasOption("s")) {
bluemapcli.startWebserver();
executed = true;
}
if (cmd.hasOption("w")) {
bluemapcli.renderMap(new File(cmd.getOptionValue("w")), !cmd.hasOption("f"));
executed = true;
}
if (executed) return;
} 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();
}
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()
);
return options;
}
private static void printHelp() {
HelpFormatter formatter = new HelpFormatter();
String filename = "bluemapcli.jar";
try {
File file = new File(BlueMapCLI.class.getProtectionDomain()
.getCodeSource()
.getLocation()
.getPath());
if (file.isFile()) {
try {
filename = "./" + new File(".").toPath().relativize(file.toPath()).toString();
} catch (IllegalArgumentException ex) {
filename = file.getAbsolutePath();
}
}
} catch (Exception ex) {}
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)"
);
}
}

View File

@ -0,0 +1,176 @@
/*
* 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.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
public class RenderManager extends Thread {
private World world;
private TileRenderer tileRenderer;
private Deque<Vector2i> tilesToRender;
private int tileCount;
private long startTime = -1;
private int renderedTiles = 0;
private Thread[] threads;
private Runnable onFinished;
public RenderManager(World world, TileRenderer tileRenderer, Collection<Vector2i> tilesToRender, int threadCount) {
this.world = world;
this.tileRenderer = tileRenderer;
//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 (v1.getY() < v1.getY()) return -1;
if (v1.getY() > v1.getY()) return 1;
if (v1.getX() < v1.getX()) return -1;
if (v1.getX() > v1.getX()) return 1;
return 0;
});
this.tilesToRender = new ArrayDeque<>(sortedTiles);
this.tileCount = this.tilesToRender.size();
this.threads = new Thread[threadCount];
}
public synchronized void start(Runnable onFinished) {
this.onFinished = onFinished;
start();
}
@Override
public void run() {
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)(((double) time / pct) * (1d - pct));
String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true);
Logger.global.logInfo("Rendered " + renderedTiles + " of " + tileCount + " tiles in " + durationString);
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();
onFinished.run();
}
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);
} catch (ChunkNotGeneratedException e) {}
renderedTiles++;
}
}
}

21
BlueMapCore/build.gradle Normal file
View File

@ -0,0 +1,21 @@
dependencies {
compile 'com.google.guava:guava:21.0'
compile 'com.google.code.gson:gson:2.8.0'
compile 'org.apache.commons:commons-lang3:3.5'
compile group: 'commons-io', name: 'commons-io', version: '2.6'
compile 'com.flowpowered:flow-math:1.0.3'
compile 'ninja.leaping.configurate:configurate-hocon:3.3'
compile 'ninja.leaping.configurate:configurate-gson:3.3'
compile 'com.github.Querz:NBT:4.0'
compile group: 'commons-cli', name: 'commons-cli', version: '1.4'
}
task zipWebroot(type: Zip) {
from fileTree('src/main/webroot')
archiveName 'webroot.zip'
destinationDir(file('/src/main/resources/'))
outputs.upToDateWhen { false }
}
//always update the zip before build
compileJava.dependsOn(zipWebroot)

View File

@ -0,0 +1,69 @@
/*
* 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.core.logger;
import java.util.Set;
import com.google.common.collect.Sets;
public abstract class AbstractLogger extends Logger {
private Set<String> noFloodLog;
public AbstractLogger() {
noFloodLog = Sets.newConcurrentHashSet();
}
@Override
public void noFloodError(String key, String message, Throwable throwable){
if (noFloodLog.add(key)) logError(message, throwable);
}
@Override
public void noFloodWarning(String key, String message){
if (noFloodLog.add(key)) logWarning(message);
}
@Override
public void noFloodInfo(String key, String message){
if (noFloodLog.add(key)) logInfo(message);
}
@Override
public void noFloodDebug(String key, String message){
if (noFloodLog.add(key)) logDebug(message);
}
@Override
public void clearNoFloodLog() {
noFloodLog.clear();
}
@Override
public void removeNoFloodKey(String key) {
noFloodLog.remove(key);
}
}

View File

@ -0,0 +1,99 @@
/*
* 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.core.logger;
public abstract class Logger {
public static Logger global = stdOut();
public abstract void logError(String message, Throwable throwable);
public abstract void logWarning(String message);
public abstract void logInfo(String message);
public abstract void logDebug(String message);
/**
* Only log the error if no message has been logged before with the same key.
*/
public abstract void noFloodError(String key, String message, Throwable throwable);
/**
* Only log the warning if no message has been logged before with the same key.
*/
public abstract void noFloodWarning(String key, String message);
/**
* Only log the info if no message has been logged before with the same key.
*/
public abstract void noFloodInfo(String key, String message);
/**
* Only log the debug-message if no message has been logged before with the same key.
*/
public abstract void noFloodDebug(String key, String message);
/**
* Only log the error if no message has been logged before with the same content.
*/
public void noFloodError(String message, Throwable throwable){
noFloodError(message, message, throwable);
}
/**
* Only log the warning if no message has been logged before with the same content.
*/
public void noFloodWarning(String message){
noFloodWarning(message, message);
}
/**
* Only log the info if no message has been logged before with the same content.
*/
public void noFloodInfo(String message){
noFloodInfo(message, message);
}
/**
* Only log the debug-message if no message has been logged before with the same content.
*/
public void noFloodDebug(String message){
noFloodDebug(message, message);
}
public abstract void clearNoFloodLog();
public abstract void removeNoFloodKey(String key);
public void removeNoFloodMessage(String message){
removeNoFloodKey(message);
}
public static Logger stdOut(){
return new PrintStreamLogger(System.out, System.err);
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.core.logger;
import java.io.PrintStream;
public class PrintStreamLogger extends AbstractLogger {
private PrintStream out, err;
public PrintStreamLogger(PrintStream out, PrintStream err) {
this.out = out;
this.err = err;
}
@Override
public void logError(String message, Throwable throwable) {
err.println("[ERROR] " + message);
throwable.printStackTrace(err);
}
@Override
public void logWarning(String message) {
out.println("[WARNING] " + message);
}
@Override
public void logInfo(String message) {
out.println("[INFO] " + message);
}
@Override
public void logDebug(String message) {
out.println("[DEBUG] " + message);
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.core.logger;
public class VoidLogger extends Logger {
@Override
public void logError(String message, Throwable throwable) {}
@Override
public void logWarning(String message) {}
@Override
public void logInfo(String message) {}
@Override
public void logDebug(String message) {}
@Override
public void noFloodError(String key, String message, Throwable throwable) {}
@Override
public void noFloodWarning(String key, String message) {}
@Override
public void noFloodInfo(String key, String message) {}
@Override
public void noFloodDebug(String key, String message) {}
@Override
public void clearNoFloodLog() {}
@Override
public void removeNoFloodKey(String key) {}
}

View File

@ -0,0 +1,75 @@
/*
* 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.core.mca;
import java.io.IOException;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.mapping.LightData;
import de.bluecolored.bluemap.core.world.BlockState;
import net.querz.nbt.CompoundTag;
public abstract class Chunk {
private final MCAWorld world;
private final Vector2i chunkPos;
protected Chunk(MCAWorld world, CompoundTag chunkTag) {
this.world = world;
CompoundTag levelData = chunkTag.getCompoundTag("Level");
chunkPos = new Vector2i(
levelData.getInt("xPos"),
levelData.getInt("zPos")
);
}
public abstract boolean isGenerated();
public Vector2i getChunkPos() {
return chunkPos;
}
public MCAWorld getWorld() {
return world;
}
public abstract BlockState getBlockState(Vector3i pos);
public abstract LightData getLightData(Vector3i pos);
public abstract String getBiomeId(Vector3i pos);
public static Chunk create(MCAWorld world, CompoundTag chunkTag) throws IOException {
int version = chunkTag.getInt("DataVersion");
if (version <= 1343) return new ChunkAnvil112(world, chunkTag);
return new ChunkAnvil113(world, chunkTag);
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.core.mca;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.mapping.BiomeIdMapper;
import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper;
import de.bluecolored.bluemap.core.mca.mapping.LightData;
import de.bluecolored.bluemap.core.world.BlockState;
import net.querz.nbt.CompoundTag;
import net.querz.nbt.ListTag;
import net.querz.nbt.mca.MCAUtil;
class ChunkAnvil112 extends Chunk {
private BlockIdMapper blockIdMapper;
private BiomeIdMapper biomeIdMapper;
private boolean isGenerated;
private Section[] sections;
private byte[] biomes;
@SuppressWarnings("unchecked")
public ChunkAnvil112(MCAWorld world, CompoundTag chunkTag) {
super(world, chunkTag);
blockIdMapper = getWorld().getBlockIdMapper();
biomeIdMapper = getWorld().getBiomeIdMapper();
CompoundTag levelData = chunkTag.getCompoundTag("Level");
isGenerated =
levelData.getBoolean("LightPopulated") &&
levelData.getBoolean("TerrainPopulated");
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag);
sections[section.getSectionY()] = section;
}
biomes = levelData.getByteArray("Biomes");
}
@Override
public boolean isGenerated() {
return isGenerated;
}
@Override
public BlockState getBlockState(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY());
Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR;
return section.getBlockState(pos);
}
@Override
public LightData getLightData(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY());
Section section = this.sections[sectionY];
if (section == null) return LightData.FULL;
return section.getLightData(pos);
}
@Override
public String getBiomeId(Vector3i pos) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int z = pos.getZ() & 0xF;
int biomeByteIndex = z * 16 + x;
return biomeIdMapper.get(biomes[biomeByteIndex]);
}
private class Section {
private int sectionY;
private byte[] blocks;
private byte[] add;
private byte[] blockLight;
private byte[] skyLight;
private byte[] data;
public Section(CompoundTag sectionData) {
this.sectionY = sectionData.getByte("Y");
this.blocks = sectionData.getByteArray("Blocks");
this.add = sectionData.getByteArray("Add");
this.blockLight = sectionData.getByteArray("BlockLight");
this.skyLight = sectionData.getByteArray("SkyLight");
this.data = sectionData.getByteArray("Data");
}
public int getSectionY() {
return sectionY;
}
public BlockState getBlockState(Vector3i pos) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF;
int blockByteIndex = y * 256 + z * 16 + x;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
int blockId = this.blocks[blockByteIndex] & 0xFF;
if (this.add.length > 0) {
blockId = blockId & (getByteHalf(this.add[blockHalfByteIndex], largeHalf) << 8);
}
int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf);
BlockState blockState = blockIdMapper.get(blockId, blockData);
return blockState;
}
public LightData getLightData(Vector3i pos) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF;
int blockByteIndex = y * 256 + z * 16 + x;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf);
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
return new LightData(skyLight, blockLight);
}
/**
* Extracts the 4 bits of the left (largeHalf = <code>true</code>) or the right (largeHalf = <code>false</code>) side of the byte stored in <code>value</code>.<br>
* The value is treated as an unsigned byte.
*/
private int getByteHalf(int value, boolean largeHalf) {
value = value & 0xFF;
if (largeHalf) {
value = value >> 4;
}
value = value & 0xF;
return value;
}
}
}

View File

@ -0,0 +1,224 @@
/*
* 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.core.mca;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.mapping.BiomeIdMapper;
import de.bluecolored.bluemap.core.mca.mapping.LightData;
import de.bluecolored.bluemap.core.world.BlockState;
import net.querz.nbt.ByteArrayTag;
import net.querz.nbt.CompoundTag;
import net.querz.nbt.IntArrayTag;
import net.querz.nbt.ListTag;
import net.querz.nbt.StringTag;
import net.querz.nbt.Tag;
import net.querz.nbt.mca.MCAUtil;
class ChunkAnvil113 extends Chunk {
private BiomeIdMapper biomeIdMapper;
private boolean isGenerated;
private Section[] sections;
private int[] biomes;
@SuppressWarnings("unchecked")
public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag) {
super(world, chunkTag);
biomeIdMapper = getWorld().getBiomeIdMapper();
CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status");
isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag);
if (section.getSectionY() >= 0) sections[section.getSectionY()] = section;
}
}
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
if (tag instanceof ByteArrayTag) {
byte[] bs = ((ByteArrayTag) tag).getValue();
biomes = new int[bs.length];
for (int i = 0; i < bs.length; i++) {
biomes[i] = bs[i];
}
}
else if (tag instanceof IntArrayTag) {
biomes = ((IntArrayTag) tag).getValue();
}
else {
biomes = new int[2048];
}
}
@Override
public boolean isGenerated() {
return isGenerated;
}
@Override
public BlockState getBlockState(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY());
Section section = this.sections[sectionY];
if (section == null) return BlockState.AIR;
return section.getBlockState(pos);
}
@Override
public LightData getLightData(Vector3i pos) {
int sectionY = MCAUtil.blockToChunk(pos.getY());
Section section = this.sections[sectionY];
if (section == null) return LightData.FULL;
return section.getLightData(pos);
}
@Override
public String getBiomeId(Vector3i pos) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int z = pos.getZ() & 0xF;
int biomeByteIndex = z * 16 + x;
return biomeIdMapper.get(biomes[biomeByteIndex]);
}
private class Section {
private int sectionY;
private byte[] blockLight;
private byte[] skyLight;
private long[] blocks;
private BlockState[] palette;
@SuppressWarnings("unchecked")
public Section(CompoundTag sectionData) {
this.sectionY = sectionData.getByte("Y");
this.blockLight = sectionData.getByteArray("BlockLight");
if (blockLight.length == 0) blockLight = new byte[2048];
this.skyLight = sectionData.getByteArray("SkyLight");
if (skyLight.length == 0) skyLight = new byte[2048];
this.blocks = sectionData.getLongArray("BlockStates");
//read block palette
ListTag<CompoundTag> paletteTag = (ListTag<CompoundTag>) sectionData.getListTag("Palette");
if (paletteTag != null) {
this.palette = new BlockState[paletteTag.size()];
for (int i = 0; i < this.palette.length; i++) {
CompoundTag stateTag = paletteTag.get(i);
String id = stateTag.getString("Name");
Map<String, String> properties = new HashMap<>();
if (stateTag.containsKey("Properties")) {
CompoundTag propertiesTag = stateTag.getCompoundTag("Properties");
for (Entry<String, Tag<?>> property : propertiesTag) {
properties.put(property.getKey(), ((StringTag) property.getValue()).getValue());
}
}
palette[i] = new BlockState(id, properties);
}
} else {
this.palette = new BlockState[0];
}
}
public int getSectionY() {
return sectionY;
}
public BlockState getBlockState(Vector3i pos) {
if (blocks.length == 0) return BlockState.AIR;
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF;
int blockIndex = y * 256 + z * 16 + x;
int bitsPerBlock = blocks.length * 64 / 4096; //64 bits per long and 4096 blocks per section
int index = blockIndex * bitsPerBlock;
int firstLong = index >> 6; // index / 64
int bitoffset = index & 0x3F; // Math.floorMod(index, 64)
long value = blocks[firstLong] >>> bitoffset;
if (bitoffset > 0 && firstLong + 1 < blocks.length) {
long value2 = blocks[firstLong + 1];
value2 = value2 << -bitoffset;
value = value | value2;
}
value = value & (0xFFFFFFFFFFFFFFFFL >>> -bitsPerBlock);
if (value >= palette.length) {
Logger.global.noFloodWarning("palettewarning", "Got palette value " + value + " but palette has size of " + palette.length + " (Future occasions of this error will not be logged)");
return BlockState.AIR;
}
return palette[(int) value];
}
public LightData getLightData(Vector3i pos) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
int y = pos.getY() & 0xF;
int z = pos.getZ() & 0xF;
int blockByteIndex = y * 256 + z * 16 + x;
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf);
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
return new LightData(skyLight, blockLight);
}
/**
* Extracts the 4 bits of the left (largeHalf = <code>true</code>) or the right (largeHalf = <code>false</code>) side of the byte stored in <code>value</code>.<br>
* The value is treated as an unsigned byte.
*/
private int getByteHalf(int value, boolean largeHalf) {
value = value & 0xFF;
if (largeHalf) {
value = value >> 4;
}
value = value & 0xF;
return value;
}
}
}

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.core.mca;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.mapping.BlockProperties;
import de.bluecolored.bluemap.core.mca.mapping.LightData;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockState;
public class MCABlock extends Block {
private MCAWorld world;
private BlockState blockState;
private LightData lightData;
private String biome;
private BlockProperties properties;
private Vector3i pos;
public MCABlock(MCAWorld world, BlockState blockState, LightData lightData, String biome, BlockProperties properties, Vector3i pos) {
this.world = world;
this.blockState = blockState;
this.lightData = lightData;
this.biome = biome;
this.properties = properties;
this.pos = pos;
}
@Override
public BlockState getBlock() {
return blockState;
}
@Override
public MCAWorld getWorld() {
return world;
}
@Override
public Vector3i getPosition() {
return pos;
}
@Override
public double getSunLightLevel() {
return lightData.getSkyLight();
}
@Override
public double getBlockLightLevel() {
return lightData.getBlockLight();
}
@Override
public boolean isCullingNeighborFaces() {
return properties.isCulling();
}
@Override
public boolean isOccludingNeighborFaces() {
return properties.isOccluding();
}
@Override
public String getBiome() {
return biome;
}
}

View File

@ -0,0 +1,438 @@
/*
* 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.core.mca;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.extensions.BlockStateExtension;
import de.bluecolored.bluemap.core.mca.extensions.DoorExtension;
import de.bluecolored.bluemap.core.mca.extensions.DoublePlantExtension;
import de.bluecolored.bluemap.core.mca.extensions.FireExtension;
import de.bluecolored.bluemap.core.mca.extensions.GlassPaneConnectExtension;
import de.bluecolored.bluemap.core.mca.extensions.NetherFenceConnectExtension;
import de.bluecolored.bluemap.core.mca.extensions.RedstoneExtension;
import de.bluecolored.bluemap.core.mca.extensions.SnowyExtension;
import de.bluecolored.bluemap.core.mca.extensions.StairShapeExtension;
import de.bluecolored.bluemap.core.mca.extensions.TripwireConnectExtension;
import de.bluecolored.bluemap.core.mca.extensions.WallConnectExtension;
import de.bluecolored.bluemap.core.mca.extensions.WoodenFenceConnectExtension;
import de.bluecolored.bluemap.core.mca.mapping.BiomeIdMapper;
import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper;
import de.bluecolored.bluemap.core.mca.mapping.BlockProperties;
import de.bluecolored.bluemap.core.mca.mapping.BlockPropertyMapper;
import de.bluecolored.bluemap.core.mca.mapping.LightData;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
import de.bluecolored.bluemap.core.world.WorldChunk;
import net.querz.nbt.CompoundTag;
import net.querz.nbt.NBTUtil;
import net.querz.nbt.Tag;
import net.querz.nbt.mca.CompressionType;
import net.querz.nbt.mca.MCAUtil;
public class MCAWorld implements World {
private static final Cache<WorldChunkHash, Chunk> CHUNK_CACHE = CacheBuilder.newBuilder().maximumSize(500).build();
private static final Multimap<String, BlockStateExtension> BLOCK_STATE_EXTENSIONS = MultimapBuilder.hashKeys().arrayListValues().build();
public static final BlockIdMapper DEFAULT_BLOCK_ID_MAPPER;
public static final BlockPropertyMapper DEFAULT_BLOCK_PROPERTY_MAPPER;
public static final BiomeIdMapper DEFAULT_BIOME_ID_MAPPER;
static {
try {
DEFAULT_BLOCK_ID_MAPPER = BlockIdMapper.create();
DEFAULT_BLOCK_PROPERTY_MAPPER = BlockPropertyMapper.create();
DEFAULT_BIOME_ID_MAPPER = BiomeIdMapper.create();
} catch (IOException e) {
throw new RuntimeException("Failed to load essential resources!", e);
}
registerBlockStateExtension(new SnowyExtension());
registerBlockStateExtension(new StairShapeExtension());
registerBlockStateExtension(new FireExtension());
registerBlockStateExtension(new RedstoneExtension());
registerBlockStateExtension(new DoorExtension());
registerBlockStateExtension(new NetherFenceConnectExtension());
registerBlockStateExtension(new TripwireConnectExtension());
registerBlockStateExtension(new WallConnectExtension());
registerBlockStateExtension(new WoodenFenceConnectExtension());
registerBlockStateExtension(new GlassPaneConnectExtension());
registerBlockStateExtension(new DoublePlantExtension());
}
private final UUID uuid;
private final Path worldFolder;
private String name;
private AABB boundaries;
private int seaLevel;
private Vector3i spawnPoint;
private BlockIdMapper blockIdMapper;
private BlockPropertyMapper blockPropertyMapper;
private BiomeIdMapper biomeIdMapper;
private MCAWorld(
Path worldFolder,
UUID uuid,
String name,
int worldHeight,
int seaLevel,
Vector3i spawnPoint,
BlockIdMapper blockIdMapper,
BlockPropertyMapper blockPropertyMapper,
BiomeIdMapper biomeIdMapper
) {
this.uuid = uuid;
this.worldFolder = worldFolder;
this.name = name;
this.boundaries = new AABB(new Vector3i(-10000000, 0, -10000000), new Vector3i(10000000, worldHeight, 10000000));
this.seaLevel = seaLevel;
this.spawnPoint = spawnPoint;
this.blockIdMapper = blockIdMapper;
this.blockPropertyMapper = blockPropertyMapper;
this.biomeIdMapper = biomeIdMapper;
}
public BlockState getBlockState(Vector3i pos) {
try {
Vector2i chunkPos = blockToChunk(pos);
Chunk chunk = getChunk(chunkPos);
return chunk.getBlockState(pos);
} catch (Exception ex) {
return BlockState.AIR;
}
}
@Override
public Block getBlock(Vector3i pos) throws ChunkNotGeneratedException {
try {
Vector2i chunkPos = blockToChunk(pos);
Chunk chunk = getChunk(chunkPos);
BlockState blockState = getExtendedBlockState(chunk, pos);
LightData lightData = chunk.getLightData(pos);
String biome = chunk.getBiomeId(pos);
BlockProperties properties = blockPropertyMapper.map(blockState);
return new MCABlock(this, blockState, lightData, biome, properties, pos);
} catch (IOException ex) {
throw new ChunkNotGeneratedException(ex); // to resolve the error, act like the chunk has not been generated yet
}
}
private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) throws ChunkNotGeneratedException {
BlockState blockState = chunk.getBlockState(pos);
for (BlockStateExtension ext : BLOCK_STATE_EXTENSIONS.get(blockState.getId())) {
blockState = ext.extend(this, pos, blockState);
}
return blockState;
}
@Override
public AABB getBoundaries() {
return boundaries;
}
@Override
public WorldChunk getWorldChunk(AABB boundaries) {
return new MCAWorldChunk(this, boundaries);
}
public Chunk getChunk(Vector2i chunkPos) throws IOException, ChunkNotGeneratedException {
try {
Chunk chunk = CHUNK_CACHE.get(new WorldChunkHash(this, chunkPos), () -> this.loadChunk(chunkPos));
if (!chunk.isGenerated()) throw new ChunkNotGeneratedException();
return chunk;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
}
else if (cause instanceof ChunkNotGeneratedException) {
throw (ChunkNotGeneratedException) cause;
}
else throw new IOException(cause);
}
}
private Chunk loadChunk(Vector2i chunkPos) throws IOException, ChunkNotGeneratedException {
Vector2i regionPos = chunkToRegion(chunkPos);
Path regionPath = getMCAFilePath(regionPos);
try (RandomAccessFile raf = new RandomAccessFile(regionPath.toFile(), "r")) {
int xzChunk = Math.floorMod(chunkPos.getY(), 32) * 32 + Math.floorMod(chunkPos.getX(), 32);
raf.seek(xzChunk * 4);
int offset = raf.read() << 16;
offset |= (raf.read() & 0xFF) << 8;
offset |= raf.read() & 0xFF;
offset *= 4096;
int size = raf.readByte() * 4096;
if (size == 0) throw new ChunkNotGeneratedException();
raf.seek(offset + 4); // +4 skip chunk size
byte compressionTypeByte = raf.readByte();
CompressionType compressionType = CompressionType.getFromID(compressionTypeByte);
if (compressionType == null) {
throw new IOException("invalid compression type " + compressionTypeByte);
}
DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD()))));
Tag<?> tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH);
if (tag instanceof CompoundTag) {
return Chunk.create(this, (CompoundTag) tag);
} else {
throw new IOException("invalid data tag: " + (tag == null ? "null" : tag.getClass().getName()));
}
}
}
public boolean isChunkGenerated(Vector2i chunkPos) {
try {
getChunk(chunkPos);
} catch (ChunkNotGeneratedException | IOException e) {
return false;
}
return true;
}
@Override
public Collection<Vector2i> getChunkList(long modifiedSinceMillis){
List<Vector2i> chunks = new ArrayList<>(10000);
for (File file : getRegionFolder().toFile().listFiles()) {
if (!file.getName().endsWith(".mca")) continue;
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
String[] filenameParts = file.getName().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
int xzChunk = z * 32 + x;
raf.seek(xzChunk * 4 + 3);
int size = raf.readByte() * 4096;
if (size == 0) continue;
raf.seek(xzChunk * 4 + 4096);
int timestamp = raf.read() << 24;
timestamp |= (raf.read() & 0xFF) << 16;
timestamp |= (raf.read() & 0xFF) << 8;
timestamp |= raf.read() & 0xFF;
if (timestamp >= (modifiedSinceMillis / 1000)) chunks.add(new Vector2i(rX * 32 + x, rZ * 32 + z));
}
}
} catch (Exception ex) {
Logger.global.logWarning("Failed to read .mca file: " + file.getAbsolutePath() + " (" + ex.toString() + ")");
}
}
return chunks;
}
@Override
public String getName() {
return name;
}
@Override
public UUID getUUID() {
return uuid;
}
@Override
public int getSeaLevel() {
return seaLevel;
}
@Override
public Vector3i getSpawnPoint() {
return spawnPoint;
}
public BlockIdMapper getBlockIdMapper() {
return blockIdMapper;
}
public BlockPropertyMapper getBlockPropertyMapper() {
return blockPropertyMapper;
}
public BiomeIdMapper getBiomeIdMapper() {
return biomeIdMapper;
}
public void setBlockIdMapper(BlockIdMapper blockIdMapper) {
this.blockIdMapper = blockIdMapper;
}
public void setBlockPropertyMapper(BlockPropertyMapper blockPropertyMapper) {
this.blockPropertyMapper = blockPropertyMapper;
}
public void setBiomeIdMapper(BiomeIdMapper biomeIdMapper) {
this.biomeIdMapper = biomeIdMapper;
}
public Path getWorldFolder() {
return worldFolder;
}
private Path getRegionFolder() {
return worldFolder.resolve("region");
}
private Path getMCAFilePath(Vector2i region) {
return getRegionFolder().resolve(MCAUtil.createNameFromRegionLocation(region.getX(), region.getY()));
}
public static MCAWorld load(Path worldFolder, UUID uuid) throws IOException {
try {
CompoundTag level = (CompoundTag) NBTUtil.readTag(worldFolder.resolve("level.dat").toFile());
CompoundTag levelData = level.getCompoundTag("Data");
String name = levelData.getString("LevelName");
int worldHeight = 255;
int seaLevel = 63;
Vector3i spawnPoint = new Vector3i(
levelData.getInt("SpawnX"),
levelData.getInt("SpawnY"),
levelData.getInt("SpawnZ")
);
return new MCAWorld(
worldFolder,
uuid,
name,
worldHeight,
seaLevel,
spawnPoint,
DEFAULT_BLOCK_ID_MAPPER,
DEFAULT_BLOCK_PROPERTY_MAPPER,
DEFAULT_BIOME_ID_MAPPER
);
} catch (ClassCastException | NullPointerException ex) {
throw new IOException("Invaid level.dat format!", ex);
}
}
public static Vector2i blockToChunk(Vector3i pos) {
return new Vector2i(
MCAUtil.blockToChunk(pos.getX()),
MCAUtil.blockToChunk(pos.getZ())
);
}
public static Vector2i blockToRegion(Vector3i pos) {
return new Vector2i(
MCAUtil.blockToRegion(pos.getX()),
MCAUtil.blockToRegion(pos.getZ())
);
}
public static Vector2i chunkToRegion(Vector2i pos) {
return new Vector2i(
MCAUtil.chunkToRegion(pos.getX()),
MCAUtil.chunkToRegion(pos.getY())
);
}
public static void registerBlockStateExtension(BlockStateExtension extension) {
for (String id : extension.getAffectedBlockIds()) {
BLOCK_STATE_EXTENSIONS.put(id, extension);
}
}
private static class WorldChunkHash {
private final UUID world;
private final Vector2i chunk;
public WorldChunkHash(MCAWorld world, Vector2i chunk) {
this.world = world.getUUID();
this.chunk = chunk;
}
@Override
public int hashCode() {
return Objects.hash(world, chunk);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof WorldChunkHash) {
WorldChunkHash other = (WorldChunkHash) obj;
return other.chunk.equals(chunk) && world.equals(other.world);
}
return false;
}
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.core.mca;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.World;
import de.bluecolored.bluemap.core.world.WorldChunk;
public class MCAWorldChunk implements WorldChunk {
private MCAWorld world;
private AABB boundaries, extendedBounds;
public MCAWorldChunk(MCAWorld world, AABB boundaries) {
this.world = world;
this.boundaries = boundaries;
this.extendedBounds = boundaries.expand(2, 2, 2);
}
@Override
public World getWorld() {
return world;
}
@Override
public Block getBlock(Vector3i pos) throws ChunkNotGeneratedException {
return world.getBlock(pos);
}
@Override
public AABB getBoundaries() {
return boundaries;
}
@Override
public WorldChunk getWorldChunk(AABB boundaries) {
return new MCAWorldChunk(world, boundaries);
}
@Override
public boolean isGenerated() {
//check one more block in every direction to make sure that extended block states can be generated!
Vector2i minChunk = MCAWorld.blockToChunk(extendedBounds.getMin().toInt());
Vector2i maxChunk = MCAWorld.blockToChunk(extendedBounds.getMax().toInt());
for (int x = minChunk.getX(); x <= maxChunk.getX(); x++) {
for (int z = minChunk.getY(); z <= maxChunk.getY(); z++) {
if (!world.isChunkGenerated(new Vector2i(x, z))) {
return false;
}
}
}
return true;
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.BlockState;
public interface BlockStateExtension {
BlockState extend(MCAWorld world, Vector3i pos, BlockState state);
Collection<String> getAffectedBlockIds();
}

View File

@ -0,0 +1,57 @@
/*
* 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.core.mca.extensions;
import java.util.Set;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public abstract class ConnectExtension implements BlockStateExtension {
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
return state
.with("north", String.valueOf(connectsTo(world, pos.add(Direction.NORTH.toVector()))))
.with("east", String.valueOf(connectsTo(world, pos.add(Direction.EAST.toVector()))))
.with("south", String.valueOf(connectsTo(world, pos.add(Direction.SOUTH.toVector()))))
.with("west", String.valueOf(connectsTo(world, pos.add(Direction.WEST.toVector()))));
}
public boolean connectsTo(MCAWorld world, Vector3i pos) {
return connectsTo(world, pos, world.getBlockState(pos));
}
public boolean connectsTo(MCAWorld world, Vector3i pos, BlockState block) {
return getAffectedBlockIds().contains(block.getFullId());
}
@Override
public abstract Set<String> getAffectedBlockIds();
}

View File

@ -0,0 +1,41 @@
/*
* 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.core.mca.extensions;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.BlockState;
public abstract class ConnectSameOrFullBlockExtension extends ConnectExtension {
@Override
public boolean connectsTo(MCAWorld world, Vector3i pos, BlockState block) {
if (super.connectsTo(world, pos, block)) return true;
return world.getBlockPropertyMapper().map(block).isCulling();
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import java.util.Map.Entry;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public class DoorExtension implements BlockStateExtension {
private static final Collection<String> AFFECTED_BLOCK_IDS = Lists.newArrayList(
"minecraft:wooden_door",
"minecraft:iron_door",
"minecraft:spruce_door",
"minecraft:birch_door",
"minecraft:jungle_door",
"minecraft:acacia_door",
"minecraft:dark_oak_door"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
BlockState otherDoor;
if (state.getProperties().get("half").equals("lower")) {
otherDoor = world.getBlockState(pos.add(Direction.UP.toVector()));
} else {
otherDoor = world.getBlockState(pos.add(Direction.DOWN.toVector()));
}
//copy all properties from the other door
for (Entry<String, String> prop : otherDoor.getProperties().entrySet()) {
if (!state.getProperties().containsKey(prop.getKey())) {
state = state.with(prop.getKey(), prop.getValue());
}
}
return state;
}
@Override
public Collection<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import java.util.Map.Entry;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public class DoublePlantExtension implements BlockStateExtension {
private static final Collection<String> AFFECTED_BLOCK_IDS = Lists.newArrayList(
"minecraft:double_plant"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
if (state.getProperties().get("half").equals("upper")) {
BlockState otherPlant = world.getBlockState(pos.add(Direction.DOWN.toVector()));
//copy all properties from the other half
for (Entry<String, String> prop : otherPlant.getProperties().entrySet()) {
if (!state.getProperties().containsKey(prop.getKey())) {
state = state.with(prop.getKey(), prop.getValue());
}
}
}
return state;
}
@Override
public Collection<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public class FireExtension implements BlockStateExtension {
private static final Collection<String> AFFECTED_BLOCK_IDS = Lists.newArrayList(
"minecraft:fire"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
BlockState below = world.getBlockState(pos.add(0, -1, 0));
boolean isOnGround = world.getBlockPropertyMapper().map(below).isCulling();
for (Direction dir : Direction.values()) {
if (dir != Direction.DOWN) {
if (!isOnGround) {
BlockState neighbor = world.getBlockState(pos.add(dir.toVector()));
state = state.with(dir.name().toLowerCase(), String.valueOf(!world.getBlockPropertyMapper().map(neighbor).isCulling()));
} else {
state = state.with(dir.name().toLowerCase(), "false");
}
}
}
return state;
}
@Override
public Collection<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.core.mca.extensions;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.Sets;
public class GlassPaneConnectExtension extends ConnectSameOrFullBlockExtension {
private static final HashSet<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
"minecraft:glass_pane",
"minecraft:stained_glass_pane",
"minecraft:iron_bars"
);
@Override
public Set<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.mca.extensions;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.Sets;
public class NetherFenceConnectExtension extends ConnectSameOrFullBlockExtension {
private static final HashSet<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
"minecraft:nether_brick_fence"
);
@Override
public Set<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,84 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import java.util.Set;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public class RedstoneExtension implements BlockStateExtension {
private static final Collection<String> AFFECTED_BLOCK_IDS = Lists.newArrayList(
"minecraft:redstone_wire"
);
private static final Set<String> CONNECTIBLE = Sets.newHashSet(
"minecraft:redstone_wire",
"minecraft:unlit_redstone_torch",
"minecraft:redstone_torch",
"minecraft:stone_button",
"minecraft:wooden_button",
"minecraft:stone_button",
"minecraft:lever",
"minecraft:stone_pressure_plate",
"minecraft:wooden_pressure_plate",
"minecraft:light_weighted_pressure_plate",
"minecraft:heavy_weighted_pressure_plate"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
state = state
.with("north", connection(world, pos, state, Direction.NORTH))
.with("east", connection(world, pos, state, Direction.EAST))
.with("south", connection(world, pos, state, Direction.SOUTH))
.with("west", connection(world, pos, state, Direction.WEST));
return state;
}
private String connection(MCAWorld world, Vector3i pos, BlockState state, Direction direction) {
BlockState next = world.getBlockState(pos.add(direction.toVector()));
if (CONNECTIBLE.contains(next.getId())) return "side";
//TODO: up
return "none";
}
@Override
public Collection<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.world.BlockState;
public class SnowyExtension implements BlockStateExtension {
private static final Collection<String> AFFECTED_BLOCK_IDS = Lists.newArrayList(
"minecraft:grass",
"minecraft:dirt"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
BlockState above = world.getBlockState(pos.add(0, 1, 0));
if (above.getId().equals("minecraft:snow_layer") || above.getId().equals("minecraft:snow")) {
return state.with("snowy", "true");
} else {
return state.with("snowy", "false");
}
}
@Override
public Collection<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,129 @@
/*
* 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.core.mca.extensions;
import java.util.Collection;
import java.util.HashSet;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Sets;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public class StairShapeExtension implements BlockStateExtension {
private static final HashSet<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
"minecraft:oak_stairs",
"minecraft:stone_stairs",
"minecraft:brick_stairs",
"minecraft:stone_brick_stairs",
"minecraft:nether_brick_stairs",
"minecraft:sandstone_stairs",
"minecraft:spruce_stairs",
"minecraft:birch_stairs",
"minecraft:jungle_stairs",
"minecraft:quartz_stairs",
"minecraft:acacia_stairs",
"minecraft:dark_oak_stairs",
"minecraft:red_sandstone_stairs",
"minecraft:purpur_stairs"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
try {
Direction facing = Direction.fromString(state.getProperties().get("facing"));
BlockState back = world.getBlockState(pos.add(facing.toVector()));
if (isStairs(back) && state.getProperties().get("half").equals(back.getProperties().get("half"))) {
Direction backFacing = Direction.fromString(back.getProperties().get("facing"));
if (facing.getAxis() != backFacing.getAxis()){
BlockState next = world.getBlockState(pos.add(backFacing.opposite().toVector()));
if (!isStairs(next) || !isEqualStairs(state, next)) {
if (backFacing == rotateYCCW(facing)){
return state.with("shape", "outer_left");
}
return state.with("shape", "outer_right");
}
}
}
BlockState front = world.getBlockState(pos.add(facing.opposite().toVector()));
if (isStairs(front) && state.getProperties().get("half").equals(front.getProperties().get("half"))) {
Direction frontFacing = Direction.fromString(front.getProperties().get("facing"));
if (facing.getAxis() != frontFacing.getAxis()){
BlockState next = world.getBlockState(pos.add(frontFacing.toVector()));
if (!isStairs(next) || !isEqualStairs(state, next)) {
if (frontFacing == rotateYCCW(facing)){
return state.with("shape", "inner_left");
}
return state.with("shape", "inner_right");
}
}
}
return state.with("shape", "straight");
} catch (IllegalArgumentException | NullPointerException ex) {
return state.with("shape", "straight");
}
}
private boolean isStairs(BlockState state) {
return AFFECTED_BLOCK_IDS.contains(state.getId());
}
private boolean isEqualStairs(BlockState stair1, BlockState stair2) {
return
stair1.getProperties().get("facing").equals(stair2.getProperties().get("facing")) &&
stair1.getProperties().get("half").equals(stair2.getProperties().get("half"));
}
private Direction rotateYCCW(Direction dir) {
switch (dir) {
case NORTH: return Direction.WEST;
case WEST: return Direction.SOUTH;
case SOUTH: return Direction.EAST;
case EAST: return Direction.NORTH;
default: return dir;
}
}
@Override
public Collection<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.mca.extensions;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.Sets;
public class TripwireConnectExtension extends ConnectExtension {
private static final HashSet<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
"minecraft:tripwire"
);
@Override
public Set<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.core.mca.extensions;
import java.util.HashSet;
import java.util.Set;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Sets;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.BlockState;
public class WallConnectExtension extends ConnectSameOrFullBlockExtension {
private static final HashSet<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
"minecraft:cobblestone_wall"
);
@Override
public BlockState extend(MCAWorld world, Vector3i pos, BlockState state) {
state = super.extend(world, pos, state);
if (
state.getProperties().get("north").equals(state.getProperties().get("south")) &&
state.getProperties().get("east").equals(state.getProperties().get("west")) &&
!state.getProperties().get("north").equals(state.getProperties().get("east")) &&
!connectsTo(world, pos.add(Direction.UP.toVector()))
) {
return state.with("up", "false");
} else {
return state.with("up", "true");
}
}
@Override
public Set<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.core.mca.extensions;
import java.util.HashSet;
import java.util.Set;
import com.google.common.collect.Sets;
public class WoodenFenceConnectExtension extends ConnectSameOrFullBlockExtension {
private static final HashSet<String> AFFECTED_BLOCK_IDS = Sets.newHashSet(
"minecraft:fence",
"minecraft:spruce_fence",
"minecraft:birch_fence",
"minecraft:jungle_fence",
"minecraft:dark_oak_fence",
"minecraft:acacia_fence"
);
@Override
public Set<String> getAffectedBlockIds() {
return AFFECTED_BLOCK_IDS;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.core.mca.mapping;
import java.io.IOException;
import java.util.Map.Entry;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BiomeIdMapper {
private static final String DEFAULT_BIOME = "ocean";
private String[] biomes;
public BiomeIdMapper() throws IOException {
biomes = new String[256];
for (int i = 0; i < biomes.length; i++) {
biomes[i] = DEFAULT_BIOME;
}
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
.setURL(getClass().getResource("/biomes.json"))
.build();
ConfigurationNode node = loader.load();
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
String biome = e.getKey().toString();
int id = e.getValue().getNode("id").getInt(-1);
if (id >= 0 && id < biomes.length) {
biomes[id] = biome;
}
}
}
public String get(int id) {
if (id < 0 || id >= biomes.length) return DEFAULT_BIOME;
return biomes[id];
}
public static BiomeIdMapper create() throws IOException {
return new BiomeIdMapper();
}
}

View File

@ -0,0 +1,119 @@
/*
* 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.core.mca.mapping;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.world.BlockState;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BlockIdMapper {
private Map<BlockIDMeta, BlockState> mappings;
public BlockIdMapper() throws IOException {
mappings = new HashMap<>();
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
.setURL(getClass().getResource("/blockIdMappings.json"))
.build();
ConfigurationNode node = loader.load();
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
String key = e.getKey().toString();
String value = e.getValue().getString();
int splitIndex = key.indexOf(':');
int blockId = Integer.parseInt(key.substring(0, splitIndex));
int blockMeta = Integer.parseInt(key.substring(splitIndex + 1));
BlockIDMeta idmeta = new BlockIDMeta(blockId, blockMeta);
BlockState state = BlockState.fromString(value);
mappings.put(idmeta, state);
}
}
public BlockState get(int id, int meta) {
if (id == 0) return BlockState.AIR;
BlockState state = mappings.get(new BlockIDMeta(id, meta));
if (state == null) {
state = mappings.get(new BlockIDMeta(id, 0)); //fallback
if (state == null) {
Logger.global.noFloodDebug(id + ":" + meta + "-blockidmapper-mappingerr", "Block ID can not be mapped: " + id + ":" + meta);
return BlockState.AIR;
}
}
return state;
}
class BlockIDMeta {
private final int id;
private final int meta;
public BlockIDMeta(int id, int meta) {
this.id = id;
this.meta = meta;
}
public int getId() {
return id;
}
public int getMeta() {
return meta;
}
@Override
public int hashCode() {
return id * 0xFFFF + meta;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BlockIDMeta) {
BlockIDMeta other = (BlockIDMeta) obj;
return other.id == id && other.meta == meta;
}
return false;
}
}
public static BlockIdMapper create() throws IOException {
return new BlockIdMapper();
}
}

View File

@ -0,0 +1,49 @@
/*
* 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.core.mca.mapping;
public class BlockProperties {
private final boolean culling, occluding, flammable;
public BlockProperties(boolean culling, boolean occluding, boolean flammable) {
this.culling = culling;
this.occluding = occluding;
this.flammable = flammable;
}
public boolean isCulling() {
return culling;
}
public boolean isOccluding() {
return occluding;
}
public boolean isFlammable() {
return flammable;
}
}

View File

@ -0,0 +1,103 @@
/*
* 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.core.mca.mapping;
import java.io.IOException;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import de.bluecolored.bluemap.core.world.BlockState;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BlockPropertyMapper {
private static final BlockProperties DEFAULT_PROPERTIES = new BlockProperties(false, false, false);
private Multimap<String, BlockStateMapping<BlockProperties>> mappings;
private LoadingCache<BlockState, BlockProperties> mappingCache;
private BlockPropertyMapper() throws IOException {
mappings = HashMultimap.create();
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
.setURL(getClass().getResource("/blockProperties.json"))
.build();
ConfigurationNode node = loader.load();
for (Entry<Object, ? extends ConfigurationNode> e : node.getChildrenMap().entrySet()){
String key = e.getKey().toString();
BlockState bsKey = BlockState.fromString(key);
BlockProperties bsValue = new BlockProperties(
e.getValue().getNode("culling").getBoolean(false),
e.getValue().getNode("occluding").getBoolean(false),
e.getValue().getNode("flammable").getBoolean(false)
);
BlockStateMapping<BlockProperties> mapping = new BlockStateMapping<>(bsKey, bsValue);
mappings.put(bsKey.getId(), mapping);
}
mappings = Multimaps.unmodifiableMultimap(mappings);
mappingCache = CacheBuilder.newBuilder()
.concurrencyLevel(8)
.maximumSize(10000)
.build(new CacheLoader<BlockState, BlockProperties>(){
@Override public BlockProperties load(BlockState key) { return mapNoCache(key); }
});
}
public BlockProperties map(BlockState from){
try {
return mappingCache.get(from);
} catch (ExecutionException e) {
//should never happen, since the CacheLoader does not throw any exceptions
throw new RuntimeException("Unexpected error while trying to map a BlockState's properties", e.getCause());
}
}
private BlockProperties mapNoCache(BlockState bs){
for (BlockStateMapping<BlockProperties> bm : mappings.get(bs.getId())){
if (bm.fitsTo(bs)){
return bm.getMapping();
}
}
return DEFAULT_PROPERTIES;
}
public static BlockPropertyMapper create() throws IOException {
return new BlockPropertyMapper();
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.core.mca.mapping;
import java.util.Map.Entry;
import de.bluecolored.bluemap.core.world.BlockState;
class BlockStateMapping<T> {
private BlockState blockState;
private T mapping;
public BlockStateMapping(BlockState blockState, T mapping) {
this.blockState = blockState;
this.mapping = mapping;
}
/**
* Returns true if the all the properties on this BlockMapping-key are the same in the provided BlockState.<br>
* Properties that are not defined in this Mapping are ignored on the provided BlockState.<br>
*/
public boolean fitsTo(BlockState blockState){
if (!this.blockState.getId().equals(blockState.getId())) return false;
for (Entry<String, String> e : this.blockState.getProperties().entrySet()){
if (!e.getValue().equals(blockState.getProperties().get(e.getKey()))){
return false;
}
}
return true;
}
public BlockState getBlockState(){
return blockState;
}
public T getMapping(){
return mapping;
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.core.mca.mapping;
public class LightData {
public static final LightData ZERO = new LightData(0, 0);
public static final LightData FULL = new LightData(15, 15);
private final int skyLight, blockLight;
public LightData(int skyLight, int blockLight) {
this.skyLight = skyLight;
this.blockLight = blockLight;
}
public int getSkyLight() {
return skyLight;
}
public int getBlockLight() {
return blockLight;
}
@Override
public String toString() {
return "LightData[B:" + getBlockLight() + "|S:" + getSkyLight() + "]";
}
}

View File

@ -0,0 +1,218 @@
/*
* 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.core.model;
import com.flowpowered.math.imaginary.Quaternionf;
import com.flowpowered.math.matrix.Matrix3f;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.util.MathUtil;
public class Face {
private Vector3f p1, p2, p3;
private Vector3f n1, n2, n3;
private Vector3f c1, c2, c3;
private Vector2f uv1, uv2, uv3;
private int materialIndex;
private boolean normalizedNormals;
public Face(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f uv1, Vector2f uv2, Vector2f uv3, int materialIndex) {
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
this.uv1 = uv1;
this.uv2 = uv2;
this.uv3 = uv3;
this.materialIndex = materialIndex;
Vector3f faceNormal = getFaceNormal();
this.n1 = faceNormal;
this.n2 = faceNormal;
this.n3 = faceNormal;
this.normalizedNormals = true;
Vector3f color = Vector3f.ONE;
this.c1 = color;
this.c2 = color;
this.c3 = color;
}
public void rotate(Quaternionf rotation){
p1 = rotation.rotate(p1);
p2 = rotation.rotate(p2);
p3 = rotation.rotate(p3);
n1 = rotation.rotate(n1);
n2 = rotation.rotate(n2);
n3 = rotation.rotate(n3);
}
public void transform(Matrix3f transformation){
p1 = transformation.transform(p1);
p2 = transformation.transform(p2);
p3 = transformation.transform(p3);
n1 = transformation.transform(n1);
n2 = transformation.transform(n2);
n3 = transformation.transform(n3);
normalizedNormals = false;
}
public void translate(Vector3f translation){
p1 = translation.add(p1);
p2 = translation.add(p2);
p3 = translation.add(p3);
}
public Vector3f getP1() {
return p1;
}
public void setP1(Vector3f p1) {
this.p1 = p1;
}
public Vector3f getP2() {
return p2;
}
public void setP2(Vector3f p2) {
this.p2 = p2;
}
public Vector3f getP3() {
return p3;
}
public void setP3(Vector3f p3) {
this.p3 = p3;
}
public Vector3f getN1() {
normlizeNormals();
return n1;
}
public void setN1(Vector3f n1) {
this.n1 = n1;
normalizedNormals = false;
}
public Vector3f getN2() {
normlizeNormals();
return n2;
}
public void setN2(Vector3f n2) {
this.n2 = n2;
normalizedNormals = false;
}
public Vector3f getN3() {
normlizeNormals();
return n3;
}
public void setN3(Vector3f n3) {
this.n3 = n3;
normalizedNormals = false;
}
public Vector3f getC1() {
return c1;
}
public void setC1(Vector3f c1) {
this.c1 = c1;
}
public Vector3f getC2() {
return c2;
}
public void setC2(Vector3f c2) {
this.c2 = c2;
}
public Vector3f getC3() {
return c3;
}
public void setC3(Vector3f c3) {
this.c3 = c3;
}
public Vector2f getUv1() {
return uv1;
}
public void setUv1(Vector2f uv1) {
this.uv1 = uv1;
}
public Vector2f getUv2() {
return uv2;
}
public void setUv2(Vector2f uv2) {
this.uv2 = uv2;
}
public Vector2f getUv3() {
return uv3;
}
public void setUv3(Vector2f uv3) {
this.uv3 = uv3;
}
public int getMaterialIndex() {
return materialIndex;
}
public void setMaterialIndex(int materialIndex) {
this.materialIndex = materialIndex;
}
public Vector3f getFaceNormal(){
return MathUtil.getSurfaceNormal(p1, p2, p3);
}
private void normlizeNormals(){
if (normalizedNormals) return;
n1 = n1.normalize();
n2 = n2.normalize();
n3 = n3.normalize();
normalizedNormals = true;
}
}

View File

@ -0,0 +1,149 @@
/*
* 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.core.model;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.flowpowered.math.imaginary.Quaternionf;
import com.flowpowered.math.matrix.Matrix3f;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
public class Model {
private List<Face> faces;
public Model() {
this.faces = new ArrayList<>();
}
/**
* Merges the given Model into this model<br>
* Faces are not cloned: So changes to the faces of the previous model will mirror in this model, but adding and removing faces will not.
*/
public void merge(Model model){
faces.addAll(model.getFaces());
}
public void addFace(Face face){
faces.add(face);
}
public void removeFace(Face face){
faces.remove(face);
}
public Collection<Face> getFaces(){
return faces;
}
public void rotate(Quaternionf rotation){
for (Face f : faces){
f.rotate(rotation);
}
}
public void transform(Matrix3f transformation){
for (Face f : faces){
f.transform(transformation);
}
}
public void translate(Vector3f translation){
for (Face f : faces){
f.translate(translation);
}
}
public BufferGeometry toBufferGeometry() {
//sort faces by material index
faces.sort((f1, f2) -> (int) Math.signum(f1.getMaterialIndex() - f2.getMaterialIndex()));
//reorganize all faces into arrays and create material-groups
int count = faces.size();
List<BufferGeometry.MaterialGroup> groups = new ArrayList<>();
int groupStart = 0;
int currentMaterialIndex = -1;
if (count > 0) currentMaterialIndex = faces.get(0).getMaterialIndex();
float[] position = new float[count * 3 * 3];
float[] normal = new float[count * 3 * 3];
float[] color = new float[count * 3 * 3];
float[] uv = new float[count * 2 * 3];
for (int itemIndex = 0; itemIndex < count; itemIndex++){
Face f = faces.get(itemIndex);
if (currentMaterialIndex != f.getMaterialIndex()){
groups.add(new BufferGeometry.MaterialGroup(currentMaterialIndex, groupStart * 3, (itemIndex - groupStart) * 3));
groupStart = itemIndex;
currentMaterialIndex = f.getMaterialIndex();
}
addVector3fToArray( position, f.getP1(), (itemIndex * 3 + 0) * 3 );
addVector3fToArray( normal, f.getN1(), (itemIndex * 3 + 0) * 3 );
addVector3fToArray( color, f.getC1(), (itemIndex * 3 + 0) * 3 );
addVector2fToArray( uv, f.getUv1(), (itemIndex * 3 + 0) * 2 );
addVector3fToArray( position, f.getP2(), (itemIndex * 3 + 1) * 3 );
addVector3fToArray( normal, f.getN2(), (itemIndex * 3 + 1) * 3 );
addVector3fToArray( color, f.getC2(), (itemIndex * 3 + 1) * 3 );
addVector2fToArray( uv, f.getUv2(), (itemIndex * 3 + 1) * 2 );
addVector3fToArray( position, f.getP3(), (itemIndex * 3 + 2) * 3 );
addVector3fToArray( normal, f.getN3(), (itemIndex * 3 + 2) * 3 );
addVector3fToArray( color, f.getC3(), (itemIndex * 3 + 2) * 3 );
addVector2fToArray( uv, f.getUv3(), (itemIndex * 3 + 2) * 2 );
}
groups.add(new BufferGeometry.MaterialGroup(currentMaterialIndex, groupStart * 3, (count - groupStart) * 3));
return new BufferGeometry(
position,
normal,
color,
uv,
groups.toArray(new BufferGeometry.MaterialGroup[groups.size()])
);
}
private void addVector3fToArray(float[] array, Vector3f v, int startIndex){
array[startIndex] = v.getX();
array[startIndex + 1] = v.getY();
array[startIndex + 2] = v.getZ();
}
private void addVector2fToArray(float[] array, Vector2f v, int startIndex){
array[startIndex] = v.getX();
array[startIndex + 1] = v.getY();
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.core.render;
public interface RenderSettings {
/**
* The strenght of ao-shading calculated for each vertex.<br>
* A value of 0 turns off ao.<br>
* The value represents the amount that each occluding face subtracts of the light-multiplier. (There are at most 3 occluding faces)
*/
default float getAmbientOcclusionStrenght() {
return 0.25f;
}
/**
* Whether faces that have a sky-light-value of 0 will be rendered or not.
*/
default boolean isExcludeFacesWithoutSunlight() {
return true;
}
/**
* A multiplier to how much faces are shaded due to their light value<br>
* This can be used to make sure blocks with a light value of 0 are not pitch black
*/
default float getLightShadeMultiplier() {
return 0.8f;
}
/**
* The maximum height of rendered blocks
*/
default int getMaxY() {
return Integer.MAX_VALUE;
}
/**
* The minimum height of rendered blocks
*/
default int getMinY() {
return 0;
}
/**
* The same as the maximum height, but blocks that are above this value are treated as AIR.<br>
* This leads to the top-faces being rendered instead of them being culled.
*/
default int getSliceY() {
return Integer.MAX_VALUE;
}
default RenderSettings copy() {
return new StaticRenderSettings(
getAmbientOcclusionStrenght(),
isExcludeFacesWithoutSunlight(),
getLightShadeMultiplier(),
getMaxY(),
getMinY(),
getSliceY()
);
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.core.render;
public class StaticRenderSettings implements RenderSettings {
private float ambientOcclusion;
private boolean excludeFacesWithoutSunlight;
private float lightShade;
private int maxY, minY, sliceY;
public StaticRenderSettings(
float ambientOcclusion,
boolean excludeFacesWithoutSunlight,
float ligheShade,
int maxY,
int minY,
int sliceY
) {
this.ambientOcclusion = ambientOcclusion;
this.excludeFacesWithoutSunlight = excludeFacesWithoutSunlight;
this.lightShade = ligheShade;
this.maxY = maxY;
this.minY = minY;
this.sliceY = sliceY;
}
@Override
public float getAmbientOcclusionStrenght() {
return ambientOcclusion;
}
@Override
public boolean isExcludeFacesWithoutSunlight() {
return excludeFacesWithoutSunlight;
}
@Override
public float getLightShadeMultiplier() {
return lightShade;
}
@Override
public int getMaxY() {
return maxY;
}
@Override
public int getMinY() {
return minY;
}
@Override
public int getSliceY() {
return sliceY;
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.core.render;
import java.io.IOException;
import de.bluecolored.bluemap.core.render.hires.HiresModel;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
public class TileRenderer {
private HiresModelManager hiresModelManager;
private LowresModelManager lowresModelManager;
private RenderSettings renderSettings;
public TileRenderer(HiresModelManager hiresModelManager, LowresModelManager lowresModelManager, RenderSettings renderSettings) {
this.hiresModelManager = hiresModelManager;
this.lowresModelManager = lowresModelManager;
this.renderSettings = renderSettings.copy();
}
/**
* Renders the provided WorldTile
* @throws ChunkNotGeneratedException if that WorldTile's WorldChunk is not fully generated
* @throws IOException if a lowres-model that needs to be updated could not be loaded
*/
public void render(WorldTile tile) throws IOException, ChunkNotGeneratedException {
HiresModel hiresModel = hiresModelManager.render(tile, renderSettings);
lowresModelManager.render(hiresModel);
}
/**
* Saves changes to disk
*/
public void save(){
lowresModelManager.save();
}
public HiresModelManager getHiresModelManager() {
return hiresModelManager;
}
public LowresModelManager getLowresModelManager() {
return lowresModelManager;
}
public RenderSettings getRenderSettings() {
return renderSettings;
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.core.render;
import java.util.Objects;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.world.World;
public class WorldTile {
private World world;
private Vector2i tile;
private int hash;
public WorldTile(World world, Vector2i tile) {
this.world = world;
this.tile = tile;
this.hash = Objects.hash(world.getUUID(), tile);
}
public World getWorld() {
return world;
}
public Vector2i getTile() {
return tile;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof WorldTile)) return false;
WorldTile that = (WorldTile) obj;
if (!this.world.getUUID().equals(that.world.getUUID())) return false;
return this.tile.equals(that.tile);
}
@Override
public int hashCode() {
return hash;
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.core.render.context;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.Block;
public interface BlockContext {
Vector3i getPosition();
/**
* This returns neighbour blocks.<br>
* The distance can not be larger than one block in each direction!<br>
*/
Block getRelativeBlock(Vector3i direction);
/**
* This returns neighbour blocks.<br>
* The distance can not be larger than one block in each direction!<br>
*/
default Block getRelativeBlock(int x, int y, int z){
return getRelativeBlock(new Vector3i(x, y, z));
}
/**
* This returns neighbour blocks.
*/
default Block getRelativeBlock(Direction direction){
return getRelativeBlock(direction.toVector());
}
}

View File

@ -0,0 +1,168 @@
/*
* 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.core.render.context;
import java.util.Collection;
import java.util.Collections;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.World;
import de.bluecolored.bluemap.core.world.WorldChunk;
public class EmptyBlockContext implements ExtendedBlockContext {
private static final EmptyBlockContext instance = new EmptyBlockContext();
public static final Block AIR_BLOCK = new AirBlock();
private EmptyBlockContext() {}
@Override
public Block getRelativeBlock(Vector3i direction) {
return AIR_BLOCK;
}
@Override
public Vector3i getPosition() {
return Vector3i.ZERO;
}
public static ExtendedBlockContext instance() {
return instance;
}
private static class AirBlock extends Block {
private BlockState state = BlockState.AIR;
@Override
public BlockState getBlock() {
return state;
}
@Override
public World getWorld() {
return new EmptyWorld();
}
@Override
public Vector3i getPosition() {
return Vector3i.ZERO;
}
@Override
public double getSunLightLevel() {
return 0d;
}
@Override
public double getBlockLightLevel() {
return 0d;
}
@Override
public boolean isCullingNeighborFaces() {
return false;
}
@Override
public String getBiome() {
return "ocean";
}
}
private static class EmptyWorld implements World {
private AABB bounds;
public EmptyWorld() {
this.bounds = new AABB(Vector3d.from(Double.POSITIVE_INFINITY), Vector3d.from(Double.NEGATIVE_INFINITY));
}
public EmptyWorld(AABB bounds){
this.bounds = bounds;
}
@Override
public World getWorld() {
return this;
}
@Override
public Block getBlock(Vector3i pos) {
return new AirBlock();
}
@Override
public AABB getBoundaries() {
return bounds;
}
@Override
public WorldChunk getWorldChunk(AABB boundaries) {
return new EmptyWorld(boundaries);
}
@Override
public boolean isGenerated() {
return false;
}
@Override
public String getName() {
return "-empty-";
}
@Override
public UUID getUUID() {
return new UUID(0, 0);
}
@Override
public int getSeaLevel() {
return 63;
}
@Override
public Vector3i getSpawnPoint() {
return new Vector3i(0, 63, 0);
}
@Override
public Collection<Vector2i> getChunkList(long modifiedSince) {
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.core.render.context;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Block;
/**
* A BlockContext that has a range of TWO blocks instead of one
*/
public interface ExtendedBlockContext extends BlockContext {
/**
* This returns neighbour blocks.<br>
* The distance can not be larger than two blocks in each direction!<br>
*/
Block getRelativeBlock(Vector3i direction);
/**
* This returns neighbour blocks.<br>
* The distance can not be larger than two blocks in each direction!<br>
*/
default Block getRelativeBlock(int x, int y, int z){
return getRelativeBlock(new Vector3i(x, y, z));
}
/**
* Returns a relative view of this ExtendedBlockContext!
* The distance can not be larger than two blocks in each direction!<br>
*/
default BlockContext getRelativeView(final Vector3i relative) {
final ExtendedBlockContext parent = this;
return new BlockContext() {
@Override
public Block getRelativeBlock(Vector3i direction) {
return parent.getRelativeBlock(direction.add(relative));
}
@Override
public Vector3i getPosition() {
return parent.getPosition().add(relative);
}
};
}
}

View File

@ -0,0 +1,52 @@
/*
* 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.core.render.context;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.WorldChunk;
public class SlicedWorldChunkBlockContext extends WorldChunkBlockContext {
private int sliceY;
/**
* Same as a {@link WorldChunkBlockContext} but if the requested Block is above the slice-value it will return air.
*/
public SlicedWorldChunkBlockContext(WorldChunk worldChunk, Vector3i blockPos, int sliceY) {
super(worldChunk, blockPos);
this.sliceY = sliceY;
}
@Override
protected Block getBlock(Vector3i position) {
if (position.getY() > sliceY) return EmptyBlockContext.AIR_BLOCK;
return super.getBlock(position);
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.core.render.context;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.WorldChunk;
public class WorldChunkBlockContext implements ExtendedBlockContext {
private Vector3i blockPos;
private WorldChunk chunk;
/**
* A BlockContext backed by a WorldChunk.
*
* This Context assumes that the world-chunk is generated around that block-position.
* If the given world chunk is not generated, using this context will result in a RuntimeException!
*/
public WorldChunkBlockContext(WorldChunk worldChunk, Vector3i blockPos) {
this.blockPos = blockPos;
this.chunk = worldChunk;
}
@Override
public Vector3i getPosition() {
return blockPos;
}
@Override
public Block getRelativeBlock(int x, int y, int z) {
Vector3i pos = blockPos.add(x, y, z);
return getBlock(pos);
}
@Override
public Block getRelativeBlock(Vector3i direction) {
Vector3i pos = blockPos.add(direction);
return getBlock(pos);
}
protected Block getBlock(Vector3i position) {
if (!chunk.containsBlock(position)) {
return EmptyBlockContext.AIR_BLOCK;
}
try {
return chunk.getBlock(position);
} catch (ChunkNotGeneratedException e) {
//we assume the chunk being generated
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.core.render.hires;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.model.Model;
/**
* A model, containing additional information about the tile it represents
*/
public class HiresModel extends Model {
private UUID world;
private Vector2i tile;
private Vector3i blockMin, blockMax, blockSize;
private int[][] heights;
private Vector4f[][] colors;
public HiresModel(UUID world, Vector2i tile, Vector3i blockMin, Vector3i blockMax) {
this.world = world;
this.tile = tile;
this.blockMin = blockMin;
this.blockMax = blockMax;
this.blockSize = blockMax.sub(blockMin).add(Vector3i.ONE);
heights = new int[blockSize.getX()][blockSize.getZ()];
colors = new Vector4f[blockSize.getX()][blockSize.getZ()];
}
public void setColor(int x, int z, Vector4f color){
colors[x - blockMin.getX()][z - blockMin.getZ()] = color;
}
public Vector4f getColor(int x, int z){
return colors[x - blockMin.getX()][z - blockMin.getZ()];
}
public void setHeight(int x, int z, int height){
heights[x - blockMin.getX()][z - blockMin.getZ()] = height;
}
public int getHeight(int x, int z){
return heights[x - blockMin.getX()][z - blockMin.getZ()];
}
public UUID getWorld(){
return world;
}
public Vector3i getBlockMin(){
return blockMin;
}
public Vector3i getBlockMax(){
return blockMax;
}
public Vector3i getBlockSize(){
return blockSize;
}
public Vector2i getTile(){
return tile;
}
}

View File

@ -0,0 +1,168 @@
/*
* 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.core.render.hires;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.concurrent.ExecutorService;
import java.util.zip.GZIPOutputStream;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.FileUtil;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
public class HiresModelManager {
private Path fileRoot;
private HiresModelRenderer renderer;
private Vector2i tileSize;
private Vector2i gridOrigin;
private ExecutorService savingExecutor;
public HiresModelManager(Path fileRoot, ResourcePack resourcePack, Vector2i tileSize, ExecutorService savingExecutor) {
this(fileRoot, new HiresModelRenderer(resourcePack), tileSize, new Vector2i(2, 2), savingExecutor);
}
public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i tileSize, Vector2i gridOrigin, ExecutorService savingExecutor) {
this.fileRoot = fileRoot;
this.renderer = renderer;
this.tileSize = tileSize;
this.gridOrigin = gridOrigin;
this.savingExecutor = savingExecutor;
}
/**
* Renders the given world tile with the provided render-settings
* @throws ChunkNotGeneratedException if a minecraft-chunk needed for thies tile is not yet generated
*/
public HiresModel render(WorldTile tile, RenderSettings renderSettings) throws ChunkNotGeneratedException {
HiresModel model = renderer.render(tile, getTileRegion(tile), renderSettings);
save(model);
return model;
}
private void save(final HiresModel model) {
final String modelJson = model.toBufferGeometry().toJson();
savingExecutor.submit(() -> save(model, modelJson));
}
private void save(HiresModel model, String modelJson){
File file = getFile(model.getTile());
try {
if (!file.exists()){
file.getParentFile().mkdirs();
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
GZIPOutputStream zos = new GZIPOutputStream(fos);
OutputStreamWriter osw = new OutputStreamWriter(zos, StandardCharsets.UTF_8);
try (
PrintWriter pw = new PrintWriter(osw);
){
pw.print(modelJson);
}
//logger.logDebug("Saved hires model: " + model.getTile());
} catch (IOException e){
Logger.global.logError("Failed to save hires model: " + file, e);
}
}
/**
* Returns the region of blocks that a tile includes
*/
public AABB getTileRegion(WorldTile tile) {
Vector3i min = new Vector3i(
tile.getTile().getX() * tileSize.getX() + gridOrigin.getX(),
tile.getWorld().getBoundaries().getMin().getFloorY(),
tile.getTile().getY() * tileSize.getY() + gridOrigin.getY()
);
Vector3i max = min.add(
tileSize.getX() - 1,
tile.getWorld().getBoundaries().getMax().getFloorY(),
tileSize.getY() - 1
);
return new AABB(min, max);
}
/**
* Returns the tile-size
*/
public Vector2i getTileSize() {
return tileSize;
}
/**
* Returns the grid-origin
*/
public Vector2i getGridOrigin() {
return gridOrigin;
}
/**
* Converts a block-position to a map-tile-coordinate
*/
public Vector2i posToTile(Vector3i pos){
return posToTile(pos.toDouble());
}
/**
* Converts a block-position to a map-tile-coordinate
*/
public Vector2i posToTile(Vector3d pos){
pos = pos.sub(new Vector3d(gridOrigin.getX(), 0.0, gridOrigin.getY()));
return Vector2i.from(
(int) Math.floor(pos.getX() / (double) getTileSize().getX()),
(int) Math.floor(pos.getZ() / (double) getTileSize().getY())
);
}
/**
* Returns the file for a tile
*/
public File getFile(Vector2i tilePos){
return FileUtil.coordsToFile(fileRoot, tilePos, "json.gz");
}
}

View File

@ -0,0 +1,115 @@
/*
* 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.core.render.hires;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.render.context.SlicedWorldChunkBlockContext;
import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModel;
import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModelFactory;
import de.bluecolored.bluemap.core.resourcepack.InvalidResourceDeclarationException;
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.MathUtil;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.ChunkNotGeneratedException;
import de.bluecolored.bluemap.core.world.WorldChunk;
public class HiresModelRenderer {
private BlockStateModelFactory modelFactory;
public HiresModelRenderer(ResourcePack resourcePack) {
this(new BlockStateModelFactory(resourcePack));
}
public HiresModelRenderer(BlockStateModelFactory modelFactory) {
this.modelFactory = modelFactory;
}
public HiresModel render(WorldTile tile, AABB region, RenderSettings renderSettings) throws ChunkNotGeneratedException {
Vector3i min = region.getMin().toInt();
Vector3i max = region.getMax().toInt();
min = new Vector3i(min.getX(), Math.max(min.getY(), renderSettings.getMinY()), min.getZ());
max = new Vector3i(max.getX(), Math.min(max.getY(), Math.min(renderSettings.getMaxY(), renderSettings.getSliceY())), max.getZ());
WorldChunk chunk = tile.getWorld().getWorldChunk(region.expand(4, 0, 4));
if (!chunk.isGenerated()) throw new ChunkNotGeneratedException();
HiresModel model = new HiresModel(tile.getWorld().getUUID(), tile.getTile(), min, max);
for (int x = min.getX(); x <= max.getX(); x++){
for (int z = min.getZ(); z <= max.getZ(); z++){
int maxHeight = 0;
Vector4f color = Vector4f.ZERO;
for (int y = min.getY(); y <= max.getY(); y++){
Block block = chunk.getBlock(x, y, z);
if (block.getBlock().getId().equals("air")) continue;
maxHeight = y;
BlockStateModel blockModel;
try {
blockModel = modelFactory.createFrom(block.getBlock(), new SlicedWorldChunkBlockContext(chunk, new Vector3i(x, y, z), renderSettings.getSliceY()), renderSettings);
} catch (NoSuchResourceException | InvalidResourceDeclarationException | NoSuchTextureException e) {
blockModel = new BlockStateModel();
Logger.global.noFloodDebug(block.getBlock().getId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlock() + " (" + e.toString() + ")");
}
blockModel.translate(new Vector3f(x, y, z).sub(min.toFloat()));
color = MathUtil.overlayColors(blockModel.getMapColor(), color);
//TODO: quick hack to random offset grass
if (block.getBlock().getId().equals("grass")){
float dx = (MathUtil.hashToFloat(x, y, z, 123984) - 0.5f) * 0.75f;
float dz = (MathUtil.hashToFloat(x, y, z, 345542) - 0.5f) * 0.75f;
blockModel.translate(new Vector3f(dx, 0, dz));
}
model.merge(blockModel);
}
model.setHeight(x, z, maxHeight);
model.setColor(x, z, color);
}
}
return model;
}
}

View File

@ -0,0 +1,68 @@
/*
* 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.core.render.hires.blockmodel;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.model.Model;
import de.bluecolored.bluemap.core.util.MathUtil;
/**
* A model with some extra information about the BlockState it represents
*/
public class BlockStateModel extends Model {
private Vector4f mapColor;
public BlockStateModel(){
this(Vector4f.ZERO);
}
public BlockStateModel(Vector4f mapColor) {
this.mapColor = mapColor;
}
@Override
public void merge(Model model) {
super.merge(model);
if (model instanceof BlockStateModel){
mergeMapColor(((BlockStateModel) model).getMapColor());
}
}
public Vector4f getMapColor() {
return mapColor;
}
public void setMapColor(Vector4f mapColor) {
this.mapColor = mapColor;
}
public void mergeMapColor(Vector4f mapColor) {
this.mapColor = MathUtil.blendColors(this.mapColor, mapColor);
}
}

View File

@ -0,0 +1,98 @@
/*
* 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.core.render.hires.blockmodel;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.context.EmptyBlockContext;
import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext;
import de.bluecolored.bluemap.core.resourcepack.BlockStateResource;
import de.bluecolored.bluemap.core.resourcepack.InvalidResourceDeclarationException;
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.world.BlockState;
public class BlockStateModelFactory {
private ResourcePack resourcePack;
public BlockStateModelFactory(ResourcePack resources) {
this.resourcePack = resources;
}
public BlockStateModel createFrom(BlockState blockState) throws NoSuchResourceException, InvalidResourceDeclarationException, NoSuchTextureException {
return createFrom(blockState, EmptyBlockContext.instance(), new RenderSettings() {
@Override
public float getLightShadeMultiplier() {
return 0;
}
@Override
public boolean isExcludeFacesWithoutSunlight() {
return false;
}
@Override
public float getAmbientOcclusionStrenght() {
return 0;
}
});
}
public BlockStateModel createFrom(BlockState blockState, ExtendedBlockContext context, RenderSettings renderSettings) throws NoSuchResourceException, InvalidResourceDeclarationException, NoSuchTextureException {
//air won't be rendered
if (
blockState.getId().equals("air") ||
blockState.getId().equals("cave_air") ||
blockState.getId().equals("void_air")
) {
return new BlockStateModel();
}
// if it is a liquid, use the LiquidModelBuilder
if (
blockState.getId().equals("water") ||
blockState.getId().equals("lava")
){
return new LiquidModelBuilder(blockState, context, resourcePack, renderSettings).build();
}
// if no other model builder matched try to find a model definition from the resource-packs and use the default ResourceModelBuilder
BlockStateResource resource = resourcePack.getBlockStateResource(blockState);
BlockStateModel model = new ResourceModelBuilder(resource, context, resourcePack, renderSettings).build();
// if block is waterlogged
if (LiquidModelBuilder.isWaterlogged(blockState)) {
BlockStateModel watermodel = new LiquidModelBuilder(WATERLOGGED_BLOCKSTATE, context, resourcePack, renderSettings).build();
model.merge(watermodel);
}
return model;
}
private BlockState WATERLOGGED_BLOCKSTATE = new BlockState("minecraft:water");
}

View File

@ -0,0 +1,245 @@
/*
* 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.core.render.hires.blockmodel;
import java.util.HashSet;
import com.flowpowered.math.matrix.Matrix3f;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector4f;
import com.google.common.collect.Sets;
import de.bluecolored.bluemap.core.model.Face;
import de.bluecolored.bluemap.core.model.Model;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext;
import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockState;
/**
* A model builder for all liquid blocks
*/
public class LiquidModelBuilder {
private static final HashSet<String> DEFAULT_WATERLOGGED_BLOCK_IDS = Sets.newHashSet(
"minecraft:seagrass",
"minecraft:tall_seagrass",
"minecraft:kelp"
);
private BlockState blockState;
private ExtendedBlockContext context;
private ResourcePack resourcePack;
private RenderSettings renderSettings;
private float[] heights;
public LiquidModelBuilder(BlockState blockState, ExtendedBlockContext context, ResourcePack resourcePack, RenderSettings renderSettings) {
this.blockState = blockState;
this.context = context;
this.resourcePack = resourcePack;
this.renderSettings = renderSettings;
this.heights = new float[]{14f, 14f, 14f, 14f};
}
public BlockStateModel build() throws NoSuchTextureException {
if (this.renderSettings.isExcludeFacesWithoutSunlight() && context.getRelativeBlock(0, 0, 0).getSunLightLevel() == 0) return new BlockStateModel();
int level = getLiquidLevel(blockState);
if (level >= 8 ||level == 0 && isLiquid(context.getRelativeBlock(0, 1, 0))){
this.heights = new float[]{16f, 16f, 16f, 16f};
return buildModel();
}
this.heights = new float[]{
getLiquidCornerHeight(-1, 0, -1),
getLiquidCornerHeight(-1, 0, 0),
getLiquidCornerHeight(0, 0, -1),
getLiquidCornerHeight(0, 0, 0)
};
return buildModel();
}
private float getLiquidCornerHeight(int x, int y, int z){
for (int ix = x; ix <= x+1; ix++){
for (int iz = z; iz<= z+1; iz++){
if (isLiquid(context.getRelativeBlock(ix, y+1, iz))){
return 16f;
}
}
}
float sumHeight = 0f;
int count = 0;
for (int ix = x; ix <= x+1; ix++){
for (int iz = z; iz<= z+1; iz++){
Block b = context.getRelativeBlock(ix, y, iz);
if (isLiquid(b)){
if (getLiquidLevel(b.getBlock()) == 0) return 14f;
sumHeight += getLiquidBaseHeight(b.getBlock());
count++;
}
else if (!isLiquidBlockingBlock(b)){
count++;
}
}
}
//should both never happen
if (sumHeight == 0) return 3f;
if (count == 0) return 3f;
return sumHeight / count;
}
private boolean isLiquidBlockingBlock(Block b){
if (b.getBlock().getId().equals("air")) return false;
return true;
}
private boolean isLiquid(Block block){
return isLiquid(block.getBlock());
}
private boolean isLiquid(BlockState blockstate){
if (blockstate.getId().equals(blockState.getId())) return true;
return LiquidModelBuilder.isWaterlogged(blockstate);
}
private float getLiquidBaseHeight(BlockState block){
int level = getLiquidLevel(block);
float baseHeight = 14f - level * 1.9f;
return baseHeight;
}
private int getLiquidLevel(BlockState block){
if (block.getProperties().containsKey("level")) {
return Integer.parseInt(block.getProperties().get("level"));
}
return 0;
}
private BlockStateModel buildModel() throws NoSuchTextureException {
BlockStateModel model = new BlockStateModel();
Vector3f[] c = new Vector3f[]{
new Vector3f( 0, 0, 0 ),
new Vector3f( 0, 0, 16 ),
new Vector3f( 16, 0, 0 ),
new Vector3f( 16, 0, 16 ),
new Vector3f( 0, heights[0], 0 ),
new Vector3f( 0, heights[1], 16 ),
new Vector3f( 16, heights[2], 0 ),
new Vector3f( 16, heights[3], 16 ),
};
int textureId = resourcePack.getTextureProvider().getTextureIndex("block/" + blockState.getId() + "_still");
Vector3f tintcolor = Vector3f.ONE;
if (blockState.getId().equals("water")) {
tintcolor = resourcePack.getBlockColorProvider().getBiomeWaterAverageColor(context);
}
createElementFace(model, Direction.DOWN, c[0], c[2], c[3], c[1], tintcolor, textureId);
createElementFace(model, Direction.UP, c[5], c[7], c[6], c[4], tintcolor, textureId);
createElementFace(model, Direction.NORTH, c[2], c[0], c[4], c[6], tintcolor, textureId);
createElementFace(model, Direction.SOUTH, c[1], c[3], c[7], c[5], tintcolor, textureId);
createElementFace(model, Direction.WEST, c[0], c[1], c[5], c[4], tintcolor, textureId);
createElementFace(model, Direction.EAST, c[3], c[2], c[6], c[7], tintcolor, textureId);
//scale down
model.transform(Matrix3f.createScaling(1f / 16f));
//calculate mapcolor
Vector4f mapcolor = resourcePack.getTextureProvider().getTexture("block/" + blockState.getId() + "_still").getColor();
mapcolor = mapcolor.mul(tintcolor.toVector4(1));
model.setMapColor(mapcolor);
return model;
}
private void createElementFace(Model model, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3, Vector3f color, int textureId) throws NoSuchTextureException {
//face culling
Block bl = context.getRelativeBlock(faceDir);
if (isLiquid(bl) || (faceDir != Direction.UP && bl.isCullingNeighborFaces())) return;
//UV
Vector4f uv = new Vector4f(0, 0, 16, 16).div(16);
//create both triangles
Vector2f[] uvs = new Vector2f[4];
uvs[0] = new Vector2f(uv.getX(), uv.getW());
uvs[1] = new Vector2f(uv.getZ(), uv.getW());
uvs[2] = new Vector2f(uv.getZ(), uv.getY());
uvs[3] = new Vector2f(uv.getX(), uv.getY());
Face f1 = new Face(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId);
Face f2 = new Face(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId);
//move face in a tiny bit to avoid z-fighting with waterlogged blocks
f1.translate(faceDir.opposite().toVector().toFloat().mul(0.01));
f2.translate(faceDir.opposite().toVector().toFloat().mul(0.01));
float light = 1f;
if (renderSettings.getLightShadeMultiplier() > 0) {
light = 0f;
for (Direction d : Direction.values()){
Block b = context.getRelativeBlock(d.toVector());
float l = (float) (Math.max(b.getBlockLightLevel(), b.getSunLightLevel()) / 15f) * renderSettings.getLightShadeMultiplier() + (1 - renderSettings.getLightShadeMultiplier());
if (l > light) light = l;
}
}
color = color.mul(light);
f1.setC1(color);
f1.setC2(color);
f1.setC3(color);
f2.setC1(color);
f2.setC2(color);
f2.setC3(color);
//add the face
model.addFace(f1);
model.addFace(f2);
}
public static boolean isWaterlogged(BlockState blockState) {
if (DEFAULT_WATERLOGGED_BLOCK_IDS.contains(blockState.getFullId())) return true;
return blockState.getProperties().getOrDefault("waterlogged", "false").equals("true");
}
}

View File

@ -0,0 +1,386 @@
/*
* 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.core.render.hires.blockmodel;
import com.flowpowered.math.TrigMath;
import com.flowpowered.math.imaginary.Complexf;
import com.flowpowered.math.imaginary.Quaternionf;
import com.flowpowered.math.matrix.Matrix3f;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.model.Face;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.context.BlockContext;
import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext;
import de.bluecolored.bluemap.core.resourcepack.BlockModelElementFaceResource;
import de.bluecolored.bluemap.core.resourcepack.BlockModelElementResource;
import de.bluecolored.bluemap.core.resourcepack.BlockModelResource;
import de.bluecolored.bluemap.core.resourcepack.BlockStateResource;
import de.bluecolored.bluemap.core.resourcepack.NoSuchTextureException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.resourcepack.TextureProvider.Texture;
import de.bluecolored.bluemap.core.util.Direction;
import de.bluecolored.bluemap.core.util.MathUtil;
import de.bluecolored.bluemap.core.util.WeighedArrayList;
import de.bluecolored.bluemap.core.world.Block;
/**
* This model builder creates a BlockStateModel using the information from parsed resource-pack json files.
*/
public class ResourceModelBuilder {
private static final Vector3f HALF_3F = Vector3f.ONE.mul(0.5);
private static final Vector3f NEG_HALF_3F = HALF_3F.negate();
private static final Vector2f HALF_2F = Vector2f.ONE.mul(0.5);
private BlockStateResource resource;
private ExtendedBlockContext context;
private ResourcePack resourcePack;
private RenderSettings renderSettings;
public ResourceModelBuilder(BlockStateResource resource, ExtendedBlockContext context, ResourcePack resourcePack, RenderSettings renderSettings) {
this.resource = resource;
this.context = context;
this.resourcePack = resourcePack;
this.renderSettings = renderSettings;
}
public BlockStateModel build() throws NoSuchTextureException {
BlockStateModel model = new BlockStateModel();
for (WeighedArrayList<BlockModelResource> bmrList : resource.getModelResources()){
BlockModelResource bmr = bmrList.get((int) Math.floor(MathUtil.hashToFloat(context.getPosition(), 23489756) * (float) bmrList.size()));
model.merge(fromModelResource(bmr));
}
return model;
}
private BlockStateModel fromModelResource(BlockModelResource bmr) throws NoSuchTextureException {
BlockStateModel model = new BlockStateModel();
for (BlockModelElementResource bmer : bmr.getElements()){
model.merge(fromModelElementResource(bmer));
}
model.translate(NEG_HALF_3F);
model.rotate(Quaternionf.fromAxesAnglesDeg(
-bmr.getXRot(),
-bmr.getYRot(),
0
));
model.translate(HALF_3F);
return model;
}
private BlockStateModel fromModelElementResource(BlockModelElementResource bmer) throws NoSuchTextureException {
BlockStateModel model = new BlockStateModel();
//create faces
Vector3f min = bmer.getFrom().min(bmer.getTo());
Vector3f max = bmer.getFrom().max(bmer.getTo());
Vector3f[] c = new Vector3f[]{
new Vector3f( min .getX(), min .getY(), min .getZ()),
new Vector3f( min .getX(), min .getY(), max .getZ()),
new Vector3f( max .getX(), min .getY(), min .getZ()),
new Vector3f( max .getX(), min .getY(), max .getZ()),
new Vector3f( min .getX(), max .getY(), min .getZ()),
new Vector3f( min .getX(), max .getY(), max .getZ()),
new Vector3f( max .getX(), max .getY(), min .getZ()),
new Vector3f( max .getX(), max .getY(), max .getZ()),
};
createElementFace(model, bmer.getDownFace(), Direction.DOWN, c[0], c[2], c[3], c[1]);
createElementFace(model, bmer.getUpFace(), Direction.UP, c[5], c[7], c[6], c[4]);
createElementFace(model, bmer.getNorthFace(), Direction.NORTH, c[2], c[0], c[4], c[6]);
createElementFace(model, bmer.getSouthFace(), Direction.SOUTH, c[1], c[3], c[7], c[5]);
createElementFace(model, bmer.getWestFace(), Direction.WEST, c[0], c[1], c[5], c[4]);
createElementFace(model, bmer.getEastFace(), Direction.EAST, c[3], c[2], c[6], c[7]);
//rotate
if (bmer.isRotation()){
Vector3f translation = bmer.getRotationOrigin();
model.translate(translation.negate());
model.rotate(Quaternionf.fromAngleDegAxis(
bmer.getRotationAngle(),
bmer.getRotationAxis().toVector().toFloat()
));
if (bmer.isRotationRescale()){
Vector3f scale =
Vector3f.ONE
.sub(bmer.getRotationAxis().toVector().toFloat())
.mul(Math.abs(TrigMath.sin(bmer.getRotationAngle() * TrigMath.DEG_TO_RAD)))
.mul(1 - (TrigMath.SQRT_OF_TWO - 1))
.add(Vector3f.ONE);
model.transform(Matrix3f.createScaling(scale));
}
model.translate(translation);
}
//scale down
model.transform(Matrix3f.createScaling(1f / 16f));
return model;
}
private void createElementFace(BlockStateModel model, BlockModelElementFaceResource face, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3) throws NoSuchTextureException {
if (face == null) return;
BlockModelResource m = face.getElement().getModel();
//face culling
if (face.isCullface()){
Block b = getRotationRelativeBlock(m, face.getCullface());
if (b.isCullingNeighborFaces()) return;
}
//light calculation
Block b = getRotationRelativeBlock(m, faceDir);
BlockContext bContext = context.getRelativeView(getRotationRelativeDirectionVector(m, faceDir.toVector().toFloat()).toInt());
float skyLight = b.getPassedSunLight(bContext);
//filter out faces that are not skylighted
if (skyLight == 0f && renderSettings.isExcludeFacesWithoutSunlight()) return;
float light = 1;
if (renderSettings.getLightShadeMultiplier() > 0) {
float blockLight = b.getPassedBlockLight(bContext);
light = (Math.max(skyLight, blockLight) / 15f) * renderSettings.getLightShadeMultiplier() + (1 - renderSettings.getLightShadeMultiplier());
if (light > 1) light = 1;
if (light < 0) light = 0;
}
//UV
Vector4f uv = face.getUv().toFloat().div(16);
//UV-Lock counter-rotation
int uvLockAngle = 0;
if (m.isUvLock()){
Quaternionf rot = Quaternionf.fromAxesAnglesDeg(m.getXRot(), m.getYRot(), 0);
uvLockAngle = (int) rot.getAxesAnglesDeg().dot(faceDir.toVector().toFloat());
//TODO: my math has stopped working, there has to be a more consistent solution
if (m.getXRot() >= 180 && m.getYRot() != 90 && m.getYRot() != 270) uvLockAngle += 180;
}
//create both triangles
Vector2f[] uvs = new Vector2f[4];
uvs[0] = new Vector2f(uv.getX(), uv.getW());
uvs[1] = new Vector2f(uv.getZ(), uv.getW());
uvs[2] = new Vector2f(uv.getZ(), uv.getY());
uvs[3] = new Vector2f(uv.getX(), uv.getY());
//face texture rotation
uvs = rotateUVOuter(uvs, uvLockAngle);
uvs = rotateUVInner(uvs, face.getRotation());
String textureName = face.getResolvedTexture();
if (textureName == null) throw new NoSuchTextureException("There is no Texture-Definition for a face: " + faceDir + " of block: " + resource.getBlock());
int textureId = resourcePack.getTextureProvider().getTextureIndex(textureName);
Face f1 = new Face(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId);
Face f2 = new Face(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId);
//calculate ao
double ao0 = 1d, ao1 = 1d, ao2 = 1d, ao3 = 1d;
if (renderSettings.getAmbientOcclusionStrenght() > 0f && m.isAmbientOcclusion()){
ao0 = testAo(m, c0, faceDir);
ao1 = testAo(m, c1, faceDir);
ao2 = testAo(m, c2, faceDir);
ao3 = testAo(m, c3, faceDir);
}
//tint the face
Vector3f color = Vector3f.ONE;
if (face.isTinted()){
color = resourcePack.getBlockColorProvider().getBlockColor(context);
}
color = color.mul(light);
Vector3f aoColor;
aoColor = color.mul(ao0);
f1.setC1(aoColor);
f2.setC1(aoColor);
aoColor = color.mul(ao1);
f1.setC2(aoColor);
aoColor = color.mul(ao2);
f1.setC3(aoColor);
f2.setC2(aoColor);
aoColor = color.mul(ao3);
f2.setC3(aoColor);
//add the face
model.addFace(f1);
model.addFace(f2);
//if is top face set model-color
Vector3f dir = getRotationRelativeDirectionVector(m, faceDir.toVector().toFloat());
BlockModelElementResource bmer = face.getElement();
if (bmer.isRotation()){
Quaternionf rot = Quaternionf.fromAngleDegAxis(
bmer.getRotationAngle(),
bmer.getRotationAxis().toVector().toFloat()
);
dir = rot.rotate(dir);
}
float a = dir.getY();
if (a > 0){
Texture t = resourcePack.getTextureProvider().getTexture(textureId);
if (t != null){
Vector4f c = t.getColor();
c = c.mul(color.toVector4(1f));
c = new Vector4f(c.getX(), c.getY(), c.getZ(), c.getW() * a);
model.mergeMapColor(c);
}
}
}
private Block getRotationRelativeBlock(BlockModelResource model, Direction direction){
return getRotationRelativeBlock(model, direction.toVector());
}
private Block getRotationRelativeBlock(BlockModelResource model, Vector3i direction){
Vector3i dir = getRotationRelativeDirectionVector(model, direction.toFloat()).round().toInt();
return context.getRelativeBlock(dir);
}
private Vector3f getRotationRelativeDirectionVector(BlockModelResource model, Vector3f direction){
Quaternionf rot = Quaternionf.fromAxesAnglesDeg(
-model.getXRot(),
-model.getYRot(),
0
);
Vector3f dir = rot.rotate(direction);
return dir;
}
private double testAo(BlockModelResource model, Vector3f vertex, Direction dir){
int occluding = 0;
int x = 0;
if (vertex.getX() == 16){
x = 1;
} else if (vertex.getX() == 0){
x = -1;
}
int y = 0;
if (vertex.getY() == 16){
y = 1;
} else if (vertex.getY() == 0){
y = -1;
}
int z = 0;
if (vertex.getZ() == 16){
z = 1;
} else if (vertex.getZ() == 0){
z = -1;
}
Vector3i rel = new Vector3i(x, y, 0);
if (rel.dot(dir.toVector()) > 0){
if (getRotationRelativeBlock(model, rel).isOccludingNeighborFaces()) occluding++;
}
rel = new Vector3i(x, 0, z);
if (rel.dot(dir.toVector()) > 0){
if (getRotationRelativeBlock(model, rel).isOccludingNeighborFaces()) occluding++;
}
rel = new Vector3i(0, y, z);
if (rel.dot(dir.toVector()) > 0){
if (getRotationRelativeBlock(model, rel).isOccludingNeighborFaces()) occluding++;
}
rel = new Vector3i(x, y, z);
if (rel.dot(dir.toVector()) > 0){
if (getRotationRelativeBlock(model, rel).isOccludingNeighborFaces()) occluding++;
}
if (occluding > 3)
occluding = 3;
return Math.max(0.0, Math.min(1.0 - ((double) occluding * renderSettings.getAmbientOcclusionStrenght()), 1.0));
}
private Vector2f[] rotateUVInner(Vector2f[] uv, int angle){
if (uv.length == 0) return uv;
int steps = getRotationSteps(angle);
for (int i = 0; i < steps; i++){
Vector2f first = uv[uv.length - 1];
System.arraycopy(uv, 0, uv, 1, uv.length - 1);
uv[0] = first;
}
return uv;
}
private Vector2f[] rotateUVOuter(Vector2f[] uv, float angle){
angle %= 360;
if (angle < 0) angle += 360;
if (angle == 0) return uv;
Complexf c = Complexf.fromAngleDeg(angle);
for (int i = 0; i < uv.length; i++){
uv[i] = uv[i].sub(HALF_2F);
uv[i] = c.rotate(uv[i]);
uv[i] = uv[i].add(HALF_2F);
}
return uv;
}
private int getRotationSteps(int angle){
angle = -Math.floorDiv(angle, 90);
angle %= 4;
if (angle < 0) angle += 4;
return angle;
}
}

View File

@ -0,0 +1,229 @@
/*
* 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.core.render.lowres;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
import de.bluecolored.bluemap.core.util.FileUtil;
import de.bluecolored.bluemap.core.util.MathUtil;
import de.bluecolored.bluemap.core.util.ModelUtils;
public class LowresModel {
private UUID world;
private Vector2i tilePos;
private BufferGeometry model;
private Map<Vector2i, LowresPoint> changes;
private boolean hasUnsavedChanges;
private final Object
fileLock = new Object(),
modelLock = new Object();
public LowresModel(UUID world, Vector2i tilePos, Vector2i gridSize) {
this(
world,
tilePos,
ModelUtils.makeGrid(gridSize).toBufferGeometry()
);
}
public LowresModel(UUID world, Vector2i tilePos, BufferGeometry model) {
this.world = world;
this.tilePos = tilePos;
this.model = model;
this.changes = new ConcurrentHashMap<>();
this.hasUnsavedChanges = true;
}
/**
* Searches for all vertices at that point on the grid-model and change the height and color.<br>
* <br>
* <i>
* Implementation note:<br>
* The vertex x, z -coords are rounded, so we can compare them using == without worrying about floating point rounding differences.<br>
* </i>
*/
public void update(Vector2i point, float height, Vector3f color){
changes.put(point, new LowresPoint(height, color));
this.hasUnsavedChanges = true;
}
/**
* 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 {
if (!force && !hasUnsavedChanges) return;
this.hasUnsavedChanges = false;
flush();
String json;
synchronized (modelLock) {
json = model.toJson();
}
synchronized (fileLock) {
if (!file.exists()){
file.getParentFile().mkdirs();
file.createNewFile();
}
try {
FileUtil.waitForFile(file, 10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
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);
try (
PrintWriter pw = new PrintWriter(osw);
){
pw.print(json);
}
}
}
public void flush(){
if (changes.isEmpty()) return;
synchronized (modelLock) {
if (changes.isEmpty()) return;
Map<Vector2i, LowresPoint> points = changes;
changes = new HashMap<>();
int vertexCount = model.position.length / 3;
for (int i = 0; i < vertexCount; i++){
int j = i * 3;
int px = Math.round(model.position[j + 0]);
int pz = Math.round(model.position[j + 2]);
Vector2i p = new Vector2i(px, pz);
LowresPoint lrp = points.get(p);
if (lrp == null) continue;
model.position[j + 1] = lrp.height;
model.color[j + 0] = lrp.color.getX();
model.color[j + 1] = lrp.color.getY();
model.color[j + 2] = lrp.color.getZ();
//recalculate normals
int f = Math.floorDiv(i, 3) * 3 * 3;
Vector3f p1 = new Vector3f(model.position[f + 0], model.position[f + 1], model.position[f + 2]);
Vector3f p2 = new Vector3f(model.position[f + 3], model.position[f + 4], model.position[f + 5]);
Vector3f p3 = new Vector3f(model.position[f + 6], model.position[f + 7], model.position[f + 8]);
Vector3f n = MathUtil.getSurfaceNormal(p1, p2, p3);
model.normal[f + 0] = n.getX(); model.normal[f + 1] = n.getY(); model.normal[f + 2] = n.getZ();
model.normal[f + 3] = n.getX(); model.normal[f + 4] = n.getY(); model.normal[f + 5] = n.getZ();
model.normal[f + 6] = n.getX(); model.normal[f + 7] = n.getY(); model.normal[f + 8] = n.getZ();
}
}
}
public BufferGeometry getBufferGeometry(){
flush();
return model;
}
public UUID getWorld(){
return world;
}
public Vector2i getTile(){
return tilePos;
}
@Override
public int hashCode() {
return Objects.hash(world, tilePos);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LowresModel){
LowresModel other = (LowresModel) obj;
if (!other.world.equals(world)) return false;
if (other.tilePos.equals(tilePos)) return true;
}
return false;
}
/**
* a point on this lowres-model-grid
*/
public class LowresPoint {
private float height;
private Vector3f color;
public LowresPoint(float height, Vector3f color) {
this.height = height;
this.color = color;
}
public LowresPoint add(LowresPoint other){
float newHeight = height + other.height;
Vector3f newColor = color.add(other.color);
return new LowresPoint(newHeight, newColor);
}
public LowresPoint div(float divisor){
float newHeight = height / divisor;
Vector3f newColor = color.div(divisor);
return new LowresPoint(newHeight, newColor);
}
}
}

View File

@ -0,0 +1,309 @@
/*
* 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.core.render.lowres;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPInputStream;
import org.apache.commons.io.IOUtils;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.hires.HiresModel;
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
import de.bluecolored.bluemap.core.util.FileUtil;
public class LowresModelManager {
private Path fileRoot;
private Vector2i gridSize;
private Vector2i pointsPerHiresTile;
private Map<File, CachedModel> models;
public LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHiresTile) {
this.fileRoot = fileRoot;
this.gridSize = gridSize;
this.pointsPerHiresTile = pointsPerHiresTile;
models = new ConcurrentHashMap<>();
}
/**
* Renders all points from the given highres-model onto the lowres-grid
*/
public void render(HiresModel hiresModel) throws IOException {
Vector3i min = hiresModel.getBlockMin();
Vector3i max = hiresModel.getBlockMax();
Vector3i size = max.sub(min).add(Vector3i.ONE);
Vector2i blocksPerPoint =
size
.toVector2(true)
.div(pointsPerHiresTile);
Vector2i pointMin = min
.toVector2(true)
.toDouble()
.div(blocksPerPoint.toDouble())
.floor()
.toInt();
for (int tx = 0; tx < pointsPerHiresTile.getX(); tx++){
for (int tz = 0; tz < pointsPerHiresTile.getY(); tz++){
double height = 0;
Vector3d color = Vector3d.ZERO;
double colorCount = 0;
for (int x = 0; x < blocksPerPoint.getX(); x++){
for (int z = 0; z < blocksPerPoint.getY(); z++){
int rx = tx * blocksPerPoint.getX() + x + min.getX();
int rz = tz * blocksPerPoint.getY() + z + min.getZ();
height += hiresModel.getHeight(rx, rz);
Vector4f c = hiresModel.getColor(rx, rz);
color = color.add(c.toVector3().toDouble().mul(c.getW()));
colorCount += c.getW();
}
}
if (colorCount > 0) color = color.div(colorCount);
int count = blocksPerPoint.getX() * blocksPerPoint.getY();
height /= count;
Vector2i point = pointMin.add(tx, tz);
update(hiresModel.getWorld(), point, (float) height, color.toFloat());
}
}
}
/**
* Saves all unsaved changes to the models to disk
*/
public synchronized void save(){
for (CachedModel model : models.values()){
saveModel(model);
}
tidyUpModelCache();
}
/**
* Updates a point on the lowresmodel-grid
*/
public void update(UUID world, Vector2i point, float height, Vector3f color) throws IOException {
Vector2i tile = pointToTile(point);
Vector2i relPoint = getPointRelativeToTile(tile, point);
LowresModel model = getModel(world, tile);
model.update(relPoint, height, color);
if (relPoint.getX() == 0){
Vector2i tile2 = tile.add(-1, 0);
Vector2i relPoint2 = getPointRelativeToTile(tile2, point);
LowresModel model2 = getModel(world, tile2);
model2.update(relPoint2, height, color);
}
if (relPoint.getY() == 0){
Vector2i tile2 = tile.add(0, -1);
Vector2i relPoint2 = getPointRelativeToTile(tile2, point);
LowresModel model2 = getModel(world, tile2);
model2.update(relPoint2, height, color);
}
if (relPoint.getX() == 0 && relPoint.getY() == 0){
Vector2i tile2 = tile.add(-1, -1);
Vector2i relPoint2 = getPointRelativeToTile(tile2, point);
LowresModel model2 = getModel(world, tile2);
model2.update(relPoint2, height, color);
}
}
/**
* Returns the file for a tile
*/
public File getFile(Vector2i tile){
return FileUtil.coordsToFile(fileRoot, tile, "json.gz");
}
private LowresModel getModel(UUID world, Vector2i tile) throws IOException {
File modelFile = getFile(tile);
CachedModel model = models.get(modelFile);
if (model == null){
synchronized (this) {
model = models.get(modelFile);
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);
try {
model = new CachedModel(world, tile, BufferGeometry.fromJson(json));
} catch (IllegalArgumentException | IOException ex){
Logger.global.logError("Failed to load lowres model: " + modelFile, ex);
//gridFile.renameTo(gridFile.toPath().getParent().resolve(gridFile.getName() + ".broken").toFile());
modelFile.delete();
}
}
}
if (model == null){
model = new CachedModel(world, tile, gridSize);
}
models.put(modelFile, model);
tidyUpModelCache();
}
}
}
return model;
}
/**
* This Method tidies up the model cache:<br>
* it saves all modified models that have not been saved for 2 minutes and<br>
* saves and removes the oldest models from the cache until the cache size is 10 or less.<br>
* <br>
* This method gets automatically called if the cache grows, but if you want to ensure model will be saved after 2 minutes, you could e.g call this method every second.<br>
*/
public synchronized void tidyUpModelCache() {
List<Entry<File, CachedModel>> entries = new ArrayList<>(models.size());
entries.addAll(models.entrySet());
entries.sort((e1, e2) -> (int) Math.signum(e1.getValue().cacheTime - e2.getValue().cacheTime));
int size = entries.size();
for (Entry<File, CachedModel> e : entries) {
if (size > 10) {
saveAndRemoveModel(e.getValue());
continue;
}
if (e.getValue().getCacheTime() > 120000) {
saveModel(e.getValue());
}
}
}
private synchronized void saveAndRemoveModel(CachedModel model) {
File modelFile = getFile(model.getTile());
models.remove(modelFile);
try {
model.save(modelFile, false);
//logger.logDebug("Saved and unloaded lowres tile: " + model.getTile());
} catch (IOException ex) {
Logger.global.logError("Failed to save and unload lowres-model: " + modelFile, ex);
}
}
private void saveModel(CachedModel model) {
File modelFile = getFile(model.getTile());
try {
model.save(modelFile, false);
//logger.logDebug("Saved lowres tile: " + model.getTile());
} catch (IOException ex) {
Logger.global.logError("Failed to save lowres-model: " + modelFile, ex);
}
model.resetCacheTime();
}
private Vector2i pointToTile(Vector2i point){
return point
.toDouble()
.div(gridSize.toDouble())
.floor()
.toInt();
}
private Vector2i getPointRelativeToTile(Vector2i tile, Vector2i point){
return point.sub(tile.mul(gridSize));
}
public Vector2i getTileSize() {
return gridSize;
}
public Vector2i getPointsPerHiresTile() {
return pointsPerHiresTile;
}
private class CachedModel extends LowresModel {
private long cacheTime;
public CachedModel(UUID world, Vector2i tilePos, BufferGeometry model) {
super(world, tilePos, model);
cacheTime = System.currentTimeMillis();
}
public CachedModel(UUID world, Vector2i tilePos, Vector2i gridSize) {
super(world, tilePos, gridSize);
cacheTime = System.currentTimeMillis();
}
public long getCacheTime() {
return System.currentTimeMillis() - cacheTime;
}
public void resetCacheTime() {
cacheTime = System.currentTimeMillis();
}
}
}

View File

@ -0,0 +1,239 @@
/*
* 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.core.resourcepack;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import javax.imageio.ImageIO;
import com.flowpowered.math.GenericMath;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.render.context.ExtendedBlockContext;
import de.bluecolored.bluemap.core.world.Block;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BlockColorProvider {
private BufferedImage foliageMap;
private BufferedImage grassMap;
private Map<String, BiomeInfo> biomeInfos;
private Map<String, String> blockColors;
public BlockColorProvider(ResourcePack resourcePack) throws IOException, NoSuchResourceException {
this.foliageMap = ImageIO.read(resourcePack.getResource(Paths.get("assets", "minecraft", "textures", "colormap", "foliage.png")));
this.grassMap = ImageIO.read(resourcePack.getResource(Paths.get("assets", "minecraft", "textures", "colormap", "grass.png")));
this.biomeInfos = new ConcurrentHashMap<>();
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
.setURL(getClass().getResource("/biomes.json"))
.build();
ConfigurationNode biomesConfig = loader.load();
for (Entry<Object, ? extends ConfigurationNode> n : biomesConfig.getChildrenMap().entrySet()){
String key = n.getKey().toString();
BiomeInfo value = new BiomeInfo();
value.humidity = n.getValue().getNode("humidity").getFloat(0.4f);
value.temp = n.getValue().getNode("temp").getFloat(0.8f);
value.watercolor = n.getValue().getNode("watercolor").getInt(4159204);
biomeInfos.put(key, value);
}
this.blockColors = new ConcurrentHashMap<>();
loader = GsonConfigurationLoader.builder()
.setURL(getClass().getResource("/blockColors.json"))
.build();
ConfigurationNode blockConfig = loader.load();
for (Entry<Object, ? extends ConfigurationNode> n : blockConfig.getChildrenMap().entrySet()){
String blockId = n.getKey().toString();
String color = n.getValue().getString();
blockColors.put(blockId, color);
}
}
public Vector3f getBlockColor(ExtendedBlockContext context){
Block block = context.getRelativeBlock(0, 0, 0);
String blockId = block.getBlock().getId();
// water color
if (blockId.equals("water")) {
return getBiomeWaterAverageColor(context);
}
String colorDef = blockColors.get(blockId);
if (colorDef == null) colorDef = blockColors.get("default");
if (colorDef == null) colorDef = "#foliage";
// grass map
if (colorDef.equals("#grass")){
return getBiomeGrassAverageColor(context);
}
// foliage map
if (colorDef.equals("#foliage")){
return getBiomeFoliageAverageColor(context);
}
int cValue = Integer.parseInt(colorDef, 16);
return colorFromInt(cValue);
}
public Vector3f getBiomeFoliageAverageColor(ExtendedBlockContext context){
Vector3f color = Vector3f.ZERO;
for (int x = -1; x <= 1; x++){
for (int z = -1; z <= 1; z++){
color = color.add(getBiomeFoliageColor(context.getRelativeBlock(x, 0, z)));
}
}
return color.div(9f);
}
private Vector3f getBiomeFoliageColor(Block block){
Vector3f color = Vector3f.ONE;
if (block.getBiome().contains("mesa")){
return colorFromInt(0x9e814d);
}
if (block.getBiome().contains("swamp")) {
return colorFromInt(0x6A7039);
}
int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0);
color = getFoliageColor(block.getBiome(), blocksAboveSeaLevel);
//improvised to match the original better
if (block.getBiome().contains("roofed_forest")){
color = color.mul(2f).add(colorFromInt(0x28340a)).div(3f);
}
return color;
}
public Vector3f getBiomeGrassAverageColor(ExtendedBlockContext context){
Vector3f color = Vector3f.ZERO;
for (int x = -1; x <= 1; x++){
for (int z = -1; z <= 1; z++){
color = color.add(getBiomeGrassColor(context.getRelativeBlock(x, 0, z)));
}
}
return color.div(9f);
}
private Vector3f getBiomeGrassColor(Block block){
Vector3f color = Vector3f.ONE;
if (block.getBiome().contains("mesa")){
return colorFromInt(0x90814d);
}
if (block.getBiome().contains("swamp")) {
return colorFromInt(0x6A7039);
}
int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0);
color = getGrassColor(block.getBiome(), blocksAboveSeaLevel);
if (block.getBiome().contains("roofed_forest")){
color = color.add(colorFromInt(0x28340a)).div(2f);
}
return color;
}
public Vector3f getBiomeWaterAverageColor(ExtendedBlockContext context){
Vector3f color = Vector3f.ZERO;
for (int x = -1; x <= 1; x++){
for (int z = -1; z <= 1; z++){
color = color.add(getBiomeWaterColor(context.getRelativeBlock(x, 0, z)));
}
}
return color.div(9f);
}
private Vector3f getBiomeWaterColor(Block block){
return colorFromInt(biomeInfos.get(block.getBiome()).watercolor);
}
private Vector3f colorFromInt(int cValue){
Color c = new Color(cValue, false);
return new Vector3f(c.getRed(), c.getGreen(), c.getBlue()).div(0xff);
}
private Vector3f getFoliageColor(String biomeId, int blocksAboveSeaLevel){
return getColorFromMap(biomeId, blocksAboveSeaLevel, foliageMap);
}
private Vector3f getGrassColor(String biomeId, int blocksAboveSeaLevel){
return getColorFromMap(biomeId, blocksAboveSeaLevel, grassMap);
}
private Vector3f getColorFromMap(String biomeId, int blocksAboveSeaLevel, BufferedImage map){
Vector2i pixel = getColorMapPosition(biomeId, blocksAboveSeaLevel).mul(map.getWidth(), map.getHeight()).floor().toInt();
int cValue = map.getRGB(GenericMath.clamp(pixel.getX(), 0, map.getWidth() - 1), GenericMath.clamp(pixel.getY(), 0, map.getHeight() - 1));
Color color = new Color(cValue, false);
return new Vector3f(color.getRed(), color.getGreen(), color.getBlue()).div(0xff);
}
private Vector2f getColorMapPosition(String biomeId, int blocksAboveSeaLevel){
BiomeInfo bi = biomeInfos.get(biomeId);
if (bi == null){
throw new NoSuchElementException("No biome found with id: " + biomeId);
}
float adjTemp = (float) GenericMath.clamp(bi.temp - (0.00166667 * (double) blocksAboveSeaLevel), 0d, 1d);
float adjHumidity = (float) GenericMath.clamp(bi.humidity, 0d, 1d) * adjTemp;
return new Vector2f(1 - adjTemp, 1 - adjHumidity);
}
class BiomeInfo {
float humidity;
float temp;
int watercolor;
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.core.resourcepack;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.util.ConfigUtil;
import de.bluecolored.bluemap.core.util.Direction;
import ninja.leaping.configurate.ConfigurationNode;
public class BlockModelElementFaceResource {
private BlockModelElementResource element;
private Vector4f uv;
private String texture;
private String resolvedTexture;
private Direction cullface;
private int rotation;
private int tintIndex;
protected BlockModelElementFaceResource(BlockModelElementResource element, ConfigurationNode declaration) throws InvalidResourceDeclarationException {
this.element = element;
try {
this.uv = getDefaultUV(declaration.getKey().toString(), element.getFrom(), element.getTo());
ConfigurationNode uv = declaration.getNode("uv");
if (!uv.isVirtual()) this.uv = ConfigUtil.readVector4f(declaration.getNode("uv"));
this.texture = declaration.getNode("texture").getString();
this.resolvedTexture = null;
this.cullface = null;
ConfigurationNode cf = declaration.getNode("cullface");
if (!cf.isVirtual()) this.cullface = Direction.fromString(cf.getString());
this.rotation = declaration.getNode("rotation").getInt(0);
this.tintIndex = declaration.getNode("tintindex").getInt(-1);
} catch (NullPointerException | IllegalArgumentException e){
throw new InvalidResourceDeclarationException(e);
}
}
public Vector4f getDefaultUV(String faceId, Vector3f from, Vector3f to){
switch (faceId){
case "down" :
case "up" :
return new Vector4f(
from.getX(), from.getZ(),
to.getX(), to.getZ()
);
case "north" :
case "south" :
return new Vector4f(
from.getX(), from.getY(),
to.getX(), to.getY()
);
case "west" :
case "east" :
return new Vector4f(
from.getZ(), from.getY(),
to.getZ(), to.getY()
);
default :
return new Vector4f(
0, 0,
16, 16
);
}
}
public BlockModelElementResource getElement(){
return element;
}
public Vector4f getUv() {
return uv;
}
public String getTexture() {
return texture;
}
public String getResolvedTexture() {
if (resolvedTexture == null){
resolvedTexture = getElement().getModel().resolveTexture(getTexture());
}
return resolvedTexture;
}
public boolean isCullface() {
return cullface != null;
}
public Direction getCullface() {
return cullface;
}
public int getRotation() {
return rotation;
}
public boolean isTinted(){
return tintIndex >= 0;
}
public int getTintIndex() {
return tintIndex;
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.core.resourcepack;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.util.Axis;
import de.bluecolored.bluemap.core.util.ConfigUtil;
import ninja.leaping.configurate.ConfigurationNode;
public class BlockModelElementResource {
private BlockModelResource model;
private Vector3f from, to;
private Vector3f rotOrigin;
private Axis rotAxis;
private float rotAngle;
private boolean rotRescale;
private boolean shade;
private BlockModelElementFaceResource down, up, north, south, west, east;
protected BlockModelElementResource(BlockModelResource model, ConfigurationNode declaration) throws InvalidResourceDeclarationException {
this.model = model;
try {
this.from = ConfigUtil.readVector3f(declaration.getNode("from"));
this.to = ConfigUtil.readVector3f(declaration.getNode("to"));
this.rotAngle = 0f;
ConfigurationNode rotation = declaration.getNode("rotation");
if (!rotation.isVirtual()){
this.rotOrigin = ConfigUtil.readVector3f(rotation.getNode("origin"));
this.rotAxis = Axis.fromString(rotation.getNode("axis").getString());
this.rotAngle = rotation.getNode("angle").getFloat();
this.rotRescale = rotation.getNode("rescale").getBoolean(false);
}
this.shade = declaration.getNode("shade").getBoolean(true);
ConfigurationNode faces = declaration.getNode("faces");
this.down = loadFace(faces.getNode("down"));
this.up = loadFace(faces.getNode("up"));
this.north = loadFace(faces.getNode("north"));
this.south = loadFace(faces.getNode("south"));
this.west = loadFace(faces.getNode("west"));
this.east = loadFace(faces.getNode("east"));
} catch (NullPointerException e){
throw new InvalidResourceDeclarationException(e);
}
}
private BlockModelElementFaceResource loadFace(ConfigurationNode faceNode) throws InvalidResourceDeclarationException {
if (faceNode.isVirtual()) return null;
return new BlockModelElementFaceResource(this, faceNode);
}
public BlockModelResource getModel(){
return model;
}
public Vector3f getFrom() {
return from;
}
public Vector3f getTo() {
return to;
}
public boolean isRotation(){
return rotAngle != 0f;
}
public Vector3f getRotationOrigin() {
return rotOrigin;
}
public Axis getRotationAxis() {
return rotAxis;
}
public float getRotationAngle() {
return rotAngle;
}
public boolean isRotationRescale() {
return rotRescale;
}
public boolean isShade() {
return shade;
}
public BlockModelElementFaceResource getDownFace() {
return down;
}
public BlockModelElementFaceResource getUpFace() {
return up;
}
public BlockModelElementFaceResource getNorthFace() {
return north;
}
public BlockModelElementFaceResource getSouthFace() {
return south;
}
public BlockModelElementFaceResource getWestFace() {
return west;
}
public BlockModelElementFaceResource getEastFace() {
return east;
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.core.resourcepack;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BlockModelResource {
private BlockStateResource blockState;
private int xRot, yRot;
private boolean uvLock;
private boolean ambientOcclusion;
private Collection<BlockModelElementResource> elements;
private Map<String, String> textures;
protected BlockModelResource(BlockStateResource blockState, ConfigurationNode declaration, ResourcePack resources) throws InvalidResourceDeclarationException {
this.blockState = blockState;
this.xRot = declaration.getNode("x").getInt(0);
this.yRot = declaration.getNode("y").getInt(0);
this.uvLock = declaration.getNode("uvlock").getBoolean(false);
this.ambientOcclusion = true;
this.elements = new Vector<>();
this.textures = new ConcurrentHashMap<>();
try {
loadModelResource(declaration.getNode("model").getString(), resources);
} catch (IOException e) {
throw new InvalidResourceDeclarationException("Model not found: " + declaration.getNode("model").getString(), e);
}
}
private void loadModelResource(String modelId, ResourcePack resources) throws IOException, InvalidResourceDeclarationException {
Path resourcePath = Paths.get("assets", "minecraft", "models", modelId + ".json");
ConfigurationNode data = GsonConfigurationLoader.builder()
.setSource(() -> new BufferedReader(new InputStreamReader(resources.getResource(resourcePath), StandardCharsets.UTF_8)))
.build()
.load();
//load parent first
ConfigurationNode parent = data.getNode("parent");
if (!parent.isVirtual()){
loadModelResource(parent.getString(), resources);
}
for (Entry<Object, ? extends ConfigurationNode> texture : data.getNode("textures").getChildrenMap().entrySet()){
String key = texture.getKey().toString();
String value = texture.getValue().getString();
textures.put(key, value);
}
ambientOcclusion = data.getNode("ambientocclusion").getBoolean(ambientOcclusion);
if (!data.getNode("elements").isVirtual()){
elements.clear();
for (ConfigurationNode e : data.getNode("elements").getChildrenList()){
elements.add(new BlockModelElementResource(this, e));
}
}
}
public BlockStateResource getBlockState(){
return blockState;
}
public int getXRot() {
return xRot;
}
public int getYRot() {
return yRot;
}
public boolean isUvLock() {
return uvLock;
}
public boolean isAmbientOcclusion() {
return ambientOcclusion;
}
public Collection<BlockModelElementResource> getElements() {
return Collections.unmodifiableCollection(elements);
}
public String resolveTexture(String key){
if (key == null) return null;
if (!key.startsWith("#")) return key;
String texture = textures.get(key.substring(1));
if (texture == null) return key;
return resolveTexture(texture);
}
public Collection<String> getAllTextureIds(){
List<String> list = new ArrayList<>();
for (String tex : textures.values()){
if (!tex.startsWith("#")) list.add(tex);
}
return list;
}
}

View File

@ -0,0 +1,154 @@
/*
* 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.core.resourcepack;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import com.google.common.base.Preconditions;
import de.bluecolored.bluemap.core.util.WeighedArrayList;
import de.bluecolored.bluemap.core.world.BlockState;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class BlockStateResource {
private BlockState block;
private Collection<WeighedArrayList<BlockModelResource>> modelResources;
protected BlockStateResource(BlockState block, ResourcePack resources) throws NoSuchResourceException, InvalidResourceDeclarationException {
this.block = Preconditions.checkNotNull(block);
this.modelResources = new Vector<>();
try {
ConfigurationNode data = GsonConfigurationLoader.builder()
.setSource(() -> new BufferedReader(new InputStreamReader(resources.getResource(getResourcePath()), StandardCharsets.UTF_8)))
.build()
.load();
load(data, resources);
} catch (IOException e) {
throw new NoSuchResourceException("There is no definition for resource-id: " + block.getId(), e);
} catch (NullPointerException e){
throw new InvalidResourceDeclarationException(e);
}
this.modelResources = Collections.unmodifiableCollection(this.modelResources);
}
private void load(ConfigurationNode data, ResourcePack resources) throws InvalidResourceDeclarationException {
//load variants
ConfigurationNode variants = data.getNode("variants");
for (Entry<Object, ? extends ConfigurationNode> e : variants.getChildrenMap().entrySet()){
if (getBlock().checkVariantCondition(e.getKey().toString())){
addModelResource(e.getValue(), resources);
break;
}
}
//load multipart
ConfigurationNode multipart = data.getNode("multipart");
for (ConfigurationNode part : multipart.getChildrenList()){
ConfigurationNode when = part.getNode("when");
if (when.isVirtual() || checkMultipartCondition(when)){
addModelResource(part.getNode("apply"), resources);
}
}
}
private void addModelResource(ConfigurationNode n, ResourcePack resources) throws InvalidResourceDeclarationException {
WeighedArrayList<BlockModelResource> models = new WeighedArrayList<>();
if (n.hasListChildren()){
//if it is a weighted list of alternative models, select one by random and weight
List<? extends ConfigurationNode> cList = n.getChildrenList();
for (ConfigurationNode c : cList){
int weight = c.getNode("weight").getInt(1);
models.add(new BlockModelResource(this, c, resources), weight);
}
} else {
models.add(new BlockModelResource(this, n, resources));
}
modelResources.add(models);
}
private boolean checkMultipartCondition(ConfigurationNode when){
ConfigurationNode or = when.getNode("OR");
if (!or.isVirtual()){
for (ConfigurationNode condition : or.getChildrenList()){
if (checkMultipartCondition(condition)) return true;
}
return false;
}
Map<String, String> blockProperties = getBlock().getProperties();
for (Entry<Object, ? extends ConfigurationNode> e : when.getChildrenMap().entrySet()){
String key = e.getKey().toString();
String[] values = e.getValue().getString().split("\\|");
boolean found = false;
for (String value : values){
if (value.equals(blockProperties.get(key))){
found = true;
break;
}
}
if (!found) return false;
}
return true;
}
public BlockState getBlock() {
return block;
}
public Collection<WeighedArrayList<BlockModelResource>> getModelResources(){
return modelResources;
}
private Path getResourcePath(){
return Paths.get("assets", block.getNamespace(), "blockstates", block.getId() + ".json");
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.resourcepack;
public class InvalidResourceDeclarationException extends Exception {
private static final long serialVersionUID = 0L;
public InvalidResourceDeclarationException() {}
public InvalidResourceDeclarationException(Throwable e) {
super(e);
}
public InvalidResourceDeclarationException(String message){
super(message);
}
public InvalidResourceDeclarationException(String message, Throwable e) {
super(message, e);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.resourcepack;
public class NoSuchResourceException extends Exception {
private static final long serialVersionUID = 0L;
public NoSuchResourceException() {}
public NoSuchResourceException(Throwable e) {
super(e);
}
public NoSuchResourceException(String message){
super(message);
}
public NoSuchResourceException(String message, Throwable e) {
super(message, e);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.resourcepack;
public class NoSuchTextureException extends Exception {
private static final long serialVersionUID = 0L;
public NoSuchTextureException() {}
public NoSuchTextureException(Throwable e) {
super(e);
}
public NoSuchTextureException(String message){
super(message);
}
public NoSuchTextureException(String message, Throwable e) {
super(message, e);
}
}

View File

@ -0,0 +1,189 @@
/*
* 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.core.resourcepack;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.world.BlockState;
public class ResourcePack {
private Map<Path, Resource> resources;
private TextureProvider textureProvider;
private BlockColorProvider blockColorProvider;
private Cache<BlockState, BlockStateResource> blockStateResourceCache;
public ResourcePack(List<File> dataSources, File textureExportFile) throws IOException, NoSuchResourceException {
this.resources = new HashMap<>();
load(dataSources);
blockStateResourceCache = CacheBuilder.newBuilder()
.maximumSize(10000)
.build();
textureProvider = new TextureProvider();
if (textureExportFile.exists()){
textureProvider.load(textureExportFile);
} else {
textureProvider.generate(this);
textureProvider.save(textureExportFile);
}
blockColorProvider = new BlockColorProvider(this);
}
private void load(List<File> dataSources) throws IOException {
resources.clear();
//load resourcepacks in order
for (File resourcePath : dataSources) overrideResourcesWith(resourcePath);
}
private void overrideResourcesWith(File resourcePath){
if (resourcePath.isFile() && resourcePath.getName().endsWith(".zip") || resourcePath.getName().endsWith(".jar")){
overrideResourcesWithZipFile(resourcePath);
return;
}
overrideResourcesWith(resourcePath, Paths.get(""));
}
private void overrideResourcesWith(File resource, Path resourcePath){
if (resource.isDirectory()){
for (File childFile : resource.listFiles()){
overrideResourcesWith(childFile, resourcePath.resolve(childFile.getName()));
}
return;
}
if (resource.isFile()){
try {
byte[] bytes = Files.readAllBytes(resource.toPath());
resources.put(resourcePath, new Resource(bytes));
} catch (IOException e) {
Logger.global.logError("Failed to load resource: " + resource, e);
}
}
}
private void overrideResourcesWithZipFile(File resourceFile){
try (
ZipFile zipFile = new ZipFile(resourceFile);
){
Enumeration<? extends ZipEntry> files = zipFile.entries();
byte[] buffer = new byte[1024];
while (files.hasMoreElements()){
ZipEntry file = files.nextElement();
if (file.isDirectory()) continue;
Path resourcePath = Paths.get("", file.getName().split("/"));
InputStream fileInputStream = zipFile.getInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.max(8, (int) file.getSize()));
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1){
bos.write(buffer, 0, bytesRead);
}
resources.put(resourcePath, new Resource(bos.toByteArray()));
}
} catch (IOException e) {
Logger.global.logError("Failed to load resource: " + resourceFile, e);
}
}
public BlockStateResource getBlockStateResource(BlockState block) throws NoSuchResourceException, InvalidResourceDeclarationException {
BlockStateResource bsr = blockStateResourceCache.getIfPresent(block);
if (bsr == null){
bsr = new BlockStateResource(block, this);
blockStateResourceCache.put(block, bsr);
}
return bsr;
}
public TextureProvider getTextureProvider(){
return textureProvider;
}
public BlockColorProvider getBlockColorProvider(){
return blockColorProvider;
}
public Map<Path, Resource> getAllResources() {
return Collections.unmodifiableMap(resources);
}
public InputStream getResource(Path resourcePath) throws NoSuchResourceException {
Resource resource = resources.get(resourcePath);
if (resource == null) throw new NoSuchResourceException("There is no resource with that path: " + resourcePath);
return resource.getStream();
}
public class Resource {
private byte[] data;
public Resource(byte[] data) {
this.data = data;
}
public InputStream getStream(){
return new ByteArrayInputStream(data);
}
}
public static void createDefaultResource(File file) throws IOException {
if (!file.exists()) {
file.getParentFile().mkdirs();
FileUtils.copyURLToFile(ResourcePack.class.getResource("/DefaultResources.zip"), file, 10000, 10000);
}
}
}

View File

@ -0,0 +1,231 @@
/*
* 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.core.resourcepack;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import javax.imageio.ImageIO;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack.Resource;
import de.bluecolored.bluemap.core.util.ConfigUtil;
import de.bluecolored.bluemap.core.util.MathUtil;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
public class TextureProvider {
private Map<String, Integer> indexMap;
private List<Texture> textures;
public TextureProvider() throws IOException {
this.indexMap = new ConcurrentHashMap<>();
this.textures = new Vector<>();
}
public int getTextureIndex(String textureId) throws NoSuchTextureException {
Integer tex = indexMap.get(textureId);
if (tex == null){
throw new NoSuchTextureException("There is no texture with id: " + textureId);
}
return tex.intValue();
}
public Texture getTexture(String textureId) throws NoSuchTextureException {
return getTexture(getTextureIndex(textureId));
}
public Texture getTexture(int index){
return textures.get(index);
}
public void generate(ResourcePack resources) throws IOException {
indexMap.clear();
textures.clear();
Path textureRoot = Paths.get("assets", "minecraft", "textures");
for (Entry<Path, Resource> entry : resources.getAllResources().entrySet()){
if (entry.getKey().startsWith(textureRoot) && entry.getKey().toString().endsWith(".png")){
BufferedImage image = ImageIO.read(entry.getValue().getStream());
if (image == null) throw new IOException("Failed to read Image: " + entry.getKey());
String path = textureRoot.relativize(entry.getKey()).normalize().toString();
String id = path
.substring(0, path.length() - ".png".length())
.replace(File.separatorChar, '/');
Texture texture = new Texture(id, image);
textures.add(texture);
indexMap.put(id, textures.size() - 1);
}
}
}
public void load(File file) throws IOException {
indexMap.clear();
textures.clear();
GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(file).build();
ConfigurationNode node = loader.load();
int i = 0;
for(ConfigurationNode n : node.getNode("textures").getChildrenList()){
Texture t = new Texture(
n.getNode("id").getString(),
n.getNode("texture").getString(),
n.getNode("transparent").getBoolean(false),
ConfigUtil.readVector4f(n.getNode("color"))
);
textures.add(t);
indexMap.put(t.getId(), i++);
}
}
public void save(File file) throws IOException {
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
GsonConfigurationLoader loader = GsonConfigurationLoader.builder().setFile(file).build();
ConfigurationNode node = loader.createEmptyNode();
for (Texture t : textures){
ConfigurationNode n = node.getNode("textures").getAppendedNode();
n.getNode("id").setValue(t.getId());
n.getNode("texture").setValue(t.getBase64());
n.getNode("transparent").setValue(t.isHalfTransparent());
ConfigUtil.writeVector4f(n.getNode("color"), t.getColor());
}
loader.save(node);
}
public class Texture {
private String id;
private String base64;
private boolean halfTransparent;
private Vector4f color;
public Texture(String id, String base64, boolean halfTransparent, Vector4f color){
this.id = id;
this.halfTransparent = halfTransparent;
this.base64 = base64;
this.color = color;
}
public Texture(String id, BufferedImage image) throws IOException {
this.id = id;
//crop off animation frames
if (image.getHeight() > image.getWidth()){
BufferedImage cropped = new BufferedImage(image.getWidth(), image.getWidth(), image.getType());
Graphics2D g = cropped.createGraphics();
g.drawImage(image, 0, 0, null);
image = cropped;
}
//check halfTransparency
this.halfTransparent = checkHalfTransparent(image);
//calculate color
this.color = calculateColor(image);
//write to Base64
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(image, "png", os);
this.base64 = "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray());
}
private Vector4f calculateColor(BufferedImage image){
Vector4f color = Vector4f.ZERO;
for (int x = 0; x < image.getWidth(); x++){
for (int y = 0; y < image.getHeight(); y++){
int pixel = image.getRGB(x, y);
double alpha = (double)((pixel >> 24) & 0xff) / (double) 0xff;
double red = (double)((pixel >> 16) & 0xff) / (double) 0xff;
double green = (double)((pixel >> 8) & 0xff) / (double) 0xff;
double blue = (double)((pixel >> 0) & 0xff) / (double) 0xff;
color = MathUtil.blendColors(new Vector4f(red, green, blue, alpha), color);
}
}
return color;
}
private boolean checkHalfTransparent(BufferedImage image){
for (int x = 0; x < image.getWidth(); x++){
for (int y = 0; y < image.getHeight(); y++){
int pixel = image.getRGB(x, y);
int alpha = (pixel >> 24) & 0xff;
if (alpha > 0x00 && alpha < 0xff){
return true;
}
}
}
return false;
}
public String getId() {
return id;
}
public String getBase64() {
return base64;
}
public boolean isHalfTransparent() {
return halfTransparent;
}
public Vector4f getColor(){
return color;
}
}
}

View File

@ -0,0 +1,312 @@
/*
* 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.core.threejs;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.flowpowered.math.GenericMath;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
public class BufferGeometry {
public final float[] position, normal, color, uv;
public final MaterialGroup[] groups;
public BufferGeometry(float[] position, float[] normal, float[] color, float[] uv, MaterialGroup[] groups) {
this.position = position;
this.normal = normal;
this.color = color;
this.uv = uv;
this.groups = groups;
}
public int getFaceCount(){
return Math.floorDiv(position.length, 3);
}
public String toJson() {
try {
StringWriter sw = new StringWriter();
Gson gson = new GsonBuilder().create();
JsonWriter json = gson.newJsonWriter(sw);
json.beginObject();
//set special values
json.name("type").value("BufferGeometry");
json.name("uuid").value(UUID.randomUUID().toString().toUpperCase());
json.name("data").beginObject();
json.name("attributes").beginObject();
json.name("position");
floatArray2Json(json, position, 3, false);
json.name("normal");
floatArray2Json(json, normal, 3, true);
json.name("color");
floatArray2Json(json, color, 3, false);
json.name("uv");
floatArray2Json(json, uv, 2, false);
json.endObject(); //attributes
json.name("groups").beginArray();
//write groups into json
for (BufferGeometry.MaterialGroup g : groups){
json.beginObject();
json.name("materialIndex").value(g.getMaterialIndex());
json.name("start").value(g.getStart());
json.name("count").value(g.getCount());
json.endObject();
}
json.endArray(); //groups
json.endObject(); //data
json.endObject(); //main-object
//save and return
json.flush();
return sw.toString();
} catch (IOException e){
//since we are using a StringWriter there should never be an IO exception thrown
throw new RuntimeException(e);
}
}
public static BufferGeometry fromJson(String jsonString) throws IOException {
Gson gson = new GsonBuilder().create();
JsonReader json = gson.newJsonReader(new StringReader(jsonString));
List<Float> positionList = new ArrayList<>(300);
List<Float> normalList = new ArrayList<>(300);
List<Float> colorList = new ArrayList<>(300);
List<Float> uvList = new ArrayList<>(200);
List<MaterialGroup> groups = new ArrayList<>(10);
json.beginObject(); //root
while (json.hasNext()){
String name1 = json.nextName();
if(name1.equals("data")){
json.beginObject(); //data
while (json.hasNext()){
String name2 = json.nextName();
if(name2.equals("attributes")){
json.beginObject(); //attributes
while (json.hasNext()){
String name3 = json.nextName();
if(name3.equals("position")){
json2FloatList(json, positionList);
}
else if(name3.equals("normal")){
json2FloatList(json, normalList);
}
else if(name3.equals("color")){
json2FloatList(json, colorList);
}
else if(name3.equals("uv")){
json2FloatList(json, uvList);
}
else json.skipValue();
}
json.endObject(); //attributes
}
else if (name2.equals("groups")){
json.beginArray(); //groups
while (json.hasNext()){
MaterialGroup group = new MaterialGroup(0, 0, 0);
json.beginObject(); //group
while (json.hasNext()){
String name3 = json.nextName();
if(name3.equals("materialIndex")){
group.setMaterialIndex(json.nextInt());
}
else if(name3.equals("start")){
group.setStart(json.nextInt());
}
else if(name3.equals("count")){
group.setCount(json.nextInt());
}
else json.skipValue();
}
json.endObject(); //group
groups.add(group);
}
json.endArray(); //groups
}
else json.skipValue();
}
json.endObject();//data
}
else json.skipValue();
}
json.endObject(); //root
//check if this is a valid BufferGeometry
int faceCount = Math.floorDiv(positionList.size(), 3);
if (positionList.size() != faceCount * 3) throw new IllegalArgumentException("Wrong count of positions! (Got " + positionList.size() + " but expected " + (faceCount * 3) + ")");
if (normalList.size() != faceCount * 3) throw new IllegalArgumentException("Wrong count of normals! (Got " + normalList.size() + " but expected " + (faceCount * 3) + ")");
if (colorList.size() != faceCount * 3) throw new IllegalArgumentException("Wrong count of colors! (Got " + colorList.size() + " but expected " + (faceCount * 3) + ")");
if (uvList.size() != faceCount * 2) throw new IllegalArgumentException("Wrong count of uvs! (Got " + uvList.size() + " but expected " + (faceCount * 2) + ")");
groups.sort((g1, g2) -> (int) Math.signum(g1.getStart() - g2.getStart()));
int nextGroup = 0;
for (MaterialGroup g : groups){
if(g.getStart() != nextGroup) throw new IllegalArgumentException("Group did not start at correct index! (Got " + g.getStart() + " but expected " + nextGroup + ")");
if(g.getCount() < 0) throw new IllegalArgumentException("Group has a negative count! (" + g.getCount() + ")");
nextGroup += g.getCount();
}
//collect values in arrays
float[] position = new float[positionList.size()];
for (int i = 0; i < position.length; i++) {
position[i] = positionList.get(i);
}
float[] normal = new float[normalList.size()];
for (int i = 0; i < normal.length; i++) {
normal[i] = normalList.get(i);
}
float[] color = new float[colorList.size()];
for (int i = 0; i < color.length; i++) {
color[i] = colorList.get(i);
}
float[] uv = new float[uvList.size()];
for (int i = 0; i < uv.length; i++) {
uv[i] = uvList.get(i);
}
return new BufferGeometry(position, normal, color, uv,
groups.toArray(new MaterialGroup[groups.size()])
);
}
private static void json2FloatList(JsonReader json, List<Float> list) throws IOException {
json.beginObject(); //root
while (json.hasNext()){
String name = json.nextName();
if(name.equals("array")){
json.beginArray(); //array
while (json.hasNext()){
list.add(new Float(json.nextDouble()));
}
json.endArray(); //array
}
else json.skipValue();
}
json.endObject(); //root
}
private static void floatArray2Json(JsonWriter json, float[] array, int itemSize, boolean normalized) throws IOException {
json.beginObject();
json.name("type").value("Float32Array");
json.name("itemSize").value(itemSize);
json.name("normalized").value(normalized);
json.name("array").beginArray();
for (int i = 0; i < array.length; i++){
//rounding and remove ".0" to save string space
double d = GenericMath.round(array[i], 3);
if (d == (int) d) json.value((int) d);
else json.value(d);
}
json.endArray();
json.endObject();
}
public static class MaterialGroup {
private int materialIndex;
private int start;
private int count;
public MaterialGroup(int materialIndex, int start, int count) {
this.materialIndex = materialIndex;
this.start = start;
this.count = count;
}
public int getMaterialIndex() {
return materialIndex;
}
public int getStart() {
return start;
}
public int getCount() {
return count;
}
public void setMaterialIndex(int materialIndex) {
this.materialIndex = materialIndex;
}
public void setStart(int start) {
this.start = start;
}
public void setCount(int count) {
this.count = count;
}
}
}

View File

@ -0,0 +1,438 @@
/*
* This file is part of SpongeAPI, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* 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.core.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Optional;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
/**
* An axis aligned bounding box. That is, an un-rotated cuboid.
* It is represented by its minimum and maximum corners.
*
* <p>The box will never be degenerate: the corners are always not equal and
* respect the minimum and maximum properties.</p>
*
* <p>This class is immutable, all objects returned are either new instances or
* itself.</p>
*/
public class AABB {
private final Vector3d min;
private final Vector3d max;
private Vector3d size = null;
private Vector3d center = null;
/**
* Constructs a new bounding box from two opposite corners.
* Fails the resulting box would be degenerate (a dimension is 0).
*
* @param firstCorner The first corner
* @param secondCorner The second corner
*/
public AABB(Vector3i firstCorner, Vector3i secondCorner) {
this(checkNotNull(firstCorner, "firstCorner").toDouble(), checkNotNull(secondCorner, "secondCorner").toDouble());
}
/**
* Constructs a new bounding box from two opposite corners.
* Fails the resulting box would be degenerate (a dimension is 0).
*
* @param x1 The first corner x coordinate
* @param y1 The first corner y coordinate
* @param z1 The first corner z coordinate
* @param x2 The second corner x coordinate
* @param y2 The second corner y coordinate
* @param z2 The second corner z coordinate
*/
public AABB(double x1, double y1, double z1, double x2, double y2, double z2) {
this(new Vector3d(x1, y1, z1), new Vector3d(x2, y2, z2));
}
/**
* Constructs a new bounding box from two opposite corners.
* Fails the resulting box would be degenerate (a dimension is 0).
*
* @param firstCorner The first corner
* @param secondCorner The second corner
*/
public AABB(Vector3d firstCorner, Vector3d secondCorner) {
checkNotNull(firstCorner, "firstCorner");
checkNotNull(secondCorner, "secondCorner");
this.min = firstCorner.min(secondCorner);
this.max = firstCorner.max(secondCorner);
checkArgument(this.min.getX() != this.max.getX(), "The box is degenerate on x");
checkArgument(this.min.getY() != this.max.getY(), "The box is degenerate on y");
checkArgument(this.min.getZ() != this.max.getZ(), "The box is degenerate on z");
}
/**
* The minimum corner of the box.
*
* @return The minimum corner
*/
public Vector3d getMin() {
return this.min;
}
/**
* The maximum corner of the box.
*
* @return The maximum corner
*/
public Vector3d getMax() {
return this.max;
}
/**
* Returns the center of the box, halfway between each corner.
*
* @return The center
*/
public Vector3d getCenter() {
if (this.center == null) {
this.center = this.min.add(getSize().div(2));
}
return this.center;
}
/**
* Gets the size of the box.
*
* @return The size
*/
public Vector3d getSize() {
if (this.size == null) {
this.size = this.max.sub(this.min);
}
return this.size;
}
/**
* Checks if the bounding box contains a point.
*
* @param point The point to check
* @return Whether or not the box contains the point
*/
public boolean contains(Vector3i point) {
checkNotNull(point, "point");
return contains(point.getX(), point.getY(), point.getZ());
}
/**
* Checks if the bounding box contains a point.
*
* @param point The point to check
* @return Whether or not the box contains the point
*/
public boolean contains(Vector3d point) {
checkNotNull(point, "point");
return contains(point.getX(), point.getY(), point.getZ());
}
/**
* Checks if the bounding box contains a point.
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
* @param z The z coordinate of the point
* @return Whether or not the box contains the point
*/
public boolean contains(double x, double y, double z) {
return this.min.getX() <= x && this.max.getX() >= x
&& this.min.getY() <= y && this.max.getY() >= y
&& this.min.getZ() <= z && this.max.getZ() >= z;
}
/**
* Checks if the bounding box intersects another.
*
* @param other The other bounding box to check
* @return Whether this bounding box intersects with the other
*/
public boolean intersects(AABB other) {
checkNotNull(other, "other");
return this.max.getX() >= other.getMin().getX() && other.getMax().getX() >= this.min.getX()
&& this.max.getY() >= other.getMin().getY() && other.getMax().getY() >= this.min.getY()
&& this.max.getZ() >= other.getMin().getZ() && other.getMax().getZ() >= this.min.getZ();
}
/**
* Tests for intersection between the box and a ray defined by a starting
* point and a direction.
*
* @param start The starting point of the ray
* @param direction The direction of the ray
* @return An intersection point, if any
*/
public Optional<IntersectionPoint> intersects(Vector3d start, Vector3d direction) {
checkNotNull(start, "start");
checkNotNull(direction, "direction");
// Adapted from: https://github.com/flow/react/blob/develop/src/main/java/com/flowpowered/react/collision/RayCaster.java#L156
// The box is interpreted as 6 infinite perpendicular places, one for each face (being expanded infinitely)
// "t" variables are multipliers: start + direction * t gives the intersection point
// Find the intersections on the -x and +x planes, oriented by direction
final double txMin;
final double txMax;
final Vector3d xNormal;
if (Math.copySign(1, direction.getX()) > 0) {
txMin = (this.min.getX() - start.getX()) / direction.getX();
txMax = (this.max.getX() - start.getX()) / direction.getX();
xNormal = Vector3d.UNIT_X;
} else {
txMin = (this.max.getX() - start.getX()) / direction.getX();
txMax = (this.min.getX() - start.getX()) / direction.getX();
xNormal = Vector3d.UNIT_X.negate();
}
// Find the intersections on the -y and +y planes, oriented by direction
final double tyMin;
final double tyMax;
final Vector3d yNormal;
if (Math.copySign(1, direction.getY()) > 0) {
tyMin = (this.min.getY() - start.getY()) / direction.getY();
tyMax = (this.max.getY() - start.getY()) / direction.getY();
yNormal = Vector3d.UNIT_Y;
} else {
tyMin = (this.max.getY() - start.getY()) / direction.getY();
tyMax = (this.min.getY() - start.getY()) / direction.getY();
yNormal = Vector3d.UNIT_Y.negate();
}
// The ray should intersect the -x plane before the +y plane and intersect
// the -y plane before the +x plane, else it is outside the box
if (txMin > tyMax || txMax < tyMin) {
return Optional.empty();
}
// Keep track of the intersection normal which also helps with floating point errors
Vector3d normalMax;
Vector3d normalMin;
// The ray intersects only the furthest min plane on the box and only the closest
// max plane on the box
double tMin;
if (tyMin == txMin) {
tMin = tyMin;
normalMin = xNormal.negate().sub(yNormal);
} else if (tyMin > txMin) {
tMin = tyMin;
normalMin = yNormal.negate();
} else {
tMin = txMin;
normalMin = xNormal.negate();
}
double tMax;
if (tyMax == txMax) {
tMax = tyMax;
normalMax = xNormal.add(yNormal);
} else if (tyMax < txMax) {
tMax = tyMax;
normalMax = yNormal;
} else {
tMax = txMax;
normalMax = xNormal;
}
// Find the intersections on the -z and +z planes, oriented by direction
final double tzMin;
final double tzMax;
final Vector3d zNormal;
if (Math.copySign(1, direction.getZ()) > 0) {
tzMin = (this.min.getZ() - start.getZ()) / direction.getZ();
tzMax = (this.max.getZ() - start.getZ()) / direction.getZ();
zNormal = Vector3d.UNIT_Z;
} else {
tzMin = (this.max.getZ() - start.getZ()) / direction.getZ();
tzMax = (this.min.getZ() - start.getZ()) / direction.getZ();
zNormal = Vector3d.UNIT_Z.negate();
}
// The ray intersects only the furthest min plane on the box and only the closest
// max plane on the box
if (tMin > tzMax || tMax < tzMin) {
return Optional.empty();
}
// The ray should intersect the closest plane outside first and the furthest
// plane outside last
if (tzMin == tMin) {
normalMin = normalMin.sub(zNormal);
} else if (tzMin > tMin) {
tMin = tzMin;
normalMin = zNormal.negate();
}
if (tzMax == tMax) {
normalMax = normalMax.add(zNormal);
} else if (tzMax < tMax) {
tMax = tzMax;
normalMax = zNormal;
}
// Both intersection points are behind the start, there are no intersections
if (tMax < 0) {
return Optional.empty();
}
// Find the final intersection multiplier and normal
final double t;
Vector3d normal;
if (tMin < 0) {
// Only the furthest intersection is after the start, so use it
t = tMax;
normal = normalMax;
} else {
// Both are after the start, use the closest one
t = tMin;
normal = normalMin;
}
normal = normal.normalize();
// To avoid rounding point errors leaving the intersection point just off the plane
// we check the normal to use the actual plane value from the box coordinates
final double x;
final double y;
final double z;
if (normal.getX() > 0) {
x = this.max.getX();
} else if (normal.getX() < 0) {
x = this.min.getX();
} else {
x = direction.getX() * t + start.getX();
}
if (normal.getY() > 0) {
y = this.max.getY();
} else if (normal.getY() < 0) {
y = this.min.getY();
} else {
y = direction.getY() * t + start.getY();
}
if (normal.getZ() > 0) {
z = this.max.getZ();
} else if (normal.getZ() < 0) {
z = this.min.getZ();
} else {
z = direction.getZ() * t + start.getZ();
}
return Optional.of(new IntersectionPoint(new Vector3d(x, y, z), normal));
}
/**
* Offsets this bounding box by a given amount and returns a new box.
*
* @param offset The offset to apply
* @return The new offset box
*/
public AABB offset(Vector3i offset) {
checkNotNull(offset, "offset");
return offset(offset.getX(), offset.getY(), offset.getZ());
}
/**
* Offsets this bounding box by a given amount and returns a new box.
*
* @param offset The offset to apply
* @return The new offset box
*/
public AABB offset(Vector3d offset) {
checkNotNull(offset, "offset");
return offset(offset.getX(), offset.getY(), offset.getZ());
}
/**
* Offsets this bounding box by a given amount and returns a new box.
*
* @param x The amount of offset for the x coordinate
* @param y The amount of offset for the y coordinate
* @param z The amount of offset for the z coordinate
* @return The new offset box
*/
public AABB offset(double x, double y, double z) {
return new AABB(this.min.add(x, y, z), this.max.add(x, y, z));
}
/**
* Expands this bounding box by a given amount in both directions and
* returns a new box. The expansion is applied half and half to the
* minimum and maximum corners.
*
* @param amount The amount of expansion to apply
* @return The new expanded box
*/
public AABB expand(Vector3i amount) {
checkNotNull(amount, "amount");
return expand(amount.getX(), amount.getY(), amount.getZ());
}
/**
* Expands this bounding box by a given amount in both directions and
* returns a new box. The expansion is applied half and half to the
* minimum and maximum corners.
*
* @param amount The amount of expansion to apply
* @return The new expanded box
*/
public AABB expand(Vector3d amount) {
checkNotNull(amount, "amount");
return expand(amount.getX(), amount.getY(), amount.getZ());
}
/**
* Expands this bounding box by a given amount in both directions and
* returns a new box. The expansion is applied half and half to the
* minimum and maximum corners.
*
* @param x The amount of expansion for the x coordinate
* @param y The amount of expansion for the y coordinate
* @param z The amount of expansion for the z coordinate
* @return The new expanded box
*/
public AABB expand(double x, double y, double z) {
x /= 2;
y /= 2;
z /= 2;
return new AABB(this.min.sub(x, y, z), this.max.add(x, y, z));
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AABB)) {
return false;
}
final AABB aabb = (AABB) other;
return this.min.equals(aabb.min) && this.max.equals(aabb.max);
}
@Override
public int hashCode() {
int result = this.min.hashCode();
result = 31 * result + this.max.hashCode();
return result;
}
@Override
public String toString() {
return "AABB(" + this.min + " to " + this.max + ")";
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.core.util;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.base.Preconditions;
public enum Axis {
X (Vector3i.UNIT_X),
Y (Vector3i.UNIT_Y),
Z (Vector3i.UNIT_Z);
private final Vector3i axisVector;
Axis(Vector3i axisVector){
this.axisVector = axisVector;
}
public Vector3i toVector(){
return axisVector;
}
public static Axis fromString(String name){
Preconditions.checkNotNull(name);
return valueOf(name.toUpperCase());
}
}

View File

@ -0,0 +1,135 @@
/*
* 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.core.util;
import java.util.List;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import com.flowpowered.math.vector.Vector4i;
import ninja.leaping.configurate.ConfigurationNode;
public class ConfigUtil {
private ConfigUtil(){}
public static Vector2i readVector2i(ConfigurationNode vectorNode){
if (vectorNode.hasListChildren()){
List<? extends ConfigurationNode> list = vectorNode.getChildrenList();
return new Vector2i(
list.get(0).getInt(),
list.get(1).getInt()
);
}
return new Vector2i(
vectorNode.getNode("x").getInt(),
vectorNode.getNode("y").getInt()
);
}
public static Vector3i readVector3i(ConfigurationNode vectorNode){
if (vectorNode.hasListChildren()){
List<? extends ConfigurationNode> list = vectorNode.getChildrenList();
return new Vector3i(
list.get(0).getInt(),
list.get(1).getInt(),
list.get(2).getInt()
);
}
return new Vector3i(
vectorNode.getNode("x").getInt(),
vectorNode.getNode("y").getInt(),
vectorNode.getNode("z").getInt()
);
}
public static Vector3f readVector3f(ConfigurationNode vectorNode){
if (vectorNode.hasListChildren()){
List<? extends ConfigurationNode> list = vectorNode.getChildrenList();
return new Vector3f(
list.get(0).getFloat(),
list.get(1).getFloat(),
list.get(2).getFloat()
);
}
return new Vector3f(
vectorNode.getNode("x").getFloat(),
vectorNode.getNode("y").getFloat(),
vectorNode.getNode("z").getFloat()
);
}
public static Vector4i readVector4i(ConfigurationNode vectorNode){
if (vectorNode.hasListChildren()){
List<? extends ConfigurationNode> list = vectorNode.getChildrenList();
return new Vector4i(
list.get(0).getInt(),
list.get(1).getInt(),
list.get(2).getInt(),
list.get(3).getInt()
);
}
return new Vector4i(
vectorNode.getNode("x").getInt(),
vectorNode.getNode("y").getInt(),
vectorNode.getNode("z").getInt(),
vectorNode.getNode("w").getInt()
);
}
public static Vector4f readVector4f(ConfigurationNode vectorNode){
if (vectorNode.hasListChildren()){
List<? extends ConfigurationNode> list = vectorNode.getChildrenList();
return new Vector4f(
list.get(0).getFloat(),
list.get(1).getFloat(),
list.get(2).getFloat(),
list.get(3).getFloat()
);
}
return new Vector4f(
vectorNode.getNode("x").getFloat(),
vectorNode.getNode("y").getFloat(),
vectorNode.getNode("z").getFloat(),
vectorNode.getNode("w").getFloat()
);
}
public static void writeVector4f(ConfigurationNode vectorNode, Vector4f v){
vectorNode.getAppendedNode().setValue(v.getX());
vectorNode.getAppendedNode().setValue(v.getY());
vectorNode.getAppendedNode().setValue(v.getZ());
vectorNode.getAppendedNode().setValue(v.getW());
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.core.util;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.base.Preconditions;
public enum Direction {
UP ( 0, 1, 0, Axis.Y),
DOWN ( 0,-1, 0, Axis.Y),
NORTH ( 0, 0,-1, Axis.Z),
SOUTH ( 0, 0, 1, Axis.Z),
WEST (-1, 0, 0, Axis.X),
EAST ( 1, 0, 0, Axis.X);
static {
UP.opposite = DOWN;
DOWN.opposite = UP;
NORTH.opposite = SOUTH;
SOUTH.opposite = NORTH;
WEST.opposite = EAST;
EAST.opposite = WEST;
}
private Vector3i dir;
private Axis axis;
private Direction opposite;
private Direction(int x, int y, int z, Axis axis) {
this.dir = new Vector3i(x, y, z);
this.axis = axis;
this.opposite = null;
}
public Vector3i toVector(){
return dir;
}
public Direction opposite() {
return opposite;
}
public Axis getAxis() {
return axis;
}
public static Direction fromString(String name){
Preconditions.checkNotNull(name);
return valueOf(name.toUpperCase());
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.core.util;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.flowpowered.math.vector.Vector2i;
public class FileUtil {
private FileUtil(){}
public static File coordsToFile(Path root, Vector2i coords, String fileType){
String path = "x" + coords.getX() + "z" + coords.getY();
char[] cs = path.toCharArray();
List<String> folders = new ArrayList<>();
String folder = "";
for (char c : cs){
folder += c;
if (c >= '0' && c <= '9'){
folders.add(folder);
folder = "";
}
}
String fileName = folders.remove(folders.size() - 1);
Path p = root;
for (String s : folders){
p = p.resolve(s);
}
return p.resolve(fileName + "." + fileType).toFile();
}
/**
* Blocks until a file can be read and written.<br>
* <i>(Do not use this method to sync file-access from different threads!)</i>
*/
public static void waitForFile(File file, long time, TimeUnit unit) throws InterruptedException {
long start = System.currentTimeMillis();
long timeout = start + TimeUnit.MILLISECONDS.convert(time, unit);
long sleepTime = 1;
while(!file.canWrite() || !file.canRead()){
Thread.sleep(sleepTime);
sleepTime = (long) Math.min(Math.ceil(sleepTime * 1.5), 1000);
if (System.currentTimeMillis() > timeout) throw new InterruptedException();
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.core.util;
import com.flowpowered.math.vector.Vector3d;
public class IntersectionPoint {
private final Vector3d intersection;
private final Vector3d normal;
public IntersectionPoint(Vector3d intersection, Vector3d normal){
this.intersection = intersection;
this.normal = normal;
}
public Vector3d getIntersection() {
return intersection;
}
public Vector3d getNormal() {
return normal;
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.core.util;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
public class MathUtil {
private MathUtil() {}
public static Vector3d getSurfaceNormal(Vector3d p1, Vector3d p2, Vector3d p3){
Vector3d u = p2.sub(p1);
Vector3d v = p3.sub(p1);
double nX = u.getY() * v.getZ() - u.getZ() * v.getY();
double nY = u.getZ() * v.getX() - u.getX() * v.getZ();
double nZ = u.getX() * v.getY() - u.getY() * v.getX();
return new Vector3d(nX, nY, nZ);
}
public static Vector3f getSurfaceNormal(Vector3f p1, Vector3f p2, Vector3f p3) {
Vector3f u = p2.sub(p1);
Vector3f v = p3.sub(p1);
float nX = u.getY() * v.getZ() - u.getZ() * v.getY();
float nY = u.getZ() * v.getX() - u.getX() * v.getZ();
float nZ = u.getX() * v.getY() - u.getY() * v.getX();
Vector3f n = new Vector3f(nX, nY, nZ);
n = n.normalize();
return n;
}
public static float hashToFloat(Vector3i pos, long seed) {
return hashToFloat(pos.getX(), pos.getY(), pos.getZ(), seed);
}
/**
* Adapted from https://github.com/SpongePowered/SpongeAPI/blob/ecd761a70219e467dea47a09fc310e8238e9911f/src/main/java/org/spongepowered/api/extra/skylands/SkylandsUtil.java
*/
public static float hashToFloat(int x, int y, int z, long seed) {
final long hash = x * 73428767 ^ y * 9122569 ^ z * 4382893 ^ seed * 457;
return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000;
}
public static Vector4f blendColors(Vector4f top, Vector4f bottom){
if (top.getW() > 0 && bottom.getW() > 0){
float a = 1 - (1 - top.getW()) * (1 - bottom.getW());
float r = (top.getX() * top.getW() / a) + (bottom.getX() * bottom.getW() * (1 - top.getW()) / a);
float g = (top.getY() * top.getW() / a) + (bottom.getY() * bottom.getW() * (1 - top.getW()) / a);
float b = (top.getZ() * top.getW() / a) + (bottom.getZ() * bottom.getW() * (1 - top.getW()) / a);
return new Vector4f(r, g, b, a);
} else if (bottom.getW() > 0) {
return bottom;
} else {
return top;
}
}
public static Vector4f overlayColors(Vector4f top, Vector4f bottom){
if (top.getW() > 0 && bottom.getW() > 0){
float p = (1 - top.getW()) * bottom.getW();
float a = p + top.getW();
float r = (p * bottom.getX() + top.getW() * top.getX()) / a;
float g = (p * bottom.getY() + top.getW() * top.getY()) / a;
float b = (p * bottom.getZ() + top.getW() * top.getZ()) / a;
return new Vector4f(r, g, b, a);
} else if (bottom.getW() > 0) {
return bottom;
} else {
return top;
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.core.util;
import com.flowpowered.math.vector.Vector2f;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.model.Face;
import de.bluecolored.bluemap.core.model.Model;
public class ModelUtils {
private ModelUtils() {}
/**
* Creates a plane-grid with alternating face-rotations.
*/
public static Model makeGrid(Vector2i gridSize){
Model m = new Model();
float y = 0;
for (int x = 0; x < gridSize.getX(); x++){
for (int z = 0; z < gridSize.getY(); z++){
Vector3f[] p = new Vector3f[]{
new Vector3f(x , y, z + 1),
new Vector3f(x + 1, y, z + 1),
new Vector3f(x + 1, y, z ),
new Vector3f(x , y, z ),
};
Vector2f[] uv = new Vector2f[]{
new Vector2f(0, 1),
new Vector2f(1, 1),
new Vector2f(1, 0),
new Vector2f(0, 0),
};
Face f1, f2;
if (x % 2 == z % 2){
f1 = new Face(p[0], p[1], p[2], uv[0], uv[1], uv[2], -1);
f2 = new Face(p[0], p[2], p[3], uv[0], uv[2], uv[3], -1);
} else {
f1 = new Face(p[0], p[1], p[3], uv[0], uv[1], uv[3], -1);
f2 = new Face(p[1], p[2], p[3], uv[1], uv[2], uv[3], -1);
}
Vector3f color = Vector3f.ZERO;
f1.setC1(color);
f1.setC2(color);
f1.setC3(color);
f2.setC1(color);
f2.setC2(color);
f2.setC3(color);
m.addFace(f1);
m.addFace(f2);
}
}
return m;
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.core.util;
import java.lang.ref.WeakReference;
public class UpdateDeamon extends Thread {
public WeakReference<Updateable> subject;
public long frequency;
public UpdateDeamon(Runnable subject, long frequency) {
this((Updateable) (()->subject.run()), frequency);
}
public UpdateDeamon(Updateable subject, long frequency) {
this.subject = new WeakReference<Updateable>(subject);
this.frequency = frequency;
this.setDaemon(true);
}
@Override
public void run() {
try {
while (true) {
Thread.sleep(frequency);
subject.get().update();
}
} catch (NullPointerException | InterruptedException ex) {}
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.core.util;
public interface Updateable {
public void update();
}

View File

@ -0,0 +1,76 @@
/*
* 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.core.util;
import java.util.ArrayList;
import java.util.List;
public class WeighedArrayList<E> extends ArrayList<E> implements List<E> {
private static final long serialVersionUID = 1L;
public WeighedArrayList() {}
public WeighedArrayList(int capacity) {
super(capacity);
}
/**
* Adds the element weight times to this list.
* @return Always true
*/
public void add(E e, int weight) {
for (int i = 0; i < weight; i++){
add(e);
}
}
/**
* Removes the first weight number of items that equal o from this list.<br>
* @return The number of elements removed.
*/
public int remove(Object o, int weight) {
int removed = 0;
if (o == null){
for (int i = 0; i < size(); i++){
if (get(i) == null){
remove(i);
removed++;
if (removed >= weight) break;
}
}
} else {
for (int i = 0; i < size(); i++){
if (o.equals(get(i))){
remove(i);
removed++;
if (removed >= weight) break;
}
}
}
return removed;
}
}

View File

@ -0,0 +1,276 @@
/*
* 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.core.web;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import de.bluecolored.bluemap.core.webserver.HttpRequest;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import de.bluecolored.bluemap.core.webserver.HttpResponse;
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
public class BlueMapWebRequestHandler implements HttpRequestHandler {
private static final long DEFLATE_MIN_SIZE = 10L * 1024L;
private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L;
private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L;
private Path webRoot;
public BlueMapWebRequestHandler(Path webRoot) {
this.webRoot = webRoot;
}
@Override
public HttpResponse handle(HttpRequest request) {
if (
!request.getMethod().equalsIgnoreCase("GET") &&
!request.getMethod().equalsIgnoreCase("POST")
) return new HttpResponse(HttpStatusCode.NOT_IMPLEMENTED);
HttpResponse response = generateResponse(request);
response.addHeader("Server", "BlueMap/WebServer");
HttpStatusCode status = response.getStatusCode();
if (status.getCode() >= 400){
response.setData(status.getCode() + " - " + status.getMessage() + "\nBlueMap/Webserver");
}
return response;
}
private HttpResponse generateResponse(HttpRequest request) {
String adress = request.getPath();
if (adress.isEmpty()) adress = "/";
String[] adressParts = adress.split("\\?", 2);
String path = adressParts[0];
String getParamString = adressParts.length > 1 ? adressParts[1] : "";
Map<String, String> getParams = new HashMap<>();
for (String getParam : getParamString.split("&")){
if (getParam.isEmpty()) continue;
String[] kv = getParam.split("=", 2);
String key = kv[0];
String value = kv.length > 1 ? kv[1] : "";
getParams.put(key, value);
}
if (path.startsWith("/")) path = path.substring(1);
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
Path filePath = webRoot;
try {
filePath = webRoot.resolve(path);
} catch (InvalidPathException e){
return new HttpResponse(HttpStatusCode.NOT_FOUND);
}
//can we use deflation?
boolean isDeflationPossible = request.getLowercaseHeader("Accept-Encoding").contains("gzip");
boolean isDeflated = false;
//check if file is in web-root
if (!filePath.normalize().startsWith(webRoot.normalize())){
return new HttpResponse(HttpStatusCode.FORBIDDEN);
}
File file = filePath.toFile();
if (!file.exists() || file.isDirectory()){
file = new File(filePath.toString() + ".gz");
isDeflated = true;
}
if (!file.exists() || file.isDirectory()){
file = new File(filePath.toString() + "/index.html");
isDeflated = false;
}
if (!file.exists() || file.isDirectory()){
file = new File(filePath.toString() + "/index.html.gz");
isDeflated = true;
}
if (!file.exists()){
return new HttpResponse(HttpStatusCode.NOT_FOUND);
}
if (isDeflationPossible && (!file.getName().endsWith(".gz"))){
File deflatedFile = new File(file.getAbsolutePath() + ".gz");
if (deflatedFile.exists()){
file = deflatedFile;
isDeflated = true;
}
}
//check if file is still in web-root
if (!file.toPath().normalize().startsWith(webRoot.normalize())){
return new HttpResponse(HttpStatusCode.FORBIDDEN);
}
//check modified
long lastModified = file.lastModified();
Set<String> modStringSet = request.getHeader("If-Modified-Since");
if (!modStringSet.isEmpty()){
try {
long since = stringToTimestamp(modStringSet.iterator().next());
if (since + 1000 >= lastModified){
return new HttpResponse(HttpStatusCode.NOT_MODIFIED);
}
} catch (IllegalArgumentException e){}
}
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
if (lastModified > 0) response.addHeader("Last-Modified", timestampToString(lastModified));
//add content type header
String filetype = file.getName().toString();
if (filetype.endsWith(".gz")) filetype = filetype.substring(3);
int pointIndex = filetype.lastIndexOf('.');
if (pointIndex >= 0) filetype = filetype.substring(pointIndex + 1);
String contentType = "text/plain";
switch (filetype) {
case "json" :
contentType = "application/json";
break;
case "png" :
contentType = "image/png";
break;
case "jpg" :
case "jpeg" :
case "jpe" :
contentType = "image/jpeg";
break;
case "svg" :
contentType = "image/svg+xml";
break;
case "css" :
contentType = "text/css";
break;
case "js" :
contentType = "text/javascript";
break;
case "html" :
case "htm" :
case "shtml" :
contentType = "text/html";
break;
case "xml" :
contentType = "text/xml";
break;
}
response.addHeader("Content-Type", contentType);
try {
if (isDeflated){
if (isDeflationPossible || file.length() > INFLATE_MAX_SIZE){
response.addHeader("Content-Encoding", "gzip");
response.setData(new FileInputStream(file));
return response;
} else {
response.setData(new GZIPInputStream(new FileInputStream(file)));
return response;
}
} else {
if (isDeflationPossible && file.length() > DEFLATE_MIN_SIZE && file.length() < DEFLATE_MAX_SIZE){
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
GZIPOutputStream zos = new GZIPOutputStream(byteOut);
IOUtils.copyLarge(fis, zos);
zos.close();
fis.close();
byte[] compressedData = byteOut.toByteArray();
response.setData(new ByteArrayInputStream(compressedData));
response.addHeader("Content-Encoding", "gzip");
return response;
} else {
response.setData(new FileInputStream(file));
return response;
}
}
} catch (FileNotFoundException e) {
return new HttpResponse(HttpStatusCode.NOT_FOUND);
} catch (IOException e) {
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
}
}
private static String timestampToString(long time){
return DateFormatUtils.format(time, "EEE, dd MMM yyy HH:mm:ss 'GMT'", TimeZone.getTimeZone("GMT"), Locale.ENGLISH);
}
private static long stringToTimestamp(String timeString) throws IllegalArgumentException {
try {
int day = Integer.parseInt(timeString.substring(5, 7));
int month = 1;
switch (timeString.substring(8, 11)){
case "Jan" : month = 0; break;
case "Feb" : month = 1; break;
case "Mar" : month = 2; break;
case "Apr" : month = 3; break;
case "May" : month = 4; break;
case "Jun" : month = 5; break;
case "Jul" : month = 6; break;
case "Aug" : month = 7; break;
case "Sep" : month = 8; break;
case "Oct" : month = 9; break;
case "Nov" : month = 10; break;
case "Dec" : month = 11; break;
}
int year = Integer.parseInt(timeString.substring(12, 16));
int hour = Integer.parseInt(timeString.substring(17, 19));
int min = Integer.parseInt(timeString.substring(20, 22));
int sec = Integer.parseInt(timeString.substring(23, 25));
GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT"));
cal.set(year, month, day, hour, min, sec);
return cal.getTimeInMillis();
} catch (NumberFormatException | IndexOutOfBoundsException e){
throw new IllegalArgumentException(e);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.core.web;
import java.io.IOException;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.webserver.WebServer;
public class BlueMapWebServer extends WebServer {
private WebFilesManager webFilesManager;
public BlueMapWebServer(WebServerConfig config) {
super(
config.getWebserverPort(),
config.getWebserverMaxConnections(),
config.getWebserverBindAdress(),
new BlueMapWebRequestHandler(config.getWebRoot())
);
this.webFilesManager = new WebFilesManager(config.getWebRoot());
}
public void updateWebfiles() throws IOException {
if (webFilesManager.needsUpdate()) {
Logger.global.logInfo("Webfiles are missing or outdated, updating...");
webFilesManager.updateFiles();
}
}
}

View File

@ -0,0 +1,73 @@
/*
* 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.core.web;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.FileUtils;
public class WebFilesManager {
private Path webRoot;
public WebFilesManager(Path webRoot) {
this.webRoot = webRoot;
}
public boolean needsUpdate() {
if (!webRoot.resolve("index.html").toFile().exists()) return true;
return false;
}
public void updateFiles() throws IOException {
URL fileResource = getClass().getResource("/webroot.zip");
File tempFile = File.createTempFile("bluemap_webroot_extraction", null);
try {
FileUtils.copyURLToFile(fileResource, tempFile, 10000, 10000);
try (ZipFile zipFile = new ZipFile(tempFile)){
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while(entries.hasMoreElements()) {
ZipEntry zipEntry = entries.nextElement();
if (zipEntry.isDirectory()) webRoot.resolve(zipEntry.getName()).toFile().mkdirs();
else {
File target = webRoot.resolve(zipEntry.getName()).toFile();
FileUtils.copyInputStreamToFile(zipFile.getInputStream(zipEntry), target);
}
}
}
} finally {
tempFile.delete();
}
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.core.web;
import java.net.InetAddress;
import java.nio.file.Path;
public interface WebServerConfig {
Path getWebRoot();
InetAddress getWebserverBindAdress();
int getWebserverPort();
int getWebserverMaxConnections();
}

View File

@ -0,0 +1,132 @@
/*
* 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.core.web;
import java.io.File;
import java.io.IOException;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.render.TileRenderer;
import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader;
public class WebSettings {
private ConfigurationLoader<? extends ConfigurationNode> configLoader;
private ConfigurationNode rootNode;
public WebSettings(File settingsFile) throws IOException {
if (!settingsFile.exists()) {
settingsFile.getParentFile().mkdirs();
settingsFile.createNewFile();
}
configLoader = GsonConfigurationLoader.builder()
.setFile(settingsFile)
.build();
load();
}
public void load() throws IOException {
rootNode = configLoader.load();
}
public void save() throws IOException {
configLoader.save(rootNode);
}
public void set(Object value, Object... path) {
rootNode.getNode(path).setValue(value);
}
public Object get(Object... path) {
return rootNode.getNode(path).getValue();
}
public String getString(Object... path) {
return rootNode.getNode(path).getString();
}
public long getInt(Object... path) {
return rootNode.getNode(path).getInt();
}
public long getLong(Object... path) {
return rootNode.getNode(path).getLong();
}
public double getFloat(Object... path) {
return rootNode.getNode(path).getFloat();
}
public double getDouble(Object... path) {
return rootNode.getNode(path).getDouble();
}
public void setFrom(TileRenderer tileRenderer, String mapId) {
Vector2i hiresTileSize = tileRenderer.getHiresModelManager().getTileSize();
Vector2i gridOrigin = tileRenderer.getHiresModelManager().getGridOrigin();
Vector2i lowresTileSize = tileRenderer.getLowresModelManager().getTileSize();
Vector2i lowresPointsPerHiresTile = tileRenderer.getLowresModelManager().getPointsPerHiresTile();
set(hiresTileSize.getX(), mapId, "hires", "tileSize", "x");
set(hiresTileSize.getY(), mapId, "hires", "tileSize", "z");
set(1, mapId, "hires", "scale", "x");
set(1, mapId, "hires", "scale", "z");
set(gridOrigin.getX(), mapId, "hires", "translate", "x");
set(gridOrigin.getY(), mapId, "hires", "translate", "z");
Vector2i pointSize = hiresTileSize.div(lowresPointsPerHiresTile);
Vector2i tileSize = pointSize.mul(lowresTileSize);
set(tileSize.getX(), mapId, "lowres", "tileSize", "x");
set(tileSize.getY(), mapId, "lowres", "tileSize", "z");
set(pointSize.getX(), mapId, "lowres", "scale", "x");
set(pointSize.getY(), mapId, "lowres", "scale", "z");
set(pointSize.getX() / 2, mapId, "lowres", "translate", "x");
set(pointSize.getY() / 2, mapId, "lowres", "translate", "z");
}
public void setHiresViewDistance(float hiresViewDistance, String mapId) {
set(hiresViewDistance, mapId, "hires", "viewDistance");
}
public void setLowresViewDistance(float lowresViewDistance, String mapId) {
set(lowresViewDistance, mapId, "lowres", "viewDistance");
}
public void setName(String name, String mapId) {
set(name, mapId, "name");
}
public String getName(String mapId) {
return getString(mapId, "name");
}
}

View File

@ -0,0 +1,126 @@
/*
* 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.core.webserver;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
import de.bluecolored.bluemap.core.logger.Logger;
public class HttpConnection implements Runnable {
private HttpRequestHandler handler;
private ServerSocket server;
private Socket connection;
private InputStream in;
private OutputStream out;
public HttpConnection(ServerSocket server, Socket connection, HttpRequestHandler handler, int timeout, TimeUnit timeoutUnit) throws IOException {
this.server = server;
this.connection = connection;
this.handler = handler;
if (isClosed()){
throw new IOException("Socket already closed!");
}
connection.setSoTimeout((int) timeoutUnit.toMillis(timeout));
in = this.connection.getInputStream();
out = this.connection.getOutputStream();
}
@Override
public void run() {
while (!isClosed() && !server.isClosed()){
try {
HttpRequest request = acceptRequest();
HttpResponse response = handler.handle(request);
sendResponse(response);
} catch (InvalidRequestException e){
try {
sendResponse(new HttpResponse(HttpStatusCode.BAD_REQUEST));
} catch (IOException e1) {}
break;
} catch (SocketTimeoutException e) {
break;
} catch (SocketException e){
break;
} catch (ConnectionClosedException e){
break;
} catch (IOException e) {
Logger.global.logError("Unexpected error while processing a HttpRequest!", e);
break;
}
}
try {
close();
} catch (IOException e){
Logger.global.logError("Error while closing HttpConnection!", e);
}
}
private void sendResponse(HttpResponse response) throws IOException {
response.write(out);
out.flush();
}
private HttpRequest acceptRequest() throws ConnectionClosedException, InvalidRequestException, IOException {
return HttpRequest.read(in);
}
public boolean isClosed(){
return !connection.isBound() || connection.isClosed() || !connection.isConnected() || connection.isOutputShutdown() || connection.isInputShutdown();
}
public void close() throws IOException {
try {
in.close();
} finally {
try {
out.close();
} finally {
connection.close();
}
}
}
public static class ConnectionClosedException extends IOException {
private static final long serialVersionUID = 1L;
}
public static class InvalidRequestException extends IOException {
private static final long serialVersionUID = 1L;
}
}

View File

@ -0,0 +1,206 @@
/*
* 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.core.webserver;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.bluecolored.bluemap.core.webserver.HttpConnection.ConnectionClosedException;
import de.bluecolored.bluemap.core.webserver.HttpConnection.InvalidRequestException;
public class HttpRequest {
private static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$");
private String method;
private String path;
private String version;
private Map<String, Set<String>> header;
private Map<String, Set<String>> headerLC;
private byte[] data;
public HttpRequest(String method, String path, String version, Map<String, Set<String>> header) {
this.method = method;
this.path = path;
this.version = version;
this.header = header;
this.headerLC = new HashMap<>();
for (Entry<String, Set<String>> e : header.entrySet()){
Set<String> values = new HashSet<>();
for (String v : e.getValue()){
values.add(v.toLowerCase());
}
headerLC.put(e.getKey().toLowerCase(), values);
}
this.data = new byte[0];
}
public String getMethod() {
return method;
}
public String getPath(){
return path;
}
public String getVersion() {
return version;
}
public Map<String, Set<String>> getHeader() {
return header;
}
public Map<String, Set<String>> getLowercaseHeader() {
return header;
}
public Set<String> getHeader(String key){
Set<String> headerValues = header.get(key);
if (headerValues == null) return Collections.emptySet();
return headerValues;
}
public Set<String> getLowercaseHeader(String key){
Set<String> headerValues = headerLC.get(key.toLowerCase());
if (headerValues == null) return Collections.emptySet();
return headerValues;
}
public InputStream getData(){
return new ByteArrayInputStream(data);
}
public static HttpRequest read(InputStream in) throws IOException, InvalidRequestException {
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
List<String> header = new ArrayList<>(20);
while(header.size() < 1000){
String headerLine = readLine(reader);
if (headerLine.isEmpty()) break;
header.add(headerLine);
}
if (header.isEmpty()) throw new InvalidRequestException();
Matcher m = REQUEST_PATTERN.matcher(header.remove(0));
if (!m.find()) throw new InvalidRequestException();
String method = m.group(1);
if (method == null) throw new InvalidRequestException();
String adress = m.group(2);
if (adress == null) throw new InvalidRequestException();
String version = m.group(3);
if (version == null) throw new InvalidRequestException();
Map<String, Set<String>> headerMap = new HashMap<String, Set<String>>();
for (String line : header){
if (line.trim().isEmpty()) continue;
String[] kv = line.split(":", 2);
if (kv.length < 2) continue;
Set<String> values = new HashSet<>();
if (kv[0].trim().equalsIgnoreCase("If-Modified-Since")){
values.add(kv[1].trim());
} else {
for(String v : kv[1].split(",")){
values.add(v.trim());
}
}
headerMap.put(kv[0].trim(), values);
}
HttpRequest request = new HttpRequest(method, adress, version, headerMap);
if (request.getLowercaseHeader("Transfer-Encoding").contains("chunked")){
try {
ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
while (dataStream.size() < 1000000){
String hexSize = reader.readLine();
int chunkSize = Integer.parseInt(hexSize, 16);
if (chunkSize <= 0) break;
byte[] data = new byte[chunkSize];
in.read(data);
dataStream.write(data);
}
if (dataStream.size() >= 1000000) {
throw new InvalidRequestException();
}
request.data = dataStream.toByteArray();
return request;
} catch (NumberFormatException ex){
return request;
}
} else {
Set<String> clSet = request.getLowercaseHeader("Content-Length");
if (clSet.isEmpty()){
return request;
} else {
try {
int cl = Integer.parseInt(clSet.iterator().next());
byte[] data = new byte[cl];
in.read(data);
request.data = data;
return request;
} catch (NumberFormatException ex){
return request;
}
}
}
}
private static String readLine(BufferedReader in) throws ConnectionClosedException, IOException {
String line = in.readLine();
if (line == null){
throw new ConnectionClosedException();
}
return line;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.core.webserver;
public interface HttpRequestHandler {
HttpResponse handle(HttpRequest request);
}

View File

@ -0,0 +1,149 @@
/*
* 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.core.webserver;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
public class HttpResponse {
private String version;
private HttpStatusCode statusCode;
private Map<String, Set<String>> header;
private InputStream data;
public HttpResponse(HttpStatusCode statusCode) {
this.version = "HTTP/1.1";
this.statusCode = statusCode;
this.header = new HashMap<>();
addHeader("Connection", "keep-alive");
}
public void addHeader(String key, String value){
Set<String> valueSet = header.get(key);
if (valueSet == null){
valueSet = new HashSet<>();
header.put(key, valueSet);
}
valueSet.add(value);
}
public void removeHeader(String key, String value){
Set<String> valueSet = header.get(key);
if (valueSet == null){
valueSet = new HashSet<>();
header.put(key, valueSet);
}
valueSet.remove(value);
}
public void setData(InputStream dataStream){
this.data = dataStream;
}
public void setData(String data){
setData(new ByteArrayInputStream(data.getBytes(StandardCharsets.UTF_8)));
}
/**
* Writes this Response to an Output-Stream.<br>
* <br>
* This method closes the data-Stream of this response so it can't be used again!
*/
public void write(OutputStream out) throws IOException {
OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8);
if (data != null){
addHeader("Transfer-Encoding", "chunked");
} else {
addHeader("Content-Length", "0");
}
writeLine(writer, version + " " + statusCode.getCode() + " " + statusCode.getMessage());
for (Entry<String, Set<String>> e : header.entrySet()){
if (e.getValue().isEmpty()) continue;
writeLine(writer, e.getKey() + ": " + StringUtils.join(e.getValue(), ", "));
}
writeLine(writer, "");
writer.flush();
if(data != null){
chunkedPipe(data, out);
out.flush();
data.close();
}
}
private void writeLine(OutputStreamWriter writer, String line) throws IOException {
writer.write(line + "\r\n");
}
private void chunkedPipe(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[1024];
int byteCount;
while ((byteCount = input.read(buffer)) != -1) {
output.write((Integer.toHexString(byteCount) + "\r\n").getBytes());
output.write(buffer, 0, byteCount);
output.write("\r\n".getBytes());
}
output.write("0\r\n\r\n".getBytes());
}
public HttpStatusCode getStatusCode(){
return statusCode;
}
public String getVersion(){
return version;
}
public Map<String, Set<String>> getHeader() {
return header;
}
public Set<String> getHeader(String key){
Set<String> headerValues = header.get(key);
if (headerValues == null) return Collections.emptySet();
return headerValues;
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.core.webserver;
public enum HttpStatusCode {
CONTINUE (100, "Continue"),
PROCESSING (102, "Processing"),
OK (200, "OK"),
MOVED_PERMANENTLY (301, "Moved Permanently"),
FOUND (302, "Found"),
SEE_OTHER (303, "See Other"),
NOT_MODIFIED (304, "Not Modified"),
BAD_REQUEST (400, "Bad Request"),
UNAUTHORIZED (401, "Unauthorized"),
FORBIDDEN (403, "Forbidden"),
NOT_FOUND (404, "Not Found"),
INTERNAL_SERVER_ERROR (500, "Internal Server Error"),
NOT_IMPLEMENTED (501, "Not Implemented"),
SERVICE_UNAVAILABLE (503, "Service Unavailable"),
HTTP_VERSION_NOT_SUPPORTED (505, "HTTP Version not supported");
private int code;
private String message;
private HttpStatusCode(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode(){
return code;
}
public String getMessage(){
return message;
}
@Override
public String toString() {
return getCode() + " " + getMessage();
}
}

View File

@ -0,0 +1,113 @@
/*
* 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.core.webserver;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import de.bluecolored.bluemap.core.logger.Logger;
public class WebServer extends Thread {
private final int port;
private final int maxConnections;
private final InetAddress bindAdress;
private HttpRequestHandler handler;
private ThreadPoolExecutor connectionThreads;
private ServerSocket server;
public WebServer(int port, int maxConnections, InetAddress bindAdress, HttpRequestHandler handler) {
this.port = port;
this.maxConnections = maxConnections;
this.bindAdress = bindAdress;
this.handler = handler;
connectionThreads = null;
}
@Override
public void run(){
close();
connectionThreads = new ThreadPoolExecutor(maxConnections, maxConnections, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
connectionThreads.allowCoreThreadTimeOut(true);
try {
server = new ServerSocket(port, maxConnections, bindAdress);
server.setSoTimeout(0);
} catch (IOException e){
Logger.global.logError("Error while starting the WebServer!", e);
return;
}
Logger.global.logInfo("WebServer started.");
while (!server.isClosed() && server.isBound()){
try {
Socket connection = server.accept();
try {
connectionThreads.execute(new HttpConnection(server, connection, handler, 10, TimeUnit.SECONDS));
} catch (RejectedExecutionException e){
connection.close();
Logger.global.logWarning("Dropped an incoming HttpConnection! (Too many connections?)");
}
} catch (SocketException e){
// this mainly occurs if the socket got closed, so we ignore this error
} catch (IOException e){
Logger.global.logError("Error while creating a new HttpConnection!", e);
}
}
Logger.global.logInfo("WebServer closed.");
}
public void close(){
if (connectionThreads != null) connectionThreads.shutdown();
try {
if (server != null && !server.isClosed()){
server.close();
}
} catch (IOException e) {
Logger.global.logError("Error while closing WebServer!", e);
}
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.core.world;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.render.context.BlockContext;
import de.bluecolored.bluemap.core.util.Direction;
public abstract class Block {
private float sunLight;
private float blockLight;
public Block() {
sunLight = -1;
blockLight = -1;
}
public abstract BlockState getBlock();
public abstract World getWorld();
public abstract Vector3i getPosition();
public abstract double getSunLightLevel();
public abstract double getBlockLightLevel();
public abstract boolean isCullingNeighborFaces();
public boolean isOccludingNeighborFaces(){
return isCullingNeighborFaces();
}
public abstract String getBiome();
/**
* This is internally used for light rendering
* It is basically the sun light that is projected onto adjacent faces
*/
public float getPassedSunLight(BlockContext context) {
if (sunLight < 0) calculateLight(context);
return sunLight;
}
/**
* This is internally used for light rendering
* It is basically the block light that is projected onto adjacent faces
*/
public float getPassedBlockLight(BlockContext context) {
if (blockLight < 0) calculateLight(context);
return blockLight;
}
private void calculateLight(BlockContext context) {
sunLight = (float) getSunLightLevel();
blockLight = (float) getBlockLightLevel();
if (blockLight > 0 || sunLight > 0) return;
for (Direction direction : Direction.values()) {
Block neighbor = context.getRelativeBlock(direction);
sunLight = (float) Math.max(neighbor.getSunLightLevel(), sunLight);
blockLight = (float) Math.max(neighbor.getBlockLightLevel(), blockLight);
}
}
}

View File

@ -0,0 +1,199 @@
/*
* 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.core.world;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Represents a BlockState<br>
* It is important that {@link #hashCode} and {@link #equals} are implemented correctly, for the caching to work properly.<br>
* <br>
* <i>The implementation of this class has to be thread-save!</i><br>
*/
public class BlockState {
private static Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.+)\\])?$");
public static final BlockState AIR = new BlockState("minecraft:air", Collections.emptyMap());
private boolean hashed;
private int hash;
private final String namespace;
private final String id;
private final Map<String, String> properties;
public BlockState(String id) {
this(id, Collections.emptyMap());
}
public BlockState(String id, Map<String, String> properties) {
this.hashed = false;
this.hash = 0;
this.properties = Collections.unmodifiableMap(new HashMap<>(properties));
//resolve namespace
String namespace = "minecraft";
int namespaceSeperator = id.indexOf(':');
if (namespaceSeperator > 0) {
namespace = id.substring(0, namespaceSeperator);
id = id.substring(namespaceSeperator + 1);
}
this.id = id;
this.namespace = namespace;
}
private BlockState(BlockState blockState, String withKey, String withValue) {
this.hashed = false;
this.hash = 0;
Map<String, String> props = new HashMap<>(blockState.getProperties());
props.put(withKey, withValue);
this.id = blockState.getId();
this.namespace = blockState.getNamespace();
this.properties = Collections.unmodifiableMap(props);
}
/**
* The namespace of this blockstate,<br>
* this is always "minecraft" in vanilla.<br>
*/
public String getNamespace() {
return namespace;
}
/**
* The id of this blockstate,<br>
* also the name of the resource-file without the filetype that represents this block-state <i>(found in mineceraft in assets/minecraft/blockstates)</i>.<br>
*/
public String getId() {
return id;
}
/**
* Returns the namespaced id of this blockstate
*/
public String getFullId() {
return getNamespace() + ":" + getId();
}
/**
* An immutable map of all properties of this block.<br>
* <br>
* For Example:<br>
* <code>
* facing = east<br>
* half = bottom<br>
* </code>
*/
public Map<String, String> getProperties() {
return properties;
}
/**
* Returns a new BlockState with the given property changed
*/
public BlockState with(String property, String value) {
return new BlockState(this, property, value);
}
public final boolean checkVariantCondition(String condition){
if (condition.isEmpty() || condition.equals("normal")) return true;
Map<String, String> blockProperties = getProperties();
String[] conditions = condition.split(",");
for (String c : conditions){
String[] kv = c.split("=", 2);
String key = kv[0];
String value = kv[1];
if (!value.equals(blockProperties.get(key))){
return false;
}
}
return true;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof BlockState)) return false;
BlockState b = (BlockState) obj;
if (!Objects.equals(getId(), b.getId())) return false;
if (!Objects.equals(getProperties(), b.getProperties())) return false;
return true;
}
@Override
public int hashCode() {
if (!hashed){
hash = Objects.hash( getId(), getProperties() );
hashed = true;
}
return hash;
}
@Override
public String toString() {
StringJoiner sj = new StringJoiner(",");
for (Entry<String, String> e : getProperties().entrySet()){
sj.add(e.getKey() + "=" + e.getValue());
}
return getId() + "[" + sj.toString() + "]";
}
public static BlockState fromString(String serializedBlockState) {
Matcher m = BLOCKSTATE_SERIALIZATION_PATTERN.matcher(serializedBlockState);
m.find();
Map<String, String> pt = new HashMap<>();
String g2 = m.group(2);
if (g2 != null){
String[] propertyStrings = g2.trim().split(",");
for (String s : propertyStrings){
String[] kv = s.split("=", 2);
pt.put(kv[0], kv[1]);
}
}
String blockId = m.group(1).trim();
return new BlockState(blockId, pt);
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.core.world;
import com.flowpowered.math.vector.Vector3i;
/**
* This class wraps another Block to cache all getters.<br>
* The implementation <b>can</b> use this to make sure all underlying getters are only called once and cached-data is used on the second call.
*/
public class CachedBlock extends Block {
private Block block;
private BlockState state;
private World world;
private Vector3i position;
private double sunLight, blockLight;
private String biome;
private boolean isCullingCached;
private boolean isCulling;
private boolean isOccludingCached;
private boolean isOccluding;
private CachedBlock(Block block) {
this.block = block;
this.state = null;
this.world = null;
this.position = null;
this.sunLight = -1;
this.blockLight = -1;
this.isCullingCached = false;
this.isCulling = false;
this.isOccludingCached = false;
this.isOccluding = false;
}
@Override
public BlockState getBlock() {
if (state == null) state = block.getBlock();
return state;
}
@Override
public World getWorld() {
if (world == null) world = block.getWorld();
return world;
}
@Override
public Vector3i getPosition() {
if (position == null) position = block.getPosition();
return position;
}
@Override
public double getSunLightLevel() {
if (sunLight == -1) sunLight = block.getSunLightLevel();
return sunLight;
}
@Override
public double getBlockLightLevel() {
if (blockLight == -1) blockLight = block.getBlockLightLevel();
return blockLight;
}
@Override
public boolean isCullingNeighborFaces() {
if (!isCullingCached){
isCulling = block.isCullingNeighborFaces();
isCullingCached = true;
}
return isCulling;
}
@Override
public boolean isOccludingNeighborFaces() {
if (!isOccludingCached){
isOccluding = block.isOccludingNeighborFaces();
isOccludingCached = true;
}
return isOccluding;
}
@Override
public String getBiome() {
if (biome == null){
biome = block.getBiome();
}
return biome;
}
public static CachedBlock of(Block block){
if (block instanceof CachedBlock) return (CachedBlock) block;
return new CachedBlock(block);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.core.world;
public class ChunkNotGeneratedException extends Exception {
private static final long serialVersionUID = 0L;
public ChunkNotGeneratedException() {}
public ChunkNotGeneratedException(Throwable e) {
super(e);
}
public ChunkNotGeneratedException(String message){
super(message);
}
public ChunkNotGeneratedException(String message, Throwable e) {
super(message, e);
}
}

View File

@ -0,0 +1,78 @@
/*
* 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.core.world;
import java.util.Collection;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
/**
* Represents a World on the Server<br>
* <br>
* <i>The implementation of this class has to be thread-save!</i><br>
*/
public interface World extends WorldChunk {
String getName();
UUID getUUID();
int getSeaLevel();
Vector3i getSpawnPoint();
/**
* Returns itself
*/
@Override
default World getWorld() {
return this;
}
/**
* Always returns false
*/
@Override
default boolean isGenerated() {
return false;
}
/**
* Returns a collection of all generated chunks.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
public default Collection<Vector2i> getChunkList(){
return getChunkList(0);
}
/**
* Returns a collection of all chunks that have been modified at or after the specified timestamp.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
public Collection<Vector2i> getChunkList(long modifiedSince);
}

View File

@ -0,0 +1,85 @@
/*
* 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.core.world;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.AABB;
/**
* Represents a chunk of a world.<br>
* <br>
* <i>The implementation of this class has to be thread-save!</i><br>
*/
public interface WorldChunk {
/**
* Returns the top-level World of this WorldChunk,
* If this WorldChunk is already a World, the method returns the same instance (<code>return this;</code>).
*/
World getWorld();
/**
* Returns the Block on the specified position.<br>
* <br>
* <i>(The implementation should not invoke the generation of new Terrain, it should rather throw a {@link ChunkNotGeneratedException} if a not generated block is requested)</i><br>
*/
Block getBlock(Vector3i pos) throws ChunkNotGeneratedException;
/**
* Returns the Block on the specified position.<br>
* <br>
* <i>(The implementation should not invoke the generation of new Terrain, it should rather throw a {@link ChunkNotGeneratedException} if a not generated block is requested)</i><br>
*/
default Block getBlock(int x, int y, int z) throws ChunkNotGeneratedException {
return getBlock(new Vector3i(x, y, z));
}
/**
* Returns true if this WorldChunk contains the given position.
*/
default boolean containsBlock(Vector3i pos){
return getBoundaries().contains(pos);
}
/**
* Returns the boundaries of the WorldChunk.<br>
*/
AABB getBoundaries();
/**
* Returns a smaller part of this WorldChunk<br>
* <br>
* This is used to give the implementation an easy way to optimize thread-save access to this world-chunk.<br>
* The {@link #getBlock} method is and should be used in favour to {@link World#getBlock}.<br>
*/
WorldChunk getWorldChunk(AABB boundaries);
/**
* Returns true if the complete WorldChunk is generated and populated by Minecraft.<br>
*/
boolean isGenerated();
}

Binary file not shown.

View File

@ -0,0 +1,452 @@
{
"ocean": {
"id": 0,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"plains": {
"id": 1,
"humidity": 0.4,
"temp": 0.8,
"watercolor": 4159204
},
"desert": {
"id": 2,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"mountains": {
"id": 3,
"humidity": 0.3,
"temp": 0.2,
"watercolor": 4159204
},
"forest": {
"id": 4,
"humidity": 0.8,
"temp": 0.7,
"watercolor": 4159204
},
"taiga": {
"id": 5,
"humidity": 0.8,
"temp": 0.25,
"watercolor": 4159204
},
"swamp": {
"id": 6,
"humidity": 0.9,
"temp": 0.8,
"watercolor": 6388580
},
"river": {
"id": 7,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"nether": {
"id": 8,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"the_end": {
"id": 9,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"frozen_ocean": {
"id": 10,
"humidity": 0.5,
"temp": 0.0,
"watercolor": 3750089
},
"frozen_river": {
"id": 11,
"humidity": 0.5,
"temp": 0.0,
"watercolor": 3750089
},
"snowy_tundra": {
"id": 12,
"humidity": 0.5,
"temp": 0.0,
"watercolor": 4159204
},
"snowy_mountains": {
"id": 13,
"humidity": 0.5,
"temp": 0.0,
"watercolor": 4159204
},
"mushroom_fields": {
"id": 14,
"humidity": 1.0,
"temp": 0.9,
"watercolor": 4159204
},
"mushroom_field_shore": {
"id": 15,
"humidity": 1.0,
"temp": 0.9,
"watercolor": 4159204
},
"beach": {
"id": 16,
"humidity": 0.4,
"temp": 0.8,
"watercolor": 4159204
},
"desert_hills": {
"id": 17,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"wooded_hills": {
"id": 18,
"humidity": 0.8,
"temp": 0.7,
"watercolor": 4159204
},
"taiga_hills": {
"id": 19,
"humidity": 0.8,
"temp": 0.25,
"watercolor": 4159204
},
"mountain_edge": {
"id": 20,
"humidity": 0.3,
"temp": 0.2,
"watercolor": 4159204
},
"jungle": {
"id": 21,
"humidity": 0.9,
"temp": 0.95,
"watercolor": 4159204
},
"jungle_hills": {
"id": 22,
"humidity": 0.9,
"temp": 0.95,
"watercolor": 4159204
},
"jungle_edge": {
"id": 23,
"humidity": 0.8,
"temp": 0.95,
"watercolor": 4159204
},
"deep_ocean": {
"id": 24,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"stone_shore": {
"id": 25,
"humidity": 0.3,
"temp": 0.2,
"watercolor": 4159204
},
"snowy_beach": {
"id": 26,
"humidity": 0.3,
"temp": 0.05,
"watercolor": 4020182
},
"birch_forest": {
"id": 27,
"humidity": 0.6,
"temp": 0.6,
"watercolor": 4159204
},
"birch_forest_hills": {
"id": 28,
"humidity": 0.6,
"temp": 0.6,
"watercolor": 4159204
},
"dark_forest": {
"id": 29,
"humidity": 0.8,
"temp": 0.7,
"watercolor": 4159204
},
"snowy_taiga": {
"id": 30,
"humidity": 0.4,
"temp": -0.5,
"watercolor": 4020182
},
"snowy_taiga_hills": {
"id": 31,
"humidity": 0.4,
"temp": -0.5,
"watercolor": 4020182
},
"giant_tree_taiga": {
"id": 32,
"humidity": 0.8,
"temp": 0.3,
"watercolor": 4159204
},
"giant_tree_taiga_hills": {
"id": 33,
"humidity": 0.8,
"temp": 0.3,
"watercolor": 4159204
},
"wooded_mountains": {
"id": 34,
"humidity": 0.3,
"temp": 0.2,
"watercolor": 4159204
},
"savanna": {
"id": 35,
"humidity": 0.0,
"temp": 1.2,
"watercolor": 4159204
},
"savanna_plateau": {
"id": 36,
"humidity": 0.0,
"temp": 1.0,
"watercolor": 4159204
},
"badlands": {
"id": 37,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"wooded_badlands_plateau": {
"id": 38,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"badlands_plateau": {
"id": 39,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"small_end_islands": {
"id": 40,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"end_midlands": {
"id": 41,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"end_highlands": {
"id": 42,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"end_barrens": {
"id": 43,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"warm_ocean": {
"id": 44,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4445678
},
"lukewarm_ocean": {
"id": 45,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4566514
},
"cold_ocean": {
"id": 46,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4020182
},
"deep_warm_ocean": {
"id": 47,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4445678
},
"deep_lukewarm_ocean": {
"id": 48,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4566514
},
"deep_cold_ocean": {
"id": 49,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4020182
},
"deep_frozen_ocean": {
"id": 50,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 3750089
},
"the_void": {
"id": 127,
"humidity": 0.5,
"temp": 0.5,
"watercolor": 4159204
},
"sunflower_plains": {
"id": 129,
"humidity": 0.4,
"temp": 0.8,
"watercolor": 4159204
},
"desert_lakes": {
"id": 130,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"gravelly_mountains": {
"id": 131,
"humidity": 0.3,
"temp": 0.2,
"watercolor": 4159204
},
"flower_forest": {
"id": 132,
"humidity": 0.8,
"temp": 0.7,
"watercolor": 4159204
},
"taiga_mountains": {
"id": 133,
"humidity": 0.8,
"temp": 0.25,
"watercolor": 4159204
},
"swamp_hills": {
"id": 134,
"humidity": 0.9,
"temp": 0.8,
"watercolor": 6388580
},
"ice_spikes": {
"id": 140,
"humidity": 0.5,
"temp": 0.0,
"watercolor": 4159204
},
"modified_jungle": {
"id": 149,
"humidity": 0.9,
"temp": 0.95,
"watercolor": 4159204
},
"modified_jungle_edge": {
"id": 151,
"humidity": 0.8,
"temp": 0.95,
"watercolor": 4159204
},
"tall_birch_forest": {
"id": 155,
"humidity": 0.6,
"temp": 0.6,
"watercolor": 4159204
},
"tall_birch_hills": {
"id": 156,
"humidity": 0.6,
"temp": 0.6,
"watercolor": 4159204
},
"dark_forest_hills": {
"id": 157,
"humidity": 0.8,
"temp": 0.7,
"watercolor": 4159204
},
"snowy_taiga_mountains": {
"id": 158,
"humidity": 0.4,
"temp": -0.5,
"watercolor": 4020182
},
"giant_spruce_taiga": {
"id": 160,
"humidity": 0.8,
"temp": 0.25,
"watercolor": 4159204
},
"giant_spruce_taiga_hills": {
"id": 161,
"humidity": 0.8,
"temp": 0.25,
"watercolor": 4159204
},
"modified_gravelly_mountains": {
"id": 162,
"humidity": 0.3,
"temp": 0.2,
"watercolor": 4159204
},
"shattered_savanna": {
"id": 163,
"humidity": 0.0,
"temp": 1.1,
"watercolor": 4159204
},
"shattered_savanna_plateau": {
"id": 164,
"humidity": 0.0,
"temp": 1.0,
"watercolor": 4159204
},
"eroded_badlands": {
"id": 165,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"modified_wooded_badlands_plateau": {
"id": 166,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"modified_badlands_plateau": {
"id": 167,
"humidity": 0.0,
"temp": 2.0,
"watercolor": 4159204
},
"bamboo_jungle": {
"id": 168,
"humidity": 0.9,
"temp": 0.95,
"watercolor": 4159204
},
"bamboo_jungle_hills": {
"id": 169,
"humidity": 0.9,
"temp": 0.95,
"watercolor": 4159204
}
}

View File

@ -0,0 +1,11 @@
{
"default": "#foliage",
"grass_block" : "#grass",
"grass" : "#grass",
"tall_grass" : "#grass",
"fern" : "#grass",
"large_fern" : "#grass",
"redstone_wire" : "ff0000",
"birch_leaves" : "86a863",
"spruce_leaves" : "51946b"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More