city-generation/Game/Assets/Scripts/Generator/RoadGenerator.cs

320 lines
13 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TransportGame.Model;
2015-05-26 16:36:44 +00:00
using TransportGame.Model.Road;
2015-05-29 16:03:08 +00:00
using TransportGame.Utils;
using Vector2 = TransportGame.Model.Vector2;
2015-05-26 16:36:44 +00:00
namespace TransportGame.Generator
{
public class RoadGenerator
{
2015-05-29 16:03:08 +00:00
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<RoadNode> qtree;
List<RoadGeneratorSegment> queue;
2015-05-26 16:36:44 +00:00
System.Random random = new System.Random();
Map map;
2015-05-29 16:03:08 +00:00
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;
2015-05-26 16:36:44 +00:00
public RoadGenerator()
{
}
2015-05-29 16:03:08 +00:00
public void Initialize(Map map)
{
2015-05-26 16:36:44 +00:00
this.map = map;
map.RoadNetwork = new RoadNetwork();
2015-05-29 16:03:08 +00:00
qtree = new QuadTree<RoadNode>(0, 0, map.Width, map.Height);
queue = new List<RoadGeneratorSegment>();
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
// 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
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
// 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));
}
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
public void Step()
{
var segment = queue.OrderBy(x => x.Time).First();
queue.Remove(segment);
// Check local constraints
if (CheckLocalConstraints(segment))
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
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;
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
// Use global goals to get new segments
foreach (var newSegment in GlobalGoals(createdSegment))
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
newSegment.Time += segment.Time + 1;
queue.Add(newSegment);
2015-05-26 16:36:44 +00:00
}
}
}
2015-05-29 16:03:08 +00:00
private bool IsObstacle(Vector2 p)
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
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<RoadGeneratorSegment> 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)
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
Vector2 randomStraight = prevPos + HighwaySegmentLength * dir.RotateDeg(random.Next(-MaximumRandomStraightAngle, MaximumRandomStraightAngle));
float randomPopulation = map.GetPopulation(randomStraight);
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
if (randomPopulation > straightPopulation)
yield return new RoadGeneratorSegment(segment.Terminal2, randomStraight, highway);
else
yield return new RoadGeneratorSegment(segment.Terminal2, straight, highway);
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
// Branch highway
if (Math.Max(straightPopulation, randomPopulation) > HighwayBranchPopulationTreshold)
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
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;
2015-05-26 16:36:44 +00:00
}
2015-05-29 16:03:08 +00:00
else if (random.NextSingle() < straightPopulation)
yield return new RoadGeneratorSegment(segment.Terminal2, straight, false);
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
// 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)
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
Vector2 rightBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation));
yield return new RoadGeneratorSegment(segment.Terminal2, rightBranch, false, (highway) ? HighwayBranchDelay : 0);
}
2015-05-26 16:36:44 +00:00
}
}
2015-05-29 16:03:08 +00:00
private bool CheckLocalConstraints(RoadGeneratorSegment segment)
2015-05-26 16:36:44 +00:00
{
2015-05-29 16:03:08 +00:00
// Constraint #1: check for obstacles
if (IsObstacle(segment.Terminal2Pos))
2015-05-26 16:36:44 +00:00
return false;
2015-05-29 16:03:08 +00:00
// 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;
2015-05-26 16:36:44 +00:00
2015-05-29 16:03:08 +00:00
if (Math.Asin(sinSlope) > SlopeLimit)
return false;
// Constraint #3: Number of intersecting roads
if (segment.Terminal1.ArticulationSegmentIds.Count > MaximumIntersectingRoads)
2015-05-26 16:36:44 +00:00
return false;
2015-05-29 16:03:08 +00:00
// 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<int> segmentIds = Enumerable.Empty<int>();
// 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)
2015-05-26 16:36:44 +00:00
return false;
2015-05-29 16:03:08 +00:00
// Split segment
var newNode = map.RoadNetwork.SplitArticulationSegment(other, inters.Value);
segment.Terminal2Pos = inters.Value;
segment.Terminal2 = newNode;
return true;
2015-05-26 16:36:44 +00:00
}
2015-05-29 16:03:08 +00:00
2015-05-29 16:10:27 +00:00
// 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)
2015-05-29 16:03:08 +00:00
{
2015-05-29 16:10:27 +00:00
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;
2015-05-29 16:03:08 +00:00
}
2015-05-29 16:10:27 +00:00
segment.Terminal2Pos = other.Terminal2.Position;
segment.Terminal2 = other.Terminal2;
return true;
}
2015-05-29 16:03:08 +00:00
//// 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 /
//}
}
2015-05-26 16:36:44 +00:00
return true;
}
}
}