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

219 lines
7.8 KiB
C#

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 (!lotTree.Boundary.Contains(pos))
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)
{
int sides = random.Next(3, 8); // Number of sides
float angle = 2 * (float)Math.PI / sides; // Angle between sides
float radius = random.NextSingle(lot.Size * 0.25f, lot.Size * 0.75f) / 2; // Length of a side
Vector2 current = lot.Position + new Vector2(random.NextSingle(-lot.Size / 4, lot.Size / 4), random.NextSingle(-lot.Size / 4, lot.Size / 4));
Vector2 dir = new Vector2(random.NextSingle(), random.NextSingle()).Normalized * radius;
List<Vector2> points = new List<Vector2>();
for (int i = 0; i < sides; i++)
{
points.Add(current);
current = current + dir;
dir = dir.Rotate(angle);
}
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(0, ConfigManager.Buildgen.MaxBuildingHeight);
}
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();
}
}
}