#include "ComplexNumbers.hpp" #include "ResponseCurve.hpp" #include "UnitTest++/UnitTest++.h" using namespace tp; SUITE(Math) { TEST(ComplexNumber) { CHECK((ComplexCart{ 3, 4 }.mod() == 5)); CHECK((abs(ComplexCart{ 1, 1 }.arg() - 0.7853981633974483) < 0.001)); CHECK((ComplexCart{ 3, 4 }.norm2() == 25)); { ComplexCart cn{ 3, 4 }; ComplexCart expected{ 0.12, -0.16 }; auto val = cn.reciprocal(); CHECK((val == expected)); } { ComplexCart cn{ 3, 4 }; ComplexCart expected{ 3, -4 }; CHECK((cn.conjugate() == expected)); } { ComplexCart cn1{ 3, 4 }; ComplexCart cn2{ 1, 2 }; ComplexCart expected{ -5, 10 }; CHECK((cn1 * cn2 == expected)); } { ComplexRad c1{ 5, 7 }; ComplexRad c2{ 10, 3 }; ComplexCart tmp1 = (c1 * c2).cart(); ComplexCart tmp2 = (c1.cart() * c2.cart()); CHECK((tmp1 - tmp2).mod() < 0.001); } } } int main() { return UnitTest::RunAllTests(); } #include "Color.hpp" #include "Noise.hpp" #include SUITE(TileableNoise) { TEST(NoiseTilesAcrossItsPeriod) { const TileableNoise noise(7); for (int index = 0; index < 20; index++) { const halnf x = 0.37f * (halnf) index; const halnf y = 1.13f + 0.21f * (halnf) index; CHECK_CLOSE(noise.gradient(x, y, 8, 4), noise.gradient(x + 8, y, 8, 4), 1e-5f); CHECK_CLOSE(noise.gradient(x, y, 8, 4), noise.gradient(x, y + 4, 8, 4), 1e-5f); } } TEST(NoiseIsZeroOnLatticeAndBounded) { const TileableNoise noise(3); CHECK_CLOSE(0.0f, noise.gradient(2, 5, 16, 16), 1e-6f); halnf low = 0; halnf high = 0; for (int y = 0; y < 64; y++) { for (int x = 0; x < 64; x++) { const halnf value = noise.gradient((halnf) x * 0.13f, (halnf) y * 0.17f, 16, 16); low = std::min(low, value); high = std::max(high, value); } } CHECK(low >= -1.0f && high <= 1.0f); CHECK(high - low > 0.8f); } TEST(SeedsChangeThePattern) { const TileableNoise a(1); const TileableNoise b(2); halnf difference = 0; for (int index = 0; index < 32; index++) difference += std::fabs(a.gradient(0.3f * index, 0.7f, 32, 32) - b.gradient(0.3f * index, 0.7f, 32, 32)); CHECK(difference > 1); } TEST(FractalStaysTileableAtEveryOctave) { const TileableNoise noise(5); const halnf weights[] = { 1, 0.5f, 0.25f, 0.125f }; CHECK_CLOSE(noise.fractal(0.4f, 0.6f, 4, weights, 4), noise.fractal(4.4f, 4.6f, 4, weights, 4), 1e-5f); } } SUITE(DisplayGamma) { TEST(GammaRoundTripsAndMatchesReferenceTable) { CHECK_CLOSE(169.0f / 16384.0f, gammaToLinear(0.125f), 2e-4f); CHECK_CLOSE(6367.0f / 16384.0f, linearToGamma(0.125f), 2e-4f); for (halnf value = 0; value <= 1.0f; value += 0.05f) CHECK_CLOSE(value, linearToGamma(gammaToLinear(value)), 1e-5f); CHECK_EQUAL(0.0f, gammaToLinear(-1)); CHECK_EQUAL(1.0f, linearToGamma(2)); } } SUITE(Vec2) { TEST(AngleIsMeasuredFromThePositiveXAxis) { CHECK_CLOSE(0.0f, Vec2F(2, 0).angle(), 1e-6f); CHECK_CLOSE(1.5707963f, Vec2F(0, 3).angle(), 1e-6f); CHECK_CLOSE(-1.5707963f, Vec2F(0, -1).angle(), 1e-6f); } TEST(RotatedTurnsCounterClockwiseAndKeepsLength) { const Vec2F turned = Vec2F(1, 0).rotated(1.5707963f); CHECK_CLOSE(0.0f, turned.x, 1e-6f); CHECK_CLOSE(1.0f, turned.y, 1e-6f); CHECK_CLOSE(Vec2F(3, 4).length(), Vec2F(3, 4).rotated(0.7f).length(), 1e-5f); } } SUITE(ResponseCurve) { TEST(DefaultCurveIsIdentity) { const ResponseCurve curve; for (halnf input : { 0.0f, 0.1f, 0.35f, 0.5f, 0.8f, 1.0f }) CHECK_CLOSE(input, curve.at(input), 1e-4f); } TEST(InputAndOutputAreClampedToUnitRange) { const ResponseCurve curve(Vec2F(0.0f, 0.0f), Vec2F(0.0f, 1.0f), Vec2F(0.2f, 1.0f), Vec2F(1.0f, 1.0f)); CHECK_CLOSE(0.0f, curve.at(-3), 1e-6f); CHECK_CLOSE(1.0f, curve.at(7), 1e-6f); for (int step = 0; step <= 20; step++) { const halnf output = curve.at((halnf) step / 20); CHECK(output >= 0.0f && output <= 1.0f); } } TEST(EndpointsStayOnTheirEdgesAndControlsStayInside) { ResponseCurve curve; curve.movePoint(ResponseCurve::kStart, Vec2F(0.6f, 0.3f)); curve.movePoint(ResponseCurve::kEnd, Vec2F(0.2f, 1.4f)); curve.movePoint(1, Vec2F(-1.0f, 0.5f)); CHECK((curve.point(ResponseCurve::kStart) == Vec2F(0.0f, 0.3f))); CHECK((curve.point(ResponseCurve::kEnd) == Vec2F(1.0f, 1.0f))); CHECK((curve.point(1) == Vec2F(0.0f, 0.5f))); CHECK_CLOSE(0.3f, curve.at(0), 1e-4f); CHECK_CLOSE(1.0f, curve.at(1), 1e-4f); } TEST(OutputGrowsWithInputForMonotonicControls) { const ResponseCurve curve(Vec2F(0.0f, 0.1f), Vec2F(0.7f, 0.1f), Vec2F(0.9f, 0.4f), Vec2F(1.0f, 1.0f)); halnf previous = curve.at(0); for (int step = 1; step <= 50; step++) { const halnf output = curve.at((halnf) step / 50); CHECK(output >= previous - 1e-5f); previous = output; } } TEST(FittedPowerFollowsMinimumAndExponent) { struct Case { halnf minimum; halnf exponent; }; for (const Case& fit : { Case{ 0.0f, 1.0f }, Case{ 0.2f, 2.0f }, Case{ 0.15f, 2.2f }, Case{ 0.3f, 0.8f } }) { const ResponseCurve curve = ResponseCurve::fittedPower(fit.minimum, fit.exponent); for (int step = 0; step <= 10; step++) { const halnf input = (halnf) step / 10; const halnf expected = fit.minimum + (1 - fit.minimum) * std::pow(input, fit.exponent); CHECK_CLOSE(expected, curve.at(input), 0.03f); } } } }