private void AddEdge(int v0, int v1, float cost) { // remove all edges crossing our clipping edge (TODO: could we split them instead?) Edge clippingEdge = new Edge(v0, v1, cost); Stack <Edge> candidates = new Stack <Edge>(); candidates.Push(clippingEdge); while (candidates.Count > 0) { Edge cEdge = candidates.Pop(); bool isect = false; if ((vertices[cEdge.v[0]] - vertices[cEdge.v[1]]).Length() > 1e-3f) { foreach (Edge e in edges) { Vertex p; if (e.v[0] != cEdge.v[0] && e.v[0] != cEdge.v[1] && e.v[1] != cEdge.v[0] && e.v[1] != cEdge.v[1] && GeomUtils.SegmentIntersect(vertices[e.v[0]], vertices[e.v[1]], vertices[cEdge.v[0]], vertices[cEdge.v[1]], out p)) { vertices.Add(p); int isectIdx = vertices.Count - 1; edges.Remove(e); edges.Add(new Edge(e.v[0], isectIdx, e.cost)); edges.Add(new Edge(isectIdx, e.v[1], e.cost)); candidates.Push(new Edge(cEdge.v[0], isectIdx, cost)); candidates.Push(new Edge(isectIdx, cEdge.v[1], cost)); isect = true; break; } } if (!isect) { edges.Add(cEdge); } } } }
public bool FlipTriangles(int tri1, int tri2, out int[] result) { Triangle triangle1 = triangles[tri1]; Debug.Assert(triangle1.valid); Triangle triangle2 = triangles[tri2]; Debug.Assert(triangle2.valid); int commonEdgeIdx = CommonEdge(triangle1, triangle2); Debug.Assert(commonEdgeIdx >= 0); Edge commonEdge = edges[commonEdgeIdx]; Debug.Assert(commonEdge.triangles[0] == tri1 || commonEdge.triangles[0] == tri2); Debug.Assert(commonEdge.triangles[1] == tri1 || commonEdge.triangles[1] == tri2); // Tri1 = A,B,C // Tri2 = B,D,C // commonEdge = B,C int localEdgeT1 = triangle1.LocalEdgeIndex(commonEdgeIdx); Debug.Assert(localEdgeT1 >= 0); int localEdgeT2 = triangle2.LocalEdgeIndex(commonEdgeIdx); Debug.Assert(localEdgeT2 >= 0); int A = VertexOutOfTriEdge(tri1, localEdgeT1); int D = VertexOutOfTriEdge(tri2, localEdgeT2); Debug.Assert(A >= 0); Debug.Assert(D >= 0); int B = (commonEdge.triangles[0] == tri1 ? commonEdge.vertices[0] : commonEdge.vertices[1]); int C = (B == commonEdge.vertices[0] ? commonEdge.vertices[1] : commonEdge.vertices[0]); Debug.Assert(A != B && A != C && A != D && B != C && B != D && C != D); result = new int[2]; if (!GeomUtils.SegmentIntersect(vertices[A], vertices[D], vertices[B], vertices[C])) { // can't flip result[0] = result[1] = -1; return(false); } float closestAngleBefore = Math.Min(ClosestAngleOnTri(A, B, C), ClosestAngleOnTri(B, D, C)); if (!IsWorthFlipping(A, B, C, D)) { // The resulting triangles would not improve the triangulation, don't bother return(false); } RemoveTriangle(tri1); RemoveTriangle(tri2); result[0] = CreateTriangle(A, B, D); result[1] = CreateTriangle(A, D, C); float closestAngleAfter = Math.Min(ClosestAngleOnTri(A, B, D), ClosestAngleOnTri(A, D, C)); //Debug.Assert(closestAngleBefore < closestAngleAfter); #if DEBUG Debug.Assert(CheckConsistency()); #endif return(true); }
private static void MergeTiles(List <PoissonSample> source, List <PoissonSample> toMerge, neighbour_e side, out List <PoissonSample> result) { List <MergeCandidate> candidates = new List <MergeCandidate>(); foreach (PoissonSample s in source) { candidates.Add(new MergeCandidate(s, 0)); } foreach (PoissonSample s in toMerge) { candidates.Add(new MergeCandidate(s, 1)); } const float scale = 1000.0f; // apply a temporary scaling to the Poisson points (in 0-1 range) // before Delaunay triangulation (we'll undo this later) to avoid // requiring very small epsilons for point snapping etc which // may lead to numeric issues. List <Vertex> vertices = new List <Vertex>(); for (int i = 0; i < candidates.Count; i++) { vertices.Add(new Vertex(candidates[i].sample.x * scale, candidates[i].sample.y * scale)); } List <int> outTriangles; Adjacency adjacency = new Adjacency(); Delaunay2D.Delaunay2DTriangulate(vertices, false, out outTriangles, ref adjacency); for (int j = 0; j < adjacency.vertices.Count; j++) { adjacency.vertices[j].x /= scale; adjacency.vertices[j].y /= scale; } Image debugImage = null; if (OnDebugStep != null) { debugImage = new Bitmap(800, 800); } Voronoi v = Voronoi.FromDelaunay(adjacency); #if DEBUG if (debugImage != null) { v.ToImage(debugImage, adjacency); OnDebugStep(debugImage, "Voronoi diagram generated from Delaunay Triangulation"); } #endif Graph g = new Graph(v, adjacency, candidates); if (debugImage != null) { g.ToImage(debugImage); OnDebugStep(debugImage, "Merging " + NeighbourName(side) + " tile: Convert voronoi diagram into a graph"); } int v0, v1; for (int i = 0; i < 4; i++) { if (i != (int)side) { g.Clip((WangTile.neighbour_e)i, out v0, out v1); } } g.Clip(side, out v0, out v1); #if DEBUG if (debugImage != null) { g.ToImage(debugImage); OnDebugStep(debugImage, "Graph generated from Voronoi Diagram"); } #endif List <int> path; g.ShortestPath(v0, v1, out path); List <Vertex> shape; g.GenerateShape(path, out shape); for (int i = 0; i < shape.Count - 1; i++) // skip last one as it is the same as the first element (they're references) { shape[i].x *= scale; shape[i].y *= scale; } if (debugImage != null) { Graphics grph = Graphics.FromImage(debugImage); Pen sp = new Pen(Color.Orange, 2); for (int i = 0; i < shape.Count - 1; i++) { grph.DrawLine(sp, shape[i].x / scale * debugImage.Width, shape[i].y / scale * debugImage.Height, shape[i + 1].x / scale * debugImage.Width, shape[i + 1].y / scale * debugImage.Height); } OnDebugStep(debugImage, "Merging " + NeighbourName(side) + " tile: Orange line displays the lowest cost seam"); } result = new List <PoissonSample>(); for (int i = 0; i < candidates.Count; i++) { bool insideSeam = GeomUtils.PointInPolygon(new Vertex(candidates[i].sample.x * scale, candidates[i].sample.y * scale), shape); if ((insideSeam && candidates[i].sourceDist == 1) || (!insideSeam && candidates[i].sourceDist == 0)) { result.Add(candidates[i].sample); } } if (debugImage != null) { Graphics grph = Graphics.FromImage(debugImage); grph.Clear(Color.White); SolidBrush brushInside = new SolidBrush(Color.Red); SolidBrush brushOutside = new SolidBrush(Color.Gray); for (int i = 0; i < result.Count; i++) { bool insideSeam = GeomUtils.PointInPolygon(new Vertex(result[i].x * scale, result[i].y * scale), shape); float r = (float)debugImage.Width * 0.01f; RectangleF rect = new RectangleF(result[i].x * debugImage.Width - r, result[i].y * debugImage.Height - r, 2.0f * r, 2.0f * r); if (insideSeam) { grph.FillEllipse(brushInside, rect); } else { grph.FillEllipse(brushOutside, rect); } } OnDebugStep(debugImage, "Merging " + NeighbourName(side) + " tile: gray dots = base tile, red dots = merged points"); } }