/*
* 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 com.bulletphysics.collision.shapes.IndexedMesh;
import gnu.trove.list.TByteList;
import gnu.trove.list.TFloatList;
import gnu.trove.list.TIntList;
import gnu.trove.list.TShortList;
import gnu.trove.list.array.TByteArrayList;
import gnu.trove.list.array.TFloatArrayList;
import gnu.trove.list.array.TIntArrayList;
import gnu.trove.list.array.TShortArrayList;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.terasology.logic.manager.VertexBufferObjectManager;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.concurrent.locks.ReentrantLock;
import static org.lwjgl.opengl.GL11.*;
/**
* Chunk meshes are used to store the vertex data of tessellated chunks.
*/
public class ChunkMesh {
/**
* Data structure for storing vertex data. Abused like a "struct" in C/C++. Just sad.
*/
public static class VertexElements {
public VertexElements() {
vertCount = 0;
normals = new TFloatArrayList();
vertices = new TFloatArrayList();
tex = new TFloatArrayList();
color = new TFloatArrayList();
indices = new TIntArrayList();
}
public final TFloatList normals;
public final TFloatList vertices;
public final TFloatList tex;
public final TFloatList color;
public final TIntList indices;
public int vertCount;
public ByteBuffer finalVertices;
public IntBuffer finalIndices;
}
/**
* Possible rendering types.
*/
public enum RENDER_TYPE {
OPAQUE(0),
TRANSLUCENT(1),
BILLBOARD(2),
WATER_AND_ICE(3);
private int _meshIndex;
private RENDER_TYPE(int index) {
_meshIndex = index;
}
public int getIndex() {
return _meshIndex;
}
}
public enum RENDER_PHASE {
OPAQUE,
BILLBOARD_AND_TRANSLUCENT,
WATER_AND_ICE;
}
/* CONST */
private static final int STRIDE = (3 + 3 + 3 + 3 + 4) * 4;
private static final int OFFSET_VERTEX = 0;
private static final int OFFSET_TEX_0 = (3 * 4);
private static final int OFFSET_TEX_1 = ((3 + 3) * 4);
private static final int OFFSET_COLOR = ((3 + 3 + 3) * 4);
private static final int OFFSET_NORMAL = ((3 + 3 + 3 + 4) * 4);
/* 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 */
public VertexElements[] _vertexElements = new VertexElements[4];
/* BULLET PHYSICS */
public IndexedMesh _indexedMesh;
private boolean _disposed = false;
/* CONCURRENCY */
public ReentrantLock _lock = new ReentrantLock();
public ChunkMesh() {
_vertexElements[0] = new VertexElements();
_vertexElements[1] = new VertexElements();
_vertexElements[2] = new VertexElements();
_vertexElements[3] = new VertexElements();
}
/**
* 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 (int i = 0; i < _vertexBuffers.length; i++)
generateVBO(i);
// 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(int id) {
if (_lock.tryLock()) {
try {
if (!_disposed && _vertexElements[id].finalIndices.limit() > 0 && _vertexElements[id].finalVertices.limit() > 0) {
_vertexBuffers[id] = VertexBufferObjectManager.getInstance().getVboId();
_idxBuffers[id] = VertexBufferObjectManager.getInstance().getVboId();
_vertexCount[id] = _vertexElements[id].finalIndices.limit();
VertexBufferObjectManager.getInstance().bufferVboElementData(_idxBuffers[id], _vertexElements[id].finalIndices, GL15.GL_STATIC_DRAW);
VertexBufferObjectManager.getInstance().bufferVboData(_vertexBuffers[id], _vertexElements[id].finalVertices, GL15.GL_STATIC_DRAW);
} else {
_vertexBuffers[id] = 0;
_idxBuffers[id] = 0;
_vertexCount[id] = 0;
}
} finally {
_lock.unlock();
}
}
}
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(3, GL11.GL_FLOAT, STRIDE, OFFSET_VERTEX);
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
glTexCoordPointer(3, GL11.GL_FLOAT, STRIDE, OFFSET_TEX_0);
GL13.glClientActiveTexture(GL13.GL_TEXTURE1);
glTexCoordPointer(3, GL11.GL_FLOAT, STRIDE, OFFSET_TEX_1);
glColorPointer(4, GL11.GL_FLOAT, STRIDE, OFFSET_COLOR);
glNormalPointer(GL11.GL_FLOAT, STRIDE, OFFSET_NORMAL);
GL11.glDrawElements(GL11.GL_TRIANGLES, _vertexCount[id], GL11.GL_UNSIGNED_INT, 0);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_VERTEX_ARRAY);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
} finally {
_lock.unlock();
}
}
}
public void render(RENDER_PHASE type) {
switch (type) {
case OPAQUE:
renderVbo(0);
break;
case BILLBOARD_AND_TRANSLUCENT:
renderVbo(1);
glDisable(GL_CULL_FACE);
renderVbo(2);
glEnable(GL_CULL_FACE);
break;
case WATER_AND_ICE:
renderVbo(3);
break;
}
}
public void dispose() {
_lock.lock();
try {
if (!_disposed) {
for (int i = 0; i < _vertexBuffers.length; i++) {
int id = _vertexBuffers[i];
VertexBufferObjectManager.getInstance().putVboId(id);
_vertexBuffers[i] = 0;
id = _idxBuffers[i];
VertexBufferObjectManager.getInstance().putVboId(id);
_idxBuffers[i] = 0;
}
_disposed = true;
_vertexElements = null;
_indexedMesh = null;
}
} finally {
_lock.unlock();
}
}
public boolean isGenerated() {
return _vertexElements == null;
}
public boolean isDisposed() {
return _disposed;
}
public int triangleCount(RENDER_PHASE phase) {
if (phase == RENDER_PHASE.OPAQUE)
return _vertexCount[0] / 3;
else if (phase == RENDER_PHASE.BILLBOARD_AND_TRANSLUCENT)
return (_vertexCount[1] + _vertexCount[2]) / 3;
else
return _vertexCount[3] / 3;
}
public int triangleCount() {
return _triangleCount;
}
public boolean isEmpty() {
return _triangleCount == 0;
}
}