/* * 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 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.math.geom.Quat4f; import org.terasology.math.geom.Vector2f; import org.terasology.math.geom.Vector3f; import org.terasology.rendering.VertexBufferObjectUtil; import org.terasology.rendering.assets.skeletalmesh.Bone; import org.terasology.rendering.assets.skeletalmesh.SkeletalMesh; import org.terasology.rendering.assets.skeletalmesh.SkeletalMeshData; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.Collection; import java.util.List; import static org.lwjgl.opengl.GL11.GL_FLOAT; 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.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 OpenGLSkeletalMesh extends SkeletalMesh { private static final int TEX_COORD_SIZE = 2; private static final int VECTOR3_SIZE = 3; private static final int STRIDE = 24; private static final int NORMAL_OFFSET = VECTOR3_SIZE * 4; private static final Logger logger = LoggerFactory.getLogger(OpenGLSkeletalMesh.class); private SkeletalMeshData data; private Vector3f scale; private Vector3f translate; private DisposalAction disposalAction; public OpenGLSkeletalMesh(ResourceUrn urn, AssetType<?, SkeletalMeshData> assetType, SkeletalMeshData data, GLBufferPool bufferPool) { super(urn, assetType); disposalAction = new DisposalAction(urn, bufferPool); getDisposalHook().setDisposeAction(disposalAction); reload(data); } public void setScaleTranslate(Vector3f newScale, Vector3f newTranslate) { this.scale = newScale; this.translate = newTranslate; } @Override protected void doReload(SkeletalMeshData newData) { try { GameThread.synch(() -> { this.data = newData; if (disposalAction.vboPosNormBuffer == 0) { disposalAction.vboPosNormBuffer = disposalAction.bufferPool.get(getUrn().toString()); } IntBuffer indexBuffer = BufferUtils.createIntBuffer(newData.getIndices().size()); indexBuffer.put(newData.getIndices().toArray()); indexBuffer.flip(); if (disposalAction.vboIndexBuffer == 0) { disposalAction.vboIndexBuffer = disposalAction.bufferPool.get(getUrn().toString()); } VertexBufferObjectUtil.bufferVboElementData(disposalAction.vboIndexBuffer, indexBuffer, GL15.GL_STATIC_DRAW); FloatBuffer uvBuffer = BufferUtils.createFloatBuffer(newData.getUVs().size() * 2); for (Vector2f uv : newData.getUVs()) { uvBuffer.put(uv.x); uvBuffer.put(uv.y); } uvBuffer.flip(); if (disposalAction.vboUVBuffer == 0) { disposalAction.vboUVBuffer = disposalAction.bufferPool.get(getUrn().toString()); } VertexBufferObjectUtil.bufferVboData(disposalAction.vboUVBuffer, uvBuffer, GL15.GL_STATIC_DRAW); }); } catch (InterruptedException e) { logger.error("Failed to reload {}", getUrn(), e); } } public void preRender() { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, disposalAction.vboUVBuffer); GL13.glClientActiveTexture(GL13.GL_TEXTURE0); glTexCoordPointer(2, GL11.GL_FLOAT, TEX_COORD_SIZE * 4, 0); GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, disposalAction.vboIndexBuffer); } public void postRender() { glDisableClientState(GL_NORMAL_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); } public void doRender(List<Vector3f> verts, List<Vector3f> normals) { FloatBuffer vertBuffer = BufferUtils.createFloatBuffer(verts.size() * 6); for (int i = 0; i < verts.size(); ++i) { Vector3f vert = verts.get(i); vertBuffer.put(vert.x * scale.x + translate.x); vertBuffer.put(vert.y * scale.y + translate.y); vertBuffer.put(vert.z * scale.z + translate.z); Vector3f norm = normals.get(i); vertBuffer.put(norm.x); vertBuffer.put(norm.y); vertBuffer.put(norm.z); } vertBuffer.flip(); VertexBufferObjectUtil.bufferVboData(disposalAction.vboPosNormBuffer, vertBuffer, GL15.GL_DYNAMIC_DRAW); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, disposalAction.vboPosNormBuffer); glVertexPointer(VECTOR3_SIZE, GL_FLOAT, STRIDE, 0); glNormalPointer(GL_FLOAT, STRIDE, NORMAL_OFFSET); GL11.glDrawElements(GL11.GL_TRIANGLES, data.getIndices().size(), GL_UNSIGNED_INT, 0); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); } public void render() { preRender(); doRender(data.getBindPoseVertexPositions(), data.getBindPoseVertexNormals()); postRender(); } public void render(List<Vector3f> bonePositions, List<Quat4f> boneRotations) { preRender(); doRender(data.getVertexPositions(bonePositions, boneRotations), data.getVertexNormals(bonePositions, boneRotations)); postRender(); } @Override public int getVertexCount() { return data.getVertexCount(); } @Override public Collection<Bone> getBones() { return data.getBones(); } @Override public Bone getBone(String boneName) { return data.getBone(boneName); } @Override public AABB getStaticAabb() { return data.getStaticAABB(); } private static class DisposalAction implements Runnable { private final ResourceUrn urn; private GLBufferPool bufferPool; private int vboPosNormBuffer; private int vboUVBuffer; private int vboIndexBuffer; DisposalAction(ResourceUrn urn, GLBufferPool bufferPool) { this.urn = urn; this.bufferPool = bufferPool; } @Override public void run() { try { GameThread.synch(() -> { if (vboIndexBuffer != 0) { bufferPool.dispose(vboIndexBuffer); vboIndexBuffer = 0; } if (vboPosNormBuffer != 0) { bufferPool.dispose(vboPosNormBuffer); vboPosNormBuffer = 0; } if (vboUVBuffer != 0) { bufferPool.dispose(vboUVBuffer); vboUVBuffer = 0; } }); } catch (InterruptedException e) { logger.error("Failed to dispose {}", urn, e); } } } }