using System; using System.Collections.Generic; using System.Linq; using System.Text; using TransportGame.Model; using TransportGame.Model.Road; using TransportGame.Utils; using Vector2 = TransportGame.Model.Vector2; namespace TransportGame.Generator { public class RoadGenerator { class RoadGeneratorSegment { public RoadNode Terminal1; public Vector2 Terminal2Pos; public RoadNode Terminal2; public bool Highway; public int Time; public RoadGeneratorSegment(RoadNode term1, Vector2 term2pos, bool highway, int time = 0) { Terminal1 = term1; Terminal2Pos = term2pos; Highway = highway; Time = time; } public override string ToString() { string str = String.Format("(gensegment, {0}->", Terminal1); if (Terminal2 == null) str += Terminal2Pos.ToString(); else str += Terminal2.ToString(); str += ", "; if (Highway) str += "highway, "; str += String.Format("time={0})", Time); return str; } } QuadTree qtree; List queue; System.Random random = new System.Random(); Map map; const float HighwaySegmentLength = 60; const float DefaultBranchPopulationTreshold = 0.12f; const float DefaultBranchProbability = 0.2f; const float DefaultSegmentLength = 24; const float SteepnessLimit = 10; const float SlopeLimit = (float)Math.PI / 6; const float RoadSegmentAngleLimit = (float)Math.PI / 4; const float RoadSnapDistance = 19; const float MinNodeDistance = 12; const int MaximumRandomStraightAngle = 45; // in degrees const int MaximumBranchAngleVariation = 12; // in degrees const float HighwayBranchPopulationTreshold = .4f; // 0..1 const float HighwayBranchProbability = .01f; const int HighwayBranchDelay = 3; const int MaximumIntersectingRoads = 5; public RoadGenerator() { } public void Initialize(Map map) { this.map = map; map.RoadNetwork = new RoadNetwork(); qtree = new QuadTree(0, 0, map.Width, map.Height); queue = new List(); // Generate positions Vector2 center = new Vector2(map.Width / 2, map.Height / 2); int maxDistanceFromCenter = map.Width / 3; Vector2 p0, p1, p2; // p2 goes in opposite direction do { // Generate point close to center of the map float gen_x = random.Next(-maxDistanceFromCenter, maxDistanceFromCenter) + random.NextSingle(); float gen_y = random.Next(-maxDistanceFromCenter, maxDistanceFromCenter) + random.NextSingle(); p0 = center + new Vector2(gen_x, gen_y); // Generate a random direction Vector2 dir = Vector2.FromDegrees(random.Next(360)) * (HighwaySegmentLength / 2); p1 = p0 + dir; p2 = p0 - dir; } while (IsObstacle(p0) || IsObstacle(p1) || IsObstacle(p2)); // Create root node var node0 = map.RoadNetwork.CreateNode(p0); qtree.Add(node0); // Create & enqueue segments queue.Add(new RoadGeneratorSegment(node0, p1, true)); queue.Add(new RoadGeneratorSegment(node0, p2, true)); } public void Step() { var segment = queue.OrderBy(x => x.Time).First(); queue.Remove(segment); // Check local constraints if (CheckLocalConstraints(segment)) { RoadSegment createdSegment; // Finish to create segment if (segment.Terminal2 != null) createdSegment = map.RoadNetwork.CreateArticulationSegment(segment.Terminal1, segment.Terminal2); else createdSegment = map.RoadNetwork.CreateArticulationSegment(segment.Terminal1, segment.Terminal2Pos); qtree.Add(createdSegment.Terminal2); createdSegment.LanesTo1 = createdSegment.LanesTo2 = (segment.Highway) ? 3 : 1; // Use global goals to get new segments foreach (var newSegment in GlobalGoals(createdSegment)) { newSegment.Time += segment.Time + 1; queue.Add(newSegment); } } } private bool IsObstacle(Vector2 p) { return !map.IsInside(p.X, p.Y) || map.IsWater(p.X, p.Y) || map.GetSteepness(p.X, p.Y) > SteepnessLimit; } public void Generate(Map map) { Initialize(map); int iterationCount = (map.Width * map.Height) / 512; for (int i = 0; i < iterationCount && queue.Count > 0; i++) Step(); } private IEnumerable GlobalGoals(RoadSegment segment) { Vector2 prevPos = segment.Terminal2.Position; Vector2 dir = (segment.Terminal2.Position - segment.Terminal1.Position).Normalized; bool highway = (segment.LanesTo1 >= 3); bool highwayBranched = false; // Going straight Vector2 straight = prevPos + dir * ((highway) ? HighwaySegmentLength : DefaultSegmentLength); float straightPopulation = map.GetPopulation(straight); // Highways... if (highway) { Vector2 randomStraight = prevPos + HighwaySegmentLength * dir.RotateDeg(random.Next(-MaximumRandomStraightAngle, MaximumRandomStraightAngle)); float randomPopulation = map.GetPopulation(randomStraight); if (randomPopulation > straightPopulation) yield return new RoadGeneratorSegment(segment.Terminal2, randomStraight, highway); else yield return new RoadGeneratorSegment(segment.Terminal2, straight, highway); // Branch highway if (Math.Max(straightPopulation, randomPopulation) > HighwayBranchPopulationTreshold) { if (random.NextSingle() < HighwayBranchProbability) { Vector2 leftBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(-90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation)); yield return new RoadGeneratorSegment(segment.Terminal2, leftBranch, highway, HighwayBranchDelay); highwayBranched = true; } if (random.NextSingle() < HighwayBranchProbability) { Vector2 rightBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation)); yield return new RoadGeneratorSegment(segment.Terminal2, rightBranch, highway, HighwayBranchDelay); highwayBranched = true; } } // Don't allow more branches if (highwayBranched) yield break; } else if (random.NextSingle() < straightPopulation) yield return new RoadGeneratorSegment(segment.Terminal2, straight, false); // Branch normal road if (straightPopulation > DefaultBranchPopulationTreshold) { if (random.NextSingle() < DefaultBranchProbability * straightPopulation) { Vector2 leftBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(-90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation)); yield return new RoadGeneratorSegment(segment.Terminal2, leftBranch, false, (highway) ? HighwayBranchDelay : 0); } if (random.NextSingle() < DefaultBranchProbability * straightPopulation) { Vector2 rightBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation)); yield return new RoadGeneratorSegment(segment.Terminal2, rightBranch, false, (highway) ? HighwayBranchDelay : 0); } } } private bool CheckLocalConstraints(RoadGeneratorSegment segment) { // Constraint #1: check for obstacles if (IsObstacle(segment.Terminal2Pos)) return false; // Constraint #2: slope float segmentLength = (segment.Highway) ? HighwaySegmentLength : DefaultSegmentLength; float levelDiff = map.GetHeight((int)segment.Terminal1.X, (int)segment.Terminal1.Y) - map.GetHeight((int)segment.Terminal2Pos.X, (int)segment.Terminal2Pos.Y); float sinSlope = Math.Abs(levelDiff) / segmentLength; if (Math.Asin(sinSlope) > SlopeLimit) return false; // Constraint #3: Number of intersecting roads if (segment.Terminal1.ArticulationSegmentIds.Count > MaximumIntersectingRoads) return false; // Constraint #4: intersections & snapping Rectangle queryArea = new Rectangle( Math.Min(segment.Terminal1.X, segment.Terminal2Pos.X) - 3 * HighwaySegmentLength, Math.Min(segment.Terminal1.Y, segment.Terminal2Pos.Y) - 3 * HighwaySegmentLength, Math.Max(segment.Terminal1.X, segment.Terminal2Pos.X) + 3 * HighwaySegmentLength, Math.Max(segment.Terminal1.Y, segment.Terminal2Pos.Y) + 3 * HighwaySegmentLength); IEnumerable segmentIds = Enumerable.Empty(); // Look for nearby segments foreach (var node in qtree.Query(queryArea)) { if (node == segment.Terminal1) continue; // Too close to another node in the area if ((node.Position - segment.Terminal2Pos).LengthSq < MinNodeDistance * MinNodeDistance) return false; segmentIds = segmentIds.Concat(node.ArticulationSegmentIds); } // Filter & sort the segments by distance segmentIds = segmentIds.Distinct().OrderBy(id => { var seg = map.RoadNetwork.ArticulationSegments[id]; var line = new LineSegment(seg.Terminal1.Position, seg.Terminal2.Position); return LineSegment.Distance(line, segment.Terminal2Pos); }); foreach (var segmentId in segmentIds) { var other = map.RoadNetwork.ArticulationSegments[segmentId]; var line1 = new LineSegment(segment.Terminal1.Position, segment.Terminal2Pos); var line2 = new LineSegment(other.Terminal1.Position, other.Terminal2.Position); Vector2? inters = LineSegment.Intersect(line1, line2); // Case #1: there is an intersection with another segment. We cut the rest of the segment if (inters.HasValue && inters.Value != segment.Terminal1.Position) { // Check angle between segments float cos = Vector2.Dot((line1.P1 - line1.P0).Normalized, (line2.P1 - line2.P0).Normalized); if (Math.Abs(Math.Acos(cos)) < RoadSegmentAngleLimit) return false; // Split segment var newNode = map.RoadNetwork.SplitArticulationSegment(other, inters.Value); segment.Terminal2Pos = inters.Value; segment.Terminal2 = newNode; return true; } // Case #2: no intersection, but the point is close enough to an existing intersection if ((segment.Terminal2Pos - other.Terminal2.Position).LengthSq <= RoadSnapDistance * RoadSnapDistance) { // Check angle between intersecting segments foreach (var intersSeg in other.Terminal2.ArticulationSegments) { float cos = Vector2.Dot((line1.P1 - line1.P0).Normalized, (intersSeg.Terminal2.Position - intersSeg.Terminal1.Position).Normalized); if (Math.Abs(Math.Acos(cos)) < RoadSegmentAngleLimit) return false; } segment.Terminal2Pos = other.Terminal2.Position; segment.Terminal2 = other.Terminal2; return true; } //// TODO: Case #3: the point is close enough to an existing road segment //float dist = LineSegment.Distance(line2, segment.Terminal2Pos); //if (dist < RoadSnapDistance) //{ // float proj0 = (float)Math.Sqrt((line2.P0 - segment.Terminal2Pos).LengthSq - dist * dist); // float percent = proj0 / //} } return true; } } }