bool _producesNonManifold(BaseMesh mesh, CollapseInfo ci) { // ... If v0 and v1 don't have nr of commonfaces common neighbours List <Vertex> vertices = mesh.vertices; List <Face> faces = mesh.faces; int common = commonFaces.Count; int tag = mesh.GetUniqueTag(); int count = faces0.Count; int[] faceArray = faces0.array; for (int i = 0; i < count; ++i) { int[] v = faces[faceArray[i]].v; // known to be a valid triangle vertices[v[0]].mark = tag; vertices[v[1]].mark = tag; vertices[v[2]].mark = tag; } count = faces1.Count; faceArray = faces1.array; for (int i = 0; i < count; ++i) { int[] v = faces[faceArray[i]].v; if (vertices[v[0]].mark == tag) { common--; if (common < 0) { return(true); } vertices[v[0]].mark = 0; } if (vertices[v[1]].mark == tag) { common--; if (common < 0) { return(true); } vertices[v[1]].mark = 0; } if (vertices[v[2]].mark == tag) { common--; if (common < 0) { return(true); } vertices[v[2]].mark = 0; } } return(common != 0); }
// the mesh needs to have its edge list and creases calculated public void InitializeCollapsing(MeshEdges _mesh, SimplifyParameters parameters) { mesh = _mesh; _parameters = parameters; // Gain some time by avoiding unecessary calculations if (_mesh.hasBoneWeights == false) { parameters.boneWeightProtection = 0.0f; } if (_mesh.hasVertexColors == false) { parameters.vertexColorProtection = 0.0f; } _calculatePdePerVertex(); // construct heap int numEdges = mesh.edgeCount(); heapNodes = new HeapNode <CollapseInfo> [numEdges]; heap = new MinHeap <CollapseInfo>(); int progressCounter = kProgressGroups; float t = Time.realtimeSinceStartup; for (int i = 0; i < numEdges; ++i) { CollapseInfo pc = new CollapseInfo(); _calculateEdgeCost(i, pc); heapNodes[i] = heap.Insert(new HeapNode <CollapseInfo>(pc.cost, pc)); progressCounter--; if (progressCounter <= 0) { progressCounter = kProgressGroups; if (Time.realtimeSinceStartup - t > kProgressInterval && progressDelegate != null) { t = Time.realtimeSinceStartup; progressDelegate("Initialize Edge " + i + "/" + numEdges, 0.1f * ((float)i) / ((float)numEdges)); } } } // shortcut for fastest calc noPenalties = (parameters.checkTopology == false && parameters.maxEdgesPerVertex == 0 && parameters.preventNonManifoldEdges == false && parameters.boneWeightProtection <= 0.0f && parameters.vertexColorProtection <= 0.0f); //sharpnessLimitSqr = parameters.minTriangleShape*parameters.minTriangleShape; //if (parameters.minTriangleShape < 0.0f) sharpnessLimitSqr = 0.0f; }
//Vector3[] tr = new Vector3[3]; /* * // rchecks result triangles against limit * bool _producesSharpTriangles(Mesh mesh, CollapseInfo ci, float limitSqr) { * for (int k = 0; k < 2; ++k) { * int vindex = ci.vp.v[k]; * List<int> linkedFaces = (k == 0) ? faces0 : faces1; * for (int i = 0; i < linkedFaces.Count; ++i) { * Face f = mesh.faces[linkedFaces[i]]; * if (f.v[0] == vindex) { tr[0] = ci.targetPosition; tr[1] = mesh.vertex(f.v[1]).coords; tr[2] = mesh.vertex(f.v[2]).coords; } * else if (f.v[1] == vindex) { tr[0] = mesh.vertex(f.v[0]).coords; tr[1] = ci.targetPosition; tr[2] = mesh.vertex(f.v[2]).coords; } * else { tr[0] = mesh.vertex(f.v[0]).coords; tr[1] = mesh.vertex(f.v[1]).coords; tr[2] = ci.targetPosition; } * float val = UnityUtils.TriangleCompactnessSqr(tr); * if (val < limitSqr) return true; * } * } * return false; * } */ // Check topology to avoid flipping the mesh by collapsing bool _producesBadTopology(BaseMesh mesh, CollapseInfo ci, int filterTag = 0) { Vector3 p1, origPos, vOpp, faceNormal, normal; List <Vertex> vertices = mesh.vertices; List <Face> faces = mesh.faces; for (int k = 0; k < 2; ++k) { int vindex = ci.vp.v[k]; origPos = vertices[vindex].coords; IndexList linkedFacesList = (k == 0) ? faces0 : faces1; int[] linkedFaces = linkedFacesList.array; for (int i = 0; i < linkedFacesList.Count; ++i) { Face f = faces[linkedFaces[i]]; if (f.mark >= filterTag) { // Construct a plane from opposing sides (p1->p2) && the face normal -> todo make this better (faster) if (f.v[0] == vindex) { p1 = vertices[f.v[1]].coords; vOpp = vertices[f.v[2]].coords - p1; } else if (f.v[1] == vindex) { p1 = vertices[f.v[2]].coords; vOpp = vertices[f.v[0]].coords - p1; } else { p1 = vertices[f.v[0]].coords; vOpp = vertices[f.v[1]].coords - p1; } faceNormal = Vector3.Cross(vOpp, origPos - p1); // the face normal normal = Vector3.Cross(faceNormal, vOpp); // normal of constructed plane if (Vector3.Dot(ci.targetPosition - p1, normal) < 0.0f) { return(true); } } } } return(false); }
// REQUIRES triangulated mesh! public int CollapseVertexPair(CollapseInfo info) { if (topology != MeshTopology.Triangles) { Debug.LogError("KrablMesh: Collapsing a vertex pair requires a triangle mesh"); return(0); } VertexPair pair = info.vp; int vindex0 = pair.v[0]; int vindex1 = pair.v[1]; Vertex vertex0 = vertices[vindex0]; Vertex vertex1 = vertices[vindex1]; int i, j; changeFaces0.Clear(); changeFaces1.Clear(); removeFaces.Clear(); CollectCollapseFacesForVertexPair(pair, changeFaces0, changeFaces1, removeFaces); // Adjust parameters of vertex0 to the new position float ratio1 = info.Ratio(this); // try baricentric projection on all the removeFaces (usually 2) int projFaceIndex = -1; Face projFace = null; int projCorner0 = 0, projCorner1 = 0; Vector3 bari = Vector3.zero; int[] v = null; for (i = 0; i < removeFaces.Count; ++i) { Face f = faces[removeFaces[i]]; v = f.v; bari = UnityUtils.BaricentricProjection(info.targetPosition, vertices[v[0]].coords, vertices[v[1]].coords, vertices[v[2]].coords); if (UnityUtils.AreBaricentricCoordsInsideTriangle(bari)) { projFaceIndex = removeFaces[i]; projFace = f; projCorner0 = projFace.CornerIndexTriangle(vindex0); projCorner1 = projFace.CornerIndexTriangle(vindex1); break; } } // There must not be invalid faces in changeFaces0 or changeFaces1 !!! /* for (i = 0; i < changeFaces0.Count; ++i) if (faces[changeFaces0[i]].valid == false) Debug.LogError("NOOO!"); * for (i = 0; i < changeFaces1.Count; ++i) if (faces[changeFaces1[i]].valid == false) Debug.LogError("NOOO!"); * for (i = 0; i < removeFaces.Count; ++i) if (faces[removeFaces[i]].valid == false) Debug.LogError("NOOO!"); */ // Deal with vertex colors and boneweights. these are per vertex. if (projFace != null) { if (hasVertexColors) { vertex0.color = bari.x * vertices[v[0]].color + bari.y * vertices[v[1]].color + bari.z * vertices[v[2]].color; } if (hasBoneWeights) { vertex0.boneWeight = UnityUtils.BoneWeightBaricentricInterpolation(vertices[v[0]].boneWeight, vertices[v[1]].boneWeight, vertices[v[2]].boneWeight, bari.x, bari.y, bari.z); } } else { if (hasVertexColors) { vertex0.color = Color.Lerp(vertex0.color, vertex1.color, ratio1); } if (hasBoneWeights) { vertex0.boneWeight = UnityUtils.BoneWeightLerp(vertex0.boneWeight, vertex1.boneWeight, ratio1); } } // Determine corner numbers for v0 in changefaces0 and v1 in changefaces1 IndexList corners0 = new IndexList(changeFaces0.Count); for (i = 0; i < changeFaces0.Count; ++i) { corners0[i] = faces[changeFaces0[i]].CornerIndexTriangle(vindex0); } IndexList corners1 = new IndexList(changeFaces1.Count); for (i = 0; i < changeFaces1.Count; ++i) { corners1[i] = faces[changeFaces1[i]].CornerIndexTriangle(vindex1); } #region Face-Dependent Attributes (Vertex normals, uv1, uv2) // NORMALS int count = 0, filterTag = GetUniqueTag(); Vector3 projNormalNew = Vector3.zero; if (projFace != null) { projNormalNew = bari.x * projFace.vertexNormal[0] + bari.y * projFace.vertexNormal[1] + bari.z * projFace.vertexNormal[2]; count = _replaceCornerNormalInFaceGroup(projFace.vertexNormal[projCorner0], projNormalNew, changeFaces0, corners0, filterTag); } if (count < changeFaces0.Count) { // there are faces which cannot use baricentric projection for (j = 0; j < removeFaces.Count; ++j) { if (removeFaces[j] != projFaceIndex) { Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1); Vector3 oldNormal = f2.vertexNormal[c0]; _replaceCornerNormalInFaceGroup(oldNormal, Vector3.Lerp(oldNormal, f2.vertexNormal[c1], ratio1), changeFaces0, corners0, filterTag); } } } count = 0; filterTag = GetUniqueTag(); if (projFace != null) { count = _replaceCornerNormalInFaceGroup(projFace.vertexNormal[projCorner1], projNormalNew, changeFaces1, corners1, filterTag); } if (count < changeFaces1.Count) { // there are faces which cannot use baricentric projection for (j = 0; j < removeFaces.Count; ++j) { if (removeFaces[j] != projFaceIndex) { Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1); Vector3 oldNormal = f2.vertexNormal[c1]; _replaceCornerNormalInFaceGroup(oldNormal, Vector3.Lerp(f2.vertexNormal[c0], oldNormal, ratio1), changeFaces1, corners1, filterTag); } } } if (hasUV1) { count = 0; filterTag = GetUniqueTag(); Vector2 projUV1New = Vector2.zero; if (projFace != null) { projUV1New = bari.x * projFace.uv1[0] + bari.y * projFace.uv1[1] + bari.z * projFace.uv1[2]; count = _replaceCornerUV1InFaceGroup(projFace.uv1[projCorner0], projUV1New, changeFaces0, corners0, filterTag); } if (count < changeFaces0.Count) { // there are faces which cannot use baricentric projection for (j = 0; j < removeFaces.Count; ++j) { if (removeFaces[j] != projFaceIndex) { Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1); Vector2 oldUV1 = f2.uv1[c0]; _replaceCornerUV1InFaceGroup(oldUV1, Vector2.Lerp(oldUV1, f2.uv1[c1], ratio1), changeFaces0, corners0, filterTag); } } } count = 0; filterTag = GetUniqueTag(); if (projFace != null) { count = _replaceCornerUV1InFaceGroup(projFace.uv1[projCorner1], projUV1New, changeFaces1, corners1, filterTag); } if (count < changeFaces1.Count) { // there are faces which cannot use baricentric projection for (j = 0; j < removeFaces.Count; ++j) { if (removeFaces[j] != projFaceIndex) { Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1); Vector2 oldUV1 = f2.uv1[c1]; _replaceCornerUV1InFaceGroup(oldUV1, Vector2.Lerp(f2.uv1[c0], oldUV1, ratio1), changeFaces1, corners1, filterTag); } } } } if (hasUV2) { count = 0; filterTag = GetUniqueTag(); Vector2 projUV2New = Vector2.zero; if (projFace != null) { projUV2New = bari.x * projFace.uv2[0] + bari.y * projFace.uv2[1] + bari.z * projFace.uv2[2]; count = _replaceCornerUV2InFaceGroup(projFace.uv2[projCorner0], projUV2New, changeFaces0, corners0, filterTag); } if (count < changeFaces0.Count) { // there are faces which cannot use baricentric projection for (j = 0; j < removeFaces.Count; ++j) { if (removeFaces[j] != projFaceIndex) { Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1); Vector2 oldUV2 = f2.uv2[c0]; _replaceCornerUV2InFaceGroup(oldUV2, Vector2.Lerp(oldUV2, f2.uv2[c1], ratio1), changeFaces0, corners0, filterTag); } } } count = 0; filterTag = GetUniqueTag(); if (projFace != null) { count = _replaceCornerUV2InFaceGroup(projFace.uv2[projCorner1], projUV2New, changeFaces1, corners1, filterTag); } if (count < changeFaces1.Count) { // there are faces which cannot use baricentric projection for (j = 0; j < removeFaces.Count; ++j) { if (removeFaces[j] != projFaceIndex) { Face f2 = faces[removeFaces[j]]; int c0 = f2.CornerIndexTriangle(vindex0), c1 = f2.CornerIndexTriangle(vindex1); Vector2 oldUV2 = f2.uv2[c1]; _replaceCornerUV2InFaceGroup(oldUV2, Vector2.Lerp(f2.uv2[c0], oldUV2, ratio1), changeFaces1, corners1, filterTag); } } } } #endregion // Move vertex to goal position vertex0.coords = info.targetPosition; // remove faces // Debug.Log("Change faces 1 num: " + changeFaces0.Count); // Debug.Log("Change faces 2 num: " + changeFaces1.Count); // Debug.Log("Remove faces num: " + removeFaces.Count); for (i = 0; i < removeFaces.Count; ++i) { UnlinkFace(removeFaces[i]); } // change vertex on vindex1 faces, update surrounding faces on vindex0 for (i = 0; i < changeFaces1.Count; ++i) { int faceIndex = changeFaces1[i]; Face f = faces[faceIndex]; if (f.valid) { f.ReplaceVertex(vindex1, vindex0); vertex0.linkedFaces.Add(faceIndex); } } // mark vindex1 as invalid vertex1.linkedFaces.Clear(); if (vertex1.valid == true) { numValidVerts--; vertex1.valid = false; } else { Debug.LogError("vindex1 was already invalid"); } return(vindex0); }
// Count number of surrounding faces after the collapse int _vertexDegreeAfterCollapse(BaseMesh mesh, CollapseInfo ci) { return(faces0.Count + faces1.Count); }
void _updateEdgePenalties(int edgeIndex, CollapseInfo cinfo, int movedVertexIndex = -1) { Edge edge = mesh.edges[edgeIndex]; int vindex0 = edge.v[0]; int vindex1 = edge.v[1]; Vertex v0 = mesh.vertices[vindex0]; Vertex v1 = mesh.vertices[vindex1]; faces0.Clear(); faces1.Clear(); commonFaces.Clear(); bool hadPenalty = (cinfo.cost >= kMeshPenaltyMaxEdgesPerVertex); cinfo.cost = cinfo.positionCost; // reset cost // determine the faces involved in the collapse .. mesh.CollectCollapseFacesForVertexPair(mesh.edges[edgeIndex], faces0, faces1, commonFaces); // Penalties int filterTag = 0; if (_parameters.preventNonManifoldEdges && _producesNonManifold(mesh, cinfo)) { cinfo.cost += kMeshPenaltyNonManifold; // largest penalty first } else { if (movedVertexIndex != -1 && hadPenalty == false) { // For cinfos that are not new and had no penalty before, all faces besides the onces connected // to the moved vertex can be skipped. filterTag = mesh.GetUniqueTag(); IndexList linkedFaces = mesh.vertices[movedVertexIndex].linkedFaces; int count = linkedFaces.Count; for (int i = 0; i < count; ++i) { mesh.faces[linkedFaces[i]].mark = filterTag; } } if (_parameters.checkTopology && _producesBadTopology(mesh, cinfo, filterTag)) { // Apply penalties for bad collapses cinfo.cost += kMeshPenaltyBadTopology; } else if (_parameters.maxEdgesPerVertex > 0 && _vertexDegreeAfterCollapse(mesh, cinfo) > _parameters.maxEdgesPerVertex) // Hard coded at 18 for now.. rarely reached, but always check! // Avoid collapses leading to excessive stars (many verts connected to one) { cinfo.cost += kMeshPenaltyMaxEdgesPerVertex; } } // Additional penalties: float val = 0.0f; if (_parameters.boneWeightProtection > 0.0f) { val += UnityUtils.BoneWeightDeltaSqr(v0.boneWeight, v1.boneWeight) * _parameters.boneWeightProtection; } if (_parameters.vertexColorProtection > 0.0f) { val += UnityUtils.ColorDeltaSqr(v0.color, v1.color) * _parameters.vertexColorProtection; } if (val != 0.0f) { cinfo.cost += 0.1f * val * mesh.EdgeLengthSqr(edgeIndex); } }
void _calculateEdgeCost(int edgeIndex, CollapseInfo cinfo) { Edge edge = mesh.edges[edgeIndex]; int vindex0 = edge.v[0]; int vindex1 = edge.v[1]; Vertex v0 = mesh.vertices[vindex0]; Vertex v1 = mesh.vertices[vindex1]; cinfo.vp = edge; PlaneDistanceError pde = pdePerVertex[edge.v[0]] + pdePerVertex[edge.v[1]]; if (_parameters.recalculateVertexPositions) { if (mesh.IsEdgeBorder(edgeIndex) == false && pde.OptimalVertex(ref cinfo.targetPosition)) { cinfo.cost = (float)pde.CalculateError(cinfo.targetPosition); // Debug.Log(">optimal placement"); } else if (pde.OptimalVertexLinear(ref cinfo.targetPosition, v0.coords, v1.coords)) { // the error term is not solvable // Try to find a vert on the line going from v0 to v1 cinfo.cost = (float)pde.CalculateError(cinfo.targetPosition); // Debug.Log(">line placement"); } else { // Choose vert from the two endpoints and the midpoint Vector3 tp = 0.5f * (v0.coords + v1.coords); double error0 = pde.CalculateError(v0.coords); double error1 = pde.CalculateError(v1.coords); double error2 = pde.CalculateError(tp); if (error0 < error1) { if (error0 < error2) { cinfo.targetPosition = v0.coords; cinfo.cost = (float)error0; } else { cinfo.targetPosition = tp; cinfo.cost = (float)error2; } } else { if (error1 < error2) { cinfo.targetPosition = v1.coords; cinfo.cost = (float)error1; } else { cinfo.targetPosition = tp; cinfo.cost = (float)error2; } } } } else { double error0 = pde.CalculateError(v0.coords); double error1 = pde.CalculateError(v1.coords); if (error0 < error1) { cinfo.targetPosition = v0.coords; cinfo.cost = (float)error0; } else { cinfo.targetPosition = v1.coords; cinfo.cost = (float)error1; } } // Choose minimal error point -> bad for border edges which are underdefined if (localizeErrors) { cinfo.cost *= 1.0f / ((float)pde.Factor()); } cinfo.positionCost = cinfo.cost; if (noPenalties == false) { _updateEdgePenalties(edgeIndex, cinfo, -1); } }
public int Collapse(int numEdgesToCollapse = 1, float maxCost = 1e6f) { int collapsesDone = 0; while (numEdgesToCollapse > 0) { HeapNode <CollapseInfo> node; do { node = heap.Extract(); } while (node != null && mesh.isVertexPairValid(node.obj.vp) == false); if (node == null) { break; } if (node.heapValue > maxCost) { break; } CollapseInfo cinfo = node.obj; VertexPair vp = cinfo.vp; // Add the error terms .. v[0] is the vertex that survives the collapse pdePerVertex[vp.v[0]].OpAdd(pdePerVertex[vp.v[1]]); // DO THE COLLAPSE int vindex = mesh.CollapseVertexPair(cinfo); // vindex is the surviving vertex index // Need to update the cost of all edges around all linked faces of vindex // these are the edges connected to vindex + all the edges connected to vindex neighbours _edgesToUpdate.Clear(); _surroundingVerts.Clear(); mesh.CollectVerticesAroundVertex(vindex, ref _surroundingVerts); // TODO: do this better -> why? int mark = mesh.GetUniqueTag(); for (int i = 0; i < _surroundingVerts.Count; ++i) { List <int> lEdges = mesh.linkedEdgesForVert(_surroundingVerts[i]); for (int j = 0; j < lEdges.Count; ++j) { int edgeIndex = lEdges[j]; Edge e = mesh.edges[edgeIndex]; if (e.mark != mark) { e.mark = mark; if (mesh.IsEdgeValid(edgeIndex)) { _edgesToUpdate.Add(edgeIndex); } } } } // DO the update for (int i = 0; i < _edgesToUpdate.Count; ++i) { int edgeIndex = _edgesToUpdate[i]; HeapNode <CollapseInfo> hnode = heapNodes[edgeIndex]; if (mesh.edges[edgeIndex].ContainsVertex(vindex)) { _calculateEdgeCost(edgeIndex, hnode.obj); } else { _updateEdgePenalties(edgeIndex, hnode.obj, vindex); } heap.Update(hnode, hnode.obj.cost); } numEdgesToCollapse--; collapsesDone++; } return(collapsesDone); }
public new int CollapseVertexPair(CollapseInfo info) { VertexPair pair = info.vp; int v0 = pair.v[0]; int v1 = pair.v[1]; List <int> v0Edges = linkedEdges[v0]; List <int> v1Edges = linkedEdges[v1]; int i; // Update edges // Mark the vertices that are connected by edges for (i = 0; i < v1Edges.Count; ++i) { int edgeIndex = v1Edges[i]; int other = edges[edgeIndex].OtherVertex(v1); vertices[other].mark = -1; } for (i = 0; i < v0Edges.Count; ++i) { int edgeIndex = v0Edges[i]; int other = edges[edgeIndex].OtherVertex(v0); vertices[other].mark = edgeIndex; } // now v1 verts that are only connected to v1 have value -1, double edge-connected verts have the edgeindex as mark for (i = 0; i < v1Edges.Count; ++i) { int edgeIndex = v1Edges[i]; if (vertices[edges[edgeIndex].OtherVertex(v1)].mark == -1) { edges[edgeIndex].ReplaceVertex(v1, v0); if (IsEdgeValid(edgeIndex)) { v0Edges.Add(edgeIndex); } } else { Edge e1 = edges[edgeIndex]; int vindex = e1.OtherVertex(v1); Edge e0 = edges[vertices[vindex].mark]; // vertex mark is edge index! // There has to be another edge connecting v0 to vertex vindex e0.crease = Mathf.Max(e1.crease, e0.crease); // keep the max crease value UnlinkEdge(edgeIndex); // no more need for this! } } // Remove invalid edges from mesh for (i = v0Edges.Count - 1; i >= 0; --i) // backwards should be faster and i stays valid! { int edgeIndex = v0Edges[i]; if (IsEdgeValid(edgeIndex) == false) { UnlinkEdge(edgeIndex); //v0Edges.Remove(edgeIndex); } } // Deal with vertices and faces in baseclass info.vp = new VertexPair(v0, v1); // the original might have been invalidated THIS IS BAD base.CollapseVertexPair(info); v1Edges.Clear(); // rebuild linkedfaces for the remaining edges for (i = 0; i < v0Edges.Count; ++i) { Edge edge = edges[v0Edges[i]]; edge.linkedFaces.Clear(); CollectVertexPairFaces(edge, edge.linkedFaces); } return(v0); }