175 lines
7.1 KiB
C#
175 lines
7.1 KiB
C#
|
|
// 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 ────────────────────────────────────────────────────────────
|
|||
|
|
|
|||
|
|
/// <summary>Returns noise in approximately [-1, 1].</summary>
|
|||
|
|
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),
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/// <summary>Returns noise remapped to [0, 1].</summary>
|
|||
|
|
public float GetNoise01(float x, float y) => (GetNoise(x, y) + 1f) * 0.5f;
|
|||
|
|
}
|