/// <summary> /// Initializes a new instance of the <see cref="Segment" /> class. /// </summary> public Segment(Vertex v0, Vertex v1, int label) { this.v0 = v0; this.v1 = v1; this.label = label; }
/// <summary> /// Read vertex information of the given line. /// </summary> /// <param name="data">The input geometry.</param> /// <param name="index">The current vertex index.</param> /// <param name="line">The current line.</param> /// <param name="attributes">Number of point attributes</param> /// <param name="marks">Number of point markers (0 or 1)</param> static void ReadVertex(List<Vertex> data, int index, string[] line, int attributes, int marks) { double x = double.Parse(line[1], nfi); double y = double.Parse(line[2], nfi); var v = new Vertex(x, y); // Read a vertex marker. if (marks > 0 && line.Length > 3 + attributes) { v.Label = int.Parse(line[3 + attributes]); } if (attributes > 0) { #if USE_ATTRIBS var attribs = new double[attributes]; // Read the vertex attributes. for (int j = 0; j < attributes; j++) { if (line.Length > 3 + j) { attribs[j] = double.Parse(line[3 + j], nfi); } } v.attributes = attribs; #endif } data.Add(v); }
/// <summary> /// Enumerate all vertices adjacent to given vertex. /// </summary> /// <param name="vertex">The center vertex.</param> /// <returns></returns> public IEnumerable<Vertex> EnumerateVertices(Vertex vertex) { BuildCache(vertex, true); foreach (var item in cache) { yield return item.Dest(); } }
public VoronoiRegion(Vertex generator) { this.id = generator.id; this.generator = generator; this.vertices = new List<Point>(); this.bounded = true; this.neighbors = new Dictionary<int, VoronoiRegion>(); }
/// <summary> /// Enumerate all triangles adjacent to given vertex. /// </summary> /// <param name="vertex">The center vertex.</param> /// <returns></returns> public IEnumerable<ITriangle> EnumerateTriangles(Vertex vertex) { BuildCache(vertex, false); foreach (var item in cache) { yield return item.tri; } }
/// <summary> /// Case 2: edge origin lies outside the domain. /// </summary> private void HandleCase2(HalfEdge edge, TVertex v1, TVertex v2) { // The vertices of the infinite edge. var p1 = (Point)edge.origin; var p2 = (Point)edge.twin.origin; // The two edges leaving p1, pointing into the mesh. var e1 = edge.twin.next; var e2 = e1.twin.next; // Check if the neighboring cell was closed before. if (edge.twin.id != edge.twin.face.edge.id) { edge.twin.face.edge.next = e1; } else { // If the cell isn't closed yet, make sure to update the faces edge pointer. e1.face.edge = e1; } // Find the two intersections with boundary edge. IntersectionHelper.IntersectSegments(v1, v2, e1.origin, e1.twin.origin, ref p2); IntersectionHelper.IntersectSegments(v1, v2, e2.origin, e2.twin.origin, ref p1); // The infinite edge will now lie on the boundary. Update pointers: e1.twin.next = edge.twin; edge.twin.next = e2; edge.twin.face = e2.face; e1.origin = edge.twin.origin; // Dissolve edge from other edges (origin and face stay the same). edge.twin.twin = null; edge.twin = null; // Close the cell. var gen = factory.CreateVertex(v1.x, v1.y); var he = factory.CreateHalfEdge(gen, edge.face); gen.leaving = he; edge.next = he; he.next = edge.face.edge; e2.twin.next = edge; // Let the face edge point to the edge leaving at generator. edge.face.edge = he; base.edges.Add(he); he.id = base.edges.Count; gen.id = offset++; base.vertices.Add(gen); }
void KruskalsMinimumSpanningTree() { //triangles = new List<Triangle>(); tmpConnections = new List <KruskalsEdge>(); connections = new List <KruskalsEdge>(); //triangles = DelaunayTriangulation.TriangulateByFlippingEdges(points); List <int> vertices = new List <int>(); int index = 0; int index2 = 0; foreach (var edge in mesh.Edges) { TriangleNet.Geometry.Vertex v0 = mesh.vertices[edge.P0]; TriangleNet.Geometry.Vertex v1 = mesh.vertices[edge.P1]; Vector3 p0 = new Vector3((float)v0.x, 0.0f, (float)v0.y); Vector3 p1 = new Vector3((float)v1.x, 0.0f, (float)v1.y); int tmp = 0; foreach (var point in Rooms) { if (p0 == point.Position) { index = tmp; break; } tmp++; } tmp = 0; foreach (var point in Rooms) { if (p1 == point.Position) { index2 = tmp; break; } tmp++; } tmpConnections.Add(new KruskalsEdge() { Vertex1 = edge.P0, Room1 = Rooms[index], Vertex2 = edge.P1, Room2 = Rooms[index2], Weight = (Vector3.Distance(p0, p1)) * (Rooms[index].weight / 20) }); if (!vertices.Contains(edge.P0)) { vertices.Add(edge.P0); } if (!vertices.Contains(edge.P1)) { vertices.Add(edge.P1); } } connections = KruskalsAlgorithm.Kruskals_MST(tmpConnections, vertices, false, connectivityPercentage); }
/// <summary> /// Find a new location for a Steiner point. /// </summary> /// <param name="org"></param> /// <param name="dest"></param> /// <param name="apex"></param> /// <param name="xi"></param> /// <param name="eta"></param> /// <param name="offcenter"></param> /// <param name="badotri"></param> /// <returns></returns> public Point FindLocation(Vertex org, Vertex dest, Vertex apex, ref double xi, ref double eta, bool offcenter, Otri badotri) { // Based on using -U switch, call the corresponding function if (behavior.MaxAngle == 0.0) { return FindNewLocationWithoutMaxAngle(org, dest, apex, ref xi, ref eta, true, badotri); } // With max angle return FindNewLocation(org, dest, apex, ref xi, ref eta, true, badotri); }
private void BuildCache(Vertex vertex, bool vertices) { cache.Clear(); Otri init = vertex.tri; Otri next = default(Otri); Otri prev = default(Otri); init.Copy(ref next); // Move counter-clockwise around the vertex. while (next.tri.id != Mesh.DUMMY) { cache.Add(next); next.Copy(ref prev); next.Onext(); if (next.Equals(init)) { break; } } if (next.tri.id == Mesh.DUMMY) { // We reached the boundary. To get all adjacent triangles, start // again at init triangle and now move clockwise. init.Copy(ref next); if (vertices) { // Don't forget to add the vertex lying on the boundary. prev.Lnext(); cache.Add(prev); } next.Oprev(); while (next.tri.id != Mesh.DUMMY) { cache.Insert(0, next); next.Oprev(); if (next.Equals(init)) { break; } } } }
public static void GenerateOffSet(IslandNetMesh islandNetMesh, float skylandDeclinePrecent, float skylandRadius) { IEnumerator <TriangleNet.Geometry.Vertex> vertexEnum = islandNetMesh.netMesh.Vertices.GetEnumerator(); for (int i = 0; i < islandNetMesh.netMesh.vertices.Count; i++) { if (!vertexEnum.MoveNext()) { break; } TriangleNet.Geometry.Vertex current = vertexEnum.Current; float precentDistanceFromTheEdge = 1 - Mathf.Sqrt(Mathf.Pow((float)current.x, 2) + Mathf.Pow((float)current.y, 2)) / (float)skylandRadius; current.x += (perlinNoise.GetNoise((float)current.x * 10, (float)current.y * 10)) * 5; current.y += (perlinNoise.GetNoise((float)current.x * 10, (float)current.y * 10)) * 5; } }
/// <summary> /// Impose alternating cuts on given vertex array. /// </summary> /// <param name="array">The vertex array.</param> /// <param name="length">The number of vertices to sort.</param> /// <param name="seed">Random seed used for pivoting.</param> public static void Alternate(Vertex[] array, int length, int seed = RANDOM_SEED) { var qs = new VertexSorter(array, seed); int divider = length >> 1; // Re-sort the array of vertices to accommodate alternating cuts. if (length - divider >= 2) { if (divider >= 2) { qs.AlternateAxes(0, divider - 1, 1); } qs.AlternateAxes(divider, length - 1, 1); } }
void CreateMap(TriangleNet.Mesh m, float maxz, float yOffset) { Dictionary <int, TriangleNet.Geometry.Vertex> id2vert = new Dictionary <int, TriangleNet.Geometry.Vertex>(); List <TriangleNet.Geometry.Vertex> vertices = new List <TriangleNet.Geometry.Vertex>(); List <ISegment> segments = new List <ISegment>(); foreach (TriangleNet.Geometry.Vertex v in m.Vertices) { id2vert[v.id] = v; vertices.Add(v); } foreach (Edge e in m.Edges) { TriangleNet.Geometry.Vertex v1 = id2vert[e.P0]; TriangleNet.Geometry.Vertex v2 = id2vert[e.P1]; segments.Add(new Segment(v1, v2)); } CreateMap(vertices, segments, maxz, yOffset); }
/// <summary> /// Case 2: edge origin lies outside the domain. /// </summary> private void HandleCase2(HalfEdge edge, TVertex v1, TVertex v2) { // The vertices of the infinite edge. var p1 = (Point)edge.origin; var p2 = (Point)edge.twin.origin; // The two edges leaving p1, pointing into the mesh. var e1 = edge.twin.next; var e2 = e1.twin.next; // Find the two intersections with boundary edge. IntersectionHelper.IntersectSegments(v1, v2, e1.origin, e1.twin.origin, ref p2); IntersectionHelper.IntersectSegments(v1, v2, e2.origin, e2.twin.origin, ref p1); // The infinite edge will now lie on the boundary. Update pointers: e1.twin.next = edge.twin; edge.twin.next = e2; edge.twin.face = e2.face; e1.origin = edge.twin.origin; edge.twin.twin = null; edge.twin = null; // Close the cell. var gen = factory.CreateVertex(v1.x, v1.y); var he = factory.CreateHalfEdge(gen, edge.face); edge.next = he; he.next = edge.face.edge; // Let the face edge point to the edge leaving at generator. edge.face.edge = he; base.edges.Add(he); he.id = base.edges.Count; gen.id = offset++; base.vertices.Add(gen); }
/// <summary> /// Linear interpolation of vertex attributes. /// </summary> /// <param name="vertex">The interpolation vertex.</param> /// <param name="triangle">The triangle containing the vertex.</param> /// <param name="n">The number of vertex attributes.</param> /// <remarks> /// The vertex is expected to lie inside the triangle. /// </remarks> public static void InterpolateAttributes(Vertex vertex, ITriangle triangle, int n) { Vertex org = triangle.GetVertex(0); Vertex dest = triangle.GetVertex(1); Vertex apex = triangle.GetVertex(2); double xdo, ydo, xao, yao; double denominator; double dx, dy; double xi, eta; // Compute the circumcenter of the triangle. xdo = dest.x - org.x; ydo = dest.y - org.y; xao = apex.x - org.x; yao = apex.y - org.y; denominator = 0.5 / (xdo * yao - xao * ydo); //dx = (yao * dodist - ydo * aodist) * denominator; //dy = (xdo * aodist - xao * dodist) * denominator; dx = vertex.x - org.x; dy = vertex.y - org.y; // To interpolate vertex attributes for the new vertex inserted at // the circumcenter, define a coordinate system with a xi-axis, // directed from the triangle's origin to its destination, and // an eta-axis, directed from its origin to its apex. // Calculate the xi and eta coordinates of the circumcenter. xi = (yao * dx - xao * dy) * (2.0 * denominator); eta = (xdo * dy - ydo * dx) * (2.0 * denominator); for (int i = 0; i < n; i++) { // Interpolate the vertex attributes. vertex.attributes[i] = org.attributes[i] + xi * (dest.attributes[i] - org.attributes[i]) + eta * (apex.attributes[i] - org.attributes[i]); } }
void CreateMap(VoronoiBase sv, float maxz, float yOffset) { Dictionary <int, TriangleNet.Geometry.Vertex> id2vert = new Dictionary <int, TriangleNet.Geometry.Vertex>(); List <TriangleNet.Geometry.Vertex> vertices = new List <TriangleNet.Geometry.Vertex>(); List <ISegment> segments = new List <ISegment>(); foreach (TriangleNet.Topology.DCEL.Vertex v in sv.Vertices) { TriangleNet.Geometry.Vertex new_v = new TriangleNet.Geometry.Vertex(v.x, v.y); id2vert[v.id] = new_v; vertices.Add(new_v); } foreach (Edge e in sv.Edges) { TriangleNet.Geometry.Vertex v1 = id2vert[e.P0]; TriangleNet.Geometry.Vertex v2 = id2vert[e.P1]; segments.Add(new Segment(v1, v2)); } CreateMap(vertices, segments, maxz, yOffset); }
/// <summary> /// Case 1: edge origin lies inside the domain. /// </summary> private void HandleCase1(HalfEdge edge, TVertex v1, TVertex v2) { //int mark = GetBoundaryMark(v1); // The infinite vertex. var v = (Point)edge.twin.origin; // The half-edge is the bisector of v1 and v2, so the projection onto the // boundary segment is actually its midpoint. v.x = (v1.x + v2.x) / 2.0; v.y = (v1.y + v2.y) / 2.0; // Close the cell connected to edge. var gen = factory.CreateVertex(v1.x, v1.y); var h1 = factory.CreateHalfEdge(edge.twin.origin, edge.face); var h2 = factory.CreateHalfEdge(gen, edge.face); edge.next = h1; h1.next = h2; h2.next = edge.face.edge; gen.leaving = h2; // Let the face edge point to the edge leaving at generator. edge.face.edge = h2; base.edges.Add(h1); base.edges.Add(h2); int count = base.edges.Count; h1.id = count; h2.id = count + 1; gen.id = offset++; base.vertices.Add(gen); }
/// <summary> /// Set the origin of the segment that includes the subsegment. /// </summary> internal void SetSegOrg(Vertex vertex) { seg.vertices[2 + orient] = vertex; }
/// <summary> /// Set destination of a subsegment. /// </summary> internal void SetDest(Vertex vertex) { seg.vertices[1 - orient] = vertex; }
/// <summary> /// Set the origin or destination of a subsegment. /// </summary> internal void SetOrg(Vertex vertex) { seg.vertices[orient] = vertex; }
/// <summary> /// Set Destination /// </summary> internal void SetDest(Vertex v) { tri.vertices[minus1Mod3[orient]] = v; }
/// <summary> /// Finds the star of a given point. /// </summary> /// <param name="badotri"></param> /// <param name="p"></param> /// <param name="q"></param> /// <param name="r"></param> /// <param name="whichPoint"></param> /// <param name="points">List of points on the star of the given point.</param> /// <returns>Number of points on the star of the given point.</returns> private int GetStarPoints(Otri badotri, Vertex p, Vertex q, Vertex r, int whichPoint, ref double[] points) { Otri neighotri = default(Otri); // for return value of the function Otri tempotri; // for temporary usage double first_x = 0, first_y = 0; // keeps the first point to be considered double second_x = 0, second_y = 0; // for determining the edge we will begin double third_x = 0, third_y = 0; // termination double[] returnPoint = new double[2]; // for keeping the returned point int numvertices = 0; // for keeping number of surrounding vertices // first determine which point to be used to find its neighbor triangles switch (whichPoint) { case 1: first_x = p.x; // point at the center first_y = p.y; second_x = r.x; // second vertex of first edge to consider second_y = r.y; third_x = q.x; // for terminating the search third_y = q.y; break; case 2: first_x = q.x; // point at the center first_y = q.y; second_x = p.x; // second vertex of first edge to consider second_y = p.y; third_x = r.x; // for terminating the search third_y = r.y; break; case 3: first_x = r.x; // point at the center first_y = r.y; second_x = q.x; // second vertex of first edge to consider second_y = q.y; third_x = p.x; // for terminating the search third_y = p.y; break; } tempotri = badotri; // add first point as the end of first edge points[numvertices] = second_x; numvertices++; points[numvertices] = second_y; numvertices++; // assign as dummy value returnPoint[0] = second_x; returnPoint[1] = second_y; // until we reach the third point of the beginning triangle do { // find the neighbor's third point where it is incident to given edge if (!GetNeighborsVertex(tempotri, first_x, first_y, second_x, second_y, ref returnPoint, ref neighotri)) { // go to next triangle tempotri = neighotri; // now the second point is the neighbor's third vertex second_x = returnPoint[0]; second_y = returnPoint[1]; // add a new point to the list of surrounding points points[numvertices] = returnPoint[0]; numvertices++; points[numvertices] = returnPoint[1]; numvertices++; } else { numvertices = 0; break; } } while (!((Math.Abs(returnPoint[0] - third_x) <= EPS) && (Math.Abs(returnPoint[1] - third_y) <= EPS))); return numvertices / 2; }
/// <summary> /// Add a vertex to the polygon. /// </summary> /// <param name="vertex">The vertex to insert.</param> public void Add(Vertex vertex) { this.points.Add(vertex); }
private void OnDrawGizmos() { if (Rooms == null) { return; } if (ShowBestPointsOnly) { foreach (var point in bestRooms) { Gizmos.color = Color.black; Gizmos.DrawWireCube(point.Position, point.Size); } } else { foreach (var point in Rooms) { Gizmos.color = Color.black; Gizmos.color = new Color(point.weight / 20, Gizmos.color.g, Gizmos.color.b, Gizmos.color.a); Gizmos.DrawWireCube(point.Position, point.Size); //Handles.Label(point.Position, point.Position.ToString()); } } if (mesh == null) { return; } if (!showConections) { Gizmos.color = Color.green; foreach (var edge in mesh.Edges) { TriangleNet.Geometry.Vertex v0 = mesh.vertices[edge.P0]; TriangleNet.Geometry.Vertex v1 = mesh.vertices[edge.P1]; Vector3 p0 = new Vector3((float)v0.x, 0.0f, (float)v0.y); Vector3 p1 = new Vector3((float)v1.x, 0.0f, (float)v1.y); Gizmos.DrawLine(p0, p1); } } if (connections == null) { return; } if (showConections) { foreach (var connection in connections) { Gizmos.color = Color.magenta; Gizmos.DrawLine(connection.Room1.Position, connection.Room2.Position); } } if (showRoomConections) { foreach (var room in bestRooms) { Gizmos.color = Color.cyan; foreach (var connection in room.connections) { Gizmos.DrawLine(room.Position, connection.Position); } } } }
public override List <SubdividableEdgeLoop <CityEdge> > GetChildren(SubdividableEdgeLoop <CityEdge> parent) { Vector2[] parentPoints = parent.GetPoints(); Polygon parentPoly = parent.GetPolygon(); //generate points of interest List <RoadDestination> pointsOfInterest = new List <RoadDestination>(); Vector2 centroid = parent.GetCenter(); //parent.EnumerateEdges((EdgeLoopEdge edge) => //{ // pointsOfInterest.Add(new RoadDestination(Vector2.Lerp(edge.a.pt, edge.b.pt, Random.Range(0.2f, 0.8f)), 1, false, true)); //}); Rect bounds = parent.GetBounds(); bounds.width = bounds.width * 2; bounds.height = bounds.height * 2; int potentialRoadPointsRt = Mathf.CeilToInt(Mathf.Sqrt(potentialRoadPoints)); float approxDiameter = Mathf.Sqrt(parentPoly.area); float minimumPerimeterDistance = approxDiameter / 4f; for (int x = 0; x < potentialRoadPointsRt; x++) { for (int y = 0; y < potentialRoadPointsRt; y++) { Vector2 point = new Vector2((x / (float)potentialRoadPointsRt) * bounds.width + bounds.xMin, (y / (float)potentialRoadPointsRt) * bounds.height + bounds.yMin); float distBtwnPts = (bounds.width + bounds.height) / (potentialRoadPoints * 2); point = point + new Vector2(Random.Range(-1f, 1f), Random.Range(-1, 1f)) * distBtwnPts * 3f; if (parentPoly.ContainsPoint(point)) // && parent.DistToPerimeter(point) > minimumPerimeterDistance) { pointsOfInterest.Add(new RoadDestination(point, 0, false, false)); } } } pointsOfInterest.Add(new RoadDestination(bounds.center + new Vector2(bounds.width * 100, bounds.height * 100), 0, false, false)); pointsOfInterest.Add(new RoadDestination(bounds.center + new Vector2(bounds.width * 100, -bounds.height * 100), 0, false, false)); pointsOfInterest.Add(new RoadDestination(bounds.center + new Vector2(-bounds.width * 100, -bounds.height * 100), 0, false, false)); pointsOfInterest.Add(new RoadDestination(bounds.center + new Vector2(-bounds.width * 100, bounds.height * 100), 0, false, false)); //triangulate points of interest to get potential road segments TriangleNet.Geometry.Polygon polygon = new TriangleNet.Geometry.Polygon(); Dictionary <TriangleNet.Geometry.Vertex, RoadDestination> vertexDestMap = new Dictionary <TriangleNet.Geometry.Vertex, RoadDestination>(); foreach (RoadDestination dest in pointsOfInterest) { TriangleNet.Geometry.Vertex vert = new TriangleNet.Geometry.Vertex(dest.point.x, dest.point.y); vertexDestMap.Add(vert, dest); polygon.Add(vert); } TriangleNet.Meshing.ConstraintOptions options = new TriangleNet.Meshing.ConstraintOptions() { ConformingDelaunay = true }; TriangleNet.Meshing.GenericMesher mesher = new TriangleNet.Meshing.GenericMesher(); TriangleNet.Meshing.IMesh mesh = mesher.Triangulate(polygon); TriangleNet.Voronoi.StandardVoronoi voronoi = new TriangleNet.Voronoi.StandardVoronoi((TriangleNet.Mesh)mesh); IEnumerable <TriangleNet.Geometry.IEdge> voronoiEdges = voronoi.Edges; List <TriangleNet.Topology.DCEL.Vertex> voronoiVerts = voronoi.Vertices; List <DividingEdge> dividingEdges = new List <DividingEdge>(); ILinkedGraphEdgeFactory <CityEdge> factory = new CityEdgeFactory(); foreach (TriangleNet.Geometry.IEdge edge in voronoiEdges) { Vector2 a = new Vector2((float)voronoiVerts[edge.P0].X, (float)voronoiVerts[edge.P0].Y); Vector2 b = new Vector2((float)voronoiVerts[edge.P1].X, (float)voronoiVerts[edge.P1].Y); dividingEdges.Add(new DividingEdge(a, b, factory, factoryParams)); } //get vertices as list //ICollection<TriangleNet.Geometry.Vertex> vertices = mesh.Vertices; //TriangleNet.Geometry.Vertex[] vertexList = new TriangleNet.Geometry.Vertex[vertices.Count]; //vertices.CopyTo(vertexList, 0); //IEnumerable<TriangleNet.Geometry.Edge> meshEdges = mesh.Edges; //build a list of dividing edges and pass it to the child collector //foreach (TriangleNet.Geometry.Edge edge in meshEdges) { // //if (vertConnections[edge.P0] > 4) // //{ // // vertConnections[edge.P0]--; // // continue; // //} // //if (vertConnections[edge.P1] > 4) // //{ // // vertConnections[edge.P1]--; // // continue; // //} // Vector2 a = new Vector2((float)vertexList[edge.P0].X, (float)vertexList[edge.P0].Y); // Vector2 b = new Vector2((float)vertexList[edge.P1].X, (float)vertexList[edge.P1].Y); // dividingEdges.Add(new DividingEdge(a, b, factory, CityEdgeType.LandPath)); //} return(CollectChildren(parent, dividingEdges)); }
/// <summary> /// Split all the encroached subsegments. /// </summary> /// <param name="triflaws">A flag that specifies whether one should take /// note of new bad triangles that result from inserting vertices to repair /// encroached subsegments.</param> /// <remarks> /// Each encroached subsegment is repaired by splitting it - inserting a /// vertex at or near its midpoint. Newly inserted vertices may encroach /// upon other subsegments; these are also repaired. /// </remarks> private void SplitEncSegs(bool triflaws) { Otri enctri = default(Otri); Otri testtri = default(Otri); Osub testsh = default(Osub); Osub currentenc = default(Osub); BadSubseg seg; Vertex eorg, edest, eapex; Vertex newvertex; InsertVertexResult success; double segmentlength, nearestpoweroftwo; double split; double multiplier, divisor; bool acuteorg, acuteorg2, acutedest, acutedest2; // Note that steinerleft == -1 if an unlimited number // of Steiner points is allowed. while (badsubsegs.Count > 0) { if (mesh.steinerleft == 0) { break; } seg = badsubsegs.Dequeue(); currentenc = seg.subseg; eorg = currentenc.Org(); edest = currentenc.Dest(); // Make sure that this segment is still the same segment it was // when it was determined to be encroached. If the segment was // enqueued multiple times (because several newly inserted // vertices encroached it), it may have already been split. if (!Osub.IsDead(currentenc.seg) && (eorg == seg.org) && (edest == seg.dest)) { // To decide where to split a segment, we need to know if the // segment shares an endpoint with an adjacent segment. // The concern is that, if we simply split every encroached // segment in its center, two adjacent segments with a small // angle between them might lead to an infinite loop; each // vertex added to split one segment will encroach upon the // other segment, which must then be split with a vertex that // will encroach upon the first segment, and so on forever. // To avoid this, imagine a set of concentric circles, whose // radii are powers of two, about each segment endpoint. // These concentric circles determine where the segment is // split. (If both endpoints are shared with adjacent // segments, split the segment in the middle, and apply the // concentric circles for later splittings.) // Is the origin shared with another segment? currentenc.Pivot(ref enctri); enctri.Lnext(ref testtri); testtri.Pivot(ref testsh); acuteorg = testsh.seg.hash != Mesh.DUMMY; // Is the destination shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acutedest = testsh.seg.hash != Mesh.DUMMY; // If we're using Chew's algorithm (rather than Ruppert's) // to define encroachment, delete free vertices from the // subsegment's diametral circle. if (!behavior.ConformingDelaunay && !acuteorg && !acutedest) { eapex = enctri.Apex(); while ((eapex.type == VertexType.FreeVertex) && ((eorg.x - eapex.x) * (edest.x - eapex.x) + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) { mesh.DeleteVertex(ref testtri); currentenc.Pivot(ref enctri); eapex = enctri.Apex(); enctri.Lprev(ref testtri); } } // Now, check the other side of the segment, if there's a triangle there. enctri.Sym(ref testtri); if (testtri.tri.id != Mesh.DUMMY) { // Is the destination shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acutedest2 = testsh.seg.hash != Mesh.DUMMY; acutedest = acutedest || acutedest2; // Is the origin shared with another segment? testtri.Lnext(); testtri.Pivot(ref testsh); acuteorg2 = testsh.seg.hash != Mesh.DUMMY; acuteorg = acuteorg || acuteorg2; // Delete free vertices from the subsegment's diametral circle. if (!behavior.ConformingDelaunay && !acuteorg2 && !acutedest2) { eapex = testtri.Org(); while ((eapex.type == VertexType.FreeVertex) && ((eorg.x - eapex.x) * (edest.x - eapex.x) + (eorg.y - eapex.y) * (edest.y - eapex.y) < 0.0)) { mesh.DeleteVertex(ref testtri); enctri.Sym(ref testtri); eapex = testtri.Apex(); testtri.Lprev(); } } } // Use the concentric circles if exactly one endpoint is shared // with another adjacent segment. if (acuteorg || acutedest) { segmentlength = Math.Sqrt((edest.x - eorg.x) * (edest.x - eorg.x) + (edest.y - eorg.y) * (edest.y - eorg.y)); // Find the power of two that most evenly splits the segment. // The worst case is a 2:1 ratio between subsegment lengths. nearestpoweroftwo = 1.0; while (segmentlength > 3.0 * nearestpoweroftwo) { nearestpoweroftwo *= 2.0; } while (segmentlength < 1.5 * nearestpoweroftwo) { nearestpoweroftwo *= 0.5; } // Where do we split the segment? split = nearestpoweroftwo / segmentlength; if (acutedest) { split = 1.0 - split; } } else { // If we're not worried about adjacent segments, split // this segment in the middle. split = 0.5; } // Create the new vertex (interpolate coordinates). newvertex = new Vertex( eorg.x + split * (edest.x - eorg.x), eorg.y + split * (edest.y - eorg.y), currentenc.seg.boundary #if USE_ATTRIBS , mesh.nextras #endif ); newvertex.type = VertexType.SegmentVertex; newvertex.hash = mesh.hash_vtx++; newvertex.id = newvertex.hash; mesh.vertices.Add(newvertex.hash, newvertex); #if USE_ATTRIBS // Interpolate attributes. for (int i = 0; i < mesh.nextras; i++) { newvertex.attributes[i] = eorg.attributes[i] + split * (edest.attributes[i] - eorg.attributes[i]); } #endif if (!Behavior.NoExact) { // Roundoff in the above calculation may yield a 'newvertex' // that is not precisely collinear with 'eorg' and 'edest'. // Improve collinearity by one step of iterative refinement. multiplier = predicates.CounterClockwise(eorg, edest, newvertex); divisor = ((eorg.x - edest.x) * (eorg.x - edest.x) + (eorg.y - edest.y) * (eorg.y - edest.y)); if ((multiplier != 0.0) && (divisor != 0.0)) { multiplier = multiplier / divisor; // Watch out for NANs. if (!double.IsNaN(multiplier)) { newvertex.x += multiplier * (edest.y - eorg.y); newvertex.y += multiplier * (eorg.x - edest.x); } } } // Check whether the new vertex lies on an endpoint. if (((newvertex.x == eorg.x) && (newvertex.y == eorg.y)) || ((newvertex.x == edest.x) && (newvertex.y == edest.y))) { logger.Error("Ran out of precision: I attempted to split a" + " segment to a smaller size than can be accommodated by" + " the finite precision of floating point arithmetic.", "Quality.SplitEncSegs()"); throw new Exception("Ran out of precision"); } // Insert the splitting vertex. This should always succeed. success = mesh.InsertVertex(newvertex, ref enctri, ref currentenc, true, triflaws); if ((success != InsertVertexResult.Successful) && (success != InsertVertexResult.Encroaching)) { logger.Error("Failure to split a segment.", "Quality.SplitEncSegs()"); throw new Exception("Failure to split a segment."); } if (mesh.steinerleft > 0) { mesh.steinerleft--; } // Check the two new subsegments to see if they're encroached. CheckSeg4Encroach(ref currentenc); currentenc.Next(); CheckSeg4Encroach(ref currentenc); } // Set subsegment's origin to NULL. This makes it possible to detect dead // badsubsegs when traversing the list of all badsubsegs. seg.org = null; } }
/// <summary> /// Find a new location for a Steiner point. /// </summary> /// <param name="torg"></param> /// <param name="tdest"></param> /// <param name="tapex"></param> /// <param name="circumcenter"></param> /// <param name="xi"></param> /// <param name="eta"></param> /// <param name="offcenter"></param> /// <param name="badotri"></param> private Point FindNewLocationWithoutMaxAngle(Vertex torg, Vertex tdest, Vertex tapex, ref double xi, ref double eta, bool offcenter, Otri badotri) { double offconstant = behavior.offconstant; // for calculating the distances of the edges double xdo, ydo, xao, yao, xda, yda; double dodist, aodist, dadist; // for exact calculation double denominator; double dx, dy, dxoff, dyoff; ////////////////////////////// HALE'S VARIABLES ////////////////////////////// // keeps the difference of coordinates edge double xShortestEdge = 0, yShortestEdge = 0, xMiddleEdge, yMiddleEdge, xLongestEdge, yLongestEdge; // keeps the square of edge lengths double shortestEdgeDist = 0, middleEdgeDist = 0, longestEdgeDist = 0; // keeps the vertices according to the angle incident to that vertex in a triangle Point smallestAngleCorner, middleAngleCorner, largestAngleCorner; // keeps the type of orientation if the triangle int orientation = 0; // keeps the coordinates of circumcenter of itself and neighbor triangle circumcenter Point myCircumcenter, neighborCircumcenter; // keeps if bad triangle is almost good or not int almostGood = 0; // keeps the cosine of the largest angle double cosMaxAngle; bool isObtuse; // 1: obtuse 0: nonobtuse // keeps the radius of petal double petalRadius; // for calculating petal center double xPetalCtr_1, yPetalCtr_1, xPetalCtr_2, yPetalCtr_2, xPetalCtr, yPetalCtr, xMidOfShortestEdge, yMidOfShortestEdge; double dxcenter1, dycenter1, dxcenter2, dycenter2; // for finding neighbor Otri neighborotri = default(Otri); double[] thirdPoint = new double[2]; //int neighborNotFound = -1; bool neighborNotFound; // for keeping the vertices of the neighbor triangle Vertex neighborvertex_1; Vertex neighborvertex_2; Vertex neighborvertex_3; // dummy variables double xi_tmp = 0, eta_tmp = 0; //vertex thirdVertex; // for petal intersection double vector_x, vector_y, xMidOfLongestEdge, yMidOfLongestEdge, inter_x, inter_y; double[] p = new double[5], voronoiOrInter = new double[4]; bool isCorrect; // for vector calculations in perturbation double ax, ay, d; double pertConst = 0.06; // perturbation constant double lengthConst = 1; // used at comparing circumcenter's distance to proposed point's distance double justAcute = 1; // used for making the program working for one direction only // for smoothing int relocated = 0;// used to differentiate between calling the deletevertex and just proposing a steiner point double[] newloc = new double[2]; // new location suggested by smoothing double origin_x = 0, origin_y = 0; // for keeping torg safe Otri delotri; // keeping the original orientation for relocation process // keeps the first and second direction suggested points double dxFirstSuggestion, dyFirstSuggestion, dxSecondSuggestion, dySecondSuggestion; // second direction variables double xMidOfMiddleEdge, yMidOfMiddleEdge; ////////////////////////////// END OF HALE'S VARIABLES ////////////////////////////// Statistic.CircumcenterCount++; // Compute the circumcenter of the triangle. xdo = tdest.x - torg.x; ydo = tdest.y - torg.y; xao = tapex.x - torg.x; yao = tapex.y - torg.y; xda = tapex.x - tdest.x; yda = tapex.y - tdest.y; // keeps the square of the distances dodist = xdo * xdo + ydo * ydo; aodist = xao * xao + yao * yao; dadist = (tdest.x - tapex.x) * (tdest.x - tapex.x) + (tdest.y - tapex.y) * (tdest.y - tapex.y); // checking if the user wanted exact arithmetic or not if (Behavior.NoExact) { denominator = 0.5 / (xdo * yao - xao * ydo); } else { // Use the counterclockwise() routine to ensure a positive (and // reasonably accurate) result, avoiding any possibility of // division by zero. denominator = 0.5 / predicates.CounterClockwise(tdest, tapex, torg); // Don't count the above as an orientation test. Statistic.CounterClockwiseCount--; } // calculate the circumcenter in terms of distance to origin point dx = (yao * dodist - ydo * aodist) * denominator; dy = (xdo * aodist - xao * dodist) * denominator; // for debugging and for keeping circumcenter to use later // coordinate value of the circumcenter myCircumcenter = new Point(torg.x + dx, torg.y + dy); delotri = badotri; // save for later ///////////////// FINDING THE ORIENTATION OF TRIANGLE ////////////////// // Find the (squared) length of the triangle's shortest edge. This // serves as a conservative estimate of the insertion radius of the // circumcenter's parent. The estimate is used to ensure that // the algorithm terminates even if very small angles appear in // the input PSLG. // find the orientation of the triangle, basically shortest and longest edges orientation = LongestShortestEdge(aodist, dadist, dodist); //printf("org: (%f,%f), dest: (%f,%f), apex: (%f,%f)\n",torg[0],torg[1],tdest[0],tdest[1],tapex[0],tapex[1]); ///////////////////////////////////////////////////////////////////////////////////////////// // 123: shortest: aodist // 213: shortest: dadist // 312: shortest: dodist // // middle: dadist // middle: aodist // middle: aodist // // longest: dodist // longest: dodist // longest: dadist // // 132: shortest: aodist // 231: shortest: dadist // 321: shortest: dodist // // middle: dodist // middle: dodist // middle: dadist // // longest: dadist // longest: aodist // longest: aodist // ///////////////////////////////////////////////////////////////////////////////////////////// switch (orientation) { case 123: // assign necessary information /// smallest angle corner: dest /// largest angle corner: apex xShortestEdge = xao; yShortestEdge = yao; xMiddleEdge = xda; yMiddleEdge = yda; xLongestEdge = xdo; yLongestEdge = ydo; shortestEdgeDist = aodist; middleEdgeDist = dadist; longestEdgeDist = dodist; smallestAngleCorner = tdest; middleAngleCorner = torg; largestAngleCorner = tapex; break; case 132: // assign necessary information /// smallest angle corner: dest /// largest angle corner: org xShortestEdge = xao; yShortestEdge = yao; xMiddleEdge = xdo; yMiddleEdge = ydo; xLongestEdge = xda; yLongestEdge = yda; shortestEdgeDist = aodist; middleEdgeDist = dodist; longestEdgeDist = dadist; smallestAngleCorner = tdest; middleAngleCorner = tapex; largestAngleCorner = torg; break; case 213: // assign necessary information /// smallest angle corner: org /// largest angle corner: apex xShortestEdge = xda; yShortestEdge = yda; xMiddleEdge = xao; yMiddleEdge = yao; xLongestEdge = xdo; yLongestEdge = ydo; shortestEdgeDist = dadist; middleEdgeDist = aodist; longestEdgeDist = dodist; smallestAngleCorner = torg; middleAngleCorner = tdest; largestAngleCorner = tapex; break; case 231: // assign necessary information /// smallest angle corner: org /// largest angle corner: dest xShortestEdge = xda; yShortestEdge = yda; xMiddleEdge = xdo; yMiddleEdge = ydo; xLongestEdge = xao; yLongestEdge = yao; shortestEdgeDist = dadist; middleEdgeDist = dodist; longestEdgeDist = aodist; smallestAngleCorner = torg; middleAngleCorner = tapex; largestAngleCorner = tdest; break; case 312: // assign necessary information /// smallest angle corner: apex /// largest angle corner: org xShortestEdge = xdo; yShortestEdge = ydo; xMiddleEdge = xao; yMiddleEdge = yao; xLongestEdge = xda; yLongestEdge = yda; shortestEdgeDist = dodist; middleEdgeDist = aodist; longestEdgeDist = dadist; smallestAngleCorner = tapex; middleAngleCorner = tdest; largestAngleCorner = torg; break; case 321: // assign necessary information default: // TODO: is this safe? /// smallest angle corner: apex /// largest angle corner: dest xShortestEdge = xdo; yShortestEdge = ydo; xMiddleEdge = xda; yMiddleEdge = yda; xLongestEdge = xao; yLongestEdge = yao; shortestEdgeDist = dodist; middleEdgeDist = dadist; longestEdgeDist = aodist; smallestAngleCorner = tapex; middleAngleCorner = torg; largestAngleCorner = tdest; break; }// end of switch // check for offcenter condition if (offcenter && (offconstant > 0.0)) { // origin has the smallest angle if (orientation == 213 || orientation == 231) { // Find the position of the off-center, as described by Alper Ungor. dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; // If the off-center is closer to destination than the // circumcenter, use the off-center instead. /// doubleLY BAD CASE /// if (dxoff * dxoff + dyoff * dyoff < (dx - xdo) * (dx - xdo) + (dy - ydo) * (dy - ydo)) { dx = xdo + dxoff; dy = ydo + dyoff; } /// ALMOST GOOD CASE /// else { almostGood = 1; } // destination has the smallest angle } else if (orientation == 123 || orientation == 132) { // Find the position of the off-center, as described by Alper Ungor. dxoff = 0.5 * xShortestEdge + offconstant * yShortestEdge; dyoff = 0.5 * yShortestEdge - offconstant * xShortestEdge; // If the off-center is closer to the origin than the // circumcenter, use the off-center instead. /// doubleLY BAD CASE /// if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) { dx = dxoff; dy = dyoff; } /// ALMOST GOOD CASE /// else { almostGood = 1; } // apex has the smallest angle } else {//orientation == 312 || orientation == 321 // Find the position of the off-center, as described by Alper Ungor. dxoff = 0.5 * xShortestEdge - offconstant * yShortestEdge; dyoff = 0.5 * yShortestEdge + offconstant * xShortestEdge; // If the off-center is closer to the origin than the // circumcenter, use the off-center instead. /// doubleLY BAD CASE /// if (dxoff * dxoff + dyoff * dyoff < dx * dx + dy * dy) { dx = dxoff; dy = dyoff; } /// ALMOST GOOD CASE /// else { almostGood = 1; } } } // if the bad triangle is almost good, apply our approach if (almostGood == 1) { /// calculate cosine of largest angle /// cosMaxAngle = (middleEdgeDist + shortestEdgeDist - longestEdgeDist) / (2 * Math.Sqrt(middleEdgeDist) * Math.Sqrt(shortestEdgeDist)); if (cosMaxAngle < 0.0) { // obtuse isObtuse = true; } else if (Math.Abs(cosMaxAngle - 0.0) <= EPS) { // right triangle (largest angle is 90 degrees) isObtuse = true; } else { // nonobtuse isObtuse = false; } /// RELOCATION (LOCAL SMOOTHING) /// /// check for possible relocation of one of triangle's points /// relocated = DoSmoothing(delotri, torg, tdest, tapex, ref newloc); /// if relocation is possible, delete that vertex and insert a vertex at the new location /// if (relocated > 0) { Statistic.RelocationCount++; dx = newloc[0] - torg.x; dy = newloc[1] - torg.y; origin_x = torg.x; // keep for later use origin_y = torg.y; switch (relocated) { case 1: //printf("Relocate: (%f,%f)\n", torg[0],torg[1]); mesh.DeleteVertex(ref delotri); break; case 2: //printf("Relocate: (%f,%f)\n", tdest[0],tdest[1]); delotri.Lnext(); mesh.DeleteVertex(ref delotri); break; case 3: //printf("Relocate: (%f,%f)\n", tapex[0],tapex[1]); delotri.Lprev(); mesh.DeleteVertex(ref delotri); break; } } else { // calculate radius of the petal according to angle constraint // first find the visible region, PETAL // find the center of the circle and radius petalRadius = Math.Sqrt(shortestEdgeDist) / (2 * Math.Sin(behavior.MinAngle * Math.PI / 180.0)); /// compute two possible centers of the petal /// // finding the center // first find the middle point of smallest edge xMidOfShortestEdge = (middleAngleCorner.x + largestAngleCorner.x) / 2.0; yMidOfShortestEdge = (middleAngleCorner.y + largestAngleCorner.y) / 2.0; // two possible centers xPetalCtr_1 = xMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); yPetalCtr_1 = yMidOfShortestEdge + Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); xPetalCtr_2 = xMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (middleAngleCorner.y - largestAngleCorner.y) / Math.Sqrt(shortestEdgeDist); yPetalCtr_2 = yMidOfShortestEdge - Math.Sqrt(petalRadius * petalRadius - (shortestEdgeDist / 4)) * (largestAngleCorner.x - middleAngleCorner.x) / Math.Sqrt(shortestEdgeDist); // find the correct circle since there will be two possible circles // calculate the distance to smallest angle corner dxcenter1 = (xPetalCtr_1 - smallestAngleCorner.x) * (xPetalCtr_1 - smallestAngleCorner.x); dycenter1 = (yPetalCtr_1 - smallestAngleCorner.y) * (yPetalCtr_1 - smallestAngleCorner.y); dxcenter2 = (xPetalCtr_2 - smallestAngleCorner.x) * (xPetalCtr_2 - smallestAngleCorner.x); dycenter2 = (yPetalCtr_2 - smallestAngleCorner.y) * (yPetalCtr_2 - smallestAngleCorner.y); // whichever is closer to smallest angle corner, it must be the center if (dxcenter1 + dycenter1 <= dxcenter2 + dycenter2) { xPetalCtr = xPetalCtr_1; yPetalCtr = yPetalCtr_1; } else { xPetalCtr = xPetalCtr_2; yPetalCtr = yPetalCtr_2; } /// find the third point of the neighbor triangle /// neighborNotFound = GetNeighborsVertex(badotri, middleAngleCorner.x, middleAngleCorner.y, smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); /// find the circumcenter of the neighbor triangle /// dxFirstSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter dyFirstSuggestion = dy; // if there is a neighbor triangle if (!neighborNotFound) { neighborvertex_1 = neighborotri.Org(); neighborvertex_2 = neighborotri.Dest(); neighborvertex_3 = neighborotri.Apex(); // now calculate neighbor's circumcenter which is the voronoi site neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, ref xi_tmp, ref eta_tmp); /// compute petal and Voronoi edge intersection /// // in order to avoid degenerate cases, we need to do a vector based calculation for line vector_x = (middleAngleCorner.y - smallestAngleCorner.y);//(-y, x) vector_y = smallestAngleCorner.x - middleAngleCorner.x; vector_x = myCircumcenter.x + vector_x; vector_y = myCircumcenter.y + vector_y; // by intersecting bisectors you will end up with the one you want to walk on // then this line and circle should be intersected CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, xPetalCtr, yPetalCtr, petalRadius, ref p); /// choose the correct intersection point /// // calculate middle point of the longest edge(bisector) xMidOfLongestEdge = (middleAngleCorner.x + smallestAngleCorner.x) / 2.0; yMidOfLongestEdge = (middleAngleCorner.y + smallestAngleCorner.y) / 2.0; // we need to find correct intersection point, since line intersects circle twice isCorrect = ChooseCorrectPoint(xMidOfLongestEdge, yMidOfLongestEdge, p[3], p[4], myCircumcenter.x, myCircumcenter.y, isObtuse); // make sure which point is the correct one to be considered if (isCorrect) { inter_x = p[3]; inter_y = p[4]; } else { inter_x = p[1]; inter_y = p[2]; } /// check if there is a Voronoi vertex between before intersection /// // check if the voronoi vertex is between the intersection and circumcenter PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); /// determine the point to be suggested /// if (p[0] > 0.0) { // there is at least one intersection point // if it is between circumcenter and intersection // if it returns 1.0 this means we have a voronoi vertex within feasible region if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) { if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) { // go back to circumcenter dxFirstSuggestion = dx; dyFirstSuggestion = dy; } else { // we are not creating a bad triangle // neighbor's circumcenter is suggested dxFirstSuggestion = voronoiOrInter[2] - torg.x; dyFirstSuggestion = voronoiOrInter[3] - torg.y; } } else { // there is no voronoi vertex between intersection point and circumcenter if (IsBadTriangleAngle(largestAngleCorner.x, largestAngleCorner.y, middleAngleCorner.x, middleAngleCorner.y, inter_x, inter_y)) { // if it is inside feasible region, then insert v2 // apply perturbation // find the distance between circumcenter and intersection point d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); // then find the vector going from intersection point to circumcenter ax = myCircumcenter.x - inter_x; ay = myCircumcenter.y - inter_y; ax = ax / d; ay = ay / d; // now calculate the new intersection point which is perturbated towards the circumcenter inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) { // go back to circumcenter dxFirstSuggestion = dx; dyFirstSuggestion = dy; } else { // intersection point is suggested dxFirstSuggestion = inter_x - torg.x; dyFirstSuggestion = inter_y - torg.y; } } else { // intersection point is suggested dxFirstSuggestion = inter_x - torg.x; dyFirstSuggestion = inter_y - torg.y; } } /// if it is an acute triangle, check if it is a good enough location /// // for acute triangle case, we need to check if it is ok to use either of them if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > lengthConst * ((smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)))) { // use circumcenter dxFirstSuggestion = dx; dyFirstSuggestion = dy; }// else we stick to what we have found }// intersection point }// if it is on the boundary, meaning no neighbor triangle in this direction, try other direction /// DO THE SAME THING FOR THE OTHER DIRECTION /// /// find the third point of the neighbor triangle /// neighborNotFound = GetNeighborsVertex(badotri, largestAngleCorner.x, largestAngleCorner.y, smallestAngleCorner.x, smallestAngleCorner.y, ref thirdPoint, ref neighborotri); /// find the circumcenter of the neighbor triangle /// dxSecondSuggestion = dx; // if we cannot find any appropriate suggestion, we use circumcenter dySecondSuggestion = dy; // if there is a neighbor triangle if (!neighborNotFound) { neighborvertex_1 = neighborotri.Org(); neighborvertex_2 = neighborotri.Dest(); neighborvertex_3 = neighborotri.Apex(); // now calculate neighbor's circumcenter which is the voronoi site neighborCircumcenter = predicates.FindCircumcenter(neighborvertex_1, neighborvertex_2, neighborvertex_3, ref xi_tmp, ref eta_tmp); /// compute petal and Voronoi edge intersection /// // in order to avoid degenerate cases, we need to do a vector based calculation for line vector_x = (largestAngleCorner.y - smallestAngleCorner.y);//(-y, x) vector_y = smallestAngleCorner.x - largestAngleCorner.x; vector_x = myCircumcenter.x + vector_x; vector_y = myCircumcenter.y + vector_y; // by intersecting bisectors you will end up with the one you want to walk on // then this line and circle should be intersected CircleLineIntersection(myCircumcenter.x, myCircumcenter.y, vector_x, vector_y, xPetalCtr, yPetalCtr, petalRadius, ref p); /// choose the correct intersection point /// // calcuwedgeslate middle point of the longest edge(bisector) xMidOfMiddleEdge = (largestAngleCorner.x + smallestAngleCorner.x) / 2.0; yMidOfMiddleEdge = (largestAngleCorner.y + smallestAngleCorner.y) / 2.0; // we need to find correct intersection point, since line intersects circle twice // this direction is always ACUTE isCorrect = ChooseCorrectPoint(xMidOfMiddleEdge, yMidOfMiddleEdge, p[3], p[4], myCircumcenter.x, myCircumcenter.y, false/*(isObtuse+1)%2*/); // make sure which point is the correct one to be considered if (isCorrect) { inter_x = p[3]; inter_y = p[4]; } else { inter_x = p[1]; inter_y = p[2]; } /// check if there is a Voronoi vertex between before intersection /// // check if the voronoi vertex is between the intersection and circumcenter PointBetweenPoints(inter_x, inter_y, myCircumcenter.x, myCircumcenter.y, neighborCircumcenter.x, neighborCircumcenter.y, ref voronoiOrInter); /// determine the point to be suggested /// if (p[0] > 0.0) { // there is at least one intersection point // if it is between circumcenter and intersection // if it returns 1.0 this means we have a voronoi vertex within feasible region if (Math.Abs(voronoiOrInter[0] - 1.0) <= EPS) { if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, neighborCircumcenter.x, neighborCircumcenter.y)) { // go back to circumcenter dxSecondSuggestion = dx; dySecondSuggestion = dy; } else { // we are not creating a bad triangle // neighbor's circumcenter is suggested dxSecondSuggestion = voronoiOrInter[2] - torg.x; dySecondSuggestion = voronoiOrInter[3] - torg.y; } } else { // there is no voronoi vertex between intersection point and circumcenter if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) { // if it is inside feasible region, then insert v2 // apply perturbation // find the distance between circumcenter and intersection point d = Math.Sqrt((inter_x - myCircumcenter.x) * (inter_x - myCircumcenter.x) + (inter_y - myCircumcenter.y) * (inter_y - myCircumcenter.y)); // then find the vector going from intersection point to circumcenter ax = myCircumcenter.x - inter_x; ay = myCircumcenter.y - inter_y; ax = ax / d; ay = ay / d; // now calculate the new intersection point which is perturbated towards the circumcenter inter_x = inter_x + ax * pertConst * Math.Sqrt(shortestEdgeDist); inter_y = inter_y + ay * pertConst * Math.Sqrt(shortestEdgeDist); if (IsBadTriangleAngle(middleAngleCorner.x, middleAngleCorner.y, largestAngleCorner.x, largestAngleCorner.y, inter_x, inter_y)) { // go back to circumcenter dxSecondSuggestion = dx; dySecondSuggestion = dy; } else { // intersection point is suggested dxSecondSuggestion = inter_x - torg.x; dySecondSuggestion = inter_y - torg.y; } } else { // intersection point is suggested dxSecondSuggestion = inter_x - torg.x; dySecondSuggestion = inter_y - torg.y; } } /// if it is an acute triangle, check if it is a good enough location /// // for acute triangle case, we need to check if it is ok to use either of them if ((smallestAngleCorner.x - myCircumcenter.x) * (smallestAngleCorner.x - myCircumcenter.x) + (smallestAngleCorner.y - myCircumcenter.y) * (smallestAngleCorner.y - myCircumcenter.y) > lengthConst * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * (smallestAngleCorner.y - (dySecondSuggestion + torg.y)))) { // use circumcenter dxSecondSuggestion = dx; dySecondSuggestion = dy; }// else we stick on what we have found } }// if it is on the boundary, meaning no neighbor triangle in this direction, the other direction might be ok if (isObtuse) { //obtuse: do nothing dx = dxFirstSuggestion; dy = dyFirstSuggestion; } else { // acute : consider other direction if (justAcute * ((smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) * (smallestAngleCorner.x - (dxSecondSuggestion + torg.x)) + (smallestAngleCorner.y - (dySecondSuggestion + torg.y)) * (smallestAngleCorner.y - (dySecondSuggestion + torg.y))) > (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) * (smallestAngleCorner.x - (dxFirstSuggestion + torg.x)) + (smallestAngleCorner.y - (dyFirstSuggestion + torg.y)) * (smallestAngleCorner.y - (dyFirstSuggestion + torg.y))) { dx = dxSecondSuggestion; dy = dySecondSuggestion; } else { dx = dxFirstSuggestion; dy = dyFirstSuggestion; } }// end if obtuse }// end of relocation }// end of almostGood Point circumcenter = new Point(); if (relocated <= 0) { circumcenter.x = torg.x + dx; circumcenter.y = torg.y + dy; } else { circumcenter.x = origin_x + dx; circumcenter.y = origin_y + dy; } xi = (yao * dx - xao * dy) * (2.0 * denominator); eta = (xdo * dy - ydo * dx) * (2.0 * denominator); return circumcenter; }
/// <summary> /// Set Apex /// </summary> internal void SetApex(Vertex v) { tri.vertices[orient] = v; }
/// <summary> /// Set the destination of the segment that includes the subsegment. /// </summary> internal void SetSegDest(Vertex vertex) { seg.vertices[3 - orient] = vertex; }
void GenMap() { /* * <settings> */ int starsAm = 10000; // What percentage of space is taken up by stars. int starDensity = 20; // Minimal corridor length relative to maximal star size. float minCorridorLengthCoeff = 3.0f; // What percentage of space is taken up by corridors. int corridorDensity = 60; float maxz = 3.0f; /* * </settings> */ float max_star_size = Mathf.Max(pos_star_sizes); Debug.Log("Max star size: " + max_star_size); float min_corridor_length = max_star_size * minCorridorLengthCoeff; float min_corridor_length_squared = Mathf.Pow(min_corridor_length, 2); int starsx = Mathf.RoundToInt(Mathf.Sqrt(starsAm)); int starsy = starsx; float width = (starsx * max_star_size) + ((starsx - 1) * min_corridor_length); float height = (starsy * max_star_size) + ((starsy - 1) * min_corridor_length); background.transform.localScale = new Vector3(width * 0.11f, 1.0f, height * 0.11f); background.GetComponent <MeshRenderer>().material.SetTextureScale("_MainTex", new Vector2(width / 70.0f, height / 70.0f)); Debug.Log("Dimensions: " + width + ", " + height); float minx = width * -0.5f; float miny = height * -0.5f; float maxx = width * 0.5f; float maxy = height * 0.5f; float max_offset_x = (width / starsx) - ((min_corridor_length - max_star_size) * 0.5f); float max_offset_y = (height / starsy) - ((min_corridor_length - max_star_size) * 0.5f); Debug.Log("Offsets: " + max_offset_x + ", " + max_offset_y); Rectangle bounds = new Rectangle(minx, miny, width, height); TriangleNet.Mesh mesh = (TriangleNet.Mesh)GenericMesher.StructuredMesh(bounds, starsx - 1, starsy - 1); List <TriangleNet.Geometry.Vertex> stars = new List <TriangleNet.Geometry.Vertex>(); List <TriangleNet.Geometry.Vertex> shuffled_verts = new List <TriangleNet.Geometry.Vertex>(mesh.Vertices); shuffled_verts.Shuffle(); int stars_amount = 0; foreach (TriangleNet.Geometry.Vertex v in shuffled_verts) { if (stars_amount > 3 && Random.Range(0, 101) > starDensity) { continue; } stars.Add(v); stars_amount++; } GenericMesher gm = new GenericMesher(new SweepLine()); mesh = (TriangleNet.Mesh)gm.Triangulate(stars); StandardVoronoi sv = new StandardVoronoi(mesh); Polygon final = new Polygon(sv.Vertices.Count); Dictionary <int, TriangleNet.Geometry.Vertex> good_stars = new Dictionary <int, TriangleNet.Geometry.Vertex>(); Dictionary <int, int> bad2goodstar = new Dictionary <int, int>(); List <int> outBoundStars = new List <int>(); foreach (TriangleNet.Topology.DCEL.Vertex v in sv.Vertices) { if (v.x < minx || v.x > maxx || v.y < miny || v.y > maxy) { outBoundStars.Add(v.id); continue; } bool invalid = false; foreach (TriangleNet.Geometry.Vertex other in good_stars.Values) { Vector2 v1 = new Vector2((float)v.x, (float)v.y); Vector2 v2 = new Vector2((float)other.x, (float)other.y); if ((v2 - v1).sqrMagnitude < min_corridor_length_squared) { invalid = true; bad2goodstar[v.id] = other.id; } } if (invalid) { continue; } TriangleNet.Geometry.Vertex new_v = new TriangleNet.Geometry.Vertex(v.x, v.y); new_v.id = v.id; good_stars[v.id] = new_v; final.Add(new_v); } List <Segment> good_segments = new List <Segment>(); foreach (Edge e in sv.Edges) { if (outBoundStars.Contains(e.P0) || outBoundStars.Contains(e.P1)) { continue; } int P0_id; int P1_id; if (bad2goodstar.ContainsKey(e.P0)) { P0_id = bad2goodstar[e.P0]; } else { P0_id = e.P0; } if (bad2goodstar.ContainsKey(e.P1)) { P1_id = bad2goodstar[e.P1]; } else { P1_id = e.P1; } if (P0_id == P1_id) { continue; } good_segments.Add(new Segment(good_stars[P0_id], good_stars[P1_id])); } Dictionary <int, List <int> > connected_stars = new Dictionary <int, List <int> >(); foreach (Segment s in good_segments) { if (!connected_stars.ContainsKey(s.P0)) { connected_stars[s.P0] = new List <int>(); } connected_stars[s.P0].Add(s.P1); if (!connected_stars.ContainsKey(s.P1)) { connected_stars[s.P1] = new List <int>(); } connected_stars[s.P1].Add(s.P0); } Debug.Log("Currently edges: " + good_segments.Count); List <Segment> temp_segments = new List <Segment>(good_segments); temp_segments.Shuffle(); foreach (Segment s in temp_segments) { if (Random.Range(0, 101) > corridorDensity && RemovalConnectionsCheck(connected_stars, s.P0, s.P1)) { connected_stars[s.P0].Remove(s.P1); connected_stars[s.P1].Remove(s.P0); good_segments.Remove(s); } } foreach (Segment s in good_segments) { final.Add(s); } Debug.Log("Stars after everything: " + final.Points.Count); Debug.Log("Corridors after everything: " + good_segments.Count); CreateMap(final, maxz, 0.0f); }
public static Mesh GetTriangulationMesh(Vector3[] verts, HashSet <int> borderVerts, Polygon boundary) { TriangleNet.Geometry.Polygon polygon = new TriangleNet.Geometry.Polygon(); //Dictionary<TriangleNet.Geometry.Vertex, float> vertexElevMap = new Dictionary<TriangleNet.Geometry.Vertex, float>(); foreach (Vector3 vert in verts) { TriangleNet.Geometry.Vertex triangulationVert = new TriangleNet.Geometry.Vertex(vert.x, vert.z); //vertexElevMap.Add(triangulationVert, vert.y); polygon.Add(triangulationVert); } TriangleNet.Meshing.ConstraintOptions options = new TriangleNet.Meshing.ConstraintOptions() { ConformingDelaunay = true }; TriangleNet.Meshing.GenericMesher mesher = new TriangleNet.Meshing.GenericMesher(); TriangleNet.Meshing.IMesh mesh = mesher.Triangulate(polygon); Vector3[] meshVerts = new Vector3[mesh.Vertices.Count]; List <int> meshTriangles = new List <int>(); Dictionary <TriangleNet.Geometry.Vertex, int> vertexIndexMap = new Dictionary <TriangleNet.Geometry.Vertex, int>(); int v = 0; foreach (TriangleNet.Geometry.Vertex triangulatorVert in mesh.Vertices) { meshVerts[v] = new Vector3((float)triangulatorVert.X, verts[v].y, (float)triangulatorVert.Y); vertexIndexMap[triangulatorVert] = v; v++; } //for (int i = 0; i < vertexList.Length; i ++) //{ // TriangleNet.Geometry.Vertex triangulatorVert = vertexList[i]; // meshVerts[i] = new Vector3((float)triangulatorVert.X, vertexElevMap[triangulatorVert], (float)triangulatorVert.Y); // vertexIndexMap[triangulatorVert] = i; //} foreach (TriangleNet.Topology.Triangle tri in mesh.Triangles) { int ind1 = vertexIndexMap[tri.GetVertex(0)]; int ind2 = vertexIndexMap[tri.GetVertex(2)]; int ind3 = vertexIndexMap[tri.GetVertex(1)]; Vector2 center = new Vector2((float)(tri.GetVertex(0).X + tri.GetVertex(1).X + tri.GetVertex(2).X) / 3f, (float)(tri.GetVertex(0).Y + tri.GetVertex(1).Y + tri.GetVertex(2).Y) / 3f); if (!(borderVerts.Contains(ind1) && borderVerts.Contains(ind2) && borderVerts.Contains(ind3)) || (boundary.ContainsPoint(center) && !boundary.PermiterContainsPoint(center))) { meshTriangles.Add(ind1); meshTriangles.Add(ind2); meshTriangles.Add(ind3); } } Mesh unityMesh = new Mesh(); unityMesh.vertices = meshVerts; unityMesh.triangles = meshTriangles.ToArray(); unityMesh.RecalculateBounds(); unityMesh.RecalculateNormals(); return(unityMesh); }
/// <summary> /// Deallocate space for a vertex, marking it dead. /// </summary> /// <param name="dyingvertex"></param> internal void VertexDealloc(Vertex dyingvertex) { // Mark the vertex as dead. This makes it possible to detect dead // vertices when traversing the list of all vertices. dyingvertex.type = VertexType.DeadVertex; vertices.Remove(dyingvertex.hash); }
/// <summary> /// Checks if smothing is possible for a given bad triangle. /// </summary> /// <param name="badotri"></param> /// <param name="torg"></param> /// <param name="tdest"></param> /// <param name="tapex"></param> /// <param name="newloc">The new location for the point, if somothing is possible.</param> /// <returns>Returns 1, 2 or 3 if smoothing will work, 0 otherwise.</returns> private int DoSmoothing(Otri badotri, Vertex torg, Vertex tdest, Vertex tapex, ref double[] newloc) { int numpoints_p = 0;// keeps the number of points in a star of point p, q, r int numpoints_q = 0; int numpoints_r = 0; //int i; double[] possibilities = new double[6];//there can be more than one possibilities int num_pos = 0; // number of possibilities int flag1 = 0, flag2 = 0, flag3 = 0; bool newLocFound = false; //vertex v1, v2, v3; // for ccw test //double p1[2], p2[2], p3[2]; //double temp[2]; //********************* TRY TO RELOCATE POINT "p" *************** // get the surrounding points of p, so this gives us the triangles numpoints_p = GetStarPoints(badotri, torg, tdest, tapex, 1, ref points_p); // check if the points in counterclockwise order // p1[0] = points_p[0]; p1[1] = points_p[1]; // p2[0] = points_p[2]; p2[1] = points_p[3]; // p3[0] = points_p[4]; p3[1] = points_p[5]; // v1 = (vertex)p1; v2 = (vertex)p2; v3 = (vertex)p3; // if(counterclockwise(m,b,v1,v2,v3) < 0){ // // reverse the order to ccw // for(i = 0; i < numpoints_p/2; i++){ // temp[0] = points_p[2*i]; // temp[1] = points_p[2*i+1]; // points_p[2*i] = points_p[2*(numpoints_p-1)-2*i]; // points_p[2*i+1] = points_p[2*(numpoints_p-1)+1-2*i]; // points_p[2*(numpoints_p-1)-2*i] = temp[0]; // points_p[2*(numpoints_p-1)+1-2*i] = temp[1]; // } // } // m.counterclockcount--; // INTERSECTION OF PETALS // first check whether the star angles are appropriate for relocation if (torg.type == VertexType.FreeVertex && numpoints_p != 0 && ValidPolygonAngles(numpoints_p, points_p)) { //newLocFound = getPetalIntersection(m, b, numpoints_p, points_p, newloc); //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_p, points_p, newloc,torg[0],torg[1]); if (behavior.MaxAngle == 0.0) { newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_p, points_p, ref newloc); } else { newLocFound = GetWedgeIntersection(numpoints_p, points_p, ref newloc); } //printf("call petal intersection for p\n"); // make sure the relocated point is a free vertex if (newLocFound) { possibilities[0] = newloc[0];// something found possibilities[1] = newloc[1]; num_pos++;// increase the number of possibilities flag1 = 1; } } //********************* TRY TO RELOCATE POINT "q" *************** // get the surrounding points of q, so this gives us the triangles numpoints_q = GetStarPoints(badotri, torg, tdest, tapex, 2, ref points_q); // // check if the points in counterclockwise order // v1[0] = points_q[0]; v1[1] = points_q[1]; // v2[0] = points_q[2]; v2[1] = points_q[3]; // v3[0] = points_q[4]; v3[1] = points_q[5]; // if(counterclockwise(m,b,v1,v2,v3) < 0){ // // reverse the order to ccw // for(i = 0; i < numpoints_q/2; i++){ // temp[0] = points_q[2*i]; // temp[1] = points_q[2*i+1]; // points_q[2*i] = points_q[2*(numpoints_q-1)-2*i]; // points_q[2*i+1] = points_q[2*(numpoints_q-1)+1-2*i]; // points_q[2*(numpoints_q-1)-2*i] = temp[0]; // points_q[2*(numpoints_q-1)+1-2*i] = temp[1]; // } // } // m.counterclockcount--; // INTERSECTION OF PETALS // first check whether the star angles are appropriate for relocation if (tdest.type == VertexType.FreeVertex && numpoints_q != 0 && ValidPolygonAngles(numpoints_q, points_q)) { //newLocFound = getPetalIntersection(m, b,numpoints_q, points_q, newloc); //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_q, points_q, newloc,tapex[0],tapex[1]); if (behavior.MaxAngle == 0.0) { newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_q, points_q, ref newloc); } else { newLocFound = GetWedgeIntersection(numpoints_q, points_q, ref newloc); } //printf("call petal intersection for q\n"); // make sure the relocated point is a free vertex if (newLocFound) { possibilities[2] = newloc[0];// something found possibilities[3] = newloc[1]; num_pos++;// increase the number of possibilities flag2 = 2; } } //********************* TRY TO RELOCATE POINT "q" *************** // get the surrounding points of r, so this gives us the triangles numpoints_r = GetStarPoints(badotri, torg, tdest, tapex, 3, ref points_r); // check if the points in counterclockwise order // v1[0] = points_r[0]; v1[1] = points_r[1]; // v2[0] = points_r[2]; v2[1] = points_r[3]; // v3[0] = points_r[4]; v3[1] = points_r[5]; // if(counterclockwise(m,b,v1,v2,v3) < 0){ // // reverse the order to ccw // for(i = 0; i < numpoints_r/2; i++){ // temp[0] = points_r[2*i]; // temp[1] = points_r[2*i+1]; // points_r[2*i] = points_r[2*(numpoints_r-1)-2*i]; // points_r[2*i+1] = points_r[2*(numpoints_r-1)+1-2*i]; // points_r[2*(numpoints_r-1)-2*i] = temp[0]; // points_r[2*(numpoints_r-1)+1-2*i] = temp[1]; // } // } // m.counterclockcount--; // INTERSECTION OF PETALS // first check whether the star angles are appropriate for relocation if (tapex.type == VertexType.FreeVertex && numpoints_r != 0 && ValidPolygonAngles(numpoints_r, points_r)) { //newLocFound = getPetalIntersection(m, b,numpoints_r, points_r, newloc); //newLocFound = getPetalIntersectionBruteForce(m, b,numpoints_r, points_r, newloc,tdest[0],tdest[1]); if (behavior.MaxAngle == 0.0) { newLocFound = GetWedgeIntersectionWithoutMaxAngle(numpoints_r, points_r, ref newloc); } else { newLocFound = GetWedgeIntersection(numpoints_r, points_r, ref newloc); } //printf("call petal intersection for r\n"); // make sure the relocated point is a free vertex if (newLocFound) { possibilities[4] = newloc[0];// something found possibilities[5] = newloc[1]; num_pos++;// increase the number of possibilities flag3 = 3; } } //printf("numpossibilities %d\n",num_pos); //////////// AFTER FINISH CHECKING EVERY POSSIBILITY, CHOOSE ANY OF THE AVAILABLE ONE ////////////////////// if (num_pos > 0) { if (flag1 > 0) { // suggest to relocate origin newloc[0] = possibilities[0]; newloc[1] = possibilities[1]; return flag1; } else { if (flag2 > 0) { // suggest to relocate apex newloc[0] = possibilities[2]; newloc[1] = possibilities[3]; return flag2; } else {// suggest to relocate destination if (flag3 > 0) { newloc[0] = possibilities[4]; newloc[1] = possibilities[5]; return flag3; } } } } return 0;// could not find any good relocation }
/// <summary> /// Insert a vertex into a Delaunay triangulation, performing flips as necessary /// to maintain the Delaunay property. /// </summary> /// <param name="newvertex">The point to be inserted.</param> /// <param name="searchtri">The triangle to start the search.</param> /// <param name="splitseg">Segment to split.</param> /// <param name="segmentflaws">Check for creation of encroached subsegments.</param> /// <param name="triflaws">Check for creation of bad quality triangles.</param> /// <returns>If a duplicate vertex or violated segment does not prevent the /// vertex from being inserted, the return value will be ENCROACHINGVERTEX if /// the vertex encroaches upon a subsegment (and checking is enabled), or /// SUCCESSFULVERTEX otherwise. In either case, 'searchtri' is set to a handle /// whose origin is the newly inserted vertex.</returns> /// <remarks> /// The point 'newvertex' is located. If 'searchtri.triangle' is not NULL, /// the search for the containing triangle begins from 'searchtri'. If /// 'searchtri.triangle' is NULL, a full point location procedure is called. /// If 'insertvertex' is found inside a triangle, the triangle is split into /// three; if 'insertvertex' lies on an edge, the edge is split in two, /// thereby splitting the two adjacent triangles into four. Edge flips are /// used to restore the Delaunay property. If 'insertvertex' lies on an /// existing vertex, no action is taken, and the value DUPLICATEVERTEX is /// returned. On return, 'searchtri' is set to a handle whose origin is the /// existing vertex. /// /// InsertVertex() does not use flip() for reasons of speed; some /// information can be reused from edge flip to edge flip, like the /// locations of subsegments. /// /// Param 'splitseg': Normally, the parameter 'splitseg' is set to NULL, /// implying that no subsegment should be split. In this case, if 'insertvertex' /// is found to lie on a segment, no action is taken, and the value VIOLATINGVERTEX /// is returned. On return, 'searchtri' is set to a handle whose primary edge is the /// violated subsegment. /// If the calling routine wishes to split a subsegment by inserting a vertex in it, /// the parameter 'splitseg' should be that subsegment. In this case, 'searchtri' /// MUST be the triangle handle reached by pivoting from that subsegment; no point /// location is done. /// /// Param 'segmentflaws': Flags that indicate whether or not there should /// be checks for the creation of encroached subsegments. If a newly inserted /// vertex encroaches upon subsegments, these subsegments are added to the list /// of subsegments to be split if 'segmentflaws' is set. /// /// Param 'triflaws': Flags that indicate whether or not there should be /// checks for the creation of bad quality triangles. If bad triangles are /// created, these are added to the queue if 'triflaws' is set. /// </remarks> internal InsertVertexResult InsertVertex(Vertex newvertex, ref Otri searchtri, ref Osub splitseg, bool segmentflaws, bool triflaws) { Otri horiz = default(Otri); Otri top = default(Otri); Otri botleft = default(Otri), botright = default(Otri); Otri topleft = default(Otri), topright = default(Otri); Otri newbotleft = default(Otri), newbotright = default(Otri); Otri newtopright = default(Otri); Otri botlcasing = default(Otri), botrcasing = default(Otri); Otri toplcasing = default(Otri), toprcasing = default(Otri); Otri testtri = default(Otri); Osub botlsubseg = default(Osub), botrsubseg = default(Osub); Osub toplsubseg = default(Osub), toprsubseg = default(Osub); Osub brokensubseg = default(Osub); Osub checksubseg = default(Osub); Osub rightsubseg = default(Osub); Osub newsubseg = default(Osub); BadSubseg encroached; //FlipStacker newflip; Vertex first; Vertex leftvertex, rightvertex, botvertex, topvertex, farvertex; Vertex segmentorg, segmentdest; int region; double area; InsertVertexResult success; LocateResult intersect; bool doflip; bool mirrorflag; bool enq; if (splitseg.seg == null) { // Find the location of the vertex to be inserted. Check if a good // starting triangle has already been provided by the caller. if (searchtri.tri.id == DUMMY) { // Find a boundary triangle. horiz.tri = dummytri; horiz.orient = 0; horiz.Sym(); // Search for a triangle containing 'newvertex'. intersect = locator.Locate(newvertex, ref horiz); } else { // Start searching from the triangle provided by the caller. searchtri.Copy(ref horiz); intersect = locator.PreciseLocate(newvertex, ref horiz, true); } } else { // The calling routine provides the subsegment in which // the vertex is inserted. searchtri.Copy(ref horiz); intersect = LocateResult.OnEdge; } if (intersect == LocateResult.OnVertex) { // There's already a vertex there. Return in 'searchtri' a triangle // whose origin is the existing vertex. horiz.Copy(ref searchtri); locator.Update(ref horiz); return InsertVertexResult.Duplicate; } if ((intersect == LocateResult.OnEdge) || (intersect == LocateResult.Outside)) { // The vertex falls on an edge or boundary. if (checksegments && (splitseg.seg == null)) { // Check whether the vertex falls on a subsegment. horiz.Pivot(ref brokensubseg); if (brokensubseg.seg.hash != DUMMY) { // The vertex falls on a subsegment, and hence will not be inserted. if (segmentflaws) { enq = behavior.NoBisect != 2; if (enq && (behavior.NoBisect == 1)) { // This subsegment may be split only if it is an // internal boundary. horiz.Sym(ref testtri); enq = testtri.tri.id != DUMMY; } if (enq) { // Add the subsegment to the list of encroached subsegments. encroached = new BadSubseg(); encroached.subseg = brokensubseg; encroached.org = brokensubseg.Org(); encroached.dest = brokensubseg.Dest(); qualityMesher.AddBadSubseg(encroached); } } // Return a handle whose primary edge contains the vertex, // which has not been inserted. horiz.Copy(ref searchtri); locator.Update(ref horiz); return InsertVertexResult.Violating; } } // Insert the vertex on an edge, dividing one triangle into two (if // the edge lies on a boundary) or two triangles into four. horiz.Lprev(ref botright); botright.Sym(ref botrcasing); horiz.Sym(ref topright); // Is there a second triangle? (Or does this edge lie on a boundary?) mirrorflag = topright.tri.id != DUMMY; if (mirrorflag) { topright.Lnext(); topright.Sym(ref toprcasing); MakeTriangle(ref newtopright); } else { // Splitting a boundary edge increases the number of boundary edges. hullsize++; } MakeTriangle(ref newbotright); // Set the vertices of changed and new triangles. rightvertex = horiz.Org(); leftvertex = horiz.Dest(); botvertex = horiz.Apex(); newbotright.SetOrg(botvertex); newbotright.SetDest(rightvertex); newbotright.SetApex(newvertex); horiz.SetOrg(newvertex); // Set the region of a new triangle. newbotright.tri.label = botright.tri.label; if (behavior.VarArea) { // Set the area constraint of a new triangle. newbotright.tri.area = botright.tri.area; } if (mirrorflag) { topvertex = topright.Dest(); newtopright.SetOrg(rightvertex); newtopright.SetDest(topvertex); newtopright.SetApex(newvertex); topright.SetOrg(newvertex); // Set the region of another new triangle. newtopright.tri.label = topright.tri.label; if (behavior.VarArea) { // Set the area constraint of another new triangle. newtopright.tri.area = topright.tri.area; } } // There may be subsegments that need to be bonded // to the new triangle(s). if (checksegments) { botright.Pivot(ref botrsubseg); if (botrsubseg.seg.hash != DUMMY) { botright.SegDissolve(dummysub); newbotright.SegBond(ref botrsubseg); } if (mirrorflag) { topright.Pivot(ref toprsubseg); if (toprsubseg.seg.hash != DUMMY) { topright.SegDissolve(dummysub); newtopright.SegBond(ref toprsubseg); } } } // Bond the new triangle(s) to the surrounding triangles. newbotright.Bond(ref botrcasing); newbotright.Lprev(); newbotright.Bond(ref botright); newbotright.Lprev(); if (mirrorflag) { newtopright.Bond(ref toprcasing); newtopright.Lnext(); newtopright.Bond(ref topright); newtopright.Lnext(); newtopright.Bond(ref newbotright); } if (splitseg.seg != null) { // Split the subsegment into two. splitseg.SetDest(newvertex); segmentorg = splitseg.SegOrg(); segmentdest = splitseg.SegDest(); splitseg.Sym(); splitseg.Pivot(ref rightsubseg); InsertSubseg(ref newbotright, splitseg.seg.boundary); newbotright.Pivot(ref newsubseg); newsubseg.SetSegOrg(segmentorg); newsubseg.SetSegDest(segmentdest); splitseg.Bond(ref newsubseg); newsubseg.Sym(); newsubseg.Bond(ref rightsubseg); splitseg.Sym(); // Transfer the subsegment's boundary marker to the vertex if required. if (newvertex.label == 0) { newvertex.label = splitseg.seg.boundary; } } if (checkquality) { flipstack.Clear(); flipstack.Push(default(Otri)); // Dummy flip (see UndoVertex) flipstack.Push(horiz); } // Position 'horiz' on the first edge to check for // the Delaunay property. horiz.Lnext(); } else { // Insert the vertex in a triangle, splitting it into three. horiz.Lnext(ref botleft); horiz.Lprev(ref botright); botleft.Sym(ref botlcasing); botright.Sym(ref botrcasing); MakeTriangle(ref newbotleft); MakeTriangle(ref newbotright); // Set the vertices of changed and new triangles. rightvertex = horiz.Org(); leftvertex = horiz.Dest(); botvertex = horiz.Apex(); newbotleft.SetOrg(leftvertex); newbotleft.SetDest(botvertex); newbotleft.SetApex(newvertex); newbotright.SetOrg(botvertex); newbotright.SetDest(rightvertex); newbotright.SetApex(newvertex); horiz.SetApex(newvertex); // Set the region of the new triangles. newbotleft.tri.label = horiz.tri.label; newbotright.tri.label = horiz.tri.label; if (behavior.VarArea) { // Set the area constraint of the new triangles. area = horiz.tri.area; newbotleft.tri.area = area; newbotright.tri.area = area; } // There may be subsegments that need to be bonded // to the new triangles. if (checksegments) { botleft.Pivot(ref botlsubseg); if (botlsubseg.seg.hash != DUMMY) { botleft.SegDissolve(dummysub); newbotleft.SegBond(ref botlsubseg); } botright.Pivot(ref botrsubseg); if (botrsubseg.seg.hash != DUMMY) { botright.SegDissolve(dummysub); newbotright.SegBond(ref botrsubseg); } } // Bond the new triangles to the surrounding triangles. newbotleft.Bond(ref botlcasing); newbotright.Bond(ref botrcasing); newbotleft.Lnext(); newbotright.Lprev(); newbotleft.Bond(ref newbotright); newbotleft.Lnext(); botleft.Bond(ref newbotleft); newbotright.Lprev(); botright.Bond(ref newbotright); if (checkquality) { flipstack.Clear(); flipstack.Push(horiz); } } // The insertion is successful by default, unless an encroached // subsegment is found. success = InsertVertexResult.Successful; if (newvertex.tri.tri != null) { // Store the coordinates of the triangle that contains newvertex. newvertex.tri.SetOrg(rightvertex); newvertex.tri.SetDest(leftvertex); newvertex.tri.SetApex(botvertex); } // Circle around the newly inserted vertex, checking each edge opposite it // for the Delaunay property. Non-Delaunay edges are flipped. 'horiz' is // always the edge being checked. 'first' marks where to stop circling. first = horiz.Org(); rightvertex = first; leftvertex = horiz.Dest(); // Circle until finished. while (true) { // By default, the edge will be flipped. doflip = true; if (checksegments) { // Check for a subsegment, which cannot be flipped. horiz.Pivot(ref checksubseg); if (checksubseg.seg.hash != DUMMY) { // The edge is a subsegment and cannot be flipped. doflip = false; if (segmentflaws) { // Does the new vertex encroach upon this subsegment? if (qualityMesher.CheckSeg4Encroach(ref checksubseg) > 0) { success = InsertVertexResult.Encroaching; } } } } if (doflip) { // Check if the edge is a boundary edge. horiz.Sym(ref top); if (top.tri.id == DUMMY) { // The edge is a boundary edge and cannot be flipped. doflip = false; } else { // Find the vertex on the other side of the edge. farvertex = top.Apex(); // In the incremental Delaunay triangulation algorithm, any of // 'leftvertex', 'rightvertex', and 'farvertex' could be vertices // of the triangular bounding box. These vertices must be // treated as if they are infinitely distant, even though their // "coordinates" are not. if ((leftvertex == infvertex1) || (leftvertex == infvertex2) || (leftvertex == infvertex3)) { // 'leftvertex' is infinitely distant. Check the convexity of // the boundary of the triangulation. 'farvertex' might be // infinite as well, but trust me, this same condition should // be applied. doflip = predicates.CounterClockwise(newvertex, rightvertex, farvertex) > 0.0; } else if ((rightvertex == infvertex1) || (rightvertex == infvertex2) || (rightvertex == infvertex3)) { // 'rightvertex' is infinitely distant. Check the convexity of // the boundary of the triangulation. 'farvertex' might be // infinite as well, but trust me, this same condition should // be applied. doflip = predicates.CounterClockwise(farvertex, leftvertex, newvertex) > 0.0; } else if ((farvertex == infvertex1) || (farvertex == infvertex2) || (farvertex == infvertex3)) { // 'farvertex' is infinitely distant and cannot be inside // the circumcircle of the triangle 'horiz'. doflip = false; } else { // Test whether the edge is locally Delaunay. doflip = predicates.InCircle(leftvertex, newvertex, rightvertex, farvertex) > 0.0; } if (doflip) { // We made it! Flip the edge 'horiz' by rotating its containing // quadrilateral (the two triangles adjacent to 'horiz'). // Identify the casing of the quadrilateral. top.Lprev(ref topleft); topleft.Sym(ref toplcasing); top.Lnext(ref topright); topright.Sym(ref toprcasing); horiz.Lnext(ref botleft); botleft.Sym(ref botlcasing); horiz.Lprev(ref botright); botright.Sym(ref botrcasing); // Rotate the quadrilateral one-quarter turn counterclockwise. topleft.Bond(ref botlcasing); botleft.Bond(ref botrcasing); botright.Bond(ref toprcasing); topright.Bond(ref toplcasing); if (checksegments) { // Check for subsegments and rebond them to the quadrilateral. topleft.Pivot(ref toplsubseg); botleft.Pivot(ref botlsubseg); botright.Pivot(ref botrsubseg); topright.Pivot(ref toprsubseg); if (toplsubseg.seg.hash == DUMMY) { topright.SegDissolve(dummysub); } else { topright.SegBond(ref toplsubseg); } if (botlsubseg.seg.hash == DUMMY) { topleft.SegDissolve(dummysub); } else { topleft.SegBond(ref botlsubseg); } if (botrsubseg.seg.hash == DUMMY) { botleft.SegDissolve(dummysub); } else { botleft.SegBond(ref botrsubseg); } if (toprsubseg.seg.hash == DUMMY) { botright.SegDissolve(dummysub); } else { botright.SegBond(ref toprsubseg); } } // New vertex assignments for the rotated quadrilateral. horiz.SetOrg(farvertex); horiz.SetDest(newvertex); horiz.SetApex(rightvertex); top.SetOrg(newvertex); top.SetDest(farvertex); top.SetApex(leftvertex); // Assign region. // TODO: check region ok (no Math.Min necessary) region = Math.Min(top.tri.label, horiz.tri.label); top.tri.label = region; horiz.tri.label = region; if (behavior.VarArea) { if ((top.tri.area <= 0.0) || (horiz.tri.area <= 0.0)) { area = -1.0; } else { // Take the average of the two triangles' area constraints. // This prevents small area constraints from migrating a // long, long way from their original location due to flips. area = 0.5 * (top.tri.area + horiz.tri.area); } top.tri.area = area; horiz.tri.area = area; } if (checkquality) { flipstack.Push(horiz); } // On the next iterations, consider the two edges that were exposed (this // is, are now visible to the newly inserted vertex) by the edge flip. horiz.Lprev(); leftvertex = farvertex; } } } if (!doflip) { // The handle 'horiz' is accepted as locally Delaunay. if (triflaws) { // Check the triangle 'horiz' for quality. qualityMesher.TestTriangle(ref horiz); } // Look for the next edge around the newly inserted vertex. horiz.Lnext(); horiz.Sym(ref testtri); // Check for finishing a complete revolution about the new vertex, or // falling outside of the triangulation. The latter will happen when // a vertex is inserted at a boundary. if ((leftvertex == first) || (testtri.tri.id == DUMMY)) { // We're done. Return a triangle whose origin is the new vertex. horiz.Lnext(ref searchtri); Otri recenttri = default(Otri); horiz.Lnext(ref recenttri); locator.Update(ref recenttri); return success; } // Finish finding the next edge around the newly inserted vertex. testtri.Lnext(ref horiz); rightvertex = leftvertex; leftvertex = horiz.Dest(); } } }
SplayNode FrontLocate(SplayNode splayroot, Otri bottommost, Vertex searchvertex, ref Otri searchtri, ref bool farright) { bool farrightflag; bottommost.Copy(ref searchtri); splayroot = Splay(splayroot, searchvertex, ref searchtri); farrightflag = false; while (!farrightflag && RightOfHyperbola(ref searchtri, searchvertex)) { searchtri.Onext(); farrightflag = searchtri.Equals(bottommost); } farright = farrightflag; return splayroot; }
/// <summary> /// Set Origin /// </summary> internal void SetOrg(Vertex v) { tri.vertices[plus1Mod3[orient]] = v; }
/// <summary> /// Generates a structured mesh. /// </summary> /// <param name="bounds">Bounds of the mesh.</param> /// <param name="nx">Number of segments in x direction.</param> /// <param name="ny">Number of segments in y direction.</param> /// <returns>Mesh</returns> public static IMesh StructuredMesh(Rectangle bounds, int nx, int ny) { var polygon = new Polygon((nx + 1) * (ny + 1)); double x, y, dx, dy, left, bottom; dx = bounds.Width / nx; dy = bounds.Height / ny; left = bounds.Left; bottom = bounds.Bottom; int i, j, k, l, n = 0; // Add vertices. var points = new Vertex[(nx + 1) * (ny + 1)]; for (i = 0; i <= nx; i++) { x = left + i * dx; for (j = 0; j <= ny; j++) { y = bottom + j * dy; points[n++] = new Vertex(x, y); } } polygon.Points.AddRange(points); n = 0; // Set vertex hash and id. foreach (var v in points) { v.hash = v.id = n++; } // Add boundary segments. var segments = polygon.Segments; segments.Capacity = 2 * (nx + ny); Vertex a, b; for (j = 0; j < ny; j++) { // Left a = points[j]; b = points[j + 1]; segments.Add(new Segment(a, b, 1)); a.Label = b.Label = 1; // Right a = points[nx * (ny + 1) + j]; b = points[nx * (ny + 1) + (j + 1)]; segments.Add(new Segment(a, b, 1)); a.Label = b.Label = 1; } for (i = 0; i < nx; i++) { // Bottom a = points[(ny + 1) * i]; b = points[(ny + 1) * (i + 1)]; segments.Add(new Segment(a, b, 1)); a.Label = b.Label = 1; // Top a = points[ny + (ny + 1) * i]; b = points[ny + (ny + 1) * (i + 1)]; segments.Add(new Segment(a, b, 1)); a.Label = b.Label = 1; } // Add triangles. var triangles = new InputTriangle[2 * nx * ny]; n = 0; for (i = 0; i < nx; i++) { for (j = 0; j < ny; j++) { k = j + (ny + 1) * i; l = j + (ny + 1) * (i + 1); // Create 2 triangles in rectangle [k, l, l + 1, k + 1]. if ((i + j) % 2 == 0) { // Diagonal from bottom left to top right. triangles[n++] = new InputTriangle(k, l, l + 1); triangles[n++] = new InputTriangle(k, l + 1, k + 1); } else { // Diagonal from top left to bottom right. triangles[n++] = new InputTriangle(k, l, k + 1); triangles[n++] = new InputTriangle(l, l + 1, k + 1); } } } return Converter.ToMesh(polygon, triangles); }
SplayNode CircleTopInsert(SplayNode splayroot, Otri newkey, Vertex pa, Vertex pb, Vertex pc, double topy) { double ccwabc; double xac, yac, xbc, ybc; double aclen2, bclen2; Point searchpoint = new Point(); // TODO: mesh.nextras Otri dummytri = default(Otri); ccwabc = predicates.CounterClockwise(pa, pb, pc); xac = pa.x - pc.x; yac = pa.y - pc.y; xbc = pb.x - pc.x; ybc = pb.y - pc.y; aclen2 = xac * xac + yac * yac; bclen2 = xbc * xbc + ybc * ybc; searchpoint.x = pc.x - (yac * bclen2 - ybc * aclen2) / (2.0 * ccwabc); searchpoint.y = topy; return SplayInsert(Splay(splayroot, searchpoint, ref dummytri), newkey, searchpoint); }
/// <summary> /// Inserts a vertex at the circumcenter of a triangle. Deletes /// the newly inserted vertex if it encroaches upon a segment. /// </summary> /// <param name="badtri"></param> private void SplitTriangle(BadTriangle badtri) { Otri badotri = default(Otri); Vertex borg, bdest, bapex; Point newloc; // Location of the new vertex double xi = 0, eta = 0; InsertVertexResult success; bool errorflag; badotri = badtri.poortri; borg = badotri.Org(); bdest = badotri.Dest(); bapex = badotri.Apex(); // Make sure that this triangle is still the same triangle it was // when it was tested and determined to be of bad quality. // Subsequent transformations may have made it a different triangle. if (!Otri.IsDead(badotri.tri) && (borg == badtri.org) && (bdest == badtri.dest) && (bapex == badtri.apex)) { errorflag = false; // Create a new vertex at the triangle's circumcenter. // Using the original (simpler) Steiner point location method // for mesh refinement. // TODO: NewLocation doesn't work for refinement. Why? Maybe // reset VertexType? if (behavior.fixedArea || behavior.VarArea) { newloc = predicates.FindCircumcenter(borg, bdest, bapex, ref xi, ref eta, behavior.offconstant); } else { newloc = newLocation.FindLocation(borg, bdest, bapex, ref xi, ref eta, true, badotri); } // Check whether the new vertex lies on a triangle vertex. if (((newloc.x == borg.x) && (newloc.y == borg.y)) || ((newloc.x == bdest.x) && (newloc.y == bdest.y)) || ((newloc.x == bapex.x) && (newloc.y == bapex.y))) { if (Log.Verbose) { logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); errorflag = true; } } else { // The new vertex must be in the interior, and therefore is a // free vertex with a marker of zero. Vertex newvertex = new Vertex(newloc.x, newloc.y, 0 #if USE_ATTRIBS , mesh.nextras #endif ); newvertex.type = VertexType.FreeVertex; // Ensure that the handle 'badotri' does not represent the longest // edge of the triangle. This ensures that the circumcenter must // fall to the left of this edge, so point location will work. // (If the angle org-apex-dest exceeds 90 degrees, then the // circumcenter lies outside the org-dest edge, and eta is // negative. Roundoff error might prevent eta from being // negative when it should be, so I test eta against xi.) if (eta < xi) { badotri.Lprev(); } // Assign triangle for attributes interpolation. newvertex.tri.tri = newvertex_tri; // Insert the circumcenter, searching from the edge of the triangle, // and maintain the Delaunay property of the triangulation. Osub tmp = default(Osub); success = mesh.InsertVertex(newvertex, ref badotri, ref tmp, true, true); if (success == InsertVertexResult.Successful) { newvertex.hash = mesh.hash_vtx++; newvertex.id = newvertex.hash; #if USE_ATTRIBS if (mesh.nextras > 0) { Interpolation.InterpolateAttributes(newvertex, newvertex.tri.tri, mesh.nextras); } #endif mesh.vertices.Add(newvertex.hash, newvertex); if (mesh.steinerleft > 0) { mesh.steinerleft--; } } else if (success == InsertVertexResult.Encroaching) { // If the newly inserted vertex encroaches upon a subsegment, // delete the new vertex. mesh.UndoVertex(); } else if (success == InsertVertexResult.Violating) { // Failed to insert the new vertex, but some subsegment was // marked as being encroached. } else { // success == DUPLICATEVERTEX // Couldn't insert the new vertex because a vertex is already there. if (Log.Verbose) { logger.Warning("New vertex falls on existing vertex.", "Quality.SplitTriangle()"); errorflag = true; } } } if (errorflag) { logger.Error("The new vertex is at the circumcenter of triangle: This probably " + "means that I am trying to refine triangles to a smaller size than can be " + "accommodated by the finite precision of floating point arithmetic.", "Quality.SplitTriangle()"); throw new Exception("The new vertex is at the circumcenter of triangle."); } } }
double CircleTop(Vertex pa, Vertex pb, Vertex pc, double ccwabc) { double xac, yac, xbc, ybc, xab, yab; double aclen2, bclen2, ablen2; Statistic.CircleTopCount++; xac = pa.x - pc.x; yac = pa.y - pc.y; xbc = pb.x - pc.x; ybc = pb.y - pc.y; xab = pa.x - pb.x; yab = pa.y - pb.y; aclen2 = xac * xac + yac * yac; bclen2 = xbc * xbc + ybc * ybc; ablen2 = xab * xab + yab * yab; return pc.y + (xac * bclen2 - xbc * aclen2 + Math.Sqrt(aclen2 * bclen2 * ablen2)) / (2.0 * ccwabc); }