/*
* 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.collect.Maps;
import gnu.trove.list.TFloatList;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TFloatArrayList;
import gnu.trove.list.array.TIntArrayList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.terasology.engine.subsystem.lwjgl.GLBufferPool;
import org.terasology.math.geom.Vector3f;
import org.terasology.rendering.VertexBufferObjectUtil;
import org.terasology.rendering.assets.material.Material;
import org.terasology.world.chunks.ChunkConstants;
import java.nio.IntBuffer;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import static org.lwjgl.opengl.GL11.GL_COLOR_ARRAY;
import static org.lwjgl.opengl.GL11.GL_CULL_FACE;
import static org.lwjgl.opengl.GL11.GL_NORMAL_ARRAY;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_COORD_ARRAY;
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
import static org.lwjgl.opengl.GL11.glColorPointer;
import static org.lwjgl.opengl.GL11.glDisable;
import static org.lwjgl.opengl.GL11.glDisableClientState;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnableClientState;
import static org.lwjgl.opengl.GL11.glNormalPointer;
import static org.lwjgl.opengl.GL11.glTexCoordPointer;
import static org.lwjgl.opengl.GL11.glVertexPointer;
/**
* Chunk meshes store, manipulate and render the vertex data of tessellated chunks.
*/
@SuppressWarnings("PointlessArithmeticExpression")
public class ChunkMesh {
/**
* Possible rendering types.
*/
public enum RenderType {
OPAQUE(0),
TRANSLUCENT(1),
BILLBOARD(2),
WATER_AND_ICE(3);
private int meshIndex;
RenderType(int index) {
meshIndex = index;
}
public int getIndex() {
return meshIndex;
}
}
public enum RenderPhase {
OPAQUE,
ALPHA_REJECT,
REFRACTIVE,
Z_PRE_PASS
}
// some constants
private static final int SIZE_VERTEX = 3; // vertices have 3 positional components, x,y,z
private static final int SIZE_TEX0 = 3; // the first texture has 3 color components, r,g,b
private static final int SIZE_TEX1 = 3; // the second texture is the same
private static final int SIZE_COLOR = 1; // the color field has 4 components, r,g,b,a
private static final int SIZE_NORMAL = 3; // normals are 3-dimensional vectors with u,v,t components
// offset to the beginning of each data field, from the start of the data regarding an individual vertex
private static final int OFFSET_VERTEX = 0;
private static final int OFFSET_TEX_0 = OFFSET_VERTEX + SIZE_VERTEX * 4;
private static final int OFFSET_TEX_1 = OFFSET_TEX_0 + SIZE_TEX0 * 4;
private static final int OFFSET_COLOR = OFFSET_TEX_1 + SIZE_TEX1 * 4;
private static final int OFFSET_NORMAL = OFFSET_COLOR + SIZE_COLOR * 4;
private static final int STRIDE = OFFSET_NORMAL + SIZE_NORMAL * 4;
// the STRIDE, above, is the gap between the beginnings of the data regarding two consecutive vertices
/* VERTEX DATA */
private final int[] vertexBuffers = new int[4];
private final int[] idxBuffers = new int[4];
private final int[] vertexCount = new int[4];
/* STATS */
private int triangleCount = -1;
/* TEMPORARY DATA */
private Map<RenderType, VertexElements> vertexElements = Maps.newEnumMap(RenderType.class);
private boolean disposed;
/* CONCURRENCY */
private ReentrantLock lock = new ReentrantLock();
/* MEASUREMENTS */
private int timeToGenerateBlockVertices;
private int timeToGenerateOptimizedBuffers;
private GLBufferPool bufferPool;
public ChunkMesh(GLBufferPool bufferPool) {
this.bufferPool = bufferPool;
for (RenderType type : RenderType.values()) {
vertexElements.put(type, new VertexElements());
}
}
public VertexElements getVertexElements(RenderType renderType) {
return vertexElements.get(renderType);
}
public boolean isGenerated() {
return vertexElements == null;
}
/**
* Generates the VBOs from the pre calculated arrays.
*
* @return True if something was generated
*/
public boolean generateVBOs() {
if (lock.tryLock()) {
try {
// IMPORTANT: A mesh can only be generated once.
if (vertexElements == null || disposed) {
return false;
}
for (RenderType type : RenderType.values()) {
generateVBO(type);
}
// Free unused space on the heap
vertexElements = null;
// Calculate the final amount of triangles
triangleCount = (vertexCount[0] + vertexCount[1] + vertexCount[2] + vertexCount[3]) / 3;
} finally {
lock.unlock();
}
return true;
}
return false;
}
private void generateVBO(RenderType type) {
VertexElements elements = vertexElements.get(type);
int id = type.getIndex();
if (!disposed && elements.finalIndices.limit() > 0 && elements.finalVertices.limit() > 0) {
vertexBuffers[id] = bufferPool.get("chunkMesh");
idxBuffers[id] = bufferPool.get("chunkMesh");
vertexCount[id] = elements.finalIndices.limit();
VertexBufferObjectUtil.bufferVboElementData(idxBuffers[id], elements.finalIndices, GL15.GL_STATIC_DRAW);
VertexBufferObjectUtil.bufferVboData(vertexBuffers[id], elements.finalVertices, GL15.GL_STATIC_DRAW);
} else {
vertexBuffers[id] = 0;
idxBuffers[id] = 0;
vertexCount[id] = 0;
}
}
private void renderVbo(int id) {
if (lock.tryLock()) {
try {
if (vertexBuffers[id] <= 0 || disposed) {
return;
}
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, idxBuffers[id]);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vertexBuffers[id]);
glVertexPointer(SIZE_VERTEX, GL11.GL_FLOAT, STRIDE, OFFSET_VERTEX);
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
glTexCoordPointer(SIZE_TEX0, GL11.GL_FLOAT, STRIDE, OFFSET_TEX_0);
GL13.glClientActiveTexture(GL13.GL_TEXTURE1);
glTexCoordPointer(SIZE_TEX1, GL11.GL_FLOAT, STRIDE, OFFSET_TEX_1);
glColorPointer(SIZE_COLOR * 4, GL11.GL_UNSIGNED_BYTE, STRIDE, OFFSET_COLOR);
glNormalPointer(GL11.GL_FLOAT, STRIDE, OFFSET_NORMAL);
GL11.glDrawElements(GL11.GL_TRIANGLES, vertexCount[id], GL11.GL_UNSIGNED_INT, 0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
} finally {
lock.unlock();
}
}
}
/**
* Updates a given material with information such as the World position of a chunk and whether it is animated.
*
* @param chunkMaterial a Material instance to be updated
* @param chunkPosition a Vector3f instance holding the world coordinates of a chunk
* @param chunkIsAnimated a boolean: true if the chunk is animated, false otherwise
*/
public void updateMaterial(Material chunkMaterial, Vector3f chunkPosition, boolean chunkIsAnimated) {
chunkMaterial.setFloat3("chunkPositionWorld",
chunkPosition.x * ChunkConstants.SIZE_X,
chunkPosition.y * ChunkConstants.SIZE_Y,
chunkPosition.z * ChunkConstants.SIZE_Z,
true);
chunkMaterial.setFloat("animated", chunkIsAnimated ? 1.0f : 0.0f, true);
}
/**
* Renders the phase-appropriate mesh of the chunk. I.e. if the RenderPhase is OPAQUE only the opaque mesh
* is rendered: other meshes stored in an instance of this class are rendered in separate rendering steps.
*
* @param phase a RenderPhase value
* @param chunkPosition a Vector3f storing the world position of the chunk.
* @param cameraPosition a Vector3f storing the world position of the point of view from which the chunk is rendered.
* @return Returns an integer representing the number of triangles rendered.
*/
public int render(ChunkMesh.RenderPhase phase, Vector3f chunkPosition, Vector3f cameraPosition) {
GL11.glPushMatrix();
// chunkPositionRelativeToCamera = chunkCoordinates * chunkDimensions - cameraCoordinate
final Vector3f chunkPositionRelativeToCamera =
new Vector3f(chunkPosition.x * ChunkConstants.SIZE_X - cameraPosition.x,
chunkPosition.y * ChunkConstants.SIZE_Y - cameraPosition.y,
chunkPosition.z * ChunkConstants.SIZE_Z - cameraPosition.z);
GL11.glTranslatef(chunkPositionRelativeToCamera.x, chunkPositionRelativeToCamera.y, chunkPositionRelativeToCamera.z);
render(phase); // this is where the chunk is actually rendered
GL11.glPopMatrix();
return triangleCount();
}
private void render(RenderPhase type) {
switch (type) {
case OPAQUE:
renderVbo(0);
break;
case ALPHA_REJECT:
renderVbo(1);
glDisable(GL_CULL_FACE);
renderVbo(2);
glEnable(GL_CULL_FACE);
break;
case REFRACTIVE:
renderVbo(3);
break;
default:
break;
}
}
/**
* Disposes of all the data stored in an instance of this class and
* the associated data stored in the GLBufferPool instance provided on construction.
*
* ChunkMesh instances cannot be un-disposed.
*/
public void dispose() {
lock.lock();
try {
if (!disposed) {
for (int i = 0; i < vertexBuffers.length; i++) {
int id = vertexBuffers[i];
if (id != 0) {
bufferPool.dispose(id);
vertexBuffers[i] = 0;
}
id = idxBuffers[i];
if (id != 0) {
bufferPool.dispose(id);
idxBuffers[i] = 0;
}
}
disposed = true;
vertexElements = null;
}
} finally {
lock.unlock();
}
}
public boolean isDisposed() {
return disposed;
}
public int triangleCount(RenderPhase phase) {
if (phase == RenderPhase.OPAQUE) {
return vertexCount[0] / 3;
} else if (phase == RenderPhase.ALPHA_REJECT) {
return (vertexCount[1] + vertexCount[2]) / 3;
} else {
return vertexCount[3] / 3;
}
}
private int triangleCount() {
return triangleCount;
}
/**
* Returns true if an instance of this class stores no triangles.
*
* @return True if no triangles are stored in the instance, false otherwise.
*/
public boolean isEmpty() {
return triangleCount == 0;
}
void setTimeToGenerateBlockVertices(int timeToGenerateBlockVertices) {
this.timeToGenerateBlockVertices = timeToGenerateBlockVertices;
}
public int getTimeToGenerateBlockVertices() {
return timeToGenerateBlockVertices;
}
void setTimeToGenerateOptimizedBuffers(int timeToGenerateOptimizedBuffers) {
this.timeToGenerateOptimizedBuffers = timeToGenerateOptimizedBuffers;
}
public int getTimeToGenerateOptimizedBuffers() {
return timeToGenerateOptimizedBuffers;
}
/**
* Data structure for storing vertex data. Abused like a "struct" in C/C++. Just sad.
*/
public static class VertexElements {
public final TFloatList normals;
public final TFloatList vertices;
public final TFloatList tex;
public final TFloatList color;
public final TIntList indices;
public final TIntList flags;
public int vertexCount;
public IntBuffer finalVertices;
public IntBuffer finalIndices;
VertexElements() {
vertexCount = 0;
normals = new TFloatArrayList();
vertices = new TFloatArrayList();
tex = new TFloatArrayList();
color = new TFloatArrayList();
indices = new TIntArrayList();
flags = new TIntArrayList();
}
}
}