commit 49ae2cf08fa85bc9cfe75e152b9aba31d7dac692 Author: Blue (Lukas Rieger) Date: Sat Nov 2 17:23:48 2019 +0100 Initial commit: Merging Projects BlueMapCore, BlueMapCLI and BlueMapSponge into one diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8e5a0b3c --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/BlueMapCLI/build.gradle b/BlueMapCLI/build.gradle new file mode 100644 index 00000000..b17d6fa5 --- /dev/null +++ b/BlueMapCLI/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile group: 'commons-cli', name: 'commons-cli', version: '1.4' + compile project(':BlueMapCore') +} diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java new file mode 100644 index 00000000..3b009d28 --- /dev/null +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/BlueMapCLI.java @@ -0,0 +1,555 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 chunks; + if (updateOnly) { + long lastRender = webSettings.getLong(mapId, "last-render"); + chunks = world.getChunkList(lastRender); + } else { + chunks = world.getChunkList(); + } + + Set 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 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 '/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)" + ); + } + +} diff --git a/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderManager.java b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderManager.java new file mode 100644 index 00000000..3f0638b3 --- /dev/null +++ b/BlueMapCLI/src/main/java/de/bluecolored/bluemap/cli/RenderManager.java @@ -0,0 +1,176 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 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 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++; + } + } + +} diff --git a/BlueMapCore/build.gradle b/BlueMapCore/build.gradle new file mode 100644 index 00000000..7344d013 --- /dev/null +++ b/BlueMapCore/build.gradle @@ -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) diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java new file mode 100644 index 00000000..40a8dcb0 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/AbstractLogger.java @@ -0,0 +1,69 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/Logger.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/Logger.java new file mode 100644 index 00000000..104d30ed --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/Logger.java @@ -0,0 +1,99 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java new file mode 100644 index 00000000..724e8904 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/PrintStreamLogger.java @@ -0,0 +1,59 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/VoidLogger.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/VoidLogger.java new file mode 100644 index 00000000..32bd8464 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/logger/VoidLogger.java @@ -0,0 +1,59 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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) {} + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java new file mode 100644 index 00000000..7db25414 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/Chunk.java @@ -0,0 +1,75 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java new file mode 100644 index 00000000..e17ae016 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java @@ -0,0 +1,172 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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) 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 = true) or the right (largeHalf = false) side of the byte stored in value.
+ * 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; + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java new file mode 100644 index 00000000..c6c2e2c9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -0,0 +1,224 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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) 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 paletteTag = (ListTag) 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 properties = new HashMap<>(); + + if (stateTag.containsKey("Properties")) { + CompoundTag propertiesTag = stateTag.getCompoundTag("Properties"); + for (Entry> 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 = true) or the right (largeHalf = false) side of the byte stored in value.
+ * 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; + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java new file mode 100644 index 00000000..dd3672ed --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCABlock.java @@ -0,0 +1,92 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java new file mode 100644 index 00000000..5ecc3144 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -0,0 +1,438 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 CHUNK_CACHE = CacheBuilder.newBuilder().maximumSize(500).build(); + private static final Multimap 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 getChunkList(long modifiedSinceMillis){ + List 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; + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorldChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorldChunk.java new file mode 100644 index 00000000..a4170372 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorldChunk.java @@ -0,0 +1,85 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/BlockStateExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/BlockStateExtension.java new file mode 100644 index 00000000..9b295ba2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/BlockStateExtension.java @@ -0,0 +1,40 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 getAffectedBlockIds(); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/ConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/ConnectExtension.java new file mode 100644 index 00000000..d29c5d05 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/ConnectExtension.java @@ -0,0 +1,57 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 getAffectedBlockIds(); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/ConnectSameOrFullBlockExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/ConnectSameOrFullBlockExtension.java new file mode 100644 index 00000000..94f0ae24 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/ConnectSameOrFullBlockExtension.java @@ -0,0 +1,41 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java new file mode 100644 index 00000000..07aa4051 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoorExtension.java @@ -0,0 +1,74 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 prop : otherDoor.getProperties().entrySet()) { + if (!state.getProperties().containsKey(prop.getKey())) { + state = state.with(prop.getKey(), prop.getValue()); + } + } + + return state; + } + + @Override + public Collection getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java new file mode 100644 index 00000000..c5203205 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/DoublePlantExtension.java @@ -0,0 +1,64 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 prop : otherPlant.getProperties().entrySet()) { + if (!state.getProperties().containsKey(prop.getKey())) { + state = state.with(prop.getKey(), prop.getValue()); + } + } + } + + return state; + } + + @Override + public Collection getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java new file mode 100644 index 00000000..e09f00fc --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/FireExtension.java @@ -0,0 +1,67 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java new file mode 100644 index 00000000..c24b4c81 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/GlassPaneConnectExtension.java @@ -0,0 +1,45 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 AFFECTED_BLOCK_IDS = Sets.newHashSet( + "minecraft:glass_pane", + "minecraft:stained_glass_pane", + "minecraft:iron_bars" + ); + + @Override + public Set getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java new file mode 100644 index 00000000..39ff3760 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/NetherFenceConnectExtension.java @@ -0,0 +1,43 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 AFFECTED_BLOCK_IDS = Sets.newHashSet( + "minecraft:nether_brick_fence" + ); + + @Override + public Set getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java new file mode 100644 index 00000000..1bc58378 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/RedstoneExtension.java @@ -0,0 +1,84 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 AFFECTED_BLOCK_IDS = Lists.newArrayList( + "minecraft:redstone_wire" + ); + + + private static final Set 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 getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java new file mode 100644 index 00000000..d72b4a94 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/SnowyExtension.java @@ -0,0 +1,58 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java new file mode 100644 index 00000000..f20df86d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/StairShapeExtension.java @@ -0,0 +1,129 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java new file mode 100644 index 00000000..7c953b41 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/TripwireConnectExtension.java @@ -0,0 +1,43 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 AFFECTED_BLOCK_IDS = Sets.newHashSet( + "minecraft:tripwire" + ); + + @Override + public Set getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java new file mode 100644 index 00000000..dc2592a8 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WallConnectExtension.java @@ -0,0 +1,64 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java new file mode 100644 index 00000000..169dfd36 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/extensions/WoodenFenceConnectExtension.java @@ -0,0 +1,48 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 getAffectedBlockIds() { + return AFFECTED_BLOCK_IDS; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java new file mode 100644 index 00000000..4427cac9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BiomeIdMapper.java @@ -0,0 +1,69 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockIdMapper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockIdMapper.java new file mode 100644 index 00000000..89f6c116 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockIdMapper.java @@ -0,0 +1,119 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 mappings; + + public BlockIdMapper() throws IOException { + mappings = new HashMap<>(); + + GsonConfigurationLoader loader = GsonConfigurationLoader.builder() + .setURL(getClass().getResource("/blockIdMappings.json")) + .build(); + + ConfigurationNode node = loader.load(); + + for (Entry 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(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockProperties.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockProperties.java new file mode 100644 index 00000000..6ea12599 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockProperties.java @@ -0,0 +1,49 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockPropertyMapper.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockPropertyMapper.java new file mode 100644 index 00000000..4fac6b1d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockPropertyMapper.java @@ -0,0 +1,103 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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> mappings; + private LoadingCache mappingCache; + + private BlockPropertyMapper() throws IOException { + mappings = HashMultimap.create(); + + GsonConfigurationLoader loader = GsonConfigurationLoader.builder() + .setURL(getClass().getResource("/blockProperties.json")) + .build(); + + ConfigurationNode node = loader.load(); + + for (Entry 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 mapping = new BlockStateMapping<>(bsKey, bsValue); + mappings.put(bsKey.getId(), mapping); + } + + mappings = Multimaps.unmodifiableMultimap(mappings); + + mappingCache = CacheBuilder.newBuilder() + .concurrencyLevel(8) + .maximumSize(10000) + .build(new CacheLoader(){ + @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 bm : mappings.get(bs.getId())){ + if (bm.fitsTo(bs)){ + return bm.getMapping(); + } + } + + return DEFAULT_PROPERTIES; + } + + public static BlockPropertyMapper create() throws IOException { + return new BlockPropertyMapper(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockStateMapping.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockStateMapping.java new file mode 100644 index 00000000..6ce1ad03 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/BlockStateMapping.java @@ -0,0 +1,63 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 { + 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.
+ * Properties that are not defined in this Mapping are ignored on the provided BlockState.
+ */ + public boolean fitsTo(BlockState blockState){ + if (!this.blockState.getId().equals(blockState.getId())) return false; + for (Entry 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; + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/LightData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/LightData.java new file mode 100644 index 00000000..2d801889 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/mapping/LightData.java @@ -0,0 +1,52 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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() + "]"; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java new file mode 100644 index 00000000..d3b771c9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Face.java @@ -0,0 +1,218 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Model.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Model.java new file mode 100644 index 00000000..38fa89da --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/Model.java @@ -0,0 +1,149 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 faces; + + public Model() { + this.faces = new ArrayList<>(); + } + + /** + * Merges the given Model into this model
+ * 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 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 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(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java new file mode 100644 index 00000000..5085db86 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/RenderSettings.java @@ -0,0 +1,88 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.
+ * A value of 0 turns off ao.
+ * 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
+ * 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.
+ * 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() + ); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/StaticRenderSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/StaticRenderSettings.java new file mode 100644 index 00000000..710f14cb --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/StaticRenderSettings.java @@ -0,0 +1,80 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} \ No newline at end of file diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java new file mode 100644 index 00000000..bd1539d9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/TileRenderer.java @@ -0,0 +1,74 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/WorldTile.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/WorldTile.java new file mode 100644 index 00000000..3393a79c --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/WorldTile.java @@ -0,0 +1,69 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/BlockContext.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/BlockContext.java new file mode 100644 index 00000000..1ae43cfd --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/BlockContext.java @@ -0,0 +1,57 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.
+ * The distance can not be larger than one block in each direction!
+ */ + Block getRelativeBlock(Vector3i direction); + + /** + * This returns neighbour blocks.
+ * The distance can not be larger than one block in each direction!
+ */ + 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()); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java new file mode 100644 index 00000000..14e8f4f2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/EmptyBlockContext.java @@ -0,0 +1,168 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 getChunkList(long modifiedSince) { + return Collections.emptyList(); + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/ExtendedBlockContext.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/ExtendedBlockContext.java new file mode 100644 index 00000000..faa278b2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/ExtendedBlockContext.java @@ -0,0 +1,72 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.
+ * The distance can not be larger than two blocks in each direction!
+ */ + Block getRelativeBlock(Vector3i direction); + + /** + * This returns neighbour blocks.
+ * The distance can not be larger than two blocks in each direction!
+ */ + 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!
+ */ + 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); + } + }; + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/SlicedWorldChunkBlockContext.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/SlicedWorldChunkBlockContext.java new file mode 100644 index 00000000..27bdd957 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/SlicedWorldChunkBlockContext.java @@ -0,0 +1,52 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/WorldChunkBlockContext.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/WorldChunkBlockContext.java new file mode 100644 index 00000000..53755b5e --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/context/WorldChunkBlockContext.java @@ -0,0 +1,79 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModel.java new file mode 100644 index 00000000..c44595f8 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModel.java @@ -0,0 +1,94 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java new file mode 100644 index 00000000..00722b15 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelManager.java @@ -0,0 +1,168 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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"); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java new file mode 100644 index 00000000..bff2d4f2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/HiresModelRenderer.java @@ -0,0 +1,115 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModel.java new file mode 100644 index 00000000..f3f2e1b8 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModel.java @@ -0,0 +1,68 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java new file mode 100644 index 00000000..9d0d1c80 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/BlockStateModelFactory.java @@ -0,0 +1,98 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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"); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java new file mode 100644 index 00000000..42cfa7d7 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/LiquidModelBuilder.java @@ -0,0 +1,245 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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"); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java new file mode 100644 index 00000000..293d5444 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/hires/blockmodel/ResourceModelBuilder.java @@ -0,0 +1,386 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java new file mode 100644 index 00000000..b0f3c534 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModel.java @@ -0,0 +1,229 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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.
+ *
+ * + * Implementation note:
+ * The vertex x, z -coords are rounded, so we can compare them using == without worrying about floating point rounding differences.
+ *
+ */ + 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 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); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java new file mode 100644 index 00000000..6d6dbead --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/render/lowres/LowresModelManager.java @@ -0,0 +1,309 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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:
+ * it saves all modified models that have not been saved for 2 minutes and
+ * saves and removes the oldest models from the cache until the cache size is 10 or less.
+ *
+ * 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.
+ */ + public synchronized void tidyUpModelCache() { + List> 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 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(); + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java new file mode 100644 index 00000000..6cf6961b --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorProvider.java @@ -0,0 +1,239 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 biomeInfos; + private Map 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 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 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; + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java new file mode 100644 index 00000000..e5c0522d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementFaceResource.java @@ -0,0 +1,142 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java new file mode 100644 index 00000000..00faf907 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelElementResource.java @@ -0,0 +1,144 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java new file mode 100644 index 00000000..3dc7a1d0 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java @@ -0,0 +1,142 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 elements; + private Map 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 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 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 getAllTextureIds(){ + List list = new ArrayList<>(); + for (String tex : textures.values()){ + if (!tex.startsWith("#")) list.add(tex); + } + return list; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java new file mode 100644 index 00000000..ca8803c1 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java @@ -0,0 +1,154 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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> 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 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 models = new WeighedArrayList<>(); + + if (n.hasListChildren()){ + + //if it is a weighted list of alternative models, select one by random and weight + List 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 blockProperties = getBlock().getProperties(); + for (Entry 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> getModelResources(){ + return modelResources; + } + + private Path getResourcePath(){ + return Paths.get("assets", block.getNamespace(), "blockstates", block.getId() + ".json"); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/InvalidResourceDeclarationException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/InvalidResourceDeclarationException.java new file mode 100644 index 00000000..c9bb0a86 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/InvalidResourceDeclarationException.java @@ -0,0 +1,43 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java new file mode 100644 index 00000000..f6fc55ef --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchResourceException.java @@ -0,0 +1,43 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchTextureException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchTextureException.java new file mode 100644 index 00000000..db69169d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/NoSuchTextureException.java @@ -0,0 +1,43 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java new file mode 100644 index 00000000..3f6f481f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java @@ -0,0 +1,189 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 resources; + + private TextureProvider textureProvider; + private BlockColorProvider blockColorProvider; + private Cache blockStateResourceCache; + + public ResourcePack(List 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 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 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 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); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java new file mode 100644 index 00000000..51babbd8 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureProvider.java @@ -0,0 +1,231 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 indexMap; + private List 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 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; + } + + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferGeometry.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferGeometry.java new file mode 100644 index 00000000..08c9a8aa --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/threejs/BufferGeometry.java @@ -0,0 +1,312 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 positionList = new ArrayList<>(300); + List normalList = new ArrayList<>(300); + List colorList = new ArrayList<>(300); + List uvList = new ArrayList<>(200); + List 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 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; + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AABB.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AABB.java new file mode 100644 index 00000000..1c1aa58f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/AABB.java @@ -0,0 +1,438 @@ +/* + * This file is part of SpongeAPI, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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. + * + *

The box will never be degenerate: the corners are always not equal and + * respect the minimum and maximum properties.

+ * + *

This class is immutable, all objects returned are either new instances or + * itself.

+ */ +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 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 + ")"; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java new file mode 100644 index 00000000..3da8e288 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java @@ -0,0 +1,51 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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()); + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtil.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtil.java new file mode 100644 index 00000000..3e143c55 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ConfigUtil.java @@ -0,0 +1,135 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 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 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 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 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()); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java new file mode 100644 index 00000000..97f8c1ec --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java @@ -0,0 +1,75 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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()); + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtil.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtil.java new file mode 100644 index 00000000..355dd0bb --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/FileUtil.java @@ -0,0 +1,76 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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.
+ * (Do not use this method to sync file-access from different threads!) + */ + 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(); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntersectionPoint.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntersectionPoint.java new file mode 100644 index 00000000..f2fc82c4 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntersectionPoint.java @@ -0,0 +1,47 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtil.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtil.java new file mode 100644 index 00000000..2d6eab19 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtil.java @@ -0,0 +1,102 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ModelUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ModelUtils.java new file mode 100644 index 00000000..b5c0d8f0 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ModelUtils.java @@ -0,0 +1,90 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/UpdateDeamon.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/UpdateDeamon.java new file mode 100644 index 00000000..29a83089 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/UpdateDeamon.java @@ -0,0 +1,54 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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(subject); + this.frequency = frequency; + this.setDaemon(true); + } + + @Override + public void run() { + try { + while (true) { + Thread.sleep(frequency); + subject.get().update(); + } + } catch (NullPointerException | InterruptedException ex) {} + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Updateable.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Updateable.java new file mode 100644 index 00000000..a8a66cfc --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Updateable.java @@ -0,0 +1,31 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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(); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WeighedArrayList.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WeighedArrayList.java new file mode 100644 index 00000000..f9df25a2 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/WeighedArrayList.java @@ -0,0 +1,76 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 extends ArrayList implements List { + 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.
+ * @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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java new file mode 100644 index 00000000..ddd3116a --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebRequestHandler.java @@ -0,0 +1,276 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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 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); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebServer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebServer.java new file mode 100644 index 00000000..79db02cc --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/BlueMapWebServer.java @@ -0,0 +1,54 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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(); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebFilesManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebFilesManager.java new file mode 100644 index 00000000..3d73d14d --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebFilesManager.java @@ -0,0 +1,73 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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(); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebServerConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebServerConfig.java new file mode 100644 index 00000000..df43550b --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebServerConfig.java @@ -0,0 +1,40 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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(); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java new file mode 100644 index 00000000..1314d3a8 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/web/WebSettings.java @@ -0,0 +1,132 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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 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"); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java new file mode 100644 index 00000000..298e3b3f --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpConnection.java @@ -0,0 +1,126 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java new file mode 100644 index 00000000..a8597c51 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequest.java @@ -0,0 +1,206 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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> header; + private Map> headerLC; + private byte[] data; + + public HttpRequest(String method, String path, String version, Map> header) { + this.method = method; + this.path = path; + this.version = version; + this.header = header; + this.headerLC = new HashMap<>(); + + for (Entry> e : header.entrySet()){ + Set 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> getHeader() { + return header; + } + + public Map> getLowercaseHeader() { + return header; + } + + public Set getHeader(String key){ + Set headerValues = header.get(key); + if (headerValues == null) return Collections.emptySet(); + return headerValues; + } + + public Set getLowercaseHeader(String key){ + Set 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 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> headerMap = new HashMap>(); + for (String line : header){ + if (line.trim().isEmpty()) continue; + + String[] kv = line.split(":", 2); + if (kv.length < 2) continue; + + Set 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 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; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequestHandler.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequestHandler.java new file mode 100644 index 00000000..23f887ef --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpRequestHandler.java @@ -0,0 +1,31 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java new file mode 100644 index 00000000..6110a93c --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpResponse.java @@ -0,0 +1,149 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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> 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 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 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.
+ *
+ * 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> 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> getHeader() { + return header; + } + + public Set getHeader(String key){ + Set headerValues = header.get(key); + if (headerValues == null) return Collections.emptySet(); + return headerValues; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpStatusCode.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpStatusCode.java new file mode 100644 index 00000000..820a2b09 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/HttpStatusCode.java @@ -0,0 +1,70 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java new file mode 100644 index 00000000..ba8a290e --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/webserver/WebServer.java @@ -0,0 +1,113 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java new file mode 100644 index 00000000..7799bff3 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java @@ -0,0 +1,91 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java new file mode 100644 index 00000000..6eb256f5 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java @@ -0,0 +1,199 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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
+ * It is important that {@link #hashCode} and {@link #equals} are implemented correctly, for the caching to work properly.
+ *
+ * The implementation of this class has to be thread-save!
+ */ +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 properties; + + public BlockState(String id) { + this(id, Collections.emptyMap()); + } + + public BlockState(String id, Map 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 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,
+ * this is always "minecraft" in vanilla.
+ */ + public String getNamespace() { + return namespace; + } + + /** + * The id of this blockstate,
+ * also the name of the resource-file without the filetype that represents this block-state (found in mineceraft in assets/minecraft/blockstates).
+ */ + 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.
+ *
+ * For Example:
+ * + * facing = east
+ * half = bottom
+ *
+ */ + public Map 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 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 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 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java new file mode 100644 index 00000000..298beeb6 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/CachedBlock.java @@ -0,0 +1,130 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.
+ * The implementation can 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); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkNotGeneratedException.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkNotGeneratedException.java new file mode 100644 index 00000000..caf71289 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/ChunkNotGeneratedException.java @@ -0,0 +1,43 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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); + } +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java new file mode 100644 index 00000000..f15d5ae3 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -0,0 +1,78 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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
+ *
+ * The implementation of this class has to be thread-save!
+ */ +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.
+ * (Be aware that the collection is not cached and recollected each time from the world-files!) + */ + public default Collection getChunkList(){ + return getChunkList(0); + } + + /** + * Returns a collection of all chunks that have been modified at or after the specified timestamp.
+ * (Be aware that the collection is not cached and recollected each time from the world-files!) + */ + public Collection getChunkList(long modifiedSince); + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/WorldChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/WorldChunk.java new file mode 100644 index 00000000..17b587b3 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/WorldChunk.java @@ -0,0 +1,85 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.
+ *
+ * The implementation of this class has to be thread-save!
+ */ +public interface WorldChunk { + + /** + * Returns the top-level World of this WorldChunk, + * If this WorldChunk is already a World, the method returns the same instance (return this;). + */ + World getWorld(); + + /** + * Returns the Block on the specified position.
+ *
+ * (The implementation should not invoke the generation of new Terrain, it should rather throw a {@link ChunkNotGeneratedException} if a not generated block is requested)
+ */ + Block getBlock(Vector3i pos) throws ChunkNotGeneratedException; + + /** + * Returns the Block on the specified position.
+ *
+ * (The implementation should not invoke the generation of new Terrain, it should rather throw a {@link ChunkNotGeneratedException} if a not generated block is requested)
+ */ + 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.
+ */ + AABB getBoundaries(); + + /** + * Returns a smaller part of this WorldChunk
+ *
+ * This is used to give the implementation an easy way to optimize thread-save access to this world-chunk.
+ * The {@link #getBlock} method is and should be used in favour to {@link World#getBlock}.
+ */ + WorldChunk getWorldChunk(AABB boundaries); + + /** + * Returns true if the complete WorldChunk is generated and populated by Minecraft.
+ */ + boolean isGenerated(); + +} diff --git a/BlueMapCore/src/main/resources/DefaultResources.zip b/BlueMapCore/src/main/resources/DefaultResources.zip new file mode 100644 index 00000000..8ba93d77 Binary files /dev/null and b/BlueMapCore/src/main/resources/DefaultResources.zip differ diff --git a/BlueMapCore/src/main/resources/biomes.json b/BlueMapCore/src/main/resources/biomes.json new file mode 100644 index 00000000..946c0b47 --- /dev/null +++ b/BlueMapCore/src/main/resources/biomes.json @@ -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 + } +} diff --git a/BlueMapCore/src/main/resources/blockColors.json b/BlueMapCore/src/main/resources/blockColors.json new file mode 100644 index 00000000..b2a2e945 --- /dev/null +++ b/BlueMapCore/src/main/resources/blockColors.json @@ -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" +} \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/blockIdMappings.json b/BlueMapCore/src/main/resources/blockIdMappings.json new file mode 100644 index 00000000..232db251 --- /dev/null +++ b/BlueMapCore/src/main/resources/blockIdMappings.json @@ -0,0 +1,1487 @@ +{ + "1:0": "minecraft:stone", + "1:1": "minecraft:granite", + "1:2": "minecraft:polished_granite", + "1:3": "minecraft:diorite", + "1:4": "minecraft:polished_diorite", + "1:5": "minecraft:andesite", + "1:6": "minecraft:polished_andesite", + "2:0": "minecraft:grass_block[snowy=false]", + "3:0": "minecraft:dirt", + "3:1": "minecraft:coarse_dirt", + "3:2": "minecraft:podzol[snowy=false]", + "4:0": "minecraft:cobblestone", + "5:0": "minecraft:oak_planks", + "5:1": "minecraft:spruce_planks", + "5:2": "minecraft:birch_planks", + "5:3": "minecraft:jungle_planks", + "5:4": "minecraft:acacia_planks", + "5:5": "minecraft:dark_oak_planks", + "6:0": "minecraft:oak_sapling[stage=0]", + "6:1": "minecraft:spruce_sapling[stage=0]", + "6:2": "minecraft:birch_sapling[stage=0]", + "6:3": "minecraft:jungle_sapling[stage=0]", + "6:4": "minecraft:acacia_sapling[stage=0]", + "6:5": "minecraft:dark_oak_sapling[stage=0]", + "6:8": "minecraft:oak_sapling[stage=1]", + "6:9": "minecraft:spruce_sapling[stage=1]", + "6:10": "minecraft:birch_sapling[stage=1]", + "6:11": "minecraft:jungle_sapling[stage=1]", + "6:12": "minecraft:acacia_sapling[stage=1]", + "6:13": "minecraft:dark_oak_sapling[stage=1]", + "7:0": "minecraft:bedrock", + "8:0": "minecraft:water[level=0]", + "8:1": "minecraft:water[level=1]", + "8:2": "minecraft:water[level=2]", + "8:3": "minecraft:water[level=3]", + "8:4": "minecraft:water[level=4]", + "8:5": "minecraft:water[level=5]", + "8:6": "minecraft:water[level=6]", + "8:7": "minecraft:water[level=7]", + "8:8": "minecraft:water[level=8]", + "8:9": "minecraft:water[level=9]", + "8:10": "minecraft:water[level=10]", + "8:11": "minecraft:water[level=11]", + "8:12": "minecraft:water[level=12]", + "8:13": "minecraft:water[level=13]", + "8:14": "minecraft:water[level=14]", + "8:15": "minecraft:water[level=15]", + "9:0": "minecraft:water[level=0]", + "9:1": "minecraft:water[level=1]", + "9:2": "minecraft:water[level=2]", + "9:3": "minecraft:water[level=3]", + "9:4": "minecraft:water[level=4]", + "9:5": "minecraft:water[level=5]", + "9:6": "minecraft:water[level=6]", + "9:7": "minecraft:water[level=7]", + "9:8": "minecraft:water[level=8]", + "9:9": "minecraft:water[level=9]", + "9:10": "minecraft:water[level=10]", + "9:11": "minecraft:water[level=11]", + "9:12": "minecraft:water[level=12]", + "9:13": "minecraft:water[level=13]", + "9:14": "minecraft:water[level=14]", + "9:15": "minecraft:water[level=15]", + "10:0": "minecraft:lava[level=0]", + "10:1": "minecraft:lava[level=1]", + "10:2": "minecraft:lava[level=2]", + "10:3": "minecraft:lava[level=3]", + "10:4": "minecraft:lava[level=4]", + "10:5": "minecraft:lava[level=5]", + "10:6": "minecraft:lava[level=6]", + "10:7": "minecraft:lava[level=7]", + "10:8": "minecraft:lava[level=8]", + "10:9": "minecraft:lava[level=9]", + "10:10": "minecraft:lava[level=10]", + "10:11": "minecraft:lava[level=11]", + "10:12": "minecraft:lava[level=12]", + "10:13": "minecraft:lava[level=13]", + "10:14": "minecraft:lava[level=14]", + "10:15": "minecraft:lava[level=15]", + "11:0": "minecraft:lava[level=0]", + "11:1": "minecraft:lava[level=1]", + "11:2": "minecraft:lava[level=2]", + "11:3": "minecraft:lava[level=3]", + "11:4": "minecraft:lava[level=4]", + "11:5": "minecraft:lava[level=5]", + "11:6": "minecraft:lava[level=6]", + "11:7": "minecraft:lava[level=7]", + "11:8": "minecraft:lava[level=8]", + "11:9": "minecraft:lava[level=9]", + "11:10": "minecraft:lava[level=10]", + "11:11": "minecraft:lava[level=11]", + "11:12": "minecraft:lava[level=12]", + "11:13": "minecraft:lava[level=13]", + "11:14": "minecraft:lava[level=14]", + "11:15": "minecraft:lava[level=15]", + "12:0": "minecraft:sand", + "12:1": "minecraft:red_sand", + "13:0": "minecraft:gravel", + "14:0": "minecraft:gold_ore", + "15:0": "minecraft:iron_ore", + "16:0": "minecraft:coal_ore", + "17:0": "minecraft:oak_log[axis=y]", + "17:1": "minecraft:spruce_log[axis=y]", + "17:2": "minecraft:birch_log[axis=y]", + "17:3": "minecraft:jungle_log[axis=y]", + "17:4": "minecraft:oak_log[axis=x]", + "17:5": "minecraft:spruce_log[axis=x]", + "17:6": "minecraft:birch_log[axis=x]", + "17:7": "minecraft:jungle_log[axis=x]", + "17:8": "minecraft:oak_log[axis=z]", + "17:9": "minecraft:spruce_log[axis=z]", + "17:10": "minecraft:birch_log[axis=z]", + "17:11": "minecraft:jungle_log[axis=z]", + "17:12": "minecraft:oak_bark", + "17:13": "minecraft:spruce_bark", + "17:14": "minecraft:birch_bark", + "17:15": "minecraft:jungle_bark", + "18:0": "minecraft:oak_leaves[check_decay=false,decayable=true]", + "18:1": "minecraft:spruce_leaves[check_decay=false,decayable=true]", + "18:2": "minecraft:birch_leaves[check_decay=false,decayable=true]", + "18:3": "minecraft:jungle_leaves[check_decay=false,decayable=true]", + "18:4": "minecraft:oak_leaves[check_decay=false,decayable=false]", + "18:5": "minecraft:spruce_leaves[check_decay=false,decayable=false]", + "18:6": "minecraft:birch_leaves[check_decay=false,decayable=false]", + "18:7": "minecraft:jungle_leaves[check_decay=false,decayable=false]", + "18:8": "minecraft:oak_leaves[check_decay=true,decayable=true]", + "18:9": "minecraft:spruce_leaves[check_decay=true,decayable=true]", + "18:10": "minecraft:birch_leaves[check_decay=true,decayable=true]", + "18:11": "minecraft:jungle_leaves[check_decay=true,decayable=true]", + "18:12": "minecraft:oak_leaves[check_decay=true,decayable=false]", + "18:13": "minecraft:spruce_leaves[check_decay=true,decayable=false]", + "18:14": "minecraft:birch_leaves[check_decay=true,decayable=false]", + "18:15": "minecraft:jungle_leaves[check_decay=true,decayable=false]", + "19:0": "minecraft:sponge", + "19:1": "minecraft:wet_sponge", + "20:0": "minecraft:glass", + "21:0": "minecraft:lapis_ore", + "22:0": "minecraft:lapis_block", + "23:0": "minecraft:dispenser[facing=down,triggered=false]", + "23:1": "minecraft:dispenser[facing=up,triggered=false]", + "23:2": "minecraft:dispenser[facing=north,triggered=false]", + "23:3": "minecraft:dispenser[facing=south,triggered=false]", + "23:4": "minecraft:dispenser[facing=west,triggered=false]", + "23:5": "minecraft:dispenser[facing=east,triggered=false]", + "23:8": "minecraft:dispenser[facing=down,triggered=true]", + "23:9": "minecraft:dispenser[facing=up,triggered=true]", + "23:10": "minecraft:dispenser[facing=north,triggered=true]", + "23:11": "minecraft:dispenser[facing=south,triggered=true]", + "23:12": "minecraft:dispenser[facing=west,triggered=true]", + "23:13": "minecraft:dispenser[facing=east,triggered=true]", + "24:0": "minecraft:sandstone", + "24:1": "minecraft:chiseled_sandstone", + "24:2": "minecraft:cut_sandstone", + "25:0": "minecraft:note_block", + "27:0": "minecraft:powered_rail[powered=false,shape=north_south]", + "27:1": "minecraft:powered_rail[powered=false,shape=east_west]", + "27:2": "minecraft:powered_rail[powered=false,shape=ascending_east]", + "27:3": "minecraft:powered_rail[powered=false,shape=ascending_west]", + "27:4": "minecraft:powered_rail[powered=false,shape=ascending_north]", + "27:5": "minecraft:powered_rail[powered=false,shape=ascending_south]", + "27:8": "minecraft:powered_rail[powered=true,shape=north_south]", + "27:9": "minecraft:powered_rail[powered=true,shape=east_west]", + "27:10": "minecraft:powered_rail[powered=true,shape=ascending_east]", + "27:11": "minecraft:powered_rail[powered=true,shape=ascending_west]", + "27:12": "minecraft:powered_rail[powered=true,shape=ascending_north]", + "27:13": "minecraft:powered_rail[powered=true,shape=ascending_south]", + "28:0": "minecraft:detector_rail[powered=false,shape=north_south]", + "28:1": "minecraft:detector_rail[powered=false,shape=east_west]", + "28:2": "minecraft:detector_rail[powered=false,shape=ascending_east]", + "28:3": "minecraft:detector_rail[powered=false,shape=ascending_west]", + "28:4": "minecraft:detector_rail[powered=false,shape=ascending_north]", + "28:5": "minecraft:detector_rail[powered=false,shape=ascending_south]", + "28:8": "minecraft:detector_rail[powered=true,shape=north_south]", + "28:9": "minecraft:detector_rail[powered=true,shape=east_west]", + "28:10": "minecraft:detector_rail[powered=true,shape=ascending_east]", + "28:11": "minecraft:detector_rail[powered=true,shape=ascending_west]", + "28:12": "minecraft:detector_rail[powered=true,shape=ascending_north]", + "28:13": "minecraft:detector_rail[powered=true,shape=ascending_south]", + "29:0": "minecraft:sticky_piston[facing=down,extended=false]", + "29:1": "minecraft:sticky_piston[facing=up,extended=false]", + "29:2": "minecraft:sticky_piston[facing=north,extended=false]", + "29:3": "minecraft:sticky_piston[facing=south,extended=false]", + "29:4": "minecraft:sticky_piston[facing=west,extended=false]", + "29:5": "minecraft:sticky_piston[facing=east,extended=false]", + "29:8": "minecraft:sticky_piston[facing=down,extended=true]", + "29:9": "minecraft:sticky_piston[facing=up,extended=true]", + "29:10": "minecraft:sticky_piston[facing=north,extended=true]", + "29:11": "minecraft:sticky_piston[facing=south,extended=true]", + "29:12": "minecraft:sticky_piston[facing=west,extended=true]", + "29:13": "minecraft:sticky_piston[facing=east,extended=true]", + "30:0": "minecraft:cobweb", + "31:0": "minecraft:dead_bush", + "31:1": "minecraft:grass", + "31:2": "minecraft:fern", + "32:0": "minecraft:dead_bush", + "33:0": "minecraft:piston[facing=down,extended=false]", + "33:1": "minecraft:piston[facing=up,extended=false]", + "33:2": "minecraft:piston[facing=north,extended=false]", + "33:3": "minecraft:piston[facing=south,extended=false]", + "33:4": "minecraft:piston[facing=west,extended=false]", + "33:5": "minecraft:piston[facing=east,extended=false]", + "33:8": "minecraft:piston[facing=down,extended=true]", + "33:9": "minecraft:piston[facing=up,extended=true]", + "33:10": "minecraft:piston[facing=north,extended=true]", + "33:11": "minecraft:piston[facing=south,extended=true]", + "33:12": "minecraft:piston[facing=west,extended=true]", + "33:13": "minecraft:piston[facing=east,extended=true]", + "34:0": "minecraft:piston_head[facing=down,short=false,type=normal]", + "34:1": "minecraft:piston_head[facing=up,short=false,type=normal]", + "34:2": "minecraft:piston_head[facing=north,short=false,type=normal]", + "34:3": "minecraft:piston_head[facing=south,short=false,type=normal]", + "34:4": "minecraft:piston_head[facing=west,short=false,type=normal]", + "34:5": "minecraft:piston_head[facing=east,short=false,type=normal]", + "34:8": "minecraft:piston_head[facing=down,short=false,type=sticky]", + "34:9": "minecraft:piston_head[facing=up,short=false,type=sticky]", + "34:10": "minecraft:piston_head[facing=north,short=false,type=sticky]", + "34:11": "minecraft:piston_head[facing=south,short=false,type=sticky]", + "34:12": "minecraft:piston_head[facing=west,short=false,type=sticky]", + "34:13": "minecraft:piston_head[facing=east,short=false,type=sticky]", + "35:0": "minecraft:white_wool", + "35:1": "minecraft:orange_wool", + "35:2": "minecraft:magenta_wool", + "35:3": "minecraft:light_blue_wool", + "35:4": "minecraft:yellow_wool", + "35:5": "minecraft:lime_wool", + "35:6": "minecraft:pink_wool", + "35:7": "minecraft:gray_wool", + "35:8": "minecraft:light_gray_wool", + "35:9": "minecraft:cyan_wool", + "35:10": "minecraft:purple_wool", + "35:11": "minecraft:blue_wool", + "35:12": "minecraft:brown_wool", + "35:13": "minecraft:green_wool", + "35:14": "minecraft:red_wool", + "35:15": "minecraft:black_wool", + "37:0": "minecraft:dandelion", + "38:0": "minecraft:poppy", + "38:1": "minecraft:blue_orchid", + "38:2": "minecraft:allium", + "38:3": "minecraft:azure_bluet", + "38:4": "minecraft:red_tulip", + "38:5": "minecraft:orange_tulip", + "38:6": "minecraft:white_tulip", + "38:7": "minecraft:pink_tulip", + "38:8": "minecraft:oxeye_daisy", + "39:0": "minecraft:brown_mushroom", + "40:0": "minecraft:red_mushroom", + "41:0": "minecraft:gold_block", + "42:0": "minecraft:iron_block", + "43:0": "minecraft:stone_slab[type=double]", + "43:1": "minecraft:sandstone_slab[type=double]", + "43:2": "minecraft:petrified_oak_slab[type=double]", + "43:3": "minecraft:cobblestone_slab[type=double]", + "43:4": "minecraft:brick_slab[type=double]", + "43:5": "minecraft:stone_brick_slab[type=double]", + "43:6": "minecraft:nether_brick_slab[type=double]", + "43:7": "minecraft:quartz_slab[type=double]", + "43:8": "minecraft:smooth_stone", + "43:9": "minecraft:smooth_sandstone", + "43:10": "minecraft:petrified_oak_slab[type=double]", + "43:11": "minecraft:cobblestone_slab[type=double]", + "43:12": "minecraft:brick_slab[type=double]", + "43:13": "minecraft:stone_brick_slab[type=double]", + "43:14": "minecraft:nether_brick_slab[type=double]", + "43:15": "minecraft:smooth_quartz", + "44:0": "minecraft:stone_slab[type=bottom]", + "44:1": "minecraft:sandstone_slab[type=bottom]", + "44:2": "minecraft:petrified_oak_slab[type=bottom]", + "44:3": "minecraft:cobblestone_slab[type=bottom]", + "44:4": "minecraft:brick_slab[type=bottom]", + "44:5": "minecraft:stone_brick_slab[type=bottom]", + "44:6": "minecraft:nether_brick_slab[type=bottom]", + "44:7": "minecraft:quartz_slab[type=bottom]", + "44:8": "minecraft:stone_slab[type=top]", + "44:9": "minecraft:sandstone_slab[type=top]", + "44:10": "minecraft:petrified_oak_slab[type=top]", + "44:11": "minecraft:cobblestone_slab[type=top]", + "44:12": "minecraft:brick_slab[type=top]", + "44:13": "minecraft:stone_brick_slab[type=top]", + "44:14": "minecraft:nether_brick_slab[type=top]", + "44:15": "minecraft:quartz_slab[type=top]", + "45:0": "minecraft:bricks", + "46:0": "minecraft:tnt[unstable=false]", + "46:1": "minecraft:tnt[unstable=true]", + "47:0": "minecraft:bookshelf", + "48:0": "minecraft:mossy_cobblestone", + "49:0": "minecraft:obsidian", + "50:1": "minecraft:wall_torch[facing=east]", + "50:2": "minecraft:wall_torch[facing=west]", + "50:3": "minecraft:wall_torch[facing=south]", + "50:4": "minecraft:wall_torch[facing=north]", + "50:5": "minecraft:torch", + "51:0": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=0]", + "51:1": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=1]", + "51:2": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=2]", + "51:3": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=3]", + "51:4": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=4]", + "51:5": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=5]", + "51:6": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=6]", + "51:7": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=7]", + "51:8": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=8]", + "51:9": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=9]", + "51:10": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=10]", + "51:11": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=11]", + "51:12": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=12]", + "51:13": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=13]", + "51:14": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=14]", + "51:15": "minecraft:fire[east=false,south=false,north=false,west=false,up=false,age=15]", + "52:0": "minecraft:mob_spawner", + "53:0": "minecraft:oak_stairs[facing=east,half=bottom,shape=straight]", + "53:1": "minecraft:oak_stairs[facing=west,half=bottom,shape=straight]", + "53:2": "minecraft:oak_stairs[facing=south,half=bottom,shape=straight]", + "53:3": "minecraft:oak_stairs[facing=north,half=bottom,shape=straight]", + "53:4": "minecraft:oak_stairs[facing=east,half=top,shape=straight]", + "53:5": "minecraft:oak_stairs[facing=west,half=top,shape=straight]", + "53:6": "minecraft:oak_stairs[facing=south,half=top,shape=straight]", + "53:7": "minecraft:oak_stairs[facing=north,half=top,shape=straight]", + "55:0": "minecraft:redstone_wire[west=none,east=none,power=0,south=none,north=none]", + "55:1": "minecraft:redstone_wire[west=none,east=none,power=1,south=none,north=none]", + "55:2": "minecraft:redstone_wire[west=none,east=none,power=2,south=none,north=none]", + "55:3": "minecraft:redstone_wire[west=none,east=none,power=3,south=none,north=none]", + "55:4": "minecraft:redstone_wire[west=none,east=none,power=4,south=none,north=none]", + "55:5": "minecraft:redstone_wire[west=none,east=none,power=5,south=none,north=none]", + "55:6": "minecraft:redstone_wire[west=none,east=none,power=6,south=none,north=none]", + "55:7": "minecraft:redstone_wire[west=none,east=none,power=7,south=none,north=none]", + "55:8": "minecraft:redstone_wire[west=none,east=none,power=8,south=none,north=none]", + "55:9": "minecraft:redstone_wire[west=none,east=none,power=9,south=none,north=none]", + "55:10": "minecraft:redstone_wire[west=none,east=none,power=10,south=none,north=none]", + "55:11": "minecraft:redstone_wire[west=none,east=none,power=11,south=none,north=none]", + "55:12": "minecraft:redstone_wire[west=none,east=none,power=12,south=none,north=none]", + "55:13": "minecraft:redstone_wire[west=none,east=none,power=13,south=none,north=none]", + "55:14": "minecraft:redstone_wire[west=none,east=none,power=14,south=none,north=none]", + "55:15": "minecraft:redstone_wire[west=none,east=none,power=15,south=none,north=none]", + "56:0": "minecraft:diamond_ore", + "57:0": "minecraft:diamond_block", + "58:0": "minecraft:crafting_table", + "59:0": "minecraft:wheat[age=0]", + "59:1": "minecraft:wheat[age=1]", + "59:2": "minecraft:wheat[age=2]", + "59:3": "minecraft:wheat[age=3]", + "59:4": "minecraft:wheat[age=4]", + "59:5": "minecraft:wheat[age=5]", + "59:6": "minecraft:wheat[age=6]", + "59:7": "minecraft:wheat[age=7]", + "60:0": "minecraft:farmland[moisture=0]", + "60:1": "minecraft:farmland[moisture=1]", + "60:2": "minecraft:farmland[moisture=2]", + "60:3": "minecraft:farmland[moisture=3]", + "60:4": "minecraft:farmland[moisture=4]", + "60:5": "minecraft:farmland[moisture=5]", + "60:6": "minecraft:farmland[moisture=6]", + "60:7": "minecraft:farmland[moisture=7]", + "61:2": "minecraft:furnace[facing=north,lit=false]", + "61:3": "minecraft:furnace[facing=south,lit=false]", + "61:4": "minecraft:furnace[facing=west,lit=false]", + "61:5": "minecraft:furnace[facing=east,lit=false]", + "62:2": "minecraft:furnace[facing=north,lit=true]", + "62:3": "minecraft:furnace[facing=south,lit=true]", + "62:4": "minecraft:furnace[facing=west,lit=true]", + "62:5": "minecraft:furnace[facing=east,lit=true]", + "64:0": "minecraft:oak_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "64:1": "minecraft:oak_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "64:2": "minecraft:oak_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "64:3": "minecraft:oak_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "64:4": "minecraft:oak_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "64:5": "minecraft:oak_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "64:6": "minecraft:oak_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "64:7": "minecraft:oak_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "64:8": "minecraft:oak_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "64:9": "minecraft:oak_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "64:10": "minecraft:oak_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "64:11": "minecraft:oak_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "65:2": "minecraft:ladder[facing=north]", + "65:3": "minecraft:ladder[facing=south]", + "65:4": "minecraft:ladder[facing=west]", + "65:5": "minecraft:ladder[facing=east]", + "66:0": "minecraft:rail[shape=north_south]", + "66:1": "minecraft:rail[shape=east_west]", + "66:2": "minecraft:rail[shape=ascending_east]", + "66:3": "minecraft:rail[shape=ascending_west]", + "66:4": "minecraft:rail[shape=ascending_north]", + "66:5": "minecraft:rail[shape=ascending_south]", + "66:6": "minecraft:rail[shape=south_east]", + "66:7": "minecraft:rail[shape=south_west]", + "66:8": "minecraft:rail[shape=north_west]", + "66:9": "minecraft:rail[shape=north_east]", + "67:0": "minecraft:cobblestone_stairs[facing=east,half=bottom,shape=straight]", + "67:1": "minecraft:cobblestone_stairs[facing=west,half=bottom,shape=straight]", + "67:2": "minecraft:cobblestone_stairs[facing=south,half=bottom,shape=straight]", + "67:3": "minecraft:cobblestone_stairs[facing=north,half=bottom,shape=straight]", + "67:4": "minecraft:cobblestone_stairs[facing=east,half=top,shape=straight]", + "67:5": "minecraft:cobblestone_stairs[facing=west,half=top,shape=straight]", + "67:6": "minecraft:cobblestone_stairs[facing=south,half=top,shape=straight]", + "67:7": "minecraft:cobblestone_stairs[facing=north,half=top,shape=straight]", + "69:0": "minecraft:lever[facing=west,face=ceiling,powered=false]", + "69:1": "minecraft:lever[facing=east,face=wall,powered=false]", + "69:2": "minecraft:lever[facing=west,face=wall,powered=false]", + "69:3": "minecraft:lever[facing=south,face=wall,powered=false]", + "69:4": "minecraft:lever[facing=north,face=wall,powered=false]", + "69:5": "minecraft:lever[facing=north,face=floor,powered=false]", + "69:6": "minecraft:lever[facing=west,face=floor,powered=false]", + "69:7": "minecraft:lever[facing=north,face=ceiling,powered=false]", + "69:8": "minecraft:lever[facing=west,face=ceiling,powered=true]", + "69:9": "minecraft:lever[facing=east,face=wall,powered=true]", + "69:10": "minecraft:lever[facing=west,face=wall,powered=true]", + "69:11": "minecraft:lever[facing=south,face=wall,powered=true]", + "69:12": "minecraft:lever[facing=north,face=wall,powered=true]", + "69:13": "minecraft:lever[facing=north,face=floor,powered=true]", + "69:14": "minecraft:lever[facing=west,face=floor,powered=true]", + "69:15": "minecraft:lever[facing=north,face=ceiling,powered=true]", + "70:0": "minecraft:stone_pressure_plate[powered=false]", + "70:1": "minecraft:stone_pressure_plate[powered=true]", + "71:0": "minecraft:iron_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "71:1": "minecraft:iron_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "71:2": "minecraft:iron_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "71:3": "minecraft:iron_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "71:4": "minecraft:iron_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "71:5": "minecraft:iron_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "71:6": "minecraft:iron_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "71:7": "minecraft:iron_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "71:8": "minecraft:iron_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "71:9": "minecraft:iron_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "71:10": "minecraft:iron_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "71:11": "minecraft:iron_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "72:0": "minecraft:oak_pressure_plate[powered=false]", + "72:1": "minecraft:oak_pressure_plate[powered=true]", + "73:0": "minecraft:redstone_ore[lit=false]", + "74:0": "minecraft:redstone_ore[lit=true]", + "75:1": "minecraft:redstone_wall_torch[facing=east,lit=false]", + "75:2": "minecraft:redstone_wall_torch[facing=west,lit=false]", + "75:3": "minecraft:redstone_wall_torch[facing=south,lit=false]", + "75:4": "minecraft:redstone_wall_torch[facing=north,lit=false]", + "75:5": "minecraft:redstone_torch[lit=false]", + "76:1": "minecraft:redstone_wall_torch[facing=east,lit=true]", + "76:2": "minecraft:redstone_wall_torch[facing=west,lit=true]", + "76:3": "minecraft:redstone_wall_torch[facing=south,lit=true]", + "76:4": "minecraft:redstone_wall_torch[facing=north,lit=true]", + "76:5": "minecraft:redstone_torch[lit=true]", + "77:0": "minecraft:stone_button[facing=north,face=ceiling,powered=false]", + "77:1": "minecraft:stone_button[facing=east,face=wall,powered=false]", + "77:2": "minecraft:stone_button[facing=west,face=wall,powered=false]", + "77:3": "minecraft:stone_button[facing=south,face=wall,powered=false]", + "77:4": "minecraft:stone_button[facing=north,face=wall,powered=false]", + "77:5": "minecraft:stone_button[facing=north,face=floor,powered=false]", + "77:8": "minecraft:stone_button[facing=north,face=ceiling,powered=true]", + "77:9": "minecraft:stone_button[facing=east,face=wall,powered=true]", + "77:10": "minecraft:stone_button[facing=west,face=wall,powered=true]", + "77:11": "minecraft:stone_button[facing=south,face=wall,powered=true]", + "77:12": "minecraft:stone_button[facing=north,face=wall,powered=true]", + "77:13": "minecraft:stone_button[facing=north,face=floor,powered=true]", + "78:0": "minecraft:snow[layers=1]", + "78:1": "minecraft:snow[layers=2]", + "78:2": "minecraft:snow[layers=3]", + "78:3": "minecraft:snow[layers=4]", + "78:4": "minecraft:snow[layers=5]", + "78:5": "minecraft:snow[layers=6]", + "78:6": "minecraft:snow[layers=7]", + "78:7": "minecraft:snow[layers=8]", + "79:0": "minecraft:ice", + "80:0": "minecraft:snow_block", + "81:0": "minecraft:cactus[age=0]", + "81:1": "minecraft:cactus[age=1]", + "81:2": "minecraft:cactus[age=2]", + "81:3": "minecraft:cactus[age=3]", + "81:4": "minecraft:cactus[age=4]", + "81:5": "minecraft:cactus[age=5]", + "81:6": "minecraft:cactus[age=6]", + "81:7": "minecraft:cactus[age=7]", + "81:8": "minecraft:cactus[age=8]", + "81:9": "minecraft:cactus[age=9]", + "81:10": "minecraft:cactus[age=10]", + "81:11": "minecraft:cactus[age=11]", + "81:12": "minecraft:cactus[age=12]", + "81:13": "minecraft:cactus[age=13]", + "81:14": "minecraft:cactus[age=14]", + "81:15": "minecraft:cactus[age=15]", + "82:0": "minecraft:clay", + "83:0": "minecraft:sugar_cane[age=0]", + "83:1": "minecraft:sugar_cane[age=1]", + "83:2": "minecraft:sugar_cane[age=2]", + "83:3": "minecraft:sugar_cane[age=3]", + "83:4": "minecraft:sugar_cane[age=4]", + "83:5": "minecraft:sugar_cane[age=5]", + "83:6": "minecraft:sugar_cane[age=6]", + "83:7": "minecraft:sugar_cane[age=7]", + "83:8": "minecraft:sugar_cane[age=8]", + "83:9": "minecraft:sugar_cane[age=9]", + "83:10": "minecraft:sugar_cane[age=10]", + "83:11": "minecraft:sugar_cane[age=11]", + "83:12": "minecraft:sugar_cane[age=12]", + "83:13": "minecraft:sugar_cane[age=13]", + "83:14": "minecraft:sugar_cane[age=14]", + "83:15": "minecraft:sugar_cane[age=15]", + "84:0": "minecraft:jukebox[has_record=false]", + "84:1": "minecraft:jukebox[has_record=true]", + "85:0": "minecraft:oak_fence[west=false,east=false,south=false,north=false]", + "86:0": "minecraft:carved_pumpkin[facing=south]", + "86:1": "minecraft:carved_pumpkin[facing=west]", + "86:2": "minecraft:carved_pumpkin[facing=north]", + "86:3": "minecraft:carved_pumpkin[facing=east]", + "87:0": "minecraft:netherrack", + "88:0": "minecraft:soul_sand", + "89:0": "minecraft:glowstone", + "90:1": "minecraft:portal[axis=x]", + "90:2": "minecraft:portal[axis=z]", + "91:0": "minecraft:jack_o_lantern[facing=south]", + "91:1": "minecraft:jack_o_lantern[facing=west]", + "91:2": "minecraft:jack_o_lantern[facing=north]", + "91:3": "minecraft:jack_o_lantern[facing=east]", + "92:0": "minecraft:cake[bites=0]", + "92:1": "minecraft:cake[bites=1]", + "92:2": "minecraft:cake[bites=2]", + "92:3": "minecraft:cake[bites=3]", + "92:4": "minecraft:cake[bites=4]", + "92:5": "minecraft:cake[bites=5]", + "92:6": "minecraft:cake[bites=6]", + "93:0": "minecraft:repeater[facing=south,delay=1,powered=false,locked=false]", + "93:1": "minecraft:repeater[facing=west,delay=1,powered=false,locked=false]", + "93:2": "minecraft:repeater[facing=north,delay=1,powered=false,locked=false]", + "93:3": "minecraft:repeater[facing=east,delay=1,powered=false,locked=false]", + "93:4": "minecraft:repeater[facing=south,delay=2,powered=false,locked=false]", + "93:5": "minecraft:repeater[facing=west,delay=2,powered=false,locked=false]", + "93:6": "minecraft:repeater[facing=north,delay=2,powered=false,locked=false]", + "93:7": "minecraft:repeater[facing=east,delay=2,powered=false,locked=false]", + "93:8": "minecraft:repeater[facing=south,delay=3,powered=false,locked=false]", + "93:9": "minecraft:repeater[facing=west,delay=3,powered=false,locked=false]", + "93:10": "minecraft:repeater[facing=north,delay=3,powered=false,locked=false]", + "93:11": "minecraft:repeater[facing=east,delay=3,powered=false,locked=false]", + "93:12": "minecraft:repeater[facing=south,delay=4,powered=false,locked=false]", + "93:13": "minecraft:repeater[facing=west,delay=4,powered=false,locked=false]", + "93:14": "minecraft:repeater[facing=north,delay=4,powered=false,locked=false]", + "93:15": "minecraft:repeater[facing=east,delay=4,powered=false,locked=false]", + "94:0": "minecraft:repeater[facing=south,delay=1,powered=true,locked=false]", + "94:1": "minecraft:repeater[facing=west,delay=1,powered=true,locked=false]", + "94:2": "minecraft:repeater[facing=north,delay=1,powered=true,locked=false]", + "94:3": "minecraft:repeater[facing=east,delay=1,powered=true,locked=false]", + "94:4": "minecraft:repeater[facing=south,delay=2,powered=true,locked=false]", + "94:5": "minecraft:repeater[facing=west,delay=2,powered=true,locked=false]", + "94:6": "minecraft:repeater[facing=north,delay=2,powered=true,locked=false]", + "94:7": "minecraft:repeater[facing=east,delay=2,powered=true,locked=false]", + "94:8": "minecraft:repeater[facing=south,delay=3,powered=true,locked=false]", + "94:9": "minecraft:repeater[facing=west,delay=3,powered=true,locked=false]", + "94:10": "minecraft:repeater[facing=north,delay=3,powered=true,locked=false]", + "94:11": "minecraft:repeater[facing=east,delay=3,powered=true,locked=false]", + "94:12": "minecraft:repeater[facing=south,delay=4,powered=true,locked=false]", + "94:13": "minecraft:repeater[facing=west,delay=4,powered=true,locked=false]", + "94:14": "minecraft:repeater[facing=north,delay=4,powered=true,locked=false]", + "94:15": "minecraft:repeater[facing=east,delay=4,powered=true,locked=false]", + "95:0": "minecraft:white_stained_glass", + "95:1": "minecraft:orange_stained_glass", + "95:2": "minecraft:magenta_stained_glass", + "95:3": "minecraft:light_blue_stained_glass", + "95:4": "minecraft:yellow_stained_glass", + "95:5": "minecraft:lime_stained_glass", + "95:6": "minecraft:pink_stained_glass", + "95:7": "minecraft:gray_stained_glass", + "95:8": "minecraft:light_gray_stained_glass", + "95:9": "minecraft:cyan_stained_glass", + "95:10": "minecraft:purple_stained_glass", + "95:11": "minecraft:blue_stained_glass", + "95:12": "minecraft:brown_stained_glass", + "95:13": "minecraft:green_stained_glass", + "95:14": "minecraft:red_stained_glass", + "95:15": "minecraft:black_stained_glass", + "96:0": "minecraft:oak_trapdoor[facing=north,half=bottom,open=false]", + "96:1": "minecraft:oak_trapdoor[facing=south,half=bottom,open=false]", + "96:2": "minecraft:oak_trapdoor[facing=west,half=bottom,open=false]", + "96:3": "minecraft:oak_trapdoor[facing=east,half=bottom,open=false]", + "96:4": "minecraft:oak_trapdoor[facing=north,half=bottom,open=true]", + "96:5": "minecraft:oak_trapdoor[facing=south,half=bottom,open=true]", + "96:6": "minecraft:oak_trapdoor[facing=west,half=bottom,open=true]", + "96:7": "minecraft:oak_trapdoor[facing=east,half=bottom,open=true]", + "96:8": "minecraft:oak_trapdoor[facing=north,half=top,open=false]", + "96:9": "minecraft:oak_trapdoor[facing=south,half=top,open=false]", + "96:10": "minecraft:oak_trapdoor[facing=west,half=top,open=false]", + "96:11": "minecraft:oak_trapdoor[facing=east,half=top,open=false]", + "96:12": "minecraft:oak_trapdoor[facing=north,half=top,open=true]", + "96:13": "minecraft:oak_trapdoor[facing=south,half=top,open=true]", + "96:14": "minecraft:oak_trapdoor[facing=west,half=top,open=true]", + "96:15": "minecraft:oak_trapdoor[facing=east,half=top,open=true]", + "97:0": "minecraft:infested_stone", + "97:1": "minecraft:infested_cobblestone", + "97:2": "minecraft:infested_stone_bricks", + "97:3": "minecraft:infested_mossy_stone_bricks", + "97:4": "minecraft:infested_cracked_stone_bricks", + "97:5": "minecraft:infested_chiseled_stone_bricks", + "98:0": "minecraft:stone_bricks", + "98:1": "minecraft:mossy_stone_bricks", + "98:2": "minecraft:cracked_stone_bricks", + "98:3": "minecraft:chiseled_stone_bricks", + "99:0": "minecraft:brown_mushroom_block[east=false,south=false,north=false,west=false,up=false,down=false]", + "99:1": "minecraft:brown_mushroom_block[east=false,south=false,north=true,west=true,up=true,down=false]", + "99:2": "minecraft:brown_mushroom_block[east=false,south=false,north=true,west=false,up=true,down=false]", + "99:3": "minecraft:brown_mushroom_block[east=true,south=false,north=true,west=false,up=true,down=false]", + "99:4": "minecraft:brown_mushroom_block[east=false,south=false,north=false,west=true,up=true,down=false]", + "99:5": "minecraft:brown_mushroom_block[east=false,south=false,north=false,west=false,up=true,down=false]", + "99:6": "minecraft:brown_mushroom_block[east=true,south=false,north=false,west=false,up=true,down=false]", + "99:7": "minecraft:brown_mushroom_block[east=false,south=true,north=false,west=true,up=true,down=false]", + "99:8": "minecraft:brown_mushroom_block[east=false,south=true,north=false,west=false,up=true,down=false]", + "99:9": "minecraft:brown_mushroom_block[east=true,south=true,north=false,west=false,up=true,down=false]", + "99:10": "minecraft:mushroom_stem[east=true,south=true,north=true,west=true,up=false,down=false]", + "99:14": "minecraft:brown_mushroom_block[east=true,south=true,north=true,west=true,up=true,down=true]", + "99:15": "minecraft:mushroom_stem[east=true,south=true,north=true,west=true,up=true,down=true]", + "100:0": "minecraft:red_mushroom_block[east=false,south=false,north=false,west=false,up=false,down=false]", + "100:1": "minecraft:red_mushroom_block[east=false,south=false,north=true,west=true,up=true,down=false]", + "100:2": "minecraft:red_mushroom_block[east=false,south=false,north=true,west=false,up=true,down=false]", + "100:3": "minecraft:red_mushroom_block[east=true,south=false,north=true,west=false,up=true,down=false]", + "100:4": "minecraft:red_mushroom_block[east=false,south=false,north=false,west=true,up=true,down=false]", + "100:5": "minecraft:red_mushroom_block[east=false,south=false,north=false,west=false,up=true,down=false]", + "100:6": "minecraft:red_mushroom_block[east=true,south=false,north=false,west=false,up=true,down=false]", + "100:7": "minecraft:red_mushroom_block[east=false,south=true,north=false,west=true,up=true,down=false]", + "100:8": "minecraft:red_mushroom_block[east=false,south=true,north=false,west=false,up=true,down=false]", + "100:9": "minecraft:red_mushroom_block[east=true,south=true,north=false,west=false,up=true,down=false]", + "100:10": "minecraft:mushroom_stem[east=true,south=true,north=true,west=true,up=false,down=false]", + "100:14": "minecraft:red_mushroom_block[east=true,south=true,north=true,west=true,up=true,down=true]", + "100:15": "minecraft:mushroom_stem[east=true,south=true,north=true,west=true,up=true,down=true]", + "101:0": "minecraft:iron_bars[west=false,east=false,south=false,north=false]", + "102:0": "minecraft:glass_pane[west=false,east=false,south=false,north=false]", + "103:0": "minecraft:melon_block", + "104:0": "minecraft:pumpkin_stem[age=0]", + "104:1": "minecraft:pumpkin_stem[age=1]", + "104:2": "minecraft:pumpkin_stem[age=2]", + "104:3": "minecraft:pumpkin_stem[age=3]", + "104:4": "minecraft:pumpkin_stem[age=4]", + "104:5": "minecraft:pumpkin_stem[age=5]", + "104:6": "minecraft:pumpkin_stem[age=6]", + "104:7": "minecraft:pumpkin_stem[age=7]", + "105:0": "minecraft:melon_stem[age=0]", + "105:1": "minecraft:melon_stem[age=1]", + "105:2": "minecraft:melon_stem[age=2]", + "105:3": "minecraft:melon_stem[age=3]", + "105:4": "minecraft:melon_stem[age=4]", + "105:5": "minecraft:melon_stem[age=5]", + "105:6": "minecraft:melon_stem[age=6]", + "105:7": "minecraft:melon_stem[age=7]", + "106:0": "minecraft:vine[west=false,east=false,up=true,south=false,north=false]", + "106:1": "minecraft:vine[west=false,east=false,up=true,south=true,north=false]", + "106:2": "minecraft:vine[west=true,east=false,up=true,south=false,north=false]", + "106:3": "minecraft:vine[west=true,east=false,up=true,south=true,north=false]", + "106:4": "minecraft:vine[west=false,east=false,up=true,south=false,north=true]", + "106:5": "minecraft:vine[west=false,east=false,up=true,south=true,north=true]", + "106:6": "minecraft:vine[west=true,east=false,up=true,south=false,north=true]", + "106:7": "minecraft:vine[west=true,east=false,up=true,south=true,north=true]", + "106:8": "minecraft:vine[west=false,east=true,up=true,south=false,north=false]", + "106:9": "minecraft:vine[west=false,east=true,up=true,south=true,north=false]", + "106:10": "minecraft:vine[west=true,east=true,up=true,south=false,north=false]", + "106:11": "minecraft:vine[west=true,east=true,up=true,south=true,north=false]", + "106:12": "minecraft:vine[west=false,east=true,up=true,south=false,north=true]", + "106:13": "minecraft:vine[west=false,east=true,up=true,south=true,north=true]", + "106:14": "minecraft:vine[west=true,east=true,up=true,south=false,north=true]", + "106:15": "minecraft:vine[west=true,east=true,up=true,south=true,north=true]", + "107:0": "minecraft:oak_fence_gate[in_wall=false,facing=south,powered=false,open=false]", + "107:1": "minecraft:oak_fence_gate[in_wall=false,facing=west,powered=false,open=false]", + "107:2": "minecraft:oak_fence_gate[in_wall=false,facing=north,powered=false,open=false]", + "107:3": "minecraft:oak_fence_gate[in_wall=false,facing=east,powered=false,open=false]", + "107:4": "minecraft:oak_fence_gate[in_wall=false,facing=south,powered=false,open=true]", + "107:5": "minecraft:oak_fence_gate[in_wall=false,facing=west,powered=false,open=true]", + "107:6": "minecraft:oak_fence_gate[in_wall=false,facing=north,powered=false,open=true]", + "107:7": "minecraft:oak_fence_gate[in_wall=false,facing=east,powered=false,open=true]", + "107:8": "minecraft:oak_fence_gate[in_wall=false,facing=south,powered=true,open=false]", + "107:9": "minecraft:oak_fence_gate[in_wall=false,facing=west,powered=true,open=false]", + "107:10": "minecraft:oak_fence_gate[in_wall=false,facing=north,powered=true,open=false]", + "107:11": "minecraft:oak_fence_gate[in_wall=false,facing=east,powered=true,open=false]", + "107:12": "minecraft:oak_fence_gate[in_wall=false,facing=south,powered=true,open=true]", + "107:13": "minecraft:oak_fence_gate[in_wall=false,facing=west,powered=true,open=true]", + "107:14": "minecraft:oak_fence_gate[in_wall=false,facing=north,powered=true,open=true]", + "107:15": "minecraft:oak_fence_gate[in_wall=false,facing=east,powered=true,open=true]", + "108:0": "minecraft:brick_stairs[facing=east,half=bottom,shape=straight]", + "108:1": "minecraft:brick_stairs[facing=west,half=bottom,shape=straight]", + "108:2": "minecraft:brick_stairs[facing=south,half=bottom,shape=straight]", + "108:3": "minecraft:brick_stairs[facing=north,half=bottom,shape=straight]", + "108:4": "minecraft:brick_stairs[facing=east,half=top,shape=straight]", + "108:5": "minecraft:brick_stairs[facing=west,half=top,shape=straight]", + "108:6": "minecraft:brick_stairs[facing=south,half=top,shape=straight]", + "108:7": "minecraft:brick_stairs[facing=north,half=top,shape=straight]", + "109:0": "minecraft:stone_brick_stairs[facing=east,half=bottom,shape=straight]", + "109:1": "minecraft:stone_brick_stairs[facing=west,half=bottom,shape=straight]", + "109:2": "minecraft:stone_brick_stairs[facing=south,half=bottom,shape=straight]", + "109:3": "minecraft:stone_brick_stairs[facing=north,half=bottom,shape=straight]", + "109:4": "minecraft:stone_brick_stairs[facing=east,half=top,shape=straight]", + "109:5": "minecraft:stone_brick_stairs[facing=west,half=top,shape=straight]", + "109:6": "minecraft:stone_brick_stairs[facing=south,half=top,shape=straight]", + "109:7": "minecraft:stone_brick_stairs[facing=north,half=top,shape=straight]", + "110:0": "minecraft:mycelium[snowy=false]", + "111:0": "minecraft:lily_pad", + "112:0": "minecraft:nether_bricks", + "113:0": "minecraft:nether_brick_fence[west=false,east=false,south=false,north=false]", + "114:0": "minecraft:nether_brick_stairs[facing=east,half=bottom,shape=straight]", + "114:1": "minecraft:nether_brick_stairs[facing=west,half=bottom,shape=straight]", + "114:2": "minecraft:nether_brick_stairs[facing=south,half=bottom,shape=straight]", + "114:3": "minecraft:nether_brick_stairs[facing=north,half=bottom,shape=straight]", + "114:4": "minecraft:nether_brick_stairs[facing=east,half=top,shape=straight]", + "114:5": "minecraft:nether_brick_stairs[facing=west,half=top,shape=straight]", + "114:6": "minecraft:nether_brick_stairs[facing=south,half=top,shape=straight]", + "114:7": "minecraft:nether_brick_stairs[facing=north,half=top,shape=straight]", + "115:0": "minecraft:nether_wart[age=0]", + "115:1": "minecraft:nether_wart[age=1]", + "115:2": "minecraft:nether_wart[age=2]", + "115:3": "minecraft:nether_wart[age=3]", + "116:0": "minecraft:enchanting_table", + "117:0": "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", + "117:1": "minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=false,has_bottle_2=false]", + "117:2": "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=true,has_bottle_2=false]", + "117:3": "minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=true,has_bottle_2=false]", + "117:4": "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=true]", + "117:5": "minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=false,has_bottle_2=true]", + "117:6": "minecraft:brewing_stand[has_bottle_0=false,has_bottle_1=true,has_bottle_2=true]", + "117:7": "minecraft:brewing_stand[has_bottle_0=true,has_bottle_1=true,has_bottle_2=true]", + "118:0": "minecraft:cauldron[level=0]", + "118:1": "minecraft:cauldron[level=1]", + "118:2": "minecraft:cauldron[level=2]", + "118:3": "minecraft:cauldron[level=3]", + "120:0": "minecraft:end_portal_frame[eye=false,facing=south]", + "120:1": "minecraft:end_portal_frame[eye=false,facing=west]", + "120:2": "minecraft:end_portal_frame[eye=false,facing=north]", + "120:3": "minecraft:end_portal_frame[eye=false,facing=east]", + "120:4": "minecraft:end_portal_frame[eye=true,facing=south]", + "120:5": "minecraft:end_portal_frame[eye=true,facing=west]", + "120:6": "minecraft:end_portal_frame[eye=true,facing=north]", + "120:7": "minecraft:end_portal_frame[eye=true,facing=east]", + "121:0": "minecraft:end_stone", + "122:0": "minecraft:dragon_egg", + "123:0": "minecraft:redstone_lamp[lit=false]", + "124:0": "minecraft:redstone_lamp[lit=true]", + "125:0": "minecraft:oak_slab[type=double]", + "125:1": "minecraft:spruce_slab[type=double]", + "125:2": "minecraft:birch_slab[type=double]", + "125:3": "minecraft:jungle_slab[type=double]", + "125:4": "minecraft:acacia_slab[type=double]", + "125:5": "minecraft:dark_oak_slab[type=double]", + "126:0": "minecraft:oak_slab[type=bottom]", + "126:1": "minecraft:spruce_slab[type=bottom]", + "126:2": "minecraft:birch_slab[type=bottom]", + "126:3": "minecraft:jungle_slab[type=bottom]", + "126:4": "minecraft:acacia_slab[type=bottom]", + "126:5": "minecraft:dark_oak_slab[type=bottom]", + "126:8": "minecraft:oak_slab[type=top]", + "126:9": "minecraft:spruce_slab[type=top]", + "126:10": "minecraft:birch_slab[type=top]", + "126:11": "minecraft:jungle_slab[type=top]", + "126:12": "minecraft:acacia_slab[type=top]", + "126:13": "minecraft:dark_oak_slab[type=top]", + "127:0": "minecraft:cocoa[facing=south,age=0]", + "127:1": "minecraft:cocoa[facing=west,age=0]", + "127:2": "minecraft:cocoa[facing=north,age=0]", + "127:3": "minecraft:cocoa[facing=east,age=0]", + "127:4": "minecraft:cocoa[facing=south,age=1]", + "127:5": "minecraft:cocoa[facing=west,age=1]", + "127:6": "minecraft:cocoa[facing=north,age=1]", + "127:7": "minecraft:cocoa[facing=east,age=1]", + "127:8": "minecraft:cocoa[facing=south,age=2]", + "127:9": "minecraft:cocoa[facing=west,age=2]", + "127:10": "minecraft:cocoa[facing=north,age=2]", + "127:11": "minecraft:cocoa[facing=east,age=2]", + "128:0": "minecraft:sandstone_stairs[facing=east,half=bottom,shape=straight]", + "128:1": "minecraft:sandstone_stairs[facing=west,half=bottom,shape=straight]", + "128:2": "minecraft:sandstone_stairs[facing=south,half=bottom,shape=straight]", + "128:3": "minecraft:sandstone_stairs[facing=north,half=bottom,shape=straight]", + "128:4": "minecraft:sandstone_stairs[facing=east,half=top,shape=straight]", + "128:5": "minecraft:sandstone_stairs[facing=west,half=top,shape=straight]", + "128:6": "minecraft:sandstone_stairs[facing=south,half=top,shape=straight]", + "128:7": "minecraft:sandstone_stairs[facing=north,half=top,shape=straight]", + "129:0": "minecraft:emerald_ore", + "131:0": "minecraft:tripwire_hook[attached=false,facing=south,powered=false]", + "131:1": "minecraft:tripwire_hook[attached=false,facing=west,powered=false]", + "131:2": "minecraft:tripwire_hook[attached=false,facing=north,powered=false]", + "131:3": "minecraft:tripwire_hook[attached=false,facing=east,powered=false]", + "131:4": "minecraft:tripwire_hook[attached=true,facing=south,powered=false]", + "131:5": "minecraft:tripwire_hook[attached=true,facing=west,powered=false]", + "131:6": "minecraft:tripwire_hook[attached=true,facing=north,powered=false]", + "131:7": "minecraft:tripwire_hook[attached=true,facing=east,powered=false]", + "131:8": "minecraft:tripwire_hook[attached=false,facing=south,powered=true]", + "131:9": "minecraft:tripwire_hook[attached=false,facing=west,powered=true]", + "131:10": "minecraft:tripwire_hook[attached=false,facing=north,powered=true]", + "131:11": "minecraft:tripwire_hook[attached=false,facing=east,powered=true]", + "131:12": "minecraft:tripwire_hook[attached=true,facing=south,powered=true]", + "131:13": "minecraft:tripwire_hook[attached=true,facing=west,powered=true]", + "131:14": "minecraft:tripwire_hook[attached=true,facing=north,powered=true]", + "131:15": "minecraft:tripwire_hook[attached=true,facing=east,powered=true]", + "132:0": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,attached=false,west=false]", + "132:1": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,attached=false,west=false]", + "132:4": "minecraft:tripwire[disarmed=false,east=false,powered=false,south=false,north=false,attached=true,west=false]", + "132:5": "minecraft:tripwire[disarmed=false,east=false,powered=true,south=false,north=false,attached=true,west=false]", + "132:8": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,attached=false,west=false]", + "132:9": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,attached=false,west=false]", + "132:12": "minecraft:tripwire[disarmed=true,east=false,powered=false,south=false,north=false,attached=true,west=false]", + "132:13": "minecraft:tripwire[disarmed=true,east=false,powered=true,south=false,north=false,attached=true,west=false]", + "133:0": "minecraft:emerald_block", + "134:0": "minecraft:spruce_stairs[facing=east,half=bottom,shape=straight]", + "134:1": "minecraft:spruce_stairs[facing=west,half=bottom,shape=straight]", + "134:2": "minecraft:spruce_stairs[facing=south,half=bottom,shape=straight]", + "134:3": "minecraft:spruce_stairs[facing=north,half=bottom,shape=straight]", + "134:4": "minecraft:spruce_stairs[facing=east,half=top,shape=straight]", + "134:5": "minecraft:spruce_stairs[facing=west,half=top,shape=straight]", + "134:6": "minecraft:spruce_stairs[facing=south,half=top,shape=straight]", + "134:7": "minecraft:spruce_stairs[facing=north,half=top,shape=straight]", + "135:0": "minecraft:birch_stairs[facing=east,half=bottom,shape=straight]", + "135:1": "minecraft:birch_stairs[facing=west,half=bottom,shape=straight]", + "135:2": "minecraft:birch_stairs[facing=south,half=bottom,shape=straight]", + "135:3": "minecraft:birch_stairs[facing=north,half=bottom,shape=straight]", + "135:4": "minecraft:birch_stairs[facing=east,half=top,shape=straight]", + "135:5": "minecraft:birch_stairs[facing=west,half=top,shape=straight]", + "135:6": "minecraft:birch_stairs[facing=south,half=top,shape=straight]", + "135:7": "minecraft:birch_stairs[facing=north,half=top,shape=straight]", + "136:0": "minecraft:jungle_stairs[facing=east,half=bottom,shape=straight]", + "136:1": "minecraft:jungle_stairs[facing=west,half=bottom,shape=straight]", + "136:2": "minecraft:jungle_stairs[facing=south,half=bottom,shape=straight]", + "136:3": "minecraft:jungle_stairs[facing=north,half=bottom,shape=straight]", + "136:4": "minecraft:jungle_stairs[facing=east,half=top,shape=straight]", + "136:5": "minecraft:jungle_stairs[facing=west,half=top,shape=straight]", + "136:6": "minecraft:jungle_stairs[facing=south,half=top,shape=straight]", + "136:7": "minecraft:jungle_stairs[facing=north,half=top,shape=straight]", + "137:0": "minecraft:command_block[conditional=false,facing=down]", + "137:1": "minecraft:command_block[conditional=false,facing=up]", + "137:2": "minecraft:command_block[conditional=false,facing=north]", + "137:3": "minecraft:command_block[conditional=false,facing=south]", + "137:4": "minecraft:command_block[conditional=false,facing=west]", + "137:5": "minecraft:command_block[conditional=false,facing=east]", + "137:8": "minecraft:command_block[conditional=true,facing=down]", + "137:9": "minecraft:command_block[conditional=true,facing=up]", + "137:10": "minecraft:command_block[conditional=true,facing=north]", + "137:11": "minecraft:command_block[conditional=true,facing=south]", + "137:12": "minecraft:command_block[conditional=true,facing=west]", + "137:13": "minecraft:command_block[conditional=true,facing=east]", + "138:0": "minecraft:beacon", + "139:0": "minecraft:cobblestone_wall[west=false,east=false,up=false,south=false,north=false]", + "139:1": "minecraft:mossy_cobblestone_wall[west=false,east=false,up=false,south=false,north=false]", + "140:0": "minecraft:potted_cactus", + "140:1": "minecraft:potted_cactus", + "140:2": "minecraft:potted_cactus", + "140:3": "minecraft:potted_cactus", + "140:4": "minecraft:potted_cactus", + "140:5": "minecraft:potted_cactus", + "140:6": "minecraft:potted_cactus", + "140:7": "minecraft:potted_cactus", + "140:8": "minecraft:potted_cactus", + "140:9": "minecraft:potted_cactus", + "140:10": "minecraft:potted_cactus", + "140:11": "minecraft:potted_cactus", + "140:12": "minecraft:potted_cactus", + "140:13": "minecraft:potted_cactus", + "140:14": "minecraft:potted_cactus", + "140:15": "minecraft:potted_cactus", + "141:0": "minecraft:carrots[age=0]", + "141:1": "minecraft:carrots[age=1]", + "141:2": "minecraft:carrots[age=2]", + "141:3": "minecraft:carrots[age=3]", + "141:4": "minecraft:carrots[age=4]", + "141:5": "minecraft:carrots[age=5]", + "141:6": "minecraft:carrots[age=6]", + "141:7": "minecraft:carrots[age=7]", + "142:0": "minecraft:potatoes[age=0]", + "142:1": "minecraft:potatoes[age=1]", + "142:2": "minecraft:potatoes[age=2]", + "142:3": "minecraft:potatoes[age=3]", + "142:4": "minecraft:potatoes[age=4]", + "142:5": "minecraft:potatoes[age=5]", + "142:6": "minecraft:potatoes[age=6]", + "142:7": "minecraft:potatoes[age=7]", + "143:0": "minecraft:oak_button[facing=north,face=ceiling,powered=false]", + "143:1": "minecraft:oak_button[facing=east,face=wall,powered=false]", + "143:2": "minecraft:oak_button[facing=west,face=wall,powered=false]", + "143:3": "minecraft:oak_button[facing=south,face=wall,powered=false]", + "143:4": "minecraft:oak_button[facing=north,face=wall,powered=false]", + "143:5": "minecraft:oak_button[facing=north,face=floor,powered=false]", + "143:8": "minecraft:oak_button[facing=north,face=ceiling,powered=true]", + "143:9": "minecraft:oak_button[facing=east,face=wall,powered=true]", + "143:10": "minecraft:oak_button[facing=west,face=wall,powered=true]", + "143:11": "minecraft:oak_button[facing=south,face=wall,powered=true]", + "143:12": "minecraft:oak_button[facing=north,face=wall,powered=true]", + "143:13": "minecraft:oak_button[facing=north,face=floor,powered=true]", + "145:0": "minecraft:anvil[facing=south]", + "145:1": "minecraft:anvil[facing=west]", + "145:2": "minecraft:anvil[facing=north]", + "145:3": "minecraft:anvil[facing=east]", + "145:4": "minecraft:chipped_anvil[facing=south]", + "145:5": "minecraft:chipped_anvil[facing=west]", + "145:6": "minecraft:chipped_anvil[facing=north]", + "145:7": "minecraft:chipped_anvil[facing=east]", + "145:8": "minecraft:damaged_anvil[facing=south]", + "145:9": "minecraft:damaged_anvil[facing=west]", + "145:10": "minecraft:damaged_anvil[facing=north]", + "145:11": "minecraft:damaged_anvil[facing=east]", + "147:0": "minecraft:light_weighted_pressure_plate[power=0]", + "147:1": "minecraft:light_weighted_pressure_plate[power=1]", + "147:2": "minecraft:light_weighted_pressure_plate[power=2]", + "147:3": "minecraft:light_weighted_pressure_plate[power=3]", + "147:4": "minecraft:light_weighted_pressure_plate[power=4]", + "147:5": "minecraft:light_weighted_pressure_plate[power=5]", + "147:6": "minecraft:light_weighted_pressure_plate[power=6]", + "147:7": "minecraft:light_weighted_pressure_plate[power=7]", + "147:8": "minecraft:light_weighted_pressure_plate[power=8]", + "147:9": "minecraft:light_weighted_pressure_plate[power=9]", + "147:10": "minecraft:light_weighted_pressure_plate[power=10]", + "147:11": "minecraft:light_weighted_pressure_plate[power=11]", + "147:12": "minecraft:light_weighted_pressure_plate[power=12]", + "147:13": "minecraft:light_weighted_pressure_plate[power=13]", + "147:14": "minecraft:light_weighted_pressure_plate[power=14]", + "147:15": "minecraft:light_weighted_pressure_plate[power=15]", + "148:0": "minecraft:heavy_weighted_pressure_plate[power=0]", + "148:1": "minecraft:heavy_weighted_pressure_plate[power=1]", + "148:2": "minecraft:heavy_weighted_pressure_plate[power=2]", + "148:3": "minecraft:heavy_weighted_pressure_plate[power=3]", + "148:4": "minecraft:heavy_weighted_pressure_plate[power=4]", + "148:5": "minecraft:heavy_weighted_pressure_plate[power=5]", + "148:6": "minecraft:heavy_weighted_pressure_plate[power=6]", + "148:7": "minecraft:heavy_weighted_pressure_plate[power=7]", + "148:8": "minecraft:heavy_weighted_pressure_plate[power=8]", + "148:9": "minecraft:heavy_weighted_pressure_plate[power=9]", + "148:10": "minecraft:heavy_weighted_pressure_plate[power=10]", + "148:11": "minecraft:heavy_weighted_pressure_plate[power=11]", + "148:12": "minecraft:heavy_weighted_pressure_plate[power=12]", + "148:13": "minecraft:heavy_weighted_pressure_plate[power=13]", + "148:14": "minecraft:heavy_weighted_pressure_plate[power=14]", + "148:15": "minecraft:heavy_weighted_pressure_plate[power=15]", + "149:0": "minecraft:comparator[mode=compare,facing=south,powered=false]", + "149:1": "minecraft:comparator[mode=compare,facing=west,powered=false]", + "149:2": "minecraft:comparator[mode=compare,facing=north,powered=false]", + "149:3": "minecraft:comparator[mode=compare,facing=east,powered=false]", + "149:4": "minecraft:comparator[mode=subtract,facing=south,powered=false]", + "149:5": "minecraft:comparator[mode=subtract,facing=west,powered=false]", + "149:6": "minecraft:comparator[mode=subtract,facing=north,powered=false]", + "149:7": "minecraft:comparator[mode=subtract,facing=east,powered=false]", + "149:8": "minecraft:comparator[mode=compare,facing=south,powered=true]", + "149:9": "minecraft:comparator[mode=compare,facing=west,powered=true]", + "149:10": "minecraft:comparator[mode=compare,facing=north,powered=true]", + "149:11": "minecraft:comparator[mode=compare,facing=east,powered=true]", + "149:12": "minecraft:comparator[mode=subtract,facing=south,powered=true]", + "149:13": "minecraft:comparator[mode=subtract,facing=west,powered=true]", + "149:14": "minecraft:comparator[mode=subtract,facing=north,powered=true]", + "149:15": "minecraft:comparator[mode=subtract,facing=east,powered=true]", + "150:0": "minecraft:comparator[mode=compare,facing=south,powered=false]", + "150:1": "minecraft:comparator[mode=compare,facing=west,powered=false]", + "150:2": "minecraft:comparator[mode=compare,facing=north,powered=false]", + "150:3": "minecraft:comparator[mode=compare,facing=east,powered=false]", + "150:4": "minecraft:comparator[mode=subtract,facing=south,powered=false]", + "150:5": "minecraft:comparator[mode=subtract,facing=west,powered=false]", + "150:6": "minecraft:comparator[mode=subtract,facing=north,powered=false]", + "150:7": "minecraft:comparator[mode=subtract,facing=east,powered=false]", + "150:8": "minecraft:comparator[mode=compare,facing=south,powered=true]", + "150:9": "minecraft:comparator[mode=compare,facing=west,powered=true]", + "150:10": "minecraft:comparator[mode=compare,facing=north,powered=true]", + "150:11": "minecraft:comparator[mode=compare,facing=east,powered=true]", + "150:12": "minecraft:comparator[mode=subtract,facing=south,powered=true]", + "150:13": "minecraft:comparator[mode=subtract,facing=west,powered=true]", + "150:14": "minecraft:comparator[mode=subtract,facing=north,powered=true]", + "150:15": "minecraft:comparator[mode=subtract,facing=east,powered=true]", + "151:0": "minecraft:daylight_detector[inverted=false,power=0]", + "151:1": "minecraft:daylight_detector[inverted=false,power=1]", + "151:2": "minecraft:daylight_detector[inverted=false,power=2]", + "151:3": "minecraft:daylight_detector[inverted=false,power=3]", + "151:4": "minecraft:daylight_detector[inverted=false,power=4]", + "151:5": "minecraft:daylight_detector[inverted=false,power=5]", + "151:6": "minecraft:daylight_detector[inverted=false,power=6]", + "151:7": "minecraft:daylight_detector[inverted=false,power=7]", + "151:8": "minecraft:daylight_detector[inverted=false,power=8]", + "151:9": "minecraft:daylight_detector[inverted=false,power=9]", + "151:10": "minecraft:daylight_detector[inverted=false,power=10]", + "151:11": "minecraft:daylight_detector[inverted=false,power=11]", + "151:12": "minecraft:daylight_detector[inverted=false,power=12]", + "151:13": "minecraft:daylight_detector[inverted=false,power=13]", + "151:14": "minecraft:daylight_detector[inverted=false,power=14]", + "151:15": "minecraft:daylight_detector[inverted=false,power=15]", + "152:0": "minecraft:redstone_block", + "153:0": "minecraft:nether_quartz_ore", + "154:0": "minecraft:hopper[facing=down,enabled=true]", + "154:2": "minecraft:hopper[facing=north,enabled=true]", + "154:3": "minecraft:hopper[facing=south,enabled=true]", + "154:4": "minecraft:hopper[facing=west,enabled=true]", + "154:5": "minecraft:hopper[facing=east,enabled=true]", + "154:8": "minecraft:hopper[facing=down,enabled=false]", + "154:10": "minecraft:hopper[facing=north,enabled=false]", + "154:11": "minecraft:hopper[facing=south,enabled=false]", + "154:12": "minecraft:hopper[facing=west,enabled=false]", + "154:13": "minecraft:hopper[facing=east,enabled=false]", + "155:0": "minecraft:quartz_block", + "155:1": "minecraft:chiseled_quartz_block", + "155:2": "minecraft:quartz_pillar[axis=y]", + "155:3": "minecraft:quartz_pillar[axis=x]", + "155:4": "minecraft:quartz_pillar[axis=z]", + "156:0": "minecraft:quartz_stairs[facing=east,half=bottom,shape=straight]", + "156:1": "minecraft:quartz_stairs[facing=west,half=bottom,shape=straight]", + "156:2": "minecraft:quartz_stairs[facing=south,half=bottom,shape=straight]", + "156:3": "minecraft:quartz_stairs[facing=north,half=bottom,shape=straight]", + "156:4": "minecraft:quartz_stairs[facing=east,half=top,shape=straight]", + "156:5": "minecraft:quartz_stairs[facing=west,half=top,shape=straight]", + "156:6": "minecraft:quartz_stairs[facing=south,half=top,shape=straight]", + "156:7": "minecraft:quartz_stairs[facing=north,half=top,shape=straight]", + "157:0": "minecraft:activator_rail[powered=false,shape=north_south]", + "157:1": "minecraft:activator_rail[powered=false,shape=east_west]", + "157:2": "minecraft:activator_rail[powered=false,shape=ascending_east]", + "157:3": "minecraft:activator_rail[powered=false,shape=ascending_west]", + "157:4": "minecraft:activator_rail[powered=false,shape=ascending_north]", + "157:5": "minecraft:activator_rail[powered=false,shape=ascending_south]", + "157:8": "minecraft:activator_rail[powered=true,shape=north_south]", + "157:9": "minecraft:activator_rail[powered=true,shape=east_west]", + "157:10": "minecraft:activator_rail[powered=true,shape=ascending_east]", + "157:11": "minecraft:activator_rail[powered=true,shape=ascending_west]", + "157:12": "minecraft:activator_rail[powered=true,shape=ascending_north]", + "157:13": "minecraft:activator_rail[powered=true,shape=ascending_south]", + "158:0": "minecraft:dropper[facing=down,triggered=false]", + "158:1": "minecraft:dropper[facing=up,triggered=false]", + "158:2": "minecraft:dropper[facing=north,triggered=false]", + "158:3": "minecraft:dropper[facing=south,triggered=false]", + "158:4": "minecraft:dropper[facing=west,triggered=false]", + "158:5": "minecraft:dropper[facing=east,triggered=false]", + "158:8": "minecraft:dropper[facing=down,triggered=true]", + "158:9": "minecraft:dropper[facing=up,triggered=true]", + "158:10": "minecraft:dropper[facing=north,triggered=true]", + "158:11": "minecraft:dropper[facing=south,triggered=true]", + "158:12": "minecraft:dropper[facing=west,triggered=true]", + "158:13": "minecraft:dropper[facing=east,triggered=true]", + "159:0": "minecraft:white_terracotta", + "159:1": "minecraft:orange_terracotta", + "159:2": "minecraft:magenta_terracotta", + "159:3": "minecraft:light_blue_terracotta", + "159:4": "minecraft:yellow_terracotta", + "159:5": "minecraft:lime_terracotta", + "159:6": "minecraft:pink_terracotta", + "159:7": "minecraft:gray_terracotta", + "159:8": "minecraft:light_gray_terracotta", + "159:9": "minecraft:cyan_terracotta", + "159:10": "minecraft:purple_terracotta", + "159:11": "minecraft:blue_terracotta", + "159:12": "minecraft:brown_terracotta", + "159:13": "minecraft:green_terracotta", + "159:14": "minecraft:red_terracotta", + "159:15": "minecraft:black_terracotta", + "160:0": "minecraft:white_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:1": "minecraft:orange_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:2": "minecraft:magenta_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:3": "minecraft:light_blue_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:4": "minecraft:yellow_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:5": "minecraft:lime_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:6": "minecraft:pink_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:7": "minecraft:gray_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:8": "minecraft:light_gray_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:9": "minecraft:cyan_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:10": "minecraft:purple_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:11": "minecraft:blue_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:12": "minecraft:brown_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:13": "minecraft:green_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:14": "minecraft:red_stained_glass_pane[west=false,east=false,south=false,north=false]", + "160:15": "minecraft:black_stained_glass_pane[west=false,east=false,south=false,north=false]", + "161:0": "minecraft:acacia_leaves[check_decay=false,decayable=true]", + "161:1": "minecraft:dark_oak_leaves[check_decay=false,decayable=true]", + "161:4": "minecraft:acacia_leaves[check_decay=false,decayable=false]", + "161:5": "minecraft:dark_oak_leaves[check_decay=false,decayable=false]", + "161:8": "minecraft:acacia_leaves[check_decay=true,decayable=true]", + "161:9": "minecraft:dark_oak_leaves[check_decay=true,decayable=true]", + "161:12": "minecraft:acacia_leaves[check_decay=true,decayable=false]", + "161:13": "minecraft:dark_oak_leaves[check_decay=true,decayable=false]", + "162:0": "minecraft:acacia_log[axis=y]", + "162:1": "minecraft:dark_oak_log[axis=y]", + "162:4": "minecraft:acacia_log[axis=x]", + "162:5": "minecraft:dark_oak_log[axis=x]", + "162:8": "minecraft:acacia_log[axis=z]", + "162:9": "minecraft:dark_oak_log[axis=z]", + "162:12": "minecraft:acacia_bark", + "162:13": "minecraft:dark_oak_bark", + "163:0": "minecraft:acacia_stairs[facing=east,half=bottom,shape=straight]", + "163:1": "minecraft:acacia_stairs[facing=west,half=bottom,shape=straight]", + "163:2": "minecraft:acacia_stairs[facing=south,half=bottom,shape=straight]", + "163:3": "minecraft:acacia_stairs[facing=north,half=bottom,shape=straight]", + "163:4": "minecraft:acacia_stairs[facing=east,half=top,shape=straight]", + "163:5": "minecraft:acacia_stairs[facing=west,half=top,shape=straight]", + "163:6": "minecraft:acacia_stairs[facing=south,half=top,shape=straight]", + "163:7": "minecraft:acacia_stairs[facing=north,half=top,shape=straight]", + "164:0": "minecraft:dark_oak_stairs[facing=east,half=bottom,shape=straight]", + "164:1": "minecraft:dark_oak_stairs[facing=west,half=bottom,shape=straight]", + "164:2": "minecraft:dark_oak_stairs[facing=south,half=bottom,shape=straight]", + "164:3": "minecraft:dark_oak_stairs[facing=north,half=bottom,shape=straight]", + "164:4": "minecraft:dark_oak_stairs[facing=east,half=top,shape=straight]", + "164:5": "minecraft:dark_oak_stairs[facing=west,half=top,shape=straight]", + "164:6": "minecraft:dark_oak_stairs[facing=south,half=top,shape=straight]", + "164:7": "minecraft:dark_oak_stairs[facing=north,half=top,shape=straight]", + "165:0": "minecraft:slime_block", + "167:0": "minecraft:iron_trapdoor[facing=north,half=bottom,open=false]", + "167:1": "minecraft:iron_trapdoor[facing=south,half=bottom,open=false]", + "167:2": "minecraft:iron_trapdoor[facing=west,half=bottom,open=false]", + "167:3": "minecraft:iron_trapdoor[facing=east,half=bottom,open=false]", + "167:4": "minecraft:iron_trapdoor[facing=north,half=bottom,open=true]", + "167:5": "minecraft:iron_trapdoor[facing=south,half=bottom,open=true]", + "167:6": "minecraft:iron_trapdoor[facing=west,half=bottom,open=true]", + "167:7": "minecraft:iron_trapdoor[facing=east,half=bottom,open=true]", + "167:8": "minecraft:iron_trapdoor[facing=north,half=top,open=false]", + "167:9": "minecraft:iron_trapdoor[facing=south,half=top,open=false]", + "167:10": "minecraft:iron_trapdoor[facing=west,half=top,open=false]", + "167:11": "minecraft:iron_trapdoor[facing=east,half=top,open=false]", + "167:12": "minecraft:iron_trapdoor[facing=north,half=top,open=true]", + "167:13": "minecraft:iron_trapdoor[facing=south,half=top,open=true]", + "167:14": "minecraft:iron_trapdoor[facing=west,half=top,open=true]", + "167:15": "minecraft:iron_trapdoor[facing=east,half=top,open=true]", + "168:0": "minecraft:prismarine", + "168:1": "minecraft:prismarine_bricks", + "168:2": "minecraft:dark_prismarine", + "169:0": "minecraft:sea_lantern", + "170:0": "minecraft:hay_block[axis=y]", + "170:4": "minecraft:hay_block[axis=x]", + "170:8": "minecraft:hay_block[axis=z]", + "171:0": "minecraft:white_carpet", + "171:1": "minecraft:orange_carpet", + "171:2": "minecraft:magenta_carpet", + "171:3": "minecraft:light_blue_carpet", + "171:4": "minecraft:yellow_carpet", + "171:5": "minecraft:lime_carpet", + "171:6": "minecraft:pink_carpet", + "171:7": "minecraft:gray_carpet", + "171:8": "minecraft:light_gray_carpet", + "171:9": "minecraft:cyan_carpet", + "171:10": "minecraft:purple_carpet", + "171:11": "minecraft:blue_carpet", + "171:12": "minecraft:brown_carpet", + "171:13": "minecraft:green_carpet", + "171:14": "minecraft:red_carpet", + "171:15": "minecraft:black_carpet", + "172:0": "minecraft:terracotta", + "173:0": "minecraft:coal_block", + "174:0": "minecraft:packed_ice", + "175:0": "minecraft:sunflower[half=lower]", + "175:1": "minecraft:lilac[half=lower]", + "175:2": "minecraft:tall_grass[half=lower]", + "175:3": "minecraft:large_fern[half=lower]", + "175:4": "minecraft:rose_bush[half=lower]", + "175:5": "minecraft:peony[half=lower]", + "175:8": "minecraft:peony[half=upper]", + "175:9": "minecraft:peony[half=upper]", + "175:10": "minecraft:peony[half=upper]", + "175:11": "minecraft:peony[half=upper]", + "178:0": "minecraft:daylight_detector[inverted=true,power=0]", + "178:1": "minecraft:daylight_detector[inverted=true,power=1]", + "178:2": "minecraft:daylight_detector[inverted=true,power=2]", + "178:3": "minecraft:daylight_detector[inverted=true,power=3]", + "178:4": "minecraft:daylight_detector[inverted=true,power=4]", + "178:5": "minecraft:daylight_detector[inverted=true,power=5]", + "178:6": "minecraft:daylight_detector[inverted=true,power=6]", + "178:7": "minecraft:daylight_detector[inverted=true,power=7]", + "178:8": "minecraft:daylight_detector[inverted=true,power=8]", + "178:9": "minecraft:daylight_detector[inverted=true,power=9]", + "178:10": "minecraft:daylight_detector[inverted=true,power=10]", + "178:11": "minecraft:daylight_detector[inverted=true,power=11]", + "178:12": "minecraft:daylight_detector[inverted=true,power=12]", + "178:13": "minecraft:daylight_detector[inverted=true,power=13]", + "178:14": "minecraft:daylight_detector[inverted=true,power=14]", + "178:15": "minecraft:daylight_detector[inverted=true,power=15]", + "179:0": "minecraft:red_sandstone", + "179:1": "minecraft:chiseled_red_sandstone", + "179:2": "minecraft:cut_red_sandstone", + "180:0": "minecraft:red_sandstone_stairs[facing=east,half=bottom,shape=straight]", + "180:1": "minecraft:red_sandstone_stairs[facing=west,half=bottom,shape=straight]", + "180:2": "minecraft:red_sandstone_stairs[facing=south,half=bottom,shape=straight]", + "180:3": "minecraft:red_sandstone_stairs[facing=north,half=bottom,shape=straight]", + "180:4": "minecraft:red_sandstone_stairs[facing=east,half=top,shape=straight]", + "180:5": "minecraft:red_sandstone_stairs[facing=west,half=top,shape=straight]", + "180:6": "minecraft:red_sandstone_stairs[facing=south,half=top,shape=straight]", + "180:7": "minecraft:red_sandstone_stairs[facing=north,half=top,shape=straight]", + "181:0": "minecraft:red_sandstone_slab[type=double]", + "181:8": "minecraft:smooth_red_sandstone", + "182:0": "minecraft:red_sandstone_slab[type=bottom]", + "182:8": "minecraft:red_sandstone_slab[type=top]", + "183:0": "minecraft:spruce_fence_gate[in_wall=false,facing=south,powered=false,open=false]", + "183:1": "minecraft:spruce_fence_gate[in_wall=false,facing=west,powered=false,open=false]", + "183:2": "minecraft:spruce_fence_gate[in_wall=false,facing=north,powered=false,open=false]", + "183:3": "minecraft:spruce_fence_gate[in_wall=false,facing=east,powered=false,open=false]", + "183:4": "minecraft:spruce_fence_gate[in_wall=false,facing=south,powered=false,open=true]", + "183:5": "minecraft:spruce_fence_gate[in_wall=false,facing=west,powered=false,open=true]", + "183:6": "minecraft:spruce_fence_gate[in_wall=false,facing=north,powered=false,open=true]", + "183:7": "minecraft:spruce_fence_gate[in_wall=false,facing=east,powered=false,open=true]", + "183:8": "minecraft:spruce_fence_gate[in_wall=false,facing=south,powered=true,open=false]", + "183:9": "minecraft:spruce_fence_gate[in_wall=false,facing=west,powered=true,open=false]", + "183:10": "minecraft:spruce_fence_gate[in_wall=false,facing=north,powered=true,open=false]", + "183:11": "minecraft:spruce_fence_gate[in_wall=false,facing=east,powered=true,open=false]", + "183:12": "minecraft:spruce_fence_gate[in_wall=false,facing=south,powered=true,open=true]", + "183:13": "minecraft:spruce_fence_gate[in_wall=false,facing=west,powered=true,open=true]", + "183:14": "minecraft:spruce_fence_gate[in_wall=false,facing=north,powered=true,open=true]", + "183:15": "minecraft:spruce_fence_gate[in_wall=false,facing=east,powered=true,open=true]", + "184:0": "minecraft:birch_fence_gate[in_wall=false,facing=south,powered=false,open=false]", + "184:1": "minecraft:birch_fence_gate[in_wall=false,facing=west,powered=false,open=false]", + "184:2": "minecraft:birch_fence_gate[in_wall=false,facing=north,powered=false,open=false]", + "184:3": "minecraft:birch_fence_gate[in_wall=false,facing=east,powered=false,open=false]", + "184:4": "minecraft:birch_fence_gate[in_wall=false,facing=south,powered=false,open=true]", + "184:5": "minecraft:birch_fence_gate[in_wall=false,facing=west,powered=false,open=true]", + "184:6": "minecraft:birch_fence_gate[in_wall=false,facing=north,powered=false,open=true]", + "184:7": "minecraft:birch_fence_gate[in_wall=false,facing=east,powered=false,open=true]", + "184:8": "minecraft:birch_fence_gate[in_wall=false,facing=south,powered=true,open=false]", + "184:9": "minecraft:birch_fence_gate[in_wall=false,facing=west,powered=true,open=false]", + "184:10": "minecraft:birch_fence_gate[in_wall=false,facing=north,powered=true,open=false]", + "184:11": "minecraft:birch_fence_gate[in_wall=false,facing=east,powered=true,open=false]", + "184:12": "minecraft:birch_fence_gate[in_wall=false,facing=south,powered=true,open=true]", + "184:13": "minecraft:birch_fence_gate[in_wall=false,facing=west,powered=true,open=true]", + "184:14": "minecraft:birch_fence_gate[in_wall=false,facing=north,powered=true,open=true]", + "184:15": "minecraft:birch_fence_gate[in_wall=false,facing=east,powered=true,open=true]", + "185:0": "minecraft:jungle_fence_gate[in_wall=false,facing=south,powered=false,open=false]", + "185:1": "minecraft:jungle_fence_gate[in_wall=false,facing=west,powered=false,open=false]", + "185:2": "minecraft:jungle_fence_gate[in_wall=false,facing=north,powered=false,open=false]", + "185:3": "minecraft:jungle_fence_gate[in_wall=false,facing=east,powered=false,open=false]", + "185:4": "minecraft:jungle_fence_gate[in_wall=false,facing=south,powered=false,open=true]", + "185:5": "minecraft:jungle_fence_gate[in_wall=false,facing=west,powered=false,open=true]", + "185:6": "minecraft:jungle_fence_gate[in_wall=false,facing=north,powered=false,open=true]", + "185:7": "minecraft:jungle_fence_gate[in_wall=false,facing=east,powered=false,open=true]", + "185:8": "minecraft:jungle_fence_gate[in_wall=false,facing=south,powered=true,open=false]", + "185:9": "minecraft:jungle_fence_gate[in_wall=false,facing=west,powered=true,open=false]", + "185:10": "minecraft:jungle_fence_gate[in_wall=false,facing=north,powered=true,open=false]", + "185:11": "minecraft:jungle_fence_gate[in_wall=false,facing=east,powered=true,open=false]", + "185:12": "minecraft:jungle_fence_gate[in_wall=false,facing=south,powered=true,open=true]", + "185:13": "minecraft:jungle_fence_gate[in_wall=false,facing=west,powered=true,open=true]", + "185:14": "minecraft:jungle_fence_gate[in_wall=false,facing=north,powered=true,open=true]", + "185:15": "minecraft:jungle_fence_gate[in_wall=false,facing=east,powered=true,open=true]", + "186:0": "minecraft:dark_oak_fence_gate[in_wall=false,facing=south,powered=false,open=false]", + "186:1": "minecraft:dark_oak_fence_gate[in_wall=false,facing=west,powered=false,open=false]", + "186:2": "minecraft:dark_oak_fence_gate[in_wall=false,facing=north,powered=false,open=false]", + "186:3": "minecraft:dark_oak_fence_gate[in_wall=false,facing=east,powered=false,open=false]", + "186:4": "minecraft:dark_oak_fence_gate[in_wall=false,facing=south,powered=false,open=true]", + "186:5": "minecraft:dark_oak_fence_gate[in_wall=false,facing=west,powered=false,open=true]", + "186:6": "minecraft:dark_oak_fence_gate[in_wall=false,facing=north,powered=false,open=true]", + "186:7": "minecraft:dark_oak_fence_gate[in_wall=false,facing=east,powered=false,open=true]", + "186:8": "minecraft:dark_oak_fence_gate[in_wall=false,facing=south,powered=true,open=false]", + "186:9": "minecraft:dark_oak_fence_gate[in_wall=false,facing=west,powered=true,open=false]", + "186:10": "minecraft:dark_oak_fence_gate[in_wall=false,facing=north,powered=true,open=false]", + "186:11": "minecraft:dark_oak_fence_gate[in_wall=false,facing=east,powered=true,open=false]", + "186:12": "minecraft:dark_oak_fence_gate[in_wall=false,facing=south,powered=true,open=true]", + "186:13": "minecraft:dark_oak_fence_gate[in_wall=false,facing=west,powered=true,open=true]", + "186:14": "minecraft:dark_oak_fence_gate[in_wall=false,facing=north,powered=true,open=true]", + "186:15": "minecraft:dark_oak_fence_gate[in_wall=false,facing=east,powered=true,open=true]", + "187:0": "minecraft:acacia_fence_gate[in_wall=false,facing=south,powered=false,open=false]", + "187:1": "minecraft:acacia_fence_gate[in_wall=false,facing=west,powered=false,open=false]", + "187:2": "minecraft:acacia_fence_gate[in_wall=false,facing=north,powered=false,open=false]", + "187:3": "minecraft:acacia_fence_gate[in_wall=false,facing=east,powered=false,open=false]", + "187:4": "minecraft:acacia_fence_gate[in_wall=false,facing=south,powered=false,open=true]", + "187:5": "minecraft:acacia_fence_gate[in_wall=false,facing=west,powered=false,open=true]", + "187:6": "minecraft:acacia_fence_gate[in_wall=false,facing=north,powered=false,open=true]", + "187:7": "minecraft:acacia_fence_gate[in_wall=false,facing=east,powered=false,open=true]", + "187:8": "minecraft:acacia_fence_gate[in_wall=false,facing=south,powered=true,open=false]", + "187:9": "minecraft:acacia_fence_gate[in_wall=false,facing=west,powered=true,open=false]", + "187:10": "minecraft:acacia_fence_gate[in_wall=false,facing=north,powered=true,open=false]", + "187:11": "minecraft:acacia_fence_gate[in_wall=false,facing=east,powered=true,open=false]", + "187:12": "minecraft:acacia_fence_gate[in_wall=false,facing=south,powered=true,open=true]", + "187:13": "minecraft:acacia_fence_gate[in_wall=false,facing=west,powered=true,open=true]", + "187:14": "minecraft:acacia_fence_gate[in_wall=false,facing=north,powered=true,open=true]", + "187:15": "minecraft:acacia_fence_gate[in_wall=false,facing=east,powered=true,open=true]", + "188:0": "minecraft:spruce_fence[west=false,east=false,south=false,north=false]", + "189:0": "minecraft:birch_fence[west=false,east=false,south=false,north=false]", + "190:0": "minecraft:jungle_fence[west=false,east=false,south=false,north=false]", + "191:0": "minecraft:dark_oak_fence[west=false,east=false,south=false,north=false]", + "192:0": "minecraft:acacia_fence[west=false,east=false,south=false,north=false]", + "193:0": "minecraft:spruce_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "193:1": "minecraft:spruce_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "193:2": "minecraft:spruce_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "193:3": "minecraft:spruce_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "193:4": "minecraft:spruce_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "193:5": "minecraft:spruce_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "193:6": "minecraft:spruce_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "193:7": "minecraft:spruce_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "193:8": "minecraft:spruce_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "193:9": "minecraft:spruce_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "193:10": "minecraft:spruce_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "193:11": "minecraft:spruce_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "194:0": "minecraft:birch_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "194:1": "minecraft:birch_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "194:2": "minecraft:birch_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "194:3": "minecraft:birch_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "194:4": "minecraft:birch_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "194:5": "minecraft:birch_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "194:6": "minecraft:birch_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "194:7": "minecraft:birch_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "194:8": "minecraft:birch_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "194:9": "minecraft:birch_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "194:10": "minecraft:birch_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "194:11": "minecraft:birch_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "195:0": "minecraft:jungle_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "195:1": "minecraft:jungle_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "195:2": "minecraft:jungle_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "195:3": "minecraft:jungle_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "195:4": "minecraft:jungle_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "195:5": "minecraft:jungle_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "195:6": "minecraft:jungle_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "195:7": "minecraft:jungle_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "195:8": "minecraft:jungle_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "195:9": "minecraft:jungle_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "195:10": "minecraft:jungle_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "195:11": "minecraft:jungle_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "196:0": "minecraft:acacia_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "196:1": "minecraft:acacia_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "196:2": "minecraft:acacia_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "196:3": "minecraft:acacia_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "196:4": "minecraft:acacia_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "196:5": "minecraft:acacia_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "196:6": "minecraft:acacia_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "196:7": "minecraft:acacia_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "196:8": "minecraft:acacia_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "196:9": "minecraft:acacia_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "196:10": "minecraft:acacia_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "196:11": "minecraft:acacia_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "197:0": "minecraft:dark_oak_door[hinge=right,facing=east,half=lower,powered=false,open=false]", + "197:1": "minecraft:dark_oak_door[hinge=right,facing=south,half=lower,powered=false,open=false]", + "197:2": "minecraft:dark_oak_door[hinge=right,facing=west,half=lower,powered=false,open=false]", + "197:3": "minecraft:dark_oak_door[hinge=right,facing=north,half=lower,powered=false,open=false]", + "197:4": "minecraft:dark_oak_door[hinge=right,facing=east,half=lower,powered=false,open=true]", + "197:5": "minecraft:dark_oak_door[hinge=right,facing=south,half=lower,powered=false,open=true]", + "197:6": "minecraft:dark_oak_door[hinge=right,facing=west,half=lower,powered=false,open=true]", + "197:7": "minecraft:dark_oak_door[hinge=right,facing=north,half=lower,powered=false,open=true]", + "197:8": "minecraft:dark_oak_door[hinge=left,facing=east,half=upper,powered=false,open=false]", + "197:9": "minecraft:dark_oak_door[hinge=right,facing=east,half=upper,powered=false,open=false]", + "197:10": "minecraft:dark_oak_door[hinge=left,facing=east,half=upper,powered=true,open=false]", + "197:11": "minecraft:dark_oak_door[hinge=right,facing=east,half=upper,powered=true,open=false]", + "198:0": "minecraft:end_rod[facing=down]", + "198:1": "minecraft:end_rod[facing=up]", + "198:2": "minecraft:end_rod[facing=north]", + "198:3": "minecraft:end_rod[facing=south]", + "198:4": "minecraft:end_rod[facing=west]", + "198:5": "minecraft:end_rod[facing=east]", + "199:0": "minecraft:chorus_plant[east=false,south=false,north=false,west=false,up=false,down=false]", + "200:0": "minecraft:chorus_flower[age=0]", + "200:1": "minecraft:chorus_flower[age=1]", + "200:2": "minecraft:chorus_flower[age=2]", + "200:3": "minecraft:chorus_flower[age=3]", + "200:4": "minecraft:chorus_flower[age=4]", + "200:5": "minecraft:chorus_flower[age=5]", + "201:0": "minecraft:purpur_block", + "202:0": "minecraft:purpur_pillar[axis=y]", + "202:4": "minecraft:purpur_pillar[axis=x]", + "202:8": "minecraft:purpur_pillar[axis=z]", + "203:0": "minecraft:purpur_stairs[facing=east,half=bottom,shape=straight]", + "203:1": "minecraft:purpur_stairs[facing=west,half=bottom,shape=straight]", + "203:2": "minecraft:purpur_stairs[facing=south,half=bottom,shape=straight]", + "203:3": "minecraft:purpur_stairs[facing=north,half=bottom,shape=straight]", + "203:4": "minecraft:purpur_stairs[facing=east,half=top,shape=straight]", + "203:5": "minecraft:purpur_stairs[facing=west,half=top,shape=straight]", + "203:6": "minecraft:purpur_stairs[facing=south,half=top,shape=straight]", + "203:7": "minecraft:purpur_stairs[facing=north,half=top,shape=straight]", + "204:0": "minecraft:purpur_slab[type=double]", + "205:0": "minecraft:purpur_slab[type=bottom]", + "205:8": "minecraft:purpur_slab[type=top]", + "206:0": "minecraft:end_stone_bricks", + "207:0": "minecraft:beetroots[age=0]", + "207:1": "minecraft:beetroots[age=1]", + "207:2": "minecraft:beetroots[age=2]", + "207:3": "minecraft:beetroots[age=3]", + "208:0": "minecraft:grass_path", + "210:0": "minecraft:repeating_command_block[conditional=false,facing=down]", + "210:1": "minecraft:repeating_command_block[conditional=false,facing=up]", + "210:2": "minecraft:repeating_command_block[conditional=false,facing=north]", + "210:3": "minecraft:repeating_command_block[conditional=false,facing=south]", + "210:4": "minecraft:repeating_command_block[conditional=false,facing=west]", + "210:5": "minecraft:repeating_command_block[conditional=false,facing=east]", + "210:8": "minecraft:repeating_command_block[conditional=true,facing=down]", + "210:9": "minecraft:repeating_command_block[conditional=true,facing=up]", + "210:10": "minecraft:repeating_command_block[conditional=true,facing=north]", + "210:11": "minecraft:repeating_command_block[conditional=true,facing=south]", + "210:12": "minecraft:repeating_command_block[conditional=true,facing=west]", + "210:13": "minecraft:repeating_command_block[conditional=true,facing=east]", + "211:0": "minecraft:chain_command_block[conditional=false,facing=down]", + "211:1": "minecraft:chain_command_block[conditional=false,facing=up]", + "211:2": "minecraft:chain_command_block[conditional=false,facing=north]", + "211:3": "minecraft:chain_command_block[conditional=false,facing=south]", + "211:4": "minecraft:chain_command_block[conditional=false,facing=west]", + "211:5": "minecraft:chain_command_block[conditional=false,facing=east]", + "211:8": "minecraft:chain_command_block[conditional=true,facing=down]", + "211:9": "minecraft:chain_command_block[conditional=true,facing=up]", + "211:10": "minecraft:chain_command_block[conditional=true,facing=north]", + "211:11": "minecraft:chain_command_block[conditional=true,facing=south]", + "211:12": "minecraft:chain_command_block[conditional=true,facing=west]", + "211:13": "minecraft:chain_command_block[conditional=true,facing=east]", + "212:0": "minecraft:frosted_ice[age=0]", + "212:1": "minecraft:frosted_ice[age=1]", + "212:2": "minecraft:frosted_ice[age=2]", + "212:3": "minecraft:frosted_ice[age=3]", + "213:0": "minecraft:magma_block", + "214:0": "minecraft:nether_wart_block", + "215:0": "minecraft:red_nether_bricks", + "216:0": "minecraft:bone_block[axis=y]", + "216:4": "minecraft:bone_block[axis=x]", + "216:8": "minecraft:bone_block[axis=z]", + "218:0": "minecraft:observer[powered=false,facing=down]", + "218:1": "minecraft:observer[powered=false,facing=up]", + "218:2": "minecraft:observer[powered=false,facing=north]", + "218:3": "minecraft:observer[powered=false,facing=south]", + "218:4": "minecraft:observer[powered=false,facing=west]", + "218:5": "minecraft:observer[powered=false,facing=east]", + "218:8": "minecraft:observer[powered=true,facing=down]", + "218:9": "minecraft:observer[powered=true,facing=up]", + "218:10": "minecraft:observer[powered=true,facing=north]", + "218:11": "minecraft:observer[powered=true,facing=south]", + "218:12": "minecraft:observer[powered=true,facing=west]", + "218:13": "minecraft:observer[powered=true,facing=east]", + "235:0": "minecraft:white_glazed_terracotta[facing=south]", + "235:1": "minecraft:white_glazed_terracotta[facing=west]", + "235:2": "minecraft:white_glazed_terracotta[facing=north]", + "235:3": "minecraft:white_glazed_terracotta[facing=east]", + "236:0": "minecraft:orange_glazed_terracotta[facing=south]", + "236:1": "minecraft:orange_glazed_terracotta[facing=west]", + "236:2": "minecraft:orange_glazed_terracotta[facing=north]", + "236:3": "minecraft:orange_glazed_terracotta[facing=east]", + "237:0": "minecraft:magenta_glazed_terracotta[facing=south]", + "237:1": "minecraft:magenta_glazed_terracotta[facing=west]", + "237:2": "minecraft:magenta_glazed_terracotta[facing=north]", + "237:3": "minecraft:magenta_glazed_terracotta[facing=east]", + "238:0": "minecraft:light_blue_glazed_terracotta[facing=south]", + "238:1": "minecraft:light_blue_glazed_terracotta[facing=west]", + "238:2": "minecraft:light_blue_glazed_terracotta[facing=north]", + "238:3": "minecraft:light_blue_glazed_terracotta[facing=east]", + "239:0": "minecraft:yellow_glazed_terracotta[facing=south]", + "239:1": "minecraft:yellow_glazed_terracotta[facing=west]", + "239:2": "minecraft:yellow_glazed_terracotta[facing=north]", + "239:3": "minecraft:yellow_glazed_terracotta[facing=east]", + "240:0": "minecraft:lime_glazed_terracotta[facing=south]", + "240:1": "minecraft:lime_glazed_terracotta[facing=west]", + "240:2": "minecraft:lime_glazed_terracotta[facing=north]", + "240:3": "minecraft:lime_glazed_terracotta[facing=east]", + "241:0": "minecraft:pink_glazed_terracotta[facing=south]", + "241:1": "minecraft:pink_glazed_terracotta[facing=west]", + "241:2": "minecraft:pink_glazed_terracotta[facing=north]", + "241:3": "minecraft:pink_glazed_terracotta[facing=east]", + "242:0": "minecraft:gray_glazed_terracotta[facing=south]", + "242:1": "minecraft:gray_glazed_terracotta[facing=west]", + "242:2": "minecraft:gray_glazed_terracotta[facing=north]", + "242:3": "minecraft:gray_glazed_terracotta[facing=east]", + "243:0": "minecraft:light_gray_glazed_terracotta[facing=south]", + "243:1": "minecraft:light_gray_glazed_terracotta[facing=west]", + "243:2": "minecraft:light_gray_glazed_terracotta[facing=north]", + "243:3": "minecraft:light_gray_glazed_terracotta[facing=east]", + "244:0": "minecraft:cyan_glazed_terracotta[facing=south]", + "244:1": "minecraft:cyan_glazed_terracotta[facing=west]", + "244:2": "minecraft:cyan_glazed_terracotta[facing=north]", + "244:3": "minecraft:cyan_glazed_terracotta[facing=east]", + "245:0": "minecraft:purple_glazed_terracotta[facing=south]", + "245:1": "minecraft:purple_glazed_terracotta[facing=west]", + "245:2": "minecraft:purple_glazed_terracotta[facing=north]", + "245:3": "minecraft:purple_glazed_terracotta[facing=east]", + "246:0": "minecraft:blue_glazed_terracotta[facing=south]", + "246:1": "minecraft:blue_glazed_terracotta[facing=west]", + "246:2": "minecraft:blue_glazed_terracotta[facing=north]", + "246:3": "minecraft:blue_glazed_terracotta[facing=east]", + "247:0": "minecraft:brown_glazed_terracotta[facing=south]", + "247:1": "minecraft:brown_glazed_terracotta[facing=west]", + "247:2": "minecraft:brown_glazed_terracotta[facing=north]", + "247:3": "minecraft:brown_glazed_terracotta[facing=east]", + "248:0": "minecraft:green_glazed_terracotta[facing=south]", + "248:1": "minecraft:green_glazed_terracotta[facing=west]", + "248:2": "minecraft:green_glazed_terracotta[facing=north]", + "248:3": "minecraft:green_glazed_terracotta[facing=east]", + "249:0": "minecraft:red_glazed_terracotta[facing=south]", + "249:1": "minecraft:red_glazed_terracotta[facing=west]", + "249:2": "minecraft:red_glazed_terracotta[facing=north]", + "249:3": "minecraft:red_glazed_terracotta[facing=east]", + "250:0": "minecraft:black_glazed_terracotta[facing=south]", + "250:1": "minecraft:black_glazed_terracotta[facing=west]", + "250:2": "minecraft:black_glazed_terracotta[facing=north]", + "250:3": "minecraft:black_glazed_terracotta[facing=east]", + "251:0": "minecraft:white_concrete", + "251:1": "minecraft:orange_concrete", + "251:2": "minecraft:magenta_concrete", + "251:3": "minecraft:light_blue_concrete", + "251:4": "minecraft:yellow_concrete", + "251:5": "minecraft:lime_concrete", + "251:6": "minecraft:pink_concrete", + "251:7": "minecraft:gray_concrete", + "251:8": "minecraft:light_gray_concrete", + "251:9": "minecraft:cyan_concrete", + "251:10": "minecraft:purple_concrete", + "251:11": "minecraft:blue_concrete", + "251:12": "minecraft:brown_concrete", + "251:13": "minecraft:green_concrete", + "251:14": "minecraft:red_concrete", + "251:15": "minecraft:black_concrete", + "252:0": "minecraft:white_concrete_powder", + "252:1": "minecraft:orange_concrete_powder", + "252:2": "minecraft:magenta_concrete_powder", + "252:3": "minecraft:light_blue_concrete_powder", + "252:4": "minecraft:yellow_concrete_powder", + "252:5": "minecraft:lime_concrete_powder", + "252:6": "minecraft:pink_concrete_powder", + "252:7": "minecraft:gray_concrete_powder", + "252:8": "minecraft:light_gray_concrete_powder", + "252:9": "minecraft:cyan_concrete_powder", + "252:10": "minecraft:purple_concrete_powder", + "252:11": "minecraft:blue_concrete_powder", + "252:12": "minecraft:brown_concrete_powder", + "252:13": "minecraft:green_concrete_powder", + "252:14": "minecraft:red_concrete_powder", + "252:15": "minecraft:black_concrete_powder", + "255:0": "minecraft:structure_block[mode=save]", + "255:1": "minecraft:structure_block[mode=load]", + "255:2": "minecraft:structure_block[mode=corner]", + "255:3": "minecraft:structure_block[mode=data]" +} diff --git a/BlueMapCore/src/main/resources/blockProperties.json b/BlueMapCore/src/main/resources/blockProperties.json new file mode 100644 index 00000000..41d46b65 --- /dev/null +++ b/BlueMapCore/src/main/resources/blockProperties.json @@ -0,0 +1,4402 @@ +{ + "minecraft:air": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:granite": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_granite": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:diorite": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_diorite": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:andesite": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_andesite": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:grass_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dirt": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:coarse_dirt": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:podzol": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cobblestone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:oak_planks": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:spruce_planks": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:birch_planks": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:jungle_planks": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:acacia_planks": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:dark_oak_planks": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:oak_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bedrock": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:water": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lava": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sand": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_sand": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:gravel": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:gold_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:iron_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:coal_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:oak_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:spruce_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:birch_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:jungle_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:acacia_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:dark_oak_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_spruce_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_birch_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_jungle_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_acacia_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_dark_oak_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_oak_log": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:oak_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:spruce_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:birch_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:jungle_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:acacia_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:dark_oak_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_oak_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_spruce_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_birch_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_jungle_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_acacia_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stripped_dark_oak_wood": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:oak_leaves": { + "culling": false, + "occluding": true, + "flammable": true + }, + "minecraft:spruce_leaves": { + "culling": false, + "occluding": true, + "flammable": true + }, + "minecraft:birch_leaves": { + "culling": false, + "occluding": true, + "flammable": true + }, + "minecraft:jungle_leaves": { + "culling": false, + "occluding": true, + "flammable": true + }, + "minecraft:acacia_leaves": { + "culling": false, + "occluding": true, + "flammable": true + }, + "minecraft:dark_oak_leaves": { + "culling": false, + "occluding": true, + "flammable": true + }, + "minecraft:sponge": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:wet_sponge": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lapis_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:lapis_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dispenser": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:chiseled_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cut_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:note_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:white_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:orange_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:magenta_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_blue_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:yellow_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lime_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pink_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gray_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_gray_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cyan_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purple_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brown_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:green_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:black_bed": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:powered_rail": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:detector_rail": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=north,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=east,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=south,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=west,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=up,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=down,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sticky_piston[facing=north,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sticky_piston[facing=east,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sticky_piston[facing=south,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sticky_piston[facing=west,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sticky_piston[facing=up,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sticky_piston[facing=down,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cobweb": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:grass": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:fern": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:dead_bush": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:seagrass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:tall_seagrass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=north,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=east,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=south,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=west,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=up,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=down,extended=true]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:piston[facing=north,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:piston[facing=east,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:piston[facing=south,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:piston[facing=west,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:piston[facing=up,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:piston[facing=down,extended=false]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:piston_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:white_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:orange_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:magenta_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:light_blue_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:yellow_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:lime_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:pink_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:gray_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:light_gray_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:cyan_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:purple_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:blue_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:brown_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:green_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:red_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:black_wool": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:moving_piston": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dandelion": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:poppy": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:blue_orchid": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:allium": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:azure_bluet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:red_tulip": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:orange_tulip": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:white_tulip": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:pink_tulip": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:oxeye_daisy": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:cornflower": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:wither_rose": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:lily_of_the_valley": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:brown_mushroom": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_mushroom": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gold_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:iron_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:tnt": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:bookshelf": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:mossy_cobblestone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:obsidian": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:torch": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:wall_torch": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:fire": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spawner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_stairs": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:chest": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:redstone_wire": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diamond_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:diamond_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:crafting_table": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:wheat": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:farmland": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:furnace": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:oak_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:ladder": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:rail": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cobblestone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_wall_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_wall_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_wall_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_wall_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_wall_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_wall_sign": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lever": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:iron_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:redstone_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:redstone_torch": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:redstone_wall_torch": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=1]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=2]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=3]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=4]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=5]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=6]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=7]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:snow[layers=8]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:ice": { + "culling": false, + "occluding": true, + "flammable": false + }, + "minecraft:snow_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cactus": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:clay": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sugar_cane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jukebox": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:oak_fence": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:pumpkin": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:netherrack": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:soul_sand": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:glowstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:nether_portal": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:carved_pumpkin": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:jack_o_lantern": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cake": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:repeater": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:white_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:orange_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:magenta_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_blue_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:yellow_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lime_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pink_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gray_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_gray_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cyan_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purple_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brown_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:green_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:black_stained_glass": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:mossy_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cracked_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:chiseled_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:infested_stone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:infested_cobblestone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:infested_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:infested_mossy_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:infested_cracked_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:infested_chiseled_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brown_mushroom_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_mushroom_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:mushroom_stem": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:iron_bars": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:melon": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:attached_pumpkin_stem": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:attached_melon_stem": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pumpkin_stem": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:melon_stem": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:vine": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:oak_fence_gate": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mycelium": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:lily_pad": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:nether_brick_fence": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_wart": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:enchanting_table": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brewing_stand": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cauldron": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_portal": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_portal_frame": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dragon_egg": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:redstone_lamp": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cocoa": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sandstone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:emerald_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:ender_chest": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:tripwire_hook": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:tripwire": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:emerald_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:spruce_stairs": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:birch_stairs": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:jungle_stairs": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:command_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:beacon": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cobblestone_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_cobblestone_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:flower_pot": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_oak_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_spruce_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_birch_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_jungle_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_acacia_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_dark_oak_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_fern": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_dandelion": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_poppy": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_blue_orchid": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_allium": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_azure_bluet": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_red_tulip": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_orange_tulip": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_white_tulip": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_pink_tulip": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_oxeye_daisy": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_cornflower": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_lily_of_the_valley": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_wither_rose": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_red_mushroom": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_brown_mushroom": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_dead_bush": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potted_cactus": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:carrots": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:potatoes": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_button": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:skeleton_skull": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:skeleton_wall_skull": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:wither_skeleton_skull": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:wither_skeleton_wall_skull": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:zombie_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:zombie_wall_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:player_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:player_wall_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:creeper_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:creeper_wall_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dragon_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dragon_wall_head": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:anvil": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:chipped_anvil": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:damaged_anvil": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:trapped_chest": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_weighted_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:heavy_weighted_pressure_plate": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:comparator": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:daylight_detector": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:redstone_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:nether_quartz_ore": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:hopper": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:quartz_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:chiseled_quartz_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:quartz_pillar": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:quartz_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:activator_rail": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dropper": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:white_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:orange_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:magenta_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_blue_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:yellow_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:lime_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:pink_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:gray_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_gray_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cyan_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purple_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:blue_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brown_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:green_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:black_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:white_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:orange_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:magenta_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_blue_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:yellow_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lime_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pink_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gray_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_gray_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cyan_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purple_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brown_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:green_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:black_stained_glass_pane": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_stairs": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:dark_oak_stairs": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:slime_block": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:barrier": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:iron_trapdoor": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:prismarine_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dark_prismarine": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:prismarine_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_prismarine_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:prismarine_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:prismarine_brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:prismarine_brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dark_prismarine_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_prismarine_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_prismarine_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_prismarine_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_prismarine_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dark_prismarine_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sea_lantern": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:hay_block": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:white_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:orange_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:magenta_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:light_blue_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:yellow_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:lime_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:pink_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:gray_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:light_gray_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:cyan_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:purple_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:blue_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:brown_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:green_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:red_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:black_carpet": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:coal_block": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:packed_ice": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sunflower": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:lilac": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:rose_bush": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:peony": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:tall_grass": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:large_fern": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:white_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:orange_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:magenta_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_blue_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:yellow_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lime_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pink_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gray_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_gray_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cyan_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purple_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brown_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:green_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:black_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:white_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:orange_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:magenta_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_blue_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:yellow_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lime_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pink_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gray_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_gray_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cyan_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purple_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brown_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:green_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:black_wall_banner": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:chiseled_red_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cut_red_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_sandstone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:oak_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:oak_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:oak_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:oak_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:spruce_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:spruce_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:spruce_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:spruce_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:spruce_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:birch_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:birch_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:birch_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:birch_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:jungle_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:jungle_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:jungle_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:jungle_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:acacia_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:acacia_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:acacia_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:acacia_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:dark_oak_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:dark_oak_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:dark_oak_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dark_oak_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:stone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:stone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_stone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_stone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_stone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_stone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_stone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_stone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sandstone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sandstone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sandstone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sandstone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sandstone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:sandstone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cut_sandstone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_sandstone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_sandstone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_sandstone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_sandstone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cut_sandstone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:petrified_oak_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:petrified_oak_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:petrified_oak_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:petrified_oak_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:petrified_oak_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:petrified_oak_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cobblestone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cobblestone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cobblestone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cobblestone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cobblestone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cobblestone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:stone_brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:stone_brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:nether_brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:nether_brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:quartz_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:quartz_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:quartz_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:quartz_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:quartz_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:quartz_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_sandstone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_sandstone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_sandstone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_sandstone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_sandstone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_sandstone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cut_red_sandstone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_red_sandstone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_red_sandstone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_red_sandstone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cut_red_sandstone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cut_red_sandstone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purpur_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purpur_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purpur_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purpur_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purpur_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purpur_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_stone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_quartz": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_red_sandstone": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:spruce_fence_gate": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:birch_fence_gate": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:jungle_fence_gate": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:acacia_fence_gate": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:dark_oak_fence_gate": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:spruce_fence": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:birch_fence": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:jungle_fence": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:acacia_fence": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:dark_oak_fence": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:spruce_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:birch_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:jungle_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:acacia_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dark_oak_door": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_rod": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:chorus_plant": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:chorus_flower": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purpur_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purpur_pillar": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purpur_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:beetroots": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:grass_path": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_gateway": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:repeating_command_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:chain_command_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:frosted_ice": { + "culling": false, + "occluding": true, + "flammable": false + }, + "minecraft:magma_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:nether_wart_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_nether_bricks": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:bone_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:structure_void": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:observer": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:white_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:orange_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:magenta_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_blue_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:yellow_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lime_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:pink_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:gray_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:light_gray_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cyan_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:purple_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brown_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:green_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:black_shulker_box": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:white_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:orange_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:magenta_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_blue_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:yellow_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:lime_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:pink_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:gray_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_gray_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cyan_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purple_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:blue_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brown_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:green_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:black_glazed_terracotta": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:white_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:orange_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:magenta_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_blue_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:yellow_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:lime_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:pink_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:gray_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_gray_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cyan_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purple_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:blue_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brown_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:green_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:black_concrete": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:white_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:orange_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:magenta_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_blue_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:yellow_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:lime_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:pink_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:gray_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:light_gray_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cyan_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:purple_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:blue_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brown_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:green_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:black_concrete_powder": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:kelp": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:kelp_plant": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dried_kelp_block": { + "culling": true, + "occluding": true, + "flammable": true + }, + "minecraft:turtle_egg": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_tube_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dead_brain_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dead_bubble_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dead_fire_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dead_horn_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:tube_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brain_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:bubble_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:fire_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:horn_coral_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:dead_tube_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_brain_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_bubble_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_fire_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_horn_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:tube_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brain_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bubble_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:fire_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:horn_coral": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_tube_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_brain_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_bubble_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_fire_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_horn_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:tube_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brain_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bubble_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:fire_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:horn_coral_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_tube_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_brain_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_bubble_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_fire_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:dead_horn_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:tube_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:brain_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bubble_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:fire_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:horn_coral_wall_fan": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sea_pickle": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:blue_ice": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:conduit": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bamboo_sapling": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bamboo": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:potted_bamboo": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:void_air": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:cave_air": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bubble_column": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_granite_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_red_sandstone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_stone_brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_diorite_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_cobblestone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_sandstone_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_quartz_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:granite_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:andesite_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_nether_brick_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_andesite_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diorite_stairs": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_granite_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_granite_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_granite_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_granite_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_granite_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_granite_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_red_sandstone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_red_sandstone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_red_sandstone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_red_sandstone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_red_sandstone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_red_sandstone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:mossy_stone_brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_stone_brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_stone_brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_stone_brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_stone_brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:mossy_stone_brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_diorite_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_diorite_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_diorite_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_diorite_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_diorite_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_diorite_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:mossy_cobblestone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_cobblestone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_cobblestone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_cobblestone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_cobblestone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:mossy_cobblestone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:end_stone_brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:end_stone_brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_sandstone_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_sandstone_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_sandstone_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_sandstone_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_sandstone_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_sandstone_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_quartz_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_quartz_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_quartz_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_quartz_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:smooth_quartz_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smooth_quartz_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:granite_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:granite_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:granite_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:granite_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:granite_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:granite_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:andesite_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:andesite_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:andesite_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:andesite_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:andesite_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:andesite_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_nether_brick_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_nether_brick_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_nether_brick_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_nether_brick_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_nether_brick_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:red_nether_brick_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_andesite_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_andesite_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_andesite_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_andesite_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:polished_andesite_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:polished_andesite_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:diorite_slab[waterlogged=true,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diorite_slab[waterlogged=false,type=top]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diorite_slab[waterlogged=true,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diorite_slab[waterlogged=false,type=bottom]": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diorite_slab[waterlogged=true,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:diorite_slab[waterlogged=false,type=double]": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:brick_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:prismarine_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_sandstone_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:mossy_stone_brick_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:granite_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:stone_brick_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:nether_brick_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:andesite_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:red_nether_brick_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sandstone_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:end_stone_brick_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:diorite_wall": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:scaffolding": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:loom": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:barrel": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:smoker": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:blast_furnace": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:cartography_table": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:fletching_table": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:grindstone": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lectern": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:smithing_table": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:stonecutter": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:bell": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:lantern": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:campfire": { + "culling": false, + "occluding": false, + "flammable": false + }, + "minecraft:sweet_berry_bush": { + "culling": false, + "occluding": false, + "flammable": true + }, + "minecraft:structure_block": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:jigsaw": { + "culling": true, + "occluding": true, + "flammable": false + }, + "minecraft:composter": { + "culling": false, + "occluding": false, + "flammable": true + } +} diff --git a/BlueMapCore/src/main/resources/webroot.zip b/BlueMapCore/src/main/resources/webroot.zip new file mode 100644 index 00000000..c9412339 Binary files /dev/null and b/BlueMapCore/src/main/resources/webroot.zip differ diff --git a/BlueMapCore/src/main/webroot/assets/compass.svg b/BlueMapCore/src/main/webroot/assets/compass.svg new file mode 100644 index 00000000..31f57e53 --- /dev/null +++ b/BlueMapCore/src/main/webroot/assets/compass.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/BlueMapCore/src/main/webroot/assets/gear.svg b/BlueMapCore/src/main/webroot/assets/gear.svg new file mode 100644 index 00000000..d5695e40 --- /dev/null +++ b/BlueMapCore/src/main/webroot/assets/gear.svg @@ -0,0 +1,19 @@ + + + + + + diff --git a/BlueMapCore/src/main/webroot/assets/skybox/down.png b/BlueMapCore/src/main/webroot/assets/skybox/down.png new file mode 100644 index 00000000..77290fc5 Binary files /dev/null and b/BlueMapCore/src/main/webroot/assets/skybox/down.png differ diff --git a/BlueMapCore/src/main/webroot/assets/skybox/east.png b/BlueMapCore/src/main/webroot/assets/skybox/east.png new file mode 100644 index 00000000..f10f641d Binary files /dev/null and b/BlueMapCore/src/main/webroot/assets/skybox/east.png differ diff --git a/BlueMapCore/src/main/webroot/assets/skybox/north.png b/BlueMapCore/src/main/webroot/assets/skybox/north.png new file mode 100644 index 00000000..f10f641d Binary files /dev/null and b/BlueMapCore/src/main/webroot/assets/skybox/north.png differ diff --git a/BlueMapCore/src/main/webroot/assets/skybox/south.png b/BlueMapCore/src/main/webroot/assets/skybox/south.png new file mode 100644 index 00000000..f10f641d Binary files /dev/null and b/BlueMapCore/src/main/webroot/assets/skybox/south.png differ diff --git a/BlueMapCore/src/main/webroot/assets/skybox/up.png b/BlueMapCore/src/main/webroot/assets/skybox/up.png new file mode 100644 index 00000000..db5cb631 Binary files /dev/null and b/BlueMapCore/src/main/webroot/assets/skybox/up.png differ diff --git a/BlueMapCore/src/main/webroot/assets/skybox/west.png b/BlueMapCore/src/main/webroot/assets/skybox/west.png new file mode 100644 index 00000000..f10f641d Binary files /dev/null and b/BlueMapCore/src/main/webroot/assets/skybox/west.png differ diff --git a/BlueMapCore/src/main/webroot/index.html b/BlueMapCore/src/main/webroot/index.html new file mode 100644 index 00000000..4f13a002 --- /dev/null +++ b/BlueMapCore/src/main/webroot/index.html @@ -0,0 +1,22 @@ + + + + + + BlueMap + + + + + + + + + + +
+ + + + + diff --git a/BlueMapCore/src/main/webroot/js/libs/bluemap.js b/BlueMapCore/src/main/webroot/js/libs/bluemap.js new file mode 100644 index 00000000..f846d529 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/bluemap.js @@ -0,0 +1,1201 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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. + */ +BlueMap = function (element, dataRoot) { + this.element = element; + this.dataRoot = dataRoot; + + this.loadingNoticeElement = $('
loading...
').appendTo($(this.element)); + + this.fileLoader = new THREE.FileLoader(); + this.blobLoader = new THREE.FileLoader(); + this.blobLoader.setResponseType("blob"); + this.bufferGeometryLoader = new THREE.BufferGeometryLoader(); + + this.initStage(); + this.locationHash = ""; + this.controls = new BlueMap.Controls(this.camera, this.element, this.hiresScene); + + this.loadSettings(function () { + this.lowresTileManager = new BlueMap.TileManager( + this, + this.settings[this.map]["lowres"]["viewDistance"], + this.loadLowresTile, + this.lowresScene, + this.settings[this.map]["lowres"]["tileSize"], + {x: 0, z: 0} + ); + + this.hiresTileManager = new BlueMap.TileManager( + this, + this.settings[this.map]["hires"]["viewDistance"], + this.loadHiresTile, + this.hiresScene, + this.settings[this.map]["hires"]["tileSize"], + {x: 0, z: 0} + ); + + this.loadHiresMaterial(function () { + this.loadLowresMaterial(function () { + this.initModules(); + this.start(); + }); + }); + }); +}; + +BlueMap.prototype.initModules = function () { + this.modules = {}; + + this.modules.compass = new BlueMap.Module.Compass(this); + this.modules.position = new BlueMap.Module.Position(this); + this.modules.mapMenu = new BlueMap.Module.MapMenu(this); + this.modules.info = new BlueMap.Module.Info(this); + this.modules.settings = new BlueMap.Module.Settings(this); +}; + +BlueMap.prototype.changeMap = function (map) { + this.hiresTileManager.close(); + this.lowresTileManager.close(); + + this.map = map; + this.controls.resetPosition(); + + this.lowresTileManager = new BlueMap.TileManager( + this, + this.settings[this.map]["lowres"]["viewDistance"], + this.loadLowresTile, + this.lowresScene, + this.settings[this.map]["lowres"]["tileSize"], + {x: 0, z: 0} + ); + + this.hiresTileManager = new BlueMap.TileManager( + this, + this.settings[this.map]["hires"]["viewDistance"], + this.loadHiresTile, + this.hiresScene, + this.settings[this.map]["hires"]["tileSize"], + {x: 0, z: 0} + ); + + this.lowresTileManager.update(); + this.hiresTileManager.update(); + + document.dispatchEvent(new Event("bluemap-map-change")); +}; + +BlueMap.prototype.loadLocationHash = function(){ + let hashVars = window.location.hash.substring(1).split(":"); + if (hashVars.length >= 1){ + if (this.settings[hashVars[0]] !== undefined && this.map !== hashVars[0]){ + this.changeMap(hashVars[0]); + } + } + if (hashVars.length >= 3){ + let x = parseInt(hashVars[1]); + let z = parseInt(hashVars[2]); + if (!isNaN(x) && !isNaN(z)){ + this.controls.targetPosition.x = x + 0.5; + this.controls.targetPosition.z = z + 0.5; + } + } + if (hashVars.length >= 6){ + let dir = parseFloat(hashVars[3]); + let dist = parseFloat(hashVars[4]); + let angle = parseFloat(hashVars[5]); + if (!isNaN(dir)) this.controls.targetDirection = dir; + if (!isNaN(dist)) this.controls.targetDistance = dist; + if (!isNaN(angle)) this.controls.targetAngle = angle; + this.controls.direction = this.controls.targetDirection; + this.controls.distance = this.controls.targetDistance; + this.controls.angle = this.controls.targetAngle; + this.controls.targetPosition.y = this.controls.minHeight; + this.controls.position.copy(this.controls.targetPosition); + } + if (hashVars.length >= 7){ + let height = parseInt(hashVars[6]); + if (!isNaN(height)){ + this.controls.minHeight = height; + this.controls.targetPosition.y = height; + this.controls.position.copy(this.controls.targetPosition); + } + } +}; + +BlueMap.prototype.start = function () { + let scope = this; + + this.loadingNoticeElement.remove(); + + this.loadLocationHash(); + + $(window).on("hashchange", function(evt){ + if (scope.locationHash === window.location.hash) return; + scope.loadLocationHash(); + }); + + this.update(); + this.render(); + + this.lowresTileManager.update(); + this.hiresTileManager.update(); +}; + +BlueMap.prototype.update = function () { + let scope = this; + setTimeout(function () { + scope.update() + }, 1000); + + this.lowresTileManager.setPosition(this.controls.targetPosition); + this.hiresTileManager.setPosition(this.controls.targetPosition); + + this.locationHash = + "#" + this.map + + ":" + Math.floor(this.controls.targetPosition.x) + + ":" + Math.floor(this.controls.targetPosition.z) + + ":" + Math.round(this.controls.targetDirection * 100) / 100 + + ":" + Math.round(this.controls.targetDistance * 100) / 100 + + ":" + Math.ceil(this.controls.targetAngle * 100) / 100 + + ":" + Math.floor(this.controls.targetPosition.y); + history.replaceState(undefined, undefined, this.locationHash); +}; + +BlueMap.prototype.render = function () { + let scope = this; + requestAnimationFrame(function () { + scope.render() + }); + + if (this.controls.update()) this.updateFrame = true; + + if (!this.updateFrame) return; + this.updateFrame = false; + + document.dispatchEvent(new Event("bluemap-update-frame")); + + this.skyboxCamera.rotation.copy(this.camera.rotation); + this.skyboxCamera.updateProjectionMatrix(); + + + this.renderer.clear(); + this.renderer.render(this.skyboxScene, this.skyboxCamera, this.renderer.getRenderTarget(), false); + this.renderer.clearDepth(); + this.renderer.render(this.lowresScene, this.camera, this.renderer.getRenderTarget(), false); + if (this.camera.position.y < 400) { + this.renderer.clearDepth(); + this.renderer.render(this.hiresScene, this.camera, this.renderer.getRenderTarget(), false); + } +}; + +BlueMap.prototype.handleContainerResize = function () { + this.camera.aspect = this.element.clientWidth / this.element.clientHeight; + this.camera.updateProjectionMatrix(); + + this.skyboxCamera.aspect = this.element.clientWidth / this.element.clientHeight; + this.skyboxCamera.updateProjectionMatrix(); + + this.renderer.setSize(this.element.clientWidth * this.quality, this.element.clientHeight * this.quality); + $(this.renderer.domElement) + .css("width", this.element.clientWidth) + .css("height", this.element.clientHeight); + + this.updateFrame = true; +}; + +BlueMap.prototype.loadSettings = function (callback) { + let scope = this; + + this.fileLoader.load(this.dataRoot + "settings.json", function (settings) { + scope.settings = JSON.parse(settings); + + scope.maps = []; + for (map in scope.settings){ + if (scope.settings.hasOwnProperty(map)){ + scope.maps.push(map); + } + } + + scope.map = scope.maps[0]; + + callback.call(scope); + }); +}; + +BlueMap.prototype.initStage = function () { + let scope = this; + + this.updateFrame = true; + this.quality = 1; + + this.renderer = new THREE.WebGLRenderer({ + alpha: true, + antialias: true, + sortObjects: false, + preserveDrawingBuffer: true, + logarithmicDepthBuffer: true, + }); + this.renderer.autoClear = false; + + this.camera = new THREE.PerspectiveCamera(75, this.element.scrollWidth / this.element.scrollHeight, 0.1, 10000); + this.camera.updateProjectionMatrix(); + + this.skyboxCamera = this.camera.clone(); + this.skyboxCamera.updateProjectionMatrix(); + + this.skyboxScene = new THREE.Scene(); + this.skyboxScene.ambient = new THREE.AmbientLight(0xffffff, 1); + this.skyboxScene.add(this.skyboxScene.ambient); + this.skyboxScene.add(this.createSkybox()); + + this.lowresScene = new THREE.Scene(); + this.lowresScene.ambient = new THREE.AmbientLight(0xffffff, 0.6); + this.lowresScene.add(this.lowresScene.ambient); + this.lowresScene.sunLight = new THREE.DirectionalLight(0xccccbb, 0.7); + this.lowresScene.sunLight.position.set(1, 5, 3); + this.lowresScene.add(this.lowresScene.sunLight); + + this.hiresScene = new THREE.Scene(); + this.hiresScene.ambient = new THREE.AmbientLight(0xffffff, 1); + this.hiresScene.add(this.hiresScene.ambient); + this.hiresScene.sunLight = new THREE.DirectionalLight(0xccccbb, 0.2); + this.hiresScene.sunLight.position.set(1, 5, 3); + this.hiresScene.add(this.hiresScene.sunLight); + + this.element.append(this.renderer.domElement); + this.handleContainerResize(); + + $(window).resize(function () { + scope.handleContainerResize() + }); +}; + +BlueMap.prototype.createSkybox = function(){ + let geometry = new THREE.CubeGeometry(10, 10, 10); + let material = [ + new THREE.MeshBasicMaterial({ + map: new THREE.TextureLoader().load('assets/skybox/south.png'), + side: THREE.BackSide + }), + new THREE.MeshBasicMaterial({ + map: new THREE.TextureLoader().load('assets/skybox/north.png'), + side: THREE.BackSide + }), + new THREE.MeshBasicMaterial({ + map: new THREE.TextureLoader().load('assets/skybox/up.png'), + side: THREE.BackSide + }), + new THREE.MeshBasicMaterial({ + map: new THREE.TextureLoader().load('assets/skybox/down.png'), + side: THREE.BackSide + }), + new THREE.MeshBasicMaterial({ + map: new THREE.TextureLoader().load('assets/skybox/east.png'), + side: THREE.BackSide + }), + new THREE.MeshBasicMaterial({ + map: new THREE.TextureLoader().load('assets/skybox/west.png'), + side: THREE.BackSide + }) + ]; + return new THREE.Mesh(geometry, material); +}; + +BlueMap.prototype.loadHiresMaterial = function (callback) { + let scope = this; + + this.fileLoader.load(this.dataRoot + "textures.json", function (textures) { + textures = JSON.parse(textures); + + let materials = []; + for (let i = 0; i < textures["textures"].length; i++) { + let t = textures["textures"][i]; + + let material = new THREE.MeshLambertMaterial({ + transparent: t["transparent"], + alphaTest: 0.01, + depthWrite: true, + depthTest: true, + blending: THREE.NormalBlending, + vertexColors: THREE.VertexColors, + side: THREE.FrontSide, + wireframe: false + }); + + let texture = new THREE.Texture(); + texture.image = BlueMap.utils.stringToImage(t["texture"]); + + texture.premultiplyAlpha = false; + texture.generateMipmaps = false; + texture.magFilter = THREE.NearestFilter; + texture.minFilter = THREE.NearestFilter; + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + texture.flipY = false; + texture.needsUpdate = true; + texture.flatShading = true; + + material.map = texture; + material.needsUpdate = true; + + materials[i] = material; + } + + scope.hiresMaterial = materials; + + callback.call(scope); + }); +}; + +BlueMap.prototype.loadLowresMaterial = function (callback) { + this.lowresMaterial = new THREE.MeshLambertMaterial({ + transparent: false, + depthWrite: true, + depthTest: true, + vertexColors: THREE.VertexColors, + side: THREE.FrontSide, + wireframe: false + }); + + callback.call(this); +}; + +BlueMap.prototype.loadHiresTile = function (tileX, tileZ, callback, onError) { + let scope = this; + + let path = this.dataRoot + "hires/" + this.map + "/"; + path += BlueMap.utils.pathFromCoords(tileX, tileZ); + path += ".json"; + + + this.bufferGeometryLoader.load(path, function (geometry) { + let object = new THREE.Mesh(geometry, scope.hiresMaterial); + + let tileSize = scope.settings[scope.map]["hires"]["tileSize"]; + let translate = scope.settings[scope.map]["hires"]["translate"]; + let scale = scope.settings[scope.map]["hires"]["scale"]; + object.position.set(tileX * tileSize.x + translate.x, 0, tileZ * tileSize.z + translate.z); + object.scale.set(scale.x, 1, scale.z); + + callback.call(scope, object); + }, function () { + + }, function (error) { + onError.call(scope, error); + }); +}; + +BlueMap.prototype.loadLowresTile = function (tileX, tileZ, callback, onError) { + let scope = this; + + let path = this.dataRoot + "lowres/" + this.map + "/"; + path += BlueMap.utils.pathFromCoords(tileX, tileZ); + path += ".json"; + + this.bufferGeometryLoader.load(path, function (geometry) { + let object = new THREE.Mesh(geometry, scope.lowresMaterial); + + let tileSize = scope.settings[scope.map]["lowres"]["tileSize"]; + let translate = scope.settings[scope.map]["lowres"]["translate"]; + let scale = scope.settings[scope.map]["lowres"]["scale"]; + object.position.set(tileX * tileSize.x + translate.x, 0, tileZ * tileSize.z + translate.z); + object.scale.set(scale.x, 1, scale.z); + + callback.call(scope, object); + }, function () { + + }, function (error) { + onError.call(scope, error); + }); +}; + + +// ###### UI ###### + +BlueMap.prototype.alert = function (content) { + let alertBox = $('#alert-box'); + if (alertBox.length === 0){ + alertBox = $('
').appendTo(this.element); + } + + let displayAlert = function(){ + let alert = $('').appendTo(alertBox); + alert.find('.alert-close-button').click(function(){ + alert.fadeOut(200, function(){ + alert.remove(); + }); + }); + alert.fadeIn(200); + }; + + let oldAlerts = alertBox.find('.alert'); + if (oldAlerts.length > 0){ + alertBox.fadeOut(200, function () { + alertBox.html(''); + alertBox.show(); + displayAlert(); + }) + } else { + displayAlert(); + } +}; + +// ###### TileManager ###### +BlueMap.TileManager = function (blueMap, viewDistance, tileLoader, scene, tileSize, position) { + this.blueMap = blueMap; + this.viewDistance = viewDistance; + this.tileLoader = tileLoader; + this.scene = scene; + this.tileSize = new THREE.Vector2(tileSize.x, tileSize.z); + + this.tile = new THREE.Vector2(position.x, position.z); + this.lastTile = this.tile.clone(); + + this.closed = false; + this.currentlyLoading = 0; + this.updateTimeout = null; + + this.tiles = {}; +}; + +BlueMap.TileManager.prototype.setPosition = function (center) { + this.tile.set(center.x, center.z).divide(this.tileSize).floor(); + + if (!this.tile.equals(this.lastTile) && !this.closed) { + this.update(); + this.lastTile.copy(this.tile); + } +}; + +BlueMap.TileManager.prototype.update = function () { + if (this.closed) return; + + //free a loader so if there was an error loading a tile we don't get stuck forever with the blocked loading process + this.currentlyLoading--; + if (this.currentlyLoading < 0) this.currentlyLoading = 0; + + this.removeFarTiles(); + this.loadCloseTiles(); +}; + +BlueMap.TileManager.prototype.removeFarTiles = function () { + let keys = Object.keys(this.tiles); + for (let i = 0; i < keys.length; i++) { + if (!this.tiles.hasOwnProperty(keys[i])) continue; + + let tile = this.tiles[keys[i]]; + + let vd = this.viewDistance; + + if ( + tile.x + vd < this.tile.x || + tile.x - vd > this.tile.x || + tile.z + vd < this.tile.y || + tile.z - vd > this.tile.y + ) { + tile.disposeModel(); + delete this.tiles[keys[i]]; + } + } +}; + +BlueMap.TileManager.prototype.removeAllTiles = function () { + let keys = Object.keys(this.tiles); + for (let i = 0; i < keys.length; i++) { + if (!this.tiles.hasOwnProperty(keys[i])) continue; + + let tile = this.tiles[keys[i]]; + tile.disposeModel(); + delete this.tiles[keys[i]]; + } +}; + +BlueMap.TileManager.prototype.close = function () { + this.closed = true; + this.removeAllTiles(); +}; + +BlueMap.TileManager.prototype.loadCloseTiles = function () { + if (this.closed) return; + + let scope = this; + + if (this.currentlyLoading < 8) { + if (!this.loadNextTile()) return; + } + + if (this.updateTimeout) clearTimeout(this.updateTimeout); + this.updateTimeout = setTimeout(function () { + scope.loadCloseTiles() + }, 0); +}; + +BlueMap.TileManager.prototype.loadNextTile = function () { + let x = 0; + let z = 0; + let d = 1; + let m = 1; + + while (m < this.viewDistance * 2) { + while (2 * x * d < m) { + if (this.tryLoadTile(this.tile.x + x, this.tile.y + z)) return true; + x = x + d; + } + while (2 * z * d < m) { + if (this.tryLoadTile(this.tile.x + x, this.tile.y + z)) return true; + z = z + d; + } + d = -1 * d; + m = m + 1; + } + + return false; +}; + +BlueMap.TileManager.prototype.tryLoadTile = function (x, z) { + if (this.closed) return; + + let scope = this; + + let tileHash = BlueMap.utils.hashTile(x, z); + + let tile = this.tiles[tileHash]; + if (tile !== undefined) return false; + + tile = new BlueMap.Tile(this.scene, x, z); + tile.isLoading = true; + + this.currentlyLoading++; + + this.tiles[tileHash] = tile; + + this.tileLoader.call(this.blueMap, x, z, function (model) { + tile.isLoading = false; + + if (tile.disposed || scope.closed) { + model.geometry.dispose(); + tile.disposeModel(); + delete scope.tiles[tileHash]; + return; + } + + scope.tiles[tileHash] = tile; + tile.setModel(model); + + scope.blueMap.updateFrame = true; + + scope.currentlyLoading--; + if (scope.currentlyLoading < 0) scope.currentlyLoading = 0; + }, function (error) { + tile.isLoading = false; + tile.disposeModel(); + + scope.currentlyLoading--; + + //console.log("Failed to load tile: ", x, z); + }); + + return true; +}; + + +// ###### Tile ###### +BlueMap.Tile = function (scene, x, z) { + this.scene = scene; + this.x = x; + this.z = z; + + this.isLoading = false; + this.disposed = false; + + this.model = null; +}; + +BlueMap.Tile.prototype.setModel = function (model) { + this.disposeModel(); + + if (model) { + this.model = model; + this.scene.add(model); + + //console.log("Added tile:", this.x, this.z); + } +}; + +BlueMap.Tile.prototype.disposeModel = function () { + this.disposed = true; + + if (this.model) { + this.scene.remove(this.model); + this.model.geometry.dispose(); + delete this.model; + + //console.log("Removed tile:", this.x, this.z); + } +}; + + +// ###### Controls ###### + +/** + * targetHeightScene and cameraHeightScene are scenes of objects that are checked via raycasting for a height for the target and the camera + */ +BlueMap.Controls = function (camera, element, heightScene) { + let scope = this; + + this.settings = { + zoom: { + min: 10, + max: 2000, + speed: 1.5, + smooth: 0.2, + }, + move: { + speed: 1.75, + smooth: 0.3, + smoothY: 0.075, + }, + tilt: { + max: Math.PI / 2.1, + speed: 1.5, + smooth: 0.3, + }, + rotate: { + speed: 1.5, + smooth: 0.3, + } + }; + + this.camera = camera; + this.element = element; + this.heightScene = heightScene; + this.minHeight = 0; + + this.raycaster = new THREE.Raycaster(); + this.rayDirection = new THREE.Vector3(0, -1, 0); + + this.resetPosition(); + + this.mouse = new THREE.Vector2(0, 0); + this.lastMouse = new THREE.Vector2(0, 0); + this.deltaMouse = new THREE.Vector2(0, 0); + + //variables used to calculate with (to prevent object creation every update) + this.orbitRot = new THREE.Euler(0, 0, 0, "YXZ"); + this.cameraPosDelta = new THREE.Vector3(0, 0, 0); + this.moveDelta = new THREE.Vector2(0, 0); + + this.keyStates = {}; + + this.KEYS = { + LEFT: 37, + UP: 38, + RIGHT: 39, + DOWN: 40, + ORBIT: THREE.MOUSE.RIGHT, + MOVE: THREE.MOUSE.LEFT + }; + this.STATES = { + NONE: -1, + ORBIT: 0, + MOVE: 1, + }; + + this.state = this.STATES.NONE; + + let canvas = $(this.element).find("canvas").get(0); + window.addEventListener('contextmenu', function (e) { + e.preventDefault(); + }, false); + canvas.addEventListener('mousedown', function (e) { + scope.onMouseDown(e); + }, false); + window.addEventListener('mousemove', function (e) { + scope.onMouseMove(e); + }, false); + window.addEventListener('mouseup', function (e) { + scope.onMouseUp(e); + }, false); + canvas.addEventListener('wheel', function (e) { + scope.onMouseWheel(e); + }, false); + window.addEventListener('keydown', function (e) { + scope.onKeyDown(e); + }, false); + window.addEventListener('keyup', function (e) { + scope.onKeyUp(e); + }, false); + + this.camera.position.set(0, 1000, 0); + this.camera.lookAt(this.position); + this.camera.updateProjectionMatrix(); +}; + +BlueMap.Controls.prototype.resetPosition = function () { + this.position = new THREE.Vector3(0, 70, 0); + this.targetPosition = new THREE.Vector3(0, 70, 0); + + this.distance = 5000; + this.targetDistance = 1000; + + this.direction = 0; + this.targetDirection = 0; + + this.angle = 0; + this.targetAngle = 0; +}; + +BlueMap.Controls.prototype.update = function () { + this.updateMouseMoves(); + + let changed = false; + + let zoomLerp = (this.distance - 100) / 200; + if (zoomLerp < 0) zoomLerp = 0; + if (zoomLerp > 1) zoomLerp = 1; + this.targetPosition.y = 300 * zoomLerp + this.minHeight * (1 - zoomLerp); + + this.position.x += (this.targetPosition.x - this.position.x) * this.settings.move.smooth; + this.position.y += (this.targetPosition.y - this.position.y) * this.settings.move.smoothY; + this.position.z += (this.targetPosition.z - this.position.z) * this.settings.move.smooth; + + this.distance += (this.targetDistance - this.distance) * this.settings.zoom.smooth; + + let deltaDir = (this.targetDirection - this.direction) * this.settings.rotate.smooth; + this.direction += deltaDir; + changed = changed || Math.abs(deltaDir) > 0.001; + + let max = Math.min(this.settings.tilt.max, this.settings.tilt.max - Math.pow(((this.distance - this.settings.zoom.min) / (this.settings.zoom.max - this.settings.zoom.min)) * Math.pow(this.settings.tilt.max, 4), 1/4)); + if (this.targetAngle > max) this.targetAngle = max; + if (this.targetAngle < 0.01) this.targetAngle = 0.001; + let deltaAngle = (this.targetAngle - this.angle) * this.settings.tilt.smooth; + this.angle += deltaAngle; + changed = changed || Math.abs(deltaAngle) > 0.001; + + let last = this.camera.position.x + this.camera.position.y + this.camera.position.z; + this.orbitRot.set(this.angle, this.direction, 0); + this.cameraPosDelta.set(0, this.distance, 0).applyEuler(this.orbitRot); + + this.camera.position.set(this.position.x + this.cameraPosDelta.x, this.position.y + this.cameraPosDelta.y, this.position.z + this.cameraPosDelta.z); + let move = last - (this.camera.position.x + this.camera.position.y + this.camera.position.z); + + changed = changed || Math.abs(move) > 0.001; + + if (changed) { + this.camera.lookAt(this.position); + this.camera.updateProjectionMatrix(); + + this.updateHeights(); + } + + return changed; +}; + +BlueMap.Controls.prototype.updateHeights = function(){ + //TODO: this can be performance-improved by only intersecting the correct tile? + + let rayStart = new THREE.Vector3(this.targetPosition.x, 300, this.targetPosition.z); + this.raycaster.set(rayStart, this.rayDirection); + this.raycaster.near = 1; + this.raycaster.far = 300; + let intersects = this.raycaster.intersectObjects(this.heightScene.children); + if (intersects.length > 0){ + this.minHeight = intersects[0].point.y; + //this.targetPosition.y = this.minHeight; + } else { + //this.targetPosition.y = 0; + } + + rayStart.set(this.camera.position.x, 300, this.camera.position.z); + this.raycaster.set(rayStart, this.rayDirection); + intersects.length = 0; + intersects = this.raycaster.intersectObjects(this.heightScene.children); + if (intersects.length > 0){ + if (intersects[0].point.y > this.minHeight){ + this.minHeight = intersects[0].point.y; + } + } +}; + +BlueMap.Controls.prototype.updateMouseMoves = function (e) { + this.deltaMouse.set(this.lastMouse.x - this.mouse.x, this.lastMouse.y - this.mouse.y); + + this.moveDelta.x = 0; + this.moveDelta.y = 0; + + if (this.keyStates[this.KEYS.UP]){ + this.moveDelta.y -= 20; + } + if (this.keyStates[this.KEYS.DOWN]){ + this.moveDelta.y += 20; + } + if (this.keyStates[this.KEYS.LEFT]){ + this.moveDelta.x -= 20; + } + if (this.keyStates[this.KEYS.RIGHT]){ + this.moveDelta.x += 20; + } + + if (this.state === this.STATES.MOVE) { + if (this.deltaMouse.x === 0 && this.deltaMouse.y === 0) return; + this.moveDelta.copy(this.deltaMouse); + } + + if (this.moveDelta.x !== 0 || this.moveDelta.y !== 0) { + this.moveDelta.rotateAround(BlueMap.utils.Vector2.ZERO, -this.direction); + this.targetPosition.set( + this.targetPosition.x + (this.moveDelta.x * this.distance / this.element.clientHeight * this.settings.move.speed), + this.targetPosition.y, + this.targetPosition.z + (this.moveDelta.y * this.distance / this.element.clientHeight * this.settings.move.speed) + ); + } + + if (this.state === this.STATES.ORBIT) { + this.targetDirection += (this.deltaMouse.x / this.element.clientHeight * Math.PI); + this.targetAngle += (this.deltaMouse.y / this.element.clientHeight * Math.PI); + } + + this.lastMouse.copy(this.mouse); +}; + +BlueMap.Controls.prototype.onMouseWheel = function (e) { + if (e.deltaY > 0) { + this.targetDistance *= this.settings.zoom.speed; + } else if (e.deltaY < 0) { + this.targetDistance /= this.settings.zoom.speed; + } + + if (this.targetDistance < this.settings.zoom.min) this.targetDistance = this.settings.zoom.min; + if (this.targetDistance > this.settings.zoom.max) this.targetDistance = this.settings.zoom.max; +}; + +BlueMap.Controls.prototype.onMouseMove = function (e) { + this.mouse.set(e.clientX, e.clientY); + + if (this.state !== this.STATES.NONE){ + e.preventDefault(); + } +}; + +BlueMap.Controls.prototype.onMouseDown = function (e) { + if (this.state !== this.STATES.NONE) return; + + switch (e.button) { + case this.KEYS.MOVE : + this.state = this.STATES.MOVE; + e.preventDefault(); + break; + case this.KEYS.ORBIT : + this.state = this.STATES.ORBIT; + e.preventDefault(); + break; + } +}; + +BlueMap.Controls.prototype.onMouseUp = function (e) { + if (this.state === this.STATES.NONE) return; + + switch (e.button) { + case this.KEYS.MOVE : + if (this.state === this.STATES.MOVE) this.state = this.STATES.NONE; + break; + case this.KEYS.ORBIT : + if (this.state === this.STATES.ORBIT) this.state = this.STATES.NONE; + break; + } +}; + +BlueMap.Controls.prototype.onKeyDown = function (e) { + this.keyStates[e.keyCode] = true; +}; + +BlueMap.Controls.prototype.onKeyUp = function (e) { + this.keyStates[e.keyCode] = false; +}; + +// ###### Modules ###### +BlueMap.Module = { + getTopRightElement: function(blueMap){ + let element = $("#bluemap-topright"); + + if (element.length === 0){ + element = $('
').appendTo(blueMap.element); + } + + return element; + }, + + getTopLeftElement: function(blueMap){ + let element = $("#bluemap-topleft"); + + if (element.length === 0){ + element = $('
').appendTo(blueMap.element); + } + + return element; + } +}; + +// ###### Modules.MapMenu ###### +BlueMap.Module.MapMenu = function (blueMap) { + let scope = this; + + this.bluemap = blueMap; + let maps = this.bluemap.settings; + + $("#bluemap-mapmenu").remove(); + this.element = $('').appendTo(BlueMap.Module.getTopLeftElement(blueMap)); + + let dropdown = $('').appendTo(this.element); + this.maplist = $('
    ').appendTo(dropdown); + + for (mapId in maps) { + if (!maps.hasOwnProperty(mapId)) continue; + + let map = maps[mapId]; + $('
  • ' + map.name + '
  • ').appendTo(this.maplist); + } + + this.maplist.find('li[map=' + this.bluemap.map + ']').hide(); + this.maplist.find('li[map]').click(function (e) { + let map = $(this).attr('map'); + scope.bluemap.changeMap(map); + }); + + $(document).on('bluemap-map-change', function(){ + scope.maplist.find('li').show(); + scope.maplist.find('li[map=' + scope.bluemap.map + ']').hide(); + + scope.element.find(".selection").html(scope.bluemap.settings[scope.bluemap.map].name); + }); +}; + +// ###### Modules.Compass ###### +BlueMap.Module.Compass = function (blueMap) { + let scope = this; + + this.blueMap = blueMap; + + $("#bluemap-compass").remove(); + this.element = $('
    ').appendTo(BlueMap.Module.getTopLeftElement(blueMap)); + this.needle = $('#bluemap-compass-needle'); + + $(document).on('bluemap-update-frame', function (){ + scope.needle.css("transform", "rotate(" + scope.blueMap.controls.direction + "rad)"); + }); + + $(this.element).click(function(){ + scope.blueMap.controls.targetDirection = 0; + scope.blueMap.controls.direction = scope.blueMap.controls.direction % (Math.PI * 2); + if (scope.blueMap.controls.direction < -Math.PI) scope.blueMap.controls.direction += Math.PI * 2; + if (scope.blueMap.controls.direction > Math.PI) scope.blueMap.controls.direction -= Math.PI * 2; + }); +}; + +// ###### Modules.Position ###### +BlueMap.Module.Position = function (blueMap) { + let scope = this; + + this.blueMap = blueMap; + + let parent = BlueMap.Module.getTopLeftElement(blueMap); + + $(".bluemap-position").remove(); + this.elementX = $('
    0
    ').appendTo(parent); + //this.elementY = $('
    0
    ').appendTo(parent); + this.elementZ = $('
    0
    ').appendTo(parent); + + $(document).on('bluemap-update-frame', function (){ + scope.elementX.html(Math.floor(scope.blueMap.controls.targetPosition.x)); + //scope.elementY.html(scope.blueMap.controls.targetPosition.y === 0 ? "-" : Math.floor(scope.blueMap.controls.targetPosition.y)); + scope.elementZ.html(Math.floor(scope.blueMap.controls.targetPosition.z)); + }); +}; + +// ###### Modules.Settings ###### +BlueMap.Module.Settings = function (blueMap) { + let scope = this; + + this.blueMap = blueMap; + + let parent = BlueMap.Module.getTopRightElement(blueMap); + + $("#bluemap-settings").remove(); + this.elementMenu = $('').appendTo(parent); + this.elementSettings = $('
    ').appendTo(parent); + this.elementSettings.click(function(){ + if (scope.elementMenu.css('display') === "none"){ + scope.elementSettings.addClass("active"); + } else { + scope.elementSettings.removeClass("active"); + } + + scope.elementMenu.animate({ + width: "toggle" + }, 200); + }); + + + /* Quality */ + + this.elementQuality = $( + '' + ).prependTo(this.elementMenu); + + this.elementQuality.find("li[quality]").click(function(){ + let desc = $(this).html(); + scope.blueMap.quality = parseFloat($(this).attr("quality")); + + scope.elementQuality.find('li').show(); + scope.elementQuality.find('li[quality=\'' + scope.blueMap.quality + '\']').hide(); + + scope.elementQuality.find('.selection > span').html(desc); + + scope.blueMap.handleContainerResize(); + }); + + + /* Render Distance */ + + this.pctToRenderDistance = function ( value , defaultValue ) { + let max = defaultValue * 5; + if (max > 20) max = 20; + + return THREE.Math.mapLinear(value, 0, 100, 1, max); + }; + + this.renderDistanceToPct = function ( value , defaultValue ) { + let max = defaultValue * 5; + if (max > 20) max = 20; + + return THREE.Math.mapLinear(value, 1, max, 0, 100); + }; + + this.init = function(){ + scope.defaultHighRes = scope.blueMap.hiresTileManager.viewDistance; + scope.defaultLowRes = scope.blueMap.lowresTileManager.viewDistance; + + scope.elementRenderDistance.html( + 'View Distance: ' + scope.blueMap.hiresTileManager.viewDistance + '' + + '' + ); + + let slider = scope.elementRenderDistance.find("input"); + slider.on('change input', function(e){ + scope.blueMap.hiresTileManager.viewDistance = scope.pctToRenderDistance(parseFloat(slider.val()), scope.defaultHighRes); + scope.blueMap.lowresTileManager.viewDistance = scope.pctToRenderDistance(parseFloat(slider.val()), scope.defaultLowRes); + scope.elementRenderDistance.find(".selection > span").html(Math.round(scope.blueMap.hiresTileManager.viewDistance * 10) / 10); + + scope.blueMap.lowresTileManager.update(); + scope.blueMap.hiresTileManager.update(); + }); + }; + + this.elementRenderDistance = $( + '' + ).prependTo(this.elementMenu); + + scope.init(); + + $(document).on('bluemap-map-change', this.init); +}; + +// ###### Modules.Info ###### +BlueMap.Module.Info = function (blueMap) { + let scope = this; + + this.blueMap = blueMap; + + let parent = BlueMap.Module.getTopRightElement(blueMap); + + $("#bluemap-info").remove(); + this.elementInfo = $('
    ').appendTo(parent); + + this.elementInfo.click(function(){ + scope.blueMap.alert( + '

    Info

    ' + + 'Visit BlueMap on GitHub!
    ' + + 'BlueMap works best with Chrome.
    ' + + '

    Controls

    ' + + 'Leftclick-drag with your mouse or use the arrow-keys to navigate.
    ' + + 'Rightclick-drag with your mouse to rotate your view.
    ' + + 'Scroll to zoom.
    ' + ); + }); +}; + +// ###### Utils ###### +BlueMap.utils = {}; + +BlueMap.utils.stringToImage = function (string) { + let image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img'); + image.src = string; + return image; +}; + +BlueMap.utils.pathFromCoords = function (x, z) { + let path = "x"; + path += BlueMap.utils.splitNumberToPath(x); + + path += "z"; + path += BlueMap.utils.splitNumberToPath(z); + + path = path.substring(0, path.length - 1); + + return path; +}; + +BlueMap.utils.splitNumberToPath = function (num) { + let path = ""; + + if (num < 0) { + num = -num; + path += "-"; + } + + let s = num.toString(); + + for (let i = 0; i < s.length; i++) { + path += s.charAt(i) + "/"; + } + + return path; +}; + +BlueMap.utils.hashTile = function (x, z) { + return "x" + x + "z" + z; +}; + +BlueMap.utils.Vector2 = {}; +BlueMap.utils.Vector2.ZERO = new THREE.Vector2(0, 0); +BlueMap.utils.Vector3 = {}; +BlueMap.utils.Vector3.ZERO = new THREE.Vector3(0, 0); diff --git a/BlueMapCore/src/main/webroot/js/libs/jquery.min.js b/BlueMapCore/src/main/webroot/js/libs/jquery.min.js new file mode 100644 index 00000000..63174a0d --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.2 jquery.com | jquery.org/license */ +(function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write(""),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b
    a",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="
    t
    ",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="
    ",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;be.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="
    ",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="

    ",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b0)for(e=d;e=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*\s*$/g,bz={option:[1,""],legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X
    ","
    "]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1>");try{for(;d1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]===""&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("
    ").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file diff --git a/BlueMapCore/src/main/webroot/js/libs/three.min.js b/BlueMapCore/src/main/webroot/js/libs/three.min.js new file mode 100644 index 00000000..ea5b0c34 --- /dev/null +++ b/BlueMapCore/src/main/webroot/js/libs/three.min.js @@ -0,0 +1,946 @@ +// threejs.org/license +(function(k,za){"object"===typeof exports&&"undefined"!==typeof module?za(exports):"function"===typeof define&&define.amd?define(["exports"],za):za(k.THREE={})})(this,function(k){function za(){}function B(a,b){this.x=a||0;this.y=b||0}function R(){this.elements=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1];0b&&(b=a[c]);return b}function D(){Object.defineProperty(this,"id",{value:Jf+=2});this.uuid=J.generateUUID();this.name="";this.type="BufferGeometry";this.index=null;this.attributes={};this.morphAttributes={};this.groups=[];this.boundingSphere=this.boundingBox=null;this.drawRange={start:0,count:Infinity};this.userData={}}function Nb(a,b,c,d,e,f){P.call(this);this.type="BoxGeometry";this.parameters={width:a,height:b,depth:c,widthSegments:d,heightSegments:e, +depthSegments:f};this.fromBufferGeometry(new pb(a,b,c,d,e,f));this.mergeVertices()}function pb(a,b,c,d,e,f){function g(a,b,c,d,e,f,g,k,Q,M,Ob){var r=f/Q,u=g/M,O=f/2,y=g/2,x=k/2;g=Q+1;var w=M+1,C=f=0,A,B,z=new p;for(B=0;Bm;m++){if(n=d[m])if(h=n[0],l=n[1]){v&&e.addAttribute("morphTarget"+m,v[h]);f&&e.addAttribute("morphNormal"+m,f[h]);c[m]=l;continue}c[m]=0}g.getUniforms().setValue(a,"morphTargetInfluences",c)}}}function Vf(a,b){var c={};return{update:function(d){var e=b.render.frame,f=d.geometry,g=a.get(d,f);c[g.id]!==e&&(f.isGeometry&&g.updateFromObject(d), +a.update(g),c[g.id]=e);return g},dispose:function(){c={}}}}function Za(a,b,c,d,e,f,g,h,l,m){a=void 0!==a?a:[];ea.call(this,a,void 0!==b?b:301,c,d,e,f,g,h,l,m);this.flipY=!1}function Rb(a,b,c){var d=a[0];if(0>=d||0/gm,function(a,c){a=S[c];if(void 0===a)throw Error("Can not resolve #include <"+c+ +">");return Zd(a)})}function Ze(a){return a.replace(/#pragma unroll_loop[\s]+?for \( int i = (\d+); i < (\d+); i \+\+ \) \{([\s\S]+?)(?=\})\}/g,function(a,c,d,e){a="";for(c=parseInt(c);c 0 ) {\n\t\tfloat fogFactor = 0.0;\n\t\tif ( fogType == 1 ) {\n\t\t\tfogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t\t} else {\n\t\t\tconst float LOG2 = 1.442695;\n\t\t\tfogFactor = exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 );\n\t\t\tfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n\t\t}\n\t\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n\t}\n}"].join("\n")); +b.compileShader(Ca);b.compileShader(G);b.attachShader(sa,Ca);b.attachShader(sa,G);b.linkProgram(sa);U=sa;O=b.getAttribLocation(U,"position");Q=b.getAttribLocation(U,"uv");f=b.getUniformLocation(U,"uvOffset");g=b.getUniformLocation(U,"uvScale");h=b.getUniformLocation(U,"rotation");l=b.getUniformLocation(U,"center");m=b.getUniformLocation(U,"scale");v=b.getUniformLocation(U,"color");n=b.getUniformLocation(U,"map");t=b.getUniformLocation(U,"opacity");q=b.getUniformLocation(U,"modelViewMatrix");r=b.getUniformLocation(U, +"projectionMatrix");k=b.getUniformLocation(U,"fogType");y=b.getUniformLocation(U,"fogDensity");w=b.getUniformLocation(U,"fogNear");x=b.getUniformLocation(U,"fogFar");A=b.getUniformLocation(U,"fogColor");b.getUniformLocation(U,"fogDepth");C=b.getUniformLocation(U,"alphaTest");sa=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");sa.width=8;sa.height=8;Ca=sa.getContext("2d");Ca.fillStyle="white";Ca.fillRect(0,0,8,8);z=new Sb(sa)}c.useProgram(U);c.initAttributes();c.enableAttribute(O); +c.enableAttribute(Q);c.disableUnusedAttributes();c.disable(b.CULL_FACE);c.enable(b.BLEND);b.bindBuffer(b.ARRAY_BUFFER,B);b.vertexAttribPointer(O,2,b.FLOAT,!1,16,0);b.vertexAttribPointer(Q,2,b.FLOAT,!1,16,8);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,qb);b.uniformMatrix4fv(r,!1,Ob.projectionMatrix.elements);c.activeTexture(b.TEXTURE0);b.uniform1i(n,0);Ca=sa=0;(G=p.fog)?(b.uniform3f(A,G.color.r,G.color.g,G.color.b),G.isFog?(b.uniform1f(w,G.near),b.uniform1f(x,G.far),b.uniform1i(k,1),Ca=sa=1):G.isFogExp2&& +(b.uniform1f(y,G.density),b.uniform1i(k,2),Ca=sa=2)):(b.uniform1i(k,0),Ca=sa=0);G=0;for(var K=u.length;Gb||a.height>b){if("data"in a){console.warn("THREE.WebGLRenderer: image in DataTexture is too big ("+a.width+"x"+a.height+").");return}b/=Math.max(a.width,a.height);var c=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");c.width=Math.floor(a.width*b);c.height=Math.floor(a.height*b);c.getContext("2d").drawImage(a,0,0,a.width,a.height,0,0,c.width,c.height);console.warn("THREE.WebGLRenderer: image is too big ("+a.width+"x"+a.height+"). Resized to "+c.width+ +"x"+c.height,a);return c}return a}function l(a){return J.isPowerOfTwo(a.width)&&J.isPowerOfTwo(a.height)}function m(a,b){return a.generateMipmaps&&b&&1003!==a.minFilter&&1006!==a.minFilter}function v(b,c,e,f){a.generateMipmap(b);d.get(c).__maxMipLevel=Math.log(Math.max(e,f))*Math.LOG2E}function n(b){return 1003===b||1004===b||1005===b?a.NEAREST:a.LINEAR}function t(b){b=b.target;b.removeEventListener("dispose",t);a:{var c=d.get(b);if(b.image&&c.__image__webglTextureCube)a.deleteTexture(c.__image__webglTextureCube); +else{if(void 0===c.__webglInit)break a;a.deleteTexture(c.__webglTexture)}d.remove(b)}b.isVideoTexture&&delete A[b.id];g.memory.textures--}function q(b){b=b.target;b.removeEventListener("dispose",q);var c=d.get(b),e=d.get(b.texture);if(b){void 0!==e.__webglTexture&&a.deleteTexture(e.__webglTexture);b.depthTexture&&b.depthTexture.dispose();if(b.isWebGLRenderTargetCube)for(e=0;6>e;e++)a.deleteFramebuffer(c.__webglFramebuffer[e]),c.__webglDepthbuffer&&a.deleteRenderbuffer(c.__webglDepthbuffer[e]);else a.deleteFramebuffer(c.__webglFramebuffer), +c.__webglDepthbuffer&&a.deleteRenderbuffer(c.__webglDepthbuffer);d.remove(b.texture);d.remove(b)}g.memory.textures--}function k(b,n){var q=d.get(b);if(b.isVideoTexture){var k=b.id,r=g.render.frame;A[k]!==r&&(A[k]=r,b.update())}if(0p;p++)r[p]=n||k?k?b.image[p].image:b.image[p]:h(b.image[p],e.maxCubemapSize);var x=r[0],y=l(x),C=f.convert(b.format),w=f.convert(b.type);u(a.TEXTURE_CUBE_MAP,b,y);for(p=0;6>p;p++)if(n)for(var O,Q=r[p].mipmaps,A=0,B=Q.length;At;t++)e.__webglFramebuffer[t]=a.createFramebuffer()}else e.__webglFramebuffer= +a.createFramebuffer();if(h){c.bindTexture(a.TEXTURE_CUBE_MAP,f.__webglTexture);u(a.TEXTURE_CUBE_MAP,b.texture,n);for(t=0;6>t;t++)p(e.__webglFramebuffer[t],b,a.COLOR_ATTACHMENT0,a.TEXTURE_CUBE_MAP_POSITIVE_X+t);m(b.texture,n)&&v(a.TEXTURE_CUBE_MAP,b.texture,b.width,b.height);c.bindTexture(a.TEXTURE_CUBE_MAP,null)}else c.bindTexture(a.TEXTURE_2D,f.__webglTexture),u(a.TEXTURE_2D,b.texture,n),p(e.__webglFramebuffer,b,a.COLOR_ATTACHMENT0,a.TEXTURE_2D),m(b.texture,n)&&v(a.TEXTURE_2D,b.texture,b.width,b.height), +c.bindTexture(a.TEXTURE_2D,null);if(b.depthBuffer){e=d.get(b);f=!0===b.isWebGLRenderTargetCube;if(b.depthTexture){if(f)throw Error("target.depthTexture not supported in Cube render targets");if(b&&b.isWebGLRenderTargetCube)throw Error("Depth Texture with cube render targets is not supported");a.bindFramebuffer(a.FRAMEBUFFER,e.__webglFramebuffer);if(!b.depthTexture||!b.depthTexture.isDepthTexture)throw Error("renderTarget.depthTexture must be an instance of THREE.DepthTexture");d.get(b.depthTexture).__webglTexture&& +b.depthTexture.image.width===b.width&&b.depthTexture.image.height===b.height||(b.depthTexture.image.width=b.width,b.depthTexture.image.height=b.height,b.depthTexture.needsUpdate=!0);k(b.depthTexture,0);e=d.get(b.depthTexture).__webglTexture;if(1026===b.depthTexture.format)a.framebufferTexture2D(a.FRAMEBUFFER,a.DEPTH_ATTACHMENT,a.TEXTURE_2D,e,0);else if(1027===b.depthTexture.format)a.framebufferTexture2D(a.FRAMEBUFFER,a.DEPTH_STENCIL_ATTACHMENT,a.TEXTURE_2D,e,0);else throw Error("Unknown depthTexture format"); +}else if(f)for(e.__webglDepthbuffer=[],f=0;6>f;f++)a.bindFramebuffer(a.FRAMEBUFFER,e.__webglFramebuffer[f]),e.__webglDepthbuffer[f]=a.createRenderbuffer(),w(e.__webglDepthbuffer[f],b);else a.bindFramebuffer(a.FRAMEBUFFER,e.__webglFramebuffer),e.__webglDepthbuffer=a.createRenderbuffer(),w(e.__webglDepthbuffer,b);a.bindFramebuffer(a.FRAMEBUFFER,null)}};this.updateRenderTargetMipmap=function(b){var e=b.texture,f=l(b);if(m(e,f)){f=b.isWebGLRenderTargetCube?a.TEXTURE_CUBE_MAP:a.TEXTURE_2D;var g=d.get(e).__webglTexture; +c.bindTexture(f,g);v(f,e,b.width,b.height);c.bindTexture(f,null)}}}function af(a,b){return{convert:function(c){if(1E3===c)return a.REPEAT;if(1001===c)return a.CLAMP_TO_EDGE;if(1002===c)return a.MIRRORED_REPEAT;if(1003===c)return a.NEAREST;if(1004===c)return a.NEAREST_MIPMAP_NEAREST;if(1005===c)return a.NEAREST_MIPMAP_LINEAR;if(1006===c)return a.LINEAR;if(1007===c)return a.LINEAR_MIPMAP_NEAREST;if(1008===c)return a.LINEAR_MIPMAP_LINEAR;if(1009===c)return a.UNSIGNED_BYTE;if(1017===c)return a.UNSIGNED_SHORT_4_4_4_4; +if(1018===c)return a.UNSIGNED_SHORT_5_5_5_1;if(1019===c)return a.UNSIGNED_SHORT_5_6_5;if(1010===c)return a.BYTE;if(1011===c)return a.SHORT;if(1012===c)return a.UNSIGNED_SHORT;if(1013===c)return a.INT;if(1014===c)return a.UNSIGNED_INT;if(1015===c)return a.FLOAT;if(1016===c){var d=b.get("OES_texture_half_float");if(null!==d)return d.HALF_FLOAT_OES}if(1021===c)return a.ALPHA;if(1022===c)return a.RGB;if(1023===c)return a.RGBA;if(1024===c)return a.LUMINANCE;if(1025===c)return a.LUMINANCE_ALPHA;if(1026=== +c)return a.DEPTH_COMPONENT;if(1027===c)return a.DEPTH_STENCIL;if(100===c)return a.FUNC_ADD;if(101===c)return a.FUNC_SUBTRACT;if(102===c)return a.FUNC_REVERSE_SUBTRACT;if(200===c)return a.ZERO;if(201===c)return a.ONE;if(202===c)return a.SRC_COLOR;if(203===c)return a.ONE_MINUS_SRC_COLOR;if(204===c)return a.SRC_ALPHA;if(205===c)return a.ONE_MINUS_SRC_ALPHA;if(206===c)return a.DST_ALPHA;if(207===c)return a.ONE_MINUS_DST_ALPHA;if(208===c)return a.DST_COLOR;if(209===c)return a.ONE_MINUS_DST_COLOR;if(210=== +c)return a.SRC_ALPHA_SATURATE;if(33776===c||33777===c||33778===c||33779===c)if(d=b.get("WEBGL_compressed_texture_s3tc"),null!==d){if(33776===c)return d.COMPRESSED_RGB_S3TC_DXT1_EXT;if(33777===c)return d.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(33778===c)return d.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(33779===c)return d.COMPRESSED_RGBA_S3TC_DXT5_EXT}if(35840===c||35841===c||35842===c||35843===c)if(d=b.get("WEBGL_compressed_texture_pvrtc"),null!==d){if(35840===c)return d.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;if(35841=== +c)return d.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;if(35842===c)return d.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;if(35843===c)return d.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG}if(36196===c&&(d=b.get("WEBGL_compressed_texture_etc1"),null!==d))return d.COMPRESSED_RGB_ETC1_WEBGL;if(37808===c||37809===c||37810===c||37811===c||37812===c||37813===c||37814===c||37815===c||37816===c||37817===c||37818===c||37819===c||37820===c||37821===c)if(d=b.get("WEBGL_compressed_texture_astc"),null!==d)return c;if(103===c||104===c)if(d=b.get("EXT_blend_minmax"), +null!==d){if(103===c)return d.MIN_EXT;if(104===c)return d.MAX_EXT}return 1020===c&&(d=b.get("WEBGL_depth_texture"),null!==d)?d.UNSIGNED_INT_24_8_WEBGL:0}}}function Ub(){H.call(this);this.type="Group"}function Z(a,b,c,d){Ra.call(this);this.type="PerspectiveCamera";this.fov=void 0!==a?a:50;this.zoom=1;this.near=void 0!==c?c:.1;this.far=void 0!==d?d:2E3;this.focus=10;this.aspect=void 0!==b?b:1;this.view=null;this.filmGauge=35;this.filmOffset=0;this.updateProjectionMatrix()}function Hc(a){Z.call(this); +this.cameras=a||[]}function bf(a){function b(){return null!==e&&!0===e.isPresenting}function c(){if(b()){var c=e.getEyeParameters("left"),f=c.renderWidth;c=c.renderHeight;w=a.getPixelRatio();y=a.getSize();a.setDrawingBufferSize(2*f,c,1);A.start()}else d.enabled&&(a.setDrawingBufferSize(y.width,y.height,w),A.stop())}var d=this,e=null,f=null,g=null,h=[],l=new R,m=new R;"undefined"!==typeof window&&"VRFrameData"in window&&(f=new window.VRFrameData,window.addEventListener("vrdisplaypresentchange",c,!1)); +var v=new R,n=new ca,t=new p,q=new Z;q.bounds=new W(0,0,.5,1);q.layers.enable(1);var k=new Z;k.bounds=new W(.5,0,.5,1);k.layers.enable(2);var u=new Hc([q,k]);u.layers.enable(1);u.layers.enable(2);var y,w,x=!1;this.enabled=!1;this.userHeight=1.6;this.getController=function(a){var b=h[a];void 0===b&&(b=new Ub,b.matrixAutoUpdate=!1,b.visible=!1,h[a]=b);return b};this.getDevice=function(){return e};this.setDevice=function(a){void 0!==a&&(e=a);A.setContext(a)};this.setPoseTarget=function(a){void 0!==a&& +(g=a)};this.getCamera=function(a){if(null===e)return a.position.set(0,d.userHeight,0),a;e.depthNear=a.near;e.depthFar=a.far;e.getFrameData(f);var b=e.stageParameters;b?l.fromArray(b.sittingToStandingTransform):l.makeTranslation(0,d.userHeight,0);b=f.pose;var c=null!==g?g:a;c.matrix.copy(l);c.matrix.decompose(c.position,c.quaternion,c.scale);null!==b.orientation&&(n.fromArray(b.orientation),c.quaternion.multiply(n));null!==b.position&&(n.setFromRotationMatrix(l),t.fromArray(b.position),t.applyQuaternion(n), +c.position.add(t));c.updateMatrixWorld();if(!1===e.isPresenting)return a;q.near=a.near;k.near=a.near;q.far=a.far;k.far=a.far;u.matrixWorld.copy(a.matrixWorld);u.matrixWorldInverse.copy(a.matrixWorldInverse);q.matrixWorldInverse.fromArray(f.leftViewMatrix);k.matrixWorldInverse.fromArray(f.rightViewMatrix);m.getInverse(l);q.matrixWorldInverse.multiply(m);k.matrixWorldInverse.multiply(m);a=c.parent;null!==a&&(v.getInverse(a.matrixWorld),q.matrixWorldInverse.multiply(v),k.matrixWorldInverse.multiply(v)); +q.matrixWorld.getInverse(q.matrixWorldInverse);k.matrixWorld.getInverse(k.matrixWorldInverse);q.projectionMatrix.fromArray(f.leftProjectionMatrix);k.projectionMatrix.fromArray(f.rightProjectionMatrix);u.projectionMatrix.copy(q.projectionMatrix);a=e.getLayers();a.length&&(a=a[0],null!==a.leftBounds&&4===a.leftBounds.length&&q.bounds.fromArray(a.leftBounds),null!==a.rightBounds&&4===a.rightBounds.length&&k.bounds.fromArray(a.rightBounds));a:for(a=0;aa.matrixWorld.determinant();aa.setMaterial(e,h);h=n(c,b.fog,e,a);I="";g(a,h,e)}else U.renderBufferDirect(c,b.fog,d,e,a,f);a.onAfterRender(U,b,c,d,e,f);z=oa.get(b,Pb||c)}function v(a,b,c){var d=Aa.get(a),g=z.state.lights;c=na.getParameters(a,g.state,z.state.shadowsArray,b,X.numPlanes,X.numIntersection,c);var h=na.getProgramCode(a,c),l=d.program,m=!0;if(void 0===l)a.addEventListener("dispose",e);else if(l.code!==h)f(a);else{if(d.lightsHash!==g.state.hash)Aa.update(a,"lightsHash",g.state.hash);else if(void 0!== +c.shaderID)return;m=!1}m&&(c.shaderID?(l=tb[c.shaderID],d.shader={name:a.type,uniforms:Ba.clone(l.uniforms),vertexShader:l.vertexShader,fragmentShader:l.fragmentShader}):d.shader={name:a.type,uniforms:a.uniforms,vertexShader:a.vertexShader,fragmentShader:a.fragmentShader},a.onBeforeCompile(d.shader,U),l=na.acquireProgram(a,d.shader,c,h),d.program=l,a.program=l);c=l.getAttributes();if(a.morphTargets)for(h=a.numSupportedMorphTargets=0;he.matrixWorld.determinant();aa.setMaterial(d,g);var h=n(a,b,d,e);a=c.id+"_"+h.id+"_"+(!0===d.wireframe);var l=!1;a!==I&&(I=a,l=!0);e.morphTargetInfluences&&(va.update(e,c,d,h),l=!0);g=c.index;var m=c.attributes.position;b=1;!0===d.wireframe&&(g=qa.getWireframeAttribute(c),b=2);a=wa;if(null!==g){var v=ja.get(g);a=xa;a.setIndex(v)}if(l){if(c&&c.isInstancedBufferGeometry&&null===ma.get("ANGLE_instanced_arrays"))console.error("THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays."); +else{aa.initAttributes();l=c.attributes;h=h.getAttributes();var t=d.defaultAttributeValues;for(A in h){var q=h[A];if(0<=q){var k=l[A];if(void 0!==k){var r=k.normalized,u=k.itemSize,p=ja.get(k);if(void 0!==p){var y=p.buffer,x=p.type;p=p.bytesPerElement;if(k.isInterleavedBufferAttribute){var w=k.data,C=w.stride;k=k.offset;w&&w.isInstancedInterleavedBuffer?(aa.enableAttributeAndDivisor(q,w.meshPerAttribute),void 0===c.maxInstancedCount&&(c.maxInstancedCount=w.meshPerAttribute*w.count)):aa.enableAttribute(q); +F.bindBuffer(F.ARRAY_BUFFER,y);F.vertexAttribPointer(q,u,x,r,C*p,k*p)}else k.isInstancedBufferAttribute?(aa.enableAttributeAndDivisor(q,k.meshPerAttribute),void 0===c.maxInstancedCount&&(c.maxInstancedCount=k.meshPerAttribute*k.count)):aa.enableAttribute(q),F.bindBuffer(F.ARRAY_BUFFER,y),F.vertexAttribPointer(q,u,x,r,0,0)}}else if(void 0!==t&&(r=t[A],void 0!==r))switch(r.length){case 2:F.vertexAttrib2fv(q,r);break;case 3:F.vertexAttrib3fv(q,r);break;case 4:F.vertexAttrib4fv(q,r);break;default:F.vertexAttrib1fv(q, +r)}}}aa.disableUnusedAttributes()}null!==g&&F.bindBuffer(F.ELEMENT_ARRAY_BUFFER,v.buffer)}v=Infinity;null!==g?v=g.count:void 0!==m&&(v=m.count);g=c.drawRange.start*b;m=null!==f?f.start*b:0;var A=Math.max(g,m);f=Math.max(0,Math.min(v,g+c.drawRange.count*b,m+(null!==f?f.count*b:Infinity))-1-A+1);if(0!==f){if(e.isMesh)if(!0===d.wireframe)aa.setLineWidth(d.wireframeLinewidth*(null===D?V:1)),a.setMode(F.LINES);else switch(e.drawMode){case 0:a.setMode(F.TRIANGLES);break;case 1:a.setMode(F.TRIANGLE_STRIP); +break;case 2:a.setMode(F.TRIANGLE_FAN)}else e.isLine?(d=d.linewidth,void 0===d&&(d=1),aa.setLineWidth(d*(null===D?V:1)),e.isLineSegments?a.setMode(F.LINES):e.isLineLoop?a.setMode(F.LINE_LOOP):a.setMode(F.LINE_STRIP)):e.isPoints&&a.setMode(F.POINTS);c&&c.isInstancedBufferGeometry?0=Sa.maxTextures&&console.warn("THREE.WebGLRenderer: Trying to use "+ +a+" texture units while this GPU supports only "+Sa.maxTextures);$d+=1;return a};this.setTexture2D=function(){var a=!1;return function(b,c){b&&b.isWebGLRenderTarget&&(a||(console.warn("THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead."),a=!0),b=b.texture);Z.setTexture2D(b,c)}}();this.setTexture=function(){var a=!1;return function(b,c){a||(console.warn("THREE.WebGLRenderer: .setTexture is deprecated, use setTexture2D instead."),a=!0);Z.setTexture2D(b, +c)}}();this.setTextureCube=function(){var a=!1;return function(b,c){b&&b.isWebGLRenderTargetCube&&(a||(console.warn("THREE.WebGLRenderer.setTextureCube: don't use cube render targets as textures. Use their .texture property instead."),a=!0),b=b.texture);b&&b.isCubeTexture||Array.isArray(b.image)&&6===b.image.length?Z.setTextureCube(b,c):Z.setTextureCubeDynamic(b,c)}}();this.setFramebuffer=function(a){H=a};this.getRenderTarget=function(){return D};this.setRenderTarget=function(a){(D=a)&&void 0===Aa.get(a).__webglFramebuffer&& +Z.setupRenderTarget(a);var b=H,c=!1;a?(b=Aa.get(a).__webglFramebuffer,a.isWebGLRenderTargetCube&&(b=b[a.activeCubeFace],c=!0),db.copy(a.viewport),S.copy(a.scissor),T=a.scissorTest):(db.copy(N).multiplyScalar(V),S.copy(ba).multiplyScalar(V),T=ea);E!==b&&(F.bindFramebuffer(F.FRAMEBUFFER,b),E=b);aa.viewport(db);aa.scissor(S);aa.setScissorTest(T);c&&(c=Aa.get(a.texture),F.framebufferTexture2D(F.FRAMEBUFFER,F.COLOR_ATTACHMENT0,F.TEXTURE_CUBE_MAP_POSITIVE_X+a.activeCubeFace,c.__webglTexture,a.activeMipMapLevel))}; +this.readRenderTargetPixels=function(a,b,c,d,e,f){if(a&&a.isWebGLRenderTarget){var g=Aa.get(a).__webglFramebuffer;if(g){var h=!1;g!==E&&(F.bindFramebuffer(F.FRAMEBUFFER,g),h=!0);try{var l=a.texture,m=l.format,n=l.type;1023!==m&&ia.convert(m)!==F.getParameter(F.IMPLEMENTATION_COLOR_READ_FORMAT)?console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format."):1009===n||ia.convert(n)===F.getParameter(F.IMPLEMENTATION_COLOR_READ_TYPE)||1015=== +n&&(ma.get("OES_texture_float")||ma.get("WEBGL_color_buffer_float"))||1016===n&&ma.get("EXT_color_buffer_half_float")?F.checkFramebufferStatus(F.FRAMEBUFFER)===F.FRAMEBUFFER_COMPLETE?0<=b&&b<=a.width-d&&0<=c&&c<=a.height-e&&F.readPixels(b,c,d,e,ia.convert(m),ia.convert(n),f):console.error("THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete."):console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.")}finally{h&& +F.bindFramebuffer(F.FRAMEBUFFER,E)}}}else console.error("THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.")};this.copyFramebufferToTexture=function(a,b,c){var d=b.image.width,e=b.image.height,f=ia.convert(b.format);this.setTexture2D(b,0);F.copyTexImage2D(F.TEXTURE_2D,c||0,f,a.x,a.y,d,e,0)};this.copyTextureToTexture=function(a,b,c,d){var e=b.image.width,f=b.image.height,g=ia.convert(c.format),h=ia.convert(c.type);this.setTexture2D(c,0);b.isDataTexture?F.texSubImage2D(F.TEXTURE_2D, +d||0,a.x,a.y,e,f,g,h,b.image.data):F.texSubImage2D(F.TEXTURE_2D,d||0,a.x,a.y,g,h,b.image)}}function Vb(a,b){this.name="";this.color=new G(a);this.density=void 0!==b?b:2.5E-4}function Wb(a,b,c){this.name="";this.color=new G(a);this.near=void 0!==b?b:1;this.far=void 0!==c?c:1E3}function vd(){H.call(this);this.type="Scene";this.overrideMaterial=this.fog=this.background=null;this.autoUpdate=!0}function ib(a){I.call(this);this.type="SpriteMaterial";this.color=new G(16777215);this.map=null;this.rotation= +0;this.lights=this.fog=!1;this.setValues(a)}function Ic(a){H.call(this);this.type="Sprite";this.material=void 0!==a?a:new ib;this.center=new B(.5,.5)}function Jc(){H.call(this);this.type="LOD";Object.defineProperties(this,{levels:{enumerable:!0,value:[]}})}function Kc(a,b){a=a||[];this.bones=a.slice(0);this.boneMatrices=new Float32Array(16*this.bones.length);if(void 0===b)this.calculateInverses();else if(this.bones.length===b.length)this.boneInverses=b.slice(0);else for(console.warn("THREE.Skeleton boneInverses is the wrong length."), +this.boneInverses=[],a=0,b=this.bones.length;ac;c++){var n=v[h[c]];var t=v[h[(c+1)%3]];f[0]=Math.min(n,t);f[1]=Math.max(n,t);n=f[0]+","+f[1];void 0===g[n]&&(g[n]={index1:f[0],index2:f[1]})}}for(n in g)m=g[n],h=a.vertices[m.index1],b.push(h.x,h.y,h.z),h=a.vertices[m.index2],b.push(h.x,h.y,h.z)}else if(a&&a.isBufferGeometry)if(h=new p,null!==a.index){l=a.attributes.position;v=a.index;var q=a.groups;0===q.length&&(q=[{start:0,count:v.count,materialIndex:0}]);a=0;for(e=q.length;ac;c++)n=v.getX(m+c),t=v.getX(m+(c+1)%3),f[0]=Math.min(n,t),f[1]=Math.max(n,t),n=f[0]+","+f[1],void 0===g[n]&&(g[n]={index1:f[0],index2:f[1]});for(n in g)m=g[n],h.fromBufferAttribute(l,m.index1),b.push(h.x,h.y,h.z),h.fromBufferAttribute(l,m.index2),b.push(h.x,h.y,h.z)}else for(l=a.attributes.position,m=0,d=l.count/3;mc;c++)g=3*m+c,h.fromBufferAttribute(l,g),b.push(h.x,h.y,h.z),g=3*m+(c+1)%3,h.fromBufferAttribute(l,g),b.push(h.x, +h.y,h.z);this.addAttribute("position",new z(b,3))}function Mc(a,b,c){P.call(this);this.type="ParametricGeometry";this.parameters={func:a,slices:b,stacks:c};this.fromBufferGeometry(new $b(a,b,c));this.mergeVertices()}function $b(a,b,c){D.call(this);this.type="ParametricBufferGeometry";this.parameters={func:a,slices:b,stacks:c};var d=[],e=[],f=[],g=[],h=new p,l=new p,m=new p,v=new p,n=new p,t,q;3>a.length&&console.error("THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter."); +var k=b+1;for(t=0;t<=c;t++){var u=t/c;for(q=0;q<=b;q++){var y=q/b;a(y,u,l);e.push(l.x,l.y,l.z);0<=y-1E-5?(a(y-1E-5,u,m),v.subVectors(l,m)):(a(y+1E-5,u,m),v.subVectors(m,l));0<=u-1E-5?(a(y,u-1E-5,m),n.subVectors(l,m)):(a(y,u+1E-5,m),n.subVectors(m,l));h.crossVectors(v,n).normalize();f.push(h.x,h.y,h.z);g.push(y,u)}}for(t=0;td&&1===a.x&&(l[b]=a.x-1);0===c.x&&0===c.z&&(l[b]=d/2/Math.PI+.5)}D.call(this);this.type="PolyhedronBufferGeometry";this.parameters={vertices:a, +indices:b,radius:c,detail:d};c=c||1;d=d||0;var h=[],l=[];(function(a){for(var c=new p,d=new p,g=new p,h=0;he&&(.2>b&&(l[a+0]+=1),.2>c&&(l[a+2]+=1),.2>d&&(l[a+4]+=1))})();this.addAttribute("position",new z(h,3));this.addAttribute("normal",new z(h.slice(),3));this.addAttribute("uv",new z(l,2));0===d?this.computeVertexNormals():this.normalizeNormals()}function Oc(a, +b){P.call(this);this.type="TetrahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new ac(a,b));this.mergeVertices()}function ac(a,b){xa.call(this,[1,1,1,-1,-1,1,-1,1,-1,1,-1,-1],[2,1,0,0,3,2,1,3,0,2,3,1],a,b);this.type="TetrahedronBufferGeometry";this.parameters={radius:a,detail:b}}function Pc(a,b){P.call(this);this.type="OctahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new ub(a,b));this.mergeVertices()}function ub(a,b){xa.call(this,[1,0,0, +-1,0,0,0,1,0,0,-1,0,0,0,1,0,0,-1],[0,2,4,0,4,3,0,3,5,0,5,2,1,2,5,1,5,3,1,3,4,1,4,2],a,b);this.type="OctahedronBufferGeometry";this.parameters={radius:a,detail:b}}function Qc(a,b){P.call(this);this.type="IcosahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new bc(a,b));this.mergeVertices()}function bc(a,b){var c=(1+Math.sqrt(5))/2;xa.call(this,[-1,c,0,1,c,0,-1,-c,0,1,-c,0,0,-1,c,0,1,c,0,-1,-c,0,1,-c,c,0,-1,c,0,1,-c,0,-1,-c,0,1],[0,11,5,0,5,1,0,1,7,0,7,10,0,10,11,1,5,9,5, +11,4,11,10,2,10,7,6,7,1,8,3,9,4,3,4,2,3,2,6,3,6,8,3,8,9,4,9,5,2,4,11,6,2,10,8,6,7,9,8,1],a,b);this.type="IcosahedronBufferGeometry";this.parameters={radius:a,detail:b}}function Rc(a,b){P.call(this);this.type="DodecahedronGeometry";this.parameters={radius:a,detail:b};this.fromBufferGeometry(new cc(a,b));this.mergeVertices()}function cc(a,b){var c=(1+Math.sqrt(5))/2,d=1/c;xa.call(this,[-1,-1,-1,-1,-1,1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,0,-d,-c,0,-d,c,0,d,-c,0,d,c,-d,-c,0,-d,c,0,d,-c,0,d,c, +0,-c,0,-d,c,0,-d,-c,0,d,c,0,d],[3,11,7,3,7,15,3,15,13,7,19,17,7,17,6,7,6,15,17,4,8,17,8,10,17,10,6,8,0,16,8,16,2,8,2,10,0,12,1,0,1,18,0,18,16,6,10,2,6,2,13,6,13,15,2,16,18,2,18,3,2,3,13,18,1,9,18,9,11,18,11,3,4,14,12,4,12,0,4,0,8,11,9,5,11,5,19,11,19,7,19,5,14,19,14,4,19,4,17,1,12,14,1,14,5,1,5,9],a,b);this.type="DodecahedronBufferGeometry";this.parameters={radius:a,detail:b}}function Sc(a,b,c,d,e,f){P.call(this);this.type="TubeGeometry";this.parameters={path:a,tubularSegments:b,radius:c,radialSegments:d, +closed:e};void 0!==f&&console.warn("THREE.TubeGeometry: taper has been removed.");a=new dc(a,b,c,d,e);this.tangents=a.tangents;this.normals=a.normals;this.binormals=a.binormals;this.fromBufferGeometry(a);this.mergeVertices()}function dc(a,b,c,d,e){function f(e){v=a.getPointAt(e/b,v);var f=g.normals[e];e=g.binormals[e];for(k=0;k<=d;k++){var m=k/d*Math.PI*2,n=Math.sin(m);m=-Math.cos(m);l.x=m*f.x+n*e.x;l.y=m*f.y+n*e.y;l.z=m*f.z+n*e.z;l.normalize();r.push(l.x,l.y,l.z);h.x=v.x+c*l.x;h.y=v.y+c*l.y;h.z= +v.z+c*l.z;q.push(h.x,h.y,h.z)}}D.call(this);this.type="TubeBufferGeometry";this.parameters={path:a,tubularSegments:b,radius:c,radialSegments:d,closed:e};b=b||64;c=c||1;d=d||8;e=e||!1;var g=a.computeFrenetFrames(b,e);this.tangents=g.tangents;this.normals=g.normals;this.binormals=g.binormals;var h=new p,l=new p,m=new B,v=new p,n,k,q=[],r=[],u=[],y=[];for(n=0;n=b;e-=d)f=df(e,a[e],a[e+1],f);f&&vb(f,f.next)&&(Vc(f),f=f.next);return f}function Wc(a,b){if(!a)return a;b||(b=a);do{var c=!1;if(a.steiner||!vb(a,a.next)&&0!==oa(a.prev,a,a.next))a=a.next;else{Vc(a);a=b=a.prev;if(a===a.next)break;c=!0}}while(c||a!==b);return b} +function Xc(a,b,c,d,e,f,g){if(a){if(!g&&f){var h=a,l=h;do null===l.z&&(l.z=ce(l.x,l.y,d,e,f)),l.prevZ=l.prev,l=l.nextZ=l.next;while(l!==h);l.prevZ.nextZ=null;l.prevZ=null;h=l;var m,v,n,k,q=1;do{l=h;var r=h=null;for(v=0;l;){v++;var p=l;for(m=n=0;mn.x?v.x>q.x?v.x:q.x:n.x>q.x?n.x:q.x,A=v.y>n.y?v.y>q.y?v.y:q.y:n.y>q.y?n.y:q.y;m=ce(v.x=m;){if(y!==r.prev&&y!==r.next&&zd(v.x,v.y,n.x,n.y,q.x,q.y,y.x,y.y)&&0<=oa(y.prev,y,y.next)){r=!1;break a}y= +y.prevZ}r=!0}}else a:if(r=a,v=r.prev,n=r,q=r.next,0<=oa(v,n,q))r=!1;else{for(m=r.next.next;m!==r.prev;){if(zd(v.x,v.y,n.x,n.y,q.x,q.y,m.x,m.y)&&0<=oa(m.prev,m,m.next)){r=!1;break a}m=m.next}r=!0}if(r)b.push(l.i/c),b.push(a.i/c),b.push(p.i/c),Vc(a),h=a=p.next;else if(a=p,a===h){if(!g)Xc(Wc(a),b,c,d,e,f,1);else if(1===g){g=b;h=c;l=a;do p=l.prev,r=l.next.next,!vb(p,r)&&ef(p,l,l.next,r)&&Yc(p,r)&&Yc(r,p)&&(g.push(p.i/h),g.push(l.i/h),g.push(r.i/h),Vc(l),Vc(l.next),l=a=r),l=l.next;while(l!==a);a=l;Xc(a, +b,c,d,e,f,2)}else if(2===g)a:{g=a;do{for(h=g.next.next;h!==g.prev;){if(l=g.i!==h.i){l=g;p=h;if(r=l.next.i!==p.i&&l.prev.i!==p.i){b:{r=l;do{if(r.i!==l.i&&r.next.i!==l.i&&r.i!==p.i&&r.next.i!==p.i&&ef(r,r.next,l,p)){r=!0;break b}r=r.next}while(r!==l);r=!1}r=!r}if(r=r&&Yc(l,p)&&Yc(p,l)){r=l;v=!1;n=(l.x+p.x)/2;p=(l.y+p.y)/2;do r.y>p!==r.next.y>p&&r.next.y!==r.y&&n<(r.next.x-r.x)*(p-r.y)/(r.next.y-r.y)+r.x&&(v=!v),r=r.next;while(r!==l);r=v}l=r}if(l){a=ff(g,h);g=Wc(g,g.next);a=Wc(a,a.next);Xc(g,b,c,d,e, +f);Xc(a,b,c,d,e,f);break a}h=h.next}g=g.next}while(g!==a)}break}}}}function Pg(a,b){return a.x-b.x}function Qg(a,b){var c=b,d=a.x,e=a.y,f=-Infinity;do{if(e<=c.y&&e>=c.next.y&&c.next.y!==c.y){var g=c.x+(e-c.y)*(c.next.x-c.x)/(c.next.y-c.y);if(g<=d&&g>f){f=g;if(g===d){if(e===c.y)return c;if(e===c.next.y)return c.next}var h=c.x=c.x&&c.x>=g&&d!==c.x&&zd(eh.x)&&Yc(c,a)&&(h=c,m=v)}c=c.next}return h}function ce(a,b,c,d,e){a=32767*(a-c)*e;b=32767*(b-d)*e;a=(a|a<<8)&16711935;a=(a|a<<4)&252645135;a=(a|a<<2)&858993459;b=(b|b<<8)&16711935;b=(b|b<<4)&252645135;b=(b|b<<2)&858993459;return(a|a<<1)&1431655765|((b|b<<1)&1431655765)<<1}function Rg(a){var b=a,c=a;do b.xoa(a.prev,a,a.next)?0<=oa(a,b,a.next)&&0<=oa(a,a.prev,b):0>oa(a,b,a.prev)||0>oa(a,a.next,b)}function ff(a,b){var c=new de(a.i,a.x,a.y),d=new de(b.i,b.x,b.y),e=a.next,f=b.prev;a.next=b;b.prev=a;c.next=e;e.prev= +c;d.next=c;c.prev=d;f.next=d;d.prev=f;return d}function df(a,b,c,d){a=new de(a,b,c);d?(a.next=d.next,a.prev=d,d.next.prev=a,d.next=a):(a.prev=a,a.next=a);return a}function Vc(a){a.next.prev=a.prev;a.prev.next=a.next;a.prevZ&&(a.prevZ.nextZ=a.nextZ);a.nextZ&&(a.nextZ.prevZ=a.prevZ)}function de(a,b,c){this.i=a;this.x=b;this.y=c;this.nextZ=this.prevZ=this.z=this.next=this.prev=null;this.steiner=!1}function gf(a){var b=a.length;2Number.EPSILON){var l=Math.sqrt(h),m=Math.sqrt(f*f+g*g);h=b.x-e/l;b=b.y+d/l; +g=((c.x-g/m-h)*g-(c.y+f/m-b)*f)/(d*g-e*f);f=h+d*g-a.x;d=b+e*g-a.y;e=f*f+d*d;if(2>=e)return new B(f,d);e=Math.sqrt(e/2)}else a=!1,d>Number.EPSILON?f>Number.EPSILON&&(a=!0):d<-Number.EPSILON?f<-Number.EPSILON&&(a=!0):Math.sign(e)===Math.sign(g)&&(a=!0),a?(f=-e,e=Math.sqrt(h)):(f=d,d=e,e=Math.sqrt(h/2));return new B(f/e,d/e)}function h(a,b){for(N=a.length;0<=--N;){var c=N;var f=N-1;0>f&&(f=a.length-1);var g,h=x+2*M;for(g=0;gk;k++){var n=m[f[k]];var t=m[f[(k+1)%3]];d[0]=Math.min(n,t);d[1]=Math.max(n,t);n=d[0]+","+d[1];void 0===e[n]?e[n]={index1:d[0],index2:d[1],face1:h,face2:void 0}:e[n].face2=h}for(n in e)if(d=e[n],void 0===d.face2||g[d.face1].normal.dot(g[d.face2].normal)<=b)f=a[d.index1], +c.push(f.x,f.y,f.z),f=a[d.index2],c.push(f.x,f.y,f.z);this.addAttribute("position",new z(c,3))}function Ab(a,b,c,d,e,f,g,h){P.call(this);this.type="CylinderGeometry";this.parameters={radiusTop:a,radiusBottom:b,height:c,radialSegments:d,heightSegments:e,openEnded:f,thetaStart:g,thetaLength:h};this.fromBufferGeometry(new ab(a,b,c,d,e,f,g,h));this.mergeVertices()}function ab(a,b,c,d,e,f,g,h){function l(c){var e,f=new B,l=new p,v=0,u=!0===c?a:b,x=!0===c?1:-1;var z=r;for(e=1;e<=d;e++)n.push(0,y*x,0),t.push(0, +x,0),q.push(.5,.5),r++;var G=r;for(e=0;e<=d;e++){var H=e/d*h+g,D=Math.cos(H);H=Math.sin(H);l.x=u*H;l.y=y*x;l.z=u*D;n.push(l.x,l.y,l.z);t.push(0,x,0);f.x=.5*D+.5;f.y=.5*H*x+.5;q.push(f.x,f.y);r++}for(e=0;ethis.duration&&this.resetDuration();this.optimize()}function Pd(a){this.manager=void 0!==a?a:ya;this.textures={}}function ie(a){this.manager=void 0!==a?a: +ya}function pc(){}function je(a){"boolean"===typeof a&&(console.warn("THREE.JSONLoader: showStatus parameter has been removed from constructor."),a=void 0);this.manager=void 0!==a?a:ya;this.withCredentials=!1}function nf(a){this.manager=void 0!==a?a:ya;this.texturePath=""}function ke(a){"undefined"===typeof createImageBitmap&&console.warn("THREE.ImageBitmapLoader: createImageBitmap() not supported.");"undefined"===typeof fetch&&console.warn("THREE.ImageBitmapLoader: fetch() not supported.");this.manager= +void 0!==a?a:ya;this.options=void 0}function le(){this.type="ShapePath";this.color=new G;this.subPaths=[];this.currentPath=null}function me(a){this.type="Font";this.data=a}function of(a){this.manager=void 0!==a?a:ya}function ne(a){this.manager=void 0!==a?a:ya}function pf(){this.type="StereoCamera";this.aspect=1;this.eyeSep=.064;this.cameraL=new Z;this.cameraL.layers.enable(1);this.cameraL.matrixAutoUpdate=!1;this.cameraR=new Z;this.cameraR.layers.enable(2);this.cameraR.matrixAutoUpdate=!1}function kd(a, +b,c){H.call(this);this.type="CubeCamera";var d=new Z(90,1,a,b);d.up.set(0,-1,0);d.lookAt(new p(1,0,0));this.add(d);var e=new Z(90,1,a,b);e.up.set(0,-1,0);e.lookAt(new p(-1,0,0));this.add(e);var f=new Z(90,1,a,b);f.up.set(0,0,1);f.lookAt(new p(0,1,0));this.add(f);var g=new Z(90,1,a,b);g.up.set(0,0,-1);g.lookAt(new p(0,-1,0));this.add(g);var h=new Z(90,1,a,b);h.up.set(0,-1,0);h.lookAt(new p(0,0,1));this.add(h);var l=new Z(90,1,a,b);l.up.set(0,-1,0);l.lookAt(new p(0,0,-1));this.add(l);this.renderTarget= +new Lb(c,c,{format:1022,magFilter:1006,minFilter:1006});this.renderTarget.texture.name="CubeCamera";this.update=function(a,b){null===this.parent&&this.updateMatrixWorld();var c=this.renderTarget,m=c.texture.generateMipmaps;c.texture.generateMipmaps=!1;c.activeCubeFace=0;a.render(b,d,c);c.activeCubeFace=1;a.render(b,e,c);c.activeCubeFace=2;a.render(b,f,c);c.activeCubeFace=3;a.render(b,g,c);c.activeCubeFace=4;a.render(b,h,c);c.texture.generateMipmaps=m;c.activeCubeFace=5;a.render(b,l,c);a.setRenderTarget(null)}; +this.clear=function(a,b,c,d){for(var e=this.renderTarget,f=0;6>f;f++)e.activeCubeFace=f,a.setRenderTarget(e),a.clear(b,c,d);a.setRenderTarget(null)}}function oe(){H.call(this);this.type="AudioListener";this.context=pe.getContext();this.gain=this.context.createGain();this.gain.connect(this.context.destination);this.filter=null}function qc(a){H.call(this);this.type="Audio";this.context=a.context;this.gain=this.context.createGain();this.gain.connect(a.getInput());this.autoplay=!1;this.buffer=null;this.loop= +!1;this.offset=this.startTime=0;this.playbackRate=1;this.isPlaying=!1;this.hasPlaybackControl=!0;this.sourceType="empty";this.filters=[]}function qe(a){qc.call(this,a);this.panner=this.context.createPanner();this.panner.connect(this.gain)}function re(a,b){this.analyser=a.context.createAnalyser();this.analyser.fftSize=void 0!==b?b:2048;this.data=new Uint8Array(this.analyser.frequencyBinCount);a.getOutput().connect(this.analyser)}function se(a,b,c){this.binding=a;this.valueSize=c;a=Float64Array;switch(b){case "quaternion":b= +this._slerp;break;case "string":case "bool":a=Array;b=this._select;break;default:b=this._lerp}this.buffer=new a(4*c);this._mixBufferRegion=b;this.referenceCount=this.useCount=this.cumulativeWeight=0}function qf(a,b,c){c=c||ra.parseTrackName(b);this._targetGroup=a;this._bindings=a.subscribe_(b,c)}function ra(a,b,c){this.path=b;this.parsedPath=c||ra.parseTrackName(b);this.node=ra.findNode(a,this.parsedPath.nodeName)||a;this.rootNode=a}function rf(){this.uuid=J.generateUUID();this._objects=Array.prototype.slice.call(arguments); +this.nCachedObjects_=0;var a={};this._indicesByUUID=a;for(var b=0,c=arguments.length;b!==c;++b)a[arguments[b].uuid]=b;this._paths=[];this._parsedPaths=[];this._bindings=[];this._bindingsIndicesByPath={};var d=this;this.stats={objects:{get total(){return d._objects.length},get inUse(){return this.total-d.nCachedObjects_}},get bindingsPerObject(){return d._bindings.length}}}function sf(a,b,c){this._mixer=a;this._clip=b;this._localRoot=c||null;a=b.tracks;b=a.length;c=Array(b);for(var d={endingStart:2400, +endingEnd:2400},e=0;e!==b;++e){var f=a[e].createInterpolant(null);c[e]=f;f.settings=d}this._interpolantSettings=d;this._interpolants=c;this._propertyBindings=Array(b);this._weightInterpolant=this._timeScaleInterpolant=this._byClipCacheIndex=this._cacheIndex=null;this.loop=2201;this._loopCount=-1;this._startTime=null;this.time=0;this._effectiveWeight=this.weight=this._effectiveTimeScale=this.timeScale=1;this.repetitions=Infinity;this.paused=!1;this.enabled=!0;this.clampWhenFinished=!1;this.zeroSlopeAtEnd= +this.zeroSlopeAtStart=!0}function te(a){this._root=a;this._initMemoryManager();this.time=this._accuIndex=0;this.timeScale=1}function Qd(a,b){"string"===typeof a&&(console.warn("THREE.Uniform: Type parameter is no longer needed."),a=b);this.value=a}function ue(){D.call(this);this.type="InstancedBufferGeometry";this.maxInstancedCount=void 0}function ve(a,b,c,d){this.data=a;this.itemSize=b;this.offset=c;this.normalized=!0===d}function rc(a,b){this.array=a;this.stride=b;this.count=void 0!==a?a.length/ +b:0;this.dynamic=!1;this.updateRange={offset:0,count:-1};this.version=0}function we(a,b,c){rc.call(this,a,b);this.meshPerAttribute=c||1}function xe(a,b,c){L.call(this,a,b);this.meshPerAttribute=c||1}function tf(a,b,c,d){this.ray=new sb(a,b);this.near=c||0;this.far=d||Infinity;this.params={Mesh:{},Line:{},LOD:{},Points:{threshold:1},Sprite:{}};Object.defineProperties(this.params,{PointCloud:{get:function(){console.warn("THREE.Raycaster: params.PointCloud has been renamed to params.Points.");return this.Points}}})} +function uf(a,b){return a.distance-b.distance}function ye(a,b,c,d){if(!1!==a.visible&&(a.raycast(b,c),!0===d)){a=a.children;d=0;for(var e=a.length;dc;c++,d++){var e=c/32*Math.PI*2,f=d/32*Math.PI*2;b.push(Math.cos(e),Math.sin(e),1,Math.cos(f),Math.sin(f),1)}a.addAttribute("position",new z(b,3));b=new T({fog:!1});this.cone=new Y(a,b);this.add(this.cone);this.update()}function yf(a){var b=[];a&&a.isBone&& +b.push(a);for(var c=0;ca?-1:0b;b++)a[b]=(16>b?"0":"")+b.toString(16);return function(){var b=4294967295*Math.random()|0,d=4294967295*Math.random()|0,e=4294967295* +Math.random()|0,f=4294967295*Math.random()|0;return(a[b&255]+a[b>>8&255]+a[b>>16&255]+a[b>>24&255]+"-"+a[d&255]+a[d>>8&255]+"-"+a[d>>16&15|64]+a[d>>24&255]+"-"+a[e&63|128]+a[e>>8&255]+"-"+a[e>>16&255]+a[e>>24&255]+a[f&255]+a[f>>8&255]+a[f>>16&255]+a[f>>24&255]).toUpperCase()}}(),clamp:function(a,b,c){return Math.max(b,Math.min(c,a))},euclideanModulo:function(a,b){return(a%b+b)%b},mapLinear:function(a,b,c,d,e){return d+(a-b)*(e-d)/(c-b)},lerp:function(a,b,c){return(1-c)*a+c*b},smoothstep:function(a, +b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a,b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(.5-Math.random())},degToRad:function(a){return a*J.DEG2RAD},radToDeg:function(a){return a*J.RAD2DEG},isPowerOfTwo:function(a){return 0===(a&a-1)&&0!==a},ceilPowerOfTwo:function(a){return Math.pow(2, +Math.ceil(Math.log(a)/Math.LN2))},floorPowerOfTwo:function(a){return Math.pow(2,Math.floor(Math.log(a)/Math.LN2))}};Object.defineProperties(B.prototype,{width:{get:function(){return this.x},set:function(a){this.x=a}},height:{get:function(){return this.y},set:function(a){this.y=a}}});Object.assign(B.prototype,{isVector2:!0,set:function(a,b){this.x=a;this.y=b;return this},setScalar:function(a){this.y=this.x=a;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this}, +setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error("index is out of range: "+a);}return this},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error("index is out of range: "+a);}},clone:function(){return new this.constructor(this.x,this.y)},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead."), +this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScaledVector:function(a,b){this.x+=a.x*b;this.y+=a.y*b;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;return this},subScalar:function(a){this.x-=a;this.y-=a;return this}, +subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiply:function(a){this.x*=a.x;this.y*=a.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divide:function(a){this.x/=a.x;this.y/=a.y;return this},divideScalar:function(a){return this.multiplyScalar(1/a)},applyMatrix3:function(a){var b=this.x,c=this.y;a=a.elements;this.x=a[0]*b+a[3]*c+a[6];this.y=a[1]*b+a[4]*c+a[7];return this},min:function(a){this.x=Math.min(this.x,a.x);this.y=Math.min(this.y,a.y);return this}, +max:function(a){this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);return this},clamp:function(a,b){this.x=Math.max(a.x,Math.min(b.x,this.x));this.y=Math.max(a.y,Math.min(b.y,this.y));return this},clampScalar:function(){var a=new B,b=new B;return function(c,d){a.set(c,c);b.set(d,d);return this.clamp(a,b)}}(),clampLength:function(a,b){var c=this.length();return this.divideScalar(c||1).multiplyScalar(Math.max(a,Math.min(b,c)))},floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y); +return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);return this},negate:function(){this.x=-this.x;this.y=-this.y;return this},dot:function(a){return this.x*a.x+this.y*a.y},lengthSq:function(){return this.x*this.x+this.y*this.y},length:function(){return Math.sqrt(this.x* +this.x+this.y*this.y)},manhattanLength:function(){return Math.abs(this.x)+Math.abs(this.y)},normalize:function(){return this.divideScalar(this.length()||1)},angle:function(){var a=Math.atan2(this.y,this.x);0>a&&(a+=2*Math.PI);return a},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x;a=this.y-a.y;return b*b+a*a},manhattanDistanceTo:function(a){return Math.abs(this.x-a.x)+Math.abs(this.y-a.y)},setLength:function(a){return this.normalize().multiplyScalar(a)}, +lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;return this},lerpVectors:function(a,b,c){return this.subVectors(b,a).multiplyScalar(c).add(a)},equals:function(a){return a.x===this.x&&a.y===this.y},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;return a},fromBufferAttribute:function(a,b,c){void 0!==c&&console.warn("THREE.Vector2: offset has been removed from .fromBufferAttribute()."); +this.x=a.getX(b);this.y=a.getY(b);return this},rotateAround:function(a,b){var c=Math.cos(b);b=Math.sin(b);var d=this.x-a.x,e=this.y-a.y;this.x=d*c-e*b+a.x;this.y=d*b+e*c+a.y;return this}});Object.assign(R.prototype,{isMatrix4:!0,set:function(a,b,c,d,e,f,g,h,l,m,k,n,t,q,r,p){var v=this.elements;v[0]=a;v[4]=b;v[8]=c;v[12]=d;v[1]=e;v[5]=f;v[9]=g;v[13]=h;v[2]=l;v[6]=m;v[10]=k;v[14]=n;v[3]=t;v[7]=q;v[11]=r;v[15]=p;return this},identity:function(){this.set(1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1);return this}, +clone:function(){return(new R).fromArray(this.elements)},copy:function(a){var b=this.elements;a=a.elements;b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return this},copyPosition:function(a){var b=this.elements;a=a.elements;b[12]=a[12];b[13]=a[13];b[14]=a[14];return this},extractBasis:function(a,b,c){a.setFromMatrixColumn(this,0);b.setFromMatrixColumn(this,1);c.setFromMatrixColumn(this, +2);return this},makeBasis:function(a,b,c){this.set(a.x,b.x,c.x,0,a.y,b.y,c.y,0,a.z,b.z,c.z,0,0,0,0,1);return this},extractRotation:function(){var a=new p;return function(b){var c=this.elements,d=b.elements,e=1/a.setFromMatrixColumn(b,0).length(),f=1/a.setFromMatrixColumn(b,1).length();b=1/a.setFromMatrixColumn(b,2).length();c[0]=d[0]*e;c[1]=d[1]*e;c[2]=d[2]*e;c[3]=0;c[4]=d[4]*f;c[5]=d[5]*f;c[6]=d[6]*f;c[7]=0;c[8]=d[8]*b;c[9]=d[9]*b;c[10]=d[10]*b;c[11]=0;c[12]=0;c[13]=0;c[14]=0;c[15]=1;return this}}(), +makeRotationFromEuler:function(a){a&&a.isEuler||console.error("THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.");var b=this.elements,c=a.x,d=a.y,e=a.z,f=Math.cos(c);c=Math.sin(c);var g=Math.cos(d);d=Math.sin(d);var h=Math.cos(e);e=Math.sin(e);if("XYZ"===a.order){a=f*h;var l=f*e,m=c*h,k=c*e;b[0]=g*h;b[4]=-g*e;b[8]=d;b[1]=l+m*d;b[5]=a-k*d;b[9]=-c*g;b[2]=k-a*d;b[6]=m+l*d;b[10]=f*g}else"YXZ"===a.order?(a=g*h,l=g*e,m=d*h,k=d*e,b[0]=a+k*c,b[4]=m*c-l, +b[8]=f*d,b[1]=f*e,b[5]=f*h,b[9]=-c,b[2]=l*c-m,b[6]=k+a*c,b[10]=f*g):"ZXY"===a.order?(a=g*h,l=g*e,m=d*h,k=d*e,b[0]=a-k*c,b[4]=-f*e,b[8]=m+l*c,b[1]=l+m*c,b[5]=f*h,b[9]=k-a*c,b[2]=-f*d,b[6]=c,b[10]=f*g):"ZYX"===a.order?(a=f*h,l=f*e,m=c*h,k=c*e,b[0]=g*h,b[4]=m*d-l,b[8]=a*d+k,b[1]=g*e,b[5]=k*d+a,b[9]=l*d-m,b[2]=-d,b[6]=c*g,b[10]=f*g):"YZX"===a.order?(a=f*g,l=f*d,m=c*g,k=c*d,b[0]=g*h,b[4]=k-a*e,b[8]=m*e+l,b[1]=e,b[5]=f*h,b[9]=-c*h,b[2]=-d*h,b[6]=l*e+m,b[10]=a-k*e):"XZY"===a.order&&(a=f*g,l=f*d,m=c*g,k= +c*d,b[0]=g*h,b[4]=-e,b[8]=d*h,b[1]=a*e+k,b[5]=f*h,b[9]=l*e-m,b[2]=m*e-l,b[6]=c*h,b[10]=k*e+a);b[3]=0;b[7]=0;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return this},makeRotationFromQuaternion:function(){var a=new p(0,0,0),b=new p(1,1,1);return function(c){return this.compose(a,c,b)}}(),lookAt:function(){var a=new p,b=new p,c=new p;return function(d,e,f){var g=this.elements;c.subVectors(d,e);0===c.lengthSq()&&(c.z=1);c.normalize();a.crossVectors(f,c);0===a.lengthSq()&&(1===Math.abs(f.z)?c.x+=1E-4:c.z+= +1E-4,c.normalize(),a.crossVectors(f,c));a.normalize();b.crossVectors(c,a);g[0]=a.x;g[4]=b.x;g[8]=c.x;g[1]=a.y;g[5]=b.y;g[9]=c.y;g[2]=a.z;g[6]=b.z;g[10]=c.z;return this}}(),multiply:function(a,b){return void 0!==b?(console.warn("THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(a,b)):this.multiplyMatrices(this,a)},premultiply:function(a){return this.multiplyMatrices(a,this)},multiplyMatrices:function(a,b){var c=a.elements,d=b.elements; +b=this.elements;a=c[0];var e=c[4],f=c[8],g=c[12],h=c[1],l=c[5],m=c[9],k=c[13],n=c[2],t=c[6],q=c[10],r=c[14],p=c[3],y=c[7],w=c[11];c=c[15];var x=d[0],A=d[4],C=d[8],O=d[12],Q=d[1],z=d[5],B=d[9],H=d[13],G=d[2],D=d[6],E=d[10],J=d[14],L=d[3],I=d[7],K=d[11];d=d[15];b[0]=a*x+e*Q+f*G+g*L;b[4]=a*A+e*z+f*D+g*I;b[8]=a*C+e*B+f*E+g*K;b[12]=a*O+e*H+f*J+g*d;b[1]=h*x+l*Q+m*G+k*L;b[5]=h*A+l*z+m*D+k*I;b[9]=h*C+l*B+m*E+k*K;b[13]=h*O+l*H+m*J+k*d;b[2]=n*x+t*Q+q*G+r*L;b[6]=n*A+t*z+q*D+r*I;b[10]=n*C+t*B+q*E+r*K;b[14]=n* +O+t*H+q*J+r*d;b[3]=p*x+y*Q+w*G+c*L;b[7]=p*A+y*z+w*D+c*I;b[11]=p*C+y*B+w*E+c*K;b[15]=p*O+y*H+w*J+c*d;return this},multiplyScalar:function(a){var b=this.elements;b[0]*=a;b[4]*=a;b[8]*=a;b[12]*=a;b[1]*=a;b[5]*=a;b[9]*=a;b[13]*=a;b[2]*=a;b[6]*=a;b[10]*=a;b[14]*=a;b[3]*=a;b[7]*=a;b[11]*=a;b[15]*=a;return this},applyToBufferAttribute:function(){var a=new p;return function(b){for(var c=0,d=b.count;cthis.determinant()&&(g=-g);c.x=f[12];c.y=f[13];c.z=f[14];b.copy(this);c=1/g;f=1/h;var m=1/l;b.elements[0]*=c; +b.elements[1]*=c;b.elements[2]*=c;b.elements[4]*=f;b.elements[5]*=f;b.elements[6]*=f;b.elements[8]*=m;b.elements[9]*=m;b.elements[10]*=m;d.setFromRotationMatrix(b);e.x=g;e.y=h;e.z=l;return this}}(),makePerspective:function(a,b,c,d,e,f){void 0===f&&console.warn("THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.");var g=this.elements;g[0]=2*e/(b-a);g[4]=0;g[8]=(b+a)/(b-a);g[12]=0;g[1]=0;g[5]=2*e/(c-d);g[9]=(c+d)/(c-d);g[13]=0;g[2]=0;g[6]=0;g[10]=-(f+ +e)/(f-e);g[14]=-2*f*e/(f-e);g[3]=0;g[7]=0;g[11]=-1;g[15]=0;return this},makeOrthographic:function(a,b,c,d,e,f){var g=this.elements,h=1/(b-a),l=1/(c-d),m=1/(f-e);g[0]=2*h;g[4]=0;g[8]=0;g[12]=-((b+a)*h);g[1]=0;g[5]=2*l;g[9]=0;g[13]=-((c+d)*l);g[2]=0;g[6]=0;g[10]=-2*m;g[14]=-((f+e)*m);g[3]=0;g[7]=0;g[11]=0;g[15]=1;return this},equals:function(a){var b=this.elements;a=a.elements;for(var c=0;16>c;c++)if(b[c]!==a[c])return!1;return!0},fromArray:function(a,b){void 0===b&&(b=0);for(var c=0;16>c;c++)this.elements[c]= +a[c+b];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);var c=this.elements;a[b]=c[0];a[b+1]=c[1];a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];a[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];a[b+9]=c[9];a[b+10]=c[10];a[b+11]=c[11];a[b+12]=c[12];a[b+13]=c[13];a[b+14]=c[14];a[b+15]=c[15];return a}});Object.assign(ca,{slerp:function(a,b,c,d){return c.copy(a).slerp(b,d)},slerpFlat:function(a,b,c,d,e,f,g){var h=c[d+0],l=c[d+1],m=c[d+2];c=c[d+3];d=e[f+0];var k=e[f+1],n=e[f+2];e=e[f+3];if(c!== +e||h!==d||l!==k||m!==n){f=1-g;var t=h*d+l*k+m*n+c*e,q=0<=t?1:-1,p=1-t*t;p>Number.EPSILON&&(p=Math.sqrt(p),t=Math.atan2(p,t*q),f=Math.sin(f*t)/p,g=Math.sin(g*t)/p);q*=g;h=h*f+d*q;l=l*f+k*q;m=m*f+n*q;c=c*f+e*q;f===1-g&&(g=1/Math.sqrt(h*h+l*l+m*m+c*c),h*=g,l*=g,m*=g,c*=g)}a[b]=h;a[b+1]=l;a[b+2]=m;a[b+3]=c}});Object.defineProperties(ca.prototype,{x:{get:function(){return this._x},set:function(a){this._x=a;this.onChangeCallback()}},y:{get:function(){return this._y},set:function(a){this._y=a;this.onChangeCallback()}}, +z:{get:function(){return this._z},set:function(a){this._z=a;this.onChangeCallback()}},w:{get:function(){return this._w},set:function(a){this._w=a;this.onChangeCallback()}}});Object.assign(ca.prototype,{set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._w=d;this.onChangeCallback();return this},clone:function(){return new this.constructor(this._x,this._y,this._z,this._w)},copy:function(a){this._x=a.x;this._y=a.y;this._z=a.z;this._w=a.w;this.onChangeCallback();return this},setFromEuler:function(a, +b){if(!a||!a.isEuler)throw Error("THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.");var c=a._x,d=a._y,e=a._z;a=a.order;var f=Math.cos,g=Math.sin,h=f(c/2),l=f(d/2);f=f(e/2);c=g(c/2);d=g(d/2);e=g(e/2);"XYZ"===a?(this._x=c*l*f+h*d*e,this._y=h*d*f-c*l*e,this._z=h*l*e+c*d*f,this._w=h*l*f-c*d*e):"YXZ"===a?(this._x=c*l*f+h*d*e,this._y=h*d*f-c*l*e,this._z=h*l*e-c*d*f,this._w=h*l*f+c*d*e):"ZXY"===a?(this._x=c*l*f-h*d*e,this._y=h*d*f+c*l*e,this._z=h*l*e+c*d* +f,this._w=h*l*f-c*d*e):"ZYX"===a?(this._x=c*l*f-h*d*e,this._y=h*d*f+c*l*e,this._z=h*l*e-c*d*f,this._w=h*l*f+c*d*e):"YZX"===a?(this._x=c*l*f+h*d*e,this._y=h*d*f+c*l*e,this._z=h*l*e-c*d*f,this._w=h*l*f-c*d*e):"XZY"===a&&(this._x=c*l*f-h*d*e,this._y=h*d*f-c*l*e,this._z=h*l*e+c*d*f,this._w=h*l*f+c*d*e);if(!1!==b)this.onChangeCallback();return this},setFromAxisAngle:function(a,b){b/=2;var c=Math.sin(b);this._x=a.x*c;this._y=a.y*c;this._z=a.z*c;this._w=Math.cos(b);this.onChangeCallback();return this},setFromRotationMatrix:function(a){var b= +a.elements,c=b[0];a=b[4];var d=b[8],e=b[1],f=b[5],g=b[9],h=b[2],l=b[6];b=b[10];var m=c+f+b;0f&&c>b?(c=2*Math.sqrt(1+c-f-b),this._w=(l-g)/c,this._x=.25*c,this._y=(a+e)/c,this._z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this._w=(d-h)/c,this._x=(a+e)/c,this._y=.25*c,this._z=(g+l)/c):(c=2*Math.sqrt(1+b-c-f),this._w=(e-a)/c,this._x=(d+h)/c,this._y=(g+l)/c,this._z=.25*c);this.onChangeCallback();return this},setFromUnitVectors:function(){var a= +new p,b;return function(c,d){void 0===a&&(a=new p);b=c.dot(d)+1;1E-6>b?(b=0,Math.abs(c.x)>Math.abs(c.z)?a.set(-c.y,c.x,0):a.set(0,-c.z,c.y)):a.crossVectors(c,d);this._x=a.x;this._y=a.y;this._z=a.z;this._w=b;return this.normalize()}}(),inverse:function(){return this.conjugate()},conjugate:function(){this._x*=-1;this._y*=-1;this._z*=-1;this.onChangeCallback();return this},dot:function(a){return this._x*a._x+this._y*a._y+this._z*a._z+this._w*a._w},lengthSq:function(){return this._x*this._x+this._y*this._y+ +this._z*this._z+this._w*this._w},length:function(){return Math.sqrt(this._x*this._x+this._y*this._y+this._z*this._z+this._w*this._w)},normalize:function(){var a=this.length();0===a?(this._z=this._y=this._x=0,this._w=1):(a=1/a,this._x*=a,this._y*=a,this._z*=a,this._w*=a);this.onChangeCallback();return this},multiply:function(a,b){return void 0!==b?(console.warn("THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this, +a)},premultiply:function(a){return this.multiplyQuaternions(a,this)},multiplyQuaternions:function(a,b){var c=a._x,d=a._y,e=a._z;a=a._w;var f=b._x,g=b._y,h=b._z;b=b._w;this._x=c*b+a*f+d*h-e*g;this._y=d*b+a*g+e*f-c*h;this._z=e*b+a*h+c*g-d*f;this._w=a*b-c*f-d*g-e*h;this.onChangeCallback();return this},slerp:function(a,b){if(0===b)return this;if(1===b)return this.copy(a);var c=this._x,d=this._y,e=this._z,f=this._w,g=f*a._w+c*a._x+d*a._y+e*a._z;0>g?(this._w=-a._w,this._x=-a._x,this._y=-a._y,this._z=-a._z, +g=-g):this.copy(a);if(1<=g)return this._w=f,this._x=c,this._y=d,this._z=e,this;a=1-g*g;if(a<=Number.EPSILON)return g=1-b,this._w=g*f+b*this._w,this._x=g*c+b*this._x,this._y=g*d+b*this._y,this._z=g*e+b*this._z,this.normalize();a=Math.sqrt(a);var h=Math.atan2(a,g);g=Math.sin((1-b)*h)/a;b=Math.sin(b*h)/a;this._w=f*g+this._w*b;this._x=c*g+this._x*b;this._y=d*g+this._y*b;this._z=e*g+this._z*b;this.onChangeCallback();return this},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&& +a._w===this._w},fromArray:function(a,b){void 0===b&&(b=0);this._x=a[b];this._y=a[b+1];this._z=a[b+2];this._w=a[b+3];this.onChangeCallback();return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this._x;a[b+1]=this._y;a[b+2]=this._z;a[b+3]=this._w;return a},onChange:function(a){this.onChangeCallback=a;return this},onChangeCallback:function(){}});Object.assign(p.prototype,{isVector3:!0,set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},setScalar:function(a){this.z=this.y= +this.x=a;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error("index is out of range: "+a);}return this},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error("index is out of range: "+a);}},clone:function(){return new this.constructor(this.x, +this.y,this.z)},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;return this},addScaledVector:function(a,b){this.x+=a.x*b;this.y+=a.y*b;this.z+= +a.z*b;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;return this},subScalar:function(a){this.x-=a;this.y-=a;this.z-=a;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;return this},multiply:function(a,b){if(void 0!==b)return console.warn("THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."), +this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;return this},multiplyVectors:function(a,b){this.x=a.x*b.x;this.y=a.y*b.y;this.z=a.z*b.z;return this},applyEuler:function(){var a=new ca;return function(b){b&&b.isEuler||console.error("THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.");return this.applyQuaternion(a.setFromEuler(b))}}(),applyAxisAngle:function(){var a=new ca;return function(b, +c){return this.applyQuaternion(a.setFromAxisAngle(b,c))}}(),applyMatrix3:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z;a=a.elements;var e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]);this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y=(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z=(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x, +c=this.y,d=this.z,e=a.x,f=a.y,g=a.z;a=a.w;var h=a*b+f*d-g*c,l=a*c+g*b-e*d,m=a*d+e*c-f*b;b=-e*b-f*c-g*d;this.x=h*a+b*-e+l*-g-m*-f;this.y=l*a+b*-f+m*-e-h*-g;this.z=m*a+b*-g+h*-f-l*-e;return this},project:function(){var a=new R;return function(b){a.multiplyMatrices(b.projectionMatrix,a.getInverse(b.matrixWorld));return this.applyMatrix4(a)}}(),unproject:function(){var a=new R;return function(b){a.multiplyMatrices(b.matrixWorld,a.getInverse(b.projectionMatrix));return this.applyMatrix4(a)}}(),transformDirection:function(a){var b= +this.x,c=this.y,d=this.z;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*b+a[6]*c+a[10]*d;return this.normalize()},divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){return this.multiplyScalar(1/a)},min:function(a){this.x=Math.min(this.x,a.x);this.y=Math.min(this.y,a.y);this.z=Math.min(this.z,a.z);return this},max:function(a){this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);this.z=Math.max(this.z,a.z);return this},clamp:function(a, +b){this.x=Math.max(a.x,Math.min(b.x,this.x));this.y=Math.max(a.y,Math.min(b.y,this.y));this.z=Math.max(a.z,Math.min(b.z,this.z));return this},clampScalar:function(){var a=new p,b=new p;return function(c,d){a.set(c,c,c);b.set(d,d,d);return this.clamp(a,b)}}(),clampLength:function(a,b){var c=this.length();return this.divideScalar(c||1).multiplyScalar(Math.max(a,Math.min(b,c)))},floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);return this},ceil:function(){this.x= +Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y):Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);return this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;return this},dot:function(a){return this.x*a.x+this.y* +a.y+this.z*a.z},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z)},manhattanLength:function(){return Math.abs(this.x)+Math.abs(this.y)+Math.abs(this.z)},normalize:function(){return this.divideScalar(this.length()||1)},setLength:function(a){return this.normalize().multiplyScalar(a)},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},lerpVectors:function(a, +b,c){return this.subVectors(b,a).multiplyScalar(c).add(a)},cross:function(a,b){return void 0!==b?(console.warn("THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(a,b)):this.crossVectors(this,a)},crossVectors:function(a,b){var c=a.x,d=a.y;a=a.z;var e=b.x,f=b.y;b=b.z;this.x=d*b-a*f;this.y=a*e-c*b;this.z=c*f-d*e;return this},projectOnVector:function(a){var b=a.dot(this)/a.lengthSq();return this.copy(a).multiplyScalar(b)},projectOnPlane:function(){var a= +new p;return function(b){a.copy(this).projectOnVector(b);return this.sub(a)}}(),reflect:function(){var a=new p;return function(b){return this.sub(a.copy(b).multiplyScalar(2*this.dot(b)))}}(),angleTo:function(a){a=this.dot(a)/Math.sqrt(this.lengthSq()*a.lengthSq());return Math.acos(J.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y;a=this.z-a.z;return b*b+c*c+a*a},manhattanDistanceTo:function(a){return Math.abs(this.x- +a.x)+Math.abs(this.y-a.y)+Math.abs(this.z-a.z)},setFromSpherical:function(a){var b=Math.sin(a.phi)*a.radius;this.x=b*Math.sin(a.theta);this.y=Math.cos(a.phi)*a.radius;this.z=b*Math.cos(a.theta);return this},setFromCylindrical:function(a){this.x=a.radius*Math.sin(a.theta);this.y=a.y;this.z=a.radius*Math.cos(a.theta);return this},setFromMatrixPosition:function(a){a=a.elements;this.x=a[12];this.y=a[13];this.z=a[14];return this},setFromMatrixScale:function(a){var b=this.setFromMatrixColumn(a,0).length(), +c=this.setFromMatrixColumn(a,1).length();a=this.setFromMatrixColumn(a,2).length();this.x=b;this.y=c;this.z=a;return this},setFromMatrixColumn:function(a,b){return this.fromArray(a.elements,4*b)},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z},fromArray:function(a,b){void 0===b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=this.z;return a},fromBufferAttribute:function(a,b,c){void 0!== +c&&console.warn("THREE.Vector3: offset has been removed from .fromBufferAttribute().");this.x=a.getX(b);this.y=a.getY(b);this.z=a.getZ(b);return this}});Object.assign(la.prototype,{isMatrix3:!0,set:function(a,b,c,d,e,f,g,h,l){var m=this.elements;m[0]=a;m[1]=d;m[2]=g;m[3]=b;m[4]=e;m[5]=h;m[6]=c;m[7]=f;m[8]=l;return this},identity:function(){this.set(1,0,0,0,1,0,0,0,1);return this},clone:function(){return(new this.constructor).fromArray(this.elements)},copy:function(a){var b=this.elements;a=a.elements; +b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return this},setFromMatrix4:function(a){a=a.elements;this.set(a[0],a[4],a[8],a[1],a[5],a[9],a[2],a[6],a[10]);return this},applyToBufferAttribute:function(){var a=new p;return function(b){for(var c=0,d=b.count;cc;c++)if(b[c]!==a[c])return!1;return!0},fromArray:function(a,b){void 0===b&&(b=0);for(var c=0;9>c;c++)this.elements[c]=a[c+b];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);var c=this.elements;a[b]=c[0];a[b+1]=c[1]; +a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];a[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];return a}});var Ff=0;ea.DEFAULT_IMAGE=void 0;ea.DEFAULT_MAPPING=300;ea.prototype=Object.assign(Object.create(za.prototype),{constructor:ea,isTexture:!0,updateMatrix:function(){this.matrix.setUvTransform(this.offset.x,this.offset.y,this.repeat.x,this.repeat.y,this.rotation,this.center.x,this.center.y)},clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.name=a.name;this.image=a.image;this.mipmaps= +a.mipmaps.slice(0);this.mapping=a.mapping;this.wrapS=a.wrapS;this.wrapT=a.wrapT;this.magFilter=a.magFilter;this.minFilter=a.minFilter;this.anisotropy=a.anisotropy;this.format=a.format;this.type=a.type;this.offset.copy(a.offset);this.repeat.copy(a.repeat);this.center.copy(a.center);this.rotation=a.rotation;this.matrixAutoUpdate=a.matrixAutoUpdate;this.matrix.copy(a.matrix);this.generateMipmaps=a.generateMipmaps;this.premultiplyAlpha=a.premultiplyAlpha;this.flipY=a.flipY;this.unpackAlignment=a.unpackAlignment; +this.encoding=a.encoding;return this},toJSON:function(a){function b(a){if(a instanceof HTMLCanvasElement)var b=a;else{b=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");b.width=a.width;b.height=a.height;var c=b.getContext("2d");a instanceof ImageData?c.putImageData(a,0,0):c.drawImage(a,0,0,a.width,a.height)}return 2048a.x||1a.x?0:1;break;case 1002:a.x=1===Math.abs(Math.floor(a.x)%2)?Math.ceil(a.x)-a.x:a.x-Math.floor(a.x)}if(0>a.y||1a.y?0:1;break;case 1002:a.y=1===Math.abs(Math.floor(a.y)%2)?Math.ceil(a.y)-a.y:a.y-Math.floor(a.y)}this.flipY&&(a.y=1-a.y)}}});Object.defineProperty(ea.prototype,"needsUpdate",{set:function(a){!0===a&&this.version++}});Object.assign(W.prototype,{isVector4:!0,set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setScalar:function(a){this.w=this.z=this.y=this.x=a;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this}, +setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=b;break;default:throw Error("index is out of range: "+a);}return this},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw Error("index is out of range: "+a);}},clone:function(){return new this.constructor(this.x,this.y,this.z, +this.w)},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn("THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;this.z=a.z+b.z;this.w=a.w+b.w;return this},addScaledVector:function(a, +b){this.x+=a.x*b;this.y+=a.y*b;this.z+=a.z*b;this.w+=a.w*b;return this},sub:function(a,b){if(void 0!==b)return console.warn("THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.w;return this},subScalar:function(a){this.x-=a;this.y-=a;this.z-=a;this.w-=a;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;this.z=a.z-b.z;this.w=a.w-b.w;return this},multiplyScalar:function(a){this.x*= +a;this.y*=a;this.z*=a;this.w*=a;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z,e=this.w;a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){return this.multiplyScalar(1/a)},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b,this.y=a.y/b,this.z=a.z/ +b);return this},setAxisAngleFromRotationMatrix:function(a){a=a.elements;var b=a[0];var c=a[4];var d=a[8],e=a[1],f=a[5],g=a[9];var h=a[2];var l=a[6];var m=a[10];if(.01>Math.abs(c-e)&&.01>Math.abs(d-h)&&.01>Math.abs(g-l)){if(.1>Math.abs(c+e)&&.1>Math.abs(d+h)&&.1>Math.abs(g+l)&&.1>Math.abs(b+f+m-3))return this.set(1,0,0,0),this;a=Math.PI;b=(b+1)/2;f=(f+1)/2;m=(m+1)/2;c=(c+e)/4;d=(d+h)/4;g=(g+l)/4;b>f&&b>m?.01>b?(l=0,c=h=.707106781):(l=Math.sqrt(b),h=c/l,c=d/l):f>m?.01>f?(l=.707106781,h=0,c=.707106781): +(h=Math.sqrt(f),l=c/h,c=g/h):.01>m?(h=l=.707106781,c=0):(c=Math.sqrt(m),l=d/c,h=g/c);this.set(l,h,c,a);return this}a=Math.sqrt((l-g)*(l-g)+(d-h)*(d-h)+(e-c)*(e-c));.001>Math.abs(a)&&(a=1);this.x=(l-g)/a;this.y=(d-h)/a;this.z=(e-c)/a;this.w=Math.acos((b+f+m-1)/2);return this},min:function(a){this.x=Math.min(this.x,a.x);this.y=Math.min(this.y,a.y);this.z=Math.min(this.z,a.z);this.w=Math.min(this.w,a.w);return this},max:function(a){this.x=Math.max(this.x,a.x);this.y=Math.max(this.y,a.y);this.z=Math.max(this.z, +a.z);this.w=Math.max(this.w,a.w);return this},clamp:function(a,b){this.x=Math.max(a.x,Math.min(b.x,this.x));this.y=Math.max(a.y,Math.min(b.y,this.y));this.z=Math.max(a.z,Math.min(b.z,this.z));this.w=Math.max(a.w,Math.min(b.w,this.w));return this},clampScalar:function(){var a,b;return function(c,d){void 0===a&&(a=new W,b=new W);a.set(c,c,c,c);b.set(d,d,d,d);return this.clamp(a,b)}}(),clampLength:function(a,b){var c=this.length();return this.divideScalar(c||1).multiplyScalar(Math.max(a,Math.min(b,c)))}, +floor:function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);this.z=Math.floor(this.z);this.w=Math.floor(this.w);return this},ceil:function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);this.z=Math.ceil(this.z);this.w=Math.ceil(this.w);return this},round:function(){this.x=Math.round(this.x);this.y=Math.round(this.y);this.z=Math.round(this.z);this.w=Math.round(this.w);return this},roundToZero:function(){this.x=0>this.x?Math.ceil(this.x):Math.floor(this.x);this.y=0>this.y?Math.ceil(this.y): +Math.floor(this.y);this.z=0>this.z?Math.ceil(this.z):Math.floor(this.z);this.w=0>this.w?Math.ceil(this.w):Math.floor(this.w);return this},negate:function(){this.x=-this.x;this.y=-this.y;this.z=-this.z;this.w=-this.w;return this},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.w},lengthSq:function(){return this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w},length:function(){return Math.sqrt(this.x*this.x+this.y*this.y+this.z*this.z+this.w*this.w)},manhattanLength:function(){return Math.abs(this.x)+ +Math.abs(this.y)+Math.abs(this.z)+Math.abs(this.w)},normalize:function(){return this.divideScalar(this.length()||1)},setLength:function(a){return this.normalize().multiplyScalar(a)},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},lerpVectors:function(a,b,c){return this.subVectors(b,a).multiplyScalar(c).add(a)},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&a.w===this.w},fromArray:function(a,b){void 0=== +b&&(b=0);this.x=a[b];this.y=a[b+1];this.z=a[b+2];this.w=a[b+3];return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this.x;a[b+1]=this.y;a[b+2]=this.z;a[b+3]=this.w;return a},fromBufferAttribute:function(a,b,c){void 0!==c&&console.warn("THREE.Vector4: offset has been removed from .fromBufferAttribute().");this.x=a.getX(b);this.y=a.getY(b);this.z=a.getZ(b);this.w=a.getW(b);return this}});kb.prototype=Object.assign(Object.create(za.prototype),{constructor:kb,isWebGLRenderTarget:!0, +setSize:function(a,b){if(this.width!==a||this.height!==b)this.width=a,this.height=b,this.dispose();this.viewport.set(0,0,a,b);this.scissor.set(0,0,a,b)},clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.width=a.width;this.height=a.height;this.viewport.copy(a.viewport);this.texture=a.texture.clone();this.depthBuffer=a.depthBuffer;this.stencilBuffer=a.stencilBuffer;this.depthTexture=a.depthTexture;return this},dispose:function(){this.dispatchEvent({type:"dispose"})}}); +Lb.prototype=Object.create(kb.prototype);Lb.prototype.constructor=Lb;Lb.prototype.isWebGLRenderTargetCube=!0;lb.prototype=Object.create(ea.prototype);lb.prototype.constructor=lb;lb.prototype.isDataTexture=!0;Object.assign(Xa.prototype,{isBox3:!0,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromArray:function(a){for(var b=Infinity,c=Infinity,d=Infinity,e=-Infinity,f=-Infinity,g=-Infinity,h=0,l=a.length;h +e&&(e=m);k>f&&(f=k);n>g&&(g=n)}this.min.set(b,c,d);this.max.set(e,f,g);return this},setFromBufferAttribute:function(a){for(var b=Infinity,c=Infinity,d=Infinity,e=-Infinity,f=-Infinity,g=-Infinity,h=0,l=a.count;he&&(e=m);k>f&&(f=k);n>g&&(g=n)}this.min.set(b,c,d);this.max.set(e,f,g);return this},setFromPoints:function(a){this.makeEmpty();for(var b=0,c=a.length;bthis.max.x||a.ythis.max.y||a.zthis.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z},getParameter:function(a,b){void 0===b&&(console.warn("THREE.Box3: .getParameter() target is now required"),b=new p);return b.set((a.x-this.min.x)/(this.max.x- +this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},intersectsBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y||a.max.zthis.max.z?!1:!0},intersectsSphere:function(){var a=new p;return function(b){this.clampPoint(b.center,a);return a.distanceToSquared(b.center)<=b.radius*b.radius}}(),intersectsPlane:function(a){if(0=a.constant},intersectsTriangle:function(){function a(a){var e;var f=0;for(e=a.length-3;f<=e;f+=3){h.fromArray(a,f);var g=m.x*Math.abs(h.x)+m.y*Math.abs(h.y)+m.z*Math.abs(h.z),l=b.dot(h),k=c.dot(h), +n=d.dot(h);if(Math.max(-Math.max(l,k,n),Math.min(l,k,n))>g)return!1}return!0}var b=new p,c=new p,d=new p,e=new p,f=new p,g=new p,h=new p,l=new p,m=new p,k=new p;return function(h){if(this.isEmpty())return!1;this.getCenter(l);m.subVectors(this.max,l);b.subVectors(h.a,l);c.subVectors(h.b,l);d.subVectors(h.c,l);e.subVectors(c,b);f.subVectors(d,c);g.subVectors(b,d);h=[0,-e.z,e.y,0,-f.z,f.y,0,-g.z,g.y,e.z,0,-e.x,f.z,0,-f.x,g.z,0,-g.x,-e.y,e.x,0,-f.y,f.x,0,-g.y,g.x,0];if(!a(h))return!1;h=[1,0,0,0,1,0,0, +0,1];if(!a(h))return!1;k.crossVectors(e,f);h=[k.x,k.y,k.z];return a(h)}}(),clampPoint:function(a,b){void 0===b&&(console.warn("THREE.Box3: .clampPoint() target is now required"),b=new p);return b.copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new p;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a=new p;return function(b){void 0===b&&(console.warn("THREE.Box3: .getBoundingSphere() target is now required"),b=new Ha); +this.getCenter(b.center);b.radius=.5*this.getSize(a).length();return b}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);this.isEmpty()&&this.makeEmpty();return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(a){if(this.isEmpty())return this;a=a.elements;var b=a[0]*this.min.x,c=a[1]*this.min.x,d=a[2]*this.min.x,e=a[0]*this.max.x,f=a[1]*this.max.x,g=a[2]*this.max.x,h=a[4]*this.min.y,l=a[5]*this.min.y,m=a[6]*this.min.y,k=a[4]*this.max.y, +n=a[5]*this.max.y,p=a[6]*this.max.y,q=a[8]*this.min.z,r=a[9]*this.min.z,u=a[10]*this.min.z,y=a[8]*this.max.z,w=a[9]*this.max.z,x=a[10]*this.max.z;this.min.x=Math.min(b,e)+Math.min(h,k)+Math.min(q,y)+a[12];this.min.y=Math.min(c,f)+Math.min(l,n)+Math.min(r,w)+a[13];this.min.z=Math.min(d,g)+Math.min(m,p)+Math.min(u,x)+a[14];this.max.x=Math.max(b,e)+Math.max(h,k)+Math.max(q,y)+a[12];this.max.y=Math.max(c,f)+Math.max(l,n)+Math.max(r,w)+a[13];this.max.z=Math.max(d,g)+Math.max(m,p)+Math.max(u,x)+a[14];return this}, +translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)}});Object.assign(Ha.prototype,{set:function(a,b){this.center.copy(a);this.radius=b;return this},setFromPoints:function(){var a=new Xa;return function(b,c){var d=this.center;void 0!==c?d.copy(c):a.setFromPoints(b).getCenter(d);for(var e=c=0,f=b.length;e=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<=this.radius*this.radius},distanceToPoint:function(a){return a.distanceTo(this.center)-this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},intersectsBox:function(a){return a.intersectsSphere(this)},intersectsPlane:function(a){return Math.abs(a.distanceToPoint(this.center))<= +this.radius},clampPoint:function(a,b){var c=this.center.distanceToSquared(a);void 0===b&&(console.warn("THREE.Sphere: .clampPoint() target is now required"),b=new p);b.copy(a);c>this.radius*this.radius&&(b.sub(this.center).normalize(),b.multiplyScalar(this.radius).add(this.center));return b},getBoundingBox:function(a){void 0===a&&(console.warn("THREE.Sphere: .getBoundingBox() target is now required"),a=new Xa);a.set(this.center,this.center);a.expandByScalar(this.radius);return a},applyMatrix4:function(a){this.center.applyMatrix4(a); +this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&a.radius===this.radius}});Object.assign(Ia.prototype,{set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a= +new p,b=new p;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d,c);return this}}(),clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+ +this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){void 0===b&&(console.warn("THREE.Plane: .projectPoint() target is now required"),b=new p);return b.copy(this.normal).multiplyScalar(-this.distanceToPoint(a)).add(a)},intersectLine:function(){var a=new p;return function(b,c){void 0===c&&(console.warn("THREE.Plane: .intersectLine() target is now required"),c=new p);var d=b.delta(a),e=this.normal.dot(d);if(0===e){if(0===this.distanceToPoint(b.start))return c.copy(b.start)}else if(e= +-(b.start.dot(this.normal)+this.constant)/e,!(0>e||1b&&0a&&0c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements;a=c[0];var d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],l=c[6],m=c[7],k=c[8],n=c[9],p=c[10],q=c[11],r=c[12],u=c[13],y=c[14];c=c[15];b[0].setComponents(f-a,m-g,q-k,c-r).normalize();b[1].setComponents(f+a,m+g,q+k,c+r).normalize();b[2].setComponents(f+d,m+h,q+n,c+u).normalize();b[3].setComponents(f- +d,m-h,q-n,c-u).normalize();b[4].setComponents(f-e,m-l,q-p,c-y).normalize();b[5].setComponents(f+e,m+l,q+p,c+y).normalize();return this},intersectsObject:function(){var a=new Ha;return function(b){var c=b.geometry;null===c.boundingSphere&&c.computeBoundingSphere();a.copy(c.boundingSphere).applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSprite:function(){var a=new Ha;return function(b){a.center.set(0,0,0);a.radius=.7071067811865476;a.applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(), +intersectsSphere:function(a){var b=this.planes,c=a.center;a=-a.radius;for(var d=0;6>d;d++)if(b[d].distanceToPoint(c)e;e++){var f=d[e];a.x=0 +g&&0>f)return!1}return!0}}(),containsPoint:function(a){for(var b=this.planes,c=0;6>c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0}});var S={alphamap_fragment:"#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif\n",alphamap_pars_fragment:"#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif\n",alphatest_fragment:"#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif\n",aomap_fragment:"#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif\n", +aomap_pars_fragment:"#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif",begin_vertex:"\nvec3 transformed = vec3( position );\n",beginnormal_vertex:"\nvec3 objectNormal = vec3( normal );\n",bsdfs:"float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tif( decayExponent > 0.0 ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tfloat maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\treturn distanceFalloff * maxDistanceCutoffFactor;\n#else\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n#endif\n\t}\n\treturn 1.0;\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;\n\treturn specularColor * AB.x + AB.y;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n", +bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif\n", +clipping_planes_fragment:"#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\tif ( clipped ) discard;\n\t#endif\n#endif\n", +clipping_planes_pars_fragment:"#if NUM_CLIPPING_PLANES > 0\n\t#if ! defined( PHYSICAL ) && ! defined( PHONG )\n\t\tvarying vec3 vViewPosition;\n\t#endif\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif\n",clipping_planes_pars_vertex:"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\n\tvarying vec3 vViewPosition;\n#endif\n",clipping_planes_vertex:"#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n", +color_fragment:"#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif",color_pars_fragment:"#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif\n",color_pars_vertex:"#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif",color_vertex:"#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif",common:"#define PI 3.14159265359\n#define PI2 6.28318530718\n#define PI_HALF 1.5707963267949\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\n", +cube_uv_reflection_fragment:"#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_textureSize (1024.0)\nint getFaceFromDirection(vec3 direction) {\n\tvec3 absDirection = abs(direction);\n\tint face = -1;\n\tif( absDirection.x > absDirection.z ) {\n\t\tif(absDirection.x > absDirection.y )\n\t\t\tface = direction.x > 0.0 ? 0 : 3;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\telse {\n\t\tif(absDirection.z > absDirection.y )\n\t\t\tface = direction.z > 0.0 ? 2 : 5;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\treturn face;\n}\n#define cubeUV_maxLods1 (log2(cubeUV_textureSize*0.25) - 1.0)\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\n\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\n\tfloat dxRoughness = dFdx(roughness);\n\tfloat dyRoughness = dFdy(roughness);\n\tvec3 dx = dFdx( vec * scale * dxRoughness );\n\tvec3 dy = dFdy( vec * scale * dyRoughness );\n\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\n\td = clamp(d, 1.0, cubeUV_rangeClamp);\n\tfloat mipLevel = 0.5 * log2(d);\n\treturn vec2(floor(mipLevel), fract(mipLevel));\n}\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\n\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\n\tfloat a = 16.0 * cubeUV_rcpTextureSize;\n\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\n\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\n\tfloat powScale = exp2_packed.x * exp2_packed.y;\n\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\n\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\n\tbool bRes = mipLevel == 0.0;\n\tscale = bRes && (scale < a) ? a : scale;\n\tvec3 r;\n\tvec2 offset;\n\tint face = getFaceFromDirection(direction);\n\tfloat rcpPowScale = 1.0 / powScale;\n\tif( face == 0) {\n\t\tr = vec3(direction.x, -direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 1) {\n\t\tr = vec3(direction.y, direction.x, direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 2) {\n\t\tr = vec3(direction.z, direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 3) {\n\t\tr = vec3(direction.x, direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse if( face == 4) {\n\t\tr = vec3(direction.y, direction.x, -direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse {\n\t\tr = vec3(direction.z, -direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\tr = normalize(r);\n\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\n\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\n\tvec2 base = offset + vec2( texelOffset );\n\treturn base + s * ( scale - 2.0 * texelOffset );\n}\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\nvec4 textureCubeUV(vec3 reflectedDirection, float roughness ) {\n\tfloat roughnessVal = roughness* cubeUV_maxLods3;\n\tfloat r1 = floor(roughnessVal);\n\tfloat r2 = r1 + 1.0;\n\tfloat t = fract(roughnessVal);\n\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\n\tfloat s = mipInfo.y;\n\tfloat level0 = mipInfo.x;\n\tfloat level1 = level0 + 1.0;\n\tlevel1 = level1 > 5.0 ? 5.0 : level1;\n\tlevel0 += min( floor( s + 0.5 ), 5.0 );\n\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\n\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\n\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\n\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\n\tvec4 result = mix(color10, color20, t);\n\treturn vec4(result.rgb, 1.0);\n}\n#endif\n", +defaultnormal_vertex:"vec3 transformedNormal = normalMatrix * objectNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n",displacementmap_pars_vertex:"#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif\n",displacementmap_vertex:"#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\n#endif\n", +emissivemap_fragment:"#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif\n",emissivemap_pars_fragment:"#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif\n",encodings_fragment:" gl_FragColor = linearToOutputTexel( gl_FragColor );\n",encodings_pars_fragment:"\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.w );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.xyz * value.w * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = min( floor( D ) / 255.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = value.rgb * cLogLuvM;\n\tXp_Y_XYZp = max(Xp_Y_XYZp, vec3(1e-6, 1e-6, 1e-6));\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract(Le);\n\tvResult.z = (Le - (floor(vResult.w*255.0))/255.0)/255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2((Le - 127.0) / 2.0);\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = Xp_Y_XYZp.rgb * cLogLuvInverseM;\n\treturn vec4( max(vRGB, 0.0), 1.0 );\n}\n", +envmap_fragment:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\tvec2 sampleUV;\n\t\treflectVec = normalize( reflectVec );\n\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\tenvColor = envMapTexelToLinear( envColor );\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif\n", +envmap_pars_fragment:"#if defined( USE_ENVMAP ) || defined( PHYSICAL )\n\tuniform float reflectivity;\n\tuniform float envMapIntensity;\n#endif\n#ifdef USE_ENVMAP\n\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\n\t\tvarying vec3 vWorldPosition;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif\n", +envmap_pars_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif\n",envmap_vertex:"#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif\n", +fog_vertex:"\n#ifdef USE_FOG\nfogDepth = -mvPosition.z;\n#endif",fog_pars_vertex:"#ifdef USE_FOG\n varying float fogDepth;\n#endif\n",fog_fragment:"#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif\n",fog_pars_fragment:"#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif\n", +gradientmap_pars_fragment:"#ifdef TOON\n\tuniform sampler2D gradientMap;\n\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\t\tfloat dotNL = dot( normal, lightDirection );\n\t\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t\t#ifdef USE_GRADIENTMAP\n\t\t\treturn texture2D( gradientMap, coord ).rgb;\n\t\t#else\n\t\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t\t#endif\n\t}\n#endif\n",lightmap_fragment:"#ifdef USE_LIGHTMAP\n\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n#endif\n", +lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif",lights_lambert_vertex:"vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvLightFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n#endif\n", +lights_pars_begin:"uniform vec3 ambientLightColor;\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t\tfloat shadowCameraNear;\n\t\tfloat shadowCameraFar;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif\n", +lights_pars_maps:"#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( queryVec, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\tvec4 envMapColor = textureCubeUV(queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent));\n\t\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\t\tvec2 sampleUV;\n\t\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif\n", +lights_phong_fragment:"BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;\n",lights_phong_pars_fragment:"varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifdef TOON\n\t\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#else\n\t\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\t\tvec3 irradiance = dotNL * directLight.color;\n\t#endif\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)\n", +lights_physical_fragment:"PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\n#ifdef STANDARD\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.clearCoat = saturate( clearCoat );\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\n#endif\n", +lights_physical_pars_fragment:"struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n\t#ifndef STANDARD\n\t\tfloat clearCoat;\n\t\tfloat clearCoatRoughness;\n\t#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos - halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos + halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos + halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos - halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifndef STANDARD\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\n\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\t#ifndef STANDARD\n\t\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifndef STANDARD\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\tfloat dotNL = dotNV;\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\n\t#ifndef STANDARD\n\t\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\n#define Material_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.specularRoughness )\n#define Material_ClearCoat_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}\n", +lights_fragment_begin:"\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = normalize( vViewPosition );\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearCoatRadiance = vec3( 0.0 );\n#endif\n", +lights_fragment_maps:"#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tirradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), maxMipLevel );\n\t#ifndef STANDARD\n\t\tclearCoatRadiance += getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), maxMipLevel );\n\t#endif\n#endif\n", +lights_fragment_end:"#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, clearCoatRadiance, geometry, material, reflectedLight );\n#endif\n",logdepthbuf_fragment:"#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif",logdepthbuf_pars_fragment:"#ifdef USE_LOGDEPTHBUF\n\tuniform float logDepthBufFC;\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#endif\n#endif\n", +logdepthbuf_pars_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#endif\n\tuniform float logDepthBufFC;\n#endif",logdepthbuf_vertex:"#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t#else\n\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\tgl_Position.z *= gl_Position.w;\n\t#endif\n#endif\n",map_fragment:"#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif\n", +map_pars_fragment:"#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n",map_particle_fragment:"#ifdef USE_MAP\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n",map_particle_pars_fragment:"#ifdef USE_MAP\n\tuniform mat3 uvTransform;\n\tuniform sampler2D map;\n#endif\n",metalnessmap_fragment:"float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif\n", +metalnessmap_pars_fragment:"#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\n\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n#endif\n",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif", +morphtarget_vertex:"#ifdef USE_MORPHTARGETS\n\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\t#endif\n#endif\n", +normal_fragment_begin:"#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n#endif\n",normal_fragment_maps:"#ifdef USE_NORMALMAP\n\t#ifdef OBJECTSPACE_NORMALMAP\n\t\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t#ifdef FLIP_SIDED\n\t\t\tnormal = - normal;\n\t\t#endif\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\tnormal = normalize( normalMatrix * normal );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif\n", +normalmap_pars_fragment:"#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n\t#ifdef OBJECTSPACE_NORMALMAP\n\t\tuniform mat3 normalMatrix;\n\t#else\n\t\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\t\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\t\tvec2 st0 = dFdx( vUv.st );\n\t\t\tvec2 st1 = dFdy( vUv.st );\n\t\t\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\t\tvec3 N = normalize( surf_norm );\n\t\t\tmat3 tsn = mat3( S, T, N );\n\t\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\t\tmapN.xy *= normalScale;\n\t\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\treturn normalize( tsn * mapN );\n\t\t}\n\t#endif\n#endif\n", +packing:"vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}\n", +premultiplied_alpha_fragment:"#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif\n",project_vertex:"vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\n",dithering_fragment:"#if defined( DITHERING )\n gl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif\n",dithering_pars_fragment:"#if defined( DITHERING )\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif\n", +roughnessmap_fragment:"float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif\n",roughnessmap_pars_fragment:"#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif",shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\n\t\tconst vec2 offset = vec2( 0.0, 1.0 );\n\t\tvec2 texelSize = vec2( 1.0 ) / size;\n\t\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\n\t\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\n\t\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\n\t\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\n\t\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\n\t\tvec2 f = fract( uv * size + 0.5 );\n\t\tfloat a = mix( lb, lt, f.y );\n\t\tfloat b = mix( rb, rt, f.y );\n\t\tfloat c = mix( a, b, f.x );\n\t\treturn c;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif\n", +shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n#endif\n", +shadowmap_vertex:"#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n#endif\n", +shadowmask_pars_fragment:"float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#endif\n\t#endif\n\treturn shadow;\n}\n", +skinbase_vertex:"#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif\n", +skinning_vertex:"#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif\n",skinnormal_vertex:"#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n#endif\n", +specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif",specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif",tonemapping_fragment:"#if defined( TONE_MAPPING )\n gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif\n",tonemapping_pars_fragment:"#ifndef saturate\n\t#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nuniform float toneMappingWhitePoint;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\nvec3 Uncharted2ToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\n", +uv_pars_fragment:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n#endif",uv_pars_vertex:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\n", +uv_vertex:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif",uv2_pars_fragment:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif",uv2_pars_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n#endif", +uv2_vertex:"#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = uv2;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\n#endif\n",cube_frag:"uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldPosition;\nvoid main() {\n\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\n\tgl_FragColor.a *= opacity;\n}\n", +cube_vert:"varying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvWorldPosition = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}\n",depth_frag:"#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - gl_FragCoord.z ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\n\t#endif\n}\n", +depth_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +distanceRGBA_frag:"#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}\n", +distanceRGBA_vert:"#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}\n", +equirect_frag:"uniform sampler2D tEquirect;\nvarying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldPosition );\n\tvec2 sampleUV;\n\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n}\n",equirect_vert:"varying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvWorldPosition = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}\n", +linedashed_frag:"uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +linedashed_vert:"uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvLineDistance = scale * lineDistance;\n\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}\n", +meshbasic_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshbasic_vert:"#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_ENVMAP\n\t#include \n\t#include \n\t#include \n\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshlambert_frag:"uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\treflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshlambert_vert:"#define LAMBERT\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshphong_frag:"#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshphong_vert:"#define PHONG\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshphysical_frag:"#define PHYSICAL\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifndef STANDARD\n\tuniform float clearCoat;\n\tuniform float clearCoatRoughness;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +meshphysical_vert:"#define PHYSICAL\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}\n", +normal_frag:"#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}\n", +normal_vert:"#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || ( defined( USE_NORMALMAP ) && ! defined( OBJECTSPACE_NORMALMAP ) )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}\n", +points_frag:"uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +points_vert:"uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_SIZEATTENUATION\n\t\tgl_PointSize = size * ( scale / - mvPosition.z );\n\t#else\n\t\tgl_PointSize = size;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n}\n", +shadow_frag:"uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n}\n",shadow_vert:"#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"}, +Ba={merge:function(a){for(var b={},c=0;c>16&255)/255;this.g=(a>>8&255)/255;this.b=(a&255)/255;return this},setRGB:function(a,b,c){this.r=a;this.g=b;this.b=c;return this},setHSL:function(){function a(a,c,d){0>d&&(d+=1);1d?c:d<2/3?a+6*(c-a)*(2/3-d):a}return function(b, +c,d){b=J.euclideanModulo(b,1);c=J.clamp(c,0,1);d=J.clamp(d,0,1);0===c?this.r=this.g=this.b=d:(c=.5>=d?d*(1+c):d+c-d*c,d=2*d-c,this.r=a(d,c,b+1/3),this.g=a(d,c,b),this.b=a(d,c,b-1/3));return this}}(),setStyle:function(a){function b(b){void 0!==b&&1>parseFloat(b)&&console.warn("THREE.Color: Alpha component of "+a+" will be ignored.")}var c;if(c=/^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec(a)){var d=c[2];switch(c[1]){case "rgb":case "rgba":if(c=/^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(d))return this.r= +Math.min(255,parseInt(c[1],10))/255,this.g=Math.min(255,parseInt(c[2],10))/255,this.b=Math.min(255,parseInt(c[3],10))/255,b(c[5]),this;if(c=/^(\d+)%\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(d))return this.r=Math.min(100,parseInt(c[1],10))/100,this.g=Math.min(100,parseInt(c[2],10))/100,this.b=Math.min(100,parseInt(c[3],10))/100,b(c[5]),this;break;case "hsl":case "hsla":if(c=/^([0-9]*\.?[0-9]+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec(d)){d=parseFloat(c[1])/ +360;var e=parseInt(c[2],10)/100,f=parseInt(c[3],10)/100;b(c[5]);return this.setHSL(d,e,f)}}}else if(c=/^#([A-Fa-f0-9]+)$/.exec(a)){c=c[1];d=c.length;if(3===d)return this.r=parseInt(c.charAt(0)+c.charAt(0),16)/255,this.g=parseInt(c.charAt(1)+c.charAt(1),16)/255,this.b=parseInt(c.charAt(2)+c.charAt(2),16)/255,this;if(6===d)return this.r=parseInt(c.charAt(0)+c.charAt(1),16)/255,this.g=parseInt(c.charAt(2)+c.charAt(3),16)/255,this.b=parseInt(c.charAt(4)+c.charAt(5),16)/255,this}a&&0a?.0773993808*a:Math.pow(.9478672986*a+.0521327014,2.4)}return function(b){this.r=a(b.r);this.g=a(b.g);this.b=a(b.b);return this}}(),copyLinearToSRGB:function(){function a(a){return.0031308>a?12.92*a:1.055*Math.pow(a,.41666)-.055}return function(b){this.r=a(b.r);this.g=a(b.g);this.b=a(b.b);return this}}(),convertSRGBToLinear:function(){this.copySRGBToLinear(this); +return this},convertLinearToSRGB:function(){this.copyLinearToSRGB(this);return this},getHex:function(){return 255*this.r<<16^255*this.g<<8^255*this.b<<0},getHexString:function(){return("000000"+this.getHex().toString(16)).slice(-6)},getHSL:function(a){void 0===a&&(console.warn("THREE.Color: .getHSL() target is now required"),a={h:0,s:0,l:0});var b=this.r,c=this.g,d=this.b,e=Math.max(b,c,d),f=Math.min(b,c,d),g,h=(f+e)/2;if(f===e)f=g=0;else{var l=e-f;f=.5>=h?l/(e+f):l/(2-e-f);switch(e){case b:g=(c- +d)/l+(cMath.abs(g)?(this._x=Math.atan2(-m,e),this._z=Math.atan2(-f,a)):(this._x=Math.atan2(n,l),this._z=0)):"YXZ"===b?(this._x=Math.asin(-d(m,-1,1)),.99999>Math.abs(m)?(this._y=Math.atan2(g,e),this._z=Math.atan2(h,l)):(this._y=Math.atan2(-k,a),this._z=0)):"ZXY"===b?(this._x=Math.asin(d(n,-1,1)),.99999>Math.abs(n)? +(this._y=Math.atan2(-k,e),this._z=Math.atan2(-f,l)):(this._y=0,this._z=Math.atan2(h,a))):"ZYX"===b?(this._y=Math.asin(-d(k,-1,1)),.99999>Math.abs(k)?(this._x=Math.atan2(n,e),this._z=Math.atan2(h,a)):(this._x=0,this._z=Math.atan2(-f,l))):"YZX"===b?(this._z=Math.asin(d(h,-1,1)),.99999>Math.abs(h)?(this._x=Math.atan2(-m,l),this._y=Math.atan2(-k,a)):(this._x=0,this._y=Math.atan2(g,e))):"XZY"===b?(this._z=Math.asin(-d(f,-1,1)),.99999>Math.abs(f)?(this._x=Math.atan2(n,l),this._y=Math.atan2(g,a)):(this._x= +Math.atan2(-m,e),this._y=0)):console.warn("THREE.Euler: .setFromRotationMatrix() given unsupported order: "+b);this._order=b;if(!1!==c)this.onChangeCallback();return this},setFromQuaternion:function(){var a=new R;return function(b,c,d){a.makeRotationFromQuaternion(b);return this.setFromRotationMatrix(a,c,d)}}(),setFromVector3:function(a,b){return this.set(a.x,a.y,a.z,b||this._order)},reorder:function(){var a=new ca;return function(b){a.setFromEuler(this);return this.setFromQuaternion(a,b)}}(),equals:function(a){return a._x=== +this._x&&a._y===this._y&&a._z===this._z&&a._order===this._order},fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];void 0!==a[3]&&(this._order=a[3]);this.onChangeCallback();return this},toArray:function(a,b){void 0===a&&(a=[]);void 0===b&&(b=0);a[b]=this._x;a[b+1]=this._y;a[b+2]=this._z;a[b+3]=this._order;return a},toVector3:function(a){return a?a.set(this._x,this._y,this._z):new p(this._x,this._y,this._z)},onChange:function(a){this.onChangeCallback=a;return this},onChangeCallback:function(){}}); +Object.assign(Wd.prototype,{set:function(a){this.mask=1<g;g++)if(d[g]===d[(g+1)%3]){a.push(f);break}for(f=a.length-1;0<=f;f--)for(d=a[f],this.faces.splice(d,1),c=0,e=this.faceVertexUvs.length;cthis.opacity&&(d.opacity=this.opacity);!0===this.transparent&&(d.transparent=this.transparent);d.depthFunc=this.depthFunc;d.depthTest=this.depthTest;d.depthWrite=this.depthWrite;0!==this.rotation&&(d.rotation=this.rotation);1!==this.linewidth&&(d.linewidth=this.linewidth);void 0!==this.dashSize&&(d.dashSize= +this.dashSize);void 0!==this.gapSize&&(d.gapSize=this.gapSize);void 0!==this.scale&&(d.scale=this.scale);!0===this.dithering&&(d.dithering=!0);0a?b.copy(this.origin):b.copy(this.direction).multiplyScalar(a).add(this.origin)},distanceToPoint:function(a){return Math.sqrt(this.distanceSqToPoint(a))},distanceSqToPoint:function(){var a=new p;return function(b){var c=a.subVectors(b,this.origin).dot(this.direction);if(0>c)return this.origin.distanceToSquared(b); +a.copy(this.direction).multiplyScalar(c).add(this.origin);return a.distanceToSquared(b)}}(),distanceSqToSegment:function(){var a=new p,b=new p,c=new p;return function(d,e,f,g){a.copy(d).add(e).multiplyScalar(.5);b.copy(e).sub(d).normalize();c.copy(this.origin).sub(a);var h=.5*d.distanceTo(e),l=-this.direction.dot(b),m=c.dot(this.direction),k=-c.dot(b),n=c.lengthSq(),p=Math.abs(1-l*l);if(0=-q?e<=q?(h=1/p,d*=h,e*=h,l=d*(d+l*e+2*m)+e*(l*d+e+2*k)+n):(e=h,d=Math.max(0, +-(l*e+m)),l=-d*d+e*(e+2*k)+n):(e=-h,d=Math.max(0,-(l*e+m)),l=-d*d+e*(e+2*k)+n):e<=-q?(d=Math.max(0,-(-l*h+m)),e=0b)return null;b=Math.sqrt(b-e);e=d-b;d+=b;return 0>e&&0>d?null:0>e?this.at(d,c):this.at(e,c)}}(),intersectsSphere:function(a){return this.distanceToPoint(a.center)<=a.radius},distanceToPlane:function(a){var b=a.normal.dot(this.direction);if(0===b)return 0===a.distanceToPoint(this.origin)?0:null;a=-(this.origin.dot(a.normal)+a.constant)/b;return 0<=a?a:null},intersectPlane:function(a, +b){a=this.distanceToPlane(a);return null===a?null:this.at(a,b)},intersectsPlane:function(a){var b=a.distanceToPoint(this.origin);return 0===b||0>a.normal.dot(this.direction)*b?!0:!1},intersectBox:function(a,b){var c=1/this.direction.x;var d=1/this.direction.y;var e=1/this.direction.z,f=this.origin;if(0<=c){var g=(a.min.x-f.x)*c;c*=a.max.x-f.x}else g=(a.max.x-f.x)*c,c*=a.min.x-f.x;if(0<=d){var h=(a.min.y-f.y)*d;d*=a.max.y-f.y}else h=(a.max.y-f.y)*d,d*=a.min.y-f.y;if(g>d||h>c)return null;if(h>g||g!== +g)g=h;if(da||h>c)return null;if(h>g||g!==g)g=h;if(ac?null:this.at(0<=g?g:c,b)},intersectsBox:function(){var a=new p;return function(b){return null!==this.intersectBox(b,a)}}(),intersectTriangle:function(){var a=new p,b=new p,c=new p,d=new p;return function(e,f,g,h,l){b.subVectors(f,e);c.subVectors(g,e);d.crossVectors(b,c);f=this.direction.dot(d);if(0 +f)h=-1,f=-f;else return null;a.subVectors(this.origin,e);e=h*this.direction.dot(c.crossVectors(a,c));if(0>e)return null;g=h*this.direction.dot(b.cross(a));if(0>g||e+g>f)return null;e=-h*a.dot(d);return 0>e?null:this.at(e/f,l)}}(),applyMatrix4:function(a){this.origin.applyMatrix4(a);this.direction.transformDirection(a);return this},equals:function(a){return a.origin.equals(this.origin)&&a.direction.equals(this.direction)}});Object.assign(Qb.prototype,{set:function(a,b){this.start.copy(a);this.end.copy(b); +return this},clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.start.copy(a.start);this.end.copy(a.end);return this},getCenter:function(a){void 0===a&&(console.warn("THREE.Line3: .getCenter() target is now required"),a=new p);return a.addVectors(this.start,this.end).multiplyScalar(.5)},delta:function(a){void 0===a&&(console.warn("THREE.Line3: .delta() target is now required"),a=new p);return a.subVectors(this.end,this.start)},distanceSq:function(){return this.start.distanceToSquared(this.end)}, +distance:function(){return this.start.distanceTo(this.end)},at:function(a,b){void 0===b&&(console.warn("THREE.Line3: .at() target is now required"),b=new p);return this.delta(b).multiplyScalar(a).add(this.start)},closestPointToPointParameter:function(){var a=new p,b=new p;return function(c,d){a.subVectors(c,this.start);b.subVectors(this.end,this.start);c=b.dot(b);c=b.dot(a)/c;d&&(c=J.clamp(c,0,1));return c}}(),closestPointToPoint:function(a,b,c){a=this.closestPointToPointParameter(a,b);void 0===c&& +(console.warn("THREE.Line3: .closestPointToPoint() target is now required"),c=new p);return this.delta(c).multiplyScalar(a).add(this.start)},applyMatrix4:function(a){this.start.applyMatrix4(a);this.end.applyMatrix4(a);return this},equals:function(a){return a.start.equals(this.start)&&a.end.equals(this.end)}});Object.assign(ta,{getNormal:function(){var a=new p;return function(b,c,d,e){void 0===e&&(console.warn("THREE.Triangle: .getNormal() target is now required"),e=new p);e.subVectors(d,c);a.subVectors(b, +c);e.cross(a);b=e.lengthSq();return 0=a.x+a.y}}()});Object.assign(ta.prototype,{set:function(a,b,c){this.a.copy(a);this.b.copy(b);this.c.copy(c);return this},setFromPointsAndIndices:function(a,b,c,d){this.a.copy(a[b]);this.b.copy(a[c]);this.c.copy(a[d]);return this},clone:function(){return(new this.constructor).copy(this)},copy:function(a){this.a.copy(a.a);this.b.copy(a.b);this.c.copy(a.c);return this},getArea:function(){var a=new p,b=new p;return function(){a.subVectors(this.c, +this.b);b.subVectors(this.a,this.b);return.5*a.cross(b).length()}}(),getMidpoint:function(a){void 0===a&&(console.warn("THREE.Triangle: .getMidpoint() target is now required"),a=new p);return a.addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)},getNormal:function(a){return ta.getNormal(this.a,this.b,this.c,a)},getPlane:function(a){void 0===a&&(console.warn("THREE.Triangle: .getPlane() target is now required"),a=new p);return a.setFromCoplanarPoints(this.a,this.b,this.c)},getBarycoord:function(a, +b){return ta.getBarycoord(a,this.a,this.b,this.c,b)},containsPoint:function(a){return ta.containsPoint(a,this.a,this.b,this.c)},intersectsBox:function(a){return a.intersectsTriangle(this)},closestPointToPoint:function(){var a=new Ia,b=[new Qb,new Qb,new Qb],c=new p,d=new p;return function(e,f){void 0===f&&(console.warn("THREE.Triangle: .closestPointToPoint() target is now required"),f=new p);var g=Infinity;a.setFromCoplanarPoints(this.a,this.b,this.c);a.projectPoint(e,c);if(!0===this.containsPoint(c))f.copy(c); +else for(b[0].set(this.a,this.b),b[1].set(this.b,this.c),b[2].set(this.c,this.a),e=0;ec.far?null:{distance:b,point:w.clone(),object:a}}function c(c,d,e,f,m,k,n,p,v){g.fromBufferAttribute(m,n);h.fromBufferAttribute(m,p);l.fromBufferAttribute(m,v);if(c=b(c,d,e,f,g,h,l,y))k&&(t.fromBufferAttribute(k,n),q.fromBufferAttribute(k,p),r.fromBufferAttribute(k,v),c.uv=a(y,g,h,l,t,q,r)),k=new Ya(n,p,v),ta.getNormal(g,h,l,k.normal),c.face=k;return c}var d=new R,e=new sb,f=new Ha, +g=new p,h=new p,l=new p,m=new p,k=new p,n=new p,t=new B,q=new B,r=new B,u=new p,y=new p,w=new p;return function(p,v){var u=this.geometry,x=this.material,w=this.matrixWorld;if(void 0!==x&&(null===u.boundingSphere&&u.computeBoundingSphere(),f.copy(u.boundingSphere),f.applyMatrix4(w),!1!==p.ray.intersectsSphere(f)&&(d.getInverse(w),e.copy(p.ray).applyMatrix4(d),null===u.boundingBox||!1!==e.intersectsBox(u.boundingBox))))if(u.isBufferGeometry){var A=u.index,z=u.attributes.position,B=u.attributes.uv,H= +u.groups;u=u.drawRange;var G;if(null!==A)if(Array.isArray(x)){var D=0;for(G=H.length;De.far||f.push({distance:p,point:b.clone(),face:null,object:this})}}(),clone:function(){return(new this.constructor(this.material)).copy(this)},copy:function(a){H.prototype.copy.call(this,a);void 0!==a.center&&this.center.copy(a.center);return this}});Jc.prototype=Object.assign(Object.create(H.prototype),{constructor:Jc,copy:function(a){H.prototype.copy.call(this,a,!1);a=a.levels;for(var b=0,c=a.length;b=d[e].distance)d[e-1].object.visible=!1,d[e].object.visible=!0;else break;for(;ef||(k.applyMatrix4(this.matrixWorld),u=d.ray.origin.distanceTo(k),ud.far||e.push({distance:u,point:h.clone().applyMatrix4(this.matrixWorld),index:g,face:null,faceIndex:null,object:this}))}}else for(g=0,r=q.length/3-1;gf||(k.applyMatrix4(this.matrixWorld),u=d.ray.origin.distanceTo(k),ud.far||e.push({distance:u,point:h.clone().applyMatrix4(this.matrixWorld),index:g,face:null,faceIndex:null,object:this}))}else if(g.isGeometry)for(l=g.vertices,m=l.length,g=0;gf||(k.applyMatrix4(this.matrixWorld),u=d.ray.origin.distanceTo(k),ud.far||e.push({distance:u,point:h.clone().applyMatrix4(this.matrixWorld),index:g, +face:null,faceIndex:null,object:this}))}}}(),clone:function(){return(new this.constructor(this.geometry,this.material)).copy(this)}});Y.prototype=Object.assign(Object.create(wa.prototype),{constructor:Y,isLineSegments:!0,computeLineDistances:function(){var a=new p,b=new p;return function(){var c=this.geometry;if(c.isBufferGeometry)if(null===c.index){for(var d=c.attributes.position,e=[],f=0,g=d.count;fd.far|| +e.push({distance:a,distanceToRay:Math.sqrt(f),point:n.clone(),index:c,face:null,object:g}))}var g=this,h=this.geometry,l=this.matrixWorld,m=d.params.Points.threshold;null===h.boundingSphere&&h.computeBoundingSphere();c.copy(h.boundingSphere);c.applyMatrix4(l);c.radius+=m;if(!1!==d.ray.intersectsSphere(c)){a.getInverse(l);b.copy(d.ray).applyMatrix4(a);m/=(this.scale.x+this.scale.y+this.scale.z)/3;var k=m*m;m=new p;var n=new p;if(h.isBufferGeometry){var t=h.index;h=h.attributes.position.array;if(null!== +t){var q=t.array;t=0;for(var r=q.length;t=a.HAVE_CURRENT_DATA&&(this.needsUpdate=!0)}});Yb.prototype=Object.create(ea.prototype); +Yb.prototype.constructor=Yb;Yb.prototype.isCompressedTexture=!0;Lc.prototype=Object.create(ea.prototype);Lc.prototype.constructor=Lc;Lc.prototype.isDepthTexture=!0;Zb.prototype=Object.create(D.prototype);Zb.prototype.constructor=Zb;Mc.prototype=Object.create(P.prototype);Mc.prototype.constructor=Mc;$b.prototype=Object.create(D.prototype);$b.prototype.constructor=$b;Nc.prototype=Object.create(P.prototype);Nc.prototype.constructor=Nc;xa.prototype=Object.create(D.prototype);xa.prototype.constructor= +xa;Oc.prototype=Object.create(P.prototype);Oc.prototype.constructor=Oc;ac.prototype=Object.create(xa.prototype);ac.prototype.constructor=ac;Pc.prototype=Object.create(P.prototype);Pc.prototype.constructor=Pc;ub.prototype=Object.create(xa.prototype);ub.prototype.constructor=ub;Qc.prototype=Object.create(P.prototype);Qc.prototype.constructor=Qc;bc.prototype=Object.create(xa.prototype);bc.prototype.constructor=bc;Rc.prototype=Object.create(P.prototype);Rc.prototype.constructor=Rc;cc.prototype=Object.create(xa.prototype); +cc.prototype.constructor=cc;Sc.prototype=Object.create(P.prototype);Sc.prototype.constructor=Sc;dc.prototype=Object.create(D.prototype);dc.prototype.constructor=dc;Tc.prototype=Object.create(P.prototype);Tc.prototype.constructor=Tc;ec.prototype=Object.create(D.prototype);ec.prototype.constructor=ec;Uc.prototype=Object.create(P.prototype);Uc.prototype.constructor=Uc;fc.prototype=Object.create(D.prototype);fc.prototype.constructor=fc;var Ug={triangulate:function(a,b,c){c=c||2;var d=b&&b.length,e=d? +b[0]*c:a.length,f=cf(a,0,e,c,!0),g=[];if(!f)return g;var h;if(d){var l=c;d=[];var m;var k=0;for(m=b.length;k80*c){var q=h=a[0];var r=d=a[1];for(l=c;lh&&(h=k),b>d&&(d=b);h=Math.max(h-q,d-r);h=0!==h?1/h:0}Xc(f,g,c,q,r,h);return g}},$a={area:function(a){for(var b= +a.length,c=0,d=b-1,e=0;e$a.area(a)},triangulateShape:function(a,b){var c=[],d=[],e=[];gf(a);hf(c,a);var f=a.length;b.forEach(gf);for(a=0;aMath.abs(g-l)?[new B(a,1-c),new B(h,1-d),new B(m,1-e),new B(n,1-b)]:[new B(g,1-c),new B(l,1-d),new B(k,1-e),new B(p,1-b)]}};Zc.prototype=Object.create(P.prototype);Zc.prototype.constructor=Zc;gc.prototype=Object.create(Ta.prototype);gc.prototype.constructor=gc;$c.prototype=Object.create(P.prototype);$c.prototype.constructor=$c;xb.prototype=Object.create(D.prototype);xb.prototype.constructor=xb;ad.prototype=Object.create(P.prototype);ad.prototype.constructor= +ad;hc.prototype=Object.create(D.prototype);hc.prototype.constructor=hc;bd.prototype=Object.create(P.prototype);bd.prototype.constructor=bd;ic.prototype=Object.create(D.prototype);ic.prototype.constructor=ic;yb.prototype=Object.create(P.prototype);yb.prototype.constructor=yb;yb.prototype.toJSON=function(){var a=P.prototype.toJSON.call(this);return kf(this.parameters.shapes,a)};zb.prototype=Object.create(D.prototype);zb.prototype.constructor=zb;zb.prototype.toJSON=function(){var a=D.prototype.toJSON.call(this); +return kf(this.parameters.shapes,a)};jc.prototype=Object.create(D.prototype);jc.prototype.constructor=jc;Ab.prototype=Object.create(P.prototype);Ab.prototype.constructor=Ab;ab.prototype=Object.create(D.prototype);ab.prototype.constructor=ab;cd.prototype=Object.create(Ab.prototype);cd.prototype.constructor=cd;dd.prototype=Object.create(ab.prototype);dd.prototype.constructor=dd;ed.prototype=Object.create(P.prototype);ed.prototype.constructor=ed;kc.prototype=Object.create(D.prototype);kc.prototype.constructor= +kc;var ka=Object.freeze({WireframeGeometry:Zb,ParametricGeometry:Mc,ParametricBufferGeometry:$b,TetrahedronGeometry:Oc,TetrahedronBufferGeometry:ac,OctahedronGeometry:Pc,OctahedronBufferGeometry:ub,IcosahedronGeometry:Qc,IcosahedronBufferGeometry:bc,DodecahedronGeometry:Rc,DodecahedronBufferGeometry:cc,PolyhedronGeometry:Nc,PolyhedronBufferGeometry:xa,TubeGeometry:Sc,TubeBufferGeometry:dc,TorusKnotGeometry:Tc,TorusKnotBufferGeometry:ec,TorusGeometry:Uc,TorusBufferGeometry:fc,TextGeometry:Zc,TextBufferGeometry:gc, +SphereGeometry:$c,SphereBufferGeometry:xb,RingGeometry:ad,RingBufferGeometry:hc,PlaneGeometry:Fc,PlaneBufferGeometry:rb,LatheGeometry:bd,LatheBufferGeometry:ic,ShapeGeometry:yb,ShapeBufferGeometry:zb,ExtrudeGeometry:wb,ExtrudeBufferGeometry:Ta,EdgesGeometry:jc,ConeGeometry:cd,ConeBufferGeometry:dd,CylinderGeometry:Ab,CylinderBufferGeometry:ab,CircleGeometry:ed,CircleBufferGeometry:kc,BoxGeometry:Nb,BoxBufferGeometry:pb});Bb.prototype=Object.create(I.prototype);Bb.prototype.constructor=Bb;Bb.prototype.isShadowMaterial= +!0;Bb.prototype.copy=function(a){I.prototype.copy.call(this,a);this.color.copy(a.color);return this};lc.prototype=Object.create(Fa.prototype);lc.prototype.constructor=lc;lc.prototype.isRawShaderMaterial=!0;Ua.prototype=Object.create(I.prototype);Ua.prototype.constructor=Ua;Ua.prototype.isMeshStandardMaterial=!0;Ua.prototype.copy=function(a){I.prototype.copy.call(this,a);this.defines={STANDARD:""};this.color.copy(a.color);this.roughness=a.roughness;this.metalness=a.metalness;this.map=a.map;this.lightMap= +a.lightMap;this.lightMapIntensity=a.lightMapIntensity;this.aoMap=a.aoMap;this.aoMapIntensity=a.aoMapIntensity;this.emissive.copy(a.emissive);this.emissiveMap=a.emissiveMap;this.emissiveIntensity=a.emissiveIntensity;this.bumpMap=a.bumpMap;this.bumpScale=a.bumpScale;this.normalMap=a.normalMap;this.normalMapType=a.normalMapType;this.normalScale.copy(a.normalScale);this.displacementMap=a.displacementMap;this.displacementScale=a.displacementScale;this.displacementBias=a.displacementBias;this.roughnessMap= +a.roughnessMap;this.metalnessMap=a.metalnessMap;this.alphaMap=a.alphaMap;this.envMap=a.envMap;this.envMapIntensity=a.envMapIntensity;this.refractionRatio=a.refractionRatio;this.wireframe=a.wireframe;this.wireframeLinewidth=a.wireframeLinewidth;this.wireframeLinecap=a.wireframeLinecap;this.wireframeLinejoin=a.wireframeLinejoin;this.skinning=a.skinning;this.morphTargets=a.morphTargets;this.morphNormals=a.morphNormals;return this};Cb.prototype=Object.create(Ua.prototype);Cb.prototype.constructor=Cb; +Cb.prototype.isMeshPhysicalMaterial=!0;Cb.prototype.copy=function(a){Ua.prototype.copy.call(this,a);this.defines={PHYSICAL:""};this.reflectivity=a.reflectivity;this.clearCoat=a.clearCoat;this.clearCoatRoughness=a.clearCoatRoughness;return this};Ka.prototype=Object.create(I.prototype);Ka.prototype.constructor=Ka;Ka.prototype.isMeshPhongMaterial=!0;Ka.prototype.copy=function(a){I.prototype.copy.call(this,a);this.color.copy(a.color);this.specular.copy(a.specular);this.shininess=a.shininess;this.map= +a.map;this.lightMap=a.lightMap;this.lightMapIntensity=a.lightMapIntensity;this.aoMap=a.aoMap;this.aoMapIntensity=a.aoMapIntensity;this.emissive.copy(a.emissive);this.emissiveMap=a.emissiveMap;this.emissiveIntensity=a.emissiveIntensity;this.bumpMap=a.bumpMap;this.bumpScale=a.bumpScale;this.normalMap=a.normalMap;this.normalMapType=a.normalMapType;this.normalScale.copy(a.normalScale);this.displacementMap=a.displacementMap;this.displacementScale=a.displacementScale;this.displacementBias=a.displacementBias; +this.specularMap=a.specularMap;this.alphaMap=a.alphaMap;this.envMap=a.envMap;this.combine=a.combine;this.reflectivity=a.reflectivity;this.refractionRatio=a.refractionRatio;this.wireframe=a.wireframe;this.wireframeLinewidth=a.wireframeLinewidth;this.wireframeLinecap=a.wireframeLinecap;this.wireframeLinejoin=a.wireframeLinejoin;this.skinning=a.skinning;this.morphTargets=a.morphTargets;this.morphNormals=a.morphNormals;return this};Db.prototype=Object.create(Ka.prototype);Db.prototype.constructor=Db; +Db.prototype.isMeshToonMaterial=!0;Db.prototype.copy=function(a){Ka.prototype.copy.call(this,a);this.gradientMap=a.gradientMap;return this};Eb.prototype=Object.create(I.prototype);Eb.prototype.constructor=Eb;Eb.prototype.isMeshNormalMaterial=!0;Eb.prototype.copy=function(a){I.prototype.copy.call(this,a);this.bumpMap=a.bumpMap;this.bumpScale=a.bumpScale;this.normalMap=a.normalMap;this.normalMapType=a.normalMapType;this.normalScale.copy(a.normalScale);this.displacementMap=a.displacementMap;this.displacementScale= +a.displacementScale;this.displacementBias=a.displacementBias;this.wireframe=a.wireframe;this.wireframeLinewidth=a.wireframeLinewidth;this.skinning=a.skinning;this.morphTargets=a.morphTargets;this.morphNormals=a.morphNormals;return this};Fb.prototype=Object.create(I.prototype);Fb.prototype.constructor=Fb;Fb.prototype.isMeshLambertMaterial=!0;Fb.prototype.copy=function(a){I.prototype.copy.call(this,a);this.color.copy(a.color);this.map=a.map;this.lightMap=a.lightMap;this.lightMapIntensity=a.lightMapIntensity; +this.aoMap=a.aoMap;this.aoMapIntensity=a.aoMapIntensity;this.emissive.copy(a.emissive);this.emissiveMap=a.emissiveMap;this.emissiveIntensity=a.emissiveIntensity;this.specularMap=a.specularMap;this.alphaMap=a.alphaMap;this.envMap=a.envMap;this.combine=a.combine;this.reflectivity=a.reflectivity;this.refractionRatio=a.refractionRatio;this.wireframe=a.wireframe;this.wireframeLinewidth=a.wireframeLinewidth;this.wireframeLinecap=a.wireframeLinecap;this.wireframeLinejoin=a.wireframeLinejoin;this.skinning= +a.skinning;this.morphTargets=a.morphTargets;this.morphNormals=a.morphNormals;return this};Gb.prototype=Object.create(T.prototype);Gb.prototype.constructor=Gb;Gb.prototype.isLineDashedMaterial=!0;Gb.prototype.copy=function(a){T.prototype.copy.call(this,a);this.scale=a.scale;this.dashSize=a.dashSize;this.gapSize=a.gapSize;return this};var Vg=Object.freeze({ShadowMaterial:Bb,SpriteMaterial:ib,RawShaderMaterial:lc,ShaderMaterial:Fa,PointsMaterial:Ja,MeshPhysicalMaterial:Cb,MeshStandardMaterial:Ua,MeshPhongMaterial:Ka, +MeshToonMaterial:Db,MeshNormalMaterial:Eb,MeshLambertMaterial:Fb,MeshDepthMaterial:fb,MeshDistanceMaterial:gb,MeshBasicMaterial:ua,LineDashedMaterial:Gb,LineBasicMaterial:T,Material:I}),Kb={enabled:!1,files:{},add:function(a,b){!1!==this.enabled&&(this.files[a]=b)},get:function(a){if(!1!==this.enabled)return this.files[a]},remove:function(a){delete this.files[a]},clear:function(){this.files={}}},ya=new ee,cb={};Object.assign(La.prototype,{load:function(a,b,c,d){void 0===a&&(a="");void 0!==this.path&& +(a=this.path+a);a=this.manager.resolveURL(a);var e=this,f=Kb.get(a);if(void 0!==f)return e.manager.itemStart(a),setTimeout(function(){b&&b(f);e.manager.itemEnd(a)},0),f;if(void 0!==cb[a])cb[a].push({onLoad:b,onProgress:c,onError:d});else{var g=a.match(/^data:(.*?)(;base64)?,(.*)$/);if(g){c=g[1];var h=!!g[2];g=g[3];g=window.decodeURIComponent(g);h&&(g=window.atob(g));try{var l=(this.responseType||"").toLowerCase();switch(l){case "arraybuffer":case "blob":var m=new Uint8Array(g.length);for(h=0;hg)e=a+1;else if(0b&&(b=0);1Number.EPSILON&&(g.normalize(), +c=Math.acos(J.clamp(d[l-1].dot(d[l]),-1,1)),e[l].applyMatrix4(h.makeRotationAxis(g,c))),f[l].crossVectors(d[l],e[l]);if(!0===b)for(c=Math.acos(J.clamp(e[0].dot(e[a]),-1,1)),c/=a,0d;)d+=c;for(;d>c;)d-=c;de&&(e=1);1E-4>d&&(d=e);1E-4>l&&(l=e);Ce.initNonuniformCatmullRom(f.x,g.x,h.x, +c.x,d,e,l);De.initNonuniformCatmullRom(f.y,g.y,h.y,c.y,d,e,l);Ee.initNonuniformCatmullRom(f.z,g.z,h.z,c.z,d,e,l)}else"catmullrom"===this.curveType&&(Ce.initCatmullRom(f.x,g.x,h.x,c.x,this.tension),De.initCatmullRom(f.y,g.y,h.y,c.y,this.tension),Ee.initCatmullRom(f.z,g.z,h.z,c.z,this.tension));b.set(Ce.calc(a),De.calc(a),Ee.calc(a));return b};pa.prototype.copy=function(a){E.prototype.copy.call(this,a);this.points=[];for(var b=0,c=a.points.length;bc.length- +2?c.length-1:a+1];c=c[a>c.length-3?c.length-1:a+2];b.set(mf(d,e.x,f.x,g.x,c.x),mf(d,e.y,f.y,g.y,c.y));return b};Pa.prototype.copy=function(a){E.prototype.copy.call(this,a);this.points=[];for(var b=0,c=a.points.length;b=b)return b=c[a]-b,a=this.curves[a],c=a.getLength(),a.getPointAt(0===c?0:1-b/c);a++}return null},getLength:function(){var a=this.getCurveLengths();return a[a.length-1]},updateArcLengths:function(){this.needsUpdate=!0;this.cacheLengths=null;this.getCurveLengths()},getCurveLengths:function(){if(this.cacheLengths&&this.cacheLengths.length===this.curves.length)return this.cacheLengths; +for(var a=[],b=0,c=0,d=this.curves.length;c=e)break a;else{f=b[1];a=e)break b}d=c;c=0}}for(;c>>1,ab;)--f;++f;if(0!==e||f!==d)e>=f&&(f=Math.max(f,1),e=f-1),a=this.getValueSize(),this.times=na.arraySlice(c,e,f),this.values=na.arraySlice(this.values,e*a,f*a);return this},validate:function(){var a=!0,b=this.getValueSize();0!==b-Math.floor(b)&&(console.error("THREE.KeyframeTrack: Invalid value size in track.",this),a=!1);var c=this.times;b=this.values; +var d=c.length;0===d&&(console.error("THREE.KeyframeTrack: Track is empty.",this),a=!1);for(var e=null,f=0;f!==d;f++){var g=c[f];if("number"===typeof g&&isNaN(g)){console.error("THREE.KeyframeTrack: Time is not a valid number.",this,f,g);a=!1;break}if(null!==e&&e>g){console.error("THREE.KeyframeTrack: Out of order keys.",this,f,g,e);a=!1;break}e=g}if(void 0!==b&&na.isTypedArray(b))for(f=0,c=b.length;f!==c;++f)if(d=b[f],isNaN(d)){console.error("THREE.KeyframeTrack: Value is not a valid number.",this, +f,d);a=!1;break}return a},optimize:function(){for(var a=this.times,b=this.values,c=this.getValueSize(),d=2302===this.getInterpolation(),e=1,f=a.length-1,g=1;gk.opacity&&(k.transparent=!0);d.setTextures(l);return d.parse(k)}}()});var Fe={decodeText:function(a){if("undefined"!==typeof TextDecoder)return(new TextDecoder).decode(a);for(var b="",c=0,d=a.length;cf;f++){var C=h[q++];var z=A[2*C];C=A[2*C+1];z=new B(z,C);2!==f&&c.faceVertexUvs[e][u].push(z);0!==f&&c.faceVertexUvs[e][u+1].push(z)}}y&&(y=3*h[q++],r.normal.set(k[y++],k[y++],k[y]),x.normal.copy(r.normal)); +if(w)for(e=0;4>e;e++)y=3*h[q++],w=new p(k[y++],k[y++],k[y]),2!==e&&r.vertexNormals.push(w),0!==e&&x.vertexNormals.push(w);n&&(n=h[q++],n=v[n],r.color.setHex(n),x.color.setHex(n));if(l)for(e=0;4>e;e++)n=h[q++],n=v[n],2!==e&&r.vertexColors.push(new G(n)),0!==e&&x.vertexColors.push(new G(n));c.faces.push(r);c.faces.push(x)}else{r=new Ya;r.a=h[q++];r.b=h[q++];r.c=h[q++];u&&(u=h[q++],r.materialIndex=u);u=c.faces.length;if(e)for(e=0;ef;f++)C=h[q++],z= +A[2*C],C=A[2*C+1],z=new B(z,C),c.faceVertexUvs[e][u].push(z);y&&(y=3*h[q++],r.normal.set(k[y++],k[y++],k[y]));if(w)for(e=0;3>e;e++)y=3*h[q++],w=new p(k[y++],k[y++],k[y]),r.vertexNormals.push(w);n&&(n=h[q++],r.color.setHex(v[n]));if(l)for(e=0;3>e;e++)n=h[q++],r.vertexColors.push(new G(v[n]));c.faces.push(r)}}d=a;q=void 0!==d.influencesPerVertex?d.influencesPerVertex:2;if(d.skinWeights)for(g=0,h=d.skinWeights.length;g +Number.EPSILON){if(0>k&&(g=b[f],l=-l,h=b[e],k=-k),!(a.yh.y))if(a.y===g.y){if(a.x===g.x)return!0}else{e=k*(a.x-g.x)-l*(a.y-g.y);if(0===e)return!0;0>e||(d=!d)}}else if(a.y===g.y&&(h.x<=a.x&&a.x<=g.x||g.x<=a.x&&a.x<=h.x))return!0}return d}var e=$a.isClockWise,f=this.subPaths;if(0===f.length)return[];if(!0===b)return c(f);b=[];if(1===f.length){var g=f[0];var h=new jb;h.curves=g.curves;b.push(h);return b}var l=!e(f[0].getPoints());l=a?!l:l;h=[];var k=[],p=[],n=0;k[n]=void 0;p[n]=[];for(var t= +0,q=f.length;td&&this._mixBufferRegion(c,a,3*b,1-d,b);d=b;for(var f=b+b;d!==f;++d)if(c[d]!==c[d+b]){e.setValue(c,a);break}},saveOriginalState:function(){var a=this.buffer,b=this.valueSize,c=3*b;this.binding.getValue(a,c);for(var d=b;d!==c;++d)a[d]=a[c+d%b];this.cumulativeWeight= +0},restoreOriginalState:function(){this.binding.setValue(this.buffer,3*this.valueSize)},_select:function(a,b,c,d,e){if(.5<=d)for(d=0;d!==e;++d)a[b+d]=a[c+d]},_slerp:function(a,b,c,d){ca.slerpFlat(a,b,a,b,a,c,d)},_lerp:function(a,b,c,d,e){for(var f=1-d,g=0;g!==e;++g){var h=b+g;a[h]=a[h]*f+a[c+g]*d}}});Object.assign(qf.prototype,{getValue:function(a,b){this.bind();var c=this._bindings[this._targetGroup.nCachedObjects_];void 0!==c&&c.getValue(a,b)},setValue:function(a,b){for(var c=this._bindings,d=this._targetGroup.nCachedObjects_, +e=c.length;d!==e;++d)c[d].setValue(a,b)},bind:function(){for(var a=this._bindings,b=this._targetGroup.nCachedObjects_,c=a.length;b!==c;++b)a[b].bind()},unbind:function(){for(var a=this._bindings,b=this._targetGroup.nCachedObjects_,c=a.length;b!==c;++b)a[b].unbind()}});Object.assign(ra,{Composite:qf,create:function(a,b,c){return a&&a.isAnimationObjectGroup?new ra.Composite(a,b,c):new ra(a,b,c)},sanitizeNodeName:function(){var a=/[\[\]\.:\/]/g;return function(b){return b.replace(/\s/g,"_").replace(a, +"")}}(),parseTrackName:function(){var a="[^"+"\\[\\]\\.:\\/".replace("\\.","")+"]",b=/((?:WC+[\/:])*)/.source.replace("WC","[^\\[\\]\\.:\\/]");a=/(WCOD+)?/.source.replace("WCOD",a);var c=/(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace("WC","[^\\[\\]\\.:\\/]"),d=/\.(WC+)(?:\[(.+)\])?/.source.replace("WC","[^\\[\\]\\.:\\/]"),e=new RegExp("^"+b+a+c+d+"$"),f=["material","materials","bones"];return function(a){var b=e.exec(a);if(!b)throw Error("PropertyBinding: Cannot parse trackName: "+a);b={nodeName:b[2], +objectName:b[3],objectIndex:b[4],propertyName:b[5],propertyIndex:b[6]};var c=b.nodeName&&b.nodeName.lastIndexOf(".");if(void 0!==c&&-1!==c){var d=b.nodeName.substring(c+1);-1!==f.indexOf(d)&&(b.nodeName=b.nodeName.substring(0,c),b.objectName=d)}if(null===b.propertyName||0===b.propertyName.length)throw Error("PropertyBinding: can not parse propertyName from trackName: "+a);return b}}(),findNode:function(a,b){if(!b||""===b||"root"===b||"."===b||-1===b||b===a.name||b===a.uuid)return a;if(a.skeleton){var c= +a.skeleton.getBoneByName(b);if(void 0!==c)return c}if(a.children){var d=function(a){for(var c=0;c=b){var p=b++,n=a[p];c[n.uuid]=k;a[k]=n;c[l]=p;a[p]=h;h=0;for(l=e;h!==l;++h){n=d[h];var t=n[k];n[k]=n[p];n[p]=t}}}this.nCachedObjects_=b},uncache:function(){for(var a=this._objects,b=a.length,c=this.nCachedObjects_,d=this._indicesByUUID,e=this._bindings,f=e.length,g=0,h=arguments.length;g!==h;++g){var l= +arguments[g].uuid,k=d[l];if(void 0!==k)if(delete d[l],kb||0===c)return;this._startTime=null;b*=c}b*=this._updateTimeScale(a);c=this._updateTime(b);a=this._updateWeight(a);if(0c.parameterPositions[1]&&(this.stopFading(),0===d&&(this.enabled=!1))}}return this._effectiveWeight=b},_updateTimeScale:function(a){var b=0;if(!this.paused){b=this.timeScale;var c=this._timeScaleInterpolant;if(null!==c){var d=c.evaluate(a)[0];b*=d;a>c.parameterPositions[1]&&(this.stopWarping(),0===b?this.paused=!0:this.timeScale=b)}}return this._effectiveTimeScale=b},_updateTime:function(a){var b=this.time+a;if(0===a)return b;var c=this._clip.duration,d=this.loop,e=this._loopCount;if(2200=== +d)a:{if(-1===e&&(this._loopCount=0,this._setEndings(!0,!0,!1)),b>=c)b=c;else if(0>b)b=0;else break a;this.clampWhenFinished?this.paused=!0:this.enabled=!1;this._mixer.dispatchEvent({type:"finished",action:this,direction:0>a?-1:1})}else{d=2202===d;-1===e&&(0<=a?(e=0,this._setEndings(!0,0===this.repetitions,d)):this._setEndings(0===this.repetitions,!0,d));if(b>=c||0>b){var f=Math.floor(b/c);b-=c*f;e+=Math.abs(f);var g=this.repetitions-e;0>=g?(this.clampWhenFinished?this.paused=!0:this.enabled=!1,b= +0a,this._setEndings(a,!a,d)):this._setEndings(!1,!1,d),this._loopCount=e,this._mixer.dispatchEvent({type:"loop",action:this,loopDelta:f}))}if(d&&1===(e&1))return this.time=b,c-b}return this.time=b},_setEndings:function(a,b,c){var d=this._interpolantSettings;c?(d.endingStart=2401,d.endingEnd=2401):(d.endingStart=a?this.zeroSlopeAtStart?2401:2400:2402,d.endingEnd=b?this.zeroSlopeAtEnd?2401:2400:2402)},_scheduleFading:function(a, +b,c){var d=this._mixer,e=d.time,f=this._weightInterpolant;null===f&&(this._weightInterpolant=f=d._lendControlInterpolant());d=f.parameterPositions;f=f.sampleValues;d[0]=e;f[0]=b;d[1]=e+a;f[1]=c;return this}});te.prototype=Object.assign(Object.create(za.prototype),{constructor:te,_bindAction:function(a,b){var c=a._localRoot||this._root,d=a._clip.tracks,e=d.length,f=a._propertyBindings;a=a._interpolants;var g=c.uuid,h=this._bindingsByRootAndName,k=h[g];void 0===k&&(k={},h[g]=k);for(h=0;h!==e;++h){var m= +d[h],p=m.name,n=k[p];if(void 0===n){n=f[h];if(void 0!==n){null===n._cacheIndex&&(++n.referenceCount,this._addInactiveBinding(n,g,p));continue}n=new se(ra.create(c,p,b&&b._propertyBindings[h].binding.parsedPath),m.ValueTypeName,m.getValueSize());++n.referenceCount;this._addInactiveBinding(n,g,p)}f[h]=n;a[h].resultBuffer=n.buffer}},_activateAction:function(a){if(!this._isActiveAction(a)){if(null===a._cacheIndex){var b=(a._localRoot||this._root).uuid,c=a._clip.uuid,d=this._actionsByClip[c];this._bindAction(a, +d&&d.knownActions[0]);this._addInactiveAction(a,c,b)}b=a._propertyBindings;c=0;for(d=b.length;c!==d;++c){var e=b[c];0===e.useCount++&&(this._lendBinding(e),e.saveOriginalState())}this._lendAction(a)}},_deactivateAction:function(a){if(this._isActiveAction(a)){for(var b=a._propertyBindings,c=0,d=b.length;c!==d;++c){var e=b[c];0===--e.useCount&&(e.restoreOriginalState(),this._takeBackBinding(e))}this._takeBackAction(a)}},_initMemoryManager:function(){this._actions=[];this._nActiveActions=0;this._actionsByClip= +{};this._bindings=[];this._nActiveBindings=0;this._bindingsByRootAndName={};this._controlInterpolants=[];this._nActiveControlInterpolants=0;var a=this;this.stats={actions:{get total(){return a._actions.length},get inUse(){return a._nActiveActions}},bindings:{get total(){return a._bindings.length},get inUse(){return a._nActiveBindings}},controlInterpolants:{get total(){return a._controlInterpolants.length},get inUse(){return a._nActiveControlInterpolants}}}},_isActiveAction:function(a){a=a._cacheIndex; +return null!==a&&athis.max.x||a.ythis.max.y?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<= +this.max.y},getParameter:function(a,b){void 0===b&&(console.warn("THREE.Box2: .getParameter() target is now required"),b=new B);return b.set((a.x-this.min.x)/(this.max.x-this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y))},intersectsBox:function(a){return a.max.xthis.max.x||a.max.ythis.max.y?!1:!0},clampPoint:function(a,b){void 0===b&&(console.warn("THREE.Box2: .clampPoint() target is now required"),b=new B);return b.copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a= +new B;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)}});ld.prototype=Object.create(H.prototype);ld.prototype.constructor=ld;ld.prototype.isImmediateRenderObject=!0;md.prototype=Object.create(Y.prototype); +md.prototype.constructor=md;md.prototype.update=function(){var a=new p,b=new p,c=new la;return function(){var d=["a","b","c"];this.object.updateMatrixWorld(!0);c.getNormalMatrix(this.object.matrixWorld);var e=this.object.matrixWorld,f=this.geometry.attributes.position,g=this.object.geometry;if(g&&g.isGeometry)for(var h=g.vertices,k=g.faces,m=g=0,p=k.length;mMath.abs(b)&&(b=1E-8);this.scale.set(.5*this.size,.5*this.size,b);this.children[0].material.side=0>b?1:0;this.lookAt(this.plane.normal);H.prototype.updateMatrixWorld.call(this,a)};var Sd,Ae;Jb.prototype=Object.create(H.prototype);Jb.prototype.constructor=Jb;Jb.prototype.setDirection= +function(){var a=new p,b;return function(c){.99999c.y?this.quaternion.set(1,0,0,0):(a.set(c.z,0,-c.x).normalize(),b=Math.acos(c.y),this.quaternion.setFromAxisAngle(a,b))}}();Jb.prototype.setLength=function(a,b,c){void 0===b&&(b=.2*a);void 0===c&&(c=.2*b);this.line.scale.set(1,Math.max(0,a-b),1);this.line.updateMatrix();this.cone.scale.set(c,b,c);this.cone.position.y=a;this.cone.updateMatrix()};Jb.prototype.setColor=function(a){this.line.material.color.copy(a); +this.cone.material.color.copy(a)};sd.prototype=Object.create(Y.prototype);sd.prototype.constructor=sd;E.create=function(a,b){console.log("THREE.Curve.create() has been deprecated");a.prototype=Object.create(E.prototype);a.prototype.constructor=a;a.prototype.getPoint=b;return a};Object.assign(bb.prototype,{createPointsGeometry:function(a){console.warn("THREE.CurvePath: .createPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.");a=this.getPoints(a);return this.createGeometry(a)}, +createSpacedPointsGeometry:function(a){console.warn("THREE.CurvePath: .createSpacedPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.");a=this.getSpacedPoints(a);return this.createGeometry(a)},createGeometry:function(a){console.warn("THREE.CurvePath: .createGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.");for(var b=new P,c=0,d=a.length;c .dropdown { + position: absolute; + background-color: white; + color: #333333; + width: 100%; + + overflow: hidden; + + transition: all 0.3s; + max-height: 0px; +} + +.dropdown-container:hover > .dropdown { + max-height: 200px; + border-color: #ddddddFF; +} + +.dropdown-container > .dropdown > ul { + margin: 0; + padding: 0; + list-style: none; +} + +.dropdown-container > .dropdown > ul > li { + cursor: pointer; +} + +.dropdown-container > .dropdown > ul > li:hover { + color: #dddddd; + background-color: #333333; +} + +#alert-box { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + align-content: center; + justify-content: center; + flex-wrap: wrap; + flex-direction: column; + pointer-events: none; +} + +#alert-box h1 { + font-size: 1.4rem; + font-weight: bold; + + margin: 0; + padding: 15px; + text-align: center; +} + +#alert-box h2 { + font-size: 1.2rem; + font-weight: bold; + + margin: 0; + padding: 15px 0 5px 0; + text-align: left; +} + +#alert-box a { + color: #333333; + text-decoration: underline; +} + +#alert-box a:hover { + color: #888888; +} + +#alert-box .alert { + position: relative; + pointer-events: all; + margin: 10px; + padding: 10px; +} + +#alert-box .alert .alert-close-button { + /*position: absolute; + top: 5px; + right: 5px; + */ + margin: -10px -10px 0px 0px; + padding: 0 0 5px 5px; + float: right; + width: 15px; + height: 15px; + line-height: 15px; + font-weight: bold; + font-size: 15px; + color: #333333; +} + +#alert-box .alert .alert-close-button::after { + content: 'x'; +} + +#alert-box .alert .alert-close-button:hover { + color: #dd3333; +} + +#map-container { + position: absolute; + width: 100%; + height: 100%; + background-color: black; + overflow: hidden; +} + +#map-container canvas { + width: 100%; + height: 100%; +} + +#bluemap-loading { + position: absolute; + width: 200px; + line-height: 20px; + padding: 20px 0; + top: calc(50% - 31px); + left: calc(50% - 101px); + text-align: center; +} + +#bluemap-topright { + position: absolute; + top: 10px; + right: 10px; + + line-height: 30px; + + display: flex; +} + +#bluemap-topright > *:not(:last-child) { + border-right: solid 1px #dddddd; +} + +#bluemap-topleft { + position: absolute; + top: 10px; + left: 10px; + + line-height: 30px; + + display: flex; +} + +#bluemap-topleft > *:not(:last-child) { + border-right: solid 1px #dddddd; +} + +#bluemap-mapmenu { + width: 200px; +} + +#bluemap-mapmenu .selection, #bluemap-mapmenu .dropdown li { + padding-left: 10px; +} + +#bluemap-compass { + width: 30px; + height: 30px; +} + +#bluemap-compass:hover #bluemap-compass-needle { + filter: invert(1); +} + +.bluemap-position { + position: relative; + + width: 60px; + height: 30px; + padding: 0 5px 0 25px; +} + +.bluemap-position::before { + position: absolute; + left: 7px; + color: #888888; +} + +.bluemap-position.pos-x::before { + content: "x:"; +} + +.bluemap-position.pos-y::before { + content: "y:"; +} + +.bluemap-position.pos-z::before { + content: "z:"; +} + +#bluemap-settings { + width: 30px; + height: 30px; +} + +#bluemap-settings.active:not(:hover) { + background-color: #dddddd; +} + +#bluemap-settings:hover > img { + filter: invert(1); +} + +#bluemap-settings-container { + display: flex; + white-space: nowrap; +} + +#bluemap-settings-quality { + width: 150px; + height: 30px; + +} + +#bluemap-settings-quality .selection, #bluemap-settings-quality .dropdown li { + padding-left: 10px; +} + +#bluemap-settings-render-distance { + width: 180px; +} + +#bluemap-settings-render-distance .selection { + padding-left: 10px; +} + +#bluemap-settings-render-distance input { + width: calc(100% - 20px); + margin: 10px; + padding: 0; +} + +#bluemap-info { + width: 30px; + height: 30px; + text-align: center; + +} + +#bluemap-info::after { + content: 'i'; + font-weight: bold; +} diff --git a/BlueMapSponge/build.gradle b/BlueMapSponge/build.gradle new file mode 100644 index 00000000..078fd8be --- /dev/null +++ b/BlueMapSponge/build.gradle @@ -0,0 +1,4 @@ +dependencies { + shadow "org.spongepowered:spongeapi:7.2.0-SNAPSHOT" + compile project(':BlueMapCore') +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/Slf4jLogger.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/Slf4jLogger.java new file mode 100644 index 00000000..0333f8f0 --- /dev/null +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/Slf4jLogger.java @@ -0,0 +1,59 @@ +/* + * This file is part of BlueMapSponge, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge; + +import org.slf4j.Logger; + +import de.bluecolored.bluemap.core.logger.AbstractLogger; + +public class Slf4jLogger extends AbstractLogger { + + private Logger out; + + public Slf4jLogger(Logger out) { + this.out = out; + } + + @Override + public void logError(String message, Throwable throwable) { + out.error(message, throwable); + } + + @Override + public void logWarning(String message) { + out.warn(message); + } + + @Override + public void logInfo(String message) { + out.info(message); + } + + @Override + public void logDebug(String message) { + out.debug(message); + } + +} diff --git a/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java new file mode 100644 index 00000000..bcf2033c --- /dev/null +++ b/BlueMapSponge/src/main/java/de/bluecolored/bluemap/sponge/SpongePlugin.java @@ -0,0 +1,70 @@ +/* + * This file is part of BlueMap, licensed under the MIT License (MIT). + * + * Copyright (c) Blue (Lukas Rieger) + * 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.sponge; + +import java.nio.file.Path; + +import javax.inject.Inject; + +import org.spongepowered.api.config.ConfigDir; +import org.spongepowered.api.plugin.Plugin; + +import de.bluecolored.bluemap.core.logger.Logger; + +@Plugin( + id = SpongePlugin.PLUGIN_ID, + name = SpongePlugin.PLUGIN_NAME, + authors = { "Blue (Lukas Rieger)" }, + description = "This plugin provides a fully 3D map of your world for your browser!", + version = SpongePlugin.PLUGIN_VERSION + ) +public class SpongePlugin { + + public static final String PLUGIN_ID = "bluemap"; + public static final String PLUGIN_NAME = "BlueMap"; + public static final String PLUGIN_VERSION = "0.0.0"; + + private static Object plugin; + + @Inject + @ConfigDir(sharedRoot = false) + private Path configurationDir; + + @Inject + public SpongePlugin(org.slf4j.Logger logger) { + plugin = this; + + Logger.global = new Slf4jLogger(logger); + } + + public Path getConfigPath(){ + return configurationDir; + } + + public static Object getPlugin() { + return plugin; + } + +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..0a98021f --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) Blue +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. diff --git a/README.md b/README.md new file mode 100644 index 00000000..2bfea8d3 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# BlueMap +**Currently not stable or finished!** + +A minecraft mapping tool that creates 3D models of your Minecraft worlds and displays them in a [web viewer](https://bluecolored.de/bluemap/). + +## Clone +Easy: + +`git clone https://github.com/BlueMap-Minecraft/BlueMap.git` + +## Build +In order to build BlueMap you simply need to run the `./gradlew shadowJar` command. +You can find the compiled JAR file in `./build/libs` + +## Contributing +You are welcome to contribute! +Just create a pull request with your changes :) diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..4faa73ab --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'java' +} + +allprojects { + repositories { + mavenCentral() + maven { + url 'https://jitpack.io' + } + maven { + name 'sponge' + url 'http://repo.spongepowered.org/maven' + } + } + + compileJava.options.compilerArgs.add '-parameters' + compileTestJava.options.compilerArgs.add '-parameters' + + apply plugin: 'java' + apply plugin: 'com.github.johnrengelman.shadow' +} + +dependencies { + compile project(':BlueMapCore') + compile project(':BlueMapCLI') + compile project(':BlueMapSponge') +} + +shadowJar { + baseName = 'BlueMap' + version = '0.0.1' + classifier = null +} + +jar { + manifest { + attributes 'Main-Class' : "de.bluecolored.bluemap.cli.BlueMapCLI" + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..5c2d1cf0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..5028f28f --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..83f2acfd --- /dev/null +++ b/gradlew @@ -0,0 +1,188 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..9618d8d9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..1a85cd28 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,8 @@ +rootProject.name = 'BlueMap' +include ':BlueMapCore' +include ':BlueMapCLI' +include ':BlueMapSponge' + +project(':BlueMapCore').projectDir = "$rootDir/BlueMapCore" as File +project(':BlueMapCLI').projectDir = "$rootDir/BlueMapCLI" as File +project(':BlueMapSponge').projectDir = "$rootDir/BlueMapSponge" as File \ No newline at end of file