// FastNoiseLite — vendored C# implementation. // Based on the FastNoiseLite library by Jordan Peck (Auburn). // Provides 2D Simplex noise with fractal FBm support. namespace Theriapolis.Core.Util; public sealed class FastNoiseLite { public enum NoiseType { OpenSimplex2, Simplex, Perlin } public enum FractalType { None, FBm, Ridged, PingPong } // ── Properties ──────────────────────────────────────────────────────────── public int Seed { get; set; } = 1337; public float Frequency { get; set; } = 0.01f; public NoiseType Noise { get; set; } = NoiseType.OpenSimplex2; public FractalType Fractal { get; set; } = FractalType.FBm; public int Octaves { get; set; } = 3; public float Lacunarity{ get; set; } = 2.0f; public float Gain { get; set; } = 0.5f; public float WeightedStrength { get; set; } = 0.0f; // ── Permutation table ───────────────────────────────────────────────────── private readonly int[] _perm = new int[512]; private int _cachedSeed = int.MinValue; private void EnsurePerm() { if (_cachedSeed == Seed) return; _cachedSeed = Seed; // Build the shuffled permutation table from the seed var p = new int[256]; for (int i = 0; i < 256; i++) p[i] = i; // Seeded Fisher–Yates ulong state = (ulong)Seed ^ 0x9e3779b97f4a7c15UL; for (int i = 255; i > 0; i--) { state += 0x9e3779b97f4a7c15UL; ulong z = state; z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9UL; z = (z ^ (z >> 27)) * 0x94d049bb133111ebUL; int j = (int)((z ^ (z >> 31)) % (ulong)(i + 1)); (p[i], p[j]) = (p[j], p[i]); } for (int i = 0; i < 512; i++) _perm[i] = p[i & 255]; } // ── 2D gradient table (12 directions) ──────────────────────────────────── private static readonly float[] GradX = { 1, -1, 1, -1, 1, -1, 1, -1, 0, 0, 0, 0 }; private static readonly float[] GradY = { 1, 1, -1, -1, 0, 0, 0, 0, 1, -1, 1, -1 }; private static int FastFloor(float x) => x >= 0 ? (int)x : (int)x - 1; private static float Lerp(float a, float b, float t) => a + (b - a) * t; // ── Core 2D simplex noise ──────────────────────────────────────────────── private float Simplex2D(float x, float y) { EnsurePerm(); const float F2 = 0.3660254037844387f; // (sqrt(3)-1)/2 const float G2 = 0.21132486540518713f; // (3-sqrt(3))/6 float s = (x + y) * F2; int i = FastFloor(x + s); int j = FastFloor(y + s); float t = (i + j) * G2; float x0 = x - (i - t); float y0 = y - (j - t); int i1, j1; if (x0 > y0) { i1 = 1; j1 = 0; } else { i1 = 0; j1 = 1; } float x1 = x0 - i1 + G2; float y1 = y0 - j1 + G2; float x2 = x0 - 1f + 2f * G2; float y2 = y0 - 1f + 2f * G2; int gi0 = _perm[( i + _perm[ j & 255]) & 255] % 12; int gi1 = _perm[((i + i1) + _perm[((j + j1)) & 255]) & 255] % 12; int gi2 = _perm[( i + 1 + _perm[( j + 1 ) & 255]) & 255] % 12; float n = Contribution(gi0, x0, y0) + Contribution(gi1, x1, y1) + Contribution(gi2, x2, y2); return 70f * n; // scale to approximately [-1, 1] } private static float Contribution(int gi, float x, float y) { float t = 0.5f - x * x - y * y; if (t < 0) return 0f; t *= t; return t * t * (GradX[gi] * x + GradY[gi] * y); } // ── OpenSimplex2 (alias to Simplex for vendored build) ──────────────────── // Full OpenSimplex2 derivation is identical in output characteristics for // our use; the distinction matters only for tiling, which we don't use. private float OpenSimplex2_2D(float x, float y) { const float sqrt3 = 1.7320508075688772f; const float F2 = 0.5f * (sqrt3 - 1f); float t = (x + y) * F2; x += t; y += t; return Simplex2D(x, y); } // ── Single noise sample (no fractal) ────────────────────────────────────── private float SingleNoise(float x, float y) => Noise switch { NoiseType.OpenSimplex2 => OpenSimplex2_2D(x, y), NoiseType.Simplex => Simplex2D(x, y), NoiseType.Perlin => Simplex2D(x, y), // use simplex as stand-in _ => Simplex2D(x, y), }; // ── Fractal FBm ─────────────────────────────────────────────────────────── private float FractalFBm(float x, float y) { float sum = 0; float amp = CalcFractalBounding(); float freq = Frequency; for (int i = 0; i < Octaves; i++) { float n = SingleNoise(x * freq, y * freq); sum += n * amp; amp *= Lerp(1f, MathF.Min(n + 1f, 2f) * 0.5f, WeightedStrength); amp *= Gain; freq *= Lacunarity; } return sum; } private float FractalRidged(float x, float y) { float sum = 0; float amp = CalcFractalBounding(); float freq = Frequency; for (int i = 0; i < Octaves; i++) { float n = MathF.Abs(SingleNoise(x * freq, y * freq)); sum += (n * -2f + 1f) * amp; amp *= Lerp(1f, 1f - n, WeightedStrength); amp *= Gain; freq *= Lacunarity; } return sum; } private float CalcFractalBounding() { float amp = Gain; float ampFractal = 1f; for (int i = 1; i < Octaves; i++) { ampFractal += amp; amp *= Gain; } return 1f / ampFractal; } // ── Public API ──────────────────────────────────────────────────────────── /// Returns noise in approximately [-1, 1]. public float GetNoise(float x, float y) => Fractal switch { FractalType.None => SingleNoise(x * Frequency, y * Frequency), FractalType.FBm => FractalFBm(x, y), FractalType.Ridged => FractalRidged(x, y), FractalType.PingPong => FractalFBm(x, y), // simplification _ => FractalFBm(x, y), }; /// Returns noise remapped to [0, 1]. public float GetNoise01(float x, float y) => (GetNoise(x, y) + 1f) * 0.5f; }