private void AddPoint(int x, int y) { var newPoints = new List <FortuneSite>(); if (points.Count > 0) { newPoints.AddRange(points.Select(point => { var site = ObjectPool <FortuneSite> .Get(); site.Initialize(point.X, point.Y, 0); return(site); })); } var newSite = ObjectPool <FortuneSite> .Get(); newSite.Initialize(x, y); newPoints.Add(newSite); ClearPoints(); points = newPoints; fortune.Run(points, edges, 0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); GenerateDelaunay(); }
public void FortuneThreePoints() { var points = new List <FortuneSite> { new FortuneSite(100, 100), new FortuneSite(200, 200), new FortuneSite(200, 150) }; var edges = FortunesAlgorithm.Run(points, 0, 0, 600, 600); var edge = edges.First; //edge 1 Assert.AreEqual(125, edge.Value.Start.X); Assert.AreEqual(175, edge.Value.Start.Y); Assert.AreEqual(0, edge.Value.End.X); Assert.AreEqual(300, edge.Value.End.Y); Assert.IsNotNull(edge.Next); edge = edge.Next; //edge 2 Assert.AreEqual(600, edge.Value.Start.X); Assert.AreEqual(175, edge.Value.Start.Y); Assert.AreEqual(125, edge.Value.End.X); Assert.AreEqual(175, edge.Value.End.Y); Assert.IsNotNull(edge.Next); edge = edge.Next; //edge 3 Assert.AreEqual(212.5, edge.Value.Start.X); Assert.AreEqual(0, edge.Value.Start.Y); Assert.AreEqual(125, edge.Value.End.X); Assert.AreEqual(175, edge.Value.End.Y); Assert.IsNull(edge.Next); }
public void FortuneColinearPoints() { var points = new List <FortuneSite> { new FortuneSite(300, 100), new FortuneSite(300, 300), new FortuneSite(300, 500) }; var edges = FortunesAlgorithm.Run(points, 0, 0, 600, 600); var edge = edges.First; Assert.AreEqual(600, edge.Value.Start.X); Assert.AreEqual(400, edge.Value.Start.Y); Assert.AreEqual(0, edge.Value.End.X); Assert.AreEqual(400, edge.Value.End.Y); Assert.IsNotNull(edge.Next); edge = edge.Next; Assert.AreEqual(600, edge.Value.Start.X); Assert.AreEqual(200, edge.Value.Start.Y); Assert.AreEqual(0, edge.Value.End.X); Assert.AreEqual(200, edge.Value.End.Y); Assert.IsNull(edge.Next); }
private void GeneratePoints() { points.Clear(); //generate points var w = graphics.GraphicsDevice.Viewport.Width; var h = graphics.GraphicsDevice.Viewport.Height; for (var i = 0; i < GEN_COUNT; i++) { points.Add(new FortuneSite( r.Next((int)(w / 20.0), (int)(19 * w / 20.0)), r.Next((int)(h / 20.0), (int)(19 * h / 20.0)))); } //uniq the points points.Sort((p1, p2) => { if (p1.X.ApproxEqual(p2.X)) { if (p1.Y.ApproxEqual(p2.Y)) { return(0); } if (p1.Y < p2.Y) { return(-1); } return(1); } if (p1.X < p2.X) { return(-1); } return(1); }); var unique = new List <FortuneSite>(points.Count / 2); var last = points.First(); unique.Add(last); for (var index = 1; index < points.Count; index++) { var point = points[index]; if (!last.X.ApproxEqual(point.X) || !last.Y.ApproxEqual(point.Y)) { unique.Add(point); last = point; } } points = unique; edges = FortunesAlgorithm.Run(points, 0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); GenerateDelaunay(); //convert ajd list to edge list... edges get double added }
public static void Main(string[] args) { var r = new Random(); var watch = new Stopwatch(); var times = new long[MAX_N, SAMPLES]; var fortune = new FortunesAlgorithm(); var output = new PoolLinkedList <VEdge>(); for (var point = 1; point *INC <= MAX_N; point++) { var numPoints = point * INC; Console.WriteLine($"Running for n = {numPoints}"); for (var sample = 1; sample <= SAMPLES; sample++) { Console.WriteLine($"\tRunning sample {sample}"); watch.Reset(); var points = GenPoints(numPoints, r); watch.Start(); fortune.Run(points, output, 0, 0, WIDTH, HEIGHT); watch.Stop(); output.Clear((edge) => { if (edge.Neighbor != null) { ObjectPool <VEdge> .Recycle(edge.Neighbor); } ObjectPool <VEdge> .Recycle(edge); }); times[point - 1, sample - 1] = watch.ElapsedMilliseconds; } } var outFile = File.CreateText("timings.csv"); var excelFile = File.CreateText("excelTimings.csv"); outFile.Write("N, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10" + Environment.NewLine); excelFile.Write("N, T (ms)" + Environment.NewLine); for (var i = 1; i *INC <= MAX_N; i++) { var s = i * INC + ", "; for (var j = 0; j < SAMPLES - 1; j++) { s += times[i - 1, j] + ", "; } s += times[i - 1, SAMPLES - 1] + Environment.NewLine; outFile.Write(s); for (var j = 1; j <= SAMPLES; j++) { excelFile.Write(i * INC + ", " + times[i - 1, j - 1] + Environment.NewLine); } } outFile.Dispose(); excelFile.Dispose(); }
private void RelaxPoints() { if (points.Count == 0) { return; } points = LloydsRelaxation.Relax(points); edges = FortunesAlgorithm.Run(points, 0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); GenerateDelaunay(); }
private void WigglePoints() { var newPoints = new List <FortuneSite>(points.Count); if (points.Count > 0) { newPoints.AddRange(points.Select(point => new FortuneSite(point.X + 5 * r.NextDouble() - 2.5, point.Y + 5 * r.NextDouble() - 2.5))); } points = newPoints; edges = FortunesAlgorithm.Run(points, 0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); GenerateDelaunay(); }
private void AddPoint(int x, int y) { var newPoints = new List <FortuneSite>(); if (points.Count > 0) { newPoints.AddRange(points.Select(point => new FortuneSite(point.X, point.Y))); } newPoints.Add(new FortuneSite(x, y)); points = newPoints; edges = FortunesAlgorithm.Run(points, 0, 0, graphics.GraphicsDevice.Viewport.Width, graphics.GraphicsDevice.Viewport.Height); GenerateDelaunay(); }
private void button1_Click(object sender, EventArgs e) { var ptCount = (double)nud_ptCount.Value; var width = (double)nud_width.Value; var height = (double)nud_height.Value; var lloyd = (double)nud_Lloyd.Value; pictureBox1.Width = (int)(width + __border); pictureBox1.Height = (int)(height + __border); var dots = PointWorker.GetInstance().GetUniqDots(0, 0, (int)width, (int)height, (int)ptCount); //_pts = dots; var minX = dots.Min(dot => dot.X); var minY = dots.Min(dot => dot.Y); var maxX = dots.Max(dot => dot.X); var maxY = dots.Max(dot => dot.Y); pb1.Value = 0; var sw = new Stopwatch(); var sites = PointWorker.GetInstance().DotsToSites(dots); var voronoyPartEdges = FortunesAlgorithm.Run(sites, minX, minY, maxX, maxY); _edges = voronoyPartEdges.ToList(); DrawR(pictureBox1); Application.DoEvents(); for (int i = 0; i < lloyd; i++) { sw.Restart(); List<FortuneSite> newC = PointWorker.GetInstance().Relax(voronoyPartEdges, minX, minY, maxX, maxY); //_sites = newC; DrawR(pictureBox1); Application.DoEvents(); sw.Restart(); voronoyPartEdges = FortunesAlgorithm.Run(newC, minX, minY, maxX, maxY); _edges = voronoyPartEdges.ToList(); //_pts = PointWorker.GetInstance().SitesToDots(newC); DrawR(pictureBox1); Application.DoEvents(); } pb1.Value = 1; }
public Graph ExecuteStep(Graph graph) { var points = _pointSupplier.Invoke(); var fortuneSites = points.Select(point => new FortuneSite(point.X, point.Y)).ToList(); FortunesAlgorithm.Run(fortuneSites, _generationMin.X, _generationMin.Y, _generationMax.X, _generationMax.Y); // add entities to graph Dictionary <FortuneSite, Entity> siteToEntityMapping = new (); foreach (var cell in fortuneSites) { var areaEntity = new Entity(_cellNameSupplier.Invoke(cell.X, cell.Y), graph); EntityComponent areaComponent = new ("AreaComponent"); areaComponent.SetProperty("cell", cell); areaEntity.Components.Add(areaComponent); ComponentUtility.AddPosition2D(areaEntity, (float)cell.X, (float)cell.Y); if (_parent == null) { graph.Entities.Add(areaEntity); } else { _parent.AddChild(areaEntity); } siteToEntityMapping.Add(cell, areaEntity); } // add relations to graph Layer layer = graph.GetOrAddLayer(_layerName); foreach (var cell in fortuneSites) { foreach (var cellNeighbor in cell.Neighbors) { layer.AddRelation(siteToEntityMapping[cell], siteToEntityMapping[cellNeighbor]); } } return(graph); }
private void Awake() { List <FortuneSite> sites = new List <FortuneSite>(); foreach (ForuneSiteMono fsm in fortuneSites) { sites.Add(fsm.GetSite()); } edges = FortunesAlgorithm.Run(sites, minX, minY, maxX, maxY); Debug.Log(edges.Count); Dictionary <Vector3, NavigationNode> navNodes = new Dictionary <Vector3, NavigationNode>(); int i = 1; foreach (VEdge edge in edges) { Vector3 start = TransposeToZ(edge.Start); Vector3 end = TransposeToZ(edge.End); if (!navNodes.ContainsKey(start)) { navNodes.Add(start, new GameObject("VoronoiNavNode" + i).AddComponent <NavigationNode>()); navNodes[start].transform.position = start; i++; } if (!navNodes.ContainsKey(end)) { navNodes.Add(end, new GameObject("VoronoiNavNode" + i).AddComponent <NavigationNode>()); navNodes[end].transform.position = end; i++; } navNodes[start].AddConnectedNode(navNodes[end]); navNodes[end].AddConnectedNode(navNodes[start]); } }
public void FortunePointBreak() { var points = new List <FortuneSite> { new FortuneSite(100, 100), new FortuneSite(500, 100), new FortuneSite(300, 200) }; var edges = FortunesAlgorithm.Run(points, 0, 0, 600, 600); var edge = edges.First; Assert.AreEqual(325, edge.Value.Start.X); Assert.AreEqual(0, edge.Value.Start.Y); Assert.AreEqual(600, edge.Value.End.X); Assert.AreEqual(550, edge.Value.End.Y); Assert.IsNotNull(edge.Next); edge = edge.Next; Assert.AreEqual(275, edge.Value.Start.X); Assert.AreEqual(0, edge.Value.Start.Y); Assert.AreEqual(0, edge.Value.End.X); Assert.AreEqual(550, edge.Value.End.Y); Assert.IsNull(edge.Next); }
public World(int widthArea, int heightArea) { regen: this.sites = new List <FortuneSite>(); this.cities = new List <Region>(); this.kingdoms = new List <Kingdom>(); this.colors = new List <Color>(); this.voronoi = false; this.affSite = true; this.croyance = false; this.pantheon = new Pantheon(); this.mapHeight = heightArea; this.mapWidth = widthArea; Random rand = new Random(); int nbSite = widthArea * heightArea / 1000; // default: /400 this.perlinMap = new double[widthArea, heightArea]; //nbSite = 30; this.colors.Add(Color.Red); this.colors.Add(Color.Black); this.colors.Add(Color.Purple); this.colors.Add(Color.Gray); this.colors.Add(Color.Cyan); this.name = NameGenerator.GenWorldName(); Perlin p = new Perlin { Persistence = 0.65, Frequency = .003333, OctaveCount = 8, Seed = /*628594615;*/ new Random().Next(999999999) }; for (int i = 0; i < widthArea; i++) { for (int j = 0; j < heightArea; j++) { perlinMap[i, j] = p.GetValue(i, j, 0); } } for (int i = 0; i < nbSite; i++) { double cadre = 50d; var x = rand.NextDouble() * widthArea; var y = rand.NextDouble() * heightArea; var tmp = new Region(x, y, perlinMap); if (!(x < this.mapWidth - cadre && x > cadre && y < this.mapHeight - cadre && y > cadre)) { tmp.City = false; } sites.Add(tmp); if (tmp.City) { cities.Add(tmp); } } if (this.cities.Count == 0) { this.Empty(); goto regen; } this.vedges = FortunesAlgorithm.Run(ref sites, 0, 0, widthArea, heightArea); // Génération Pays int nbCapital = 5; while (nbCapital > 0) { var tmp = cities.ElementAt(new Random().Next(cities.Count)); if (!tmp.Capital) { var r = new Random().Next(this.colors.Count); tmp.Capital = true; this.kingdoms.Add(new Kingdom(tmp, this.colors.ElementAt(r), this.pantheon)); tmp.GenRegion(this.perlinMap, r + this.colors.ElementAt(r).ToArgb()); this.colors.RemoveAt(r); nbCapital--; } } List <string> verifKingdomName = new List <string>(); foreach (Kingdom k in this.kingdoms) { if (verifKingdomName.Contains(k.Name)) { k.Name = NameGenerator.GenKingdomName(); } verifKingdomName.Add(k.Name); k.Purge(); } int n = 0; foreach (Region c in cities) { if (!c.Capital) { c.GenRegion(this.perlinMap, new Random().Next(999999) + n); } n++; } //Génération des religions var pTmp = this.pantheon.Gods.Where(g => !g.Forgot).ToList(); foreach (Kingdom k in this.kingdoms.Where(e => e.Type == KingdomType.Théocratie)) { var id = rand.Next(pTmp.Count); var r = pTmp.ElementAt(id); k.God = r; r.Capitale = k.Capital; r.Followers.Add(k.Capital); k.Capital.God = r; k.Capital.GodInfluence = 1; pTmp.RemoveAt(id); } foreach (God g in pTmp) { var tmp = this.cities.Where(c => c.Kingdom != null ? c.Kingdom.Type != KingdomType.Théocratie : true).ToList(); var r = tmp.ElementAt(rand.Next(tmp.Count)); r.GodInfluence = 1; r.God = g; g.Capitale = r; g.Followers.Add(r); } // foreach region avec 1 de GodInfluence expendre en -0.1 jusqu'a rencontrer une influence equivalente ou atteindre 0.1 foreach (Region r in this.sites) { if (r.GodInfluence == 1) { var g = this.pantheon.Gods.Where(e => e == r.God).FirstOrDefault(); g.GenInfluence(r); } } foreach (FortuneSite site in sites) { foreach (VEdge vedge in vedges) { if (vedge.Left == site || vedge.Right == site) { if (!site.Cell.Contains(vedge)) { site.Cell.Add(vedge); } } } } }
public static void PopulateWithInitialData(TimelineLayer board, NamelessGame game) { var resolution = WorldGenConstants.Resolution; var random = new InternalRandom(game.WorldSettings.GlobalRandom.Next()); for (int x = 0; x < game.WorldSettings.WorldBoardWidth; x++) { for (int y = 0; y < game.WorldSettings.WorldBoardHeight; y++) { var worldTile = new WorldTile(new Microsoft.Xna.Framework.Point(x, y)); var tile = game.WorldSettings.TerrainGen.GetTileWithoutRiverWater(x, y, (float)game.WorldSettings.WorldBoardWidth / resolution); worldTile.Terrain = tile.Terrain; worldTile.Biome = tile.Biome; board.WorldTiles[x, y] = worldTile; } } //generate elevation map for river gen for (int i = 0; i < resolution; i++) { for (int j = 0; j < resolution; j++) { //fill it with terrain heght with current noises using resolution board.ElevationMap[i][j] = game.WorldSettings.TerrainGen.GetHeightNoise(i, j, 1); } } //copy elevationArray var fillArray = new TileForGeneration[resolution][]; for (int i = 0; i < resolution; i++) { fillArray[i] = new TileForGeneration[resolution]; for (int j = 0; j < resolution; j++) { fillArray[i][j] = new TileForGeneration() { fillValue = board.ElevationMap[i][j], x = i, y = j, isWater = false }; } } List <FortuneSite> points = new List <FortuneSite>(); LinkedList <VEdge> edges = new LinkedList <VEdge>(); for (var i = 0; i < resolution; i++) { points.Add(new FortuneSite( random.Next(0, resolution - 1), random.Next(0, resolution - 1))); } //uniq the points points.Sort((p1, p2) => { if (p1.X.ApproxEqual(p2.X)) { if (p1.Y.ApproxEqual(p2.Y)) { return(0); } if (p1.Y < p2.Y) { return(-1); } return(1); } if (p1.X < p2.X) { return(-1); } return(1); }); var unique = new List <FortuneSite>(points.Count / 2); var last = points.First(); unique.Add(last); for (var index = 1; index < points.Count; index++) { var point = points[index]; if (!last.X.ApproxEqual(point.X) || !last.Y.ApproxEqual(point.Y)) { unique.Add(point); last = point; } } points = unique; edges = FortunesAlgorithm.Run(points, 0, 0, resolution - 1, resolution - 1); //VEdge.Start is a VPoint with location VEdge.Start.X and VEdge.End.Y //VEdge.End is the ending point for the edge //FortuneSite.Neighbors contains the site's neighbors in the Delaunay Triangulation var waterBitmap = new Bitmap(resolution, resolution); var graphics = Graphics.FromImage(waterBitmap); Pen whitePen = new Pen(System.Drawing.Color.White, 1); var edgesByTheSea = edges.Where( x => board.ElevationMap[(int)x.Start.X][(int)x.Start.Y] < TileNoiseInterpreter.SeaLevelThreshold && board.ElevationMap[(int)x.End.X][(int)x.End.Y] >= TileNoiseInterpreter.SeaLevelThreshold || board.ElevationMap[(int)x.End.X][(int)x.End.Y] < TileNoiseInterpreter.SeaLevelThreshold && board.ElevationMap[(int)x.Start.X][(int)x.Start.Y] >= TileNoiseInterpreter.SeaLevelThreshold ); var allInlandEdges = edges.Where( x => board.ElevationMap[(int)x.End.X][(int)x.End.Y] >= TileNoiseInterpreter.SeaLevelThreshold && board.ElevationMap[(int)x.Start.X][(int)x.Start.Y] >= TileNoiseInterpreter.SeaLevelThreshold ).ToList(); //randommly remove some rivers; var cullingChance = 15; var culledEdges = allInlandEdges.Where(edge => random.Next(1, 101) > cullingChance).ToList(); culledEdges.AddRange(edgesByTheSea.Where(edge => random.Next(1, 101) > cullingChance)); //remove desert rivers with high probability var cullingDesertChance = 90; //if not in desert biome ignore, if in desert, cull with high probability culledEdges = culledEdges.Where(edge => (board.WorldTiles[(int)edge.Start.X, (int)edge.Start.Y].Biome.HasFlag(Biomes.Desert | Biomes.Jungle | Biomes.Savannah) && board.WorldTiles[(int)edge.End.X, (int)edge.End.Y].Biome.HasFlag(Biomes.Desert | Biomes.Jungle | Biomes.Savannah)) || random.Next(1, 101) < cullingDesertChance).ToList(); var finalEdges = new List <VEdge>(); finalEdges.AddRange(culledEdges); foreach (var edge in finalEdges) { var pointCountForLerpAndRandomization = 7; var listVectorEdgePoints = new List <Vector2>(); var startVector = new Vector2((float)edge.Start.X, (float)edge.Start.Y); var endVector = new Vector2((float)edge.End.X, (float)edge.End.Y); var perpendicular = (endVector - startVector); perpendicular.Normalize(); perpendicular = new Vector2(perpendicular.Y, -perpendicular.X); listVectorEdgePoints.Add(startVector); var riverWiggle = 3; for (int i = 1; i < pointCountForLerpAndRandomization - 1; i++) { var newPoint = Vector2.Lerp(startVector, endVector, ((float)i / (pointCountForLerpAndRandomization - 1))) + (perpendicular * (random.Next(-riverWiggle, riverWiggle))); listVectorEdgePoints.Add(newPoint); } listVectorEdgePoints.Add(endVector); graphics.DrawCurve(whitePen, listVectorEdgePoints.Select(x => x.ToPoint().ToPoint()).ToArray()); } for (int x = 3; x < resolution - 3; x++) { for (int y = 3; y < resolution - 3; y++) { if (waterBitmap.GetPixel(x, y).R > 0) { //fillArray[x][y].isWater = true; if (board.ElevationMap[x][y] >= TileNoiseInterpreter.SeaLevelThreshold) { fillArray[x][y].isWater = true; } else { Queue <TileForGeneration> neighbours = new Queue <TileForGeneration>(); GenerationUtility.GetNeighbours(fillArray, neighbours, x, y, 2); if (neighbours.Any(n => board.ElevationMap[n.x][n.y] >= TileNoiseInterpreter.SeaLevelThreshold)) { fillArray[x][y].isWater = true; } } } } } for (int i = 0; i < resolution; i++) { for (int j = 0; j < resolution; j++) { board.RiverMap[i][j] = fillArray[i][j].isWater; } } var riverBorderMapCopyForCalcultaion = new bool[resolution][]; for (int i = 0; i < resolution; i++) { riverBorderMapCopyForCalcultaion[i] = new bool[resolution]; for (int j = 0; j < resolution; j++) { if (board.RiverMap[i][j]) { bool borderedByAnythingBesidesWater = false; //not really a radius, more like an side lenght of a square var searchRadius = 3; if (i > searchRadius && i < resolution - searchRadius && j > searchRadius && j < resolution - searchRadius) { for (int k = i - searchRadius; k < i + searchRadius + 1; k++) { for (int l = j - searchRadius; l < j + searchRadius + 1; l++) { if (!board.RiverMap[k][l]) { borderedByAnythingBesidesWater = true; } } } } if (borderedByAnythingBesidesWater) { board.RiverBorderMap[i][j] = true; riverBorderMapCopyForCalcultaion[i][j] = true; } } } } var pointsNotConnectedToStartingPoints = new List <TileForGeneration>(); for (int i = 0; i < resolution; i++) { for (int j = 0; j < resolution; j++) { if (board.RiverBorderMap[i][j]) { pointsNotConnectedToStartingPoints.Add(fillArray[i][j]); } } } GenerationUtility.AnalyzeAndAddLines(pointsNotConnectedToStartingPoints, riverBorderMapCopyForCalcultaion, out var borderLines); for (int i = 0; i < resolution; i++) { for (int j = 0; j < resolution; j++) { board.InlandWaterConnectivity[i][j] = new TileForInlandWaterConnectivity() { x = i, y = j, isWater = board.RiverMap[i][j] }; } } foreach (var borderLine in borderLines) { var line = new WaterBorderLine() { Points = borderLine.Select(p => new Microsoft.Xna.Framework.Point(p.x, p.y)).ToList() }; board.BorderLines.Add(line); foreach (var p in line.Points) { board.InlandWaterConnectivity[p.X][p.Y].WaterBorderLines.Add(line); } } #if DEBUG ImageWriter.WaterWriteImage(board.RiverMap, board.ElevationMap, resolution, "C:\\11\\RiverMap.png", new Color(1, 0, 0, 1f)); ImageWriter.WaterWriteImage(board.RiverBorderMap, board.ElevationMap, resolution, "C:\\11\\RiverBorderMap.png", new Color(1, 0, 0, 1f)); ImageWriter.RiverBordersWriteImage(borderLines, board.ElevationMap, resolution, "C:\\11\\riverBordersLines.png"); #endif for (int x = 0; x < game.WorldSettings.WorldBoardWidth; x++) { for (int y = 0; y < game.WorldSettings.WorldBoardHeight; y++) { var worldTile = board.WorldTiles[x, y]; if (board.RiverMap[x][y] && worldTile.Terrain != TerrainTypes.Water) { worldTile.Terrain = TerrainTypes.Water; worldTile.Biome = Biomes.River; } } } }
public static GameObject LabelPolygon(List <List <GraphNode> > polygonBorders, string label, Color color) { // 1. Compute voronoi of given points with "VoronoiLib" List <FortuneSite> points = new List <FortuneSite>(); List <GraphNode> nodes = new List <GraphNode>(); foreach (List <GraphNode> polygonBorder in polygonBorders) { foreach (GraphNode n in polygonBorder) { if (!nodes.Contains(n)) { nodes.Add(n); } } } foreach (GraphNode n in nodes) { points.Add(new FortuneSite(n.Vertex.x, n.Vertex.y)); } float margin = 0.2f; float minX = nodes.Min(x => x.Vertex.x) - margin; float maxX = nodes.Max(x => x.Vertex.x) + margin; float minY = nodes.Min(x => x.Vertex.y) - margin; float maxY = nodes.Max(x => x.Vertex.y) + margin; LinkedList <VEdge> voronoi = FortunesAlgorithm.Run(points, minX, minY, maxX, maxY); // 2. Remove all edges that have either start or end outside of polygon List <Vector2> vertices = nodes.Select(x => x.Vertex).ToList(); List <VEdge> edgesToRemove = new List <VEdge>(); foreach (VEdge edge in voronoi) { if (!GeometryFunctions.IsPointInPolygon4(vertices, new Vector2((float)edge.Start.X, (float)edge.Start.Y)) || !GeometryFunctions.IsPointInPolygon4(vertices, new Vector2((float)edge.End.X, (float)edge.End.Y))) { edgesToRemove.Add(edge); } } foreach (VEdge edge in edgesToRemove) { voronoi.Remove(edge); } // DEBUG voronoi foreach (VEdge edge in voronoi) { Debug.DrawLine(new Vector3((float)edge.Start.X, 0f, (float)edge.Start.Y), new Vector3((float)edge.End.X, 0f, (float)edge.End.Y), Color.red, 30); } // 3. Turn remaining edges into a graph (create a list of for each point representing the points it is connected to) Dictionary <VPoint, List <VPoint> > voronoiGraph = new Dictionary <VPoint, List <VPoint> >(); foreach (VEdge edge in voronoi) { // handle start point if (!voronoiGraph.ContainsKey(edge.Start)) { voronoiGraph.Add(edge.Start, new List <VPoint>() { edge.End }); } else if (!voronoiGraph[edge.Start].Contains(edge.End)) { voronoiGraph[edge.Start].Add(edge.End); } // handle end point if (!voronoiGraph.ContainsKey(edge.End)) { voronoiGraph.Add(edge.End, new List <VPoint>() { edge.Start }); } else if (!voronoiGraph[edge.End].Contains(edge.Start)) { voronoiGraph[edge.End].Add(edge.Start); } } // 4. Find longest path (with consideration for straightness) between two leaves in graph - this is the centerline Dictionary <VPoint, List <VPoint> > voronoiLeaves = voronoiGraph.Where(x => x.Value.Count == 1).ToDictionary(x => x.Key, x => x.Value); float curvePenalty = 0.00007f; float longestDistance = float.MinValue; List <VPoint> centerLine = new List <VPoint>(); float longestDistanceNoPenalty = float.MinValue; List <VPoint> centerLineNoPenalty = new List <VPoint>(); foreach (KeyValuePair <VPoint, List <VPoint> > startPoint in voronoiLeaves) { foreach (KeyValuePair <VPoint, List <VPoint> > endPoint in voronoiLeaves) { if (startPoint.Key == endPoint.Key) { continue; } List <VPoint> leavesPath = GetShortestPath(new List <VPoint>() { startPoint.Key }, endPoint.Key, voronoiGraph); if (leavesPath == null) { continue; } float distanceWithPenalty = GetPathDistance(leavesPath, curvePenalty); if (distanceWithPenalty > longestDistance) { longestDistance = distanceWithPenalty; centerLine = leavesPath; } float distanceNoPenalty = GetPathDistance(leavesPath, 0f); if (distanceNoPenalty > longestDistanceNoPenalty) { longestDistanceNoPenalty = distanceNoPenalty; centerLineNoPenalty = leavesPath; } } } // If the straight centerline is too short, take the longer curvy one instead if (GetPathDistance(centerLine, 0f) < 0.4f * GetPathDistance(centerLineNoPenalty, 0f)) { centerLine = centerLineNoPenalty; } // 5. Smoothen the centerline int smoothSteps = 5; List <VPoint> smoothCenterLine = new List <VPoint>(); smoothCenterLine.AddRange(centerLine); for (int i = 0; i < smoothSteps; i++) { smoothCenterLine = SmoothLine(smoothCenterLine); } // DEBUG centerline //Debug.Log("Longest path without curve penalty: " + GetPathDistance(centerLineNoPenalty, 0f)); //Debug.Log("Longest path with curve penalty: " + GetPathDistance(centerLine, curvePenalty)); //for (int i = 1; i < centerLineNoPenalty.Count; i++) Debug.DrawLine(new Vector3((float)centerLineNoPenalty[i - 1].X, 0f, (float)centerLineNoPenalty[i - 1].Y), new Vector3((float)centerLineNoPenalty[i].X, 0f, (float)centerLineNoPenalty[i].Y), Color.blue, 30); //for (int i = 1; i < centerLine.Count; i++) Debug.DrawLine(new Vector3((float)centerLine[i - 1].X, 0f, (float)centerLine[i - 1].Y), new Vector3((float)centerLine[i].X, 0f, (float)centerLine[i].Y), Color.red, 30); for (int i = 1; i < smoothCenterLine.Count; i++) { Debug.DrawLine(new Vector3((float)smoothCenterLine[i - 1].X, 0f, (float)smoothCenterLine[i - 1].Y), new Vector3((float)smoothCenterLine[i].X, 0f, (float)smoothCenterLine[i].Y), Color.blue, 30); } // 6. Make sure the path goes from left to right double xChange = 0; for (int i = 1; i < smoothCenterLine.Count; i++) { xChange += smoothCenterLine[i].X - smoothCenterLine[i - 1].X; } if (xChange < 0) { smoothCenterLine.Reverse(); } // 7. Place text along centerline return(DrawTextAlongPath(label, smoothCenterLine, color)); }