protected virtual void InitializeQueue() { int NE = mesh.EdgeCount; Nodes = new QEdge[2 * NE]; // [RMS] do we need this many? NodePool = new MemoryPool <QEdge>(NE); EdgeQueue = new g3ext.FastPriorityQueue <QEdge>(NE); int cur_eid = start_edges(); bool done = false; do { if (mesh.IsEdge(cur_eid)) { Index2i ev = mesh.GetEdgeV(cur_eid); QuadricError Q = new QuadricError(ref vertQuadrics[ev.a], ref vertQuadrics[ev.b]); Vector3d opt = OptimalPoint(Q, ev.a, ev.b); double err = Q.Evaluate(opt); QEdge ee = NodePool.Allocate(); ee.Initialize(cur_eid, Q, opt); Nodes[cur_eid] = ee; EdgeQueue.Enqueue(ee, (float)err); } cur_eid = next_edge(cur_eid, out done); } while (done == false); }
public virtual void DoReduce() { if (mesh.TriangleCount == 0) // badness if we don't catch this... { return; } begin_pass(); begin_setup(); Precompute(); InitializeVertexQuadrics(); InitializeQueue(); end_setup(); begin_ops(); begin_collapse(); while (EdgeQueue.Count > 0) { // termination criteria if (ReduceMode == TargetModes.VertexCount) { if (mesh.VertexCount <= TargetCount) { break; } } else { if (mesh.TriangleCount <= TargetCount) { break; } } COUNT_ITERATIONS++; int eid = EdgeQueue.Dequeue(); if (!mesh.IsEdge(eid)) { continue; } int vKept; ProcessResult result = CollapseEdge(eid, EdgeQuadrics[eid].collapse_pt, out vKept); if (result == ProcessResult.Ok_Collapsed) { vertQuadrics[vKept] = EdgeQuadrics[eid].q; UpdateNeighbours(vKept); } } end_collapse(); end_ops(); Reproject(); end_pass(); }
protected virtual void InitializeQueue() { int NE = mesh.EdgeCount; int MaxEID = mesh.MaxEdgeID; EdgeQuadrics = new QEdge[MaxEID]; EdgeQueue = new IndexPriorityQueue(MaxEID); float[] edgeErrors = new float[MaxEID]; // vertex quadrics can be computed in parallel gParallel.ForEach(mesh.EdgeIndices(), (eid) => { Index2i ev = mesh.GetEdgeV(eid); QuadricError Q = new QuadricError(ref vertQuadrics[ev.a], ref vertQuadrics[ev.b]); Vector3d opt = OptimalPoint(eid, ref Q, ev.a, ev.b); edgeErrors[eid] = (float)Q.Evaluate(opt); EdgeQuadrics[eid] = new QEdge(eid, Q, opt); }); // sorted pq insert is faster, so sort edge errors array and index map int[] indices = new int[MaxEID]; for (int i = 0; i < MaxEID; ++i) { indices[i] = i; } Array.Sort(edgeErrors, indices); // now do inserts for (int i = 0; i < edgeErrors.Length; ++i) { int eid = indices[i]; if (mesh.IsEdge(eid)) { QEdge edge = EdgeQuadrics[eid]; EdgeQueue.Enqueue(edge.eid, edgeErrors[i]); } } /* * // previous code that does unsorted insert. This is marginally slower, but * // might get even slower on larger meshes? have only tried up to about 350k. * // (still, this function is not the bottleneck...) * int cur_eid = start_edges(); * bool done = false; * do { * if (mesh.IsEdge(cur_eid)) { * QEdge edge = EdgeQuadrics[cur_eid]; * double err = errList[cur_eid]; * EdgeQueue.Enqueue(cur_eid, (float)err); * } * cur_eid = next_edge(cur_eid, out done); * } while (done == false); */ }
public static void EdgeLengthStatsFromEdges(DMesh3 mesh, IEnumerable <int> EdgeItr, out double minEdgeLen, out double maxEdgeLen, out double avgEdgeLen, int samples = 0) { minEdgeLen = double.MaxValue; maxEdgeLen = double.MinValue; avgEdgeLen = 0; int avg_count = 0; int MaxID = mesh.MaxEdgeID; Vector3d a = Vector3d.Zero, b = Vector3d.Zero; foreach (int eid in EdgeItr) { if (mesh.IsEdge(eid)) { mesh.GetEdgeV(eid, ref a, ref b); double len = a.Distance(b); if (len < minEdgeLen) { minEdgeLen = len; } if (len > maxEdgeLen) { maxEdgeLen = len; } avgEdgeLen += len; avg_count++; } } ; avgEdgeLen /= (double)avg_count; }
/// <summary> /// Split the mesh edges at the iso-crossings, unless edge is /// shorter than min_len, or inserted point would be within min_len or vertex /// [TODO] do we want to return any info here?? /// </summary> public void SplitAtIsoCrossings(double min_len = 0) { foreach (var pair in EdgeLocations) { int eid = pair.Key; Vector3d pos = pair.Value; if (!Mesh.IsEdge(eid)) { continue; } Index2i ev = Mesh.GetEdgeV(eid); Vector3d a = Mesh.GetVertex(ev.a); Vector3d b = Mesh.GetVertex(ev.b); if (a.Distance(b) < min_len) { continue; } Vector3d mid = (a + b) * 0.5; if (a.Distance(mid) < min_len || b.Distance(mid) < min_len) { continue; } DMesh3.EdgeSplitInfo splitInfo; if (Mesh.SplitEdge(eid, out splitInfo) == MeshResult.Ok) { Mesh.SetVertex(splitInfo.vNew, pos); } } }
/// <summary> /// Linear edge-refinement pass, followed by smoothing and projection /// - Edges are processed in prime-modulo-order to break symmetry /// - smoothing is done in parallel if EnableParallelSmooth = true /// - Projection pass if ProjectionMode == AfterRefinement /// - number of modified edges returned in ModifiedEdgesLastPass /// </summary> public void BasicRemeshPass() { if (mesh.TriangleCount == 0) // badness if we don't catch this... { return; } begin_pass(); // Iterate over all edges in the mesh at start of pass. // Some may be removed, so we skip those. // However, some old eid's may also be re-used, so we will touch // some new edges. Can't see how we could efficiently prevent this. // // We are using a modulo-index loop to break symmetry/pathological conditions. // For example in a highly tessellated minimal cylinder, if the top/bottom loops have // sequential edge IDs, and all edges are < min edge length, then we can easily end // up successively collapsing each tiny edge, and eroding away the entire mesh! // By using modulo-index loop we jump around and hence this is unlikely to happen. // begin_ops(); int nMaxEdgeID = mesh.MaxEdgeID; int nPrime = 31337; // any prime will do... int eid = 0; ModifiedEdgesLastPass = 0; do { if (mesh.IsEdge(eid)) { ProcessResult result = ProcessEdge(eid); if (result == ProcessResult.Ok_Collapsed || result == ProcessResult.Ok_Flipped || result == ProcessResult.Ok_Split) { ModifiedEdgesLastPass++; } } eid = (eid + nPrime) % nMaxEdgeID; } while (eid != 0); end_ops(); begin_smooth(); if (EnableSmoothing && SmoothSpeedT > 0) { FullSmoothPass_InPlace(EnableParallelSmooth); DoDebugChecks(); } end_smooth(); begin_project(); if (target != null && ProjectionMode == TargetProjectionMode.AfterRefinement) { FullProjectionPass(); DoDebugChecks(); } end_project(); end_pass(); }
/// <summary> /// Linear edge-refinement pass, followed by smoothing and projection /// - Edges are processed in prime-modulo-order to break symmetry /// - smoothing is done in parallel if EnableParallelSmooth = true /// - Projection pass if ProjectionMode == AfterRefinement /// - number of modified edges returned in ModifiedEdgesLastPass /// </summary> public virtual void BasicRemeshPass() { if (mesh.TriangleCount == 0) // badness if we don't catch this... { return; } begin_pass(); // Iterate over all edges in the mesh at start of pass. // Some may be removed, so we skip those. // However, some old eid's may also be re-used, so we will touch // some new edges. Can't see how we could efficiently prevent this. // begin_ops(); int cur_eid = start_edges(); bool done = false; ModifiedEdgesLastPass = 0; do { if (mesh.IsEdge(cur_eid)) { ProcessResult result = ProcessEdge(cur_eid); if (result == ProcessResult.Ok_Collapsed || result == ProcessResult.Ok_Flipped || result == ProcessResult.Ok_Split) { ModifiedEdgesLastPass++; } } cur_eid = next_edge(cur_eid, out done); } while (done == false); end_ops(); begin_smooth(); if (EnableSmoothing && SmoothSpeedT > 0) { FullSmoothPass_InPlace(EnableParallelSmooth); DoDebugChecks(); } end_smooth(); begin_project(); if (target != null && ProjectionMode == TargetProjectionMode.AfterRefinement) { FullProjectionPass(); DoDebugChecks(); } end_project(); end_pass(); }
// for all edges, disable flip/split/collapse // for all vertices, pin in current position public static void FixEdges(MeshConstraints cons, DMesh3 mesh, IEnumerable <int> edges) { foreach (int ei in edges) { if (mesh.IsEdge(ei)) { cons.SetOrUpdateEdgeConstraint(ei, EdgeConstraint.FullyConstrained); Index2i ev = mesh.GetEdgeV(ei); cons.SetOrUpdateVertexConstraint(ev.a, VertexConstraint.Pinned); cons.SetOrUpdateVertexConstraint(ev.b, VertexConstraint.Pinned); } } }
public void Select(int eid) { Debug.Assert(Mesh.IsEdge(eid)); if (Mesh.IsEdge(eid)) { add(eid); } }
/// <summary> /// boundary vertices of mesh, but based on edges, so returns each vertex twice! /// </summary> public static IEnumerable <int> BoundaryEdgeVertices(DMesh3 mesh) { int N = mesh.MaxEdgeID; for (int i = 0; i < N; ++i) { if (mesh.IsEdge(i) && mesh.IsBoundaryEdge(i)) { Index2i ev = mesh.GetEdgeV(i); yield return(ev.a); yield return(ev.b); } } }
public static IEnumerable <int> FilteredEdges(DMesh3 mesh, Func <DMesh3, int, bool> FilterF) { int N = mesh.MaxEdgeID; for (int i = 0; i < N; ++i) { if (mesh.IsEdge(i)) { if (FilterF(mesh, i)) { yield return(i); } } } }
public static IEnumerable <int> GroupBoundaryEdges(DMesh3 mesh) { int N = mesh.MaxEdgeID; for (int i = 0; i < N; ++i) { if (mesh.IsEdge(i)) { if (mesh.IsGroupBoundaryEdge(i)) { yield return(i); } } } }
public static IEnumerable <int> InteriorEdges(DMesh3 mesh) { int N = mesh.MaxEdgeID; for (int i = 0; i < N; ++i) { if (mesh.IsEdge(i)) { if (mesh.IsBoundaryEdge(i) == false) { yield return(i); } } } }
// for all mesh boundary edges, disable flip/split/collapse // for all mesh boundary vertices, pin in current position public static void FixAllBoundaryEdges(MeshConstraints cons, DMesh3 mesh) { int NE = mesh.MaxEdgeID; for (int ei = 0; ei < NE; ++ei) { if (mesh.IsEdge(ei) && mesh.IsBoundaryEdge(ei)) { cons.SetOrUpdateEdgeConstraint(ei, EdgeConstraint.FullyConstrained); Index2i ev = mesh.GetEdgeV(ei); cons.SetOrUpdateVertexConstraint(ev.a, VertexConstraint.Pinned); cons.SetOrUpdateVertexConstraint(ev.b, VertexConstraint.Pinned); } } }
public static void DebugEdgeInfo(DMesh3 mesh, params int[] edgeIds) { foreach (var edgeId in edgeIds) { Debug.Write($"Info on edge {edgeId}... "); if (!mesh.IsEdge(edgeId)) { Debug.WriteLine("Not an edge."); continue; } var e = mesh.GetEdge(edgeId); Debug.WriteLine($"IsBorder: {mesh.IsBoundaryEdge(edgeId)}"); var from = mesh.GetVertex(e.a); var to = mesh.GetVertex(e.b); Debug.WriteLine($" from: {from.x},{from.y},{from.z} to {to.x},{to.y},{to.z}"); } }
// for all mesh boundary vertices, pin in current position, but allow splits public static void FixAllBoundaryEdges_AllowSplit(MeshConstraints cons, DMesh3 mesh, int setID) { EdgeConstraint edgeCons = new EdgeConstraint(EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoCollapse); VertexConstraint vertCons = new VertexConstraint(true, setID); int NE = mesh.MaxEdgeID; for (int ei = 0; ei < NE; ++ei) { if (mesh.IsEdge(ei) && mesh.IsBoundaryEdge(ei)) { cons.SetOrUpdateEdgeConstraint(ei, edgeCons); Index2i ev = mesh.GetEdgeV(ei); cons.SetOrUpdateVertexConstraint(ev.a, vertCons); cons.SetOrUpdateVertexConstraint(ev.b, vertCons); } } }
public static void EdgeLengthStats(DMesh3 mesh, out double minEdgeLen, out double maxEdgeLen, out double avgEdgeLen, int samples = 0) { minEdgeLen = double.MaxValue; maxEdgeLen = double.MinValue; avgEdgeLen = 0; int avg_count = 0; int MaxID = mesh.MaxEdgeID; // if we are only taking some samples, use a prime-modulo-loop instead of random int nPrime = (samples == 0) ? 1 : nPrime = 31337; int max_count = (samples == 0) ? MaxID : samples; Vector3d a = Vector3d.Zero, b = Vector3d.Zero; int eid = 0; int count = 0; do { if (mesh.IsEdge(eid)) { mesh.GetEdgeV(eid, ref a, ref b); double len = a.Distance(b); if (len < minEdgeLen) { minEdgeLen = len; } if (len > maxEdgeLen) { maxEdgeLen = len; } avgEdgeLen += len; avg_count++; } eid = (eid + nPrime) % MaxID; } while (eid != 0 && count++ < max_count); avgEdgeLen /= (double)avg_count; }
public virtual bool Apply() { HashSet <int> OnCurveVerts = new HashSet <int>(); // original vertices that were epsilon-coincident w/ curve vertices insert_corners(OnCurveVerts); // [RMS] not using this? //HashSet<int> corner_v = new HashSet<int>(CurveVertices); // not sure we need to track all of these HashSet <int> ZeroEdges = new HashSet <int>(); HashSet <int> ZeroVertices = new HashSet <int>(); OnCutEdges = new HashSet <int>(); HashSet <int> NewEdges = new HashSet <int>(); HashSet <int> NewCutVertices = new HashSet <int>(); sbyte[] signs = new sbyte[2 * Mesh.MaxVertexID + 2 * Curve.VertexCount]; HashSet <int> segTriangles = new HashSet <int>(); HashSet <int> segVertices = new HashSet <int>(); HashSet <int> segEdges = new HashSet <int>(); // loop over segments, insert each one in sequence int N = (IsLoop) ? Curve.VertexCount : Curve.VertexCount - 1; for (int si = 0; si < N; ++si) { int i0 = si; int i1 = (si + 1) % Curve.VertexCount; Segment2d seg = new Segment2d(Curve[i0], Curve[i1]); int i0_vid = CurveVertices[i0]; int i1_vid = CurveVertices[i1]; // If these vertices are already connected by an edge, we can just continue. int existing_edge = Mesh.FindEdge(i0_vid, i1_vid); if (existing_edge != DMesh3.InvalidID) { add_cut_edge(existing_edge); continue; } if (triSpatial != null) { segTriangles.Clear(); segVertices.Clear(); segEdges.Clear(); AxisAlignedBox2d segBounds = new AxisAlignedBox2d(seg.P0); segBounds.Contain(seg.P1); segBounds.Expand(MathUtil.ZeroTolerancef * 10); triSpatial.FindTrianglesInRange(segBounds, segTriangles); IndexUtil.TrianglesToVertices(Mesh, segTriangles, segVertices); IndexUtil.TrianglesToEdges(Mesh, segTriangles, segEdges); } int MaxVID = Mesh.MaxVertexID; IEnumerable <int> vertices = Interval1i.Range(MaxVID); if (triSpatial != null) { vertices = segVertices; } // compute edge-crossing signs // [TODO] could walk along mesh from a to b, rather than computing for entire mesh? if (signs.Length < MaxVID) { signs = new sbyte[2 * MaxVID]; } gParallel.ForEach(vertices, (vid) => { if (Mesh.IsVertex(vid)) { if (vid == i0_vid || vid == i1_vid) { signs[vid] = 0; } else { Vector2d v2 = PointF(vid); // tolerance defines band in which we will consider values to be zero signs[vid] = (sbyte)seg.WhichSide(v2, SpatialEpsilon); } } else { signs[vid] = sbyte.MaxValue; } }); // have to skip processing of new edges. If edge id // is > max at start, is new. Otherwise if in NewEdges list, also new. // (need both in case we re-use an old edge index) int MaxEID = Mesh.MaxEdgeID; NewEdges.Clear(); NewCutVertices.Clear(); NewCutVertices.Add(i0_vid); NewCutVertices.Add(i1_vid); // cut existing edges with segment IEnumerable <int> edges = Interval1i.Range(MaxEID); if (triSpatial != null) { edges = segEdges; } foreach (int eid in edges) { if (Mesh.IsEdge(eid) == false) { continue; } if (eid >= MaxEID || NewEdges.Contains(eid)) { continue; } // cannot cut boundary edges? if (Mesh.IsBoundaryEdge(eid)) { continue; } Index2i ev = Mesh.GetEdgeV(eid); int eva_sign = signs[ev.a]; int evb_sign = signs[ev.b]; // [RMS] should we be using larger epsilon here? If we don't track OnCurveVerts explicitly, we // need to at least use same epsilon we passed to insert_corner_from_bary...do we still also // need that to catch the edges we split in the poke? bool eva_in_segment = false; if (eva_sign == 0) { eva_in_segment = OnCurveVerts.Contains(ev.a) || Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + SpatialEpsilon); } bool evb_in_segment = false; if (evb_sign == 0) { evb_in_segment = OnCurveVerts.Contains(ev.b) || Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + SpatialEpsilon); } // If one or both vertices are on-segment, we have special case. // If just one vertex is on the segment, we can skip this edge. // If both vertices are on segment, then we can just re-use this edge. if (eva_in_segment || evb_in_segment) { if (eva_in_segment && evb_in_segment) { ZeroEdges.Add(eid); add_cut_edge(eid); NewCutVertices.Add(ev.a); NewCutVertices.Add(ev.b); } else { int zvid = eva_in_segment ? ev.a : ev.b; ZeroVertices.Add(zvid); NewCutVertices.Add(zvid); } continue; } // no crossing if (eva_sign * evb_sign > 0) { continue; } // compute segment/segment intersection Vector2d va = PointF(ev.a); Vector2d vb = PointF(ev.b); Segment2d edge_seg = new Segment2d(va, vb); IntrSegment2Segment2 intr = new IntrSegment2Segment2(seg, edge_seg); intr.Compute(); if (intr.Type == IntersectionType.Segment) { // [RMS] we should have already caught this above, so if it happens here it is probably spurious? // we should have caught this case above, but numerics are different so it might occur again ZeroEdges.Add(eid); NewCutVertices.Add(ev.a); NewCutVertices.Add(ev.b); add_cut_edge(eid); continue; } else if (intr.Type != IntersectionType.Point) { continue; // no intersection } Vector2d x = intr.Point0; double t = Math.Sqrt(x.DistanceSquared(va) / va.DistanceSquared(vb)); // this case happens if we aren't "on-segment" but after we do the test the intersection pt // is within epsilon of one end of the edge. This is a spurious t-intersection and we // can ignore it. Some other edge should exist that picks up this vertex as part of it. // [TODO] what about if this edge is degenerate? bool x_in_segment = Math.Abs(edge_seg.Project(x)) < (edge_seg.Extent - SpatialEpsilon); if (!x_in_segment) { continue; } Index2i et = Mesh.GetEdgeT(eid); spatial_remove_triangles(et.a, et.b); // split edge at this segment DMesh3.EdgeSplitInfo splitInfo; MeshResult result = Mesh.SplitEdge(eid, out splitInfo, t); if (result != MeshResult.Ok) { throw new Exception("MeshInsertUVSegment.Apply: SplitEdge failed - " + result.ToString()); //return false; } // move split point to intersection position SetPointF(splitInfo.vNew, x); NewCutVertices.Add(splitInfo.vNew); NewEdges.Add(splitInfo.eNewBN); NewEdges.Add(splitInfo.eNewCN); spatial_add_triangles(et.a, et.b); spatial_add_triangles(splitInfo.eNewT2, splitInfo.eNewT3); // some splits - but not all - result in new 'other' edges that are on // the polypath. We want to keep track of these edges so we can extract loop later. Index2i ecn = Mesh.GetEdgeV(splitInfo.eNewCN); if (NewCutVertices.Contains(ecn.a) && NewCutVertices.Contains(ecn.b)) { add_cut_edge(splitInfo.eNewCN); } // since we don't handle bdry edges this should never be false, but maybe we will handle bdry later... if (splitInfo.eNewDN != DMesh3.InvalidID) { NewEdges.Add(splitInfo.eNewDN); Index2i edn = Mesh.GetEdgeV(splitInfo.eNewDN); if (NewCutVertices.Contains(edn.a) && NewCutVertices.Contains(edn.b)) { add_cut_edge(splitInfo.eNewDN); } } } } // extract the cut paths if (EnableCutSpansAndLoops) { find_cut_paths(OnCutEdges); } return(true); } // Apply()
/// <summary> /// Check if this m2 is the same as this mesh. By default only checks /// vertices and triangles, turn on other parameters w/ flags /// </summary> public bool IsSameMesh(DMesh3 m2, bool bCheckEdges = false, bool bCheckNormals = false, bool bCheckColors = false, bool bCheckUVs = false, bool bCheckGroups = false, float Epsilon = MathUtil.Epsilonf) { if (VertexCount != m2.VertexCount) { return(false); } if (TriangleCount != m2.TriangleCount) { return(false); } foreach (int vid in VertexIndices()) { if (m2.IsVertex(vid) == false || GetVertex(vid).EpsilonEqual(m2.GetVertex(vid), Epsilon) == false) { return(false); } } foreach (int tid in TriangleIndices()) { if (m2.IsTriangle(tid) == false || GetTriangle(tid).Equals(m2.GetTriangle(tid)) == false) { return(false); } } if (bCheckEdges) { if (EdgeCount != m2.EdgeCount) { return(false); } foreach (int eid in EdgeIndices()) { if (m2.IsEdge(eid) == false || GetEdge(eid).Equals(m2.GetEdge(eid)) == false) { return(false); } } } if (bCheckNormals) { if (HasVertexNormals != m2.HasVertexNormals) { return(false); } if (HasVertexNormals) { foreach (int vid in VertexIndices()) { if (GetVertexNormal(vid).EpsilonEqual(m2.GetVertexNormal(vid), Epsilon) == false) { return(false); } } } } if (bCheckColors) { if (HasVertexColors != m2.HasVertexColors) { return(false); } if (HasVertexColors) { foreach (int vid in VertexIndices()) { if (GetVertexColor(vid).EpsilonEqual(m2.GetVertexColor(vid), Epsilon) == false) { return(false); } } } } if (bCheckUVs) { if (HasVertexUVs != m2.HasVertexUVs) { return(false); } if (HasVertexUVs) { foreach (int vid in VertexIndices()) { if (GetVertexUV(vid).EpsilonEqual(m2.GetVertexUV(vid), Epsilon) == false) { return(false); } } } } if (bCheckGroups) { if (HasTriangleGroups != m2.HasTriangleGroups) { return(false); } if (HasTriangleGroups) { foreach (int tid in TriangleIndices()) { if (GetTriangleGroup(tid) != m2.GetTriangleGroup(tid)) { return(false); } } } } return(true); }
/// <summary> /// Check if this m2 is the same as this mesh. By default only checks /// vertices and triangles, turn on other parameters w/ flags /// </summary> public bool IsSameMesh(DMesh3 m2, bool bCheckConnectivity, bool bCheckEdgeIDs = false, bool bCheckNormals = false, bool bCheckColors = false, bool bCheckUVs = false, bool bCheckGroups = false, float Epsilon = MathUtil.Epsilonf) { if (VertexCount != m2.VertexCount) { return(false); } if (TriangleCount != m2.TriangleCount) { return(false); } foreach (int vid in VertexIndices()) { if (m2.IsVertex(vid) == false || GetVertex(vid).EpsilonEqual(m2.GetVertex(vid), Epsilon) == false) { return(false); } } foreach (int tid in TriangleIndices()) { if (m2.IsTriangle(tid) == false || GetTriangle(tid).Equals(m2.GetTriangle(tid)) == false) { return(false); } } if (bCheckConnectivity) { foreach (int eid in EdgeIndices()) { Index4i e = GetEdge(eid); int other_eid = m2.FindEdge(e.a, e.b); if (other_eid == InvalidID) { return(false); } Index4i oe = m2.GetEdge(other_eid); if (Math.Min(e.c, e.d) != Math.Min(oe.c, oe.d) || Math.Max(e.c, e.d) != Math.Max(oe.c, oe.d)) { return(false); } } } if (bCheckEdgeIDs) { if (EdgeCount != m2.EdgeCount) { return(false); } foreach (int eid in EdgeIndices()) { if (m2.IsEdge(eid) == false || GetEdge(eid).Equals(m2.GetEdge(eid)) == false) { return(false); } } } if (bCheckNormals) { if (HasVertexNormals != m2.HasVertexNormals) { return(false); } if (HasVertexNormals) { foreach (int vid in VertexIndices()) { if (GetVertexNormal(vid).EpsilonEqual(m2.GetVertexNormal(vid), Epsilon) == false) { return(false); } } } } if (bCheckColors) { if (HasVertexColors != m2.HasVertexColors) { return(false); } if (HasVertexColors) { foreach (int vid in VertexIndices()) { if (GetVertexColor(vid).EpsilonEqual(m2.GetVertexColor(vid), Epsilon) == false) { return(false); } } } } if (bCheckUVs) { if (HasVertexUVs != m2.HasVertexUVs) { return(false); } if (HasVertexUVs) { foreach (int vid in VertexIndices()) { if (GetVertexUV(vid).EpsilonEqual(m2.GetVertexUV(vid), Epsilon) == false) { return(false); } } } } if (bCheckGroups) { if (HasTriangleGroups != m2.HasTriangleGroups) { return(false); } if (HasTriangleGroups) { foreach (int tid in TriangleIndices()) { if (GetTriangleGroup(tid) != m2.GetTriangleGroup(tid)) { return(false); } } } } return(true); }
public bool IsVertex(int vID) { return(Mesh.IsEdge(vID) && Mesh.IsBoundaryEdge(vID)); }
/// <summary> /// Find the set of boundary EdgeLoops. Note that if we encounter topological /// issues, we will throw MeshBoundaryLoopsException w/ more info (if possible) /// </summary> public bool Compute() { // This algorithm assumes that triangles are oriented consistently, // so closed boundary-loop can be followed by walking edges in-order Loops = new List <EdgeLoop>(); Spans = new List <EdgeSpan>(); // early-out if we don't actually have boundaries if (Mesh.CachedIsClosed) { return(true); } int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. var used_edge = new BitArray(NE); used_edge.SetAll(false); // current loop is stored here, cleared after each loop extracted var loop_edges = new List <int>(); // [RMS] not sure we need this... var loop_verts = new List <int>(); var bowties = new List <int>(); // Temp buffer for reading back all boundary edges of a vertex. // probably always small but in pathological cases it could be large... int[] all_e = new int[16]; // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? // process all edges of mesh for (int eid = 0; eid < NE; ++eid) { if (!Mesh.IsEdge(eid)) { continue; } if (used_edge[eid] == true) { continue; } if (Mesh.IsBoundaryEdge(eid) == false) { continue; } if (EdgeFilterF != null && EdgeFilterF(eid) == false) { used_edge[eid] = true; continue; } // ok this is start of a boundary chain int eStart = eid; used_edge[eStart] = true; loop_edges.Add(eStart); int eCur = eid; // follow the chain in order of oriented edges bool bClosed = false; bool bIsOpenSpan = false; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; if (bIsOpenSpan) { cure_a = ev.b; cure_b = ev.a; } else { loop_verts.Add(cure_a); } int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); // have to filter this list, if we are filtering. this is ugly. if (EdgeFilterF != null) { if (bdry_nbrs > 2) { if (bdry_nbrs >= all_e.Length) { all_e = new int[bdry_nbrs]; } // we may repreat this below...irritating... int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); num_be = BufferUtil.CountValid(all_e, EdgeFilterF, num_be); } else { if (EdgeFilterF(e0) == false) { bdry_nbrs--; } if (EdgeFilterF(e1) == false) { bdry_nbrs--; } } } if (bdry_nbrs < 2) { // hit an 'endpoint' vertex (should only happen when Filter is on...) if (SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found open span at vertex " + cure_b) { UnclosedLoop = true }; } if (bIsOpenSpan) { bClosed = true; continue; } else { bIsOpenSpan = true; // begin open span eCur = loop_edges[0]; // restart at other end of loop loop_edges.Reverse(); // do this so we can push to front continue; } } int eNext = -1; if (bdry_nbrs > 2) { // found "bowtie" vertex...things just got complicated! if (cure_b == loop_verts[0]) { // The "end" of the current edge is the same as the start vertex. // This means we can close the loop here. Might as well! eNext = -2; // sentinel value used below } else { // try to find an unused outgoing edge that is oriented properly. // This could create sub-loops, we will handle those later if (bdry_nbrs >= all_e.Length) { all_e = new int[2 * bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); if (EdgeFilterF != null) { num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be); } // Try to pick the best "turn left" vertex. eNext = find_left_turn_edge(eCur, cure_b, all_e, num_be, used_edge); if (eNext == -1) { if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b) { BowtieFailure = true }; } // ok, we are stuck. all we can do now is terminate this loop and keep it as a span if (bIsOpenSpan) { bClosed = true; } else { bIsOpenSpan = true; bClosed = true; } continue; } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { // walk forward to next available edge Debug.Assert(e0 == eCur || e1 == eCur); eNext = (e0 == eCur) ? e1 : e0; } if (eNext == -2) { // found a bowtie vert that is the same as start-of-loop, so we // are just closing it off explicitly bClosed = true; } else if (eNext == eStart) { // found edge at start of loop, so loop is done. bClosed = true; } else if (used_edge[eNext] != false) { // disaster case - the next edge is already used, but it is not the start of our loop // All we can do is convert to open span and terminate if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: encountered repeated edge " + eNext) { RepeatedEdge = true }; } bIsOpenSpan = true; bClosed = true; } else { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); used_edge[eNext] = true; eCur = eNext; } } if (bIsOpenSpan) { SawOpenSpans = true; if (SpanBehavior == SpanBehaviors.Compute) { loop_edges.Reverse(); // orient properly var span = EdgeSpan.FromEdges(Mesh, loop_edges); Spans.Add(span); } } else if (bowties.Count > 0) { // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops Subloops subloops = extract_subloops(loop_verts, loop_edges, bowties); foreach (var loop in subloops.Loops) { Loops.Add(loop); } if (subloops.Spans.Count > 0) { FellBackToSpansOnFailure = true; foreach (var span in subloops.Spans) { Spans.Add(span); } } } else { // clean simple loop, convert to EdgeLoop instance var loop = new EdgeLoop(Mesh); loop.Vertices = loop_verts.ToArray(); loop.Edges = loop_edges.ToArray(); Loops.Add(loop); } // reset these lists loop_edges.Clear(); loop_verts.Clear(); bowties.Clear(); } return(true); }
public bool Insert() { OuterInsert = new MeshInsertUVPolyCurve(Mesh, Polygon.Outer); Util.gDevAssert(OuterInsert.Validate() == ValidationStatus.Ok); bool outerApplyOK = OuterInsert.Apply(); if (outerApplyOK == false || OuterInsert.Loops.Count == 0) { return(false); } if (SimplifyInsertion) { OuterInsert.Simplify(); } HoleInserts = new List <MeshInsertUVPolyCurve>(Polygon.Holes.Count); for (int hi = 0; hi < Polygon.Holes.Count; ++hi) { var insert = new MeshInsertUVPolyCurve(Mesh, Polygon.Holes[hi]); Util.gDevAssert(insert.Validate() == ValidationStatus.Ok); insert.Apply(); if (SimplifyInsertion) { insert.Simplify(); } HoleInserts.Add(insert); } // find a triangle connected to loop that is inside the polygon // [TODO] maybe we could be a bit more robust about this? at least // check if triangle is too degenerate... int seed_tri = -1; EdgeLoop outer_loop = OuterInsert.Loops[0]; for (int i = 0; i < outer_loop.EdgeCount; ++i) { if (!Mesh.IsEdge(outer_loop.Edges[i])) { continue; } Index2i et = Mesh.GetEdgeT(outer_loop.Edges[i]); Vector3d ca = Mesh.GetTriCentroid(et.a); bool in_a = Polygon.Outer.Contains(ca.xy); Vector3d cb = Mesh.GetTriCentroid(et.b); bool in_b = Polygon.Outer.Contains(cb.xy); if (in_a && in_b == false) { seed_tri = et.a; break; } else if (in_b && in_a == false) { seed_tri = et.b; break; } } if (seed_tri == -1) { throw new Exception("MeshPolygonsInserter: could not find seed triangle!"); } // make list of all outer & hole edges InsertedPolygonEdges = new HashSet <int>(outer_loop.Edges); foreach (var insertion in HoleInserts) { foreach (int eid in insertion.Loops[0].Edges) { InsertedPolygonEdges.Add(eid); } } // flood-fill inside loop from seed triangle InteriorTriangles = new MeshFaceSelection(Mesh); InteriorTriangles.FloodFill(seed_tri, null, (eid) => { return(InsertedPolygonEdges.Contains(eid) == false); }); return(true); }
public bool IsVertex(int vID) { return(Mesh.IsEdge(vID)); }
public virtual bool Apply() { insert_corners(); // [RMS] not using this? //HashSet<int> corner_v = new HashSet<int>(CurveVertices); // not sure we need to track all of these HashSet <int> ZeroEdges = new HashSet <int>(); HashSet <int> ZeroVertices = new HashSet <int>(); OnCutEdges = new HashSet <int>(); // loop over segments, insert each one in sequence int N = (IsLoop) ? Curve.VertexCount : Curve.VertexCount - 1; for (int si = 0; si < N; ++si) { int i0 = si; int i1 = (si + 1) % Curve.VertexCount; Segment2d seg = new Segment2d(Curve[i0], Curve[i1]); int i0_vid = CurveVertices[i0]; int i1_vid = CurveVertices[i1]; // If these vertices are already connected by an edge, we can just continue. int existing_edge = Mesh.FindEdge(i0_vid, i1_vid); if (existing_edge != DMesh3.InvalidID) { OnCutEdges.Add(existing_edge); continue; } // compute edge-crossing signs // [TODO] could walk along mesh from a to b, rather than computing for entire mesh? int MaxVID = Mesh.MaxVertexID; int[] signs = new int[MaxVID]; gParallel.ForEach(Interval1i.Range(MaxVID), (vid) => { if (Mesh.IsVertex(vid)) { if (vid == i0_vid || vid == i1_vid) { signs[vid] = 0; } else { Vector2d v2 = PointF(vid); // tolerance defines band in which we will consider values to be zero signs[vid] = seg.WhichSide(v2, MathUtil.ZeroTolerance); } } else { signs[vid] = int.MaxValue; } }); // have to skip processing of new edges. If edge id // is > max at start, is new. Otherwise if in NewEdges list, also new. // (need both in case we re-use an old edge index) int MaxEID = Mesh.MaxEdgeID; HashSet <int> NewEdges = new HashSet <int>(); HashSet <int> NewCutVertices = new HashSet <int>(); NewCutVertices.Add(i0_vid); NewCutVertices.Add(i1_vid); // cut existing edges with segment for (int eid = 0; eid < MaxEID; ++eid) { if (Mesh.IsEdge(eid) == false) { continue; } if (eid >= MaxEID || NewEdges.Contains(eid)) { continue; } // cannot cut boundary edges? if (Mesh.IsBoundaryEdge(eid)) { continue; } Index2i ev = Mesh.GetEdgeV(eid); int eva_sign = signs[ev.a]; int evb_sign = signs[ev.b]; bool eva_in_segment = false; if (eva_sign == 0) { eva_in_segment = Math.Abs(seg.Project(PointF(ev.a))) < (seg.Extent + MathUtil.ZeroTolerance); } bool evb_in_segment = false; if (evb_sign == 0) { evb_in_segment = Math.Abs(seg.Project(PointF(ev.b))) < (seg.Extent + MathUtil.ZeroTolerance); } // If one or both vertices are on-segment, we have special case. // If just one vertex is on the segment, we can skip this edge. // If both vertices are on segment, then we can just re-use this edge. if (eva_in_segment || evb_in_segment) { if (eva_in_segment && evb_in_segment) { ZeroEdges.Add(eid); OnCutEdges.Add(eid); } else { ZeroVertices.Add(eva_in_segment ? ev.a : ev.b); } continue; } // no crossing if (eva_sign * evb_sign > 0) { continue; } // compute segment/segment intersection Vector2d va = PointF(ev.a); Vector2d vb = PointF(ev.b); Segment2d edge_seg = new Segment2d(va, vb); IntrSegment2Segment2 intr = new IntrSegment2Segment2(seg, edge_seg); intr.Compute(); if (intr.Type == IntersectionType.Segment) { // [RMS] we should have already caught this above, so if it happens here it is probably spurious? // we should have caught this case above, but numerics are different so it might occur again ZeroEdges.Add(eid); OnCutEdges.Add(eid); continue; } else if (intr.Type != IntersectionType.Point) { continue; // no intersection } Vector2d x = intr.Point0; // this case happens if we aren't "on-segment" but after we do the test the intersection pt // is within epsilon of one end of the edge. This is a spurious t-intersection and we // can ignore it. Some other edge should exist that picks up this vertex as part of it. // [TODO] what about if this edge is degenerate? bool x_in_segment = Math.Abs(edge_seg.Project(x)) < (edge_seg.Extent - MathUtil.ZeroTolerance); if (!x_in_segment) { continue; } // split edge at this segment DMesh3.EdgeSplitInfo splitInfo; MeshResult result = Mesh.SplitEdge(eid, out splitInfo); if (result != MeshResult.Ok) { throw new Exception("MeshInsertUVSegment.Cut: failed in SplitEdge"); //return false; } // move split point to intersection position SetPointF(splitInfo.vNew, x); NewCutVertices.Add(splitInfo.vNew); NewEdges.Add(splitInfo.eNewBN); NewEdges.Add(splitInfo.eNewCN); // some splits - but not all - result in new 'other' edges that are on // the polypath. We want to keep track of these edges so we can extract loop later. Index2i ecn = Mesh.GetEdgeV(splitInfo.eNewCN); if (NewCutVertices.Contains(ecn.a) && NewCutVertices.Contains(ecn.b)) { OnCutEdges.Add(splitInfo.eNewCN); } // since we don't handle bdry edges this should never be false, but maybe we will handle bdry later... if (splitInfo.eNewDN != DMesh3.InvalidID) { NewEdges.Add(splitInfo.eNewDN); Index2i edn = Mesh.GetEdgeV(splitInfo.eNewDN); if (NewCutVertices.Contains(edn.a) && NewCutVertices.Contains(edn.b)) { OnCutEdges.Add(splitInfo.eNewDN); } } } } //MeshEditor editor = new MeshEditor(Mesh); //foreach (int eid in OnCutEdges) // editor.AppendBox(new Frame3f(Mesh.GetEdgePoint(eid, 0.5)), 0.1f); //Util.WriteDebugMesh(Mesh, string.Format("C:\\git\\geometry3SharpDemos\\geometry3Test\\test_output\\after_inserted.obj")); // extract the cut paths if (EnableCutSpansAndLoops) { find_cut_paths(OnCutEdges); } return(true); } // Apply()
/// <summary> /// Find the set of boundary EdgeLoops. Note that if we encounter topological /// issues, we will throw MeshBoundaryLoopsException w/ more info (if possible) /// </summary> public bool Compute() { // This algorithm assumes that triangles are oriented consistently, // so closed boundary-loop can be followed by walking edges in-order Loops = new List <EdgeLoop>(); Spans = new List <EdgeSpan>(); // early-out if we don't actually have boundaries if (Mesh.CachedIsClosed) { return(true); } int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. BitArray used_edge = new BitArray(NE); used_edge.SetAll(false); // current loop is stored here, cleared after each loop extracted List <int> loop_edges = new List <int>(); // [RMS] not sure we need this... List <int> loop_verts = new List <int>(); List <int> bowties = new List <int>(); // Temp buffer for reading back all boundary edges of a vertex. // probably always small but in pathological cases it could be large... int[] all_e = new int[16]; // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? // process all edges of mesh for (int eid = 0; eid < NE; ++eid) { if (!Mesh.IsEdge(eid)) { continue; } if (used_edge[eid] == true) { continue; } if (Mesh.IsBoundaryEdge(eid) == false) { continue; } if (EdgeFilterF != null && EdgeFilterF(eid) == false) { used_edge[eid] = true; continue; } // ok this is start of a boundary chain int eStart = eid; used_edge[eStart] = true; loop_edges.Add(eStart); int eCur = eid; // follow the chain in order of oriented edges bool bClosed = false; bool bIsOpenSpan = false; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; if (bIsOpenSpan) { cure_a = ev.b; cure_b = ev.a; } else { loop_verts.Add(cure_a); } int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); // have to filter this list, if we are filtering. this is ugly. if (EdgeFilterF != null) { if (bdry_nbrs > 2) { if (bdry_nbrs >= all_e.Length) { all_e = new int[bdry_nbrs]; } // we may repreat this below...irritating... int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); num_be = BufferUtil.CountValid(all_e, EdgeFilterF, num_be); } else { if (EdgeFilterF(e0) == false) { bdry_nbrs--; } if (EdgeFilterF(e1) == false) { bdry_nbrs--; } } } if (bdry_nbrs < 2) // hit an 'endpoint' vertex (should only happen when Filter is on...) { if (SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found open span at vertex " + cure_b) { UnclosedLoop = true } } ; if (bIsOpenSpan) { bClosed = true; continue; } else { bIsOpenSpan = true; // begin open span eCur = loop_edges[0]; // restart at other end of loop loop_edges.Reverse(); // do this so we can push to front continue; } } int eNext = -1; if (bdry_nbrs > 2) { // found "bowtie" vertex...things just got complicated! if (cure_b == loop_verts[0]) { // The "end" of the current edge is the same as the start vertex. // This means we can close the loop here. Might as well! eNext = -2; // sentinel value used below } else { // try to find an unused outgoing edge that is oriented properly. // This could create sub-loops, we will handle those later if (bdry_nbrs >= all_e.Length) { all_e = new int[2 * bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); if (EdgeFilterF != null) { num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be); } // Try to pick the best "turn left" vertex. eNext = find_left_turn_edge(eCur, cure_b, all_e, num_be, used_edge); if (eNext == -1) { if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b) { BowtieFailure = true } } ; // ok, we are stuck. all we can do now is terminate this loop and keep it as a span if (bIsOpenSpan) { bClosed = true; } else { bIsOpenSpan = true; bClosed = true; } continue; } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { // walk forward to next available edge Debug.Assert(e0 == eCur || e1 == eCur); eNext = (e0 == eCur) ? e1 : e0; } if (eNext == -2) { // found a bowtie vert that is the same as start-of-loop, so we // are just closing it off explicitly bClosed = true; } else if (eNext == eStart) { // found edge at start of loop, so loop is done. bClosed = true; } else if (used_edge[eNext] != false) { // disaster case - the next edge is already used, but it is not the start of our loop // All we can do is convert to open span and terminate if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: encountered repeated edge " + eNext) { RepeatedEdge = true } } ; bIsOpenSpan = true; bClosed = true; } else { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); used_edge[eNext] = true; eCur = eNext; } } if (bIsOpenSpan) { SawOpenSpans = true; if (SpanBehavior == SpanBehaviors.Compute) { loop_edges.Reverse(); // orient properly EdgeSpan span = EdgeSpan.FromEdges(Mesh, loop_edges); Spans.Add(span); } } else if (bowties.Count > 0) { // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops Subloops subloops = extract_subloops(loop_verts, loop_edges, bowties); foreach (var loop in subloops.Loops) { Loops.Add(loop); } if (subloops.Spans.Count > 0) { FellBackToSpansOnFailure = true; foreach (var span in subloops.Spans) { Spans.Add(span); } } } else { // clean simple loop, convert to EdgeLoop instance EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = loop_verts.ToArray(); loop.Edges = loop_edges.ToArray(); Loops.Add(loop); } // reset these lists loop_edges.Clear(); loop_verts.Clear(); bowties.Clear(); } return(true); } // [TODO] cache this in a dictionary? we will not need very many, but we will // need each multiple times! Vector3d get_vtx_normal(int vid) { Vector3d n = Vector3d.Zero; foreach (int ti in Mesh.VtxTrianglesItr(vid)) { n += Mesh.GetTriNormal(ti); } n.Normalize(); return(n); } // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. // We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. // So, we compute the tangent plane at bowtie_v, and then the signed angle for each // viable edge in this plane. // // [TODO] handle degenerate edges. what do we do then? Currently will only chose // degenerate edge if there are no other options (I think...) int find_left_turn_edge(int incoming_e, int bowtie_v, int[] bdry_edges, int bdry_edges_count, BitArray used_edges) { // compute normal and edge [a,bowtie] Vector3d n = get_vtx_normal(bowtie_v); int other_v = Mesh.edge_other_v(incoming_e, bowtie_v); Vector3d ab = Mesh.GetVertex(bowtie_v) - Mesh.GetVertex(other_v); // our winner int best_e = -1; double best_angle = double.MaxValue; for (int i = 0; i < bdry_edges_count; ++i) { int bdry_eid = bdry_edges[i]; if (used_edges[bdry_eid] == true) { continue; // this edge is already used } Index2i bdry_ev = Mesh.GetOrientedBoundaryEdgeV(bdry_eid); if (bdry_ev.a != bowtie_v) { continue; // have to be able to chain to end of current edge, orientation-wise } // compute projected angle Vector3d bc = Mesh.GetVertex(bdry_ev.b) - Mesh.GetVertex(bowtie_v); float fAngleS = MathUtil.PlaneAngleSignedD((Vector3f)ab, (Vector3f)bc, (Vector3f)n); // turn left! if (best_angle == double.MaxValue || fAngleS < best_angle) { best_angle = fAngleS; best_e = bdry_eid; } } // [RMS] w/ bowtie vertices and open spans, this does happen //Debug.Assert(best_e != -1); return(best_e); }
public virtual bool Cut() { double invalidDist = double.MinValue; MeshEdgeSelection CutEdgeSet = null; MeshVertexSelection CutVertexSet = null; if (CutFaceSet != null) { CutEdgeSet = new MeshEdgeSelection(Mesh, CutFaceSet); CutVertexSet = new MeshVertexSelection(Mesh, CutEdgeSet); } // compute signs int MaxVID = Mesh.MaxVertexID; double[] signs = new double[MaxVID]; gParallel.ForEach(Interval1i.Range(MaxVID), (vid) => { if (Mesh.IsVertex(vid)) { Vector3d v = Mesh.GetVertex(vid); signs[vid] = (v - PlaneOrigin).Dot(PlaneNormal); } else { signs[vid] = invalidDist; } }); HashSet <int> ZeroEdges = new HashSet <int>(); HashSet <int> ZeroVertices = new HashSet <int>(); HashSet <int> OnCutEdges = new HashSet <int>(); // have to skip processing of new edges. If edge id // is > max at start, is new. Otherwise if in NewEdges list, also new. int MaxEID = Mesh.MaxEdgeID; HashSet <int> NewEdges = new HashSet <int>(); IEnumerable <int> edgeItr = Interval1i.Range(MaxEID); if (CutEdgeSet != null) { edgeItr = CutEdgeSet; } // cut existing edges with plane, using edge split foreach (int eid in edgeItr) { if (Mesh.IsEdge(eid) == false) { continue; } if (eid >= MaxEID || NewEdges.Contains(eid)) { continue; } Index2i ev = Mesh.GetEdgeV(eid); double f0 = signs[ev.a]; double f1 = signs[ev.b]; // If both signs are 0, this edge is on-contour // If one sign is 0, that vertex is on-contour int n0 = (Math.Abs(f0) < MathUtil.Epsilon) ? 1 : 0; int n1 = (Math.Abs(f1) < MathUtil.Epsilon) ? 1 : 0; if (n0 + n1 > 0) { if (n0 + n1 == 2) { ZeroEdges.Add(eid); } else { ZeroVertices.Add((n0 == 1) ? ev[0] : ev[1]); } continue; } // no crossing if (f0 * f1 > 0) { continue; } DMesh3.EdgeSplitInfo splitInfo; MeshResult result = Mesh.SplitEdge(eid, out splitInfo); if (result != MeshResult.Ok) { throw new Exception("MeshPlaneCut.Cut: failed in SplitEdge"); //return false; } // SplitEdge just bisects edge - use plane intersection instead double t = f0 / (f0 - f1); Vector3d newPos = (1 - t) * Mesh.GetVertex(ev.a) + (t) * Mesh.GetVertex(ev.b); Mesh.SetVertex(splitInfo.vNew, newPos); NewEdges.Add(splitInfo.eNewBN); NewEdges.Add(splitInfo.eNewCN); OnCutEdges.Add(splitInfo.eNewCN); if (splitInfo.eNewDN != DMesh3.InvalidID) { NewEdges.Add(splitInfo.eNewDN); OnCutEdges.Add(splitInfo.eNewDN); } } // remove one-rings of all positive-side vertices. IEnumerable <int> vertexSet = Interval1i.Range(MaxVID); if (CutVertexSet != null) { vertexSet = CutVertexSet; } foreach (int vid in vertexSet) { if (signs[vid] > 0 && Mesh.IsVertex(vid)) { Mesh.RemoveVertex(vid, true, false); } } // ok now we extract boundary loops, but restricted // to either the zero-edges we found, or the edges we created! bang!! Func <int, bool> CutEdgeFilterF = (eid) => { if (OnCutEdges.Contains(eid) || ZeroEdges.Contains(eid)) { return(true); } return(false); }; try { MeshBoundaryLoops loops = new MeshBoundaryLoops(Mesh, false); loops.EdgeFilterF = CutEdgeFilterF; loops.Compute(); CutLoops = loops.Loops; CutSpans = loops.Spans; CutLoopsFailed = false; FoundOpenSpans = CutSpans.Count > 0; } catch { CutLoops = new List <EdgeLoop>(); CutLoopsFailed = true; } return(true); } // Cut()
// This algorithm assumes that triangles are oriented consistently, // so boundary-loop can be followed public bool Compute() { Loops = new List <EdgeLoop>(); int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. BitArray used_edge = new BitArray(NE); used_edge.SetAll(false); // current loop is stored here, cleared after each loop extracted List <int> loop_edges = new List <int>(); // [RMS] not sure we need this... List <int> loop_verts = new List <int>(); List <int> bowties = new List <int>(); // Temp buffer for reading back all boundary edges of a vertex. // probably always small but in pathological cases it could be large... int[] all_e = new int[16]; // process all edges of mesh for (int eid = 0; eid < NE; ++eid) { if (!Mesh.IsEdge(eid)) { continue; } if (used_edge[eid] == true) { continue; } if (Mesh.edge_is_boundary(eid) == false) { continue; } // ok this is start of a boundary chain int eStart = eid; used_edge[eStart] = true; loop_edges.Add(eStart); int eCur = eid; // follow the chain in order of oriented edges bool bClosed = false; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; loop_verts.Add(cure_a); int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); if (bdry_nbrs < 2) { throw new Exception("MeshBoundaryLoops.Compute: found broken neighbourhood at vertex " + cure_b); } int eNext = -1; if (bdry_nbrs > 2) { // found "bowtie" vertex...things just got complicated! if (cure_b == loop_verts[0]) { // The "end" of the current edge is the same as the start vertex. // This means we can close the loop here. Might as well! eNext = -2; // sentinel value used below } else { // try to find an unused outgoing edge that is oriented properly. // This could create sub-loops, we will handle those later if (bdry_nbrs >= all_e.Length) { all_e = new int[bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); // Try to pick the best "turn left" vertex. eNext = find_left_turn_edge(eCur, cure_b, all_e, num_be, used_edge); if (eNext == -1) { throw new Exception("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b); } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { Debug.Assert(e0 == eCur || e1 == eCur); eNext = (e0 == eCur) ? e1 : e0; } if (eNext == -2) { // found a bowtie vert that is the same as start-of-loop, so we // are just closing it off explicitly bClosed = true; } else if (eNext == eStart) { // found edge at start of loop, so loop is done. bClosed = true; } else { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); eCur = eNext; used_edge[eCur] = true; } } // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops if (bowties.Count > 0) { List <EdgeLoop> subloops = extract_subloops(loop_verts, loop_edges, bowties); for (int i = 0; i < subloops.Count; ++i) { Loops.Add(subloops[i]); } } else { // clean simple loop, convert to EdgeLoop instance EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = loop_verts.ToArray(); loop.Edges = loop_edges.ToArray(); Loops.Add(loop); } // reset these lists loop_edges.Clear(); loop_verts.Clear(); bowties.Clear(); } return(true); }
/// <summary> /// Find the set of boundary EdgeLoops. Note that if we encounter topological /// issues, we will throw MeshBoundaryLoopsException w/ more info (if possible) /// </summary> public bool Compute() { // This algorithm assumes that triangles are oriented consistently, // so closed boundary-loop can be followed by walking edges in-order Loops = new List <EdgeLoop>(); Spans = new List <EdgeSpan>(); int NE = Mesh.MaxEdgeID; // Temporary memory used to indicate when we have "used" an edge. BitArray used_edge = new BitArray(NE); used_edge.SetAll(false); // current loop is stored here, cleared after each loop extracted List <int> loop_edges = new List <int>(); // [RMS] not sure we need this... List <int> loop_verts = new List <int>(); List <int> bowties = new List <int>(); // Temp buffer for reading back all boundary edges of a vertex. // probably always small but in pathological cases it could be large... int[] all_e = new int[16]; // [TODO] might make sense to precompute some things here, like num_be for each bdry vtx? // process all edges of mesh for (int eid = 0; eid < NE; ++eid) { if (!Mesh.IsEdge(eid)) { continue; } if (used_edge[eid] == true) { continue; } if (Mesh.IsBoundaryEdge(eid) == false) { continue; } if (EdgeFilterF != null && EdgeFilterF(eid) == false) { used_edge[eid] = true; continue; } // ok this is start of a boundary chain int eStart = eid; used_edge[eStart] = true; loop_edges.Add(eStart); int eCur = eid; // follow the chain in order of oriented edges bool bClosed = false; bool bIsOpenSpan = false; while (!bClosed) { Index2i ev = Mesh.GetOrientedBoundaryEdgeV(eCur); int cure_a = ev.a, cure_b = ev.b; if (bIsOpenSpan) { cure_a = ev.b; cure_b = ev.a; } else { loop_verts.Add(cure_a); } int e0 = -1, e1 = 1; int bdry_nbrs = Mesh.VtxBoundaryEdges(cure_b, ref e0, ref e1); // have to filter this list, if we are filtering. this is ugly. if (EdgeFilterF != null) { if (bdry_nbrs > 2) { if (bdry_nbrs >= all_e.Length) { all_e = new int[bdry_nbrs]; } // we may repreat this below...irritating... int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); num_be = BufferUtil.CountValid(all_e, EdgeFilterF, num_be); } else { if (EdgeFilterF(e0) == false) { bdry_nbrs--; } if (EdgeFilterF(e1) == false) { bdry_nbrs--; } } } if (bdry_nbrs < 2) // hit an 'endpoint' vertex (should only happen when Filter is on...) { if (SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found open span at vertex " + cure_b) { UnclosedLoop = true } } ; if (bIsOpenSpan) { bClosed = true; continue; } else { bIsOpenSpan = true; // begin open span eCur = loop_edges[0]; // restart at other end of loop loop_edges.Reverse(); // do this so we can push to front continue; } } int eNext = -1; if (bdry_nbrs > 2) { // found "bowtie" vertex...things just got complicated! if (cure_b == loop_verts[0]) { // The "end" of the current edge is the same as the start vertex. // This means we can close the loop here. Might as well! eNext = -2; // sentinel value used below } else { // try to find an unused outgoing edge that is oriented properly. // This could create sub-loops, we will handle those later if (bdry_nbrs >= all_e.Length) { all_e = new int[2 * bdry_nbrs]; } int num_be = Mesh.VtxAllBoundaryEdges(cure_b, all_e); Debug.Assert(num_be == bdry_nbrs); if (EdgeFilterF != null) { num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be); } // Try to pick the best "turn left" vertex. eNext = find_left_turn_edge(eCur, cure_b, all_e, num_be, used_edge); if (eNext == -1) { if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b) { BowtieFailure = true } } ; // ok, we are stuck. all we can do now is terminate this loop and keep it as a span if (bIsOpenSpan) { bClosed = true; } else { bIsOpenSpan = true; bClosed = true; } continue; } } if (bowties.Contains(cure_b) == false) { bowties.Add(cure_b); } } else { // walk forward to next available edge Debug.Assert(e0 == eCur || e1 == eCur); eNext = (e0 == eCur) ? e1 : e0; } if (eNext == -2) { // found a bowtie vert that is the same as start-of-loop, so we // are just closing it off explicitly bClosed = true; } else if (eNext == eStart) { // found edge at start of loop, so loop is done. bClosed = true; } else if (used_edge[eNext] != false) { // disaster case - the next edge is already used, but it is not the start of our loop // All we can do is convert to open span and terminate if (FailureBehavior == FailureBehaviors.ThrowException || SpanBehavior == SpanBehaviors.ThrowException) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: encountered repeated edge " + eNext) { RepeatedEdge = true } } ; bIsOpenSpan = true; bClosed = true; } else { // push onto accumulated list Debug.Assert(used_edge[eNext] == false); loop_edges.Add(eNext); used_edge[eNext] = true; eCur = eNext; } } if (bIsOpenSpan) { SawOpenSpans = true; if (SpanBehavior == SpanBehaviors.Compute) { loop_edges.Reverse(); // orient properly EdgeSpan span = EdgeSpan.FromEdges(Mesh, loop_edges); Spans.Add(span); } } else if (bowties.Count > 0) { // if we saw a bowtie vertex, we might need to break up this loop, // so call extract_subloops List <EdgeLoop> subloops = extract_subloops(loop_verts, loop_edges, bowties); for (int i = 0; i < subloops.Count; ++i) { Loops.Add(subloops[i]); } } else { // clean simple loop, convert to EdgeLoop instance EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = loop_verts.ToArray(); loop.Edges = loop_edges.ToArray(); Loops.Add(loop); } // reset these lists loop_edges.Clear(); loop_verts.Clear(); bowties.Clear(); } return(true); } // [TODO] cache this in a dictionary? we will not need very many, but we will // need each multiple times! Vector3d get_vtx_normal(int vid) { Vector3d n = Vector3d.Zero; foreach (int ti in Mesh.VtxTrianglesItr(vid)) { n += Mesh.GetTriNormal(ti); } n.Normalize(); return(n); } // ok, bdry_edges[0...bdry_edges_count] contains the boundary edges coming out of bowtie_v. // We want to pick the best one to continue the loop that came in to bowtie_v on incoming_e. // If the loops are all sane, then we will get the smallest loops by "turning left" at bowtie_v. // So, we compute the tangent plane at bowtie_v, and then the signed angle for each // viable edge in this plane. // // [TODO] handle degenerate edges. what do we do then? Currently will only chose // degenerate edge if there are no other options (I think...) int find_left_turn_edge(int incoming_e, int bowtie_v, int[] bdry_edges, int bdry_edges_count, BitArray used_edges) { // compute normal and edge [a,bowtie] Vector3d n = get_vtx_normal(bowtie_v); int other_v = Mesh.edge_other_v(incoming_e, bowtie_v); Vector3d ab = Mesh.GetVertex(bowtie_v) - Mesh.GetVertex(other_v); // our winner int best_e = -1; double best_angle = double.MaxValue; for (int i = 0; i < bdry_edges_count; ++i) { int bdry_eid = bdry_edges[i]; if (used_edges[bdry_eid] == true) { continue; // this edge is already used } Index2i bdry_ev = Mesh.GetOrientedBoundaryEdgeV(bdry_eid); if (bdry_ev.a != bowtie_v) { continue; // have to be able to chain to end of current edge, orientation-wise } // compute projected angle Vector3d bc = Mesh.GetVertex(bdry_ev.b) - Mesh.GetVertex(bowtie_v); float fAngleS = MathUtil.PlaneAngleSignedD((Vector3f)ab, (Vector3f)bc, (Vector3f)n); // turn left! if (best_angle == double.MaxValue || fAngleS < best_angle) { best_angle = fAngleS; best_e = bdry_eid; } } // [RMS] w/ bowtie vertices and open spans, this does happen //Debug.Assert(best_e != -1); return(best_e); } // This is called when loopV contains one or more "bowtie" vertices. // These vertices *might* be duplicated in loopV (but not necessarily) // If they are, we have to break loopV into subloops that don't contain duplicates. // // The list bowties contains all the possible duplicates // (all v in bowties occur in loopV at least once) // // Currently loopE is not used, and the returned EdgeLoop objects do not have their Edges // arrays initialized. Perhaps to improve in future. List <EdgeLoop> extract_subloops(List <int> loopV, List <int> loopE, List <int> bowties) { List <EdgeLoop> subs = new List <EdgeLoop>(); // figure out which bowties we saw are actually duplicated in loopV List <int> dupes = new List <int>(); foreach (int bv in bowties) { if (count_in_list(loopV, bv) > 1) { dupes.Add(bv); } } // we might not actually have any duplicates, if we got luck. Early out in that case if (dupes.Count == 0) { subs.Add(new EdgeLoop(Mesh) { Vertices = loopV.ToArray(), Edges = loopE.ToArray(), BowtieVertices = bowties.ToArray() }); return(subs); } // This loop extracts subloops until we have dealt with all the // duplicate vertices in loopV while (dupes.Count > 0) { // Find shortest "simple" loop, ie a loop from a bowtie to itself that // does not contain any other bowties. This is an independent loop. // We're doing a lot of extra work here if we only have one element in dupes... int bi = 0, bv = 0; int start_i = -1, end_i = -1; int bv_shortest = -1; int shortest = int.MaxValue; for ( ; bi < dupes.Count; ++bi) { bv = dupes[bi]; if (is_simple_bowtie_loop(loopV, dupes, bv, out start_i, out end_i)) { int len = count_span(loopV, start_i, end_i); if (len < shortest) { bv_shortest = bv; shortest = len; } } } if (bv_shortest == -1) { throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: Cannot find a valid simple loop"); } if (bv != bv_shortest) { bv = bv_shortest; // running again just to get start_i and end_i... is_simple_bowtie_loop(loopV, dupes, bv, out start_i, out end_i); } Debug.Assert(loopV[start_i] == bv && loopV[end_i] == bv); EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = extract_span(loopV, start_i, end_i, true); loop.Edges = EdgeLoop.VertexLoopToEdgeLoop(Mesh, loop.Vertices); loop.BowtieVertices = bowties.ToArray(); subs.Add(loop); // If there are no more duplicates of this bowtie, we can treat // it like a regular vertex now if (count_in_list(loopV, bv) < 2) { dupes.Remove(bv); } } // Should have one loop left that contains duplicates. // Extract this as a separate loop int nLeft = 0; for (int i = 0; i < loopV.Count; ++i) { if (loopV[i] != -1) { nLeft++; } } if (nLeft > 0) { EdgeLoop loop = new EdgeLoop(Mesh); loop.Vertices = new int[nLeft]; int vi = 0; for (int i = 0; i < loopV.Count; ++i) { if (loopV[i] != -1) { loop.Vertices[vi++] = loopV[i]; } } loop.Edges = EdgeLoop.VertexLoopToEdgeLoop(Mesh, loop.Vertices); loop.BowtieVertices = bowties.ToArray(); subs.Add(loop); } return(subs); } /* * In all the functions below, the list loopV is assumed to possibly * contain "removed" vertices indicated by -1. These are ignored. */ // Check if the loop from bowtieV to bowtieV inside loopV contains any other bowtie verts. // Also returns start and end indices in loopV of "clean" loop // Note that start may be < end, if the "clean" loop wraps around the end bool is_simple_bowtie_loop(List <int> loopV, List <int> bowties, int bowtieV, out int start_i, out int end_i) { // find two indices of bowtie vert start_i = find_index(loopV, 0, bowtieV); end_i = find_index(loopV, start_i + 1, bowtieV); if (is_simple_path(loopV, bowties, bowtieV, start_i, end_i)) { return(true); } else if (is_simple_path(loopV, bowties, bowtieV, end_i, start_i)) { int tmp = start_i; start_i = end_i; end_i = tmp; return(true); } else { return(false); // not a simple bowtie loop! } } // check if forward path from loopV[i1] to loopV[i2] contains any bowtie verts other than bowtieV bool is_simple_path(List <int> loopV, List <int> bowties, int bowtieV, int i1, int i2) { int N = loopV.Count; for (int i = i1; i != i2; i = (i + 1) % N) { int vi = loopV[i]; if (vi == -1) { continue; // skip removed vertices } if (vi != bowtieV && bowties.Contains(vi)) { return(false); } } return(true); } // Read out the span from loop[i0] to loop [i1-1] into an array. // If bMarkInvalid, then these values are set to -1 in loop int[] extract_span(List <int> loop, int i0, int i1, bool bMarkInvalid) { int num = count_span(loop, i0, i1); int[] a = new int[num]; int ai = 0; int N = loop.Count; for (int i = i0; i != i1; i = (i + 1) % N) { if (loop[i] != -1) { a[ai++] = loop[i]; if (bMarkInvalid) { loop[i] = -1; } } } return(a); } // count number of valid vertices in l between loop[i0] and loop[i1-1] int count_span(List <int> l, int i0, int i1) { int c = 0; int N = l.Count; for (int i = i0; i != i1; i = (i + 1) % N) { if (l[i] != -1) { c++; } } return(c); } // find the index of item in loop, starting at start index int find_index(List <int> loop, int start, int item) { for (int i = start; i < loop.Count; ++i) { if (loop[i] == item) { return(i); } } return(-1); } // count number of times item appears in loop int count_in_list(List <int> loop, int item) { int c = 0; for (int i = 0; i < loop.Count; ++i) { if (loop[i] == item) { c++; } } return(c); } } }