Finished road generator
This commit is contained in:
		@@ -2,7 +2,7 @@
 | 
			
		||||
<biome>
 | 
			
		||||
	<name>Grassland</name>
 | 
			
		||||
	<height>100</height>
 | 
			
		||||
	<moisture min=".1" max=".5"/>
 | 
			
		||||
	<moisture min=".03" max=".3"/>
 | 
			
		||||
	<vegetationDensity min=".2" max=".5" />
 | 
			
		||||
  <textures>
 | 
			
		||||
    <texture src="grass" expr="0.5" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,8 @@
 | 
			
		||||
fileFormatVersion: 2
 | 
			
		||||
guid: 50d52558244dee444936bce1f2bd9e0a
 | 
			
		||||
guid: 04f446df0e76dde479e71408d721ffcc
 | 
			
		||||
timeCreated: 1432915085
 | 
			
		||||
licenseType: Free
 | 
			
		||||
TextScriptImporter:
 | 
			
		||||
  userData: 
 | 
			
		||||
  assetBundleName: 
 | 
			
		||||
  assetBundleVariant: 
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,6 @@ namespace TransportGame.Generator
 | 
			
		||||
            RoadGenerator roadGenerator = new RoadGenerator();
 | 
			
		||||
            roadGenerator.Generate(map);
 | 
			
		||||
 | 
			
		||||
            Logger.DumpMap(map, "withroads.map");
 | 
			
		||||
 | 
			
		||||
            // Done
 | 
			
		||||
            return map;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,6 @@ using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using TransportGame.Model;
 | 
			
		||||
using TransportGame.Noise;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace TransportGame.Generator
 | 
			
		||||
{
 | 
			
		||||
@@ -14,9 +13,13 @@ namespace TransportGame.Generator
 | 
			
		||||
 | 
			
		||||
        public void Generate(Map map)
 | 
			
		||||
        {
 | 
			
		||||
            // Generate range
 | 
			
		||||
            float mp = (float)(map.Width * map.Height) / (1024 * 1024); // For 4k x 4k range should be around 900
 | 
			
		||||
            map.PopulationCenterRange = mp * 155f / 5f + 1490f / 3f;    // For 2k x 2k range should be around 600
 | 
			
		||||
 | 
			
		||||
            // Generate a number of points
 | 
			
		||||
            int maxPoints = map.Width * map.Height / (1024 * 512);
 | 
			
		||||
            int points = random.Next(maxPoints / 4, maxPoints);
 | 
			
		||||
            int maxPoints = 16 * (int) Math.Sqrt(mp);
 | 
			
		||||
            int points = random.Next(maxPoints / 2, maxPoints);
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < points; ++i)
 | 
			
		||||
            {
 | 
			
		||||
@@ -24,12 +27,12 @@ namespace TransportGame.Generator
 | 
			
		||||
 | 
			
		||||
                do
 | 
			
		||||
                {
 | 
			
		||||
                    px = random.Next(map.Width);
 | 
			
		||||
                    py = random.Next(map.Height);
 | 
			
		||||
                    px = random.Next(map.Width / 6, 5 * map.Width / 6);
 | 
			
		||||
                    py = random.Next(map.Height / 6, 5 * map.Height / 6);
 | 
			
		||||
                }
 | 
			
		||||
                while (map.IsWater(px, py));
 | 
			
		||||
 | 
			
		||||
                map.PopulationCenters.Add(new Point(px, py));
 | 
			
		||||
                map.PopulationCenters.Add(new Vector2(px, py));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,142 +4,383 @@ using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using TransportGame.Model;
 | 
			
		||||
using TransportGame.Model.Road;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
using TransportGame.Utils;
 | 
			
		||||
using Vector2 = TransportGame.Model.Vector2;
 | 
			
		||||
 | 
			
		||||
namespace TransportGame.Generator
 | 
			
		||||
{
 | 
			
		||||
    public class RoadGenerator
 | 
			
		||||
    {
 | 
			
		||||
        class RoadGeneratorSegment
 | 
			
		||||
        {
 | 
			
		||||
            public RoadNode Terminal1;
 | 
			
		||||
            public Vector2 Terminal2Pos;
 | 
			
		||||
            public RoadNode Terminal2;
 | 
			
		||||
            public bool Highway;
 | 
			
		||||
            public int Time;
 | 
			
		||||
 | 
			
		||||
            public RoadGeneratorSegment(RoadNode term1, Vector2 term2pos, bool highway, int time = 0)
 | 
			
		||||
            {
 | 
			
		||||
                Terminal1 = term1;
 | 
			
		||||
                Terminal2Pos = term2pos;
 | 
			
		||||
                Highway = highway;
 | 
			
		||||
                Time = time;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            public override string ToString()
 | 
			
		||||
            {
 | 
			
		||||
                string str = String.Format("(gensegment, {0}->", Terminal1);
 | 
			
		||||
 | 
			
		||||
                if (Terminal2 == null)
 | 
			
		||||
                    str += Terminal2Pos.ToString();
 | 
			
		||||
                else str += Terminal2.ToString();
 | 
			
		||||
 | 
			
		||||
                str += ", ";
 | 
			
		||||
                if (Highway)
 | 
			
		||||
                    str += "highway, ";
 | 
			
		||||
                str += String.Format("time={0})", Time);
 | 
			
		||||
 | 
			
		||||
                return str;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        QuadTree<RoadNode> qtree;
 | 
			
		||||
        List<RoadGeneratorSegment> queue;
 | 
			
		||||
 | 
			
		||||
        System.Random random = new System.Random();
 | 
			
		||||
        Map map;
 | 
			
		||||
 | 
			
		||||
        const int segmentCountLimit = 100;
 | 
			
		||||
        const int maxSegmentLength = 20;
 | 
			
		||||
        const int minSegmentLength = 1;
 | 
			
		||||
        const float HighwaySegmentLength = 60;
 | 
			
		||||
        const float DefaultBranchPopulationTreshold = 0.12f;
 | 
			
		||||
        const float DefaultBranchProbability = 0.2f;
 | 
			
		||||
        const float DefaultSegmentLength = 24;
 | 
			
		||||
        const float SteepnessLimit = 10;
 | 
			
		||||
        const float SlopeLimit = (float)Math.PI / 6;
 | 
			
		||||
        const float RoadSegmentAngleLimit = (float)Math.PI / 4;
 | 
			
		||||
        const float RoadSnapDistance = 19;
 | 
			
		||||
        const float MinNodeDistance = 12;
 | 
			
		||||
        const int MaximumRandomStraightAngle = 45; // in degrees
 | 
			
		||||
        const int MaximumBranchAngleVariation = 12; // in degrees
 | 
			
		||||
        const float HighwayBranchPopulationTreshold = .4f; // 0..1
 | 
			
		||||
        const float HighwayBranchProbability = .01f;
 | 
			
		||||
        const int HighwayBranchDelay = 3;
 | 
			
		||||
        const int MaximumIntersectingRoads = 5;
 | 
			
		||||
 | 
			
		||||
        public RoadGenerator()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Generate(Map map)
 | 
			
		||||
        public void Initialize(Map map)
 | 
			
		||||
        {
 | 
			
		||||
            this.map = map;
 | 
			
		||||
            map.RoadNetwork = new RoadNetwork();
 | 
			
		||||
            qtree = new QuadTree<RoadNode>(0, 0, map.Width, map.Height);
 | 
			
		||||
            queue = new List<RoadGeneratorSegment>();
 | 
			
		||||
 | 
			
		||||
            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)
 | 
			
		||||
            // Generate positions
 | 
			
		||||
            Vector2 center = new Vector2(map.Width / 2, map.Height / 2);
 | 
			
		||||
            int maxDistanceFromCenter = map.Width / 3;
 | 
			
		||||
 | 
			
		||||
            Vector2 p0, p1, p2; // p2 goes in opposite direction
 | 
			
		||||
 | 
			
		||||
            do
 | 
			
		||||
            {
 | 
			
		||||
                first.X = map.PopulationCenters.First().X;
 | 
			
		||||
                first.Y = map.PopulationCenters.First().Y;
 | 
			
		||||
                // Generate point close to center of the map
 | 
			
		||||
                float gen_x = random.Next(-maxDistanceFromCenter, maxDistanceFromCenter) + random.NextSingle();
 | 
			
		||||
                float gen_y = random.Next(-maxDistanceFromCenter, maxDistanceFromCenter) + random.NextSingle();
 | 
			
		||||
 | 
			
		||||
                p0 = center + new Vector2(gen_x, gen_y);
 | 
			
		||||
 | 
			
		||||
                // Generate a random direction
 | 
			
		||||
                Vector2 dir = Vector2.FromDegrees(random.Next(360)) * (HighwaySegmentLength / 2);
 | 
			
		||||
                p1 = p0 + dir;
 | 
			
		||||
                p2 = p0 - dir;
 | 
			
		||||
 | 
			
		||||
            } while (IsObstacle(p0) || IsObstacle(p1) || IsObstacle(p2));
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("Generated initial segment: {0} -> {1}", p0, p1);
 | 
			
		||||
            // Logger.Info("Generated initial segment: {0} -> {1}", p0, p2);
 | 
			
		||||
 | 
			
		||||
            // Create root node
 | 
			
		||||
            var node0 = map.RoadNetwork.CreateNode(p0);
 | 
			
		||||
            qtree.Add(node0);
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("Generated root node: {0}", node0);
 | 
			
		||||
 | 
			
		||||
            // Create & enqueue segments
 | 
			
		||||
            queue.Add(new RoadGeneratorSegment(node0, p1, true));
 | 
			
		||||
            queue.Add(new RoadGeneratorSegment(node0, p2, true));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Step()
 | 
			
		||||
        {
 | 
			
		||||
            // Logger.Info(">>> BEGAN STEP <<<");
 | 
			
		||||
 | 
			
		||||
            var segment = queue.OrderBy(x => x.Time).First();
 | 
			
		||||
            queue.Remove(segment);
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("Dequeued segment {0}", segment);
 | 
			
		||||
 | 
			
		||||
            // Check local constraints
 | 
			
		||||
            if (CheckLocalConstraints(segment))
 | 
			
		||||
            {
 | 
			
		||||
                // Logger.Info("Local constraints check succeeded.");
 | 
			
		||||
                RoadSegment createdSegment;
 | 
			
		||||
 | 
			
		||||
                // Finish to create segment
 | 
			
		||||
                if (segment.Terminal2 != null)
 | 
			
		||||
                    createdSegment = map.RoadNetwork.CreateArticulationSegment(segment.Terminal1, segment.Terminal2);
 | 
			
		||||
 | 
			
		||||
                else
 | 
			
		||||
                    createdSegment = map.RoadNetwork.CreateArticulationSegment(segment.Terminal1, segment.Terminal2Pos);
 | 
			
		||||
 | 
			
		||||
                qtree.Add(createdSegment.Terminal2);
 | 
			
		||||
                createdSegment.LanesTo1 = createdSegment.LanesTo2 = (segment.Highway) ? 3 : 1;
 | 
			
		||||
 | 
			
		||||
                // Use global goals to get new segments
 | 
			
		||||
                foreach (var newSegment in GlobalGoals(createdSegment))
 | 
			
		||||
                {
 | 
			
		||||
                    newSegment.Time += segment.Time + 1;
 | 
			
		||||
                    queue.Add(newSegment);
 | 
			
		||||
                    // Logger.Info("Added segment to queue: {0}", newSegment);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                first.X = random.Next(map.Width);
 | 
			
		||||
                first.Y = random.Next(map.Height);
 | 
			
		||||
                // Logger.Info("Local constraints check failed!");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            // Go through each node
 | 
			
		||||
            for (int i = 0; i < segmentCountLimit && queue.Count > 0; i++)
 | 
			
		||||
        private bool IsObstacle(Vector2 p)
 | 
			
		||||
        {
 | 
			
		||||
            return !map.IsInside(p.X, p.Y) || map.IsWater(p.X, p.Y) || map.GetSteepness(p.X, p.Y) > SteepnessLimit;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Generate(Map map)
 | 
			
		||||
        {
 | 
			
		||||
            Initialize(map);
 | 
			
		||||
 | 
			
		||||
            int iterationCount = (map.Width * map.Height) / 512;
 | 
			
		||||
 | 
			
		||||
            for (int i = 0; i < iterationCount && queue.Count > 0; i++)
 | 
			
		||||
                Step();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<RoadGeneratorSegment> GlobalGoals(RoadSegment segment)
 | 
			
		||||
        {
 | 
			
		||||
            Vector2 prevPos = segment.Terminal2.Position;
 | 
			
		||||
            Vector2 dir = (segment.Terminal2.Position - segment.Terminal1.Position).Normalized;
 | 
			
		||||
            bool highway = (segment.LanesTo1 >= 3);
 | 
			
		||||
            bool highwayBranched = false;
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("> Computing global goals. prevPos={0}, dir={1}, highway={2}", prevPos, dir, highway);
 | 
			
		||||
 | 
			
		||||
            // Going straight
 | 
			
		||||
            Vector2 straight = prevPos + dir * ((highway) ? HighwaySegmentLength : DefaultSegmentLength);
 | 
			
		||||
            float straightPopulation = map.GetPopulation(straight);
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("> Straight={0} StraightPopulation={1}", straight, straightPopulation);
 | 
			
		||||
 | 
			
		||||
            // Highways...
 | 
			
		||||
            if (highway)
 | 
			
		||||
            {
 | 
			
		||||
                RoadNode node = queue.Dequeue();
 | 
			
		||||
                // Logger.Info("> Highway case:");
 | 
			
		||||
 | 
			
		||||
                // Produce solutions based on global goals
 | 
			
		||||
                foreach (var next in GlobalGoals(node))
 | 
			
		||||
                Vector2 randomStraight = prevPos + HighwaySegmentLength * dir.RotateDeg(random.Next(-MaximumRandomStraightAngle, MaximumRandomStraightAngle));
 | 
			
		||||
                float randomPopulation = map.GetPopulation(randomStraight);
 | 
			
		||||
 | 
			
		||||
                // Logger.Info("> RandomStraight={0} RandomPopulation={1}", randomStraight, randomPopulation);
 | 
			
		||||
 | 
			
		||||
                if (randomPopulation > straightPopulation)
 | 
			
		||||
                {
 | 
			
		||||
                    if (CheckLocalConstraints(node, next))
 | 
			
		||||
                    // Logger.Info("> Yielding random straight vector.");
 | 
			
		||||
                    yield return new RoadGeneratorSegment(segment.Terminal2, randomStraight, highway);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    // Logger.Info("> Yielding straight vector.");
 | 
			
		||||
                    yield return new RoadGeneratorSegment(segment.Terminal2, straight, highway);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Branch highway
 | 
			
		||||
                if (Math.Max(straightPopulation, randomPopulation) > HighwayBranchPopulationTreshold)
 | 
			
		||||
                {
 | 
			
		||||
                    // Logger.Info("> Above treshold. Branching...");
 | 
			
		||||
                    if (random.NextSingle() < HighwayBranchProbability)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 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;
 | 
			
		||||
                        Vector2 leftBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(-90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation));
 | 
			
		||||
                        yield return new RoadGeneratorSegment(segment.Terminal2, leftBranch, highway, HighwayBranchDelay);
 | 
			
		||||
 | 
			
		||||
                        // Create a segment
 | 
			
		||||
                        var segment = map.RoadNetwork.CreateArticulationSegment();
 | 
			
		||||
                        highwayBranched = true;
 | 
			
		||||
                        // Logger.Info("> Branch to the left: {0}", leftBranch);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (random.NextSingle() < HighwayBranchProbability)
 | 
			
		||||
                    {
 | 
			
		||||
                        Vector2 rightBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation));
 | 
			
		||||
                        yield return new RoadGeneratorSegment(segment.Terminal2, rightBranch, highway, HighwayBranchDelay);
 | 
			
		||||
 | 
			
		||||
                        // 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);
 | 
			
		||||
                        highwayBranched = true;
 | 
			
		||||
                        // Logger.Info("> Branch to the right: {0}", rightBranch);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<RoadNode> GlobalGoals(RoadNode node)
 | 
			
		||||
        {
 | 
			
		||||
            // Goal #1 - connect population centers
 | 
			
		||||
            foreach (var popCenter in map.PopulationCenters.Skip(1))
 | 
			
		||||
                // Don't allow more branches
 | 
			
		||||
                if (highwayBranched)
 | 
			
		||||
                    yield break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            else if (random.NextSingle() < straightPopulation)
 | 
			
		||||
            {
 | 
			
		||||
                // 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
 | 
			
		||||
                };
 | 
			
		||||
                // Logger.Info("> Not highway. Yielding straight vector.");
 | 
			
		||||
                yield return new RoadGeneratorSegment(segment.Terminal2, straight, false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 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)
 | 
			
		||||
            // Branch normal road
 | 
			
		||||
            if (straightPopulation > DefaultBranchPopulationTreshold)
 | 
			
		||||
            {
 | 
			
		||||
                // 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()
 | 
			
		||||
                // Logger.Info("Straight population above branch treshold. Branching...");
 | 
			
		||||
                if (random.NextSingle() < DefaultBranchProbability * straightPopulation)
 | 
			
		||||
                {
 | 
			
		||||
                    X = node.X + dx * length,
 | 
			
		||||
                    Y = node.Y + dy * length
 | 
			
		||||
                };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
                    Vector2 leftBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(-90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation));
 | 
			
		||||
                    yield return new RoadGeneratorSegment(segment.Terminal2, leftBranch, false, (highway) ? HighwayBranchDelay : 0);
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
                    // Logger.Info("> Branch to the left: {0}", leftBranch);
 | 
			
		||||
                }
 | 
			
		||||
                if (random.NextSingle() < DefaultBranchProbability * straightPopulation)
 | 
			
		||||
                {
 | 
			
		||||
                    Vector2 rightBranch = prevPos + HighwaySegmentLength * dir.RotateDeg(90 + random.Next(-MaximumBranchAngleVariation, MaximumBranchAngleVariation));
 | 
			
		||||
                    yield return new RoadGeneratorSegment(segment.Terminal2, rightBranch, false, (highway) ? HighwayBranchDelay : 0);
 | 
			
		||||
 | 
			
		||||
                    // Logger.Info("> Branch to the right: {0}", rightBranch);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool CheckLocalConstraints(RoadGeneratorSegment segment)
 | 
			
		||||
        {
 | 
			
		||||
            // Logger.Info("Checking local constraints...");
 | 
			
		||||
 | 
			
		||||
            // Constraint #1: check for obstacles
 | 
			
		||||
            if (IsObstacle(segment.Terminal2Pos))
 | 
			
		||||
            {
 | 
			
		||||
                // Logger.Info("Obstacle.");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Constraint #2: slope
 | 
			
		||||
            float segmentLength = (segment.Highway) ? HighwaySegmentLength : DefaultSegmentLength;
 | 
			
		||||
            float levelDiff = map.GetHeight((int)segment.Terminal1.X, (int)segment.Terminal1.Y) -
 | 
			
		||||
                map.GetHeight((int)segment.Terminal2Pos.X, (int)segment.Terminal2Pos.Y);
 | 
			
		||||
            float sinSlope = Math.Abs(levelDiff) / segmentLength;
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("Level difference is {0}, slope is (rads) {1}", levelDiff, Math.Asin(sinSlope));
 | 
			
		||||
 | 
			
		||||
            if (Math.Asin(sinSlope) > SlopeLimit)
 | 
			
		||||
            {
 | 
			
		||||
                // Logger.Info("Slope too big!");
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Constraint #3: Number of intersecting roads
 | 
			
		||||
            if (segment.Terminal1.ArticulationSegmentIds.Count > MaximumIntersectingRoads)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            // Constraint #4: intersections & snapping
 | 
			
		||||
            Rectangle queryArea = new Rectangle(
 | 
			
		||||
                Math.Min(segment.Terminal1.X, segment.Terminal2Pos.X) - 3 * HighwaySegmentLength,
 | 
			
		||||
                Math.Min(segment.Terminal1.Y, segment.Terminal2Pos.Y) - 3 * HighwaySegmentLength,
 | 
			
		||||
                Math.Max(segment.Terminal1.X, segment.Terminal2Pos.X) + 3 * HighwaySegmentLength,
 | 
			
		||||
                Math.Max(segment.Terminal1.Y, segment.Terminal2Pos.Y) + 3 * HighwaySegmentLength);
 | 
			
		||||
 | 
			
		||||
            // Logger.Info("Searching area {0} for intersecting segments.", queryArea);
 | 
			
		||||
 | 
			
		||||
            IEnumerable<int> segmentIds = Enumerable.Empty<int>();
 | 
			
		||||
 | 
			
		||||
            // Look for nearby segments
 | 
			
		||||
            foreach (var node in qtree.Query(queryArea))
 | 
			
		||||
            {
 | 
			
		||||
                // Logger.Info("Found node: {0}", node);
 | 
			
		||||
                if (node == segment.Terminal1)
 | 
			
		||||
                {
 | 
			
		||||
                    // Logger.Info("Node is originating node. Will ignore.");
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Too close to another node in the area
 | 
			
		||||
                if ((node.Position - segment.Terminal2Pos).LengthSq < MinNodeDistance * MinNodeDistance)
 | 
			
		||||
                    return false;
 | 
			
		||||
 | 
			
		||||
                segmentIds = segmentIds.Concat(node.ArticulationSegmentIds);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 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);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                // Logger.Info("Found segment: {0}. Will test intersection between segments {0} and {1}", other, line1, line2);
 | 
			
		||||
 | 
			
		||||
                Vector2? inters = LineSegment.Intersect(line1, line2);
 | 
			
		||||
 | 
			
		||||
                // Case #1: there is an intersection with another segment. We cut the rest of the segment
 | 
			
		||||
                if (inters.HasValue && inters.Value != segment.Terminal1.Position)
 | 
			
		||||
                {
 | 
			
		||||
                    // Logger.Info("Found intersection point: {0}", inters);
 | 
			
		||||
 | 
			
		||||
                    // Check angle between segments
 | 
			
		||||
                    float cos = Vector2.Dot((line1.P1 - line1.P0).Normalized, (line2.P1 - line2.P0).Normalized);
 | 
			
		||||
                    if (Math.Abs(Math.Acos(cos)) < RoadSegmentAngleLimit)
 | 
			
		||||
                    {
 | 
			
		||||
                        // Logger.Info("Angle between segments is too small ({0} rads)", Math.Abs(Math.Acos(cos)));
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Split segment
 | 
			
		||||
                    var newNode = map.RoadNetwork.SplitArticulationSegment(other, inters.Value);
 | 
			
		||||
                    segment.Terminal2Pos = inters.Value;
 | 
			
		||||
                    segment.Terminal2 = newNode;
 | 
			
		||||
                    // Logger.Info("Performed split in point: {0}", newNode);
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
                else // Logger.Info("Does not intersect.");
 | 
			
		||||
 | 
			
		||||
                    // Case #2: no intersection, but the point is close enough to an existing intersection
 | 
			
		||||
                    if ((segment.Terminal2Pos - other.Terminal2.Position).LengthSq <= RoadSnapDistance * RoadSnapDistance)
 | 
			
		||||
                    {
 | 
			
		||||
                        // 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);
 | 
			
		||||
                            if (Math.Abs(Math.Acos(cos)) < RoadSegmentAngleLimit)
 | 
			
		||||
                            {
 | 
			
		||||
                                // Logger.Info("Angle between segments is too small ({0} rads)", Math.Abs(Math.Acos(cos)));
 | 
			
		||||
                                return false;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        // Logger.Info("Point is close to existing intersection: {0}, will snap.", other.Terminal2);
 | 
			
		||||
                        segment.Terminal2Pos = other.Terminal2.Position;
 | 
			
		||||
                        segment.Terminal2 = other.Terminal2;
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                //// TODO: Case #3: the point is close enough to an existing road segment
 | 
			
		||||
                //float dist = LineSegment.Distance(line2, segment.Terminal2Pos);
 | 
			
		||||
                //if (dist < RoadSnapDistance)
 | 
			
		||||
                //{
 | 
			
		||||
                //    float proj0 = (float)Math.Sqrt((line2.P0 - segment.Terminal2Pos).LengthSq - dist * dist);
 | 
			
		||||
                //    float percent = proj0 / 
 | 
			
		||||
                //}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,9 @@ namespace TransportGame.Model
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlArray("populationCenters")]
 | 
			
		||||
        [XmlArrayItem("center")]
 | 
			
		||||
        public List<Point> PopulationCenters { get; set; }
 | 
			
		||||
        public List<Vector2> PopulationCenters { get; set; }
 | 
			
		||||
 | 
			
		||||
        public float PopulationCenterRange { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the articulation road network
 | 
			
		||||
@@ -93,7 +95,7 @@ namespace TransportGame.Model
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        public Map()
 | 
			
		||||
        {
 | 
			
		||||
            PopulationCenters = new List<Point>();
 | 
			
		||||
            PopulationCenters = new List<Vector2>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -104,7 +106,7 @@ namespace TransportGame.Model
 | 
			
		||||
        public Map(int width, int height)
 | 
			
		||||
        {
 | 
			
		||||
            heightmap = new float[width, height];
 | 
			
		||||
            PopulationCenters = new List<Point>();
 | 
			
		||||
            PopulationCenters = new List<Vector2>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #endregion
 | 
			
		||||
@@ -137,9 +139,19 @@ namespace TransportGame.Model
 | 
			
		||||
        /// <param name="x">X</param>
 | 
			
		||||
        /// <param name="y">Y</param>
 | 
			
		||||
        /// <returns></returns>
 | 
			
		||||
        public bool IsWater(int x, int y)
 | 
			
		||||
        public bool IsWater(float x, float y)
 | 
			
		||||
        {
 | 
			
		||||
            return GetHeight(x, y) <= WaterLevel;
 | 
			
		||||
            return GetHeight((int)x, (int)y) <= WaterLevel;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Returns true if specified cell is a water cell
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="p">Position vector</param>
 | 
			
		||||
        /// <returns></returns>
 | 
			
		||||
        public bool IsWater(Vector2 p)
 | 
			
		||||
        {
 | 
			
		||||
            return IsWater(p.X, p.Y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
@@ -148,11 +160,21 @@ namespace TransportGame.Model
 | 
			
		||||
        /// <param name="x">X</param>
 | 
			
		||||
        /// <param name="y">Y</param>
 | 
			
		||||
        /// <returns>True if coordinates are inside the map</returns>
 | 
			
		||||
        public bool IsInside(int x, int y)
 | 
			
		||||
        public bool IsInside(float x, float y)
 | 
			
		||||
        {
 | 
			
		||||
            return x >= 0 && y >= 0 && x < Width && y < Height;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Returns true if given coordinates is inside the map
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="p">Position vector</param>
 | 
			
		||||
        /// <returns>True if coordinates are inside the map</returns>
 | 
			
		||||
        public bool IsInside(Vector2 p)
 | 
			
		||||
        {
 | 
			
		||||
            return IsInside(p.X, p.Y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets steepness in specified point
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -170,27 +192,56 @@ namespace TransportGame.Model
 | 
			
		||||
            return dx * dx + dy * dy;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets steepness in specified point
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="x">X</param>
 | 
			
		||||
        /// <param name="y">Y</param>
 | 
			
		||||
        /// <returns>Steepness</returns>
 | 
			
		||||
        public float GetSteepness(float x, float y)
 | 
			
		||||
        {
 | 
			
		||||
            return GetSteepness((int)x, (int)y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets steepness in specified point
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="pos">Position</param>
 | 
			
		||||
        /// <returns>Steepness</returns>
 | 
			
		||||
        public float GetSteepness(Vector2 pos)
 | 
			
		||||
        {
 | 
			
		||||
            return GetSteepness((int)pos.X, (int)pos.Y);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets population using terrain coordinates
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="x">X</param>
 | 
			
		||||
        /// <param name="y">Y</param>
 | 
			
		||||
        /// <returns>Population</returns>
 | 
			
		||||
        public float GetPopulation(int x, int y)
 | 
			
		||||
        public float GetPopulation(float x, float y)
 | 
			
		||||
        {
 | 
			
		||||
            const int maxDistance = 400;
 | 
			
		||||
            float value = 0;
 | 
			
		||||
            return GetPopulation(new Vector2(x, y));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets population using terrain coordinates
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="p">Position</param>
 | 
			
		||||
        /// <returns>Population</returns>
 | 
			
		||||
        public float GetPopulation(Vector2 p)
 | 
			
		||||
        {
 | 
			
		||||
            float value = 0.05f;
 | 
			
		||||
 | 
			
		||||
            foreach (var point in PopulationCenters)
 | 
			
		||||
            {
 | 
			
		||||
                int x1 = x - point.X;
 | 
			
		||||
                int y1 = y - point.Y;
 | 
			
		||||
                Vector2 diff = p - point;
 | 
			
		||||
                float dist = diff.LengthSq;
 | 
			
		||||
 | 
			
		||||
                int dist = x1 * x1 + y1 * y1;
 | 
			
		||||
                if (dist < maxDistance * maxDistance)
 | 
			
		||||
                if (dist < PopulationCenterRange * PopulationCenterRange)
 | 
			
		||||
                {
 | 
			
		||||
                    float influence = 1 - (float)dist / (float)(maxDistance * maxDistance);
 | 
			
		||||
                    influence = Mathf.Pow(influence, 3); // Ease
 | 
			
		||||
                    float influence = 1 - (float)dist / (float)(PopulationCenterRange * PopulationCenterRange);
 | 
			
		||||
                    influence = Mathf.Pow(influence, 6) * 0.7f; // Ease
 | 
			
		||||
                    value = Mathf.Clamp01(value + influence);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Xml.Serialization;
 | 
			
		||||
using TransportGame.Utils;
 | 
			
		||||
 | 
			
		||||
namespace TransportGame.Model.Road
 | 
			
		||||
{
 | 
			
		||||
@@ -12,20 +13,19 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        private int lastNodeId = -1, lastSegmentId = -1;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the road nodes
 | 
			
		||||
        /// Gets the road nodes
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlIgnore]
 | 
			
		||||
        public Dictionary<int, RoadNode> Nodes { get; private set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the road segments for the articulation graph
 | 
			
		||||
        /// Gets the road segments for the articulation graph
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlIgnore]
 | 
			
		||||
        public Dictionary<int, RoadSegment> ArticulationSegments { get; private set; }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the road segments for the intersection graph
 | 
			
		||||
        /// Gets the road segments for the intersection graph
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlIgnore]
 | 
			
		||||
        public Dictionary<int, RoadSegment> IntersectionSegments { get; private set; }
 | 
			
		||||
@@ -44,7 +44,10 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            {
 | 
			
		||||
                Nodes.Clear();
 | 
			
		||||
                foreach (var node in value)
 | 
			
		||||
                {
 | 
			
		||||
                    node.ParentNetwork = this;
 | 
			
		||||
                    Nodes.Add(node.Id, node);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +65,10 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            {
 | 
			
		||||
                ArticulationSegments.Clear();
 | 
			
		||||
                foreach (var segment in value)
 | 
			
		||||
                {
 | 
			
		||||
                    segment.ParentNetwork = this;
 | 
			
		||||
                    ArticulationSegments.Add(segment.Id, segment);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -80,7 +86,10 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            {
 | 
			
		||||
                ArticulationSegments.Clear();
 | 
			
		||||
                foreach (var segment in value)
 | 
			
		||||
                {
 | 
			
		||||
                    segment.ParentNetwork = this;
 | 
			
		||||
                    ArticulationSegments.Add(segment.Id, segment);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    
 | 
			
		||||
@@ -97,8 +106,9 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a node and returns it
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Created node</returns>
 | 
			
		||||
        public RoadNode CreateNode()
 | 
			
		||||
        /// <param name="pos">Position</param>
 | 
			
		||||
        /// <returns></returns>
 | 
			
		||||
        public RoadNode CreateNode(Vector2 pos)
 | 
			
		||||
        {
 | 
			
		||||
            // Skip IDs that already exist
 | 
			
		||||
            while (Nodes.ContainsKey(++lastNodeId)) ;
 | 
			
		||||
@@ -107,18 +117,39 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            RoadNode node = new RoadNode()
 | 
			
		||||
            {
 | 
			
		||||
                Id = lastNodeId,
 | 
			
		||||
                ParentNetwork = this
 | 
			
		||||
                ParentNetwork = this,
 | 
			
		||||
                Position = pos
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            Nodes.Add(node.Id, node);
 | 
			
		||||
            return node;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a node and returns it
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Created node</returns>
 | 
			
		||||
        public RoadNode CreateNode()
 | 
			
		||||
        {
 | 
			
		||||
            return CreateNode(Vector2.Zero);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a segment and returns it
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <returns>Created segment</returns>
 | 
			
		||||
        public RoadSegment CreateArticulationSegment()
 | 
			
		||||
        {
 | 
			
		||||
            return CreateArticulationSegment(null, null);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a segment and returns it
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="term1">First terminal</param>
 | 
			
		||||
        /// <param name="term2">Second terminal</param>
 | 
			
		||||
        /// <returns>Created segment</returns>
 | 
			
		||||
        public RoadSegment CreateArticulationSegment(RoadNode term1, RoadNode term2)
 | 
			
		||||
        {
 | 
			
		||||
            // Skip IDs that already exist
 | 
			
		||||
            while (ArticulationSegments.ContainsKey(++lastSegmentId)) ;
 | 
			
		||||
@@ -127,13 +158,59 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            RoadSegment segment = new RoadSegment()
 | 
			
		||||
            {
 | 
			
		||||
                Id = lastSegmentId,
 | 
			
		||||
                ParentNetwork = this
 | 
			
		||||
                ParentNetwork = this,
 | 
			
		||||
                Terminal1 = term1,
 | 
			
		||||
                Terminal2 = term2
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // Set links
 | 
			
		||||
            if (term1 != null)
 | 
			
		||||
                term1.ArticulationSegmentIds.Add(segment.Id);
 | 
			
		||||
            
 | 
			
		||||
            if (term2 != null)
 | 
			
		||||
                term2.ArticulationSegmentIds.Add(segment.Id);
 | 
			
		||||
 | 
			
		||||
            ArticulationSegments.Add(segment.Id, segment);
 | 
			
		||||
            return segment;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an articulation segment
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="term1">First terminal</param>
 | 
			
		||||
        /// <param name="term2pos">Position of second terminal</param>
 | 
			
		||||
        /// <returns>Road segment</returns>
 | 
			
		||||
        public RoadSegment CreateArticulationSegment(RoadNode term1, Vector2 term2pos)
 | 
			
		||||
        {
 | 
			
		||||
            var term2 = CreateNode(term2pos);
 | 
			
		||||
            return CreateArticulationSegment(term1, term2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an articulation segment
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="term1pos">Position of first terminal</param>
 | 
			
		||||
        /// <param name="term2">Second terminal</param>
 | 
			
		||||
        /// <returns>Road segment</returns>
 | 
			
		||||
        public RoadSegment CreateArticulationSegment(Vector2 term1pos, RoadNode term2)
 | 
			
		||||
        {
 | 
			
		||||
            var term1 = CreateNode(term1pos);
 | 
			
		||||
            return CreateArticulationSegment(term1, term2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates an articulation segment
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="term1pos">Position of first terminal</param>
 | 
			
		||||
        /// <param name="term2pos">Position of second terminal</param>
 | 
			
		||||
        /// <returns>Road segment</returns>
 | 
			
		||||
        public RoadSegment CreateArticulationSegment(Vector2 term1pos, Vector2 term2pos)
 | 
			
		||||
        {
 | 
			
		||||
            var term1 = CreateNode(term1pos);
 | 
			
		||||
            var term2 = CreateNode(term2pos);
 | 
			
		||||
            return CreateArticulationSegment(term1, term2);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a segment and returns it
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -153,5 +230,35 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            IntersectionSegments.Add(segment.Id, segment);
 | 
			
		||||
            return segment;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Splits an articulation segment in two segments
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="segment"></param>
 | 
			
		||||
        /// <param name="point"></param>
 | 
			
		||||
        /// <returns>Newly created road node</returns>
 | 
			
		||||
        public RoadNode SplitArticulationSegment(RoadSegment segment, Vector2 point)
 | 
			
		||||
        {
 | 
			
		||||
            // Get current terminals
 | 
			
		||||
            var term1 = segment.Terminal1;
 | 
			
		||||
            var term2 = segment.Terminal2;
 | 
			
		||||
            int l1 = segment.LanesTo1, l2 = segment.LanesTo2;
 | 
			
		||||
 | 
			
		||||
            // Create new terminal
 | 
			
		||||
            var newTerm = CreateNode(point);
 | 
			
		||||
 | 
			
		||||
            // Delete exinsting segment
 | 
			
		||||
            term1.ArticulationSegmentIds.Remove(segment.Id);
 | 
			
		||||
            term2.ArticulationSegmentIds.Remove(segment.Id);
 | 
			
		||||
            ArticulationSegments.Remove(segment.Id);
 | 
			
		||||
 | 
			
		||||
            // Create split segments
 | 
			
		||||
            var seg1 = CreateArticulationSegment(term1, newTerm);
 | 
			
		||||
            var seg2 = CreateArticulationSegment(newTerm, term2);
 | 
			
		||||
            seg1.LanesTo1 = seg2.LanesTo1 = l1;
 | 
			
		||||
            seg1.LanesTo2 = seg2.LanesTo2 = l2;
 | 
			
		||||
 | 
			
		||||
            return newTerm;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ namespace TransportGame.Model.Road
 | 
			
		||||
    /// Road node
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    [XmlRoot("node")]
 | 
			
		||||
    public class RoadNode
 | 
			
		||||
    public class RoadNode : IPositionable
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets a unique identifier for this node
 | 
			
		||||
@@ -63,7 +63,7 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the adjacent setments
 | 
			
		||||
        /// Gets the adjacent segments
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlIgnore]
 | 
			
		||||
        public IEnumerable<RoadSegment> Segments
 | 
			
		||||
@@ -82,5 +82,27 @@ namespace TransportGame.Model.Road
 | 
			
		||||
            ArticulationSegmentIds = new List<int>();
 | 
			
		||||
            IntersectionSegmentIds = new List<int>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the position
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlIgnore]
 | 
			
		||||
        public Vector2 Position
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                return new Vector2(X, Y);
 | 
			
		||||
            }
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                X = value.X;
 | 
			
		||||
                Y = value.Y;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
        {
 | 
			
		||||
            return string.Format("(node id={0}, {1})", Id, Position);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,11 +44,11 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                return ParentNetwork.Nodes[Terminal1Id];
 | 
			
		||||
                return (Terminal1Id == -1) ? null : ParentNetwork.Nodes[Terminal1Id];
 | 
			
		||||
            }
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                Terminal1Id = value.Id;
 | 
			
		||||
                Terminal1Id = (value == null) ? -1 : value.Id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
@@ -60,11 +60,11 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        {
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                return ParentNetwork.Nodes[Terminal2Id];
 | 
			
		||||
                return (Terminal2Id == -1) ? null : ParentNetwork.Nodes[Terminal2Id];
 | 
			
		||||
            }
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                Terminal2Id = value.Id;
 | 
			
		||||
                Terminal2Id = (value == null) ? -1 : value.Id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -79,7 +79,7 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [XmlAttribute("lanesTo1")]
 | 
			
		||||
        public int LanesTo1 { get; set; }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes road segment
 | 
			
		||||
        /// </summary>
 | 
			
		||||
@@ -87,6 +87,13 @@ namespace TransportGame.Model.Road
 | 
			
		||||
        {
 | 
			
		||||
            LanesTo1 = 1;
 | 
			
		||||
            LanesTo2 = 1;
 | 
			
		||||
            Terminal1Id = -1;
 | 
			
		||||
            Terminal2Id = -1;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public override string ToString()
 | 
			
		||||
        {
 | 
			
		||||
            return string.Format("(segment id={0}, {1}->{2})", Id, Terminal1, Terminal2);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Xml.Serialization;
 | 
			
		||||
using UnityEngine;
 | 
			
		||||
 | 
			
		||||
namespace TransportGame.Model
 | 
			
		||||
@@ -21,12 +22,14 @@ namespace TransportGame.Model
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the X component
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public float X { get; private set; }
 | 
			
		||||
        [XmlAttribute("x")]
 | 
			
		||||
        public float X { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the Y component
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public float Y { get; private set; }
 | 
			
		||||
        [XmlAttribute("y")]
 | 
			
		||||
        public float Y { get; set; }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a vector2
 | 
			
		||||
 
 | 
			
		||||
@@ -39,10 +39,10 @@ public class TerrainGeneratorScript : MonoBehaviour
 | 
			
		||||
		};
 | 
			
		||||
		water.triangles = new[] { 0, 1, 2, 2, 1, 3 };
 | 
			
		||||
		water.uv = new[] {
 | 
			
		||||
			new Vector2(0, 0),
 | 
			
		||||
			new Vector2(0, 1),
 | 
			
		||||
			new Vector2(1, 0),
 | 
			
		||||
			new Vector2(1, 1)
 | 
			
		||||
			new UnityEngine.Vector2(0, 0),
 | 
			
		||||
			new UnityEngine.Vector2(0, 1),
 | 
			
		||||
			new UnityEngine.Vector2(1, 0),
 | 
			
		||||
			new UnityEngine.Vector2(1, 1)
 | 
			
		||||
		};
 | 
			
		||||
		water.RecalculateNormals();
 | 
			
		||||
 | 
			
		||||
@@ -102,7 +102,27 @@ public class TerrainGeneratorScript : MonoBehaviour
 | 
			
		||||
        yield return null;
 | 
			
		||||
 | 
			
		||||
		// Set up textures
 | 
			
		||||
        SetupSplatmaps(terrainData);
 | 
			
		||||
        //SetupSplatmaps(terrainData);
 | 
			
		||||
 | 
			
		||||
        // -- 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)
 | 
			
		||||
@@ -162,4 +182,18 @@ public class TerrainGeneratorScript : MonoBehaviour
 | 
			
		||||
	void Update()
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void OnGUI()
 | 
			
		||||
    {
 | 
			
		||||
        Event e = Event.current;
 | 
			
		||||
        if (e.type == EventType.KeyDown)
 | 
			
		||||
        {
 | 
			
		||||
            if (e.keyCode == KeyCode.Home)
 | 
			
		||||
            {
 | 
			
		||||
                Logger.Warning("Writing to file...");
 | 
			
		||||
                Logger.DumpMap(map, "map.map");
 | 
			
		||||
                Logger.Warning("Wrote map to file.");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user