/*
* 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.opengl;
import com.bulletphysics.linearmath.Transform;
import com.google.common.collect.Lists;
import gnu.trove.iterator.TFloatIterator;
import gnu.trove.iterator.TIntIterator;
import gnu.trove.list.TFloatList;
import gnu.trove.list.TIntList;
import gnu.trove.list.array.TIntArrayList;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.assets.AssetType;
import org.terasology.assets.ResourceUrn;
import org.terasology.engine.GameThread;
import org.terasology.engine.subsystem.lwjgl.GLBufferPool;
import org.terasology.math.AABB;
import org.terasology.rendering.VertexBufferObjectUtil;
import org.terasology.rendering.assets.mesh.Mesh;
import org.terasology.rendering.assets.mesh.MeshData;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;
import static org.lwjgl.opengl.GL11.GL_COLOR_ARRAY;
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_UNSIGNED_INT;
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
import static org.lwjgl.opengl.GL11.glColorPointer;
import static org.lwjgl.opengl.GL11.glDisableClientState;
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;
/**
*/
public class OpenGLMesh extends Mesh {
private static final Logger logger = LoggerFactory.getLogger(OpenGLMesh.class);
private static final int FLOAT_SIZE = 4;
private AABB aabb;
private MeshData data;
private int stride;
private int vertexOffset;
private int texCoord0Offset;
private int texCoord1Offset;
private int colorOffset;
private int normalOffset;
private boolean hasTexCoord0;
private boolean hasTexCoord1;
private boolean hasColor;
private boolean hasNormal;
private int indexCount;
private DisposalAction disposalAction;
public OpenGLMesh(ResourceUrn urn, AssetType<?, MeshData> assetType, GLBufferPool bufferPool, MeshData data) {
super(urn, assetType);
this.disposalAction = new DisposalAction(urn, bufferPool);
reload(data);
}
@Override
protected void doReload(MeshData newData) {
try {
GameThread.synch(() -> buildMesh(newData));
} catch (InterruptedException e) {
logger.error("Failed to reload {}", getUrn(), e);
}
}
@Override
public AABB getAABB() {
return aabb;
}
@Override
public TFloatList getVertices() {
return data.getVertices();
}
public void preRender() {
if (!isDisposed()) {
glEnableClientState(GL_VERTEX_ARRAY);
if (hasTexCoord0 || hasTexCoord1) {
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (hasColor) {
glEnableClientState(GL_COLOR_ARRAY);
}
if (hasNormal) {
glEnableClientState(GL_NORMAL_ARRAY);
}
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, disposalAction.vboVertexBuffer);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, disposalAction.vboIndexBuffer);
glVertexPointer(VERTEX_SIZE, GL11.GL_FLOAT, stride, vertexOffset);
if (hasTexCoord0) {
GL13.glClientActiveTexture(GL13.GL_TEXTURE0);
glTexCoordPointer(TEX_COORD_0_SIZE, GL11.GL_FLOAT, stride, texCoord0Offset);
}
if (hasTexCoord1) {
GL13.glClientActiveTexture(GL13.GL_TEXTURE1);
glTexCoordPointer(TEX_COORD_1_SIZE, GL11.GL_FLOAT, stride, texCoord1Offset);
}
if (hasColor) {
glColorPointer(COLOR_SIZE, GL11.GL_FLOAT, stride, colorOffset);
}
if (hasNormal) {
glNormalPointer(GL11.GL_FLOAT, stride, normalOffset);
}
} else {
logger.error("Attempted to render disposed mesh: {}", getUrn());
}
}
public void postRender() {
if (!isDisposed()) {
if (hasNormal) {
glDisableClientState(GL_NORMAL_ARRAY);
}
if (hasColor) {
glDisableClientState(GL_COLOR_ARRAY);
}
if (hasTexCoord0 || hasTexCoord1) {
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
glDisableClientState(GL_VERTEX_ARRAY);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
} else {
logger.error("Attempted to render disposed mesh: {}", getUrn());
}
}
public void doRender() {
if (!isDisposed()) {
GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, 0);
} else {
logger.error("Attempted to render disposed mesh: {}", getUrn());
}
}
@Override
public void render() {
if (!isDisposed()) {
preRender();
doRender();
postRender();
} else {
logger.error("Attempted to render disposed mesh: {}", getUrn());
}
}
public int addToBatch(Transform transform, Transform normalTransform, TFloatList vertexData, TIntList indexData, int indexOffset) {
int uv1 = 0;
int uv2 = 0;
int n = 0;
int c = 0;
for (int v = 0; v < data.getVertices().size(); v += VERTEX_SIZE) {
javax.vecmath.Vector3f vert = new javax.vecmath.Vector3f(data.getVertices().get(v), data.getVertices().get(v + 1), data.getVertices().get(v + 2));
transform.transform(vert);
vertexData.add(vert.x);
vertexData.add(vert.y);
vertexData.add(vert.z);
for (int i = 0; i < TEX_COORD_0_SIZE; ++i) {
vertexData.add(data.getTexCoord0().get(uv1 + i));
}
for (int i = 0; i < TEX_COORD_1_SIZE; ++i) {
vertexData.add(data.getTexCoord1().get(uv2 + i));
}
javax.vecmath.Vector3f norm = new javax.vecmath.Vector3f(data.getNormals().get(n), data.getNormals().get(n + 1), data.getNormals().get(n + 2));
normalTransform.transform(norm);
vertexData.add(norm.x);
vertexData.add(norm.y);
vertexData.add(norm.z);
for (int i = 0; i < COLOR_SIZE; ++i) {
vertexData.add(data.getColors().get(c + i));
}
uv1 += TEX_COORD_0_SIZE;
uv2 += TEX_COORD_1_SIZE;
n += NORMAL_SIZE;
c += COLOR_SIZE;
}
TIntIterator indexIterator = data.getIndices().iterator();
while (indexIterator.hasNext()) {
indexData.add(indexIterator.next() + indexOffset);
}
return indexOffset + data.getVertices().size() / VERTEX_SIZE;
}
private void buildMesh(MeshData newData) {
this.data = newData;
List<TFloatIterator> parts = Lists.newArrayList();
TIntList partSizes = new TIntArrayList();
int vertexCount = newData.getVertices().size() / VERTEX_SIZE;
int vertexSize = VERTEX_SIZE;
parts.add(newData.getVertices().iterator());
partSizes.add(VERTEX_SIZE);
if (newData.getTexCoord0() != null && newData.getTexCoord0().size() / TEX_COORD_0_SIZE == vertexCount) {
parts.add(newData.getTexCoord0().iterator());
partSizes.add(TEX_COORD_0_SIZE);
texCoord0Offset = vertexSize * FLOAT_SIZE;
vertexSize += TEX_COORD_0_SIZE;
hasTexCoord0 = true;
}
if (newData.getTexCoord1() != null && newData.getTexCoord1().size() / TEX_COORD_1_SIZE == vertexCount) {
parts.add(newData.getTexCoord1().iterator());
partSizes.add(TEX_COORD_1_SIZE);
texCoord1Offset = vertexSize * FLOAT_SIZE;
vertexSize += TEX_COORD_1_SIZE;
hasTexCoord1 = true;
}
if (newData.getNormals() != null && newData.getNormals().size() / NORMAL_SIZE == vertexCount) {
parts.add(newData.getNormals().iterator());
partSizes.add(NORMAL_SIZE);
normalOffset = vertexSize * FLOAT_SIZE;
vertexSize += NORMAL_SIZE;
hasNormal = true;
}
if (newData.getColors() != null && newData.getColors().size() / COLOR_SIZE == vertexCount) {
parts.add(newData.getColors().iterator());
partSizes.add(COLOR_SIZE);
colorOffset = vertexSize * FLOAT_SIZE;
vertexSize += COLOR_SIZE;
hasColor = true;
}
stride = vertexSize * FLOAT_SIZE;
indexCount = newData.getIndices().size();
createVertexBuffer(parts, partSizes, vertexCount, vertexSize);
createIndexBuffer(newData.getIndices());
aabb = AABB.createEncompasing(newData.getVertices());
}
private void createVertexBuffer(List<TFloatIterator> parts, TIntList partSizes, int vertexCount, int vertexSize) {
FloatBuffer vertexBuffer = BufferUtils.createFloatBuffer(vertexSize * vertexCount);
for (int v = 0; v < vertexCount; ++v) {
for (int partIndex = 0; partIndex < parts.size(); ++partIndex) {
TFloatIterator part = parts.get(partIndex);
for (int i = 0; i < partSizes.get(partIndex); ++i) {
vertexBuffer.put(part.next());
}
}
}
vertexBuffer.flip();
if (disposalAction.vboVertexBuffer == 0) {
disposalAction.vboVertexBuffer = disposalAction.bufferPool.get(getUrn().toString());
}
VertexBufferObjectUtil.bufferVboData(disposalAction.vboVertexBuffer, vertexBuffer, GL15.GL_STATIC_DRAW);
vertexBuffer.flip();
}
private void createIndexBuffer(TIntList indexList) {
IntBuffer indexBuffer = BufferUtils.createIntBuffer(indexList.size());
TIntIterator iterator = indexList.iterator();
while (iterator.hasNext()) {
indexBuffer.put(iterator.next());
}
indexBuffer.flip();
if (disposalAction.vboIndexBuffer == 0) {
disposalAction.vboIndexBuffer = disposalAction.bufferPool.get(getUrn().toString());
}
VertexBufferObjectUtil.bufferVboElementData(disposalAction.vboIndexBuffer, indexBuffer, GL15.GL_STATIC_DRAW);
indexBuffer.flip();
}
private static class DisposalAction implements Runnable {
private final ResourceUrn urn;
private final GLBufferPool bufferPool;
private int vboVertexBuffer;
private int vboIndexBuffer;
DisposalAction(ResourceUrn urn, GLBufferPool bufferPool) {
this.urn = urn;
this.bufferPool = bufferPool;
}
@Override
public void run() {
try {
GameThread.synch(() -> {
if (vboVertexBuffer != 0) {
bufferPool.dispose(vboVertexBuffer);
vboVertexBuffer = 0;
}
if (vboIndexBuffer != 0) {
bufferPool.dispose(vboIndexBuffer);
vboIndexBuffer = 0;
}
});
} catch (InterruptedException e) {
logger.error("Failed to dispose {}", urn, e);
}
}
}
}