Initial attempt at road generation
This commit is contained in:
parent
b6b2dce32e
commit
22f1905b1b
@ -32,9 +32,10 @@ namespace TransportGame.Generator
|
|||||||
populationGen.Generate(map);
|
populationGen.Generate(map);
|
||||||
|
|
||||||
// Generate roads
|
// Generate roads
|
||||||
// TODO: Generate roads
|
RoadGenerator roadGenerator = new RoadGenerator();
|
||||||
|
roadGenerator.Generate(map);
|
||||||
|
|
||||||
Logger.DumpMap(map, "withpop.map");
|
Logger.DumpMap(map, "withroads.map");
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return map;
|
return map;
|
||||||
|
@ -3,17 +3,145 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using TransportGame.Model;
|
using TransportGame.Model;
|
||||||
|
using TransportGame.Model.Road;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Assets.Scripts.Generator
|
namespace TransportGame.Generator
|
||||||
{
|
{
|
||||||
public class RoadGenerator
|
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 RoadGenerator()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Generate(Map map)
|
public void Generate(Map map)
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,16 @@ namespace TransportGame.Model.Road
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the road network
|
||||||
|
/// </summary>
|
||||||
|
public RoadNetwork()
|
||||||
|
{
|
||||||
|
Nodes = new Dictionary<int, RoadNode>();
|
||||||
|
ArticulationSegments = new Dictionary<int,RoadSegment>();
|
||||||
|
IntersectionSegments = new Dictionary<int,RoadSegment>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a node and returns it
|
/// Creates a node and returns it
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -13,7 +13,8 @@ namespace TransportGame.MapViewer
|
|||||||
None = 0,
|
None = 0,
|
||||||
Elevation = 1,
|
Elevation = 1,
|
||||||
Population = 2,
|
Population = 2,
|
||||||
All = Elevation | Population
|
RoadArticulations = 3,
|
||||||
|
All = Elevation | Population | RoadArticulations
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -57,9 +58,13 @@ namespace TransportGame.MapViewer
|
|||||||
// Create texture on which to draw
|
// Create texture on which to draw
|
||||||
Bitmap24 bitmap = new Bitmap24(Convert.ToInt32(map.Width * Scale), Convert.ToInt32(map.Height * Scale));
|
Bitmap24 bitmap = new Bitmap24(Convert.ToInt32(map.Width * Scale), Convert.ToInt32(map.Height * Scale));
|
||||||
|
|
||||||
// First layer - cells
|
// Elevation, population
|
||||||
DrawPixels(bitmap, map, (layers & Layers.Elevation) > 0, (layers & Layers.Population) > 0);
|
DrawPixels(bitmap, map, (layers & Layers.Elevation) > 0, (layers & Layers.Population) > 0);
|
||||||
|
|
||||||
|
// Roads
|
||||||
|
if ((layers & Layers.RoadArticulations) > 0)
|
||||||
|
DrawRoads(bitmap, map);
|
||||||
|
|
||||||
// Done
|
// Done
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
@ -83,6 +88,12 @@ namespace TransportGame.MapViewer
|
|||||||
int mapX = Convert.ToInt32(x / Scale);
|
int mapX = Convert.ToInt32(x / Scale);
|
||||||
int mapY = Convert.ToInt32(y / Scale);
|
int mapY = Convert.ToInt32(y / Scale);
|
||||||
|
|
||||||
|
if (mapX >= map.Width)
|
||||||
|
mapX = map.Width - 1;
|
||||||
|
|
||||||
|
if (mapY >= map.Height)
|
||||||
|
mapY = map.Height - 1;
|
||||||
|
|
||||||
// Draw water
|
// Draw water
|
||||||
if (map.IsWater(mapX, mapY))
|
if (map.IsWater(mapX, mapY))
|
||||||
bitmap[x, y] = WaterColor;
|
bitmap[x, y] = WaterColor;
|
||||||
@ -108,5 +119,18 @@ namespace TransportGame.MapViewer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawRoads(Bitmap24 bitmap, Map map)
|
||||||
|
{
|
||||||
|
// Draw road segments
|
||||||
|
foreach (var pair in map.RoadNetwork.ArticulationSegments)
|
||||||
|
{
|
||||||
|
bitmap.DrawLine(Convert.ToInt32(pair.Value.Terminal1.X * Scale),
|
||||||
|
Convert.ToInt32(pair.Value.Terminal1.Y * Scale),
|
||||||
|
Convert.ToInt32(pair.Value.Terminal2.X * Scale),
|
||||||
|
Convert.ToInt32(pair.Value.Terminal2.Y * Scale),
|
||||||
|
Colors.Black);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
<CheckBox IsChecked="{Binding LayersAll}">(All)</CheckBox>
|
<CheckBox IsChecked="{Binding LayersAll}">(All)</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding LayerElevation}">Elevation</CheckBox>
|
<CheckBox IsChecked="{Binding LayerElevation}">Elevation</CheckBox>
|
||||||
<CheckBox IsChecked="{Binding LayerPopulation}">Population</CheckBox>
|
<CheckBox IsChecked="{Binding LayerPopulation}">Population</CheckBox>
|
||||||
|
<CheckBox IsChecked="{Binding LayerRoadArticulations}">Roads (articulation map)</CheckBox>
|
||||||
|
|
||||||
<Button Name="buttonRender" Grid.Column="1" Content="Render" Click="buttonRender_Click" />
|
<Button Name="buttonRender" Grid.Column="1" Content="Render" Click="buttonRender_Click" />
|
||||||
|
|
||||||
|
@ -109,10 +109,10 @@ namespace TransportGame.MapViewer
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (LayerElevation && LayerPopulation)
|
if (LayerElevation && LayerPopulation && LayerRoadArticulations)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
if (!LayerElevation && !LayerPopulation)
|
if (!LayerElevation && !LayerPopulation && !LayerRoadArticulations)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -171,6 +171,28 @@ namespace TransportGame.MapViewer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the population layer flag
|
||||||
|
/// </summary>
|
||||||
|
public bool LayerRoadArticulations
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _layers.HasFlag(MapRenderer.Layers.RoadArticulations);
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value) _layers |= MapRenderer.Layers.RoadArticulations;
|
||||||
|
else _layers &= ~MapRenderer.Layers.RoadArticulations;
|
||||||
|
|
||||||
|
if (PropertyChanged != null)
|
||||||
|
{
|
||||||
|
PropertyChanged(this, new PropertyChangedEventArgs("LayerRoadArticulations"));
|
||||||
|
PropertyChanged(this, new PropertyChangedEventArgs("LayersAll"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -68,5 +68,35 @@ namespace TransportGame.MapViewer.Model
|
|||||||
|
|
||||||
return 3 * (x + y * Width);
|
return 3 * (x + y * Width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DrawLine(int x0, int y0, int x1, int y1, Color color)
|
||||||
|
{
|
||||||
|
int dx = Math.Abs(x1 - x0);
|
||||||
|
int sx = x0 < x1 ? 1 : -1;
|
||||||
|
|
||||||
|
int dy = Math.Abs(y1 - y0);
|
||||||
|
int sy = y0 < y1 ? 1 : -1;
|
||||||
|
|
||||||
|
int err = (dx > dy ? dx : -dy) / 2, e2;
|
||||||
|
|
||||||
|
for (; ; )
|
||||||
|
{
|
||||||
|
this[x0, y0] = color;
|
||||||
|
if (x0 == x1 && y0 == y1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
e2 = err;
|
||||||
|
if (e2 > -dx)
|
||||||
|
{
|
||||||
|
err -= dy;
|
||||||
|
x0 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dy)
|
||||||
|
{
|
||||||
|
err += dx;
|
||||||
|
y0 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,20 @@ namespace TransportGame.MapViewer.Storage
|
|||||||
{
|
{
|
||||||
public static Map Read(string file)
|
public static Map Read(string file)
|
||||||
{
|
{
|
||||||
return SerializationHelper.DeserializeXml<Map>(file);
|
Map map = SerializationHelper.DeserializeXml<Map>(file);
|
||||||
|
|
||||||
|
// Fix road network issue
|
||||||
|
foreach (var pair in map.RoadNetwork.ArticulationSegments)
|
||||||
|
pair.Value.ParentNetwork = map.RoadNetwork;
|
||||||
|
|
||||||
|
foreach (var pair in map.RoadNetwork.IntersectionSegments)
|
||||||
|
pair.Value.ParentNetwork = map.RoadNetwork;
|
||||||
|
|
||||||
|
foreach (var pair in map.RoadNetwork.Nodes)
|
||||||
|
pair.Value.ParentNetwork = map.RoadNetwork;
|
||||||
|
|
||||||
|
// Done
|
||||||
|
return map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user