Files
2026-06-22 16:18:34 +02:00

98 lines
3.4 KiB
C#

using UnityEngine;
namespace Game.WorldGen
{
/// <summary>
/// Deterministic, seedable 2D gradient (Perlin) noise plus fBm / ridged variants.
/// All sampling is done in continuous world space so neighbouring terrain tiles
/// share identical edge values automatically -> seamless stitching by construction.
/// </summary>
public sealed class NoiseSampler
{
readonly int[] _perm = new int[512];
public NoiseSampler(int seed)
{
var p = new int[256];
for (int i = 0; i < 256; i++) p[i] = i;
// Fisher-Yates shuffle driven by the seed -> reproducible permutation table.
var rng = new System.Random(seed);
for (int i = 255; i > 0; i--)
{
int j = rng.Next(i + 1);
(p[i], p[j]) = (p[j], p[i]);
}
for (int i = 0; i < 512; i++) _perm[i] = p[i & 255];
}
static float Fade(float t) => t * t * t * (t * (t * 6f - 15f) + 10f);
static float Grad(int hash, float x, float y)
{
switch (hash & 7)
{
case 0: return x + y;
case 1: return -x + y;
case 2: return x - y;
case 3: return -x - y;
case 4: return x;
case 5: return -x;
case 6: return y;
default: return -y;
}
}
/// <summary>Classic improved-Perlin gradient noise. Returns roughly [-1, 1].</summary>
public float Perlin(float x, float y)
{
int xi = Mathf.FloorToInt(x) & 255;
int yi = Mathf.FloorToInt(y) & 255;
float xf = x - Mathf.Floor(x);
float yf = y - Mathf.Floor(y);
float u = Fade(xf);
float v = Fade(yf);
int aa = _perm[_perm[xi] + yi];
int ab = _perm[_perm[xi] + yi + 1];
int ba = _perm[_perm[xi + 1] + yi];
int bb = _perm[_perm[xi + 1] + yi + 1];
float x1 = Mathf.Lerp(Grad(aa, xf, yf), Grad(ba, xf - 1f, yf), u);
float x2 = Mathf.Lerp(Grad(ab, xf, yf - 1f), Grad(bb, xf - 1f, yf - 1f), u);
return Mathf.Lerp(x1, x2, v); // ~[-1, 1]
}
/// <summary>Fractal Brownian motion (stacked octaves). Returns ~[-1, 1].</summary>
public float Fbm(float x, float y, int octaves, float lacunarity, float gain)
{
float sum = 0f, amp = 1f, freq = 1f, norm = 0f;
for (int i = 0; i < octaves; i++)
{
sum += amp * Perlin(x * freq, y * freq);
norm += amp;
amp *= gain;
freq *= lacunarity;
}
return norm > 0f ? sum / norm : 0f;
}
/// <summary>Ridged multifractal — sharp mountain crests. Returns [0, 1].</summary>
public float Ridged(float x, float y, int octaves, float lacunarity, float gain)
{
float sum = 0f, amp = 1f, freq = 1f, norm = 0f;
for (int i = 0; i < octaves; i++)
{
float n = 1f - Mathf.Abs(Perlin(x * freq, y * freq));
n *= n; // sharpen the ridges
sum += amp * n;
norm += amp;
amp *= gain;
freq *= lacunarity;
}
return norm > 0f ? sum / norm : 0f;
}
}
}