package graphics.mesh;
import graphics.material.Material;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import org.lwjgl.BufferUtils;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.GL15.*;
import static org.lwjgl.opengl.GL20.*;
/**
* Mesh is a representation of an object in 3d-space.
* (Vertices, texture coordinates, normals)
*
* Obj-file must have vertices, texture-coordinates, and normals (v/vt/vn)
*
* Polygons are automatically converted to triangles.
*
* @author simokr
*/
public class Mesh {
private boolean ready;
protected FloatBuffer VBO;
private int VBOid, maxFloatCount, stride, usage;
protected int indiceCount;
protected int keyframeCount;
protected Material defaultMaterial;
private int primitiveType;
public Mesh(){
ready = false;
VBOid = 0;
VBO = null;
indiceCount = 0;
keyframeCount = 0;
maxFloatCount = 0;
stride = 0;
usage = 0;
defaultMaterial = new Material();
primitiveType = GL_TRIANGLES;
}
/**
* Builds a Mesh from given data
*
* @param data VBO contents
* @param stride VBO stride
* @param usage VBO usage (GL_STATIC_DRAW, GL_STREAM_DRAW..)
* @param saveVBO
* @return success
*/
public boolean create(ArrayList<Float> data, int stride, int usage, boolean saveVBO){
if(data == null)
return false;
FloatBuffer temporaryBuffer = BufferUtils.createFloatBuffer(data.size());
for(int i=0;i<data.size();i++)
temporaryBuffer.put(data.get(i));
temporaryBuffer.flip();
return this.create(temporaryBuffer, stride, usage, saveVBO);
}
/**
* Builds a Mesh from given data. Also sets the maximum size of the VBO.
*
* @param data VBO contents
* @param stride VBO stride
* @param usage VBO usage (GL_STATIC_DRAW, GL_STREAM_DRAW..)
* @param saveVBO
* @return success
*/
public boolean create(FloatBuffer data, int stride, int usage, boolean saveVBO){
if(data == null)
return false;
if(stride < 1){
System.err.println("Incorrect stride. Must be over 0.");
return false;
}
if(this.isReady())
this.free();
data.rewind();
if(data.remaining()%stride != 0){
System.err.println("Incorrect VBO stride. Must have: "+stride);
return false;
}
this.VBO = data;
if(usage != GL_STATIC_DRAW && usage != GL_STREAM_DRAW){
System.err.println("Incorrect VBO usage. Must have: GL_STATIC_DRAW or GL_STREAM_DRAW.");
return false;
}
/* Create VBO handle */
if(!createVBO(usage, this.VBO.remaining())){
System.err.println("Failed to generate VBO handle");
return false;
}
/* Update data to GPU */
this.setVBO();
this.maxFloatCount = this.VBO.remaining();
this.indiceCount = this.VBO.remaining()/stride;
this.keyframeCount = 1;
this.stride = stride;
this.usage = usage;
if(!saveVBO)
this.VBO = null;
//System.out.println("Loaded a Mesh from data - " + this.indiceCount + " indices.");
this.ready = true;
return true;
}
/**
* Builds a Mesh with multiple keyframes.
* @param data
* @param usage
* @param saveVBO
* @param keyframeCount
* @param indiceCount
* @return success
*/
public boolean create(FloatBuffer data, int usage, boolean saveVBO, int keyframeCount, int indiceCount){
if(data == null)
return false;
if(this.isReady())
this.free();
data.rewind();
this.VBO = data;
if(usage != GL_STATIC_DRAW && usage != GL_STREAM_DRAW){
System.err.println("Incorrect VBO usage. Must have: GL_STATIC_DRAW or GL_STREAM_DRAW.");
return false;
}
/* Create VBO handle */
if(!createVBO(usage, this.VBO.remaining())){
System.err.println("Failed to generate VBO handle");
return false;
}
/* Update data to GPU */
this.setVBO();
this.maxFloatCount = this.VBO.remaining();
this.indiceCount = indiceCount;
this.keyframeCount = keyframeCount;
this.usage = usage;
this.stride = 9;
if(!saveVBO)
this.VBO = null;
//System.out.println("Loaded a Mesh from data - " + this.indiceCount + " indices.");
this.ready = true;
return true;
}
protected boolean update(){
if(!this.isReady())
return false;
if(this.VBO.remaining()%this.stride != 0){
System.err.println("Incorrect VBO stride. Must have: "+stride);
return false;
}
if(this.VBO.remaining() > this.maxFloatCount){
System.err.println("Data overflow, failed to update. Max: "+this.maxFloatCount+", had: "+this.VBO.remaining());
return false;
}
/* Update data to GPU */
this.setVBO();
this.indiceCount = this.VBO.remaining()/this.stride;
return true;
}
/**
* @return true is mesh is ready to be rendered
*/
public boolean isReady(){
return this.ready;
}
/**
* Frees the mesh data from GPU
*/
public void free(){
if(this.ready){
//System.out.print("Freeing mesh "+this.VBOid+" "+glIsBuffer(VBOid)+ " ");
glDeleteBuffers(VBOid);
//System.out.print("Freed mesh "+this.VBOid+" "+glIsBuffer(VBOid)+"\n");
}
else
System.out.print("Failed to free "+this.VBOid+" status: "+glIsBuffer(VBOid)+"\n");
if(this.VBO != null){
this.VBO.clear();
this.VBO = null;
}
this.VBOid = 0;
this.indiceCount = 0;
this.ready = false;
this.defaultMaterial = new Material();
}
/**
* Returns a FloatBuffer (read only) containing the mesh VBO.
*
* @return VBO or null
*/
public FloatBuffer getData(){
if(this.VBO == null)
return null;
return this.VBO.asReadOnlyBuffer();
}
public int getIndiceCount(){
return this.indiceCount;
}
public int getKeyframeCount(){
return this.keyframeCount;
}
public Material getDefaultMaterial(){
return new Material(this.defaultMaterial);
}
/**
* Render this mesh
*/
public void render(){
if(!this.ready)
return;
this.bind();
glDrawArrays(this.primitiveType, 0, this.indiceCount);
this.unbind();
}
public void render(int keyframe1, int keyframe2){
if(!this.ready)
return;
keyframe1 = keyframe1%this.getKeyframeCount();
keyframe2 = keyframe2%this.getKeyframeCount();
glBindBuffer(GL_ARRAY_BUFFER, this.VBOid);
this.setVertexPointers(keyframe1, keyframe2);
glDrawArrays(this.primitiveType, 0, this.indiceCount);
this.unsetVertexPointers((keyframe2 > -1));
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
protected void bind(){
glBindBuffer(GL_ARRAY_BUFFER, this.VBOid);
this.setVertexPointers();
}
protected void unbind(){
this.unsetVertexPointers();
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
private int genVBOId(){
IntBuffer buf = BufferUtils.createIntBuffer(1);
glGenBuffers(buf);
return buf.get(0);
}
/**
* Creates a new Vertex Buffer Object with given parameters.
*
* @param usage VBO usage (GL_STATIC_DRAW, GL_STREAM_DRAW..)
* @param floatCount number of floats in VBO (or space to be reserved)
* @return
*/
private boolean createVBO(int usage, int floatCount){
this.VBOid = this.genVBOId();
if(this.VBOid < 1){
return false;
}
int dataSize = floatCount*4;
glBindBuffer(GL_ARRAY_BUFFER, this.VBOid);
glBufferData(GL_ARRAY_BUFFER, dataSize, usage);
glBindBuffer(GL_ARRAY_BUFFER, 0);
return true;
}
/**
* Updates VBO contents to GPU
*
* @return false if VBOid couldn't be generated
*/
private void setVBO(){
glBindBuffer(GL_ARRAY_BUFFER, this.VBOid);
glBufferSubData(GL_ARRAY_BUFFER, 0, this.VBO);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
protected void setPrimitiveType(int type){
if(type == GL_TRIANGLES || type == GL_POINTS){
this.primitiveType = type;
}
else{
System.err.println("Invalid primitive type given.");
}
}
protected void setVertexPointers(){
this.setVertexPointers(0, -1);
}
protected void setVertexPointers(int keyframe1, int keyframe2){
int position1_offset = this.indiceCount*3*4*keyframe1;
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, position1_offset);
int offset = this.indiceCount*3*4*this.keyframeCount;
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, offset);
offset += this.indiceCount*2*4;
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 3, GL_FLOAT, false, 0, offset+position1_offset);
offset += this.indiceCount*3*4*this.keyframeCount;
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 1, GL_FLOAT, false, 0, offset);
if(keyframe2 > -1){
int position2_offset = this.indiceCount*3*4*keyframe2;
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, false, 0, position2_offset);
position2_offset = (this.indiceCount*3*4*this.keyframeCount) + (this.indiceCount*2*4) + (this.indiceCount*3*4*keyframe2);
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 3, GL_FLOAT, false, 0, position2_offset);
}
}
protected void unsetVertexPointers(){
this.unsetVertexPointers(false);
}
protected void unsetVertexPointers(boolean isKeyframe2){
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glDisableVertexAttribArray(3);
if(isKeyframe2){
glDisableVertexAttribArray(4);
glDisableVertexAttribArray(5);
}
}
}