Exemplo n.º 1
0
        protected virtual void SetDelaunayResult(List <Triangle> delaunay)
        {
            Delaunay = delaunay;

            if (BuildDiagonstics)
            {
                foreach (var tri in Delaunay)
                {
                    var radius = Math.Sqrt(GeoMath.EuclideanDistance2(tri.Circumcenter, tri.A));
                    DiagnosticGeometry.Add(new DiagnosticGeometry(DiagGeometryType.Circle, DiagColor.Yellow, tri.Circumcenter, new Point(radius, 0)));
                }
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// Legalizes triangles around given edge.
        /// </summary>
        private int Legalize(int a)
        {
            var b = halfedges[a];

            var a0 = a - a % 3;
            var b0 = b - b % 3;

            var al = a0 + (a + 1) % 3;
            var ar = a0 + (a + 2) % 3;
            var bl = b0 + (b + 2) % 3;

            var p0 = triangles[ar];
            var pr = triangles[a];
            var pl = triangles[al];
            var p1 = triangles[bl];

            var illegal = GeoMath.IsPointInCircumcircle(p0.CartesianPoint, pr.CartesianPoint, pl.CartesianPoint, p1.CartesianPoint);

            if (illegal && b != -1)
            {
                triangles[a] = p1;
                triangles[b] = p0;

                Link(a, halfedges[bl]);
                Link(b, halfedges[ar]);
                Link(ar, bl);

                var br = b0 + (b + 1) % 3;

                Legalize(a);

                return(Legalize(br));
            }

            return(ar);
        }
Exemplo n.º 3
0
        /// <summary>
        /// Calculates Delaunay triangulation via a naive implementation of the incremental Bowyer-Waston algorithm.
        /// Expected runtime is O(n²).
        /// </summary>
        /// <remarks>
        /// https://www.maths.tcd.ie/~martins7/Voro/Sean_Martin_Poster.pdf
        /// https://en.wikipedia.org/wiki/Bowyer%E2%80%93Watson_algorithm
        /// https://www.codeguru.com/cpp/cpp/algorithms/general/article.php/c8901/Delaunay-Triangles.htm
        /// https://eclass.uoa.gr/modules/document/file.php/D42/%CE%94%CE%B9%CE%B1%CF%86%CE%AC%CE%BD%CE%B5%CE%B9%CE%B5%CF%82/2a.delaunay.pdf - Slide 40+
        /// </remarks>
        public override void CalculateDelaunay()
        {
            if (!CheckDelaunayConditions())
            {
                return;
            }

            var triangulation = new HashSet <Triangle>();

            // Start with the supertriangle, which contains all available points
            var supertriangle = CalculateSupertriangle();

            triangulation.Add(supertriangle);

            // Keep a list of triangles where the point is within its circumcircle
            var conflictingTriangles = new List <Triangle>();
            var polygonEdges         = new List <Edge>();

            // Incrementally add points to existing set of triangles
            foreach (var p in Points)
            {
                // Find triangles where given point is in its circumcircle (conflicting)
                foreach (var tri in triangulation)
                {
                    if (GeoMath.IsPointInCircumcircle(tri, p))
                    {
                        conflictingTriangles.Add(tri);

                        // Remove neighbouring edges of conflicting triangles

                        // Edge A
                        var edgeA = tri.GetEdgeA();
                        if (polygonEdges.Contains(edgeA))
                        {
                            polygonEdges.Remove(edgeA);
                        }
                        else
                        {
                            polygonEdges.Add(edgeA);
                        }

                        // Edge B
                        var edgeB = tri.GetEdgeB();
                        if (polygonEdges.Contains(edgeB))
                        {
                            polygonEdges.Remove(edgeB);
                        }
                        else
                        {
                            polygonEdges.Add(edgeB);
                        }

                        // Edge C
                        var edgeC = tri.GetEdgeC();
                        if (polygonEdges.Contains(edgeC))
                        {
                            polygonEdges.Remove(edgeC);
                        }
                        else
                        {
                            polygonEdges.Add(edgeC);
                        }
                    }
                }

                // Remove conflicting triangles from hashset
                foreach (var tri in conflictingTriangles)
                {
                    triangulation.Remove(tri);
                }

                // Create new triangles by connecting all polygon vertices to the newly added vertex
                foreach (var edge in polygonEdges)
                {
                    triangulation.Add(new Triangle(edge.Start, edge.End, p));
                }

                conflictingTriangles.Clear();
                polygonEdges.Clear();
            }

            // Only add triangles to final triangulation result which do not share a vertex with the super-triangle


            var delaunay = new List <Triangle>(triangulation.Count);

            foreach (var tri in triangulation)
            {
                if (!supertriangle.HasVertex(tri.A) &&
                    !supertriangle.HasVertex(tri.B) &&
                    !supertriangle.HasVertex(tri.C))
                {
                    delaunay.Add(tri);
                }
            }

            SetDelaunayResult(delaunay);
        }
Exemplo n.º 4
0
        /// <summary>
        /// Calculates Delaunay triangulation using the more efficient Sweep-Circle algorithm.
        /// Considered one of the fastest algorithms in empirical tests.
        /// Expected runtime is O(n log n).
        /// </summary>
        /// <remarks>
        /// http://cglab.ca/~biniaz/papers/Sweep%20Circle.pdf
        /// </remarks>
        public override void CalculateDelaunay()
        {
            if (!CheckDelaunayConditions())
            {
                return;
            }

            // Calculate origin point and seed triangle
            (Point origin, Triangle seed) = CalculateOriginSeedTriangle(out int i0, out int i1, out int i2);
            center = origin;

            if (i0 == -1)
            {
                SetDelaunayResult(new List <Triangle>());
                return; // No Delaunay triangulation exists
            }

            var len = Points.Length;

            polarPoints = new PolarPoint[len];

            var seed1 = new PolarPoint();
            var seed2 = new PolarPoint();
            var seed3 = new PolarPoint();

            for (var i = 0; i < len; i++)
            {
                var p  = Points[i];
                var pp = new PolarPoint(
                    p,
                    GeoMath.EuclideanDistance2(center, p),
                    GeoMath.PolarPseudoAngle(center, p)
                    );

                if (p.Equals(seed.A))
                {
                    seed1 = pp;
                }
                else if (p.Equals(seed.B))
                {
                    seed2 = pp;
                }
                else if (p.Equals(seed.C))
                {
                    seed3 = pp;
                }

                polarPoints[i] = pp;
            }

            // Sort polar points by radius in ascending order
            QuickSortPoints(polarPoints, 0, len - 1);

            // Hashtable for edges of convex hull
            hashSize = (int)Math.Sqrt(len);
            hash     = new PointNode[hashSize + 2];

            // Circular doubly-linked list which will hold the convex hull
            var e = hull = InsertNode(seed1);

            HashEdge(e);
            e.t = 0;
            e   = InsertNode(seed2, e);
            HashEdge(e);
            e.t = 1;
            e   = InsertNode(seed3, e);
            HashEdge(e);
            e.t = 2;

            var maxTriangles = 2 * len - 5;

            triangles = new PolarPoint[maxTriangles * 3];
            halfedges = new int[maxTriangles * 3];

            AddTriangle(seed1, seed2, seed3, -1, -1, -1);

            var prev = new PolarPoint(new Point(-1, -1), -1, -1);

            for (var i = 0; i < len; i++)
            {
                var pp = polarPoints[i];

                if (seed.HasVertex(pp.CartesianPoint))
                {
                    continue; // Skip seed triangle points
                }
                if (prev.CartesianPoint.Equals(pp.CartesianPoint))
                {
                    continue; // Ignore duplicate point
                }
                // Find a visible edge on the convex hull using edge hash
                var       startKey = HashKey(pp);
                var       key      = startKey;
                PointNode start;
                do
                {
                    start = hash[key];
                    key   = (key + 1) % hashSize;
                }while ((start == null || start.Removed) && key != startKey);

                e = start;
                while (GeoMath.TriangleAreaDeterminant(pp.CartesianPoint, e.Point.CartesianPoint, e.Next.Point.CartesianPoint) <= 0)
                {
                    e = e.Next;
#if DEBUG
                    if (e == start)
                    {
                        throw new Exception("Processing error. Input points invalid or seed triangle wrongly oriented.");
                    }
#endif
                }

                var walkBack = e == start;

                // Add first triangle
                var t = AddTriangle(e.Point, pp, e.Next.Point, -1, -1, e.t);

                e.t = t; // Keep track of boundary triangles on hull
                e   = InsertNode(pp, e);

                // Recursively flip triangles from the point until they satisfy the Delaunay condition
                e.t = Legalize(t + 2);
                if (e.Previous.Previous.t == halfedges[t + 1])
                {
                    e.Previous.Previous.t = t + 2;
                }

                // Walk forward through the hull, adding more triangles and flipping recursively
                var q = e.Next;
                while (GeoMath.TriangleAreaDeterminant(pp.CartesianPoint, q.Point.CartesianPoint, q.Next.Point.CartesianPoint) > 0)
                {
                    t            = AddTriangle(q.Point, pp, q.Next.Point, q.Previous.t, -1, q.t);
                    q.Previous.t = Legalize(t + 2); // Flipping
                    hull         = RemoveNode(q);
                    q            = q.Next;
                }

                if (walkBack)
                {
                    // Walk backward from the other side, adding more triangles and flipping recursively
                    q = e.Previous;
                    while (GeoMath.TriangleAreaDeterminant(pp.CartesianPoint, q.Previous.Point.CartesianPoint, q.Point.CartesianPoint) > 0)
                    {
                        t = AddTriangle(q.Previous.Point, pp, q.Point, -1, q.t, q.Previous.t);
                        Legalize(t + 2); // Flipping
                        q.Previous.t = t;
                        hull         = RemoveNode(q);
                        q            = q.Previous;
                    }
                }

                // Save the two new edges in the hashtable
                HashEdge(e);
                HashEdge(e.Previous);

                prev = pp;
            }

            // Create Delaunay triangles from points
            var delaunay = new List <Triangle>(trianglesPointIndex / 3);
            for (var i = 0; i < trianglesPointIndex;)
            {
                delaunay.Add(new Triangle(
                                 triangles[i++].CartesianPoint,
                                 triangles[i++].CartesianPoint,
                                 triangles[i++].CartesianPoint
                                 ));
            }

            SetDelaunayResult(delaunay);
        }
Exemplo n.º 5
0
        public override void CalculateVoronoi(double viewportWidth, double viewportHeight)
        {
            if (Delaunay == null)
            {
                throw new InvalidOperationException("Delaunay triangulation needs to be calculated first.");
            }

            VoronoiEdges  = new List <Edge>(Delaunay.Count * 2);
            VoronoiPoints = new List <Point>(Delaunay.Count);

            if (Delaunay.Count < 1)
            {
                return; // Nothing to do
            }
            // Add circumenters of all Delaunay triangles
            for (var i = 0; i < Delaunay.Count; i++)
            {
                VoronoiPoints.Add(Delaunay[i].Circumcenter);
            }

            // Connect all neighbouring Delaunay triangle circumcenters
            for (var i = 0; i < halfedges.Length; i++)
            {
                var j = halfedges[i];
                if (j < i || j < 0)
                {
                    continue;
                }

                var edge = new Edge(VoronoiPoints[i / 3], VoronoiPoints[j / 3]);
                VoronoiEdges.Add(edge);
            }

            // Calculate infinity edges of convex hull
            var node = hull;
            var head = node;

            do
            {
                var p1 = node.Point.CartesianPoint;
                var p2 = node.Next.Point.CartesianPoint;
                var c  = VoronoiPoints[node.t / 3];

                // Delta vector
                var dx    = (p1.X + p2.X) / 2 - c.X;
                var dy    = (p1.Y + p2.Y) / 2 - c.Y;
                var dxAbs = dx < 0 ? -dx : dx;
                var dyAbs = dy < 0 ? -dy : dy;

                // Set direction
                var det = GeoMath.TriangleAreaDeterminant(p1, p2, c) > 0 ? -1 : 1;
                dx *= det;
                dy *= det;

                // Stretch vector to boundary rectangle (viewport)
                if (dxAbs > dyAbs)
                {
                    // Normalize delta vector
                    dx  = dx < 0 ? -1 : 1;
                    dy /= dxAbs;

                    if (dx < 0)
                    {
                        dx *= c.X;
                        dy *= c.X;
                    }
                    else
                    {
                        dx *= viewportWidth - c.X;
                        dy *= viewportWidth - c.X;
                    }
                }
                else
                {
                    // Normalize delta vector
                    dx /= dyAbs;
                    dy  = dy < 0 ? -1 : 1;

                    if (dy < 0)
                    {
                        dx *= c.Y;
                        dy *= c.Y;
                    }
                    else
                    {
                        dx *= viewportHeight - c.Y;
                        dy *= viewportHeight - c.Y;
                    }
                }

                VoronoiEdges.Add(new Edge(c, new Point(c.X + dx, c.Y + dy)));
            }while ((node = node.Next) != head);
        }
Exemplo n.º 6
0
        protected (Point, Triangle) CalculateOriginSeedTriangle(out int idx1, out int idx2, out int idx3)
        {
            idx1 = -1;
            idx2 = -1;
            idx3 = -1;

            // Calculate origin point
            (Point min, Point max) = CalculateBoundaryBox();

            var origin = new Point((min.X + max.X) / 2, (min.Y + max.Y) / 2);

            // Find first seed point closest to origin
            {
                var minDist = double.MaxValue;
                for (var i = 0; i < Points.Length; i++)
                {
                    var dist = GeoMath.EuclideanDistance2(Points[i], origin);
                    if (dist < minDist)
                    {
                        idx1    = i;
                        minDist = dist;
                    }
                }
            }

            // Find point closest to seed point
            {
                var minDist = double.MaxValue;
                for (var i = 0; i < Points.Length; i++)
                {
                    if (i == idx1)
                    {
                        continue;
                    }
                    var dist = GeoMath.EuclideanDistance2(Points[i], Points[idx1]);
                    if (dist < minDist)
                    {
                        idx2    = i;
                        minDist = dist;
                    }
                }
            }

            // Find point which creates the smallest circumcircle
            {
                var minCircRadius2 = double.MaxValue;
                for (var i = 0; i < Points.Length; i++)
                {
                    if (i == idx1 || i == idx2)
                    {
                        continue;
                    }
                    var circRadius2 = GeoMath.Circumradius2(Points[idx1], Points[idx2], Points[i]);
                    if (circRadius2 < minCircRadius2)
                    {
                        idx3           = i;
                        minCircRadius2 = circRadius2;
                    }
                }

                if (idx3 == -1)
                {
                    idx1 = -1;
                    idx2 = -1;
                    return(new Point(), Triangle.Zero);  // No Delaunay triangulation exists
                }
            }

            // Make sure seed triangle points are oriented counter-clockwise
            if (GeoMath.TriangleAreaDeterminant(Points[idx1], Points[idx2], Points[idx3]) > 0)
            {
                var tmp = idx2;
                idx2 = idx3;
                idx3 = tmp;
            }

            // Update origin to represent midpoint of seed triangle
            origin = GeoMath.CalculateMidpoint(Points[idx1], Points[idx2], Points[idx3]);

            var seed = new Triangle(Points[idx1], Points[idx2], Points[idx3]);

            if (BuildDiagonstics)
            {
                // Add boundary box and seed point/triangle to diagnostics
                DiagnosticGeometry.Add(new DiagnosticGeometry(DiagGeometryType.Line, DiagColor.Red, seed.GetEdgeA().Start, seed.GetEdgeA().End));
                DiagnosticGeometry.Add(new DiagnosticGeometry(DiagGeometryType.Line, DiagColor.Red, seed.GetEdgeB().Start, seed.GetEdgeB().End));
                DiagnosticGeometry.Add(new DiagnosticGeometry(DiagGeometryType.Line, DiagColor.Red, seed.GetEdgeC().Start, seed.GetEdgeC().End));
                DiagnosticGeometry.Add(new DiagnosticGeometry(DiagGeometryType.Line, DiagColor.Red, min, new Point(min.X, max.Y), new Point(max.X, max.Y), new Point(max.X, max.Y), new Point(max.X, min.Y), min));
                DiagnosticGeometry.Add(new DiagnosticGeometry(DiagGeometryType.Vertex, DiagColor.Red, origin));
            }

            return(origin, seed);
        }