using System; using System.Collections.Generic; using System.Linq; using System.Text; using TransportGame.Model; using TransportGame.Model.Road; using UnityEngine; namespace TransportGame.Generator { public class RoadGenerator { System.Random random = new System.Random(); Map map; const int segmentCountLimit = 100; const int maxSegmentLength = 20; const int minSegmentLength = 1; public RoadGenerator() { } public void Generate(Map map) { this.map = map; map.RoadNetwork = new RoadNetwork(); Queue queue = new Queue(); RoadNode first = map.RoadNetwork.CreateNode(); queue.Enqueue(first); // Set starting point if (map.PopulationCenters != null && map.PopulationCenters.Count > 0) { first.X = map.PopulationCenters.First().X; first.Y = map.PopulationCenters.First().Y; } else { first.X = random.Next(map.Width); first.Y = random.Next(map.Height); } // Go through each node for (int i = 0; i < segmentCountLimit && queue.Count > 0; i++) { RoadNode node = queue.Dequeue(); // Produce solutions based on global goals foreach (var next in GlobalGoals(node)) { if (CheckLocalConstraints(node, next)) { // Next is a temporary node - create a node on the road network var other = map.RoadNetwork.CreateNode(); other.X = next.X; other.Y = next.Y; // Create a segment var segment = map.RoadNetwork.CreateArticulationSegment(); // Assign IDs segment.Terminal1Id = node.Id; segment.Terminal2Id = other.Id; node.ArticulationSegmentIds.Add(segment.Id); other.ArticulationSegmentIds.Add(segment.Id); // Enqueue node queue.Enqueue(other); } } } } public IEnumerable GlobalGoals(RoadNode node) { // Goal #1 - connect population centers foreach (var popCenter in map.PopulationCenters.Skip(1)) { // Get direction vector float dx = popCenter.X - node.X; float dy = popCenter.Y - node.Y; // Ignore if too close to population center if (Math.Abs(dx) <= 1 && Math.Abs(dy) <= 1) continue; // Calculate length of direction vector (we need to normalize it) float dlen = Mathf.Sqrt(dx * dx + dy * dy); // Length of segment int length = random.Next(minSegmentLength, maxSegmentLength); // Calculate coordinates yield return new RoadNode() { X = node.X + length * dx / dlen, Y = node.Y + length * dy / dlen }; } // Goal #2 - random segments depending on how populated is area - max 25% chance if (random.NextDouble() < map.GetPopulation(Convert.ToInt32(node.X), Convert.ToInt32(node.Y)) * 4) { // Generate direction vector float dx = Convert.ToSingle(random.NextDouble()) * 2 - 1; float dy = Convert.ToSingle(random.NextDouble()) * 2 - 1; int length = random.Next(minSegmentLength, maxSegmentLength); // Calculate coordinates yield return new RoadNode() { X = node.X + dx * length, Y = node.Y + dy * length }; } } public bool CheckLocalConstraints(RoadNode first, RoadNode second) { // Make sure point is inside map if (!map.IsInside(Convert.ToInt32(second.X), Convert.ToInt32(second.Y))) return false; // Cannot build on water if (map.IsWater(Convert.ToInt32(second.X), Convert.ToInt32(second.Y))) return false; // Check steepness int mix = Convert.ToInt32(Math.Min(first.X, second.X)); int max = Convert.ToInt32(Math.Max(first.X, second.X)); int miy = Convert.ToInt32(Math.Min(first.Y, second.Y)); int may = Convert.ToInt32(Math.Max(first.Y, second.Y)); for (int x = mix; x <= max; x++) for (int y = miy; y <= may; y++) { if (map.GetSteepness(x, y) > 1) return false; } return true; } } }