Created
September 22, 2023 14:32
-
-
Save DeadZoneLuna/99475b2706b0ce47839a45d83c47b10c to your computer and use it in GitHub Desktop.
Implements the continuous uniform distribution algorithm from vstdlib (Source Engine)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using FlaxEngine; | |
// Don't forget to replace with your namespace | |
namespace FirstPersonShooter.FirstPersonShooter | |
{ | |
/// <summary> | |
/// Implements the continuous uniform distribution algorithm from vstdlib. | |
/// </summary> | |
public class Uniform | |
{ | |
#region Constants | |
// Constants used for generating random numbers. | |
private const int MULTIPLIER = 16807; | |
private const int MODULUS = 2147483647; | |
private const int QUOTIENT = 127773; | |
private const int REMAINDER = 2836; | |
private const double EPSILON = 1.2e-7; | |
private const int TABLE_SIZE = 32; | |
// Constants used for generating random numbers as floats. | |
private const float INV_MODULUS = 1.0f / MODULUS; | |
private const double INV_EPSILON = 1.0f - EPSILON; | |
private const float FLOAT_EPSILON = (float)INV_EPSILON; | |
// Constant used to limit the maximum random range. | |
public const uint MAX_RANDOM_RANGE = 0x7FFFFFFFU; | |
#endregion | |
#region Members | |
// The seed used for generating random numbers. | |
private int _Seed; | |
// A value used for generating random numbers. | |
private int _LastRandom; | |
// An array used for generating random numbers. | |
private int[] _Table = new int[TABLE_SIZE]; | |
// The singleton instance of this class. | |
private static Uniform _Uniform = new Uniform(); | |
public static Uniform Random { get { return _Uniform; } } | |
public float Value { get { return Range(); } } | |
#endregion | |
#region Constructors | |
/// <summary> | |
/// Creates a new instance of the Uniform class with a given seed. | |
/// </summary> | |
/// <param name="seed">The seed used for generating random numbers.</param> | |
public Uniform(int seed = 0) | |
{ | |
SetSeed(seed); | |
} | |
#endregion | |
#region Methods | |
/// <summary> | |
/// Sets the seed used for generating random numbers. | |
/// </summary> | |
/// <param name="seed">The seed used for generating random numbers.</param> | |
public void SetSeed(int seed) | |
{ | |
lock (this) | |
{ | |
// Use a negative seed to avoid some issues. | |
_Seed = (seed < 0) ? seed : -seed; | |
// Reset the last random number. | |
_LastRandom = 0; | |
} | |
} | |
/// <summary> | |
/// Generates a random float within a given range. | |
/// </summary> | |
/// <param name="min">The minimum value of the range.</param> | |
/// <param name="max">The maximum value of the range.</param> | |
/// <returns>A random floating point number within [min, max].</returns> | |
public float Range(float min = 0.0f, float max = 1.0f) | |
{ | |
// Generate a float within [0, 1]. | |
float u = INV_MODULUS * GenerateRandomNumber(); | |
// If the random float is greater than InvEpsilon, use FloatEpsilon instead to avoid NaN. | |
if (u > INV_EPSILON) | |
u = FLOAT_EPSILON; | |
// Scale the random float to fit within the specified range. | |
return (u * (max - min)) + min; | |
} | |
/// <summary> | |
/// Generates a random floating-point number within [min..max] in the specified exponent. | |
/// </summary> | |
/// <param name="min">The minimum value of the range.</param> | |
/// <param name="max">The maximum value of the range.</param> | |
/// <param name="exp">The exponent used to raise the randomly generated number.</param> | |
/// <returns>Returns a random floating point number within [min..max] in the specified exponent.</returns> | |
public float RangeExp(float min = 0.0f, float max = 1.0f, float exp = 1.0f) | |
{ | |
// Generate a float within [0, 1]. | |
float u = INV_MODULUS * GenerateRandomNumber(); | |
// If the random float is greater than InvEpsilon, use FloatEpsilon instead to avoid NaN. | |
if (u > INV_EPSILON) | |
u = FLOAT_EPSILON; | |
// Apply the exponent if it is not equal to 1. | |
if (exp != 1.0f) | |
u = Mathf.Pow(u, exp); | |
// Scale the random float to fit within the specified range. | |
return (u * (max - min)) + min; | |
} | |
/// <summary> | |
/// Generates a random integer within the specified range [min..max]. | |
/// </summary> | |
/// <param name="min">The minimum value of the range.</param> | |
/// <param name="max">The maximum value of the range.</param> | |
/// <returns>Returns a random integer within the specified range [min..max].</returns> | |
public int Range(int min, int max) | |
{ | |
// Compute the range of possible values (inclusive). | |
uint range = (uint)(max - min + 1); | |
//Debug.Assert(range == max - (long)min + 1); // Check that we didn't overflow int. | |
// Check that the values provide an acceptable range. | |
// This ensures that we won't return a number outside of the requested range. | |
//Debug.Assert(range - 1 <= MAX_RANDOM_RANGE); | |
if (range <= 1 || MAX_RANDOM_RANGE < range - 1) | |
{ | |
//Debug.Assert(min == max); // This is the only time it is OK to have a range containing a single number | |
return min; | |
} | |
// The following maps a uniform distribution on the interval [0, MAX_RANDOM_RANGE] | |
// to a smaller, client-specified range of [0, x-1] in a way that doesn't bias | |
// the uniform distribution unfavorably. Even for a worst-case x, the loop is | |
// guaranteed to be taken no more than half the time, so for that worst-case x, | |
// the average number of times through the loop is 2. For cases where x is | |
// much smaller than MAX_RANDOM_RANGE, the average number of times through the | |
// loop is very close to 1. | |
uint n; | |
uint maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE + 1) % range); | |
do | |
{ | |
n = (uint)GenerateRandomNumber(); | |
} while (n > maxAcceptable); | |
// Return the result within the specified range. | |
return (int)(min + (n % range)); | |
} | |
/// <summary> | |
/// Generates a random number using the uniform distribution algorithm. | |
/// The method generates a random integer within the range [0, 2^31 - 1] | |
/// using the linear congruential generator algorithm. | |
/// </summary> | |
/// <remarks> | |
/// The algorithm uses a seed to generate a sequence of random numbers. | |
/// The sequence of numbers is deterministic and repeatable if the same seed is used. | |
/// The algorithm has a full period of 2^31 - 2, meaning that it will produce | |
/// every possible value in the range [0, 2^31 - 1], except for the seed value itself, | |
/// which is skipped to avoid generating a zero value. | |
/// </remarks> | |
/// <returns>Returns a random number within the range [0, 2^31 - 1].</returns> | |
public int GenerateRandomNumber() | |
{ | |
lock (this) | |
{ | |
int index; | |
int temp; | |
// If the seed is non-positive or the last generated value was zero, | |
// reset the seed to a positive value and generate a new sequence of random values. | |
if (_Seed <= 0 || _LastRandom == 0) | |
{ | |
_Seed = -_Seed; | |
_Seed = _Seed < 1 ? 1 : _Seed; | |
// Populate table with random values. | |
for (index = TABLE_SIZE + 7; index >= 0; index--) | |
{ | |
temp = _Seed / QUOTIENT; | |
_Seed = MULTIPLIER * (_Seed - temp * QUOTIENT) - REMAINDER * temp; | |
if (_Seed < 0) _Seed += MODULUS; | |
if (index < TABLE_SIZE) _Table[index] = _Seed; | |
} | |
_LastRandom = _Table[0]; | |
} | |
temp = _Seed / QUOTIENT; | |
_Seed = MULTIPLIER * (_Seed - temp * QUOTIENT) - REMAINDER * temp; | |
if (_Seed < 0) _Seed += MODULUS; | |
// Check if the index is within bounds. | |
index = _LastRandom / (1 + (MODULUS - 1) / TABLE_SIZE); | |
if (index >= TABLE_SIZE || index < 0) | |
{ | |
Debug.LogWarningFormat("Uniform had an array overrun: tried to write to element {0} of 0..31.", index); | |
// Clamp the index. | |
index &= TABLE_SIZE - 1; | |
} | |
_LastRandom = _Table[index]; | |
_Table[index] = _Seed; | |
return _LastRandom; | |
} | |
} | |
public override string ToString() | |
{ | |
return $"Seed = {_Seed} | LastRandom = {_LastRandom}"; | |
} | |
#endregion | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment