Initial attempt at road generation
This commit is contained in:
		@@ -32,9 +32,10 @@ namespace TransportGame.Generator
 | 
			
		||||
            populationGen.Generate(map);
 | 
			
		||||
 | 
			
		||||
            // Generate roads
 | 
			
		||||
            // TODO: Generate roads
 | 
			
		||||
            RoadGenerator roadGenerator = new RoadGenerator();
 | 
			
		||||
            roadGenerator.Generate(map);
 | 
			
		||||
 | 
			
		||||
            Logger.DumpMap(map, "withpop.map");
 | 
			
		||||
            Logger.DumpMap(map, "withroads.map");
 | 
			
		||||
 | 
			
		||||
            // Done
 | 
			
		||||
            return map;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,17 +3,145 @@ using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using TransportGame.Model;
 | 
			
		||||
using TransportGame.Model.Road;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace Assets.Scripts.Generator
 | 
			
		||||
namespace TransportGame.Generator
 | 
			
		||||
{
 | 
			
		||||
    public class RoadGenerator
 | 
			
		||||
    {
 | 
			
		||||
        System.Random random = new System.Random();
 | 
			
		||||
        Map map;
 | 
			
		||||
 | 
			
		||||
        const int segmentCountLimit = 100;
 | 
			
		||||
        const int maxSegmentLength = 20;
 | 
			
		||||
        const int minSegmentLength = 1;
 | 
			
		||||
 | 
			
		||||
        public RoadGenerator()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Generate(Map map)
 | 
			
		||||
        {
 | 
			
		||||
            this.map = map;
 | 
			
		||||
            map.RoadNetwork = new RoadNetwork();
 | 
			
		||||
 | 
			
		||||
            Queue<RoadNode> queue = new Queue<RoadNode>();
 | 
			
		||||
            RoadNode first = map.RoadNetwork.CreateNode();
 | 
			
		||||
            queue.Enqueue(first);
 | 
			
		||||
            
 | 
			
		||||
            // Set starting point
 | 
			
		||||
            if (map.PopulationCenters != null && map.PopulationCenters.Count > 0)
 | 
			
		||||
            {
 | 
			
		||||
                first.X = map.PopulationCenters.First().X;
 | 
			
		||||
                first.Y = map.PopulationCenters.First().Y;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                first.X = random.Next(map.Width);
 | 
			
		||||
                first.Y = random.Next(map.Height);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Go through each node
 | 
			
		||||
            for (int i = 0; i < segmentCountLimit && queue.Count > 0; i++)
 | 
			
		||||
            {
 | 
			
		||||
                RoadNode node = queue.Dequeue();
 | 
			
		||||
 | 
			
		||||
                // Produce solutions based on global goals
 | 
			
		||||
                foreach (var next in GlobalGoals(node))
 | 
			
		||||
                {
 | 
			
		||||
                    if (CheckLocalConstraints(node, next))
 | 
			
		||||
                    {
 | 
			
		||||
                        // Next is a temporary node - create a node on the road network
 | 
			
		||||
                        var other = map.RoadNetwork.CreateNode();
 | 
			
		||||
                        other.X = next.X;
 | 
			
		||||
                        other.Y = next.Y;
 | 
			
		||||
 | 
			
		||||
                        // Create a segment
 | 
			
		||||
                        var segment = map.RoadNetwork.CreateArticulationSegment();
 | 
			
		||||
 | 
			
		||||
                        // Assign IDs
 | 
			
		||||
                        segment.Terminal1Id = node.Id;
 | 
			
		||||
                        segment.Terminal2Id = other.Id;
 | 
			
		||||
                        node.ArticulationSegmentIds.Add(segment.Id);
 | 
			
		||||
                        other.ArticulationSegmentIds.Add(segment.Id);
 | 
			
		||||
 | 
			
		||||
                        // Enqueue node
 | 
			
		||||
                        queue.Enqueue(other);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<RoadNode> GlobalGoals(RoadNode node)
 | 
			
		||||
        {
 | 
			
		||||
            // Goal #1 - connect population centers
 | 
			
		||||
            foreach (var popCenter in map.PopulationCenters.Skip(1))
 | 
			
		||||
            {
 | 
			
		||||
                // Get direction vector
 | 
			
		||||
                float dx = popCenter.X - node.X;
 | 
			
		||||
                float dy = popCenter.Y - node.Y;
 | 
			
		||||
 | 
			
		||||
                // Ignore if too close to population center
 | 
			
		||||
                if (Math.Abs(dx) <= 1 && Math.Abs(dy) <= 1)
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                // Calculate length of direction vector (we need to normalize it)
 | 
			
		||||
                float dlen = Mathf.Sqrt(dx * dx + dy * dy);
 | 
			
		||||
 | 
			
		||||
                // Length of segment
 | 
			
		||||
                int length = random.Next(minSegmentLength, maxSegmentLength);
 | 
			
		||||
 | 
			
		||||
                // Calculate coordinates
 | 
			
		||||
                yield return new RoadNode()
 | 
			
		||||
                {
 | 
			
		||||
                    X = node.X + length * dx / dlen,
 | 
			
		||||
                    Y = node.Y + length * dy / dlen
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Goal #2 - random segments depending on how populated is area - max 25% chance
 | 
			
		||||
            if (random.NextDouble() < map.GetPopulation(Convert.ToInt32(node.X), Convert.ToInt32(node.Y)) * 4)
 | 
			
		||||
            {
 | 
			
		||||
                // Generate direction vector
 | 
			
		||||
                float dx = Convert.ToSingle(random.NextDouble()) * 2 - 1;
 | 
			
		||||
                float dy = Convert.ToSingle(random.NextDouble()) * 2 - 1;
 | 
			
		||||
 | 
			
		||||
                int length = random.Next(minSegmentLength, maxSegmentLength);
 | 
			
		||||
 | 
			
		||||
                // Calculate coordinates
 | 
			
		||||
                yield return new RoadNode()
 | 
			
		||||
                {
 | 
			
		||||
                    X = node.X + dx * length,
 | 
			
		||||
                    Y = node.Y + dy * length
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public bool CheckLocalConstraints(RoadNode first, RoadNode second)
 | 
			
		||||
        {
 | 
			
		||||
            // Make sure point is inside map
 | 
			
		||||
            if (!map.IsInside(Convert.ToInt32(second.X), Convert.ToInt32(second.Y)))
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            // Cannot build on water
 | 
			
		||||
            if (map.IsWater(Convert.ToInt32(second.X), Convert.ToInt32(second.Y)))
 | 
			
		||||
                return false;
 | 
			
		||||
            
 | 
			
		||||
            // Check steepness
 | 
			
		||||
            int mix = Convert.ToInt32(Math.Min(first.X, second.X));
 | 
			
		||||
            int max = Convert.ToInt32(Math.Max(first.X, second.X));
 | 
			
		||||
            int miy = Convert.ToInt32(Math.Min(first.Y, second.Y));
 | 
			
		||||
            int may = Convert.ToInt32(Math.Max(first.Y, second.Y));
 | 
			
		||||
 | 
			
		||||
            for (int x = mix; x <= max; x++)
 | 
			
		||||
                for (int y = miy; y <= may; y++)
 | 
			
		||||
                {
 | 
			
		||||
                    if (map.GetSteepness(x, y) > 1)
 | 
			
		||||
                        return false;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -84,6 +84,16 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes the road network
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public RoadNetwork()
 | 
			
		||||
        {
 | 
			
		||||
            Nodes = new Dictionary<int, RoadNode>();
 | 
			
		||||
            ArticulationSegments = new Dictionary<int,RoadSegment>();
 | 
			
		||||
            IntersectionSegments = new Dictionary<int,RoadSegment>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a node and returns it
 | 
			
		||||
        /// </summary>
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user