diff --git a/src/Magnum/Math/CMakeLists.txt b/src/Magnum/Math/CMakeLists.txt index 749d824d7e..663c2ae861 100644 --- a/src/Magnum/Math/CMakeLists.txt +++ b/src/Magnum/Math/CMakeLists.txt @@ -49,6 +49,7 @@ set(MagnumMath_HEADERS Quaternion.h Packing.h PackingBatch.h + Random.h Range.h RectangularMatrix.h StrictWeakOrdering.h diff --git a/src/Magnum/Math/Random.h b/src/Magnum/Math/Random.h new file mode 100644 index 0000000000..e7457d4ee3 --- /dev/null +++ b/src/Magnum/Math/Random.h @@ -0,0 +1,100 @@ +#ifndef Magnum_Math_Random_h +#define Magnum_Math_Random_h + +// TO DO Licence things. + +#include +#include +#include "Magnum/Types.h" +#include "Magnum/Math/Constants.h" +#include "Magnum/Math/Vector2.h" +#include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Quaternion.h" +#include "Magnum/Math/Functions.h" + +namespace Magnum +{ +namespace Math +{ + +namespace Random +{ +class RandomGenerator +{ +public: + RandomGenerator() + { + std::seed_seq seeds{{ + static_cast(std::random_device{}()), + static_cast(std::chrono::steady_clock::now() + .time_since_epoch() + .count()), + }}; + g = std::mt19937{seeds}; + }; + template + typename std::enable_if::value, T>::type + generate(T start = -Magnum::Math::Constants::inf(), + T end = Magnum::Math::Constants::inf()) + { + return std::uniform_int_distribution{start, end}(g); + } + + template + typename std::enable_if::value, T>::type + generate(T start = -Magnum::Math::Constants::inf(), + T end = Magnum::Math::Constants::inf()) + { + return std::uniform_real_distribution{start, end}(g); + } + +private: + // namespace Implementation + std::mt19937 g; +}; + +template +T randomScalar(RandomGenerator &g, T begin = 0.0f, T end = 1.0f) +{ + + return g.generate(static_cast(begin), + static_cast(end)); +} + +template +Vector2 randomUnitVector2(RandomGenerator &g) +{ + auto a = g.generate(0.0f, 2 * Math::Constants::pi()); + return {std::cos(a), std::sin(a)}; +} + +template +Vector3 randomUnitVector3(RandomGenerator &g) +{ + // Better to have it "theta" and "z" than three random numbers. + // https://mathworld.wolfram.com/SpherePointPicking.html + auto a = g.generate(0.0f, 2 * Math::Constants::pi()); + auto z = randomScalar(g, -1.0f, -1.0f); + auto r = sqrt(1 - z * z); + return {r * std::cos(a), r * std::sin(a), z}; +} + +template +Quaternion randomRotation(RandomGenerator &g) +{ + //http://planning.cs.uiuc.edu/node198.html + auto u = randomScalar(g); + auto v = 2 * Math::Constants::pi() * randomScalar(g); + auto w = 2 * Math::Constants::pi() * randomScalar(g); + return Quaternion({sqrt(1 - u) * std::sin(v), + sqrt(1 - u) * std::cos(v), + sqrt(u) * std::sin(w)}, + sqrt(u) * std::cos(w)); +} + +} // namespace Random + +} // namespace Math +} // namespace Magnum + +#endif diff --git a/src/Magnum/Math/Test/CMakeLists.txt b/src/Magnum/Math/Test/CMakeLists.txt index de48dbe704..873a0e95c9 100644 --- a/src/Magnum/Math/Test/CMakeLists.txt +++ b/src/Magnum/Math/Test/CMakeLists.txt @@ -70,6 +70,8 @@ corrade_add_test(MathStrictWeakOrderingTest StrictWeakOrderingTest.cpp LIBRARIES corrade_add_test(MathMatrixBenchmark MatrixBenchmark.cpp LIBRARIES MagnumMathTestLib) +corrade_add_test(MathRandomTest RandomTest.cpp LIBRARIES MagnumMathTestLib) + set_property(TARGET MathVectorTest MathMatrixTest @@ -85,6 +87,8 @@ set_property(TARGET MathDistanceTest MathIntersectionTest + + MathRandomTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( @@ -130,4 +134,6 @@ set_target_properties( MathConfigurationValueTest MathStrictWeakOrderingTest + + MathRandomTest PROPERTIES FOLDER "Magnum/Math/Test") diff --git a/src/Magnum/Math/Test/RandomTest.cpp b/src/Magnum/Math/Test/RandomTest.cpp new file mode 100644 index 0000000000..043575a6fc --- /dev/null +++ b/src/Magnum/Math/Test/RandomTest.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include "Magnum/Math/Random.h" + +namespace Magnum +{ +namespace Math +{ + +namespace Test +{ +namespace +{ + +struct RandomTest : Corrade::TestSuite::Tester +{ + explicit RandomTest(); + + void randScalar(); + void unitVector2(); + void unitVector3(); + void randomRotation(); + void randomDiceChiSquare(); +}; + +typedef Vector<2, Float> Vector2; +typedef Vector<3, Float> Vector3; +typedef Math::Constants Constants; + +RandomTest::RandomTest() +{ + Corrade::TestSuite::Tester::addRepeatedTests( + {&RandomTest::randScalar, + &RandomTest::unitVector2, + &RandomTest::unitVector3, + &RandomTest::randomRotation}, + /*repeat number*/ 200); + Corrade::TestSuite::Tester::addTests( + {&RandomTest::randomDiceChiSquare}); +} + +void RandomTest::randScalar() +{ + Math::Random::RandomGenerator g; + CORRADE_COMPARE_AS(Math::Random::randomScalar(g, -1.0, 1.0), 1.0f, Corrade::TestSuite::Compare::LessOrEqual); + CORRADE_COMPARE_AS(Math::Random::randomScalar(g, -1.0, 1.0), -1.0f, Corrade::TestSuite::Compare::GreaterOrEqual); +} + +void RandomTest::unitVector2() +{ + Math::Random::RandomGenerator g; + CORRADE_COMPARE((Math::Random::randomUnitVector2(g)).length(), 1.0f); +} +void RandomTest::unitVector3() +{ + Math::Random::RandomGenerator g; + + CORRADE_COMPARE((Math::Random::randomUnitVector3(g)).length(), 1.0f); +} + +void RandomTest::randomRotation() +{ + Math::Random::RandomGenerator g; + + CORRADE_COMPARE(Math::Random::randomRotation(g).length(), 1.0f); +} + +void RandomTest::randomDiceChiSquare() +{ + // A step by step explanation + // https://rpg.stackexchange.com/questions/70802/how-can-i-test-whether-a-die-is-fair + Math::Random::RandomGenerator g; + + int error_count = 0; // We have 1 chance to over shoot. Thats why no repeated test. + + const Int dice_side = 20; + const Int expected = 10000; + const Float thresholdfor100 = 36.191; + + for (auto i = 0; i < 100; i++) + { + std::vector faces(dice_side, 0); + for (std::size_t i = 0; i < expected * dice_side; i++) + faces[Math::Random::randomScalar(g, 0, dice_side - 1)]++; + Float chi_square = 0.0f; + for (std::size_t i = 0; i < dice_side; i++) + chi_square += Float(pow((faces[i] - expected), 2)) / expected; + if (chi_square > thresholdfor100) + error_count++; + } + CORRADE_COMPARE_AS(error_count, 2, Corrade::TestSuite::Compare::Less); +} + +} // namespace +} // namespace Test +} // namespace Math +} // namespace Magnum + +CORRADE_TEST_MAIN(Magnum::Math::Test::RandomTest)