// MIT License - Copyright (c) 2023 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.Random { using System; using System.Runtime.Serialization; using System.Text.Json.Serialization; using ProtoBuf; /// /// A hash-based PRNG inspired by Squirrel Eiserloh's "Squirrel Noise" approach for deterministic noise. /// /// /// /// Reference implementation: https://youtu.be/LWFzPP8ZbdU?t=2673 /// /// /// Squirrel-style generators are simple, stateless transformations that can produce deterministic pseudo-noise /// values from integer coordinates or an advancing internal position. This implementation exposes both a sequential /// and coordinate-based noise via without advancing state. /// /// Pros: /// /// Great for deterministic spatial noise (e.g., tiles, grid sampling) with no stored sequence. /// Fast and simple; ideal for repeatable content by position. /// /// Cons: /// /// Statistical properties are good for noise but not designed for all PRNG use-cases. /// Not cryptographically secure. /// /// When to use: /// /// Procedural textures, terrain, per-cell randomness where the same input produces the same output. /// /// When not to use: /// /// When you need a classic stream-based PRNG for sequences or shuffles; prefer PCG/Xoroshiro/RomuDuo. /// /// /// /// /// using WallstopStudios.UnityHelpers.Core.Random; /// /// var rng = new SquirrelRandom(seed: 12345); /// // Deterministic by coordinate (does not advance RNG): /// float noise = rng.NextNoise(x: 10, y: 42); /// /// // Sequential usage: /// int val = rng.Next(0, 100); /// /// [RandomGeneratorMetadata( RandomQuality.Good, "Hash-based generator built on Squirrel3; good equidistribution for table lookups but not extensively tested beyond moderate ranges.", "Squirrel Eiserloh", "https://youtu.be/LWFzPP8ZbdU?t=2673" // GDC talk on Squirrel noise )] [Serializable] [DataContract] [ProtoContract] public sealed class SquirrelRandom : AbstractRandom { private const uint BitNoise1 = 0xB5297A4D; private const uint BitNoise2 = 0x68E31DA4; private const uint BitNoise3 = 0x1B56C4E9; private const int LargePrime = 198491317; public static readonly SquirrelRandom Instance = ThreadLocalRandom.Instance; public override RandomState InternalState => BuildState(_position); [ProtoMember(6)] private uint _position; public SquirrelRandom() : this(Guid.NewGuid().GetHashCode()) { } public SquirrelRandom(int seed) { _position = unchecked((uint)seed); } [JsonConstructor] public SquirrelRandom(RandomState internalState) { _position = unchecked((uint)internalState.State1); RestoreCommonState(internalState); } public override uint NextUint() { return NextUintInternal(ref _position); } // Does not advance the RNG public float NextNoise(int x, int y) { return NextNoise(x, y, _position); } public override IRandom Copy() { return new SquirrelRandom(InternalState); } private static uint NextUintInternal(ref uint seed) { seed *= BitNoise1; seed ^= seed >> 8; seed += BitNoise2; seed ^= seed << 8; seed *= BitNoise3; seed ^= seed >> 8; return seed; } // https://youtu.be/LWFzPP8ZbdU?t=2906 private static float NextNoise(int x, uint seed) { uint result = unchecked((uint)x); result *= BitNoise1; result += seed; result ^= result >> 8; result += BitNoise2; result ^= result << 8; result *= BitNoise3; result ^= result >> 8; return (result >> 8) * MagicFloat; } private static float NextNoise(int x, int y, uint seed) { return NextNoise(x + LargePrime * y, seed); } } }