/* * 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.Vector3f; import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector4f; import de.bluecolored.bluemap.core.map.TextureGallery; 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.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.Element; import de.bluecolored.bluemap.core.resources.resourcepack.blockmodel.Face; 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.MatrixM4f; import de.bluecolored.bluemap.core.util.math.VectorM2f; import de.bluecolored.bluemap.core.util.math.VectorM3f; import de.bluecolored.bluemap.core.world.BlockProperties; import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.block.BlockNeighborhood; import de.bluecolored.bluemap.core.world.block.ExtendedBlock; /** * This model builder creates a BlockStateModel using the information from parsed resource-pack json files. */ @SuppressWarnings("DuplicatedCode") public class ResourceModelBuilder { private static final float BLOCK_SCALE = 1f / 16f; private final ResourcePack resourcePack; private final TextureGallery textureGallery; private final RenderSettings renderSettings; private final BlockColorCalculatorFactory.BlockColorCalculator blockColorCalculator; 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 BlockNeighborhood block; private Variant variant; private BlockModel modelResource; private BlockModelView blockModel; private Color blockColor; private float blockColorOpacity; public ResourceModelBuilder(ResourcePack resourcePack, TextureGallery textureGallery, RenderSettings renderSettings) { this.resourcePack = resourcePack; this.textureGallery = textureGallery; this.renderSettings = renderSettings; this.blockColorCalculator = resourcePack.getColorCalculatorFactory().createCalculator(); 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); } private final MatrixM4f modelTransform = new MatrixM4f(); public void build(BlockNeighborhood block, Variant variant, BlockModelView blockModel, Color color) { this.block = block; this.blockModel = blockModel; this.blockColor = color; this.blockColorOpacity = 0f; this.variant = variant; this.modelResource = variant.getModel().getResource(); this.tintColor.set(0, 0, 0, -1, true); // render model int modelStart = blockModel.getStart(); Element[] elements = modelResource.getElements(); if (elements != null) { for (Element element : elements) { buildModelElementResource(element, blockModel.initialize()); } } if (color.a > 0) { color.flatten().straight(); color.a = blockColorOpacity; } blockModel.initialize(modelStart); // apply model-rotation if (variant.isRotated()) { blockModel.transform(modelTransform.identity() .translate(-0.5f, -0.5f, -0.5f) .multiplyTo(variant.getRotationMatrix()) .translate(0.5f, 0.5f, 0.5f) ); } //random offset if (block.getProperties().isRandomOffset()){ float dx = (hashToFloat(block.getX(), block.getZ(), 123984) - 0.5f) * 0.75f; float dz = (hashToFloat(block.getX(), block.getZ(), 345542) - 0.5f) * 0.75f; blockModel.translate(dx, 0, dz); } } private final MatrixM4f modelElementTransform = new MatrixM4f(); private void buildModelElementResource(Element element, BlockModelView blockModel) { //create faces Vector3f from = element.getFrom(); Vector3f to = element.getTo(); 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()); 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(element, Direction.DOWN, c[0], c[2], c[3], c[1]); createElementFace(element, Direction.UP, c[5], c[7], c[6], c[4]); createElementFace(element, Direction.NORTH, c[2], c[0], c[4], c[6]); createElementFace(element, Direction.SOUTH, c[1], c[3], c[7], c[5]); createElementFace(element, Direction.WEST, c[0], c[1], c[5], c[4]); createElementFace(element, Direction.EAST, c[3], c[2], c[6], c[7]); blockModel.initialize(modelStart); //rotate and scale down blockModel.transform(modelElementTransform .copy(element.getRotation().getMatrix()) .scale(BLOCK_SCALE, BLOCK_SCALE, BLOCK_SCALE) ); } private final VectorM3f faceRotationVector = new VectorM3f(0, 0, 0); private void createElementFace(Element element, Direction faceDir, VectorM3f c0, VectorM3f c1, VectorM3f c2, VectorM3f c3) { Face face = element.getFaces().get(faceDir); if (face == null) return; Vector3i faceDirVector = faceDir.toVector(); // light calculation ExtendedBlock facedBlockNeighbor = getRotationRelativeBlock(faceDir); LightData blockLightData = block.getLightData(); LightData facedLightData = facedBlockNeighbor.getLightData(); int sunLight = Math.max(blockLightData.getSkyLight(), facedLightData.getSkyLight()); int blockLight = Math.max(blockLightData.getBlockLight(), facedLightData.getBlockLight()); // filter out faces that are in a "cave" that should not be rendered if ( block.isRemoveIfCave() && (renderSettings.isCaveDetectionUsesBlockLight() ? Math.max(blockLight, sunLight) : sunLight) == 0 ) return; // face culling if (face.getCullface() != null) { ExtendedBlock b = getRotationRelativeBlock(face.getCullface()); BlockProperties p = b.getProperties(); if (p.isCulling()) return; if (p.getCullingIdentical() && b.getBlockState().equals(block.getBlockState())) return; } // 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 ); // ####### texture ResourcePath texturePath = face.getTexture().getTexturePath(modelResource.getTextures()::get); int textureId = textureGallery.get(texturePath); tileModel.setMaterialIndex(face1, textureId); tileModel.setMaterialIndex(face2, textureId); // ####### 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 (variant.isUvlock() && variant.isRotated()) { float xRotSin = TrigMath.sin(variant.getX() * TrigMath.DEG_TO_RAD); float xRotCos = TrigMath.cos(variant.getX() * TrigMath.DEG_TO_RAD); uvRotation = variant.getY() * (faceDirVector.getY() * xRotCos + faceDirVector.getZ() * xRotSin) + variant.getX() * (1 - faceDirVector.getY()); } // rotate uv's if (uvRotation != 0){ uvRotation = (float)(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.getTintindex() >= 0) { 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 (modelResource.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() ); faceRotationVector.rotateAndScale(element.getRotation().getMatrix()); makeRotationRelative(faceRotationVector); float a = faceRotationVector.y; if (a > 0.01 && texturePath != null) { Texture texture = texturePath.getResource(resourcePack::getTexture); if (texture != null) { mapColor.set(texture.getColorPremultiplied()); if (tintColor.a >= 0) { mapColor.multiply(tintColor); } // apply light float combinedLight = Math.max(sunLight / 15f, blockLight / 15f); combinedLight = (1 - renderSettings.getAmbientLight()) * combinedLight + renderSettings.getAmbientLight(); mapColor.r *= combinedLight; mapColor.g *= combinedLight; mapColor.b *= combinedLight; if (mapColor.a > blockColorOpacity) blockColorOpacity = mapColor.a; blockColor.add(mapColor); } } } private ExtendedBlock getRotationRelativeBlock(Direction direction){ return getRotationRelativeBlock(direction.toVector()); } private ExtendedBlock getRotationRelativeBlock(Vector3i direction){ return getRotationRelativeBlock( direction.getX(), direction.getY(), direction.getZ() ); } private final VectorM3f rotationRelativeBlockDirection = new VectorM3f(0, 0, 0); private ExtendedBlock getRotationRelativeBlock(int dx, int dy, int dz){ rotationRelativeBlockDirection.set(dx, dy, dz); makeRotationRelative(rotationRelativeBlockDirection); return block.getNeighborBlock( Math.round(rotationRelativeBlockDirection.x), Math.round(rotationRelativeBlockDirection.y), Math.round(rotationRelativeBlockDirection.z) ); } private void makeRotationRelative(VectorM3f direction){ if (variant.isRotated()) direction.transform(variant.getRotationMatrix()); } private float testAo(VectorM3f vertex, Direction dir){ Vector3i dirVec = dir.toVector(); int occluding = 0; int x = 0; if (vertex.x == 16){ x = 1; } else if (vertex.x == 0){ x = -1; } int y = 0; if (vertex.y == 16){ y = 1; } else if (vertex.y == 0){ y = -1; } int z = 0; if (vertex.z == 16){ z = 1; } else if (vertex.z == 0){ z = -1; } if (x * dirVec.getX() + y * dirVec.getY() > 0){ if (getRotationRelativeBlock(x, y, 0).getProperties().isOccluding()) occluding++; } if (x * dirVec.getX() + z * dirVec.getZ() > 0){ if (getRotationRelativeBlock(x, 0, z).getProperties().isOccluding()) occluding++; } if (y * dirVec.getY() + z * dirVec.getZ() > 0){ if (getRotationRelativeBlock(0, y, z).getProperties().isOccluding()) occluding++; } if (x * dirVec.getX() + y * dirVec.getY() + z * dirVec.getZ() > 0){ if (getRotationRelativeBlock(x, y, z).getProperties().isOccluding()) occluding++; } if (occluding > 3) occluding = 3; return Math.max(0f, Math.min(1f - occluding * 0.25f, 1f)); } private static float hashToFloat(int x, int z, long seed) { final long hash = x * 73428767L ^ z * 4382893L ^ seed * 457; return (hash * (hash + 456149) & 0x00ffffff) / (float) 0x01000000; } }