From ae6f7022c13eff5fbec977b48404af2ff03c97d6 Mon Sep 17 00:00:00 2001 From: Dmitry Stulov Date: Tue, 16 Apr 2019 12:35:59 +0200 Subject: [PATCH] Add reproducible cross-platform random System.Random implementation and feed wave spectrum from it. --- .../Scripts/Helpers/MersenneTwisterRandom.cs | 286 ++++++++++++++++++ .../Crest/Scripts/Shapes/OceanWaveSpectrum.cs | 7 +- .../Scripts/Shapes/ShapeGerstnerBatched.cs | 33 +- 3 files changed, 304 insertions(+), 22 deletions(-) create mode 100644 crest/Assets/Crest/Crest/Scripts/Helpers/MersenneTwisterRandom.cs diff --git a/crest/Assets/Crest/Crest/Scripts/Helpers/MersenneTwisterRandom.cs b/crest/Assets/Crest/Crest/Scripts/Helpers/MersenneTwisterRandom.cs new file mode 100644 index 000000000..e607d2f81 --- /dev/null +++ b/crest/Assets/Crest/Crest/Scripts/Helpers/MersenneTwisterRandom.cs @@ -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 */ + /* */ + + /// + /// Implements a Mersenne Twister Random Number Generator. This class provides the same interface + /// as the standard System.Random number generator, plus some additional functions. + /// + + 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 }; + + /// + /// Create a twister with the specified seed. All sequences started with the same seed will contain + /// the same random numbers in the same order. + /// + /// The seed with which to start the twister. + + public MersenneTwister( uint seed ) + { + Seed = seed; + } + + + /// + /// Create a twister seeded from the system clock to make it as random as possible. + /// + + public MersenneTwister() + : this( ( (uint) DateTime.Now.Ticks ) ) // A random initial seed is used. + { + } + + + /// + /// 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. + /// + + 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_; + } + } + + + /// + /// Generate a random uint. + /// + /// A random uint. + + 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; + } + + + /// + /// Returns the next uint in the random sequence. + /// + /// The next uint in the random sequence. + + public virtual uint NextUInt() + { + return this.GenerateUInt(); + } + + + /// + /// Returns a random number between 0 and a specified maximum. + /// + /// The upper bound of the random number to be generated. maxValue must be greater than or equal to zero. + /// 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. + + public virtual uint NextUInt( uint maxValue ) + { + return (uint) ( this.GenerateUInt() / ( (double) uint.MaxValue / maxValue ) ); + } + + + /// + /// Returns an unsigned random number from a specified range. + /// + /// The lower bound of the random number returned. + /// The upper bound of the random number returned. maxValue must be greater than or equal to minValue. + /// 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. + + 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 ); + } + + + /// + /// Returns a nonnegative random number. + /// + /// A 32-bit signed integer greater than or equal to zero and less than int.MaxValue. + + public override int Next() + { + return (int) ( this.GenerateUInt() / 2 ); + } + + + /// + /// Returns a nonnegative random number less than the specified maximum. + /// + /// The upper bound of the random number to be generated. maxValue must be greater than or equal to zero. + /// 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. + + 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 ) ); + } + + + /// + /// Returns a signed random number from a specified range. + /// + /// The lower bound of the random number returned. + /// The upper bound of the random number returned. maxValue must be greater than or equal to minValue. + /// 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. + + 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 ); + } + + + /// + /// Fills an array of bytes with random numbers from 0..255 + /// + /// The array to be filled with random numbers. + + 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 ) ); + } + + + /// + /// Returns a double-precision random number in the range [0..1[ + /// + /// A random double-precision floating point number greater than or equal to 0.0, and less than 1.0. + + public override double NextDouble() + { + return (double) this.GenerateUInt() / uint.MaxValue; + } + } +} +} diff --git a/crest/Assets/Crest/Crest/Scripts/Shapes/OceanWaveSpectrum.cs b/crest/Assets/Crest/Crest/Scripts/Shapes/OceanWaveSpectrum.cs index ba84a2567..d62e526ff 100644 --- a/crest/Assets/Crest/Crest/Scripts/Shapes/OceanWaveSpectrum.cs +++ b/crest/Assets/Crest/Crest/Scripts/Shapes/OceanWaveSpectrum.cs @@ -102,7 +102,8 @@ float ComputeWaveSpeed(float wavelength) /// /// Samples spectrum to generate wave data. Wavelengths will be in ascending order. /// - 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; @@ -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; } diff --git a/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerBatched.cs b/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerBatched.cs index a97320e9a..18c7fd487 100644 --- a/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerBatched.cs +++ b/crest/Assets/Crest/Crest/Scripts/Shapes/ShapeGerstnerBatched.cs @@ -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; @@ -40,7 +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; // IMPORTANT - this mirrors the constant with the same name in ShapeGerstnerBatch.shader, both must be updated together! const int BATCH_SIZE = 32; @@ -74,12 +74,9 @@ 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++) @@ -87,12 +84,10 @@ void InitPhases() 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) @@ -115,19 +110,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(); @@ -262,7 +256,8 @@ int UpdateBatch(int lodIdx, int firstComponent, int lastComponentNonInc, Materia 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; }