mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-22 02:26:00 +01:00
Model-Rewrite: Complete, further performance improvements in progress
This commit is contained in:
parent
4c23bd5add
commit
d88a25ad47
@ -30,6 +30,7 @@
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.config.*;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.debug.OneBlockWorld;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.BmMap;
|
||||
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
|
||||
|
@ -529,14 +529,14 @@ public int debugBlockCommand(CommandContext<S> context) {
|
||||
new Thread(() -> {
|
||||
// collect and output debug info
|
||||
Vector3i blockPos = position.floor().toInt();
|
||||
Block block = world.getBlock(blockPos);
|
||||
Block blockBelow = world.getBlock(blockPos.add(0, -1, 0));
|
||||
Block block = new Block(world, blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
Block blockBelow = new Block(null, 0, 0, 0).copy(block).add(0, -1, 0);
|
||||
|
||||
String blockIdMeta = "";
|
||||
String blockBelowIdMeta = "";
|
||||
|
||||
if (world instanceof MCAWorld) {
|
||||
MCAChunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos));
|
||||
MCAChunk chunk = ((MCAWorld) world).getChunkAtBlock(blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
if (chunk instanceof ChunkAnvil112) {
|
||||
blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")";
|
||||
blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")";
|
||||
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.config;
|
||||
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
@ -76,7 +77,7 @@ public BlockIdConfig(ConfigurationNode node, ConfigurationLoader<? extends Confi
|
||||
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 BlockState get(String id, int numeralId, int meta) {
|
||||
state = idMappings.get(new BlockIDMeta(id, 0));
|
||||
if (state == null) {
|
||||
state = numeralMappings.get(new BlockNumeralIDMeta(numeralId, 0));
|
||||
if (state == null) state = new BlockState(id);
|
||||
if (state == null) state = new BlockState(MinecraftVersion.EARLIEST_SUPPORTED, id);
|
||||
}
|
||||
|
||||
idMappings.put(idmeta, state);
|
||||
|
@ -27,6 +27,7 @@
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import de.bluecolored.bluemap.core.BlueMap;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper;
|
||||
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
|
||||
@ -67,7 +68,7 @@ public BlockPropertiesConfig(ConfigurationNode node, ResourcePack resourcePack,
|
||||
for (Entry<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 @@ private BlockProperties mapNoCache(BlockState bs){
|
||||
}
|
||||
|
||||
BlockProperties generated = BlockProperties.SOLID;
|
||||
MinecraftVersion version = MinecraftVersion.LATEST_SUPPORTED;
|
||||
|
||||
if (resourcePack != null) {
|
||||
try {
|
||||
boolean culling = false;
|
||||
boolean occluding = false;
|
||||
|
||||
for(TransformedBlockModelResource model : resourcePack.getBlockStateResource(bs).getModels(bs)) {
|
||||
for(TransformedBlockModelResource model : resourcePack.getBlockStateResource(bs).getModels(bs, new ArrayList<>(10))) {
|
||||
culling = culling || model.getModel().isCulling();
|
||||
occluding = occluding || model.getModel().isOccluding();
|
||||
if (culling && occluding) break;
|
||||
@ -113,9 +115,11 @@ private BlockProperties mapNoCache(BlockState bs){
|
||||
|
||||
generated = new BlockProperties(culling, occluding, generated.isFlammable());
|
||||
} catch (NoSuchResourceException ignore) {} //ignoring this because it will be logged later again if we try to render that block
|
||||
|
||||
version = resourcePack.getMinecraftVersion();
|
||||
}
|
||||
|
||||
mappings.computeIfAbsent(bs.getFullId(), k -> new ArrayList<>()).add(new BlockStateMapping<>(new BlockState(bs.getFullId()), generated));
|
||||
mappings.computeIfAbsent(bs.getFullId(), k -> new ArrayList<>()).add(new BlockStateMapping<>(new BlockState(version, bs.getFullId()), generated));
|
||||
if (autopoulationConfigLoader != null) {
|
||||
synchronized (autopoulationConfigLoader) {
|
||||
try {
|
||||
|
@ -129,7 +129,7 @@ public void loadResourceConfigs(File configFolder, ResourcePack resourcePack) th
|
||||
false
|
||||
);
|
||||
blockColorsConfigNode = joinFromResourcePack(resourcePack, "blockColors.json", blockColorsConfigNode);
|
||||
resourcePack.getBlockColorCalculator().loadColorConfig(blockColorsConfigNode);
|
||||
resourcePack.getBlockColorCalculatorFactory().loadColorConfig(blockColorsConfigNode);
|
||||
|
||||
//load blockIds.json from resources, config-folder and resourcepack
|
||||
URL blockIdsConfigUrl = BlueMap.class.getResource("/de/bluecolored/bluemap/" + resourcePack.getMinecraftVersion().getResource().getResourcePrefix() + "/blockIds.json");
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -27,9 +27,9 @@
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import de.bluecolored.bluemap.core.debug.DebugDump;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresModel;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresModelManager;
|
||||
import de.bluecolored.bluemap.core.map.lowres.LowresModelManager;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresTileMeta;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.world.Grid;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
@ -103,8 +103,8 @@ public void renderTile(Vector2i tile) {
|
||||
|
||||
long start = System.nanoTime();
|
||||
|
||||
HiresModel hiresModel = hiresModelManager.render(world, tile);
|
||||
lowresModelManager.render(hiresModel);
|
||||
HiresTileMeta tileMeta = hiresModelManager.render(world, tile);
|
||||
lowresModelManager.render(tileMeta);
|
||||
|
||||
long end = System.nanoTime();
|
||||
long delta = end - start;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -25,7 +25,6 @@
|
||||
package de.bluecolored.bluemap.core.map.hires;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
@ -62,39 +61,43 @@ public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Grid tileGr
|
||||
/**
|
||||
* Renders the given world tile with the provided render-settings
|
||||
*/
|
||||
public HiresModel render(World world, Vector2i tile) {
|
||||
public HiresTileMeta render(World world, Vector2i tile) {
|
||||
Vector2i tileMin = tileGrid.getCellMin(tile);
|
||||
Vector2i tileMax = tileGrid.getCellMax(tile);
|
||||
|
||||
Vector3i modelMin = new Vector3i(tileMin.getX(), Integer.MIN_VALUE, tileMin.getY());
|
||||
Vector3i modelMax = new Vector3i(tileMax.getX(), Integer.MAX_VALUE, tileMax.getY());
|
||||
|
||||
HiresModel model = renderer.render(world, modelMin, modelMax);
|
||||
HiresTileModel model = HiresTileModel.claimInstance();
|
||||
|
||||
HiresTileMeta tileMeta = renderer.render(world, modelMin, modelMax, model);
|
||||
save(model, tile);
|
||||
return model;
|
||||
|
||||
HiresTileModel.recycleInstance(model);
|
||||
|
||||
return tileMeta;
|
||||
}
|
||||
|
||||
private void save(final HiresModel model, Vector2i tile) {
|
||||
final String modelJson = model.toBufferGeometry().toJson();
|
||||
save(modelJson, tile);
|
||||
}
|
||||
|
||||
private void save(String modelJson, Vector2i tile){
|
||||
private void save(final HiresTileModel model, Vector2i tile) {
|
||||
File file = getFile(tile, useGzip);
|
||||
|
||||
|
||||
OutputStream os = null;
|
||||
try {
|
||||
OutputStream os = new BufferedOutputStream(AtomicFileHelper.createFilepartOutputStream(file));
|
||||
os = AtomicFileHelper.createFilepartOutputStream(file);
|
||||
os = new BufferedOutputStream(os);
|
||||
if (useGzip) os = new GZIPOutputStream(os);
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
||||
try (
|
||||
PrintWriter pw = new PrintWriter(osw);
|
||||
){
|
||||
pw.print(modelJson);
|
||||
}
|
||||
|
||||
//logger.logDebug("Saved hires model: " + model.getTile());
|
||||
|
||||
model.writeBufferGeometryJson(os);
|
||||
} catch (IOException e){
|
||||
Logger.global.logError("Failed to save hires model: " + file, e);
|
||||
} finally {
|
||||
try {
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to close file: " + file, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,20 +108,6 @@ public Grid getTileGrid() {
|
||||
return tileGrid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a block-position to a map-tile-coordinate
|
||||
*/
|
||||
public Vector2i posToTile(Vector3i pos){
|
||||
return tileGrid.getCell(pos.toVector2(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a block-position to a map-tile-coordinate
|
||||
*/
|
||||
public Vector2i posToTile(Vector3d pos){
|
||||
return tileGrid.getCell(new Vector2i(pos.getFloorX(), pos.getFloorZ()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the file for a tile
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 int size() {
|
||||
|
||||
public int add(int count) {
|
||||
ensureCapacity(count);
|
||||
return this.size += count;
|
||||
int start = this.size;
|
||||
this.size += count;
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setPositions(
|
||||
public HiresTileModel setPositions(
|
||||
int face,
|
||||
double x1, double y1, double z1,
|
||||
double x2, double y2, double z2,
|
||||
@ -81,9 +98,11 @@ public void setPositions(
|
||||
position[index + 6 ] = x3;
|
||||
position[index + 6 + 1] = y3;
|
||||
position[index + 6 + 2] = z3;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setUvs(
|
||||
public HiresTileModel setUvs(
|
||||
int face,
|
||||
float u1, float v1,
|
||||
float u2, float v2,
|
||||
@ -99,9 +118,11 @@ public void setUvs(
|
||||
|
||||
uv[index + 4 ] = u3;
|
||||
uv[index + 4 + 1] = v3;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setAOs(
|
||||
public HiresTileModel setAOs(
|
||||
int face,
|
||||
float ao1, float ao2, float ao3
|
||||
) {
|
||||
@ -110,9 +131,11 @@ public void setAOs(
|
||||
ao[index ] = ao1;
|
||||
ao[index + 1] = ao2;
|
||||
ao[index + 2] = ao3;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setColor(
|
||||
public HiresTileModel setColor(
|
||||
int face,
|
||||
float r, float g, float b
|
||||
){
|
||||
@ -121,21 +144,26 @@ public void setColor(
|
||||
color[index ] = r;
|
||||
color[index + 1] = g;
|
||||
color[index + 2] = b;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setSunlight(int face, int sl) {
|
||||
public HiresTileModel setSunlight(int face, int sl) {
|
||||
sunlight[face * FI_SUNLIGHT] = (byte) sl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setBlocklight(int face, int bl) {
|
||||
public HiresTileModel setBlocklight(int face, int bl) {
|
||||
blocklight[face * FI_BLOCKLIGHT] = (byte) bl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setMaterialIndex(int face, int m) {
|
||||
public HiresTileModel setMaterialIndex(int face, int m) {
|
||||
materialIndex[face * FI_MATERIAL_INDEX] = m;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void rotate(
|
||||
public HiresTileModel rotate(
|
||||
int start, int count,
|
||||
float angle, float axisX, float axisY, float axisZ
|
||||
) {
|
||||
@ -157,10 +185,10 @@ public void rotate(
|
||||
qz /= qLength;
|
||||
qw /= qLength;
|
||||
|
||||
rotateWithQuaternion(start, count, qx, qy, qz, qw);
|
||||
return rotateByQuaternion(start, count, qx, qy, qz, qw);
|
||||
}
|
||||
|
||||
public void rotate(
|
||||
public HiresTileModel rotate(
|
||||
int start, int count,
|
||||
float pitch, float yaw, float roll
|
||||
) {
|
||||
@ -192,61 +220,135 @@ public void rotate(
|
||||
qz = qwA * qz3 + qzA * qw3,
|
||||
qw = qwA * qw3 - qzA * qz3;
|
||||
|
||||
rotateWithQuaternion(start, count, qx, qy, qz, qw);
|
||||
return rotateByQuaternion(start, count, qx, qy, qz, qw);
|
||||
}
|
||||
|
||||
private void rotateWithQuaternion(
|
||||
public HiresTileModel rotateByQuaternion(
|
||||
int start, int count,
|
||||
double qx, double qy, double qz, double qw
|
||||
) {
|
||||
double x, y, z, px, py, pz, pw;
|
||||
int end = start + count, index;
|
||||
for (int face = start; face < end; face++) {
|
||||
index = face * FI_COLOR;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
index = face * FI_POSITION + i * 3;
|
||||
|
||||
x = position[index ];
|
||||
y = position[index + 1];
|
||||
z = position[index + 2];
|
||||
x = position[index];
|
||||
y = position[index + 1];
|
||||
z = position[index + 2];
|
||||
|
||||
px = qw * x + qy * z - qz * y;
|
||||
py = qw * y + qz * x - qx * z;
|
||||
pz = qw * z + qx * y - qy * x;
|
||||
pw = -qx * x - qy * y - qz * z;
|
||||
px = qw * x + qy * z - qz * y;
|
||||
py = qw * y + qz * x - qx * z;
|
||||
pz = qw * z + qx * y - qy * x;
|
||||
pw = -qx * x - qy * y - qz * z;
|
||||
|
||||
position[index ] = pw * -qx + px * qw - py * qz + pz * qy;
|
||||
position[index + 1] = pw * -qy + py * qw - pz * qx + px * qz;
|
||||
position[index + 2] = pw * -qz + pz * qw - px * qy + py * qx;
|
||||
position[index] = pw * -qx + px * qw - py * qz + pz * qy;
|
||||
position[index + 1] = pw * -qy + py * qw - pz * qx + px * qz;
|
||||
position[index + 2] = pw * -qz + pz * qw - px * qy + py * qx;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void scale(
|
||||
public HiresTileModel scale(
|
||||
int start, int count,
|
||||
float scale
|
||||
double sx, double sy, double sz
|
||||
) {
|
||||
int startIndex = start * FI_POSITION;
|
||||
int endIndex = count * FI_POSITION + startIndex;
|
||||
int end = start + count, index;
|
||||
for (int face = start; face < end; face++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
index = face * FI_POSITION + i * 3;
|
||||
position[index ] *= sx;
|
||||
position[index + 1] *= sy;
|
||||
position[index + 2] *= sz;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
position[i] *= scale;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void translate(
|
||||
public HiresTileModel translate(
|
||||
int start, int count,
|
||||
double dx, double dy, double dz
|
||||
) {
|
||||
int end = start + count, index;
|
||||
for (int face = start; face < end; face++) {
|
||||
index = face * FI_COLOR;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
index = face * FI_POSITION + i * 3;
|
||||
position[index ] += dx;
|
||||
position[index + 1] += dy;
|
||||
position[index + 2] += dz;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
public HiresTileModel transform(int start, int count, MatrixM3f t) {
|
||||
return transform(start, count,
|
||||
t.m00, t.m01, t.m02,
|
||||
t.m10, t.m11, t.m12,
|
||||
t.m20, t.m21, t.m22
|
||||
);
|
||||
}
|
||||
|
||||
public HiresTileModel transform(
|
||||
int start, int count,
|
||||
float m00, float m01, float m02,
|
||||
float m10, float m11, float m12,
|
||||
float m20, float m21, float m22
|
||||
) {
|
||||
return transform(start, count,
|
||||
m00, m01, m02, 0,
|
||||
m10, m11, m12, 0,
|
||||
m20, m21, m22, 0,
|
||||
0, 0, 0, 1
|
||||
);
|
||||
}
|
||||
|
||||
public HiresTileModel transform(int start, int count, MatrixM4f t) {
|
||||
return transform(start, count,
|
||||
t.m00, t.m01, t.m02, t.m03,
|
||||
t.m10, t.m11, t.m12, t.m13,
|
||||
t.m20, t.m21, t.m22, t.m23,
|
||||
t.m30, t.m31, t.m32, t.m33
|
||||
);
|
||||
}
|
||||
|
||||
public HiresTileModel transform(
|
||||
int start, int count,
|
||||
float m00, float m01, float m02, float m03,
|
||||
float m10, float m11, float m12, float m13,
|
||||
float m20, float m21, float m22, float m23,
|
||||
float m30, float m31, float m32, float m33
|
||||
) {
|
||||
int end = start + count, index;
|
||||
double x, y, z;
|
||||
for (int face = start; face < end; face++) {
|
||||
for (int i = 0; i < 3; i++) {
|
||||
index = face * FI_POSITION + i * 3;
|
||||
x = position[index ];
|
||||
y = position[index + 1];
|
||||
z = position[index + 2];
|
||||
|
||||
position[index ] = m00 * x + m01 * y + m02 * z + m03;
|
||||
position[index + 1] = m10 * x + m11 * y + m12 * z + m13;
|
||||
position[index + 2] = m20 * x + m21 * y + m22 * z + m23;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public HiresTileModel reset(int size) {
|
||||
this.size = size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HiresTileModel clear() {
|
||||
this.size = 0;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void ensureCapacity(int count) {
|
||||
@ -284,4 +386,345 @@ private void setCapacity(int capacity) {
|
||||
materialIndex = new int [capacity * FI_MATERIAL_INDEX];
|
||||
}
|
||||
|
||||
public void writeBufferGeometryJson(OutputStream out) throws IOException {
|
||||
sort();
|
||||
|
||||
Gson gson = new GsonBuilder().create();
|
||||
JsonWriter json = gson.newJsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
|
||||
|
||||
json.beginObject(); // main-object
|
||||
|
||||
// set special values
|
||||
json.name("type").value("BufferGeometry");
|
||||
json.name("uuid").value(UUID.randomUUID().toString().toUpperCase());
|
||||
|
||||
json.name("data").beginObject(); // data
|
||||
json.name("attributes").beginObject(); // attributes
|
||||
|
||||
writePositionArray(json);
|
||||
writeNormalArray(json);
|
||||
writeColorArray(json);
|
||||
writeUvArray(json);
|
||||
writeAoArray(json);
|
||||
writeBlocklightArray(json);
|
||||
writeSunlightArray(json);
|
||||
|
||||
json.endObject(); // attributes
|
||||
|
||||
writeMaterialGroups(json);
|
||||
|
||||
json.endObject(); // data
|
||||
json.endObject(); // main-object
|
||||
|
||||
// save and return
|
||||
json.flush();
|
||||
}
|
||||
|
||||
private void writePositionArray(JsonWriter json) throws IOException {
|
||||
json.name("position");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(3);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
int posSize = size * FI_POSITION;
|
||||
for (int i = 0; i < posSize; i++) {
|
||||
writeRounded(json, position[i]);
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeNormalArray(JsonWriter json) throws IOException {
|
||||
VectorM3f normal = new VectorM3f(0, 0, 0);
|
||||
|
||||
json.name("normal");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(3);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
|
||||
int pi, i, j;
|
||||
for (i = 0; i < size; i++) {
|
||||
pi = i * FI_POSITION;
|
||||
calculateSurfaceNormal(
|
||||
position[pi ], position[pi + 1], position[pi + 2],
|
||||
position[pi + 3], position[pi + 4], position[pi + 5],
|
||||
position[pi + 6], position[pi + 7], position[pi + 8],
|
||||
normal
|
||||
);
|
||||
|
||||
for (j = 0; j < 3; j++) { // all 3 points
|
||||
writeRounded(json, normal.x);
|
||||
writeRounded(json, normal.y);
|
||||
writeRounded(json, normal.z);
|
||||
}
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeColorArray(JsonWriter json) throws IOException {
|
||||
json.name("color");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(3);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
int colorSize = size * FI_COLOR, i, j;
|
||||
for (i = 0; i < colorSize; i += 3) {
|
||||
for (j = 0; j < 3; j++) {
|
||||
writeRounded(json, color[i]);
|
||||
writeRounded(json, color[i + 1]);
|
||||
writeRounded(json, color[i + 2]);
|
||||
}
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeUvArray(JsonWriter json) throws IOException {
|
||||
json.name("uv");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(2);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
int uvSize = size * FI_UV;
|
||||
for (int i = 0; i < uvSize; i++) {
|
||||
writeRounded(json, uv[i]);
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeAoArray(JsonWriter json) throws IOException {
|
||||
json.name("ao");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(1);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
int aoSize = size * FI_AO;
|
||||
for (int i = 0; i < aoSize; i++) {
|
||||
writeRounded(json, ao[i]);
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeBlocklightArray(JsonWriter json) throws IOException {
|
||||
json.name("blocklight");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(1);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
int blSize = size * FI_BLOCKLIGHT;
|
||||
for (int i = 0; i < blSize; i++) {
|
||||
json.value(blocklight[i]);
|
||||
json.value(blocklight[i]);
|
||||
json.value(blocklight[i]);
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeSunlightArray(JsonWriter json) throws IOException {
|
||||
json.name("sunlight");
|
||||
json.beginObject();
|
||||
|
||||
json.name("type").value("Float32Array");
|
||||
json.name("itemSize").value(1);
|
||||
json.name("normalized").value(false);
|
||||
|
||||
json.name("array").beginArray();
|
||||
int blSize = size * FI_SUNLIGHT;
|
||||
for (int i = 0; i < blSize; i++) {
|
||||
json.value(sunlight[i]);
|
||||
json.value(sunlight[i]);
|
||||
json.value(sunlight[i]);
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
}
|
||||
|
||||
private void writeMaterialGroups(JsonWriter json) throws IOException {
|
||||
json.name("groups").beginArray(); // groups
|
||||
|
||||
if (size > 0) {
|
||||
|
||||
int miSize = size * FI_MATERIAL_INDEX, lastMaterial = materialIndex[0], material = lastMaterial, groupStart = 0;
|
||||
|
||||
json.beginObject();
|
||||
json.name("materialIndex").value(material);
|
||||
json.name("start").value(0);
|
||||
|
||||
for (int i = 1; i < miSize; i++) {
|
||||
material = materialIndex[i];
|
||||
|
||||
if (material != lastMaterial) {
|
||||
json.name("count").value((i - groupStart) * 3);
|
||||
json.endObject();
|
||||
|
||||
groupStart = i;
|
||||
|
||||
json.beginObject();
|
||||
json.name("materialIndex").value(material);
|
||||
json.name("start").value(groupStart * 3);
|
||||
}
|
||||
|
||||
lastMaterial = material;
|
||||
}
|
||||
|
||||
json.name("count").value((miSize - groupStart) * 3);
|
||||
json.endObject();
|
||||
|
||||
}
|
||||
|
||||
json.endArray(); // groups
|
||||
}
|
||||
|
||||
private void writeRounded(JsonWriter json, double value) throws IOException {
|
||||
// rounding and remove ".0" to save string space
|
||||
double d = Math.round(value * 10000d) / 10000d;
|
||||
if (d == (long) d) json.value((long) d);
|
||||
else json.value(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does an optimized selection sort to sort all faces based on their material-index.
|
||||
* A selection sort is chosen, because it requires the least amount of swaps, which seem (untested) to be the most expensive operation here
|
||||
*/
|
||||
private void sort() {
|
||||
if (size <= 1) return; // nothing to sort
|
||||
|
||||
int prev = Integer.MIN_VALUE, min, minIndex, i, j;
|
||||
for (i = 0; i < size - 1; i++){
|
||||
minIndex = i;
|
||||
min = materialIndex[minIndex];
|
||||
if (min <= prev) continue; // shortcut
|
||||
|
||||
for (j = i + 1; j < size; j++){
|
||||
if (materialIndex[j] < min){
|
||||
minIndex = j;
|
||||
min = materialIndex[minIndex];
|
||||
}
|
||||
}
|
||||
|
||||
if (minIndex != i) {
|
||||
swap(minIndex, i);
|
||||
}
|
||||
|
||||
prev = min;
|
||||
}
|
||||
}
|
||||
|
||||
private void swap(int face1, int face2) {
|
||||
int i, if1, if2, vi;
|
||||
double vd;
|
||||
float vf;
|
||||
byte vb;
|
||||
|
||||
//swap positions
|
||||
if1 = face1 * FI_POSITION;
|
||||
if2 = face2 * FI_POSITION;
|
||||
for (i = 0; i < FI_POSITION; i++){
|
||||
vd = position[if1 + i];
|
||||
position[if1 + i] = position[if2 + i];
|
||||
position[if2 + i] = vd;
|
||||
}
|
||||
|
||||
//swap uv
|
||||
if1 = face1 * FI_UV;
|
||||
if2 = face2 * FI_UV;
|
||||
for (i = 0; i < FI_UV; i++){
|
||||
vf = uv[if1 + i];
|
||||
uv[if1 + i] = uv[if2 + i];
|
||||
uv[if2 + i] = vf;
|
||||
}
|
||||
|
||||
//swap ao
|
||||
if1 = face1 * FI_AO;
|
||||
if2 = face2 * FI_AO;
|
||||
for (i = 0; i < FI_AO; i++){
|
||||
vf = ao[if1 + i];
|
||||
ao[if1 + i] = ao[if2 + i];
|
||||
ao[if2 + i] = vf;
|
||||
}
|
||||
|
||||
//swap color
|
||||
if1 = face1 * FI_COLOR;
|
||||
if2 = face2 * FI_COLOR;
|
||||
for (i = 0; i < FI_COLOR; i++){
|
||||
vf = color[if1 + i];
|
||||
color[if1 + i] = color[if2 + i];
|
||||
color[if2 + i] = vf;
|
||||
}
|
||||
|
||||
//swap sunlight (assuming FI_SUNLIGHT = 1)
|
||||
vb = sunlight[face1];
|
||||
sunlight[face1] = sunlight[face2];
|
||||
sunlight[face2] = vb;
|
||||
|
||||
//swap blocklight (assuming FI_BLOCKLIGHT = 1)
|
||||
vb = blocklight[face1];
|
||||
blocklight[face1] = blocklight[face2];
|
||||
blocklight[face2] = vb;
|
||||
|
||||
//swap material-index (assuming FI_MATERIAL_INDEX = 1)
|
||||
vi = materialIndex[face1];
|
||||
materialIndex[face1] = materialIndex[face2];
|
||||
materialIndex[face2] = vi;
|
||||
}
|
||||
|
||||
public static HiresTileModel claimInstance() {
|
||||
HiresTileModel instance = INSTANCE_POOL.poll();
|
||||
if (instance != null) {
|
||||
instance.clear();
|
||||
} else {
|
||||
instance = new HiresTileModel(100);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static void recycleInstance(HiresTileModel instance) {
|
||||
instance.clear();
|
||||
INSTANCE_POOL.offer(instance);
|
||||
}
|
||||
|
||||
private static void calculateSurfaceNormal(
|
||||
double p1x, double p1y, double p1z,
|
||||
double p2x, double p2y, double p2z,
|
||||
double p3x, double p3y, double p3z,
|
||||
VectorM3f target
|
||||
){
|
||||
p2x -= p1x; p2y -= p1y; p2z -= p1z;
|
||||
p3x -= p1x; p3y -= p1y; p3z -= p1z;
|
||||
|
||||
p1x = p2y * p3z - p2z * p3y;
|
||||
p1y = p2z * p3x - p2x * p3z;
|
||||
p1z = p2x * p3y - p2y * p3x;
|
||||
|
||||
double length = Math.sqrt(p1x * p1x + p1y * p1y + p1z * p1z);
|
||||
p1x /= length;
|
||||
p1y /= length;
|
||||
p1z /= length;
|
||||
|
||||
target.set((float) p1x, (float) p1y, (float) p1z);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
|
||||
}
|
||||
|
@ -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 @@ private float getLiquidCornerHeight(int x, int y, int z){
|
||||
|
||||
float sumHeight = 0f;
|
||||
int count = 0;
|
||||
BlockState neighborBlockState;
|
||||
|
||||
for (int ix = x; ix <= x+1; ix++){
|
||||
for (int iz = z; iz<= z+1; iz++){
|
||||
Block b = block.getRelativeBlock(ix, y, iz);
|
||||
if (isLiquid(b)){
|
||||
if (getLiquidLevel(b.getBlockState()) == 0) return 14f;
|
||||
for (ix = x; ix <= x+1; ix++){
|
||||
for (iz = z; iz<= z+1; iz++){
|
||||
neighborBlockState = getNeighborBlock(ix, 0, iz).getBlockState();
|
||||
if (isSameLiquid(neighborBlockState)){
|
||||
if (getLiquidLevel(neighborBlockState) == 0) return 14f;
|
||||
|
||||
sumHeight += getLiquidBaseHeight(b.getBlockState());
|
||||
sumHeight += getLiquidBaseHeight(neighborBlockState);
|
||||
count++;
|
||||
}
|
||||
|
||||
else if (!isLiquidBlockingBlock(b)){
|
||||
else if (!isLiquidBlockingBlock(neighborBlockState)){
|
||||
count++;
|
||||
}
|
||||
}
|
||||
@ -167,93 +199,171 @@ else if (!isLiquidBlockingBlock(b)){
|
||||
return sumHeight / count;
|
||||
}
|
||||
|
||||
private boolean isLiquidBlockingBlock(Block block){
|
||||
if (block.getBlockState().equals(BlockState.AIR)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isLiquid(Block block){
|
||||
return isLiquid(block.getBlockState());
|
||||
private boolean isLiquidBlockingBlock(BlockState blockState){
|
||||
return !blockState.equals(BlockState.AIR);
|
||||
}
|
||||
|
||||
private boolean isLiquid(BlockState blockState){
|
||||
if (blockState.getFullId().equals(liquidBlockState.getFullId())) return true;
|
||||
return LiquidModelBuilder.isWaterlogged(blockState);
|
||||
private boolean isSameLiquid(BlockState blockState){
|
||||
if (blockState.getFullId().equals(this.blockState.getFullId())) return true;
|
||||
return this.blockState.isWater && blockState.isWaterlogged;
|
||||
}
|
||||
|
||||
private float getLiquidBaseHeight(BlockState block){
|
||||
int level = getLiquidLevel(block);
|
||||
float baseHeight = 14f - level * 1.9f;
|
||||
return baseHeight;
|
||||
return level >= 8 ? 16f : 14f - level * 1.9f;
|
||||
}
|
||||
|
||||
private int getLiquidLevel(BlockState block){
|
||||
if (block.getProperties().containsKey("level")) {
|
||||
return Integer.parseInt(block.getProperties().get("level"));
|
||||
}
|
||||
return 0;
|
||||
String levelString = block.getProperties().get("level");
|
||||
return levelString != null ? Integer.parseInt(levelString) : 0;
|
||||
}
|
||||
|
||||
private void createElementFace(ExtendedModel model, Direction faceDir, Vector3f c0, Vector3f c1, Vector3f c2, Vector3f c3, Vector3f color, int textureId) {
|
||||
|
||||
//face culling
|
||||
Block bl = block.getRelativeBlock(faceDir);
|
||||
if (isLiquid(bl) || (faceDir != Direction.UP && bl.isCullingNeighborFaces())) return;
|
||||
|
||||
//UV
|
||||
Vector4f uv = new Vector4f(0, 0, 16, 16).div(16);
|
||||
|
||||
//create both triangles
|
||||
Vector2f[] uvs = new Vector2f[4];
|
||||
uvs[0] = new Vector2f(uv.getX(), uv.getW());
|
||||
uvs[1] = new Vector2f(uv.getZ(), uv.getW());
|
||||
uvs[2] = new Vector2f(uv.getZ(), uv.getY());
|
||||
uvs[3] = new Vector2f(uv.getX(), uv.getY());
|
||||
|
||||
ExtendedFace f1 = new ExtendedFace(c0, c1, c2, uvs[0], uvs[1], uvs[2], textureId);
|
||||
ExtendedFace f2 = new ExtendedFace(c0, c2, c3, uvs[0], uvs[2], uvs[3], textureId);
|
||||
|
||||
// move face in a tiny bit to avoid z-fighting with waterlogged blocks (doesn't work because it is rounded back when storing the model later)
|
||||
//f1.translate(faceDir.opposite().toVector().toFloat().mul(0.01));
|
||||
//f2.translate(faceDir.opposite().toVector().toFloat().mul(0.01));
|
||||
|
||||
float blockLight = bl.getBlockLightLevel();
|
||||
float sunLight = bl.getSunLightLevel();
|
||||
|
||||
private final MatrixM3f uvTransform = new MatrixM3f();
|
||||
private boolean createElementFace(Direction faceDir, VectorM3f c0, VectorM3f c1, VectorM3f c2, VectorM3f c3, Color color, int stillTextureId, int flowTextureId) {
|
||||
Vector3i faceDirVector = faceDir.toVector();
|
||||
|
||||
//face culling
|
||||
Block bl = getNeighborBlock(
|
||||
faceDirVector.getX(),
|
||||
faceDirVector.getY(),
|
||||
faceDirVector.getZ()
|
||||
);
|
||||
|
||||
if (isSameLiquid(bl.getBlockState()) || (faceDir != Direction.UP && bl.isCullingNeighborFaces())) return false;
|
||||
|
||||
// initialize the faces
|
||||
blockModel.initialize();
|
||||
blockModel.add(2);
|
||||
|
||||
HiresTileModel tileModel = blockModel.getHiresTile();
|
||||
int face1 = blockModel.getStart();
|
||||
int face2 = face1 + 1;
|
||||
|
||||
// ####### positions
|
||||
tileModel.setPositions(face1,
|
||||
c0.x, c0.y, c0.z,
|
||||
c1.x, c1.y, c1.z,
|
||||
c2.x, c2.y, c2.z
|
||||
);
|
||||
tileModel.setPositions(face2,
|
||||
c0.x, c0.y, c0.z,
|
||||
c2.x, c2.y, c2.z,
|
||||
c3.x, c3.y, c3.z
|
||||
);
|
||||
|
||||
//UV
|
||||
uvs[0].set(0, 1);
|
||||
uvs[1].set(1, 1);
|
||||
uvs[2].set(1, 0);
|
||||
uvs[3].set(0, 0);
|
||||
|
||||
// still/flow ?
|
||||
boolean flow = false;
|
||||
if (faceDir == Direction.UP) {
|
||||
int flowAngle = getFlowingAngle();
|
||||
if (flowAngle != -1) {
|
||||
flow = true;
|
||||
uvTransform
|
||||
.identity()
|
||||
.translate(-0.5f, -0.5f)
|
||||
.scale(0.5f, 0.5f, 1)
|
||||
.rotate(-flowAngle, 0, 0, 1)
|
||||
.translate(0.5f, 0.5f);
|
||||
|
||||
uvs[0].transform(uvTransform);
|
||||
uvs[1].transform(uvTransform);
|
||||
uvs[2].transform(uvTransform);
|
||||
uvs[3].transform(uvTransform);
|
||||
}
|
||||
} else if (faceDir != Direction.DOWN) {
|
||||
flow = true;
|
||||
|
||||
uvs[0].transform(FLOWING_UV_SCALE);
|
||||
uvs[1].transform(FLOWING_UV_SCALE);
|
||||
uvs[2].transform(FLOWING_UV_SCALE);
|
||||
uvs[3].transform(FLOWING_UV_SCALE);
|
||||
}
|
||||
|
||||
tileModel.setUvs(face1,
|
||||
uvs[0].x, uvs[0].y,
|
||||
uvs[1].x, uvs[1].y,
|
||||
uvs[2].x, uvs[2].y
|
||||
);
|
||||
|
||||
tileModel.setUvs(face2,
|
||||
uvs[0].x, uvs[0].y,
|
||||
uvs[2].x, uvs[2].y,
|
||||
uvs[3].x, uvs[3].y
|
||||
);
|
||||
|
||||
// texture index
|
||||
tileModel.setMaterialIndex(face1, flow ? flowTextureId : stillTextureId);
|
||||
tileModel.setMaterialIndex(face2, flow ? flowTextureId : stillTextureId);
|
||||
|
||||
// color
|
||||
tileModel.setColor(face1, color.r, color.g, color.b);
|
||||
tileModel.setColor(face2, color.r, color.g, color.b);
|
||||
|
||||
//ao
|
||||
tileModel.setAOs(face1, 1, 1, 1);
|
||||
tileModel.setAOs(face2, 1, 1, 1);
|
||||
|
||||
// light
|
||||
int blockLight, sunLight;
|
||||
if (faceDir == Direction.UP) {
|
||||
blockLight = block.getBlockLightLevel();
|
||||
sunLight = block.getSunLightLevel();
|
||||
} else {
|
||||
blockLight = bl.getBlockLightLevel();
|
||||
sunLight = bl.getSunLightLevel();
|
||||
}
|
||||
|
||||
f1.setC1(color);
|
||||
f1.setC2(color);
|
||||
f1.setC3(color);
|
||||
f2.setC1(color);
|
||||
f2.setC2(color);
|
||||
f2.setC3(color);
|
||||
|
||||
f1.setBl1(blockLight);
|
||||
f1.setBl2(blockLight);
|
||||
f1.setBl3(blockLight);
|
||||
f2.setBl1(blockLight);
|
||||
f2.setBl2(blockLight);
|
||||
f2.setBl3(blockLight);
|
||||
|
||||
f1.setSl1(sunLight);
|
||||
f1.setSl2(sunLight);
|
||||
f1.setSl3(sunLight);
|
||||
f2.setSl1(sunLight);
|
||||
f2.setSl2(sunLight);
|
||||
f2.setSl3(sunLight);
|
||||
|
||||
//add the face
|
||||
model.addFace(f1);
|
||||
model.addFace(f2);
|
||||
|
||||
tileModel.setBlocklight(face1, blockLight);
|
||||
tileModel.setBlocklight(face2, blockLight);
|
||||
|
||||
tileModel.setSunlight(face1, sunLight);
|
||||
tileModel.setSunlight(face2, sunLight);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean isWaterlogged(BlockState blockState) {
|
||||
if (DEFAULT_WATERLOGGED_BLOCK_IDS.contains(blockState.getFullId())) return true;
|
||||
return blockState.getProperties().getOrDefault("waterlogged", "false").equals("true");
|
||||
|
||||
private Block getNeighborBlock(int dx, int dy, int dz) {
|
||||
int i = (dx + 1) * 9 + (dy + 1) * 3 + (dz + 1);
|
||||
if (i == 13) return block;
|
||||
return blocksAround[i].set(
|
||||
block.getWorld(),
|
||||
block.getX() + dx,
|
||||
block.getY() + dy,
|
||||
block.getZ() + dz
|
||||
);
|
||||
}
|
||||
|
||||
private final VectorM2f flowingVector = new VectorM2f(0, 0);
|
||||
private int getFlowingAngle() {
|
||||
float own = getLiquidBaseHeight(blockState) * BLOCK_SCALE;
|
||||
if (own > 0.8) return -1;
|
||||
|
||||
flowingVector.set(0, 0);
|
||||
|
||||
flowingVector.x += compareLiquidHeights(own, -1, 0);
|
||||
flowingVector.x -= compareLiquidHeights(own, 1, 0);
|
||||
|
||||
flowingVector.y -= compareLiquidHeights(own, 0, -1);
|
||||
flowingVector.y += compareLiquidHeights(own, 0, 1);
|
||||
|
||||
if (flowingVector.x == 0 && flowingVector.y == 0) return -1; // not flowing
|
||||
|
||||
int angle = (int) (flowingVector.angleTo(0, -1) * TrigMath.RAD_TO_DEG);
|
||||
return flowingVector.x < 0 ? angle : -angle;
|
||||
}
|
||||
|
||||
private float compareLiquidHeights(float ownHeight, int dx, int dz) {
|
||||
BlockState state = getNeighborBlock(dx, 0, dz).getBlockState();
|
||||
if (state.isAir) return 0;
|
||||
if (!isSameLiquid(state)) return 0;
|
||||
|
||||
float otherHeight = getLiquidBaseHeight(state) * BLOCK_SCALE;
|
||||
return otherHeight - ownHeight;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -28,7 +28,6 @@
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
|
||||
import de.bluecolored.bluemap.core.util.AtomicFileHelper;
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import de.bluecolored.bluemap.core.util.MathUtils;
|
||||
import de.bluecolored.bluemap.core.util.ModelUtils;
|
||||
|
||||
@ -96,7 +95,7 @@ public void save(File file, boolean force, boolean useGzip) throws IOException {
|
||||
if (useGzip) os = new GZIPOutputStream(os);
|
||||
OutputStreamWriter osw = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
||||
try (
|
||||
PrintWriter pw = new PrintWriter(osw);
|
||||
PrintWriter pw = new PrintWriter(osw)
|
||||
){
|
||||
pw.print(json);
|
||||
}
|
||||
@ -120,7 +119,7 @@ public void flush(){
|
||||
|
||||
for (int i = 0; i < vertexCount; i++){
|
||||
int j = i * 3;
|
||||
int px = Math.round(position[j + 0]);
|
||||
int px = Math.round(position[j ]);
|
||||
int pz = Math.round(position[j + 2]);
|
||||
|
||||
Vector2i p = new Vector2i(px, pz);
|
||||
@ -130,19 +129,19 @@ public void flush(){
|
||||
|
||||
position[j + 1] = lrp.height;
|
||||
|
||||
color[j + 0] = lrp.color.getX();
|
||||
color[j ] = lrp.color.getX();
|
||||
color[j + 1] = lrp.color.getY();
|
||||
color[j + 2] = lrp.color.getZ();
|
||||
|
||||
//recalculate normals
|
||||
int f = Math.floorDiv(i, 3) * 3 * 3;
|
||||
Vector3f p1 = new Vector3f(position[f + 0], position[f + 1], position[f + 2]);
|
||||
Vector3f p1 = new Vector3f(position[f ], position[f + 1], position[f + 2]);
|
||||
Vector3f p2 = new Vector3f(position[f + 3], position[f + 4], position[f + 5]);
|
||||
Vector3f p3 = new Vector3f(position[f + 6], position[f + 7], position[f + 8]);
|
||||
|
||||
Vector3f n = MathUtils.getSurfaceNormal(p1, p2, p3);
|
||||
|
||||
normal[f + 0] = n.getX(); normal[f + 1] = n.getY(); normal[f + 2] = n.getZ();
|
||||
normal[f ] = n.getX(); normal[f + 1] = n.getY(); normal[f + 2] = n.getZ();
|
||||
normal[f + 3] = n.getX(); normal[f + 4] = n.getY(); normal[f + 5] = n.getZ();
|
||||
normal[f + 6] = n.getX(); normal[f + 7] = n.getY(); normal[f + 8] = n.getZ();
|
||||
}
|
||||
@ -165,18 +164,6 @@ public LowresPoint(float height, Vector3f color) {
|
||||
this.height = height;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public LowresPoint add(LowresPoint other){
|
||||
float newHeight = height + other.height;
|
||||
Vector3f newColor = color.add(other.color);
|
||||
return new LowresPoint(newHeight, newColor);
|
||||
}
|
||||
|
||||
public LowresPoint div(float divisor){
|
||||
float newHeight = height / divisor;
|
||||
Vector3f newColor = color.div(divisor);
|
||||
return new LowresPoint(newHeight, newColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,11 +24,13 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.map.lowres;
|
||||
|
||||
import com.flowpowered.math.vector.*;
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresModel;
|
||||
import de.bluecolored.bluemap.core.map.hires.HiresTileMeta;
|
||||
import de.bluecolored.bluemap.core.threejs.BufferGeometry;
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
@ -41,7 +43,6 @@
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
@ -68,52 +69,45 @@ public LowresModelManager(Path fileRoot, Vector2i pointsPerLowresTile, Vector2i
|
||||
/**
|
||||
* Renders all points from the given hires-model onto the lowres-grid
|
||||
*/
|
||||
public void render(HiresModel hiresModel) {
|
||||
Vector3i min = hiresModel.getBlockMin();
|
||||
Vector3i max = hiresModel.getBlockMax();
|
||||
Vector3i size = max.sub(min).add(Vector3i.ONE);
|
||||
|
||||
Vector2i blocksPerPoint =
|
||||
size
|
||||
.toVector2(true)
|
||||
.div(pointsPerHiresTile);
|
||||
|
||||
Vector2i pointMin = min
|
||||
.toVector2(true)
|
||||
.toDouble()
|
||||
.div(blocksPerPoint.toDouble())
|
||||
.floor()
|
||||
.toInt();
|
||||
public void render(HiresTileMeta tileMeta) {
|
||||
Vector2i blocksPerPoint = new Vector2i(
|
||||
tileMeta.getSizeX() / pointsPerHiresTile.getX(),
|
||||
tileMeta.getSizeZ() / pointsPerHiresTile.getY()
|
||||
);
|
||||
|
||||
Vector2i pointMin = new Vector2i(
|
||||
Math.floorDiv(tileMeta.getMinX(), blocksPerPoint.getX()),
|
||||
Math.floorDiv(tileMeta.getMinZ(), blocksPerPoint.getY())
|
||||
);
|
||||
|
||||
Color
|
||||
pointColor = new Color(),
|
||||
columnColor = new Color();
|
||||
|
||||
for (int tx = 0; tx < pointsPerHiresTile.getX(); tx++){
|
||||
for (int tz = 0; tz < pointsPerHiresTile.getY(); tz++){
|
||||
|
||||
double height = 0;
|
||||
|
||||
Vector3d color = Vector3d.ZERO;
|
||||
double colorCount = 0;
|
||||
pointColor.set(0, 0, 0, 0, true);
|
||||
|
||||
for (int x = 0; x < blocksPerPoint.getX(); x++){
|
||||
for (int z = 0; z < blocksPerPoint.getY(); z++){
|
||||
|
||||
int rx = tx * blocksPerPoint.getX() + x + min.getX();
|
||||
int rz = tz * blocksPerPoint.getY() + z + min.getZ();
|
||||
height += hiresModel.getHeight(rx, rz);
|
||||
|
||||
Vector4f c = hiresModel.getColor(rx, rz);
|
||||
color = color.add(c.toVector3().toDouble().mul(c.getW()));
|
||||
colorCount += c.getW();
|
||||
int rx = tx * blocksPerPoint.getX() + x + tileMeta.getMinX();
|
||||
int rz = tz * blocksPerPoint.getY() + z + tileMeta.getMinZ();
|
||||
height += tileMeta.getHeight(rx, rz);
|
||||
|
||||
tileMeta.getColor(rx, rz, columnColor).premultiplied();
|
||||
pointColor.add(columnColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (colorCount > 0) color = color.div(colorCount);
|
||||
|
||||
|
||||
pointColor.flatten().straight();
|
||||
|
||||
int count = blocksPerPoint.getX() * blocksPerPoint.getY();
|
||||
height /= count;
|
||||
|
||||
Vector2i point = pointMin.add(tx, tz);
|
||||
update(hiresModel.getWorld(), point, (float) height, color.toFloat());
|
||||
|
||||
|
||||
update(pointMin.getX() + tx, pointMin.getY() + tz, (float) height, pointColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -132,31 +126,36 @@ public synchronized void save(){
|
||||
/**
|
||||
* Updates a point on the lowres-model-grid
|
||||
*/
|
||||
public void update(UUID world, Vector2i point, float height, Vector3f color) {
|
||||
public void update(int px, int pz, float height, Color color) {
|
||||
if (color.premultiplied) throw new IllegalArgumentException("Color can not be premultiplied!");
|
||||
|
||||
Vector2i point = new Vector2i(px, pz);
|
||||
Vector3f colorV = new Vector3f(color.r, color.g, color.b);
|
||||
|
||||
Vector2i tile = pointToTile(point);
|
||||
Vector2i relPoint = getPointRelativeToTile(tile, point);
|
||||
LowresModel model = getModel(world, tile);
|
||||
model.update(relPoint, height, color);
|
||||
LowresModel model = getModel(tile);
|
||||
model.update(relPoint, height, colorV);
|
||||
|
||||
if (relPoint.getX() == 0){
|
||||
Vector2i tile2 = tile.add(-1, 0);
|
||||
Vector2i relPoint2 = getPointRelativeToTile(tile2, point);
|
||||
LowresModel model2 = getModel(world, tile2);
|
||||
model2.update(relPoint2, height, color);
|
||||
LowresModel model2 = getModel(tile2);
|
||||
model2.update(relPoint2, height, colorV);
|
||||
}
|
||||
|
||||
if (relPoint.getY() == 0){
|
||||
Vector2i tile2 = tile.add(0, -1);
|
||||
Vector2i relPoint2 = getPointRelativeToTile(tile2, point);
|
||||
LowresModel model2 = getModel(world, tile2);
|
||||
model2.update(relPoint2, height, color);
|
||||
LowresModel model2 = getModel(tile2);
|
||||
model2.update(relPoint2, height, colorV);
|
||||
}
|
||||
|
||||
if (relPoint.getX() == 0 && relPoint.getY() == 0){
|
||||
Vector2i tile2 = tile.add(-1, -1);
|
||||
Vector2i relPoint2 = getPointRelativeToTile(tile2, point);
|
||||
LowresModel model2 = getModel(world, tile2);
|
||||
model2.update(relPoint2, height, color);
|
||||
LowresModel model2 = getModel(tile2);
|
||||
model2.update(relPoint2, height, colorV);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +166,7 @@ public File getFile(Vector2i tile, boolean useGzip){
|
||||
return FileUtils.coordsToFile(fileRoot, tile, "json" + (useGzip ? ".gz" : ""));
|
||||
}
|
||||
|
||||
private LowresModel getModel(UUID world, Vector2i tile) {
|
||||
private LowresModel getModel(Vector2i tile) {
|
||||
|
||||
File modelFile = getFile(tile, useGzip);
|
||||
CachedModel model = models.get(modelFile);
|
||||
@ -257,15 +256,17 @@ private void saveModel(File modelFile, CachedModel model) {
|
||||
}
|
||||
|
||||
private Vector2i pointToTile(Vector2i point){
|
||||
return point
|
||||
.toDouble()
|
||||
.div(pointsPerLowresTile.toDouble())
|
||||
.floor()
|
||||
.toInt();
|
||||
return new Vector2i(
|
||||
Math.floorDiv(point.getX(), pointsPerLowresTile.getX()),
|
||||
Math.floorDiv(point.getY(), pointsPerLowresTile.getY())
|
||||
);
|
||||
}
|
||||
|
||||
private Vector2i getPointRelativeToTile(Vector2i tile, Vector2i point){
|
||||
return point.sub(tile.mul(pointsPerLowresTile));
|
||||
return new Vector2i(
|
||||
point.getX() - tile.getX() * pointsPerLowresTile.getX(),
|
||||
point.getY() - tile.getY() * pointsPerLowresTile.getY()
|
||||
);
|
||||
}
|
||||
|
||||
public Vector2i getTileSize() {
|
||||
|
@ -88,14 +88,14 @@ public boolean isGenerated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
int sectionY = pos.getY() >> 4;
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(pos);
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
public String getBlockIdMeta(Vector3i pos) {
|
||||
@ -109,17 +109,17 @@ public String getBlockIdMeta(Vector3i pos) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (!hasLight) return LightData.SKY;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(15, 0);
|
||||
|
||||
int sectionY = pos.getY() >> 4;
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
||||
return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY;
|
||||
return (y < 0) ? target.set(0, 0) : target.set(15, 0);
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return LightData.SKY;
|
||||
if (section == null) return target.set(15, 0);
|
||||
|
||||
return section.getLightData(pos);
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,6 +129,7 @@ public Biome getBiome(int x, int y, int z) {
|
||||
int biomeByteIndex = z * 16 + x;
|
||||
|
||||
if (biomeByteIndex >= this.biomes.length) return Biome.DEFAULT;
|
||||
|
||||
return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF);
|
||||
}
|
||||
|
||||
@ -158,10 +159,9 @@ public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
@ -202,10 +202,9 @@ public String getBlockIdMeta(Vector3i pos) {
|
||||
return blockId + ":" + blockData + " " + forgeIdMapping;
|
||||
}
|
||||
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
@ -213,7 +212,7 @@ public LightData getLightData(Vector3i pos) {
|
||||
int blockLight = getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf);
|
||||
int skyLight = getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf);
|
||||
|
||||
return new LightData(skyLight, blockLight);
|
||||
return target.set(skyLight, blockLight);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
@ -38,6 +38,8 @@
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ChunkAnvil113 extends MCAChunk {
|
||||
private static final MinecraftVersion VERSION = new MinecraftVersion(1, 13);
|
||||
|
||||
private BiomeMapper biomeIdMapper;
|
||||
|
||||
private boolean isGenerated;
|
||||
@ -97,28 +99,28 @@ public boolean isGenerated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
int sectionY = pos.getY() >> 4;
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(pos);
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (!hasLight) return LightData.SKY;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(15, 0);
|
||||
|
||||
int sectionY = pos.getY() >> 4;
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
||||
return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY;
|
||||
return (y < 0) ? target.set(0, 0) : target.set(15, 0);
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return LightData.SKY;
|
||||
if (section == null) return target.set(15, 0);
|
||||
|
||||
return section.getLightData(pos);
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,6 +130,7 @@ public Biome getBiome(int x, int y, int z) {
|
||||
int biomeIntIndex = z * 16 + x;
|
||||
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT;
|
||||
|
||||
return biomeIdMapper.get(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
||||
@ -175,7 +178,7 @@ public Section(CompoundTag sectionData) {
|
||||
}
|
||||
}
|
||||
|
||||
palette[i] = new BlockState(id, properties);
|
||||
palette[i] = new BlockState(VERSION, id, properties);
|
||||
}
|
||||
} else {
|
||||
this.palette = new BlockState[0];
|
||||
@ -188,12 +191,11 @@ public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockIndex = y * 256 + z * 16 + x;
|
||||
|
||||
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
||||
@ -205,20 +207,19 @@ public BlockState getBlockState(Vector3i pos) {
|
||||
return palette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
|
||||
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
int blockLight = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
|
||||
int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
|
||||
|
||||
return new LightData(skyLight, blockLight);
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
@ -38,6 +38,8 @@
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ChunkAnvil115 extends MCAChunk {
|
||||
private static final MinecraftVersion VERSION = new MinecraftVersion(1, 15);
|
||||
|
||||
private BiomeMapper biomeIdMapper;
|
||||
|
||||
private boolean isGenerated;
|
||||
@ -97,28 +99,28 @@ public boolean isGenerated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
int sectionY = pos.getY() >> 4;
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length) return BlockState.AIR;
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(pos);
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (!hasLight) return LightData.SKY;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(15, 0);
|
||||
|
||||
int sectionY = pos.getY() >> 4;
|
||||
int sectionY = y >> 4;
|
||||
if (sectionY < 0 || sectionY >= this.sections.length)
|
||||
return (pos.getY() < 0) ? LightData.ZERO : LightData.SKY;
|
||||
return (y < 0) ? target.set(0, 0) : target.set(15, 0);
|
||||
|
||||
Section section = this.sections[sectionY];
|
||||
if (section == null) return LightData.SKY;
|
||||
if (section == null) return target.set(15, 0);
|
||||
|
||||
return section.getLightData(pos);
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,7 +130,9 @@ public Biome getBiome(int x, int y, int z) {
|
||||
y = y / 4;
|
||||
int biomeIntIndex = y * 16 + z * 4 + x;
|
||||
|
||||
if (biomeIntIndex < 0) return Biome.DEFAULT;
|
||||
if (biomeIntIndex >= this.biomes.length) return Biome.DEFAULT;
|
||||
|
||||
return biomeIdMapper.get(biomes[biomeIntIndex]);
|
||||
}
|
||||
|
||||
@ -176,7 +180,7 @@ public Section(CompoundTag sectionData) {
|
||||
}
|
||||
}
|
||||
|
||||
palette[i] = new BlockState(id, properties);
|
||||
palette[i] = new BlockState(VERSION, id, properties);
|
||||
}
|
||||
} else {
|
||||
this.palette = new BlockState[0];
|
||||
@ -189,12 +193,11 @@ public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockIndex = y * 256 + z * 16 + x;
|
||||
|
||||
long value = MCAMath.getValueFromLongStream(blocks, blockIndex, bitsPerBlock);
|
||||
@ -206,20 +209,19 @@ public BlockState getBlockState(Vector3i pos) {
|
||||
return palette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
|
||||
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
int blockLight = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
|
||||
int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
|
||||
|
||||
return new LightData(skyLight, blockLight);
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
@ -32,18 +32,23 @@
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
import net.querz.nbt.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class ChunkAnvil116 extends MCAChunk {
|
||||
private static final MinecraftVersion VERSION = new MinecraftVersion(1, 16);
|
||||
|
||||
private BiomeMapper biomeIdMapper;
|
||||
|
||||
private boolean isGenerated;
|
||||
private boolean hasLight;
|
||||
private Map<Integer, Section> sections;
|
||||
|
||||
private int sectionMin, sectionMax;
|
||||
private Section[] sections;
|
||||
|
||||
private int[] biomes;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -62,11 +67,14 @@ public ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, Biome
|
||||
isGenerated = !status.equals("empty");
|
||||
}
|
||||
|
||||
this.sections = new HashMap<>(); // Is using a has-map the fastest/best way for an int->Object mapping?
|
||||
this.sectionMin = Integer.MAX_VALUE;
|
||||
this.sectionMax = Integer.MIN_VALUE;
|
||||
if (levelData.containsKey("Sections")) {
|
||||
for (CompoundTag sectionTag : ((ListTag<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 ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, Biome
|
||||
if (sectionMin > y) sectionMin = y;
|
||||
if (sectionMax < y) sectionMax = y;
|
||||
|
||||
sections.put(y, section);
|
||||
sectionList.add(section);
|
||||
}
|
||||
|
||||
sections = new Section[1 + sectionMax - sectionMin];
|
||||
for (Section section : sectionList) {
|
||||
sections[section.sectionY - sectionMin] = section;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,25 +116,25 @@ public boolean isGenerated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
int sectionY = pos.getY() >> 4;
|
||||
|
||||
Section section = this.sections.get(sectionY);
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return BlockState.AIR;
|
||||
|
||||
return section.getBlockState(pos);
|
||||
return section.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (!hasLight) return LightData.SKY;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (!hasLight) return target.set(15, 0);
|
||||
|
||||
int sectionY = pos.getY() >> 4;
|
||||
int sectionY = y >> 4;
|
||||
|
||||
Section section = this.sections.get(sectionY);
|
||||
if (section == null) return (sectionY < sectionMin) ? LightData.ZERO : LightData.SKY;
|
||||
Section section = getSection(sectionY);
|
||||
if (section == null) return (sectionY < sectionMin) ? target.set(0, 0) : target.set(15, 0);
|
||||
|
||||
return section.getLightData(pos);
|
||||
return section.getLightData(x, y, z, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -150,6 +163,12 @@ public int getMaxY(int x, int z) {
|
||||
return sectionMax * 16 + 15;
|
||||
}
|
||||
|
||||
private Section getSection(int y) {
|
||||
y -= sectionMin;
|
||||
if (y < 0 || y >= this.sections.length) return null;
|
||||
return this.sections[y];
|
||||
}
|
||||
|
||||
private static class Section {
|
||||
private static final String AIR_ID = "minecraft:air";
|
||||
|
||||
@ -194,7 +213,7 @@ public Section(CompoundTag sectionData) {
|
||||
}
|
||||
}
|
||||
|
||||
palette[i] = new BlockState(id, properties);
|
||||
palette[i] = new BlockState(VERSION, id, properties);
|
||||
}
|
||||
} else {
|
||||
this.palette = new BlockState[0];
|
||||
@ -207,12 +226,11 @@ public int getSectionY() {
|
||||
return sectionY;
|
||||
}
|
||||
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (blocks.length == 0) return BlockState.AIR;
|
||||
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockIndex = y * 256 + z * 16 + x;
|
||||
|
||||
long value = MCAMath.getValueFromLongArray(blocks, blockIndex, bitsPerBlock);
|
||||
@ -223,21 +241,20 @@ public BlockState getBlockState(Vector3i pos) {
|
||||
|
||||
return palette[(int) value];
|
||||
}
|
||||
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return LightData.ZERO;
|
||||
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
if (blockLight.length == 0 && skyLight.length == 0) return target.set(0, 0);
|
||||
|
||||
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
int y = pos.getY() & 0xF;
|
||||
int z = pos.getZ() & 0xF;
|
||||
x &= 0xF; y &= 0xF; z &= 0xF; // Math.floorMod(pos.getX(), 16)
|
||||
|
||||
int blockByteIndex = y * 256 + z * 16 + x;
|
||||
int blockHalfByteIndex = blockByteIndex >> 1; // blockByteIndex / 2
|
||||
boolean largeHalf = (blockByteIndex & 0x1) != 0; // (blockByteIndex % 2) == 0
|
||||
|
||||
int blockLight = this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0;
|
||||
int skyLight = this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0;
|
||||
|
||||
return new LightData(skyLight, blockLight);
|
||||
return target.set(
|
||||
this.skyLight.length > 0 ? MCAMath.getByteHalf(this.skyLight[blockHalfByteIndex], largeHalf) : 0,
|
||||
this.blockLight.length > 0 ? MCAMath.getByteHalf(this.blockLight[blockHalfByteIndex], largeHalf) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,6 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.LightData;
|
||||
@ -40,13 +38,13 @@ public boolean isGenerated() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
return BlockState.AIR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LightData getLightData(Vector3i pos) {
|
||||
return LightData.ZERO;
|
||||
public LightData getLightData(int x, int y, int z, LightData target) {
|
||||
return target.set(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,7 +24,6 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.mca;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.world.Biome;
|
||||
import de.bluecolored.bluemap.core.world.BlockState;
|
||||
import de.bluecolored.bluemap.core.world.Chunk;
|
||||
@ -47,21 +46,27 @@ protected MCAChunk(CompoundTag chunkTag) {
|
||||
|
||||
@Override
|
||||
public abstract boolean isGenerated();
|
||||
|
||||
|
||||
@Override
|
||||
public int getDataVersion() {
|
||||
return dataVersion;
|
||||
}
|
||||
|
||||
public abstract BlockState getBlockState(Vector3i pos);
|
||||
|
||||
public abstract LightData getLightData(Vector3i pos);
|
||||
|
||||
|
||||
@Override
|
||||
public abstract BlockState getBlockState(int x, int y, int z);
|
||||
|
||||
@Override
|
||||
public abstract LightData getLightData(int x, int y, int z, LightData target);
|
||||
|
||||
@Override
|
||||
public abstract Biome getBiome(int x, int y, int z);
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return 255;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinY(int x, int z) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -36,6 +36,8 @@
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper;
|
||||
import de.bluecolored.bluemap.core.util.ArrayPool;
|
||||
import de.bluecolored.bluemap.core.util.math.VectorM2i;
|
||||
import de.bluecolored.bluemap.core.world.*;
|
||||
import net.querz.nbt.CompoundTag;
|
||||
import net.querz.nbt.ListTag;
|
||||
@ -57,8 +59,8 @@ public class MCAWorld implements World {
|
||||
@DebugDump private final UUID uuid;
|
||||
@DebugDump private final Path worldFolder;
|
||||
private final MinecraftVersion minecraftVersion;
|
||||
@DebugDump private String name;
|
||||
@DebugDump private Vector3i spawnPoint;
|
||||
@DebugDump private final String name;
|
||||
@DebugDump private final Vector3i spawnPoint;
|
||||
|
||||
private final LoadingCache<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 @@ private MCAWorld(
|
||||
}
|
||||
|
||||
public BlockState getBlockState(Vector3i pos) {
|
||||
return getChunk(blockToChunk(pos)).getBlockState(pos);
|
||||
return getChunk(pos.getX() >> 4, pos.getZ() >> 4).getBlockState(pos.getX(), pos.getY(), pos.getZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return getChunk(x >> 4, z >> 4).getBiome(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock(Vector3i pos) {
|
||||
MCAChunk chunk = getChunk(blockToChunk(pos));
|
||||
BlockState blockState = getExtendedBlockState(chunk, pos);
|
||||
LightData lightData = chunk.getLightData(pos);
|
||||
Biome biome = chunk.getBiome(pos.getX(), pos.getY(), pos.getZ());
|
||||
BlockProperties properties = blockPropertiesMapper.get(blockState);
|
||||
return new Block(this, blockState, lightData, biome, properties, pos);
|
||||
}
|
||||
|
||||
private BlockState getExtendedBlockState(MCAChunk chunk, Vector3i pos) {
|
||||
BlockState blockState = chunk.getBlockState(pos);
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
MCAChunk chunk = getChunk(x >> 4, z >> 4);
|
||||
BlockState blockState = chunk.getBlockState(x, y, z);
|
||||
|
||||
if (chunk instanceof ChunkAnvil112) { // only use extensions if old format chunk (1.12) in the new format block-states are saved with extensions
|
||||
for (BlockStateExtension ext : blockStateExtensions.getOrDefault(blockState.getFullId(), Collections.emptyList())) {
|
||||
blockState = ext.extend(this, pos, blockState);
|
||||
List<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 MCAChunk getChunk(Vector2i pos) {
|
||||
|
||||
@Override
|
||||
public MCARegion getRegion(int x, int z) {
|
||||
return regionCache.get(new Vector2i(x, z));
|
||||
return getRegion(new Vector2i(x, z));
|
||||
}
|
||||
|
||||
public MCARegion getRegion(Vector2i pos) {
|
||||
return regionCache.get(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -256,7 +268,7 @@ public void cleanUpChunkCache() {
|
||||
public BlockIdMapper getBlockIdMapper() {
|
||||
return blockIdMapper;
|
||||
}
|
||||
|
||||
|
||||
public BlockPropertiesMapper getBlockPropertiesMapper() {
|
||||
return blockPropertiesMapper;
|
||||
}
|
||||
@ -415,13 +427,6 @@ public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
|
||||
throw new IOException("Invaid level.dat format!", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector2i blockToChunk(Vector3i pos) {
|
||||
return new Vector2i(
|
||||
pos.getX() >> 4,
|
||||
pos.getZ() >> 4
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,12 +24,15 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.resourcepack;
|
||||
|
||||
import com.flowpowered.math.TrigMath;
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Face;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
|
||||
import de.bluecolored.bluemap.core.util.Axis;
|
||||
import de.bluecolored.bluemap.core.util.Direction;
|
||||
import de.bluecolored.bluemap.core.util.math.Axis;
|
||||
import de.bluecolored.bluemap.core.util.math.MatrixM4f;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
|
||||
|
||||
@ -39,6 +42,7 @@
|
||||
import java.util.Map.Entry;
|
||||
|
||||
public class BlockModelResource {
|
||||
private static final double FIT_TO_BLOCK_SCALE_MULTIPLIER = 2 - Math.sqrt(2);
|
||||
|
||||
private ModelType modelType = ModelType.NORMAL;
|
||||
|
||||
@ -79,6 +83,7 @@ public class Element {
|
||||
|
||||
private Vector3f from = Vector3f.ZERO, to = new Vector3f(16f, 16f, 16f);
|
||||
private Rotation rotation = new Rotation();
|
||||
private MatrixM4f rotationMatrix;
|
||||
private boolean shade = true;
|
||||
private EnumMap<Direction, Face> faces = new EnumMap<>(Direction.class);
|
||||
private boolean fullCube = false;
|
||||
@ -134,6 +139,10 @@ public Rotation getRotation() {
|
||||
return rotation;
|
||||
}
|
||||
|
||||
public MatrixM4f getRotationMatrix() {
|
||||
return rotationMatrix;
|
||||
}
|
||||
|
||||
public boolean isShade() {
|
||||
return shade;
|
||||
}
|
||||
@ -318,7 +327,7 @@ private BlockModelResource buildNoReset(String modelPath, boolean renderElements
|
||||
break;
|
||||
}
|
||||
|
||||
if (texture.getColor().getW() < 1) {
|
||||
if (texture.getColorStraight().a < 1) {
|
||||
blockModel.culling = false;
|
||||
break;
|
||||
}
|
||||
@ -346,6 +355,37 @@ private Element buildElement(BlockModelResource model, ConfigurationNode node, S
|
||||
element.rotation.axis = Axis.fromString(node.node("rotation", "axis").getString("x"));
|
||||
if (!node.node("rotation", "origin").virtual()) element.rotation.origin = readVector3f(node.node("rotation", "origin"));
|
||||
element.rotation.rescale = node.node("rotation", "rescale").getBoolean(false);
|
||||
|
||||
// rotation matrix
|
||||
float angle = element.rotation.angle;
|
||||
Vector3i axis = element.rotation.axis.toVector();
|
||||
Vector3f origin = element.rotation.origin;
|
||||
boolean rescale = element.rotation.rescale;
|
||||
|
||||
MatrixM4f rot = new MatrixM4f();
|
||||
if (angle != 0f) {
|
||||
rot.translate(-origin.getX(), -origin.getY(), -origin.getZ());
|
||||
rot.rotate(
|
||||
angle,
|
||||
axis.getX(),
|
||||
axis.getY(),
|
||||
axis.getZ()
|
||||
);
|
||||
|
||||
if (rescale) {
|
||||
float scale = (float) (Math.abs(TrigMath.sin(angle * TrigMath.DEG_TO_RAD)) * FIT_TO_BLOCK_SCALE_MULTIPLIER);
|
||||
rot.scale(
|
||||
(1 - axis.getX()) * scale + 1,
|
||||
(1 - axis.getY()) * scale + 1,
|
||||
(1 - axis.getZ()) * scale + 1
|
||||
);
|
||||
}
|
||||
|
||||
rot.translate(origin.getX(), origin.getY(), origin.getZ());
|
||||
}
|
||||
element.rotationMatrix = rot;
|
||||
} else {
|
||||
element.rotationMatrix = new MatrixM4f();
|
||||
}
|
||||
|
||||
boolean allDirs = true;
|
||||
|
@ -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 Collection<TransformedBlockModelResource> getModels(BlockState blockState
|
||||
if (allMatch == null) allMatch = variant;
|
||||
continue;
|
||||
}
|
||||
|
||||
models.add(variant.getModel(pos));
|
||||
return models;
|
||||
|
||||
targetCollection.add(variant.getModel(x, y, z));
|
||||
return targetCollection;
|
||||
}
|
||||
}
|
||||
|
||||
if (allMatch != null) {
|
||||
models.add(allMatch.getModel(pos));
|
||||
return models;
|
||||
targetCollection.add(allMatch.getModel(x, y, z));
|
||||
return targetCollection;
|
||||
}
|
||||
|
||||
for (Variant variant : multipart) {
|
||||
if (variant.condition.matches(blockState)) {
|
||||
models.add(variant.getModel(pos));
|
||||
targetCollection.add(variant.getModel(x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
//fallback to first variant
|
||||
if (models.isEmpty() && !variants.isEmpty()) {
|
||||
models.add(variants.get(0).getModel(pos));
|
||||
if (targetCollection.isEmpty() && !variants.isEmpty()) {
|
||||
targetCollection.add(variants.get(0).getModel(x, y, z));
|
||||
}
|
||||
|
||||
return models;
|
||||
return targetCollection;
|
||||
}
|
||||
|
||||
private static class Variant {
|
||||
@ -103,10 +102,10 @@ private static class Variant {
|
||||
private Variant() {
|
||||
}
|
||||
|
||||
public TransformedBlockModelResource getModel(Vector3i pos) {
|
||||
public TransformedBlockModelResource getModel(int x, int y, int z) {
|
||||
if (models.isEmpty()) throw new IllegalStateException("A variant must have at least one model!");
|
||||
|
||||
double selection = MathUtils.hashToFloat(pos, 827364) * totalWeight; // random based on position
|
||||
double selection = MathUtils.hashToFloat(x, y, z, 827364) * totalWeight; // random based on position
|
||||
for (Weighted<TransformedBlockModelResource> w : models) {
|
||||
selection -= w.weight;
|
||||
if (selection <= 0) return w.value;
|
||||
|
@ -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 ResourcePack(MinecraftVersion minecraftVersion) {
|
||||
foliageMap.setRGB(0, 0, 0xFF00FF00);
|
||||
grassMap = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
|
||||
grassMap.setRGB(0, 0, 0xFF00FF00);
|
||||
blockColorCalculator = new BlockColorCalculator(foliageMap, grassMap);
|
||||
blockColorCalculatorFactory = new BlockColorCalculatorFactory(foliageMap, grassMap);
|
||||
configs = new HashMap<>();
|
||||
}
|
||||
|
||||
@ -181,14 +181,14 @@ public void load(File... sources) throws InterruptedException {
|
||||
|
||||
try {
|
||||
foliageMap = ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/foliage.png"));
|
||||
blockColorCalculator.setFoliageMap(foliageMap);
|
||||
blockColorCalculatorFactory.setFoliageMap(foliageMap);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load foliagemap!", ex);
|
||||
}
|
||||
|
||||
try {
|
||||
grassMap = ImageIO.read(sourcesAccess.readFile("assets/minecraft/textures/colormap/grass.png"));
|
||||
blockColorCalculator.setGrassMap(grassMap);
|
||||
blockColorCalculatorFactory.setGrassMap(grassMap);
|
||||
} catch (IOException ex) {
|
||||
Logger.global.logError("Failed to load grassmap!", ex);
|
||||
}
|
||||
@ -210,8 +210,8 @@ public BlockStateResource getBlockStateResource(BlockState state) throws NoSuchR
|
||||
return resource;
|
||||
}
|
||||
|
||||
public BlockColorCalculator getBlockColorCalculator() {
|
||||
return blockColorCalculator;
|
||||
public BlockColorCalculatorFactory getBlockColorCalculatorFactory() {
|
||||
return blockColorCalculatorFactory;
|
||||
}
|
||||
|
||||
public MinecraftVersion getMinecraftVersion() {
|
||||
|
@ -24,20 +24,21 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.resourcepack;
|
||||
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
public class Texture {
|
||||
|
||||
private final int id;
|
||||
private final String path;
|
||||
private Vector4f color;
|
||||
private boolean isHalfTransparent;
|
||||
private String texture;
|
||||
private final Color color, colorPremultiplied;
|
||||
private final boolean isHalfTransparent;
|
||||
private final String texture;
|
||||
|
||||
protected Texture(int id, String path, Vector4f color, boolean halfTransparent, String texture) {
|
||||
protected Texture(int id, String path, Color color, boolean halfTransparent, String texture) {
|
||||
this.id = id;
|
||||
this.path = path;
|
||||
this.color = color;
|
||||
this.color = new Color().set(color).straight();
|
||||
this.colorPremultiplied = new Color().set(color).premultiplied();
|
||||
this.isHalfTransparent = halfTransparent;
|
||||
this.texture = texture;
|
||||
}
|
||||
@ -54,9 +55,17 @@ public String getPath() {
|
||||
* Returns the calculated median color of the {@link Texture}.
|
||||
* @return The median color of this {@link Texture}
|
||||
*/
|
||||
public Vector4f getColor() {
|
||||
public Color getColorStraight() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the calculated median color of the {@link Texture} (premultiplied).
|
||||
* @return The (premultiplied) median color of this {@link Texture}
|
||||
*/
|
||||
public Color getColorPremultiplied() {
|
||||
return colorPremultiplied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link Texture} has half-transparent pixels or not.
|
||||
|
@ -24,11 +24,11 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.resourcepack;
|
||||
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import com.google.gson.*;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resourcepack.fileaccess.FileAccess;
|
||||
import de.bluecolored.bluemap.core.util.FileUtils;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
@ -95,12 +95,12 @@ public void saveTextureFile(File file) throws IOException {
|
||||
textureNode.addProperty("texture", texture.getTexture());
|
||||
textureNode.addProperty("transparent", texture.isHalfTransparent());
|
||||
|
||||
Vector4f color = texture.getColor();
|
||||
Color color = texture.getColorStraight();
|
||||
JsonArray colorNode = new JsonArray();
|
||||
colorNode.add(color.getX());
|
||||
colorNode.add(color.getY());
|
||||
colorNode.add(color.getZ());
|
||||
colorNode.add(color.getW());
|
||||
colorNode.add(color.r);
|
||||
colorNode.add(color.g);
|
||||
colorNode.add(color.b);
|
||||
colorNode.add(color.a);
|
||||
|
||||
textureNode.add("color", colorNode);
|
||||
|
||||
@ -141,14 +141,14 @@ public synchronized void loadTextureFile(File file) throws IOException, ParseRes
|
||||
int size = textures.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
while (i >= textureList.size()) { //prepopulate with placeholder so we don't get an IndexOutOfBounds below
|
||||
textureList.add(new Texture(textureList.size(), "empty", Vector4f.ZERO, false, EMPTY_BASE64));
|
||||
textureList.add(new Texture(textureList.size(), "empty", new Color(), false, EMPTY_BASE64));
|
||||
}
|
||||
|
||||
try {
|
||||
JsonObject texture = textures.get(i).getAsJsonObject();
|
||||
String path = texture.get("id").getAsString();
|
||||
boolean transparent = texture.get("transparent").getAsBoolean();
|
||||
Vector4f color = readVector4f(texture.get("color").getAsJsonArray());
|
||||
Color color = readColor(texture.get("color").getAsJsonArray());
|
||||
textureList.set(i, new Texture(i, path, color, transparent, EMPTY_BASE64));
|
||||
} catch (ParseResourceException | RuntimeException ex) {
|
||||
Logger.global.logWarning("Failed to load texture with id " + i + " from texture file " + file + "!");
|
||||
@ -188,7 +188,7 @@ public synchronized Texture loadTexture(FileAccess fileAccess, String path) thro
|
||||
boolean halfTransparent = checkHalfTransparent(image);
|
||||
|
||||
//calculate color
|
||||
Vector4f color = calculateColor(image);
|
||||
Color color = calculateColor(image);
|
||||
|
||||
//write to Base64
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
@ -237,7 +237,7 @@ private synchronized void regenerateMap() {
|
||||
}
|
||||
}
|
||||
|
||||
private Vector4f readVector4f(JsonArray jsonArray) throws ParseResourceException {
|
||||
private Color readColor(JsonArray jsonArray) throws ParseResourceException {
|
||||
if (jsonArray.size() < 4) throw new ParseResourceException("Failed to load Vector4: Not enough values in list-node!");
|
||||
|
||||
float r = jsonArray.get(0).getAsFloat();
|
||||
@ -245,7 +245,7 @@ private Vector4f readVector4f(JsonArray jsonArray) throws ParseResourceException
|
||||
float b = jsonArray.get(2).getAsFloat();
|
||||
float a = jsonArray.get(3).getAsFloat();
|
||||
|
||||
return new Vector4f(r, g, b, a);
|
||||
return new Color().set(r, g, b, a, false);
|
||||
}
|
||||
|
||||
private boolean checkHalfTransparent(BufferedImage image){
|
||||
@ -262,17 +262,17 @@ private boolean checkHalfTransparent(BufferedImage image){
|
||||
return false;
|
||||
}
|
||||
|
||||
private Vector4f calculateColor(BufferedImage image){
|
||||
double alpha = 0d, red = 0d, green = 0d, blue = 0d;
|
||||
private Color calculateColor(BufferedImage image){
|
||||
float alpha = 0f, red = 0f, green = 0f, blue = 0f;
|
||||
int count = 0;
|
||||
|
||||
for (int x = 0; x < image.getWidth(); x++){
|
||||
for (int y = 0; y < image.getHeight(); y++){
|
||||
int pixel = image.getRGB(x, y);
|
||||
double pixelAlpha = (double)((pixel >> 24) & 0xff) / (double) 0xff;
|
||||
double pixelRed = (double)((pixel >> 16) & 0xff) / (double) 0xff;
|
||||
double pixelGreen = (double)((pixel >> 8) & 0xff) / (double) 0xff;
|
||||
double pixelBlue = (double)((pixel >> 0) & 0xff) / (double) 0xff;
|
||||
float pixelAlpha = ((pixel >> 24) & 0xff) / 255f;
|
||||
float pixelRed = ((pixel >> 16) & 0xff) / 255f;
|
||||
float pixelGreen = ((pixel >> 8) & 0xff) / 255f;
|
||||
float pixelBlue = (pixel & 0xff) / 255f;
|
||||
|
||||
count++;
|
||||
alpha += pixelAlpha;
|
||||
@ -282,14 +282,14 @@ private Vector4f calculateColor(BufferedImage image){
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0 || alpha == 0) return Vector4f.ZERO;
|
||||
if (count == 0 || alpha == 0) return new Color();
|
||||
|
||||
red /= alpha;
|
||||
green /= alpha;
|
||||
blue /= alpha;
|
||||
alpha /= count;
|
||||
|
||||
return new Vector4f(red, green, blue, alpha);
|
||||
return new Color().set(red, green, blue, alpha, false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -28,8 +28,8 @@
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
import de.bluecolored.bluemap.core.model.VectorM3f;
|
||||
|
||||
@Deprecated //TODO
|
||||
public class MathUtils {
|
||||
|
||||
private MathUtils() {}
|
||||
@ -73,27 +73,6 @@ public static Vector3f getSurfaceNormal(Vector3f p1, Vector3f p2, Vector3f p3) {
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the surface-normal of a plane spanned between three vectors.
|
||||
* @param p1 The first vector
|
||||
* @param p2 The second vector
|
||||
* @param p3 The third vector
|
||||
* @return The calculated normal
|
||||
*/
|
||||
public static VectorM3f getSurfaceNormal(VectorM3f p1, VectorM3f p2, VectorM3f p3) {
|
||||
float ux = p2.x - p1.x, uy = p2.y - p1.y, uz = p2.z - p1.z;
|
||||
float vx = p3.x - p1.x, vy = p3.y - p1.y, vz = p3.z - p1.z;
|
||||
|
||||
float nX = uy * vz - uz * vy;
|
||||
float nY = uz * vx - ux * vz;
|
||||
float nZ = ux * vy - uy * vx;
|
||||
|
||||
VectorM3f n = new VectorM3f(nX, nY, nZ);
|
||||
n.normalize();
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Hashes the provided position to a random float between 0 and 1.<br>
|
||||
|
@ -31,6 +31,7 @@
|
||||
import de.bluecolored.bluemap.core.model.Face;
|
||||
import de.bluecolored.bluemap.core.model.Model;
|
||||
|
||||
@Deprecated //TODO
|
||||
public class ModelUtils {
|
||||
|
||||
private ModelUtils() {}
|
||||
|
@ -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;
|
||||
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -24,11 +24,8 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3f;
|
||||
import com.flowpowered.math.vector.Vector4f;
|
||||
|
||||
import de.bluecolored.bluemap.core.util.ConfigUtils;
|
||||
import de.bluecolored.bluemap.core.util.MathUtils;
|
||||
import de.bluecolored.bluemap.core.util.math.Color;
|
||||
import org.spongepowered.configurate.ConfigurationNode;
|
||||
|
||||
public class Biome {
|
||||
@ -39,14 +36,14 @@ public class Biome {
|
||||
private int numeralId = 0;
|
||||
private float humidity = 0.5f;
|
||||
private float temp = 0.5f;
|
||||
private Vector3f waterColor = MathUtils.color3FromInt(4159204);
|
||||
private Color waterColor = new Color().set(4159204).premultiplied();
|
||||
|
||||
private Vector4f overlayFoliageColor = Vector4f.ZERO;
|
||||
private Vector4f overlayGrassColor = Vector4f.ZERO;
|
||||
private Color overlayFoliageColor = new Color().premultiplied();
|
||||
private Color overlayGrassColor = new Color().premultiplied();
|
||||
|
||||
private Biome() {}
|
||||
|
||||
public Biome(String id, int numeralId, float humidity, float temp, Vector3f waterColor) {
|
||||
public Biome(String id, int numeralId, float humidity, float temp, Color waterColor) {
|
||||
this.id = id;
|
||||
this.numeralId = numeralId;
|
||||
this.humidity = humidity;
|
||||
@ -54,7 +51,7 @@ public Biome(String id, int numeralId, float humidity, float temp, Vector3f wate
|
||||
this.waterColor = waterColor;
|
||||
}
|
||||
|
||||
public Biome(String id, int numeralId, float humidity, float temp, Vector3f waterColor, Vector4f overlayFoliageColor, Vector4f overlayGrassColor) {
|
||||
public Biome(String id, int numeralId, float humidity, float temp, Color waterColor, Color overlayFoliageColor, Color overlayGrassColor) {
|
||||
this (id, numeralId, humidity, temp, waterColor);
|
||||
|
||||
this.overlayFoliageColor = overlayFoliageColor;
|
||||
@ -77,15 +74,15 @@ public float getTemp() {
|
||||
return temp;
|
||||
}
|
||||
|
||||
public Vector3f getWaterColor() {
|
||||
public Color getWaterColor() {
|
||||
return waterColor;
|
||||
}
|
||||
|
||||
public Vector4f getOverlayFoliageColor() {
|
||||
public Color getOverlayFoliageColor() {
|
||||
return overlayFoliageColor;
|
||||
}
|
||||
|
||||
public Vector4f getOverlayGrassColor() {
|
||||
public Color getOverlayGrassColor() {
|
||||
return overlayGrassColor;
|
||||
}
|
||||
|
||||
@ -96,9 +93,9 @@ public static Biome create(String id, ConfigurationNode node) {
|
||||
biome.numeralId = node.node("id").getInt(biome.numeralId);
|
||||
biome.humidity = node.node("humidity").getFloat(biome.humidity);
|
||||
biome.temp = node.node("temp").getFloat(biome.temp);
|
||||
try { biome.waterColor = MathUtils.color3FromInt(ConfigUtils.readColorInt(node.node("watercolor"))); } catch (NumberFormatException ignored) {}
|
||||
try { biome.overlayFoliageColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.node("foliagecolor"))); } catch (NumberFormatException ignored) {}
|
||||
try { biome.overlayGrassColor = MathUtils.color4FromInt(ConfigUtils.readColorInt(node.node("grasscolor"))); } catch (NumberFormatException ignored) {}
|
||||
try { biome.waterColor = new Color().set(ConfigUtils.readColorInt(node.node("watercolor"))).premultiplied(); } catch (NumberFormatException ignored) {}
|
||||
try { biome.overlayFoliageColor = new Color().set(ConfigUtils.readColorInt(node.node("foliagecolor"))).premultiplied(); } catch (NumberFormatException ignored) {}
|
||||
try { biome.overlayGrassColor = new Color().set(ConfigUtils.readColorInt(node.node("grasscolor"))).premultiplied(); } catch (NumberFormatException ignored) {}
|
||||
|
||||
return biome;
|
||||
}
|
||||
|
@ -30,68 +30,137 @@
|
||||
public class Block {
|
||||
|
||||
private World world;
|
||||
private int x, y, z;
|
||||
|
||||
private Chunk chunk;
|
||||
|
||||
private BlockState blockState;
|
||||
private BlockProperties properties;
|
||||
private LightData lightData;
|
||||
private Biome biome;
|
||||
private BlockProperties properties;
|
||||
private Vector3i pos;
|
||||
|
||||
private float sunLight;
|
||||
private float blockLight;
|
||||
private int sunLight;
|
||||
private int blockLight;
|
||||
|
||||
private final transient LightData tempLight;
|
||||
|
||||
public Block(World world, BlockState blockState, LightData lightData, Biome biome, BlockProperties properties, Vector3i pos) {
|
||||
public Block(World world, int x, int y, int z) {
|
||||
tempLight = new LightData(0, 0);
|
||||
|
||||
set(world, x, y, z);
|
||||
}
|
||||
|
||||
public Block set(World world, int x, int y, int z) {
|
||||
if (this.x == x && this.y == y && this.z == z && this.world == world) return this;
|
||||
|
||||
this.world = world;
|
||||
this.blockState = blockState;
|
||||
this.lightData = lightData;
|
||||
this.biome = biome;
|
||||
this.properties = properties;
|
||||
this.pos = pos;
|
||||
|
||||
this.sunLight = -1;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
reset();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Block set(int x, int y, int z) {
|
||||
if (this.x == x && this.y == y && this.z == z) return this;
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
reset();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
this.chunk = null;
|
||||
|
||||
this.blockState = null;
|
||||
this.properties = null;
|
||||
this.lightData = new LightData(-1, -1);
|
||||
this.biome = null;
|
||||
|
||||
this.blockLight = -1;
|
||||
this.sunLight = -1;
|
||||
}
|
||||
|
||||
public BlockState getBlockState() {
|
||||
return blockState;
|
||||
|
||||
public Block add(int dx, int dy, int dz) {
|
||||
return set(x + dx, y + dy, z + dz);
|
||||
}
|
||||
|
||||
|
||||
public Block copy(Block source) {
|
||||
return set(source.world, source.x, source.y, source.z);
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public Vector3i getPosition() {
|
||||
return pos;
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
public Chunk getChunk() {
|
||||
if (chunk == null) chunk = world.getChunkAtBlock(x, y, z);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public BlockState getBlockState() {
|
||||
if (blockState == null) blockState = getChunk().getBlockState(x, y, z);
|
||||
return blockState;
|
||||
}
|
||||
|
||||
public BlockProperties getProperties() {
|
||||
if (properties == null) properties = world.getBlockProperties(getBlockState());
|
||||
return properties;
|
||||
}
|
||||
|
||||
public LightData getLightData() {
|
||||
if (lightData.getSkyLight() < 0) getChunk().getLightData(x, y, z, lightData);
|
||||
return lightData;
|
||||
}
|
||||
|
||||
public Biome getBiome() {
|
||||
if (biome == null) biome = getChunk().getBiome(x, y, z);
|
||||
return biome;
|
||||
}
|
||||
|
||||
public int getSunLightLevel() {
|
||||
return getLightData().getSkyLight();
|
||||
}
|
||||
|
||||
public float getSunLightLevel() {
|
||||
return lightData.getSkyLight();
|
||||
}
|
||||
|
||||
public float getBlockLightLevel() {
|
||||
return lightData.getBlockLight();
|
||||
public int getBlockLightLevel() {
|
||||
return getLightData().getBlockLight();
|
||||
}
|
||||
|
||||
public boolean isCullingNeighborFaces() {
|
||||
return properties.isCulling();
|
||||
return getProperties().isCulling();
|
||||
}
|
||||
|
||||
public boolean isFlammable() {
|
||||
return properties.isFlammable();
|
||||
return getProperties().isFlammable();
|
||||
}
|
||||
|
||||
public boolean isOccludingNeighborFaces(){
|
||||
return properties.isOccluding();
|
||||
}
|
||||
|
||||
public Biome getBiome() {
|
||||
return biome;
|
||||
return getProperties().isOccluding();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is internally used for light rendering
|
||||
* It is basically the sun light that is projected onto adjacent faces
|
||||
*/
|
||||
public float getPassedSunLight() {
|
||||
public int getPassedSunLight() {
|
||||
if (sunLight < 0) calculateLight();
|
||||
return sunLight;
|
||||
}
|
||||
@ -100,7 +169,7 @@ public float getPassedSunLight() {
|
||||
* This is internally used for light rendering
|
||||
* It is basically the block light that is projected onto adjacent faces
|
||||
*/
|
||||
public float getPassedBlockLight() {
|
||||
public int getPassedBlockLight() {
|
||||
if (blockLight < 0) calculateLight();
|
||||
return blockLight;
|
||||
}
|
||||
@ -110,61 +179,29 @@ private void calculateLight() {
|
||||
blockLight = getBlockLightLevel();
|
||||
|
||||
if (blockLight > 0 || sunLight > 0) return;
|
||||
|
||||
|
||||
Vector3i dirV;
|
||||
int nx, ny, nz;
|
||||
for (Direction direction : Direction.values()) {
|
||||
Block neighbor = getRelativeBlock(direction);
|
||||
sunLight = Math.max(neighbor.getSunLightLevel(), sunLight);
|
||||
blockLight = Math.max(neighbor.getBlockLightLevel(), blockLight);
|
||||
dirV = direction.toVector();
|
||||
nx = dirV.getX() + x;
|
||||
ny = dirV.getY() + y;
|
||||
nz = dirV.getZ() + z;
|
||||
|
||||
world.getLightData(nx, ny, nz, tempLight);
|
||||
|
||||
sunLight = Math.max(tempLight.getSkyLight(), sunLight);
|
||||
blockLight = Math.max(tempLight.getBlockLight(), blockLight);
|
||||
}
|
||||
}
|
||||
|
||||
public Block getRelativeBlock(int x, int y, int z) {
|
||||
Vector3i pos = getPosition().add(x, y, z);
|
||||
return getWorld().getBlock(pos);
|
||||
}
|
||||
|
||||
public Block getRelativeBlock(Vector3i direction) {
|
||||
Vector3i pos = getPosition().add(direction);
|
||||
return getWorld().getBlock(pos);
|
||||
}
|
||||
|
||||
public Block getRelativeBlock(Direction direction){
|
||||
return getRelativeBlock(direction.toVector());
|
||||
}
|
||||
|
||||
public void setWorld(World world) {
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public void setBlockState(BlockState blockState) {
|
||||
this.blockState = blockState;
|
||||
}
|
||||
|
||||
public void setLightData(LightData lightData) {
|
||||
this.lightData = lightData;
|
||||
|
||||
this.blockLight = -1f;
|
||||
this.sunLight = -1f;
|
||||
}
|
||||
|
||||
public void setBiome(Biome biome) {
|
||||
this.biome = biome;
|
||||
}
|
||||
|
||||
public void setProperties(BlockProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public void setPos(Vector3i pos) {
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Block{" +
|
||||
"blockState=" + blockState +
|
||||
", biome=" + biome +
|
||||
", pos=" + pos +
|
||||
"world=" + world +
|
||||
", x=" + x +
|
||||
", y=" + y +
|
||||
", z=" + z +
|
||||
", sunLight=" + sunLight +
|
||||
", blockLight=" + blockLight +
|
||||
'}';
|
||||
|
@ -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 @@
|
||||
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 BlockState(String id, Map<String, String> properties) {
|
||||
this.id = id;
|
||||
this.namespace = namespace;
|
||||
this.fullId = namespace + ":" + id;
|
||||
|
||||
// special fast-access properties
|
||||
this.isAir =
|
||||
"minecraft:air".equals(this.fullId) ||
|
||||
"minecraft:cave_air".equals(this.fullId) ||
|
||||
"minecraft:void_air".equals(this.fullId);
|
||||
|
||||
this.isWater = "minecraft:water".equals(this.fullId);
|
||||
|
||||
this.isWaterlogged =
|
||||
DEFAULT_WATERLOGGED_BLOCK_IDS.contains(this.fullId) ||
|
||||
"true".equals(this.properties.get("waterlogged"));
|
||||
|
||||
if (version.isAtLeast(MinecraftVersion.THE_FLATTENING)) {
|
||||
this.isRandomOffset = OFFSET_BLOCK_IDS.contains(this.fullId);
|
||||
} else {
|
||||
this.isRandomOffset =
|
||||
"minecraft:tall_grass".equals(this.fullId);
|
||||
}
|
||||
}
|
||||
|
||||
private BlockState(BlockState blockState, String withKey, String withValue) {
|
||||
@ -88,7 +144,15 @@ private BlockState(BlockState blockState, String withKey, String withValue) {
|
||||
this.id = blockState.getId();
|
||||
this.namespace = blockState.getNamespace();
|
||||
this.fullId = namespace + ":" + id;
|
||||
this.properties = Collections.unmodifiableMap(props);
|
||||
this.properties = props;
|
||||
|
||||
// special fast-access properties
|
||||
this.isAir = blockState.isAir;
|
||||
this.isWater = blockState.isWater;
|
||||
this.isWaterlogged =
|
||||
DEFAULT_WATERLOGGED_BLOCK_IDS.contains(this.fullId) ||
|
||||
"true".equals(this.properties.get("waterlogged"));
|
||||
this.isRandomOffset = blockState.isRandomOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -165,7 +229,7 @@ public String toString() {
|
||||
return getFullId() + "[" + sj.toString() + "]";
|
||||
}
|
||||
|
||||
public static BlockState fromString(String serializedBlockState) throws IllegalArgumentException {
|
||||
public static BlockState fromString(MinecraftVersion version, String serializedBlockState) throws IllegalArgumentException {
|
||||
try {
|
||||
Matcher m = BLOCKSTATE_SERIALIZATION_PATTERN.matcher(serializedBlockState);
|
||||
m.find();
|
||||
@ -182,7 +246,7 @@ public static BlockState fromString(String serializedBlockState) throws IllegalA
|
||||
|
||||
String blockId = m.group(1).trim();
|
||||
|
||||
return new BlockState(blockId, pt);
|
||||
return new BlockState(version, blockId, pt);
|
||||
} catch (RuntimeException ex) {
|
||||
throw new IllegalArgumentException("'" + serializedBlockState + "' could not be parsed to a BlockState!");
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@ -96,23 +97,16 @@ public Grid getRegionGrid() {
|
||||
public Biome getBiome(int x, int y, int z) {
|
||||
return world.getBiome(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock(Vector3i pos) {
|
||||
if (!isInside(pos)) return createAirBlock(pos);
|
||||
|
||||
Block block = world.getBlock(pos);
|
||||
block.setWorld(this);
|
||||
return block;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Block getBlock(int x, int y, int z) {
|
||||
if (!isInside(x, y, z)) return createAirBlock(new Vector3i(x, y, z));
|
||||
|
||||
Block block = world.getBlock(x, y, z);
|
||||
block.setWorld(this);
|
||||
return block;
|
||||
@Override
|
||||
public BlockProperties getBlockProperties(BlockState blockState) {
|
||||
return world.getBlockProperties(blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(int x, int y, int z) {
|
||||
if (!isInside(x, y, z)) return BlockState.AIR;
|
||||
return world.getBlockState(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -120,6 +114,11 @@ public Chunk getChunk(int x, int z) {
|
||||
return world.getChunk(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunkAtBlock(int x, int y, int z) {
|
||||
return world.getChunkAtBlock(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Region getRegion(int x, int z) {
|
||||
return world.getRegion(x, z);
|
||||
@ -153,9 +152,10 @@ public void invalidateChunkCache(int x, int z) {
|
||||
public void cleanUpChunkCache() {
|
||||
world.cleanUpChunkCache();
|
||||
}
|
||||
|
||||
private boolean isInside(Vector3i blockPos) {
|
||||
return isInside(blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
|
||||
@Override
|
||||
public BlockPropertiesMapper getBlockPropertiesMapper() {
|
||||
return world.getBlockPropertiesMapper();
|
||||
}
|
||||
|
||||
private boolean isInside(int x, int z) {
|
||||
@ -176,15 +176,4 @@ private boolean isInside(int x, int y, int z) {
|
||||
y <= max.getY();
|
||||
}
|
||||
|
||||
private Block createAirBlock(Vector3i pos) {
|
||||
return new Block(
|
||||
this,
|
||||
BlockState.AIR,
|
||||
pos.getY() < this.min.getY() ? LightData.ZERO : LightData.SKY,
|
||||
Biome.DEFAULT,
|
||||
BlockProperties.TRANSPARENT,
|
||||
pos
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,11 +26,11 @@
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import de.bluecolored.bluemap.core.mca.mapping.BlockPropertiesMapper;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Represents a World on the Server<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 @@ default Block getBlock(int x, int y, int z) {
|
||||
* Cleans up invalid cache-entries to free up memory
|
||||
*/
|
||||
void cleanUpChunkCache();
|
||||
|
||||
/**
|
||||
* Returns the block-properties manager used for this world
|
||||
*/
|
||||
BlockPropertiesMapper getBlockPropertiesMapper();
|
||||
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -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"
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.world;
|
||||
|
||||
import de.bluecolored.bluemap.core.MinecraftVersion;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
@ -33,12 +34,12 @@ public class BlockStateTest {
|
||||
|
||||
@Test
|
||||
public void testIdNamespace() {
|
||||
BlockState blockState = new BlockState("someblock");
|
||||
BlockState blockState = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "someblock");
|
||||
assertEquals("minecraft:someblock", blockState.getFullId());
|
||||
assertEquals("minecraft", blockState.getNamespace());
|
||||
assertEquals("someblock", blockState.getId());
|
||||
|
||||
blockState = new BlockState("somemod:someblock");
|
||||
blockState = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock");
|
||||
assertEquals("somemod:someblock", blockState.getFullId());
|
||||
assertEquals("somemod", blockState.getNamespace());
|
||||
assertEquals("someblock", blockState.getId());
|
||||
@ -46,7 +47,7 @@ public void testIdNamespace() {
|
||||
|
||||
@Test
|
||||
public void testToString() {
|
||||
BlockState blockState = new BlockState("someblock");
|
||||
BlockState blockState = new BlockState(MinecraftVersion.LATEST_SUPPORTED, "someblock");
|
||||
assertEquals("minecraft:someblock[]", blockState.toString());
|
||||
|
||||
blockState = blockState.with("testProp", "testVal");
|
||||
@ -63,19 +64,19 @@ public void testToString() {
|
||||
|
||||
@Test
|
||||
public void testFromString() {
|
||||
BlockState blockState = BlockState.fromString("somemod:someblock");
|
||||
BlockState blockState = BlockState.fromString(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock");
|
||||
assertEquals("somemod:someblock", blockState.getFullId());
|
||||
assertEquals("somemod", blockState.getNamespace());
|
||||
assertEquals("someblock", blockState.getId());
|
||||
assertTrue(blockState.getProperties().isEmpty());
|
||||
|
||||
blockState = BlockState.fromString("somemod:someblock[]");
|
||||
blockState = BlockState.fromString(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock[]");
|
||||
assertEquals("somemod:someblock", blockState.getFullId());
|
||||
assertEquals("somemod", blockState.getNamespace());
|
||||
assertEquals("someblock", blockState.getId());
|
||||
assertTrue(blockState.getProperties().isEmpty());
|
||||
|
||||
blockState = BlockState.fromString("somemod:someblock[testProp=testVal,testProp2=testVal2]");
|
||||
blockState = BlockState.fromString(MinecraftVersion.LATEST_SUPPORTED, "somemod:someblock[testProp=testVal,testProp2=testVal2]");
|
||||
assertEquals("somemod:someblock", blockState.getFullId());
|
||||
assertEquals("somemod", blockState.getNamespace());
|
||||
assertEquals("someblock", blockState.getId());
|
||||
|
Loading…
Reference in New Issue
Block a user