From d88a25ad47c0456558ac25856af6119c7206c600 Mon Sep 17 00:00:00 2001 From: "Blue (Lukas Rieger)" Date: Sat, 17 Jul 2021 16:06:50 +0200 Subject: [PATCH] Model-Rewrite: Complete, further performance improvements in progress --- .../bluemap/common/BlueMapService.java | 1 + .../common/plugin/commands/Commands.java | 6 +- .../bluemap/core/config/BlockIdConfig.java | 5 +- .../core/config/BlockPropertiesConfig.java | 10 +- .../bluemap/core/config/ConfigManager.java | 2 +- .../bluemap/core/debug/OneBlockWorld.java | 121 ++++ .../bluecolored/bluemap/core/map/BmMap.java | 6 +- .../core/map/hires/BlockModelView.java | 129 ++++ .../bluemap/core/map/hires/HiresModel.java | 88 --- .../core/map/hires/HiresModelManager.java | 57 +- .../core/map/hires/HiresModelRenderer.java | 107 ++-- .../bluemap/core/map/hires/HiresTileMeta.java | 82 +++ .../core/map/hires/HiresTileModel.java | 509 ++++++++++++++- .../map/hires/blockmodel/BlockStateModel.java | 70 --- .../blockmodel/BlockStateModelFactory.java | 85 +-- .../hires/blockmodel/LiquidModelBuilder.java | 440 ++++++++----- .../blockmodel/ResourceModelBuilder.java | 589 +++++++++--------- .../bluemap/core/map/lowres/LowresModel.java | 23 +- .../core/map/lowres/LowresModelManager.java | 105 ++-- .../bluemap/core/mca/ChunkAnvil112.java | 35 +- .../bluemap/core/mca/ChunkAnvil113.java | 53 +- .../bluemap/core/mca/ChunkAnvil115.java | 54 +- .../bluemap/core/mca/ChunkAnvil116.java | 85 ++- .../bluemap/core/mca/EmptyChunk.java | 8 +- .../bluemap/core/mca/MCAChunk.java | 19 +- .../bluemap/core/mca/MCAWorld.java | 63 +- .../bluemap/core/model/HiresTileModel.java | 128 ---- .../bluemap/core/model/TileModel.java | 208 ------- .../resourcepack/BlockColorCalculator.java | 224 ------- .../BlockColorCalculatorFactory.java | 251 ++++++++ .../core/resourcepack/BlockModelResource.java | 44 +- .../core/resourcepack/BlockStateResource.java | 35 +- .../core/resourcepack/ResourcePack.java | 22 +- .../bluemap/core/resourcepack/Texture.java | 23 +- .../core/resourcepack/TextureGallery.java | 38 +- .../TransformedBlockModelResource.java | 31 +- .../bluemap/core/util/ArrayPool.java | 59 ++ .../bluemap/core/util/Direction.java | 7 +- .../bluemap/core/util/IntersectionPoint.java | 47 -- .../bluemap/core/util/MathUtils.java | 23 +- .../bluemap/core/util/ModelUtils.java | 1 + .../bluemap/core/util/{ => math}/Axis.java | 2 +- .../bluemap/core/util/math/Color.java | 125 ++++ .../bluemap/core/util/math/MatrixM3f.java | 186 ++++++ .../bluemap/core/util/math/MatrixM4f.java | 228 +++++++ .../bluemap/core/util/math/VectorM2f.java | 84 +++ .../bluemap/core/util/math/VectorM2i.java | 95 +++ .../bluemap/core/util/math/VectorM3f.java | 84 +++ .../bluecolored/bluemap/core/world/Biome.java | 27 +- .../bluecolored/bluemap/core/world/Block.java | 201 +++--- .../bluemap/core/world/BlockState.java | 96 ++- .../bluecolored/bluemap/core/world/Chunk.java | 12 + .../bluemap/core/world/LightData.java | 13 +- .../bluemap/core/world/SlicedWorld.java | 49 +- .../bluecolored/bluemap/core/world/World.java | 26 +- .../bluemap/mc1_12/blockColors.json | 6 +- .../bluemap/mc1_13/blockColors.json | 6 +- .../bluemap/mc1_15/blockColors.json | 6 +- .../bluemap/mc1_16/blockColors.json | 9 +- .../bluemap/core/world/BlockStateTest.java | 13 +- 60 files changed, 3295 insertions(+), 1866 deletions(-) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/OneBlockWorld.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/BlockModelView.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileMeta.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/HiresTileModel.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/TileModel.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculatorFactory.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ArrayPool.java delete mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntersectionPoint.java rename BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/{ => math}/Axis.java (97%) create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Color.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM3f.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM4f.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2f.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2i.java create mode 100644 BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM3f.java diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java index ae7229b8..43af4672 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/BlueMapService.java @@ -30,6 +30,7 @@ import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.config.*; import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.debug.OneBlockWorld; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.map.BmMap; import de.bluecolored.bluemap.core.map.hires.RenderSettings; diff --git a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java index fb981649..7b916886 100644 --- a/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java +++ b/BlueMapCommon/src/main/java/de/bluecolored/bluemap/common/plugin/commands/Commands.java @@ -529,14 +529,14 @@ public int debugBlockCommand(CommandContext context) { new Thread(() -> { // collect and output debug info Vector3i blockPos = position.floor().toInt(); - Block block = world.getBlock(blockPos); - Block blockBelow = world.getBlock(blockPos.add(0, -1, 0)); + Block block = new Block(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()); + Block blockBelow = new Block(null, 0, 0, 0).copy(block).add(0, -1, 0); String blockIdMeta = ""; String blockBelowIdMeta = ""; if (world instanceof MCAWorld) { - MCAChunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos)); + MCAChunk chunk = ((MCAWorld) world).getChunkAtBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ()); if (chunk instanceof ChunkAnvil112) { blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")"; blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")"; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java index 5535c911..1d0fd0fa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockIdConfig.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.config; +import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; import de.bluecolored.bluemap.core.world.BlockState; @@ -76,7 +77,7 @@ public BlockIdConfig(ConfigurationNode node, ConfigurationLoader= 0) { BlockNumeralIDMeta idmeta = new BlockNumeralIDMeta(blockNumeralId, blockMeta); @@ -160,7 +161,7 @@ public BlockState get(String id, int numeralId, int meta) { state = idMappings.get(new BlockIDMeta(id, 0)); if (state == null) { state = numeralMappings.get(new BlockNumeralIDMeta(numeralId, 0)); - if (state == null) state = new BlockState(id); + if (state == null) state = new BlockState(MinecraftVersion.EARLIEST_SUPPORTED, id); } idMappings.put(idmeta, state); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java index 1a72e9c7..8654aee7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/BlockPropertiesConfig.java @@ -27,6 +27,7 @@ import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.LoadingCache; import de.bluecolored.bluemap.core.BlueMap; +import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; @@ -67,7 +68,7 @@ public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack, for (Entry e : node.childrenMap().entrySet()){ String key = e.getKey().toString(); try { - BlockState bsKey = BlockState.fromString(key); + BlockState bsKey = BlockState.fromString(resourcePack.getMinecraftVersion(), key); BlockProperties bsValue = new BlockProperties( e.getValue().node("culling").getBoolean(true), e.getValue().node("occluding").getBoolean(true), @@ -99,13 +100,14 @@ private BlockProperties mapNoCache(BlockState bs){ } BlockProperties generated = BlockProperties.SOLID; + MinecraftVersion version = MinecraftVersion.LATEST_SUPPORTED; if (resourcePack != null) { try { boolean culling = false; boolean occluding = false; - for(TransformedBlockModelResource model : resourcePack.getBlockStateResource(bs).getModels(bs)) { + for(TransformedBlockModelResource model : resourcePack.getBlockStateResource(bs).getModels(bs, new ArrayList<>(10))) { culling = culling || model.getModel().isCulling(); occluding = occluding || model.getModel().isOccluding(); if (culling && occluding) break; @@ -113,9 +115,11 @@ private BlockProperties mapNoCache(BlockState bs){ generated = new BlockProperties(culling, occluding, generated.isFlammable()); } catch (NoSuchResourceException ignore) {} //ignoring this because it will be logged later again if we try to render that block + + version = resourcePack.getMinecraftVersion(); } - mappings.computeIfAbsent(bs.getFullId(), k -> new ArrayList<>()).add(new BlockStateMapping<>(new BlockState(bs.getFullId()), generated)); + mappings.computeIfAbsent(bs.getFullId(), k -> new ArrayList<>()).add(new BlockStateMapping<>(new BlockState(version, bs.getFullId()), generated)); if (autopoulationConfigLoader != null) { synchronized (autopoulationConfigLoader) { try { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java index 79f4883d..03f52c96 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/config/ConfigManager.java @@ -129,7 +129,7 @@ public void loadResourceConfigs(File configFolder, ResourcePack resourcePack) th false ); blockColorsConfigNode = joinFromResourcePack(resourcePack, "blockColors.json", blockColorsConfigNode); - resourcePack.getBlockColorCalculator().loadColorConfig(blockColorsConfigNode); + resourcePack.getBlockColorCalculatorFactory().loadColorConfig(blockColorsConfigNode); //load blockIds.json from resources, config-folder and resourcepack URL blockIdsConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResource().getResourcePrefix() + "/blockIds.json"); diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/OneBlockWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/OneBlockWorld.java new file mode 100644 index 00000000..439ea52a --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/debug/OneBlockWorld.java @@ -0,0 +1,121 @@ +package de.bluecolored.bluemap.core.debug; + +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper; +import de.bluecolored.bluemap.core.world.*; + +import java.nio.file.Path; +import java.util.Collection; +import java.util.UUID; + +public class OneBlockWorld implements World { + + private final World delegate; + + public OneBlockWorld(World delegate) { + this.delegate = delegate; + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public UUID getUUID() { + return delegate.getUUID(); + } + + @Override + public Path getSaveFolder() { + return delegate.getSaveFolder(); + } + + @Override + public int getSeaLevel() { + return 64; + } + + @Override + public Vector3i getSpawnPoint() { + return new Vector3i(0, 70, 0); + } + + @Override + public int getMaxY(int x, int z) { + return 255; + } + + @Override + public int getMinY(int x, int z) { + return 0; + } + + @Override + public Grid getChunkGrid() { + return delegate.getChunkGrid(); + } + + @Override + public Grid getRegionGrid() { + return delegate.getRegionGrid(); + } + + @Override + public Biome getBiome(int x, int y, int z) { + return Biome.DEFAULT; + } + + @Override + public BlockState getBlockState(int x, int y, int z) { + if (x == 0 && z == 0 && y == 70) return BlockState.MISSING; + return BlockState.AIR; + } + + @Override + public BlockProperties getBlockProperties(BlockState blockState) { + return delegate.getBlockProperties(blockState); + } + + @Override + public Chunk getChunkAtBlock(int x, int y, int z) { + return delegate.getChunkAtBlock(x, y, z); + } + + @Override + public Chunk getChunk(int x, int z) { + return delegate.getChunk(x, z); + } + + @Override + public Region getRegion(int x, int z) { + return delegate.getRegion(x, z); + } + + @Override + public Collection listRegions() { + return delegate.listRegions(); + } + + @Override + public void invalidateChunkCache() { + delegate.invalidateChunkCache(); + } + + @Override + public void invalidateChunkCache(int x, int z) { + delegate.invalidateChunkCache(x, z); + } + + @Override + public void cleanUpChunkCache() { + delegate.cleanUpChunkCache(); + } + + @Override + public BlockPropertiesMapper getBlockPropertiesMapper() { + return delegate.getBlockPropertiesMapper(); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java index 25be6188..266660d1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/BmMap.java @@ -27,9 +27,9 @@ import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.core.debug.DebugDump; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.map.hires.HiresModel; import de.bluecolored.bluemap.core.map.hires.HiresModelManager; import de.bluecolored.bluemap.core.map.lowres.LowresModelManager; +import de.bluecolored.bluemap.core.map.hires.HiresTileMeta; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.world.Grid; import de.bluecolored.bluemap.core.world.World; @@ -103,8 +103,8 @@ public void renderTile(Vector2i tile) { long start = System.nanoTime(); - HiresModel hiresModel = hiresModelManager.render(world, tile); - lowresModelManager.render(hiresModel); + HiresTileMeta tileMeta = hiresModelManager.render(world, tile); + lowresModelManager.render(tileMeta); long end = System.nanoTime(); long delta = end - start; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/BlockModelView.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/BlockModelView.java new file mode 100644 index 00000000..fdddcb59 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/BlockModelView.java @@ -0,0 +1,129 @@ +package de.bluecolored.bluemap.core.map.hires; + +import de.bluecolored.bluemap.core.util.math.MatrixM3f; +import de.bluecolored.bluemap.core.util.math.MatrixM4f; + +public class BlockModelView { + + private HiresTileModel hiresTile; + private int start, size; + + public BlockModelView(HiresTileModel hiresTile) { + initialize(hiresTile); + } + + public BlockModelView initialize(HiresTileModel hiresTile, int start) { + this.hiresTile = hiresTile; + this.start = start; + this.size = hiresTile.size() - start; + + return this; + } + + public BlockModelView initialize(HiresTileModel hiresTile) { + this.hiresTile = hiresTile; + this.start = hiresTile.size(); + this.size = 0; + + return this; + } + + public BlockModelView initialize(int start) { + this.start = start; + this.size = hiresTile.size() - start; + + return this; + } + + public BlockModelView initialize() { + this.start = hiresTile.size(); + this.size = 0; + + return this; + } + + public BlockModelView reset() { + hiresTile.reset(this.start); + this.size = 0; + + return this; + } + + public int add(int count) { + int s = hiresTile.add(count); + if (s != start + size) throw new IllegalStateException("Size of HiresTileModel had external changes since view-initialisation!"); + this.size += count; + return s; + } + + public BlockModelView rotate(float angle, float axisX, float axisY, float axisZ) { + hiresTile.rotate(start, size, angle, axisX, axisY, axisZ); + return this; + } + + public BlockModelView rotate(float pitch, float yaw, float roll) { + hiresTile.rotate(start, size, pitch, yaw, roll); + return this; + } + + public BlockModelView scale(double sx, double sy, double sz) { + hiresTile.scale(start, size, sx, sy, sz); + return this; + } + + public BlockModelView translate(double dx, double dy, double dz) { + hiresTile.translate(start, size, dx, dy, dz); + return this; + } + + public BlockModelView transform(MatrixM3f t) { + hiresTile.transform(start, size, t); + return this; + } + + public BlockModelView transform( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 + ) { + hiresTile.transform(start, size, + m00, m01, m02, + m10, m11, m12, + m20, m21, m22 + ); + return this; + } + + public BlockModelView transform(MatrixM4f t) { + hiresTile.transform(start, size, t); + return this; + } + + public BlockModelView transform( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 + ) { + hiresTile.transform(start, size, + m00, m01, m02, m03, + m10, m11, m12, m13, + m20, m21, m22, m23, + m30, m31, m32, m33 + ); + return this; + } + + public HiresTileModel getHiresTile() { + return hiresTile; + } + + public int getStart() { + return start; + } + + public int getSize() { + return size; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java deleted file mode 100644 index bbe01eb6..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModel.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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.map.hires; - -import com.flowpowered.math.vector.Vector3i; -import com.flowpowered.math.vector.Vector4f; -import de.bluecolored.bluemap.core.model.ExtendedModel; - -import java.util.UUID; - -/** - * A model, containing additional information about the tile it represents - */ -public class HiresModel extends ExtendedModel { - - private UUID world; - private Vector3i blockMin, blockMax, blockSize; - - private int[][] heights; - private Vector4f[][] colors; - - public HiresModel(UUID world, Vector3i blockMin, Vector3i blockMax) { - this.world = world; - 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){ - Vector4f color = colors[x - blockMin.getX()][z - blockMin.getZ()]; - if (color == null) return Vector4f.ZERO; - return color; - } - - 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; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java index 43f177a6..dc5eba6f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelManager.java @@ -25,7 +25,6 @@ package de.bluecolored.bluemap.core.map.hires; 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.resourcepack.ResourcePack; @@ -62,39 +61,43 @@ public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Grid tileGr /** * Renders the given world tile with the provided render-settings */ - public HiresModel render(World world, Vector2i tile) { + public HiresTileMeta render(World world, Vector2i tile) { Vector2i tileMin = tileGrid.getCellMin(tile); Vector2i tileMax = tileGrid.getCellMax(tile); Vector3i modelMin = new Vector3i(tileMin.getX(), Integer.MIN_VALUE, tileMin.getY()); Vector3i modelMax = new Vector3i(tileMax.getX(), Integer.MAX_VALUE, tileMax.getY()); - HiresModel model = renderer.render(world, modelMin, modelMax); + HiresTileModel model = HiresTileModel.claimInstance(); + + HiresTileMeta tileMeta = renderer.render(world, modelMin, modelMax, model); save(model, tile); - return model; + + HiresTileModel.recycleInstance(model); + + return tileMeta; } - private void save(final HiresModel model, Vector2i tile) { - final String modelJson = model.toBufferGeometry().toJson(); - save(modelJson, tile); - } - - private void save(String modelJson, Vector2i tile){ + private void save(final HiresTileModel model, Vector2i tile) { File file = getFile(tile, useGzip); - + + OutputStream os = null; try { - OutputStream os = new BufferedOutputStream(AtomicFileHelper.createFilepartOutputStream(file)); + os = AtomicFileHelper.createFilepartOutputStream(file); + os = new BufferedOutputStream(os); if (useGzip) os = new GZIPOutputStream(os); - OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); - try ( - PrintWriter pw = new PrintWriter(osw); - ){ - pw.print(modelJson); - } - - //logger.logDebug("Saved hires model: " + model.getTile()); + + model.writeBufferGeometryJson(os); } catch (IOException e){ Logger.global.logError("Failed to save hires model: " + file, e); + } finally { + try { + if (os != null) { + os.close(); + } + } catch (IOException e) { + Logger.global.logError("Failed to close file: " + file, e); + } } } @@ -105,20 +108,6 @@ public Grid getTileGrid() { return tileGrid; } - /** - * Converts a block-position to a map-tile-coordinate - */ - public Vector2i posToTile(Vector3i pos){ - return tileGrid.getCell(pos.toVector2(true)); - } - - /** - * Converts a block-position to a map-tile-coordinate - */ - public Vector2i posToTile(Vector3d pos){ - return tileGrid.getCell(new Vector2i(pos.getFloorX(), pos.getFloorZ())); - } - /** * Returns the file for a tile */ diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java index f31114d3..06d1498d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresModelRenderer.java @@ -24,100 +24,109 @@ */ package de.bluecolored.bluemap.core.map.hires; -import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3i; -import com.flowpowered.math.vector.Vector4f; -import de.bluecolored.bluemap.core.MinecraftVersion; -import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModel; import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.util.MathUtils; +import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.World; public class HiresModelRenderer { - private final String grassId; - + private final ResourcePack resourcePack; private final RenderSettings renderSettings; - private final BlockStateModelFactory modelFactory; public HiresModelRenderer(ResourcePack resourcePack, RenderSettings renderSettings) { this.renderSettings = renderSettings; - this.modelFactory = new BlockStateModelFactory(resourcePack, renderSettings); - - if (resourcePack.getMinecraftVersion().isBefore(MinecraftVersion.THE_FLATTENING)) { - grassId = "minecraft:tall_grass"; - } else { - grassId = "minecraft:grass"; - } + this.resourcePack = resourcePack; } - public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) { + public HiresTileMeta render(World world, Vector3i modelMin, Vector3i modelMax, HiresTileModel model) { Vector3i min = modelMin.max(renderSettings.getMin()); Vector3i max = modelMax.min(renderSettings.getMax()); - Vector3f modelAnchor = new Vector3f(modelMin.getX(), 0, modelMin.getZ()); - - HiresModel model = new HiresModel(world.getUUID(), modelMin, modelMax); - - for (int x = min.getX(); x <= max.getX(); x++){ - for (int z = min.getZ(); z <= max.getZ(); z++){ + Vector3i modelAnchor = new Vector3i(modelMin.getX(), 0, modelMin.getZ()); - int maxHeight = 0; - Vector4f color = Vector4f.ZERO; + HiresTileMeta tileMeta = new HiresTileMeta(modelMin.getX(), modelMin.getZ(), modelMax.getX(), modelMax.getZ()); //TODO: recycle tilemeta instances? - int minY = Math.max(min.getY(), world.getMinY(x, z)); - int maxY = Math.min(max.getY(), world.getMaxY(x, z)); + // create new for each tile-render since the factory is not threadsafe + BlockStateModelFactory modelFactory = new BlockStateModelFactory(resourcePack, renderSettings); - for (int y = minY; y <= maxY; y++){ - Block block = world.getBlock(x, y, z); - if (block.getBlockState().equals(BlockState.AIR)) continue; + int maxHeight, minY, maxY; + float dx, dz; + Color columnColor = new Color(), blockColor = new Color(); + Block block = new Block(world, 0, 0, 0); + BlockModelView blockModel = new BlockModelView(model); + + int x, y, z; + for (x = min.getX(); x <= max.getX(); x++){ + for (z = min.getZ(); z <= max.getZ(); z++){ + + maxHeight = 0; + columnColor.set(0, 0, 0, 1, true); + + minY = Math.max(min.getY(), world.getMinY(x, z)); + maxY = Math.min(max.getY(), world.getMaxY(x, z)); + + for (y = minY; y <= maxY; y++){ + block.set(x, y, z); + blockColor.set(0, 0, 0, 0, true); + blockModel.initialize(); - BlockStateModel blockModel; try { - blockModel = modelFactory.createFrom(block); + modelFactory.render(block, blockModel, blockColor); } catch (NoSuchResourceException e) { try { - blockModel = modelFactory.createFrom(block, BlockState.MISSING); + modelFactory.render(block, BlockState.MISSING, blockModel.reset(), blockColor); } catch (NoSuchResourceException e2) { e.addSuppressed(e2); - blockModel = new BlockStateModel(); } //Logger.global.noFloodDebug(block.getBlockState().getFullId() + "-hiresModelRenderer-blockmodelerr", "Failed to create BlockModel for BlockState: " + block.getBlockState() + " (" + e.toString() + ")"); } // skip empty blocks - if (blockModel.getFaces().isEmpty()) continue; + if (blockModel.getSize() <= 0) continue; // move block-model to correct position - blockModel.translate(new Vector3f(x - modelAnchor.getX(), y - modelAnchor.getY(), z - modelAnchor.getZ())); + blockModel.translate(x - modelAnchor.getX(), y - modelAnchor.getY(), z - modelAnchor.getZ()); //update color and height (only if not 100% translucent) - Vector4f blockColor = blockModel.getMapColor(); - if (blockColor.getW() > 0) { + if (blockColor.a > 0) { maxHeight = y; - color = MathUtils.overlayColors(blockModel.getMapColor(), color); + columnColor.overlay(blockColor); } - //quick hack to random offset grass - if (block.getBlockState().getFullId().equals(grassId)){ - float dx = (MathUtils.hashToFloat(x, y, z, 123984) - 0.5f) * 0.75f; - float dz = (MathUtils.hashToFloat(x, y, z, 345542) - 0.5f) * 0.75f; - blockModel.translate(new Vector3f(dx, 0, dz)); + //random offset + if (block.getBlockState().isRandomOffset){ + dx = (hashToFloat(x, z, 123984) - 0.5f) * 0.75f; + dz = (hashToFloat(x, z, 345542) - 0.5f) * 0.75f; + blockModel.translate(dx, 0, dz); } - - model.merge(blockModel); } - model.setHeight(x, z, maxHeight); - model.setColor(x, z, color); + tileMeta.setHeight(x, z, maxHeight); + tileMeta.setColor(x, z, columnColor); } } - - return model; + + return tileMeta; + } + + /** + * Hashes the provided position to a random float between 0 and 1.
+ *
+ * (Implementation adapted from https://github.com/SpongePowered/SpongeAPI/blob/ecd761a70219e467dea47a09fc310e8238e9911f/src/main/java/org/spongepowered/api/extra/skylands/SkylandsUtil.java) + * + * @param x The x component of the position + * @param z The z component of the position + * @param seed A seed for the hashing + * @return The hashed value between 0 and 1 + */ + public static float hashToFloat(int x, int z, long seed) { + final long hash = x * 73428767 ^ z * 4382893 ^ seed * 457; + return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileMeta.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileMeta.java new file mode 100644 index 00000000..722498f6 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileMeta.java @@ -0,0 +1,82 @@ +package de.bluecolored.bluemap.core.map.hires; + +import de.bluecolored.bluemap.core.util.math.Color; + +public class HiresTileMeta { + + private final int[] heights; + private final float[] colors; + + private final int minX, minZ, maxX, maxZ, sizeX, sizeZ; + + public HiresTileMeta(int minX, int minZ, int maxX, int maxZ) { + this.minX = minX; + this.minZ = minZ; + + this.maxX = maxX; + this.maxZ = maxZ; + + this.sizeX = maxX - minX + 1; + this.sizeZ = maxZ - minZ + 1; + + this.heights = new int[sizeX * sizeZ]; + this.colors = new float[sizeX * sizeZ * 4]; + } + + public void setHeight(int x, int z, int height) { + heights[(x - minX) * sizeZ + (z - minZ)] = height; + } + + public int getHeight(int x, int z) { + return heights[(x - minX) * sizeZ + (z - minZ)]; + } + + public void setColor(int x, int z, Color color) { + if (!color.premultiplied) throw new IllegalArgumentException("Color should be premultiplied!"); + setColor(x, z, color.r, color.g, color.b, color.a); + } + + private void setColor(int x, int z, float r, float g, float b, float a) { + int index = (x - minX) * sizeZ + (z - minZ) * 4; + colors[index ] = r; + colors[index + 1] = g; + colors[index + 2] = b; + colors[index + 3] = a; + } + + public Color getColor(int x, int z, Color target) { + int index = (x - minX) * sizeZ + (z - minZ) * 4; + return target.set( + colors[index ], + colors[index + 1], + colors[index + 2], + colors[index + 3], + true + ); + } + + public int getMinX() { + return minX; + } + + public int getMinZ() { + return minZ; + } + + public int getMaxX() { + return maxX; + } + + public int getMaxZ() { + return maxZ; + } + + public int getSizeX() { + return sizeX; + } + + public int getSizeZ() { + return sizeZ; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java index 96a382b2..47d1f088 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/HiresTileModel.java @@ -25,6 +25,19 @@ package de.bluecolored.bluemap.core.map.hires; import com.flowpowered.math.TrigMath; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; +import de.bluecolored.bluemap.core.util.math.MatrixM3f; +import de.bluecolored.bluemap.core.util.math.MatrixM4f; +import de.bluecolored.bluemap.core.util.math.VectorM3f; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; public class HiresTileModel { private static final double GROW_MULTIPLIER = 1.5; @@ -39,6 +52,8 @@ public class HiresTileModel { FI_BLOCKLIGHT = 1 , FI_MATERIAL_INDEX = 1 ; + private static final ConcurrentLinkedQueue INSTANCE_POOL = new ConcurrentLinkedQueue<>(); + private int capacity; private int size; @@ -59,10 +74,12 @@ public int size() { public int add(int count) { ensureCapacity(count); - return this.size += count; + int start = this.size; + this.size += count; + return start; } - public void setPositions( + public HiresTileModel setPositions( int face, double x1, double y1, double z1, double x2, double y2, double z2, @@ -81,9 +98,11 @@ public void setPositions( position[index + 6 ] = x3; position[index + 6 + 1] = y3; position[index + 6 + 2] = z3; + + return this; } - public void setUvs( + public HiresTileModel setUvs( int face, float u1, float v1, float u2, float v2, @@ -99,9 +118,11 @@ public void setUvs( uv[index + 4 ] = u3; uv[index + 4 + 1] = v3; + + return this; } - public void setAOs( + public HiresTileModel setAOs( int face, float ao1, float ao2, float ao3 ) { @@ -110,9 +131,11 @@ public void setAOs( ao[index ] = ao1; ao[index + 1] = ao2; ao[index + 2] = ao3; + + return this; } - public void setColor( + public HiresTileModel setColor( int face, float r, float g, float b ){ @@ -121,21 +144,26 @@ public void setColor( color[index ] = r; color[index + 1] = g; color[index + 2] = b; + + return this; } - public void setSunlight(int face, int sl) { + public HiresTileModel setSunlight(int face, int sl) { sunlight[face * FI_SUNLIGHT] = (byte) sl; + return this; } - public void setBlocklight(int face, int bl) { + public HiresTileModel setBlocklight(int face, int bl) { blocklight[face * FI_BLOCKLIGHT] = (byte) bl; + return this; } - public void setMaterialIndex(int face, int m) { + public HiresTileModel setMaterialIndex(int face, int m) { materialIndex[face * FI_MATERIAL_INDEX] = m; + return this; } - public void rotate( + public HiresTileModel rotate( int start, int count, float angle, float axisX, float axisY, float axisZ ) { @@ -157,10 +185,10 @@ public void rotate( qz /= qLength; qw /= qLength; - rotateWithQuaternion(start, count, qx, qy, qz, qw); + return rotateByQuaternion(start, count, qx, qy, qz, qw); } - public void rotate( + public HiresTileModel rotate( int start, int count, float pitch, float yaw, float roll ) { @@ -192,61 +220,135 @@ public void rotate( qz = qwA * qz3 + qzA * qw3, qw = qwA * qw3 - qzA * qz3; - rotateWithQuaternion(start, count, qx, qy, qz, qw); + return rotateByQuaternion(start, count, qx, qy, qz, qw); } - private void rotateWithQuaternion( + public HiresTileModel rotateByQuaternion( int start, int count, double qx, double qy, double qz, double qw ) { double x, y, z, px, py, pz, pw; int end = start + count, index; for (int face = start; face < end; face++) { - index = face * FI_COLOR; + for (int i = 0; i < 3; i++) { + index = face * FI_POSITION + i * 3; - x = position[index ]; - y = position[index + 1]; - z = position[index + 2]; + x = position[index]; + y = position[index + 1]; + z = position[index + 2]; - px = qw * x + qy * z - qz * y; - py = qw * y + qz * x - qx * z; - pz = qw * z + qx * y - qy * x; - pw = -qx * x - qy * y - qz * z; + px = qw * x + qy * z - qz * y; + py = qw * y + qz * x - qx * z; + pz = qw * z + qx * y - qy * x; + pw = -qx * x - qy * y - qz * z; - position[index ] = pw * -qx + px * qw - py * qz + pz * qy; - position[index + 1] = pw * -qy + py * qw - pz * qx + px * qz; - position[index + 2] = pw * -qz + pz * qw - px * qy + py * qx; + position[index] = pw * -qx + px * qw - py * qz + pz * qy; + position[index + 1] = pw * -qy + py * qw - pz * qx + px * qz; + position[index + 2] = pw * -qz + pz * qw - px * qy + py * qx; + } } + + return this; } - public void scale( + public HiresTileModel scale( int start, int count, - float scale + double sx, double sy, double sz ) { - int startIndex = start * FI_POSITION; - int endIndex = count * FI_POSITION + startIndex; + int end = start + count, index; + for (int face = start; face < end; face++) { + for (int i = 0; i < 3; i++) { + index = face * FI_POSITION + i * 3; + position[index ] *= sx; + position[index + 1] *= sy; + position[index + 2] *= sz; + } + } - for (int i = startIndex; i < endIndex; i++) - position[i] *= scale; + return this; } - public void translate( + public HiresTileModel translate( int start, int count, double dx, double dy, double dz ) { int end = start + count, index; for (int face = start; face < end; face++) { - index = face * FI_COLOR; for (int i = 0; i < 3; i++) { + index = face * FI_POSITION + i * 3; position[index ] += dx; position[index + 1] += dy; position[index + 2] += dz; } } + + return this; } - public void clear() { + public HiresTileModel transform(int start, int count, MatrixM3f t) { + return transform(start, count, + t.m00, t.m01, t.m02, + t.m10, t.m11, t.m12, + t.m20, t.m21, t.m22 + ); + } + + public HiresTileModel transform( + int start, int count, + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 + ) { + return transform(start, count, + m00, m01, m02, 0, + m10, m11, m12, 0, + m20, m21, m22, 0, + 0, 0, 0, 1 + ); + } + + public HiresTileModel transform(int start, int count, MatrixM4f t) { + return transform(start, count, + t.m00, t.m01, t.m02, t.m03, + t.m10, t.m11, t.m12, t.m13, + t.m20, t.m21, t.m22, t.m23, + t.m30, t.m31, t.m32, t.m33 + ); + } + + public HiresTileModel transform( + int start, int count, + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 + ) { + int end = start + count, index; + double x, y, z; + for (int face = start; face < end; face++) { + for (int i = 0; i < 3; i++) { + index = face * FI_POSITION + i * 3; + x = position[index ]; + y = position[index + 1]; + z = position[index + 2]; + + position[index ] = m00 * x + m01 * y + m02 * z + m03; + position[index + 1] = m10 * x + m11 * y + m12 * z + m13; + position[index + 2] = m20 * x + m21 * y + m22 * z + m23; + } + } + + return this; + } + + public HiresTileModel reset(int size) { + this.size = size; + return this; + } + + public HiresTileModel clear() { this.size = 0; + return this; } private void ensureCapacity(int count) { @@ -284,4 +386,345 @@ private void setCapacity(int capacity) { materialIndex = new int [capacity * FI_MATERIAL_INDEX]; } + public void writeBufferGeometryJson(OutputStream out) throws IOException { + sort(); + + Gson gson = new GsonBuilder().create(); + JsonWriter json = gson.newJsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8)); + + json.beginObject(); // main-object + + // set special values + json.name("type").value("BufferGeometry"); + json.name("uuid").value(UUID.randomUUID().toString().toUpperCase()); + + json.name("data").beginObject(); // data + json.name("attributes").beginObject(); // attributes + + writePositionArray(json); + writeNormalArray(json); + writeColorArray(json); + writeUvArray(json); + writeAoArray(json); + writeBlocklightArray(json); + writeSunlightArray(json); + + json.endObject(); // attributes + + writeMaterialGroups(json); + + json.endObject(); // data + json.endObject(); // main-object + + // save and return + json.flush(); + } + + private void writePositionArray(JsonWriter json) throws IOException { + json.name("position"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(3); + json.name("normalized").value(false); + + json.name("array").beginArray(); + int posSize = size * FI_POSITION; + for (int i = 0; i < posSize; i++) { + writeRounded(json, position[i]); + } + json.endArray(); + json.endObject(); + } + + private void writeNormalArray(JsonWriter json) throws IOException { + VectorM3f normal = new VectorM3f(0, 0, 0); + + json.name("normal"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(3); + json.name("normalized").value(false); + + json.name("array").beginArray(); + + int pi, i, j; + for (i = 0; i < size; i++) { + pi = i * FI_POSITION; + calculateSurfaceNormal( + position[pi ], position[pi + 1], position[pi + 2], + position[pi + 3], position[pi + 4], position[pi + 5], + position[pi + 6], position[pi + 7], position[pi + 8], + normal + ); + + for (j = 0; j < 3; j++) { // all 3 points + writeRounded(json, normal.x); + writeRounded(json, normal.y); + writeRounded(json, normal.z); + } + } + json.endArray(); + json.endObject(); + } + + private void writeColorArray(JsonWriter json) throws IOException { + json.name("color"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(3); + json.name("normalized").value(false); + + json.name("array").beginArray(); + int colorSize = size * FI_COLOR, i, j; + for (i = 0; i < colorSize; i += 3) { + for (j = 0; j < 3; j++) { + writeRounded(json, color[i]); + writeRounded(json, color[i + 1]); + writeRounded(json, color[i + 2]); + } + } + json.endArray(); + json.endObject(); + } + + private void writeUvArray(JsonWriter json) throws IOException { + json.name("uv"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(2); + json.name("normalized").value(false); + + json.name("array").beginArray(); + int uvSize = size * FI_UV; + for (int i = 0; i < uvSize; i++) { + writeRounded(json, uv[i]); + } + json.endArray(); + json.endObject(); + } + + private void writeAoArray(JsonWriter json) throws IOException { + json.name("ao"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(1); + json.name("normalized").value(false); + + json.name("array").beginArray(); + int aoSize = size * FI_AO; + for (int i = 0; i < aoSize; i++) { + writeRounded(json, ao[i]); + } + json.endArray(); + json.endObject(); + } + + private void writeBlocklightArray(JsonWriter json) throws IOException { + json.name("blocklight"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(1); + json.name("normalized").value(false); + + json.name("array").beginArray(); + int blSize = size * FI_BLOCKLIGHT; + for (int i = 0; i < blSize; i++) { + json.value(blocklight[i]); + json.value(blocklight[i]); + json.value(blocklight[i]); + } + json.endArray(); + json.endObject(); + } + + private void writeSunlightArray(JsonWriter json) throws IOException { + json.name("sunlight"); + json.beginObject(); + + json.name("type").value("Float32Array"); + json.name("itemSize").value(1); + json.name("normalized").value(false); + + json.name("array").beginArray(); + int blSize = size * FI_SUNLIGHT; + for (int i = 0; i < blSize; i++) { + json.value(sunlight[i]); + json.value(sunlight[i]); + json.value(sunlight[i]); + } + json.endArray(); + json.endObject(); + } + + private void writeMaterialGroups(JsonWriter json) throws IOException { + json.name("groups").beginArray(); // groups + + if (size > 0) { + + int miSize = size * FI_MATERIAL_INDEX, lastMaterial = materialIndex[0], material = lastMaterial, groupStart = 0; + + json.beginObject(); + json.name("materialIndex").value(material); + json.name("start").value(0); + + for (int i = 1; i < miSize; i++) { + material = materialIndex[i]; + + if (material != lastMaterial) { + json.name("count").value((i - groupStart) * 3); + json.endObject(); + + groupStart = i; + + json.beginObject(); + json.name("materialIndex").value(material); + json.name("start").value(groupStart * 3); + } + + lastMaterial = material; + } + + json.name("count").value((miSize - groupStart) * 3); + json.endObject(); + + } + + json.endArray(); // groups + } + + private void writeRounded(JsonWriter json, double value) throws IOException { + // rounding and remove ".0" to save string space + double d = Math.round(value * 10000d) / 10000d; + if (d == (long) d) json.value((long) d); + else json.value(d); + } + + /** + * Does an optimized selection sort to sort all faces based on their material-index. + * A selection sort is chosen, because it requires the least amount of swaps, which seem (untested) to be the most expensive operation here + */ + private void sort() { + if (size <= 1) return; // nothing to sort + + int prev = Integer.MIN_VALUE, min, minIndex, i, j; + for (i = 0; i < size - 1; i++){ + minIndex = i; + min = materialIndex[minIndex]; + if (min <= prev) continue; // shortcut + + for (j = i + 1; j < size; j++){ + if (materialIndex[j] < min){ + minIndex = j; + min = materialIndex[minIndex]; + } + } + + if (minIndex != i) { + swap(minIndex, i); + } + + prev = min; + } + } + + private void swap(int face1, int face2) { + int i, if1, if2, vi; + double vd; + float vf; + byte vb; + + //swap positions + if1 = face1 * FI_POSITION; + if2 = face2 * FI_POSITION; + for (i = 0; i < FI_POSITION; i++){ + vd = position[if1 + i]; + position[if1 + i] = position[if2 + i]; + position[if2 + i] = vd; + } + + //swap uv + if1 = face1 * FI_UV; + if2 = face2 * FI_UV; + for (i = 0; i < FI_UV; i++){ + vf = uv[if1 + i]; + uv[if1 + i] = uv[if2 + i]; + uv[if2 + i] = vf; + } + + //swap ao + if1 = face1 * FI_AO; + if2 = face2 * FI_AO; + for (i = 0; i < FI_AO; i++){ + vf = ao[if1 + i]; + ao[if1 + i] = ao[if2 + i]; + ao[if2 + i] = vf; + } + + //swap color + if1 = face1 * FI_COLOR; + if2 = face2 * FI_COLOR; + for (i = 0; i < FI_COLOR; i++){ + vf = color[if1 + i]; + color[if1 + i] = color[if2 + i]; + color[if2 + i] = vf; + } + + //swap sunlight (assuming FI_SUNLIGHT = 1) + vb = sunlight[face1]; + sunlight[face1] = sunlight[face2]; + sunlight[face2] = vb; + + //swap blocklight (assuming FI_BLOCKLIGHT = 1) + vb = blocklight[face1]; + blocklight[face1] = blocklight[face2]; + blocklight[face2] = vb; + + //swap material-index (assuming FI_MATERIAL_INDEX = 1) + vi = materialIndex[face1]; + materialIndex[face1] = materialIndex[face2]; + materialIndex[face2] = vi; + } + + public static HiresTileModel claimInstance() { + HiresTileModel instance = INSTANCE_POOL.poll(); + if (instance != null) { + instance.clear(); + } else { + instance = new HiresTileModel(100); + } + return instance; + } + + public static void recycleInstance(HiresTileModel instance) { + instance.clear(); + INSTANCE_POOL.offer(instance); + } + + private static void calculateSurfaceNormal( + double p1x, double p1y, double p1z, + double p2x, double p2y, double p2z, + double p3x, double p3y, double p3z, + VectorM3f target + ){ + p2x -= p1x; p2y -= p1y; p2z -= p1z; + p3x -= p1x; p3y -= p1y; p3z -= p1z; + + p1x = p2y * p3z - p2z * p3y; + p1y = p2z * p3x - p2x * p3z; + p1z = p2x * p3y - p2y * p3x; + + double length = Math.sqrt(p1x * p1x + p1y * p1y + p1z * p1z); + p1x /= length; + p1y /= length; + p1z /= length; + + target.set((float) p1x, (float) p1y, (float) p1z); + } + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java deleted file mode 100644 index 0cc13514..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModel.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.map.hires.blockmodel; - -import com.flowpowered.math.vector.Vector4f; - -import de.bluecolored.bluemap.core.model.ExtendedFace; -import de.bluecolored.bluemap.core.model.ExtendedModel; -import de.bluecolored.bluemap.core.model.Model; -import de.bluecolored.bluemap.core.util.MathUtils; - -/** - * A model with some extra information about the BlockState it represents - */ -public class BlockStateModel extends ExtendedModel { - - 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 = MathUtils.blendColors(this.mapColor, mapColor); - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java index f88d751a..60b98130 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/BlockStateModelFactory.java @@ -24,73 +24,80 @@ */ package de.bluecolored.bluemap.core.map.hires.blockmodel; +import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.map.hires.BlockModelView; import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; -import de.bluecolored.bluemap.core.resourcepack.BlockStateResource; -import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; -import de.bluecolored.bluemap.core.resourcepack.ResourcePack; -import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource; +import de.bluecolored.bluemap.core.resourcepack.*; +import de.bluecolored.bluemap.core.util.math.Color; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; +import java.util.ArrayList; +import java.util.Collection; + public class BlockStateModelFactory { - private final RenderSettings renderSettings; private final ResourcePack resourcePack; + private final ResourceModelBuilder resourceModelBuilder; + private final LiquidModelBuilder liquidModelBuilder; + + private final Collection bmrs; public BlockStateModelFactory(ResourcePack resourcePack, RenderSettings renderSettings) { - this.renderSettings = renderSettings; this.resourcePack = resourcePack; + + Block[] neighborCache = new Block[3 * 3 * 3]; + for (int i = 0; i < neighborCache.length; i++) { + neighborCache[i] = new Block(null, 0, 0, 0); + } + + this.resourceModelBuilder = new ResourceModelBuilder(resourcePack, renderSettings, neighborCache); + this.liquidModelBuilder = new LiquidModelBuilder(resourcePack, renderSettings, neighborCache); + + this.bmrs = new ArrayList<>(); } - public BlockStateModel createFrom(Block block) throws NoSuchResourceException { - return createFrom(block, block.getBlockState()); + public void render(Block block, BlockModelView blockModel, Color blockColor) throws NoSuchResourceException { + render(block, block.getBlockState(), blockModel, blockColor); } - public BlockStateModel createFrom(Block block, BlockState blockState) throws NoSuchResourceException { + public void render(Block block, BlockState blockState, BlockModelView blockModel, Color blockColor) throws NoSuchResourceException { //shortcut for air - if ( - blockState.getFullId().equals("minecraft:air") || - blockState.getFullId().equals("minecraft:cave_air") || - blockState.getFullId().equals("minecraft:void_air") - ) { - return new BlockStateModel(); + if (blockState.isAir) return; + + int modelStart = blockModel.getStart(); + + // render block + renderModel(block, blockState, blockModel.initialize(), blockColor); + + // add water if block is waterlogged + if (blockState.isWaterlogged) { + renderModel(block, WATERLOGGED_BLOCKSTATE, blockModel.initialize(), blockColor); } - - BlockStateModel model = createModel(block, blockState); - - // if block is waterlogged - if (LiquidModelBuilder.isWaterlogged(blockState)) { - model.merge(createModel(block, WATERLOGGED_BLOCKSTATE)); - } - - return model; + + blockModel.initialize(modelStart); + } - private BlockStateModel createModel(Block block, BlockState blockState) throws NoSuchResourceException { - + private void renderModel(Block block, BlockState blockState, BlockModelView blockModel, Color blockColor) throws NoSuchResourceException { + int modelStart = blockModel.getStart(); + BlockStateResource resource = resourcePack.getBlockStateResource(blockState); - BlockStateModel model = new BlockStateModel(); - BlockColorCalculator colorCalculator = resourcePack.getBlockColorCalculator(); - ResourceModelBuilder modelBuilder = new ResourceModelBuilder(block, renderSettings, colorCalculator); - LiquidModelBuilder liquidBuilder = new LiquidModelBuilder(block, blockState, resourcePack.getMinecraftVersion(), renderSettings, colorCalculator); - - for (TransformedBlockModelResource bmr : resource.getModels(blockState, block.getPosition())){ + for (TransformedBlockModelResource bmr : resource.getModels(blockState, block.getX(), block.getY(), block.getZ(), bmrs)){ switch (bmr.getModel().getType()){ case LIQUID: - model.merge(liquidBuilder.build(bmr)); + liquidModelBuilder.build(block, blockState, bmr, blockModel.initialize(), blockColor); break; default: - model.merge(modelBuilder.build(bmr)); + resourceModelBuilder.build(block, bmr, blockModel.initialize(), blockColor); break; } } - - return model; - + + blockModel.initialize(modelStart); } - private final static BlockState WATERLOGGED_BLOCKSTATE = new BlockState("minecraft:water"); + private final static BlockState WATERLOGGED_BLOCKSTATE = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "minecraft:water"); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java index 64b30b50..97188fde 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java @@ -24,118 +24,149 @@ */ package de.bluecolored.bluemap.core.map.hires.blockmodel; -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.flowpowered.math.TrigMath; +import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.MinecraftVersion; +import de.bluecolored.bluemap.core.logger.Logger; +import de.bluecolored.bluemap.core.map.hires.BlockModelView; +import de.bluecolored.bluemap.core.map.hires.HiresTileModel; import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import de.bluecolored.bluemap.core.model.ExtendedFace; -import de.bluecolored.bluemap.core.model.ExtendedModel; -import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; -import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; +import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculatorFactory; +import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.Texture; import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource; import de.bluecolored.bluemap.core.util.Direction; +import de.bluecolored.bluemap.core.util.math.Color; +import de.bluecolored.bluemap.core.util.math.MatrixM3f; +import de.bluecolored.bluemap.core.util.math.VectorM2f; +import de.bluecolored.bluemap.core.util.math.VectorM3f; import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.BlockState; -import java.util.Arrays; -import java.util.HashSet; - /** * A model builder for all liquid blocks */ public class LiquidModelBuilder { - - private static final HashSet DEFAULT_WATERLOGGED_BLOCK_IDS = new HashSet<>(Arrays.asList( - "minecraft:seagrass", - "minecraft:tall_seagrass", - "minecraft:kelp", - "minecraft:kelp_plant", - "minecraft:bubble_column" - )); - - private final BlockState liquidBlockState; - private final Block block; + private static final float BLOCK_SCALE = 1f / 16f; + private static final MatrixM3f FLOWING_UV_SCALE = new MatrixM3f() + .identity() + .translate(-0.5f, -0.5f) + .scale(0.5f, 0.5f, 1) + .translate(0.5f, 0.5f); + + private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator; private final RenderSettings renderSettings; - private final BlockColorCalculator colorCalculator; private final boolean useWaterColorMap; - - public LiquidModelBuilder(Block block, BlockState liquidBlockState, MinecraftVersion minecraftVersion, RenderSettings renderSettings, BlockColorCalculator colorCalculator) { - this.block = block; + + private final VectorM3f[] corners; + private final Block[] blocksAround; + private final VectorM2f[] uvs = new VectorM2f[4]; + + private Block block; + private BlockState blockState; + private TransformedBlockModelResource blockModelResource; + private BlockModelView blockModel; + private Color blockColor; + + public LiquidModelBuilder(ResourcePack resourcePack, RenderSettings renderSettings, Block[] neighborCache) { + this.blockColorCalculator = resourcePack.getBlockColorCalculatorFactory().createCalculator(); this.renderSettings = renderSettings; - this.liquidBlockState = liquidBlockState; - this.colorCalculator = colorCalculator; - this.useWaterColorMap = minecraftVersion.isAtLeast(new MinecraftVersion(1, 13)); - } + this.useWaterColorMap = resourcePack.getMinecraftVersion().isAtLeast(new MinecraftVersion(1, 13)); - public BlockStateModel build(TransformedBlockModelResource bmr) { - return build(bmr.getModel()); - } - - public BlockStateModel build(BlockModelResource bmr) { - if (this.renderSettings.isExcludeFacesWithoutSunlight() && block.getSunLightLevel() == 0) return new BlockStateModel(); - - int level = getLiquidLevel(block.getBlockState()); - float[] heights = new float[]{16f, 16f, 16f, 16f}; - float coloralpha = 0.2f; - - if (level < 8 && !(level == 0 && isLiquid(block.getRelativeBlock(0, 1, 0)))){ - heights = new float[]{ - getLiquidCornerHeight(-1, 0, -1), - getLiquidCornerHeight(-1, 0, 0), - getLiquidCornerHeight(0, 0, -1), - getLiquidCornerHeight(0, 0, 0) - }; - - coloralpha = 0.8f; - } - - BlockStateModel model = new BlockStateModel(); - Texture texture = bmr.getTexture("still"); - - 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 ), + corners = new VectorM3f[]{ + new VectorM3f( 0, 0, 0 ), + new VectorM3f( 0, 0, 16 ), + new VectorM3f( 16, 0, 0 ), + new VectorM3f( 16, 0, 16 ), + new VectorM3f( 0, 16, 0 ), + new VectorM3f( 0, 16, 16 ), + new VectorM3f( 16, 16, 0 ), + new VectorM3f( 16, 16, 16 ), }; - int textureId = texture.getId(); - Vector3f tintcolor = Vector3f.ONE; - if (useWaterColorMap && liquidBlockState.getFullId().equals("minecraft:water")) { - tintcolor = colorCalculator.getWaterAverageColor(block); - } + this.blocksAround = neighborCache; + + for (int i = 0; i < uvs.length; i++) uvs[i] = new VectorM2f(0, 0); + } + + public void build(Block block, BlockState blockState, TransformedBlockModelResource bmr, BlockModelView blockModel, Color color) { + this.block = block; + this.blockState = blockState; + this.blockModelResource = bmr; + this.blockModel = blockModel; + this.blockColor = color; + + build(); + } + + private final Color tintcolor = new Color(); + private void build() { + if (this.renderSettings.isExcludeFacesWithoutSunlight() && block.getSunLightLevel() == 0) return; - 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); - + int level = getLiquidLevel(blockState); + + if (level < 8 && !(level == 0 && isSameLiquid(getNeighborBlock(0, 1, 0).getBlockState()))){ + corners[4].y = getLiquidCornerHeight(-1, -1); + corners[5].y = getLiquidCornerHeight(-1, 0); + corners[6].y = getLiquidCornerHeight(0, -1); + corners[7].y = getLiquidCornerHeight(0, 0); + } else { + corners[4].y = 16f; + corners[5].y = 16f; + corners[6].y = 16f; + corners[7].y = 16f; + } + + Texture stillTexture = blockModelResource.getModel().getTexture("still"); + Texture flowTexture = blockModelResource.getModel().getTexture("flow"); + + int stillTextureId = stillTexture.getId(); + int flowTextureId = flowTexture.getId(); + + tintcolor.set(1f, 1f, 1f, 1f, true); + if (useWaterColorMap && blockState.isWater) { + blockColorCalculator.getWaterAverageColor(block, tintcolor); + } + + int modelStart = blockModel.getStart(); + + VectorM3f[] c = corners; + createElementFace(Direction.DOWN, c[0], c[2], c[3], c[1], tintcolor, stillTextureId, flowTextureId); + boolean upFaceRendered = + createElementFace(Direction.UP, c[5], c[7], c[6], c[4], tintcolor, stillTextureId, flowTextureId); + createElementFace(Direction.NORTH, c[2], c[0], c[4], c[6], tintcolor, stillTextureId, flowTextureId); + createElementFace(Direction.SOUTH, c[1], c[3], c[7], c[5], tintcolor, stillTextureId, flowTextureId); + createElementFace(Direction.WEST, c[0], c[1], c[5], c[4], tintcolor, stillTextureId, flowTextureId); + createElementFace(Direction.EAST, c[3], c[2], c[6], c[7], tintcolor, stillTextureId, flowTextureId); + + blockModel.initialize(modelStart); + //scale down - model.transform(Matrix3f.createScaling(1f / 16f)); + blockModel.scale(BLOCK_SCALE, BLOCK_SCALE, BLOCK_SCALE); //calculate mapcolor - Vector4f mapcolor = texture.getColor(); - mapcolor = mapcolor.mul(tintcolor.toVector4(coloralpha)); - model.setMapColor(mapcolor); - - return model; + if (upFaceRendered) { + blockColor.set(stillTexture.getColorPremultiplied()); + blockColor.multiply(tintcolor); + + // apply light + float sl = block.getSunLightLevel() / 16f; + blockColor.r *= sl; + blockColor.g *= sl; + blockColor.b *= sl; + } else { + blockColor.set(0, 0, 0, 0, true); + } } - - 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(block.getRelativeBlock(ix, y+1, iz))){ + + private float getLiquidCornerHeight(int x, int z){ + int ix, iz; + + for (ix = x; ix <= x+1; ix++){ + for (iz = z; iz<= z+1; iz++){ + if (isSameLiquid(getNeighborBlock(ix, 1, iz).getBlockState())){ return 16f; } } @@ -143,18 +174,19 @@ private float getLiquidCornerHeight(int x, int y, int z){ float sumHeight = 0f; int count = 0; + BlockState neighborBlockState; - for (int ix = x; ix <= x+1; ix++){ - for (int iz = z; iz<= z+1; iz++){ - Block b = block.getRelativeBlock(ix, y, iz); - if (isLiquid(b)){ - if (getLiquidLevel(b.getBlockState()) == 0) return 14f; + for (ix = x; ix <= x+1; ix++){ + for (iz = z; iz<= z+1; iz++){ + neighborBlockState = getNeighborBlock(ix, 0, iz).getBlockState(); + if (isSameLiquid(neighborBlockState)){ + if (getLiquidLevel(neighborBlockState) == 0) return 14f; - sumHeight += getLiquidBaseHeight(b.getBlockState()); + sumHeight += getLiquidBaseHeight(neighborBlockState); count++; } - else if (!isLiquidBlockingBlock(b)){ + else if (!isLiquidBlockingBlock(neighborBlockState)){ count++; } } @@ -167,93 +199,171 @@ else if (!isLiquidBlockingBlock(b)){ return sumHeight / count; } - private boolean isLiquidBlockingBlock(Block block){ - if (block.getBlockState().equals(BlockState.AIR)) return false; - return true; - } - - private boolean isLiquid(Block block){ - return isLiquid(block.getBlockState()); + private boolean isLiquidBlockingBlock(BlockState blockState){ + return !blockState.equals(BlockState.AIR); } - private boolean isLiquid(BlockState blockState){ - if (blockState.getFullId().equals(liquidBlockState.getFullId())) return true; - return LiquidModelBuilder.isWaterlogged(blockState); + private boolean isSameLiquid(BlockState blockState){ + if (blockState.getFullId().equals(this.blockState.getFullId())) return true; + return this.blockState.isWater && blockState.isWaterlogged; } private float getLiquidBaseHeight(BlockState block){ int level = getLiquidLevel(block); - float baseHeight = 14f - level * 1.9f; - return baseHeight; + return level >= 8 ? 16f : 14f - level * 1.9f; } private int getLiquidLevel(BlockState block){ - if (block.getProperties().containsKey("level")) { - return Integer.parseInt(block.getProperties().get("level")); - } - return 0; + String levelString = block.getProperties().get("level"); + return levelString != null ? Integer.parseInt(levelString) : 0; } - - private void createElementFace(ExtendedModel model, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3, Vector3f color, int textureId) { - - //face culling - Block bl = block.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()); - - ExtendedFace f1 = new ExtendedFace(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId); - ExtendedFace f2 = new ExtendedFace(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId); - - // move face in a tiny bit to avoid z-fighting with waterlogged blocks (doesn't work because it is rounded back when storing the model later) - //f1.translate(faceDir.opposite().toVector().toFloat().mul(0.01)); - //f2.translate(faceDir.opposite().toVector().toFloat().mul(0.01)); - - float blockLight = bl.getBlockLightLevel(); - float sunLight = bl.getSunLightLevel(); - + private final MatrixM3f uvTransform = new MatrixM3f(); + private boolean createElementFace(Direction faceDir, VectorM3f c0, VectorM3f c1, VectorM3f c2, VectorM3f c3, Color color, int stillTextureId, int flowTextureId) { + Vector3i faceDirVector = faceDir.toVector(); + + //face culling + Block bl = getNeighborBlock( + faceDirVector.getX(), + faceDirVector.getY(), + faceDirVector.getZ() + ); + + if (isSameLiquid(bl.getBlockState()) || (faceDir != Direction.UP && bl.isCullingNeighborFaces())) return false; + + // initialize the faces + blockModel.initialize(); + blockModel.add(2); + + HiresTileModel tileModel = blockModel.getHiresTile(); + int face1 = blockModel.getStart(); + int face2 = face1 + 1; + + // ####### positions + tileModel.setPositions(face1, + c0.x, c0.y, c0.z, + c1.x, c1.y, c1.z, + c2.x, c2.y, c2.z + ); + tileModel.setPositions(face2, + c0.x, c0.y, c0.z, + c2.x, c2.y, c2.z, + c3.x, c3.y, c3.z + ); + + //UV + uvs[0].set(0, 1); + uvs[1].set(1, 1); + uvs[2].set(1, 0); + uvs[3].set(0, 0); + + // still/flow ? + boolean flow = false; + if (faceDir == Direction.UP) { + int flowAngle = getFlowingAngle(); + if (flowAngle != -1) { + flow = true; + uvTransform + .identity() + .translate(-0.5f, -0.5f) + .scale(0.5f, 0.5f, 1) + .rotate(-flowAngle, 0, 0, 1) + .translate(0.5f, 0.5f); + + uvs[0].transform(uvTransform); + uvs[1].transform(uvTransform); + uvs[2].transform(uvTransform); + uvs[3].transform(uvTransform); + } + } else if (faceDir != Direction.DOWN) { + flow = true; + + uvs[0].transform(FLOWING_UV_SCALE); + uvs[1].transform(FLOWING_UV_SCALE); + uvs[2].transform(FLOWING_UV_SCALE); + uvs[3].transform(FLOWING_UV_SCALE); + } + + tileModel.setUvs(face1, + uvs[0].x, uvs[0].y, + uvs[1].x, uvs[1].y, + uvs[2].x, uvs[2].y + ); + + tileModel.setUvs(face2, + uvs[0].x, uvs[0].y, + uvs[2].x, uvs[2].y, + uvs[3].x, uvs[3].y + ); + + // texture index + tileModel.setMaterialIndex(face1, flow ? flowTextureId : stillTextureId); + tileModel.setMaterialIndex(face2, flow ? flowTextureId : stillTextureId); + + // color + tileModel.setColor(face1, color.r, color.g, color.b); + tileModel.setColor(face2, color.r, color.g, color.b); + + //ao + tileModel.setAOs(face1, 1, 1, 1); + tileModel.setAOs(face2, 1, 1, 1); + + // light + int blockLight, sunLight; if (faceDir == Direction.UP) { blockLight = block.getBlockLightLevel(); sunLight = block.getSunLightLevel(); + } else { + blockLight = bl.getBlockLightLevel(); + sunLight = bl.getSunLightLevel(); } - - f1.setC1(color); - f1.setC2(color); - f1.setC3(color); - f2.setC1(color); - f2.setC2(color); - f2.setC3(color); - - f1.setBl1(blockLight); - f1.setBl2(blockLight); - f1.setBl3(blockLight); - f2.setBl1(blockLight); - f2.setBl2(blockLight); - f2.setBl3(blockLight); - - f1.setSl1(sunLight); - f1.setSl2(sunLight); - f1.setSl3(sunLight); - f2.setSl1(sunLight); - f2.setSl2(sunLight); - f2.setSl3(sunLight); - - //add the face - model.addFace(f1); - model.addFace(f2); + + tileModel.setBlocklight(face1, blockLight); + tileModel.setBlocklight(face2, blockLight); + + tileModel.setSunlight(face1, sunLight); + tileModel.setSunlight(face2, sunLight); + + return true; } - - public static boolean isWaterlogged(BlockState blockState) { - if (DEFAULT_WATERLOGGED_BLOCK_IDS.contains(blockState.getFullId())) return true; - return blockState.getProperties().getOrDefault("waterlogged", "false").equals("true"); + + private Block getNeighborBlock(int dx, int dy, int dz) { + int i = (dx + 1) * 9 + (dy + 1) * 3 + (dz + 1); + if (i == 13) return block; + return blocksAround[i].set( + block.getWorld(), + block.getX() + dx, + block.getY() + dy, + block.getZ() + dz + ); + } + + private final VectorM2f flowingVector = new VectorM2f(0, 0); + private int getFlowingAngle() { + float own = getLiquidBaseHeight(blockState) * BLOCK_SCALE; + if (own > 0.8) return -1; + + flowingVector.set(0, 0); + + flowingVector.x += compareLiquidHeights(own, -1, 0); + flowingVector.x -= compareLiquidHeights(own, 1, 0); + + flowingVector.y -= compareLiquidHeights(own, 0, -1); + flowingVector.y += compareLiquidHeights(own, 0, 1); + + if (flowingVector.x == 0 && flowingVector.y == 0) return -1; // not flowing + + int angle = (int) (flowingVector.angleTo(0, -1) * TrigMath.RAD_TO_DEG); + return flowingVector.x < 0 ? angle : -angle; + } + + private float compareLiquidHeights(float ownHeight, int dx, int dz) { + BlockState state = getNeighborBlock(dx, 0, dz).getBlockState(); + if (state.isAir) return 0; + if (!isSameLiquid(state)) return 0; + + float otherHeight = getLiquidBaseHeight(state) * BLOCK_SCALE; + return otherHeight - ownHeight; } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java index 610a54fe..26659101 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/ResourceModelBuilder.java @@ -25,356 +25,367 @@ package de.bluecolored.bluemap.core.map.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.ExtendedFace; +import de.bluecolored.bluemap.core.map.hires.BlockModelView; +import de.bluecolored.bluemap.core.map.hires.HiresTileModel; import de.bluecolored.bluemap.core.map.hires.RenderSettings; -import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; -import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; -import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Rotation; -import de.bluecolored.bluemap.core.resourcepack.Texture; -import de.bluecolored.bluemap.core.resourcepack.TransformedBlockModelResource; +import de.bluecolored.bluemap.core.resourcepack.*; import de.bluecolored.bluemap.core.util.Direction; -import de.bluecolored.bluemap.core.util.Lazy; +import de.bluecolored.bluemap.core.util.math.Color; +import de.bluecolored.bluemap.core.util.math.MatrixM4f; +import de.bluecolored.bluemap.core.util.math.VectorM2f; +import de.bluecolored.bluemap.core.util.math.VectorM3f; 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 static final float BLOCK_SCALE = 1f / 16f; + + private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator; + private final RenderSettings renderSettings; + + private final VectorM3f[] corners = new VectorM3f[8]; + private final VectorM2f[] rawUvs = new VectorM2f[4]; + private final VectorM2f[] uvs = new VectorM2f[4]; + private final Color tintColor = new Color(); + private final Color mapColor = new Color(); + private final Block[] blocksAround; + private Block block; - private RenderSettings renderSettings; - private Lazy tintColor; + private TransformedBlockModelResource blockModelResource; + private BlockModelView blockModel; + private Color blockColor; - public ResourceModelBuilder(Block block, RenderSettings renderSettings, BlockColorCalculator colorCalculator) { - this.block = block; + public ResourceModelBuilder(ResourcePack resourcePack, RenderSettings renderSettings, Block[] neighborCache) { + this.blockColorCalculator = resourcePack.getBlockColorCalculatorFactory().createCalculator(); this.renderSettings = renderSettings; - this.tintColor = new Lazy<>(() -> colorCalculator.getBlockColor(block)); + this.blocksAround = neighborCache; + + for (int i = 0; i < corners.length; i++) corners[i] = new VectorM3f(0, 0, 0); + for (int i = 0; i < uvs.length; i++) rawUvs[i] = new VectorM2f(0, 0); } - - public BlockStateModel build(TransformedBlockModelResource bmr) { - BlockStateModel model = new BlockStateModel(); - - for (BlockModelResource.Element element : bmr.getModel().getElements()){ - model.merge(fromModelElementResource(element, bmr)); + + private final MatrixM4f modelTransform = new MatrixM4f(); + public void build(Block block, TransformedBlockModelResource bmr, BlockModelView blockModel, Color color) { + this.block = block; + this.blockModel = blockModel; + this.blockColor = color; + this.blockModelResource = bmr; + + this.tintColor.set(0, 0, 0, -1, true); + + // render model + int modelStart = blockModel.getStart(); + + for (BlockModelResource.Element element : blockModelResource.getModel().getElements()){ + buildModelElementResource(element, blockModel.initialize()); } - - if (!bmr.getRotation().equals(Vector2f.ZERO)) { - model.translate(NEG_HALF_3F); - model.rotate(Quaternionf.fromAxesAnglesDeg( - -bmr.getRotation().getX(), - -bmr.getRotation().getY(), - 0 - )); - model.translate(HALF_3F); + + blockModel.initialize(modelStart); + + // apply model-rotation + if (blockModelResource.hasRotation()) { + blockModel.transform(modelTransform.identity() + .translate(-0.5f, -0.5f, -0.5f) + .multiplyTo(blockModelResource.getRotationMatrix()) + .translate(0.5f, 0.5f, 0.5f) + ); } - - return model; + } - - private BlockStateModel fromModelElementResource(BlockModelResource.Element bmer, TransformedBlockModelResource bmr) { - BlockStateModel model = new BlockStateModel(); - + + private final MatrixM4f modelElementTransform = new MatrixM4f(); + private void buildModelElementResource(BlockModelResource.Element bmer, BlockModelView blockModel) { + //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, bmr, bmer, Direction.DOWN, c[0], c[2], c[3], c[1]); - createElementFace(model, bmr, bmer, Direction.UP, c[5], c[7], c[6], c[4]); - createElementFace(model, bmr, bmer, Direction.NORTH, c[2], c[0], c[4], c[6]); - createElementFace(model, bmr, bmer, Direction.SOUTH, c[1], c[3], c[7], c[5]); - createElementFace(model, bmr, bmer, Direction.WEST, c[0], c[1], c[5], c[4]); - createElementFace(model, bmr, bmer, Direction.EAST, c[3], c[2], c[6], c[7]); + Vector3f from = bmer.getFrom(); + Vector3f to = bmer.getTo(); - //rotate - Rotation rotation = bmer.getRotation(); - if (rotation.getAngle() != 0f){ - Vector3f translation = rotation.getOrigin(); - model.translate(translation.negate()); - - Vector3f rotAxis = rotation.getAxis().toVector().toFloat(); - - model.rotate(Quaternionf.fromAngleDegAxis( - rotation.getAngle(), - rotAxis - )); + float + minX = Math.min(from.getX(), to.getX()), + minY = Math.min(from.getY(), to.getY()), + minZ = Math.min(from.getZ(), to.getZ()), + maxX = Math.max(from.getX(), to.getX()), + maxY = Math.max(from.getY(), to.getY()), + maxZ = Math.max(from.getZ(), to.getZ()); - if (rotation.isRescale()){ - Vector3f scale = - Vector3f.ONE - .sub(rotAxis) - .mul(Math.abs(TrigMath.sin(rotation.getAngle() * 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; + VectorM3f[] c = corners; + c[0].x = minX; c[0].y = minY; c[0].z = minZ; + c[1].x = minX; c[1].y = minY; c[1].z = maxZ; + c[2].x = maxX; c[2].y = minY; c[2].z = minZ; + c[3].x = maxX; c[3].y = minY; c[3].z = maxZ; + c[4].x = minX; c[4].y = maxY; c[4].z = minZ; + c[5].x = minX; c[5].y = maxY; c[5].z = maxZ; + c[6].x = maxX; c[6].y = maxY; c[6].z = minZ; + c[7].x = maxX; c[7].y = maxY; c[7].z = maxZ; + + int modelStart = blockModel.getStart(); + createElementFace(bmer, Direction.DOWN, c[0], c[2], c[3], c[1]); + createElementFace(bmer, Direction.UP, c[5], c[7], c[6], c[4]); + createElementFace(bmer, Direction.NORTH, c[2], c[0], c[4], c[6]); + createElementFace(bmer, Direction.SOUTH, c[1], c[3], c[7], c[5]); + createElementFace(bmer, Direction.WEST, c[0], c[1], c[5], c[4]); + createElementFace(bmer, Direction.EAST, c[3], c[2], c[6], c[7]); + blockModel.initialize(modelStart); + + //rotate and scale down + blockModel.transform(modelElementTransform + .copy(bmer.getRotationMatrix()) + .scale(BLOCK_SCALE, BLOCK_SCALE, BLOCK_SCALE) + ); } - - private void createElementFace(BlockStateModel model, TransformedBlockModelResource modelResource, BlockModelResource.Element element, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3) { + + private final VectorM3f faceRotationVector = new VectorM3f(0, 0, 0); + private void createElementFace(BlockModelResource.Element element, Direction faceDir, VectorM3f c0, VectorM3f c1, VectorM3f c2, VectorM3f c3) { BlockModelResource.Element.Face face = element.getFaces().get(faceDir); - if (face == null) return; - - //face culling - if (face.getCullface() != null){ - Block b = getRotationRelativeBlock(modelResource.getRotation(), face.getCullface()); + + Vector3i faceDirVector = faceDir.toVector(); + + // face culling + if (face.getCullface() != null) { + Block b = getRotationRelativeBlock(face.getCullface()); if (b.isCullingNeighborFaces()) return; } - //light calculation - Block facedBlockNeighbor = getRotationRelativeBlock(modelResource.getRotation(), faceDir); - float sunLight = facedBlockNeighbor.getPassedSunLight(); - - //filter out faces that are not sunlighted + // light calculation + Block facedBlockNeighbor = getRotationRelativeBlock(faceDir); + int sunLight = facedBlockNeighbor.getPassedSunLight(); + int blockLight = facedBlockNeighbor.getPassedBlockLight(); + + // filter out faces that are not sun-lighted if (sunLight == 0f && renderSettings.isExcludeFacesWithoutSunlight()) return; - float blockLight = facedBlockNeighbor.getPassedBlockLight(); + // initialize the faces + blockModel.initialize(); + blockModel.add(2); - //UV - Vector4f uv = face.getUv().toFloat().div(16); - - //UV-Lock counter-rotation - int uvLockAngle = 0; - Vector2f rotation = modelResource.getRotation(); - if (modelResource.isUVLock()){ - Quaternionf rot = Quaternionf.fromAxesAnglesDeg(rotation.getX(), rotation.getY(), 0); - uvLockAngle = (int) rot.getAxesAnglesDeg().dot(faceDir.toVector().toFloat()); - - //my math has stopped working, there has to be a more consistent solution for this... - if (rotation.getX() >= 180 && rotation.getY() != 90 && rotation.getY() != 270) uvLockAngle += 180; - } + HiresTileModel tileModel = blockModel.getHiresTile(); + int face1 = blockModel.getStart(); + int face2 = face1 + 1; - //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()); - + // ####### positions + tileModel.setPositions(face1, + c0.x, c0.y, c0.z, + c1.x, c1.y, c1.z, + c2.x, c2.y, c2.z + ); + tileModel.setPositions(face2, + c0.x, c0.y, c0.z, + c2.x, c2.y, c2.z, + c3.x, c3.y, c3.z + ); + + // ####### texture Texture texture = face.getTexture(); int textureId = texture.getId(); - - ExtendedFace f1; - ExtendedFace f2; - - try { - f1 = new ExtendedFace(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId); - f2 = new ExtendedFace(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId); - } catch (ArithmeticException ex) { - // This error is thrown when a model defined a face that has no surface (all 3 points are on one line) - // we catch it here and simply ignore the face - return; - } - - //tint the face - Vector3f color = Vector3f.ONE; - if (face.isTinted()){ - color = tintColor.getValue(); - } - - f1.setC1(color); - f1.setC2(color); - f1.setC3(color); - f2.setC1(color); - f2.setC2(color); - f2.setC3(color); - - f1.setBl1(blockLight); - f1.setBl2(blockLight); - f1.setBl3(blockLight); - f2.setBl1(blockLight); - f2.setBl2(blockLight); - f2.setBl3(blockLight); - - f1.setSl1(sunLight); - f1.setSl2(sunLight); - f1.setSl3(sunLight); - f2.setSl1(sunLight); - f2.setSl2(sunLight); - f2.setSl3(sunLight); - - //calculate ao - float ao0 = 1f, ao1 = 1f, ao2 = 1f, ao3 = 1f; - if (modelResource.getModel().isAmbientOcclusion()){ - ao0 = testAo(modelResource.getRotation(), c0, faceDir); - ao1 = testAo(modelResource.getRotation(), c1, faceDir); - ao2 = testAo(modelResource.getRotation(), c2, faceDir); - ao3 = testAo(modelResource.getRotation(), c3, faceDir); - } - - f1.setAo1(ao0); - f1.setAo2(ao1); - f1.setAo3(ao2); - f2.setAo1(ao0); - f2.setAo2(ao2); - f2.setAo3(ao3); - - //add the face - model.addFace(f1); - model.addFace(f2); - - //if is top face set model-color - Vector3f dir = getRotationRelativeDirectionVector(modelResource.getRotation(), faceDir.toVector().toFloat()); + tileModel.setMaterialIndex(face1, textureId); + tileModel.setMaterialIndex(face2, textureId); - if (element.getRotation().getAngle() > 0){ - Quaternionf rot = Quaternionf.fromAngleDegAxis( - element.getRotation().getAngle(), - element.getRotation().getAxis().toVector().toFloat() - ); - dir = rot.rotate(dir); + // ####### UV + Vector4f uvRaw = face.getUv(); + float + uvx = uvRaw.getX() / 16f, + uvy = uvRaw.getY() / 16f, + uvz = uvRaw.getZ() / 16f, + uvw = uvRaw.getW() / 16f; + + rawUvs[0].set(uvx, uvw); + rawUvs[1].set(uvz, uvw); + rawUvs[2].set(uvz, uvy); + rawUvs[3].set(uvx, uvy); + + // face-rotation + int rotationSteps = Math.floorDiv(face.getRotation(), 90) % 4; + if (rotationSteps < 0) rotationSteps += 4; + for (int i = 0; i < 4; i++) + uvs[i] = rawUvs[(rotationSteps + i) % 4]; + + // UV-Lock counter-rotation + float uvRotation = 0f; + if (blockModelResource.isUVLock() && blockModelResource.hasRotation()) { + Vector2f rotation = blockModelResource.getRotation(); + + float xRotSin = TrigMath.sin(rotation.getX() * TrigMath.DEG_TO_RAD); + float xRotCos = TrigMath.cos(rotation.getX() * TrigMath.DEG_TO_RAD); + + uvRotation = + rotation.getY() * (faceDirVector.getY() * xRotCos + faceDirVector.getZ() * xRotSin) + + rotation.getX() * (1 - faceDirVector.getY()); } - - float a = dir.getY(); + + // rotate uv's + if (uvRotation != 0){ + uvRotation *= TrigMath.DEG_TO_RAD; + float cx = TrigMath.cos(uvRotation), cy = TrigMath.sin(uvRotation); + for (VectorM2f uv : uvs) { + uv.translate(-0.5f, -0.5f); + uv.rotate(cx, cy); + uv.translate(0.5f, 0.5f); + } + } + + tileModel.setUvs(face1, + uvs[0].x, uvs[0].y, + uvs[1].x, uvs[1].y, + uvs[2].x, uvs[2].y + ); + + tileModel.setUvs(face2, + uvs[0].x, uvs[0].y, + uvs[2].x, uvs[2].y, + uvs[3].x, uvs[3].y + ); + + + // ####### face-tint + if (face.isTinted()) { + if (tintColor.a < 0) { + blockColorCalculator.getBlockColor(block, tintColor); + } + + tileModel.setColor(face1, tintColor.r, tintColor.g, tintColor.b); + tileModel.setColor(face2, tintColor.r, tintColor.g, tintColor.b); + } else { + tileModel.setColor(face1, 1, 1, 1); + tileModel.setColor(face2, 1, 1, 1); + } + + // ####### blocklight + tileModel.setBlocklight(face1, blockLight); + tileModel.setBlocklight(face2, blockLight); + + // ####### sunlight + tileModel.setSunlight(face1, sunLight); + tileModel.setSunlight(face2, sunLight); + + // ######## AO + float ao0 = 1f, ao1 = 1f, ao2 = 1f, ao3 = 1f; + if (blockModelResource.getModel().isAmbientOcclusion()){ + ao0 = testAo(c0, faceDir); + ao1 = testAo(c1, faceDir); + ao2 = testAo(c2, faceDir); + ao3 = testAo(c3, faceDir); + } + + tileModel.setAOs(face1, ao0, ao1, ao2); + tileModel.setAOs(face2, ao0, ao2, ao3); + + //if is top face set model-color + faceRotationVector.set( + faceDirVector.getX(), + faceDirVector.getY(), + faceDirVector.getZ() + ); + makeRotationRelative(faceRotationVector); + faceRotationVector.rotateAndScale(element.getRotationMatrix()); + + float a = faceRotationVector.y; if (a > 0){ - Vector4f c = texture.getColor(); - c = c.mul(color.toVector4(1f)); - c = new Vector4f(c.getX(), c.getY(), c.getZ(), c.getW() * a); - model.mergeMapColor(c); + mapColor.set(texture.getColorPremultiplied()); + if (tintColor.a >= 0) { + mapColor.multiply(tintColor); + } + + // apply light + float sl = sunLight / 16f; + mapColor.r *= sl; + mapColor.g *= sl; + mapColor.b *= sl; + + blockColor.add(mapColor); } - } - - private Block getRotationRelativeBlock(Vector2f modelRotation, Direction direction){ - return getRotationRelativeBlock(modelRotation, direction.toVector()); + + private Block getNeighborBlock(int dx, int dy, int dz) { + int i = (dx + 1) * 9 + (dy + 1) * 3 + (dz + 1); + if (i == 13) return block; + return blocksAround[i].set( + block.getWorld(), + block.getX() + dx, + block.getY() + dy, + block.getZ() + dz + ); } - - private Block getRotationRelativeBlock(Vector2f modelRotation, Vector3i direction){ - Vector3i dir = getRotationRelativeDirectionVector(modelRotation, direction.toFloat()).round().toInt(); - return block.getRelativeBlock(dir); + + private Block getRotationRelativeBlock(Direction direction){ + return getRotationRelativeBlock(direction.toVector()); } - - private Vector3f getRotationRelativeDirectionVector(Vector2f modelRotation, Vector3f direction){ - Quaternionf rot = Quaternionf.fromAxesAnglesDeg( - -modelRotation.getX(), - -modelRotation.getY(), - 0 - ); - Vector3f dir = rot.rotate(direction); - return dir; + + + private Block getRotationRelativeBlock(Vector3i direction){ + return getRotationRelativeBlock( + direction.getX(), + direction.getY(), + direction.getZ() + ); } - - private float testAo(Vector2f modelRotation, Vector3f vertex, Direction dir){ + + private final VectorM3f rotationRelativeBlockDirection = new VectorM3f(0, 0, 0); + private Block getRotationRelativeBlock(int dx, int dy, int dz){ + rotationRelativeBlockDirection.set(dx, dy, dz); + makeRotationRelative(rotationRelativeBlockDirection); + + return getNeighborBlock( + Math.round(rotationRelativeBlockDirection.x), + Math.round(rotationRelativeBlockDirection.y), + Math.round(rotationRelativeBlockDirection.z) + ); + } + + private void makeRotationRelative(VectorM3f direction){ + direction.transform(blockModelResource.getRotationMatrix()); + } + + private float testAo(VectorM3f vertex, Direction dir){ + Vector3i dirVec = dir.toVector(); int occluding = 0; int x = 0; - if (vertex.getX() == 16){ + if (vertex.x == 16){ x = 1; - } else if (vertex.getX() == 0){ + } else if (vertex.x == 0){ x = -1; } int y = 0; - if (vertex.getY() == 16){ + if (vertex.y == 16){ y = 1; - } else if (vertex.getY() == 0){ + } else if (vertex.y == 0){ y = -1; } int z = 0; - if (vertex.getZ() == 16){ + if (vertex.z == 16){ z = 1; - } else if (vertex.getZ() == 0){ + } else if (vertex.z == 0){ z = -1; } - Vector3i rel = new Vector3i(x, y, 0); - if (rel.dot(dir.toVector()) > 0){ - if (getRotationRelativeBlock(modelRotation, rel).isOccludingNeighborFaces()) occluding++; + + if (x * dirVec.getX() + y * dirVec.getY() > 0){ + if (getRotationRelativeBlock(x, y, 0).isOccludingNeighborFaces()) occluding++; + } + + if (x * dirVec.getX() + z * dirVec.getZ() > 0){ + if (getRotationRelativeBlock(x, 0, z).isOccludingNeighborFaces()) occluding++; + } + + if (y * dirVec.getY() + z * dirVec.getZ() > 0){ + if (getRotationRelativeBlock(0, y, z).isOccludingNeighborFaces()) occluding++; + } + + if (x * dirVec.getX() + y * dirVec.getY() + z * dirVec.getZ() > 0){ + if (getRotationRelativeBlock(x, y, z).isOccludingNeighborFaces()) occluding++; } - rel = new Vector3i(x, 0, z); - if (rel.dot(dir.toVector()) > 0){ - if (getRotationRelativeBlock(modelRotation, rel).isOccludingNeighborFaces()) occluding++; - } - - rel = new Vector3i(0, y, z); - if (rel.dot(dir.toVector()) > 0){ - if (getRotationRelativeBlock(modelRotation, rel).isOccludingNeighborFaces()) occluding++; - } - - rel = new Vector3i(x, y, z); - if (rel.dot(dir.toVector()) > 0){ - if (getRotationRelativeBlock(modelRotation, rel).isOccludingNeighborFaces()) occluding++; - } - - if (occluding > 3) - occluding = 3; - + if (occluding > 3) occluding = 3; return Math.max(0f, Math.min(1f - occluding * 0.25f, 1f)); } - - 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/map/lowres/LowresModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java index de48ff9e..552b56f8 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModel.java @@ -28,7 +28,6 @@ import com.flowpowered.math.vector.Vector3f; import de.bluecolored.bluemap.core.threejs.BufferGeometry; import de.bluecolored.bluemap.core.util.AtomicFileHelper; -import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.util.ModelUtils; @@ -96,7 +95,7 @@ public void save(File file, boolean force, boolean useGzip) throws IOException { if (useGzip) os = new GZIPOutputStream(os); OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8); try ( - PrintWriter pw = new PrintWriter(osw); + PrintWriter pw = new PrintWriter(osw) ){ pw.print(json); } @@ -120,7 +119,7 @@ public void flush(){ for (int i = 0; i < vertexCount; i++){ int j = i * 3; - int px = Math.round(position[j + 0]); + int px = Math.round(position[j ]); int pz = Math.round(position[j + 2]); Vector2i p = new Vector2i(px, pz); @@ -130,19 +129,19 @@ public void flush(){ position[j + 1] = lrp.height; - color[j + 0] = lrp.color.getX(); + color[j ] = lrp.color.getX(); color[j + 1] = lrp.color.getY(); color[j + 2] = lrp.color.getZ(); //recalculate normals int f = Math.floorDiv(i, 3) * 3 * 3; - Vector3f p1 = new Vector3f(position[f + 0], position[f + 1], position[f + 2]); + Vector3f p1 = new Vector3f(position[f ], position[f + 1], position[f + 2]); Vector3f p2 = new Vector3f(position[f + 3], position[f + 4], position[f + 5]); Vector3f p3 = new Vector3f(position[f + 6], position[f + 7], position[f + 8]); Vector3f n = MathUtils.getSurfaceNormal(p1, p2, p3); - normal[f + 0] = n.getX(); normal[f + 1] = n.getY(); normal[f + 2] = n.getZ(); + normal[f ] = n.getX(); normal[f + 1] = n.getY(); normal[f + 2] = n.getZ(); normal[f + 3] = n.getX(); normal[f + 4] = n.getY(); normal[f + 5] = n.getZ(); normal[f + 6] = n.getX(); normal[f + 7] = n.getY(); normal[f + 8] = n.getZ(); } @@ -165,18 +164,6 @@ 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/map/lowres/LowresModelManager.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java index 7cc3f154..1d631151 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/lowres/LowresModelManager.java @@ -24,11 +24,13 @@ */ package de.bluecolored.bluemap.core.map.lowres; -import com.flowpowered.math.vector.*; +import com.flowpowered.math.vector.Vector2i; +import com.flowpowered.math.vector.Vector3f; import de.bluecolored.bluemap.core.logger.Logger; -import de.bluecolored.bluemap.core.map.hires.HiresModel; +import de.bluecolored.bluemap.core.map.hires.HiresTileMeta; import de.bluecolored.bluemap.core.threejs.BufferGeometry; import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.util.math.Color; import org.apache.commons.io.IOUtils; import java.io.File; @@ -41,7 +43,6 @@ 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; @@ -68,52 +69,45 @@ public LowresModelManager(Path fileRoot, Vector2i pointsPerLowresTile, Vector2i /** * Renders all points from the given hires-model onto the lowres-grid */ - public void render(HiresModel hiresModel) { - 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(); + public void render(HiresTileMeta tileMeta) { + Vector2i blocksPerPoint = new Vector2i( + tileMeta.getSizeX() / pointsPerHiresTile.getX(), + tileMeta.getSizeZ() / pointsPerHiresTile.getY() + ); + + Vector2i pointMin = new Vector2i( + Math.floorDiv(tileMeta.getMinX(), blocksPerPoint.getX()), + Math.floorDiv(tileMeta.getMinZ(), blocksPerPoint.getY()) + ); + + Color + pointColor = new Color(), + columnColor = new Color(); 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; + pointColor.set(0, 0, 0, 0, true); 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(); + int rx = tx * blocksPerPoint.getX() + x + tileMeta.getMinX(); + int rz = tz * blocksPerPoint.getY() + z + tileMeta.getMinZ(); + height += tileMeta.getHeight(rx, rz); + + tileMeta.getColor(rx, rz, columnColor).premultiplied(); + pointColor.add(columnColor); } } - - if (colorCount > 0) color = color.div(colorCount); - + + pointColor.flatten().straight(); + int count = blocksPerPoint.getX() * blocksPerPoint.getY(); height /= count; - - Vector2i point = pointMin.add(tx, tz); - update(hiresModel.getWorld(), point, (float) height, color.toFloat()); - + + update(pointMin.getX() + tx, pointMin.getY() + tz, (float) height, pointColor); } } } @@ -132,31 +126,36 @@ public synchronized void save(){ /** * Updates a point on the lowres-model-grid */ - public void update(UUID world, Vector2i point, float height, Vector3f color) { + public void update(int px, int pz, float height, Color color) { + if (color.premultiplied) throw new IllegalArgumentException("Color can not be premultiplied!"); + + Vector2i point = new Vector2i(px, pz); + Vector3f colorV = new Vector3f(color.r, color.g, color.b); + Vector2i tile = pointToTile(point); Vector2i relPoint = getPointRelativeToTile(tile, point); - LowresModel model = getModel(world, tile); - model.update(relPoint, height, color); + LowresModel model = getModel(tile); + model.update(relPoint, height, colorV); 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); + LowresModel model2 = getModel(tile2); + model2.update(relPoint2, height, colorV); } 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); + LowresModel model2 = getModel(tile2); + model2.update(relPoint2, height, colorV); } 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); + LowresModel model2 = getModel(tile2); + model2.update(relPoint2, height, colorV); } } @@ -167,7 +166,7 @@ public File getFile(Vector2i tile, boolean useGzip){ return FileUtils.coordsToFile(fileRoot, tile, "json" + (useGzip ? ".gz" : "")); } - private LowresModel getModel(UUID world, Vector2i tile) { + private LowresModel getModel(Vector2i tile) { File modelFile = getFile(tile, useGzip); CachedModel model = models.get(modelFile); @@ -257,15 +256,17 @@ private void saveModel(File modelFile, CachedModel model) { } private Vector2i pointToTile(Vector2i point){ - return point - .toDouble() - .div(pointsPerLowresTile.toDouble()) - .floor() - .toInt(); + return new Vector2i( + Math.floorDiv(point.getX(), pointsPerLowresTile.getX()), + Math.floorDiv(point.getY(), pointsPerLowresTile.getY()) + ); } private Vector2i getPointRelativeToTile(Vector2i tile, Vector2i point){ - return point.sub(tile.mul(pointsPerLowresTile)); + return new Vector2i( + point.getX() - tile.getX() * pointsPerLowresTile.getX(), + point.getY() - tile.getY() * pointsPerLowresTile.getY() + ); } public Vector2i getTileSize() { 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 index f606140e..4a02590f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil112.java @@ -88,14 +88,14 @@ public boolean isGenerated() { } @Override - public BlockState getBlockState(Vector3i pos) { - int sectionY = pos.getY() >> 4; + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR; Section section = this.sections[sectionY]; if (section == null) return BlockState.AIR; - return section.getBlockState(pos); + return section.getBlockState(x, y, z); } public String getBlockIdMeta(Vector3i pos) { @@ -109,17 +109,17 @@ public String getBlockIdMeta(Vector3i pos) { } @Override - public LightData getLightData(Vector3i pos) { - if (!hasLight) return LightData.SKY; + public LightData getLightData(int x, int y, int z, LightData target) { + if (!hasLight) return target.set(15, 0); - int sectionY = pos.getY() >> 4; + int sectionY = y >> 4; if (sectionY < 0 || sectionY >= this.sections.length) - return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY; + return (y < 0) ? target.set(0, 0) : target.set(15, 0); Section section = this.sections[sectionY]; - if (section == null) return LightData.SKY; + if (section == null) return target.set(15, 0); - return section.getLightData(pos); + return section.getLightData(x, y, z, target); } @Override @@ -129,6 +129,7 @@ public Biome getBiome(int x, int y, int z) { int biomeByteIndex = z * 16 + x; if (biomeByteIndex >= this.biomes.length) return Biome.DEFAULT; + return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF); } @@ -158,10 +159,9 @@ 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; + public BlockState getBlockState(int x, int y, int z) { + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockByteIndex = y * 256 + z * 16 + x; int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 @@ -202,10 +202,9 @@ public String getBlockIdMeta(Vector3i pos) { return blockId + ":" + blockData + " " + forgeIdMapping; } - 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; + public LightData getLightData(int x, int y, int z, LightData target) { + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockByteIndex = y * 256 + z * 16 + x; int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 @@ -213,7 +212,7 @@ public LightData getLightData(Vector3i pos) { int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf); int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf); - return new LightData(skyLight, blockLight); + return target.set(skyLight, blockLight); } /** 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 index 76437095..e9bc60e5 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil113.java @@ -24,7 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; -import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.world.Biome; @@ -38,6 +38,8 @@ import java.util.Map.Entry; public class ChunkAnvil113 extends MCAChunk { + private static final MinecraftVersion VERSION = new MinecraftVersion(1, 13); + private BiomeMapper biomeIdMapper; private boolean isGenerated; @@ -97,28 +99,28 @@ public boolean isGenerated() { } @Override - public BlockState getBlockState(Vector3i pos) { - int sectionY = pos.getY() >> 4; + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR; Section section = this.sections[sectionY]; if (section == null) return BlockState.AIR; - return section.getBlockState(pos); + return section.getBlockState(x, y, z); } @Override - public LightData getLightData(Vector3i pos) { - if (!hasLight) return LightData.SKY; + public LightData getLightData(int x, int y, int z, LightData target) { + if (!hasLight) return target.set(15, 0); - int sectionY = pos.getY() >> 4; + int sectionY = y >> 4; if (sectionY < 0 || sectionY >= this.sections.length) - return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY; + return (y < 0) ? target.set(0, 0) : target.set(15, 0); Section section = this.sections[sectionY]; - if (section == null) return LightData.SKY; + if (section == null) return target.set(15, 0); - return section.getLightData(pos); + return section.getLightData(x, y, z, target); } @Override @@ -128,6 +130,7 @@ public Biome getBiome(int x, int y, int z) { int biomeIntIndex = z * 16 + x; if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT; + return biomeIdMapper.get(biomes[biomeIntIndex]); } @@ -175,7 +178,7 @@ public Section(CompoundTag sectionData) { } } - palette[i] = new BlockState(id, properties); + palette[i] = new BlockState(VERSION, id, properties); } } else { this.palette = new BlockState[0]; @@ -188,12 +191,11 @@ public int getSectionY() { return sectionY; } - public BlockState getBlockState(Vector3i pos) { + public BlockState getBlockState(int x, int y, int z) { 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; + + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockIndex = y * 256 + z * 16 + x; long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock); @@ -205,20 +207,19 @@ public BlockState getBlockState(Vector3i pos) { return palette[(int) value]; } - public LightData getLightData(Vector3i pos) { - if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO; - - int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) - int y = pos.getY() & 0xF; - int z = pos.getZ() & 0xF; + public LightData getLightData(int x, int y, int z, LightData target) { + if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0); + + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockByteIndex = y * 256 + z * 16 + x; int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 - int blockLight = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0; - int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0; - - return new LightData(skyLight, blockLight); + return target.set( + this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0, + this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0 + ); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java index 4e9ecc9b..ffb7f451 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil115.java @@ -24,7 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; -import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.world.Biome; @@ -38,6 +38,8 @@ import java.util.Map.Entry; public class ChunkAnvil115 extends MCAChunk { + private static final MinecraftVersion VERSION = new MinecraftVersion(1, 15); + private BiomeMapper biomeIdMapper; private boolean isGenerated; @@ -97,28 +99,28 @@ public boolean isGenerated() { } @Override - public BlockState getBlockState(Vector3i pos) { - int sectionY = pos.getY() >> 4; + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR; Section section = this.sections[sectionY]; if (section == null) return BlockState.AIR; - return section.getBlockState(pos); + return section.getBlockState(x, y, z); } @Override - public LightData getLightData(Vector3i pos) { - if (!hasLight) return LightData.SKY; + public LightData getLightData(int x, int y, int z, LightData target) { + if (!hasLight) return target.set(15, 0); - int sectionY = pos.getY() >> 4; + int sectionY = y >> 4; if (sectionY < 0 || sectionY >= this.sections.length) - return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY; + return (y < 0) ? target.set(0, 0) : target.set(15, 0); Section section = this.sections[sectionY]; - if (section == null) return LightData.SKY; + if (section == null) return target.set(15, 0); - return section.getLightData(pos); + return section.getLightData(x, y, z, target); } @Override @@ -128,7 +130,9 @@ public Biome getBiome(int x, int y, int z) { y = y / 4; int biomeIntIndex = y * 16 + z * 4 + x; + if (biomeIntIndex < 0) return Biome.DEFAULT; if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT; + return biomeIdMapper.get(biomes[biomeIntIndex]); } @@ -176,7 +180,7 @@ public Section(CompoundTag sectionData) { } } - palette[i] = new BlockState(id, properties); + palette[i] = new BlockState(VERSION, id, properties); } } else { this.palette = new BlockState[0]; @@ -189,12 +193,11 @@ public int getSectionY() { return sectionY; } - public BlockState getBlockState(Vector3i pos) { + public BlockState getBlockState(int x, int y, int z) { 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; + + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockIndex = y * 256 + z * 16 + x; long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock); @@ -206,20 +209,19 @@ public BlockState getBlockState(Vector3i pos) { return palette[(int) value]; } - public LightData getLightData(Vector3i pos) { - if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO; - - int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) - int y = pos.getY() & 0xF; - int z = pos.getZ() & 0xF; + public LightData getLightData(int x, int y, int z, LightData target) { + if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0); + + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockByteIndex = y * 256 + z * 16 + x; int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 - int blockLight = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0; - int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0; - - return new LightData(skyLight, blockLight); + return target.set( + this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0, + this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0 + ); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java index b64aeaec..28c8f5d1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/ChunkAnvil116.java @@ -24,7 +24,7 @@ */ package de.bluecolored.bluemap.core.mca; -import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.world.Biome; @@ -32,18 +32,23 @@ import de.bluecolored.bluemap.core.world.LightData; import net.querz.nbt.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; public class ChunkAnvil116 extends MCAChunk { + private static final MinecraftVersion VERSION = new MinecraftVersion(1, 16); + private BiomeMapper biomeIdMapper; private boolean isGenerated; private boolean hasLight; - private Map sections; + private int sectionMin, sectionMax; + private Section[] sections; + private int[] biomes; @SuppressWarnings("unchecked") @@ -62,11 +67,14 @@ public ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, Biome isGenerated = !status.equals("empty"); } - this.sections = new HashMap<>(); // Is using a has-map the fastest/best way for an int->Object mapping? - this.sectionMin = Integer.MAX_VALUE; - this.sectionMax = Integer.MIN_VALUE; if (levelData.containsKey("Sections")) { - for (CompoundTag sectionTag : ((ListTag) levelData.getListTag("Sections"))) { + this.sectionMin = Integer.MAX_VALUE; + this.sectionMax = Integer.MIN_VALUE; + + ListTag sectionsTag = (ListTag) levelData.getListTag("Sections"); + ArrayList
sectionList = new ArrayList<>(sectionsTag.size()); + + for (CompoundTag sectionTag : sectionsTag) { if (sectionTag.getListTag("Palette") == null) continue; // ignore empty sections Section section = new Section(sectionTag); @@ -75,7 +83,12 @@ public ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, Biome if (sectionMin > y) sectionMin = y; if (sectionMax < y) sectionMax = y; - sections.put(y, section); + sectionList.add(section); + } + + sections = new Section[1 + sectionMax - sectionMin]; + for (Section section : sectionList) { + sections[section.sectionY - sectionMin] = section; } } @@ -103,25 +116,25 @@ public boolean isGenerated() { } @Override - public BlockState getBlockState(Vector3i pos) { - int sectionY = pos.getY() >> 4; - - Section section = this.sections.get(sectionY); + public BlockState getBlockState(int x, int y, int z) { + int sectionY = y >> 4; + + Section section = getSection(sectionY); if (section == null) return BlockState.AIR; - return section.getBlockState(pos); + return section.getBlockState(x, y, z); } @Override - public LightData getLightData(Vector3i pos) { - if (!hasLight) return LightData.SKY; + public LightData getLightData(int x, int y, int z, LightData target) { + if (!hasLight) return target.set(15, 0); - int sectionY = pos.getY() >> 4; + int sectionY = y >> 4; - Section section = this.sections.get(sectionY); - if (section == null) return (sectionY < sectionMin) ? LightData.ZERO : LightData.SKY; + Section section = getSection(sectionY); + if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(15, 0); - return section.getLightData(pos); + return section.getLightData(x, y, z, target); } @Override @@ -150,6 +163,12 @@ public int getMaxY(int x, int z) { return sectionMax * 16 + 15; } + private Section getSection(int y) { + y -= sectionMin; + if (y < 0 || y >= this.sections.length) return null; + return this.sections[y]; + } + private static class Section { private static final String AIR_ID = "minecraft:air"; @@ -194,7 +213,7 @@ public Section(CompoundTag sectionData) { } } - palette[i] = new BlockState(id, properties); + palette[i] = new BlockState(VERSION, id, properties); } } else { this.palette = new BlockState[0]; @@ -207,12 +226,11 @@ public int getSectionY() { return sectionY; } - public BlockState getBlockState(Vector3i pos) { + public BlockState getBlockState(int x, int y, int z) { 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; + + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockIndex = y * 256 + z * 16 + x; long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock); @@ -223,21 +241,20 @@ public BlockState getBlockState(Vector3i pos) { return palette[(int) value]; } - - public LightData getLightData(Vector3i pos) { - if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO; + + public LightData getLightData(int x, int y, int z, LightData target) { + if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0); - int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) - int y = pos.getY() & 0xF; - int z = pos.getZ() & 0xF; + x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16) + int blockByteIndex = y * 256 + z * 16 + x; int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2 boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0 - int blockLight = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0; - int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0; - - return new LightData(skyLight, blockLight); + return target.set( + this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0, + this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0 + ); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java index 4820ae77..15e7dfd3 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/EmptyChunk.java @@ -24,8 +24,6 @@ */ package de.bluecolored.bluemap.core.mca; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.LightData; @@ -40,13 +38,13 @@ public boolean isGenerated() { } @Override - public BlockState getBlockState(Vector3i pos) { + public BlockState getBlockState(int x, int y, int z) { return BlockState.AIR; } @Override - public LightData getLightData(Vector3i pos) { - return LightData.ZERO; + public LightData getLightData(int x, int y, int z, LightData target) { + return target.set(0, 0); } @Override diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java index 641b7757..d0c05ee4 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAChunk.java @@ -24,7 +24,6 @@ */ package de.bluecolored.bluemap.core.mca; -import com.flowpowered.math.vector.Vector3i; import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.Chunk; @@ -47,21 +46,27 @@ protected MCAChunk(CompoundTag chunkTag) { @Override public abstract boolean isGenerated(); - + + @Override public int getDataVersion() { return dataVersion; } - - public abstract BlockState getBlockState(Vector3i pos); - - public abstract LightData getLightData(Vector3i pos); - + + @Override + public abstract BlockState getBlockState(int x, int y, int z); + + @Override + public abstract LightData getLightData(int x, int y, int z, LightData target); + + @Override public abstract Biome getBiome(int x, int y, int z); + @Override public int getMaxY(int x, int z) { return 255; } + @Override public int getMinY(int x, int z) { return 0; } 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 index 57941216..21b807f6 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/mca/MCAWorld.java @@ -36,6 +36,8 @@ import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper; +import de.bluecolored.bluemap.core.util.ArrayPool; +import de.bluecolored.bluemap.core.util.math.VectorM2i; import de.bluecolored.bluemap.core.world.*; import net.querz.nbt.CompoundTag; import net.querz.nbt.ListTag; @@ -57,8 +59,8 @@ public class MCAWorld implements World { @DebugDump private final UUID uuid; @DebugDump private final Path worldFolder; private final MinecraftVersion minecraftVersion; - @DebugDump private String name; - @DebugDump private Vector3i spawnPoint; + @DebugDump private final String name; + @DebugDump private final Vector3i spawnPoint; private final LoadingCache regionCache; private final LoadingCache chunkCache; @@ -69,7 +71,7 @@ public class MCAWorld implements World { private final Map> blockStateExtensions; - @DebugDump private boolean ignoreMissingLightData; + @DebugDump private final boolean ignoreMissingLightData; private final Map forgeBlockMappings; @@ -126,36 +128,42 @@ private MCAWorld( } public BlockState getBlockState(Vector3i pos) { - return getChunk(blockToChunk(pos)).getBlockState(pos); + return getChunk(pos.getX() >> 4, pos.getZ() >> 4).getBlockState(pos.getX(), pos.getY(), pos.getZ()); } @Override public Biome getBiome(int x, int y, int z) { return getChunk(x >> 4, z >> 4).getBiome(x, y, z); } - - @Override - public Block getBlock(Vector3i pos) { - MCAChunk chunk = getChunk(blockToChunk(pos)); - BlockState blockState = getExtendedBlockState(chunk, pos); - LightData lightData = chunk.getLightData(pos); - Biome biome = chunk.getBiome(pos.getX(), pos.getY(), pos.getZ()); - BlockProperties properties = blockPropertiesMapper.get(blockState); - return new Block(this, blockState, lightData, biome, properties, pos); - } - private BlockState getExtendedBlockState(MCAChunk chunk, Vector3i pos) { - BlockState blockState = chunk.getBlockState(pos); - + @Override + public BlockState getBlockState(int x, int y, int z) { + MCAChunk chunk = getChunk(x >> 4, z >> 4); + BlockState blockState = chunk.getBlockState(x, y, z); + if (chunk instanceof ChunkAnvil112) { // only use extensions if old format chunk (1.12) in the new format block-states are saved with extensions - for (BlockStateExtension ext : blockStateExtensions.getOrDefault(blockState.getFullId(), Collections.emptyList())) { - blockState = ext.extend(this, pos, blockState); + List applicableExtensions = blockStateExtensions.getOrDefault(blockState.getFullId(), Collections.emptyList()); + if (!applicableExtensions.isEmpty()) { + Vector3i pos = new Vector3i(x, y, z); + for (BlockStateExtension ext : applicableExtensions) { + blockState = ext.extend(this, pos, blockState); + } } } - + return blockState; } + @Override + public BlockProperties getBlockProperties(BlockState blockState) { + return blockPropertiesMapper.get(blockState); + } + + @Override + public MCAChunk getChunkAtBlock(int x, int y, int z) { + return getChunk(new Vector2i(x >> 4, z >> 4)); + } + @Override public MCAChunk getChunk(int x, int z) { return getChunk(new Vector2i(x, z)); @@ -167,7 +175,11 @@ public MCAChunk getChunk(Vector2i pos) { @Override public MCARegion getRegion(int x, int z) { - return regionCache.get(new Vector2i(x, z)); + return getRegion(new Vector2i(x, z)); + } + + public MCARegion getRegion(Vector2i pos) { + return regionCache.get(pos); } @Override @@ -256,7 +268,7 @@ public void cleanUpChunkCache() { public BlockIdMapper getBlockIdMapper() { return blockIdMapper; } - + public BlockPropertiesMapper getBlockPropertiesMapper() { return blockPropertiesMapper; } @@ -415,13 +427,6 @@ public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio throw new IOException("Invaid level.dat format!", ex); } } - - public static Vector2i blockToChunk(Vector3i pos) { - return new Vector2i( - pos.getX() >> 4, - pos.getZ() >> 4 - ); - } @Override public String toString() { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/HiresTileModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/HiresTileModel.java deleted file mode 100644 index 6b200fa1..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/HiresTileModel.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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; - -public class HiresTileModel extends TileModel { - - // attributes per-vertex * per-face - private static final int - FI_UV = 2 * 3, - FI_AO = 3, - FI_COLOR = 3 , - FI_SUNLIGHT = 1 , - FI_BLOCKLIGHT = 1 , - FI_MATERIAL_INDEX = 1 ; - - private float[] color, uv, ao; - private byte[] sunlight, blocklight; - private int[] materialIndex; - - public HiresTileModel(int initialCapacity) { - super(initialCapacity); - } - - public void setUvs( - int face, - float u1, float v1, - float u2, float v2, - float u3, float v3 - ){ - int index = face * FI_UV; - - uv[index ] = u1; - uv[index + 1] = v1; - - uv[index + 2 ] = u2; - uv[index + 2 + 1] = v2; - - uv[index + 4 ] = u3; - uv[index + 4 + 1] = v3; - } - - public void setAOs( - int face, - float ao1, float ao2, float ao3 - ) { - int index = face * FI_AO; - - ao[index ] = ao1; - ao[index + 1] = ao2; - ao[index + 2] = ao3; - } - - public void setColor( - int face, - float r, float g, float b - ){ - int index = face * FI_COLOR; - - color[index ] = r; - color[index + 1] = g; - color[index + 2] = b; - } - - public void setSunlight(int face, int sl) { - sunlight[face * FI_SUNLIGHT] = (byte) sl; - } - - public void setBlocklight(int face, int bl) { - blocklight[face * FI_BLOCKLIGHT] = (byte) bl; - } - - public void setMaterialIndex(int face, int m) { - materialIndex[face * FI_MATERIAL_INDEX] = m; - } - - protected void grow(int count) { - float[] _color = color, _uv = uv, _ao = ao; - byte[] _sunlight = sunlight, _blocklight = blocklight; - int[] _materialIndex = materialIndex; - - super.grow(count); - - int size = size(); - System.arraycopy(_uv, 0, uv, 0, size * FI_UV); - System.arraycopy(_ao, 0, ao, 0, size * FI_AO); - - System.arraycopy(_color, 0, color, 0, size * FI_COLOR); - System.arraycopy(_sunlight, 0, sunlight, 0, size * FI_SUNLIGHT); - System.arraycopy(_blocklight, 0, blocklight, 0, size * FI_BLOCKLIGHT); - System.arraycopy(_materialIndex, 0, materialIndex, 0, size * FI_MATERIAL_INDEX); - } - - protected void setCapacity(int capacity) { - super.setCapacity(capacity); - - // attributes capacity * per-vertex * per-face - uv = new float [capacity * FI_UV]; - ao = new float [capacity * FI_AO]; - - color = new float [capacity * FI_COLOR]; - sunlight = new byte [capacity * FI_SUNLIGHT]; - blocklight = new byte [capacity * FI_BLOCKLIGHT]; - materialIndex = new int [capacity * FI_MATERIAL_INDEX]; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/TileModel.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/TileModel.java deleted file mode 100644 index d4644ad0..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/model/TileModel.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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.TrigMath; - -public abstract class TileModel { - private static final double GROW_MULTIPLIER = 1.5; - - private static final int FI_POSITION = 3 * 3; - private double[] position; - - private int capacity; - private int size; - - public TileModel(int initialCapacity) { - if (initialCapacity < 0) throw new IllegalArgumentException("initialCapacity is negative"); - setCapacity(initialCapacity); - clear(); - } - - public int size() { - return size; - } - - public int add(int count) { - ensureCapacity(count); - return this.size += count; - } - - public void setPositions( - int face, - double x1, double y1, double z1, - double x2, double y2, double z2, - double x3, double y3, double z3 - ){ - int index = face * FI_POSITION; - - position[index ] = x1; - position[index + 1] = y1; - position[index + 2] = z1; - - position[index + 3 ] = x2; - position[index + 3 + 1] = y2; - position[index + 3 + 2] = z2; - - position[index + 6 ] = x3; - position[index + 6 + 1] = y3; - position[index + 6 + 2] = z3; - } - - public void rotate( - int start, int count, - float angle, float axisX, float axisY, float axisZ - ) { - - // create quaternion - double halfAngle = Math.toRadians(angle) * 0.5; - double q = TrigMath.sin(halfAngle) / Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); - - double //quaternion - qx = axisX * q, - qy = axisY * q, - qz = axisZ * q, - qw = TrigMath.cos(halfAngle), - qLength = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw); - - // normalize quaternion - qx /= qLength; - qy /= qLength; - qz /= qLength; - qw /= qLength; - - rotateWithQuaternion(start, count, qx, qy, qz, qw); - } - - public void rotate( - int start, int count, - float pitch, float yaw, float roll - ) { - - double - halfYaw = Math.toRadians(yaw) * 0.5, - qy1 = TrigMath.sin(halfYaw), - qw1 = TrigMath.cos(halfYaw), - - halfPitch = Math.toRadians(pitch) * 0.5, - qx2 = TrigMath.sin(halfPitch), - qw2 = TrigMath.cos(halfPitch), - - halfRoll = Math.toRadians(roll) * 0.5, - qz3 = TrigMath.sin(halfRoll), - qw3 = TrigMath.cos(halfRoll); - - // multiply 1 with 2 - double - qxA = qw1 * qx2, - qyA = qy1 * qw2, - qzA = - qy1 * qx2, - qwA = qw1 * qw2; - - // multiply with 3 - double - qx = qxA * qw3 + qyA * qz3, - qy = qyA * qw3 - qxA * qz3, - qz = qwA * qz3 + qzA * qw3, - qw = qwA * qw3 - qzA * qz3; - - rotateWithQuaternion(start, count, qx, qy, qz, qw); - } - - private void rotateWithQuaternion( - int start, int count, - double qx, double qy, double qz, double qw - ) { - double x, y, z, px, py, pz, pw; - int end = start + count, index; - for (int face = start; face < end; face++) { - index = face * FI_POSITION; - - x = position[index ]; - y = position[index + 1]; - z = position[index + 2]; - - px = qw * x + qy * z - qz * y; - py = qw * y + qz * x - qx * z; - pz = qw * z + qx * y - qy * x; - pw = -qx * x - qy * y - qz * z; - - position[index ] = pw * -qx + px * qw - py * qz + pz * qy; - position[index + 1] = pw * -qy + py * qw - pz * qx + px * qz; - position[index + 2] = pw * -qz + pz * qw - px * qy + py * qx; - } - } - - public void scale( - int start, int count, - float scale - ) { - int startIndex = start * FI_POSITION; - int endIndex = count * FI_POSITION + startIndex; - - for (int i = startIndex; i < endIndex; i++) - position[i] *= scale; - } - - public void translate( - int start, int count, - double dx, double dy, double dz - ) { - int end = start + count, index; - for (int face = start; face < end; face++) { - index = face * FI_POSITION; - for (int i = 0; i < 3; i++) { - position[index ] += dx; - position[index + 1] += dy; - position[index + 2] += dz; - } - } - } - - public void clear() { - this.size = 0; - } - - protected void grow(int count) { - double[] _position = position; - - int newCapacity = (int) (capacity * GROW_MULTIPLIER) + count; - setCapacity(newCapacity); - - System.arraycopy(_position, 0, position, 0, size * FI_POSITION); - } - - private void ensureCapacity(int count) { - if (size + count > capacity){ - grow(count); - } - } - - protected void setCapacity(int capacity) { - this.capacity = capacity; - position = new double [capacity * FI_POSITION]; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java deleted file mode 100644 index 2b752b0c..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculator.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * 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.GenericMath; -import com.flowpowered.math.vector.Vector2f; -import com.flowpowered.math.vector.Vector2i; -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector3i; -import de.bluecolored.bluemap.core.debug.DebugDump; -import de.bluecolored.bluemap.core.util.ConfigUtils; -import de.bluecolored.bluemap.core.util.MathUtils; -import de.bluecolored.bluemap.core.world.Biome; -import de.bluecolored.bluemap.core.world.Block; -import de.bluecolored.bluemap.core.world.World; -import org.spongepowered.configurate.ConfigurationNode; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.Function; - -@DebugDump -public class BlockColorCalculator { - - private BufferedImage foliageMap; - private BufferedImage grassMap; - - private final Map> blockColorMap; - - public BlockColorCalculator(BufferedImage foliageMap, BufferedImage grassMap) { - this.foliageMap = foliageMap; - this.grassMap = grassMap; - - this.blockColorMap = new HashMap<>(); - } - - public void loadColorConfig(ConfigurationNode colorConfig) { - blockColorMap.clear(); - - for (Entry entry : colorConfig.childrenMap().entrySet()){ - String key = entry.getKey().toString(); - String value = entry.getValue().getString(""); - - Function colorFunction; - switch (value) { - case "@foliage": - colorFunction = this::getFoliageAverageColor; - break; - case "@grass": - colorFunction = this::getGrassAverageColor; - break; - case "@water": - colorFunction = this::getWaterAverageColor; - break; - default: - final Vector3f color = MathUtils.color3FromInt(ConfigUtils.readColorInt(entry.getValue())); - colorFunction = context -> color; - break; - } - - blockColorMap.put(key, colorFunction); - } - } - - public Vector3f getBlockColor(Block block){ - String blockId = block.getBlockState().getFullId(); - - Function colorFunction = blockColorMap.get(blockId); - if (colorFunction == null) colorFunction = blockColorMap.get("default"); - if (colorFunction == null) colorFunction = this::getFoliageAverageColor; - - return colorFunction.apply(block); - } - - public Vector3f getWaterAverageColor(Block block){ - Vector3f color = Vector3f.ZERO; - - int count = 0; - for (Biome biome : iterateAverageBiomes(block)) { - color = color.add(biome.getWaterColor()); - count++; - } - - return color.div(count); - } - - public Vector3f getFoliageAverageColor(Block block){ - Vector3f color = Vector3f.ZERO; - - int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); - - int count = 0; - for (Biome biome : iterateAverageBiomes(block)) { - color = color.add(getFoliageColor(biome, blocksAboveSeaLevel)); - count++; - } - - return color.div(count); - } - - public Vector3f getFoliageColor(Biome biome, int blocksAboveSeaLevel){ - Vector3f mapColor = getColorFromMap(biome, blocksAboveSeaLevel, foliageMap); - Vector3f overlayColor = biome.getOverlayFoliageColor().toVector3(); - float overlayAlpha = biome.getOverlayFoliageColor().getW(); - return mapColor.mul(1f - overlayAlpha).add(overlayColor.mul(overlayAlpha)); - } - - public Vector3f getGrassAverageColor(Block block){ - Vector3f color = Vector3f.ZERO; - - int blocksAboveSeaLevel = Math.max(block.getPosition().getY() - block.getWorld().getSeaLevel(), 0); - - int count = 0; - for (Biome biome : iterateAverageBiomes(block)) { - color = color.add(getGrassColor(biome, blocksAboveSeaLevel)); - count++; - } - - return color.div(count); - } - - public Vector3f getGrassColor(Biome biome, int blocksAboveSeaLevel){ - Vector3f mapColor = getColorFromMap(biome, blocksAboveSeaLevel, grassMap); - Vector3f overlayColor = biome.getOverlayGrassColor().toVector3(); - float overlayAlpha = biome.getOverlayGrassColor().getW(); - return mapColor.mul(1f - overlayAlpha).add(overlayColor.mul(overlayAlpha)); - } - - private Vector3f getColorFromMap(Biome biome, int blocksAboveSeaLevel, BufferedImage map){ - Vector2i pixel = getColorMapPosition(biome, 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(Biome biome, int blocksAboveSeaLevel){ - float adjTemp = (float) GenericMath.clamp(biome.getTemp() - (0.00166667 * blocksAboveSeaLevel), 0d, 1d); - float adjHumidity = (float) GenericMath.clamp(biome.getHumidity(), 0d, 1d) * adjTemp; - return new Vector2f(1 - adjTemp, 1 - adjHumidity); - } - - private Iterable iterateAverageBiomes(Block block){ - Vector3i pos = block.getPosition(); - Vector3i radius = new Vector3i(2, 1, 2); - - final World world = block.getWorld(); - final int sx = pos.getX() - radius.getX(), - sy = Math.max(0, pos.getY() - radius.getY()), - sz = pos.getZ() - radius.getZ(); - final int mx = pos.getX() + radius.getX(), - my = Math.min(255, pos.getY() + radius.getY()), - mz = pos.getZ() + radius.getZ(); - - return () -> new Iterator() { - private int x = sx, - y = sy, - z = sz; - - @Override - public boolean hasNext() { - return z < mz || y < my || x < mx; - } - - @Override - public Biome next() { - x++; - if (x > mx) { - x = sx; - y++; - } - if (y > my) { - y = sy; - z++; - } - - return world.getBiome(x, y, z); - } - }; - } - - public BufferedImage getFoliageMap() { - return foliageMap; - } - - public void setFoliageMap(BufferedImage foliageMap) { - this.foliageMap = foliageMap; - } - - public BufferedImage getGrassMap() { - return grassMap; - } - - public void setGrassMap(BufferedImage grassMap) { - this.grassMap = grassMap; - } - -} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculatorFactory.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculatorFactory.java new file mode 100644 index 00000000..f4fc1925 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockColorCalculatorFactory.java @@ -0,0 +1,251 @@ +/* + * 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.GenericMath; +import de.bluecolored.bluemap.core.debug.DebugDump; +import de.bluecolored.bluemap.core.util.ConfigUtils; +import de.bluecolored.bluemap.core.util.math.Color; +import de.bluecolored.bluemap.core.world.Biome; +import de.bluecolored.bluemap.core.world.Block; +import org.spongepowered.configurate.ConfigurationNode; + +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +@DebugDump +public class BlockColorCalculatorFactory { + + private BufferedImage foliageMap; + private BufferedImage grassMap; + + private final Map blockColorMap; + + public BlockColorCalculatorFactory(BufferedImage foliageMap, BufferedImage grassMap) { + this.foliageMap = foliageMap; + this.grassMap = grassMap; + + this.blockColorMap = new HashMap<>(); + } + + public void loadColorConfig(ConfigurationNode colorConfig) { + blockColorMap.clear(); + + for (Entry entry : colorConfig.childrenMap().entrySet()){ + String key = entry.getKey().toString(); + String value = entry.getValue().getString(""); + + ColorFunction colorFunction; + switch (value) { + case "@foliage": + colorFunction = BlockColorCalculator::getFoliageAverageColor; + break; + case "@grass": + colorFunction = BlockColorCalculator::getGrassAverageColor; + break; + case "@water": + colorFunction = BlockColorCalculator::getWaterAverageColor; + break; + case "@redstone": + colorFunction = BlockColorCalculator::getRedstoneColor; + break; + default: + final Color color = new Color(); + color.set(ConfigUtils.readColorInt(entry.getValue())).premultiplied(); + colorFunction = (calculator, block, target) -> target.set(color); + break; + } + + blockColorMap.put(key, colorFunction); + } + } + + public void setFoliageMap(BufferedImage foliageMap) { + this.foliageMap = foliageMap; + } + + public BufferedImage getFoliageMap() { + return foliageMap; + } + + public void setGrassMap(BufferedImage grassMap) { + this.grassMap = grassMap; + } + + public BufferedImage getGrassMap() { + return grassMap; + } + + public BlockColorCalculator createCalculator() { + return new BlockColorCalculator(); + } + + @FunctionalInterface + private interface ColorFunction { + Color invoke(BlockColorCalculator calculator, Block block, Color target); + } + + public class BlockColorCalculator { + + private final Color tempColor = new Color(); + + public Color getBlockColor(Block block, Color target) { + String blockId = block.getBlockState().getFullId(); + + ColorFunction colorFunction = blockColorMap.get(blockId); + if (colorFunction == null) colorFunction = blockColorMap.get("default"); + if (colorFunction == null) colorFunction = BlockColorCalculator::getFoliageAverageColor; + + return colorFunction.invoke(this, block, target); + } + + public Color getRedstoneColor(Block block, Color target) { + String powerString = block.getBlockState().getProperties().get("power"); + + int power = 15; + if (powerString != null) { + power = Integer.parseInt(powerString); + } + + return target.set( + (power + 5f) / 20f, 0f, 0f, + 1f, true + ); + } + + public Color getWaterAverageColor(Block block, Color target) { + target.set(0, 0, 0, 0, true); + + int x, y, z, + minX = block.getX() - 2, + maxX = block.getX() + 2, + minY = block.getY() - 1, + maxY = block.getY() + 1, + minZ = block.getZ() - 2, + maxZ = block.getZ() + 2; + + for (x = minX; x <= maxX; x++) { + for (y = minY; y <= maxY; y++) { + for (z = minZ; z <= maxZ; z++) { + target.add(block + .getWorld() + .getBiome(x, y, z) + .getWaterColor() + ); + } + } + } + + return target.flatten(); + } + + public Color getFoliageAverageColor(Block block, Color target) { + target.set(0, 0, 0, 0, true); + + int x, y, z, + minX = block.getX() - 2, + maxX = block.getX() + 2, + minY = block.getY() - 1, + maxY = block.getY() + 1, + minZ = block.getZ() - 2, + maxZ = block.getZ() + 2; + + int seaLevel = block.getWorld().getSeaLevel(); + int blocksAboveSeaLevel; + Biome biome; + + for (y = minY; y <= maxY; y++) { + blocksAboveSeaLevel = Math.max(block.getY() - seaLevel, 0); + + for (x = minX; x <= maxX; x++) { + for (z = minZ; z <= maxZ; z++) { + biome = block.getWorld().getBiome(x, y, z); + target.add(getFoliageColor(biome, blocksAboveSeaLevel, tempColor)); + } + } + } + + return target.flatten(); + } + + public Color getFoliageColor(Biome biome, int blocksAboveSeaLevel, Color target) { + getColorFromMap(biome, blocksAboveSeaLevel, foliageMap, target); + return target.overlay(biome.getOverlayFoliageColor()); + } + + public Color getGrassAverageColor(Block block, Color target) { + target.set(0, 0, 0, 0, true); + + int x, y, z, + minX = block.getX() - 2, + maxX = block.getX() + 2, + minY = block.getY() - 1, + maxY = block.getY() + 1, + minZ = block.getZ() - 2, + maxZ = block.getZ() + 2; + + int seaLevel = block.getWorld().getSeaLevel(); + int blocksAboveSeaLevel; + Biome biome; + + for (y = minY; y <= maxY; y++) { + blocksAboveSeaLevel = Math.max(block.getY() - seaLevel, 0); + + for (x = minX; x <= maxX; x++) { + for (z = minZ; z <= maxZ; z++) { + biome = block.getWorld().getBiome(x, y, z); + target.add(getGrassColor(biome, blocksAboveSeaLevel, tempColor)); + } + } + } + + return target.flatten(); + } + + public Color getGrassColor(Biome biome, int blocksAboveSeaLevel, Color target) { + getColorFromMap(biome, blocksAboveSeaLevel, grassMap, target); + return target.overlay(biome.getOverlayGrassColor()); + } + + private void getColorFromMap(Biome biome, int blocksAboveSeaLevel, BufferedImage map, Color target) { + float adjTemp = (float) GenericMath.clamp(biome.getTemp() - (0.00166667 * blocksAboveSeaLevel), 0, 1); + float adjHumidity = (float) GenericMath.clamp(biome.getHumidity(), 0, 1) * adjTemp; + + int x = (int) ((1 - adjTemp) * map.getWidth()); + int y = (int) ((1 - adjHumidity) * map.getHeight()); + + int cValue = map.getRGB( + GenericMath.clamp(x, 0, map.getWidth() - 1), + GenericMath.clamp(y, 0, map.getHeight() - 1) + ); + + target.set(cValue); + } + + } + +} 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 index cdde5c7f..fe83444e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockModelResource.java @@ -24,12 +24,15 @@ */ package de.bluecolored.bluemap.core.resourcepack; +import com.flowpowered.math.TrigMath; import com.flowpowered.math.vector.Vector3f; +import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Face; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; -import de.bluecolored.bluemap.core.util.Axis; import de.bluecolored.bluemap.core.util.Direction; +import de.bluecolored.bluemap.core.util.math.Axis; +import de.bluecolored.bluemap.core.util.math.MatrixM4f; import org.spongepowered.configurate.ConfigurationNode; import org.spongepowered.configurate.gson.GsonConfigurationLoader; @@ -39,6 +42,7 @@ import java.util.Map.Entry; public class BlockModelResource { + private static final double FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2); private ModelType modelType = ModelType.NORMAL; @@ -79,6 +83,7 @@ public class Element { private Vector3f from = Vector3f.ZERO, to = new Vector3f(16f, 16f, 16f); private Rotation rotation = new Rotation(); + private MatrixM4f rotationMatrix; private boolean shade = true; private EnumMap faces = new EnumMap<>(Direction.class); private boolean fullCube = false; @@ -134,6 +139,10 @@ public Rotation getRotation() { return rotation; } + public MatrixM4f getRotationMatrix() { + return rotationMatrix; + } + public boolean isShade() { return shade; } @@ -318,7 +327,7 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements break; } - if (texture.getColor().getW() < 1) { + if (texture.getColorStraight().a < 1) { blockModel.culling = false; break; } @@ -346,6 +355,37 @@ private Element buildElement(BlockModelResource model, ConfigurationNode node, S element.rotation.axis = Axis.fromString(node.node("rotation", "axis").getString("x")); if (!node.node("rotation", "origin").virtual()) element.rotation.origin = readVector3f(node.node("rotation", "origin")); element.rotation.rescale = node.node("rotation", "rescale").getBoolean(false); + + // rotation matrix + float angle = element.rotation.angle; + Vector3i axis = element.rotation.axis.toVector(); + Vector3f origin = element.rotation.origin; + boolean rescale = element.rotation.rescale; + + MatrixM4f rot = new MatrixM4f(); + if (angle != 0f) { + rot.translate(-origin.getX(), -origin.getY(), -origin.getZ()); + rot.rotate( + angle, + axis.getX(), + axis.getY(), + axis.getZ() + ); + + if (rescale) { + float scale = (float) (Math.abs(TrigMath.sin(angle * TrigMath.DEG_TO_RAD)) * FIT_TO_BLOCK_SCALE_MULTIPLIER); + rot.scale( + (1 - axis.getX()) * scale + 1, + (1 - axis.getY()) * scale + 1, + (1 - axis.getZ()) * scale + 1 + ); + } + + rot.translate(origin.getX(), origin.getY(), origin.getZ()); + } + element.rotationMatrix = rot; + } else { + element.rotationMatrix = new MatrixM4f(); } boolean allDirs = true; 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 index d1b0e56e..0873eb11 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/BlockStateResource.java @@ -51,15 +51,14 @@ public class BlockStateResource { private final List variants = new ArrayList<>(0); private final Collection multipart = new ArrayList<>(0); - private BlockStateResource() { + private BlockStateResource() {} + + public Collection getModels(BlockState blockState, Collection targetCollection) { + return getModels(blockState, 0, 0, 0, targetCollection); } - public Collection getModels(BlockState blockState) { - return getModels(blockState, Vector3i.ZERO); - } - - public Collection getModels(BlockState blockState, Vector3i pos) { - Collection models = new ArrayList<>(1); + public Collection getModels(BlockState blockState, int x, int y, int z, Collection targetCollection) { + targetCollection.clear(); Variant allMatch = null; for (Variant variant : variants) { @@ -68,29 +67,29 @@ public Collection getModels(BlockState blockState if (allMatch == null) allMatch = variant; continue; } - - models.add(variant.getModel(pos)); - return models; + + targetCollection.add(variant.getModel(x, y, z)); + return targetCollection; } } if (allMatch != null) { - models.add(allMatch.getModel(pos)); - return models; + targetCollection.add(allMatch.getModel(x, y, z)); + return targetCollection; } for (Variant variant : multipart) { if (variant.condition.matches(blockState)) { - models.add(variant.getModel(pos)); + targetCollection.add(variant.getModel(x, y, z)); } } //fallback to first variant - if (models.isEmpty() && !variants.isEmpty()) { - models.add(variants.get(0).getModel(pos)); + if (targetCollection.isEmpty() && !variants.isEmpty()) { + targetCollection.add(variants.get(0).getModel(x, y, z)); } - return models; + return targetCollection; } private static class Variant { @@ -103,10 +102,10 @@ private static class Variant { private Variant() { } - public TransformedBlockModelResource getModel(Vector3i pos) { + public TransformedBlockModelResource getModel(int x, int y, int z) { if (models.isEmpty()) throw new IllegalStateException("A variant must have at least one model!"); - double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; // random based on position + double selection = MathUtils.hashToFloat(x, y, z, 827364) * totalWeight; // random based on position for (Weighted w : models) { selection -= w.weight; if (selection <= 0) return w.value; 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 index 3f94e712..53a5a12d 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/ResourcePack.java @@ -55,17 +55,17 @@ public class ResourcePack { private final MinecraftVersion minecraftVersion; - protected Map blockStateResources; - protected Map blockModelResources; - protected TextureGallery textures; + protected final Map blockStateResources; + protected final Map blockModelResources; + protected final TextureGallery textures; - private final BlockColorCalculator blockColorCalculator; + private final BlockColorCalculatorFactory blockColorCalculatorFactory; + private final Map> configs; + private BufferedImage foliageMap; private BufferedImage grassMap; - private Map> configs; - public ResourcePack(MinecraftVersion minecraftVersion) { this.minecraftVersion = minecraftVersion; @@ -76,7 +76,7 @@ public ResourcePack(MinecraftVersion minecraftVersion) { foliageMap.setRGB(0, 0, 0xFF00FF00); grassMap = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB); grassMap.setRGB(0, 0, 0xFF00FF00); - blockColorCalculator = new BlockColorCalculator(foliageMap, grassMap); + blockColorCalculatorFactory = new BlockColorCalculatorFactory(foliageMap, grassMap); configs = new HashMap<>(); } @@ -181,14 +181,14 @@ public void load(File... sources) throws InterruptedException { try { foliageMap = ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/foliage.png")); - blockColorCalculator.setFoliageMap(foliageMap); + blockColorCalculatorFactory.setFoliageMap(foliageMap); } catch (IOException ex) { Logger.global.logError("Failed to load foliagemap!", ex); } try { grassMap = ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/grass.png")); - blockColorCalculator.setGrassMap(grassMap); + blockColorCalculatorFactory.setGrassMap(grassMap); } catch (IOException ex) { Logger.global.logError("Failed to load grassmap!", ex); } @@ -210,8 +210,8 @@ public BlockStateResource getBlockStateResource(BlockState state) throws NoSuchR return resource; } - public BlockColorCalculator getBlockColorCalculator() { - return blockColorCalculator; + public BlockColorCalculatorFactory getBlockColorCalculatorFactory() { + return blockColorCalculatorFactory; } public MinecraftVersion getMinecraftVersion() { diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java index 14c3985a..7624a05b 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/Texture.java @@ -24,20 +24,21 @@ */ package de.bluecolored.bluemap.core.resourcepack; -import com.flowpowered.math.vector.Vector4f; +import de.bluecolored.bluemap.core.util.math.Color; public class Texture { private final int id; private final String path; - private Vector4f color; - private boolean isHalfTransparent; - private String texture; + private final Color color, colorPremultiplied; + private final boolean isHalfTransparent; + private final String texture; - protected Texture(int id, String path, Vector4f color, boolean halfTransparent, String texture) { + protected Texture(int id, String path, Color color, boolean halfTransparent, String texture) { this.id = id; this.path = path; - this.color = color; + this.color = new Color().set(color).straight(); + this.colorPremultiplied = new Color().set(color).premultiplied(); this.isHalfTransparent = halfTransparent; this.texture = texture; } @@ -54,9 +55,17 @@ public String getPath() { * Returns the calculated median color of the {@link Texture}. * @return The median color of this {@link Texture} */ - public Vector4f getColor() { + public Color getColorStraight() { return color; } + + /** + * Returns the calculated median color of the {@link Texture} (premultiplied). + * @return The (premultiplied) median color of this {@link Texture} + */ + public Color getColorPremultiplied() { + return colorPremultiplied; + } /** * Returns whether the {@link Texture} has half-transparent pixels or not. diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java index 42f2980a..ce653215 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TextureGallery.java @@ -24,11 +24,11 @@ */ package de.bluecolored.bluemap.core.resourcepack; -import com.flowpowered.math.vector.Vector4f; import com.google.gson.*; import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess; import de.bluecolored.bluemap.core.util.FileUtils; +import de.bluecolored.bluemap.core.util.math.Color; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -95,12 +95,12 @@ public void saveTextureFile(File file) throws IOException { textureNode.addProperty("texture", texture.getTexture()); textureNode.addProperty("transparent", texture.isHalfTransparent()); - Vector4f color = texture.getColor(); + Color color = texture.getColorStraight(); JsonArray colorNode = new JsonArray(); - colorNode.add(color.getX()); - colorNode.add(color.getY()); - colorNode.add(color.getZ()); - colorNode.add(color.getW()); + colorNode.add(color.r); + colorNode.add(color.g); + colorNode.add(color.b); + colorNode.add(color.a); textureNode.add("color", colorNode); @@ -141,14 +141,14 @@ public synchronized void loadTextureFile(File file) throws IOException, ParseRes int size = textures.size(); for (int i = 0; i < size; i++) { while (i >= textureList.size()) { //prepopulate with placeholder so we don't get an IndexOutOfBounds below - textureList.add(new Texture(textureList.size(), "empty", Vector4f.ZERO, false, EMPTY_BASE64)); + textureList.add(new Texture(textureList.size(), "empty", new Color(), false, EMPTY_BASE64)); } try { JsonObject texture = textures.get(i).getAsJsonObject(); String path = texture.get("id").getAsString(); boolean transparent = texture.get("transparent").getAsBoolean(); - Vector4f color = readVector4f(texture.get("color").getAsJsonArray()); + Color color = readColor(texture.get("color").getAsJsonArray()); textureList.set(i, new Texture(i, path, color, transparent, EMPTY_BASE64)); } catch (ParseResourceException | RuntimeException ex) { Logger.global.logWarning("Failed to load texture with id " + i + " from texture file " + file + "!"); @@ -188,7 +188,7 @@ public synchronized Texture loadTexture(FileAccess fileAccess, String path) thro boolean halfTransparent = checkHalfTransparent(image); //calculate color - Vector4f color = calculateColor(image); + Color color = calculateColor(image); //write to Base64 ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -237,7 +237,7 @@ private synchronized void regenerateMap() { } } - private Vector4f readVector4f(JsonArray jsonArray) throws ParseResourceException { + private Color readColor(JsonArray jsonArray) throws ParseResourceException { if (jsonArray.size() < 4) throw new ParseResourceException("Failed to load Vector4: Not enough values in list-node!"); float r = jsonArray.get(0).getAsFloat(); @@ -245,7 +245,7 @@ private Vector4f readVector4f(JsonArray jsonArray) throws ParseResourceException float b = jsonArray.get(2).getAsFloat(); float a = jsonArray.get(3).getAsFloat(); - return new Vector4f(r, g, b, a); + return new Color().set(r, g, b, a, false); } private boolean checkHalfTransparent(BufferedImage image){ @@ -262,17 +262,17 @@ private boolean checkHalfTransparent(BufferedImage image){ return false; } - private Vector4f calculateColor(BufferedImage image){ - double alpha = 0d, red = 0d, green = 0d, blue = 0d; + private Color calculateColor(BufferedImage image){ + float alpha = 0f, red = 0f, green = 0f, blue = 0f; int count = 0; for (int x = 0; x < image.getWidth(); x++){ for (int y = 0; y < image.getHeight(); y++){ int pixel = image.getRGB(x, y); - double pixelAlpha = (double)((pixel >> 24) & 0xff) / (double) 0xff; - double pixelRed = (double)((pixel >> 16) & 0xff) / (double) 0xff; - double pixelGreen = (double)((pixel >> 8) & 0xff) / (double) 0xff; - double pixelBlue = (double)((pixel >> 0) & 0xff) / (double) 0xff; + float pixelAlpha = ((pixel >> 24) & 0xff) / 255f; + float pixelRed = ((pixel >> 16) & 0xff) / 255f; + float pixelGreen = ((pixel >> 8) & 0xff) / 255f; + float pixelBlue = (pixel & 0xff) / 255f; count++; alpha += pixelAlpha; @@ -282,14 +282,14 @@ private Vector4f calculateColor(BufferedImage image){ } } - if (count == 0 || alpha == 0) return Vector4f.ZERO; + if (count == 0 || alpha == 0) return new Color(); red /= alpha; green /= alpha; blue /= alpha; alpha /= count; - return new Vector4f(red, green, blue, alpha); + return new Color().set(red, green, blue, alpha, false); } } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java index 90cf0eb2..c44bcb4f 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/resourcepack/TransformedBlockModelResource.java @@ -25,24 +25,43 @@ package de.bluecolored.bluemap.core.resourcepack; import com.flowpowered.math.vector.Vector2f; +import de.bluecolored.bluemap.core.util.math.MatrixM3f; public class TransformedBlockModelResource { - private Vector2f rotation = Vector2f.ZERO; - private boolean uvLock = false; - - private BlockModelResource model; + private final Vector2f rotation; + private final boolean uvLock; + private final BlockModelResource model; + + private final boolean hasRotation; + private final MatrixM3f rotationMatrix; public TransformedBlockModelResource(Vector2f rotation, boolean uvLock, BlockModelResource model) { this.model = model; this.rotation = rotation; this.uvLock = uvLock; + + this.hasRotation = !rotation.equals(Vector2f.ZERO); + this.rotationMatrix = new MatrixM3f() + .rotate( + -rotation.getX(), + -rotation.getY(), + 0 + ); } - + public Vector2f getRotation() { return rotation; } - + + public boolean hasRotation() { + return hasRotation; + } + + public MatrixM3f getRotationMatrix() { + return rotationMatrix; + } + public boolean isUVLock() { return uvLock; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ArrayPool.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ArrayPool.java new file mode 100644 index 00000000..ac3721b3 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ArrayPool.java @@ -0,0 +1,59 @@ +package de.bluecolored.bluemap.core.util; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; + +/** + * A pool of objects that (lazily) maintains a specific size of objects. + * that threads can take, use and return. + * It discards excessive objects and creates new ones when needed. + */ +public class ArrayPool { + + private final Object[] objects; + private final int capacity, maxConcurrency; + private final Supplier supplier; + + private final AtomicInteger head, tail; // head is excluded, tail included + private int size; + + public ArrayPool(int capacity, int maxConcurrency, Supplier supplier) { + this.capacity = capacity; + this.objects = new Object[capacity + maxConcurrency * 2]; + this.maxConcurrency = maxConcurrency; + this.supplier = supplier; + + this.head = new AtomicInteger(0); + this.tail = new AtomicInteger(0); + this.size = 0; + } + + @SuppressWarnings("unchecked") + public T take() { + while (size < maxConcurrency) add(supplier.get()); + + size --; + return (T) objects[tail.getAndUpdate(this::nextPointer)]; + } + + + public void add(T resource) { + while (size > capacity + maxConcurrency) take(); + + objects[head.getAndUpdate(this::nextPointer)] = resource; + size ++; + } + + public int size() { + return size; + } + + public int capacity() { + return capacity; + } + + private int nextPointer(int prev) { + return (prev + 1) % objects.length; + } + +} 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 index 6b4000d2..c27d3b86 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Direction.java @@ -25,6 +25,7 @@ package de.bluecolored.bluemap.core.util; import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.util.math.Axis; import java.util.Objects; @@ -61,11 +62,11 @@ public enum Direction { EAST.right = SOUTH; } - private Vector3i dir; - private Axis axis; + private final Vector3i dir; + private final Axis axis; private Direction opposite, left, right; - private Direction(int x, int y, int z, Axis axis) { + Direction(int x, int y, int z, Axis axis) { this.dir = new Vector3i(x, y, z); this.axis = axis; this.opposite = null; 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 deleted file mode 100644 index f2fc82c4..00000000 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/IntersectionPoint.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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/MathUtils.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java index 979b240d..d944ec71 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/MathUtils.java @@ -28,8 +28,8 @@ import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; -import de.bluecolored.bluemap.core.model.VectorM3f; +@Deprecated //TODO public class MathUtils { private MathUtils() {} @@ -73,27 +73,6 @@ public static Vector3f getSurfaceNormal(Vector3f p1, Vector3f p2, Vector3f p3) { return n; } - /** - * Calculates the surface-normal of a plane spanned between three vectors. - * @param p1 The first vector - * @param p2 The second vector - * @param p3 The third vector - * @return The calculated normal - */ - public static VectorM3f getSurfaceNormal(VectorM3f p1, VectorM3f p2, VectorM3f p3) { - float ux = p2.x - p1.x, uy = p2.y - p1.y, uz = p2.z - p1.z; - float vx = p3.x - p1.x, vy = p3.y - p1.y, vz = p3.z - p1.z; - - float nX = uy * vz - uz * vy; - float nY = uz * vx - ux * vz; - float nZ = ux * vy - uy * vx; - - VectorM3f n = new VectorM3f(nX, nY, nZ); - n.normalize(); - - return n; - } - /** * Hashes the provided position to a random float between 0 and 1.
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 index 16f08e00..1fa9f3ec 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ModelUtils.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/ModelUtils.java @@ -31,6 +31,7 @@ import de.bluecolored.bluemap.core.model.Face; import de.bluecolored.bluemap.core.model.Model; +@Deprecated //TODO public class ModelUtils { private ModelUtils() {} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Axis.java similarity index 97% rename from BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java rename to BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Axis.java index 665e6caa..faa496a7 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/Axis.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Axis.java @@ -22,7 +22,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package de.bluecolored.bluemap.core.util; +package de.bluecolored.bluemap.core.util.math; import com.flowpowered.math.vector.Vector3i; diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Color.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Color.java new file mode 100644 index 00000000..e314ce73 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/Color.java @@ -0,0 +1,125 @@ +package de.bluecolored.bluemap.core.util.math; + +public class Color { + + public float r, g, b, a; + public boolean premultiplied; + + public Color set(float r, float g, float b, float a, boolean premultiplied) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + this.premultiplied = premultiplied; + return this; + } + + public Color set(Color color) { + this.r = color.r; + this.g = color.g; + this.b = color.b; + this.a = color.a; + this.premultiplied = color.premultiplied; + return this; + } + + public Color set(int color) { + this.r = ((color >> 16) & 0xFF) / 255f; + this.g = ((color >> 8) & 0xFF) / 255f; + this.b = (color & 0xFF) / 255f; + this.a = ((color >> 24) & 0xFF) / 255f; + this.premultiplied = false; + + if (this.a == 0) this.a = 1f; // if the given integer does not define an alpha, we assume 1f + + return this; + } + + public Color add(Color color) { + if (color.a < 1f && !color.premultiplied){ + throw new IllegalArgumentException("Can only add premultiplied colors with alpha!"); + } + + premultiplied(); + + this.r += color.r; + this.g += color.g; + this.b += color.b; + this.a += color.a; + + return this; + } + + public Color multiply(Color color) { + if (color.premultiplied) premultiplied(); + else straight(); + + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; + this.a *= color.a; + + return this; + } + + public Color overlay(Color color) { + if (color.a < 1f && !color.premultiplied) throw new IllegalArgumentException("Can only overlay premultiplied colors with alpha!"); + + premultiplied(); + + float p = 1 - color.a; + this.a = p * this.a + color.a; + this.r = p * this.r + color.r; + this.g = p * this.g + color.g; + this.b = p * this.b + color.b; + + return this; + } + + public Color flatten() { + if (this.a == 1f) return this; + + if (premultiplied) { + this.r /= this.a; + this.g /= this.a; + this.b /= this.a; + } + + this.a = 1f; + + return this; + } + + public Color premultiplied() { + if (!premultiplied) { + this.r *= this.a; + this.g *= this.a; + this.b *= this.a; + this.premultiplied = true; + } + return this; + } + + public Color straight() { + if (premultiplied) { + float m = 1f / this.a; + this.r *= m; + this.g *= m; + this.b *= m; + this.premultiplied = false; + } + return this; + } + + @Override + public String toString() { + return "Color{" + + "r=" + r + + ", g=" + g + + ", b=" + b + + ", a=" + a + + ", premultiplied=" + premultiplied + + '}'; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM3f.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM3f.java new file mode 100644 index 00000000..e112ef61 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM3f.java @@ -0,0 +1,186 @@ +/* + * 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.math; + +import com.flowpowered.math.TrigMath; + +public class MatrixM3f { + + public float m00 = 1f, m01, m02; + public float m10, m11 = 1f, m12; + public float m20, m21, m22 = 1f; + + public MatrixM3f set( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 + ) { + this.m00 = m00; this.m01 = m01; this.m02 = m02; + this.m10 = m10; this.m11 = m11; this.m12 = m12; + this.m20 = m20; this.m21 = m21; this.m22 = m22; + return this; + } + + public MatrixM3f invert() { + float det = determinant(); + return set( + (m11 * m22 - m21 * m12) / det, -(m01 * m22 - m21 * m02) / det, (m01 * m12 - m02 * m11) / det, + -(m10 * m22 - m20 * m12) / det, (m00 * m22 - m20 * m02) / det, -(m00 * m12 - m10 * m02) / det, + (m10 * m21 - m20 * m11) / det, -(m00 * m21 - m20 * m01) / det, (m00 * m11 - m01 * m10) / det + ); + } + + public MatrixM3f identity() { + return set( + 1f, 0f, 0f, + 0f, 1f, 0f, + 0f, 0f, 1f + ); + } + + public MatrixM3f scale(float x, float y, float z) { + return multiplyTo( + x, 0f, 0f, + 0f, y, 0f, + 0f, 0f, z + ); + } + + public MatrixM3f translate(float x, float y) { + return multiplyTo( + 1f, 0f, x, + 0f, 1f, y, + 0f, 0f, 1f + ); + } + + public MatrixM3f rotate(float angle, float axisX, float axisY, float axisZ) { + + // create quaternion + double halfAngle = Math.toRadians(angle) * 0.5; + double q = TrigMath.sin(halfAngle) / Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); + + double //quaternion + qx = axisX * q, + qy = axisY * q, + qz = axisZ * q, + qw = TrigMath.cos(halfAngle), + qLength = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw); + + // normalize quaternion + qx /= qLength; + qy /= qLength; + qz /= qLength; + qw /= qLength; + + return rotateByQuaternion((float) qx, (float) qy, (float) qz, (float) qw); + } + + public MatrixM3f rotate(float pitch, float yaw, float roll) { + + double + halfYaw = Math.toRadians(yaw) * 0.5, + qy1 = TrigMath.sin(halfYaw), + qw1 = TrigMath.cos(halfYaw), + + halfPitch = Math.toRadians(pitch) * 0.5, + qx2 = TrigMath.sin(halfPitch), + qw2 = TrigMath.cos(halfPitch), + + halfRoll = Math.toRadians(roll) * 0.5, + qz3 = TrigMath.sin(halfRoll), + qw3 = TrigMath.cos(halfRoll); + + // multiply 1 with 2 + double + qxA = qw1 * qx2, + qyA = qy1 * qw2, + qzA = - qy1 * qx2, + qwA = qw1 * qw2; + + // multiply with 3 + return rotateByQuaternion( + (float) (qxA * qw3 + qyA * qz3), + (float) (qyA * qw3 - qxA * qz3), + (float) (qwA * qz3 + qzA * qw3), + (float) (qwA * qw3 - qzA * qz3) + ); + } + + public MatrixM3f rotateByQuaternion(float qx, float qy, float qz, float qw) { + return multiplyTo( + 1 - 2 * qy * qy - 2 * qz * qz, + 2 * qx * qy - 2 * qw * qz, + 2 * qx * qz + 2 * qw * qy, + 2 * qx * qy + 2 * qw * qz, + 1 - 2 * qx * qx - 2 * qz * qz, + 2 * qy * qz - 2 * qw * qx, + 2 * qx * qz - 2 * qw * qy, + 2 * qy * qz + 2 * qx * qw, + 1 - 2 * qx * qx - 2 * qy * qy + ); + } + + public MatrixM3f multiply( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 + ) { + return set ( + this.m00 * m00 + this.m01 * m10 + this.m02 * m20, + this.m00 * m01 + this.m01 * m11 + this.m02 * m21, + this.m00 * m02 + this.m01 * m12 + this.m02 * m22, + this.m10 * m00 + this.m11 * m10 + this.m12 * m20, + this.m10 * m01 + this.m11 * m11 + this.m12 * m21, + this.m10 * m02 + this.m11 * m12 + this.m12 * m22, + this.m20 * m00 + this.m21 * m10 + this.m22 * m20, + this.m20 * m01 + this.m21 * m11 + this.m22 * m21, + this.m20 * m02 + this.m21 * m12 + this.m22 * m22 + ); + } + + public MatrixM3f multiplyTo( + float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22 + ) { + return set ( + m00 * this.m00 + m01 * this.m10 + m02 * this.m20, + m00 * this.m01 + m01 * this.m11 + m02 * this.m21, + m00 * this.m02 + m01 * this.m12 + m02 * this.m22, + m10 * this.m00 + m11 * this.m10 + m12 * this.m20, + m10 * this.m01 + m11 * this.m11 + m12 * this.m21, + m10 * this.m02 + m11 * this.m12 + m12 * this.m22, + m20 * this.m00 + m21 * this.m10 + m22 * this.m20, + m20 * this.m01 + m21 * this.m11 + m22 * this.m21, + m20 * this.m02 + m21 * this.m12 + m22 * this.m22 + ); + } + + public float determinant() { + return m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM4f.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM4f.java new file mode 100644 index 00000000..74ee3e80 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/MatrixM4f.java @@ -0,0 +1,228 @@ +/* + * 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.math; + +import com.flowpowered.math.TrigMath; + +public class MatrixM4f { + + public float m00 = 1f, m01, m02, m03; + public float m10, m11 = 1f, m12, m13; + public float m20, m21, m22 = 1f, m23; + public float m30, m31, m32, m33 = 1f; + + public MatrixM4f set( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 + ) { + this.m00 = m00; this.m01 = m01; this.m02 = m02; this.m03 = m03; + this.m10 = m10; this.m11 = m11; this.m12 = m12; this.m13 = m13; + this.m20 = m20; this.m21 = m21; this.m22 = m22; this.m23 = m23; + this.m30 = m30; this.m31 = m31; this.m32 = m32; this.m33 = m33; + return this; + } + + public MatrixM4f copy(MatrixM4f m) { + return set( + m.m00, m.m01, m.m02, m.m03, + m.m10, m.m11, m.m12, m.m13, + m.m20, m.m21, m.m22, m.m23, + m.m30, m.m31, m.m32, m.m33 + ); + } + + public MatrixM4f identity() { + return set( + 1f, 0f, 0f, 0f, + 0f, 1f, 0f, 0f, + 0f, 0f, 1f, 0f, + 0f, 0f, 0f, 1f + ); + } + + public MatrixM4f translate(float x, float y, float z) { + return multiplyTo( + 1f, 0f, 0f, x, + 0f, 1f, 0f, y, + 0f, 0f, 1f, z, + 0f, 0f, 0f, 1f + ); + } + + public MatrixM4f scale(float x, float y, float z) { + return multiplyTo( + x, 0f, 0f, 0f, + 0f, y, 0f, 0f, + 0f, 0f, z, 0f, + 0f, 0f, 0f, 1f + ); + } + + public MatrixM4f rotate(float angle, float axisX, float axisY, float axisZ) { + + // create quaternion + double halfAngle = Math.toRadians(angle) * 0.5; + double q = TrigMath.sin(halfAngle) / Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); + + double //quaternion + qx = axisX * q, + qy = axisY * q, + qz = axisZ * q, + qw = TrigMath.cos(halfAngle), + qLength = Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw); + + // normalize quaternion + qx /= qLength; + qy /= qLength; + qz /= qLength; + qw /= qLength; + + return rotateByQuaternion((float) qx, (float) qy, (float) qz, (float) qw); + } + + public MatrixM4f rotate(float pitch, float yaw, float roll) { + double + halfYaw = Math.toRadians(yaw) * 0.5, + qy1 = TrigMath.sin(halfYaw), + qw1 = TrigMath.cos(halfYaw), + + halfPitch = Math.toRadians(pitch) * 0.5, + qx2 = TrigMath.sin(halfPitch), + qw2 = TrigMath.cos(halfPitch), + + halfRoll = Math.toRadians(roll) * 0.5, + qz3 = TrigMath.sin(halfRoll), + qw3 = TrigMath.cos(halfRoll); + + // multiply 1 with 2 + double + qxA = qw1 * qx2, + qyA = qy1 * qw2, + qzA = - qy1 * qx2, + qwA = qw1 * qw2; + + // multiply with 3 + return rotateByQuaternion( + (float) (qxA * qw3 + qyA * qz3), + (float) (qyA * qw3 - qxA * qz3), + (float) (qwA * qz3 + qzA * qw3), + (float) (qwA * qw3 - qzA * qz3) + ); + } + + public MatrixM4f rotateByQuaternion(float qx, float qy, float qz, float qw) { + return multiplyTo( + 1 - 2 * qy * qy - 2 * qz * qz, + 2 * qx * qy - 2 * qw * qz, + 2 * qx * qz + 2 * qw * qy, + 0, + 2 * qx * qy + 2 * qw * qz, + 1 - 2 * qx * qx - 2 * qz * qz, + 2 * qy * qz - 2 * qw * qx, + 0, + 2 * qx * qz - 2 * qw * qy, + 2 * qy * qz + 2 * qx * qw, + 1 - 2 * qx * qx - 2 * qy * qy, + 0, + 0, 0, 0, 1 + ); + } + + public MatrixM4f multiply( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 + ) { + return set ( + this.m00 * m00 + this.m01 * m10 + this.m02 * m20 + this.m03 * m30, + this.m00 * m01 + this.m01 * m11 + this.m02 * m21 + this.m03 * m31, + this.m00 * m02 + this.m01 * m12 + this.m02 * m22 + this.m03 * m32, + this.m00 * m03 + this.m01 * m13 + this.m02 * m23 + this.m03 * m33, + this.m10 * m00 + this.m11 * m10 + this.m12 * m20 + this.m13 * m30, + this.m10 * m01 + this.m11 * m11 + this.m12 * m21 + this.m13 * m31, + this.m10 * m02 + this.m11 * m12 + this.m12 * m22 + this.m13 * m32, + this.m10 * m03 + this.m11 * m13 + this.m12 * m23 + this.m13 * m33, + this.m20 * m00 + this.m21 * m10 + this.m22 * m20 + this.m23 * m30, + this.m20 * m01 + this.m21 * m11 + this.m22 * m21 + this.m23 * m31, + this.m20 * m02 + this.m21 * m12 + this.m22 * m22 + this.m23 * m32, + this.m20 * m03 + this.m21 * m13 + this.m22 * m23 + this.m23 * m33, + this.m30 * m00 + this.m31 * m10 + this.m32 * m20 + this.m33 * m30, + this.m30 * m01 + this.m31 * m11 + this.m32 * m21 + this.m33 * m31, + this.m30 * m02 + this.m31 * m12 + this.m32 * m22 + this.m33 * m32, + this.m30 * m03 + this.m31 * m13 + this.m32 * m23 + this.m33 * m33 + ); + } + + public MatrixM4f multiplyTo( + float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 + ) { + return set ( + m00 * this.m00 + m01 * this.m10 + m02 * this.m20 + m03 * this.m30, + m00 * this.m01 + m01 * this.m11 + m02 * this.m21 + m03 * this.m31, + m00 * this.m02 + m01 * this.m12 + m02 * this.m22 + m03 * this.m32, + m00 * this.m03 + m01 * this.m13 + m02 * this.m23 + m03 * this.m33, + m10 * this.m00 + m11 * this.m10 + m12 * this.m20 + m13 * this.m30, + m10 * this.m01 + m11 * this.m11 + m12 * this.m21 + m13 * this.m31, + m10 * this.m02 + m11 * this.m12 + m12 * this.m22 + m13 * this.m32, + m10 * this.m03 + m11 * this.m13 + m12 * this.m23 + m13 * this.m33, + m20 * this.m00 + m21 * this.m10 + m22 * this.m20 + m23 * this.m30, + m20 * this.m01 + m21 * this.m11 + m22 * this.m21 + m23 * this.m31, + m20 * this.m02 + m21 * this.m12 + m22 * this.m22 + m23 * this.m32, + m20 * this.m03 + m21 * this.m13 + m22 * this.m23 + m23 * this.m33, + m30 * this.m00 + m31 * this.m10 + m32 * this.m20 + m33 * this.m30, + m30 * this.m01 + m31 * this.m11 + m32 * this.m21 + m33 * this.m31, + m30 * this.m02 + m31 * this.m12 + m32 * this.m22 + m33 * this.m32, + m30 * this.m03 + m31 * this.m13 + m32 * this.m23 + m33 * this.m33 + ); + } + + public MatrixM4f multiplyTo(MatrixM3f m) { + return set ( + m.m00 * this.m00 + m.m01 * this.m10 + m.m02 * this.m20, + m.m00 * this.m01 + m.m01 * this.m11 + m.m02 * this.m21, + m.m00 * this.m02 + m.m01 * this.m12 + m.m02 * this.m22, + m.m00 * this.m03 + m.m01 * this.m13 + m.m02 * this.m23, + m.m10 * this.m00 + m.m11 * this.m10 + m.m12 * this.m20, + m.m10 * this.m01 + m.m11 * this.m11 + m.m12 * this.m21, + m.m10 * this.m02 + m.m11 * this.m12 + m.m12 * this.m22, + m.m10 * this.m03 + m.m11 * this.m13 + m.m12 * this.m23, + m.m20 * this.m00 + m.m21 * this.m10 + m.m22 * this.m20, + m.m20 * this.m01 + m.m21 * this.m11 + m.m22 * this.m21, + m.m20 * this.m02 + m.m21 * this.m12 + m.m22 * this.m22, + m.m20 * this.m03 + m.m21 * this.m13 + m.m22 * this.m23, + this.m30, + this.m31, + this.m32, + this.m33 + ); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2f.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2f.java new file mode 100644 index 00000000..efbf6c69 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2f.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.util.math; + +import com.flowpowered.math.GenericMath; +import com.flowpowered.math.TrigMath; + +public class VectorM2f { + + public float x, y; + + public VectorM2f(float x, float y) { + this.x = x; + this.y = y; + } + + public VectorM2f set(float x, float y) { + this.x = x; + this.y = y; + return this; + } + + public VectorM2f translate(float x, float y) { + this.x += x; + this.y += y; + return this; + } + + public VectorM2f rotate(float sx, float sy) { //sx,sy should be normalized + return set(x * sx - y * sy, y * sx + x * sy); + } + + public VectorM2f transform(MatrixM3f t) { + return set( + t.m00 * x + t.m01 * y + t.m02, + t.m10 * x + t.m11 * y + t.m12 + ); + } + + public VectorM2f normalize() { + final float length = length(); + x /= length; + y /= length; + return this; + } + + public float length() { + return (float) Math.sqrt(lengthSquared()); + } + + public float lengthSquared() { + return x * x + y * y; + } + + public float angleTo(float x, float y) { + return (float) TrigMath.acos( + (this.x * x + this.y * y) / + (this.length() * Math.sqrt(x * x + y * y)) + ); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2i.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2i.java new file mode 100644 index 00000000..1e8a5d89 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM2i.java @@ -0,0 +1,95 @@ +/* + * 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.math; + +public class VectorM2i { + + public int x, y; + + public VectorM2i() {} + + public VectorM2i(VectorM2i from) { + this.x = from.x; + this.y = from.y; + } + + public VectorM2i(int x, int y) { + this.x = x; + this.y = y; + } + + public VectorM2i set(int x, int y) { + this.x = x; + this.y = y; + return this; + } + + public VectorM2i normalize() { + final float length = length(); + x /= length; + y /= length; + return this; + } + + public VectorM2i add(int x, int y) { + this.x += x; + this.y += y; + return this; + } + + public VectorM2i div(int x, int y) { + this.x /= x; + this.y /= y; + return this; + } + + public VectorM2i floorDiv(int x, int y) { + this.x = Math.floorDiv(this.x, x); + this.y = Math.floorDiv(this.y, y); + return this; + } + + public int length() { + return (int) Math.sqrt(lengthSquared()); + } + + public int lengthSquared() { + return x * x + y * y; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VectorM2i vectorM2i = (VectorM2i) o; + return x == vectorM2i.x && y == vectorM2i.y; + } + + @Override + public int hashCode() { + return x ^ (y + 34985735); + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM3f.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM3f.java new file mode 100644 index 00000000..fcbebfa9 --- /dev/null +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/util/math/VectorM3f.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.util.math; + +public class VectorM3f { + + public float x, y, z; + + public VectorM3f(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public VectorM3f set(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + + public VectorM3f transform(MatrixM3f t) { + return set( + t.m00 * x + t.m01 * y + t.m02 * z, + t.m10 * x + t.m11 * y + t.m12 * z, + t.m20 * x + t.m21 * y + t.m22 * z + ); + } + + public VectorM3f transform(MatrixM4f t) { + return set( + t.m00 * x + t.m01 * y + t.m02 * z + t.m03, + t.m10 * x + t.m11 * y + t.m12 * z + t.m13, + t.m20 * x + t.m21 * y + t.m22 * z + t.m23 + ); + } + + public VectorM3f rotateAndScale(MatrixM4f t) { + return set( + t.m00 * x + t.m01 * y + t.m02 * z, + t.m10 * x + t.m11 * y + t.m12 * z, + t.m20 * x + t.m21 * y + t.m22 * z + ); + } + + public VectorM3f normalize() { + final float length = length(); + x /= length; + y /= length; + z /= length; + return this; + } + + public float length() { + return (float) Math.sqrt(lengthSquared()); + } + + public double lengthSquared() { + return x * x + y * y + z * z; + } + +} diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java index a76f2032..5270a76e 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Biome.java @@ -24,11 +24,8 @@ */ package de.bluecolored.bluemap.core.world; -import com.flowpowered.math.vector.Vector3f; -import com.flowpowered.math.vector.Vector4f; - import de.bluecolored.bluemap.core.util.ConfigUtils; -import de.bluecolored.bluemap.core.util.MathUtils; +import de.bluecolored.bluemap.core.util.math.Color; import org.spongepowered.configurate.ConfigurationNode; public class Biome { @@ -39,14 +36,14 @@ public class Biome { private int numeralId = 0; private float humidity = 0.5f; private float temp = 0.5f; - private Vector3f waterColor = MathUtils.color3FromInt(4159204); + private Color waterColor = new Color().set(4159204).premultiplied(); - private Vector4f overlayFoliageColor = Vector4f.ZERO; - private Vector4f overlayGrassColor = Vector4f.ZERO; + private Color overlayFoliageColor = new Color().premultiplied(); + private Color overlayGrassColor = new Color().premultiplied(); private Biome() {} - public Biome(String id, int numeralId, float humidity, float temp, Vector3f waterColor) { + public Biome(String id, int numeralId, float humidity, float temp, Color waterColor) { this.id = id; this.numeralId = numeralId; this.humidity = humidity; @@ -54,7 +51,7 @@ public Biome(String id, int numeralId, float humidity, float temp, Vector3f wate this.waterColor = waterColor; } - public Biome(String id, int numeralId, float humidity, float temp, Vector3f waterColor, Vector4f overlayFoliageColor, Vector4f overlayGrassColor) { + public Biome(String id, int numeralId, float humidity, float temp, Color waterColor, Color overlayFoliageColor, Color overlayGrassColor) { this (id, numeralId, humidity, temp, waterColor); this.overlayFoliageColor = overlayFoliageColor; @@ -77,15 +74,15 @@ public float getTemp() { return temp; } - public Vector3f getWaterColor() { + public Color getWaterColor() { return waterColor; } - public Vector4f getOverlayFoliageColor() { + public Color getOverlayFoliageColor() { return overlayFoliageColor; } - public Vector4f getOverlayGrassColor() { + public Color getOverlayGrassColor() { return overlayGrassColor; } @@ -96,9 +93,9 @@ public static Biome create(String id, ConfigurationNode node) { biome.numeralId = node.node("id").getInt(biome.numeralId); biome.humidity = node.node("humidity").getFloat(biome.humidity); biome.temp = node.node("temp").getFloat(biome.temp); - try { biome.waterColor = MathUtils.color3FromInt(ConfigUtils.readColorInt(node.node("watercolor"))); } catch (NumberFormatException ignored) {} - try { biome.overlayFoliageColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.node("foliagecolor"))); } catch (NumberFormatException ignored) {} - try { biome.overlayGrassColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.node("grasscolor"))); } catch (NumberFormatException ignored) {} + try { biome.waterColor = new Color().set(ConfigUtils.readColorInt(node.node("watercolor"))).premultiplied(); } catch (NumberFormatException ignored) {} + try { biome.overlayFoliageColor = new Color().set(ConfigUtils.readColorInt(node.node("foliagecolor"))).premultiplied(); } catch (NumberFormatException ignored) {} + try { biome.overlayGrassColor = new Color().set(ConfigUtils.readColorInt(node.node("grasscolor"))).premultiplied(); } catch (NumberFormatException ignored) {} return biome; } 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 index be85aedd..e39868fa 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Block.java @@ -30,68 +30,137 @@ public class Block { private World world; + private int x, y, z; + + private Chunk chunk; + private BlockState blockState; + private BlockProperties properties; private LightData lightData; private Biome biome; - private BlockProperties properties; - private Vector3i pos; - private float sunLight; - private float blockLight; + private int sunLight; + private int blockLight; + + private final transient LightData tempLight; - public Block(World world, BlockState blockState, LightData lightData, Biome biome, BlockProperties properties, Vector3i pos) { + public Block(World world, int x, int y, int z) { + tempLight = new LightData(0, 0); + + set(world, x, y, z); + } + + public Block set(World world, int x, int y, int z) { + if (this.x == x && this.y == y && this.z == z && this.world == world) return this; + this.world = world; - this.blockState = blockState; - this.lightData = lightData; - this.biome = biome; - this.properties = properties; - this.pos = pos; - - this.sunLight = -1; + this.x = x; + this.y = y; + this.z = z; + + reset(); + + return this; + } + + public Block set(int x, int y, int z) { + if (this.x == x && this.y == y && this.z == z) return this; + + this.x = x; + this.y = y; + this.z = z; + + reset(); + + return this; + } + + private void reset() { + this.chunk = null; + + this.blockState = null; + this.properties = null; + this.lightData = new LightData(-1, -1); + this.biome = null; + this.blockLight = -1; + this.sunLight = -1; } - - public BlockState getBlockState() { - return blockState; + + public Block add(int dx, int dy, int dz) { + return set(x + dx, y + dy, z + dz); } - + + public Block copy(Block source) { + return set(source.world, source.x, source.y, source.z); + } + public World getWorld() { return world; } - - public Vector3i getPosition() { - return pos; + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getZ() { + return z; + } + + public Chunk getChunk() { + if (chunk == null) chunk = world.getChunkAtBlock(x, y, z); + return chunk; + } + + public BlockState getBlockState() { + if (blockState == null) blockState = getChunk().getBlockState(x, y, z); + return blockState; + } + + public BlockProperties getProperties() { + if (properties == null) properties = world.getBlockProperties(getBlockState()); + return properties; + } + + public LightData getLightData() { + if (lightData.getSkyLight() < 0) getChunk().getLightData(x, y, z, lightData); + return lightData; + } + + public Biome getBiome() { + if (biome == null) biome = getChunk().getBiome(x, y, z); + return biome; + } + + public int getSunLightLevel() { + return getLightData().getSkyLight(); } - public float getSunLightLevel() { - return lightData.getSkyLight(); - } - - public float getBlockLightLevel() { - return lightData.getBlockLight(); + public int getBlockLightLevel() { + return getLightData().getBlockLight(); } public boolean isCullingNeighborFaces() { - return properties.isCulling(); + return getProperties().isCulling(); } public boolean isFlammable() { - return properties.isFlammable(); + return getProperties().isFlammable(); } public boolean isOccludingNeighborFaces(){ - return properties.isOccluding(); - } - - public Biome getBiome() { - return biome; + return getProperties().isOccluding(); } /** * This is internally used for light rendering * It is basically the sun light that is projected onto adjacent faces */ - public float getPassedSunLight() { + public int getPassedSunLight() { if (sunLight < 0) calculateLight(); return sunLight; } @@ -100,7 +169,7 @@ public float getPassedSunLight() { * This is internally used for light rendering * It is basically the block light that is projected onto adjacent faces */ - public float getPassedBlockLight() { + public int getPassedBlockLight() { if (blockLight < 0) calculateLight(); return blockLight; } @@ -110,61 +179,29 @@ private void calculateLight() { blockLight = getBlockLightLevel(); if (blockLight > 0 || sunLight > 0) return; - + + Vector3i dirV; + int nx, ny, nz; for (Direction direction : Direction.values()) { - Block neighbor = getRelativeBlock(direction); - sunLight = Math.max(neighbor.getSunLightLevel(), sunLight); - blockLight = Math.max(neighbor.getBlockLightLevel(), blockLight); + dirV = direction.toVector(); + nx = dirV.getX() + x; + ny = dirV.getY() + y; + nz = dirV.getZ() + z; + + world.getLightData(nx, ny, nz, tempLight); + + sunLight = Math.max(tempLight.getSkyLight(), sunLight); + blockLight = Math.max(tempLight.getBlockLight(), blockLight); } } - - public Block getRelativeBlock(int x, int y, int z) { - Vector3i pos = getPosition().add(x, y, z); - return getWorld().getBlock(pos); - } - - public Block getRelativeBlock(Vector3i direction) { - Vector3i pos = getPosition().add(direction); - return getWorld().getBlock(pos); - } - - public Block getRelativeBlock(Direction direction){ - return getRelativeBlock(direction.toVector()); - } - - public void setWorld(World world) { - this.world = world; - } - - public void setBlockState(BlockState blockState) { - this.blockState = blockState; - } - - public void setLightData(LightData lightData) { - this.lightData = lightData; - - this.blockLight = -1f; - this.sunLight = -1f; - } - - public void setBiome(Biome biome) { - this.biome = biome; - } - - public void setProperties(BlockProperties properties) { - this.properties = properties; - } - - public void setPos(Vector3i pos) { - this.pos = pos; - } @Override public String toString() { return "Block{" + - "blockState=" + blockState + - ", biome=" + biome + - ", pos=" + pos + + "world=" + world + + ", x=" + x + + ", y=" + y + + ", z=" + z + ", sunLight=" + sunLight + ", blockLight=" + 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 index 9a525584..9fb26843 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/BlockState.java @@ -24,16 +24,13 @@ */ package de.bluecolored.bluemap.core.world; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import de.bluecolored.bluemap.core.MinecraftVersion; + +import java.util.*; 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.
@@ -43,9 +40,45 @@ public class BlockState { private static final Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$"); + + private static final HashSet DEFAULT_WATERLOGGED_BLOCK_IDS = new HashSet<>(Arrays.asList( + "minecraft:seagrass", + "minecraft:tall_seagrass", + "minecraft:kelp", + "minecraft:kelp_plant", + "minecraft:bubble_column" + )); + + private static final HashSet OFFSET_BLOCK_IDS = new HashSet<>(Arrays.asList( + "minecraft:grass", + "minecraft:tall_grass", + "minecraft:fern", + "minecraft:dandelion", + "minecraft:cornflower", + "minecraft:poppy", + "minecraft:blue_orchid", + "minecraft:allium", + "minecraft:azure_bluet", + "minecraft:red_tulip", + "minecraft:orange_tulip", + "minecraft:white_tulip", + "minecraft:pink_tulip", + "minecraft:oxeye_daisy", + "minecraft:lily_of_the_valley", + "minecraft:wither_rose", + "minecraft:crimson_roots", + "minecraft:warped_roots", + "minecraft:nether_sprouts", + "minecraft:rose_bush", + "minecraft:peony", + "minecraft:lilac", + "minecraft:sunflower", + "minecraft:hanging_roots", + "minecraft:small_dripleaf" + )); - public static final BlockState AIR = new BlockState("minecraft:air", Collections.emptyMap()); - public static final BlockState MISSING = new BlockState("bluemap:missing", Collections.emptyMap()); + public static final BlockState AIR = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "minecraft:air", Collections.emptyMap()); + public static final BlockState MISSING = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "bluemap:missing", Collections.emptyMap()); private boolean hashed; private int hash; @@ -55,16 +88,20 @@ public class BlockState { private final String fullId; private final Map properties; - public BlockState(String id) { - this(id, Collections.emptyMap()); + // special fast-access properties + public final boolean isAir, isWater, isWaterlogged, isRandomOffset; + + public BlockState(MinecraftVersion version, String id) { + this(version, id, Collections.emptyMap()); } - public BlockState(String id, Map properties) { + public BlockState(MinecraftVersion version, String id, Map properties) { this.hashed = false; this.hash = 0; - this.properties = Collections.unmodifiableMap(new HashMap<>(properties)); - + //this.properties = Collections.unmodifiableMap(new HashMap<>(properties)); // <- not doing this to reduce object-creation + this.properties = properties; + //resolve namespace String namespace = "minecraft"; int namespaceSeperator = id.indexOf(':'); @@ -76,6 +113,25 @@ public BlockState(String id, Map properties) { this.id = id; this.namespace = namespace; this.fullId = namespace + ":" + id; + + // special fast-access properties + this.isAir = + "minecraft:air".equals(this.fullId) || + "minecraft:cave_air".equals(this.fullId) || + "minecraft:void_air".equals(this.fullId); + + this.isWater = "minecraft:water".equals(this.fullId); + + this.isWaterlogged = + DEFAULT_WATERLOGGED_BLOCK_IDS.contains(this.fullId) || + "true".equals(this.properties.get("waterlogged")); + + if (version.isAtLeast(MinecraftVersion.THE_FLATTENING)) { + this.isRandomOffset = OFFSET_BLOCK_IDS.contains(this.fullId); + } else { + this.isRandomOffset = + "minecraft:tall_grass".equals(this.fullId); + } } private BlockState(BlockState blockState, String withKey, String withValue) { @@ -88,7 +144,15 @@ private BlockState(BlockState blockState, String withKey, String withValue) { this.id = blockState.getId(); this.namespace = blockState.getNamespace(); this.fullId = namespace + ":" + id; - this.properties = Collections.unmodifiableMap(props); + this.properties = props; + + // special fast-access properties + this.isAir = blockState.isAir; + this.isWater = blockState.isWater; + this.isWaterlogged = + DEFAULT_WATERLOGGED_BLOCK_IDS.contains(this.fullId) || + "true".equals(this.properties.get("waterlogged")); + this.isRandomOffset = blockState.isRandomOffset; } /** @@ -165,7 +229,7 @@ public String toString() { return getFullId() + "[" + sj.toString() + "]"; } - public static BlockState fromString(String serializedBlockState) throws IllegalArgumentException { + public static BlockState fromString(MinecraftVersion version, String serializedBlockState) throws IllegalArgumentException { try { Matcher m = BLOCKSTATE_SERIALIZATION_PATTERN.matcher(serializedBlockState); m.find(); @@ -182,7 +246,7 @@ public static BlockState fromString(String serializedBlockState) throws IllegalA String blockId = m.group(1).trim(); - return new BlockState(blockId, pt); + return new BlockState(version, blockId, pt); } catch (RuntimeException ex) { throw new IllegalArgumentException("'" + serializedBlockState + "' could not be parsed to a BlockState!"); } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java index 08f121f7..0565c3b1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/Chunk.java @@ -28,4 +28,16 @@ public interface Chunk { boolean isGenerated(); + int getDataVersion(); + + BlockState getBlockState(int x, int y, int z); + + LightData getLightData(int x, int y, int z, LightData target); + + Biome getBiome(int x, int y, int z); + + int getMaxY(int x, int z); + + int getMinY(int x, int z); + } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java index 74cce20c..fdb378b1 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/LightData.java @@ -25,18 +25,19 @@ package de.bluecolored.bluemap.core.world; public class LightData { - - public static final LightData ZERO = new LightData(0, 0); - public static final LightData SKY = new LightData(15, 0); - public static final LightData FULL = new LightData(15, 15); - - private final int skyLight, blockLight; + private int skyLight, blockLight; public LightData(int skyLight, int blockLight) { this.skyLight = skyLight; this.blockLight = blockLight; } + public LightData set(int skyLight, int blockLight) { + this.skyLight = skyLight; + this.blockLight = blockLight; + return this; + } + public int getSkyLight() { return skyLight; } diff --git a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java index d3f4fa36..9c97f0b3 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/SlicedWorld.java @@ -26,6 +26,7 @@ import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper; import java.nio.file.Path; import java.util.ArrayList; @@ -96,23 +97,16 @@ public Grid getRegionGrid() { public Biome getBiome(int x, int y, int z) { return world.getBiome(x, y, z); } - - @Override - public Block getBlock(Vector3i pos) { - if (!isInside(pos)) return createAirBlock(pos); - - Block block = world.getBlock(pos); - block.setWorld(this); - return block; - } - - @Override - public Block getBlock(int x, int y, int z) { - if (!isInside(x, y, z)) return createAirBlock(new Vector3i(x, y, z)); - Block block = world.getBlock(x, y, z); - block.setWorld(this); - return block; + @Override + public BlockProperties getBlockProperties(BlockState blockState) { + return world.getBlockProperties(blockState); + } + + @Override + public BlockState getBlockState(int x, int y, int z) { + if (!isInside(x, y, z)) return BlockState.AIR; + return world.getBlockState(x, y, z); } @Override @@ -120,6 +114,11 @@ public Chunk getChunk(int x, int z) { return world.getChunk(x, z); } + @Override + public Chunk getChunkAtBlock(int x, int y, int z) { + return world.getChunkAtBlock(x, y, z); + } + @Override public Region getRegion(int x, int z) { return world.getRegion(x, z); @@ -153,9 +152,10 @@ public void invalidateChunkCache(int x, int z) { public void cleanUpChunkCache() { world.cleanUpChunkCache(); } - - private boolean isInside(Vector3i blockPos) { - return isInside(blockPos.getX(), blockPos.getY(), blockPos.getZ()); + + @Override + public BlockPropertiesMapper getBlockPropertiesMapper() { + return world.getBlockPropertiesMapper(); } private boolean isInside(int x, int z) { @@ -176,15 +176,4 @@ private boolean isInside(int x, int y, int z) { y <= max.getY(); } - private Block createAirBlock(Vector3i pos) { - return new Block( - this, - BlockState.AIR, - pos.getY() < this.min.getY() ? LightData.ZERO : LightData.SKY, - Biome.DEFAULT, - BlockProperties.TRANSPARENT, - pos - ); - } - } 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 index fd7fd30a..89f05b94 100644 --- a/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java +++ b/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/world/World.java @@ -26,11 +26,11 @@ import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector3i; +import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper; import java.nio.file.Path; import java.util.Collection; import java.util.UUID; -import java.util.function.Predicate; /** * Represents a World on the Server
@@ -61,18 +61,21 @@ public interface World { * Returns the {@link Biome} on the specified position or the default biome if the block is not generated yet. */ Biome getBiome(int x, int y, int z); - + /** - * Returns the {@link Block} on the specified position or an air-block if the block is not generated yet. + * Returns the {@link BlockState} on the specified position or an air-block if the block is not generated yet. */ - Block getBlock(Vector3i pos); - + BlockState getBlockState(int x, int y, int z); + /** - * Returns the {@link Block} on the specified position or an air-block if the block is not generated yet. + * Returns the BlockProperties for a block-state */ - default Block getBlock(int x, int y, int z) { - return getBlock(new Vector3i(x, y, z)); - } + BlockProperties getBlockProperties(BlockState blockState); + + /** + * Returns the {@link Chunk} on the specified block-position + */ + Chunk getChunkAtBlock(int x, int y, int z); /** * Returns the {@link Chunk} on the specified chunk-position @@ -104,5 +107,10 @@ default Block getBlock(int x, int y, int z) { * Cleans up invalid cache-entries to free up memory */ void cleanUpChunkCache(); + + /** + * Returns the block-properties manager used for this world + */ + BlockPropertiesMapper getBlockPropertiesMapper(); } diff --git a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_12/blockColors.json b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_12/blockColors.json index e2bc5b48..f6cc1982 100644 --- a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_12/blockColors.json +++ b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_12/blockColors.json @@ -7,8 +7,8 @@ "minecraft:double_grass": "@grass", "minecraft:fern": "@grass", "minecraft:double_fern": "@grass", - "minecraft:redstone_wire": "#ff0000", - "minecraft:birch_leaves": "#86a863", - "minecraft:spruce_leaves": "#51946b", + "minecraft:redstone_wire": "@redstone", + "minecraft:birch_leaves": 8431445, + "minecraft:spruce_leaves": 6396257, "minecraft:stonecutter": "#ffffff" } \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_13/blockColors.json b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_13/blockColors.json index b5c34f4e..9d34b3f1 100644 --- a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_13/blockColors.json +++ b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_13/blockColors.json @@ -7,8 +7,8 @@ "minecraft:tall_grass": "@grass", "minecraft:fern": "@grass", "minecraft:large_fern": "@grass", - "minecraft:redstone_wire": "#ff0000", - "minecraft:birch_leaves": "#86a863", - "minecraft:spruce_leaves": "#51946b", + "minecraft:redstone_wire": "@redstone", + "minecraft:birch_leaves": 8431445, + "minecraft:spruce_leaves": 6396257, "minecraft:stonecutter": "#ffffff" } \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_15/blockColors.json b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_15/blockColors.json index b5c34f4e..9d34b3f1 100644 --- a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_15/blockColors.json +++ b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_15/blockColors.json @@ -7,8 +7,8 @@ "minecraft:tall_grass": "@grass", "minecraft:fern": "@grass", "minecraft:large_fern": "@grass", - "minecraft:redstone_wire": "#ff0000", - "minecraft:birch_leaves": "#86a863", - "minecraft:spruce_leaves": "#51946b", + "minecraft:redstone_wire": "@redstone", + "minecraft:birch_leaves": 8431445, + "minecraft:spruce_leaves": 6396257, "minecraft:stonecutter": "#ffffff" } \ No newline at end of file diff --git a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_16/blockColors.json b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_16/blockColors.json index b5c34f4e..6a01c30d 100644 --- a/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_16/blockColors.json +++ b/BlueMapCore/src/main/resources/de/bluecolored/bluemap/mc1_16/blockColors.json @@ -2,13 +2,16 @@ "default": "@foliage", "minecraft:water": "@water", "minecraft:cauldron": "@water", + "minecraft:water_cauldron": "@water", + "minecraft:powder_snow_cauldron": "#ffffff", + "minecraft:lava_cauldron": "#ffffff", "minecraft:grass_block": "@grass", "minecraft:grass": "@grass", "minecraft:tall_grass": "@grass", "minecraft:fern": "@grass", "minecraft:large_fern": "@grass", - "minecraft:redstone_wire": "#ff0000", - "minecraft:birch_leaves": "#86a863", - "minecraft:spruce_leaves": "#51946b", + "minecraft:redstone_wire": "@redstone", + "minecraft:birch_leaves": 8431445, + "minecraft:spruce_leaves": 6396257, "minecraft:stonecutter": "#ffffff" } \ No newline at end of file diff --git a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java index 9a2d8b1a..7fd5226a 100644 --- a/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java +++ b/BlueMapCore/src/test/java/de/bluecolored/bluemap/core/world/BlockStateTest.java @@ -24,6 +24,7 @@ */ package de.bluecolored.bluemap.core.world; +import de.bluecolored.bluemap.core.MinecraftVersion; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,12 +34,12 @@ public class BlockStateTest { @Test public void testIdNamespace() { - BlockState blockState = new BlockState("someblock"); + BlockState blockState = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "someblock"); assertEquals("minecraft:someblock", blockState.getFullId()); assertEquals("minecraft", blockState.getNamespace()); assertEquals("someblock", blockState.getId()); - blockState = new BlockState("somemod:someblock"); + blockState = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock"); assertEquals("somemod:someblock", blockState.getFullId()); assertEquals("somemod", blockState.getNamespace()); assertEquals("someblock", blockState.getId()); @@ -46,7 +47,7 @@ public void testIdNamespace() { @Test public void testToString() { - BlockState blockState = new BlockState("someblock"); + BlockState blockState = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "someblock"); assertEquals("minecraft:someblock[]", blockState.toString()); blockState = blockState.with("testProp", "testVal"); @@ -63,19 +64,19 @@ public void testToString() { @Test public void testFromString() { - BlockState blockState = BlockState.fromString("somemod:someblock"); + BlockState blockState = BlockState.fromString(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock"); assertEquals("somemod:someblock", blockState.getFullId()); assertEquals("somemod", blockState.getNamespace()); assertEquals("someblock", blockState.getId()); assertTrue(blockState.getProperties().isEmpty()); - blockState = BlockState.fromString("somemod:someblock[]"); + blockState = BlockState.fromString(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock[]"); assertEquals("somemod:someblock", blockState.getFullId()); assertEquals("somemod", blockState.getNamespace()); assertEquals("someblock", blockState.getId()); assertTrue(blockState.getProperties().isEmpty()); - blockState = BlockState.fromString("somemod:someblock[testProp=testVal,testProp2=testVal2]"); + blockState = BlockState.fromString(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock[testProp=testVal,testProp2=testVal2]"); assertEquals("somemod:someblock", blockState.getFullId()); assertEquals("somemod", blockState.getNamespace()); assertEquals("someblock", blockState.getId());