package Renderer; import java.nio.IntBuffer; import java.util.ArrayList; import LDraw.Support.Range; //============================================================================== // //File: MeshSmooth // //MeshSmooth is a set of C functions that merge triangle meshes and calculate //smoothed normals in a way that happens to be usefor for LDraw models. //MeshSmooth takes care of the following processing: // //- "Welding" very-close vertices that do not have the exact same location due // to rounding errors in sub-part matrix transforms. // //- Optionally locating T junctions and subdividing the faces. // //- Determining smooth and creased edges basde on the presence of lines and // crease angles. // //- Resolving BFC errors. (The normals are generated correctly for two-sided // lighting, but no attempt to determine a front is made; the output must // still support two-sided lighting and have culling disabled.) // //- Calculating smooth normals for shared vertices. // //- Merging vertices that are completely equal and calculating mesh indices. // //Usage: // //A client creates a mesh structure with a pre-declared count of tris, quads //and lines, then adds them. // //Once all data is added, a series of processing functions are called to //transform the data. // //Finally, for output, the final mesh counts are queried and written to storage //provided by the client. This API is suitable for writing directly to memory- //mapped VBOs. // //Textures: // //Faces can be tagged with an integer "texture ID" TID; the API will track face //TID and output the mesh in TID order. This allows a single mesh to be drawn //as a series of sub-draw-calls with texture changes in between them. // //Texture IDs should be sequential and zero based. // //============================================================================== public class MeshSmooth { public static final float EPSI = 0.05f; public static final float EPSI2 = 0.00025f; // Compare two unique 3-d points in space for location-sameness. public static int compare_points(float[] p1, int offset_1, float[] p2, int offset_2) { for (int i = 0; i < 3; i++) { if (p1[offset_1 + i] < p2[offset_2 + i]) return -1; if (p1[offset_1 + i] > p2[offset_2 + i]) return 1; } return 0; } public static int compare_points(float[] p1, float[] p2) { return compare_points(p1, 0, p2, 0); } // Compare two vertices for complete match-up of all vertices - vertex, // normal, color. // If these all match, we could merge the vertices on the graphics card. public static int compare_vertices(Vertex v1, Vertex v2) { float[] location_v1 = v1.getLocation(); float[] location_v2 = v2.getLocation(); float[] normal_v1 = v1.getNormal(); float[] normal_v2 = v2.getNormal(); float[] color_v1 = v1.getColor(); float[] color_v2 = v2.getColor(); int returnValue = 0; if ((returnValue = compare_points(location_v1, location_v2)) != 0) return returnValue; if ((returnValue = compare_points(normal_v1, normal_v2)) != 0) return returnValue; if ((returnValue = compare_points(color_v1, color_v2)) != 0) return returnValue; if (color_v1[3] < color_v2[3]) return -1; if (color_v1[3] > color_v2[3]) return 1; return 0; } // Compare only the "Nth" location field, e.g. only x, y, or z. // Used to organize points along a single axis. public static int compare_nth(Vertex v1, Vertex v2, int n) { float[] location_v1 = v1.getLocation(); float[] location_v2 = v2.getLocation(); if (location_v1[n] < location_v2[n]) return -1; if (location_v1[n] > location_v2[n]) return 1; return 0; } // 10-coordinate bubble sort - in other words, the array of vertices is // sorted // lexicographically based on all 10 coords (position, normal, and color). // WHY a bubble sort??! Well, it turns out that when we run this, we are // already // sorted by location, and thus the 'disturbance' of orders are very // localized // and small. So it is faster to run bubble sort - while its worst case time // is // really quite bad, it gets fast when we are nearly sorted. public static void bubble_sort_10(ArrayList<Vertex> vertices, int count) { boolean swapped; do { int high_count = 0; swapped = false; for (int i = 1; i < count; ++i) if (compare_vertices(vertices.get(i - 1), vertices.get(i)) > 0) { swapVertexInArrayList(vertices, i - 1, i); swapped = true; high_count = i; } count = high_count; } while (swapped); } private static void swapVertexInArrayList(ArrayList<Vertex> vertices, int i, int j) { Vertex temp_i = vertices.get(i); vertices.set(i, vertices.get(j)); vertices.set(j, temp_i); } // sort APIs are wrapped in functions that don't have an algo, e.g. "just // sort by // 10 coords" so we can easily try different algos and see which is fastest. public static void sort_vertices_10(ArrayList<Vertex> vertices, int count) { MeshSmooth.bubble_sort_10(vertices, count); } // 3-coordinate quick-sort. The range of arr from [left to right] // (inclusive!!) // is sorted using quick-sort. For totally unsorted data, this is a good // sort // choice. Only location is used to sort. public static void quickSort_3(ArrayList<Vertex> vertices, int left, int right) { int i = left, j = right; Vertex pivot_ptr = vertices.get((left + right) / 2); float pivot[] = { pivot_ptr.getLocation()[0], pivot_ptr.getLocation()[1], pivot_ptr.getLocation()[2] }; /* partition */ while (i <= j) { while (MeshSmooth.compare_points(vertices.get(i).getLocation(), 0, pivot, 0) < 0) { ++i; } while (MeshSmooth.compare_points(vertices.get(j).getLocation(), 0, pivot, 0) > 0) { --j; } if (i <= j) { if (i != j) { swapVertexInArrayList(vertices, i, j); } ++i; --j; } } if (left < j) quickSort_3(vertices, left, j); if (i < right) quickSort_3(vertices, i, right); } // Quick-sort, but based only on the "nth" coordinate - lets us rapidly // sort by x, y, or z. We want quicksort because changing the sort axis // is likely to radically change the order, and thus we are not near-sorted // to begin with. public static void quickSort_n(ArrayList<Vertex> vertices, int left, int right, int n) { int i = left, j = right; Vertex pivot_ptr = vertices.get((left + right) / 2); Vertex pivot = pivot_ptr; /* partition */ while (i <= j) { while (compare_nth(vertices.get(i), pivot, n) < 0) { i++; } while (compare_nth(vertices.get(j), pivot, n) > 0) { j--; } if (i <= j) { if (i != j) { swapVertexInArrayList(vertices, i, j); } ++i; --j; } } if (left < j) quickSort_n(vertices, left, j, n); if (i < right) quickSort_n(vertices, i, right, n); } // General sort by location API, see sort_vertices_10 for // logic. public static void sort_vertices_3(ArrayList<Vertex> vertices, int count) { quickSort_3(vertices, 0, count - 1); } // Search primitive. Given a sorted (by location) array of vertices and a // target point (p3) this routine finds the range // [begin, end) that has points of equal location to p. begin == end if // there are on points matching p; in this case, // begin and end will _not_ be "near" p in any way. // // The beginning of the range is found via binary search; the end is found // by linearly walking forward to find the end. // Since we have relatively small numbers of equal points, this linear walk // is fine. public static Range range_for_point(ArrayList<Vertex> vertices, int base, int stop, float p[]) { int len = stop - base; int index = 0; int begin; int end; while (len > 0) { int half = len / 2; Vertex middle = vertices.get(index + half); int res = MeshSmooth.compare_points(middle.getLocation(), 0, p, 0); if (res < 0) { index = index + half + 1; len = len - half - 1; } else { len = half; } } begin = index; while (index != stop && MeshSmooth.compare_points(vertices.get(index).getLocation(), 0, p, 0) == 0) { ++index; } end = index; return new Range(begin, end - begin); } // Given a vertex q already in our array of sorted vertices [base, stop) we // find the range [begin, end) that is entirely colocated // with q. Q will be in the range [begin, end). We do this with a linear // walk - we know all these vertices are near each other, // so we just go find them without jumping. public static Range range_for_vertex(ArrayList<Vertex> vertices, int base, int stop, int qIndex) { int begin = 0, end = 0; int targetIndex = 0; int b = qIndex; int e = qIndex; while (b >= base && compare_points(vertices.get(b).getLocation(), vertices.get(qIndex).getLocation()) == 0) --b; ++b; while (e < stop && compare_points(vertices.get(e).getLocation(), vertices.get(qIndex).getLocation()) == 0) ++e; assert (b < e); assert (b <= qIndex); assert (e > qIndex); begin = b; end = e; return new Range(begin, end - begin); } // Utilities to return the next vertex index of a face from i, in either the // clock-wise // or counter-clockwise direction. public static int CCW(Face f, int i) { assert (i >= 0 && i < f.getDegree()); return (i + 1) % f.getDegree(); } public static int CW(Face f, int i) { assert (i >= 0 && i < f.getDegree()); return (i + f.getDegree() - 1) % f.getDegree(); } // Predicate: do the two face normals n1 and n2 form a crease? Flip should // be true if the winding order // of the two tris is flipped. public static boolean is_crease(float n1[], float n2[], boolean flip) { float dot = vec3f_dot(n1, n2); if (flip == true) { return (dot > -0.5); } else return (dot < 0.5); } // #pragma mark - // ============================================================================== // 3-D MATH UTILS // ============================================================================== // vec3 and vec4 APIs refer to "vector" numbers, e.g. small arrays of float. // So vec3 . float[3], and vec4 . float[4]. // Normalize vector N in place if not zero-length. public static void vec3f_normalize(float N[]) { float len = (float) Math.sqrt(N[0] * N[0] + N[1] * N[1] + N[2] * N[2]); if (len != 0) { len = 1.0f / len; N[0] *= len; N[1] *= len; N[2] *= len; } } // copy vec3: d = s. public static void vec3f_copy(float[] d, float[] s) { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; } // copy vec4: d = s public static void vec4f_copy(float[] d, float[] s) { d[0] = s[0]; d[1] = s[1]; d[2] = s[2]; d[3] = s[3]; } // return dot product of vec3's d1 and d2. public static float vec3f_dot(float[] v1, float[] v2) { return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; } // vec3: dst = b - a. (or: vector dst points from A to B). public static void vec3f_diff(float[] dst, float[] a, float[] b) { dst[0] = b[0] - a[0]; dst[1] = b[1] - a[1]; dst[2] = b[2] - a[2]; } // Return the square of the length of the distance between two vec3 points // p1, p2. public static float vec3f_length2(float[] p1, float[] p2) { float d[] = new float[3]; vec3f_diff(d, p1, p2); return vec3f_dot(d, d); } // 3-d comparison of p1 and p2 (simple true/false, not a comparator). public static boolean vec3f_eq(float[] p1, float[] p2) { return p1[0] == p2[0] && p1[1] == p2[1] && p1[2] == p2[2]; } // vec3 cross product, e.g. dst = v1 x v2. public static void vec3_cross(float[] dst, float[] v1, float[] v2) { dst[0] = (v1[1] * v2[2]) - (v1[2] * v2[1]); dst[1] = (v1[2] * v2[0]) - (v1[0] * v2[2]); dst[2] = (v1[0] * v2[1]) - (v1[1] * v2[0]); } // Returns true if the projection of B onto the line AC is in between (but // not on) A and C. public boolean in_between_line(float[] a, float[] b, float[] c) { float ab[] = new float[3]; float ac[] = new float[3]; float cb[] = new float[3]; vec3f_diff(ab, a, b); vec3f_diff(ac, a, c); vec3f_diff(cb, c, b); return vec3f_dot(ab, ac) > 0.0f && vec3f_dot(cb, ac) < 0.0f; } // project p onto the line along v through o, return it in proj. public void proj_onto_line(float[] proj, float[] o, float[] v, float[] p) { float op[] = new float[3]; vec3f_diff(op, o, p); float scalar = vec3f_dot(op, v) / vec3f_dot(v, v); proj[0] = o[0] + scalar * v[0]; proj[1] = o[1] + scalar * v[1]; proj[2] = o[2] + scalar * v[2]; } // // #pragma mark - // // #define mirror(f,n) ((f)->index[(n)]) // Given a vertex, this routine returns a colocated vertex from the // neighboring triangle // if the mesh is circulated around V counter-clockwise. // // null is returned if there is no adjacent triangle to V's triangle in the // CCW direction. // Note that when a line 'creases' the mesh, the triangles are _not_ // connected, so we // get back null. // // the int pointed to by did_reverse is set to 0 if v's and the return // vertex's triangle // have the same winding direction; it is set to 1 if the winding direction // of the two // tris is opposite. // todo public static Vertex circulate_ccw(Vertex v, IntBuffer did_reverse) { // .------V,M We use "leading neighbor" syntax, so (2) is the cw(v) // neighbor of 1. // \ / \ Conveniently, "M" is the defining vertex for edge X as defined // by 2. // \ 1 x \ So 1->neigbhor(cw(v)) is M's index. // \ / 2 \ One special case: if we are flipped, we need to go CCW from // 'M'. // cw-------. Face face_1 = v.getFace(); int cw = CW(face_1, v.getIndex()); Face face_2 = face_1.neighbor[cw]; assert (face_2 != null); if (face_2 == null) return null; int M = v.face.index[cw]; // int face1_flip[] = face_1.getFlip(); did_reverse.put(0, face_1.flip[cw]); Vertex ret = (face_1.flip[cw] != 0) ? face_2.vertex[CCW(face_2, M)] : face_2.vertex[M]; assert (MeshSmooth.compare_points(v.getLocation(), 0, ret.getLocation(), 0) == 0); assert (ret != v); return ret; } // Same as above, but the circulation is done in the clockwise direction. public static Vertex circulate_cw(Vertex v, IntBuffer did_reverse) { // .-------V V itself defines the edge we want to traverse, but M is out // of position - to // \ / \ recover V we want CCW(M). But if we are flipped, we just need M // itself. // \ 2 x \ ... // \ / 1 \ ... // M-------. Face face_1 = v.face; Face face_2 = face_1.neighbor[v.getIndex()]; assert (face_2 != null); if (face_2 == null) return null; int M = v.face.index[v.getIndex()]; did_reverse.put(0, face_1.flip[v.getIndex()]); Vertex ret = (face_1.flip[v.getIndex()] != 0) ? face_2.vertex[M] : face_2.vertex[CCW(face_2, M)]; assert (MeshSmooth.compare_points(v.getLocation(), 0, ret.getLocation(), 0) == 0); assert (ret != v); return ret; } // This routine circulates V in the CCW direction if *dir is 1, and CW // if *dir is 0. Return semantics match the circulators above. // If the next triangle reverses winding, *dir is negated so that an // additional call with *dir's new value will have the same _effective_ // direction. public static Vertex circulate_any(Vertex v, IntBuffer dir) { IntBuffer did_reverse = IntBuffer.allocate(1); Vertex ret; // assert(*dir == -1 || *dir == 1); if (dir.get(0) > 0) ret = circulate_ccw(v, did_reverse); else ret = circulate_cw(v, did_reverse); if (did_reverse.get() != 0) dir.put(0, dir.get(0) * -1); return ret; } // // #pragma mark - // //============================================================================== // // VALIDATION UTILITIES // //============================================================================== // // // // These routines validate that the invariances of the internal mesh // structure // // haven't been stomped on. They are meant only for debug builds, slow // the algo // // down, and call a fatal assert() when things go bad. // // #if DEBUG // // // Validates that the meshes are sorted in ascending order according to // all // // ten params (vertex, normal and color. // void validate_vertex_sort_10(struct Mesh * mesh) // { // int i; // for(i = 1; i < mesh->vertex_count; ++i) // { // assert(compare_vertices(mesh->vertices+i-1,mesh->vertices+i) <= 0); // } // } // // // Validates that the meshes are sorted in ascending order according to // // position, ignoring normal and color. // void validate_vertex_sort_3(struct Mesh * mesh) // { // int i; // for(i = 1; i < mesh->vertex_count; ++i) // { // int comp = // compare_points(mesh->vertices[i-1].getLocation(),mesh->vertices[i].getLocation()); // if(comp > 0) // { // assert(!"out of order"); // } // } // } // // // This verifies that all vertices and faces link to each other // symetrically. // void validate_vertex_links(struct Mesh * mesh) // { // int i, j; // for(i = 0; i < mesh->vertex_count; ++i) // { // assert(mesh->vertices[i].face.vertex[mesh->vertices[i].index] == // mesh->vertices+i); // } // for(i = 0; i < mesh->face_count; ++i) // for(j = 0; j < mesh->faces[i].degree; ++j) // { // assert(mesh->faces[i].vertex[j]->face == mesh->faces+i); // } // } // // // This validates that all neighboring triangles share the same // // located vertices on their shared edge, the shared edge index // // number is sane, and that the "reversed" flag is set only when // // the winding order really is reversed. // void validate_neighbors(struct Mesh * mesh) // { // int f,i; // for(f = 0; f < mesh->face_count; ++f) // for(i = 0; i < mesh->faces[f].degree; ++i) // { // Face face = mesh->faces+f; // if(face.neighbor[i] && face.neighbor[i] != null) // { // Face n = face.neighbor[i]; // int ni = face.index[i]; // assert(n->neighbor[ni] == face); // // assert(face.flip[i] == n->flip[ni]); // // Vertex p1 = face.vertex[ i ]; // Vertex p2 = face.vertex[CCW(face,i)]; // // Vertex n1 = n->vertex[ ni ]; // Vertex n2 = n->vertex[CCW(n,ni)]; // // int okay_fwd = // (compare_points(n1.getLocation(),p2.getLocation()) == 0) && // (compare_points(n2.getLocation(),p1.getLocation()) == 0); // // int okay_rev = // (compare_points(n1.getLocation(),p1.getLocation()) == 0) && // (compare_points(n2.getLocation(),p2.getLocation()) == 0); // // assert(!okay_fwd || face.flip[i] == 0); // assert(!okay_rev || face.flip[i] == 1); // // assert(okay_fwd != okay_rev); // #if WANT_CREASE // assert(!okay_fwd || vec3f_dot(face.normal,n->normal) > 0.0); // assert(!okay_rev || vec3f_dot(face.normal,n->normal) < 0.0); // #endif // } // } // } // #endif // // #pragma mark - // //============================================================================== // // MAIN API IMPLEMENTATION // //============================================================================== // // // Add one face to the mesh. Quads and tris can be added in any order but // all // // quads and tris (polygons) must be added before all lines. // // When passing a face, simply pass null for any 'extra' vertices - that // is, // // to create a line, pass null for p3 and p4; to create a triangle, pass // null for // // p3. The color is the color of the entire face in RGBA; the face normal // is // // computed for you. // // // // tid is the 'texture ID', a 0-based counted number identifying which // texture // // state this face gets. Texture IDs must be consecutive, zero based and // // positive, but do not need to be submitted in any particular order, and // the // // highest TID does not have to be pre-declared; the library simply // watches // // the TIDs on input. // // // // Technically lines have TIDs as well - typically TID 0 is used to mean // the // // 'untextured texture group' and is used for lines and untextured // polygons. // // // // The TIDs are used to output sets of draw commands that share common // texture state - // // that is, faces, quads and lines are ouput in TID order. // void add_face(struct Mesh * mesh, float p1[3], float p2[3], float p3[3], // float p4[3], float color[4], int tid) // { // #if SLOW_CHECKING // if(vec3f_length2(p1,p2) <= EPSI2) mesh->flags |= TINY_INITIAL_TRIANGLE; // if(p3) // { // if(vec3f_length2(p1,p3) <= EPSI2) mesh->flags |= TINY_INITIAL_TRIANGLE; // if(vec3f_length2(p2,p3) <= EPSI2) mesh->flags |= TINY_INITIAL_TRIANGLE; // } // if(p4) // { // if(vec3f_length2(p1,p4) <= EPSI2) mesh->flags |= TINY_INITIAL_TRIANGLE; // if(vec3f_length2(p2,p4) <= EPSI2) mesh->flags |= TINY_INITIAL_TRIANGLE; // if(vec3f_length2(p3,p4) <= EPSI2) mesh->flags |= TINY_INITIAL_TRIANGLE; // } // #endif // int i; // // // // grab a new face, grab verts for it // Face f = mesh->faces + mesh->face_count++; // f->tid = tid; // if(tid > mesh->highest_tid) // mesh->highest_tid = tid; // if(p3) // { // float v1[3] = { p2[0]-p1[0],p2[1]-p1[1],p2[2]-p1[2]}; // float v2[3] = { p3[0]-p1[0],p3[1]-p1[1],p3[2]-p1[2]}; // vec3_cross(f->normal,v1,v2); // vec3f_normalize(f->normal); // } // else // { // f->normal[0] = f->normal[2] = 0.0f; // f->normal[1] = 1.0f; // } // // f->degree = p4 ? 4 : (p3 ? 3 : 2); // // f->vertex[0] = mesh->vertices + mesh->vertex_count++; // f->vertex[1] = mesh->vertices + mesh->vertex_count++; // f->vertex[2] = p3 ? mesh->vertices + mesh->vertex_count++ : null; // f->vertex[3] = p4 ? (mesh->vertices + mesh->vertex_count++) : null; // // f->neighbor[0] = f->neighbor[1] = f->neighbor[2] = f->neighbor[3] = // null; // f->t_list[0] = f->t_list[1] = f->t_list[2] = f->t_list[3] = null; // // f->index[0] = f->index[1] = f->index[2] = f->index[3] = -1; // f->flip[0] = f->flip[1] = f->flip[2] = f->flip[3] = -1; // // vec4f_copy(f->color, color); // // // for(i = 0; i < f->degree; ++i) // { // vec3f_copy(f->vertex[i]->normal,f->normal); // vec4f_copy(f->vertex[i]->color,color); // f->vertex[i]->prev = f->vertex[i]->next = null; // } // // vec3f_copy(f->vertex[0].getLocation(),p1); // vec3f_copy(f->vertex[1].getLocation(),p2); // if(p3) // vec3f_copy(f->vertex[2].getLocation(),p3); // if(p4) // vec3f_copy(f->vertex[3].getLocation(),p4); // // f->vertex[0]->index = 0; // f->vertex[1]->index = 1; // if(f->vertex[2]) // f->vertex[2]->index = 2; // if(f->vertex[3]) // f->vertex[3]->index = 3; // // f->vertex[0]->face = // f->vertex[1]->face = f; // if(f->vertex[2]) // f->vertex[2]->face = f; // if(f->vertex[3]) // f->vertex[3]->face = f; // } // // // Utility: this is the visior used to snap vertices to each other. // // Snapping is done by linking nearby vertices into a ring whose // // centroid is later found. // public static void visit_vertex_to_snap(Vertex v, void * ref) // { // Vertex o = (Vertex) ref; // Vertex p, * n; // if(o != v) // { // assert(!vec3f_eq(o.getLocation(),v.getLocation())); // // if(vec3f_length2(o.getLocation(), v.getLocation()) < EPSI2) // { // // // Check if o is already in v's sybling list BEFORE v. If so, bail. // for(n = v.prev; n; n = n->prev) // if(n == o) // return; // // // Scan forward to find last node in v's list. // n = v; // assert(n != o); // while(n->next) // { // n = n->next; // if(n == o) // Already connected to o? Eject! // return; // } // // p = o; // assert(p != v); // while(p->prev) // { // p = p->prev; // assert(p != v); // this would imply our linkage is not doubly linked. // } // // assert(n->next == null); // assert(p->prev == null); // n->next = p; // p->prev = n; // } // } // } // // // This function does a bunch of post-geometry-adding processing: // // 1. It sorts the vertices in XYZ order for correct indexing. This // // forces colocated vertices together in the list. // // 2. It indexes vertices into an R-tree. // // 3. It performs a two-step snapping process by // // 3a. Locating rings of too-close vertices and // // 3b. Setting each member of the ring to the ring's centroid location. // // 4. Vertices are resorted AGAIN. // // 5. The links from faces to vertices must be rebuilt due to sorting. // // 6. Degenerate quads/tris are marked as 'creased' on all sides. // // // // Notes: // // 2 and 4 are BOTH necessary - the first sort is needed to pre-sorted // // the data for the R-tree interface. // // The second sort is needed because the order of sort is ruined by // // changing XYZ geometry locations. // // // // Re: 6, we don't want to delete degenerate quads (a degen quad // // might be a visible triangle) but passing degenerate geometry to the // // smoother causes problems - so instead we 'seal off' this geometry to // // avoid further problems. // void finish_faces_and_sort(struct Mesh * mesh) // { // int v, f; // int total_before = 0, total_after = 0; // // // sort vertices by 10 params // sort_vertices_3(mesh->vertices,mesh->vertex_count); // // mesh->index = index_vertices(mesh->vertices,mesh->vertex_count); // // #if DEBUG // validate_vertex_sort_3(mesh); // #endif // // // for(v = 0; v < mesh->vertex_count; ++v) // { // if(v == 0 || // compare_points(mesh->vertices[v-1].getLocation(),mesh->vertices[v].getLocation()) // != 0) // { // ++total_before; // Vertex vi = mesh->vertices + v; // float mib[3] = { vi.getLocation()[0] - EPSI, vi.getLocation()[1] - EPSI, // vi.getLocation()[2] - EPSI }; // float mab[3] = { vi.getLocation()[0] + EPSI, vi.getLocation()[1] + EPSI, // vi.getLocation()[2] + EPSI }; // scan_rtree(mesh->index, mib, mab, visit_vertex_to_snap, vi); // } // } // // for(v = 0; v < mesh->vertex_count; ++v) // if(v == 0 || // compare_points(mesh->vertices[v-1].getLocation(),mesh->vertices[v].getLocation()) // != 0) // if(mesh->vertices[v].prev == null) // { // if(mesh->vertices[v].next != null) // { // Vertex i; // float count = 0.0f; // float p[3] = { 0 }; // for(i=mesh->vertices+v;i;i=i->next) // { // count += 1.0f; // p[0] += i.getLocation()[0]; // p[1] += i.getLocation()[1]; // p[2] += i.getLocation()[2]; // } // // assert(count > 0.0f); // count = 1.0f / count; // p[0] *= count; // p[1] *= count; // p[2] *= count; // // i = mesh->vertices+v; // while(i) // { // int has_more = 0; // Vertex k = i; // i = i->next; // do // { // has_more = // (k+1) < mesh->vertices+mesh->vertex_count && // compare_points(k.getLocation(),(k+1).getLocation()) == 0; // // k.getLocation()[0] = p[0]; // k.getLocation()[1] = p[1]; // k.getLocation()[2] = p[2]; // k->prev = null; // k->next = null; // ++k; // } while(has_more); // // } // } // // ++total_after; // } // // printf("BEFORE: %d, AFTER: %d\n", total_before, total_after); // // sort_vertices_3(mesh->vertices,mesh->vertex_count); // // // then re-build ptr indices into faces since we moved vertices // for(v = 0; v < mesh->vertex_count; ++v) // { // mesh->vertices[v].face.vertex[mesh->vertices[v].index] = // mesh->vertices+v; // } // // for(f = 0; f < mesh->face_count; ++f) // { // if(mesh->faces[f].degree == 3) // { // float [] p1 = mesh->faces[f].vertex[0].getLocation(); // float [] p2 = mesh->faces[f].vertex[1].getLocation(); // float [] p3 = mesh->faces[f].vertex[2].getLocation(); // if (compare_points(p1,p2)==0 || // compare_points(p2,p3)==0 || // compare_points(p1,p3)==0) // { // mesh->faces[f].neighbor[0] = // mesh->faces[f].neighbor[1] = // mesh->faces[f].neighbor[2] = // mesh->faces[f].neighbor[3] = null; // } // // } // if(mesh->faces[f].degree == 4) // { // float [] p1 = mesh->faces[f].vertex[0].getLocation(); // float [] p2 = mesh->faces[f].vertex[1].getLocation(); // float [] p3 = mesh->faces[f].vertex[2].getLocation(); // float [] p4 = mesh->faces[f].vertex[3].getLocation(); // // if (compare_points(p1,p2)==0 || // compare_points(p2,p3)==0 || // compare_points(p1,p3)==0 || // compare_points(p3,p4)==0 || // compare_points(p2,p4)==0 || // compare_points(p1,p4)==0) // { // mesh->faces[f].neighbor[0] = // mesh->faces[f].neighbor[1] = // mesh->faces[f].neighbor[2] = // mesh->faces[f].neighbor[3] = null; // } // } // } // // #if DEBUG // validate_vertex_sort_3(mesh); // validate_vertex_links(mesh); // // #if SLOW_CHECKING // { // int i, j; // for(i = 0; i < mesh->vertex_count; ++i) // for(j = 0; j < i; ++j) // { // if(!vec3f_eq(mesh->vertices[i].getLocation(),mesh->vertices[j].getLocation())) // { // if(vec3f_length2(mesh->vertices[i].getLocation(),mesh->vertices[j].getLocation()) // <= CHECK_EPSI2) // mesh->flags |= TINY_RESULTING_GEOM; // } // } // } // #endif // // #endif // // } // // // // Once all creases have been marked, this routine locates all colocated // mesh // // edges going in opposite directions (opposite direction colocated edges // mean // // the faces go in the same direction) that are not already marked as // neighbors // // or creases. If the potential join between faces is too sharp, it is // marked // // as a crease, otherwise the edges are recorded as neighbors of each // other. // // When we are done every polygon edge is a crease or neighbor of // someone. // void finish_creases_and_join(struct Mesh * mesh) // { // int fi; // int i; // Face f; // // for(fi = 0; fi < mesh->poly_count; ++fi) // { // f = mesh->faces+fi; // assert(f->degree >= 3); // for(i = 0; i < f->degree; ++i) // { // if(f->neighbor[i] == null) // { // // CCW(i)/P1 // // / \ The directed edge we want goes FROM i TO ccw. // // / i So p2 = ccw, p1 = i, that is, we want our OTHER // // / \ neighbor to go FROM cw TO CCW // // .---------i/P2 // // Vertex p1 = f->vertex[CCW(f,i)]; // Vertex p2 = f->vertex[ i ]; // Vertex begin, * end, * v; // // // range_for_point(mesh->vertices,mesh->vertex_count,&begin,&end,p1.getLocation()); // range_for_vertex(mesh->vertices,mesh->vertices + // mesh->vertex_count,&begin,&end,p1); // for(v = begin; v != end; ++v) // { // if(v.face == f) // continue; // // // P1/v-----x Normal case - Since p1->p2 is the ideal direction of our // // \ / neighbor, p2 = ccw(v). Thus p1(v) names our edge. // // v / // // \ / // // P2/CCW(V) // // // P1/v-----x Backward winding case - thus p2 is CW from P1, // // \ / and P2 (cw(v) names our edge. // // cw(v) / // // \ / // // P2/CW(V) // // // assert(compare_points(p1.getLocation(),v.getLocation())==0); // // Face n = v.face; // Vertex dst = n->vertex[CCW(n,v.getIndex())]; // #if WANT_INVERTS // Vertex inv = n->vertex[ CW(n,v.getIndex())]; // #endif // if(dst->face.degree > 2) // if(compare_points(dst.getLocation(),p2.getLocation())==0) // { // int ni = v.getIndex(); // assert(f->neighbor[i] == null); // if(n->neighbor[ni] == null) // { // #if WANT_CREASE // if(is_crease(f->normal,n->normal,false)) // { // f->neighbor[i] = null; // n->neighbor[ni] = null; // f->index[i] = -1; // n->index[ni] = -1; // break; // } // else // #endif // { // // v.dst matches p1->p2. We have neighbors. // // Store both - avoid half the work when we get to our neighbor. // f->neighbor[i] = n; // n->neighbor[ni] = f; // f->index[i] = ni; // n->index[ni] = i; // f->flip[i] = 0; // n->flip[ni] = 0; // break; // } // } // } // #if WANT_INVERTS // if(inv.face.degree > 2) // if(compare_points(inv.getLocation(),p2.getLocation())==0) // { // int ni = CW(v.face,v.getIndex()); // assert(f->neighbor[i] == null); // if(n->neighbor[ni] == null) // { // #if WANT_CREASE // if(is_crease(f->normal,n->normal,true)) // { // f->neighbor[i] = null; // n->neighbor[ni] = null; // f->index[i] = -1; // n->index[ni] = -1; // break; // } // else // #endif // { // // v.dst matches p1->p2. We have neighbors. // // Store both - avoid half the work when we get to our neighbor. // f->neighbor[i] = n; // n->neighbor[ni] = f; // f->index[i] = ni; // n->index[ni] = i; // f->flip[i] = 1; // n->flip[ni] = 1; // break; // } // } // } // #endif // // } // } // if(f->neighbor[i] == null) // { // f->neighbor[i] = null; // f->index[i] = -1; // } // } // } // // #if DEBUG // validate_neighbors(mesh); // #endif // } // // Utility function: given a vertex (for a specific face) this // returns a relative weighting for smoothing based on the normal // of this tri. This is doen using trig - the angle that the triangle // circulates around the vertex defines its contribution to smoothing. // This ensures subdivision of triangles produces weights that sum to be // equal to the original face, and thus subdivision doesn't change our // normals (which is what we would hope for). public static float weight_for_vertex(Vertex v) { return 1.0f; // Vertex prev = v.face.vertex[CCW(v.face, v.getIndex())]; // Vertex next = v.face.vertex[CW(v.face, v.getIndex())]; // float v1[], v2[], d; // v1 = new float[3]; // v2 = new float[3]; // // vec3f_diff(v1, v.getLocation(), prev.getLocation()); // vec3f_diff(v2, v.getLocation(), next.getLocation()); // vec3f_normalize(v1); // vec3f_normalize(v2); // // d = vec3f_dot(v1, v2); // if (d > 1.0f) // d = 1.0f; // if (d < -1.0f) // d = -1.0f; // return (float) Math.acos(d); } // // // Once all neighbors have been found, this routine calculates the // // actual per-vertex smooth normals. This is done by circulating // // each vertex (via its neighbors) to find all contributing triangles, // // computing a weighted average (from for each triangle) and applying // // the new averaged normal to all participating vertices. // // // // A few key points: // // - Circulation around the vertex only goes by neigbhor. So creases // // (lack of a neighbor) partition the triangles around our vertex into // // adjacent groups, each of which get their own smoothing. // // - This is what makes a 'creased' shape flat-shaded: the creases keep // // us from circulating more than one triangle. // // - We weight our average normal by the angle the triangle spans around // // the vertex, not just a straight average of all participating // triangles. // // We do not want to bias our normal toward the direction of more small // // triangles. // void smooth_vertices(struct Mesh * mesh) // { // int f; // int i; // for(f = 0; f < mesh->poly_count; ++f) // for(i = 0; i < mesh->faces[f].degree; ++i) // { // // For each vertex, we are going to circulate around attached faces, // averaging up our normals. // // Vertex v = mesh->faces[f].vertex[i]; // // // First, go clock-wise around, starting at ourselves, until we loop back // on ourselves (a closed smooth // // circuite - the center vert on a stud top is like this) or we run out // of vertices. // // Vertex c = v; // float N[3] = { 0 }; // int ctr = 0; // int circ_dir = -1; // float w; // do { // ++ctr; // //printf("\tAdd: %f,%f,%f\n",c->normal[0],c->normal[1],c->normal[2]); // // w = weight_for_vertex(c); // // if(vec3f_dot(v.face.normal,c->face.normal) > 0.0) // { // N[0] += w*c->face.normal[0]; // N[1] += w*c->face.normal[1]; // N[2] += w*c->face.normal[2]; // } // else // { // N[0] -= w*c->face.normal[0]; // N[1] -= w*c->face.normal[1]; // N[2] -= w*c->face.normal[2]; // } // // c = circulate_any(c,&circ_dir); // // } while(c != null && c != v); // // // Now if we did NOT make it back to ourselves it means we are a // disconnected circulation. For example // // a semi-circle fan's center will do this if we start from a middle tri. // // Circulate in the OTHER direction, skipping ourselves, until we run // out. // // if(c != v) // { // circ_dir = 1; // c = circulate_any(v,&circ_dir); // while(c) // { // ++ctr; // //printf("\tAdd: %f,%f,%f\n",c->normal[0],c->normal[1],c->normal[2]); // w = weight_for_vertex(c); // if(vec3f_dot(v.face.normal,c->face.normal) > 0.0) // { // N[0] += w*c->face.normal[0]; // N[1] += w*c->face.normal[1]; // N[2] += w*c->face.normal[2]; // } // else // { // N[0] -= w*c->face.normal[0]; // N[1] -= w*c->face.normal[1]; // N[2] -= w*c->face.normal[2]; // } // // c = circulate_any(c,&circ_dir); // // // Invariant: if we did NOT close-loop up top, we should NOT close-loop // down here - that would imply // // a triangulation where our neighbor info was assymetric, which would be // "bad". // assert(c != v); // } // } // // vec3f_normalize(N); // //printf("Final: %f %f %f\t%f %f %f (%d)\n",v.getLocation()[0],v.getLocation()[1], // v.getLocation()[2], N[0],N[1],N[2], ctr); // v.normal[0] = N[0]; // v.normal[1] = N[1]; // v.normal[2] = N[2]; // #if DEBUG_SHOW_NORMALS_AS_COLOR // v.color[0] = N[0] * 0.5 + 0.5; // v.color[1] = N[1] * 0.5 + 0.5; // v.color[2] = N[2] * 0.5 + 0.5; // v.color[3] = 1.0f; // #endif // // } // } // // // This routine merges vertices that have the same complete (10-float) // // value to optimize down the size of geometry in VRAM. We do this // // by resorting by all components and then recognizing adjacently equal // // vertices. This routine rebuilds the ptrs from triangles so that all // // faces see the 'shared' vertex. By convention the first of a group // // of equal vertices in the vertex array is the one we will keep/use. // void merge_vertices(struct Mesh * mesh) // { // // Once smoothing is done, indexing the mesh is actually pretty easy: // // First, we re-sort the vertex list; now that our normals and colors // // are consolidated, equal-value vertices in the final mesh will pool // // together. // // // // Then (having moved our vertices) we need to rebuild the face.vertex // // pointers...when we do this, we simply use the FIRST vertex among // // equals for every face. // // // // The result is that for every redundent vertex, every face will // // agree on which ptr to use. This means that we can build an indexed // // mesh off of the ptrs and get maximum sharing. // // int v; // // int unique = 0; // Vertex first_of_equals = mesh->vertices; // // // Resort according ot our xyz + normal + color // sort_vertices_10(mesh->vertices,mesh->vertex_count); // // // Re-set the tri ptrs again, but...for each IDENTICAL source vertex, use // the FIRST of them as the ptr // for(v = 0; v < mesh->vertex_count; ++v) // { // if(compare_vertices(first_of_equals, mesh->vertices+v) != 0) // { // first_of_equals = mesh->vertices+v; // } // mesh->vertices[v].face.vertex[mesh->vertices[v].index] = first_of_equals; // if(mesh->vertices+v == first_of_equals) // { // mesh->vertices[v].index = -1; // ++unique; // } // else // mesh->vertices[v].index = -2; // } // // #if DEBUG // validate_vertex_sort_10(mesh); // #endif // mesh->unique_vertex_count = unique; // //printf("Before: %d vertices, after: %d\n", mesh->vertex_count, unique); // } // // // This returns the final counts for vertices and indices in a mesh - // after merging, // // subdivising, etc. our original counts may be changed, so clients need // to know // // how much VBO space to allocate. // void get_final_mesh_counts(struct Mesh * m, int []total_vertices,int // []total_indices) // { // *total_vertices = m->unique_vertex_count; // *total_indices = m->vertex_count; // } // // // This cleans our mesh, deallocating all internal memory. // void destroy_mesh(struct Mesh * mesh) // { // int f,i; // #if DEBUG // #if SLOW_CHECKING // if(mesh->flags & TINY_INITIAL_TRIANGLE) // printf("ERROR: TINY INITIAL TRIANGLE.\n"); // if(mesh->flags & TINY_RESULTING_GEOM) // printf("ERROR: TINY RESULTING GEOMETRY.\n"); // #endif // #endif // // destroy_rtree(mesh->index); // // for(f = 0; f < mesh->face_count; ++f) // { // Face fp = mesh->faces+f; // for(i = 0; i < fp->degree; ++i) // { // struct VertexInsert * tj, *k; // tj = fp->t_list[i]; // while(tj) // { // k = tj; // tj = tj->next; // free(k); // } // } // } // // free(mesh->vertices); // free(mesh->faces); // free(mesh); // } // // // // // #pragma mark - // //============================================================================== // // T JUNCTION REMOVAL // //============================================================================== // // // This code attempts to remove "T" junctions from a mesh. Since an ASCII // // picture is worth 1000 words... // // // // B B // // /|\ /|\ // // / | \ / | \ // // A C--E -> A--C--E // // \ | / \ | / // // \|/ \|/ // // D D // // // // Given 3 triangles ADB, BCE, and CDE, we have a T junction at "C" - the // vertex C // // is colinear with hte edge DB (as part of ADB), and this is bad. This // is bad // // because: (1) C's normal contributions won't include ADB (since C is // not a vertex // // of ADB) and (2) we may get a cracking artifact at C from the graphics // card. // // // // We try to fix this by subdividing DB at C to produce two triangles ADC // and ACB. // // // // IMHO: T junction removal sucks. It's hard to set up the heuristics to // get good // // junction removal, huge numbers of tiny triangles can be added, and the // models // // that need it are usually problematic enough that it doesn't work, // while slowing // // down smoothing and increasing vertex count. I recommend that clients // _not_ use // // this functionality - rather T junctions should probably be addressed // by: // // // // 1. Fixing the source parts that cause T junctions and // // 2. Aggressively adopting textures for complex patterned parts. // // // // // // When we are looking for T junctions, we use this structure to // 'remember' which // // edge we are working on from the R-tree visitor. // // struct t_finder_info_t { // int split_quads; // The number of quads that have been split. Each quad // with a // // subdivision must be triangulated, changing our face count, so // // we have to track this. // int inserted_pts; // Number of points inserted into edges - this // increases triangle // // count so we must track it. // Vertex v1; // Start and end vertices of the edge we are testing. // Vertex v2; // Face f; // The face that v1/v2 belong to. // int i; // The side index i of face f that we are tracking, e.g. // f->vertices[i] == v1 // float line_dir[3]; // A normalized direction vector from v1 to v2, used // to order the intrusions. // }; // // // // This is the call back that gets called once for each vertex V that // _might_ be near an edge (e.g. // // via a bounding box test). We project the point onto the line and see // how far the intruding point // // is from the projection on the line. If the point is close, it's a T // junction and we record it // // on a linked list ?or this side, in order of distanec along the line. // // // // Since (1) duplicate vertices are not processed and (2) near-duplicate // vertices were removed long ago // // and (3) the bounding box ensures that our point is 'within' the line // segment's span, any near-line // // point _is_ a T. // void visit_possible_t_junc(Vertex v, void * ref) // { // struct t_finder_info_t * info = (struct t_finder_info_t *) ref; // assert(!vec3f_eq(info.getLocation()_v1,info.getLocation()_v2)); // if(!vec3f_eq(v.getLocation(),info.getLocation()_v1) && // !vec3f_eq(v.getLocation(),info.getLocation()_v2)) // if(in_between_line(info.getLocation()_v1,v.getLocation(),info.getLocation()_v2)) // { // float proj_p[3]; // proj_onto_line(proj_p, info.getLocation()_v1,info->line_dir, // v.getLocation()); // // float dist2_lat = vec3f_length2(v.getLocation(), proj_p); // float dist2_lon = vec3f_length2(v.getLocation(), info.getLocation()_v1); // // if (dist2_lat < EPSI2) // { // assert(info->f->degree == 4 || info->f->degree == 3); // if(info->f->degree == 4) // if(info->f->t_list[0] == null && // info->f->t_list[1] == null && // info->f->t_list[2] == null && // info->f->t_list[3] == null) // ++info->split_quads; // ++info->inserted_pts; // struct VertexInsert ** prev = &info->f->t_list[info->i]; // // while(*prev && (*prev)->dist < dist2_lon) // prev = &(*prev)->next; // // struct VertexInsert * vi = (struct VertexInsert *) malloc(sizeof(struct // VertexInsert)); // vi->dist = dist2_lon; // vi->vert = v; // vi->next = *prev; // *prev = vi; // // // printf("possible T %f: %f,%f,%f (%f,%f,%f -> %f,%f,%f)\n", // // sqrtf(dist2_lon), // // v.getLocation()[0],v.getLocation()[1],v.getLocation()[2], // // // info.getLocation()_v1[0],info.getLocation()_v1[1],info.getLocation()_v1[2], // // // info.getLocation()_v2[0],info.getLocation()_v2[1],info.getLocation()_v2[2]); // } // } // } // // // Given a convex polygon (specified by an interleaved XYZ array "poly" // and pt_count points, this routine // // cuts down the degree of the polygon by cutting off an 'ear' (that is, // a non-reflex vertex). The ear is // // added as a triangle, and the polygon loses a vertex. This is done by // cutting off the sharpest corners // // first. // // // // BEFORE: AFTER // // // // A--B--C A--B // // | | | \ // // | | | \ // // D E D E // // | | | | // // F--G--H F--G--H // // // // (BEC is added as a new trinagle.) // // // }