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