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

148 lines
5.0 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;
using UnityEngine;
2015-05-26 16:36:44 +00:00
namespace TransportGame.Generator
{
public class RoadGenerator
{
2015-05-26 16:36:44 +00:00
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)
{
2015-05-26 16:36:44 +00:00
this.map = map;
map.RoadNetwork = new RoadNetwork();
Queue<RoadNode> queue = new Queue<RoadNode>();
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<RoadNode> 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;
}
}
}