using System; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; // Based on: // Progressive Mesh type Polygon Reduction Algorithm // by Stan Melax (c) 1998 // http://www.melax.com/polychop/ namespace NWH.DWP2.MeshDecimation { public class MeshDecimate { public float ratio = 0.5f; public float smoothAngle = 45.0f; public bool lockSelPoint = true; public List selectedVertices = new List(); public bool bRecalculateNormals; public float lodDataSize; public Vector3[] finalVertices; public Vector3[] finalNormals; public Vector2[] finalUVs; public int[] finalTriangles; public bool preCalculateDone; public int lastTarget; private float smoothAngleDot; private Tri[] myTriangles; private Vert[] myLODVertices; private History[] collapseHistory; private List cache = new List(); private int cacheSize; private int[] triOrder; private int[] originalTriangles; private Vector3[] originalVertices; private Vector2[] originalUVs; private Vector3[] originalNormals; private int[] sharedTriangles; private Vector3[] sharedVertices; private int currentcnt; private int searchIndex; private float ComputeEdgeCollapseCosts(Vert u, Vert v) { int i; int j; Tri faceU; Tri faceV; float edgelength = (v.position - u.position).sqrMagnitude; float cost = 0; // find the "vFaces" triangles that are on the edge uv List vFaces = new List(); int uFaceCount = u.face.Count; for (i = 0; i < uFaceCount; ++i) { faceU = u.face[i]; if (faceU.HasVertex(v)) { vFaces.Add(faceU); } } // use the triangle facing most away from the sides // to determine our curvature term int vFaceCount = vFaces.Count; for (i = 0; i < uFaceCount; ++i) { float mindot = 1; // curve for face i and closer side to it faceU = u.face[i]; Vector3 faceN = faceU.normal; for (j = 0; j < vFaceCount; ++j) { // use dot product of face normals. '^' defined in vector faceV = vFaces[j]; Vector3 ns = faceV.normal; float dot = (1 - (faceN.x * ns.x + faceN.y * ns.y + faceN.z * ns.z)) * 0.5f; if (dot < mindot) { mindot = dot; } } if (mindot > cost) { cost = mindot; } } if (u.IsBorder() && vFaceCount > 1) { cost = 1.0f; } // texture UV check // if neighbor face has different uv // means that shouldn't be collapsed. // set its priority as higher cost. int found = 0; for (i = 0; i < uFaceCount; ++i) { faceU = u.face[i]; Vector2 uv = faceU.uvAt(u); for (j = 0; j < vFaceCount; ++j) { faceV = vFaces[j]; if (uv == faceV.uvAt(u)) { break; } } if (j == vFaceCount) { ++found; } } // all neighbor faces share same uv // so set u as higher cost. if (found > 0) { cost = 1.0f; } if (u.selected && lockSelPoint) { cost = 6553.5f; } // the more coplanar the lower the curvature term // cost 0 means u and v are on the same plane. return edgelength * cost; } private void ComputeEdgeCostAtVertex(Vert v) { // compute the edge collapse cost for all edges that start // from vertex v. Since we are only interested in reducing // the object by selecting the min cost edge at each step, we // only cache the cost of the least cost edge at this vertex // (in member variable collapse) as well as the value of the // cost (in member variable cost). if (v.neighbor.Count == 0) { // v doesn't have neighbors so it costs nothing to collapse v.collapse = null; v.cost = 0; //-0.01f; return; } v.cost = 65535; v.collapse = null; // search all neighboring edges for "least cost" edge int neighborCount = v.neighbor.Count; float cost; for (int i = 0; i < neighborCount; ++i) { cost = ComputeEdgeCollapseCosts(v, v.neighbor[i]); if (cost < v.cost) { v.collapse = v.neighbor[i]; // candidate for edge collapse v.cost = cost; // cost of the collapse } } } private void ComputeAllEdgeCollapseCosts() { // For all the edges, compute the difference it would make // to the model if it was collapsed. The least of these // per vertex is cached in each vertex object. int count = myLODVertices.Length; for (int i = 0; i < count; ++i) { Vert v = myLODVertices[i]; ComputeEdgeCostAtVertex(v); cache.Insert(i, v); } } private void UnCollapse(History his) { int i; int n; Tri t; List l = his.removedTriangles; n = l.Count; for (i = 0; i < n; ++i) { myTriangles[l[i]].deleted = false; } List list = his.replacedVertex; n = list.Count; for (i = 0; i < n; ++i) { ArrayList tmp = list[i]; t = myTriangles[(int) tmp[0]]; int changedIndex = (int) tmp[1]; if (changedIndex == 0) { t.v0 = myLODVertices[(int) tmp[2]]; t.vn0 = (Vector3) tmp[3]; t.uv0 = (Vector2) tmp[4]; } else if (changedIndex == 1) { t.v1 = myLODVertices[(int) tmp[2]]; t.vn1 = (Vector3) tmp[3]; t.uv1 = (Vector2) tmp[4]; } else { t.v2 = myLODVertices[(int) tmp[2]]; t.vn2 = (Vector3) tmp[3]; t.uv2 = (Vector2) tmp[4]; } } } private void Collapse(History his) { int i; int n; Tri t; List l = his.removedTriangles; n = l.Count; for (i = 0; i < n; ++i) { myTriangles[l[i]].deleted = true; } List list = his.replacedVertex; n = list.Count; for (i = 0; i < n; ++i) { ArrayList tmp = list[i]; t = myTriangles[(int) tmp[0]]; int changedIndex = (int) tmp[1]; if (changedIndex == 0) { t.v0 = myLODVertices[(int) tmp[5]]; t.vn0 = (Vector3) tmp[6]; t.uv0 = (Vector2) tmp[7]; } else if (changedIndex == 1) { t.v1 = myLODVertices[(int) tmp[5]]; t.vn1 = (Vector3) tmp[6]; t.uv1 = (Vector2) tmp[7]; } else { t.v2 = myLODVertices[(int) tmp[5]]; t.vn2 = (Vector3) tmp[6]; t.uv2 = (Vector2) tmp[7]; } } } private void CollapseTest() { Vert u = cache[searchIndex++]; Vert v = u.collapse; // which Vert will be collapsed. History his = new History(); collapseHistory[currentcnt - 1] = his; // u is a vertex all by itself so just delete it if (v != null && v.deleted) { u.RemoveVert(); return; } if (v == null) { u.RemoveVert(); return; } int i; int j; Tri uFace; Tri vFace; int vFaceCount; int neighborCount = u.neighbor.Count; Vert[] neighbors = new Vert[neighborCount]; int count = u.face.Count; // make tmp a list of all the neighbors of u for (i = 0; i < neighborCount; ++i) { neighbors[i] = u.neighbor[i]; } // make a list and add face to the list if it has v. List vFaces = new List(); for (i = 0; i < count; ++i) { uFace = u.face[i]; if (uFace.HasVertex(v)) { vFaces.Add(uFace); } } vFaceCount = vFaces.Count; // delete triangles on edge uv: for (i = u.face.Count - 1; i >= 0; --i) { try { uFace = u.face[i]; if (uFace.HasVertex(v)) { uFace.RemoveTriangle(his); } } catch { } } // update remaining triangles to have v instead of u Vector2 u_uv; Vector2 foundUV = new Vector2(); Vector3 foundVN = new Vector3(); for (i = u.face.Count - 1; i >= 0; --i) { uFace = u.face[i]; if (!uFace.deleted) { u_uv = uFace.uvAt(u); for (j = 0; j < vFaceCount; ++j) { vFace = vFaces[j]; if (u_uv == vFace.uvAt(u)) { foundUV = vFace.uvAt(v); foundVN = vFace.normalAt(v); break; } } uFace.ReplaceVertex(u, v, foundUV, foundVN, his); } } u.RemoveVert(); // recompute the edge collapse costs in neighborhood Vert neighbor; float oldCost; for (i = 0; i < neighborCount; ++i) { neighbor = neighbors[i]; oldCost = neighbor.cost; ComputeEdgeCostAtVertex(neighbor); if (oldCost > neighbor.cost) { SortLeft(neighbor); } else { SortRight(neighbor); } } } private void SortRight(Vert v) { int cacheIndex = cache.IndexOf(v); if (cacheIndex == cacheSize - 1) { return; } float cost = v.cost; Vert c2 = cache[cacheIndex + 1]; if (cost == c2.cost) { return; } int maxIndex = cacheSize - 2; while (cost > c2.cost && cacheIndex < maxIndex) { cache[cacheIndex++] = c2; c2 = cache[cacheIndex + 1]; } if (cost > c2.cost) { cache[cacheIndex++] = c2; } cache[cacheIndex] = v; } private void SortLeft(Vert v) { int cacheIndex = cache.IndexOf(v); if (cacheIndex == searchIndex) { return; } float cost = v.cost; Vert c2 = cache[cacheIndex - 1]; if (cost == c2.cost) { return; } while (cost < c2.cost && cacheIndex > searchIndex + 2) { cache[cacheIndex--] = c2; c2 = cache[cacheIndex - 1]; } if (cost < c2.cost) { cache[cacheIndex--] = c2; } cache[cacheIndex] = v; } public void PreCalculate(Mesh tmpMesh) { int i; int j; smoothAngleDot = 1 - smoothAngle / 90.0f; int[] tris = tmpMesh.triangles; originalTriangles = tmpMesh.triangles; originalVertices = tmpMesh.vertices; if (tmpMesh.uv.Length > 0) { originalUVs = tmpMesh.uv; } else { List uvs = new List(); foreach (Vector2 nr in tmpMesh.normals) { uvs.Add(nr); } originalUVs = uvs.ToArray(); } originalNormals = tmpMesh.normals; int triNum = tris.Length; int vertNum = originalVertices.Length; List newVertices = new List(); int n; int foundAt = -1; int indice; Vector3 v; for (i = 0; i < triNum; ++i) { indice = tris[i]; v = originalVertices[indice]; n = newVertices.Count; foundAt = -1; for (j = 0; j < n; ++j) { if (newVertices[j] == v) { foundAt = j; break; } } if (foundAt != -1) { tris[i] = foundAt; } else { tris[i] = n; newVertices.Insert(n, v); } } sharedTriangles = tris; sharedVertices = newVertices.ToArray(); myTriangles = new Tri[sharedTriangles.Length / 3]; myLODVertices = new Vert[sharedVertices.Length]; ComputeProgressiveMesh(); preCalculateDone = true; // calculate triangle remove order triOrder = new int[myTriangles.Length]; n = collapseHistory.Length; int cnt = 0; for (i = 0; i < n; ++i) { History his = collapseHistory[i]; List list = his.removedTriangles; int m = list.Count; for (j = 0; j < m; ++j) { triOrder[cnt++] = list[j]; } } } public void Calculate(Mesh tmpMesh) { ProgressiveMesh(ratio); int i; int j; int foundAt = -1; Vector3 v = new Vector3(); Vector3 vn = new Vector3(); Vector3 dvn = new Vector3(); Vector2 vuv = new Vector2(); //History his = new History(); int cnt = 0; int vertsCount = myLODVertices.Length; int trisCount = myTriangles.Length; int reducedTriCount = 0; foreach (Tri t in myTriangles) { if (t.deleted) { continue; } ++reducedTriCount; } int minTriCount = reducedTriCount * 3; int[] tris = new int[minTriCount]; Vector3[] verts = new Vector3[minTriCount]; Vector2[] uvs = new Vector2[minTriCount]; Vector3[] norms = new Vector3[minTriCount]; int[] indices = new int[minTriCount]; for (i = 0; i < reducedTriCount; ++i) { Tri tri = myTriangles[triOrder[i]]; int cnt1 = cnt + 1; int cnt2 = cnt + 2; Vert v0 = tri.v0; Vert v1 = tri.v1; Vert v2 = tri.v2; verts[cnt] = v0.position; verts[cnt1] = v1.position; verts[cnt2] = v2.position; tris[cnt] = cnt; tris[cnt1] = cnt1; tris[cnt2] = cnt2; uvs[cnt] = tri.uv0; uvs[cnt1] = tri.uv1; uvs[cnt2] = tri.uv2; norms[cnt] = tri.vn0; norms[cnt1] = tri.vn1; norms[cnt2] = tri.vn2; indices[cnt] = tri.defaultIndex0; indices[cnt1] = tri.defaultIndex1; indices[cnt2] = tri.defaultIndex2; cnt += 3; } int triNum = tris.Length; List newVertices = new List(); List newUVs = new List(); List newNormals = new List(); List newDNormals = new List(); if (bRecalculateNormals) { for (i = 0; i < triNum; ++i) { v = verts[i]; vuv = uvs[i]; vn = norms[i]; int n = newVertices.Count; foundAt = -1; for (j = 0; j < n; ++j) { if (newVertices[j] == v && newUVs[j] == vuv && Vector3.Dot(newNormals[j], vn) > smoothAngleDot) { foundAt = j; break; } } if (foundAt != -1) { tris[i] = foundAt; } else { tris[i] = n; newVertices[n] = v; newUVs[n] = vuv; newNormals[n] = vn; newDNormals[n] = dvn; } } } else { for (i = 0; i < triNum; ++i) { v = verts[i]; vuv = uvs[i]; vn = norms[i]; dvn = originalNormals[indices[i]]; int n = newVertices.Count; foundAt = -1; for (j = 0; j < n; ++j) { if (newVertices[j] == v && newUVs[j] == vuv && newDNormals[j] == dvn) { foundAt = j; break; } } if (foundAt != -1) { tris[i] = foundAt; } else { tris[i] = n; newVertices.Insert(n, v); newUVs.Insert(n, vuv); newNormals.Insert(n, vn); newDNormals.Insert(n, dvn); } } } finalVertices = newVertices.ToArray(); finalNormals = newNormals.ToArray(); finalUVs = newUVs.ToArray(); finalTriangles = tris; } private void ComputeProgressiveMesh() { int i; int j; int n; Tri t; int vertexCount = sharedVertices.Length; int triangleCount = sharedTriangles.Length; Array.Clear(myLODVertices, 0, myLODVertices.Length); for (i = 0; i < vertexCount; ++i) { Vector3 dv = sharedVertices[i]; bool sel = false; n = selectedVertices.Count; for (j = 0; j < n; ++j) { if (selectedVertices[j] == dv) { sel = true; break; } } myLODVertices[i] = new Vert(dv, i, sel); } // new myTris Array.Clear(myTriangles, 0, myTriangles.Length); int cnt = 0; for (i = 0; i < triangleCount; i += 3) { t = new Tri(cnt, myLODVertices[sharedTriangles[i]], myLODVertices[sharedTriangles[i + 1]], myLODVertices[sharedTriangles[i + 2]], originalUVs[originalTriangles[i]], originalUVs[originalTriangles[i + 1]], originalUVs[originalTriangles[i + 2]]); t.SetDefaultIndices(originalTriangles[i], originalTriangles[i + 1], originalTriangles[i + 2]); if (bRecalculateNormals) { t.vn0 = t.vn1 = t.vn2 = t.normal; } else { t.vn0 = originalNormals[originalTriangles[i]]; t.vn1 = originalNormals[originalTriangles[i + 1]]; t.vn2 = originalNormals[originalTriangles[i + 2]]; } myTriangles[cnt] = t; ++cnt; } cache = new List(); cacheSize = vertexCount; if (bRecalculateNormals) { RecalculateNormal(); // set normals for vertex. } ComputeAllEdgeCollapseCosts(); // cache all edge collapse costs //System.Array.Sort(cache, myComparer); // lower cost to the left. cache = cache.OrderBy(p => p.cost).ToList(); collapseHistory = new History[vertexCount]; currentcnt = myLODVertices.Length + 1; searchIndex = 0; while (--currentcnt > 0) { CollapseTest(); } // LOD Data size calculation n = collapseHistory.Length; int tmpBytes = 0; History tmpHis; for (i = 0; i < n; ++i) { tmpHis = collapseHistory[i]; tmpBytes += ( tmpHis.removedTriangles.Count * 2 + tmpHis.replacedVertex.Count * 14 ) * 4; } lodDataSize = tmpBytes; } private void ProgressiveMesh(float ratio) { int i; int target = Mathf.FloorToInt(ratio * sharedVertices.Length); if (lastTarget < target) { for (i = lastTarget; i < target; ++i) { UnCollapse(collapseHistory[i]); } } else { for (i = lastTarget - 1; i >= target; --i) { Collapse(collapseHistory[i]); } } lastTarget = target; } private void RecalculateNormal() { int n = myTriangles.Length; for (int i = 0; i < n; ++i) { Tri f = myTriangles[i]; if (f.deleted) { continue; } f.RecalculateAvgNormals(smoothAngleDot); } } } }