Initial attempt at road generation

This commit is contained in:
Tiberiu Chibici 2015-05-26 19:36:44 +03:00
parent b6b2dce32e
commit 22f1905b1b
10 changed files with 239 additions and 10 deletions

View File

@ -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;

View File

@ -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;
} }
} }
} }

View File

@ -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>

View File

@ -23,7 +23,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = Assembly-CSharp.csproj StartupItem = Assembly-CSharp.csproj
Policies = $0 Policies = $0
$0.TextStylePolicy = $1 $0.TextStylePolicy = $1

View File

@ -29,7 +29,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = Assembly-CSharp.csproj StartupItem = Assembly-CSharp.csproj
Policies = $0 Policies = $0
$0.TextStylePolicy = $1 $0.TextStylePolicy = $1

View File

@ -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);
}
}
} }
} }

View File

@ -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" />

View File

@ -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>

View File

@ -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;
}
}
}
} }
} }

View File

@ -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;
} }
} }
} }