442 lines
19 KiB
C#
442 lines
19 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using TransportGame.Business;
|
|
using TransportGame.Model;
|
|
using TransportGame.Model.Road;
|
|
using TransportGame.Utils;
|
|
using UnityEngine;
|
|
using Vector2 = TransportGame.Model.Vector2;
|
|
|
|
//#define DEBUG_ROAD_MESH_GENERATOR
|
|
|
|
namespace TransportGame.Unity
|
|
{
|
|
class RoadMeshGenerator
|
|
{
|
|
#region Constants
|
|
|
|
private float SidewalkWidth { get { return ConfigManager.Roadgen.SidewalkWidth; } }
|
|
|
|
private float LaneWidth { get { return ConfigManager.Roadgen.LaneWidth; } }
|
|
|
|
// Raises road above actual height of terrain
|
|
private float RaiseOffset { get { return ConfigManager.Roadgen.RaiseOffset; } }
|
|
|
|
// Height of sidewalk
|
|
private float SidewalkHeight { get { return ConfigManager.Roadgen.SidewalkHeight; } }
|
|
|
|
// On the sides of the roads, so that we can't see the road from the underside
|
|
private float SideCoverHeight { get { return ConfigManager.Roadgen.SideCoverHeight; } }
|
|
|
|
#endregion
|
|
|
|
#region Private fields
|
|
|
|
private Map map;
|
|
|
|
private Dictionary<int, Vector2[]> segmentTerminal1Limit = new Dictionary<int, Vector2[]>();
|
|
private Dictionary<int, Vector2[]> segmentTerminal2Limit = new Dictionary<int, Vector2[]>();
|
|
|
|
private GameObject parent = new GameObject("roadNetwork");
|
|
|
|
#endregion
|
|
|
|
public Material RoadMaterial { get; set; }
|
|
public Material SidewalkMaterial { get; set; }
|
|
|
|
public IEnumerable Generate(Map map)
|
|
{
|
|
this.map = map;
|
|
|
|
List<int> visitedNodes = new List<int>();
|
|
List<int> visitedSegments = new List<int>();
|
|
Queue<RoadNode> queue = new Queue<RoadNode>();
|
|
queue.Enqueue(map.RoadNetwork.Nodes.First().Value);
|
|
int i = 0;
|
|
|
|
while (queue.Count > 0)
|
|
{
|
|
// Pop a node
|
|
RoadNode node = queue.Dequeue();
|
|
visitedNodes.Add(node.Id);
|
|
|
|
// Generate intersection
|
|
GenerateIntersection(node);
|
|
|
|
// Enqueue
|
|
foreach (var segment in node.ArticulationSegments)
|
|
{
|
|
// Generate segments linked to node that have been visited
|
|
if (visitedNodes.Contains(segment.Terminal1Id) && visitedNodes.Contains(segment.Terminal2Id))
|
|
{
|
|
if (!visitedSegments.Contains(segment.Id))
|
|
{
|
|
GenerateSegment(segment);
|
|
visitedSegments.Add(segment.Id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (node == segment.Terminal1)
|
|
queue.Enqueue(segment.Terminal2);
|
|
else queue.Enqueue(segment.Terminal1);
|
|
}
|
|
}
|
|
|
|
// Take a break
|
|
++i;
|
|
if (i % 20 == 0)
|
|
yield return null;
|
|
}
|
|
|
|
Logger.Info("Finished generating road mesh.");
|
|
}
|
|
|
|
private int LanesTo(RoadSegment segment, RoadNode terminal)
|
|
{
|
|
if (segment.Terminal1 == terminal)
|
|
return segment.LanesTo1;
|
|
|
|
if (segment.Terminal2 == terminal)
|
|
return segment.LanesTo2;
|
|
|
|
return -1;
|
|
}
|
|
|
|
private int LanesFrom(RoadSegment segment, RoadNode terminal)
|
|
{
|
|
if (segment.Terminal1 == terminal)
|
|
return segment.LanesTo2;
|
|
|
|
if (segment.Terminal2 == terminal)
|
|
return segment.LanesTo1;
|
|
|
|
return -1;
|
|
}
|
|
|
|
private Vector2 DirectionFrom(RoadSegment segment, RoadNode terminal)
|
|
{
|
|
return (segment.Terminal1 == terminal) ? segment.Direction : -segment.Direction;
|
|
}
|
|
|
|
public void GenerateIntersection(RoadNode node)
|
|
{
|
|
List<Vector3> vertices = new List<Vector3>();
|
|
List<UnityEngine.Vector2> uv = new List<UnityEngine.Vector2>();
|
|
List<int> triangles = new List<int>();
|
|
int vindex = 0;
|
|
|
|
// Helper function
|
|
Action<Vector2, float> addV = (v, h) =>
|
|
{
|
|
Vector3 v3 = new Vector3(v.Y, map.GetHeight((int)node.X, (int)node.Y) + RaiseOffset + h, v.X);
|
|
UnityEngine.Vector2 v2 = new UnityEngine.Vector2(v.Y / 3 - v3.y, v.X / 3 - v3.y);
|
|
|
|
vertices.Add(v3);
|
|
uv.Add(v2);
|
|
};
|
|
|
|
// Add current node
|
|
addV(node.Position, 0);
|
|
vindex += 1;
|
|
|
|
// Sort adjacent segments in trigonometric order
|
|
var segs = node.ArticulationSegments.OrderBy(segment =>
|
|
{
|
|
Vector2 dir = DirectionFrom(segment, node);
|
|
|
|
if (dir.X >= 0 && dir.Y >= 0) // First quadrant:
|
|
return dir.Y; // Y coordinate grows from 0 to 1
|
|
|
|
else if (dir.X <= 0 && dir.Y >= 0) // Second quadrant:
|
|
return 1 + Math.Abs(dir.X); // X goes from 0 to -1
|
|
|
|
else if (dir.X <= 0 && dir.Y <= 0) // Third quadrant:
|
|
return 2 + Math.Abs(dir.Y); // Y goes from 0 to -1
|
|
|
|
else return 3 + dir.X; // Fourth quadrant: X grows from 0 to 1
|
|
}).ToArray();
|
|
|
|
Vector2[] sideCrns = new Vector2[segs.Length];
|
|
Vector2[] strCrns = new Vector2[segs.Length];
|
|
|
|
// Holds intersection points for perpendicular on segment that goes through sidewalk corners
|
|
// 0, 1, 2, 3 - 0 sidewalk, 1 street, 2 street, 3 sidewalk (in trigonometric order). (when first segment)
|
|
// 4, 5, 6, 7 - the same (when second segment)
|
|
Vector2[,] p = new Vector2[8, segs.Length];
|
|
|
|
// Process each pair of segments
|
|
for (int i = 0; i < segs.Length; ++i)
|
|
{
|
|
// Wrap around
|
|
int j = i + 1;
|
|
if (j >= segs.Length)
|
|
j = 0;
|
|
|
|
// Get direction vectors
|
|
var dir0 = DirectionFrom(segs[i], node);
|
|
var dir1 = DirectionFrom(segs[j], node);
|
|
float cross = Vector2.Cross(dir0, dir1);
|
|
|
|
// Colliniar vectors
|
|
if (Math.Abs(cross) < 1e-2)
|
|
{
|
|
var dirP = dir0.RotateDeg(90);
|
|
|
|
strCrns[i] = node.Position + dirP * LanesTo(segs[i], node) * LaneWidth;
|
|
sideCrns[i] = strCrns[i] + dirP * SidewalkWidth;
|
|
}
|
|
|
|
// Vectors not colliniar
|
|
else
|
|
{
|
|
Vector2 off0 = dir0 * ((LanesFrom(segs[j], node) * LaneWidth) / cross);
|
|
Vector2 off1 = dir1 * ((LanesTo(segs[i], node) * LaneWidth) / cross);
|
|
strCrns[i] = node.Position + off0 + off1;
|
|
sideCrns[i] = strCrns[i] + dir0 * (SidewalkWidth / cross) + dir1 * (SidewalkWidth / cross);
|
|
}
|
|
|
|
// Calculate perpendicular vectors from sidewalk corner on first segment
|
|
Vector2 perp0 = dir0.RotateDeg(-90);
|
|
p[3, i] = sideCrns[i];
|
|
p[2, i] = p[3, i] + perp0 * SidewalkWidth;
|
|
p[1, i] = p[2, i] + perp0 * (segs[i].LanesTo1 + segs[i].LanesTo2) * LaneWidth;
|
|
p[0, i] = p[1, i] + perp0 * SidewalkWidth;
|
|
|
|
// Calculate perpendicular vectors from sidewalk corner on second segment
|
|
Vector2 perp1 = dir1.RotateDeg(90);
|
|
p[4, j] = sideCrns[i];
|
|
p[5, j] = p[4, j] + perp1 * SidewalkWidth;
|
|
p[6, j] = p[5, j] + perp1 * (segs[j].LanesTo1 + segs[j].LanesTo2) * LaneWidth;
|
|
p[7, j] = p[6, j] + perp1 * SidewalkWidth;
|
|
}
|
|
|
|
if (segs.Length == 1)
|
|
{
|
|
var dir = DirectionFrom(segs[0], node);
|
|
Vector2 perp = dir.RotateDeg(-90);
|
|
Vector2[] end = new[] {
|
|
node.Position + perp * (LanesFrom(segs[0], node) * LaneWidth + SidewalkWidth),
|
|
node.Position + perp * (LanesFrom(segs[0], node) * LaneWidth),
|
|
node.Position - perp * (LanesTo(segs[0], node) * LaneWidth),
|
|
node.Position - perp * (LanesTo(segs[0], node) * LaneWidth + SidewalkWidth)
|
|
};
|
|
|
|
if (segs[0].Terminal1 == node)
|
|
{
|
|
segmentTerminal1Limit[segs[0].Id] = end;
|
|
}
|
|
else
|
|
{
|
|
segmentTerminal2Limit[segs[0].Id] = end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
// Now we generate for each segment
|
|
for (int i = 0; i < segs.Length; i++)
|
|
{
|
|
// Find furthest perpendicular
|
|
float dist0 = (p[0, i] - node.Position).LengthSq;
|
|
float dist1 = (p[4, i] - node.Position).LengthSq;
|
|
|
|
int furth = (dist0 > dist1) ? 0 : 4;
|
|
int cornerLeft = i;
|
|
int cornerRight = (i - 1 >= 0) ? i - 1 : segs.Length - 1;
|
|
|
|
// Add to limit dictionaries
|
|
if (segs[i].Terminal1 == node)
|
|
{
|
|
segmentTerminal1Limit[segs[i].Id] = new[] { p[furth, i], p[furth + 1, i], p[furth + 2, i], p[furth + 3, i] };
|
|
}
|
|
else
|
|
{
|
|
segmentTerminal2Limit[segs[i].Id] = new[] { p[furth, i], p[furth + 1, i], p[furth + 2, i], p[furth + 3, i] };
|
|
}
|
|
|
|
// Build road
|
|
// Right side
|
|
addV(p[furth, i], SidewalkHeight); // 0 - p0 raised
|
|
addV(sideCrns[cornerRight], SidewalkHeight); // 1 - side corner raised
|
|
addV(sideCrns[cornerRight], SidewalkHeight - SideCoverHeight); // 2 - side corner cover
|
|
addV(p[furth, i], SidewalkHeight - SideCoverHeight); // 3 - p0 cover
|
|
addV(p[furth + 1, i], SidewalkHeight); // 4 - p1 raised
|
|
addV(strCrns[cornerRight], SidewalkHeight); // 5 - street corner raised
|
|
addV(strCrns[cornerRight], 0); // 6 - street corner
|
|
addV(p[furth + 1, i], 0); // 7 - p1
|
|
|
|
// Left side
|
|
addV(p[furth + 2, i], 0); // 8 - p2
|
|
addV(strCrns[cornerLeft], 0); // 9 - street corner
|
|
addV(strCrns[cornerLeft], SidewalkHeight); //10 - street corner raised
|
|
addV(p[furth + 2, i], SidewalkHeight); //11 - p2 raised
|
|
addV(p[furth + 3, i], SidewalkHeight - SideCoverHeight); //12 - p3 cover
|
|
addV(sideCrns[cornerLeft], SidewalkHeight - SideCoverHeight); //13 - side corner cover
|
|
addV(sideCrns[cornerLeft], SidewalkHeight); //14 - side corner raised
|
|
addV(p[furth + 3, i], SidewalkHeight); //15 - p3 raised
|
|
|
|
// Right sidewalk
|
|
triangles.AddRange(new[] { vindex, vindex + 1, vindex + 2 }); // sidewalk side cover 0
|
|
triangles.AddRange(new[] { vindex, vindex + 2, vindex + 3 }); // sidewalk side cover 1
|
|
triangles.AddRange(new[] { vindex, vindex + 4, vindex + 1 }); // sidewalk surface 0
|
|
triangles.AddRange(new[] { vindex + 4, vindex + 5, vindex + 1 }); // sidewalk surface 1
|
|
triangles.AddRange(new[] { vindex + 4, vindex + 7, vindex + 6 }); // sidewalk road raise 0
|
|
triangles.AddRange(new[] { vindex + 4, vindex + 6, vindex + 5 }); // sidewalk road raise 1
|
|
|
|
// Road surface
|
|
triangles.AddRange(new[] { vindex + 8, vindex + 6, vindex + 7 }); // road surface 0
|
|
triangles.AddRange(new[] { vindex + 8, vindex + 9, vindex + 6 }); // road surface 0
|
|
triangles.AddRange(new[] { vindex + 6, vindex + 9, 0 }); // road surface 0
|
|
|
|
// Left sidewalk
|
|
triangles.AddRange(new[] { vindex + 11, vindex + 10, vindex + 9 }); // sidewalk road raise 0
|
|
triangles.AddRange(new[] { vindex + 11, vindex + 9, vindex + 8 }); // sidewalk road raise 1
|
|
triangles.AddRange(new[] { vindex + 11, vindex + 14, vindex + 10 });// sidewalk surface 0
|
|
triangles.AddRange(new[] { vindex + 11, vindex + 15, vindex + 14 });// sidewalk surface 1
|
|
triangles.AddRange(new[] { vindex + 15, vindex + 12, vindex + 13 });// sidewalk side cover 0
|
|
triangles.AddRange(new[] { vindex + 15, vindex + 13, vindex + 14 });// sidewalk side cover 1
|
|
|
|
// update vindex
|
|
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("intersection" + node.Id);
|
|
inters.transform.parent = parent.transform;
|
|
|
|
MeshFilter meshFilter = inters.AddComponent<MeshFilter>();
|
|
meshFilter.mesh = mesh;
|
|
|
|
MeshRenderer meshRenderer = inters.AddComponent<MeshRenderer>();
|
|
meshRenderer.materials = new[] { RoadMaterial };
|
|
}
|
|
|
|
#if DEBUG_ROAD_MESH_GENERATOR
|
|
// Debug draw
|
|
Debug.DrawLine(
|
|
new Vector3(node.Y, map.GetHeight((int)node.X, (int)node.Y) + 0.5f, node.X),
|
|
new Vector3(node.Y, map.GetHeight((int)node.X, (int)node.Y) + 1.2f, node.X),
|
|
Color.white, 10000);
|
|
|
|
foreach (var sidewalkCorner in sideCrns)
|
|
Debug.DrawLine(
|
|
new Vector3(sidewalkCorner.Y, map.GetHeight((int)sidewalkCorner.X, (int)sidewalkCorner.Y) + 0.5f, sidewalkCorner.X),
|
|
new Vector3(sidewalkCorner.Y, map.GetHeight((int)sidewalkCorner.X, (int)sidewalkCorner.Y) + 1.1f, sidewalkCorner.X),
|
|
Color.cyan, 10000);
|
|
|
|
foreach (var strCorner in strCrns)
|
|
Debug.DrawLine(
|
|
new Vector3(strCorner.Y, map.GetHeight((int)strCorner.X, (int)strCorner.Y) + 0.5f, strCorner.X),
|
|
new Vector3(strCorner.Y, map.GetHeight((int)strCorner.X, (int)strCorner.Y) + 1.1f, strCorner.X),
|
|
Color.blue, 10000);
|
|
|
|
for (int i = 0; i < segs.Length; i++)
|
|
for (int j = 0; j < 8; j++)
|
|
Debug.DrawLine(
|
|
new Vector3(p[j, i].Y, map.GetHeight((int)p[j, i].X, (int)p[j, i].Y) + 0.5f, p[j, i].X),
|
|
new Vector3(p[j, i].Y, map.GetHeight((int)p[j, i].X, (int)p[j, i].Y) + 1.0f, p[j, i].X),
|
|
Color.green, 10000);
|
|
#endif
|
|
}
|
|
|
|
public void GenerateSegment(RoadSegment s)
|
|
{
|
|
List<Vector3> vertices = new List<Vector3>();
|
|
List<UnityEngine.Vector2> uv = new List<UnityEngine.Vector2>();
|
|
List<int> triangles = new List<int>();
|
|
|
|
// Helper function
|
|
Action<Vector2, float, int> addV = (v, h, term) =>
|
|
{
|
|
if (term == 1)
|
|
h += map.GetHeight((int)s.Terminal1.X, (int)s.Terminal1.Y);
|
|
else
|
|
h += map.GetHeight((int)s.Terminal2.X, (int)s.Terminal2.Y);
|
|
|
|
Vector3 v3 = new Vector3(v.Y, RaiseOffset + h, v.X);
|
|
UnityEngine.Vector2 v2 = new UnityEngine.Vector2(v.Y / 3 - v3.y, v.X / 3 - v3.y);
|
|
|
|
vertices.Add(v3);
|
|
uv.Add(v2);
|
|
};
|
|
|
|
Vector2[] pT1 = segmentTerminal1Limit[s.Id];
|
|
Vector2[] pT2 = segmentTerminal2Limit[s.Id];
|
|
|
|
addV(pT1[0], SidewalkHeight - SideCoverHeight, 1);
|
|
addV(pT1[0], SidewalkHeight, 1);
|
|
addV(pT1[1], SidewalkHeight, 1);
|
|
addV(pT1[1], 0, 1);
|
|
addV(pT1[2], 0, 1);
|
|
addV(pT1[2], SidewalkHeight, 1);
|
|
addV(pT1[3], SidewalkHeight, 1);
|
|
addV(pT1[3], SidewalkHeight - SideCoverHeight, 1);
|
|
|
|
addV(pT2[0], SidewalkHeight - SideCoverHeight, 2);
|
|
addV(pT2[0], SidewalkHeight, 2);
|
|
addV(pT2[1], SidewalkHeight, 2);
|
|
addV(pT2[1], 0, 2);
|
|
addV(pT2[2], 0, 2);
|
|
addV(pT2[2], SidewalkHeight, 2);
|
|
addV(pT2[3], SidewalkHeight, 2);
|
|
addV(pT2[3], SidewalkHeight - SideCoverHeight, 2);
|
|
|
|
triangles.AddRange(new[] { 0, 15, 14 }); // sidewalk side cover 0
|
|
triangles.AddRange(new[] { 0, 14, 1 }); // sidewalk side cover 1
|
|
triangles.AddRange(new[] { 1, 14, 13 }); // sidewalk surface 0
|
|
triangles.AddRange(new[] { 1, 13, 2 }); // sidewalk surface 1
|
|
triangles.AddRange(new[] { 2, 12, 3 }); // sidewalk road raise 0
|
|
triangles.AddRange(new[] { 2, 13, 12 }); // sidewalk road raise 1
|
|
|
|
// Road surface
|
|
triangles.AddRange(new[] { 3, 12, 11 }); // road surface 0
|
|
triangles.AddRange(new[] { 3, 11, 4 }); // road surface 0
|
|
|
|
// Left sidewalk
|
|
triangles.AddRange(new[] { 4, 11, 10 }); // sidewalk road raise 0
|
|
triangles.AddRange(new[] { 4, 10, 5 }); // sidewalk road raise 1
|
|
triangles.AddRange(new[] { 5, 10, 9 }); // sidewalk surface 0
|
|
triangles.AddRange(new[] { 5, 9, 6 }); // sidewalk surface 1
|
|
triangles.AddRange(new[] { 6, 9, 8 }); // sidewalk side cover 0
|
|
triangles.AddRange(new[] { 6, 8, 7 }); // sidewalk side cover 1
|
|
|
|
// 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("road" + s.Id);
|
|
inters.transform.parent = parent.transform;
|
|
|
|
MeshFilter meshFilter = inters.AddComponent<MeshFilter>();
|
|
meshFilter.mesh = mesh;
|
|
|
|
MeshRenderer meshRenderer = inters.AddComponent<MeshRenderer>();
|
|
meshRenderer.materials = new[] { RoadMaterial };
|
|
|
|
#if DEBUG_ROAD_MESH_GENERATOR
|
|
// Debug draw
|
|
Color color = (s.LanesTo1 >= 3) ? Color.magenta : Color.red;
|
|
Debug.DrawLine(
|
|
new Vector3(s.Terminal1.Y, map.GetHeight((int)s.Terminal1.X, (int)s.Terminal1.Y) + 1f, s.Terminal1.X),
|
|
new Vector3(s.Terminal2.Y, map.GetHeight((int)s.Terminal2.X, (int)s.Terminal2.Y) + 1f, s.Terminal2.X),
|
|
color, 10000);
|
|
#endif
|
|
}
|
|
}
|
|
}
|