Пример #1
0
        // gets slow for small res factor...
        public static DMesh3 MakeRemeshedCappedCylinder(double fResFactor = 1.0)
        {
            DMesh3 mesh = TestUtil.MakeCappedCylinder(false, 128);

            MeshUtil.ScaleMesh(mesh, Frame3f.Identity, new Vector3f(1, 2, 1));

            // construct mesh projection target
            DMesh3         meshCopy = new DMesh3(mesh);
            DMeshAABBTree3 tree     = new DMeshAABBTree3(meshCopy);

            tree.Build();
            MeshProjectionTarget target = new MeshProjectionTarget()
            {
                Mesh = meshCopy, Spatial = tree
            };
            MeshConstraints cons     = new MeshConstraints();
            EdgeRefineFlags useFlags = EdgeRefineFlags.NoFlip;

            foreach (int eid in mesh.EdgeIndices())
            {
                double fAngle = MeshUtil.OpeningAngleD(mesh, eid);
                if (fAngle > 30.0f)
                {
                    cons.SetOrUpdateEdgeConstraint(eid, new EdgeConstraint(useFlags));
                    Index2i ev      = mesh.GetEdgeV(eid);
                    int     nSetID0 = (mesh.GetVertex(ev[0]).y > 1) ? 1 : 2;
                    int     nSetID1 = (mesh.GetVertex(ev[1]).y > 1) ? 1 : 2;
                    cons.SetOrUpdateVertexConstraint(ev[0], new VertexConstraint(true, nSetID0));
                    cons.SetOrUpdateVertexConstraint(ev[1], new VertexConstraint(true, nSetID1));
                }
            }
            Remesher r = new Remesher(mesh);

            r.SetExternalConstraints(cons);
            r.SetProjectionTarget(target);
            r.Precompute();
            r.EnableFlips     = r.EnableSplits = r.EnableCollapses = true;
            r.MinEdgeLength   = 0.1f * fResFactor;
            r.MaxEdgeLength   = 0.2f * fResFactor;
            r.EnableSmoothing = true;
            r.SmoothSpeedT    = 0.5f;
            for (int k = 0; k < 20; ++k)
            {
                r.BasicRemeshPass();
            }
            return(mesh);
        }
Пример #2
0
        /// <summary>
        /// given list of edges of MeshA, and vertex map from A to B, map to list of edges on B
        /// </summary>
        public static List <int> MapEdgesViaVertexMap(IIndexMap AtoBV, NGonsCore.geometry3Sharp.mesh.DMesh3 MeshA, NGonsCore.geometry3Sharp.mesh.DMesh3 MeshB, List <int> edges)
        {
            int        N      = edges.Count;
            List <int> result = new List <int>(N);

            for (int i = 0; i < N; ++i)
            {
                int     eid_a = edges[i];
                Index2i aev   = MeshA.GetEdgeV(eid_a);
                int     bev0  = AtoBV[aev.a];
                int     bev1  = AtoBV[aev.b];
                int     eid_b = MeshB.FindEdge(bev0, bev0);
                Debug.Assert(eid_b != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID);
                result.Add(eid_b);
            }
            return(result);
        }
Пример #3
0
        // After remeshing we may create an internal edge between two boundary vertices [a,b].
        // Those vertices will be merged with vertices c and d in the base mesh. If the edge
        // [c,d] already exists in the base mesh, then after the merge we would have at least
        // 3 triangles at this edge. Dang.
        //
        // A common example is a 'fin' triangle that would duplicate a
        // 'fin' on the border of the base mesh after removing the submesh, but this situation can
        // arise anywhere (eg think about one-triangle-wide strips).
        //
        // This is very hard to remove, but we can at least avoid creating non-manifold edges (which
        // with the current DMesh3 will be prevented, hence leaving a hole) by splitting the
        // internal edge in the submesh (which presumably we were remeshing anyway, so changes are ok).
        public void RepairPossibleNonManifoldEdges()
        {
            // [TODO] do we need to repeat this more than once? I don't think so...

            // repair submesh
            int        NE          = Region.SubMesh.MaxEdgeID;
            List <int> split_edges = new List <int>();

            for (int eid = 0; eid < NE; ++eid)
            {
                if (Region.SubMesh.IsEdge(eid) == false)
                {
                    continue;
                }
                if (Region.SubMesh.IsBoundaryEdge(eid))
                {
                    continue;
                }
                Index2i edgev = Region.SubMesh.GetEdgeV(eid);
                if (Region.SubMesh.Vertex_is_boundary(edgev.a) && Region.SubMesh.Vertex_is_boundary(edgev.b))
                {
                    // ok, we have an internal edge where both verts are on the boundary
                    // now check if it is an edge in the base mesh
                    int base_a = Region.MapVertexToBaseMesh(edgev.a);
                    int base_b = Region.MapVertexToBaseMesh(edgev.b);
                    if (base_a != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID && base_b != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID)
                    {
                        // both vertices in base mesh...right?
                        Debug.Assert(Region.BaseMesh.IsVertex(base_a) && Region.BaseMesh.IsVertex(base_b));
                        int base_eid = Region.BaseMesh.FindEdge(base_a, base_b);
                        if (base_eid != NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID)
                        {
                            split_edges.Add(eid);
                        }
                    }
                }
            }

            // split any problem edges we found and repeat this loop
            for (int i = 0; i < split_edges.Count; ++i)
            {
                NGonsCore.geometry3Sharp.mesh.DMesh3.EdgeSplitInfo split_info;
                Region.SubMesh.SplitEdge(split_edges[i], out split_info);
            }
        }
Пример #4
0
        public void Debug_print_vertex(int v)
        {
            System.Console.WriteLine("Vertex " + v.ToString());
            List <int> tris = new List <int>();

            GetVtxTriangles(v, tris, false);
            System.Console.WriteLine(string.Format("  Tris {0}  Edges {1}  refcount {2}", tris.Count, GetVtxEdges(v).Count, vertices_refcount.RefCount(v)));
            foreach (int t in tris)
            {
                Index3i tv = GetTriangle(t), te = GetTriEdges(t);
                System.Console.WriteLine(string.Format("  t{6} {0} {1} {2}   te {3} {4} {5}", tv[0], tv[1], tv[2], te[0], te[1], te[2], t));
            }
            foreach (int e in GetVtxEdges(v))
            {
                Index2i ev = GetEdgeV(e), et = GetEdgeT(e);
                System.Console.WriteLine(string.Format("  e{4} {0} {1} / {2} {3}", ev[0], ev[1], et[0], et[1], e));
            }
        }
Пример #5
0
        bool collapse_degenerate_edges(
            double minLength, bool bBoundaryOnly,
            out int collapseCount)
        {
            collapseCount = 0;
            // don't iterate sequentially because there may be pathological cases
            foreach (int eid in MathUtil.ModuloIteration(Mesh.MaxEdgeID))
            {
                if (Cancelled())
                {
                    break;
                }

                if (Mesh.IsEdge(eid) == false)
                {
                    continue;
                }

                bool is_boundary_edge = Mesh.IsBoundaryEdge(eid);
                if (bBoundaryOnly && is_boundary_edge == false)
                {
                    continue;
                }

                Index2i  ev = Mesh.GetEdgeV(eid);
                Vector3d a = Mesh.GetVertex(ev.a), b = Mesh.GetVertex(ev.b);
                if (a.Distance(b) < minLength)
                {
                    int keep    = Mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b;
                    int discard = (keep == ev.a) ? ev.b : ev.a;
                    DMesh3.EdgeCollapseInfo collapseInfo;
                    MeshResult result = Mesh.CollapseEdge(keep, discard, out collapseInfo);
                    if (result == MeshResult.Ok)
                    {
                        ++collapseCount;
                        if (Mesh.IsBoundaryVertex(keep) == false || is_boundary_edge)
                        {
                            Mesh.SetVertex(keep, (a + b) * 0.5);
                        }
                    }
                }
            }
            return(true);
        }
Пример #6
0
        // for all mesh boundary edges, disable flip/split/collapse
        // for all mesh boundary vertices, pin in current position
        public static void FixAllGroupBoundaryEdges(MeshConstraints cons, NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, bool bPinVertices)
        {
            int NE = mesh.MaxEdgeID;

            for (int ei = 0; ei < NE; ++ei)
            {
                if (mesh.IsEdge(ei) && mesh.IsGroupBoundaryEdge(ei))
                {
                    cons.SetOrUpdateEdgeConstraint(ei, EdgeConstraint.FullyConstrained);

                    if (bPinVertices)
                    {
                        Index2i ev = mesh.GetEdgeV(ei);
                        cons.SetOrUpdateVertexConstraint(ev.a, VertexConstraint.Pinned);
                        cons.SetOrUpdateVertexConstraint(ev.b, VertexConstraint.Pinned);
                    }
                }
            }
        }
Пример #7
0
        // for all mesh boundary vertices, pin in current position, but allow collapses
        public static void FixAllBoundaryEdges_AllowCollapse(MeshConstraints cons, NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, int setID)
        {
            EdgeConstraint   edgeCons = new EdgeConstraint(EdgeRefineFlags.NoFlip | EdgeRefineFlags.NoSplit);
            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);
                }
            }
        }
Пример #8
0
        // Support for ordering a set of unique indices into the vertex pool.  On
        // output it is guaranteed that:  v0 < v1 < v2.  This is used to guarantee
        // consistent queries when the vertex ordering of a primitive is permuted,
        // a necessity when using floating-point arithmetic that suffers from
        // numerical round-off errors.  The input indices are considered the
        // positive ordering.  The output indices are either positively ordered
        // (an even number of transpositions occurs during sorting) or negatively
        // ordered (an odd number of transpositions occurs during sorting).  The
        // functions return 'true' for a positive ordering and 'false' for a
        // negative ordering.

        bool Sort(ref int v0, ref int v1)
        {
            int  j0, j1;
            bool positive;

            if (v0 < v1)
            {
                j0 = 0; j1 = 1; positive = true;
            }
            else
            {
                j0 = 1; j1 = 0; positive = false;
            }
            Index2i value = new Index2i(v0, v1);

            v0 = value[j0];
            v1 = value[j1];
            return(positive);
        }
Пример #9
0
        private bool CollapseDegenerateEdges(DMesh3 mesh,
                                             CancellationToken cancellationToken,
                                             double minLength,
                                             bool bBoundaryOnly,
                                             out int collapseCount)
        {
            collapseCount = 0;
            // don't iterate sequentially because there may be pathological cases
            foreach (int eid in MathUtil.ModuloIteration(mesh.MaxEdgeID))
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (mesh.IsEdge(eid) == false)
                {
                    continue;
                }

                bool isBoundaryEdge = mesh.IsBoundaryEdge(eid);
                if (bBoundaryOnly && isBoundaryEdge == false)
                {
                    continue;
                }

                Index2i  ev = mesh.GetEdgeV(eid);
                Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b);
                if (a.Distance(b) < minLength)
                {
                    int        keep    = mesh.IsBoundaryVertex(ev.a) ? ev.a : ev.b;
                    int        discard = (keep == ev.a) ? ev.b : ev.a;
                    MeshResult result  = mesh.CollapseEdge(keep, discard, out DMesh3.EdgeCollapseInfo collapseInfo);
                    if (result == MeshResult.Ok)
                    {
                        ++collapseCount;
                        if (mesh.IsBoundaryVertex(keep) == false || isBoundaryEdge)
                        {
                            mesh.SetVertex(keep, (a + b) * 0.5);
                        }
                    }
                }
            }

            return(true);
        }
Пример #10
0
        public virtual int[] AddTriangleFan_OrderedEdgeLoop(int center, int[] edge_loop, int group_id = -1)
        {
            int N = edge_loop.Length;

            int[] new_tris = new int[N];

            int i = 0;

            for (i = 0; i < N; ++i)
            {
                if (Mesh.IsBoundaryEdge(edge_loop[i]) == false)
                {
                    goto operation_failed;
                }

                Index2i ev = Mesh.GetOrientedBoundaryEdgeV(edge_loop[i]);
                int     a = ev.a, b = ev.b;

                Index3i newT    = new Index3i(center, b, a);
                int     new_tid = Mesh.AppendTriangle(newT, group_id);
                if (new_tid == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID)
                {
                    goto operation_failed;
                }

                new_tris[i] = new_tid;
            }

            return(new_tris);


operation_failed:
            // remove what we added so far
            if (i > 0)
            {
                if (Remove_triangles(new_tris, i - 1) == false)
                {
                    throw new Exception("MeshConstructor.AddTriangleFan_OrderedEdgeLoop: failed to add fan, and also failed to back out changes.");
                }
            }
            return(null);
        }
Пример #11
0
        public void ComputeBoundaryInfo(IEnumerable <int> triangles, int tri_count_est)
        {
            // set of base-mesh triangles that are in submesh
            IndexFlagSet sub_tris = new IndexFlagSet(BaseMesh.MaxTriangleID, tri_count_est);

            foreach (int ti in triangles)
            {
                sub_tris[ti] = true;
            }

            BaseBorderV   = new IndexHashSet();
            BaseBorderE   = new IndexHashSet();
            BaseBoundaryE = new IndexHashSet();

            // Iterate through edges in submesh roi on base mesh. If
            // one of the tris of the edge is not in submesh roi, then this
            // is a boundary edge.
            //
            // (edge iteration via triangle iteration processes each internal edge twice...)
            foreach (int ti in triangles)
            {
                Index3i tedges = BaseMesh.GetTriEdges(ti);
                for (int j = 0; j < 3; ++j)
                {
                    int     eid  = tedges[j];
                    Index2i tris = BaseMesh.GetEdgeT(eid);
                    if (tris.b == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID)         // this is a boundary edge
                    {
                        BaseBoundaryE[eid] = true;
                    }
                    else if (sub_tris[tris.a] != sub_tris[tris.b])      // this is a border edge
                    {
                        BaseBorderE[eid] = true;
                        Index2i ve = BaseMesh.GetEdgeV(eid);
                        BaseBorderV[ve.a] = true;
                        BaseBorderV[ve.b] = true;
                    }
                }
            }
        }
Пример #12
0
        public MeshRegionBoundaryLoops(NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, int[] RegionTris, bool bAutoCompute = true)
        {
            this.Mesh = mesh;

            // make flag set for included triangles
            triangles = new IndexFlagSet(mesh.MaxTriangleID, RegionTris.Length);
            for (int i = 0; i < RegionTris.Length; ++i)
            {
                triangles[RegionTris[i]] = true;
            }

            // make flag set for included edges
            // NOTE: this currently processes non-boundary-edges twice. Could
            // avoid w/ another IndexFlagSet, but the check is inexpensive...
            edges = new IndexFlagSet(mesh.MaxEdgeID, RegionTris.Length);
            for (int i = 0; i < RegionTris.Length; ++i)
            {
                int     tid = RegionTris[i];
                Index3i te  = Mesh.GetTriEdges(tid);
                for (int j = 0; j < 3; ++j)
                {
                    int eid = te[j];
                    if (!edges.Contains(eid))
                    {
                        Index2i et = mesh.GetEdgeT(eid);
                        if (et.b == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID || triangles[et.a] != triangles[et.b])
                        {
                            edges.Add(eid);
                        }
                    }
                }
            }


            if (bAutoCompute)
            {
                Compute();
            }
        }
Пример #13
0
        /// <summary>
        /// specify subset of vertices that have known correspondences.
        /// </summary>
        public void AddKnownCorrespondences(int[] verts0, int[] verts1)
        {
            int N = verts0.Length;

            if (N != verts1.Length)
            {
                throw new Exception("MeshStitchLoops.AddKnownCorrespondence: lengths not the same!");
            }

            // construct list of pair correspondences as loop indices
            List <Index2i> pairs = new List <Index2i>();

            for (int k = 0; k < N; ++k)
            {
                int i0 = Loop0.FindVertexIndex(verts0[k]);
                int i1 = Loop1.FindVertexIndex(verts1[k]);
                pairs.Add(new Index2i(i0, i1));
            }

            // sort by increasing index in loop0 (arbitrary)
            pairs.Sort((pair1, pair2) => { return(pair1.a.CompareTo(pair2.a)); });

            // now construct spans
            List <span> new_spans = new List <span>();

            for (int k = 0; k < pairs.Count; ++k)
            {
                Index2i p1 = pairs[k];
                Index2i p2 = pairs[(k + 1) % pairs.Count];
                span    s  = new span()
                {
                    span0 = new Interval1i(p1.a, p2.a),
                    span1 = new Interval1i(p1.b, p2.b)
                };
                new_spans.Add(s);
            }
            spans = new_spans;
        }
Пример #14
0
        static int find_pair_edge(DMesh3 mesh, int eid, List <int> candidates)
        {
            Index2i  ev = mesh.GetEdgeV(eid);
            Vector3d a = mesh.GetVertex(ev.a), b = mesh.GetVertex(ev.b);
            double   eps = 100 * MathUtil.Epsilonf;

            foreach (int eother in candidates)
            {
                if (eother == eid)
                {
                    continue;
                }
                Index2i  ov = mesh.GetEdgeV(eother);
                Vector3d c = mesh.GetVertex(ov.a), d = mesh.GetVertex(ov.b);
                if ((a.EpsilonEqual(c, eps) && b.EpsilonEqual(d, eps)) ||
                    (b.EpsilonEqual(c, eps) && a.EpsilonEqual(d, eps)))
                {
                    return(eother);
                }
            }
            ;
            return(DMesh3.InvalidID);
        }
Пример #15
0
        public static ValidationStatus IsBoundaryLoop(NGonsCore.geometry3Sharp.mesh.DMesh3 mesh, EdgeLoop loop)
        {
            int N = loop.Vertices.Length;

            for (int i = 0; i < N; ++i)
            {
                if (!mesh.Vertex_is_boundary(loop.Vertices[i]))
                {
                    return(ValidationStatus.NotBoundaryVertex);
                }
            }

            for (int i = 0; i < N; ++i)
            {
                int a = loop.Vertices[i];
                int b = loop.Vertices[(i + 1) % N];

                int eid = mesh.FindEdge(a, b);
                if (eid == NGonsCore.geometry3Sharp.mesh.DMesh3.InvalidID)
                {
                    return(ValidationStatus.VerticesNotConnectedByEdge);
                }

                if (mesh.IsBoundaryEdge(eid) == false)
                {
                    return(ValidationStatus.NotBoundaryEdge);
                }

                Index2i ev = mesh.GetOrientedBoundaryEdgeV(eid);
                if (!(ev.a == a && ev.b == b))
                {
                    return(ValidationStatus.IncorrectLoopOrientation);
                }
            }

            return(ValidationStatus.Ok);
        }
Пример #16
0
        UseFillType classify_hole()
        {
            return(UseFillType.MinimalFill);

#if false
            int NV = FillLoop.VertexCount;
            int NE = FillLoop.EdgeCount;

            Vector3d size = FillLoop.ToCurve().GetBoundingBox().Diagonal;

            NormalHistogram hist = new NormalHistogram(4096, true);

            for (int k = 0; k < NE; ++k)
            {
                int      eid = FillLoop.Edges[k];
                Index2i  et  = Mesh.GetEdgeT(eid);
                Vector3d n   = Mesh.GetTriNormal(et.a);
                hist.Count(n, 1.0, true);
            }

            if (hist.UsedBins.Count == 1)
            {
                return(UseFillType.PlanarFill);
            }

            //int nontrivial_bins = 0;
            //foreach ( int bin in hist.UsedBins ) {
            //    if (hist.Counts[bin] > 8)
            //        nontrivial_bins++;
            //}
            //if (nontrivial_bins > 0)
            //    return UseFillType.PlanarSpansFill;

            return(UseFillType.SmoothFill);
#endif
        }
Пример #17
0
        // this function collapses edges until it can't anymore
        static void collapse_to_convergence(DMesh3 mesh)
        {
            bool bContinue = true;

            while (bContinue)
            {
                bContinue = false;
                for (int eid = 0; eid < mesh.MaxEdgeID; ++eid)
                {
                    if (!mesh.IsEdge(eid))
                    {
                        continue;
                    }
                    Index2i ev = mesh.GetEdgeV(eid);
                    DMesh3.EdgeCollapseInfo collapseInfo;
                    MeshResult result = mesh.CollapseEdge(ev[0], ev[1], out collapseInfo);
                    if (result == MeshResult.Ok)
                    {
                        bContinue = true;
                        break;
                    }
                }
            }
        }
Пример #18
0
        public bool Apply()
        {
            // do a simple fill
            SimpleHoleFiller simplefill = new SimpleHoleFiller(Mesh, FillLoop);
            int  fill_gid = Mesh.AllocateTriangleGroup();
            bool bOK      = simplefill.Fill(fill_gid);

            if (bOK == false)
            {
                return(false);
            }

            if (FillLoop.Vertices.Length <= 3)
            {
                FillTriangles = simplefill.NewTriangles;
                FillVertices  = new int[0];
                return(true);
            }

            // extract the simple fill mesh as a submesh, via RegionOperator, so we can backsub later
            HashSet <int> intial_fill_tris = new HashSet <int>(simplefill.NewTriangles);

            regionop = new RegionOperator(Mesh, simplefill.NewTriangles,
                                          (submesh) => { submesh.ComputeTriMaps = true; });
            fillmesh = regionop.Region.SubMesh;

            // for each boundary vertex, compute the exterior angle sum
            // we will use this to compute gaussian curvature later
            boundaryv           = new HashSet <int>(MeshIterators.BoundaryEdgeVertices(fillmesh));
            exterior_angle_sums = new Dictionary <int, double>();
            if (IgnoreBoundaryTriangles == false)
            {
                foreach (int sub_vid in boundaryv)
                {
                    double angle_sum = 0;
                    int    base_vid  = regionop.Region.MapVertexToBaseMesh(sub_vid);
                    foreach (int tid in regionop.BaseMesh.VtxTrianglesItr(base_vid))
                    {
                        if (intial_fill_tris.Contains(tid) == false)
                        {
                            Index3i et  = regionop.BaseMesh.GetTriangle(tid);
                            int     idx = IndexUtil.find_tri_index(base_vid, ref et);
                            angle_sum += regionop.BaseMesh.GetTriInternalAngleR(tid, idx);
                        }
                    }
                    exterior_angle_sums[sub_vid] = angle_sum;
                }
            }


            // try to guess a reasonable edge length that will give us enough geometry to work with in simplify pass
            double loop_mine, loop_maxe, loop_avge, fill_mine, fill_maxe, fill_avge;

            MeshQueries.EdgeLengthStatsFromEdges(Mesh, FillLoop.Edges, out loop_mine, out loop_maxe, out loop_avge);
            MeshQueries.EdgeLengthStats(fillmesh, out fill_mine, out fill_maxe, out fill_avge);
            double remesh_target_len = loop_avge;

            if (fill_maxe / remesh_target_len > 10)
            {
                remesh_target_len = fill_maxe / 10;
            }
            //double remesh_target_len = Math.Min(loop_avge, fill_avge / 4);

            // remesh up to target edge length, ideally gives us some triangles to work with
            RemesherPro remesh1 = new RemesherPro(fillmesh);

            remesh1.SmoothSpeedT = 1.0;
            MeshConstraintUtil.FixAllBoundaryEdges(remesh1);
            //remesh1.SetTargetEdgeLength(remesh_target_len / 2);       // would this speed things up? on large regions?
            //remesh1.FastestRemesh();
            remesh1.SetTargetEdgeLength(remesh_target_len);
            remesh1.FastestRemesh();

            /*
             * first round: collapse to minimal mesh, while flipping to try to
             * get to ballpark minimal mesh. We stop these passes as soon as
             * we have done two rounds where we couldn't do another collapse
             *
             * This is the most unstable part of the algorithm because there
             * are strong ordering effects. maybe we could sort the edges somehow??
             */

            int zero_collapse_passes = 0;
            int collapse_passes      = 0;

            while (collapse_passes++ < 20 && zero_collapse_passes < 2)
            {
                // collapse pass
                int NE        = fillmesh.MaxEdgeID;
                int collapses = 0;
                for (int ei = 0; ei < NE; ++ei)
                {
                    if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei))
                    {
                        continue;
                    }
                    Index2i ev = fillmesh.GetEdgeV(ei);
                    bool    a_bdry = boundaryv.Contains(ev.a), b_bdry = boundaryv.Contains(ev.b);
                    if (a_bdry && b_bdry)
                    {
                        continue;
                    }
                    int      keepv  = (a_bdry) ? ev.a : ev.b;
                    int      otherv = (keepv == ev.a) ? ev.b : ev.a;
                    Vector3d newv   = fillmesh.GetVertex(keepv);
                    if (MeshUtil.CheckIfCollapseCreatesFlip(fillmesh, ei, newv))
                    {
                        continue;
                    }
                    DMesh3.EdgeCollapseInfo info;
                    MeshResult result = fillmesh.CollapseEdge(keepv, otherv, out info);
                    if (result == MeshResult.Ok)
                    {
                        collapses++;
                    }
                }
                if (collapses == 0)
                {
                    zero_collapse_passes++;
                }
                else
                {
                    zero_collapse_passes = 0;
                }

                // flip pass. we flip in these cases:
                //  1) if angle between current triangles is too small (slightly more than 90 degrees, currently)
                //  2) if angle between flipped triangles is smaller than between current triangles
                //  3) if flipped edge length is shorter *and* such a flip won't flip the normal
                NE = fillmesh.MaxEdgeID;
                Vector3d n1, n2, on1, on2;
                for (int ei = 0; ei < NE; ++ei)
                {
                    if (fillmesh.IsEdge(ei) == false || fillmesh.IsBoundaryEdge(ei))
                    {
                        continue;
                    }
                    bool do_flip = false;

                    Index2i ev = fillmesh.GetEdgeV(ei);
                    MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2);
                    double dot_cur  = n1.Dot(n2);
                    double dot_flip = on1.Dot(on2);
                    if (n1.Dot(n2) < 0.1 || dot_flip > dot_cur + MathUtil.Epsilonf)
                    {
                        do_flip = true;
                    }

                    if (do_flip == false)
                    {
                        Index2i otherv   = fillmesh.GetEdgeOpposingV(ei);
                        double  len_e    = fillmesh.GetVertex(ev.a).Distance(fillmesh.GetVertex(ev.b));
                        double  len_flip = fillmesh.GetVertex(otherv.a).Distance(fillmesh.GetVertex(otherv.b));
                        if (len_flip < len_e)
                        {
                            if (MeshUtil.CheckIfEdgeFlipCreatesFlip(fillmesh, ei) == false)
                            {
                                do_flip = true;
                            }
                        }
                    }

                    if (do_flip)
                    {
                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                    }
                }
            }

            // Sometimes, for some reason, we have a remaining interior vertex (have only ever seen one?)
            // Try to force removal of such vertices, even if it makes ugly mesh
            remove_remaining_interior_verts();


            // enable/disable passes.
            bool DO_FLATTER_PASS   = true;
            bool DO_CURVATURE_PASS = OptimizeDevelopability && true;
            bool DO_AREA_PASS      = OptimizeDevelopability && OptimizeTriangles && true;


            /*
             * In this pass we repeat the flipping iterations from the previous pass.
             *
             * Note that because of the always-flip-if-dot-is-small case (commented),
             * this pass will frequently not converge, as some number of edges will
             * be able to flip back and forth (because neither has large enough dot).
             * This is not ideal, but also, if we remove this behavior, then we
             * generally get worse fills. This case basically introduces a sort of
             * randomization factor that lets us escape local minima...
             *
             */

            HashSet <int> remaining_edges = new HashSet <int>(fillmesh.EdgeIndices());
            HashSet <int> updated_edges   = new HashSet <int>();

            int flatter_passes    = 0;
            int zero_flips_passes = 0;

            while (flatter_passes++ < 40 && zero_flips_passes < 2 && remaining_edges.Count() > 0 && DO_FLATTER_PASS)
            {
                zero_flips_passes++;
                foreach (int ei in remaining_edges)
                {
                    if (fillmesh.IsBoundaryEdge(ei))
                    {
                        continue;
                    }

                    bool do_flip = false;

                    Index2i  ev = fillmesh.GetEdgeV(ei);
                    Vector3d n1, n2, on1, on2;
                    MeshUtil.GetEdgeFlipNormals(fillmesh, ei, out n1, out n2, out on1, out on2);
                    double dot_cur  = n1.Dot(n2);
                    double dot_flip = on1.Dot(on2);
                    if (flatter_passes < 20 && dot_cur < 0.1)   // this check causes oscillatory behavior
                    {
                        do_flip = true;
                    }
                    if (dot_flip > dot_cur + MathUtil.Epsilonf)
                    {
                        do_flip = true;
                    }

                    if (do_flip)
                    {
                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                        if (result == MeshResult.Ok)
                        {
                            zero_flips_passes = 0;
                            add_all_edges(ei, updated_edges);
                        }
                    }
                }

                var tmp = remaining_edges;
                remaining_edges = updated_edges;
                updated_edges   = tmp; updated_edges.Clear();
            }


            int curvature_passes = 0;

            if (DO_CURVATURE_PASS)
            {
                curvatures = new double[fillmesh.MaxVertexID];
                foreach (int vid in fillmesh.VertexIndices())
                {
                    update_curvature(vid);
                }

                remaining_edges = new HashSet <int>(fillmesh.EdgeIndices());
                updated_edges   = new HashSet <int>();

                /*
                 *  In this pass we try to minimize gaussian curvature at all the vertices.
                 *  This will recover sharp edges, etc, and do lots of good stuff.
                 *  However, this pass will not make much progress if we are not already
                 *  relatively close to a minimal mesh, so it really relies on the previous
                 *  passes getting us in the ballpark.
                 */
                while (curvature_passes++ < 40 && remaining_edges.Count() > 0 && DO_CURVATURE_PASS)
                {
                    foreach (int ei in remaining_edges)
                    {
                        if (fillmesh.IsBoundaryEdge(ei))
                        {
                            continue;
                        }

                        Index2i ev = fillmesh.GetEdgeV(ei);
                        Index2i ov = fillmesh.GetEdgeOpposingV(ei);

                        int find_other = fillmesh.FindEdge(ov.a, ov.b);
                        if (find_other != DMesh3.InvalidID)
                        {
                            continue;
                        }

                        double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b);
                        if (total_curv_cur < MathUtil.ZeroTolerancef)
                        {
                            continue;
                        }

                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                        if (result != MeshResult.Ok)
                        {
                            continue;
                        }

                        double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b);

                        bool keep_flip = total_curv_flip < total_curv_cur - MathUtil.ZeroTolerancef;
                        if (keep_flip == false)
                        {
                            result = fillmesh.FlipEdge(ei, out info);
                        }
                        else
                        {
                            update_curvature(ev.a); update_curvature(ev.b);
                            update_curvature(ov.a); update_curvature(ov.b);
                            add_all_edges(ei, updated_edges);
                        }
                    }
                    var tmp = remaining_edges;
                    remaining_edges = updated_edges;
                    updated_edges   = tmp; updated_edges.Clear();
                }
            }
            //System.Console.WriteLine("collapse {0}   flatter {1}   curvature {2}", collapse_passes, flatter_passes, curvature_passes);

            /*
             * In this final pass, we try to improve triangle quality. We flip if
             * the flipped triangles have better total aspect ratio, and the
             * curvature doesn't change **too** much. The .DevelopabilityTolerance
             * parameter determines what is "too much" curvature change.
             */
            if (DO_AREA_PASS)
            {
                remaining_edges = new HashSet <int>(fillmesh.EdgeIndices());
                updated_edges   = new HashSet <int>();
                int area_passes = 0;
                while (remaining_edges.Count() > 0 && area_passes < 20)
                {
                    area_passes++;
                    foreach (int ei in remaining_edges)
                    {
                        if (fillmesh.IsBoundaryEdge(ei))
                        {
                            continue;
                        }

                        Index2i ev = fillmesh.GetEdgeV(ei);
                        Index2i ov = fillmesh.GetEdgeOpposingV(ei);

                        int find_other = fillmesh.FindEdge(ov.a, ov.b);
                        if (find_other != DMesh3.InvalidID)
                        {
                            continue;
                        }

                        double total_curv_cur = curvature_metric_cached(ev.a, ev.b, ov.a, ov.b);

                        double a = aspect_metric(ei);
                        if (a > 1)
                        {
                            continue;
                        }

                        DMesh3.EdgeFlipInfo info;
                        MeshResult          result = fillmesh.FlipEdge(ei, out info);
                        if (result != MeshResult.Ok)
                        {
                            continue;
                        }

                        double total_curv_flip = curvature_metric_eval(ev.a, ev.b, ov.a, ov.b);

                        bool keep_flip = Math.Abs(total_curv_cur - total_curv_flip) < DevelopabilityTolerance;
                        if (keep_flip == false)
                        {
                            result = fillmesh.FlipEdge(ei, out info);
                        }
                        else
                        {
                            update_curvature(ev.a); update_curvature(ev.b);
                            update_curvature(ov.a); update_curvature(ov.b);
                            add_all_edges(ei, updated_edges);
                        }
                    }
                    var tmp = remaining_edges;
                    remaining_edges = updated_edges;
                    updated_edges   = tmp; updated_edges.Clear();
                }
            }


            regionop.BackPropropagate();
            FillTriangles = regionop.CurrentBaseTriangles;
            FillVertices  = regionop.CurrentBaseInteriorVertices().ToArray();

            return(true);
        }
Пример #19
0
        /// <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>();

            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.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;
                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);

                    // 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)
                    {
                        throw new MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: found broken neighbourhood at vertex " + cure_b)
                              {
                                  UnclosedLoop = true
                              }
                    }
                    ;

                    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);

                            if (EdgeFilterF != null)
                            {
                                num_be = BufferUtil.FilterInPlace(all_e, EdgeFilterF, num_be);
                            }

                            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 MeshBoundaryLoopsException("MeshBoundaryLoops.Compute: cannot find valid outgoing edge at bowtie vertex " + cure_b)
                                      {
                                          BowtieFailure = true
                                      }
                            }
                            ;
                        }

                        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);
        }

        // [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 = math.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;
                }
            }
            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);
        }
    }
Пример #20
0
        private static DGraph2 CreateMinGraph(DGraph2 input, int NV, out Dictionary <int, List <int> > minEdgePaths, out DVector <double> edgeWeights)
        {
            /*
             * OK, as input we have a graph of our original polygon and a bunch of inserted
             * segments ("spans"). Orig polygon segments have gid < 0, and span segments >= 0.
             * However between polygon/span junctions, we have an arbitrary # of polygon edges.
             * So first step is to simplify these to single-edge "connectors", in new graph MinGraph.
             * the [connector-edge, path] mappings (if pathlen > 1) are stored in MinEdgePaths
             * We also store a weight for each connector edge in EdgeWeights (just distance for now)
             */

            var minGraph = new DGraph2();

            minEdgePaths = new Dictionary <int, List <int> >();
            edgeWeights  = new DVector <double>();
            edgeWeights.resize(NV);
            BitArray done_edge = new BitArray(input.MaxEdgeID);  // we should see each edge twice, this avoids repetition

            // vertex map from input graph to MinGraph
            int[] MapV = new int[NV];
            for (int i = 0; i < NV; ++i)
            {
                MapV[i] = -1;
            }

            for (int a = 0; a < NV; ++a)
            {
                if (input.IsVertex(a) == false || input.IsJunctionVertex(a) == false)
                {
                    continue;
                }

                if (MapV[a] == -1)
                {
                    MapV[a] = minGraph.AppendVertex(input.GetVertex(a));
                }

                foreach (int eid in input.VtxEdgesItr(a))
                {
                    if (done_edge[eid])
                    {
                        continue;
                    }

                    Index2i ev = input.GetEdgeV(eid);
                    int     b  = (ev.a == a) ? ev.b : ev.a;

                    if (input.IsJunctionVertex(b))
                    {
                        // if we have junction/juntion connection, we can just copy this edge to MinGraph

                        if (MapV[b] == -1)
                        {
                            MapV[b] = minGraph.AppendVertex(input.GetVertex(b));
                        }

                        int gid      = input.GetEdgeGroup(eid);
                        int existing = minGraph.FindEdge(MapV[a], MapV[b]);
                        if (existing == DMesh3.InvalidID)
                        {
                            int    new_eid  = minGraph.AppendEdge(MapV[a], MapV[b], gid);
                            double path_len = input.GetEdgeSegment(eid).Length;
                            edgeWeights.insertAt(path_len, new_eid);
                        }
                        else
                        {
                            // we may have inserted this edge already in the simplify branch, this happens eg at the
                            // edge of a circle where the minimal path is between the same vertices as the segment.
                            // But if this is also a fill edge, we want to treat it that way (determind via positive gid)
                            if (gid >= 0)
                            {
                                minGraph.SetEdgeGroup(existing, gid);
                            }
                        }
                    }
                    else
                    {
                        // not a junction - walk until we find other vtx, and add single edge to MinGraph
                        List <int> path = DGraph2Util.WalkToNextNonRegularVtx(input, a, eid);
                        if (path == null || path.Count < 2)
                        {
                            throw new Exception("build_min_graph: invalid walk!");
                        }

                        int c = path[path.Count - 1];

                        // it is somehow possible to get loops...
                        if (c == a)
                        {
                            goto skip_this_edge;
                        }

                        if (MapV[c] == -1)
                        {
                            MapV[c] = minGraph.AppendVertex(input.GetVertex(c));
                        }

                        if (minGraph.FindEdge(MapV[a], MapV[c]) == DMesh3.InvalidID)
                        {
                            int new_eid = minGraph.AppendEdge(MapV[a], MapV[c], -2);
                            path.Add(MapV[a]); path.Add(MapV[c]);
                            minEdgePaths[new_eid] = path;
                            double path_len = DGraph2Util.PathLength(input, path);
                            edgeWeights.insertAt(path_len, new_eid);
                        }
                    }

skip_this_edge:
                    done_edge[eid] = true;
                }
            }
            return(minGraph);
        }
Пример #21
0
        private FillCurveSet2d WalkPathGraph(DGraph2 pathGraph)
        {
            var boundaries = IdentifyBoundaryHashSet(pathGraph);

            var paths = new FillCurveSet2d();

            // walk paths from boundary vertices
            while (boundaries.Count > 0)
            {
                int start_vid = boundaries.First();
                boundaries.Remove(start_vid);
                int vid = start_vid;
                int eid = pathGraph.GetVtxEdges(vid)[0];

                var path = new FillCurve <FillSegment>()
                {
                    FillType = this.FillType
                };

                path.BeginCurve(pathGraph.GetVertex(vid));
                while (true)
                {
                    Index2i next = DGraph2Util.NextEdgeAndVtx(eid, vid, pathGraph);
                    eid = next.a;
                    vid = next.b;
                    int gid = pathGraph.GetEdgeGroup(eid);
                    if (gid < 0)
                    {
                        path.AddToCurve(pathGraph.GetVertex(vid), new FillSegment(true));
                    }
                    else
                    {
                        path.AddToCurve(pathGraph.GetVertex(vid));
                    }

                    if (boundaries.Contains(vid))
                    {
                        boundaries.Remove(vid);
                        break;
                    }
                }

                // discard paths that are too short
                if (path.TotalLength() < MinPathLengthMM)
                {
                    continue;
                }

                // run polyline simplification to get rid of unneccesary detail in connectors
                // [TODO] we could do this at graph level...)
                // [TODO] maybe should be checking for collisions? we could end up creating
                //  non-trivial overlaps here...
                if (SimplifyAmount != SimplificationLevel.None && path.Elements.Count > 1)
                {
                    path = SimplifyPath(path);
                }

                paths.Append(path);
            }
            return(paths);
        }
Пример #22
0
        public virtual void RemeshIteration()
        {
            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();

            IEnumerable <int> edgesItr = EdgesIterator();

            if (modified_edges == null)
            {
                modified_edges = new HashSet <int>();
            }
            else
            {
                edges_buffer.Clear(); edges_buffer.AddRange(modified_edges);
                edgesItr = edges_buffer;
                modified_edges.Clear();
            }

            int startEdges = Mesh.EdgeCount;
            int flips = 0, splits = 0, collapes = 0;

            ModifiedEdgesLastPass = 0;
            int processedLastPass = 0;

            foreach (int cur_eid in edgesItr)
            {
                if (Cancelled())
                {
                    return;
                }

                if (mesh.IsEdge(cur_eid))
                {
                    Index2i ev = mesh.GetEdgeV(cur_eid);
                    Index2i ov = mesh.GetEdgeOpposingV(cur_eid);

                    // TODO: optimize the queuing here, are over-doing it!
                    // TODO: be able to queue w/o flip (eg queue from smooth never requires flip check)

                    processedLastPass++;
                    ProcessResult result = ProcessEdge(cur_eid);
                    if (result == ProcessResult.Ok_Collapsed)
                    {
                        queue_one_ring(ev.a); queue_one_ring(ev.b);
                        queue_one_ring(ov.a); queue_one_ring(ov.b);
                        ModifiedEdgesLastPass++;
                        collapes++;
                    }
                    else if (result == ProcessResult.Ok_Split)
                    {
                        queue_one_ring(ev.a); queue_one_ring(ev.b);
                        queue_one_ring(ov.a); queue_one_ring(ov.b);
                        ModifiedEdgesLastPass++;
                        splits++;
                    }
                    else if (result == ProcessResult.Ok_Flipped)
                    {
                        queue_one_ring(ev.a); queue_one_ring(ev.b);
                        queue_one_ring(ov.a); queue_one_ring(ov.b);
                        ModifiedEdgesLastPass++;
                        flips++;
                    }
                }
            }
            end_ops();

            //System.Console.WriteLine("RemeshIteration: start {0}  end {1}  processed: {2}   modified: {3}  queue: {4}",
            //    startEdges, Mesh.EdgeCount, processedLastPass, ModifiedEdgesLastPass, modified_edges.Count);
            //System.Console.WriteLine("   flips {0}  splits {1}  collapses {2}", flips, splits, collapes);

            if (Cancelled())
            {
                return;
            }

            begin_smooth();
            if (EnableSmoothing && SmoothSpeedT > 0)
            {
                TrackedSmoothPass(EnableParallelSmooth);
                DoDebugChecks();
            }
            end_smooth();

            if (Cancelled())
            {
                return;
            }

            begin_project();
            if (ProjectionTarget != null && ProjectionMode == TargetProjectionMode.AfterRefinement)
            {
                //FullProjectionPass();

                if (UseFaceAlignedProjection)
                {
                    for (int i = 0; i < FaceProjectionPassesPerIteration; ++i)
                    {
                        TrackedFaceProjectionPass();
                    }
                }
                else
                {
                    TrackedProjectionPass(EnableParallelProjection);
                }
                DoDebugChecks();
            }
            end_project();

            end_pass();
        }
Пример #23
0
        List <Polygon2d> decompose_cluster_up(DMesh3 mesh)
        {
            optimize_mesh(mesh);
            mesh.CompactInPlace();
            mesh.DiscardTriangleGroups(); mesh.EnableTriangleGroups(0);

            double minLength = Settings.MaxBridgeWidthMM * 0.75;
            double minArea   = minLength * minLength;

            Dictionary <int, double>         areas   = new Dictionary <int, double>();
            Dictionary <int, HashSet <int> > trisets = new Dictionary <int, HashSet <int> >();
            HashSet <int> active_groups = new HashSet <int>();

            Action <int, int> add_tri_to_group = (tid, gid) => {
                mesh.SetTriangleGroup(tid, gid);
                areas[gid] = areas[gid] + mesh.GetTriArea(tid);
                trisets[gid].Add(tid);
            };
            Action <int, int> add_group_to_group = (gid, togid) => {
                var set = trisets[togid];
                foreach (int tid in trisets[gid])
                {
                    mesh.SetTriangleGroup(tid, togid);
                    set.Add(tid);
                }
                areas[togid] += areas[gid];
                active_groups.Remove(gid);
            };
            Func <IEnumerable <int>, int> find_min_area_group = (tri_itr) => {
                int min_gid = -1; double min_area = double.MaxValue;
                foreach (int tid in tri_itr)
                {
                    int    gid = mesh.GetTriangleGroup(tid);
                    double a   = areas[gid];
                    if (a < min_area)
                    {
                        min_area = a;
                        min_gid  = gid;
                    }
                }
                return(min_gid);
            };


            foreach (int eid in MeshIterators.InteriorEdges(mesh))
            {
                Index2i et = mesh.GetEdgeT(eid);
                if (mesh.GetTriangleGroup(et.a) != 0 || mesh.GetTriangleGroup(et.b) != 0)
                {
                    continue;
                }
                int gid = mesh.AllocateTriangleGroup();
                areas[gid]   = 0;
                trisets[gid] = new HashSet <int>();
                active_groups.Add(gid);
                add_tri_to_group(et.a, gid);
                add_tri_to_group(et.b, gid);
            }
            foreach (int tid in mesh.TriangleIndices())
            {
                if (mesh.GetTriangleGroup(tid) != 0)
                {
                    continue;
                }
                int gid = find_min_area_group(mesh.TriTrianglesItr(tid));
                add_tri_to_group(tid, gid);
            }


            IndexPriorityQueue pq = new IndexPriorityQueue(mesh.MaxGroupID);

            foreach (var pair in areas)
            {
                pq.Insert(pair.Key, (float)pair.Value);
            }
            while (pq.Count > 0)
            {
                int gid = pq.First;
                pq.Remove(gid);
                if (areas[gid] > minArea)                    // ??
                {
                    break;
                }

                List <int> nbr_groups = find_neighbour_groups(mesh, gid, trisets[gid]);
                int        min_gid = -1; double min_area = double.MaxValue;
                foreach (int ngid in nbr_groups)
                {
                    double a = areas[ngid];
                    if (a < min_area)
                    {
                        min_area = a;
                        min_gid  = ngid;
                    }
                }
                if (min_gid != -1)
                {
                    add_group_to_group(gid, min_gid);
                    pq.Remove(min_gid);
                    pq.Insert(min_gid, (float)areas[min_gid]);
                }
            }



            List <Polygon2d> result = new List <Polygon2d>();

            int[][] sets = FaceGroupUtil.FindTriangleSetsByGroup(mesh);
            foreach (var set in sets)
            {
                result.Add(make_poly(mesh, set));
            }
            return(result);
        }
Пример #24
0
        // [TODO] projection pass
        //   - only project vertices modified by smooth pass?
        //   - and/or verts in set of modified edges?
        protected virtual void TrackedFaceProjectionPass()
        {
            IOrientedProjectionTarget normalTarget = ProjectionTarget as IOrientedProjectionTarget;

            if (normalTarget == null)
            {
                throw new Exception("RemesherPro.TrackedFaceProjectionPass: projection target does not have normals!");
            }

            InitializeBuffersForFacePass();

            SpinLock buffer_lock = new SpinLock();

            // this function computes rotated position of triangle, such that it
            // aligns with face normal on target surface. We accumulate weighted-average
            // of vertex positions, which we will then use further down where possible.
            Action <int> process_triangle = (tid) => {
                Vector3d normal; double area; Vector3d centroid;
                mesh.GetTriInfo(tid, out normal, out area, out centroid);

                Vector3d projNormal;
                Vector3d projPos = normalTarget.Project(centroid, out projNormal);

                Index3i  tv = mesh.GetTriangle(tid);
                Vector3d v0 = mesh.GetVertex(tv.a), v1 = mesh.GetVertex(tv.b), v2 = mesh.GetVertex(tv.c);

                // ugh could probably do this more efficiently...
                Frame3f triF = new Frame3f(centroid, normal);
                v0 = triF.ToFrameP(ref v0); v1 = triF.ToFrameP(ref v1); v2 = triF.ToFrameP(ref v2);
                triF.AlignAxis(2, (Vector3f)projNormal);
                triF.Origin = (Vector3f)projPos;
                v0          = triF.FromFrameP(ref v0); v1 = triF.FromFrameP(ref v1); v2 = triF.FromFrameP(ref v2);

                double dot = normal.Dot(projNormal);
                dot = MathUtil.Clamp(dot, 0, 1.0);
                double w = area * (dot * dot * dot);

                bool taken = false;
                buffer_lock.Enter(ref taken);
                vBufferV[tv.a] += w * v0; vBufferVWeights[tv.a] += w;
                vBufferV[tv.b] += w * v1; vBufferVWeights[tv.b] += w;
                vBufferV[tv.c] += w * v2; vBufferVWeights[tv.c] += w;
                buffer_lock.Exit();
            };

            // compute face-aligned vertex positions
            gParallel.ForEach(mesh.TriangleIndices(), process_triangle);


            // ok now we filter out all the positions we can't change, as well as vertices that
            // did not actually move. We also queue any edges that moved far enough to fall
            // under min/max edge length thresholds
            gParallel.ForEach(mesh.VertexIndices(), (vID) => {
                vModifiedV[vID] = false;
                if (vBufferVWeights[vID] < MathUtil.ZeroTolerance)
                {
                    return;
                }
                if (vertex_is_constrained(vID))
                {
                    return;
                }
                if (VertexControlF != null && (VertexControlF(vID) & VertexControl.NoProject) != 0)
                {
                    return;
                }

                Vector3d curpos  = mesh.GetVertex(vID);
                Vector3d projPos = vBufferV[vID] / vBufferVWeights[vID];
                if (curpos.EpsilonEqual(projPos, MathUtil.ZeroTolerancef))
                {
                    return;
                }

                vModifiedV[vID] = true;
                vBufferV[vID]   = projPos;

                foreach (int eid in mesh.VtxEdgesItr(vID))
                {
                    Index2i ev      = Mesh.GetEdgeV(eid);
                    int othervid    = (ev.a == vID) ? ev.b : ev.a;
                    Vector3d otherv = mesh.GetVertex(othervid);
                    double old_len  = curpos.Distance(otherv);
                    double new_len  = projPos.Distance(otherv);
                    if (new_len < MinEdgeLength || new_len > MaxEdgeLength)
                    {
                        queue_edge_safe(eid);
                    }
                }
            });


            // update vertices
            ApplyVertexBuffer(true);
        }
Пример #25
0
        protected virtual void TrackedSmoothPass(bool bParallel)
        {
            InitializeVertexBufferForPass();

            Func <DMesh3, int, double, Vector3d> smoothFunc = MeshUtil.UniformSmooth;

            if (CustomSmoothF != null)
            {
                smoothFunc = CustomSmoothF;
            }
            else
            {
                if (SmoothType == SmoothTypes.MeanValue)
                {
                    smoothFunc = MeshUtil.MeanValueSmooth;
                }
                else if (SmoothType == SmoothTypes.Cotan)
                {
                    smoothFunc = MeshUtil.CotanSmooth;
                }
            }

            Action <int> smooth = (vID) => {
                Vector3d vCur      = Mesh.GetVertex(vID);
                bool     bModified = false;
                Vector3d vSmoothed = ComputeSmoothedVertexPos(vID, smoothFunc, out bModified);
                //if (vCur.EpsilonEqual(vSmoothed, MathUtil.ZeroTolerancef))
                //    bModified = false;
                if (bModified)
                {
                    vModifiedV[vID] = true;
                    vBufferV[vID]   = vSmoothed;

                    foreach (int eid in mesh.VtxEdgesItr(vID))
                    {
                        Index2i  ev       = Mesh.GetEdgeV(eid);
                        int      othervid = (ev.a == vID) ? ev.b : ev.a;
                        Vector3d otherv   = mesh.GetVertex(othervid);
                        double   old_len  = vCur.Distance(otherv);
                        double   new_len  = vSmoothed.Distance(otherv);
                        if (new_len < MinEdgeLength || new_len > MaxEdgeLength)
                        {
                            queue_edge_safe(eid);
                        }
                    }
                }
            };


            if (bParallel)
            {
                gParallel.ForEach <int>(smooth_vertices(), smooth);
            }
            else
            {
                foreach (int vID in smooth_vertices())
                {
                    smooth(vID);
                }
            }

            ApplyVertexBuffer(bParallel);
            //System.Console.WriteLine("Smooth Pass: queue: {0}", modified_edges.Count);
        }
Пример #26
0
        public void CollapseEdge(int eid, Frame3f posL, bool bInteractive)
        {
            if (PreviewMesh.IsEdge(eid) == false)
            {
                DebugUtil.Log("MeshEditorTool.CollapseEdge: invalid eid!");
                return;
            }
            Index2i ev = PreviewMesh.GetEdgeV(eid);
            int     keep = ev.a, discard = ev.b;
            bool    boundarya = PreviewMesh.IsBoundaryVertex(keep);
            bool    boundaryb = PreviewMesh.IsBoundaryVertex(discard);

            if (boundaryb && !boundarya)
            {
                keep = ev.b; discard = ev.a;
            }
            HashSet <int> removeT = new HashSet <int>();

            foreach (int tid in PreviewMesh.VtxTrianglesItr(keep))
            {
                removeT.Add(tid);
            }
            foreach (int tid in PreviewMesh.VtxTrianglesItr(discard))
            {
                removeT.Add(tid);
            }

            RemoveTrianglesMeshChange removeChange = null;
            AddTrianglesMeshChange    addChange    = null;

            previewSO.EditAndUpdateMesh((mesh) => {
                removeChange = new RemoveTrianglesMeshChange();
                removeChange.InitializeFromExisting(mesh, removeT);

                DMesh3.EdgeCollapseInfo collapseInfo;
                if (mesh.CollapseEdge(keep, discard, out collapseInfo) == MeshResult.Ok)
                {
                    foreach (int tid in removeT)
                    {
                        PreviewSpatial.RemoveTriangle(tid);
                    }
                    foreach (int tid in PreviewMesh.VtxTrianglesItr(keep))
                    {
                        PreviewSpatial.AddTriangle(tid);
                    }
                    if (boundarya == false & boundaryb == false)
                    {
                        mesh.SetVertex(keep, posL.Origin);
                    }

                    addChange = new AddTrianglesMeshChange();
                    addChange.InitializeFromExisting(mesh, new List <int>()
                    {
                        keep
                    }, get_vtx_tris(mesh, keep));
                }
            }, GeometryEditTypes.ArbitraryEdit);

            if (addChange != null)
            {
                add_replace_change(removeChange, addChange, bInteractive);
            }
        }
Пример #27
0
        private void FilterSelfOverlaps(double overlapRadius, bool bResample = true)
        {
            // [RMS] this tolerance business is not workign properly right now. The problem is
            //  that decimator loses corners!

            // To simplify the computation we are going to resample the curve so that no adjacent
            // are within a given distance. Then we can use distance-to-segments, with the two adjacent
            // segments filtered out, to measure self-distance

            double dist_thresh      = overlapRadius;
            double sharp_thresh_deg = 45;

            //Profiler.Start("InitialResample");

            // resample graph. the degenerate-edge thing is necessary to
            // filter out tiny segments that are functionally sharp corners,
            // but geometrically are made of multiple angles < threshold
            // (maybe there is a better way to do this?)
            DGraph2Resampler r = new DGraph2Resampler(Graph);

            r.CollapseDegenerateEdges(overlapRadius / 10);
            if (bResample)
            {
                r.SplitToMaxEdgeLength(overlapRadius / 2);
                r.CollapseToMinEdgeLength(overlapRadius / 3);
            }
            r.CollapseDegenerateEdges(overlapRadius / 10);

            //Profiler.StopAndAccumulate("InitialResample");
            //Profiler.Start("SharpCorners");

            // find sharp corners
            List <int> sharp_corners = new List <int>();

            foreach (int vid in Graph.VertexIndices())
            {
                if (is_fixed_v(vid))
                {
                    continue;
                }
                double open_angle = Graph.OpeningAngle(vid);
                if (open_angle < sharp_thresh_deg)
                {
                    sharp_corners.Add(vid);
                }
            }

            // disconnect at sharp corners
            foreach (int vid in sharp_corners)
            {
                if (Graph.IsVertex(vid) == false)
                {
                    continue;
                }
                int      e0     = Graph.GetVtxEdges(vid)[0];
                Index2i  ev     = Graph.GetEdgeV(e0);
                int      otherv = (ev.a == vid) ? ev.b : ev.a;
                Vector2d newpos = Graph.GetVertex(vid);  //0.5 * (Graph.GetVertex(vid) + Graph.GetVertex(otherv));
                Graph.RemoveEdge(e0, false);
                int newvid = Graph.AppendVertex(newpos);
                Graph.AppendEdge(newvid, otherv);
            }

            //Profiler.StopAndAccumulate("SharpCorners");
            //Profiler.Start("HashTable");

            // build edge hash table  (cell size is just a ballpark guess here...)
            edge_hash = new SegmentHashGrid2d <int>(3 * overlapRadius, -1);
            foreach (int eid in Graph.EdgeIndices())
            {
                Segment2d seg = Graph.GetEdgeSegment(eid);
                edge_hash.InsertSegment(eid, seg.Center, seg.Extent);
            }

            if (CollisionGraph.EdgeCount > 0)
            {
                collision_edge_hash = new SegmentHashGrid2d <int>(3 * CollisionRadius, -1);
                foreach (int eid in CollisionGraph.EdgeIndices())
                {
                    Segment2d seg = CollisionGraph.GetEdgeSegment(eid);
                    collision_edge_hash.InsertSegment(eid, seg.Center, seg.Extent);
                }
            }

            //Profiler.StopAndAccumulate("HashTable");
            //Profiler.Start("Erode1");

            // Step 1: erode from boundary vertices
            List <int> boundaries = new List <int>();

            foreach (int vid in Graph.VertexIndices())
            {
                if (Graph.GetVtxEdgeCount(vid) == 1)
                {
                    boundaries.Add(vid);
                }
            }
            foreach (int vid in boundaries)
            {
                if (Graph.IsVertex(vid) == false)
                {
                    continue;
                }
                double dist           = MinSelfSegDistance(vid, 2 * dist_thresh);
                double collision_dist = MinCollisionConstraintDistance(vid, CollisionRadius);
                if (dist < dist_thresh || collision_dist < CollisionRadius)
                {
                    int eid = Graph.GetVtxEdges(vid)[0];
                    decimate_forward(vid, eid, dist_thresh);
                }
            }

            //Profiler.StopAndAccumulate("Erode1");
            //Profiler.Start("OpenAngleSort");

            //
            // Step 2: find any other possible self-overlaps and erode them.
            //

            // sort all vertices by opening angle. For any overlap, we can erode
            // on either side. Prefer to erode on side with higher curvature.
            List <Vector2d> remaining_v = new List <Vector2d>(Graph.MaxVertexID);

            foreach (int vid in Graph.VertexIndices())
            {
                if (is_fixed_v(vid))
                {
                    continue;
                }
                double open_angle = Graph.OpeningAngle(vid);
                if (open_angle == double.MaxValue)
                {
                    continue;
                }
                remaining_v.Add(new Vector2d(vid, open_angle));
            }
            remaining_v.Sort((a, b) => { return((a.y < b.y) ? -1 : (a.y > b.y ? 1 : 0)); });

            //Profiler.StopAndAccumulate("OpenAngleSort");
            //Profiler.Start("Erode2");

            // look for overlap vertices. When we find one, erode on both sides.
            foreach (Vector2d vinfo in remaining_v)
            {
                int vid = (int)vinfo.x;
                if (Graph.IsVertex(vid) == false)
                {
                    continue;
                }
                double dist = MinSelfSegDistance(vid, 2 * dist_thresh);
                if (dist < dist_thresh)
                {
                    List <int> nbrs = new List <int>(Graph.GetVtxEdges(vid));
                    foreach (int eid in nbrs)
                    {
                        if (Graph.IsEdge(eid))    // may have been decimated!
                        {
                            decimate_forward(vid, eid, dist_thresh);
                        }
                    }
                }
            }

            //Profiler.StopAndAccumulate("Erode2");
            //Profiler.Start("FlatCollapse");

            // get rid of extra vertices
            r.CollapseFlatVertices(FinalFlatCollapseAngleThreshDeg);

            //Profiler.StopAndAccumulate("FlatCollapse");
        }
Пример #28
0
        /// <summary>
        /// fill poly w/ adjacent straight line segments, connected by connectors
        /// </summary>
        protected FillCurveSet2d ComputeFillPaths(GeneralPolygon2d poly)
        {
            FillCurveSet2d paths = new FillCurveSet2d();

            // smooth the input poly a little bit, this simplifies the filling
            // (simplify after?)
            //GeneralPolygon2d smoothed = poly.Duplicate();
            //CurveUtils2.LaplacianSmoothConstrained(smoothed, 0.5, 5, ToolWidth / 2, true, false);
            //poly = smoothed;

            // compute 2D non-manifold graph consisting of original polygon and
            // inserted line segments
            DGraph2 spanGraph = ComputeSpanGraph(poly);

            if (spanGraph == null || spanGraph.VertexCount == poly.VertexCount)
            {
                return(paths);
            }


            DGraph2 pathGraph = BuildPathGraph(spanGraph);

            // filter out self-overlaps from graph
            if (FilterSelfOverlaps)
            {
                PathOverlapRepair repair = new PathOverlapRepair(pathGraph);
                repair.OverlapRadius       = ToolWidth * SelfOverlapToolWidthX;
                repair.PreserveEdgeFilterF = (eid) => {
                    return(repair.Graph.GetEdgeGroup(eid) > 0);
                };
                repair.Compute();
                pathGraph = repair.GetResultGraph();
            }


            HashSet <int> boundaries = new HashSet <int>();

            foreach (int vid in pathGraph.VertexIndices())
            {
                if (pathGraph.IsBoundaryVertex(vid))
                {
                    boundaries.Add(vid);
                }
                if (pathGraph.IsJunctionVertex(vid))
                {
                    throw new Exception("DenseLinesFillPolygon: PathGraph has a junction???");
                }
            }

            // walk paths from boundary vertices
            while (boundaries.Count > 0)
            {
                int start_vid = boundaries.First();
                boundaries.Remove(start_vid);
                int vid = start_vid;
                int eid = pathGraph.GetVtxEdges(vid)[0];

                FillPolyline2d path = new FillPolyline2d()
                {
                    TypeFlags = this.TypeFlags
                };

                path.AppendVertex(pathGraph.GetVertex(vid));
                while (true)
                {
                    Index2i next = DGraph2Util.NextEdgeAndVtx(eid, vid, pathGraph);
                    eid = next.a;
                    vid = next.b;
                    int gid = pathGraph.GetEdgeGroup(eid);
                    if (gid < 0)
                    {
                        path.AppendVertex(pathGraph.GetVertex(vid), TPVertexFlags.IsConnector);
                    }
                    else
                    {
                        path.AppendVertex(pathGraph.GetVertex(vid));
                    }

                    if (boundaries.Contains(vid))
                    {
                        boundaries.Remove(vid);
                        break;
                    }
                }

                // discard paths that are too short
                if (path.ArcLength < MinPathLengthMM)
                {
                    continue;
                }


                // run polyline simplification to get rid of unneccesary detail in connectors
                // [TODO] we could do this at graph level...)
                // [TODO] maybe should be checkign for collisions? we could end up creating
                //  non-trivial overlaps here...
                if (SimplifyAmount != SimplificationLevel.None && path.VertexCount > 2)
                {
                    PolySimplification2 simp = new PolySimplification2(path);
                    switch (SimplifyAmount)
                    {
                    default:
                    case SimplificationLevel.Minor:
                        simp.SimplifyDeviationThreshold = ToolWidth / 4; break;

                    case SimplificationLevel.Aggressive:
                        simp.SimplifyDeviationThreshold = ToolWidth; break;

                    case SimplificationLevel.Moderate:
                        simp.SimplifyDeviationThreshold = ToolWidth / 2; break;
                    }
                    simp.Simplify();
                    path = new FillPolyline2d(simp.Result.ToArray())
                    {
                        TypeFlags = this.TypeFlags
                    };
                }

                paths.Append(path);
            }


            // Check to make sure that we are not putting way too much material in the
            // available volume. Computes extrusion volume from path length and if the
            // ratio is too high, scales down the path thickness
            // TODO: do we need to compute volume? If we just divide everything by
            // height we get the same scaling, no? Then we don't need layer height.
            if (MaxOverfillRatio > 0)
            {
                throw new NotImplementedException("this is not finished yet");
#if false
                double LayerHeight = 0.2;               // AAAHHH hardcoded nonono

                double len         = paths.TotalLength();
                double extrude_vol = ExtrusionMath.PathLengthToVolume(LayerHeight, ToolWidth, len);
                double polygon_vol = LayerHeight * Math.Abs(poly.Area);
                double ratio       = extrude_vol / polygon_vol;

                if (ratio > MaxOverfillRatio && PathSpacing == ToolWidth)
                {
                    double use_width = ExtrusionMath.WidthFromTargetVolume(LayerHeight, len, polygon_vol);
                    //System.Console.WriteLine("Extrusion volume: {0}   PolyVolume: {1}   % {2}   ScaledWidth: {3}",
                    //extrude_vol, polygon_vol, extrude_vol / polygon_vol, use_width);

                    foreach (var path in paths.Curves)
                    {
                        path.CustomThickness = use_width;
                    }
                }
#endif
            }

            return(paths);
        }
Пример #29
0
        /// <summary>
        /// Assumption is that input graph is a polygon with inserted ray-spans. We want to
        /// find a set of paths (ie no junctions) that cover all the spans, and travel between
        /// adjacent spans along edges of the input polygon.
        /// </summary>
        protected DGraph2 BuildPathGraph(DGraph2 input)
        {
            int NV = input.MaxVertexID;

            /*
             * OK, as input we have a graph of our original polygon and a bunch of inserted
             * segments ("spans"). Orig polygon segments have gid < 0, and span segments >= 0.
             * However between polygon/span junctions, we have an arbitrary # of polygon edges.
             * So first step is to simplify these to single-edge "connectors", in new graph MinGraph.
             * the [connector-edge, path] mappings (if pathlen > 1) are stored in MinEdgePaths
             * We also store a weight for each connector edge in EdgeWeights (just distance for now)
             */

            DGraph2 MinGraph = new DGraph2();
            Dictionary <int, List <int> > MinEdgePaths = new Dictionary <int, List <int> >();
            DVector <double> EdgeWeights = new DVector <double>(); EdgeWeights.resize(NV);
            BitArray         done_edge   = new BitArray(input.MaxEdgeID); // we should see each edge twice, this avoids repetition

            // vertex map from input graph to MinGraph
            int[] MapV = new int[NV];
            for (int i = 0; i < NV; ++i)
            {
                MapV[i] = -1;
            }

            for (int a = 0; a < NV; ++a)
            {
                if (input.IsVertex(a) == false || input.IsJunctionVertex(a) == false)
                {
                    continue;
                }

                if (MapV[a] == -1)
                {
                    MapV[a] = MinGraph.AppendVertex(input.GetVertex(a));
                }

                foreach (int eid in input.VtxEdgesItr(a))
                {
                    if (done_edge[eid])
                    {
                        continue;
                    }

                    Index2i ev = input.GetEdgeV(eid);
                    int     b  = (ev.a == a) ? ev.b : ev.a;

                    if (input.IsJunctionVertex(b))
                    {
                        // if we have junction/juntion connection, we can just copy this edge to MinGraph

                        if (MapV[b] == -1)
                        {
                            MapV[b] = MinGraph.AppendVertex(input.GetVertex(b));
                        }

                        int gid      = input.GetEdgeGroup(eid);
                        int existing = MinGraph.FindEdge(MapV[a], MapV[b]);
                        if (existing == DMesh3.InvalidID)
                        {
                            int    new_eid  = MinGraph.AppendEdge(MapV[a], MapV[b], gid);
                            double path_len = input.GetEdgeSegment(eid).Length;
                            EdgeWeights.insertAt(path_len, new_eid);
                        }
                        else
                        {
                            // we may have inserted this edge already in the simplify branch, this happens eg at the
                            // edge of a circle where the minimal path is between the same vertices as the segment.
                            // But if this is also a fill edge, we want to treat it that way (determind via positive gid)
                            if (gid >= 0)
                            {
                                MinGraph.SetEdgeGroup(existing, gid);
                            }
                        }
                    }
                    else
                    {
                        // not a junction - walk until we find other vtx, and add single edge to MinGraph
                        List <int> path = DGraph2Util.WalkToNextNonRegularVtx(input, a, eid);
                        if (path == null || path.Count < 2)
                        {
                            throw new Exception("build_min_graph: invalid walk!");
                        }

                        int c = path[path.Count - 1];

                        // it is somehow possible to get loops...
                        if (c == a)
                        {
                            goto skip_this_edge;
                        }

                        if (MapV[c] == -1)
                        {
                            MapV[c] = MinGraph.AppendVertex(input.GetVertex(c));
                        }

                        if (MinGraph.FindEdge(MapV[a], MapV[c]) == DMesh3.InvalidID)
                        {
                            int new_eid = MinGraph.AppendEdge(MapV[a], MapV[c], -2);
                            path.Add(MapV[a]); path.Add(MapV[c]);
                            MinEdgePaths[new_eid] = path;
                            double path_len = DGraph2Util.PathLength(input, path);
                            EdgeWeights.insertAt(path_len, new_eid);
                        }
                    }

skip_this_edge:
                    done_edge[eid] = true;
                }
            }


            // [TODO] filter MinGraph to remove invalid connectors
            //    - can a connector between two connectors happen? that would be bad.
            ///   - connector that is too close to paths should be ignored (ie avoid collisions)


            /*
             * Now that we have MinGraph, we can easily walk between the spans because
             * they are connected by at most one edge. To find a sequence of spans, we
             * pick one to start, then walk along connectors, discarding as we go,
             * so that we don't pass through these vertices again. Repeat until
             * there are no remaining spans.
             */

            // [TODO]
            //  do we actually have to delete from MinGraph? this prevents us from doing
            //  certain things, like trying different options. Maybe could use a hash for
            //  remaining vertices and edges instead?

            DGraph2  PathGraph = new DGraph2();
            Vector2d sortAxis  = Vector2d.FromAngleDeg(AngleDeg).Perp;

            while (true)
            {
                // find most extreme edge to start at
                // [TODO] could use segment gid here as we set them based on insertion span!
                // [TODO] could use a smarter metric? like, closest to previous last endpoint? Using
                //   extrema like this tends to produce longest spans, though...
                double min_dot   = double.MaxValue;
                int    start_eid = -1;
                foreach (int eid in MinGraph.EdgeIndices())
                {
                    Index3i evg = MinGraph.GetEdge(eid);
                    if (evg.c >= 0)
                    {
                        double dot = MinGraph.GetVertex(evg.a).Dot(sortAxis);
                        if (dot < min_dot)
                        {
                            min_dot   = dot;
                            start_eid = eid;
                        }
                    }
                }
                if (start_eid == -1)
                {
                    break;   // if we could not find a start edge, we must be done!
                }
                // ok now walk forward through connectors and spans. We do this in
                // connector/span pairs - we are always at an end-of-span point, and
                // we pick a next-connector and then a next-span.
                // We need to keep track of vertices in both the pathgraph and mingraph,
                // these are the "new" and "old" vertices
                Index3i start_evg = MinGraph.GetEdge(start_eid);
                int     new_start = PathGraph.AppendVertex(MinGraph.GetVertex(start_evg.a));
                int     new_prev  = PathGraph.AppendVertex(MinGraph.GetVertex(start_evg.b));
                int     old_prev  = start_evg.b;
                PathGraph.AppendEdge(new_start, new_prev, start_evg.c);
                MinGraph.RemoveVertex(start_evg.a, true);
                while (true)
                {
                    // choose next connector edge, outgoing from current vtx
                    int connector_e = -1;
                    foreach (int eid in MinGraph.VtxEdgesItr(old_prev))
                    {
                        Index3i evg = MinGraph.GetEdge(eid);
                        if (evg.c >= 0)
                        {
                            continue;  // what??
                        }
                        if (connector_e == -1 || EdgeWeights[connector_e] > EdgeWeights[eid])
                        {
                            connector_e = eid;
                        }
                    }
                    if (connector_e == -1)
                    {
                        break;
                    }

                    // find the vertex at end of connector
                    Index3i conn_evg   = MinGraph.GetEdge(connector_e);
                    int     old_conn_v = (conn_evg.a == old_prev) ? conn_evg.b : conn_evg.a;

                    // can never look at prev vertex again, or any edges connected to it
                    // [TODO] are we sure none of these edges are unused spans?!?
                    MinGraph.RemoveVertex(old_prev, true);

                    // now find outgoing span edge
                    int span_e = -1;
                    foreach (int eid in MinGraph.VtxEdgesItr(old_conn_v))
                    {
                        Index3i evg = MinGraph.GetEdge(eid);
                        if (evg.c >= 0)
                        {
                            span_e = eid;
                            break;
                        }
                    }
                    if (span_e == -1)
                    {
                        break;   // disaster!
                    }
                    // find vertex at far end of span
                    Index3i span_evg   = MinGraph.GetEdge(span_e);
                    int     old_span_v = (span_evg.a == old_conn_v) ? span_evg.b : span_evg.a;

                    // ok we want to insert the connectr to the path graph, however the
                    // connector might actually have come from a more complex path in the input graph.
                    int new_conn_next = -1;
                    if (MinEdgePaths.ContainsKey(connector_e))
                    {
                        // complex path case. Note that the order [old_prev, old_conn_v] may be the opposite
                        // of the order in the pathv. But above, we appended the [a,b] edge order to the pathv.
                        // So we can check if we need to flip, but this means we need to be a bit clever w/ indices...
                        List <int> pathv     = MinEdgePaths[connector_e];
                        int        N         = pathv.Count;
                        int        path_prev = new_prev;
                        int        k         = 1;
                        if (pathv[N - 2] != old_prev)     // case where order flipped
                        {
                            pathv.Reverse();
                            k = 3;
                        }
                        else
                        {
                            N = N - 2;
                        }
                        while (k < N)
                        {
                            int path_next = PathGraph.AppendVertex(input.GetVertex(pathv[k]));
                            PathGraph.AppendEdge(path_prev, path_next);
                            path_prev = path_next;
                            k++;
                        }
                        new_conn_next = path_prev;
                    }
                    else
                    {
                        new_conn_next = PathGraph.AppendVertex(MinGraph.GetVertex(old_conn_v));
                        PathGraph.AppendEdge(new_prev, new_conn_next, conn_evg.c);
                    }

                    // add span to path
                    int new_fill_next = PathGraph.AppendVertex(MinGraph.GetVertex(old_span_v));
                    PathGraph.AppendEdge(new_conn_next, new_fill_next, span_evg.c);

                    // remove the connector vertex
                    MinGraph.RemoveVertex(old_conn_v, true);

                    // next iter starts at far end of span
                    new_prev = new_fill_next;
                    old_prev = old_span_v;
                }

                sortAxis = -sortAxis;
            }


            // for testing/debugging
            //SVGWriter writer = new SVGWriter();
            ////writer.AddGraph(input, SVGWriter.Style.Outline("blue", 0.1f));
            //writer.AddGraph(MinGraph, SVGWriter.Style.Outline("red", 0.1f));
            ////foreach ( int eid in MinGraph.EdgeIndices() ) {
            ////    if ( MinGraph.GetEdgeGroup(eid) >= 0 )  writer.AddLine(MinGraph.GetEdgeSegment(eid), SVGWriter.Style.Outline("green", 0.07f));
            ////}
            ////writer.AddGraph(MinGraph, SVGWriter.Style.Outline("black", 0.03f));
            //writer.AddGraph(PathGraph, SVGWriter.Style.Outline("black", 0.03f));
            //foreach (int vid in PathGraph.VertexIndices()) {
            //    if (PathGraph.IsBoundaryVertex(vid))
            //        writer.AddCircle(new Circle2d(PathGraph.GetVertex(vid), 0.5f), SVGWriter.Style.Outline("blue", 0.03f));
            //}
            ////writer.AddGraph(IntervalGraph, SVGWriter.Style.Outline("black", 0.03f));
            //writer.Write("c:\\scratch\\MIN_GRAPH.svg");


            return(PathGraph);
        }
Пример #30
0
        /// <summary>
        /// This pass only does edge splits. Returns number of split edges.
        /// Tracks previously-split
        /// </summary>
        public int FastSplitIteration()
        {
            if (mesh.TriangleCount == 0)    // badness if we don't catch this...
            {
                return(0);
            }

            PushState();
            EnableFlips    = EnableCollapses = EnableSmoothing = false;
            ProjectionMode = TargetProjectionMode.NoProjection;

            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();

            IEnumerable <int> edgesItr = EdgesIterator();

            if (modified_edges == null)
            {
                modified_edges = new HashSet <int>();
            }
            else
            {
                edges_buffer.Clear(); edges_buffer.AddRange(modified_edges);
                edgesItr = edges_buffer;
                modified_edges.Clear();
            }

            int startEdges = Mesh.EdgeCount;
            int splitEdges = 0;

            // When we split an edge, we need to check it and the adjacent ones we added.
            // Because of overhead in ProcessEdge, it is worth it to do a distance-check here
            double max_edge_len_sqr = MaxEdgeLength * MaxEdgeLength;

            SplitF = (edgeID, a, b, vNew) => {
                Vector3d v = Mesh.GetVertex(vNew);
                foreach (int eid in Mesh.VtxEdgesItr(vNew))
                {
                    Index2i ev       = Mesh.GetEdgeV(eid);
                    int     othervid = (ev.a == vNew) ? ev.b : ev.a;
                    if (mesh.GetVertex(othervid).DistanceSquared(ref v) > max_edge_len_sqr)
                    {
                        queue_edge(eid);
                    }
                }
                //queue_one_ring(vNew);
            };


            ModifiedEdgesLastPass = 0;
            int processedLastPass = 0;

            foreach (int cur_eid in edgesItr)
            {
                if (Cancelled())
                {
                    goto abort_compute;
                }

                if (mesh.IsEdge(cur_eid))
                {
                    Index2i ev = mesh.GetEdgeV(cur_eid);
                    Index2i ov = mesh.GetEdgeOpposingV(cur_eid);

                    processedLastPass++;
                    ProcessResult result = ProcessEdge(cur_eid);
                    if (result == ProcessResult.Ok_Split)
                    {
                        // new edges queued by SplitF
                        ModifiedEdgesLastPass++;
                        splitEdges++;
                    }
                }
            }
            end_ops();

            //System.Console.WriteLine("FastSplitIteration: start {0}  end {1}  processed: {2}   modified: {3}  queue: {4}",
            //    startEdges, Mesh.EdgeCount, processedLastPass, ModifiedEdgesLastPass, modified_edges.Count);

abort_compute:
            SplitF = null;
            PopState();

            end_pass();

            return(splitEdges);
        }