Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Riverheim v1.3.1
BepInEx/plugins/Riverheim.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using DelaunatorSharp; using Microsoft.CodeAnalysis; using Riverheim.Configuration; using Riverheim.Configuration.Extensions; using Riverheim.Pipeline; using Riverheim.Pipeline.Extensions; using Riverheim.Rendering; using Riverheim.Rendering.Biomes; using Riverheim.Rendering.Rivers; using Riverheim.Rendering.Splats; using Riverheim.Util; using Riverheim.Util.Debug; using Riverheim.Util.Geometry; using Riverheim.Util.Grids; using Riverheim.Util.Interpolation; using Riverheim.Util.Mathematics; using Riverheim.Util.Random; using Riverheim.Util.Random.Noise; using Riverheim.Util.Random.Noise.Primitives; using Riverheim.Util.Random.Primitives; using Riverheim.Util.Splines; using Riverheim.Util.Tilings; using Riverheim.Util.Tilings.Containers; using Riverheim.Util.Tilings.Internal; using Riverheim.Util.Tilings.Processing; using Riverheim.World; using Riverheim.World.Biomes; using Riverheim.World.Biomes.Competitive; using Riverheim.World.Height; using Riverheim.World.Height.Mountains; using Riverheim.World.Landmass; using Riverheim.World.Rivers; using Riverheim.World.Splats; using Riverheim.World.Tilings; using Worldhalla.API; using Worldhalla.API.Attributes; using Worldhalla.API.Settings; using Worldhalla.API.Settings.Controls; using Worldhalla.API.World; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Riverheim")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Comprehensive terrain generator for Valheim")] [assembly: AssemblyFileVersion("1.3.1.0")] [assembly: AssemblyInformationalVersion("1.3.1+7a361b24ebc146c5637b40c4d8f11413f4e8db1c")] [assembly: AssemblyProduct("Riverheim")] [assembly: AssemblyTitle("Riverheim")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.3.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } internal static class PluginInfo { public const string PLUGIN_GUID = "Riverheim"; public const string PLUGIN_NAME = "Riverheim"; public const string PLUGIN_VERSION = "1.3.1"; } namespace Riverheim.Plugin { [BepInPlugin("dev.gurebu.riverheim.stable", "Riverheim", "1.3.1")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class RiverheimPlugin : BaseUnityPlugin { private const string WORLDHALLA_GUID = "dev.gurebu.worldhalla"; private const string GUID = "dev.gurebu.riverheim.stable"; private const string PLUGIN_NAME = "Riverheim"; public static ConfigEntry<bool> ParallelGeneration; public static ConfigEntry<bool> PipelineCaching; public void Awake() { ParallelGeneration = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "Parallel generation", true, "Whether world generation should compute across multiple threads in parallel. Disabling this makes generation slower but easier to debug."); PipelineCaching = ((BaseUnityPlugin)this).Config.Bind<bool>("Performance", "Pipeline caching", false, "Significantly increases memory usage to cache generation intermediates. Doesn't speed up generation unless you're using another mod to reload"); Logger.SetLogger(delegate(Logger.LogLevel level, object message) { switch (level) { case Logger.LogLevel.Info: ((BaseUnityPlugin)this).Logger.LogInfo(message); break; case Logger.LogLevel.Warning: ((BaseUnityPlugin)this).Logger.LogWarning(message); break; case Logger.LogLevel.Error: ((BaseUnityPlugin)this).Logger.LogError(message); break; default: throw new ArgumentOutOfRangeException("level", level, null); } }); } } } namespace Riverheim.Plugin.Worldhalla { public class SettingsFormImpl : ISettingsForm { private readonly WorldSettings current; private readonly List<string> presets; private readonly Select preset; public IReadOnlyList<Control> Controls { get; } public SettingsFormImpl(WorldSettings current) { //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown this.current = current; List<ConfigManager.PresetInfo> source = (from info in ConfigManager.GetAvailablePresets() where !info.Hidden || info.Id == current.Preset select info).ToList(); presets = source.Select((ConfigManager.PresetInfo info) => info.Id).ToList(); preset = new Select("Preset", (IReadOnlyList<string>)source.Select((ConfigManager.PresetInfo info) => info.DisplayName).ToList(), Math.Max(0, presets.IndexOf(current.Preset)), (string)null); Controls = new <>z__ReadOnlyArray<Control>((Control[])(object)new Control[1] { (Control)preset }); } public ISettings Build() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) string text = presets[preset.Selected]; if (text == current.Preset) { return (ISettings)(object)new SettingsImpl(current); } if (!ConfigManager.TryGetLatestSettings(text, out var settings)) { throw new SettingsException("Unknown preset '" + text + "'"); } return (ISettings)(object)new SettingsImpl(settings); } } public class SettingsImpl(WorldSettings settings) : ISettings { public readonly WorldSettings settings = settings; public byte[] SaveToBinaryBlob() { return WorldSettingSerialization.Serialize(settings); } public string Summary() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("Preset: " + (ConfigManager.GetAvailablePresets().FirstOrDefault((ConfigManager.PresetInfo info) => info.Id == settings.Preset).DisplayName ?? settings.Preset)); return stringBuilder.ToString(); } } [WorldhallaWorldGenerator("dev.gurebu.riverheim")] public class WorldGeneratorImpl : IWorldGenerator, ISettingsHandler { private const string GUID = "dev.gurebu.riverheim"; public string Moniker => "RH"; public string Name => "Riverheim"; public string Description => "Worlds with larger continents and terrain based on sprawling river networks."; public string Version => "1.3.1"; public Feature[] Features => (Feature[])(object)new Feature[1]; public ISettings DefaultSettings => (ISettings)(object)new SettingsImpl(ConfigManager.DefaultWorldSettings()); public string CommandLineUsage => "preset <name>"; public ISettings BuildSettingsFromBinaryBlob(byte[] data) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) try { return (ISettings)(object)new SettingsImpl(WorldSettingSerialization.Deserialize(data)); } catch (WorldSettingSerialization.Error error) { throw new SettingsException(error.Message); } } public ISettings BuildSettingsFromCommandLine(string settings) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) string[] array = settings.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (array.Length == 0) { return DefaultSettings; } if (array.Length != 2 || array[0] != "preset") { throw new SettingsException("Expected 'preset <name>', got '" + settings + "'"); } if (!ConfigManager.TryGetLatestSettings(array[1], out var settings2)) { throw new SettingsException("Unknown preset '" + array[1] + "'"); } return (ISettings)(object)new SettingsImpl(settings2); } public ISettingsForm CreateForm(ISettings current) { return (ISettingsForm)(object)new SettingsFormImpl(((SettingsImpl)(object)current).settings); } public IWorldState Generate(int seed, ISettings settings, IProgress<float> progress, CancellationToken cancellation) { GeneratorPipeline pipeline = BuildPipeline(); WorldSettings settings2 = (settings as SettingsImpl).settings; pipeline.TaskCompleted += delegate(GeneratorPipeline.TaskCompletionEvent evt) { progress.Report((float)evt.progress / (float)pipeline.Length); }; Logger.LogInfo($"Generating world seed {seed} with preset: '{settings2.Preset}' v.{settings2.PresetVersion}..."); pipeline.Compute(seed, ConfigManager.GetConfig(settings2), cancellation); return (IWorldState)(object)new WorldStateImpl(pipeline.GetResult<WorldRenderer>("Rendering/WorldRenderer")); } private static GeneratorPipeline BuildPipeline() { string[] resultResourceIds = new string[1] { "Rendering/WorldRenderer" }; GeneratorPipeline pipeline = new GeneratorPipeline.Builder(resultResourceIds).SetParallel(RiverheimPlugin.ParallelGeneration.Value).SetClearUnneededCache(!RiverheimPlugin.PipelineCaching.Value).Build(); pipeline.PipelineCompleted += delegate(GeneratorPipeline.PipelineCompletionEvent evt) { Logger.LogInfo($"World generation completed in {evt.duration.TotalMilliseconds:0.}ms"); }; pipeline.TaskCompleted += delegate(GeneratorPipeline.TaskCompletionEvent evt) { string text = ""; string text2 = string.Join(", ", evt.resources); string text3 = evt.duration.TotalMilliseconds.ToString("0."); Logger.LogInfo(evt.cached ? $"[{evt.progress} / {pipeline.Length}] {text2} retrieved from cache" : $"[{evt.progress} / {pipeline.Length}] {text2} computed in {text3}ms {text}"); }; return pipeline; } } public class WorldStateImpl(WorldRenderer renderer) : IWorldState { private static readonly Biome[] Biomes = BuildBiomes(); private static Biome[] BuildBiomes() { int num = 0; foreach (Biome value in Enum.GetValues(typeof(Biome))) { if ((int)value > num) { num = (int)value; } } Biome[] array = new Biome[num + 1]; array[2] = (Biome)1; array[3] = (Biome)1; array[5] = (Biome)2; array[6] = (Biome)4; array[4] = (Biome)8; array[13] = (Biome)8; array[7] = (Biome)16; array[10] = (Biome)32; array[11] = (Biome)32; array[12] = (Biome)64; array[1] = (Biome)256; array[8] = (Biome)512; array[9] = (Biome)512; return (Biome[])(object)array; } private static Biome BiomeConv(Biome biome) { return Biomes[(int)biome]; } public Biome GetBiome(float x, float y) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) return BiomeConv(renderer.GetBiome(new Rfloat2(x, y))); } public float GetForestation(float x, float y) { return (float)renderer.GetForestation(new Rfloat2(x, y)); } public PointData RenderPoint(float x, float y, Biome biome) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) IWorldGen.PointData pointData = renderer.RenderPoint(new Rfloat2(x, y)); return new PointData { biome = BiomeConv(pointData.biome), height = (float)pointData.height, dirt = (float)pointData.dirt, fertility = (float)pointData.fertility, lava = (float)pointData.lava, pave = (float)pointData.pave, snow = (float)pointData.snow }; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } } internal sealed class <>z__ReadOnlyArray<T> : IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>, IReadOnlyList<T>, ICollection<T>, IList<T> { int IReadOnlyCollection<T>.Count => _items.Length; T IReadOnlyList<T>.this[int index] => _items[index]; int ICollection<T>.Count => _items.Length; bool ICollection<T>.IsReadOnly => true; T IList<T>.this[int index] { get { return _items[index]; } set { throw new NotSupportedException(); } } public <>z__ReadOnlyArray(T[] items) { _items = items; } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_items).GetEnumerator(); } IEnumerator<T> IEnumerable<T>.GetEnumerator() { return ((IEnumerable<T>)_items).GetEnumerator(); } void ICollection<T>.Add(T item) { throw new NotSupportedException(); } void ICollection<T>.Clear() { throw new NotSupportedException(); } bool ICollection<T>.Contains(T item) { return ((ICollection<T>)_items).Contains(item); } void ICollection<T>.CopyTo(T[] array, int arrayIndex) { ((ICollection<T>)_items).CopyTo(array, arrayIndex); } bool ICollection<T>.Remove(T item) { throw new NotSupportedException(); } int IList<T>.IndexOf(T item) { return ((IList<T>)_items).IndexOf(item); } void IList<T>.Insert(int index, T item) { throw new NotSupportedException(); } void IList<T>.RemoveAt(int index) { throw new NotSupportedException(); } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] internal sealed class IsUnmanagedAttribute : Attribute { } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace Riverheim { internal static class DeterministicMath { private const short INLINE = 256; public const double PI = Math.PI; public const double RELATIVE_ERROR = 1E-10; private const double TWO_PI = Math.PI * 2.0; private const double HALF_PI = Math.PI / 2.0; private const double EIGHTH_OF_PI = Math.PI / 8.0; private const double TAN_PI_OVER_8 = 0.4142135623730951; private const double SQRT2 = 1.4142135623730951; private const double LN2 = 0.6931471805599453; private const double LN2_INV = 1.4426950408889634; [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double GuardNaN(double x) { throw new ArgumentOutOfRangeException($"Function input outside range: {x}"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Abs(double x) { return BitConverter.Int64BitsToDouble(BitConverter.DoubleToInt64Bits(x) & 0x7FFFFFFFFFFFFFFFL); } public static double Sqrt(double x) { if ((x < 0.0 || double.IsNaN(x)) ? true : false) { return GuardNaN(x); } if (x == 0.0 || double.IsPositiveInfinity(x)) { return x; } return x * InvSqrt(x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double InvSqrt(double x) { double num = 0.5 * x; long num2 = BitConverter.DoubleToInt64Bits(x); num2 = 6910469410427058089L - (num2 >> 1); double num3 = BitConverter.Int64BitsToDouble(num2); num3 *= 1.5 - num * num3 * num3; num3 *= 1.5 - num * num3 * num3; return num3 * (1.5 - num * num3 * num3); } public static double Exp(double x) { if (double.IsNaN(x)) { return GuardNaN(x); } if (double.IsNegativeInfinity(x) || x < -745.133) { return 0.0; } if (double.IsPositiveInfinity(x) || x > 709.782) { return double.PositiveInfinity; } double num = x * 1.4426950408889634; long num2 = (long)(num + ((num >= 0.0) ? 0.5 : (-0.5))); double num3 = x - (double)num2 * 0.6931471805599453; return (1.0 + num3 * (1.0 + num3 * (0.5 + num3 * (1.0 / 6.0 + num3 * (1.0 / 24.0 + num3 * (1.0 / 120.0 + num3 * (1.0 / 720.0 + num3 * (0.0001984126984126984 + num3 * (2.48015873015873E-05 + num3 * 2.7557319223985893E-06))))))))) * BitConverter.Int64BitsToDouble(num2 + 1023 << 52); } public static double Log(double x) { if (double.IsNaN(x) || x < 0.0) { return GuardNaN(x); } if (x == 0.0) { return double.NegativeInfinity; } int num = 0; if (x < 2.2250738585072014E-308) { x *= 4503599627370496.0; num = -52; } long num2 = BitConverter.DoubleToInt64Bits(x); int num3 = (int)((num2 >> 52) & 0x7FF) - 1023 + num; double num4 = BitConverter.Int64BitsToDouble((num2 & 0xFFFFFFFFFFFFFL) | 0x3FF0000000000000L); if (num4 > 1.4142135623730951) { num4 *= 0.5; num3++; } double num5 = (num4 - 1.0) / (num4 + 1.0); double num6 = num5 * num5; return 2.0 * num5 * (1.0 + num6 * (1.0 / 3.0 + num6 * (0.2 + num6 * (1.0 / 7.0 + num6 * (1.0 / 9.0 + num6 * (1.0 / 11.0)))))) + (double)num3 * 0.6931471805599453; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static double Pow(double x, long p) { bool flag = p < 0; if (flag) { p = -p; } double num = 1.0; double num2 = x; while (p > 0) { if ((p & 1) != 0L) { num *= num2; } num2 *= num2; p >>= 1; } if (!flag) { return num; } return 1.0 / num; } public static double Pow(double x, int p) { if (p == 0) { return 1.0; } if (x == 0.0) { if (p <= 0) { return double.PositiveInfinity; } return 0.0; } return Pow(x, (long)p); } public static double Pow(double x, double p) { if (x < 0.0) { return GuardNaN(x); } if (p == 0.0) { return 1.0; } if (x == 0.0) { if (!(p > 0.0)) { return double.PositiveInfinity; } return 0.0; } long num = (long)p; if (p == (double)num) { return Pow(x, num); } if (p == 0.5) { return Sqrt(x); } return Exp(p * Log(x)); } public static double Sin(double x) { if (!double.IsFinite(x)) { return GuardNaN(x); } x %= Math.PI * 2.0; if (x > Math.PI) { x -= Math.PI * 2.0; } else if (x < -Math.PI) { x += Math.PI * 2.0; } if (x > Math.PI / 2.0) { x = Math.PI - x; } else if (x < -Math.PI / 2.0) { x = -Math.PI - x; } double num = x * x; return x * (1.0 - num * (1.0 / 6.0 - num * (1.0 / 120.0 - num * (0.0001984126984126984 - num * (2.7557319223985893E-06 - num * (2.505210838544172E-08 - num * (1.6059043836821613E-10 - num * 7.647163731819816E-13))))))); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Cos(double x) { return Sin(x + Math.PI / 2.0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Tan(double x) { return Sin(x) / Cos(x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Asin(double x) { if (x < -1.0 || x > 1.0) { return GuardNaN(x); } return Atan2(x, Sqrt(1.0 - x * x)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Acos(double x) { if (x < -1.0 || x > 1.0) { return GuardNaN(x); } return Atan2(Sqrt(1.0 - x * x), x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan(double x) { if (!double.IsFinite(x)) { return GuardNaN(x); } bool num = x < 0.0; x = Abs(x); bool num2 = x > 1.0; if (num2) { x = 1.0 / x; } bool num3 = x > 0.4142135623730951; if (num3) { x = (x - 0.4142135623730951) / (1.0 + x * 0.4142135623730951); } double num4 = x * x; double num5 = x * (1.0 - num4 * (1.0 / 3.0 - num4 * (0.2 - num4 * (1.0 / 7.0 - num4 * (1.0 / 9.0 - num4 * (1.0 / 11.0 - num4 * (1.0 / 13.0 - num4 * (1.0 / 15.0 - num4 * (1.0 / 17.0 - num4 * (1.0 / 19.0 - num4 * (1.0 / 21.0 - num4 * (1.0 / 23.0)))))))))))); if (num3) { num5 += Math.PI / 8.0; } if (num2) { num5 = Math.PI / 2.0 - num5; } if (num) { num5 = 0.0 - num5; } return num5; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan2(double y, double x) { if (x > 0.0) { return Atan(y / x); } if (x < 0.0) { if (!(y >= 0.0)) { return Atan(y / x) - Math.PI; } return Atan(y / x) + Math.PI; } if (y > 0.0) { return Math.PI / 2.0; } if (y < 0.0) { return -Math.PI / 2.0; } return 0.0; } } public readonly struct InertFloat { private readonly float value; private InertFloat(float value) { this.value = value; } public static implicit operator double(InertFloat f) { return f.value; } public static implicit operator InertFloat(double f) { return new InertFloat((float)f); } public override string ToString() { return $"{value}"; } } public readonly struct InertFloat2 { private readonly float x; private readonly float y; private InertFloat2(float x, float y) { this.x = x; this.y = y; } public static implicit operator Rfloat2(InertFloat2 f2) { return new Rfloat2(f2.x, f2.y); } public static implicit operator InertFloat2(Rfloat2 f2) { return new InertFloat2((float)f2.x, (float)f2.y); } public override string ToString() { return $"({x}, {y})"; } } public readonly struct Rfloat2 : IEquatable<Rfloat2> { private const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; private const double TOLERANCE = 1E-06; public readonly double x; public readonly double y; public double Length { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return Rmath.Sqrt(x * x + y * y); } } public double SqrLength { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { return x * x + y * y; } } public bool IsNegligible { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { if (Rmath.Abs(x) < 1E-06) { return Rmath.Abs(y) < 1E-06; } return false; } } public static Rfloat2 Zero => new Rfloat2(0.0, 0.0); public static Rfloat2 One => new Rfloat2(1.0, 1.0); public Rfloat2 Normalized { [MethodImpl(MethodImplOptions.AggressiveInlining)] get { double length = Length; if (!(length > 1E-06)) { return Zero; } return new Rfloat2(x / length, y / length); } } public Rfloat2(double x, double y) { this.x = x; this.y = y; } public Rfloat2(double val) : this(val, val) { } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static implicit operator Rfloat2(Rint2 ivec) { return new Rfloat2(ivec.x, ivec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Distance(Rfloat2 lhs, Rfloat2 rhs) { return (lhs - rhs).Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Dot(Rfloat2 lhs, Rfloat2 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Cross(Rfloat2 lhs, Rfloat2 rhs) { return lhs.x * rhs.y - lhs.y * rhs.x; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Angle(Rfloat2 a, Rfloat2 b) { double num = Rmath.Sqrt(a.SqrLength * b.SqrLength); if (num < 1E-06) { return 0.0; } return Rmath.Acos(Rmath.Clamp(Dot(a, b) / num, -1.0, 1.0)) * (double)Rmath.Sign(Cross(a, b)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 UnclampedLerp(double t, Rfloat2 a, Rfloat2 b) { return new Rfloat2(Rmath.UnclampedLerp(t, a.x, b.x), Rmath.UnclampedLerp(t, a.y, b.y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 Lerp(double t, Rfloat2 a, Rfloat2 b) { return new Rfloat2(Rmath.Lerp(t, a.x, b.x), Rmath.Lerp(t, a.y, b.y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public double DistanceTo(Rfloat2 other) { return (this - other).Length; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rint2 FloorToInt() { return new Rint2(Rmath.FloorToInt(x), Rmath.FloorToInt(y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rint2 CeilToInt() { return new Rint2(Rmath.CeilToInt(x), Rmath.CeilToInt(y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public Rint2 RoundToInt() { return new Rint2(Rmath.RoundToInt(x), Rmath.RoundToInt(y)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rfloat2 other) { if (x.Equals(other.x)) { return y.Equals(other.y); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Rfloat2 other) { return Equals(other); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return HashCode.Combine(x, y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { return $"({x}, {y})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rfloat2 lhs, Rfloat2 rhs) { if (Rmath.Abs(lhs.x - rhs.x) < 1E-06) { return Rmath.Abs(lhs.y - rhs.y) < 1E-06; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rfloat2 lhs, Rfloat2 rhs) { if (!(Rmath.Abs(lhs.x - rhs.x) > 1E-06)) { return Rmath.Abs(lhs.y - rhs.y) > 1E-06; } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator +(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x + rhs.x, lhs.y + rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x - rhs.x, lhs.y - rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(Rfloat2 vec) { return new Rfloat2(0.0 - vec.x, 0.0 - vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator +(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x + scalar, vec.y + scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator +(double scalar, Rfloat2 vec) { return new Rfloat2(scalar * vec.x, scalar * vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x - scalar, vec.y - scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator -(double scalar, Rfloat2 vec) { return new Rfloat2(scalar - vec.x, scalar - vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator *(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x * rhs.x, lhs.y * rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator *(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x * scalar, vec.y * scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator *(double scalar, Rfloat2 vec) { return new Rfloat2(scalar * vec.x, scalar * vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator /(Rfloat2 lhs, Rfloat2 rhs) { return new Rfloat2(lhs.x / rhs.x, lhs.y / rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator /(Rfloat2 vec, double scalar) { return new Rfloat2(vec.x / scalar, vec.y / scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rfloat2 operator /(double scalar, Rfloat2 vec) { return new Rfloat2(scalar / vec.x, scalar / vec.y); } } [Serializable] public struct Rint2 : IEquatable<Rint2> { private const MethodImplOptions INLINE = MethodImplOptions.AggressiveInlining; public readonly int x; public readonly int y; public static Rint2 Zero => new Rint2(0, 0); public static Rint2 One => new Rint2(1, 1); public Rint2(int x, int y) { this.x = x; this.y = y; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(Rint2 other) { if (x == other.x) { return y == other.y; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (obj is Rint2 other) { return Equals(other); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() { return HashCode.Combine(x, y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator ==(Rint2 left, Rint2 right) { return left.Equals(right); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool operator !=(Rint2 left, Rint2 right) { return !left.Equals(right); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() { return $"({x}, {y})"; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator +(Rint2 lhs, Rint2 rhs) { return new Rint2(lhs.x + rhs.x, lhs.y + rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(Rint2 lhs, Rint2 rhs) { return new Rint2(lhs.x - rhs.x, lhs.y - rhs.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(Rint2 vec) { return new Rint2(-vec.x, -vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator +(Rint2 vec, int scalar) { return new Rint2(vec.x + scalar, vec.y + scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator +(int scalar, Rint2 vec) { return new Rint2(scalar * vec.x, scalar * vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(Rint2 vec, int scalar) { return new Rint2(vec.x - scalar, vec.y - scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator -(int scalar, Rint2 vec) { return new Rint2(scalar - vec.x, scalar - vec.y); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator *(Rint2 lhs, Rint2 rhs) { return new Rint2(lhs.x * rhs.x, lhs.y * rhs.x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator *(Rint2 vec, int scalar) { return new Rint2(vec.x * scalar, vec.y * scalar); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rint2 operator *(int scalar, Rint2 vec) { return new Rint2(scalar * vec.x, scalar * vec.y); } } public static class Rmath { [Serializable] public struct Range { public double min; public double max; public bool IsEmpty => min.Equals(max); public bool IsPositive => max > min; public static Range Unit => new Range { min = -1.0, max = 1.0 }; public static Range From(double a, double b) { if (!(a <= b)) { return new Range { min = b, max = a }; } return new Range { min = a, max = b }; } } [Serializable] public struct RemapConfig { public double fromA; public double fromB; public double toA; public double toB; public Range From { get { return new Range { min = fromA, max = fromB }; } set { double min = value.min; double max = value.max; fromA = min; fromB = max; } } public Range To { get { return new Range { min = toA, max = toB }; } set { double min = value.min; double max = value.max; toA = min; toB = max; } } public bool IsEmpty { get { if (!From.IsEmpty) { return To.IsEmpty; } return true; } } } private const short INLINE = 256; public const double PI = Math.PI; public const double RAD_TO_DEG = 180.0 / Math.PI; public const double DEG_TO_RAD = Math.PI / 180.0; public const double EPSILON = 1E-06; public const double RELATIVE_TOLERANCE = 1E-10; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int FloorToInt(double f) { if (f < 0.0) { return (int)f - 1; } return (int)f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CeilToInt(double f) { if (f > 0.0) { return (int)f + 1; } return (int)f; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int RoundToInt(double f) { return FloorToInt(f + 0.5); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Min(int a, int b) { if (a >= b) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Max(int a, int b) { if (a <= b) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Min(double a, double b) { if (!double.IsNaN(a) && !(a < b)) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Max(double a, double b) { if (!double.IsNaN(a) && !(a > b)) { return b; } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Abs(double value) { return DeterministicMath.Abs(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int Sign(double value) { if (!(value < 0.0)) { return 1; } return -1; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Sqrt(double value) { return DeterministicMath.Sqrt(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Pow(double value, int power) { return DeterministicMath.Pow(value, power); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Pow(double value, double power) { return DeterministicMath.Pow(value, power); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Exp(double value) { return DeterministicMath.Exp(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Log(double value) { return DeterministicMath.Log(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Sin(double value) { return DeterministicMath.Sin(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Cos(double value) { return DeterministicMath.Cos(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Tan(double value) { return DeterministicMath.Tan(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Asin(double value) { return DeterministicMath.Asin(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Acos(double value) { return DeterministicMath.Acos(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan(double value) { return DeterministicMath.Atan(value); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Atan2(double y, double x) { return DeterministicMath.Atan2(y, x); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Smooth3(double t) { return t * t * (3.0 - 2.0 * t); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Smooth5(double t) { return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double UnclampedLerp(double t, double a, double b) { return a + t * (b - a); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double UnclampedLerp(double t, Range range) { return UnclampedLerp(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Lerp(double t, double a, double b) { return UnclampedLerp(Clamp01(t), a, b); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Lerp(double t, Range range) { return Lerp(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmoothStep(double t, double a, double b) { if (!(t <= 0.0)) { if (t >= 1.0) { return b; } return UnclampedLerp(Smooth3(t), a, b); } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmoothStep(double t, Range range) { return SmoothStep(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmootherStep(double t, double a, double b) { if (!(t <= 0.0)) { if (t >= 1.0) { return b; } return UnclampedLerp(Smooth5(t), a, b); } return a; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double SmootherStep(double t, Range range) { return SmootherStep(t, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double InverseLerp(double value, double a, double b) { if (a.Equals(b)) { return 0.5; } return Clamp01((value - a) / (b - a)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double InverseLerp(double value, Range range) { return InverseLerp(value, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp(double value, double min, double max) { if (value < min) { return min; } if (!(value > max)) { return value; } return max; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp(double value, Range range) { return Clamp(value, range.min, range.max); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Clamp01(double value) { if (!(value < 0.0)) { if (value > 1.0) { return 1.0; } return value; } return 0.0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Remap(double value, RemapConfig config) { return Lerp(InverseLerp(value, config.fromA, config.fromB), config.toA, config.toB); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Within(double value, Range range) { if (value >= range.min) { return value <= range.max; } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Rectifier(double value) { if (!(value > 0.0)) { return 0.0; } return value; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double Curvature(Rfloat2 d, Rfloat2 d2) { double num = 1.0 + d.SqrLength; return d2.Length / Sqrt(num * num * num); } } [Serializable] public struct CommonConfig { public double worldSize; public double tileSpacing; public double mountainHeight; public double oceanDepth; public double startingAreaRadius; } public interface IWorldGen { public struct PointData { public Biome biome; public double height; public double fertility; public double lava; public double snow; public double dirt; public double pave; } double WorldSize { get; } Biome GetBiome(Rfloat2 pos); double GetForestation(Rfloat2 pos); PointData RenderPoint(Rfloat2 pos); } [Serializable] [CompositeConfig] public struct WorldGenerationConfig { public CommonConfig common; public WorldConfig world; public RenderingConfig rendering; } } namespace Riverheim.World { public readonly struct ExtendedTileFlags { public bool IsCenter { get; } public bool IsLand => !IsWater; public bool IsWater { get { if (!IsOcean) { return IsLake; } return true; } } public bool IsOcean { get; } public bool IsLake { get; } public bool IsCoast { get; } public bool IsOceanCoast { get; } public bool IsLakeCoast { get; } public ExtendedTileFlags(bool isCenter, bool isOcean, bool isLake, bool isOceanCoast, bool isLakeCoast) { IsCenter = isCenter; IsOcean = isOcean; IsLake = isLake; IsCoast = isOceanCoast || isLakeCoast; IsOceanCoast = isOceanCoast; IsLakeCoast = isLakeCoast; } } [Producer(new string[] { "Flags/Extended" })] [Fork("Region")] public class ExtendedTileFlagStage : IGeneratorStage<TileData<ExtendedTileFlags>> { public const string OUTPUT = "Flags/Extended"; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> baseFlags; [Resource("Heights/Lakes")] public TileSet lakes; public TileData<ExtendedTileFlags> Compute() { return tiling.CreateData((Tile tile) => new ExtendedTileFlags(baseFlags[tile].IsCenter, baseFlags[tile].IsOcean, lakes.Contains(tile), baseFlags[tile].IsCoast, IsLakeCoast(tile, lakes))); } private bool IsLakeCoast(Tile tile, TileSet lakeTiles) { if (!baseFlags[tile].IsLand) { return false; } bool isLake = lakeTiles.Contains(tile); return tile.GetNeighbors().Any((Tile neighbor) => lakeTiles.Contains(neighbor) != isLake); } } [Producer(new string[] { "Forestation" })] [Fork("Region")] public class ForestationStage : IGeneratorStage<TileData<InertFloat>> { [Serializable] public struct Config { public double noiseScale; public NoiseField.FractalConfig noiseFractal; public double marshBonus; public double plainsRiverBonus; public Rmath.Range plainsRiverBonusProximityRange; public Rmath.Range plainsRiverBonusHeightRange; public double meadowsRiverBonus; public Rmath.Range meadowsRiverBonusProximityRange; } public const string OUTPUT = "Forestation"; [Random("Forestation")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Biomes")] public TileData<Biome> biomes; [Resource("Rivers/Proximity")] public TileData<InertFloat> riverProximity; [Resource("Heights/SettledHeightMap")] public TileData<InertFloat> heights; public TileData<InertFloat> Compute() { NoiseField noise = random.BiasedNoise2D("Forestation", config.noiseScale).Fractal(config.noiseFractal); return tiling.CreateData((Func<Tile, InertFloat>)delegate(Tile tile) { double result = 1.0 - noise.Sample(tile.Center); return Rmath.Clamp01(AdjustResult(biomes[tile], tile, result)); }); } private double AdjustResult(Biome biome, Tile tile, double result) { switch (biome) { case Biome.Meadows: { double meadowsRiverBonus = config.meadowsRiverBonus; meadowsRiverBonus *= 1.0 - Rmath.InverseLerp(riverProximity[tile], config.meadowsRiverBonusProximityRange); return result + meadowsRiverBonus; } case Biome.Marsh: return result + config.marshBonus; case Biome.Plains: { double plainsRiverBonus = config.plainsRiverBonus; plainsRiverBonus *= 1.0 - Rmath.InverseLerp(riverProximity[tile], config.plainsRiverBonusProximityRange); plainsRiverBonus *= 1.0 - Rmath.InverseLerp(heights[tile], config.plainsRiverBonusHeightRange); return result + plainsRiverBonus; } case Biome.Mistglade: return 0.0; default: return result; } } } public class LandmassInfo { public int border; public double area; public double coastline; public double Compactness => Math.PI * 4.0 * area / (coastline * coastline); } public struct LandmassMetrics { public int count; public double totalArea; public double maxArea; public double avgArea; public double avgCompactness; } public struct RiverMetrics { public int count; public double maxLength; public double avgLength; } public class BiomeMetrics { public double totalArea; public double innerArea; } public struct WorldMetrics { public LandmassMetrics landmassMetrics; public RiverMetrics riverMetrics; public Dictionary<Biome, BiomeMetrics> biomeMetrics; public double highestPoint; public double centerCoastalProximity; public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("World Metrics:"); stringBuilder.AppendLine(" Total Land: " + ToPercentage(landmassMetrics.totalArea)); stringBuilder.AppendLine($" Landmass Count: {landmassMetrics.count}"); stringBuilder.AppendLine(" Largest Landmass: " + ToPercentage(landmassMetrics.maxArea)); stringBuilder.AppendLine(" Average Landmass: " + ToPercentage(landmassMetrics.avgArea)); stringBuilder.AppendLine($" Average Compactness: {landmassMetrics.avgCompactness:0.0}"); stringBuilder.AppendLine($" Highest point: {highestPoint:0.0}m"); stringBuilder.AppendLine(); stringBuilder.AppendLine($" Total Rivers: {riverMetrics.count}"); stringBuilder.AppendLine($" Longest River: {riverMetrics.maxLength:0.0}m"); stringBuilder.AppendLine($" Average River: {riverMetrics.avgLength:0.0}m"); stringBuilder.AppendLine(); foreach (Biome key in this.biomeMetrics.Keys) { BiomeMetrics biomeMetrics = this.biomeMetrics[key]; stringBuilder.AppendLine($" {key} Total Area (Inner): {ToPercentage(biomeMetrics.totalArea)} ({ToPercentage(biomeMetrics.innerArea)})"); } return stringBuilder.ToString(); } private static string ToPercentage(double value) { return $"{value * 100.0:0.00}%"; } } [Producer(new string[] { "WorldMetrics" })] public class WorldMetricCalculator : IGeneratorStage<WorldMetrics> { public const string OUTPUT = "WorldMetrics"; [Resource("MainTiling")] public Tiling tiling; [Resource("Regions/Tiling")] public Tiling[] regionTilings; [Resource("Regions/Mapping")] public SubtilingMapping[] regionMappings; [Resource("LandmassIds")] public TileData<int>[] landmassIds; [Resource("Rivers/Splines")] public River[][] rivers; [Resource("Biomes")] public TileData<Biome>[] biomes; [Resource("Heights/JoinedHeights")] public TileData<InertFloat> heights; [Resource("CoastalProximity")] public TileData<InertFloat>[] coastalProximity; public WorldMetrics Compute() { return new WorldMetrics { landmassMetrics = ComputeLandmassMetrics(), riverMetrics = ComputeRiverMetrics(RegionStage.JoinRegionData(rivers)), biomeMetrics = ComputeBiomeMetrics(tiling, RegionStage.JoinRegionData(tiling, regionTilings, regionMappings, biomes)), highestPoint = tiling.Tiles.Max((Func<Tile, double>)((Tile tile) => heights[tile])), centerCoastalProximity = coastalProximity[0][tiling.GetTile(Rfloat2.Zero).Value] }; } private static List<LandmassInfo> ComputeLandmassInfos(Tiling tiling, TileData<int> landmassIds) { Dictionary<int, LandmassInfo> dictionary = new Dictionary<int, LandmassInfo>(); foreach (Tile tile in tiling.Tiles) { int landmassId = tile.Data(landmassIds); if (landmassId != -1) { if (!dictionary.ContainsKey(landmassId)) { dictionary[landmassId] = new LandmassInfo(); } dictionary[landmassId].area += tile.Area; if (tile.GetNeighbors().Any((Tile neighbor) => neighbor.Data(landmassIds) != landmassId)) { dictionary[landmassId].border++; } } } foreach (LandmassInfo value in dictionary.Values) { value.coastline = (double)value.border * tiling.Spacing; } return dictionary.Values.ToList(); } private LandmassMetrics ComputeLandmassMetrics() { List<LandmassInfo> list = new List<LandmassInfo>(); foreach (int value in Enum.GetValues(typeof(Region))) { list.AddRange(ComputeLandmassInfos(regionTilings[value], landmassIds[value])); } return new LandmassMetrics { count = list.Count, totalArea = list.Sum((LandmassInfo x) => x.area) / tiling.TotalArea, maxArea = list.Max((LandmassInfo x) => x.area) / tiling.TotalArea, avgArea = list.Average((LandmassInfo x) => x.area) / tiling.TotalArea, avgCompactness = list.Average((LandmassInfo x) => x.Compactness) }; } private static RiverMetrics ComputeRiverMetrics(River[] rivers) { List<double> source = rivers.Select(RiverLength).ToList(); return new RiverMetrics { count = rivers.Length, maxLength = ((rivers.Length != 0) ? source.Max() : 0.0), avgLength = ((rivers.Length != 0) ? source.Average() : 0.0) }; } private static double RiverLength(River river) { return river.spline.Length; } private static Dictionary<Biome, BiomeMetrics> ComputeBiomeMetrics(Tiling tiling, TileData<Biome> biomes) { Dictionary<Biome, BiomeMetrics> dictionary = new Dictionary<Biome, BiomeMetrics>(); foreach (Tile tile in tiling.Tiles) { if (!dictionary.ContainsKey(tile.Data(biomes))) { dictionary[tile.Data(biomes)] = new BiomeMetrics(); } double num = tile.Area / tiling.TotalArea; dictionary[tile.Data(biomes)].totalArea += num; if (tile.GetNeighbors().All((Tile neighbor) => neighbor.Data(biomes) == tile.Data(biomes))) { dictionary[tile.Data(biomes)].innerArea += num; } } return dictionary; } } public enum PolarProximityType { Any, North, South } public delegate double PolarProximityMap(Rfloat2 pos, PolarProximityType type = PolarProximityType.Any); [Serializable] public struct PolarBeltConfig { public double offset; public double radius; public double noiseScale; public double noiseAmplitude; } [Producer(new string[] { "PolarProximity" })] public class PolarProximityStage : IGeneratorStage<PolarProximityMap> { public const string OUTPUT = "PolarProximity"; [Random("PolarProximityMap")] public RandomDirectory random; [Config] public PolarBeltConfig config; public PolarProximityMap Compute() { NoiseField noise = random.Noise2D("PolarProximity", config.noiseScale); return delegate(Rfloat2 pos, PolarProximityType type) { double num = Rmath.Atan2(pos.x, (pos.y < 0.0) ? (pos.y - config.offset) : pos.y); double num2 = config.radius + Rmath.Sin(num * 20.0) * 100.0; double num3 = (pos + new Rfloat2(0.0, config.offset)).Length - num2; double num4 = (pos - new Rfloat2(0.0, config.offset)).Length - num2; double num5 = type switch { PolarProximityType.North => num3, PolarProximityType.South => num4, PolarProximityType.Any => Rmath.Max(num3, num4), _ => throw new ArgumentOutOfRangeException(), }; double num6 = num5 / config.noiseAmplitude; return num5 + Rmath.Clamp01(num6 * num6) * config.noiseAmplitude * noise.Sample(pos); }; } } public enum Region { Main, SouthPole, NorthPole } [Serializable] public struct RegionalConfig<T> : ILaneConfig<T>, ILaneConfig { public T main; public T southPole; public T northPole; readonly object ILaneConfig.Get(int lane) { return Get((Region)lane); } public readonly T Get(int lane) { return Get((Region)lane); } public readonly T Get(Region region) { return region switch { Region.Main => main, Region.SouthPole => southPole, Region.NorthPole => northPole, _ => default(T), }; } public IEnumerable<(Region, T)> All() { yield return (Region.Main, main); yield return (Region.SouthPole, southPole); yield return (Region.NorthPole, northPole); } } [ForkingDimension("Region", typeof(Region))] [Producer(new string[] { "Regions" })] public class RegionStage : IGeneratorStage<TileData<Region>[]> { public const string OUTPUT = "Regions"; public const string FORK_DIMENSION = "Region"; [Config] public CommonConfig commonConfig; [Config] public PolarBeltConfig polarBeltConfig; [Resource("MainTiling")] public Tiling tiling; [Resource("TilingLods")] public Tiling[] tilingLods; [Resource("PolarProximity")] public PolarProximityMap polarProximity; public TileData<Region>[] Compute() { TileData<Region>[] array = new TileData<Region>[tilingLods.Length + 1]; for (int i = 0; i < array.Length; i++) { array[i] = ComputeLod(i); } return array; } private TileData<Region> ComputeLod(int lod) { if (lod == 0) { MutableTileData<Region> mutableTileData = tiling.CreateData((Tile tile) => GetRegion(tile, polarProximity)); FixOrphanRegions(mutableTileData); return mutableTileData; } return tilingLods[lod - 1].CreateData((Tile tile) => GetRegion(tile, polarProximity)); } private static Region GetRegion(Tile tile, PolarProximityMap polarProximity) { if (polarProximity(tile.Center, PolarProximityType.North) > 0.0) { return Region.NorthPole; } if (polarProximity(tile.Center, PolarProximityType.South) > 0.0) { return Region.SouthPole; } return Region.Main; } private Rfloat2 RegionOrigin(Region region) { double num = Rmath.Lerp(0.5, commonConfig.worldSize, polarBeltConfig.radius - polarBeltConfig.offset); return region switch { Region.Main => Rfloat2.Zero, Region.NorthPole => new Rfloat2(0.0, num), Region.SouthPole => new Rfloat2(0.0, 0.0 - num), _ => throw new ArgumentOutOfRangeException(), }; } private void FixOrphanRegions(MutableTileData<Region> regions) { MutableTileData<int> mutableTileData = MarkRegionComponents(regions); Dictionary<Region, int> dictionary = new Dictionary<Region, int>(); foreach (Region value in Enum.GetValues(typeof(Region))) { Tile? tile = tiling.GetTile(RegionOrigin(value)); if (tile.HasValue) { Tile valueOrDefault = tile.GetValueOrDefault(); dictionary[value] = mutableTileData[valueOrDefault]; } } TileQueue tileQueue = new TileQueue(tiling); foreach (Tile tile2 in tiling.Tiles) { Region region2 = regions[tile2]; if (mutableTileData[tile2] != dictionary[region2]) { continue; } tileQueue.Enqueue(tile2); while (tileQueue.Count > 0) { foreach (Tile neighbor in tileQueue.Dequeue().GetNeighbors()) { if (mutableTileData[neighbor] != dictionary[regions[neighbor]]) { regions[neighbor] = region2; mutableTileData[neighbor] = dictionary[region2]; tileQueue.Enqueue(neighbor); } } } } } private MutableTileData<int> MarkRegionComponents(TileData<Region> regions) { MutableTileData<int> mutableTileData = tiling.CreateData(-1); int num = 0; TileQueue tileQueue = new TileQueue(tiling); foreach (Tile tile in tiling.Tiles) { if (mutableTileData[tile] != -1) { continue; } mutableTileData[tile] = num; tileQueue.Enqueue(tile); while (tileQueue.Count > 0) { foreach (Tile neighbor in tileQueue.Dequeue().GetNeighbors()) { if (regions[neighbor] == regions[tile] && mutableTileData[neighbor] == -1) { mutableTileData[neighbor] = num; tileQueue.Enqueue(neighbor); } } } num++; } return mutableTileData; } public static TileData<T> JoinRegionData<T>(Tiling tiling, Tiling[] regionTilings, SubtilingMapping[] regionMappings, TileData<T>[] regionData) { MutableTileData<T> mutableTileData = tiling.CreateData<T>(); foreach (int value in Enum.GetValues(typeof(Region))) { SubtilingMapping subtilingMapping = regionMappings[value]; foreach (Tile tile in regionTilings[value].Tiles) { mutableTileData[subtilingMapping.GetParentTile(tile)] = regionData[value][tile]; } } return mutableTileData; } public static T[] JoinRegionData<T>(T[][] regionData) { List<T> list = new List<T>(); foreach (int value in Enum.GetValues(typeof(Region))) { list.AddRange(regionData[value]); } return list.ToArray(); } } public readonly struct TileFlags { public bool IsCenter { get; } public bool IsLand { get; } public bool IsOcean => !IsLand; public bool IsCoast { get; } public TileFlags(bool isLand, bool isCenter, bool isCoast) { IsLand = isLand; IsCenter = isCenter; IsCoast = isCoast; } } [Producer(new string[] { "Flags/Base" })] [Fork("Region")] public class TileFlagStage : IGeneratorStage<TileData<TileFlags>> { public const string OUTPUT = "Flags/Base"; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("LandmassIds")] public TileData<int> landmassIds; public TileData<TileFlags> Compute() { return tiling.CreateData((Tile tile) => new TileFlags(IsLandTile(tile), tile.Id == tiling.GetTile(Rfloat2.Zero)?.Id, IsCoastalTile(tile))); } private bool IsLandTile(Tile tile) { return tile.Data(landmassIds) != -1; } private bool IsCoastalTile(Tile tile) { foreach (Tile neighbor in tile.GetNeighbors()) { if (IsLandTile(tile) != IsLandTile(neighbor)) { return true; } } return false; } } [Producer(new string[] { "TravelDistance" })] [Fork("Region")] public class TravelDistanceStage : IGeneratorStage<TileData<InertFloat>> { [Serializable] public struct Config { public bool normalize; public double landCost; public double riverCost; public double oceanCost; public double embarkCost; public FrontierRaymarchingConfig frontier; } [Serializable] public struct FrontierRaymarchingConfig { public int rays; public int maxSteps; public double stepRate; public double noiseScale; public double noiseMagnitude; } public const string OUTPUT = "TravelDistance"; [Lane] public Region region; [Random("TravelDistance")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("MainTiling")] public Tiling mainTiling; [Resource("Regions")] public TileData<Region>[] regions; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("PrunedRivers")] public TileData<RiverTile> rivers; public TileData<InertFloat> Compute() { TileSet frontier = Frontier(); if (frontier.Count == 0) { return tiling.CreateData((Func<Tile, InertFloat>)((Tile tile) => tile.Center.Length)); } NoiseField noise = config.frontier.noiseMagnitude * random.Noise2D("FrontierNoise", config.frontier.noiseScale); TileData<InertFloat> tileData = tiling.DistanceTransform((Tile tile) => (!frontier.Contains(tile)) ? ((double?)null) : new double?(tile.Center.Length * (1.0 + noise.Sample(tile.Center))), (Tile _) => true, (Tile t1, Tile t2) => Rfloat2.Distance(t1.Center, t2.Center) * EdgeCostMultiplier(t1, t2)); if (!config.normalize) { return tileData; } double correction = GetAverageRealDistanceRate(tileData); return tileData.Transform((Func<InertFloat, InertFloat>)((InertFloat distance) => correction * (double)distance)); } private double GetAverageRealDistanceRate(TileData<InertFloat> distances) { double num = 0.0; int num2 = 0; foreach (Tile tile in tiling.Tiles) { if (tile.HopsToBounds <= 0) { num += tile.Center.SqrLength / ((double)distances[tile] * (double)distances[tile]); num2++; } } return num / (double)num2; } private TileSet Frontier() { Tile? tile = tiling.GetTile(Rfloat2.Zero); if (tile.HasValue) { Tile valueOrDefault = tile.GetValueOrDefault(); return TileSet.Of(valueOrDefault); } TileSet tileSet = new TileSet(tiling); if (config.frontier.rays == 0) { return tileSet; } TileData<InertFloat> tileData = mainTiling.DistanceTransform((Tile tile2) => (regions[0][tile2] != region) ? ((double?)null) : new double?(0.0), (Tile _) => true, (Tile t1, Tile t2) => Rfloat2.Distance(t1.Center, t2.Center)); double spacing = tiling.Spacing; for (int num = 0; num < config.frontier.rays; num++) { Direction direction = new Direction(Math.PI * 2.0 * (double)num / (double)config.frontier.rays); Rfloat2 zero = Rfloat2.Zero; for (int num2 = 0; num2 < config.frontier.maxSteps; num2++) { tile = tiling.GetTile(zero); if (tile.HasValue) { Tile valueOrDefault2 = tile.GetValueOrDefault(); tileSet.Add(valueOrDefault2); break; } tile = mainTiling.GetTile(zero); double num3; if (tile.HasValue) { Tile valueOrDefault3 = tile.GetValueOrDefault(); num3 = tileData[valueOrDefault3]; } else { num3 = spacing; } double num4 = num3; zero += Rmath.Max(spacing, num4 * config.frontier.stepRate) * direction.ToVector(); } } return tileSet; } private double EdgeCostMultiplier(Tile t1, Tile t2) { if (flags[t1].IsLand != flags[t2].IsLand) { return config.embarkCost; } if (flags[t1].IsOcean || flags[t2].IsOcean) { return config.oceanCost; } if (rivers[t1].IsRiver() || rivers[t2].IsRiver()) { return config.riverCost; } return config.landCost; } } [Serializable] [CompositeConfig] public record WorldConfig { public WorldVerticesCalculator.Config tiling; public PolarBeltConfig polar; public RegionalConfig<TravelDistanceStage.Config> travel; public LandmassConfig landmass; public RiverConfig rivers; public HeightConfig height; public BiomeConfig biomes; public Riverheim.World.Splats.SplatConfig splats; public RegionalConfig<ForestationStage.Config> forestation; } public class WorldState { public readonly Tiling tiling; public readonly TriangleMesh interpolationMesh; public readonly TileData<InertFloat> heights; public readonly TileData<Biome> biomes; public readonly TileData<InertFloat> forestation; public readonly River[] rivers; public readonly Splat[] splats; public WorldState(Tiling tiling, TriangleMesh interpolationMesh, TileData<InertFloat> heights, TileData<Biome> biomes, TileData<InertFloat> forestation, River[] rivers, Splat[] splats) { this.tiling = tiling; this.interpolationMesh = interpolationMesh; this.heights = heights; this.biomes = biomes; this.forestation = forestation; this.rivers = rivers; this.splats = splats; } } [Producer(new string[] { "WorldState" })] public class WorldStateCompiler : IGeneratorStage<WorldState> { public const string OUTPUT = "WorldState"; [Resource("MainTiling")] public Tiling tiling; [Resource("MainInterpolationMesh")] public TriangleMesh interpolationMesh; [Resource("Regions/Tiling")] public Tiling[] regionTilings; [Resource("Regions/Mapping")] public SubtilingMapping[] regionMappings; [Resource("Heights/JoinedHeights")] public TileData<InertFloat> heights; [Resource("Biomes")] public TileData<Biome>[] biomes; [Resource("Rivers/Splines")] public River[][] rivers; [Resource("Splats")] public Splat[] splats; [Resource("Forestation")] public TileData<InertFloat>[] forestation; public WorldState Compute() { return new WorldState(tiling, interpolationMesh, heights, RegionStage.JoinRegionData(tiling, regionTilings, regionMappings, biomes), RegionStage.JoinRegionData(tiling, regionTilings, regionMappings, forestation), RegionStage.JoinRegionData(rivers), splats); } } } namespace Riverheim.World.Tilings { [Producer(new string[] { "MainTiling", "MainInterpolationMesh" })] public class MainTilingStage : IGeneratorStage<(Tiling, TriangleMesh)> { public const string TILING = "MainTiling"; public const string MESH = "MainInterpolationMesh"; [Resource("WorldVertices")] public WorldVertices[] vertexLayers; public (Tiling, TriangleMesh) Compute() { WorldVertices worldVertices = vertexLayers[0]; BarycentricDualTilingBuilder barycentricDualTilingBuilder = new BarycentricDualTilingBuilder(worldVertices.vertices, worldVertices.spacing); return (barycentricDualTilingBuilder.Build(), barycentricDualTilingBuilder.BuildMesh()); } } [Producer(new string[] { "Regions/Tiling", "Regions/Mapping" })] [Fork("Region")] public class RegionTilingStage : IGeneratorStage<(Tiling, SubtilingMapping)> { public const string TILING = "Regions/Tiling"; public const string MAPPING = "Regions/Mapping"; [Lane] public Region region; [Resource("MainTiling")] public Tiling tiling; [Resource("Regions")] public TileData<Region>[] regions; public (Tiling, SubtilingMapping) Compute() { SubtilingMapping mapping; return (tiling.CreateSubTiling((Tile tile) => regions[0][tile] == region, out mapping), mapping); } } [Producer(new string[] { "TilingLods" })] public class TilingLodStage : IGeneratorStage<Tiling[]> { public const string OUTPUT = "TilingLods"; [Resource("WorldVertices")] public WorldVertices[] worldVertices; public Tiling[] Compute() { Tiling[] array = new Tiling[this.worldVertices.Length - 1]; for (int i = 0; i < array.Length; i++) { WorldVertices worldVertices = this.worldVertices[i + 1]; VoronoiTilingBuilder voronoiTilingBuilder = new VoronoiTilingBuilder(worldVertices.vertices, worldVertices.spacing, worldVertices.boundingRadius); array[i] = voronoiTilingBuilder.Build(); } return array; } } public struct WorldVertices { public double spacing; public double boundingRadius; public InertFloat2[] vertices; } [Producer(new string[] { "WorldVertices" })] public class WorldVerticesCalculator : IGeneratorStage<WorldVertices[]> { [Serializable] public struct Config { public int lodLevels; public double lodScale; } public const string OUTPUT = "WorldVertices"; [Random("WorldVertices")] public RandomDirectory random; [Config] public CommonConfig commonConfig; [Config] public Config config; public WorldVertices[] Compute() { WorldVertices[] array = new WorldVertices[1 + config.lodLevels]; for (int i = 0; i < array.Length; i++) { double num = commonConfig.tileSpacing * Rmath.Pow(config.lodScale, i); double boundingRadius = commonConfig.worldSize + Rmath.Sqrt(3.0) * num; InertFloat2[] vertices = (from point in new PoissonDiscSampler(random.Sequence(i.ToString()), boundingRadius, num / Rmath.Sqrt(2.0)).GetPoints() where point.SqrLength < boundingRadius * boundingRadius select point).Select((Func<Rfloat2, InertFloat2>)((Rfloat2 point) => point)).ToArray(); array[i] = new WorldVertices { spacing = num, boundingRadius = boundingRadius, vertices = vertices }; } return array; } } } namespace Riverheim.World.Splats { public record Crag : Splat { public InertFloat radius; public InertFloat terrace; public InertFloat cliffiness; } public record Pond : Splat { public InertFloat radius; } [Producer(new string[] { "Splats/RiverSources" })] [Fork("Region")] public class RiverSourceSplatStage : IGeneratorStage<Splat[]> { [Serializable] public struct Config { public AllBiomeConfig<bool> allowedBiomes; public CragConfig crags; public PondConfig ponds; } [Serializable] public struct SpawnFeatureAffinityConfig { public enum AffinityType { None, LowerBound, UpperBound, Both } public AffinityType type; public Rmath.Range lowerBound; public Rmath.Range upperBound; } [Serializable] public struct CragConfig { public double frequency; public SpawnFeatureAffinityConfig height; public SpawnFeatureAffinityConfig riverLength; public CragRandomizer.Config random; } [Serializable] public struct PondConfig { public double frequency; public SpawnFeatureAffinityConfig height; public SpawnFeatureAffinityConfig riverLength; public double stepback; public PondRandomizer.Config random; } public const string OUTPUT = "Splats/RiverSources"; [Random("Splats/RiverSources")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Regions/Mapping")] public SubtilingMapping mapping; [Resource("MainTiling")] public Tiling mainTiling; [Resource("MainInterpolationMesh")] public TriangleMesh mesh; [Resource("Rivers/Splines")] public River[] rivers; [Resource("Biomes/PostprocessedHeightMap")] public TileData<InertFloat> heights; [Resource("Biomes")] public TileData<Biome> biomes; public Splat[] Compute() { List<Crag> list = new List<Crag>(); List<Pond> list2 = new List<Pond>(); RandomIndexer randomIndexer = random.Indexer("Crags/Spawn"); CragRandomizer cragRandomizer = new CragRandomizer(random.Sub("Crags"), config.crags.random); RandomIndexer randomIndexer2 = random.Indexer("Ponds/Spawn"); PondRandomizer pondRandomizer = new PondRandomizer(random.Sub("Ponds"), config.ponds.random); BarycentricInterpolator barycentricInterpolator = new BarycentricInterpolator(mainTiling, mesh); River[] array = rivers; for (int i = 0; i < array.Length; i++) { River river = array[i]; Rfloat2 pos = river.spline.EvaluatePosition(1.0); Tile? tile = tiling.GetTile(pos); if (!tile.HasValue) { continue; } Tile valueOrDefault = tile.GetValueOrDefault(); if (config.allowedBiomes.Get(biomes[valueOrDefault])) { double num = barycentricInterpolator.InterpolateOnSubtilingData(pos, heights, mapping); double length = river.spline.Length; double frequency = config.crags.frequency; frequency *= SpawnProb(num, config.crags.height); frequency *= SpawnProb(length, config.crags.riverLength); if (randomIndexer.Single(valueOrDefault.Id).Value < frequency) { list.Add(cragRandomizer.Generate(valueOrDefault.Id, pos, num)); } double frequency2 = config.ponds.frequency; frequency2 *= SpawnProb(num, config.ponds.height); frequency2 *= SpawnProb(length, config.ponds.riverLength); if (randomIndexer2.Single(valueOrDefault.Id).Value < frequency2) { Rfloat2 pos2 = river.spline.EvaluatePosition(1.0 - config.ponds.stepback / length); list2.Add(pondRandomizer.Generate(valueOrDefault.Id, pos2)); } } } List<Splat> list3 = new List<Splat>(); list3.AddRange(list); list3.AddRange(list2); return list3.ToArray(); } private static double SpawnProb(double value, SpawnFeatureAffinityConfig config) { return config.type switch { SpawnFeatureAffinityConfig.AffinityType.LowerBound => Rmath.Smooth3(Rmath.InverseLerp(value, config.lowerBound)), SpawnFeatureAffinityConfig.AffinityType.UpperBound => 1.0 - Rmath.Smooth3(Rmath.InverseLerp(value, config.upperBound)), SpawnFeatureAffinityConfig.AffinityType.Both => Rmath.Smooth3(Rmath.InverseLerp(value, config.lowerBound)) - Rmath.Smooth3(Rmath.InverseLerp(value, config.upperBound)), SpawnFeatureAffinityConfig.AffinityType.None => 1.0, _ => 0.0, }; } } public class CragRandomizer { [Serializable] public struct Config { public Rmath.Range height; public Rmath.Range radius; public Rmath.Range terrace; } private readonly Config config; private readonly RandomIndexer height; private readonly RandomIndexer radius; private readonly RandomIndexer terrace; public CragRandomizer(RandomDirectory random, Config config) { this.config = config; height = random.Indexer("Height"); radius = random.Indexer("Radius"); terrace = random.Indexer("Terrace"); } public Crag Generate(int id, Rfloat2 pos, double baseHeight) { double num = height.Single(id).Range(config.height); return new Crag { pos = pos, height = baseHeight + num, radius = radius.Single(id).Range(config.radius), terrace = num * terrace.Single(id).Range(config.terrace), cliffiness = 0.0 }; } } public class PondRandomizer { [Serializable] public struct Config { public Rmath.Range height; public Rmath.Range radius; } private readonly Config config; private readonly RandomIndexer height; private readonly RandomIndexer radius; public PondRandomizer(RandomDirectory random, Config config) { this.config = config; height = random.Indexer("Height"); radius = random.Indexer("Radius"); } public Pond Generate(int id, Rfloat2 pos) { return new Pond { pos = pos, height = height.Single(id).Range(config.height), radius = radius.Single(id).Range(config.radius) }; } } public record Plateau : Splat { public InertFloat2 p1; public InertFloat2 p2; public InertFloat radius; public InertFloat2 incline; public Direction bulgeDirection; public InertFloat2 bulgeMagnitude; public double maxNeighborHeight; public bool inverted; } [Serializable] public struct PlateauConfig { public double spawnNoiseScale; public PlateauMountainPeakConfig mountainPeaks; public PlateauSeasideCliffConfig coastalCliffs; public PlateauHillConfig hills; } [Serializable] public struct PlateauMountainPeakConfig { public double spawnRate; public double minHeightDiff; public Rmath.Range size; public ClampingConfig gradientClamping; public Rpoint2 bulge; public double cragThreshold; public Rmath.Range cragHeight; public double cragRadius; public double cragTerrace; public AllBiomeConfig<bool> allowedBiomes; } [Serializable] public struct PlateauSeasideCliffConfig { public double spawnRate; public double neighborHeight; public double minHeight; public double maxHeight; public Rmath.Range size; public ClampingConfig gradientClamping; public Rmath.Range bulge; } [Serializable] public struct PlateauHillConfig { public double spawnRate; public double minHeightDiff; public Rmath.Range size; public double maxLength; public ClampingConfig gradientClamping; public Rpoint2 bulge; public AllBiomeConfig<bool> allowedBiomes; } [Serializable] public struct ClampingConfig { public enum Mode { Linear, Asymptotic } public Mode mode; public double min; public double max; public double midpoint; public double pow; public double rate; } [Producer(new string[] { "Splats/Plateaus" })] [Fork("Region")] public class PlateauStage : IGeneratorStage<Splat[]> { public const string OUTPUT = "Splats/Plateaus"; [Random("Plateaus")] public RandomDirectory random; [LaneConfig] public PlateauConfig config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Biomes/PostprocessedHeightMap")] public TileData<InertFloat> heights; [Resource("Biomes")] public TileData<Biome> biomes; public Splat[] Compute() { List<Splat> list = new List<Splat>(); TileSet occupied = new TileSet(tiling); AddMountainPeaks(list, occupied); AddCoastalCliffs(list, occupied); AddHills(list, occupied); return list.ToArray(); } private void AddMountainPeaks(List<Splat> result, TileSet occupied) { PlateauMountainPeakConfig cfg = config.mountainPeaks; NoiseField noiseField = random.BiasedNoise2D("SpawnRate/MountainPeaks", config.spawnNoiseScale); RandomIndexer randomIndexer = random.Indexer("Size"); foreach (Tile tile in tiling.Tiles) { if (cfg.allowedBiomes.Get(biomes[tile]) && !(noiseField.Sample(tile.Center) > cfg.spawnRate) && !tile.GetNeighbors().Any((Tile neighbor) => !cfg.allowedBiomes.Get(biomes[neighbor])) && !tile.GetNeighbors().Any((Tile neighbor) => (double)neighbor.Data(heights) > (double)tile.Data(heights) - cfg.minHeightDiff) && occupied.Add(tile)) { Rfloat2 gradient = HeightUtility.ComputeRoughness(tile, heights).Gradient; if (gradient.Length < cfg.cragThreshold) { result.Add(new Crag { pos = tile.Center, height = (double)tile.Data(heights) + randomIndexer.Single(tile.Id).Range(cfg.cragHeight), radius = cfg.cragRadius, terrace = cfg.cragTerrace, cliffiness = 1.0 }); } else { result.Add(new Plateau { pos = tile.Center, height = tile.Data(heights), radius = Rmath.Lerp(randomIndexer.Single(tile.Id).Value, cfg.size), incline = Clamp(gradient, cfg.gradientClamping), bulgeDirection = new Direction(gradient), bulgeMagnitude = cfg.bulge }); } } } } private void AddCoastalCliffs(List<Splat> result, TileSet occupied) { NoiseField noiseField = random.BiasedNoise2D("SpawnRate/CoastalCliffs", config.spawnNoiseScale); RandomIndexer randomIndexer = random.Indexer("Size"); RandomIndexer randomIndexer2 = random.Indexer("PlateauBulge"); TileSet tileSet = new TileSet(tiling); List<(Tile, Tile)> list = new List<(Tile, Tile)>(); foreach (Tile tile2 in tiling.Tiles) { if ((double)tile2.Data(heights) < config.coastalCliffs.minHeight || (double)tile2.Data(heights) > config.coastalCliffs.maxHeight || noiseField.Sample(tile2.Center) > config.coastalCliffs.spawnRate || tile2.GetNeighbors().All((Tile neighbor) => (double)neighbor.Data(heights) >= config.coastalCliffs.neighborHeight) || !occupied.Add(tile2)) { continue; } tileSet.Add(tile2); foreach (Tile neighbor in tile2.GetNeighbors()) { if (tileSet.Contains(neighbor)) { list.Add((tile2, neighbor)); } } } foreach (var item3 in list) { Tile item = item3.Item1; Tile item2 = item3.Item2; Rfloat2 v = 0.5 * (item.Center + item2.Center); double num = 0.5 * ((double)item.Data(heights) + (double)item2.Data(heights)); double num2 = Rmath.Lerp(randomIndexer.Single(item.Id, item2.Id).Value, config.coastalCliffs.size); Rfloat2 rfloat = heights.InclineVector(item, item2); List<Tile> list2 = item.GetNeighbors().Intersect(item2.GetNeighbors()).ToList(); if (list2.Count != 2) { continue; } int num3 = list2.Count((Tile neighbor) => (double)neighbor.Data(heights) >= config.coastalCliffs.neighborHeight); if (num3 != 0) { if (num3 != 1) { continue; } Tile tile = list2.First((Tile neighbor) => (double)neighbor.Data(heights) >= config.coastalCliffs.neighborHeight); rfloat += Clamp(HeightUtility.InclineVector(v, num, tile.Center, tile.Data(heights)), config.coastalCliffs.gradientClamping); } else { int num4 = Rmath.Sign(Rfloat2.Dot(Normal(item.Center, item2.Center), list2[1].Center - list2[0].Center)); rfloat += num4 * heights.InclineVector(list2[0], list2[1]); } Rfloat2 rfloat2 = 0.5 * (item.Center + item2.Center); result.Add(new Plateau { pos = rfloat2, p1 = item.Center - rfloat2, p2 = item2.Center - rfloat2, height = num, radius = num2, incline = rfloat, bulgeDirection = new Direction(rfloat), bulgeMagnitude = randomIndexer2.Double(item.Id, item2.Id).Range(config.coastalCliffs.bulge) }); } } private void AddHills(List<Splat> result, TileSet occupied) { NoiseField noiseField = random.BiasedNoise2D("SpawnRate/Hills", config.spawnNoiseScale); RandomIndexer randomIndexer = random.Indexer("Size"); RandomIndexer randomIndexer2 = random.Indexer("Length"); foreach (Tile tile in tiling.Tiles) { if (config.hills.allowedBiomes.Get(tile.Data(biomes)) && !(noiseField.Sample(tile.Center) > config.hills.spawnRate) && !tile.GetNeighbors().Any((Tile neighbor) => (double)neighbor.Data(heights) > (double)tile.Data(heights) - config.hills.minHeightDiff) && occupied.Add(tile)) { Rfloat2 rfloat = Clamp(HeightUtility.ComputeRoughness(tile, heights).Gradient, config.hills.gradientClamping); Rfloat2 rfloat2 = rfloat.Normalized * randomIndexer2.Single(tile.Id).Value * 0.5 * config.hills.maxLength; result.Add(new Plateau { pos = tile.Center, p1 = rfloat2, p2 = -rfloat2, height = tile.Data(heights), radius = Rmath.Lerp(randomIndexer.Single(tile.Id).Value, config.hills.size), incline = rfloat, bulgeDirection = new Direction(rfloat), bulgeMagnitude = config.hills.bulge }); } } } private static Rfloat2 Normal(Rfloat2 v1, Rfloat2 v2) { Rfloat2 rfloat = v2 - v1; return new Rfloat2(rfloat.y, 0.0 - rfloat.x); } private static Rfloat2 Clamp(Rfloat2 vector, ClampingConfig config) { if (vector.IsNegligible) { return config.min * new Rfloat2(-1.0, 0.0); } double length = vector.Length; double num = config.max - config.min; return config.mode switch { ClampingConfig.Mode.Linear => Rmath.Clamp(length, config.min, config.max), ClampingConfig.Mode.Asymptotic => config.min + num * Functions.Saturation(length / num, config.midpoint, config.pow), _ => throw new ArgumentOutOfRangeException(), } / length * vector; } } [Serializable] [CompositeConfig] public record SplatConfig { public RegionalConfig<PlateauConfig> plateaus; public RegionalConfig<RiverSourceSplatStage.Config> riverSourceSplats; } public abstract record Splat { public InertFloat2 pos; public InertFloat height; } [Producer(new string[] { "Splats" })] public class SplatJoinStage : IGeneratorStage<Splat[]> { public const string OUTPUT = "Splats"; [Resource("Splats/Plateaus")] public Splat[][] regionalRoughnessSplats; [Resource("Splats/RiverSources")] public Splat[][] regionalRiverSourceSplats; public Splat[] Compute() { List<Splat> list = new List<Splat>(); list.AddRange(regionalRoughnessSplats.SelectMany((Splat[] splats) => splats)); list.AddRange(regionalRiverSourceSplats.SelectMany((Splat[] splats) => splats)); return list.ToArray(); } } } namespace Riverheim.World.Rivers { public struct Channel { public TileList path; public double width; } [Producer(new string[] { "Rivers/Channels" })] [Fork("Region")] public class ChannelStage : IGeneratorStage<Channel[]> { [Serializable] public record Config { public double width; } public const string OUTPUT = "Rivers/Channels"; [LaneConfig] public Config config; [Random("Rivers/Channels")] public RandomDirectory random; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("Landmasses/MergedPartitions")] public TileData<int> partitions; public Channel[] Compute() { List<Channel> list = new List<Channel>(); if (config.width <= 0.0) { return list.ToArray(); } RandomIndexer selectionRandom = random.Indexer("Selection"); foreach (TileSet faultLine in GetFaultLines()) { if (MakeChannel(faultLine, selectionRandom, out var channel)) { list.Add(channel); } } return list.ToArray(); } private List<TileSet> GetFaultLines() { Dictionary<Rint2, TileSet> dictionary = new Dictionary<Rint2, TileSet>(); foreach (Tile tile in tiling.Tiles) { if (tile.Data(flags).IsOcean) { continue; } int num = partitions[tile]; foreach (Tile neighbor in tile.GetNeighbors()) { if (neighbor.Data(flags).IsOcean) { continue; } int num2 = partitions[neighbor]; if (num != num2) { Rint2 key = ((num > num2) ? new Rint2(num2, num) : new Rint2(num, num2)); if (!dictionary.ContainsKey(key)) { dictionary[key] = new TileSet(tiling); } dictionary[key].Add(tile); } } } return new List<TileSet>(dictionary.Values); } private bool MakeChannel(TileSet faultLine, RandomIndexer selectionRandom, out Channel channel) { (TileSet, TileSet) oceanCoastalTiles = GetOceanCoastalTiles(faultLine); if (oceanCoastalTiles.Item1.Count == 0 || oceanCoastalTiles.Item2.Count == 0) { channel = default(Channel); return false; } Tile origin = Select(oceanCoastalTiles.Item1, selectionRandom); Tile target = Select(oceanCoastalTiles.Item2, selectionRandom); TileList tileList = TilingUtility.FindPathToTile(origin, target, (Tile tile) => faultLine.Contains(tile) || tile == target); if (tileList == null) { channel = default(Channel); return false; } channel = new Channel { path = tileList, width = config.width }; return true; } private Tile Select(TileSet tiles, RandomIndexer selectionRandom) { TileHeap tileHeap = new TileHeap(tiling, HeapType.Min); foreach (Tile item in tiles.Unordered()) { tileHeap.AddOrUpdate(item, selectionRandom.Single(item.Id).Value); } return tileHeap.Pop(); } private (TileSet, TileSet) GetOceanCoastalTiles(TileSet faultLine) { TileList tileList = new TileList(tiling); foreach (Tile item in faultLine.Unordered()) { if (!item.Data(flags).IsCoast) { continue; } foreach (Tile neighbor in item.GetNeighbors()) { if (neighbor.Data(flags).IsOcean) { tileList.Add(neighbor); } } } TileSet tileSet = new TileSet(tiling); TileSet tileSet2 = new TileSet(tiling); if (tileList.Count == 0) { return (tileSet, tileSet2); } TileSet tileSet3 = TileSet.Of(tileList[0]); foreach (Tile neighbor2 in tileList[0].GetNeighbors(3, (Tile neighbor) => neighbor.Data(flags).IsOcean)) { tileSet3.Add(neighbor2); } foreach (Tile item2 in tileList) { (tileSet3.Contains(item2) ? tileSet : tileSet2).Add(item2); } return (tileSet, tileSet2); } } [Serializable] public struct PrecipitationConfig { public double noiseScale; public double variability; public double spawnMinRadius; public double spawnMaxRadius; public double basinContribution; public double minimum; } [Producer(new string[] { "Precipitation" })] [Fork("Region")] public class PrecipitationStage : IGeneratorStage<TileData<InertFloat>> { public const string OUTPUT = "Precipitation"; [Random("PrecipitationMap")] public RandomDirectory random; [LaneConfig] public PrecipitationConfig config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Landmasses/Artifacts")] public TileData<LandmassArtifacts> artifacts; public TileData<InertFloat> Compute() { NoiseField noise = random.Noise2D("Noise", config.noiseScale); return tiling.CreateData((Func<Tile, InertFloat>)delegate(Tile tile) { double num = Rmath.Clamp(config.variability * noise.Sample(tile.Center), Rmath.Lerp(Rmath.InverseLerp(tile.Center.Length, config.spawnMinRadius, config.spawnMaxRadius), 1.0, -1.0), 1.0); if (tile.Data(artifacts).isBasin) { num += config.basinContribution; } return Rmath.Max(config.minimum, 1.0 + num); }); } } [Serializable] [CompositeConfig] public record RiverConfig { public RegionalConfig<PrecipitationConfig> precipitation; public RegionalConfig<RiverOriginFinder.Config> origins; public RegionalConfig<RiverFillStage.Config> floodfill; public RegionalConfig<RiverWidthConfig> width; public RegionalConfig<RiverPruneStage.Config> prune; public RegionalConfig<RiverCuriosityConfig> curiosities; public RegionalConfig<ChannelStage.Config> channels; public RegionalConfig<RiverPolylineIndexBuilder.Config> polylineIndex; public RegionalConfig<RiverMeanderer.Config> meander; public RegionalConfig<RiverSplineBuilder.Config> splines; } [Serializable] public struct RiverCuriosityConfig { public double sourceMult; public double sourceMinLength; public double sourceMaxLength; public double proximityMult; public double proximityScanRange; public double proximityDifferentBasinsMultiplier; public double proximityMinRatio; public double proximityMaxRatio; public double angleMult; public double angleMinDegrees; public double angleMaxDegrees; public double angleNormWidth; public double smoothingRange; } [Producer(new string[] { "Rivers/Curiosities" })] [Fork("Region")] public class RiverCuriosityStage : IGeneratorStage<TileData<InertFloat>> { private class CuriosityData { public readonly Dictionary<int, double> lengthsById; public InertFloat score; public double Length { get { double num = 0.0; foreach (double value in lengthsById.Values) { num += value; } return num; } } public CuriosityData(Dictionary<int, double> lengths) { lengthsById = ((lengths == null) ? new Dictionary<int, double>() : new Dictionary<int, double>(lengths)); score = 0.0; } } public const string OUTPUT = "Rivers/Curiosities"; [Config] public CommonConfig commonConfig; [LaneConfig] public RiverCuriosityConfig config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("PrunedRivers")] public TileData<RiverTile> rivers; [Resource("RiverTileMetrics")] public TileData<RiverBasinTile> basins; private MutableTileData<CuriosityData> data; public TileData<InertFloat> Compute() { data = tiling.CreateData<CuriosityData>(); MutableTileData<InertFloat> mutableTileData = tiling.CreateData<InertFloat>(); TileList tileList = FindMouths(); foreach (Tile item in tileList) { MarkLengths(item); } foreach (Tile item2 in tileList) { MarkScores(item2); } foreach (Tile item3 in tileList) { SmoothScores(item3, mutableTileData); } return mutableTileData; } private TileList FindMouths() { TileList tileList = new TileList(tiling); foreach (Tile tile in tiling.Tiles) { if (tile.Data(rivers) != null && tile.Data(rivers).isRiverMouth) { tileList.Add(tile); } } return tileList; } private void MarkLengths(Tile tile, Dictionary<int, double> parentLengths = null, double segmentLength = 0.0) { tile.Data(data) = new CuriosityData(parentLengths); Dictionary<int, double> lengthsById = tile.Data(data).lengthsById; int riverId = tile.Data(rivers).riverId; if (!lengthsById.ContainsKey(riverId)) { lengthsById[riverId] = segmentLength; } else { lengthsById[riverId] += segmentLength; } foreach (Tile inlet in GetInlets(tile)) { MarkLengths(inlet, lengthsById, Rfloat2.Distance(tile.Center, inlet.Center)); } } private void MarkScores(Tile tile) { double num = tile.Data(data).score; num += config.sourceMult * ComputeSourceScore(tile); num += config.proximityMult * ComputeProximityScore(tile); num += config.angleMult * ComputeAngleScore(tile); tile.Data(data).score = num; foreach (Tile inlet in GetInlets(tile)) { MarkScores(inlet); } } private double ComputeSourceScore(Tile tile) { if (!tile.Data(rivers).IsRiverTermination()) { return 0.0; } return Rmath.InverseLerp(tile.Data(data).Length, config.sourceMinLength, config.sourceMaxLength); } private double ComputeProximityScore(Tile tile) { double num = 0.0; foreach (Tile item in tile.Scan(config.proximityScanRange)) { if (item.Data(data) != null) { double num2 = Rfloat2.Distance(tile.Center, item.Center); double num3 = GraphDistance(tile, item); double num4 = ((tile.Data(basins).basinId != item.Data(basins).basinId) ? config.proximityDifferentBasinsMultiplier : 1.0); double num5 = Rmath.InverseLerp(num3 / num2 * num4, config.proximityMinRatio, config.proximityMaxRatio); num += num5 * Functions.Gauss(num2 / config.proximityScanRange, 3.0); } } double num6 = commonConfig.tileSpacing / config.proximityScanRange; return num * num6 * num6; } private double ComputeAngleScore(Tile tile) { double num = 0.0; Direction direction = tile.DirectionTo(GetOutlet(tile)); foreach (Tile inlet in GetInlets(tile)) { double num2 = Rmath.Abs(tile.DirectionTo(inlet) - direction); double num3 = 1.0 - Rmath.InverseLerp(num2 * (180.0 / Math.PI), config.angleMinDegrees, config.angleMaxDegrees); num += num3 * (double)tile.Data(basins).width / config.angleNormWidth; } return num; } private void SmoothScores(Tile tile, MutableTileData<InertFloat> result) { double smoothingRange = config.smoothingRange; InertFloat score = tile.Data(data).score; if ((double)score > 0.0) { ref InertFloat reference = ref tile.Data(result); reference = (double)reference + (double)score; foreach (Tile item in tile.Scan(smoothingRange)) { Rfloat2 rfloat = (tile.Center - item.Center) / smoothingRange; ref InertFloat reference2 = ref item.Data(result); reference2 = (double)reference2 + (double)score * Functions.Gauss2(rfloat.SqrLength, 3.0); } } foreach (Tile inlet in GetInlets(tile)) { SmoothScores(inlet, result); } } private double GraphDistance(Tile from, Tile to) { Dictionary<int, double> dictionary = new Dictionary<int, double>(from.Data(data).lengthsById); foreach (KeyValuePair<int, double> item in to.Data(data).lengthsById) { if (!dictionary.ContainsKey(item.Key)) { dictionary[item.Key] = 0.0 - item.Value; } else { dictionary[item.Key] -= item.Value; } } double num = 0.0; foreach (double value in dictionary.Values) { num += Rmath.Abs(value); } return num; } private Tile GetOutlet(Tile tile) { return tiling.GetTile(tile.Data(rivers).outletId); } private IEnumerable<Tile> GetInlets(Tile tile) { return tile.Data(rivers).inletIds.Select((int inletId) => tiling.GetTile(inletId)); } } public class RiverFillTile { public readonly Tile outlet; public readonly Couple<Tile> inlets; public readonly InertFloat loss; public RiverFillTile(Tile outlet, Couple<Tile> inlets, double loss) { this.outlet = outlet; this.inlets = inlets; this.loss = loss; } } [Producer(new string[] { "Rivers/FloodFill" })] [Fork("Region")] public class RiverFillStage : IGeneratorStage<TileData<RiverFillTile>> { [Serializable] public struct Config { public double temperature; public double maxOriginRandomLoss; public RiverFillGuide.Config guide; } private class GraphNode { public readonly Tile outlet; public readonly TileList inlets; public readonly InertFloat loss; public GraphNode(Tile outlet, double loss) { this.outlet = outlet; this.loss = loss; inlets = new TileList(outlet.Tiling); } public RiverFillTile ToRiverFillTile() { return new RiverFillTile(outlet, Couple<Tile>.MakeCouple(inlets.ToArray()), loss); } } public const string OUTPUT = "Rivers/FloodFill"; [Random("Rivers/FloodFill")] public RandomDirectory random; [LaneConfig] public Config config; [Resource("Regions/Tiling")] public Tiling tiling; [Resource("Flags/Base")] public TileData<TileFlags> flags; [Resource("LandmassIds")] public TileData<int> landmassIds; [Resource("Landmasses/Artifacts")] public TileData<LandmassArtifacts> artifacts; [Resource("Precipitation")] public TileData<InertFloat> precipitationMap; [Resource("Rivers/Origins")] public List<RiverOrigin> riverOrigins; private double Loss(TileData<GraphNode> graph, Tile tile) { return (double)graph[tile].loss - (double)precipitationMap[tile]; } public TileData<RiverFillTile> Compute() { MutableTileData<GraphNode> mutableTileData = tiling.CreateData<GraphNode>(); RiverFillGuide guide = new RiverFillGuide(random.Indexer("Guide"), config.guide); Dictionary<int, TileList> dictionary = InitializeOrigins(mutableTileData); foreach (int key in dictionary.Keys) { ProcessLandmass(mutableTileData, guide, dictionary[key]); } return mutableTileData.Transform((GraphNode node) => node?.ToRiverFillTile()); } private Dictionary<int, TileList> InitializeOrigins(MutableTileData<GraphNode> graph) { RandomIndexer randomIndexer = random.Indexer("OriginRandomLoss"); Dictionary<int, TileList> dictionary = new Dictionary<int, TileList>(); foreach (RiverOrigin riverOrigin in riverOrigins) { int key = tiling.GetTile(riverOrigin.tileId).Data(landmassIds); if (!dictionary.ContainsKey(key)) { dictionary[key] = new TileList(tiling); } Tile tile = tiling.GetTile(riverOrigin.tileId); double loss = randomIndexer.Single(riverOrigin.tileId).Range(config.maxOriginRandomLoss); tile.Data(graph) = new GraphNode(tiling.GetTile(riverOrigin.seawardTileId), loss); dictionary[key].Add(tile); } return dictionary; } private void ProcessLandmass(MutableTileData<GraphNode> graph, RiverFillGuide guide, TileList origins) { RandomTileHeap randomTileHeap = new RandomTileHeap(tiling, random.Indexer("TileSelection"), config.temperature); foreach (Tile origin in origins) { randomTileHeap.AddOrUpdate(origin, Loss(graph, origin)); } while (randomTileHeap.Count > 0) { Tile tile = randomTileHeap.Pop(); foreach (RiverFillGuide.Continuation item in SelectInlets(graph, guide, tile)) { graph[tile].inlets.Add(item.tile); graph[item.tile] = new GraphNode(tile, (double)graph[tile].loss + item.loss); randomTileHeap.AddOrUpdate(item.tile, Loss(graph, item.tile)); } } } private Couple<RiverFillGuide.Continuation> SelectInlets(TileData<GraphNode> graph, RiverFillGuide guide, Tile tile) { List<Tile> list = new List<Tile>(); foreach (Tile neighbor in tile.GetNeighbors()) { if (!(neighbor == tile.Data(graph).outlet) && RiverUtility.IsSuitableLand(flags[neighbor], artifacts[neighbor]) && neighbor.Data(graph) == null) { list.Add(neighbor); } } return guide.SelectConfiguration(tile, list, graph[tile].outlet); } } public class RiverFillGuide { [Serializable] public struct Config { public double temperature; public RiverFillGuideLossCalculator.Config loss; } public struct Continuation { public Tile tile; public double loss; } private class Configuration { public int id; public double loss; public Couple<Continuation> inlets; } private readonly RandomIndexer random; private readonly Config config; private readonly RiverFillGuideLossCalculator lossGuide; public RiverFillGuide(RandomIndexer random, Config config) { this.random = random; this.config = config; lossGuide = new RiverFillGuideLossCalculator(config.loss); } public Couple<Continuation> SelectConfiguration(Tile tile, List<Tile> candidates, Tile outlet) { if (candidates.Count == 0) { return default(Couple<Continuation>); } IEnumerable<Configuration> candidates2 = from inlet in candidates.AllPairings() select MakeConfiguration(tile, inlet, outlet); return RandomSelect(candidates2).inlets; } private Configuration MakeConfiguration(Tile tile, Couple<Tile> inlets, Tile outlet) { Direction outlet2 = outlet.DirectionTo(tile); Couple<Direction> inlets2 = inlets.Transform((Tile inlet) => tile.DirectionTo(inlet)); Couple<double> inletLosses; return new Configuration { id = ConfigurationId(tile, inlets), loss = lossGuide.ConfigurationLoss(inlets2, outlet2, out inletLosses), inlets = inlets.Transform((int i) => new Continuation { tile = inlets[i], loss = inletLosses[i] }) }; } private static int ConfigurationId(Tile origin, Couple<Tile> inlets) { int id = origin.Id; int x = ((inlets.Count > 0) ? inlets[0].Id : (-1)); int y = ((inlets.Count > 1) ? inlets[1].Id : (-1)); return (int)HashTools.IntHash32((uint)id, x, y); } private Configuration RandomSelect(IEnumerable<Configuration> candidates) { Configuration result = null; double num = double.MinValue; foreach (Configuration candidate in candidates) { double num2 = random.Single(candidate.id).Gumbel(0.0 - candidate.loss, config.temperature); if (num2 > num) { num = num2; result = candidate; } } return result; } } public class RiverFillGuideLossCalculator { [Serializable] public struct Config { public double energyPenaltyPow; public double aestheticPenaltySteepness; public double haltConfigurationLoss; public double bendConfigurationLoss; public double bendDeflectionLoss; public double bendAestheticLoss; public double forkDeflectionLoss; public double forkImbalanceLoss; public double forkAestheticLoss; public double energyMult; public double continuationLoss; } private readonly Config config; public RiverFillGuideLossCalculator(Config config) { this.config = config; } public double ConfigurationLoss(Couple<Direction> inlets, Direction outlet, out Couple<double> inletLosses) { return inlets.Count switch { 0 => HaltLoss(out inletLosses), 1 => BendLoss(inlets[0], outlet, out inletLosses), 2 => ForkLoss(inlets[0], inlets[1], outlet, out inletLosses), _ => throw new IndexOutOfRangeException(), }; } private double HaltLoss(out Couple<double> losses) { losses = default(Couple<double>); return config.haltConfigurationLoss; } private double BendLoss(Directi