/* * Copyright 2013 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.base.Stopwatch; import gnu.trove.iterator.TIntIterator; import org.lwjgl.BufferUtils; import org.terasology.engine.subsystem.lwjgl.GLBufferPool; import org.terasology.math.Direction; import org.terasology.math.TeraMath; import org.terasology.math.geom.Vector3f; import org.terasology.monitoring.PerformanceMonitor; import org.terasology.rendering.RenderMath; import org.terasology.world.ChunkView; import org.terasology.world.block.Block; import org.terasology.world.chunks.ChunkConstants; import java.util.concurrent.TimeUnit; /** * Generates tessellated chunk meshes from chunks. * */ public final class ChunkTessellator { private static int statVertexArrayUpdateCount; private GLBufferPool bufferPool; public ChunkTessellator(GLBufferPool bufferPool) { this.bufferPool = bufferPool; } public ChunkMesh generateMesh(ChunkView chunkView, int meshHeight, int verticalOffset) { PerformanceMonitor.startActivity("GenerateMesh"); ChunkMesh mesh = new ChunkMesh(bufferPool); final Stopwatch watch = Stopwatch.createStarted(); for (int x = 0; x < ChunkConstants.SIZE_X; x++) { for (int z = 0; z < ChunkConstants.SIZE_Z; z++) { for (int y = verticalOffset; y < verticalOffset + meshHeight; y++) { Block block = chunkView.getBlock(x, y, z); if (block != null && block.getMeshGenerator() != null) { block.getMeshGenerator().generateChunkMesh(chunkView, mesh, x, y, z); } } } } watch.stop(); mesh.setTimeToGenerateBlockVertices((int) watch.elapsed(TimeUnit.MILLISECONDS)); watch.reset().start(); generateOptimizedBuffers(chunkView, mesh); watch.stop(); mesh.setTimeToGenerateOptimizedBuffers((int) watch.elapsed(TimeUnit.MILLISECONDS)); statVertexArrayUpdateCount++; PerformanceMonitor.endActivity(); return mesh; } private void generateOptimizedBuffers(ChunkView chunkView, ChunkMesh mesh) { PerformanceMonitor.startActivity("OptimizeBuffers"); for (ChunkMesh.RenderType type : ChunkMesh.RenderType.values()) { ChunkMesh.VertexElements elements = mesh.getVertexElements(type); // Vertices double to account for light info elements.finalVertices = BufferUtils.createIntBuffer( elements.vertices.size() + /* POSITION */ elements.tex.size() + /* TEX0 (UV0 and flags) */ elements.tex.size() + /* TEX1 (lighting data) */ elements.flags.size() + /* FLAGS */ elements.color.size() + /* COLOR */ elements.normals.size() /* NORMALS */ ); int cTex = 0; int cColor = 0; int cFlags = 0; for (int i = 0; i < elements.vertices.size(); i += 3, cTex += 2, cColor += 4, cFlags++) { Vector3f vertexPos = new Vector3f( elements.vertices.get(i), elements.vertices.get(i + 1), elements.vertices.get(i + 2)); /* POSITION */ elements.finalVertices.put(Float.floatToIntBits(vertexPos.x)); elements.finalVertices.put(Float.floatToIntBits(vertexPos.y)); elements.finalVertices.put(Float.floatToIntBits(vertexPos.z)); /* UV0 - TEX DATA 0 */ elements.finalVertices.put(Float.floatToIntBits(elements.tex.get(cTex))); elements.finalVertices.put(Float.floatToIntBits(elements.tex.get(cTex + 1))); /* FLAGS */ elements.finalVertices.put(Float.floatToIntBits(elements.flags.get(cFlags))); float[] result = new float[3]; Vector3f normal = new Vector3f(elements.normals.get(i), elements.normals.get(i + 1), elements.normals.get(i + 2)); calcLightingValuesForVertexPos(chunkView, vertexPos, result, normal); /* LIGHTING DATA / TEX DATA 1 */ elements.finalVertices.put(Float.floatToIntBits(result[0])); elements.finalVertices.put(Float.floatToIntBits(result[1])); elements.finalVertices.put(Float.floatToIntBits(result[2])); /* PACKED COLOR */ final int packedColor = RenderMath.packColor( elements.color.get(cColor), elements.color.get(cColor + 1), elements.color.get(cColor + 2), elements.color.get(cColor + 3)); elements.finalVertices.put(packedColor); /* NORMALS */ elements.finalVertices.put(Float.floatToIntBits(normal.x)); elements.finalVertices.put(Float.floatToIntBits(normal.y)); elements.finalVertices.put(Float.floatToIntBits(normal.z)); } elements.finalIndices = BufferUtils.createIntBuffer(elements.indices.size()); TIntIterator indexIterator = elements.indices.iterator(); while (indexIterator.hasNext()) { elements.finalIndices.put(indexIterator.next()); } elements.finalVertices.flip(); elements.finalIndices.flip(); } PerformanceMonitor.endActivity(); } private void calcLightingValuesForVertexPos(ChunkView chunkView, Vector3f vertexPos, float[] output, Vector3f normal) { PerformanceMonitor.startActivity("calcLighting"); float[] lights = new float[8]; float[] blockLights = new float[8]; Block[] blocks = new Block[4]; PerformanceMonitor.startActivity("gatherLightInfo"); Direction dir = Direction.inDirection(normal); switch (dir) { case LEFT: case RIGHT: blocks[0] = chunkView.getBlock((vertexPos.x + 0.8f * normal.x), (vertexPos.y + 0.1f), (vertexPos.z + 0.1f)); blocks[1] = chunkView.getBlock((vertexPos.x + 0.8f * normal.x), (vertexPos.y + 0.1f), (vertexPos.z - 0.1f)); blocks[2] = chunkView.getBlock((vertexPos.x + 0.8f * normal.x), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); blocks[3] = chunkView.getBlock((vertexPos.x + 0.8f * normal.x), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); break; case FORWARD: case BACKWARD: blocks[0] = chunkView.getBlock((vertexPos.x + 0.1f), (vertexPos.y + 0.1f), (vertexPos.z + 0.8f * normal.z)); blocks[1] = chunkView.getBlock((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.8f * normal.z)); blocks[2] = chunkView.getBlock((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.8f * normal.z)); blocks[3] = chunkView.getBlock((vertexPos.x - 0.1f), (vertexPos.y + 0.1f), (vertexPos.z + 0.8f * normal.z)); break; default: blocks[0] = chunkView.getBlock((vertexPos.x + 0.1f), (vertexPos.y + 0.8f * normal.y), (vertexPos.z + 0.1f)); blocks[1] = chunkView.getBlock((vertexPos.x + 0.1f), (vertexPos.y + 0.8f * normal.y), (vertexPos.z - 0.1f)); blocks[2] = chunkView.getBlock((vertexPos.x - 0.1f), (vertexPos.y + 0.8f * normal.y), (vertexPos.z - 0.1f)); blocks[3] = chunkView.getBlock((vertexPos.x - 0.1f), (vertexPos.y + 0.8f * normal.y), (vertexPos.z + 0.1f)); } lights[0] = chunkView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); lights[1] = chunkView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); lights[2] = chunkView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); lights[3] = chunkView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); lights[4] = chunkView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); lights[5] = chunkView.getSunlight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); lights[6] = chunkView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); lights[7] = chunkView.getSunlight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); blockLights[0] = chunkView.getLight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); blockLights[1] = chunkView.getLight((vertexPos.x + 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); blockLights[2] = chunkView.getLight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z - 0.1f)); blockLights[3] = chunkView.getLight((vertexPos.x - 0.1f), (vertexPos.y + 0.8f), (vertexPos.z + 0.1f)); blockLights[4] = chunkView.getLight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z + 0.1f)); blockLights[5] = chunkView.getLight((vertexPos.x + 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); blockLights[6] = chunkView.getLight((vertexPos.x - 0.1f), (vertexPos.y - 0.1f), (vertexPos.z - 0.1f)); blockLights[7] = chunkView.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) { Block b = blocks[i]; if (b.isShadowCasting() && !b.isTranslucent()) { occCounter++; } else if (b.isShadowCasting()) { occCounterBillboard++; } } } double resultAmbientOcclusion = (TeraMath.pow(0.40, occCounter) + TeraMath.pow(0.80, 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(); } public static int getVertexArrayUpdateCount() { return statVertexArrayUpdateCount; } }