Generated buildings
This commit is contained in:
@ -11,8 +11,11 @@ namespace TransportGame.Generator
|
||||
{
|
||||
public class BuildingGenerator
|
||||
{
|
||||
private const float LotSquareSize = 1f;
|
||||
private const float LotSpacing = 0.1f;
|
||||
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;
|
||||
@ -20,43 +23,75 @@ namespace TransportGame.Generator
|
||||
|
||||
private void AllocateLots()
|
||||
{
|
||||
float advance = LotSquareSize + LotSpacing;
|
||||
|
||||
// Generate lots for every segment
|
||||
foreach (var pair in map.RoadNetwork.ArticulationSegments)
|
||||
{
|
||||
var seg = pair.Value;
|
||||
var dir = seg.Direction;
|
||||
bool didSomething;
|
||||
var segment = pair.Value;
|
||||
var dir = segment.Direction;
|
||||
var perp = dir.RotateDeg(90);
|
||||
|
||||
float width0 = ConfigManager.Roadgen.SidewalkWidth + ConfigManager.Roadgen.LaneWidth * seg.LanesTo1;
|
||||
float width1 = ConfigManager.Roadgen.SidewalkWidth + ConfigManager.Roadgen.LaneWidth * seg.LanesTo2;
|
||||
float width0 = ConfigManager.Roadgen.SidewalkWidth + ConfigManager.Roadgen.LaneWidth * segment.LanesTo1;
|
||||
float width1 = ConfigManager.Roadgen.SidewalkWidth + ConfigManager.Roadgen.LaneWidth * segment.LanesTo2;
|
||||
|
||||
for (Vector2 pos = seg.Terminal1.Position; (pos - seg.Terminal2.Position).LengthSq > advance * advance; pos += dir * advance)
|
||||
Vector2 start = segment.Terminal1.Position;
|
||||
Vector2 end = segment.Terminal2.Position;
|
||||
Vector2 posL = start, posR = start, nposL, nposR;
|
||||
int attempts = 0;
|
||||
|
||||
do
|
||||
{
|
||||
// Build lot squares
|
||||
Vector2[] left = new Vector2[4];
|
||||
left[0] = pos + perp * (width0 + LotSpacing + LotSquareSize);
|
||||
left[1] = pos + perp * (width0 + LotSpacing);
|
||||
left[2] = pos + dir * LotSquareSize + perp * (width0 + LotSpacing);
|
||||
left[3] = pos + dir * LotSquareSize + perp * (width0 + LotSpacing + LotSquareSize);
|
||||
didSomething = false;
|
||||
float sizeL = random.NextSingle(LotSquareMinSize, LotSquareMaxSize), sizeR = random.NextSingle(LotSquareMinSize, LotSquareMaxSize);
|
||||
nposL = posL + dir * sizeL;
|
||||
nposR = posR + dir * sizeR;
|
||||
|
||||
Vector2[] right = new Vector2[4];
|
||||
right[0] = pos - perp * (width0 + LotSpacing + LotSquareSize);
|
||||
right[1] = pos - perp * (width0 + LotSpacing);
|
||||
right[2] = pos + dir * LotSquareSize - perp * (width0 + LotSpacing);
|
||||
right[3] = pos + dir * LotSquareSize - perp * (width0 + LotSpacing + LotSquareSize);
|
||||
// Left side
|
||||
if ((posL - end).LengthSq >= sizeL * sizeL)
|
||||
{
|
||||
didSomething = true;
|
||||
|
||||
BuildingLot lot0 = new BuildingLot(left);
|
||||
BuildingLot lot1 = new BuildingLot(right);
|
||||
// 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);
|
||||
|
||||
// Test for intersections
|
||||
if (CanAllocate(pos, lot0))
|
||||
lotTree.Add(lot0);
|
||||
BuildingLot lot = new BuildingLot(sizeL, left);
|
||||
|
||||
if (CanAllocate(pos, lot1))
|
||||
lotTree.Add(lot1);
|
||||
}
|
||||
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
|
||||
@ -65,26 +100,34 @@ namespace TransportGame.Generator
|
||||
|
||||
private bool CanAllocate(Vector2 pos, BuildingLot lot0)
|
||||
{
|
||||
if (!lotTree.Boundary.Contains(pos))
|
||||
return false;
|
||||
|
||||
// Test other lots
|
||||
Rectangle lotArea = new Rectangle(pos.X - 2 * LotSquareSize, pos.Y - 2 * LotSquareSize, pos.X + 2 * LotSquareSize, pos.Y + 2 * LotSquareSize);
|
||||
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.Intersect(lot0, lot))
|
||||
if (BuildingLot.DoesIntersect(lot0, lot))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Test nearby roads
|
||||
Rectangle roadArea = new Rectangle(
|
||||
pos.X - 2 * ConfigManager.Roadgen.HighwaySegmentLength,
|
||||
pos.Y - 2 * ConfigManager.Roadgen.HighwaySegmentLength,
|
||||
pos.X + 2 * ConfigManager.Roadgen.HighwaySegmentLength,
|
||||
pos.Y + 2 * ConfigManager.Roadgen.HighwaySegmentLength);
|
||||
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.Intersect(lot0, segment.AsLineSegment()))
|
||||
if (BuildingLot.DoesIntersect(lot0, segment.AsLineSegment()))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -95,12 +138,69 @@ namespace TransportGame.Generator
|
||||
|
||||
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);
|
||||
|
@ -35,6 +35,10 @@ namespace TransportGame.Generator
|
||||
RoadGenerator roadGenerator = new RoadGenerator();
|
||||
roadGenerator.Generate(map);
|
||||
|
||||
// Generate buildings
|
||||
BuildingGenerator buildingGenerator = new BuildingGenerator();
|
||||
buildingGenerator.Generate(map);
|
||||
|
||||
// Done
|
||||
return map;
|
||||
}
|
||||
|
20
Game/Assets/Scripts/Model/Building.cs
Normal file
20
Game/Assets/Scripts/Model/Building.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace TransportGame.Model
|
||||
{
|
||||
public class Building
|
||||
{
|
||||
/// <summary>
|
||||
/// From lowest to highest level
|
||||
/// </summary>
|
||||
public Polygon[][] Polygons { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// From lowest to highest level
|
||||
/// </summary>
|
||||
public float[] LevelHeights { get; set; }
|
||||
}
|
||||
}
|
12
Game/Assets/Scripts/Model/Building.cs.meta
Normal file
12
Game/Assets/Scripts/Model/Building.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ef412bd6ccb705428e5569cf84b018f
|
||||
timeCreated: 1433489978
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Business;
|
||||
using TransportGame.Utils;
|
||||
|
||||
namespace TransportGame.Model
|
||||
{
|
||||
@ -13,21 +14,54 @@ namespace TransportGame.Model
|
||||
/// </summary>
|
||||
public Vector2[] Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the size of the lot
|
||||
/// </summary>
|
||||
public float Size { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the building lot
|
||||
/// </summary>
|
||||
public BuildingLot()
|
||||
{
|
||||
Points = new Vector2[4];
|
||||
}
|
||||
|
||||
public BuildingLot(params Vector2[] points)
|
||||
/// <summary>
|
||||
/// Initializes lot with given size
|
||||
/// </summary>
|
||||
/// <param name="size">size</param>
|
||||
public BuildingLot(float size)
|
||||
{
|
||||
Points = new Vector2[4];
|
||||
Size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes lot with given points and size
|
||||
/// </summary>
|
||||
/// <param name="size">Size</param>
|
||||
/// <param name="points">Points</param>
|
||||
public BuildingLot(float size, params Vector2[] points)
|
||||
{
|
||||
Points = points;
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public BuildingLot(IEnumerable<Vector2> points)
|
||||
/// <summary>
|
||||
/// Initializes lot with given points and size
|
||||
/// </summary>
|
||||
/// <param name="size">Size</param>
|
||||
/// <param name="points">Points</param>
|
||||
public BuildingLot(float size, IEnumerable<Vector2> points)
|
||||
{
|
||||
Points = points.ToArray();
|
||||
Size = size;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the lot position
|
||||
/// </summary>
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return Points.Aggregate((x, y) => x + y) / Points.Length; }
|
||||
@ -39,9 +73,9 @@ namespace TransportGame.Model
|
||||
/// <param name="a">First lot</param>
|
||||
/// <param name="b">Second lot</param>
|
||||
/// <returns></returns>
|
||||
public static bool Intersect(BuildingLot a, BuildingLot b)
|
||||
public static bool DoesIntersect(BuildingLot a, BuildingLot b)
|
||||
{
|
||||
return (a.Position - b.Position).LengthSq <= ConfigManager.Buildgen.LotSquareSize + ConfigManager.Buildgen.LotSpacing;
|
||||
return Algorithmss.DoPolygonsIntersect(a.Points, b.Points);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -50,7 +84,7 @@ namespace TransportGame.Model
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Intersect(BuildingLot a, LineSegment b)
|
||||
public static bool DoesIntersect(BuildingLot a, LineSegment b)
|
||||
{
|
||||
for (int i = 0; i < a.Points.Length; i++)
|
||||
{
|
||||
|
12
Game/Assets/Scripts/Model/BuildingLot.cs.meta
Normal file
12
Game/Assets/Scripts/Model/BuildingLot.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d4e9031660798e43954c6c698daf966
|
||||
timeCreated: 1433365105
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -8,19 +8,49 @@ namespace TransportGame.Model.Config
|
||||
public class BuildingGeneratorConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Lot size
|
||||
/// Minimum lot size
|
||||
/// </summary>
|
||||
public float LotSquareSize { get; set; }
|
||||
public float LotSquareMinSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum lot size
|
||||
/// </summary>
|
||||
public float LotSquareMaxSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Space between lots
|
||||
/// </summary>
|
||||
public float LotSpacing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of attempts to generate a lot in a specific area
|
||||
/// </summary>
|
||||
public int MaxLotAttempts { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of levels on a building
|
||||
/// </summary>
|
||||
public int MaxBuildingLevels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum height for a building
|
||||
/// </summary>
|
||||
public float MaxBuildingHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of primitive polygons to be generated per level
|
||||
/// </summary>
|
||||
public int MaxPolygonsPerLevel { get; set; }
|
||||
|
||||
public BuildingGeneratorConfig()
|
||||
{
|
||||
LotSquareSize = 1f;
|
||||
LotSquareMinSize = 5f;
|
||||
LotSquareMaxSize = 20f;
|
||||
LotSpacing = 0.1f;
|
||||
MaxLotAttempts = 3;
|
||||
MaxBuildingHeight = 20f;
|
||||
MaxBuildingLevels = 5;
|
||||
MaxPolygonsPerLevel = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67b392479c775904c970253005f9c856
|
||||
timeCreated: 1433365105
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -75,6 +75,9 @@ namespace TransportGame.Model
|
||||
[XmlArrayItem("center")]
|
||||
public List<Vector2> PopulationCenters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the range of one population center (distance how far it influences)
|
||||
/// </summary>
|
||||
public float PopulationCenterRange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -89,6 +92,12 @@ namespace TransportGame.Model
|
||||
[XmlElement("lots")]
|
||||
public List<BuildingLot> BuildingLots { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the buildings
|
||||
/// </summary>
|
||||
[XmlElement("buildings")]
|
||||
public List<Building> Buildings { get; set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
|
247
Game/Assets/Scripts/Model/Polygon.cs
Normal file
247
Game/Assets/Scripts/Model/Polygon.cs
Normal file
@ -0,0 +1,247 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Utils;
|
||||
|
||||
namespace TransportGame.Model
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a polygon
|
||||
/// </summary>
|
||||
public class Polygon : IPositionable
|
||||
{
|
||||
#region Private types
|
||||
|
||||
private struct Edge
|
||||
{
|
||||
public int U, V;
|
||||
|
||||
public Edge(int U, int V)
|
||||
{
|
||||
this.U = U;
|
||||
this.V = V;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the points that define the polygon
|
||||
/// </summary>
|
||||
public Vector2[] Points { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gravitational center
|
||||
/// </summary>
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return Points.Aggregate((x, y) => x + y) / Points.Length; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes polygon
|
||||
/// </summary>
|
||||
public Polygon()
|
||||
{
|
||||
Points = new Vector2[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes polygon using given points
|
||||
/// </summary>
|
||||
/// <param name="points">Points</param>
|
||||
public Polygon(IEnumerable<Vector2> points)
|
||||
{
|
||||
Points = points.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes polygon using given points
|
||||
/// </summary>
|
||||
/// <param name="points">Points</param>
|
||||
public Polygon(params Vector2[] points)
|
||||
{
|
||||
Points = points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares angle between two NORMALIZED vectors.
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
private static float CompareAngle(Vector2 a, Vector2 b)
|
||||
{
|
||||
float sin = Vector2.Cross(a, b);
|
||||
float cos = Vector2.Dot(a, b);
|
||||
|
||||
if (sin >= 0 && cos >= 0)
|
||||
return sin;
|
||||
|
||||
else if (sin >= 0 && cos <= 0)
|
||||
return 1 + Math.Abs(cos);
|
||||
|
||||
else if (sin <= 0 && cos <= 0)
|
||||
return 2 + Math.Abs(sin);
|
||||
|
||||
else return 3 + cos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the union between given polygons
|
||||
/// </summary>
|
||||
/// <param name="polys">Polygons</param>
|
||||
/// <returns>Polygon representing union</returns>
|
||||
public static Polygon Union(params Polygon[] polys)
|
||||
{
|
||||
return Union(polys);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the union between given polygons
|
||||
/// </summary>
|
||||
/// <param name="polys">Polygons</param>
|
||||
/// <returns>Polygon representing union</returns>
|
||||
public static Polygon Union(IEnumerable<Polygon> polys)
|
||||
{
|
||||
List<Vector2> vertices = new List<Vector2>();
|
||||
List<Edge> edges = new List<Edge>();
|
||||
List<Vector2> union = new List<Vector2>();
|
||||
|
||||
foreach (var poly in polys)
|
||||
{
|
||||
// Add all points
|
||||
for (int i = 0; i < poly.Points.Length; i++)
|
||||
{
|
||||
int j = (i + 1) % poly.Points.Length;
|
||||
|
||||
// Get/add first point
|
||||
int indexi = vertices.IndexOf(poly.Points[i]);
|
||||
if (indexi == -1)
|
||||
{
|
||||
vertices.Add(poly.Points[i]);
|
||||
indexi = vertices.Count - 1;
|
||||
}
|
||||
|
||||
// Get/add second point
|
||||
int indexj = vertices.IndexOf(poly.Points[j]);
|
||||
if (indexj == -1)
|
||||
{
|
||||
vertices.Add(poly.Points[j]);
|
||||
indexj = vertices.Count - 1;
|
||||
}
|
||||
|
||||
// Add edge
|
||||
edges.Add(new Edge(indexi, indexj));
|
||||
}
|
||||
}
|
||||
|
||||
// Intersect edges
|
||||
for (int i = 0; i < edges.Count; i++)
|
||||
for (int j = i + 1; j < edges.Count; j++)
|
||||
{
|
||||
LineSegment a = new LineSegment(vertices[edges[i].U], vertices[edges[i].V]);
|
||||
LineSegment b = new LineSegment(vertices[edges[j].U], vertices[edges[j].V]);
|
||||
|
||||
var inters = LineSegment.Intersect(a, b);
|
||||
if (inters.HasValue && inters.Value != a.P0 && inters.Value != a.P1 && inters.Value != b.P0 && inters.Value != b.P1)
|
||||
{
|
||||
vertices.Add(inters.Value);
|
||||
int index = vertices.Count - 1;
|
||||
|
||||
edges.Add(new Edge(index, edges[i].V));
|
||||
edges[i] = new Edge(edges[i].U, index);
|
||||
edges.Add(new Edge(index, edges[j].V));
|
||||
edges[j] = new Edge(edges[j].U, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute union
|
||||
int start = 0;
|
||||
|
||||
// Find starting point
|
||||
for (int i = 0; i < vertices.Count; i++)
|
||||
if (vertices[i].X + vertices[i].Y < vertices[start].X + vertices[start].Y)
|
||||
start = i;
|
||||
|
||||
int v = start, vold = -1;
|
||||
Vector2 prev = vertices[v].Normalized;
|
||||
|
||||
do
|
||||
{
|
||||
union.Add(vertices[v]);
|
||||
|
||||
int newV = -1;
|
||||
float smallestAngle = -1;
|
||||
Vector2 smallestDir = Vector2.Zero;
|
||||
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
if ((edge.U == v || edge.V == v) && edge.V != vold && edge.U != vold)
|
||||
{
|
||||
int otherv = (edge.U == v) ? edge.V : edge.U;
|
||||
Vector2 dir = (vertices[otherv] - vertices[v]).Normalized;
|
||||
|
||||
// Find smallest angle
|
||||
float cmpAngle = CompareAngle(-prev, dir);
|
||||
if (cmpAngle < smallestAngle || smallestAngle < 0)
|
||||
{
|
||||
newV = otherv;
|
||||
smallestAngle = cmpAngle;
|
||||
smallestDir = dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance
|
||||
prev = smallestDir;
|
||||
vold = v;
|
||||
v = newV;
|
||||
|
||||
} while (v != start);
|
||||
|
||||
return new Polygon(union);
|
||||
}
|
||||
|
||||
public static bool DoPolygonsIntersect(Polygon a, Polygon b)
|
||||
{
|
||||
foreach (var poly in new[] { a, b })
|
||||
{
|
||||
for (int i = 0; i < poly.Points.Length; i++)
|
||||
{
|
||||
int j = (i + 1) % poly.Points.Length;
|
||||
|
||||
var normal = new Vector2(poly.Points[j].Y - poly.Points[i].Y, poly.Points[i].X - poly.Points[j].X);
|
||||
|
||||
double? minA = null, maxA = null;
|
||||
foreach (var p in a.Points)
|
||||
{
|
||||
var projected = Vector2.Dot(normal, p);
|
||||
if (minA == null || projected < minA)
|
||||
minA = projected;
|
||||
if (maxA == null || projected > maxA)
|
||||
maxA = projected;
|
||||
}
|
||||
|
||||
double? minB = null, maxB = null;
|
||||
foreach (var p in b.Points)
|
||||
{
|
||||
var projected = Vector2.Dot(normal, p);
|
||||
if (minB == null || projected < minB)
|
||||
minB = projected;
|
||||
if (maxB == null || projected > maxB)
|
||||
maxB = projected;
|
||||
}
|
||||
|
||||
if (maxA <= minB || maxB <= minA)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
12
Game/Assets/Scripts/Model/Polygon.cs.meta
Normal file
12
Game/Assets/Scripts/Model/Polygon.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ee96020375a8f3d4bb7a04e9f7a6b05e
|
||||
timeCreated: 1433489979
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
97
Game/Assets/Scripts/Unity/BuildingMeshGenerator.cs
Normal file
97
Game/Assets/Scripts/Unity/BuildingMeshGenerator.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Model;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TransportGame.Unity
|
||||
{
|
||||
public class BuildingMeshGenerator
|
||||
{
|
||||
public Material BuildingMaterial { get; set; }
|
||||
|
||||
private GameObject parent = new GameObject("buildings");
|
||||
private Map map;
|
||||
|
||||
public IEnumerable Generate(Map map)
|
||||
{
|
||||
this.map = map;
|
||||
|
||||
foreach (var building in map.Buildings)
|
||||
{
|
||||
GenerateBuilding(building);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateBuilding(Building building)
|
||||
{
|
||||
List<Vector3> vertices = new List<Vector3>();
|
||||
List<UnityEngine.Vector2> uv = new List<UnityEngine.Vector2>();
|
||||
List<int> triangles = new List<int>();
|
||||
int vIndex = 0;
|
||||
|
||||
if (building.Polygons.Length == 0)
|
||||
return;
|
||||
|
||||
float terrainHeight = building.Polygons[0].SelectMany(poly => poly.Points).Min(pt => map.GetHeight((int)pt.X, (int)pt.Y));
|
||||
float minY, maxY = terrainHeight;
|
||||
|
||||
for (int i = 0; i < building.Polygons.Length; i++)
|
||||
{
|
||||
// Update minY and maxY
|
||||
minY = maxY;
|
||||
maxY += building.LevelHeights[i];
|
||||
|
||||
for (int p = 0; p < building.Polygons[i].Length; p++)
|
||||
{
|
||||
// Build mesh
|
||||
var center = building.Polygons[i][p].Position;
|
||||
var pts = building.Polygons[i][p].Points;
|
||||
|
||||
for (int j = 0; j < pts.Length; j++)
|
||||
{
|
||||
int k = (j + 1) % pts.Length;
|
||||
|
||||
vertices.Add(new Vector3(pts[j].Y, minY, pts[j].X));
|
||||
vertices.Add(new Vector3(pts[k].Y, minY, pts[k].X));
|
||||
vertices.Add(new Vector3(pts[k].Y, maxY, pts[k].X));
|
||||
vertices.Add(new Vector3(pts[j].Y, maxY, pts[j].X));
|
||||
vertices.Add(new Vector3(center.Y, maxY, center.X));
|
||||
|
||||
uv.Add(new UnityEngine.Vector2(0, 0));
|
||||
uv.Add(new UnityEngine.Vector2(0, 1));
|
||||
uv.Add(new UnityEngine.Vector2(1, 1));
|
||||
uv.Add(new UnityEngine.Vector2(1, 0));
|
||||
uv.Add(new UnityEngine.Vector2(0.5f, 0.5f));
|
||||
|
||||
triangles.AddRange(new[] { vIndex, vIndex + 1, vIndex + 2 });
|
||||
triangles.AddRange(new[] { vIndex, vIndex + 2, vIndex + 3 });
|
||||
triangles.AddRange(new[] { vIndex + 4, vIndex + 3, vIndex + 2 }); // top side
|
||||
|
||||
vIndex = vertices.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct mesh
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.vertices = vertices.ToArray();
|
||||
mesh.triangles = triangles.ToArray();
|
||||
mesh.uv = uv.ToArray();
|
||||
mesh.RecalculateNormals();
|
||||
|
||||
// Construct game object
|
||||
GameObject inters = new GameObject("building");
|
||||
inters.transform.parent = parent.transform;
|
||||
|
||||
MeshFilter meshFilter = inters.AddComponent<MeshFilter>();
|
||||
meshFilter.mesh = mesh;
|
||||
|
||||
MeshRenderer meshRenderer = inters.AddComponent<MeshRenderer>();
|
||||
meshRenderer.materials = new[] { BuildingMaterial };
|
||||
}
|
||||
}
|
||||
}
|
12
Game/Assets/Scripts/Unity/BuildingMeshGenerator.cs.meta
Normal file
12
Game/Assets/Scripts/Unity/BuildingMeshGenerator.cs.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 285289bd35a5aac429d15f8fdf99512f
|
||||
timeCreated: 1433859861
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -16,6 +16,7 @@ public class TerrainGeneratorScript : MonoBehaviour
|
||||
public GameObject WaterObject;
|
||||
public Texture2D[] Textures;
|
||||
public Material RoadMaterial;
|
||||
public Material BuildingMaterial;
|
||||
|
||||
// Use this for initialization
|
||||
void Start()
|
||||
@ -54,7 +55,7 @@ public class TerrainGeneratorScript : MonoBehaviour
|
||||
private IEnumerator GenerateMap()
|
||||
{
|
||||
// Generate terrain
|
||||
foreach (var i in Task.RunAsync(GenerateTerrainThread))
|
||||
foreach (var i in Task.Await(GenerateTerrainThread))
|
||||
yield return i;
|
||||
|
||||
// Generate terrain data
|
||||
@ -105,20 +106,62 @@ public class TerrainGeneratorScript : MonoBehaviour
|
||||
|
||||
// Set up textures
|
||||
Logger.Info("Setting up textures...");
|
||||
foreach (var i in SetupSplatmaps(terrainData))
|
||||
BeginSetupSplatmaps(terrainData);
|
||||
|
||||
foreach (var lot in map.BuildingLots)
|
||||
{
|
||||
for (int i = 0; i < lot.Points.Length; i++)
|
||||
{
|
||||
int j = (i + 1) % lot.Points.Length;
|
||||
|
||||
if (!map.IsInside(lot.Points[i]))
|
||||
{
|
||||
Logger.Warning("Generated point not inside: {0}", lot.Points[i]);
|
||||
continue;
|
||||
}
|
||||
if (!map.IsInside(lot.Points[j]))
|
||||
{
|
||||
Logger.Warning("Generated point not inside: {0}", lot.Points[j]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.DrawLine(
|
||||
new Vector3(lot.Points[i].Y, map.GetHeight((int)lot.Points[i].X, (int)lot.Points[i].Y) + 0.5f, lot.Points[i].X),
|
||||
new Vector3(lot.Points[j].Y, map.GetHeight((int)lot.Points[j].X, (int)lot.Points[j].Y) + 0.5f, lot.Points[j].X),
|
||||
Color.yellow, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate road & building mesh (we run the loops in parallel)
|
||||
Logger.Info("Generating buildings and roads...");
|
||||
|
||||
BuildingMeshGenerator buildingMeshGenerator = new BuildingMeshGenerator();
|
||||
buildingMeshGenerator.BuildingMaterial = BuildingMaterial;
|
||||
var it1 = buildingMeshGenerator.Generate(map).GetEnumerator();
|
||||
|
||||
RoadMeshGenerator roadMeshGenerator = new RoadMeshGenerator();
|
||||
roadMeshGenerator.RoadMaterial = RoadMaterial;
|
||||
var it2 = roadMeshGenerator.Generate(map).GetEnumerator();
|
||||
|
||||
bool stop;
|
||||
do
|
||||
{
|
||||
stop = true;
|
||||
if (it1.MoveNext())
|
||||
{
|
||||
yield return it1.Current;
|
||||
stop = false;
|
||||
}
|
||||
if (it2.MoveNext())
|
||||
{
|
||||
yield return it2.Current;
|
||||
stop = false;
|
||||
}
|
||||
} while (!stop);
|
||||
|
||||
// Finish setting up textures
|
||||
foreach (var i in EndSetupSplatmaps(terrainData))
|
||||
yield return i;
|
||||
|
||||
// Generate road mesh
|
||||
Logger.Info("Generating roads...");
|
||||
RoadMeshGenerator meshGenerator = new RoadMeshGenerator();
|
||||
meshGenerator.RoadMaterial = RoadMaterial;
|
||||
foreach (object i in meshGenerator.Generate(map))
|
||||
yield return i;
|
||||
|
||||
// -- DEBUG --
|
||||
|
||||
foreach (var center in map.PopulationCenters)
|
||||
Debug.DrawLine(new Vector3(center.Y, 0, center.X), new Vector3(center.Y, map.Biome.Height, center.X), Color.yellow, 100000);
|
||||
}
|
||||
|
||||
private float[,,] GenerateSplatData(int alphaWidth, int alphaHeight, int alphaLayers)
|
||||
@ -174,14 +217,21 @@ public class TerrainGeneratorScript : MonoBehaviour
|
||||
return splatData;
|
||||
}
|
||||
|
||||
private IEnumerable SetupSplatmaps(TerrainData terrainData)
|
||||
float[, ,] splatData = null;
|
||||
private Task splatmapsTask = null;
|
||||
|
||||
private void BeginSetupSplatmaps(TerrainData terrainData)
|
||||
{
|
||||
float[, ,] splatData = null;
|
||||
int alphaW = terrainData.alphamapWidth;
|
||||
int alphaH = terrainData.alphamapHeight;
|
||||
int alphaL = terrainData.alphamapLayers;
|
||||
|
||||
foreach (var i in Task.RunAsync(() => splatData = GenerateSplatData(alphaW, alphaH, alphaL)))
|
||||
splatmapsTask = Task.RunAsync(() => splatData = GenerateSplatData(alphaW, alphaH, alphaL));
|
||||
}
|
||||
|
||||
private IEnumerable EndSetupSplatmaps(TerrainData terrainData)
|
||||
{
|
||||
foreach (var i in splatmapsTask.Await())
|
||||
yield return i;
|
||||
|
||||
terrainData.SetAlphamaps(0, 0, splatData);
|
||||
|
@ -2,11 +2,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TransportGame.Model;
|
||||
|
||||
namespace TransportGame.Utils
|
||||
{
|
||||
public static class Algorithmss
|
||||
{
|
||||
|
||||
public static bool DoPolygonsIntersect(Vector2[] a, Vector2[] b)
|
||||
{
|
||||
foreach (var poly in new[] {a, b})
|
||||
{
|
||||
for (int i = 0; i < poly.Length; i++)
|
||||
{
|
||||
int j = (i + 1) % poly.Length;
|
||||
|
||||
var normal = new Vector2(poly[j].Y - poly[i].Y, poly[i].X - poly[j].X);
|
||||
|
||||
double? minA = null, maxA = null;
|
||||
foreach (var p in a)
|
||||
{
|
||||
var projected = Vector2.Dot(normal, p);
|
||||
if (minA == null || projected < minA)
|
||||
minA = projected;
|
||||
if (maxA == null || projected > maxA)
|
||||
maxA = projected;
|
||||
}
|
||||
|
||||
double? minB = null, maxB = null;
|
||||
foreach (var p in b)
|
||||
{
|
||||
var projected = Vector2.Dot(normal, p);
|
||||
if (minB == null || projected < minB)
|
||||
minB = projected;
|
||||
if (maxB == null || projected > maxB)
|
||||
maxB = projected;
|
||||
}
|
||||
|
||||
if (maxA <= minB || maxB <= minA)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,41 +7,55 @@ using System.Threading;
|
||||
|
||||
namespace TransportGame.Utils
|
||||
{
|
||||
public static class Task
|
||||
public class Task
|
||||
{
|
||||
private class TaskInfo
|
||||
public bool? Success { get; private set; }
|
||||
|
||||
private Exception thrownException;
|
||||
private Action action;
|
||||
private Thread thread;
|
||||
|
||||
private Task()
|
||||
{
|
||||
public Action Action { get; set; }
|
||||
public bool? Success { get; set; }
|
||||
public Exception ThrownException { get; set; }
|
||||
}
|
||||
|
||||
private static void RunAsync_ActionThread(object info)
|
||||
{
|
||||
TaskInfo taskInfo = (TaskInfo)info;
|
||||
Task taskInfo = (Task)info;
|
||||
|
||||
try
|
||||
{
|
||||
taskInfo.Action();
|
||||
taskInfo.action();
|
||||
taskInfo.Success = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskInfo.ThrownException = ex;
|
||||
taskInfo.thrownException = ex;
|
||||
taskInfo.Success = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable RunAsync(Action action)
|
||||
public static Task RunAsync(Action action)
|
||||
{
|
||||
// Set up task info object
|
||||
TaskInfo taskInfo = new TaskInfo();
|
||||
taskInfo.Action = action;
|
||||
Task taskInfo = new Task();
|
||||
taskInfo.action = action;
|
||||
taskInfo.thread = new Thread(RunAsync_ActionThread);
|
||||
|
||||
// Start thread and wait
|
||||
var thread = new Thread(RunAsync_ActionThread);
|
||||
thread.Start(taskInfo);
|
||||
// Start thread
|
||||
taskInfo.thread.Start(taskInfo);
|
||||
|
||||
// Return task info
|
||||
return taskInfo;
|
||||
}
|
||||
|
||||
public static IEnumerable Await(Action action)
|
||||
{
|
||||
return Task.RunAsync(action).Await();
|
||||
}
|
||||
|
||||
public IEnumerable Await()
|
||||
{
|
||||
// Wait for thread to finish
|
||||
while (thread.ThreadState == ThreadState.Running)
|
||||
yield return null;
|
||||
@ -49,8 +63,8 @@ namespace TransportGame.Utils
|
||||
thread.Join();
|
||||
|
||||
// Rethrow exception
|
||||
if (taskInfo.Success.HasValue && !taskInfo.Success.Value)
|
||||
throw new Exception("Task failed", taskInfo.ThrownException);
|
||||
if (Success.HasValue && !Success.Value)
|
||||
throw new Exception("Task failed", thrownException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user