Skip to content

Instantly share code, notes, and snippets.

@DeadZoneLuna
Created September 22, 2023 14:32
Show Gist options
  • Save DeadZoneLuna/99475b2706b0ce47839a45d83c47b10c to your computer and use it in GitHub Desktop.
Save DeadZoneLuna/99475b2706b0ce47839a45d83c47b10c to your computer and use it in GitHub Desktop.
Implements the continuous uniform distribution algorithm from vstdlib (Source Engine)
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