diff --git a/include/cinder/QuasiRand.h b/include/cinder/QuasiRand.h new file mode 100644 index 0000000000..c839b47660 --- /dev/null +++ b/include/cinder/QuasiRand.h @@ -0,0 +1,208 @@ +/* + Copyright (c) 2020, The Cinder Project: http://libcinder.org + All rights reserved. + + This code is intended for use with the Cinder C++ library: http://libcinder.org + + Portions of this code based on the excellent article by Martin Roberts: + http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/ + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that + the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include "cinder/Export.h" +#include "cinder/Vector.h" + +#include +#include +#include + +namespace cinder { + +template ::value, int> = 0> +class CI_API QuasiRandT { + public: + QuasiRandT() = default; + + QuasiRandT( uint32_t seed ) + : mSeed( seed ) + { + } + + //! Resets the quasi-random generator to the specific seed \a seedValue. + void seed( uint32_t seedValue ) { mSeed = seedValue; } + + //! Returns a quasi-random float in the range [0.0f,1.0f). + T nextFloat() + { + static T sIrrational = T{ 1 } / phi( 1 ); + return recurrence( T{ 0.5 }, sIrrational, ++sSeed ); + } + + //! Returns a quasi-random float in the range [0.0f,v). + T nextFloat( T v ) { return nextFloat() * v; } + + //! Returns a quasi-random float in the range [a,b). + T nextFloat( T a, T b ) { return nextFloat() * ( b - a ) + a; } + + //! Returns a quasi-random float in the range [a,b] or the range [-b,-a). + T posNegFloat( T a, T b ) + { + static std::mt19937 sBase( 310u ); + if( sBase() & 1 ) + return nextFloat( a, b ); + + return -nextFloat( a, b ); + } + + //! Returns two corresponding quasi-random floats in the range [0.0f,1.0f). + void nextFloats( T &a, T &b ) + { + static T sIrrationalA = T{ 1 } / phi( 2 ); + static T sIrrationalB = T{ 1 } / ( phi( 2 ) * phi( 2 ) ); + ++sSeed; + a = recurrence( T{ 0.5 }, sIrrationalA, sSeed ); + b = recurrence( T{ 0.5 }, sIrrationalB, sSeed ); + } + + //! Returns three corresponding quasi-random floats in the range [0.0f,1.0f). + void nextFloats( T &a, T &b, T &c ) + { + static T sIrrationalA = T{ 1 } / phi( 3 ); + static T sIrrationalB = T{ 1 } / ( phi( 3 ) * phi( 3 ) ); + static T sIrrationalC = T{ 1 } / ( phi( 3 ) * phi( 3 ) * phi( 3 ) ); + ++sSeed; + a = recurrence( T{ 0.5 }, sIrrationalA, sSeed ); + b = recurrence( T{ 0.5 }, sIrrationalB, sSeed ); + c = recurrence( T{ 0.5 }, sIrrationalC, sSeed ); + } + + //! Returns a quasi-random vec2 that represents a point on the unit circle. + glm::vec<2, T, glm::defaultp> nextVec2() + { + const T theta = randFloat() * T{ M_PI * 2.0 }; + return glm::vec<2, T, glm::defaultp>( cos( theta ), sin( theta ) ); + } + + //! Returns a quasi-random vec3 that represents a point on the unit sphere. + glm::vec<3, T, glm::defaultp> nextVec3() + { + T phi, cosTheta; + randFloats( phi, cosTheta ); + + phi *= T{ M_PI * 2.0 }; + cosTheta = T{ 2 } * cosTheta - T{ 1 }; + + T rho = sqrt( T{ 1 } - cosTheta * cosTheta ); + T x = rho * cos( phi ); + T y = rho * sin( phi ); + T z = cosTheta; + + return glm::vec<3, T, glm::defaultp>( x, y, z ); + } + + //! Resets the static quasi-random generator to the specific seed \a seedValue. + static void randSeed( uint32_t seedValue ) { sSeed = seedValue; } + + //! Returns a quasi-random float in the range [0.0f,1.0f). + static T randFloat() + { + static T sIrrational = T{ 1 } / phi( 1 ); + return recurrence( T{ 0.5 }, sIrrational, ++sSeed ); + } + + //! Returns two corresponding quasi-random floats in the range [0.0f,1.0f). + static void randFloats( T &a, T &b ) + { + static T sIrrationalA = T{ 1 } / phi( 2 ); + static T sIrrationalB = T{ 1 } / ( phi( 2 ) * phi( 2 ) ); + ++sSeed; + a = recurrence( T{ 0.5 }, sIrrationalA, sSeed ); + b = recurrence( T{ 0.5 }, sIrrationalB, sSeed ); + } + + //! Returns three corresponding quasi-random floats in the range [0.0f,1.0f). + static void randFloats( T &a, T &b, T &c ) + { + static T sIrrationalA = T{ 1 } / phi( 3 ); + static T sIrrationalB = T{ 1 } / ( phi( 3 ) * phi( 3 ) ); + static T sIrrationalC = T{ 1 } / ( phi( 3 ) * phi( 3 ) * phi( 3 ) ); + ++sSeed; + a = recurrence( T{ 0.5 }, sIrrationalA, sSeed ); + b = recurrence( T{ 0.5 }, sIrrationalB, sSeed ); + c = recurrence( T{ 0.5 }, sIrrationalC, sSeed ); + } + + //! Returns a quasi-random vec2 that represents a point on the unit circle. + static glm::vec<2, T, glm::defaultp> randVec2() + { + const T theta = randFloat() * T{ M_PI * 2.0 }; + return glm::vec<2, T, glm::defaultp>( cos( theta ), sin( theta ) ); + } + + //! Returns a quasi-random vec3 that represents a point on the unit sphere. + static glm::vec<3, T, glm::defaultp> randVec3() + { + T phi, cosTheta; + randFloats( phi, cosTheta ); + + phi *= T{ M_PI * 2.0 }; + cosTheta = T{ 2 } * cosTheta - T{ 1 }; + + T rho = sqrt( T{ 1 } - cosTheta * cosTheta ); + T x = rho * cos( phi ); + T y = rho * sin( phi ); + T z = cosTheta; + + return glm::vec<3, T, glm::defaultp>( x, y, z ); + } + + private: + //! Returns the fractional part of \a value. + static T fract( T value ) + { + static T integral; + return modf( value, &integral ); + } + + //! Returns a quasi-random compatible irrational number. For d=1, this is the golden ratio. + static T phi( uint32_t d ) + { + T x{ 2 }; + for( int i = 0; i < 10; ++i ) + x = pow( T{ 1 } + x, T{ 1 } / ( d + 1 ) ); + return x; + } + + //! Helper function. Returns the n-th value in a recurrence sequence based on \a irrational. + static T recurrence( T base, T irrational, uint32_t n ) { return fract( base + n * irrational ); } + + + uint32_t mSeed = 0; + + static uint32_t sSeed; +}; + +using QuasiRand = QuasiRandT; +using QuasiRandf = QuasiRandT; +using QuasiRandd = QuasiRandT; +using QuasiRandld = QuasiRandT; + +} // namespace cinder diff --git a/include/cinder/Rand.h b/include/cinder/Rand.h index a80cfae4ee..dbe6af927f 100644 --- a/include/cinder/Rand.h +++ b/include/cinder/Rand.h @@ -27,227 +27,186 @@ namespace cinder { -class CI_API Rand { - public: - Rand() = default; +template ::value, int> = 0> +class CI_API RandT { + public: + RandT() = default; - Rand( uint32_t seed ) + RandT( uint32_t seed ) : mBase( seed ) - {} - - //! Re-seeds the random generator - void seed( uint32_t seedValue ) { - mBase.seed( seedValue ); } + //! Re-seeds the random generator + void seed( uint32_t seedValue ) { mBase.seed( seedValue ); } + //! returns a random boolean value - bool nextBool() - { - return mBase() & 1; - } + bool nextBool() { return mBase() & 1; } //! returns a random integer in the range [-2147483648,2147483647] - int32_t nextInt() - { - return mBase(); - } + int32_t nextInt() { return mBase(); } //! returns a random integer in the range [0,4294967296) - uint32_t nextUint() - { - return mBase(); - } + uint32_t nextUint() { return mBase(); } //! returns a random integer in the range [0,v) int32_t nextInt( int32_t v ) { - if( v <= 0 ) return 0; + if( v <= 0 ) + return 0; return mBase() % v; } //! returns a random integer in the range [0,v) uint32_t nextUint( uint32_t v ) { - if( v == 0 ) return 0; + if( v == 0 ) + return 0; return mBase() % v; } //! returns a random integer in the range [a,b) - int32_t nextInt( int32_t a, int32_t b ) - { - return nextInt( b - a ) + a; - } + int32_t nextInt( int32_t a, int32_t b ) { return nextInt( b - a ) + a; } //! returns a random float in the range [0.0f,1.0f) - float nextFloat() - { - return mFloatGen( mBase ); - } + T nextFloat() { return mFloatGen( mBase ); } //! returns a random float in the range [0.0f,v) - float nextFloat( float v ) - { - return mFloatGen( mBase ) * v; - } + T nextFloat( T v ) { return nextFloat() * v; } //! returns a random float in the range [a,b) - float nextFloat( float a, float b ) - { - return mFloatGen( mBase ) * ( b - a ) + a; - } + T nextFloat( T a, T b ) { return nextFloat() * ( b - a ) + a; } //! returns a random float in the range [a,b] or the range [-b,-a) - float posNegFloat( float a, float b ) + T posNegFloat( T a, T b ) { if( nextBool() ) return nextFloat( a, b ); - else - return -nextFloat( a, b ); + + return -nextFloat( a, b ); } //! returns a random vec3 that represents a point on the unit sphere - vec3 nextVec3() + glm::vec<3, T, glm::defaultp> nextVec3() { - float phi = nextFloat( (float)M_PI * 2.0f ); - float costheta = nextFloat( -1.0f, 1.0f ); + const T phi = nextFloat( T{ M_PI * 2.0 } ); + const T cosTheta = nextFloat( T{ -1 }, T{ 1 } ); - float rho = math::sqrt( 1.0f - costheta * costheta ); - float x = rho * math::cos( phi ); - float y = rho * math::sin( phi ); - float z = costheta; + const T rho = sqrt( T{ 1 } - cosTheta * cosTheta ); + const T x = rho * cos( phi ); + const T y = rho * sin( phi ); + const T z = cosTheta; - return vec3( x, y, z ); + return glm::vec<3, T, glm::defaultp>( x, y, z ); } //! returns a random vec2 that represents a point on the unit circle - vec2 nextVec2() + glm::vec<2, T, glm::defaultp> nextVec2() { - float theta = nextFloat( (float)M_PI * 2.0f ); - return vec2( math::cos( theta ), math::sin( theta ) ); + const T theta = nextFloat( T{ M_PI * 2.0 } ); + return glm::vec<2, T, glm::defaultp>( cos( theta ), sin( theta ) ); } //! returns a random float via Gaussian distribution, with a mean of 0 and a standard deviation of 1.0 - float nextGaussian() - { - return mNormDist( mBase ); - } + T nextGaussian() { return mNormDist( mBase ); } // STATICS //! Resets the static random generator to a random seed - static void randomize() - { - sBase.seed( std::random_device{}() ); - } + static void randomize() { sBase.seed( std::random_device{}() ); } //! Resets the static random generator to the specific seed \a seedValue - static void randSeed( uint32_t seedValue ) - { - sBase.seed( seedValue ); - } + static void randSeed( uint32_t seedValue ) { sBase.seed( seedValue ); } //! returns a random boolean value - static bool randBool() - { - return sBase() & 1; - } + static bool randBool() { return sBase() & 1; } //! returns a random integer in the range [-2147483648,2147483647] - static int32_t randInt() - { - return sBase(); - } + static int32_t randInt() { return sBase(); } //! returns a random integer in the range [0,4294967296) - static uint32_t randUint() - { - return sBase(); - } + static uint32_t randUint() { return sBase(); } //! returns a random integer in the range [0,v) static int32_t randInt( int32_t v ) { - if( v <= 0 ) return 0; - else return sBase() % v; + if( v <= 0 ) + return 0; + + return sBase() % v; } //! returns a random integer in the range [0,v) static uint32_t randUint( uint32_t v ) { - if( v == 0 ) return 0; - else return sBase() % v; + if( v == 0 ) + return 0; + + return sBase() % v; } //! returns a random integer in the range [a,b) - static int32_t randInt( int32_t a, int32_t b ) - { - return randInt( b - a ) + a; - } + static int32_t randInt( int32_t a, int32_t b ) { return randInt( b - a ) + a; } //! returns a random float in the range [0.0f,1.0f) - static float randFloat() - { - return sFloatGen( sBase ); - } + static T randFloat() { return sFloatGen( sBase ); } //! returns a random float in the range [0.0f,v) - static float randFloat( float v ) - { - return sFloatGen( sBase ) * v; - } + static T randFloat( T v ) { return randFloat() * v; } //! returns a random float in the range [a,b) - static float randFloat( float a, float b ) - { - return sFloatGen( sBase ) * ( b - a ) + a; - } + static T randFloat( T a, T b ) { return randFloat() * ( b - a ) + a; } //! returns a random float in the range [a,b) or the range [-b,-a) - static float randPosNegFloat( float a, float b ) + static T randPosNegFloat( T a, T b ) { if( randBool() ) return randFloat( a, b ); - else - return -randFloat( a, b ); + + return -randFloat( a, b ); } //! returns a random vec3 that represents a point on the unit sphere - static vec3 randVec3() + static glm::vec<3, T, glm::defaultp> randVec3() { - float phi = randFloat( (float)M_PI * 2.0f ); - float costheta = randFloat( -1.0f, 1.0f ); + const T phi = randFloat( T{ M_PI * 2.0 } ); + const T cosTheta = randFloat( T{ -1 }, T{ 1 } ); - float rho = math::sqrt( 1.0f - costheta * costheta ); - float x = rho * math::cos( phi ); - float y = rho * math::sin( phi ); - float z = costheta; + const T rho = sqrt( T{ 1 } - cosTheta * cosTheta ); + const T x = rho * cos( phi ); + const T y = rho * sin( phi ); + const T z = cosTheta; - return vec3( x, y, z ); + return glm::vec<3, T, glm::defaultp>( x, y, z ); } //! returns a random vec2 that represents a point on the unit circle - static vec2 randVec2() + static glm::vec<2, T, glm::defaultp> randVec2() { - float theta = randFloat( (float)M_PI * 2.0f ); - return vec2( math::cos( theta ), math::sin( theta ) ); + const T theta = randFloat( T{ M_PI * 2.0 } ); + return glm::vec<2, T, glm::defaultp>( math::cos( theta ), math::sin( theta ) ); } //! returns a random float via Gaussian distribution - static float randGaussian() + static T randGaussian() { - static std::normal_distribution dist{}; + static std::normal_distribution dist{}; return dist( sBase ); } private: - std::mt19937 mBase; - std::uniform_real_distribution mFloatGen; - std::normal_distribution mNormDist; + std::mt19937 mBase; + std::uniform_real_distribution mFloatGen; + std::normal_distribution mNormDist; - static std::mt19937 sBase; - static std::uniform_real_distribution sFloatGen; + static std::mt19937 sBase; + static std::uniform_real_distribution sFloatGen; }; +using Rand = RandT; +using Randf = RandT; +using Randd = RandT; +using Randld = RandT; + //! Resets the static random generator to the specific seed \a seedValue CI_API inline void randSeed( uint32_t seedValue ) { Rand::randSeed( seedValue ); } diff --git a/proj/vc2019/cinder.vcxproj b/proj/vc2019/cinder.vcxproj index 3c671a9e23..70ddec4855 100644 --- a/proj/vc2019/cinder.vcxproj +++ b/proj/vc2019/cinder.vcxproj @@ -825,6 +825,7 @@ + @@ -1135,6 +1136,7 @@ + @@ -1400,4 +1402,4 @@ - + \ No newline at end of file diff --git a/proj/vc2019/cinder.vcxproj.filters b/proj/vc2019/cinder.vcxproj.filters index 7bf675aa95..53cecac66e 100644 --- a/proj/vc2019/cinder.vcxproj.filters +++ b/proj/vc2019/cinder.vcxproj.filters @@ -1080,6 +1080,9 @@ Source Files\gl\nv + + Source Files + @@ -2270,5 +2273,8 @@ Header Files\gl\nv + + Header Files + \ No newline at end of file diff --git a/src/cinder/QuasiRand.cpp b/src/cinder/QuasiRand.cpp new file mode 100644 index 0000000000..76c1f91649 --- /dev/null +++ b/src/cinder/QuasiRand.cpp @@ -0,0 +1,33 @@ +/* + Copyright (c) 2020, The Cinder Project: http://libcinder.org + All rights reserved. + + This code is intended for use with the Cinder C++ library: http://libcinder.org + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that + the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and + the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "cinder/QuasiRand.h" + +namespace cinder { + +uint32_t QuasiRandT::sSeed = 0; +uint32_t QuasiRandT::sSeed = 0; +uint32_t QuasiRandT::sSeed = 0; + +} // namespace cinder \ No newline at end of file diff --git a/src/cinder/Rand.cpp b/src/cinder/Rand.cpp index aa35044dc4..58f0018632 100644 --- a/src/cinder/Rand.cpp +++ b/src/cinder/Rand.cpp @@ -7,9 +7,9 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this list of conditions and + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED @@ -26,7 +26,11 @@ namespace cinder { -std::mt19937 Rand::sBase( 310u ); -std::uniform_real_distribution Rand::sFloatGen; +std::mt19937 RandT::sBase( 310u ); +std::uniform_real_distribution RandT::sFloatGen; +std::mt19937 RandT::sBase( 310u ); +std::uniform_real_distribution RandT::sFloatGen; +std::mt19937 RandT::sBase( 310u ); +std::uniform_real_distribution RandT::sFloatGen; -} // ci \ No newline at end of file +} // namespace cinder \ No newline at end of file