package glmodel; import java.util.ArrayList; import org.lwjgl.util.glu.*; import java.nio.*; /** * Holds a mesh object containing triangles and vertices. Verts and Triangles * are stored as ArrayLists for flexibility and also as arrays for speedier * processing. * * The rebuild() function converts the ArrayLists to arrays, assigns * neighbor triangles to each vert, and recalculates normals. Handles normal * smoothing, and preserves sharp edges. * * see projectVerts() for setting screen positions (Zdepth) of verts * see sortTriangles() for Z depth sorting * see regenerateNormals() to calculate smoothed normals * * oct 2008: added funcs to handle groups * sep 2008: changed gluProject() params due to lwjgl 2.0 changes * jun 2006: add makeclone() */ public class GL_Mesh { // TEMPORARY lists hold verts and tris while mesh is being loaded or built dynamically public ArrayList vertexData = new ArrayList(); public ArrayList triangleData = new ArrayList(); // Arrays hold verts and tris after mesh is done being loaded or built (see rebuild()) public GL_Vertex[] vertices; public GL_Triangle[] triangles; public int numVertices = 0; public int numTriangles = 0; public String name=""; // This object's name // calculate the cosine of the smoothing angle (in degrees) (see registerSmoothNeighbors()) private float maxSmoothingAngle = 89f; private float cos_angle = (float)Math.cos(Math.toRadians(maxSmoothingAngle)); // name of material file from .obj file (or null) public String materialLibeName = null; public GLMaterial[] materials = null; // For managing triangle groups. See makeGroups(). // start with 1 group (default) GL_Triangle[][] groupFaces = new GL_Triangle[1][]; String[] groupNames = new String[] {"default"}; String[] groupMaterialNames = new String[] {null}; int currentGroup = 0; // temporary buffer used by projectVerts() private FloatBuffer projectedVert = allocFloats(16); public GL_Mesh() { } /** * the smoothing angle determines how smooth or faceted a model will appear. * If the angle between the normals of two neighboring faces is greater than * the smoothing angle then the faces will be smoothed (the vertex normals * at the edge of the faces will be averaged. *
* N1 example: the angle between face normals 1 and 2 * | is 90 degrees. If maxSmoothingAngle is 80 * _____|___ these faces will have a hard edge. If * face1 |___ N2 maxSmoothingAngle is 100 they will be smoothed. * | * | * face2 ** * @param maxSmoothingAngle */ public void setSmoothingAngle(float maxSmoothingAngle) { this.maxSmoothingAngle = maxSmoothingAngle; cos_angle = (float)Math.cos(Math.toRadians(maxSmoothingAngle)); } /** * get the vertex for the given index (aka vertex ID) * this assumes that rebuild() was run to build the vertices array from temporary vertexData * @param id of vertex * @return the vertex */ public GL_Vertex vertex(int id) { if (vertexData != null) { return (GL_Vertex) vertexData.get(id); } else { return vertices[id]; } } public GL_Triangle triangle(int id) { if (triangleData != null) { return (GL_Triangle) triangleData.get(id); } else { return triangles[id]; } } /** * add vertex to arraylist. * rebuild() will copy verts into an array for faster retrieval. * tag each vert with an ID (its index into the vertex array) * this is needed for makeClone() to correctly clone this mesh * @param newVertex */ public void addVertex(GL_Vertex newVertex) { newVertex.ID = vertexData.size(); vertexData.add(newVertex); } public void addVertex(float x, float y, float z) { addVertex(new GL_Vertex(x,y,z)); } public void addTriangle(GL_Triangle newTriangle) { triangleData.add(newTriangle); } public void addTriangle(int v1, int v2, int v3) { addTriangle(vertex(v1),vertex(v2),vertex(v3)); } public void addTriangle(GL_Vertex a, GL_Vertex b, GL_Vertex c) { addTriangle(new GL_Triangle(a,b,c)); } public void removeVertex(GL_Vertex v) { vertexData.remove(v); } public void removeTriangle(GL_Triangle t) { triangleData.remove(t); } public void removeVertexAt(int pos) { vertexData.remove(pos); } public void removeTriangleAt(int pos) { triangleData.remove(pos); } /** * Copy vertex and triangle data into arrays for faster * access. Find the neighbor triangles for each vertex. * This data should not change once the mesh is loaded, * so we call rebuild() only when the object is imported. */ public void rebuild() { if (vertexData == null || triangleData == null) { System.out.println("GL_Mesh.rebuild(): can't rebuild mesh after finalize() was run."); return; } // Copy vertices to array numVertices = vertexData.size(); vertices = new GL_Vertex[numVertices]; for (int i=0; i < numVertices; i++) { vertices[i] = vertex(i); vertices[i].ID = i; // vert ID is the index into vert array vertices[i].resetNeighbors(); // clear the neighbor triangle list } // Copy triangles to array GL_Triangle tri; numTriangles = triangleData.size(); triangles = new GL_Triangle[numTriangles]; for (int i=0; i < numTriangles; i++) { triangles[i] = tri = (GL_Triangle)triangleData.get(i); tri.ID = i; // register the triangle as a "neighbor" of its verts tri.p1.addNeighborTri(tri); tri.p2.addNeighborTri(tri); tri.p3.addNeighborTri(tri); } } public void rebuild_OLD() { GL_Triangle tri; // Generate faster structure for vertices numVertices = vertexData.size(); vertices = new GL_Vertex[numVertices]; for (int i=0; i < numVertices; i++) { vertices[i] = (GL_Vertex)vertexData.get(i); } // Generate faster structure for triangles numTriangles = triangleData.size(); triangles = new GL_Triangle[numTriangles]; for (int i=0; i < numTriangles; i++) { triangles[i]=(GL_Triangle)triangleData.get(i); //enum.nextElement(); triangles[i].ID = i; } //////////////////////////////////////////////// // find neighboring triangles for each vertex // clear the neighbors list for all verts for (int i=0; i < numVertices; i++) { vertices[i].ID = i; vertices[i].resetNeighbors(); } // register each triangle as a "neighbor" of the triangle's verts for (int i=0; i < numTriangles; i++) { tri = triangles[i]; tri.p1.addNeighborTri(tri); tri.p2.addNeighborTri(tri); tri.p3.addNeighborTri(tri); } } /** * remove tempoarary vert and triangle arraylists. These lists are used * to gather verts and triangles dynamically (ie. when loading or building a mesh * algorithmically). Once the mesh is complete then call finalize() to convert the * arraylists to fixed-length arrays, and to free up redundant arraylist memory. * CAN'T REBUILD AGAIN once finalize has been run! */ public void finalize() { if (vertexData == null || triangleData == null) { System.out.println("GL_Mesh.finalize(): looks like finalize() was already run."); return; } // convert vert and tri lists to arrays //rebuild(); // call this separately // remove vert and tri lists vertexData.clear(); triangleData.clear(); vertexData = triangleData = null; // optimize the vertex neighbor lists for (int i=0; i < vertices.length; i++) { vertices[i].neighborTris.trimToSize(); } } /////////////////////////////////////////////// // Since a vertex can be shared by several triangles // and each triangle may have a different normal (ie. two triangles // form a 90 degree edge), then we need to store the neighboring // triangles for each vertex in each triangle. When a vertex is // shared by several triangles, each triangle can have a different // normal for that vert, based on the neighbors of that triangle. /** * For the given Vert in the given Triangle, make a list of triangles * that are neighbors to the given Triangle. Only count as neighbors * those triangles that form a smooth surface with this triangle, * meaning the angle between this triangle and the neighbor triangle * is > 90 degrees (the actual min degrees value is in cos_angle). * * Requires that rebuild() has been run so that the vertex has * a list of neighbor triangles populated (see addNeighborTri()), * and the triangle face normals have been calculated. (see * GL_Triangle.regenerateNormal()). */ public void registerSmoothNeighbors(ArrayList neighborTris, GL_Vertex v, GL_Triangle t) { GL_Triangle neighborTri; // any triangle that is neighbor to the vert, is neighbor to that verts parent triangle // for each triangle that touches the vertex... for (int i=0; i < v.neighborTris.size(); i++) { neighborTri = (GL_Triangle)v.neighborTris.get(i); // Test for > 90 degree angle between triangle t and this triangle if (GL_Triangle.onSameSurface(t,neighborTri,cos_angle)) { // if the angle is obtuse enough, we'll smooth the two triangles together // add the neighbor triangle to a list of "smooth neighbors" if (!neighborTris.contains(neighborTri)) { neighborTris.add(neighborTri); } } } if (neighborTris.size() == 0) { // no neighbors found. Use the verts parent triangle neighborTris.add(t); } } /** * Recalculate normals for each vertex in each triangle. * This allows a vertex to have a different normal for each * triangle it's in (so we can have sharp edges or smooth surfaces). * * Requires that neighoring triangles have already been set. * @see rebuild(), registerNeighbors(), setSmoothingAngle() */ public void regenerateNormals() { GL_Triangle tri; // first calculate all triangle (face) normals for (int i=0; i < numTriangles; i++) { triangles[i].recalcFaceNormal(); } // Register the "smooth" neighbor triangles for each vert in // each triangle. Triangles that share verts are neighbors. // A "smooth" neighbor is a triangle that will be treated // as part of the same surface as this triangle. // See setSmoothingAngle() to set the angle at which triangles // are treated as two separate surfaces. for (int i=0;i < numTriangles; i++) { tri = triangles[i]; tri.resetNeighbors(); registerSmoothNeighbors(tri.neighborsP1, tri.p1, tri); registerSmoothNeighbors(tri.neighborsP2, tri.p2, tri); registerSmoothNeighbors(tri.neighborsP3, tri.p3, tri); } // next calculate normals for each vert in each triangle. // the vert normal is the average of it's neighbor triangle normals. for (int i=0; i < numTriangles; i++) { tri = triangles[i]; tri.norm1 = tri.recalcVertexNormal(tri.neighborsP1); tri.norm2 = tri.recalcVertexNormal(tri.neighborsP2); tri.norm3 = tri.recalcVertexNormal(tri.neighborsP3); } } /** * Return minimum point in the mesh. */ public GL_Vector min() { if (numVertices==0) return new GL_Vector(0f,0f,0f); float minX = vertices[0].pos.x; float minY = vertices[0].pos.y; float minZ = vertices[0].pos.z; for (int i=0; i