From 066457adba0cfce0c4bd3a17345e727858a7102b Mon Sep 17 00:00:00 2001 From: Green Sky Date: Tue, 23 Mar 2021 19:14:11 +0100 Subject: [PATCH] add Squirrel Eiserloh's rng, and own helper --- external/CMakeLists.txt | 2 + external/SquirrelNoise/CMakeLists.txt | 18 + .../src/squirrel_noise/RawNoise.cpp | 37 + .../src/squirrel_noise/RawNoise.hpp | 165 ++++ .../src/squirrel_noise/SmoothNoise.cpp | 756 ++++++++++++++++++ .../src/squirrel_noise/SmoothNoise.hpp | 95 +++ framework/CMakeLists.txt | 1 + framework/random/CMakeLists.txt | 24 + framework/random/src/mm/random/srng.cpp | 2 + framework/random/src/mm/random/srng.hpp | 57 ++ framework/std_utils/CMakeLists.txt | 1 - 11 files changed, 1157 insertions(+), 1 deletion(-) create mode 100644 external/SquirrelNoise/CMakeLists.txt create mode 100644 external/SquirrelNoise/src/squirrel_noise/RawNoise.cpp create mode 100644 external/SquirrelNoise/src/squirrel_noise/RawNoise.hpp create mode 100644 external/SquirrelNoise/src/squirrel_noise/SmoothNoise.cpp create mode 100644 external/SquirrelNoise/src/squirrel_noise/SmoothNoise.hpp create mode 100644 framework/random/CMakeLists.txt create mode 100644 framework/random/src/mm/random/srng.cpp create mode 100644 framework/random/src/mm/random/srng.hpp diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index d5c6d34..c69d4ae 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -15,6 +15,8 @@ add_subdirectory("tracy") include("${CMAKE_CURRENT_SOURCE_DIR}/entt.cmake") include("${CMAKE_CURRENT_SOURCE_DIR}/glm.cmake") +add_subdirectory("SquirrelNoise") + set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_MultipleHeaders ON CACHE INTERNAL "") add_subdirectory("json") # link with "nlohmann_json::nlohmann_json" diff --git a/external/SquirrelNoise/CMakeLists.txt b/external/SquirrelNoise/CMakeLists.txt new file mode 100644 index 0000000..fa03da7 --- /dev/null +++ b/external/SquirrelNoise/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.2) +project(SquirrelNoise) + +add_library(squirrel_noise + ./src/squirrel_noise/RawNoise.hpp + ./src/squirrel_noise/RawNoise.cpp + + # TODO: seperate smooth? + ./src/squirrel_noise/SmoothNoise.hpp + ./src/squirrel_noise/SmoothNoise.cpp +) + +target_include_directories(squirrel_noise PUBLIC "src") + +target_link_libraries(squirrel_noise + PRIVATE glm +) + diff --git a/external/SquirrelNoise/src/squirrel_noise/RawNoise.cpp b/external/SquirrelNoise/src/squirrel_noise/RawNoise.cpp new file mode 100644 index 0000000..02cda44 --- /dev/null +++ b/external/SquirrelNoise/src/squirrel_noise/RawNoise.cpp @@ -0,0 +1,37 @@ +//----------------------------------------------------------------------------------------------- +// RawNoise.cpp +// +#include "./RawNoise.hpp" + +namespace SquirrelNoise4 { + +//----------------------------------------------------------------------------------------------- +// Fast hash of an int32 into a different (unrecognizable) uint32. +// +// Returns an uint32_t containing 32 reasonably-well-scrambled bits, based on the hash +// of a given (signed) integer input parameter (position/index) and [optional] seed. Kind of +// like looking up a value in an infinitely large table of previously generated random numbers. +// +// The bit-noise constants and bit-shifts were evolved by a genetic algorithm using the +// "BigCrush" test for fitness, and have so far produced excellent test results. +// +// I call this particular approach SquirrelNoise (version 4). +// +uint32_t Get1dNoiseUint32( int32_t positionX, uint32_t seed ) { + const uint32_t BIT_NOISE1 = 0xD2A80A23; // 0b1101'0010'1010'1000'0000'1010'0010'0011; + const uint32_t BIT_NOISE2 = 0xA884F197; // 0b1010'1000'1000'0100'1111'0001'1001'0111; + const uint32_t BIT_NOISE3 = 0x1B56C4E9; // 0b0001'1011'0101'0110'1100'0100'1110'1001; + + uint32_t mangledBits = (uint32_t) positionX; + mangledBits *= BIT_NOISE1; + mangledBits += seed; + mangledBits ^= (mangledBits >> 7); + mangledBits += BIT_NOISE2; + mangledBits ^= (mangledBits >> 8); + mangledBits *= BIT_NOISE3; + mangledBits ^= (mangledBits >> 11); + return mangledBits; +} + +} // SquirrelNoise4 + diff --git a/external/SquirrelNoise/src/squirrel_noise/RawNoise.hpp b/external/SquirrelNoise/src/squirrel_noise/RawNoise.hpp new file mode 100644 index 0000000..cbdc0e5 --- /dev/null +++ b/external/SquirrelNoise/src/squirrel_noise/RawNoise.hpp @@ -0,0 +1,165 @@ +//----------------------------------------------------------------------------------------------- +// RawNoise.hpp +// +#pragma once + +#include + +namespace SquirrelNoise4 { + +///////////////////////////////////////////////////////////////////////////////////////////////// +// SquirrelNoise4 - Squirrel's Raw Noise utilities (version 4) +// +// This code is made available under the Creative Commons attribution 3.0 license (CC-BY-3.0 US): +// Attribution in source code comments (even closed-source/commercial code) is sufficient. +// License summary and text available at: https://creativecommons.org/licenses/by/3.0/us/ +// +// These noise functions were written by Squirrel Eiserloh as a cheap and simple substitute for +// the [sometimes awful] bit-noise sample code functions commonly found on the web, many of which +// are hugely biased or terribly patterned, e.g. having bits which are on (or off) 75% or even +// 100% of the time (or are WAY too overkill-and-slow for our needs, such as MD5 or SHA). +// +// Note: This is work in progress, and has not yet been tested thoroughly. Use at your own risk. +// Please report any bugs, issues, or bothersome cases to SquirrelEiserloh at gmail.com. +// +// The following functions are all based on a simple bit-noise hash function which returns an +// uint32_t containing 32 reasonably-well-scrambled bits, based on a given (signed) +// integer input parameter (position/index) and [optional] seed. Kind of like looking up a +// value in an infinitely large [non-existent] table of previously rolled random numbers. +// +// These functions are deterministic and random-access / order-independent (i.e. state-free), +// so they are particularly well-suited for use in smoothed/fractal/simplex/Perlin noise +// functions and out-of-order (or on-demand) procedural content generation (i.e. that mountain +// village is the same whether you generated it first or last, ahead of time or just now). +// +// The N-dimensional variations simply hash their multidimensional coordinates down to a single +// 32-bit index and then proceed as usual, so while results are not unique they should +// (hopefully) not seem locally predictable or repetitive. +// +// Modified by Erik Scholz 2021, no change to the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + + +//----------------------------------------------------------------------------------------------- +// Raw pseudorandom noise functions (random-access / deterministic). Basis of all other noise. +// +uint32_t Get1dNoiseUint32( int32_t index, uint32_t seed=0 ); +uint32_t Get2dNoiseUint32( int32_t indexX, int32_t indexY, uint32_t seed=0 ); +uint32_t Get3dNoiseUint32( int32_t indexX, int32_t indexY, int32_t indexZ, uint32_t seed=0 ); +uint32_t Get4dNoiseUint32( int32_t indexX, int32_t indexY, int32_t indexZ, int32_t indexT, uint32_t seed=0 ); + +//----------------------------------------------------------------------------------------------- +// Same functions, mapped to floats in [0,1] for convenience. +// +float Get1dNoiseZeroToOne( int32_t index, uint32_t seed=0 ); +float Get2dNoiseZeroToOne( int32_t indexX, int32_t indexY, uint32_t seed=0 ); +float Get3dNoiseZeroToOne( int32_t indexX, int32_t indexY, int32_t indexZ, uint32_t seed=0 ); +float Get4dNoiseZeroToOne( int32_t indexX, int32_t indexY, int32_t indexZ, int32_t indexT, uint32_t seed=0 ); + +//----------------------------------------------------------------------------------------------- +// Same functions, mapped to floats in [-1,1] for convenience. +// +float Get1dNoiseNegOneToOne( int32_t index, uint32_t seed=0 ); +float Get2dNoiseNegOneToOne( int32_t indexX, int32_t indexY, uint32_t seed=0 ); +float Get3dNoiseNegOneToOne( int32_t indexX, int32_t indexY, int32_t indexZ, uint32_t seed=0 ); +float Get4dNoiseNegOneToOne( int32_t indexX, int32_t indexY, int32_t indexZ, int32_t indexT, uint32_t seed=0 ); + + +///////////////////////////////////////////////////////////////////////////////////////////////// +// Simple functions inlined below +///////////////////////////////////////////////////////////////////////////////////////////////// + + +//----------------------------------------------------------------------------------------------- +inline uint32_t Get2dNoiseUint32( int32_t indexX, int32_t indexY, uint32_t seed ) +{ + const int32_t PRIME_NUMBER = 198491317; // Large prime number with non-boring bits + return Get1dNoiseUint32( indexX + (PRIME_NUMBER * indexY), seed ); +} + + +//----------------------------------------------------------------------------------------------- +inline uint32_t Get3dNoiseUint32( int32_t indexX, int32_t indexY, int32_t indexZ, uint32_t seed ) +{ + const int32_t PRIME1 = 198491317; // Large prime number with non-boring bits + const int32_t PRIME2 = 6542989; // Large prime number with distinct and non-boring bits + return Get1dNoiseUint32( indexX + (PRIME1 * indexY) + (PRIME2 * indexZ), seed ); +} + + +//----------------------------------------------------------------------------------------------- +inline uint32_t Get4dNoiseUint32( int32_t indexX, int32_t indexY, int32_t indexZ, int32_t indexT, uint32_t seed ) +{ + const int32_t PRIME1 = 198491317; // Large prime number with non-boring bits + const int32_t PRIME2 = 6542989; // Large prime number with distinct and non-boring bits + const int32_t PRIME3 = 357239; // Large prime number with distinct and non-boring bits + return Get1dNoiseUint32( indexX + (PRIME1 * indexY) + (PRIME2 * indexZ) + (PRIME3 * indexT), seed ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get1dNoiseZeroToOne( int32_t index, uint32_t seed ) +{ + const double ONE_OVER_MAX_UINT = (1.0 / (double) 0xFFFFFFFF); + return (float)( ONE_OVER_MAX_UINT * (double) Get1dNoiseUint32( index, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get2dNoiseZeroToOne( int32_t indexX, int32_t indexY, uint32_t seed ) +{ + const double ONE_OVER_MAX_UINT = (1.0 / (double) 0xFFFFFFFF); + return (float)( ONE_OVER_MAX_UINT * (double) Get2dNoiseUint32( indexX, indexY, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get3dNoiseZeroToOne( int32_t indexX, int32_t indexY, int32_t indexZ, uint32_t seed ) +{ + const double ONE_OVER_MAX_UINT = (1.0 / (double) 0xFFFFFFFF); + return (float)( ONE_OVER_MAX_UINT * (double) Get3dNoiseUint32( indexX, indexY, indexZ, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get4dNoiseZeroToOne( int32_t indexX, int32_t indexY, int32_t indexZ, int32_t indexT, uint32_t seed ) +{ + const double ONE_OVER_MAX_UINT = (1.0 / (double) 0xFFFFFFFF); + return (float)( ONE_OVER_MAX_UINT * (double) Get4dNoiseUint32( indexX, indexY, indexZ, indexT, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get1dNoiseNegOneToOne( int32_t index, uint32_t seed ) +{ + const double ONE_OVER_MAX_INT = (1.0 / (double) 0x7FFFFFFF); + return (float)( ONE_OVER_MAX_INT * (double) (int32_t) Get1dNoiseUint32( index, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get2dNoiseNegOneToOne( int32_t indexX, int32_t indexY, uint32_t seed ) +{ + const double ONE_OVER_MAX_INT = (1.0 / (double) 0x7FFFFFFF); + return (float)( ONE_OVER_MAX_INT * (double) (int32_t) Get2dNoiseUint32( indexX, indexY, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get3dNoiseNegOneToOne( int32_t indexX, int32_t indexY, int32_t indexZ, uint32_t seed ) +{ + const double ONE_OVER_MAX_INT = (1.0 / (double) 0x7FFFFFFF); + return (float)( ONE_OVER_MAX_INT * (double) (int32_t) Get3dNoiseUint32( indexX, indexY, indexZ, seed ) ); +} + + +//----------------------------------------------------------------------------------------------- +inline float Get4dNoiseNegOneToOne( int32_t indexX, int32_t indexY, int32_t indexZ, int32_t indexT, uint32_t seed ) +{ + const double ONE_OVER_MAX_INT = (1.0 / (double) 0x7FFFFFFF); + return (float)( ONE_OVER_MAX_INT * (double) (int32_t) Get4dNoiseUint32( indexX, indexY, indexZ, indexT, seed ) ); +} + +} // SquirrelNoise4 + diff --git a/external/SquirrelNoise/src/squirrel_noise/SmoothNoise.cpp b/external/SquirrelNoise/src/squirrel_noise/SmoothNoise.cpp new file mode 100644 index 0000000..1a9d2b8 --- /dev/null +++ b/external/SquirrelNoise/src/squirrel_noise/SmoothNoise.cpp @@ -0,0 +1,756 @@ +//----------------------------------------------------------------------------------------------- +// SmoothNoise.cpp +// +#include "./RawNoise.hpp" + +#include +#include +#include + +#include +#include + + +///////////////////////////////////////////////////////////////////////////////////////////////// +// For all fractal (and Perlin) noise functions, the following internal naming conventions +// are used, primarily to help me visualize 3D and 4D constructs clearly. They need not +// have any actual bearing on / relationship to actual external coordinate systems. +// +// 1D noise: only X (+east / -west) +// 2D noise: also Y (+north / -south) +// 3D noise: also Z (+above / -below) +// 4D noise: also T (+after / -before) +///////////////////////////////////////////////////////////////////////////////////////////////// + +namespace SquirrelNoise4 { + +namespace Easing { + static float SmoothStep( float t ) { + //return 2*(t*t*t) - 3*(t*t); + return t*t * (2*t-3); // optimised + } +} // Easing + + +//----------------------------------------------------------------------------------------------- +float Compute1dFractal( float position, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float currentPosition = position * (1.f / scale); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine noise values at nearby integer "grid point" positions + float positionFloor = ::glm::floor( currentPosition ); + int32_t indexWest = (int32_t) positionFloor; + int32_t indexEast = indexWest + 1; + float valueWest = Get1dNoiseZeroToOne( indexWest, seed ); + float valueEast = Get1dNoiseZeroToOne( indexEast, seed ); + + // Do a smoothed (nonlinear) weighted average of nearby grid point values + float distanceFromWest = currentPosition - positionFloor; + float weightEast = Easing::SmoothStep( distanceFromWest ); // Gives rounder (nonlinear) results + float weightWest = 1.f - weightEast; + float noiseZeroToOne = (valueWest * weightWest) + (valueEast * weightEast); + float noiseThisOctave = 2.f * (noiseZeroToOne - 0.5f); // Map from [0,1] to [-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPosition *= octaveScale; + currentPosition += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used! + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +float Compute2dFractal( float posX, float posY, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float invScale = (1.f / scale); + ::glm::vec2 currentPos( posX * invScale, posY * invScale ); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine noise values at nearby integer "grid point" positions + ::glm::vec2 cellMins( ::glm::floor( currentPos.x ), ::glm::floor( currentPos.y ) ); + int32_t indexWestX = (int32_t) cellMins.x; + int32_t indexSouthY = (int32_t) cellMins.y; + int32_t indexEastX = indexWestX + 1; + int32_t indexNorthY = indexSouthY + 1; + float valueSouthWest = Get2dNoiseZeroToOne( indexWestX, indexSouthY, seed ); + float valueSouthEast = Get2dNoiseZeroToOne( indexEastX, indexSouthY, seed ); + float valueNorthWest = Get2dNoiseZeroToOne( indexWestX, indexNorthY, seed ); + float valueNorthEast = Get2dNoiseZeroToOne( indexEastX, indexNorthY, seed ); + + // Do a smoothed (nonlinear) weighted average of nearby grid point values + ::glm::vec2 displacementFromMins = currentPos - cellMins; + float weightEast = Easing::SmoothStep( displacementFromMins.x ); + float weightNorth = Easing::SmoothStep( displacementFromMins.y ); + float weightWest = 1.f - weightEast; + float weightSouth = 1.f - weightNorth; + + float blendSouth = (weightEast * valueSouthEast) + (weightWest * valueSouthWest); + float blendNorth = (weightEast * valueNorthEast) + (weightWest * valueNorthWest); + float blendTotal = (weightSouth * blendSouth) + (weightNorth * blendNorth); + float noiseThisOctave = 2.f * (blendTotal - 0.5f); // Map from [0,1] to [-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPos *= octaveScale; + currentPos.x += OCTAVE_OFFSET; // Add "irrational" offsets to noise position components + currentPos.y += OCTAVE_OFFSET; // at each octave to break up their grid alignment + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +float Compute3dFractal( float posX, float posY, float posZ, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float invScale = (1.f / scale); + ::glm::vec3 currentPos( posX * invScale, posY * invScale, posZ * invScale ); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine noise values at nearby integer "grid point" positions + ::glm::vec3 cellMins( ::glm::floor( currentPos.x ), ::glm::floor( currentPos.y ), ::glm::floor( currentPos.z ) ); + int32_t indexWestX = (int32_t) cellMins.x; + int32_t indexSouthY = (int32_t) cellMins.y; + int32_t indexBelowZ = (int32_t) cellMins.z; + int32_t indexEastX = indexWestX + 1; + int32_t indexNorthY = indexSouthY + 1; + int32_t indexAboveZ = indexBelowZ + 1; + + // Noise grid cell has 8 corners in 3D + float aboveSouthWest = Get3dNoiseZeroToOne( indexWestX, indexSouthY, indexAboveZ, seed ); + float aboveSouthEast = Get3dNoiseZeroToOne( indexEastX, indexSouthY, indexAboveZ, seed ); + float aboveNorthWest = Get3dNoiseZeroToOne( indexWestX, indexNorthY, indexAboveZ, seed ); + float aboveNorthEast = Get3dNoiseZeroToOne( indexEastX, indexNorthY, indexAboveZ, seed ); + float belowSouthWest = Get3dNoiseZeroToOne( indexWestX, indexSouthY, indexBelowZ, seed ); + float belowSouthEast = Get3dNoiseZeroToOne( indexEastX, indexSouthY, indexBelowZ, seed ); + float belowNorthWest = Get3dNoiseZeroToOne( indexWestX, indexNorthY, indexBelowZ, seed ); + float belowNorthEast = Get3dNoiseZeroToOne( indexEastX, indexNorthY, indexBelowZ, seed ); + + // Do a smoothed (nonlinear) weighted average of nearby grid point values + ::glm::vec3 displacementFromMins = currentPos - cellMins; + + float weightEast = Easing::SmoothStep( displacementFromMins.x ); + float weightNorth = Easing::SmoothStep( displacementFromMins.y ); + float weightAbove = Easing::SmoothStep( displacementFromMins.z ); + float weightWest = 1.f - weightEast; + float weightSouth = 1.f - weightNorth; + float weightBelow = 1.f - weightAbove; + + // 8-way blend (8 -> 4 -> 2 -> 1) + float blendBelowSouth = (weightEast * belowSouthEast) + (weightWest * belowSouthWest); + float blendBelowNorth = (weightEast * belowNorthEast) + (weightWest * belowNorthWest); + float blendAboveSouth = (weightEast * aboveSouthEast) + (weightWest * aboveSouthWest); + float blendAboveNorth = (weightEast * aboveNorthEast) + (weightWest * aboveNorthWest); + float blendBelow = (weightSouth * blendBelowSouth) + (weightNorth * blendBelowNorth); + float blendAbove = (weightSouth * blendAboveSouth) + (weightNorth * blendAboveNorth); + float blendTotal = (weightBelow * blendBelow) + (weightAbove * blendAbove); + float noiseThisOctave = 2.f * (blendTotal - 0.5f); // Map from [0,1] to [-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPos *= octaveScale; + currentPos.x += OCTAVE_OFFSET; // Add "irrational" offsets to noise position components + currentPos.y += OCTAVE_OFFSET; // at each octave to break up their grid alignment + currentPos.z += OCTAVE_OFFSET; + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +float Compute4dFractal( float posX, float posY, float posZ, float posT, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float invScale = (1.f / scale); + ::glm::vec4 currentPos( posX * invScale, posY * invScale, posZ * invScale, posT * invScale ); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine noise values at nearby integer "grid point" positions + ::glm::vec4 cellMins( ::glm::floor( currentPos.x ), ::glm::floor( currentPos.y ), ::glm::floor( currentPos.z ), ::glm::floor( currentPos.w ) ); + int32_t indexWestX = (int32_t) cellMins.x; + int32_t indexSouthY = (int32_t) cellMins.y; + int32_t indexBelowZ = (int32_t) cellMins.z; + int32_t indexBeforeT = (int32_t) cellMins.w; + int32_t indexEastX = indexWestX + 1; + int32_t indexNorthY = indexSouthY + 1; + int32_t indexAboveZ = indexBelowZ + 1; + int32_t indexAfterT = indexBeforeT + 1; + + // Noise grid cell has 16 "corners" in 4D + float beforeBelowSW = Get4dNoiseZeroToOne( indexWestX, indexSouthY, indexBelowZ, indexBeforeT, seed ); + float beforeBelowSE = Get4dNoiseZeroToOne( indexEastX, indexSouthY, indexBelowZ, indexBeforeT, seed ); + float beforeBelowNW = Get4dNoiseZeroToOne( indexWestX, indexNorthY, indexBelowZ, indexBeforeT, seed ); + float beforeBelowNE = Get4dNoiseZeroToOne( indexEastX, indexNorthY, indexBelowZ, indexBeforeT, seed ); + float beforeAboveSW = Get4dNoiseZeroToOne( indexWestX, indexSouthY, indexAboveZ, indexBeforeT, seed ); + float beforeAboveSE = Get4dNoiseZeroToOne( indexEastX, indexSouthY, indexAboveZ, indexBeforeT, seed ); + float beforeAboveNW = Get4dNoiseZeroToOne( indexWestX, indexNorthY, indexAboveZ, indexBeforeT, seed ); + float beforeAboveNE = Get4dNoiseZeroToOne( indexEastX, indexNorthY, indexAboveZ, indexBeforeT, seed ); + + float afterBelowSW = Get4dNoiseZeroToOne( indexWestX, indexSouthY, indexBelowZ, indexAfterT, seed ); + float afterBelowSE = Get4dNoiseZeroToOne( indexEastX, indexSouthY, indexBelowZ, indexAfterT, seed ); + float afterBelowNW = Get4dNoiseZeroToOne( indexWestX, indexNorthY, indexBelowZ, indexAfterT, seed ); + float afterBelowNE = Get4dNoiseZeroToOne( indexEastX, indexNorthY, indexBelowZ, indexAfterT, seed ); + float afterAboveSW = Get4dNoiseZeroToOne( indexWestX, indexSouthY, indexAboveZ, indexAfterT, seed ); + float afterAboveSE = Get4dNoiseZeroToOne( indexEastX, indexSouthY, indexAboveZ, indexAfterT, seed ); + float afterAboveNW = Get4dNoiseZeroToOne( indexWestX, indexNorthY, indexAboveZ, indexAfterT, seed ); + float afterAboveNE = Get4dNoiseZeroToOne( indexEastX, indexNorthY, indexAboveZ, indexAfterT, seed ); + + // Do a smoothed (nonlinear) weighted average of nearby grid point values + ::glm::vec4 displacementFromMins = currentPos - cellMins; + + float weightEast = Easing::SmoothStep( displacementFromMins.x ); + float weightNorth = Easing::SmoothStep( displacementFromMins.y ); + float weightAbove = Easing::SmoothStep( displacementFromMins.z ); + float weightAfter = Easing::SmoothStep( displacementFromMins.w ); + float weightWest = 1.f - weightEast; + float weightSouth = 1.f - weightNorth; + float weightBelow = 1.f - weightAbove; + float weightBefore = 1.f - weightAfter; + + // 16-way blend (16 -> 8 -> 4 -> 2 -> 1) + float blendBeforeBelowSouth = (weightEast * beforeBelowSE) + (weightWest * beforeBelowSW); + float blendBeforeBelowNorth = (weightEast * beforeBelowNE) + (weightWest * beforeBelowNW); + float blendBeforeAboveSouth = (weightEast * beforeAboveSE) + (weightWest * beforeAboveSW); + float blendBeforeAboveNorth = (weightEast * beforeAboveNE) + (weightWest * beforeAboveNW); + float blendAfterBelowSouth = (weightEast * afterBelowSE) + (weightWest * afterBelowSW); + float blendAfterBelowNorth = (weightEast * afterBelowNE) + (weightWest * afterBelowNW); + float blendAfterAboveSouth = (weightEast * afterAboveSE) + (weightWest * afterAboveSW); + float blendAfterAboveNorth = (weightEast * afterAboveNE) + (weightWest * afterAboveNW); + float blendBeforeBelow = (weightSouth * blendBeforeBelowSouth) + (weightNorth * blendBeforeBelowNorth); + float blendBeforeAbove = (weightSouth * blendBeforeAboveSouth) + (weightNorth * blendBeforeAboveNorth); + float blendAfterBelow = (weightSouth * blendAfterBelowSouth) + (weightNorth * blendAfterBelowNorth); + float blendAfterAbove = (weightSouth * blendAfterAboveSouth) + (weightNorth * blendAfterAboveNorth); + float blendBefore = (weightBelow * blendBeforeBelow) + (weightAbove * blendBeforeAbove); + float blendAfter = (weightBelow * blendAfterBelow) + (weightAbove * blendAfterAbove); + float blendTotal = (weightBefore * blendBefore) + (weightAfter * blendAfter); + float noiseThisOctave = 2.f * (blendTotal - 0.5f); // Map from [0,1] to [-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPos *= octaveScale; + currentPos.x += OCTAVE_OFFSET; // Add "irrational" offsets to noise position components + currentPos.y += OCTAVE_OFFSET; // at each octave to break up their grid alignment + currentPos.z += OCTAVE_OFFSET; + currentPos.w += OCTAVE_OFFSET; + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +// Perlin noise is fractal noise with "gradient vector smoothing" applied. +// +// In 1D, the gradients are trivial: -1.0 or 1.0, so resulting noise is boring at one octave. +// +float Compute1dPerlin( float position, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + const float gradients[2] = { -1.f, 1.f }; // 1D unit "gradient" vectors; one back, one forward + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float currentPosition = position * (1.f / scale); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine random "gradient vectors" (just +1 or -1 for 1D Perlin) for surrounding corners + float positionFloor = (float) ::glm::floor( currentPosition ); + int32_t indexWest = (int32_t) positionFloor; + int32_t indexEast = indexWest + 1; + float gradientWest = gradients[ Get1dNoiseUint32( indexWest, seed ) & 0x00000001 ]; + float gradientEast = gradients[ Get1dNoiseUint32( indexEast, seed ) & 0x00000001 ]; + + // Dot each point's gradient with displacement from point to position + float displacementFromWest = currentPosition - positionFloor; // always positive + float displacementFromEast = displacementFromWest - 1.f; // always negative + float dotWest = gradientWest * displacementFromWest; // 1D "dot product" is... multiply + float dotEast = gradientEast * displacementFromEast; + + // Do a smoothed (nonlinear) weighted average of dot results + float weightEast = Easing::SmoothStep( displacementFromWest ); + float weightWest = 1.f - weightEast; + float blendTotal = (weightWest * dotWest) + (weightEast * dotEast); + float noiseThisOctave = 2.f * blendTotal; // 1D Perlin is in [-.5,.5]; map to [-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPosition *= octaveScale; + currentPosition += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +// Perlin noise is fractal noise with "gradient vector smoothing" applied. +// +// In 2D, gradients are unit-length vectors in various directions with even angular distribution. +// +float Compute2dPerlin( float posX, float posY, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + const ::glm::vec2 gradients[ 8 ] = // Normalized unit vectors in 8 quarter-cardinal directions + { + ::glm::vec2( +0.923879533f, +0.382683432f ), // 22.5 degrees (ENE) + ::glm::vec2( +0.382683432f, +0.923879533f ), // 67.5 degrees (NNE) + ::glm::vec2( -0.382683432f, +0.923879533f ), // 112.5 degrees (NNW) + ::glm::vec2( -0.923879533f, +0.382683432f ), // 157.5 degrees (WNW) + ::glm::vec2( -0.923879533f, -0.382683432f ), // 202.5 degrees (WSW) + ::glm::vec2( -0.382683432f, -0.923879533f ), // 247.5 degrees (SSW) + ::glm::vec2( +0.382683432f, -0.923879533f ), // 292.5 degrees (SSE) + ::glm::vec2( +0.923879533f, -0.382683432f ) // 337.5 degrees (ESE) + }; + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float invScale = (1.f / scale); + ::glm::vec2 currentPos( posX * invScale, posY * invScale ); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine random unit "gradient vectors" for surrounding corners + ::glm::vec2 cellMins( ::glm::floor( currentPos.x ), ::glm::floor( currentPos.y ) ); + ::glm::vec2 cellMaxs( cellMins.x + 1.f, cellMins.y + 1.f ); + int32_t indexWestX = (int32_t) cellMins.x; + int32_t indexSouthY = (int32_t) cellMins.y; + int32_t indexEastX = indexWestX + 1; + int32_t indexNorthY = indexSouthY + 1; + + uint32_t noiseSW = Get2dNoiseUint32( indexWestX, indexSouthY, seed ); + uint32_t noiseSE = Get2dNoiseUint32( indexEastX, indexSouthY, seed ); + uint32_t noiseNW = Get2dNoiseUint32( indexWestX, indexNorthY, seed ); + uint32_t noiseNE = Get2dNoiseUint32( indexEastX, indexNorthY, seed ); + + const ::glm::vec2& gradientSW = gradients[ noiseSW & 0x00000007 ]; + const ::glm::vec2& gradientSE = gradients[ noiseSE & 0x00000007 ]; + const ::glm::vec2& gradientNW = gradients[ noiseNW & 0x00000007 ]; + const ::glm::vec2& gradientNE = gradients[ noiseNE & 0x00000007 ]; + + // Dot each corner's gradient with displacement from corner to position + ::glm::vec2 displacementFromSW( currentPos.x - cellMins.x, currentPos.y - cellMins.y ); + ::glm::vec2 displacementFromSE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y ); + ::glm::vec2 displacementFromNW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y ); + ::glm::vec2 displacementFromNE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y ); + + float dotSouthWest = ::glm::dot( gradientSW, displacementFromSW ); + float dotSouthEast = ::glm::dot( gradientSE, displacementFromSE ); + float dotNorthWest = ::glm::dot( gradientNW, displacementFromNW ); + float dotNorthEast = ::glm::dot( gradientNE, displacementFromNE ); + + // Do a smoothed (nonlinear) weighted average of dot results + float weightEast = Easing::SmoothStep( displacementFromSW.x ); + float weightNorth = Easing::SmoothStep( displacementFromSW.y ); + float weightWest = 1.f - weightEast; + float weightSouth = 1.f - weightNorth; + + float blendSouth = (weightEast * dotSouthEast) + (weightWest * dotSouthWest); + float blendNorth = (weightEast * dotNorthEast) + (weightWest * dotNorthWest); + float blendTotal = (weightSouth * blendSouth) + (weightNorth * blendNorth); + float noiseThisOctave = blendTotal * (1.f / 0.662578106f); // 2D Perlin is in [-.662578106,.662578106]; map to ~[-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPos *= octaveScale; + currentPos.x += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + currentPos.y += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +// Perlin noise is fractal noise with "gradient vector smoothing" applied. +// +// In 3D, gradients are unit-length vectors in random (3D) directions. +// +float Compute3dPerlin( float posX, float posY, float posZ, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + const float fSQRT_3_OVER_3 = 0.577350269189f; + const ::glm::vec3 gradients[ 8 ] = // Traditional "12 edges" requires modulus and isn't any better. + { + ::glm::vec3( +fSQRT_3_OVER_3, +fSQRT_3_OVER_3, +fSQRT_3_OVER_3 ), // Normalized unit 3D vectors + ::glm::vec3( -fSQRT_3_OVER_3, +fSQRT_3_OVER_3, +fSQRT_3_OVER_3 ), // pointing toward cube + ::glm::vec3( +fSQRT_3_OVER_3, -fSQRT_3_OVER_3, +fSQRT_3_OVER_3 ), // corners, so components + ::glm::vec3( -fSQRT_3_OVER_3, -fSQRT_3_OVER_3, +fSQRT_3_OVER_3 ), // are all sqrt(3)/3, i.e. + ::glm::vec3( +fSQRT_3_OVER_3, +fSQRT_3_OVER_3, -fSQRT_3_OVER_3 ), // 0.5773502691896257645091f. + ::glm::vec3( -fSQRT_3_OVER_3, +fSQRT_3_OVER_3, -fSQRT_3_OVER_3 ), // These are slightly better + ::glm::vec3( +fSQRT_3_OVER_3, -fSQRT_3_OVER_3, -fSQRT_3_OVER_3 ), // than axes (1,0,0) and much + ::glm::vec3( -fSQRT_3_OVER_3, -fSQRT_3_OVER_3, -fSQRT_3_OVER_3 ) // faster than edges (1,1,0). + }; + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float invScale = (1.f / scale); + ::glm::vec3 currentPos( posX * invScale, posY * invScale, posZ * invScale ); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine random unit "gradient vectors" for surrounding corners + ::glm::vec3 cellMins( ::glm::floor( currentPos.x ), ::glm::floor( currentPos.y ), ::glm::floor( currentPos.z ) ); + ::glm::vec3 cellMaxs( cellMins.x + 1.f, cellMins.y + 1.f, cellMins.z + 1.f ); + int32_t indexWestX = (int32_t) cellMins.x; + int32_t indexSouthY = (int32_t) cellMins.y; + int32_t indexBelowZ = (int32_t) cellMins.z; + int32_t indexEastX = indexWestX + 1; + int32_t indexNorthY = indexSouthY + 1; + int32_t indexAboveZ = indexBelowZ + 1; + + uint32_t noiseBelowSW = Get3dNoiseUint32( indexWestX, indexSouthY, indexBelowZ, seed ); + uint32_t noiseBelowSE = Get3dNoiseUint32( indexEastX, indexSouthY, indexBelowZ, seed ); + uint32_t noiseBelowNW = Get3dNoiseUint32( indexWestX, indexNorthY, indexBelowZ, seed ); + uint32_t noiseBelowNE = Get3dNoiseUint32( indexEastX, indexNorthY, indexBelowZ, seed ); + uint32_t noiseAboveSW = Get3dNoiseUint32( indexWestX, indexSouthY, indexAboveZ, seed ); + uint32_t noiseAboveSE = Get3dNoiseUint32( indexEastX, indexSouthY, indexAboveZ, seed ); + uint32_t noiseAboveNW = Get3dNoiseUint32( indexWestX, indexNorthY, indexAboveZ, seed ); + uint32_t noiseAboveNE = Get3dNoiseUint32( indexEastX, indexNorthY, indexAboveZ, seed ); + + ::glm::vec3 gradientBelowSW = gradients[ noiseBelowSW & 0x00000007 ]; + ::glm::vec3 gradientBelowSE = gradients[ noiseBelowSE & 0x00000007 ]; + ::glm::vec3 gradientBelowNW = gradients[ noiseBelowNW & 0x00000007 ]; + ::glm::vec3 gradientBelowNE = gradients[ noiseBelowNE & 0x00000007 ]; + ::glm::vec3 gradientAboveSW = gradients[ noiseAboveSW & 0x00000007 ]; + ::glm::vec3 gradientAboveSE = gradients[ noiseAboveSE & 0x00000007 ]; + ::glm::vec3 gradientAboveNW = gradients[ noiseAboveNW & 0x00000007 ]; + ::glm::vec3 gradientAboveNE = gradients[ noiseAboveNE & 0x00000007 ]; + + // Dot each corner's gradient with displacement from corner to position + ::glm::vec3 displacementFromBelowSW( currentPos.x - cellMins.x, currentPos.y - cellMins.y, currentPos.z - cellMins.z ); + ::glm::vec3 displacementFromBelowSE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y, currentPos.z - cellMins.z ); + ::glm::vec3 displacementFromBelowNW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y, currentPos.z - cellMins.z ); + ::glm::vec3 displacementFromBelowNE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y, currentPos.z - cellMins.z ); + ::glm::vec3 displacementFromAboveSW( currentPos.x - cellMins.x, currentPos.y - cellMins.y, currentPos.z - cellMaxs.z ); + ::glm::vec3 displacementFromAboveSE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y, currentPos.z - cellMaxs.z ); + ::glm::vec3 displacementFromAboveNW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y, currentPos.z - cellMaxs.z ); + ::glm::vec3 displacementFromAboveNE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y, currentPos.z - cellMaxs.z ); + + float dotBelowSW = ::glm::dot( gradientBelowSW, displacementFromBelowSW ); + float dotBelowSE = ::glm::dot( gradientBelowSE, displacementFromBelowSE ); + float dotBelowNW = ::glm::dot( gradientBelowNW, displacementFromBelowNW ); + float dotBelowNE = ::glm::dot( gradientBelowNE, displacementFromBelowNE ); + float dotAboveSW = ::glm::dot( gradientAboveSW, displacementFromAboveSW ); + float dotAboveSE = ::glm::dot( gradientAboveSE, displacementFromAboveSE ); + float dotAboveNW = ::glm::dot( gradientAboveNW, displacementFromAboveNW ); + float dotAboveNE = ::glm::dot( gradientAboveNE, displacementFromAboveNE ); + + // Do a smoothed (nonlinear) weighted average of dot results + float weightEast = Easing::SmoothStep( displacementFromBelowSW.x ); + float weightNorth = Easing::SmoothStep( displacementFromBelowSW.y ); + float weightAbove = Easing::SmoothStep( displacementFromBelowSW.z ); + float weightWest = 1.f - weightEast; + float weightSouth = 1.f - weightNorth; + float weightBelow = 1.f - weightAbove; + + // 8-way blend (8 -> 4 -> 2 -> 1) + float blendBelowSouth = (weightEast * dotBelowSE) + (weightWest * dotBelowSW); + float blendBelowNorth = (weightEast * dotBelowNE) + (weightWest * dotBelowNW); + float blendAboveSouth = (weightEast * dotAboveSE) + (weightWest * dotAboveSW); + float blendAboveNorth = (weightEast * dotAboveNE) + (weightWest * dotAboveNW); + float blendBelow = (weightSouth * blendBelowSouth) + (weightNorth * blendBelowNorth); + float blendAbove = (weightSouth * blendAboveSouth) + (weightNorth * blendAboveNorth); + float blendTotal = (weightBelow * blendBelow) + (weightAbove * blendAbove); + float noiseThisOctave = blendTotal * (1.f / 0.793856621f); // 3D Perlin is in [-.793856621,.793856621]; map to ~[-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPos *= octaveScale; + currentPos.x += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + currentPos.y += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + currentPos.z += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + + +//----------------------------------------------------------------------------------------------- +// Perlin noise is fractal noise with "gradient vector smoothing" applied. +// +// In 4D, gradients are unit-length hyper-vectors in random (4D) directions. +// +float Compute4dPerlin( float posX, float posY, float posZ, float posT, float scale, uint32_t numOctaves, float octavePersistence, float octaveScale, bool renormalize, uint32_t seed ) +{ + const float OCTAVE_OFFSET = 0.636764989593174f; // Translation/bias to add to each octave + + const ::glm::vec4 gradients[ 16 ] = // Hard to tell if this is any better in 4D than just having 8 + { + ::glm::vec4( +0.5f, +0.5f, +0.5f, +0.5f ), // Normalized unit 4D vectors pointing toward each + ::glm::vec4( -0.5f, +0.5f, +0.5f, +0.5f ), // of the 16 hypercube corners, so components are + ::glm::vec4( +0.5f, -0.5f, +0.5f, +0.5f ), // all sqrt(4)/4, i.e. one-half. + ::glm::vec4( -0.5f, -0.5f, +0.5f, +0.5f ), // + ::glm::vec4( +0.5f, +0.5f, -0.5f, +0.5f ), // It's hard to tell whether these are any better + ::glm::vec4( -0.5f, +0.5f, -0.5f, +0.5f ), // or worse than vectors facing axes (1,0,0,0) or + ::glm::vec4( +0.5f, -0.5f, -0.5f, +0.5f ), // 3D edges (.7,.7,0,0) or 4D edges (.57,.57,.57,0) + ::glm::vec4( -0.5f, -0.5f, -0.5f, +0.5f ), // but less-axial gradients looked a little better + ::glm::vec4( +0.5f, +0.5f, +0.5f, -0.5f ), // with 2D and 3D noise so I'm assuming this is as + ::glm::vec4( -0.5f, +0.5f, +0.5f, -0.5f ), // good or better as any other gradient-selection + ::glm::vec4( +0.5f, -0.5f, +0.5f, -0.5f ), // scheme (and is crazy-fast). *shrug* + ::glm::vec4( -0.5f, -0.5f, +0.5f, -0.5f ), // + ::glm::vec4( +0.5f, +0.5f, -0.5f, -0.5f ), // Plus, we want a power-of-two number of evenly- + ::glm::vec4( -0.5f, +0.5f, -0.5f, -0.5f ), // distributed gradients, so we can cheaply select + ::glm::vec4( +0.5f, -0.5f, -0.5f, -0.5f ), // one from bit-noise (use bit-mask, not modulus). + ::glm::vec4( -0.5f, -0.5f, -0.5f, -0.5f ) // + }; + + float totalNoise = 0.f; + float totalAmplitude = 0.f; + float currentAmplitude = 1.f; + float invScale = (1.f / scale); + ::glm::vec4 currentPos( posX * invScale, posY * invScale, posZ * invScale, posT * invScale ); + + for( uint32_t octaveNum = 0; octaveNum < numOctaves; ++ octaveNum ) + { + // Determine random unit "gradient vectors" for 16 surrounding 4D (hypercube) cell corners + ::glm::vec4 cellMins( ::glm::floor( currentPos.x ), ::glm::floor( currentPos.y ), ::glm::floor( currentPos.z ), ::glm::floor( currentPos.w ) ); + ::glm::vec4 cellMaxs( cellMins.x + 1.f, cellMins.y + 1.f, cellMins.z + 1.f, cellMins.w + 1.f ); + int32_t indexWestX = (int32_t) cellMins.x; + int32_t indexSouthY = (int32_t) cellMins.y; + int32_t indexBelowZ = (int32_t) cellMins.z; + int32_t indexBeforeT = (int32_t) cellMins.w; + int32_t indexEastX = indexWestX + 1; + int32_t indexNorthY = indexSouthY + 1; + int32_t indexAboveZ = indexBelowZ + 1; + int32_t indexAfterT = indexBeforeT + 1; + + // "BeforeBSW" stands for "BeforeBelowSouthWest" below (i.e. 4D hypercube mins), etc. + uint32_t noiseBeforeBSW = Get4dNoiseUint32( indexWestX, indexSouthY, indexBelowZ, indexBeforeT, seed ); + uint32_t noiseBeforeBSE = Get4dNoiseUint32( indexEastX, indexSouthY, indexBelowZ, indexBeforeT, seed ); + uint32_t noiseBeforeBNW = Get4dNoiseUint32( indexWestX, indexNorthY, indexBelowZ, indexBeforeT, seed ); + uint32_t noiseBeforeBNE = Get4dNoiseUint32( indexEastX, indexNorthY, indexBelowZ, indexBeforeT, seed ); + uint32_t noiseBeforeASW = Get4dNoiseUint32( indexWestX, indexSouthY, indexAboveZ, indexBeforeT, seed ); + uint32_t noiseBeforeASE = Get4dNoiseUint32( indexEastX, indexSouthY, indexAboveZ, indexBeforeT, seed ); + uint32_t noiseBeforeANW = Get4dNoiseUint32( indexWestX, indexNorthY, indexAboveZ, indexBeforeT, seed ); + uint32_t noiseBeforeANE = Get4dNoiseUint32( indexEastX, indexNorthY, indexAboveZ, indexBeforeT, seed ); + uint32_t noiseAfterBSW = Get4dNoiseUint32( indexWestX, indexSouthY, indexBelowZ, indexAfterT, seed ); + uint32_t noiseAfterBSE = Get4dNoiseUint32( indexEastX, indexSouthY, indexBelowZ, indexAfterT, seed ); + uint32_t noiseAfterBNW = Get4dNoiseUint32( indexWestX, indexNorthY, indexBelowZ, indexAfterT, seed ); + uint32_t noiseAfterBNE = Get4dNoiseUint32( indexEastX, indexNorthY, indexBelowZ, indexAfterT, seed ); + uint32_t noiseAfterASW = Get4dNoiseUint32( indexWestX, indexSouthY, indexAboveZ, indexAfterT, seed ); + uint32_t noiseAfterASE = Get4dNoiseUint32( indexEastX, indexSouthY, indexAboveZ, indexAfterT, seed ); + uint32_t noiseAfterANW = Get4dNoiseUint32( indexWestX, indexNorthY, indexAboveZ, indexAfterT, seed ); + uint32_t noiseAfterANE = Get4dNoiseUint32( indexEastX, indexNorthY, indexAboveZ, indexAfterT, seed ); + + // Mask with 15 (mod 16) to look up in gradients table + ::glm::vec4 gradientBeforeBSW = gradients[ noiseBeforeBSW & 0x0000000F ]; + ::glm::vec4 gradientBeforeBSE = gradients[ noiseBeforeBSE & 0x0000000F ]; + ::glm::vec4 gradientBeforeBNW = gradients[ noiseBeforeBNW & 0x0000000F ]; + ::glm::vec4 gradientBeforeBNE = gradients[ noiseBeforeBNE & 0x0000000F ]; + ::glm::vec4 gradientBeforeASW = gradients[ noiseBeforeASW & 0x0000000F ]; + ::glm::vec4 gradientBeforeASE = gradients[ noiseBeforeASE & 0x0000000F ]; + ::glm::vec4 gradientBeforeANW = gradients[ noiseBeforeANW & 0x0000000F ]; + ::glm::vec4 gradientBeforeANE = gradients[ noiseBeforeANE & 0x0000000F ]; + ::glm::vec4 gradientAfterBSW = gradients[ noiseAfterBSW & 0x0000000F ]; + ::glm::vec4 gradientAfterBSE = gradients[ noiseAfterBSE & 0x0000000F ]; + ::glm::vec4 gradientAfterBNW = gradients[ noiseAfterBNW & 0x0000000F ]; + ::glm::vec4 gradientAfterBNE = gradients[ noiseAfterBNE & 0x0000000F ]; + ::glm::vec4 gradientAfterASW = gradients[ noiseAfterASW & 0x0000000F ]; + ::glm::vec4 gradientAfterASE = gradients[ noiseAfterASE & 0x0000000F ]; + ::glm::vec4 gradientAfterANW = gradients[ noiseAfterANW & 0x0000000F ]; + ::glm::vec4 gradientAfterANE = gradients[ noiseAfterANE & 0x0000000F ]; + + // Dot each corner's gradient with displacement from corner to position + ::glm::vec4 displacementFromBeforeBSW( currentPos.x - cellMins.x, currentPos.y - cellMins.y, currentPos.z - cellMins.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeBSE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y, currentPos.z - cellMins.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeBNW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y, currentPos.z - cellMins.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeBNE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y, currentPos.z - cellMins.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeASW( currentPos.x - cellMins.x, currentPos.y - cellMins.y, currentPos.z - cellMaxs.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeASE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y, currentPos.z - cellMaxs.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeANW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y, currentPos.z - cellMaxs.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromBeforeANE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y, currentPos.z - cellMaxs.z, currentPos.w - cellMins.w ); + ::glm::vec4 displacementFromAfterBSW( currentPos.x - cellMins.x, currentPos.y - cellMins.y, currentPos.z - cellMins.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterBSE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y, currentPos.z - cellMins.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterBNW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y, currentPos.z - cellMins.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterBNE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y, currentPos.z - cellMins.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterASW( currentPos.x - cellMins.x, currentPos.y - cellMins.y, currentPos.z - cellMaxs.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterASE( currentPos.x - cellMaxs.x, currentPos.y - cellMins.y, currentPos.z - cellMaxs.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterANW( currentPos.x - cellMins.x, currentPos.y - cellMaxs.y, currentPos.z - cellMaxs.z, currentPos.w - cellMaxs.w ); + ::glm::vec4 displacementFromAfterANE( currentPos.x - cellMaxs.x, currentPos.y - cellMaxs.y, currentPos.z - cellMaxs.z, currentPos.w - cellMaxs.w ); + + float dotBeforeBSW = ::glm::dot( gradientBeforeBSW, displacementFromBeforeBSW ); + float dotBeforeBSE = ::glm::dot( gradientBeforeBSE, displacementFromBeforeBSE ); + float dotBeforeBNW = ::glm::dot( gradientBeforeBNW, displacementFromBeforeBNW ); + float dotBeforeBNE = ::glm::dot( gradientBeforeBNE, displacementFromBeforeBNE ); + float dotBeforeASW = ::glm::dot( gradientBeforeASW, displacementFromBeforeASW ); + float dotBeforeASE = ::glm::dot( gradientBeforeASE, displacementFromBeforeASE ); + float dotBeforeANW = ::glm::dot( gradientBeforeANW, displacementFromBeforeANW ); + float dotBeforeANE = ::glm::dot( gradientBeforeANE, displacementFromBeforeANE ); + float dotAfterBSW = ::glm::dot( gradientAfterBSW, displacementFromAfterBSW ); + float dotAfterBSE = ::glm::dot( gradientAfterBSE, displacementFromAfterBSE ); + float dotAfterBNW = ::glm::dot( gradientAfterBNW, displacementFromAfterBNW ); + float dotAfterBNE = ::glm::dot( gradientAfterBNE, displacementFromAfterBNE ); + float dotAfterASW = ::glm::dot( gradientAfterASW, displacementFromAfterASW ); + float dotAfterASE = ::glm::dot( gradientAfterASE, displacementFromAfterASE ); + float dotAfterANW = ::glm::dot( gradientAfterANW, displacementFromAfterANW ); + float dotAfterANE = ::glm::dot( gradientAfterANE, displacementFromAfterANE ); + + // Do a smoothed (nonlinear) weighted average of dot results + float weightEast = Easing::SmoothStep( displacementFromBeforeBSW.x ); + float weightNorth = Easing::SmoothStep( displacementFromBeforeBSW.y ); + float weightAbove = Easing::SmoothStep( displacementFromBeforeBSW.z ); + float weightAfter = Easing::SmoothStep( displacementFromBeforeBSW.w ); + float weightWest = 1.f - weightEast; + float weightSouth = 1.f - weightNorth; + float weightBelow = 1.f - weightAbove; + float weightBefore = 1.f - weightAfter; + + // 16-way blend (16 -> 8 -> 4 -> 2 -> 1) + float blendBeforeBelowSouth = (weightEast * dotBeforeBSE) + (weightWest * dotBeforeBSW); + float blendBeforeBelowNorth = (weightEast * dotBeforeBNE) + (weightWest * dotBeforeBNW); + float blendBeforeAboveSouth = (weightEast * dotBeforeASE) + (weightWest * dotBeforeASW); + float blendBeforeAboveNorth = (weightEast * dotBeforeANE) + (weightWest * dotBeforeANW); + float blendAfterBelowSouth = (weightEast * dotAfterBSE) + (weightWest * dotAfterBSW); + float blendAfterBelowNorth = (weightEast * dotAfterBNE) + (weightWest * dotAfterBNW); + float blendAfterAboveSouth = (weightEast * dotAfterASE) + (weightWest * dotAfterASW); + float blendAfterAboveNorth = (weightEast * dotAfterANE) + (weightWest * dotAfterANW); + float blendBeforeBelow = (weightSouth * blendBeforeBelowSouth) + (weightNorth * blendBeforeBelowNorth); + float blendBeforeAbove = (weightSouth * blendBeforeAboveSouth) + (weightNorth * blendBeforeAboveNorth); + float blendAfterBelow = (weightSouth * blendAfterBelowSouth) + (weightNorth * blendAfterBelowNorth); + float blendAfterAbove = (weightSouth * blendAfterAboveSouth) + (weightNorth * blendAfterAboveNorth); + float blendBefore = (weightBelow * blendBeforeBelow) + (weightAbove * blendBeforeAbove); + float blendAfter = (weightBelow * blendAfterBelow) + (weightAbove * blendAfterAbove); + float blendTotal = (weightBefore * blendBefore) + (weightAfter * blendAfter); + float noiseThisOctave = blendTotal * (1.f / 0.6875f); // 4D Perlin is in [-.6875,.6875]; map to ~[-1,1] + + // Accumulate results and prepare for next octave (if any) + totalNoise += noiseThisOctave * currentAmplitude; + totalAmplitude += currentAmplitude; + currentAmplitude *= octavePersistence; + currentPos *= octaveScale; + currentPos.x += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + currentPos.y += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + currentPos.z += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + currentPos.w += OCTAVE_OFFSET; // Add "irrational" offset to de-align octave grids + ++ seed; // Eliminates octaves "echoing" each other (since each octave is uniquely seeded) + } + + // Re-normalize total noise to within [-1,1] and fix octaves pulling us far away from limits + if( renormalize && totalAmplitude > 0.f ) + { + totalNoise /= totalAmplitude; // Amplitude exceeds 1.0 if octaves are used + totalNoise = (totalNoise * 0.5f) + 0.5f; // Map to [0,1] + totalNoise = Easing::SmoothStep( totalNoise ); // Push towards extents (octaves pull us away) + totalNoise = (totalNoise * 2.0f) - 1.f; // Map back to [-1,1] + } + + return totalNoise; +} + +} // SquirrelNoise4 + diff --git a/external/SquirrelNoise/src/squirrel_noise/SmoothNoise.hpp b/external/SquirrelNoise/src/squirrel_noise/SmoothNoise.hpp new file mode 100644 index 0000000..69d15c4 --- /dev/null +++ b/external/SquirrelNoise/src/squirrel_noise/SmoothNoise.hpp @@ -0,0 +1,95 @@ +//----------------------------------------------------------------------------------------------- +// SmoothNoise.hpp +// +#pragma once + +#include + +namespace SquirrelNoise4 { + +///////////////////////////////////////////////////////////////////////////////////////////////// +// Squirrel's Smooth Noise utilities (version 4) +// +// This code is made available under the Creative Commons attribution 3.0 license (CC-BY-3.0 US): +// Attribution in source code comments (even closed-source/commercial code) is sufficient. +// License summary and text available at: https://creativecommons.org/licenses/by/3.0/us/ +// +// Note: This is work in progress, and has not yet been tested thoroughly. Use at your own risk. +// Please report any bugs, issues, or bothersome cases to SquirrelEiserloh at gmail.com. +// +// The following functions are all based on a simple bit-noise function which returns an unsigned +// integer containing 32 reasonably-well-scrambled bits, based on a given (signed) integer +// input parameter (position/index) and [optional] seed. Kind of like looking up a value in an +// infinitely large [non-existent] table of previously rolled random numbers. +// +// These functions are deterministic and random-access / order-independent (i.e. state-free), +// so they are particularly well-suited for use in out-of-order (or or-demand) procedural +// content generation (i.e. that mountain village is the same whether you generated it +// first or last, ahead of time or just now). +// +// My implementations of fractal and Perlin noise include a few improvements over the stock +// versions I've seen used: +// * Functions can take seeds (independent of index/position) with unique-but-consistent results +// * Each octave is offset (translation/bias) to dramatically reduce multi-octave feedback. +// * Vector gradients are in power-of-two sets, to avoid modulus ops (uses bitwise masks instead) +// * Octave persistence and scale are adjustable (not necessarily 0.5 and 2.0) +// * Multi-octave noise can be "normalized" to be mapped back to within [-1,1], or not +// +// Note: these functions assume the presence of the glm math library; +// +// Modified by Erik Scholz 2021, no change to the license. +// +///////////////////////////////////////////////////////////////////////////////////////////////// + +//const float fSQRT_3_OVER_3 = sqrtf(3.f)/3.f; +const float fSQRT_3_OVER_3 = 0.57735026918962576450f; + +//----------------------------------------------------------------------------------------------- +// Smooth/fractal pseudorandom noise functions (random-access / deterministic) +// +// These are less "organic" (and more axial) than Perlin's functions, but simpler and faster. +// +// Number of layers of noise added together +// Amplitude multiplier for each subsequent octave (each octave is quieter) +// Frequency multiplier for each subsequent octave (each octave is busier) +// If true, uses nonlinear (SmoothStep3) renormalization to within [-1,1] +// +float Compute1dFractalNoise( float position, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); +float Compute2dFractalNoise( float posX, float posY, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); +float Compute3dFractalNoise( float posX, float posY, float posZ, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); +float Compute4dFractalNoise( float posX, float posY, float posZ, float posT, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); + + +//----------------------------------------------------------------------------------------------- +// Perlin noise functions (random-access / deterministic) +// +// Perlin noise is slightly more expensive, but more organic-looking (less axial) than regular +// square fractal noise, through the use of blended dot products vs. randomized gradient vectors. +// +// Number of layers of noise added together +// Amplitude multiplier for each subsequent octave (each octave is quieter) +// Frequency multiplier for each subsequent octave (each octave is busier) +// If true, uses nonlinear (SmoothStep3) renormalization to within [-1,1] +// +float Compute1dPerlinNoise( float position, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); +float Compute2dPerlinNoise( float posX, float posY, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); +float Compute3dPerlinNoise( float posX, float posY, float posZ, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); +float Compute4dPerlinNoise( float posX, float posY, float posZ, float posT, float scale=1.f, uint32_t numOctaves=1, float octavePersistence=0.5f, float octaveScale=2.f, bool renormalize=true, uint32_t seed=0 ); + + +//----------------------------------------------------------------------------------------------- +// Simplex noise functions (random-access / deterministic) +// +// Simplex noise (also by Ken Perlin) is theoretically faster than - and supposedly superior to - +// Perlin noise, in that it is more organic-looking. I'm not sure I like the look of it better, +// however; examples of cross-sectional 4D simplex noise look worse to me than 4D Perlin does. +// +// Also, Simplex noise is based on a regular simplex (2D triangle, 3D tetrahedron, 4-simplex/5-cell) +// grid, which is slightly more fiddly, so I haven't bothered writing my own yet. +// +// #TODO: Implement simplex noise in 2D, 3D, 4D (1D simplex is identical to 1D Perlin, I think?) +// #TODO: Test actual simplex noise implementation in 2D/3D to compare speeds (branches vs. ops!) +// + +} // SquirrelNoise4 + diff --git a/framework/CMakeLists.txt b/framework/CMakeLists.txt index 8f3030f..8e11d3e 100644 --- a/framework/CMakeLists.txt +++ b/framework/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(logger) add_subdirectory(resource_manager) add_subdirectory(common_components) add_subdirectory(std_utils) +add_subdirectory(random) add_subdirectory(screen_director) add_subdirectory(filesystem) add_subdirectory(simple_scene) diff --git a/framework/random/CMakeLists.txt b/framework/random/CMakeLists.txt new file mode 100644 index 0000000..d954586 --- /dev/null +++ b/framework/random/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.2) +project(random CXX) + +add_library(random + ./src/mm/random/srng.hpp + ./src/mm/random/srng.cpp +) + +target_include_directories(random PUBLIC "src") + +target_compile_features(random PUBLIC cxx_std_17) + +target_link_libraries(random + PUBLIC + squirrel_noise + std_utils +) + +############################## + +#if (BUILD_TESTING) + #add_subdirectory(test) +#endif() + diff --git a/framework/random/src/mm/random/srng.cpp b/framework/random/src/mm/random/srng.cpp new file mode 100644 index 0000000..1f8035e --- /dev/null +++ b/framework/random/src/mm/random/srng.cpp @@ -0,0 +1,2 @@ +#include "./srng.hpp" + diff --git a/framework/random/src/mm/random/srng.hpp b/framework/random/src/mm/random/srng.hpp new file mode 100644 index 0000000..9c0d3f6 --- /dev/null +++ b/framework/random/src/mm/random/srng.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +namespace MM::Random { + +// Seeded (Pseudo-) Random Number Generator +struct SRNG { + uint32_t seed = 1337; + int32_t pos = 0; + + // basic + + uint32_t getNext(void) { + return SquirrelNoise4::Get1dNoiseUint32(pos++, seed); + } + + float zeroToOne(void) { + return SquirrelNoise4::Get1dNoiseZeroToOne(pos++, seed); + } + + float negOneToOne(void) { + return SquirrelNoise4::Get1dNoiseNegOneToOne(pos++, seed); + } + + // advanced + + uint32_t minMax(uint32_t min, uint32_t max) { + return (getNext() % ((max - min) + 1)) + min; + } + + bool roll(float prob) { + return zeroToOne() <= prob; + } + + // more advanced + + // inclusive + // TODO: test for floats + template + T range(const ScalarRange2& range) { + return (getNext() % ((range.max() - range.min()) + 1)) + range.min(); + } + + // for conviniece + uint32_t operator()(void) { + return getNext(); + } + + bool operator()(float prob) { + return roll(prob); + } +}; + +} // MM::Random + diff --git a/framework/std_utils/CMakeLists.txt b/framework/std_utils/CMakeLists.txt index fa09f46..1570ae5 100644 --- a/framework/std_utils/CMakeLists.txt +++ b/framework/std_utils/CMakeLists.txt @@ -22,4 +22,3 @@ if (BUILD_TESTING) add_subdirectory(test) endif() -