BlueMap/BlueMapCore/src/main/java/de/bluecolored/bluemap/core/map/hires/blockmodel/LiquidModelBuilder.java

373 lines
14 KiB
Java

/*
* 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.TrigMath;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.map.TextureGallery;
import de.bluecolored.bluemap.core.map.hires.BlockModelView;
import de.bluecolored.bluemap.core.map.hires.TileModel;
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resources.BlockColorCalculatorFactory;
import de.bluecolored.bluemap.core.resources.ResourcePath;
import de.bluecolored.bluemap.core.resources.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.BlockModel;
import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.TextureVariable;
import de.bluecolored.bluemap.core.resources.resourcepack.blockstate.Variant;
import de.bluecolored.bluemap.core.resources.resourcepack.texture.Texture;
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.BlockNeighborhood;
import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.block.ExtendedBlock;
/**
* A model builder for all liquid blocks
*/
@SuppressWarnings("DuplicatedCode")
public class LiquidModelBuilder {
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 ResourcePack resourcePack;
private final TextureGallery textureGallery;
private final RenderSettings renderSettings;
private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator;
private final VectorM3f[] corners;
private final VectorM2f[] uvs = new VectorM2f[4];
private BlockNeighborhood<?> block;
private BlockState blockState;
private BlockModel modelResource;
private BlockModelView blockModel;
private Color blockColor;
public LiquidModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) {
this.resourcePack = resourcePack;
this.textureGallery = textureGallery;
this.renderSettings = renderSettings;
this.blockColorCalculator = resourcePack.getColorCalculatorFactory().createCalculator();
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 ),
};
for (int i = 0; i < uvs.length; i++) uvs[i] = new VectorM2f(0, 0);
}
public void build(BlockNeighborhood<?> block, BlockState blockState, Variant variant, BlockModelView blockModel, Color color) {
this.block = block;
this.blockState = blockState;
this.modelResource = variant.getModel().getResource();
this.blockModel = blockModel;
this.blockColor = color;
build();
}
private final Color tintcolor = new Color();
private void build() {
int blockLight = block.getBlockLightLevel();
int sunLight = block.getSunLightLevel();
// filter out blocks that are in a "cave" that should not be rendered
if (
this.block.isRemoveIfCave() &&
(renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0
) return;
int level = blockState.getLiquidLevel();
if (level < 8 && !(level == 0 && isSameLiquid(block.getNeighborBlock(0, 1, 0)))){
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;
}
TextureVariable stillVariable = modelResource.getTextures().get("still");
TextureVariable flowVariable = modelResource.getTextures().get("flow");
ResourcePath<Texture> stillTexturePath = stillVariable == null ? null : stillVariable
.getTexturePath(modelResource.getTextures()::get);
ResourcePath<Texture> flowTexturePath = flowVariable == null ? null : flowVariable
.getTexturePath(modelResource.getTextures()::get);
int stillTextureId = textureGallery.get(stillTexturePath);
int flowTextureId = textureGallery.get(flowTexturePath);
tintcolor.set(1f, 1f, 1f, 1f, true);
if (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
blockModel.scale(BLOCK_SCALE, BLOCK_SCALE, BLOCK_SCALE);
//calculate mapcolor
if (upFaceRendered) {
Texture stillTexture = stillTexturePath == null ? null : stillTexturePath.getResource(resourcePack::getTexture);
if (stillTexture != null) {
blockColor.set(stillTexture.getColorPremultiplied());
blockColor.multiply(tintcolor);
// apply light
float combinedLight = Math.max(sunLight, blockLight) / 15f;
combinedLight = (renderSettings.getAmbientLight() + combinedLight) / (renderSettings.getAmbientLight() + 1f);
blockColor.r *= combinedLight;
blockColor.g *= combinedLight;
blockColor.b *= combinedLight;
}
} else {
blockColor.set(0, 0, 0, 0, true);
}
}
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(block.getNeighborBlock(ix, 1, iz))){
return 16f;
}
}
}
float sumHeight = 0f;
int count = 0;
ExtendedBlock<?> neighbor;
BlockState neighborBlockState;
for (ix = x; ix <= x+1; ix++){
for (iz = z; iz<= z+1; iz++){
neighbor = block.getNeighborBlock(ix, 0, iz);
neighborBlockState = neighbor.getBlockState();
if (isSameLiquid(neighbor)){
if (neighborBlockState.getLiquidLevel() == 0) return 14f;
sumHeight += getLiquidBaseHeight(neighborBlockState);
count++;
}
else if (!isLiquidBlockingBlock(neighborBlockState)){
count++;
}
}
}
//should both never happen
if (sumHeight == 0) return 3f;
if (count == 0) return 3f;
return sumHeight / count;
}
private boolean isLiquidBlockingBlock(BlockState blockState){
return !blockState.isAir();
}
@SuppressWarnings("StringEquality")
private boolean isSameLiquid(ExtendedBlock<?> block){
if (block.getBlockState().getFormatted() == this.blockState.getFormatted()) return true;
return this.blockState.isWater() && (block.getBlockState().isWaterlogged() || block.getProperties().isAlwaysWaterlogged());
}
private float getLiquidBaseHeight(BlockState block){
int level = block.getLiquidLevel();
return level >= 8 ? 16f : 14f - level * 1.9f;
}
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
ExtendedBlock<?> bl = block.getNeighborBlock(
faceDirVector.getX(),
faceDirVector.getY(),
faceDirVector.getZ()
);
if (isSameLiquid(bl) || (faceDir != Direction.UP && bl.getProperties().isCulling())) return false;
// initialize the faces
blockModel.initialize();
blockModel.add(2);
TileModel 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();
}
tileModel.setBlocklight(face1, blockLight);
tileModel.setBlocklight(face2, blockLight);
tileModel.setSunlight(face1, sunLight);
tileModel.setSunlight(face2, sunLight);
return true;
}
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) {
ExtendedBlock<?> neighbor = block.getNeighborBlock(dx, 0, dz);
if (neighbor.getBlockState().isAir()) return 0;
if (!isSameLiquid(neighbor)) return 0;
float otherHeight = getLiquidBaseHeight(neighbor.getBlockState()) * BLOCK_SCALE;
return otherHeight - ownHeight;
}
}