From 8e714566f43078c043d817f78a03cc129a4e91e0 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sun, 23 Aug 2026 13:53:27 +0200 Subject: [PATCH] feat(core): add Perlin noise generator for terrain shaping --- core/include/vf/Noise.h | 27 +++++++++++++ core/src/Noise.cpp | 90 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 core/include/vf/Noise.h create mode 100644 core/src/Noise.cpp diff --git a/core/include/vf/Noise.h b/core/include/vf/Noise.h new file mode 100644 index 0000000..c6ca195 --- /dev/null +++ b/core/include/vf/Noise.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace vf { + +// Improved Perlin noise (Ken Perlin, 2002) over a seeded permutation table. +// Output of noise2D/noise3D is in the range [-1, 1]. +class PerlinNoise { +public: + explicit PerlinNoise(uint32_t seed = 1337u) { reseed(seed); } + + void reseed(uint32_t seed); + + float noise2D(float x, float y) const; + float noise3D(float x, float y, float z) const; + + // Fractal Brownian motion: sum of `octaves` scaled noise layers. + // Result normalised to roughly [-1, 1]. + float fbm2D(float x, float y, int octaves, float lacunarity, float gain) const; + +private: + std::array perm; +}; + +} // namespace vf diff --git a/core/src/Noise.cpp b/core/src/Noise.cpp new file mode 100644 index 0000000..2838591 --- /dev/null +++ b/core/src/Noise.cpp @@ -0,0 +1,90 @@ +#include "vf/Noise.h" + +#include +#include + +namespace vf { + +namespace { + +inline float fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); } + +inline float lerp(float a, float b, float t) { return a + t * (b - a); } + +inline 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 == 12 || h == 14 ? x : z); + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); +} + +} // namespace + +void PerlinNoise::reseed(uint32_t seed) { + std::array base; + std::iota(base.begin(), base.end(), 0); + + // xorshift32 permutation shuffle, fully determined by the seed. + uint32_t state = seed ? seed : 0x9e3779b9u; + auto next = [&state]() { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + return state; + }; + + for (int i = 255; i > 0; --i) { + int j = static_cast(next() % static_cast(i + 1)); + std::swap(base[i], base[j]); + } + for (int i = 0; i < 512; ++i) perm[i] = base[i & 255]; +} + +float PerlinNoise::noise3D(float x, float y, float z) const { + int xi = static_cast(std::floor(x)) & 255; + int yi = static_cast(std::floor(y)) & 255; + int zi = static_cast(std::floor(z)) & 255; + + float xf = x - std::floor(x); + float yf = y - std::floor(y); + float zf = z - std::floor(z); + + float u = fade(xf); + float v = fade(yf); + float w = fade(zf); + + int a = perm[xi] + yi; + int aa = perm[a] + zi; + int ab = perm[a + 1] + zi; + int b = perm[xi + 1] + yi; + int ba = perm[b] + zi; + int bb = perm[b + 1] + zi; + + float x1 = lerp(grad(perm[aa], xf, yf, zf), grad(perm[ba], xf - 1, yf, zf), u); + float x2 = lerp(grad(perm[ab], xf, yf - 1, zf), grad(perm[bb], xf - 1, yf - 1, zf), u); + float y1 = lerp(x1, x2, v); + + float x3 = lerp(grad(perm[aa + 1], xf, yf, zf - 1), grad(perm[ba + 1], xf - 1, yf, zf - 1), u); + float x4 = lerp(grad(perm[ab + 1], xf, yf - 1, zf - 1), grad(perm[bb + 1], xf - 1, yf - 1, zf - 1), u); + float y2 = lerp(x3, x4, v); + + return lerp(y1, y2, w); +} + +float PerlinNoise::noise2D(float x, float y) const { return noise3D(x, y, 0.0f); } + +float PerlinNoise::fbm2D(float x, float y, int octaves, float lacunarity, float gain) const { + float sum = 0.0f; + float amplitude = 1.0f; + float frequency = 1.0f; + float totalAmplitude = 0.0f; + for (int i = 0; i < octaves; ++i) { + sum += amplitude * noise2D(x * frequency, y * frequency); + totalAmplitude += amplitude; + amplitude *= gain; + frequency *= lacunarity; + } + return totalAmplitude > 0.0f ? sum / totalAmplitude : 0.0f; +} + +} // namespace vf