diff --git a/Code/Framework/AzCore/AzCore/Math/Random.h b/Code/Framework/AzCore/AzCore/Math/Random.h index 5ae37433ec..165f65dc33 100644 --- a/Code/Framework/AzCore/AzCore/Math/Random.h +++ b/Code/Framework/AzCore/AzCore/Math/Random.h @@ -86,4 +86,94 @@ namespace AZ Normal, UniformReal }; + + //! Halton sequences are deterministic, quasi-random sequences with low discrepancy. They + //! are useful for generating evenly distributed points. + //! See https://en.wikipedia.org/wiki/Halton_sequence for more information. + + //! Returns a single halton number. + //! @param index The index of the number. Indices start at 1. Using index 0 will return 0. + //! @param base The numerical base of the halton number. + inline float GetHaltonNumber(uint32_t index, uint32_t base) + { + float fraction = 1.0f; + float result = 0.0f; + + while (index > 0) + { + fraction = fraction / base; + result += fraction * (index % base); + index = floor(index / base); + } + + return result; + } + + //! A helper class for generating arrays of Halton sequences in n dimensions. + //! The class holds the state of which bases to use, the starting offset + //! of each dimension and how much to increment between each index for each + //! dimension. + template + class HaltonSequence + { + public: + + //! Initializes a Halton sequence with some bases. By default there is no + //! offset and the index increments by one between each number. + HaltonSequence(AZStd::array bases) + : m_bases(bases) + { + m_offsets.fill(1); // Halton sequences start at index 1. + m_increments.fill(1); + } + + //! Returns a Halton sequence in an array of N length + template + AZStd::array, N> GetHaltonSequence() + { + AZStd::array, N> result; + + AZStd::array indices = m_offsets; + + // Generator that returns the Halton number for all bases for a single entry. + auto f = [&] () + { + AZStd::array item; + for (auto d = 0; d < Dimensions; ++d) + { + item[d] = GetHaltonNumber(indices[d], m_bases[d]); + indices[d] += m_increments[d]; + } + return item; + }; + + AZStd::generate(result.begin(), result.end(), f); + return result; + } + + //! Sets the offsets per dimension to start generating a sequence from. + //! By default, there is no offset (offset of 0 corresponds to starting at index 1) + void SetOffsets(AZStd::array offsets) + { + m_offsets = offsets; + + // Halton sequences start at index 1, so increment all the indices. + AZStd::for_each(m_offsets.begin(), m_offsets.end(), [](uint32_t &n){ n++; }); + } + + //! Sets the increment between numbers in the halton sequence per dimension + //! By default this is 1, meaning that no numbers are skipped. Can be negative + //! to generate numbers in reverse order. + void SetIncrements(AZStd::array increments) + { + m_increments = increments; + } + + private: + + AZStd::array m_bases; + AZStd::array m_offsets; + AZStd::array m_increments; + + }; } diff --git a/Code/Framework/AzCore/Tests/Math/RandomTests.cpp b/Code/Framework/AzCore/Tests/Math/RandomTests.cpp new file mode 100644 index 0000000000..ace7d99704 --- /dev/null +++ b/Code/Framework/AzCore/Tests/Math/RandomTests.cpp @@ -0,0 +1,74 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +using namespace AZ; + +namespace UnitTest +{ + TEST(MATH_Random, GetHaltonNumber) + { + EXPECT_FLOAT_EQ(0.5, GetHaltonNumber(1, 2)); + EXPECT_FLOAT_EQ(898.0f / 2187.0f, GetHaltonNumber(1234, 3)); + EXPECT_FLOAT_EQ(5981.0f / 15625.0f, GetHaltonNumber(4321, 5)); + } + + TEST(MATH_Random, HaltonSequence) + { + HaltonSequence<3> sequence({ 2, 3, 5 }); + auto regularSequence = sequence.GetHaltonSequence<5>(); + + EXPECT_FLOAT_EQ(1.0f / 2.0f, regularSequence[0][0]); + EXPECT_FLOAT_EQ(1.0f / 3.0f, regularSequence[0][1]); + EXPECT_FLOAT_EQ(1.0f / 5.0f, regularSequence[0][2]); + + EXPECT_FLOAT_EQ(1.0f / 4.0f, regularSequence[1][0]); + EXPECT_FLOAT_EQ(2.0f / 3.0f, regularSequence[1][1]); + EXPECT_FLOAT_EQ(2.0f / 5.0f, regularSequence[1][2]); + + EXPECT_FLOAT_EQ(3.0f / 4.0f, regularSequence[2][0]); + EXPECT_FLOAT_EQ(1.0f / 9.0f, regularSequence[2][1]); + EXPECT_FLOAT_EQ(3.0f / 5.0f, regularSequence[2][2]); + + EXPECT_FLOAT_EQ(1.0f / 8.0f, regularSequence[3][0]); + EXPECT_FLOAT_EQ(4.0f / 9.0f, regularSequence[3][1]); + EXPECT_FLOAT_EQ(4.0f / 5.0f, regularSequence[3][2]); + + EXPECT_FLOAT_EQ(5.0f / 8.0f, regularSequence[4][0]); + EXPECT_FLOAT_EQ(7.0f / 9.0f, regularSequence[4][1]); + EXPECT_FLOAT_EQ(1.0f / 25.0f, regularSequence[4][2]); + + sequence.SetOffsets({ 1, 2, 3 }); + auto offsetSequence = sequence.GetHaltonSequence<2>(); + + EXPECT_FLOAT_EQ(1.0f / 4.0f, offsetSequence[0][0]); + EXPECT_FLOAT_EQ(1.0f / 9.0f, offsetSequence[0][1]); + EXPECT_FLOAT_EQ(4.0f / 5.0f, offsetSequence[0][2]); + + EXPECT_FLOAT_EQ(3.0f / 4.0f, offsetSequence[1][0]); + EXPECT_FLOAT_EQ(4.0f / 9.0f, offsetSequence[1][1]); + EXPECT_FLOAT_EQ(1.0f / 25.0f, offsetSequence[1][2]); + + sequence.SetIncrements({ 1, 2, 3 }); + auto incrementedSequence = sequence.GetHaltonSequence<2>(); + + EXPECT_FLOAT_EQ(1.0f / 4.0f, incrementedSequence[0][0]); + EXPECT_FLOAT_EQ(1.0f / 9.0f, incrementedSequence[0][1]); + EXPECT_FLOAT_EQ(4.0f / 5.0f, incrementedSequence[0][2]); + + EXPECT_FLOAT_EQ(3.0f / 4.0f, incrementedSequence[1][0]); + EXPECT_FLOAT_EQ(7.0f / 9.0f, incrementedSequence[1][1]); + EXPECT_FLOAT_EQ(11.0f / 25.0f, incrementedSequence[1][2]); + } +} diff --git a/Code/Framework/AzCore/Tests/azcoretests_files.cmake b/Code/Framework/AzCore/Tests/azcoretests_files.cmake index f90717d003..78b2701d92 100644 --- a/Code/Framework/AzCore/Tests/azcoretests_files.cmake +++ b/Code/Framework/AzCore/Tests/azcoretests_files.cmake @@ -152,6 +152,7 @@ set(FILES Math/PlaneTests.cpp Math/QuaternionPerformanceTests.cpp Math/QuaternionTests.cpp + Math/RandomTests.cpp Math/ShapeIntersectionPerformanceTests.cpp Math/ShapeIntersectionTests.cpp Math/SfmtTests.cpp