diff --git a/Game/Assembly-CSharp-vs.csproj b/Game/Assembly-CSharp-vs.csproj index c9086fb..d26af97 100644 --- a/Game/Assembly-CSharp-vs.csproj +++ b/Game/Assembly-CSharp-vs.csproj @@ -66,6 +66,7 @@ + diff --git a/Game/Assembly-CSharp.csproj b/Game/Assembly-CSharp.csproj index 1be76cc..f175971 100644 --- a/Game/Assembly-CSharp.csproj +++ b/Game/Assembly-CSharp.csproj @@ -66,6 +66,7 @@ + diff --git a/Game/Assets/Data/Biomes/Mountain.xml b/Game/Assets/Data/Biomes/Mountain.xml index 5226aa8..ec8ae97 100644 --- a/Game/Assets/Data/Biomes/Mountain.xml +++ b/Game/Assets/Data/Biomes/Mountain.xml @@ -1,7 +1,7 @@  Mountain - 500 + 400 diff --git a/Game/Assets/Data/Materials.meta b/Game/Assets/Data/Materials.meta new file mode 100644 index 0000000..fd60a98 --- /dev/null +++ b/Game/Assets/Data/Materials.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 6fed1937e656c34458559bd2469f5455 +folderAsset: yes +timeCreated: 1433236765 +licenseType: Free +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Data/Materials/sidewalk.mat b/Game/Assets/Data/Materials/sidewalk.mat new file mode 100644 index 0000000..05010f2 Binary files /dev/null and b/Game/Assets/Data/Materials/sidewalk.mat differ diff --git a/Game/Assets/Data/Materials/sidewalk.mat.meta b/Game/Assets/Data/Materials/sidewalk.mat.meta new file mode 100644 index 0000000..349cc56 --- /dev/null +++ b/Game/Assets/Data/Materials/sidewalk.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ddd2e09300d67f041977284f4d24a0c9 +timeCreated: 1433236871 +licenseType: Free +NativeFormatImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Data/Materials/skybox.mat b/Game/Assets/Data/Materials/skybox.mat new file mode 100644 index 0000000..01043af Binary files /dev/null and b/Game/Assets/Data/Materials/skybox.mat differ diff --git a/Game/Assets/Data/Materials/skybox.mat.meta b/Game/Assets/Data/Materials/skybox.mat.meta new file mode 100644 index 0000000..1a03b1e --- /dev/null +++ b/Game/Assets/Data/Materials/skybox.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bf5942f32f0096844add5527185930f0 +timeCreated: 1433257206 +licenseType: Free +NativeFormatImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Data/Materials/street.mat b/Game/Assets/Data/Materials/street.mat new file mode 100644 index 0000000..574b40e Binary files /dev/null and b/Game/Assets/Data/Materials/street.mat differ diff --git a/Game/Assets/Data/Materials/street.mat.meta b/Game/Assets/Data/Materials/street.mat.meta new file mode 100644 index 0000000..f20aed1 --- /dev/null +++ b/Game/Assets/Data/Materials/street.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c62816e268432f840b564c961363e771 +timeCreated: 1433236770 +licenseType: Free +NativeFormatImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Data/Textures/sidewalk.jpg b/Game/Assets/Data/Textures/sidewalk.jpg new file mode 100644 index 0000000..e9cb3bd Binary files /dev/null and b/Game/Assets/Data/Textures/sidewalk.jpg differ diff --git a/Game/Assets/Data/Textures/sidewalk.jpg.meta b/Game/Assets/Data/Textures/sidewalk.jpg.meta new file mode 100644 index 0000000..ecdc552 --- /dev/null +++ b/Game/Assets/Data/Textures/sidewalk.jpg.meta @@ -0,0 +1,55 @@ +fileFormatVersion: 2 +guid: ea607de0299a0a54a8650b9ca86fa298 +timeCreated: 1433235814 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + linearTexture: 0 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 0 + textureType: -1 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Data/Textures/street.gif b/Game/Assets/Data/Textures/street.gif new file mode 100644 index 0000000..6dd104a Binary files /dev/null and b/Game/Assets/Data/Textures/street.gif differ diff --git a/Game/Assets/Data/Textures/street.gif.meta b/Game/Assets/Data/Textures/street.gif.meta new file mode 100644 index 0000000..86c4a09 --- /dev/null +++ b/Game/Assets/Data/Textures/street.gif.meta @@ -0,0 +1,55 @@ +fileFormatVersion: 2 +guid: 5db8f6cba4795374c8d741df2458d00e +timeCreated: 1433235809 +licenseType: Free +TextureImporter: + fileIDToRecycleName: {} + serializedVersion: 2 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + linearTexture: 0 + correctGamma: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: .25 + normalMapFilter: 0 + isReadable: 0 + grayScaleToAlpha: 0 + generateCubemap: 0 + cubemapConvolution: 0 + cubemapConvolutionSteps: 8 + cubemapConvolutionExponent: 1.5 + seamlessCubemap: 0 + textureFormat: -1 + maxTextureSize: 2048 + textureSettings: + filterMode: -1 + aniso: -1 + mipBias: -1 + wrapMode: -1 + nPOTScale: 1 + lightmap: 0 + rGBM: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: .5, y: .5} + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spritePixelsToUnits: 100 + alphaIsTransparency: 0 + textureType: -1 + buildTargetSettings: [] + spriteSheet: + sprites: [] + spritePackingTag: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Main.unity b/Game/Assets/Main.unity index a97991b..f104383 100644 Binary files a/Game/Assets/Main.unity and b/Game/Assets/Main.unity differ diff --git a/Game/Assets/Scripts/Generator/RoadGenerator.cs b/Game/Assets/Scripts/Generator/RoadGenerator.cs index 00ba354..6826cef 100644 --- a/Game/Assets/Scripts/Generator/RoadGenerator.cs +++ b/Game/Assets/Scripts/Generator/RoadGenerator.cs @@ -55,7 +55,7 @@ namespace TransportGame.Generator const float DefaultBranchProbability = 0.2f; const float DefaultSegmentLength = 24; const float SteepnessLimit = 10; - const float SlopeLimit = (float)Math.PI / 6; + const float SlopeLimit = (float)Math.PI / 7; const float RoadSegmentAngleLimit = (float)Math.PI / 4; const float RoadSnapDistance = 19; const float MinNodeDistance = 12; @@ -154,7 +154,7 @@ namespace TransportGame.Generator private IEnumerable GlobalGoals(RoadSegment segment) { Vector2 prevPos = segment.Terminal2.Position; - Vector2 dir = (segment.Terminal2.Position - segment.Terminal1.Position).Normalized; + Vector2 dir = segment.Direction; bool highway = (segment.LanesTo1 >= 3); bool highwayBranched = false; @@ -258,18 +258,14 @@ namespace TransportGame.Generator } // Filter & sort the segments by distance - segmentIds = segmentIds.Distinct().OrderBy(id => - { - var seg = map.RoadNetwork.ArticulationSegments[id]; - var line = new LineSegment(seg.Terminal1.Position, seg.Terminal2.Position); - return LineSegment.Distance(line, segment.Terminal2Pos); - }); + 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 = new LineSegment(other.Terminal1.Position, other.Terminal2.Position); + var line2 = other.AsLineSegment(); Vector2? inters = LineSegment.Intersect(line1, line2); @@ -294,7 +290,7 @@ namespace TransportGame.Generator // Check angle between intersecting segments foreach (var intersSeg in other.Terminal2.ArticulationSegments) { - float cos = Vector2.Dot((line1.P1 - line1.P0).Normalized, (intersSeg.Terminal2.Position - intersSeg.Terminal1.Position).Normalized); + float cos = Vector2.Dot(line1.Direction, intersSeg.Direction); if (Math.Abs(Math.Acos(cos)) < RoadSegmentAngleLimit) return false; } diff --git a/Game/Assets/Scripts/Model/LineSegment.cs b/Game/Assets/Scripts/Model/LineSegment.cs index 65d2606..5eb33da 100644 --- a/Game/Assets/Scripts/Model/LineSegment.cs +++ b/Game/Assets/Scripts/Model/LineSegment.cs @@ -43,6 +43,14 @@ namespace TransportGame.Model } } + public Vector2 Direction + { + get + { + return (P1 - P0).Normalized; + } + } + public LineSegment(Vector2 p0, Vector2 p1) : this() { diff --git a/Game/Assets/Scripts/Model/Road/RoadSegment.cs b/Game/Assets/Scripts/Model/Road/RoadSegment.cs index a066e5d..4008dd9 100644 --- a/Game/Assets/Scripts/Model/Road/RoadSegment.cs +++ b/Game/Assets/Scripts/Model/Road/RoadSegment.cs @@ -80,6 +80,33 @@ namespace TransportGame.Model.Road [XmlAttribute("lanesTo1")] public int LanesTo1 { get; set; } + /// + /// Gets the direction vector of this road segment + /// + [XmlIgnore] + public Vector2 Direction + { + get + { + if (Terminal1Id == -1 || Terminal2Id == -1 || ParentNetwork == null) + return Vector2.Zero; + + return (Terminal2.Position - Terminal1.Position).Normalized; + } + } + + /// + /// Gets the road segment as a line segment + /// + /// + public LineSegment AsLineSegment() + { + if (Terminal1Id == -1 || Terminal2Id == -1 || ParentNetwork == null) + throw new InvalidOperationException("Terminals are not initialized."); + + return new LineSegment(Terminal1.Position, Terminal2.Position); + } + /// /// Initializes road segment /// diff --git a/Game/Assets/Scripts/Model/Vector2.cs b/Game/Assets/Scripts/Model/Vector2.cs index 7cdeac2..5a250b2 100644 --- a/Game/Assets/Scripts/Model/Vector2.cs +++ b/Game/Assets/Scripts/Model/Vector2.cs @@ -78,6 +78,22 @@ namespace TransportGame.Model } } + /// + /// Gets the normalized vector raised to second power + /// + /// + /// This is less computationally expensive (no need to calculate square root). + /// + /// Normalized vector + public Vector2 NormalizedSq + { + get + { + float len2 = LengthSq; + return new Vector2(X * X / len2, Y * Y / len2); + } + } + /// /// Rotates vector by given number of radians /// @@ -123,6 +139,16 @@ namespace TransportGame.Model return new Vector2(a.X - b.X, a.Y - b.Y); } + /// + /// Negation operator + /// + /// Vector + /// Negated vector + public static Vector2 operator -(Vector2 a) + { + return new Vector2(-a.X, -a.Y); + } + /// /// Multiply by constant /// @@ -188,6 +214,28 @@ namespace TransportGame.Model return a.X * b.X + a.Y * b.Y; } + /// + /// Returns the magnitude of the cross product between the two vectors (z considered 0) + /// + /// First vector + /// Second vector + /// Magnitude of cross product + public static float Cross(Vector2 a, Vector2 b) + { + return (a.X * b.Y) - (a.Y * b.X); + } + + /// + /// Tests if two vectors are colliniar + /// + /// a + /// b + /// True if vectors are colliniar + public static bool AreColliniar(Vector2 a, Vector2 b) + { + return Math.Abs(Cross(a, b)) < 1e-12; + } + /// /// Gets the vector corresponding with specified angle (in radians) /// diff --git a/Game/Assets/Scripts/Unity/InitializeScript.cs b/Game/Assets/Scripts/Unity/InitializeScript.cs index 52e2260..0d0b207 100644 --- a/Game/Assets/Scripts/Unity/InitializeScript.cs +++ b/Game/Assets/Scripts/Unity/InitializeScript.cs @@ -13,12 +13,10 @@ public class InitializeScript : MonoBehaviour // Load configuration Logger.Info("Loading configuration..."); ConfigurationManager.LoadConfiguration(); - Logger.Info("Finished loading configuration."); // Load biomes Logger.Info("Loading biomes..."); BiomeManager.LoadBiomes(); - Logger.Info("Finished loading biomes."); } // Update is called once per frame diff --git a/Game/Assets/Scripts/Unity/RoadMeshGenerator.cs b/Game/Assets/Scripts/Unity/RoadMeshGenerator.cs new file mode 100644 index 0000000..def8ec3 --- /dev/null +++ b/Game/Assets/Scripts/Unity/RoadMeshGenerator.cs @@ -0,0 +1,434 @@ +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 + } + } +} diff --git a/Game/Assets/Scripts/Unity/RoadMeshGenerator.cs.meta b/Game/Assets/Scripts/Unity/RoadMeshGenerator.cs.meta new file mode 100644 index 0000000..6da3234 --- /dev/null +++ b/Game/Assets/Scripts/Unity/RoadMeshGenerator.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 6515820b5324dbf4baa10c3a1a480eaa +timeCreated: 1432970903 +licenseType: Free +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Game/Assets/Scripts/Unity/TerrainGeneratorScript.cs b/Game/Assets/Scripts/Unity/TerrainGeneratorScript.cs index 373babd..3ce771d 100644 --- a/Game/Assets/Scripts/Unity/TerrainGeneratorScript.cs +++ b/Game/Assets/Scripts/Unity/TerrainGeneratorScript.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Linq; using TransportGame.Generator; using TransportGame.Model; +using TransportGame.Unity; using TransportGame.Utils; using UnityEngine; @@ -14,6 +15,7 @@ public class TerrainGeneratorScript : MonoBehaviour public int TerrainHeight = 1024; public GameObject WaterObject; public Texture2D[] Textures; + public Material RoadMaterial; // Use this for initialization void Start() @@ -101,55 +103,49 @@ public class TerrainGeneratorScript : MonoBehaviour } yield return null; - // Set up textures - //SetupSplatmaps(terrainData); + // Set up textures + Logger.Info("Setting up textures..."); + foreach (var i in SetupSplatmaps(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); - - // Debug - draw lines - if (map != null && map.RoadNetwork != null) - { - foreach (var segment in map.RoadNetwork.ArticulationSegments) - { - Color color = (segment.Value.LanesTo1 >= 3) ? Color.magenta : Color.red; - Debug.DrawLine(new Vector3(segment.Value.Terminal1.Y, map.Biome.Height / 2, segment.Value.Terminal1.X), new Vector3(segment.Value.Terminal2.Y, map.Biome.Height / 2, segment.Value.Terminal2.X), color, 10000); - } - - foreach (var node in map.RoadNetwork.Nodes) - { - Debug.DrawLine(new Vector3(node.Value.Y, map.Biome.Height / 2, node.Value.X), new Vector3(node.Value.Y, map.Biome.Height / 2 + 5, node.Value.X), Color.blue, 10000); - } - } } - private void SetupSplatmaps(TerrainData terrainData) + private float[,,] GenerateSplatData(int alphaWidth, int alphaHeight, int alphaLayers) { - float[, ,] splatData = new float[terrainData.alphamapWidth, terrainData.alphamapHeight, terrainData.alphamapLayers]; - Expression[] expressions = new Expression[terrainData.alphamapLayers]; + float[, ,] splatData = new float[alphaWidth, alphaHeight, alphaLayers]; + Expression[] expressions = new Expression[alphaLayers]; - for (int y = 0; y < terrainData.alphamapHeight; y++) - for (int x = 0; x < terrainData.alphamapWidth; x++) + for (int y = 0; y < alphaHeight; y++) + for (int x = 0; x < alphaWidth; x++) { - float y_01 = (float)y / (float)terrainData.alphamapHeight; - float x_01 = (float)x / (float)terrainData.alphamapWidth; + float y_01 = (float)y / (float)alphaHeight; + float x_01 = (float)x / (float)alphaWidth; int ix = Mathf.RoundToInt(x_01 * TerrainWidth); int iy = Mathf.RoundToInt(y_01 * TerrainHeight); // Get height float height = map.GetHeight(ix, iy); - + // Get steepness float steepness = map.GetSteepness(ix, iy); - + // Go through each texture layer - float[] weights = new float[terrainData.alphamapLayers]; + float[] weights = new float[alphaLayers]; float sum = 0; - for (int t = 0; t < terrainData.alphamapLayers; t++) + for (int t = 0; t < alphaLayers; t++) { // Set up expression if (expressions[t] == null) @@ -160,7 +156,7 @@ public class TerrainGeneratorScript : MonoBehaviour expressions[t].Variables["height"] = height; expressions[t].Variables["steepness"] = steepness; - expressions[t].Variables["maxHeight"] = terrainData.size.y; + expressions[t].Variables["maxHeight"] = map.Biome.Height; expressions[t].Variables["waterLevel"] = map.WaterLevel - 1f; // Obtain weight @@ -169,12 +165,25 @@ public class TerrainGeneratorScript : MonoBehaviour } // Normalize and copy weights - for (int t = 0; t < terrainData.alphamapLayers; t++) + for (int t = 0; t < alphaLayers; t++) { splatData[x, y, t] = weights[t] / sum; } } + return splatData; + } + + private IEnumerable SetupSplatmaps(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))) + yield return i; + terrainData.SetAlphamaps(0, 0, splatData); } diff --git a/Game/Assets/Scripts/Utils/Logger.cs b/Game/Assets/Scripts/Utils/Logger.cs index eb94805..becc142 100644 --- a/Game/Assets/Scripts/Utils/Logger.cs +++ b/Game/Assets/Scripts/Utils/Logger.cs @@ -61,6 +61,10 @@ namespace TransportGame.Utils case Level.Critical: UnityEngine.Debug.LogError(String.Format(format, args)); break; + + case Level.Info: + UnityEngine.Debug.Log(String.Format(format, args)); + break; } } } diff --git a/Game/Game-csharp.sln b/Game/Game-csharp.sln index f961dca..b14d0ca 100644 --- a/Game/Game-csharp.sln +++ b/Game/Game-csharp.sln @@ -23,7 +23,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution + GlobalSection(MonoDevelopProperties) = preSolution StartupItem = Assembly-CSharp.csproj Policies = $0 $0.TextStylePolicy = $1 diff --git a/Game/Game.sln b/Game/Game.sln index 4705d3c..df6f52f 100644 --- a/Game/Game.sln +++ b/Game/Game.sln @@ -29,7 +29,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution + GlobalSection(MonoDevelopProperties) = preSolution StartupItem = Assembly-CSharp.csproj Policies = $0 $0.TextStylePolicy = $1 diff --git a/Game/UnityVS.Game.CSharp.csproj b/Game/UnityVS.Game.CSharp.csproj index 157c132..d0b6ec6 100644 --- a/Game/UnityVS.Game.CSharp.csproj +++ b/Game/UnityVS.Game.CSharp.csproj @@ -7,16 +7,14 @@ 2.0 {02576F1D-BE9C-CFA7-763D-1EBF63B36977} Library - - + Assembly-CSharp 512 {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} .NETFramework v3.5 Unity Subset v3.5 - - + Game:1 StandaloneWindows64:19 5.0.1f1 @@ -90,6 +88,7 @@ + @@ -109,8 +108,5 @@ - - - - \ No newline at end of file +