Refactoring, optimizations
This commit is contained in:
235
Game/Assets/Scripts/Business/Generator/BuildingGenerator.cs
Normal file
235
Game/Assets/Scripts/Business/Generator/BuildingGenerator.cs
Normal file
@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Business;
|
||||
using TransportGame.Model;
|
||||
using TransportGame.Model.Road;
|
||||
using TransportGame.Utils;
|
||||
|
||||
namespace TransportGame.Generator
|
||||
{
|
||||
public class BuildingGenerator
|
||||
{
|
||||
Random random = new Random();
|
||||
|
||||
private float LotSquareMinSize { get { return ConfigManager.Buildgen.LotSquareMinSize; } }
|
||||
private float LotSquareMaxSize { get { return ConfigManager.Buildgen.LotSquareMaxSize; } }
|
||||
private float LotSpacing { get { return ConfigManager.Buildgen.LotSpacing; } }
|
||||
|
||||
private Map map;
|
||||
QuadTree<RoadNode> nodeTree;
|
||||
QuadTree<BuildingLot> lotTree;
|
||||
|
||||
private void AllocateLots()
|
||||
{
|
||||
// Generate lots for every segment
|
||||
foreach (var pair in map.RoadNetwork.ArticulationSegments)
|
||||
{
|
||||
bool didSomething;
|
||||
var segment = pair.Value;
|
||||
var dir = segment.Direction;
|
||||
var perp = dir.RotateDeg(90);
|
||||
|
||||
float width0 = ConfigManager.Roadgen.SidewalkWidth + ConfigManager.Roadgen.LaneWidth * segment.LanesTo1;
|
||||
float width1 = ConfigManager.Roadgen.SidewalkWidth + ConfigManager.Roadgen.LaneWidth * segment.LanesTo2;
|
||||
|
||||
Vector2 start = segment.Terminal1.Position;
|
||||
Vector2 end = segment.Terminal2.Position;
|
||||
Vector2 posL = start, posR = start, nposL, nposR;
|
||||
int attempts = 0;
|
||||
|
||||
do
|
||||
{
|
||||
didSomething = false;
|
||||
float sizeL = random.NextSingle(LotSquareMinSize, LotSquareMaxSize), sizeR = random.NextSingle(LotSquareMinSize, LotSquareMaxSize);
|
||||
nposL = posL + dir * sizeL;
|
||||
nposR = posR + dir * sizeR;
|
||||
|
||||
// Left side
|
||||
if ((posL - end).LengthSq >= sizeL * sizeL)
|
||||
{
|
||||
didSomething = true;
|
||||
|
||||
// Build lot squares
|
||||
Vector2[] left = new Vector2[4];
|
||||
left[0] = posL + perp * (width0 + LotSpacing + sizeL);
|
||||
left[1] = posL + perp * (width0 + LotSpacing);
|
||||
left[2] = nposL + perp * (width0 + LotSpacing);
|
||||
left[3] = nposL + perp * (width0 + LotSpacing + sizeL);
|
||||
|
||||
BuildingLot lot = new BuildingLot(sizeL, left);
|
||||
|
||||
if (CanAllocate(posL, lot))
|
||||
lotTree.Add(lot);
|
||||
|
||||
// Advance
|
||||
posL = nposL;
|
||||
}
|
||||
|
||||
// Right side
|
||||
if ((posR - end).LengthSq >= sizeR * sizeR)
|
||||
{
|
||||
didSomething = true;
|
||||
|
||||
// Build lot squares
|
||||
Vector2[] right = new Vector2[4];
|
||||
right[0] = posR - perp * (width1 + LotSpacing + sizeR);
|
||||
right[1] = posR - perp * (width1 + LotSpacing);
|
||||
right[2] = nposR - perp * (width1 + LotSpacing);
|
||||
right[3] = nposR - perp * (width1 + LotSpacing + sizeR);
|
||||
|
||||
BuildingLot lot = new BuildingLot(sizeR, right);
|
||||
|
||||
if (CanAllocate(posL, lot))
|
||||
lotTree.Add(lot);
|
||||
|
||||
// Advance
|
||||
posR = nposR;
|
||||
}
|
||||
|
||||
if (!didSomething)
|
||||
attempts++;
|
||||
|
||||
} while (attempts < ConfigManager.Buildgen.MaxLotAttempts);
|
||||
}
|
||||
|
||||
// Done
|
||||
map.BuildingLots = lotTree.ToList();
|
||||
}
|
||||
|
||||
private bool CanAllocate(Vector2 pos, BuildingLot lot0)
|
||||
{
|
||||
if (lot0.Points.Any(p => !lotTree.Boundary.Contains(p)))
|
||||
return false;
|
||||
|
||||
// Test other lots
|
||||
Rectangle lotArea = new Rectangle(
|
||||
pos.X - 2f * LotSquareMaxSize,
|
||||
pos.Y - 2f * LotSquareMaxSize,
|
||||
pos.X + 2f * LotSquareMaxSize,
|
||||
pos.Y + 2f * LotSquareMaxSize);
|
||||
|
||||
foreach (var lot in lotTree.Query(lotArea))
|
||||
{
|
||||
if (BuildingLot.DoesIntersect(lot0, lot))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test nearby roads
|
||||
Rectangle roadArea = new Rectangle(
|
||||
pos.X - 1.1f * ConfigManager.Roadgen.HighwaySegmentLength,
|
||||
pos.Y - 1.1f * ConfigManager.Roadgen.HighwaySegmentLength,
|
||||
pos.X + 1.1f * ConfigManager.Roadgen.HighwaySegmentLength,
|
||||
pos.Y + 1.1f * ConfigManager.Roadgen.HighwaySegmentLength);
|
||||
|
||||
foreach (var node in nodeTree.Query(roadArea))
|
||||
{
|
||||
foreach (var segment in node.ArticulationSegments)
|
||||
{
|
||||
if (BuildingLot.DoesIntersect(lot0, segment.AsLineSegment()))
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void GenerateBuildings()
|
||||
{
|
||||
foreach (var lot in map.BuildingLots)
|
||||
map.Buildings.Add(GenerateBuilding(lot));
|
||||
}
|
||||
|
||||
Polygon GeneratePrimitivePolygon(BuildingLot lot)
|
||||
{
|
||||
List<Vector2> points = new List<Vector2>();
|
||||
int sides = random.Next(4, 7); // Number of sides
|
||||
float angle = 2 * (float)Math.PI / sides; // Angle between sides
|
||||
bool ok;
|
||||
|
||||
do
|
||||
{
|
||||
// Reset
|
||||
ok = true;
|
||||
points.Clear();
|
||||
|
||||
// Generate radius, start position and direction
|
||||
float radius = random.NextSingle(lot.Size * 0.25f, lot.Size * 0.5f); // Length of a side
|
||||
Vector2 current = lot.Position + new Vector2(random.NextSingle(-lot.Size / 2, lot.Size / 2), random.NextSingle(-lot.Size / 2, lot.Size / 2));
|
||||
Vector2 dir = new Vector2(random.NextSingle(), random.NextSingle()).Normalized * radius;
|
||||
|
||||
// Generate polygon
|
||||
for (int i = 0; i < sides; i++)
|
||||
{
|
||||
points.Add(current);
|
||||
|
||||
// Make sure every point is inside
|
||||
if (!lot.IsInside(current))
|
||||
{
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
current = current + dir;
|
||||
dir = dir.Rotate(angle);
|
||||
}
|
||||
} while (!ok);
|
||||
|
||||
return new Polygon(points);
|
||||
}
|
||||
|
||||
private Building GenerateBuilding(BuildingLot lot)
|
||||
{
|
||||
Building b = new Building();
|
||||
|
||||
int levelCount = random.Next(1, ConfigManager.Buildgen.MaxBuildingLevels);
|
||||
b.LevelHeights = new float[levelCount];
|
||||
b.Polygons = new Polygon[levelCount][];
|
||||
|
||||
for (int i = levelCount - 1; i >= 0; --i)
|
||||
{
|
||||
List<Polygon> polys = new List<Polygon>();
|
||||
|
||||
for (int j = 0; j < 1 + random.Next(ConfigManager.Buildgen.MaxPolygonsPerLevel); j++)
|
||||
polys.Add(GeneratePrimitivePolygon(lot));
|
||||
|
||||
if (i + 1 < levelCount)
|
||||
{
|
||||
polys.AddRange(b.Polygons[i + 1]);
|
||||
b.LevelHeights[i] = random.NextSingle(0, b.LevelHeights[i + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
b.LevelHeights[i] = random.NextSingle(ConfigManager.Buildgen.MinBuildingHeight, ConfigManager.Buildgen.MaxBuildingHeight) * map.GetPopulation(lot.Position);
|
||||
}
|
||||
|
||||
for (int j = 0; j < random.Next(ConfigManager.Buildgen.MaxPolygonsPerLevel); j++)
|
||||
polys.Add(GeneratePrimitivePolygon(lot));
|
||||
|
||||
b.Polygons[i] = polys.ToArray();
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
public void Generate(Map map)
|
||||
{
|
||||
this.map = map;
|
||||
map.Buildings = new List<Building>();
|
||||
|
||||
// Construct node tree
|
||||
nodeTree = new QuadTree<RoadNode>(0, 0, map.Width, map.Height);
|
||||
foreach (var pair in map.RoadNetwork.Nodes)
|
||||
nodeTree.Add(pair.Value);
|
||||
|
||||
lotTree = new QuadTree<BuildingLot>(0, 0, map.Width, map.Height);
|
||||
|
||||
// Allocate lots
|
||||
AllocateLots();
|
||||
|
||||
GenerateBuildings();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 387d471e5e9dff54b9cb832e01b969ec
|
||||
timeCreated: 1433340433
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
46
Game/Assets/Scripts/Business/Generator/CityGenerator.cs
Normal file
46
Game/Assets/Scripts/Business/Generator/CityGenerator.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Generator;
|
||||
using TransportGame.Model;
|
||||
using TransportGame.Utils;
|
||||
|
||||
namespace TransportGame.Generator
|
||||
{
|
||||
/// <summary>
|
||||
/// Complete city generator. Generates everything, from terrain to buildings
|
||||
/// </summary>
|
||||
public class CityGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a city
|
||||
/// </summary>
|
||||
/// <param name="width">Width</param>
|
||||
/// <param name="height">Height</param>
|
||||
/// <returns>City</returns>
|
||||
public Map Generate(int width, int height)
|
||||
{
|
||||
Map map;
|
||||
|
||||
// Generate terrain
|
||||
TerrainGenerator terrainGen = new TerrainGenerator();
|
||||
map = terrainGen.Generate(width, height);
|
||||
|
||||
// Generate population map
|
||||
PopulationCentersGenerator populationGen = new PopulationCentersGenerator();
|
||||
populationGen.Generate(map);
|
||||
|
||||
// Generate roads
|
||||
RoadGenerator roadGenerator = new RoadGenerator();
|
||||
roadGenerator.Generate(map);
|
||||
|
||||
// Generate buildings
|
||||
BuildingGenerator buildingGenerator = new BuildingGenerator();
|
||||
buildingGenerator.Generate(map);
|
||||
|
||||
// Done
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
12
Game/Assets/Scripts/Business/Generator/CityGenerator.cs.meta
Normal file
12
Game/Assets/Scripts/Business/Generator/CityGenerator.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 723d5ad7f5932f649971961655fff523
|
||||
timeCreated: 1432200350
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Model;
|
||||
using TransportGame.Noise;
|
||||
|
||||
namespace TransportGame.Generator
|
||||
{
|
||||
public class PopulationCentersGenerator
|
||||
{
|
||||
System.Random random = new System.Random();
|
||||
|
||||
public void Generate(Map map)
|
||||
{
|
||||
// Generate range
|
||||
float mp = (float)(map.Width * map.Height) / (1024 * 1024); // For 4k x 4k range should be around 900
|
||||
map.PopulationCenterRange = mp * 31f + 496.66f; // For 2k x 2k range should be around 600
|
||||
|
||||
// Generate a number of points
|
||||
int maxPoints = 16 * (int) Math.Sqrt(mp);
|
||||
int points = random.Next(maxPoints / 2, maxPoints);
|
||||
|
||||
for (int i = 0; i < points; ++i)
|
||||
{
|
||||
int px, py;
|
||||
|
||||
do
|
||||
{
|
||||
px = random.Next(map.Width / 8, 7 * map.Width / 8);
|
||||
py = random.Next(map.Height / 8, 7 * map.Height / 8);
|
||||
}
|
||||
while (map.IsWater(px, py));
|
||||
|
||||
map.PopulationCenters.Add(new Vector2(px, py));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 186756c7c690a3d4c8e633e3007523e0
|
||||
timeCreated: 1432200350
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
316
Game/Assets/Scripts/Business/Generator/RoadGenerator.cs
Normal file
316
Game/Assets/Scripts/Business/Generator/RoadGenerator.cs
Normal file
@ -0,0 +1,316 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Business;
|
||||
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<RoadNode> qtree;
|
||||
List<RoadGeneratorSegment> queue;
|
||||
|
||||
System.Random random = new System.Random();
|
||||
Map map;
|
||||
|
||||
private float HighwaySegmentLength { get { return ConfigManager.Roadgen.HighwaySegmentLength; } }
|
||||
private float DefaultBranchPopulationTreshold { get { return ConfigManager.Roadgen.DefaultBranchPopulationTreshold; } }
|
||||
private float DefaultBranchProbability { get { return ConfigManager.Roadgen.DefaultBranchProbability; } }
|
||||
private float DefaultSegmentLength { get { return ConfigManager.Roadgen.DefaultSegmentLength; } }
|
||||
private float SteepnessLimit { get { return ConfigManager.Roadgen.SteepnessLimit; } }
|
||||
private float SlopeLimit { get { return ConfigManager.Roadgen.SlopeLimit; } }
|
||||
private float RoadSegmentAngleLimit { get { return ConfigManager.Roadgen.RoadSegmentAngleLimit; } }
|
||||
private float RoadSnapDistance { get { return ConfigManager.Roadgen.RoadSnapDistance; } }
|
||||
private float MinNodeDistance { get { return ConfigManager.Roadgen.MinNodeDistance; } }
|
||||
private int MaximumRandomStraightAngle { get { return ConfigManager.Roadgen.MaximumRandomStraightAngle; } }
|
||||
private int MaximumBranchAngleVariation { get { return ConfigManager.Roadgen.MaximumBranchAngleVariation; } }
|
||||
private float HighwayBranchPopulationTreshold { get { return ConfigManager.Roadgen.HighwayBranchPopulationTreshold; } }
|
||||
private float HighwayBranchProbability { get { return ConfigManager.Roadgen.HighwayBranchProbability; } }
|
||||
private int HighwayBranchDelay { get { return ConfigManager.Roadgen.HighwayBranchDelay; } }
|
||||
private int MaximumIntersectingRoads { get { return ConfigManager.Roadgen.MaximumIntersectingRoads; } }
|
||||
|
||||
public RoadGenerator()
|
||||
{
|
||||
}
|
||||
|
||||
public void Initialize(Map map)
|
||||
{
|
||||
this.map = map;
|
||||
map.RoadNetwork = new RoadNetwork();
|
||||
qtree = new QuadTree<RoadNode>(0, 0, map.Width, map.Height);
|
||||
queue = new List<RoadGeneratorSegment>();
|
||||
|
||||
// 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));
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
public void Step()
|
||||
{
|
||||
var segment = queue.OrderBy(x => x.Time).First();
|
||||
queue.Remove(segment);
|
||||
|
||||
// Check local constraints
|
||||
if (CheckLocalConstraints(segment))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<RoadGeneratorSegment> GlobalGoals(RoadSegment segment)
|
||||
{
|
||||
Vector2 prevPos = segment.Terminal2.Position;
|
||||
Vector2 dir = segment.Direction;
|
||||
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)
|
||||
{
|
||||
Vector2 randomStraight = prevPos + HighwaySegmentLength * dir.RotateDeg(random.Next(-MaximumRandomStraightAngle, MaximumRandomStraightAngle));
|
||||
float randomPopulation = map.GetPopulation(randomStraight);
|
||||
|
||||
if (randomPopulation > straightPopulation)
|
||||
yield return new RoadGeneratorSegment(segment.Terminal2, randomStraight, highway);
|
||||
else
|
||||
yield return new RoadGeneratorSegment(segment.Terminal2, straight, highway);
|
||||
|
||||
// Branch highway
|
||||
if (Math.Max(straightPopulation, randomPopulation) > HighwayBranchPopulationTreshold)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
else if (random.NextSingle() < straightPopulation)
|
||||
yield return new RoadGeneratorSegment(segment.Terminal2, straight, false);
|
||||
|
||||
// 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)
|
||||
{
|
||||
Vector2 rightBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation));
|
||||
yield return new RoadGeneratorSegment(segment.Terminal2, rightBranch, false, (highway) ? HighwayBranchDelay : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckLocalConstraints(RoadGeneratorSegment segment)
|
||||
{
|
||||
// Constraint #1: check for obstacles
|
||||
if (IsObstacle(segment.Terminal2Pos))
|
||||
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;
|
||||
|
||||
if (Math.Asin(sinSlope) > SlopeLimit)
|
||||
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);
|
||||
|
||||
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 =>
|
||||
LineSegment.Distance(map.RoadNetwork.ArticulationSegments[id].AsLineSegment(), segment.Terminal2Pos));
|
||||
|
||||
foreach (var segmentId in segmentIds)
|
||||
{
|
||||
var other = map.RoadNetwork.ArticulationSegments[segmentId];
|
||||
var line1 = new LineSegment(segment.Terminal1.Position, segment.Terminal2Pos);
|
||||
var line2 = other.AsLineSegment();
|
||||
|
||||
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)
|
||||
return false;
|
||||
|
||||
// Split segment
|
||||
var newNode = map.RoadNetwork.SplitArticulationSegment(other, inters.Value);
|
||||
segment.Terminal2Pos = inters.Value;
|
||||
segment.Terminal2 = newNode;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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.Direction, intersSeg.Direction);
|
||||
if (Math.Abs(Math.Acos(cos)) < RoadSegmentAngleLimit)
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
12
Game/Assets/Scripts/Business/Generator/RoadGenerator.cs.meta
Normal file
12
Game/Assets/Scripts/Business/Generator/RoadGenerator.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca14a99c3a7bbee468354076f2d6f486
|
||||
timeCreated: 1431067754
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
71
Game/Assets/Scripts/Business/Generator/TerrainGenerator.cs
Normal file
71
Game/Assets/Scripts/Business/Generator/TerrainGenerator.cs
Normal file
@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using TransportGame.Business;
|
||||
using TransportGame.Model;
|
||||
using TransportGame.Noise;
|
||||
using TransportGame.Utils;
|
||||
using TransportGame.Utils.Algorithms;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TransportGame.Generator
|
||||
{
|
||||
public class TerrainGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying noise generator
|
||||
/// </summary>
|
||||
public NoiseGenerator Noise { get; set; }
|
||||
|
||||
private System.Random random = new System.Random();
|
||||
|
||||
public TerrainGenerator()
|
||||
{
|
||||
Noise = new PerlinNoiseGenerator();
|
||||
|
||||
if (ConfigManager.Tergen == null)
|
||||
throw new Exception("Not initialized!");
|
||||
|
||||
Noise.Octaves = ConfigManager.Tergen.NoiseOctaves;
|
||||
Noise.NonLinearPower = ConfigManager.Tergen.NoiseNonLinearPower;
|
||||
Noise.Scale = ConfigManager.Tergen.ElevationScale;
|
||||
}
|
||||
|
||||
public Map Generate(int width, int height)
|
||||
{
|
||||
// Create map
|
||||
Map map = new Map(width, height);
|
||||
|
||||
// Pick a random biome
|
||||
map.Biome = PickBiome();
|
||||
Logger.Info("Picked biome: {0}", map.Biome.Name);
|
||||
|
||||
// Generate elevation
|
||||
GenerateElevation(map);
|
||||
|
||||
// Generate water level
|
||||
float waterAmount = random.NextSingle(map.Biome.Moisture.Minimum, map.Biome.Moisture.Maximum);
|
||||
map.WaterLevel = Mathf.Pow(waterAmount, ConfigManager.Tergen.WaterNonLinearPower) * map.Biome.Height;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private Biome PickBiome()
|
||||
{
|
||||
int biomeCount = BiomeManager.Biomes.Count();
|
||||
int biome = random.Next(biomeCount);
|
||||
|
||||
return BiomeManager.Biomes.ElementAt(biome);
|
||||
}
|
||||
|
||||
private void GenerateElevation(Map map)
|
||||
{
|
||||
for (int x = 0; x < map.Width; ++x)
|
||||
for (int y = 0; y < map.Height; ++y)
|
||||
map.Heightmap[x, y] = Noise.Generate(x, y, 0, 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4042531a480f4149bdda36c67975b0c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
Reference in New Issue
Block a user