Aetheria/Assets/Scripts/Galaxy.cs
2020-03-23 01:43:37 +00:00

262 lines
No EOL
8.1 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
//using MessagePack;
using UnityEngine;
using NaughtyAttributes;
using JM.LinqFaster;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using static NoiseFbm;
[CreateAssetMenu(menuName = "Aetheria/Galaxy")]
public class Galaxy : ScriptableObject
{
public GalaxyMapData MapData;
}
[Serializable]
public class GalaxyMapData
{
public int Arms = 4;
public float Twist = 10;
public float TwistPower = 2;
[HideInInspector]
public GalaxyMapLayerData StarDensity;
[HideInInspector]
public Dictionary<string,GalaxyMapLayerData> ResourceDensities = new Dictionary<string, GalaxyMapLayerData>();
//[HideInInspector]
public List<StarData> Stars = new List<StarData>();
}
public static class StarListExtensions
{
class DijkstraStar
{
public float Cost;
public DijkstraStar Parent;
public StarData Star;
}
public static List<StarData> FindPath(this List<StarData> stars, StarData source, StarData target, bool bestFirst = false)
{
SortedList<float,DijkstraStar> members = new SortedList<float,DijkstraStar>{{0,new DijkstraStar{Star = source}}};
List<DijkstraStar> searched = new List<DijkstraStar>();
while (true)
{
var s = members.FirstOrDefault(m => !searched.Contains(m.Value)).Value; // Lowest cost unsearched node
if (s == null) return null; // No vertices left unsearched
if (s.Star == target) // We found the path
{
Stack<DijkstraStar> path = new Stack<DijkstraStar>(); // Since we start at the end, use a LIFO collection
path.Push(s);
while(path.Peek().Parent!=null) // Keep pushing until we reach the start, which has no parent
path.Push(path.Peek().Parent);
return path.Select(dv => dv.Star).ToList();
}
// foreach (var dijkstraStar in s.Star.Links.Select(i => stars[i]) // All adjacent stars
// .Where(n => members.All(m => m.Value.Star != n)) // That are not members
// .Select(n => new DijkstraStar {Parent = s, Star = n, Cost = s.Cost + (s.Star.Position - n.Position).magnitude}))
// For each adjacent star (filter already visited stars unless heuristic is in use)
foreach (var dijkstraStar in s.Star.Links.WhereSelectF(i => !bestFirst || members.All(m => m.Value.Star != stars[i]),
// Cost is parent cost plus distance
i => new DijkstraStar {Parent = s, Star = stars[i], Cost = s.Cost + (s.Star.Position - stars[i].Position).magnitude}))
// Add new member to list, sorted by cost plus optional heuristic
members.Add(bestFirst ? dijkstraStar.Cost + (dijkstraStar.Star.Position - target.Position).magnitude : dijkstraStar.Cost, dijkstraStar);
searched.Add(s);
}
}
}
[Serializable]
public class StarData
{
public Vector2 Position;
public List<int> Links = new List<int>();
}
public static class NoiseFbm
{
public static float fBm(float2 p, int octaves, float frequency, float offset, float amplitude, float lacunarity, float gain)
{
float freq = frequency, amp = .5f;
float sum = 0;
for(int i = 0; i < octaves; i++)
{
sum += snoise(p * freq) * amp;
freq *= lacunarity;
amp *= gain;
}
return (sum + offset)*amplitude;
}
}
[Serializable]
public class GalaxyMapLayerData
{
public float CoreBoost = 1.05f;
public float CoreBoostOffset = .1f;
public float CoreBoostPower = 2.25f;
public float SpokeScale = 1;
public float SpokeOffset = 0;
public float EdgeReduction = 3;
public float NoiseOffset = 0;
public float NoiseAmplitude = 1.5f;
public float NoiseGain = .7f;
public float NoiseLacunarity = 2;
public int NoiseOctaves = 7;
public float NoiseFrequency = 1;
public float NoisePosition = 1337;
public float Evaluate(float2 uv, GalaxyMapData galaxy)
{
float2 offset = -float2(.5f, .5f)+uv;
float circle = (.5f-length(offset))*2;
float angle = pow(length(offset)*2,galaxy.TwistPower) * galaxy.Twist;
float2 twist = float2(offset.x*cos(angle) - offset.y*sin(angle), offset.x*sin(angle) + offset.y*cos(angle));
float atan = atan2(twist.y,twist.x);
float spokes = (sin(atan*galaxy.Arms) + SpokeOffset) * SpokeScale;
float noise = fBm(uv + float2(NoisePosition), NoiseOctaves, NoiseFrequency, NoiseOffset, NoiseAmplitude, NoiseLacunarity, NoiseGain);
float shape = lerp(spokes - EdgeReduction * length(offset), 1, pow(circle + CoreBoostOffset, CoreBoostPower) * CoreBoost);
float gal = max(shape - noise * saturate(circle), 0);
return gal;
}
}
public static class Vector2Extension {
public static Vector2 Rotate(this Vector2 v, float degrees) {
float sin = Mathf.Sin(degrees * Mathf.Deg2Rad);
float cos = Mathf.Cos(degrees * Mathf.Deg2Rad);
float tx = v.x;
float ty = v.y;
v.x = (cos * tx) - (sin * ty);
v.y = (sin * tx) + (cos * ty);
return v;
}
}
[Serializable]
public class NameData
{
public Dictionary<string, Dictionary<string, List<string>>> Names = new Dictionary<string, Dictionary<string, List<string>>>();
public void Add(string dataString)
{
//Debug.Log("Adding " + dataString);
var all = dataString.Split(',');
var name = all[0].Split(" -.".ToCharArray(),StringSplitOptions.RemoveEmptyEntries).OrderByDescending(s=>s.Length).First();
var region = all[1].Split('/');
var continent = region[0];
var capital = region[1];
if(!Names.ContainsKey(continent))
Names[continent] = new Dictionary<string, List<string>>();
if(!Names[continent].ContainsKey(capital))
Names[continent][capital] = new List<string>();
Names[continent][capital].Add(name);
}
}
[Serializable]
public class ContinentData
{
public string Name;
public List<RegionData> Regions;
}
[Serializable]
public class RegionData
{
public string Name;
public List<string> Towns;
}
public class VoronoiLink
{
public Vertex2 point1;
public Vertex2 point2;
public float Length { get { return (point1.ToVector2() - point2.ToVector2()).magnitude; } }
public VoronoiLink(Vertex2 p1, Vertex2 p2)
{
point1 = p1;
point2 = p2;
}
public bool ContainsPoint(Vertex2 p)
{
return point1 == p || point2 == p;
}
}
public static class VoronoiLinkExtensions
{
class DijkstraVertex
{
public double Cost;
public double Heuristic;
public DijkstraVertex Parent;
public Vertex2 Vertex;
}
public static bool ContainsLine(this IEnumerable<VoronoiLink> data, VoronoiLink l)
{
return
data.Any(
line =>
(line.point1 == l.point1 && line.point2 == l.point2) ||
(line.point1 == l.point2 && line.point2 == l.point1));
}
public static IEnumerable<Vertex2> FindPath(this IEnumerable<VoronoiLink> data, Vertex2 source, Vertex2 target, bool bestFirst = true)
{
List<DijkstraVertex> members = new List<DijkstraVertex>{new DijkstraVertex{Vertex = source}};
List<Vertex2> searched = new List<Vertex2>();
while (true)
{
var v = members.Where(m=>!searched.Contains(m.Vertex)).OrderBy(m=>bestFirst?m.Heuristic:m.Cost).FirstOrDefault();
if (v == null) return null; // No vertices left unsearched
if (v.Vertex == target)
{
Stack<DijkstraVertex> path = new Stack<DijkstraVertex>();
path.Push(v);
while(path.Peek().Parent!=null)
path.Push(path.Peek().Parent);
return path.Select(dv => dv.Vertex).ToList();
}
members.AddRange(data.Where(l => l.point1 == v.Vertex).Select(l => l.point2).Concat(data.Where(l => l.point2 == v.Vertex).Select(l => l.point1))
.Where(n => members.All(m => m.Vertex != n)).Select(n =>
{
var c = new DijkstraVertex {Parent = v, Vertex = n, Cost = v.Cost + v.Vertex.Distance(n)};
if (bestFirst)
c.Heuristic = c.Cost + c.Vertex.Distance(target);
return c;
}));
searched.Add(v.Vertex);
}
}
public static IEnumerable<Vertex2> ConnectedRegion(this IEnumerable<VoronoiLink> data, Vertex2 v)
{
List<Vertex2> members = new List<Vertex2>();
members.Add(v);
while (true)
{
int lastCount = members.Count;
// For each member, add all vertices that are connected to it via a line but are not already a member
foreach (Vertex2 m in members.ToArray())
members.AddRange(data.Where(l => l.point1 == m).Select(l => l.point2)
.Concat(data.Where(l => l.point2 == m).Select(l => l.point1))
.Where(n => !members.Contains(n)));
// If we have stopped finding neighbors, stop traversing
if (members.Count == lastCount)
return members;
}
}
}