/* * Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com> * * 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 gnu.trove.iterator.TFloatIterator; import gnu.trove.iterator.TIntIterator; import javax.vecmath.Vector3f; import javax.vecmath.Vector4f; import org.lwjgl.BufferUtils; import org.spout.api.geo.cuboid.Chunk; import org.spout.engine.world.SpoutWorld; import org.terasology.logic.world.WorldView; import org.terasology.math.Region3i; import org.terasology.math.Side; import org.terasology.math.Vector3i; import org.terasology.performanceMonitor.PerformanceMonitor; import org.terasology.teraspout.TeraBlock; import com.bulletphysics.collision.shapes.IndexedMesh; import com.bulletphysics.collision.shapes.ScalarType; /** * Generates tessellated chunk meshes from chunks. * * @author Benjamin Glatzel <benjamin.glatzel@me.com> */ public final class ChunkTessellator { private static final int FLOAT_BYTES = 4; private static final int INT_BYTES = 4; private static int _statVertexArrayUpdateCount = 0; private SpoutWorld biomeProvider; public ChunkTessellator(SpoutWorld biomeProvider) { this.biomeProvider = biomeProvider; } public ChunkMesh generateMesh(WorldView worldView, Vector3i chunkPos, int meshHeight, int verticalOffset) { PerformanceMonitor.startActivity("GenerateMesh"); ChunkMesh mesh = new ChunkMesh(); Vector3i chunkOffset = new Vector3i(chunkPos.x * Chunk.BLOCKS.SIZE, chunkPos.y * Chunk.BLOCKS.SIZE, chunkPos.z * Chunk.BLOCKS.SIZE); for (int x = 0; x < Chunk.BLOCKS.SIZE; x++) { for (int z = 0; z < Chunk.BLOCKS.SIZE; z++) { // float biomeTemp = biomeProvider.getTemperatureAt(chunkOffset.x + x, chunkOffset.z + z); // float biomeHumidity = biomeProvider.getHumidityAt(chunkOffset.x + x, chunkOffset.z + z); for (int y = verticalOffset; y < verticalOffset + meshHeight; y++) { TeraBlock block = worldView.getBlock(x, y, z); if (block == null || block.isInvisible()) continue; generateBlockVertices(worldView, mesh, x, y, z); } } } generateOptimizedBuffers(worldView, mesh); _statVertexArrayUpdateCount++; PerformanceMonitor.endActivity(); return mesh; } private void generateOptimizedBuffers(WorldView worldView, ChunkMesh mesh) { PerformanceMonitor.startActivity("OptimizeBuffers"); generateBulletBuffers(mesh); for (int j = 0; j < mesh._vertexElements.length; j++) { // Vertices double to account for light info mesh._vertexElements[j].finalVertices = BufferUtils.createByteBuffer(mesh._vertexElements[j].vertices.size() * 2 * 4 + mesh._vertexElements[j].tex.size() * 4 + mesh._vertexElements[j].color.size() * 4 + mesh._vertexElements[j].normals.size() * 4); int cTex = 0; int cColor = 0; for (int i = 0; i < mesh._vertexElements[j].vertices.size(); i += 3, cTex += 3, cColor += 4) { Vector3f vertexPos = new Vector3f(mesh._vertexElements[j].vertices.get(i), mesh._vertexElements[j].vertices.get(i + 1), mesh._vertexElements[j].vertices.get(i + 2)); mesh._vertexElements[j].finalVertices.putFloat(vertexPos.x); mesh._vertexElements[j].finalVertices.putFloat(vertexPos.y); mesh._vertexElements[j].finalVertices.putFloat(vertexPos.z); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].tex.get(cTex)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].tex.get(cTex + 1)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].tex.get(cTex + 2)); float[] result = new float[3]; calcLightingValuesForVertexPos(worldView, vertexPos, result); mesh._vertexElements[j].finalVertices.putFloat(result[0]); mesh._vertexElements[j].finalVertices.putFloat(result[1]); mesh._vertexElements[j].finalVertices.putFloat(result[2]); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].color.get(cColor)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].color.get(cColor + 1)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].color.get(cColor + 2)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].color.get(cColor + 3)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].normals.get(i)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].normals.get(i + 1)); mesh._vertexElements[j].finalVertices.putFloat(mesh._vertexElements[j].normals.get(i + 2)); } mesh._vertexElements[j].finalIndices = BufferUtils.createIntBuffer(mesh._vertexElements[j].indices.size()); TIntIterator indexIterator = mesh._vertexElements[j].indices.iterator(); while (indexIterator.hasNext()) { mesh._vertexElements[j].finalIndices.put(indexIterator.next()); } mesh._vertexElements[j].finalVertices.flip(); mesh._vertexElements[j].finalIndices.flip(); } PerformanceMonitor.endActivity(); } private void generateBulletBuffers(ChunkMesh mesh) { mesh._indexedMesh = new IndexedMesh(); mesh._indexedMesh.vertexBase = BufferUtils.createByteBuffer(mesh._vertexElements[0].vertices.size() * FLOAT_BYTES); mesh._indexedMesh.triangleIndexBase = BufferUtils.createByteBuffer(mesh._vertexElements[0].indices.size() * INT_BYTES); mesh._indexedMesh.triangleIndexStride = 3 * INT_BYTES; mesh._indexedMesh.vertexStride = 3 * FLOAT_BYTES; mesh._indexedMesh.numVertices = mesh._vertexElements[0].vertices.size() / 3; mesh._indexedMesh.numTriangles = mesh._vertexElements[0].indices.size() / 3; mesh._indexedMesh.indexType = ScalarType.INTEGER; TIntIterator indexIterator = mesh._vertexElements[0].indices.iterator(); while (indexIterator.hasNext()) { mesh._indexedMesh.triangleIndexBase.putInt(indexIterator.next()); } TFloatIterator vertIterator = mesh._vertexElements[0].vertices.iterator(); while (vertIterator.hasNext()) { mesh._indexedMesh.vertexBase.putFloat(vertIterator.next()); } } private void calcLightingValuesForVertexPos(WorldView worldView, Vector3f vertexPos, float[] output) { PerformanceMonitor.startActivity("calcLighting"); float[] lights = new float[8]; float[] blockLights = new float[8]; TeraBlock[] blocks = new TeraBlock[4]; PerformanceMonitor.startActivity("gatherLightInfo"); blocks[0] = worldView.getBlock((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); blocks[1] = worldView.getBlock((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); blocks[2] = worldView.getBlock((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); blocks[3] = worldView.getBlock((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); lights[0] = worldView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); lights[1] = worldView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); lights[2] = worldView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); lights[3] = worldView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); lights[4] = worldView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); lights[5] = worldView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); lights[6] = worldView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); lights[7] = worldView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); blockLights[0] = worldView.getLight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); blockLights[1] = worldView.getLight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); blockLights[2] = worldView.getLight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); blockLights[3] = worldView.getLight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); blockLights[4] = worldView.getLight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); blockLights[5] = worldView.getLight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); blockLights[6] = worldView.getLight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); blockLights[7] = worldView.getLight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); PerformanceMonitor.endActivity(); float resultLight = 0; float resultBlockLight = 0; int counterLight = 0; int counterBlockLight = 0; int occCounter = 0; int occCounterBillboard = 0; for (int i = 0; i < 8; i++) { if (lights[i] > 0) { resultLight += lights[i]; counterLight++; } if (blockLights[i] > 0) { resultBlockLight += blockLights[i]; counterBlockLight++; } if (i < 4) { TeraBlock b = blocks[i]; if (b.isCastsShadows() && b.getBlockForm() != TeraBlock.BLOCK_FORM.BILLBOARD) { occCounter++; } else if (b.isCastsShadows() && b.getBlockForm() == TeraBlock.BLOCK_FORM.BILLBOARD) { occCounterBillboard++; } } } double resultAmbientOcclusion = (Math.pow(0.60, occCounter) + Math.pow(0.86, occCounterBillboard)) / 2.0; if (counterLight == 0) output[0] = 0; else output[0] = resultLight / counterLight / 15f; if (counterBlockLight == 0) output[1] = 0; else output[1] = resultBlockLight / counterBlockLight / 15f; output[2] = (float) resultAmbientOcclusion; PerformanceMonitor.endActivity(); } private void generateBlockVertices(WorldView view, ChunkMesh mesh, int x, int y, int z) { TeraBlock block = view.getBlock(x, y, z); /* * Determine the render process. */ ChunkMesh.RENDER_TYPE renderType = ChunkMesh.RENDER_TYPE.TRANSLUCENT; if (!block.isTransparent()) renderType = ChunkMesh.RENDER_TYPE.OPAQUE; if (block.getTitle().equals("Water") || block.getTitle().equals("Ice")) renderType = ChunkMesh.RENDER_TYPE.WATER_AND_ICE; if (block.getBlockForm() == TeraBlock.BLOCK_FORM.BILLBOARD) renderType = ChunkMesh.RENDER_TYPE.BILLBOARD; TeraBlock.BLOCK_FORM blockForm = block.getBlockForm(); if (block.getCenterMesh() != null) { Vector4f colorOffset = block.calcColorOffsetFor(Side.TOP); block.getCenterMesh().appendTo(mesh, x, y, z, colorOffset, renderType.getIndex()); } boolean[] drawDir = new boolean[6]; for (Side side : Side.values()) { Vector3i offset = side.getVector3i(); TeraBlock blockToCheck = view.getBlock(x + offset.x, y + offset.y, z + offset.z); drawDir[side.ordinal()] = isSideVisibleForBlockTypes(blockToCheck, block, side); } if (y == 0) { drawDir[Side.BOTTOM.ordinal()] = false; } // If the block is lowered, some more faces may have to be drawn if (blockForm == TeraBlock.BLOCK_FORM.LOWERED_BLOCK) { // Draw horizontal sides if visible from below for (Side side : Side.horizontalSides()) { Vector3i offset = side.getVector3i(); TeraBlock blockToCheck = view.getBlock(x + offset.x, y - 1, z + offset.z); drawDir[side.ordinal()] |= isSideVisibleForBlockTypes(blockToCheck, block, side); } // Draw the top if below a non-lowered block // TODO: Don't need to render the top if each side and the block above each side are either liquid or opaque solids. TeraBlock blockToCheck = view.getBlock(x, y + 1, z); drawDir[Side.TOP.ordinal()] |= blockToCheck.getBlockForm() != TeraBlock.BLOCK_FORM.LOWERED_BLOCK; TeraBlock bottomBlock = view.getBlock(x, y - 1, z); if (bottomBlock.getBlockForm() == TeraBlock.BLOCK_FORM.LOWERED_BLOCK || bottomBlock.getId() == 0x0) { for (Side dir : Side.values()) { if (drawDir[dir.ordinal()]) { Vector4f colorOffset = block.calcColorOffsetFor(dir); block.getLoweredSideMesh(dir).appendTo(mesh, x, y, z, colorOffset, renderType.getIndex()); } } return; } } for (Side dir : Side.values()) { if (drawDir[dir.ordinal()]) { Vector4f colorOffset = block.calcColorOffsetFor(dir); block.getSideMesh(dir).appendTo(mesh, x, y, z, colorOffset, renderType.getIndex()); } } } /** * 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(TeraBlock blockToCheck, TeraBlock currentBlock, Side side) { if (currentBlock.getSideMesh(side) == null) return false; // Liquids can be transparent but there should be no visible adjacent faces // !!! In comparison to leaves !!! if (currentBlock.isLiquid() && blockToCheck.isLiquid()) return false; return blockToCheck.getId() == 0x0 || !blockToCheck.isBlockingSide(side.reverse()) || (!currentBlock.isTranslucent() && blockToCheck.isTranslucent()); } public static int getVertexArrayUpdateCount() { return _statVertexArrayUpdateCount; } }