package com.bitwaffle.spaceguts.graphics.model;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import javax.vecmath.Point2f;
import javax.vecmath.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import com.bitwaffle.spaceout.resources.Textures;
import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.collision.shapes.ConvexHullShape;
import com.bulletphysics.collision.shapes.ConvexShape;
import com.bulletphysics.collision.shapes.ShapeHull;
import com.bulletphysics.util.ObjectArrayList;
/**
* Used to build a model being read in from a file. One of these should be
* created for every model being created, and the ultimate outcome is getting a
* {@link Model} from calling <code>makeModel</code>
*
* @author TranquilMarmot
* @see ModelLoader
*/
public class ModelBuilder {
/** the vertices of the model */
private ObjectArrayList<Vector3f> vertices;
/** the normals of the model */
private ArrayList<Vector3f> normals;
/** the texture coordinates of the model */
private ArrayList<Point2f> textureCoords;
/** which vertices to call */
private ArrayList<int[]> vertexIndices;
/** which normals to call */
private ArrayList<int[]> normalIndices;
/** which texture coordinates to call */
private ArrayList<int[]> textureIndices;
/** max and min values for the model being built */
public float maxX, minX, maxY, minY, maxZ, minZ = 0.0f;
/**
* See {@link ModelPart}
*/
private int currentIndex = 0, count = 0;
/** material to use for current ModelPart*/
private Material currentMaterial;
/** all the model parts */
private ArrayList<ModelPart> modelParts;
/** Whether or not we're in the middle of making a ModelPart (endModelPart hasn't been called after beginModelPart) */
private boolean makingModelPart = false;
/**
* ModelBuilder initializer
*/
public ModelBuilder() {
/*
* We have to add a blank element to the beginning of each list, as the obj
* file starts referencing elements at 1, but ArrayLists start at 0
*/
vertices = new ObjectArrayList<Vector3f>();
vertices.add(new Vector3f(0.0f, 0.0f, 0.0f));
normals = new ArrayList<Vector3f>();
normals.add(new Vector3f(0.0f, 0.0f, 0.0f));
textureCoords = new ArrayList<Point2f>();
textureCoords.add(new Point2f(0.0f, 0.0f));
// these just store which vertices to grab, don't need to add a blank
// element to them
vertexIndices = new ArrayList<int[]>();
normalIndices = new ArrayList<int[]>();
textureIndices = new ArrayList<int[]>();
// initialize model parts array
modelParts = new ArrayList<ModelPart>();
}
/**
* Add a vertex to the model being built
*
* @param vertex
* The vertex to add
*/
public void addVertex(Vector3f vertex) {
// check for max and min values
if(vertex.x > maxX)
maxX = vertex.x;
if(vertex.x < minX)
minX = vertex.x;
if(vertex.y > maxY)
maxY = vertex.y;
if(vertex.y < minY)
minY = vertex.y;
if(vertex.z > maxZ)
maxZ = vertex.z;
if(vertex.z < minZ)
minZ = vertex.z;
vertices.add(vertex);
}
/**
* Add vertex indices to the model being built. Automatically splits quads
* into triangles for simplicity.
*
* @param indices The indices to add
*/
public void addVertexIndices(int[] indices) {
// add if it's just a triangle
if (indices.length == 3){
vertexIndices.add(indices);
// increase the count of indices for the current model part by 3 (one triangle)
count += 3;
}
// else split the quad into two triangles
else if (indices.length == 4) {
/*
* NOTE
* It might be important to note that, for every quad split into a triangle,
* two of its vertex indices are duplicated. So what was originally
* 4 indices become 6 indices.
* This makes drawing much simpler, as OpenGL can only draw in one mode
* (i.e. GL_TRIANGLES or GL_QUADS). So we don't have to keep switching
* between the two (just draw all triangles).
*/
/*
* Given indices 0,1,2,3 a quad can be split into two triangles:
* 0,1,2 and 2,3,0
*/
int[] tri1 = new int[3];
tri1[0] = indices[0];
tri1[1] = indices[1];
tri1[2] = indices[2];
vertexIndices.add(tri1);
int[] tri2 = new int[3];
tri2[0] = indices[2];
tri2[1] = indices[3];
tri2[2] = indices[0];
vertexIndices.add(tri2);
// increase the count of indices for the current model part by 6 (two triangle)
count += 6;
} else {
System.out
.println("Error! Array not a triangle or a quad! (ModelBuilder)");
}
}
/**
* Add a normal to the model being built.
*
* @param vertex
* The vertex to add
*/
public void addNormal(Vector3f vertex) {
normals.add(vertex);
}
/**
* Add normal indices to the model being built. Automatically splits quads
* into triangles for simplicity.
*
* @param indices The indices to add
*/
public void addNormalIndices(int[] indices) {
// add if it's just a triangle
if (indices.length == 3)
normalIndices.add(indices);
// else split the quad into two triangles
else if (indices.length == 4) {
int[] tri1 = new int[3];
tri1[0] = indices[0];
tri1[1] = indices[1];
tri1[2] = indices[2];
normalIndices.add(tri1);
int[] tri2 = new int[3];
tri2[0] = indices[2];
tri2[1] = indices[3];
tri2[2] = indices[0];
normalIndices.add(tri2);
} else {
System.out
.println("Error! Array not a triangle or a quad! (ModelBuilder)");
}
}
/**
* Add texture coordinates to the model being built. Only 2D texture coordinates are supported right now.
* @param point The texture coordinates to add
*/
public void addTextureCoords(Point2f point) {
textureCoords.add(point);
}
/**
* Add texture coordinate indices to the model being built. Automatically splits quads
* into triangles for simplicity.
* @param indices The indices to add
*/
public void addTetxureIndices(int[] indices) {
if (indices.length == 3)
textureIndices.add(indices);
else if (indices.length == 4) {
int[] tri1 = new int[3];
tri1[0] = indices[0];
tri1[1] = indices[1];
tri1[2] = indices[2];
textureIndices.add(tri1);
int[] tri2 = new int[3];
tri2[0] = indices[2];
tri2[1] = indices[3];
tri2[2] = indices[0];
textureIndices.add(tri2);
} else {
System.out
.println("Error! Array not a triangle or a quad! (ModelBuilder)");
}
}
/**
* This method should be called after all the vertices, faces, and normals
* and their respective indices have been added.
* @return A model built using the current indices
*/
public Model makeModel(Textures texture) {
if(makingModelPart){
endModelPart();
}
return new Model(buildCollisionShape(), fillVertexArray(), modelParts, texture);
}
/**
* Fills an array buffer with the given data
* @return The vertex array object handle
*/
private int fillVertexArray(){
// get a handle for a VAO
int vaoHandle = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoHandle);
// create buffers and fill them with data
FloatBuffer vertBuffer = BufferUtils.createFloatBuffer(vertexIndices.size() * 9);
FloatBuffer normBuffer = BufferUtils.createFloatBuffer(normalIndices.size() * 9);
FloatBuffer texBuffer = BufferUtils.createFloatBuffer(textureIndices.size() * 6);
for(int i = 0; i < vertexIndices.size(); i++){
int[] triVerts = vertexIndices.get(i);
int[] triNorms = normalIndices.get(i);
int[] triTex = textureIndices.get(i);
Vector3f firstVert = vertices.get(triVerts[0]);
vertBuffer.put(firstVert.x);
vertBuffer.put(firstVert.y);
vertBuffer.put(firstVert.z);
Vector3f firstNorm = normals.get(triNorms[0]);
normBuffer.put(firstNorm.x);
normBuffer.put(firstNorm.y);
normBuffer.put(firstNorm.z);
Point2f firstTex = textureCoords.get(triTex[0]);
texBuffer.put(firstTex.x);
texBuffer.put(1 - firstTex.y);
Vector3f secondVert = vertices.get(triVerts[1]);
vertBuffer.put(secondVert.x);
vertBuffer.put(secondVert.y);
vertBuffer.put(secondVert.z);
Vector3f secondNorm = normals.get(triNorms[1]);
normBuffer.put(secondNorm.x);
normBuffer.put(secondNorm.y);
normBuffer.put(secondNorm.z);
Point2f secondTex = textureCoords.get(triTex[1]);
texBuffer.put(secondTex.x);
texBuffer.put(1 - secondTex.y);
Vector3f thirdVert = vertices.get(triVerts[2]);
vertBuffer.put(thirdVert.x);
vertBuffer.put(thirdVert.y);
vertBuffer.put(thirdVert.z);
Vector3f thirdNorm = normals.get(triNorms[2]);
normBuffer.put(thirdNorm.x);
normBuffer.put(thirdNorm.y);
normBuffer.put(thirdNorm.z);
Point2f thirdTex = textureCoords.get(triTex[2]);
texBuffer.put(thirdTex.x);
texBuffer.put(1 - thirdTex.y);
}
// be kind, please rewind()!
vertBuffer.rewind();
normBuffer.rewind();
texBuffer.rewind();
// handles for filling buffer objects
IntBuffer vboHandles = BufferUtils.createIntBuffer(3);
GL15.glGenBuffers(vboHandles);
// actually fill the buffers
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboHandles.get(0));
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, vertBuffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(0);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboHandles.get(1));
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, normBuffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(1, 3, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(1);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboHandles.get(2));
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, texBuffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(2, 2, GL11.GL_FLOAT, false, 0, 0L);
GL20.glEnableVertexAttribArray(2);
GL30.glBindVertexArray(0);
return vaoHandle;
}
/**
* This should be called whenever a new set of vertices with a different material needs to be created.
* This should be called after all vertices have been added and while vertex indices are being added
* @param mat Material to use for incoming vertex indices
*/
public void startModelPart(Material mat){
// end the current model part if we're making one
if(isMakingModelPart())
endModelPart();
// set the current material
currentMaterial = mat;
// let everyone know that we're now making a model part
makingModelPart = true;
}
/**
* Ends the current model part
*/
public void endModelPart(){
// add the model part
modelParts.add(new ModelPart(currentMaterial, currentIndex, count));
// advance the current index and set count to 0 for the next model part
currentIndex += count;
count = 0;
// let everyone know that we're done making the current model part
makingModelPart = false;
}
/**
* @return Whether or not a model part is being made right now
*/
public boolean isMakingModelPart(){
return makingModelPart;
}
/**
* Builds a collision shape for this model
* @return A convex hull collision shape representing this model
*/
private CollisionShape buildCollisionShape(){
ConvexShape originalConvexShape = new ConvexHullShape(vertices);
// create a hull based on the vertices
ShapeHull hull = new ShapeHull(originalConvexShape);
float margin = originalConvexShape.getMargin();
hull.buildHull(margin);
return new ConvexHullShape(hull.getVertexPointer());
}
}