public static Voronoi FromDelaunay(Adjacency adjacency) { Voronoi v = new Voronoi(); int[] triangleRemapping = new int[adjacency.triangles.Count]; for (int i = 0; i < adjacency.triangles.Count; i++) { Adjacency.Triangle t = adjacency.triangles[i]; if (!t.valid) { triangleRemapping[i] = -1; continue; } Vertex c; float r; t.CircumCircle(adjacency.vertices, out c, out r); triangleRemapping[i] = v.vertices.Count; v.vertices.Add(c); } for (int i = 0; i < adjacency.triangles.Count; i++) { Adjacency.Triangle t = adjacency.triangles[i]; if (!t.valid) { continue; } for (int e = 0; e < 3; e++) { int adjT = adjacency.AdjacentTriangle(i, e); if (adjT < 0 || adjacency.triangles[adjT].valid == false) { continue; } if (triangleRemapping[adjT] < 0) { continue; // already processed } int voronoiV0 = triangleRemapping[i]; int voronoiV1 = triangleRemapping[adjT]; Debug.Assert(voronoiV0 >= 0 && voronoiV1 >= 0); // the 2 vertices shared by the adjacent triangles will become // the voronoi cells divided by the edge we're creating int cellA = adjacency.edges[Math.Abs(t.edges[e]) - 1].vertices[0]; int cellB = adjacency.edges[Math.Abs(t.edges[e]) - 1].vertices[1]; v.edges.Add(new Edge(voronoiV0, voronoiV1, cellA, cellB)); } triangleRemapping[i] = -1; } return(v); }
public static Voronoi FromDelaunay(Adjacency adjacency) { Voronoi v = new Voronoi(); int[] triangleRemapping = new int[adjacency.triangles.Count]; for (int i = 0; i < adjacency.triangles.Count; i++) { Adjacency.Triangle t = adjacency.triangles[i]; if (!t.valid) { triangleRemapping[i] = -1; continue; } Vertex c; float r; t.CircumCircle( adjacency.vertices, out c, out r); triangleRemapping[i] = v.vertices.Count; v.vertices.Add(c); } for (int i = 0; i < adjacency.triangles.Count; i++) { Adjacency.Triangle t = adjacency.triangles[i]; if (!t.valid) { continue; } for (int e = 0; e < 3; e++) { int adjT = adjacency.AdjacentTriangle(i, e); if (adjT < 0 || adjacency.triangles[adjT].valid == false) continue; if (triangleRemapping[adjT] < 0) continue; // already processed int voronoiV0 = triangleRemapping[i]; int voronoiV1 = triangleRemapping[adjT]; Debug.Assert(voronoiV0 >= 0 && voronoiV1 >= 0); // the 2 vertices shared by the adjacent triangles will become // the voronoi cells divided by the edge we're creating int cellA = adjacency.edges[Math.Abs(t.edges[e]) - 1].vertices[0]; int cellB = adjacency.edges[Math.Abs(t.edges[e]) - 1].vertices[1]; v.edges.Add(new Edge(voronoiV0, voronoiV1, cellA, cellB)); } triangleRemapping[i] = -1; } return v; }
public Graph(Voronoi voronoi, Adjacency adjacency, List <WangTile.MergeCandidate> candidates) { vertices = voronoi.vertices; this.candidates = candidates; edges = new List <Edge>(); float maxCellDist = float.MinValue; for (int i = 0; i < voronoi.edges.Count; i++) { Voronoi.Edge edge = voronoi.edges[i]; bool found = false; foreach (Edge e in edges) { if (e.v[0] == edge.v[0] && e.v[1] == edge.v[1] || e.v[0] == edge.v[1] && e.v[1] == edge.v[0]) { found = true; break; } } if (!found) { Vertex cellA = edge.cell[0] < adjacency.vertices.Count ? adjacency.vertices[edge.cell[0]] : null; Vertex cellB = edge.cell[1] < adjacency.vertices.Count ? adjacency.vertices[edge.cell[1]] : null; float cellDist = (cellA != null & cellB != null) ? (cellA - cellB).Length() : float.MaxValue; if (cellDist < float.MaxValue) { maxCellDist = Math.Max(maxCellDist, cellDist); } Graph.Edge gEdge = new Graph.Edge(edge.v[0], edge.v[1], cellDist); edges.Add(gEdge); } } for (int i = 0; i < edges.Count; i++) { float dist = edges[i].cost; edges[i].cost = dist < float.MaxValue ? (float)Math.Pow(1.0f - dist / maxCellDist, 100.0f) : INFINITY_COST; // the exponent value is suggested in the paper } }
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"); } }