Model-Rewrite: Complete, further performance improvements in progress

This commit is contained in:
Blue (Lukas Rieger) 2021-07-17 16:06:50 +02:00
parent 4c23bd5add
commit d88a25ad47
No known key found for this signature in database
GPG Key ID: 904C4995F9E1F800
60 changed files with 3295 additions and 1866 deletions

View File

@ -30,6 +30,7 @@ import de.bluecolored.bluemap.common.web.WebSettings;
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;

View File

@ -529,14 +529,14 @@ public class Commands<S> {
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)) + ")";

View File

@ -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 class BlockIdConfig implements BlockIdMapper {
blockNumeralId = -1;
}
int blockMeta = Integer.parseInt(key.substring(splitIndex + 1));
BlockState state = BlockState.fromString(value);
BlockState state = BlockState.fromString(MinecraftVersion.EARLIEST_SUPPORTED, value);
if (blockNumeralId >= 0) {
BlockNumeralIDMeta idmeta = new BlockNumeralIDMeta(blockNumeralId, blockMeta);
@ -160,7 +161,7 @@ public class BlockIdConfig implements BlockIdMapper {
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);

View File

@ -27,6 +27,7 @@ package de.bluecolored.bluemap.core.config;
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 class BlockPropertiesConfig implements BlockPropertiesMapper {
for (Entry<Object, ? extends ConfigurationNode> 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 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
}
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 @@ public class BlockPropertiesConfig implements BlockPropertiesMapper {
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 {

View File

@ -129,7 +129,7 @@ public class ConfigManager {
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");

View File

@ -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<Vector2i> 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();
}
}

View File

@ -27,9 +27,9 @@ package de.bluecolored.bluemap.core.map;
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 class BmMap {
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;

View File

@ -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;
}
}

View File

@ -1,88 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.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;
}
}

View File

@ -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 class HiresModelManager {
/**
* 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 class HiresModelManager {
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
*/

View File

@ -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.<br>
* <br>
* <i>(Implementation adapted from https://github.com/SpongePowered/SpongeAPI/blob/ecd761a70219e467dea47a09fc310e8238e9911f/src/main/java/org/spongepowered/api/extra/skylands/SkylandsUtil.java)</i>
*
* @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;
}
}

View File

@ -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;
}
}

View File

@ -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<HiresTileModel> INSTANCE_POOL = new ConcurrentLinkedQueue<>();
private int capacity;
private int size;
@ -59,10 +74,12 @@ public class HiresTileModel {
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 class HiresTileModel {
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 class HiresTileModel {
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 class HiresTileModel {
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 class HiresTileModel {
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 class HiresTileModel {
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 class HiresTileModel {
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 @@ public class HiresTileModel {
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);
}
}

View File

@ -1,70 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.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<ExtendedFace> 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);
}
}

View File

@ -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<TransformedBlockModelResource> 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");
}

View File

@ -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<String> 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 @@ public class LiquidModelBuilder {
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 @@ public class LiquidModelBuilder {
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;
}
}

View File

@ -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<Vector3f> 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;
}
}

View File

@ -28,7 +28,6 @@ import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
import de.bluecolored.bluemap.core.util.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 class LowresModel {
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 class LowresModel {
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 class LowresModel {
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 class LowresModel {
this.height = height;
this.color = color;
}
public LowresPoint add(LowresPoint other){
float newHeight = height + other.height;
Vector3f newColor = color.add(other.color);
return new LowresPoint(newHeight, newColor);
}
public LowresPoint div(float divisor){
float newHeight = height / divisor;
Vector3f newColor = color.div(divisor);
return new LowresPoint(newHeight, newColor);
}
}
}

View File

@ -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.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPInputStream;
@ -68,52 +69,45 @@ public class LowresModelManager {
/**
* 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 class LowresModelManager {
/**
* 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 class LowresModelManager {
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 @@ public class LowresModelManager {
}
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() {

View File

@ -88,14 +88,14 @@ public class ChunkAnvil112 extends MCAChunk {
}
@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 class ChunkAnvil112 extends MCAChunk {
}
@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 class ChunkAnvil112 extends MCAChunk {
int biomeByteIndex = z * 16 + x;
if (biomeByteIndex >= this.biomes.length) return Biome.DEFAULT;
return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF);
}
@ -158,10 +159,9 @@ public class ChunkAnvil112 extends MCAChunk {
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 class ChunkAnvil112 extends MCAChunk {
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 class ChunkAnvil112 extends MCAChunk {
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf);
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
return new LightData(skyLight, blockLight);
return target.set(skyLight, blockLight);
}
/**

View File

@ -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;
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 class ChunkAnvil113 extends MCAChunk {
}
@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 class ChunkAnvil113 extends MCAChunk {
int biomeIntIndex = z * 16 + x;
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT;
return biomeIdMapper.get(biomes[biomeIntIndex]);
}
@ -175,7 +178,7 @@ public class ChunkAnvil113 extends MCAChunk {
}
}
palette[i] = new BlockState(id, properties);
palette[i] = new BlockState(VERSION, id, properties);
}
} else {
this.palette = new BlockState[0];
@ -188,12 +191,11 @@ public class ChunkAnvil113 extends MCAChunk {
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 class ChunkAnvil113 extends MCAChunk {
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
);
}
}

View File

@ -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;
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 class ChunkAnvil115 extends MCAChunk {
}
@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 class ChunkAnvil115 extends MCAChunk {
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 class ChunkAnvil115 extends MCAChunk {
}
}
palette[i] = new BlockState(id, properties);
palette[i] = new BlockState(VERSION, id, properties);
}
} else {
this.palette = new BlockState[0];
@ -189,12 +193,11 @@ public class ChunkAnvil115 extends MCAChunk {
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 class ChunkAnvil115 extends MCAChunk {
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
);
}
}

View File

@ -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.BlockState;
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<Integer, Section> sections;
private int sectionMin, sectionMax;
private Section[] sections;
private int[] biomes;
@SuppressWarnings("unchecked")
@ -62,11 +67,14 @@ public class ChunkAnvil116 extends MCAChunk {
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<CompoundTag>) levelData.getListTag("Sections"))) {
this.sectionMin = Integer.MAX_VALUE;
this.sectionMax = Integer.MIN_VALUE;
ListTag<CompoundTag> sectionsTag = (ListTag<CompoundTag>) levelData.getListTag("Sections");
ArrayList<Section> 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 class ChunkAnvil116 extends MCAChunk {
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 class ChunkAnvil116 extends MCAChunk {
}
@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 class ChunkAnvil116 extends MCAChunk {
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 class ChunkAnvil116 extends MCAChunk {
}
}
palette[i] = new BlockState(id, properties);
palette[i] = new BlockState(VERSION, id, properties);
}
} else {
this.palette = new BlockState[0];
@ -207,12 +226,11 @@ public class ChunkAnvil116 extends MCAChunk {
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 class ChunkAnvil116 extends MCAChunk {
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
);
}
}

View File

@ -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 class EmptyChunk extends MCAChunk {
}
@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

View File

@ -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 @@ public abstract class MCAChunk implements Chunk {
@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;
}

View File

@ -36,6 +36,8 @@ import de.bluecolored.bluemap.core.mca.extensions.*;
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<Vector2i, MCARegion> regionCache;
private final LoadingCache<Vector2i, MCAChunk> chunkCache;
@ -69,7 +71,7 @@ public class MCAWorld implements World {
private final Map<String, List<BlockStateExtension>> blockStateExtensions;
@DebugDump private boolean ignoreMissingLightData;
@DebugDump private final boolean ignoreMissingLightData;
private final Map<Integer, String> forgeBlockMappings;
@ -126,36 +128,42 @@ public class MCAWorld implements World {
}
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<BlockStateExtension> 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 class MCAWorld implements World {
@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 class MCAWorld implements World {
public BlockIdMapper getBlockIdMapper() {
return blockIdMapper;
}
public BlockPropertiesMapper getBlockPropertiesMapper() {
return blockPropertiesMapper;
}
@ -415,13 +427,6 @@ public class MCAWorld implements World {
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() {

View File

@ -1,128 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.model;
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];
}
}

View File

@ -1,208 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.model;
import com.flowpowered.math.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];
}
}

View File

@ -1,224 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.resourcepack;
import com.flowpowered.math.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<String, Function<Block, Vector3f>> 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<Object, ? extends ConfigurationNode> entry : colorConfig.childrenMap().entrySet()){
String key = entry.getKey().toString();
String value = entry.getValue().getString("");
Function<Block, Vector3f> 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<Block, Vector3f> 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<Biome> 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<Biome>() {
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;
}
}

View File

@ -0,0 +1,251 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.resourcepack;
import com.flowpowered.math.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<String, ColorFunction> 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<Object, ? extends ConfigurationNode> 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);
}
}
}

View File

@ -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.*;
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 BlockModelResource {
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<Direction, Face> faces = new EnumMap<>(Direction.class);
private boolean fullCube = false;
@ -134,6 +139,10 @@ public class BlockModelResource {
return rotation;
}
public MatrixM4f getRotationMatrix() {
return rotationMatrix;
}
public boolean isShade() {
return shade;
}
@ -318,7 +327,7 @@ public class BlockModelResource {
break;
}
if (texture.getColor().getW() < 1) {
if (texture.getColorStraight().a < 1) {
blockModel.culling = false;
break;
}
@ -346,6 +355,37 @@ public class BlockModelResource {
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;

View File

@ -51,15 +51,14 @@ public class BlockStateResource {
private final List<Variant> variants = new ArrayList<>(0);
private final Collection<Variant> multipart = new ArrayList<>(0);
private BlockStateResource() {
private BlockStateResource() {}
public Collection<TransformedBlockModelResource> getModels(BlockState blockState, Collection<TransformedBlockModelResource> targetCollection) {
return getModels(blockState, 0, 0, 0, targetCollection);
}
public Collection<TransformedBlockModelResource> getModels(BlockState blockState) {
return getModels(blockState, Vector3i.ZERO);
}
public Collection<TransformedBlockModelResource> getModels(BlockState blockState, Vector3i pos) {
Collection<TransformedBlockModelResource> models = new ArrayList<>(1);
public Collection<TransformedBlockModelResource> getModels(BlockState blockState, int x, int y, int z, Collection<TransformedBlockModelResource> targetCollection) {
targetCollection.clear();
Variant allMatch = null;
for (Variant variant : variants) {
@ -68,29 +67,29 @@ public class BlockStateResource {
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 @@ public class BlockStateResource {
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<TransformedBlockModelResource> w : models) {
selection -= w.weight;
if (selection <= 0) return w.value;

View File

@ -55,17 +55,17 @@ public class ResourcePack {
private final MinecraftVersion minecraftVersion;
protected Map<String, BlockStateResource> blockStateResources;
protected Map<String, BlockModelResource> blockModelResources;
protected TextureGallery textures;
protected final Map<String, BlockStateResource> blockStateResources;
protected final Map<String, BlockModelResource> blockModelResources;
protected final TextureGallery textures;
private final BlockColorCalculator blockColorCalculator;
private final BlockColorCalculatorFactory blockColorCalculatorFactory;
private final Map<String, List<Resource>> configs;
private BufferedImage foliageMap;
private BufferedImage grassMap;
private Map<String, List<Resource>> configs;
public ResourcePack(MinecraftVersion minecraftVersion) {
this.minecraftVersion = minecraftVersion;
@ -76,7 +76,7 @@ public class ResourcePack {
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 class ResourcePack {
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 class ResourcePack {
return resource;
}
public BlockColorCalculator getBlockColorCalculator() {
return blockColorCalculator;
public BlockColorCalculatorFactory getBlockColorCalculatorFactory() {
return blockColorCalculatorFactory;
}
public MinecraftVersion getMinecraftVersion() {

View File

@ -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 class Texture {
* 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.

View File

@ -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 class TextureGallery {
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 class TextureGallery {
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 class TextureGallery {
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 @@ public class TextureGallery {
}
}
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 @@ public class TextureGallery {
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 @@ public class TextureGallery {
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 @@ public class TextureGallery {
}
}
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);
}
}

View File

@ -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;
}

View File

@ -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<T> {
private final Object[] objects;
private final int capacity, maxConcurrency;
private final Supplier<T> supplier;
private final AtomicInteger head, tail; // head is excluded, tail included
private int size;
public ArrayPool(int capacity, int maxConcurrency, Supplier<T> 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;
}
}

View File

@ -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;

View File

@ -1,47 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util;
import com.flowpowered.math.vector.Vector3d;
public class IntersectionPoint {
private final Vector3d intersection;
private final Vector3d normal;
public IntersectionPoint(Vector3d intersection, Vector3d normal){
this.intersection = intersection;
this.normal = normal;
}
public Vector3d getIntersection() {
return intersection;
}
public Vector3d getNormal() {
return normal;
}
}

View File

@ -28,8 +28,8 @@ import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.model.VectorM3f;
@Deprecated //TODO
public class MathUtils {
private MathUtils() {}
@ -73,27 +73,6 @@ public class MathUtils {
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.<br>

View File

@ -31,6 +31,7 @@ import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.model.Face;
import de.bluecolored.bluemap.core.model.Model;
@Deprecated //TODO
public class ModelUtils {
private ModelUtils() {}

View File

@ -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;

View File

@ -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 +
'}';
}
}

View File

@ -0,0 +1,186 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util.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);
}
}

View File

@ -0,0 +1,228 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util.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
);
}
}

View File

@ -0,0 +1,84 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.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))
);
}
}

View File

@ -0,0 +1,95 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util.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);
}
}

View File

@ -0,0 +1,84 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.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;
}
}

View File

@ -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 class Biome {
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 class Biome {
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 class Biome {
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;
}

View File

@ -30,68 +30,137 @@ import de.bluecolored.bluemap.core.util.Direction;
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 class Block {
* 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 @@ public class Block {
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 +
'}';

View File

@ -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<br>
* It is important that {@link #hashCode} and {@link #equals} are implemented correctly, for the caching to work properly.<br>
@ -43,9 +40,45 @@ import java.util.StringJoiner;
public class BlockState {
private static final Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$");
private static final HashSet<String> 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<String> 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<String, String> 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<String, String> properties) {
public BlockState(MinecraftVersion version, String id, Map<String, String> 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 class BlockState {
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 @@ public class BlockState {
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 class BlockState {
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 class BlockState {
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!");
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -26,6 +26,7 @@ package de.bluecolored.bluemap.core.world;
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 class SlicedWorld implements World {
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 class SlicedWorld implements World {
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 class SlicedWorld implements World {
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 @@ public class SlicedWorld implements World {
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
);
}
}

View File

@ -26,11 +26,11 @@ package de.bluecolored.bluemap.core.world;
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<br>
@ -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 @@ public interface World {
* Cleans up invalid cache-entries to free up memory
*/
void cleanUpChunkCache();
/**
* Returns the block-properties manager used for this world
*/
BlockPropertiesMapper getBlockPropertiesMapper();
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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 class BlockStateTest {
@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 class BlockStateTest {
@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());