/* * ==== Fast Quadratic Mesh Simplification ==== * Ported and extended from https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/ * * Typically used for simplifying meshes the 3D reconstruction generates. * */ using Microsoft.Xna.Framework; using System; using System.Collections.Generic; namespace FSO.Common.MeshSimplify { public class Simplify { public List triangles = new List(); public List vertices = new List(); public List refs = new List(); public void simplify_mesh(int target_count, double agressiveness = 7, int iterations = 100) { //for (int i=0; i(); var deleted1 = new List(); int triangle_count = triangles.Count; for (int iteration=0; iteration threshold) continue; if (t.deleted) continue; if (t.dirty) continue; for (int j = 0; j < 3; j++) { if (t.err[j] < threshold) { int i0 = t.v[j]; var v0 = vertices[i0]; int i1 = t.v[(j + 1) % 3]; var v1 = vertices[i1]; // Border check if (v0.border != v1.border) continue; // Compute vertex to collapse to Vector3 p = Vector3.Zero; calculate_error(i0, i1, ref p); deleted0.Clear(); // normals temporarily for (int n = 0; n < v0.tcount; n++) deleted0.Add(0); deleted1.Clear(); // normals temporarily for (int n = 0; n < v1.tcount; n++) deleted1.Add(0); // dont remove if flipped if (flipped(p, i0, i1, v0, v1, deleted0)) continue; if (flipped(p, i1, i0, v1, v0, deleted1)) continue; // not flipped, so remove edge var vec = v1.p - v0.p; var vec2 = p - v0.p; vec2 /= vec.Length(); vec /= vec.Length(); var lp = Vector3.Dot(vec, vec2); v0.p = p; v0.t = Vector2.Lerp(v0.t, v1.t, lp); v0.q = v1.q + v0.q; int tstart = refs.Count; update_triangles(i0, v0, deleted0, ref deleted_triangles); update_triangles(i0, v1, deleted1, ref deleted_triangles); int tcount = refs.Count - tstart; if (tcount <= v0.tcount) { // save ram for (int tc=0; tc deleted) { int bordercount = 0; for (int k=0; k 0.999) return true; Vector3 n; n = Vector3.Cross(d1, d2); n.Normalize(); deleted[k] = 0; if (Vector3.Dot(n, t.n) < 0.2) return true; } return false; } // Update triangle connections and edge error after a edge is collapsed void update_triangles(int i0, MSVertex v, List deleted, ref int deleted_triangles) { Vector3 p = Vector3.Zero; for (int k = 0; k < v.tcount; k++) { var r = refs[v.tstart + k]; var t = triangles[r.tid]; if (t.deleted) continue; if (k < deleted.Count && deleted[k] > 0) { t.deleted = true; deleted_triangles++; continue; } t.v[r.tvertex] = i0; t.dirty = true; t.err[0] = calculate_error(t.v[0], t.v[1], ref p); t.err[1] = calculate_error(t.v[1], t.v[2], ref p); t.err[2] = calculate_error(t.v[2], t.v[0], ref p); t.err[3] = Math.Min(t.err[0], Math.Min(t.err[1], t.err[2])); refs.Add(r); } } // compact triangles, compute edge error and build reference list void update_mesh(int iteration) { if (iteration > 0) // compact triangles { int dst = 0; for (int i = 0; i vcount = new List(); List vids = new List(); for (int i = 0; i < vertices.Count; i++) vertices[i].border = false; for (int i = 0; i < vertices.Count; i++) { var v = vertices[i]; vcount.Clear(); vids.Clear(); for (int j = 0; j < v.tcount; j++) { int k = refs[v.tstart + j].tid; var t = triangles[k]; for (k = 0; k < 3; k++) { int ofs = 0, id = t.v[k]; while (ofs < vcount.Count) { if (vids[ofs] == id) break; ofs++; } if (ofs == vcount.Count) { vcount.Add(1); vids.Add(id); } else vcount[ofs]++; } } for (int j = 0; j < vcount.Count; j++) { if (vcount[j] == 1) vertices[vids[j]].border = true; } } } } // Finally compact mesh before exiting void compact_mesh() { int dst = 0; for (int i = 0; i < vertices.Count; i++) { vertices[i].tcount = 0; } for (int i = 0; i < triangles.Count; i++) { if (!triangles[i].deleted) { var t = triangles[i]; triangles[dst++] = t; for (int j = 0; j < 3; j++) vertices[t.v[j]].tcount = 1; } } triangles.RemoveRange(dst, triangles.Count - dst); dst = 0; for (int i = 0; i < vertices.Count; i++) { if (vertices[i].tcount > 0) { vertices[i].tstart = dst; vertices[dst].p = vertices[i].p; vertices[dst].t = vertices[i].t; dst++; } } for (int i = 0; i < triangles.Count; i++) { var t = triangles[i]; for (int j = 0; j < 3; j++) t.v[j] = vertices[t.v[j]].tstart; } vertices.RemoveRange(dst, vertices.Count - dst); } // Error between vertex and Quadric double vertex_error(SymmetricMatrix q, double x, double y, double z) { return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z + 2 * q[3] * x + q[4] * y * y + 2 * q[5] * y * z + 2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9]; } // Error for one edge double calculate_error(int id_v1, int id_v2, ref Vector3 p_result) { // compute interpolated vertex SymmetricMatrix q = vertices[id_v1].q + vertices[id_v2].q; bool border = vertices[id_v1].border && vertices[id_v2].border; double error = 0; double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7); if (det != 0 && !border) { // q_delta is invertible p_result.X = (float)(-1 / det * (q.det(1, 2, 3, 4, 5, 6, 5, 7, 8))); // vx = A41/det(q_delta) p_result.Y = (float)(1 / det * (q.det(0, 2, 3, 1, 5, 6, 2, 7, 8))); // vy = A42/det(q_delta) p_result.Z = (float)(-1 / det * (q.det(0, 1, 3, 1, 4, 6, 2, 5, 8))); // vz = A43/det(q_delta) error = vertex_error(q, p_result.X, p_result.Y, p_result.Z); } else { // det = 0 -> try to find best result Vector3 p1 = vertices[id_v1].p; Vector3 p2 = vertices[id_v2].p; Vector3 p3 = (p1 + p2) / 2; double error1 = vertex_error(q, p1.X, p1.Y, p1.Z); double error2 = vertex_error(q, p2.X, p2.Y, p2.Z); double error3 = vertex_error(q, p3.X, p3.Y, p3.Z); error = Math.Min(error1, Math.Min(error2, error3)); if (error1 == error) p_result = p1; if (error2 == error) p_result = p2; if (error3 == error) p_result = p3; } return error; } } }