/// <summary> /// Simplifies the mesh to a desired quality. /// </summary> /// <param name="quality">The target quality (between 0 and 1).</param> public void SimplifyMesh(float quality) { quality = Mathf.Clamp01(quality); int deletedTris = 0; ResizableArray <bool> deleted0 = new ResizableArray <bool>(20); ResizableArray <bool> deleted1 = new ResizableArray <bool>(20); var triangles = this.triangles.Data; int triangleCount = this.triangles.Length; int startTrisCount = triangleCount; var vertices = this.vertices.Data; int targetTrisCount = Mathf.RoundToInt(triangleCount * quality); for (int iteration = 0; iteration < 100; iteration++) { if ((startTrisCount - deletedTris) <= targetTrisCount) { break; } // Update mesh once in a while if ((iteration % 5) == 0) { UpdateMesh(iteration); triangles = this.triangles.Data; triangleCount = this.triangles.Length; vertices = this.vertices.Data; } // Clear dirty flag for (int i = 0; i < triangleCount; i++) { triangles[i].dirty = false; } // All triangles with edges below the threshold will be removed // // The following numbers works well for most models. // If it does not, try to adjust the 3 parameters double threshold = 0.000000001 * Math.Pow(iteration + 3, agressiveness); if (verbose) { Debug.LogFormat("iteration {0} - triangles {1} threshold {2}", iteration, (startTrisCount - deletedTris), threshold); } // Remove vertices & mark deleted triangles RemoveVertexPass(startTrisCount, targetTrisCount, threshold, deleted0, deleted1, ref deletedTris); } CompactMesh(); if (verbose) { Debug.LogFormat("Finished simplification with triangle count {0}", this.triangles.Length); } }
/// <summary> /// Simplifies the mesh without losing too much quality. /// </summary> public void SimplifyMeshLossless() { int deletedTris = 0; ResizableArray <bool> deleted0 = new ResizableArray <bool>(0); ResizableArray <bool> deleted1 = new ResizableArray <bool>(0); var triangles = this.triangles.Data; int triangleCount = this.triangles.Length; int startTrisCount = triangleCount; var vertices = this.vertices.Data; for (int iteration = 0; iteration < 9999; iteration++) { // Update mesh constantly UpdateMesh(iteration); triangles = this.triangles.Data; triangleCount = this.triangles.Length; vertices = this.vertices.Data; // Clear dirty flag for (int i = 0; i < triangleCount; i++) { triangles[i].dirty = false; } // All triangles with edges below the threshold will be removed // // The following numbers works well for most models. // If it does not, try to adjust the 3 parameters double threshold = DoubleEpsilon; if (verbose) { Debug.LogFormat("Lossless iteration {0} - triangles {1}", iteration, triangleCount); } // Remove vertices & mark deleted triangles RemoveVertexPass(startTrisCount, 0, threshold, deleted0, deleted1, ref deletedTris); if (deletedTris <= 0) { break; } deletedTris = 0; } CompactMesh(); if (verbose) { Debug.LogFormat("Finished simplification with triangle count {0}", this.triangles.Length); } }
/// <summary> /// Creates a new mesh simplifier. /// </summary> /// <param name="mesh">The original mesh to simplify.</param> public MeshSimplifier(Mesh mesh) { if (mesh == null) { throw new ArgumentNullException("mesh"); } triangles = new ResizableArray <Triangle>(0); vertices = new ResizableArray <Vertex>(0); refs = new ResizableArray <Ref>(0); Initialize(mesh); }
/// <summary> /// Check if a triangle flips when this edge is removed /// </summary> private bool Flipped(Vector3d p, int i0, int i1, ref Vertex v0, ResizableArray <bool> deleted) { int tcount = v0.tcount; var refs = this.refs.Data; var triangles = this.triangles.Data; var vertices = this.vertices.Data; for (int k = 0; k < tcount; k++) { Ref r = refs[v0.tstart + k]; Triangle t = triangles[r.tid]; if (t.deleted) { continue; } int s = r.tvertex; int id1 = t[(s + 1) % 3]; int id2 = t[(s + 2) % 3]; if (id1 == i1 || id2 == i1) { deleted[k] = true; continue; } Vector3d d1 = vertices[id1].p - p; d1.Normalize(); Vector3d d2 = vertices[id2].p - p; d2.Normalize(); double dot = Vector3d.Dot(ref d1, ref d2); if (System.Math.Abs(dot) > 0.999) { return(true); } Vector3d n; Vector3d.Cross(ref d1, ref d2, out n); n.Normalize(); deleted[k] = false; dot = Vector3d.Dot(ref n, ref t.n); if (dot < 0.2) { return(true); } } return(false); }
private void InitializeVertexAttribute <T>(T[] attributeValues, ref ResizableArray <T> attributeArray) { if (attributeValues != null && attributeValues.Length == vertices.Length) { if (attributeArray == null) { attributeArray = new ResizableArray <T>(attributeValues.Length); } else { attributeArray.Resize(attributeValues.Length); } var arrayData = attributeArray.Data; Array.Copy(attributeValues, 0, arrayData, 0, attributeValues.Length); } else { attributeArray = null; } }
/// <summary> /// Finally compact mesh before exiting. /// </summary> private void CompactMesh() { int dst = 0; var vertices = this.vertices.Data; int vertexCount = this.vertices.Length; for (int i = 0; i < vertexCount; i++) { vertices[i].tcount = 0; } var triangles = this.triangles.Data; int triangleCount = this.triangles.Length; for (int i = 0; i < triangleCount; i++) { var triangle = triangles[i]; if (!triangle.deleted) { triangles[dst++] = triangle; vertices[triangle.v0].tcount = 1; vertices[triangle.v1].tcount = 1; vertices[triangle.v2].tcount = 1; } } this.triangles.Resize(dst); triangles = this.triangles.Data; triangleCount = dst; var vertNormals = (this.vertNormals != null ? this.vertNormals.Data : null); var vertTangents = (this.vertTangents != null ? this.vertTangents.Data : null); var vertUV1 = (this.vertUV1 != null ? this.vertUV1.Data : null); var vertUV2 = (this.vertUV2 != null ? this.vertUV2.Data : null); var vertUV3 = (this.vertUV3 != null ? this.vertUV3.Data : null); var vertUV4 = (this.vertUV4 != null ? this.vertUV4.Data : null); var vertColors = (this.vertColors != null ? this.vertColors.Data : null); var vertBoneWeights = (this.vertBoneWeights != null ? this.vertBoneWeights.Data : null); dst = 0; for (int i = 0; i < vertexCount; i++) { var vert = vertices[i]; if (vert.tcount > 0) { vert.tstart = dst; vertices[i] = vert; if (dst != i) { vertices[dst].p = vert.p; if (vertNormals != null) { vertNormals[dst] = vertNormals[i]; } if (vertTangents != null) { vertTangents[dst] = vertTangents[i]; } if (vertUV1 != null) { vertUV1[dst] = vertUV1[i]; } if (vertUV2 != null) { vertUV2[dst] = vertUV2[i]; } if (vertUV3 != null) { vertUV3[dst] = vertUV3[i]; } if (vertUV4 != null) { vertUV4[dst] = vertUV4[i]; } if (vertColors != null) { vertColors[dst] = vertColors[i]; } if (vertBoneWeights != null) { vertBoneWeights[dst] = vertBoneWeights[i]; } } ++dst; } } for (int i = 0; i < triangleCount; i++) { var triangle = triangles[i]; triangle.v0 = vertices[triangle.v0].tstart; triangle.v1 = vertices[triangle.v1].tstart; triangle.v2 = vertices[triangle.v2].tstart; triangles[i] = triangle; } this.vertices.Resize(dst, true); if (vertNormals != null) { this.vertNormals.Resize(dst, true); } if (vertTangents != null) { this.vertTangents.Resize(dst, true); } if (vertUV1 != null) { this.vertUV1.Resize(dst, true); } if (vertUV2 != null) { this.vertUV2.Resize(dst, true); } if (vertUV3 != null) { this.vertUV3.Resize(dst, true); } if (vertUV4 != null) { this.vertUV4.Resize(dst, true); } if (vertColors != null) { this.vertColors.Resize(dst, true); } if (vertBoneWeights != null) { this.vertBoneWeights.Resize(dst, true); } }
/// <summary> /// Compact triangles, compute edge error and build reference list. /// </summary> /// <param name="iteration">The iteration index.</param> private void UpdateMesh(int iteration) { var triangles = this.triangles.Data; var vertices = this.vertices.Data; int triangleCount = this.triangles.Length; int vertexCount = this.vertices.Length; if (iteration > 0) // compact triangles { int dst = 0; for (int i = 0; i < triangleCount; i++) { var triangle = triangles[i]; if (!triangle.deleted) { if (dst != i) { triangles[dst] = triangle; } dst++; } } this.triangles.Resize(dst); triangles = this.triangles.Data; triangleCount = dst; } // Init Quadrics by Plane & Edge Errors // // required at the beginning ( iteration == 0 ) // recomputing during the simplification is not required, // but mostly improves the result for closed meshes if (iteration == 0) { for (int i = 0; i < vertexCount; i++) { vertices[i].q = new SymmetricMatrix(); } Vector3d n, p0, p1, p2, p10, p20, dummy; SymmetricMatrix sm; for (int i = 0; i < triangleCount; i++) { var triangle = triangles[i]; var vert0 = vertices[triangle.v0]; var vert1 = vertices[triangle.v1]; var vert2 = vertices[triangle.v2]; p0 = vert0.p; p1 = vert1.p; p2 = vert2.p; p10 = p1 - p0; p20 = p2 - p0; Vector3d.Cross(ref p10, ref p20, out n); n.Normalize(); triangles[i].n = n; sm = new SymmetricMatrix(n.x, n.y, n.z, -Vector3d.Dot(ref n, ref p0)); vert0.q += sm; vert1.q += sm; vert2.q += sm; vertices[triangle.v0] = vert0; vertices[triangle.v1] = vert1; vertices[triangle.v2] = vert2; } for (int i = 0; i < triangleCount; i++) { // Calc Edge Error var triangle = triangles[i]; //triangle.area = CalculateArea(triangle.v0, triangle.v1, triangle.v2); triangle.err0 = CalculateError(triangle.v0, triangle.v1, out dummy); triangle.err1 = CalculateError(triangle.v1, triangle.v2, out dummy); triangle.err2 = CalculateError(triangle.v2, triangle.v0, out dummy); triangle.err3 = MathHelper.Min(triangle.err0, triangle.err1, triangle.err2); triangles[i] = triangle; } } // Init Reference ID list for (int i = 0; i < vertexCount; i++) { var vertex = vertices[i]; vertex.tstart = 0; vertex.tcount = 0; vertices[i] = vertex; } for (int i = 0; i < triangleCount; i++) { var triangle = triangles[i]; ++vertices[triangle.v0].tcount; ++vertices[triangle.v1].tcount; ++vertices[triangle.v2].tcount; } int tstart = 0; for (int i = 0; i < vertexCount; i++) { var vertex = vertices[i]; vertex.tstart = tstart; tstart += vertex.tcount; vertex.tcount = 0; vertices[i] = vertex; } // Write References this.refs.Resize(tstart); var refs = this.refs.Data; for (int i = 0; i < triangleCount; i++) { var triangle = triangles[i]; var vert0 = vertices[triangle.v0]; var vert1 = vertices[triangle.v1]; var vert2 = vertices[triangle.v2]; refs[vert0.tstart + vert0.tcount].Set(i, 0); refs[vert1.tstart + vert1.tcount].Set(i, 1); refs[vert2.tstart + vert2.tcount].Set(i, 2); ++vert0.tcount; ++vert1.tcount; ++vert2.tcount; vertices[triangle.v0] = vert0; vertices[triangle.v1] = vert1; vertices[triangle.v2] = vert2; } // Identify boundary : vertices[].border=0,1 if (iteration == 0) { List <int> vcount = new List <int>(); List <int> vids = new List <int>(); for (int i = 0; i < vertexCount; i++) { vertices[i].border = false; } int ofs; int id; for (int i = 0; i < vertexCount; i++) { var vertex = vertices[i]; vcount.Clear(); vids.Clear(); int tcount = vertex.tcount; for (int j = 0; j < tcount; j++) { int k = refs[vertex.tstart + j].tid; Triangle t = triangles[k]; for (k = 0; k < 3; k++) { ofs = 0; id = t[k]; while (ofs < vcount.Count) { if (vids[ofs] == id) { break; } ++ofs; } if (ofs == vcount.Count) { vcount.Add(1); vids.Add(id); } else { ++vcount[ofs]; } } } int vcountCount = vcount.Count; for (int j = 0; j < vcountCount; j++) { if (vcount[j] == 1) { id = vids[j]; vertices[id].border = true; } } } } }
/// <summary> /// Remove vertices and mark deleted triangles /// </summary> private void RemoveVertexPass(int startTrisCount, int targetTrisCount, double threshold, ResizableArray <bool> deleted0, ResizableArray <bool> deleted1, ref int deletedTris) { var triangles = this.triangles.Data; int triangleCount = this.triangles.Length; var vertices = this.vertices.Data; Vertex v0, v1; Vector3d p; for (int i = 0; i < triangleCount; i++) { var t = triangles[i]; if (t.dirty || t.deleted || t.err3 > threshold) { continue; } t.GetErrors(errArr); for (int j = 0; j < 3; j++) { if (errArr[j] > threshold) { continue; } int i0 = t[j]; int i1 = t[(j + 1) % 3]; v0 = vertices[i0]; v1 = vertices[i1]; // Border check if (v0.border != v1.border) { continue; } // If borders should be kept if (keepBorders && (v0.border || v1.border)) { continue; } // Compute vertex to collapse to CalculateError(i0, i1, out p); deleted0.Resize(v0.tcount); // normals temporarily deleted1.Resize(v1.tcount); // normals temporarily // Don't remove if flipped if (Flipped(p, i0, i1, ref v0, deleted0)) { continue; } if (Flipped(p, i1, i0, ref v1, deleted1)) { continue; } // Not flipped, so remove edge v0.p = p; v0.q += v1.q; vertices[i0] = v0; MergeVertices(i0, i1); int tstart = refs.Length; UpdateTriangles(i0, ref v0, deleted0, ref deletedTris); UpdateTriangles(i0, ref v1, deleted1, ref deletedTris); int tcount = refs.Length - tstart; if (tcount <= v0.tcount) { // save ram if (tcount > 0) { var refsArr = refs.Data; Array.Copy(refsArr, tstart, refsArr, v0.tstart, tcount); } } else { // append vertices[i0].tstart = tstart; } vertices[i0].tcount = tcount; break; } // Check if we are already done if ((startTrisCount - deletedTris) <= targetTrisCount) { break; } } }
/// <summary> /// Creates a new mesh simplifier. /// </summary> public MeshSimplifier() { triangles = new ResizableArray <Triangle>(0); vertices = new ResizableArray <Vertex>(0); refs = new ResizableArray <Ref>(0); }