Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reproducible cross-platform random System.Random implementation #224

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 286 additions & 0 deletions crest/Assets/Crest/Crest/Scripts/Helpers/MersenneTwisterRandom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WaveShapeCalculator
{
using System;

namespace Random
{
/* C# Version Copyright (C) 2001 Akihilo Kramot (Takel). */
/* C# porting from a C-program for MT19937, originaly coded by */
/* Takuji Nishimura, considering the suggestions by */
/* Topher Cooper and Marc Rieffel in July-Aug. 1997. */
/* This library is free software under the Artistic license: */
/* */
/* You can find the original C-program at */
/* http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html */
/* */

/// <summary>
/// Implements a Mersenne Twister Random Number Generator. This class provides the same interface
/// as the standard System.Random number generator, plus some additional functions.
/// </summary>

public class MersenneTwister: System.Random
{
/* Period parameters */
private const int N = 624;
private const int M = 397;
private const uint MATRIX_A = 0x9908b0df; /* constant vector a */
private const uint UPPER_MASK = 0x80000000; /* most significant w-r bits */
private const uint LOWER_MASK = 0x7fffffff; /* least significant r bits */

/* Tempering parameters */
private const uint TEMPERING_MASK_B = 0x9d2c5680;
private const uint TEMPERING_MASK_C = 0xefc60000;

private static uint TEMPERING_SHIFT_U( uint y ) { return ( y >> 11 ); }
private static uint TEMPERING_SHIFT_S( uint y ) { return ( y << 7 ); }
private static uint TEMPERING_SHIFT_T( uint y ) { return ( y << 15 ); }
private static uint TEMPERING_SHIFT_L( uint y ) { return ( y >> 18 ); }

private uint[] mt = new uint[N]; /* the array for the state vector */

private uint seed_;
private short mti;

private static uint[] mag01 = { 0x0, MATRIX_A };

/// <summary>
/// Create a twister with the specified seed. All sequences started with the same seed will contain
/// the same random numbers in the same order.
/// </summary>
/// <param name="seed">The seed with which to start the twister.</param>

public MersenneTwister( uint seed )
{
Seed = seed;
}


/// <summary>
/// Create a twister seeded from the system clock to make it as random as possible.
/// </summary>

public MersenneTwister()
: this( ( (uint) DateTime.Now.Ticks ) ) // A random initial seed is used.
{
}


/// <summary>
/// The seed that was used to start the random number generator.
/// Setting the seed resets the random number generator with the new seed.
/// All sequences started with the same seed will contain the same random numbers in the same order.
/// </summary>

public uint Seed
{
set
{
seed_ = value;

/* setting initial seeds to mt[N] using */
/* the generator Line 25 of Table 1 in */
/* [KNUTH 1981, The Art of Computer Programming */
/* Vol. 2 (2nd Ed.), pp102] */

mt[0] = seed_ & 0xffffffffU;
for ( mti = 1; mti < N; mti++ )
{
mt[mti] = ( 69069 * mt[mti - 1] ) & 0xffffffffU;
}
}

get
{
return seed_;
}
}


/// <summary>
/// Generate a random uint.
/// </summary>
/// <returns>A random uint.</returns>

protected uint GenerateUInt()
{
uint y;

/* mag01[x] = x * MATRIX_A for x=0,1 */

if ( mti >= N ) /* generate N words at one time */
{
short kk;

for ( kk = 0; kk < N - M; kk++ )
{
y = ( mt[kk] & UPPER_MASK ) | ( mt[kk + 1] & LOWER_MASK );
mt[kk] = mt[kk + M] ^ ( y >> 1 ) ^ mag01[y & 0x1];
}

for ( ; kk < N - 1; kk++ )
{
y = ( mt[kk] & UPPER_MASK ) | ( mt[kk + 1] & LOWER_MASK );
mt[kk] = mt[kk + ( M - N )] ^ ( y >> 1 ) ^ mag01[y & 0x1];
}

y = ( mt[N - 1] & UPPER_MASK ) | ( mt[0] & LOWER_MASK );
mt[N - 1] = mt[M - 1] ^ ( y >> 1 ) ^ mag01[y & 0x1];

mti = 0;
}

y = mt[mti++];
y ^= TEMPERING_SHIFT_U( y );
y ^= TEMPERING_SHIFT_S( y ) & TEMPERING_MASK_B;
y ^= TEMPERING_SHIFT_T( y ) & TEMPERING_MASK_C;
y ^= TEMPERING_SHIFT_L( y );

return y;
}


/// <summary>
/// Returns the next uint in the random sequence.
/// </summary>
/// <returns>The next uint in the random sequence.</returns>

public virtual uint NextUInt()
{
return this.GenerateUInt();
}


/// <summary>
/// Returns a random number between 0 and a specified maximum.
/// </summary>
/// <param name="maxValue">The upper bound of the random number to be generated. maxValue must be greater than or equal to zero.</param>
/// <returns>A 32-bit unsigned integer greater than or equal to zero, and less than maxValue; that is, the range of return values includes zero but not MaxValue.</returns>

public virtual uint NextUInt( uint maxValue )
{
return (uint) ( this.GenerateUInt() / ( (double) uint.MaxValue / maxValue ) );
}


/// <summary>
/// Returns an unsigned random number from a specified range.
/// </summary>
/// <param name="minValue">The lower bound of the random number returned.</param>
/// <param name="maxValue">The upper bound of the random number returned. maxValue must be greater than or equal to minValue.</param>
/// <returns>A 32-bit signed integer greater than or equal to minValue and less than maxValue;
/// that is, the range of return values includes minValue but not MaxValue.
/// If minValue equals maxValue, minValue is returned.</returns>

public virtual uint NextUInt( uint minValue, uint maxValue ) /* throws ArgumentOutOfRangeException */
{
if (minValue >= maxValue)
{
if (minValue == maxValue)
{
return minValue;
}
else
{
throw new ArgumentOutOfRangeException("minValue", "NextUInt() called with minValue >= maxValue");
}
}

return (uint) ( this.GenerateUInt() / ( (double) uint.MaxValue / ( maxValue - minValue ) ) + minValue );
}


/// <summary>
/// Returns a nonnegative random number.
/// </summary>
/// <returns>A 32-bit signed integer greater than or equal to zero and less than int.MaxValue.</returns>

public override int Next()
{
return (int) ( this.GenerateUInt() / 2 );
}


/// <summary>
/// Returns a nonnegative random number less than the specified maximum.
/// </summary>
/// <param name="maxValue">The upper bound of the random number to be generated. maxValue must be greater than or equal to zero.</param>
/// <returns>A 32-bit signed integer greater than or equal to zero, and less than maxValue;
/// that is, the range of return values includes zero but not MaxValue.</returns>

public override int Next( int maxValue ) /* throws ArgumentOutOfRangeException */
{
if ( maxValue <= 0 )
{
if ( maxValue == 0 )
return 0;
else
throw new ArgumentOutOfRangeException( "maxValue", "Next() called with a negative parameter" );
}

return (int) ( this.GenerateUInt() / ( uint.MaxValue / maxValue ) );
}


/// <summary>
/// Returns a signed random number from a specified range.
/// </summary>
/// <param name="minValue">The lower bound of the random number returned.</param>
/// <param name="maxValue">The upper bound of the random number returned. maxValue must be greater than or equal to minValue.</param>
/// <returns>A 32-bit signed integer greater than or equal to minValue and less than maxValue;
/// that is, the range of return values includes minValue but not MaxValue.
/// If minValue equals maxValue, minValue is returned.</returns>

public override int Next( int minValue, int maxValue ) /* ArgumentOutOfRangeException */
{
if (minValue >= maxValue)
{
if (minValue == maxValue)
{
return minValue;
}
else
{
throw new ArgumentOutOfRangeException("minValue", "Next() called with minValue > maxValue");
}
}

return (int) ( this.GenerateUInt() / ( (double) uint.MaxValue / ( maxValue - minValue ) ) + minValue );
}


/// <summary>
/// Fills an array of bytes with random numbers from 0..255
/// </summary>
/// <param name="buffer">The array to be filled with random numbers.</param>

public override void NextBytes( byte[] buffer ) /* throws ArgumentNullException*/
{
int bufLen = buffer.Length;

if ( buffer == null )
throw new ArgumentNullException("buffer");

for ( int idx = 0; idx < bufLen; idx++ )
buffer[idx] = (byte) ( this.GenerateUInt() / ( uint.MaxValue / byte.MaxValue ) );
}


/// <summary>
/// Returns a double-precision random number in the range [0..1[
/// </summary>
/// <returns>A random double-precision floating point number greater than or equal to 0.0, and less than 1.0.</returns>

public override double NextDouble()
{
return (double) this.GenerateUInt() / uint.MaxValue;
}
}
}
}
7 changes: 4 additions & 3 deletions crest/Assets/Crest/Crest/Scripts/Shapes/OceanWaveSpectrum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ float ComputeWaveSpeed(float wavelength)
/// <summary>
/// Samples spectrum to generate wave data. Wavelengths will be in ascending order.
/// </summary>
public void GenerateWaveData(int componentsPerOctave, ref float[] wavelengths, ref float[] anglesDeg)
public void GenerateWaveData(System.Random randomStateBkp, int componentsPerOctave, ref float[] wavelengths,
ref float[] anglesDeg)
{
var totalComponents = NUM_OCTAVES * componentsPerOctave;

Expand All @@ -122,9 +123,9 @@ public void GenerateWaveData(int componentsPerOctave, ref float[] wavelengths, r
// wavelengths in sorted order!
var minWavelengthi = minWavelength + invComponentsPerOctave * minWavelength * i;
var maxWavelengthi = Mathf.Min(minWavelengthi + invComponentsPerOctave * minWavelength, 2f * minWavelength);
wavelengths[index] = Mathf.Lerp(minWavelengthi, maxWavelengthi, Random.value);
wavelengths[index] = Mathf.Lerp(minWavelengthi, maxWavelengthi, (float)randomStateBkp.NextDouble());

var rnd = (i + Random.value) * invComponentsPerOctave;
var rnd = (i + (float)randomStateBkp.NextDouble()) * invComponentsPerOctave;
anglesDeg[index] = (2f * rnd - 1f) * _waveDirectionVariance;
}

Expand Down
32 changes: 14 additions & 18 deletions crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerBatched.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class ShapeGerstnerBatched : MonoBehaviour, ICollProvider
[Range(0f, 1f)]
public float _weight = 1f;

public int _randomSeed = 0;
public uint _randomSeed = 0;

// data for all components
float[] _wavelengths;
Expand All @@ -40,6 +40,7 @@ public class ShapeGerstnerBatched : MonoBehaviour, ICollProvider

// Shader to be used to render evaluate Gerstner waves for each LOD
Shader _waveShader;
private WaveShapeCalculator.Random.MersenneTwister randomGenerator;

static int sp_TwoPiOverWavelengths = Shader.PropertyToID("_TwoPiOverWavelengths");
static int sp_Amplitudes = Shader.PropertyToID("_Amplitudes");
Expand Down Expand Up @@ -84,25 +85,20 @@ void Start()
}
}

void InitPhases()
void InitPhases(System.Random random)
{
// Set random seed to get repeatable results
Random.State randomStateBkp = Random.state;
Random.InitState(_randomSeed);

var totalComps = _componentsPerOctave * OceanWaveSpectrum.NUM_OCTAVES;
_phases = new float[totalComps];
for (var octave = 0; octave < OceanWaveSpectrum.NUM_OCTAVES; octave++)
{
for (var i = 0; i < _componentsPerOctave; i++)
{
var index = octave * _componentsPerOctave + i;
var rnd = (i + Random.value) / _componentsPerOctave;
var rnd = (i + (float)random.NextDouble()) / _componentsPerOctave;
_phases[index] = 2f * Mathf.PI * rnd;
}
}

Random.state = randomStateBkp;
}

public void SetOrigin(Vector3 newOrigin)
Expand All @@ -125,19 +121,18 @@ public void SetOrigin(Vector3 newOrigin)
void Update()
{
if (OceanRenderer.Instance == null) return;

//create or reseed
if (randomGenerator == null)
randomGenerator = new WaveShapeCalculator.Random.MersenneTwister(_randomSeed);
else
randomGenerator.Seed = _randomSeed;
if (_phases == null || _phases.Length != _componentsPerOctave * OceanWaveSpectrum.NUM_OCTAVES)
{
InitPhases();
InitPhases(randomGenerator);
randomGenerator.Seed = _randomSeed; //reseed again after phases init
}
_spectrum.GenerateWaveData(randomGenerator,_componentsPerOctave, ref _wavelengths, ref _angleDegs);

// Set random seed to get repeatable results
Random.State randomStateBkp = Random.state;
Random.InitState(_randomSeed);

_spectrum.GenerateWaveData(_componentsPerOctave, ref _wavelengths, ref _angleDegs);

Random.state = randomStateBkp;

UpdateAmplitudes();

Expand Down Expand Up @@ -272,7 +267,8 @@ int UpdateBatch(int lodIdx, int firstComponent, int lastComponentNonInc, IProper

if (dropped > 0)
{
Debug.LogWarning(string.Format("Gerstner LOD{0}: Batch limit reached, dropped {1} wavelengths. To support bigger batch sizes, see the comment around the BATCH_SIZE declaration.", lodIdx, dropped), this);
//commented out to prevent too more warnings
//Debug.LogWarning($"Gerstner LOD{lodIdx}: Batch limit reached, dropped {dropped} wavelengths. To support bigger batch sizes, see the comment around the BATCH_SIZE declaration.", this);
numComponents = BATCH_SIZE;
}

Expand Down