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)); // Logger.Info("Generated initial segment: {0} -> {1}", p0, p1); // Logger.Info("Generated initial segment: {0} -> {1}", p0, p2); // Create root node var node0 = map.RoadNetwork.CreateNode(p0); qtree.Add(node0); // Logger.Info("Generated root node: {0}", node0); // Create & enqueue segments queue.Add(new RoadGeneratorSegment(node0, p1, true)); queue.Add(new RoadGeneratorSegment(node0, p2, true)); } public void Step() { // Logger.Info(">>> BEGAN STEP <<<"); var segment = queue.OrderBy(x => x.Time).First(); queue.Remove(segment); // Logger.Info("Dequeued segment {0}", segment); // Check local constraints if (CheckLocalConstraints(segment)) { // Logger.Info("Local constraints check succeeded."); 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); // Logger.Info("Added segment to queue: {0}", newSegment); } } else { // Logger.Info("Local constraints check failed!"); } } 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; // Logger.Info("> Computing global goals. prevPos={0}, dir={1}, highway={2}", prevPos, dir, highway); // Going straight Vector2 straight = prevPos + dir * ((highway) ? HighwaySegmentLength : DefaultSegmentLength); float straightPopulation = map.GetPopulation(straight); // Logger.Info("> Straight={0} StraightPopulation={1}", straight, straightPopulation); // Highways... if (highway) { // Logger.Info("> Highway case:"); Vector2 randomStraight = prevPos + HighwaySegmentLength * dir.RotateDeg(random.Next(-MaximumRandomStraightAngle, MaximumRandomStraightAngle)); float randomPopulation = map.GetPopulation(randomStraight); // Logger.Info("> RandomStraight={0} RandomPopulation={1}", randomStraight, randomPopulation); if (randomPopulation > straightPopulation) { // Logger.Info("> Yielding random straight vector."); yield return new RoadGeneratorSegment(segment.Terminal2, randomStraight, highway); } else { // Logger.Info("> Yielding straight vector."); yield return new RoadGeneratorSegment(segment.Terminal2, straight, highway); } // Branch highway if (Math.Max(straightPopulation, randomPopulation) > HighwayBranchPopulationTreshold) { // Logger.Info("> Above treshold. Branching..."); 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; // Logger.Info("> Branch to the left: {0}", leftBranch); } 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; // Logger.Info("> Branch to the right: {0}", rightBranch); } } // Don't allow more branches if (highwayBranched) yield break; } else if (random.NextSingle() < straightPopulation) { // Logger.Info("> Not highway. Yielding straight vector."); yield return new RoadGeneratorSegment(segment.Terminal2, straight, false); } // Branch normal road if (straightPopulation > DefaultBranchPopulationTreshold) { // Logger.Info("Straight population above branch treshold. Branching..."); 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); // Logger.Info("> Branch to the left: {0}", leftBranch); } 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); // Logger.Info("> Branch to the right: {0}", rightBranch); } } } private bool CheckLocalConstraints(RoadGeneratorSegment segment) { // Logger.Info("Checking local constraints..."); // Constraint #1: check for obstacles if (IsObstacle(segment.Terminal2Pos)) { // Logger.Info("Obstacle."); 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; // Logger.Info("Level difference is {0}, slope is (rads) {1}", levelDiff, Math.Asin(sinSlope)); if (Math.Asin(sinSlope) > SlopeLimit) { // Logger.Info("Slope too big!"); 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); // Logger.Info("Searching area {0} for intersecting segments.", queryArea); IEnumerable segmentIds = Enumerable.Empty(); // Look for nearby segments foreach (var node in qtree.Query(queryArea)) { // Logger.Info("Found node: {0}", node); if (node == segment.Terminal1) { // Logger.Info("Node is originating node. Will ignore."); 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); // Logger.Info("Found segment: {0}. Will test intersection between segments {0} and {1}", other, line1, line2); 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) { // Logger.Info("Found intersection point: {0}", inters); // 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) { // Logger.Info("Angle between segments is too small ({0} rads)", Math.Abs(Math.Acos(cos))); return false; } // Split segment var newNode = map.RoadNetwork.SplitArticulationSegment(other, inters.Value); segment.Terminal2Pos = inters.Value; segment.Terminal2 = newNode; // Logger.Info("Performed split in point: {0}", newNode); return true; } else // Logger.Info("Does not intersect."); // 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) { // Logger.Info("Angle between segments is too small ({0} rads)", Math.Abs(Math.Acos(cos))); return false; } } // Logger.Info("Point is close to existing intersection: {0}, will snap.", other.Terminal2); 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; } } }