Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions SharpEngine.Core.Numerics/MathExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace SharpEngine.Core.Numerics;

/// <summary>
/// Provides mathematical utility functions.
/// </summary>
public static partial class MathExtensions
{
/// <summary>
/// Returns the largest integer less than or equal to the specified float value.
/// </summary>
/// <param name="value">The float value.</param>
/// <returns>The largest integer less than or equal to the specified float value.</returns>
public static int FastFloor(float value)
{
int integer = (int)value;

return value < integer ? integer - 1 : integer;
}

/// <summary>
/// Smooths the input value.
/// </summary>
/// <param name="t">The input value.</param>
/// <returns>The smoothed value.</returns>
public static float Fade(float t)
{
return t * t * t * (t * (t * 6f - 15f) + 10f);
}

/// <summary>
/// Performs linear interpolation between two values.
/// </summary>
/// <param name="a">The start value.</param>
/// <param name="b">The end value.</param>
/// <param name="t">The interpolation factor.</param>
/// <returns>The interpolated value.</returns>
public static float Lerp(float a, float b, float t)
{
return a + t * (b - a);
}

/// <summary>
/// Computes the gradient for a given hash and coordinates.
/// </summary>
/// <param name="hash">The hash value.</param>
/// <param name="x">The x coordinate.</param>
/// <param name="y">The y coordinate.</param>
/// <param name="z">The z coordinate.</param>
/// <returns>The computed gradient.</returns>
public static float Grad(int hash, float x, float y, float z)
{
int h = hash & 15;

float u = h < 8 ? x : y;
float v = h < 4
? y
: h is 12 or 14
? x
: z;

Check warning on line 59 in SharpEngine.Core.Numerics/MathExtensions.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=Av3boy_SharpEngine&issues=AZ8OVQ7Gh1Y3gYnR0kDY&open=AZ8OVQ7Gh1Y3gYnR0kDY&pullRequest=133

return ((h & 1) == 0 ? u : -u) +
((h & 2) == 0 ? v : -v);
}
}
45 changes: 45 additions & 0 deletions SharpEngine.Core.Numerics/Noise/INoiseGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace SharpEngine.Core.Numerics.Noise;

/// <summary>
/// Defines a noise generator with controllable fractal parameters.
/// </summary>
public interface INoiseGenerator
{
/// <summary>Gets or sets the scale factor applied to the input coordinates.</summary>
/// <remarks>
/// Higher values make the noise appear more zoomed out.
/// </remarks>
float Scale { get; set; }

/// <summary>Gets or sets the number of octaves (noise layers) to combine.</summary>
int Octaves { get; set; }

/// <summary>
/// Gets or sets the persistence,
/// which controls how much each octave contributes relative to the previous one (amplitude decay per octave).
/// </summary>
float Persistence { get; set; }

/// <summary>
/// Gets or sets the lacunarity,
/// which controls how quickly the frequency increases per octave.
/// </summary>
float Lacunarity { get; set; }

/// <summary>
/// Samples the noise at the given 2D coordinates.
/// </summary>
/// <param name="x">The X coordinate.</param>
/// <param name="z">The Z coordinate.</param>
/// <returns>A normalized value in the range [0, 1].</returns>
float Sample2D(float x, float z);

/// <summary>
/// Samples the noise at the given 3D coordinates.
/// </summary>
/// <param name="x">The X coordinate.</param>
/// <param name="y">The Y coordinate.</param>
/// <param name="z">The Z coordinate.</param>
/// <returns>A normalized value in the range [0, 1].</returns>
float Sample3D(float x, float y, float z);
}
191 changes: 191 additions & 0 deletions SharpEngine.Core.Numerics/Noise/PerlinNoiseGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using MathEx = SharpEngine.Core.Numerics.MathExtensions;

namespace SharpEngine.Core.Numerics.Noise;

/// <summary>
/// Deterministic Perlin noise generator with fractal octave support.
/// </summary>
public sealed class PerlinNoiseGenerator : INoiseGenerator
{
private readonly int[] _permutation = new int[512];

private float _scale = 32f;
private int _octaves = 4;
private float _persistence = 0.5f;
private float _lacunarity = 2f;

/// <summary>
/// Creates a new Perlin noise generator.
/// </summary>
/// <param name="seed">The seed used to generate the permutation table.</param>
public PerlinNoiseGenerator(int seed = 1337)
{
InitializePermutation(seed);
}

/// <inheritdoc />
public float Scale
{
get => _scale;
set => _scale = value <= 0f ? 0.0001f : value;
}

/// <inheritdoc />
public int Octaves
{
get => _octaves;
set => _octaves = Math.Max(1, value);
}

/// <inheritdoc />
public float Persistence
{
get => _persistence;
set => _persistence = Math.Max(0f, value);
}

/// <inheritdoc />
public float Lacunarity
{
get => _lacunarity;
set => _lacunarity = Math.Max(0.0001f, value);
}

/// <inheritdoc />
public float Sample2D(float x, float z)
{
float total = 0f;
float amplitude = 1f;
float frequency = 1f;
float maxAmplitude = 0f;

for (int i = 0; i < Octaves; i++)
{
float sampleX = x / Scale * frequency;
float sampleZ = z / Scale * frequency;

float noise = Perlin(sampleX, 0f, sampleZ);

total += noise * amplitude;
maxAmplitude += amplitude;

amplitude *= Persistence;
frequency *= Lacunarity;
}

float normalized = total / maxAmplitude;

return ToUnitRange(normalized);
}

/// <inheritdoc />
public float Sample3D(float x, float y, float z)
{
float total = 0f;
float amplitude = 1f;
float frequency = 1f;
float maxAmplitude = 0f;

for (int i = 0; i < Octaves; i++)
{
float sampleX = x / Scale * frequency;
float sampleY = y / Scale * frequency;
float sampleZ = z / Scale * frequency;

float noise = Perlin(sampleX, sampleY, sampleZ);

total += noise * amplitude;
maxAmplitude += amplitude;

amplitude *= Persistence;
frequency *= Lacunarity;
}

float normalized = total / maxAmplitude;

return ToUnitRange(normalized);
}

private void InitializePermutation(int seed)
{
int[] source = new int[256];

for (int i = 0; i < source.Length; i++)
source[i] = i;

Random random = new(seed);

for (int i = source.Length - 1; i > 0; i--)
{
int swapIndex = random.Next(i + 1);

(source[i], source[swapIndex]) = (source[swapIndex], source[i]);
}

for (int i = 0; i < 512; i++)
_permutation[i] = source[i & 255];
}

private float Perlin(float x, float y, float z)
{
int xi = MathEx.FastFloor(x) & 255;
int yi = MathEx.FastFloor(y) & 255;
int zi = MathEx.FastFloor(z) & 255;

float xf = x - MathEx.FastFloor(x);
float yf = y - MathEx.FastFloor(y);
float zf = z - MathEx.FastFloor(z);

float u = MathEx.Fade(xf);
float v = MathEx.Fade(yf);
float w = MathEx.Fade(zf);

var yiOffset = _permutation[xi] + yi;

int aaa = _permutation[_permutation[yiOffset] + zi];
int aba = _permutation[_permutation[yiOffset + 1] + zi];
int aab = _permutation[_permutation[yiOffset] + zi + 1];
int abb = _permutation[_permutation[yiOffset + 1] + zi + 1];

var yiOffsetNext = _permutation[xi + 1] + yi;

int baa = _permutation[_permutation[yiOffsetNext] + zi];
int bba = _permutation[_permutation[yiOffsetNext + 1] + zi];
int bab = _permutation[_permutation[yiOffsetNext] + zi + 1];
int bbb = _permutation[_permutation[yiOffsetNext + 1] + zi + 1];

float x1 = MathEx.Lerp(
MathEx.Grad(aaa, xf, yf, zf),
MathEx.Grad(baa, xf - 1f, yf, zf),
u);

float x2 = MathEx.Lerp(
MathEx.Grad(aba, xf, yf - 1f, zf),
MathEx.Grad(bba, xf - 1f, yf - 1f, zf),
u);

float y1 = MathEx.Lerp(x1, x2, v);

x1 = MathEx.Lerp(
MathEx.Grad(aab, xf, yf, zf - 1f),
MathEx.Grad(bab, xf - 1f, yf, zf - 1f),
u);

x2 = MathEx.Lerp(
MathEx.Grad(abb, xf, yf - 1f, zf - 1f),
MathEx.Grad(bbb, xf - 1f, yf - 1f, zf - 1f),
u);

float y2 = MathEx.Lerp(x1, x2, v);

return MathEx.Lerp(y1, y2, w);
}

private static float ToUnitRange(float value)
{
// Perlin is roughly in [-1, 1], so remap to [0, 1].
float result = value * 0.5f + 0.5f;

return Math.Clamp(result, 0f, 1f);
}
}