]> git.localhorst.tv Git - blobs.git/commitdiff
rand lib from blank
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 13 Nov 2017 21:28:14 +0000 (22:28 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Mon, 13 Nov 2017 21:28:14 +0000 (22:28 +0100)
src/rand/GaloisLFSR.hpp [new file with mode: 0644]
src/rand/OctaveNoise.hpp [new file with mode: 0644]
src/rand/SimplexNoise.hpp [new file with mode: 0644]
src/rand/WorleyNoise.hpp [new file with mode: 0644]
src/rand/noise.cpp [new file with mode: 0644]
tst/rand/GaloisLFSRTest.cpp [new file with mode: 0644]
tst/rand/GaloisLFSRTest.hpp [new file with mode: 0644]
tst/rand/StabilityTest.cpp [new file with mode: 0644]
tst/rand/StabilityTest.hpp [new file with mode: 0644]

diff --git a/src/rand/GaloisLFSR.hpp b/src/rand/GaloisLFSR.hpp
new file mode 100644 (file)
index 0000000..83dc0e5
--- /dev/null
@@ -0,0 +1,87 @@
+#ifndef BLOBS_RAND_GALOISLFSR_HPP_
+#define BLOBS_RAND_GALOISLFSR_HPP_
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+
+
+namespace blobs {
+namespace rand {
+
+class GaloisLFSR {
+
+public:
+       // seed should be non-zero
+       explicit GaloisLFSR(std::uint64_t seed) noexcept
+       : state(seed) {
+               if (state == 0) {
+                       state = 1;
+               }
+       }
+
+       // get the next bit
+       bool operator ()() noexcept {
+               bool result = state & 1;
+               state >>= 1;
+               if (result) {
+                       state |= 0x8000000000000000;
+                       state ^= mask;
+               } else {
+                       state &= 0x7FFFFFFFFFFFFFFF;
+               }
+               return result;
+       }
+
+       template<class T>
+       T operator ()(T &out) noexcept {
+               constexpr int num_bits =
+                       std::numeric_limits<T>::digits +
+                       std::numeric_limits<T>::is_signed;
+               for (int i = 0; i < num_bits; ++i) {
+                       operator ()();
+               }
+               return out = static_cast<T>(state);
+       }
+
+       /// special case for randrom(boolean), since static_cast<bool>(0b10) == true
+       bool operator ()(bool &out) noexcept {
+               return out = operator ()();
+       }
+
+       template<class T>
+       T Next() noexcept {
+               T next;
+               return (*this)(next);
+       }
+
+       float SNorm() noexcept {
+               return float(Next<std::uint32_t>()) * (1.0f / 2147483647.5f) - 1.0f;
+       }
+
+       float UNorm() noexcept {
+               return float(Next<std::uint32_t>()) * (1.0f / 4294967295.0f);
+       }
+
+       template<class Container>
+       typename Container::reference From(Container &c) {
+               assert(c.size() > 0);
+               return c[Next<typename Container::size_type>() % c.size()];
+       }
+       template<class Container>
+       typename Container::const_reference From(const Container &c) {
+               assert(c.size() > 0);
+               return c[Next<typename Container::size_type>() % c.size()];
+       }
+
+private:
+       std::uint64_t state;
+       // bits 64, 63, 61, and 60 set to 1 (counting from 1 lo to hi)
+       static constexpr std::uint64_t mask = 0xD800000000000000;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/rand/OctaveNoise.hpp b/src/rand/OctaveNoise.hpp
new file mode 100644 (file)
index 0000000..6a91c85
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef BLOBS_RAND_OCTAVENOISE_HPP_
+#define BLOBS_RAND_OCTAVENOISE_HPP_
+
+#include "../graphics/glm.hpp"
+
+
+namespace blobs {
+namespace rand {
+
+template<class Noise>
+float OctaveNoise(
+       const Noise &noise,
+       const glm::vec3 &in,
+       int num,
+       float persistence,
+       float frequency = 1.0f,
+       float amplitude = 1.0f,
+       float growth = 2.0f
+) {
+       float total = 0.0f;
+       float max = 0.0f;
+       for (int i = 0; i < num; ++i) {
+               total += noise(in * frequency) * amplitude;
+               max += amplitude;
+               amplitude *= persistence;
+               frequency *= growth;
+       }
+
+       return total / max;
+}
+
+}
+}
+
+#endif
diff --git a/src/rand/SimplexNoise.hpp b/src/rand/SimplexNoise.hpp
new file mode 100644 (file)
index 0000000..9a72e64
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef BLOBS_RAND_SIMPLEXNOISE_HPP_
+#define BLOBS_RAND_SIMPLEXNOISE_HPP_
+
+#include "../graphics/glm.hpp"
+
+#include <cstdint>
+
+
+namespace blobs {
+namespace rand {
+
+class SimplexNoise {
+
+public:
+       explicit SimplexNoise(std::uint64_t seed) noexcept;
+
+       float operator ()(const glm::vec3 &) const noexcept;
+
+private:
+       int Perm(int idx) const noexcept;
+       int Perm12(int idx) const noexcept;
+       const glm::vec3 &Grad(int idx) const noexcept;
+
+private:
+       int perm[512];
+       int perm12[512];
+       glm::vec3 grad[12];
+       glm::ivec3 second_ints[8];
+       glm::ivec3 third_ints[8];
+       glm::vec3 second_floats[8];
+       glm::vec3 third_floats[8];
+
+};
+
+}
+}
+
+#endif
diff --git a/src/rand/WorleyNoise.hpp b/src/rand/WorleyNoise.hpp
new file mode 100644 (file)
index 0000000..34c4871
--- /dev/null
@@ -0,0 +1,27 @@
+#ifndef BLOBS_RAND_WORLEYNOISE_HPP_
+#define BLOBS_RAND_WORLEYNOISE_HPP_
+
+#include "../graphics/glm.hpp"
+
+
+namespace blobs {
+namespace rand {
+
+/// implementation of Worley noise (aka Cell or Voroni noise)
+class WorleyNoise {
+
+public:
+       explicit WorleyNoise(unsigned int seed) noexcept;
+
+       float operator ()(const glm::vec3 &) const noexcept;
+
+private:
+       const unsigned int seed;
+       const int num_points;
+
+};
+
+}
+}
+
+#endif
diff --git a/src/rand/noise.cpp b/src/rand/noise.cpp
new file mode 100644 (file)
index 0000000..07ab817
--- /dev/null
@@ -0,0 +1,212 @@
+#include "GaloisLFSR.hpp"
+#include "SimplexNoise.hpp"
+#include "WorleyNoise.hpp"
+
+#include <cmath>
+#include <glm/gtx/norm.hpp>
+
+
+namespace {
+
+constexpr float one_third = 1.0f/3.0f;
+constexpr float one_sixth = 1.0f/6.0f;
+
+}
+
+namespace blobs {
+namespace rand {
+
+SimplexNoise::SimplexNoise(std::uint64_t seed) noexcept
+: grad({
+       {  1.0f,  1.0f,  0.0f },
+       { -1.0f,  1.0f,  0.0f },
+       {  1.0f, -1.0f,  0.0f },
+       { -1.0f, -1.0f,  0.0f },
+       {  1.0f,  0.0f,  1.0f },
+       { -1.0f,  0.0f,  1.0f },
+       {  1.0f,  0.0f, -1.0f },
+       { -1.0f,  0.0f, -1.0f },
+       {  0.0f,  1.0f,  1.0f },
+       {  0.0f, -1.0f,  1.0f },
+       {  0.0f,  1.0f, -1.0f },
+       {  0.0f, -1.0f, -1.0f },
+})
+, second_ints({
+                    // x>y x>z y>z
+       { 0, 0, 1 }, //  0   0   0  ZYX
+       { 0, 1, 0 }, //  0   0   1  YZX
+       { 0, 0, 1 }, //  0   1   0  illogical, but ZYX
+       { 0, 1, 0 }, //  0   1   1  YXZ
+       { 0, 0, 1 }, //  1   0   0  ZXY
+       { 1, 0, 0 }, //  1   0   1  illogical, but XYZ
+       { 1, 0, 0 }, //  1   1   0  XZY
+       { 1, 0, 0 }, //  1   1   1  XYZ
+})
+, third_ints({
+                    // x>y x>z y>z
+       { 0, 1, 1 }, //  0   0   0  ZYX
+       { 0, 1, 1 }, //  0   0   1  YZX
+       { 0, 1, 1 }, //  0   1   0  illogical, but ZYX
+       { 1, 1, 0 }, //  0   1   1  YXZ
+       { 1, 0, 1 }, //  1   0   0  ZXY
+       { 1, 1, 0 }, //  1   0   1  illogical, but XYZ
+       { 1, 0, 1 }, //  1   1   0  XZY
+       { 1, 1, 0 }, //  1   1   1  XYZ
+})
+, second_floats({
+                             // x>y x>z y>z
+       { 0.0f, 0.0f, 1.0f }, //  0   0   0  ZYX
+       { 0.0f, 1.0f, 0.0f }, //  0   0   1  YZX
+       { 0.0f, 0.0f, 1.0f }, //  0   1   0  illogical, but ZYX
+       { 0.0f, 1.0f, 0.0f }, //  0   1   1  YXZ
+       { 0.0f, 0.0f, 1.0f }, //  1   0   0  ZXY
+       { 1.0f, 0.0f, 0.0f }, //  1   0   1  illogical, but XYZ
+       { 1.0f, 0.0f, 0.0f }, //  1   1   0  XZY
+       { 1.0f, 0.0f, 0.0f }, //  1   1   1  XYZ
+})
+, third_floats({
+                             // x>y x>z y>z
+       { 0.0f, 1.0f, 1.0f }, //  0   0   0  ZYX
+       { 0.0f, 1.0f, 1.0f }, //  0   0   1  YZX
+       { 0.0f, 1.0f, 1.0f }, //  0   1   0  illogical, but ZYX
+       { 1.0f, 1.0f, 0.0f }, //  0   1   1  YXZ
+       { 1.0f, 0.0f, 1.0f }, //  1   0   0  ZXY
+       { 1.0f, 1.0f, 0.0f }, //  1   0   1  illogical, but XYZ
+       { 1.0f, 0.0f, 1.0f }, //  1   1   0  XZY
+       { 1.0f, 1.0f, 0.0f }, //  1   1   1  XYZ
+}) {
+       GaloisLFSR random(seed ^ 0x0123456789ACBDEF);
+       unsigned char value;
+       for (size_t i = 0; i < 256; ++i) {
+               perm[i] = random(value);
+               perm[i] &= 0xFF;
+               perm[i + 256] = perm[i];
+               perm12[i] = perm[i] % 12;
+               perm12[i + 256] = perm12[i];
+       }
+}
+
+
+float SimplexNoise::operator ()(const glm::vec3 &in) const noexcept {
+       float skew = (in.x + in.y + in.z) * one_third;
+
+       glm::vec3 skewed(glm::floor(in + skew));
+       float tr = (skewed.x + skewed.y + skewed.z) * one_sixth;
+
+       glm::vec3 unskewed(skewed - tr);
+       glm::vec3 relative(in - unskewed);
+
+       bool x_ge_y = relative.x >= relative.y;
+       bool x_ge_z = relative.x >= relative.z;
+       bool y_ge_z = relative.y >= relative.z;
+       unsigned int st = (x_ge_y << 2) | (x_ge_z << 1) | y_ge_z;
+
+       glm::ivec3 second_int(second_ints[st]);
+       glm::ivec3 third_int(third_ints[st]);
+       glm::vec3 second_float(second_floats[st]);
+       glm::vec3 third_float(third_floats[st]);
+
+       glm::vec3 offset[4] = {
+               in - unskewed,
+               relative - second_float + one_sixth,
+               relative - third_float + one_third,
+               relative - 0.5f,
+       };
+
+       int index[3] = {
+               (int)(skewed.x) & 0xFF,
+               (int)(skewed.y) & 0xFF,
+               (int)(skewed.z) & 0xFF,
+       };
+
+       float n = 0.0f;
+
+       // I know 0.6 is wrong, but for some reason it looks better than 0.5
+
+       // 0
+       float t = glm::clamp(0.6f - glm::length2(offset[0]), 0.0f, 1.0f);
+       t *= t;
+       int corner = Perm12(index[0] + Perm(index[1] + Perm(index[2])));
+       n += t * t * glm::dot(Grad(corner), offset[0]);
+
+       // 1
+       t = glm::clamp(0.6f - glm::length2(offset[1]), 0.0f, 1.0f);
+       t *= t;
+       corner = Perm12(index[0] + second_int.x + Perm(index[1] + second_int.y + Perm(index[2] + second_int.z)));
+       n += t * t * glm::dot(Grad(corner), offset[1]);
+
+       // 2
+       t = glm::clamp(0.6f - glm::length2(offset[2]), 0.0f, 1.0f);
+       t *= t;
+       corner = Perm12(index[0] + third_int.x + Perm(index[1] + third_int.y + Perm(index[2] + third_int.z)));
+       n += t * t * glm::dot(Grad(corner), offset[2]);
+
+       // 3
+       t = glm::clamp(0.6f - glm::length2(offset[3]), 0.0f, 1.0f);
+       t *= t;
+       corner = Perm12(index[0] + 1 + Perm(index[1] + 1 + Perm(index[2] + 1)));
+       n += t * t * glm::dot(Grad(corner), offset[3]);
+
+       return 32.0f * n;
+}
+
+
+int SimplexNoise::Perm(int idx) const noexcept {
+       return perm[idx];
+}
+
+int SimplexNoise::Perm12(int idx) const noexcept {
+       return perm12[idx];
+}
+
+const glm::vec3 &SimplexNoise::Grad(int idx) const noexcept {
+       return grad[idx];
+}
+
+
+WorleyNoise::WorleyNoise(unsigned int seed) noexcept
+: seed(seed)
+, num_points(8) {
+
+}
+
+float WorleyNoise::operator ()(const glm::vec3 &in) const noexcept {
+       glm::vec3 center = glm::floor(in);
+
+       float closest = 1.0f;  // cannot be farther away than 1.0
+
+       for (int z = -1; z <= 1; ++z) {
+               for (int y = -1; y <= 1; ++y) {
+                       for (int x = -1; x <= 1; ++x) {
+                               glm::vec3 cube(center.x + x, center.y + y, center.z + z);
+                               unsigned int cube_rand =
+                                       (unsigned(cube.x) * 130223) ^
+                                       (unsigned(cube.y) * 159899) ^
+                                       (unsigned(cube.z) * 190717) ^
+                                       seed;
+
+                               for (int i = 0; i < num_points; ++i) {
+                                       glm::vec3 point(cube);
+                                       cube_rand = 190667 * cube_rand + 109807;
+                                       point.x += float(cube_rand % 262144) / 262144.0f;
+                                       cube_rand = 135899 * cube_rand + 189169;
+                                       point.y += float(cube_rand % 262144) / 262144.0f;
+                                       cube_rand = 159739 * cube_rand + 112139;
+                                       point.z += float(cube_rand % 262144) / 262144.0f;
+
+                                       float distance = glm::distance(in, point);
+                                       if (distance < closest) {
+                                               closest = distance;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       // closest ranges (0, 1), so normalizing to (-1,1) is trivial
+       // though heavily biased towards lower numbers
+       return 2.0f * closest - 1.0f;
+}
+
+}
+}
diff --git a/tst/rand/GaloisLFSRTest.cpp b/tst/rand/GaloisLFSRTest.cpp
new file mode 100644 (file)
index 0000000..0078207
--- /dev/null
@@ -0,0 +1,86 @@
+#include "GaloisLFSRTest.hpp"
+
+#include "rand/GaloisLFSR.hpp"
+
+#include <algorithm>
+#include <sstream>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(blobs::rand::test::GaloisLFSRTest);
+
+using namespace std;
+
+
+namespace blobs {
+namespace rand {
+namespace test {
+
+void GaloisLFSRTest::setUp() {
+
+}
+
+void GaloisLFSRTest::tearDown() {
+
+}
+
+void GaloisLFSRTest::testFloatNorm() {
+       GaloisLFSR random(4);
+       for (int i = 0; i < 64; ++i) {
+               float value = random.SNorm();
+               AssertBetween(
+                       "random signed normal float",
+                       -1.0f, 1.0f, value);
+       }
+       for (int i = 0; i < 64; ++i) {
+               float value = random.UNorm();
+               AssertBetween(
+                       "random unsigned normal float",
+                       0.0f, 1.0f, value);
+       }
+}
+
+void GaloisLFSRTest::testFromContainer() {
+       GaloisLFSR random(5);
+       const vector<int> container({ 1, 2, 3, 4, 5 });
+       for (int i = 0; i < 64; ++i) {
+               int element = random.From(container);
+               AssertContains(
+                       "random element from container",
+                       container, element);
+       }
+}
+
+void GaloisLFSRTest::AssertBetween(
+       string message,
+       float minimum,
+       float maximum,
+       float actual
+) {
+       stringstream msg;
+       msg << message << ": " << actual << " not in ["
+               << minimum << ',' << maximum << ']';
+       CPPUNIT_ASSERT_MESSAGE(
+               msg.str(),
+               minimum <= actual && actual <= maximum);
+
+}
+
+void GaloisLFSRTest::AssertContains(
+       string message,
+       const vector<int> &container,
+       int element
+) {
+       stringstream msg;
+       msg << message << ": " << element << " not in { ";
+       for (int i : container) {
+               msg << i << ' ';
+       }
+       msg << '}';
+       CPPUNIT_ASSERT_MESSAGE(
+               msg.str(),
+               find(container.begin(), container.end(), element) != container.end());
+
+}
+
+}
+}
+}
diff --git a/tst/rand/GaloisLFSRTest.hpp b/tst/rand/GaloisLFSRTest.hpp
new file mode 100644 (file)
index 0000000..512bb99
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef BLOBS_TEST_RAND_GALOISLFSRTEST_HPP
+#define BLOBS_TEST_RAND_GALOISLFSRTEST_HPP
+
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <string>
+#include <vector>
+
+
+namespace blobs {
+namespace rand {
+namespace test {
+
+class GaloisLFSRTest
+: public CppUnit::TestFixture {
+
+CPPUNIT_TEST_SUITE(GaloisLFSRTest);
+
+CPPUNIT_TEST(testFloatNorm);
+CPPUNIT_TEST(testFromContainer);
+
+CPPUNIT_TEST_SUITE_END();
+
+public:
+       void setUp();
+       void tearDown();
+
+       void testFloatNorm();
+       void testFromContainer();
+
+       /// check if value is in range [minimum,maximum]
+       static void AssertBetween(
+               std::string message,
+               float minimum,
+               float maximum,
+               float actual);
+
+       static void AssertContains(
+               std::string message,
+               const std::vector<int> &container,
+               int element);
+
+};
+
+}
+}
+}
+
+#endif
diff --git a/tst/rand/StabilityTest.cpp b/tst/rand/StabilityTest.cpp
new file mode 100644 (file)
index 0000000..c6c6339
--- /dev/null
@@ -0,0 +1,305 @@
+#include "StabilityTest.hpp"
+
+#include "rand/GaloisLFSR.hpp"
+#include "rand/SimplexNoise.hpp"
+#include "rand/WorleyNoise.hpp"
+
+#include <cstdint>
+#include <string>
+#include <sstream>
+#include <glm/gtx/io.hpp>
+
+CPPUNIT_TEST_SUITE_REGISTRATION(blobs::rand::test::StabilityTest);
+
+using namespace std;
+
+
+namespace blobs {
+namespace rand {
+namespace test {
+
+void StabilityTest::setUp() {
+
+}
+
+void StabilityTest::tearDown() {
+
+}
+
+
+void StabilityTest::testRNG() {
+       GaloisLFSR random(0);
+       uint16_t value;
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #1 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #2 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #3 from RNG",
+               uint16_t(0xB000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #4 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #5 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #6 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #7 from RNG",
+               uint16_t(0x4500), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #8 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #9 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #10 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #11 from RNG",
+               uint16_t(0x2E70), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #12 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #13 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #14 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #15 from RNG",
+               uint16_t(0x1011), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #16 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #17 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #18 from RNG",
+               uint16_t(0xB000), value
+       );
+       value = random.Next<uint16_t>();
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #19 from RNG (using Next())",
+               uint16_t(0x0B0B), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #20 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #21 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #22 from RNG",
+               uint16_t(0x1500), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #23 from RNG",
+               uint16_t(0x0454), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #24 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #25 from RNG",
+               uint16_t(0x0000), value
+       );
+       value = random.Next<uint16_t>();
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #26 from RNG (using Next())",
+               uint16_t(0xC970), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #27 from RNG",
+               uint16_t(0x02E5), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #28 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #29 from RNG",
+               uint16_t(0x0000), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #30 from RNG",
+               uint16_t(0x0101), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #31 from RNG",
+               uint16_t(0x0100), value
+       );
+       random(value);
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected value #32 from RNG",
+               uint16_t(0x0000), value
+       );
+
+       GaloisLFSR random1(1);
+       uint16_t value1;
+       for (int i = 0; i < 32; ++i) {
+               random1(value1);
+       }
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "RNG with seeds 0 and 1 differ",
+               value, value1
+       );
+
+       GaloisLFSR random_bool(0);
+       bool value_bool;
+       for (int i = 0; i < (16 * 32); ++i) {
+               random_bool(value_bool);
+       }
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected result for bool",
+               false, value_bool
+       );
+
+       GaloisLFSR random8(0);
+       uint8_t value8;
+       for (int i = 0; i < 31; ++i) {
+               random8(value8);
+       }
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected result for uint8",
+               uint8_t(0x10), value8
+       );
+
+       GaloisLFSR random32(0);
+       uint32_t value32;
+       for (int i = 0; i < 16; ++i) {
+               random32(value32);
+       }
+       CPPUNIT_ASSERT_EQUAL_MESSAGE(
+               "unexpected result for uint32",
+               uint32_t(0xB0000000), value32
+       );
+}
+
+void StabilityTest::testSimplex() {
+       SimplexNoise noise(0);
+
+       Assert(noise, glm::vec3(0.0f, 0.0f, 0.0f),  0.0f);
+       Assert(noise, glm::vec3(0.0f, 0.0f, 1.0f),  0.652221322059631f);
+       Assert(noise, glm::vec3(0.0f, 1.0f, 0.0f),  0.867977976799011f);
+       Assert(noise, glm::vec3(0.0f, 1.0f, 1.0f), -0.107878111302853f);
+       Assert(noise, glm::vec3(1.0f, 0.0f, 0.0f), -0.107878260314465f);
+       Assert(noise, glm::vec3(1.0f, 0.0f, 1.0f), -6.31356940061778e-08f);
+       Assert(noise, glm::vec3(1.0f, 1.0f, 0.0f), -0.107878245413303f);
+       Assert(noise, glm::vec3(1.0f, 1.0f, 1.0f),  0.0f);
+
+       Assert(noise, glm::vec3( 0.0f,  0.0f, -1.0f), -0.107878483831882f);
+       Assert(noise, glm::vec3( 0.0f, -1.0f,  0.0f), -0.760099768638611f);
+       Assert(noise, glm::vec3( 0.0f, -1.0f, -1.0f),  0.0f);
+       Assert(noise, glm::vec3(-1.0f,  0.0f,  0.0f),  0.760099768638611f);
+       Assert(noise, glm::vec3(-1.0f,  0.0f, -1.0f),  0.0f);
+       Assert(noise, glm::vec3(-1.0f, -1.0f,  0.0f), -0.107878118753433f);
+       Assert(noise, glm::vec3(-1.0f, -1.0f, -1.0f),  0.0f);
+}
+
+void StabilityTest::testWorley() {
+       WorleyNoise noise(0);
+
+       Assert(noise, glm::vec3(0.0f, 0.0f, 0.0f), -0.117765009403229f);
+       Assert(noise, glm::vec3(0.0f, 0.0f, 1.0f), -0.209876894950867f);
+       Assert(noise, glm::vec3(0.0f, 1.0f, 0.0f), -0.290086328983307f);
+       Assert(noise, glm::vec3(0.0f, 1.0f, 1.0f), -0.332393705844879f);
+       Assert(noise, glm::vec3(1.0f, 0.0f, 0.0f), -0.621925830841064f);
+       Assert(noise, glm::vec3(1.0f, 0.0f, 1.0f), -0.338455379009247f);
+       Assert(noise, glm::vec3(1.0f, 1.0f, 0.0f), -0.386664032936096f);
+       Assert(noise, glm::vec3(1.0f, 1.0f, 1.0f), -0.533940434455872f);
+
+       Assert(noise, glm::vec3( 0.0f,  0.0f, -1.0f), -0.425480604171753f);
+       Assert(noise, glm::vec3( 0.0f, -1.0f,  0.0f), -0.189745843410492f);
+       Assert(noise, glm::vec3( 0.0f, -1.0f, -1.0f), -0.30408102273941f);
+       Assert(noise, glm::vec3(-1.0f,  0.0f,  0.0f), -0.618566155433655f);
+       Assert(noise, glm::vec3(-1.0f,  0.0f, -1.0f), -0.060045599937439f);
+       Assert(noise, glm::vec3(-1.0f, -1.0f,  0.0f), -0.366827547550201f);
+       Assert(noise, glm::vec3(-1.0f, -1.0f, -1.0f), -0.575981974601746f);
+}
+
+void StabilityTest::Assert(
+       const SimplexNoise &noise,
+       const glm::vec3 &position,
+       float expected
+) {
+       stringstream msg;
+       msg << "unexpected simplex noise value at " << position;
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               msg.str(),
+               expected, noise(position), numeric_limits<float>::epsilon()
+       );
+}
+
+void StabilityTest::Assert(
+       const WorleyNoise &noise,
+       const glm::vec3 &position,
+       float expected
+) {
+       stringstream msg;
+       msg << "unexpected worley noise value at " << position;
+       CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE(
+               msg.str(),
+               expected, noise(position), numeric_limits<float>::epsilon()
+       );
+}
+
+}
+}
+}
diff --git a/tst/rand/StabilityTest.hpp b/tst/rand/StabilityTest.hpp
new file mode 100644 (file)
index 0000000..8f38114
--- /dev/null
@@ -0,0 +1,53 @@
+#ifndef BLOBS_TEST_RAND_STABILITYTEST_HPP
+#define BLOBS_TEST_RAND_STABILITYTEST_HPP
+
+#include "graphics/glm.hpp"
+
+#include <cppunit/extensions/HelperMacros.h>
+
+
+
+namespace blobs {
+namespace rand {
+
+class SimplexNoise;
+class WorleyNoise;
+
+namespace test {
+
+class StabilityTest
+: public CppUnit::TestFixture {
+
+CPPUNIT_TEST_SUITE(StabilityTest);
+
+CPPUNIT_TEST(testRNG);
+CPPUNIT_TEST(testSimplex);
+CPPUNIT_TEST(testWorley);
+
+CPPUNIT_TEST_SUITE_END();
+
+public:
+       void setUp();
+       void tearDown();
+
+       void testRNG();
+       void testSimplex();
+       void testWorley();
+
+       static void Assert(
+               const SimplexNoise &noise,
+               const glm::vec3 &position,
+               float expected);
+
+       static void Assert(
+               const WorleyNoise &noise,
+               const glm::vec3 &position,
+               float expected);
+
+};
+
+}
+}
+}
+
+#endif