Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user