using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Text; 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 const float SidewalkWidth = .8f; private const float LaneWidth = 1f; private const float RaiseOffset = 0.8f; // Raises road above actual height of terrain private const float SidewalkHeight = 0.1f; // Height of sidewalk private const float SideCoverHeight = 0.1f; // On the sides of the roads, so that we can't see the road from the underside #endregion #region Private fields private Map map; private Dictionary segmentTerminal1Limit = new Dictionary(); private Dictionary segmentTerminal2Limit = new Dictionary(); 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 visitedNodes = new List(); List visitedSegments = new List(); Queue queue = new Queue(); 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 vertices = new List(); List uv = new List(); List triangles = new List(); int vindex = 0; // Helper function Action 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.mesh = mesh; MeshRenderer meshRenderer = inters.AddComponent(); 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 vertices = new List(); List uv = new List(); List triangles = new List(); // Helper function Action 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.mesh = mesh; MeshRenderer meshRenderer = inters.AddComponent(); 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 } } }