/* * Copyright 2015 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.rendering.primitives; import com.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.terasology.assets.ResourceUrn; import org.terasology.math.Side; import org.terasology.math.geom.Vector3i; import org.terasology.math.geom.Vector4f; import org.terasology.rendering.assets.mesh.Mesh; import org.terasology.world.ChunkView; import org.terasology.world.biomes.Biome; import org.terasology.world.block.Block; import org.terasology.world.block.BlockAppearance; import org.terasology.world.block.BlockPart; import org.terasology.world.block.shapes.BlockMeshPart; import java.util.Map; public class BlockMeshGeneratorSingleShape implements BlockMeshGenerator { private static final Logger logger = LoggerFactory.getLogger(BlockMeshGeneratorSingleShape.class); private Block block; private Mesh mesh; public BlockMeshGeneratorSingleShape(Block block) { this.block = block; } @Override public void generateChunkMesh(ChunkView view, ChunkMesh chunkMesh, int x, int y, int z) { Biome selfBiome = view.getBiome(x, y, z); Block selfBlock = view.getBlock(x, y, z); // TODO: Needs review - too much hardcoded special cases and corner cases resulting from this. ChunkVertexFlag vertexFlag = ChunkVertexFlag.NORMAL; if (selfBlock.isWater()) { if (view.getBlock(x, y + 1, z).isWater()) { vertexFlag = ChunkVertexFlag.WATER; } else { vertexFlag = ChunkVertexFlag.WATER_SURFACE; } } else if (selfBlock.isLava()) { vertexFlag = ChunkVertexFlag.LAVA; } else if (selfBlock.isWaving() && selfBlock.isDoubleSided()) { vertexFlag = ChunkVertexFlag.WAVING; } else if (selfBlock.isWaving()) { vertexFlag = ChunkVertexFlag.WAVING_BLOCK; } // Gather adjacent blocks Map<Side, Block> adjacentBlocks = Maps.newEnumMap(Side.class); for (Side side : Side.values()) { Vector3i offset = side.getVector3i(); Block blockToCheck = view.getBlock(x + offset.x, y + offset.y, z + offset.z); adjacentBlocks.put(side, blockToCheck); } BlockAppearance blockAppearance = selfBlock.getAppearance(adjacentBlocks); /* * Determine the render process. */ ChunkMesh.RenderType renderType = ChunkMesh.RenderType.TRANSLUCENT; if (!selfBlock.isTranslucent()) { renderType = ChunkMesh.RenderType.OPAQUE; } // TODO: Review special case, or alternatively compare uris. if (selfBlock.isWater() || selfBlock.isIce()) { renderType = ChunkMesh.RenderType.WATER_AND_ICE; } if (selfBlock.isDoubleSided()) { renderType = ChunkMesh.RenderType.BILLBOARD; } if (blockAppearance.getPart(BlockPart.CENTER) != null) { Vector4f colorOffset = selfBlock.calcColorOffsetFor(BlockPart.CENTER, selfBiome); blockAppearance.getPart(BlockPart.CENTER).appendTo(chunkMesh, x, y, z, colorOffset, renderType, vertexFlag); } boolean[] drawDir = new boolean[6]; for (Side side : Side.values()) { drawDir[side.ordinal()] = blockAppearance.getPart(BlockPart.fromSide(side)) != null && isSideVisibleForBlockTypes(adjacentBlocks.get(side), selfBlock, side); } // If the selfBlock is lowered, some more faces may have to be drawn if (selfBlock.isLiquid()) { Block bottomBlock = adjacentBlocks.get(Side.BOTTOM); // Draw horizontal sides if visible from below for (Side side : Side.horizontalSides()) { Vector3i offset = side.getVector3i(); Block adjacentBelow = view.getBlock(x + offset.x, y - 1, z + offset.z); Block adjacent = adjacentBlocks.get(side); boolean visible = (blockAppearance.getPart(BlockPart.fromSide(side)) != null && isSideVisibleForBlockTypes(adjacentBelow, selfBlock, side) && !isSideVisibleForBlockTypes(bottomBlock, adjacent, side.reverse())); drawDir[side.ordinal()] |= visible; } // Draw the top if below a non-lowered selfBlock // TODO: Don't need to render the top if each side and the selfBlock above each side are either liquid or opaque solids. Block blockToCheck = adjacentBlocks.get(Side.TOP); drawDir[Side.TOP.ordinal()] |= !blockToCheck.isLiquid(); if (bottomBlock.isLiquid() || bottomBlock.getMeshGenerator() == null) { for (Side dir : Side.values()) { if (drawDir[dir.ordinal()]) { Vector4f colorOffset = selfBlock.calcColorOffsetFor(BlockPart.fromSide(dir), selfBiome); selfBlock.getLoweredLiquidMesh(dir).appendTo(chunkMesh, x, y, z, colorOffset, renderType, vertexFlag); } } return; } } for (Side dir : Side.values()) { if (drawDir[dir.ordinal()]) { Vector4f colorOffset = selfBlock.calcColorOffsetFor(BlockPart.fromSide(dir), selfBiome); // TODO: Needs review since the new per-vertex flags introduce a lot of special scenarios - probably a per-side setting? if (selfBlock.isGrass() && dir != Side.TOP && dir != Side.BOTTOM) { blockAppearance.getPart(BlockPart.fromSide(dir)).appendTo(chunkMesh, x, y, z, colorOffset, renderType, ChunkVertexFlag.COLOR_MASK); } else { //if(dir == Side.TOP) logger.info("Generating: " + (new Vector3i(x, y, z)).toString() + " " + view.getChunkRegion().toString() + " " + dir.toString()); if (blockAppearance.getPart(BlockPart.fromSide(dir)) == null) { // TODO: This would catch something like water blocks attempting to render with a "fixed" trimmedLoweredCube shape // That shape has its top trimmed down a bit to let water sit slightly lower than land, however, underwater this shouldn't show // Normally we would configure that shape with CENTER instead of TOP, that way the trimmed part wouldn't occlude in a stack // But with that handling you don't get water blocks occluding tops underwater... and there's no TOP to retrieve below -> NPE logger.debug("Cannot render side '{}' for a block - no stored block appearance for it. renderType {}, vertexFlag {}", dir, renderType, vertexFlag); } else { blockAppearance.getPart(BlockPart.fromSide(dir)).appendTo(chunkMesh, x, y, z, colorOffset, renderType, vertexFlag); } } } } } /** * Returns true if the side should be rendered adjacent to the second side provided. * * @param blockToCheck The block to check * @param currentBlock The current block * @return True if the side is visible for the given block types */ private boolean isSideVisibleForBlockTypes(Block blockToCheck, Block currentBlock, Side side) { // Liquids can be transparent but there should be no visible adjacent faces if (currentBlock.isLiquid() && blockToCheck.isLiquid()) { return false; } return currentBlock.isWaving() != blockToCheck.isWaving() || blockToCheck.getMeshGenerator() == null || !blockToCheck.isFullSide(side.reverse()) || (!currentBlock.isTranslucent() && blockToCheck.isTranslucent()); } @Override public Mesh getStandaloneMesh() { if (mesh == null || mesh.isDisposed()) { generateMesh(); } return mesh; } private void generateMesh() { Tessellator tessellator = new Tessellator(); for (BlockPart dir : BlockPart.values()) { BlockMeshPart part = block.getPrimaryAppearance().getPart(dir); if (part != null) { if (block.isDoubleSided()) { tessellator.addMeshPartDoubleSided(part); } else { tessellator.addMeshPart(part); } } } mesh = tessellator.generateMesh(new ResourceUrn("engine", "blockmesh", block.getURI().toString())); } }