// Headless tests of Core/DescentMath.h. The known-answer numbers were computed by an independent port of the v2 // JavaScript (01_core.js, 03_world.js) in Python, so they check this port against the browser build's behaviour. #include "Tests/DescentTestCommon.h" #include "Core/DescentMath.h" #if WITH_DEV_AUTOMATION_TESTS IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentMathHelpersTest, "Descent.Math.Helpers", DESCENT_TEST_FLAGS) bool FDescentMathHelpersTest::RunTest(const FString& Parameters) { TestEqual(TEXT("clamp below"), Descent::Clamp(-3.0, 0.0, 1.0), 0.0); TestEqual(TEXT("clamp above"), Descent::Clamp(3.0, 0.0, 1.0), 1.0); TestEqual(TEXT("clamp inside"), Descent::Clamp(0.25, 0.0, 1.0), 0.25); TestEqual(TEXT("lerp"), Descent::Lerp(10.0, 20.0, 0.25), 12.5); // smoothstep: 0 and 1 at the edges, 0.5 in the middle, flat outside TestEqual(TEXT("smooth at e0"), Descent::Smooth(2.0, 6.0, 2.0), 0.0); TestEqual(TEXT("smooth at e1"), Descent::Smooth(2.0, 6.0, 6.0), 1.0); TestEqual(TEXT("smooth in the middle"), Descent::Smooth(2.0, 6.0, 4.0), 0.5, 1e-12); TestEqual(TEXT("smooth below"), Descent::Smooth(2.0, 6.0, -10.0), 0.0); TestEqual(TEXT("smooth above"), Descent::Smooth(2.0, 6.0, 99.0), 1.0); TestEqual(TEXT("smooth at a quarter: 3t^2-2t^3"), Descent::Smooth(0.0, 1.0, 0.25), 0.15625, 1e-12); // damp: after 1/k seconds the gap has closed by 1 - 1/e; two half steps equal one whole step (frame-rate independence) TestEqual(TEXT("damp after 1/k"), Descent::Damp(0.0, 1.0, 4.0, 0.25), 1.0 - FMath::Exp(-1.0), 1e-12); const double Two = Descent::Damp(Descent::Damp(0.0, 10.0, 3.0, 0.1), 10.0, 3.0, 0.1); TestEqual(TEXT("damp is frame-rate independent"), Two, Descent::Damp(0.0, 10.0, 3.0, 0.2), 1e-9); TestEqual(TEXT("damp with dt 0 stays"), Descent::Damp(7.0, 100.0, 5.0, 0.0), 7.0); // smin: far apart = plain min; equal = min - k/4; smooth between TestEqual(TEXT("smin far apart"), Descent::SMin(1.0, 9.0, 2.0), 1.0); TestEqual(TEXT("smin equal inputs"), Descent::SMin(3.0, 3.0, 2.0), 3.0 - 0.5, 1e-12); TestTrue(TEXT("smin is below min near the blend"), Descent::SMin(3.0, 3.5, 2.0) < 3.0); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentMathDatumTest, "Descent.Math.DatumAndUnits", DESCENT_TEST_FLAGS) bool FDescentMathDatumTest::RunTest(const FString& Parameters) { // Depth = DATUM - Z, DATUM = 1780 (section 0). The rim of the Throat is Z +40, depth 1,740. The start is Z -30, depth 1,810. TestEqual(TEXT("datum"), Descent::DatumM, 1780.0); TestEqual(TEXT("depth at the rim"), Descent::DepthOfZ(40.0), 1740.0); TestEqual(TEXT("depth at the carrier start"), Descent::DepthOfZ(-30.0), 1810.0); TestEqual(TEXT("depth at the goal anchor"), Descent::DepthOfZ(-1325.0), 3105.0); TestEqual(TEXT("depth at sea level of the datum"), Descent::DepthOfZ(1780.0), 0.0); TestEqual(TEXT("ZOfDepth inverts DepthOfZ"), Descent::ZOfDepth(Descent::DepthOfZ(-777.5)), -777.5); TestEqual(TEXT("depth from Unreal units"), Descent::DepthOfWorldUU(FVector(123.0, -456.0, -3000.0)), 1810.0); TestEqual(TEXT("m to cm"), Descent::MetresToCm(6.99), 699.0, 1e-9); TestEqual(TEXT("cm to m"), Descent::CmToMetres(327.0), 3.27, 1e-12); TestTrue(TEXT("vector conversions agree with DescentUnits.h"), Descent::MetresToCm(FVector(1, 2, 3)).Equals(Descent::MetresToUU(FVector(1, 2, 3)))); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentMathRngTest, "Descent.Math.Rng", DESCENT_TEST_FLAGS) bool FDescentMathRngTest::RunTest(const FString& Parameters) { // mulberry32(1337) as the browser build produced it. Descent::FRng R(1337u); TestEqual(TEXT("mulberry32(1337) #1"), R.Next(), 0.1844118325971067, 1e-15); TestEqual(TEXT("mulberry32(1337) #2"), R.Next(), 0.18998925131745636, 1e-15); TestEqual(TEXT("mulberry32(1337) #3"), R.Next(), 0.8104719922412187, 1e-15); TestEqual(TEXT("mulberry32(1337) #4"), R.Next(), 0.6437488221563399, 1e-15); // Same seed, same sequence; different seed, different sequence; always in [0, 1). Descent::FRng A(42u), B(42u), C(43u); bool bSame = true, bDiffers = false, bInRange = true; for (int32 I = 0; I < 1000; ++I) { const double X = A.Next(), Y = B.Next(), Z = C.Next(); bSame &= (X == Y); bDiffers |= (X != Z); bInRange &= (X >= 0.0 && X < 1.0); } TestTrue(TEXT("same seed repeats"), bSame); TestTrue(TEXT("different seed differs"), bDiffers); TestTrue(TEXT("always in [0, 1)"), bInRange); Descent::FRng D(7u); bool bRangeOk = true; for (int32 I = 0; I < 500; ++I) { const double V = D.Range(-3.0, 5.0); bRangeOk &= (V >= -3.0 && V < 5.0); const int32 N = D.Int(6); bRangeOk &= (N >= 0 && N < 6); } TestTrue(TEXT("Range and Int stay in range"), bRangeOk); TestEqual(TEXT("Int(0) is 0"), D.Int(0), 0); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentMathNoiseTest, "Descent.Math.Noise3", DESCENT_TEST_FLAGS) bool FDescentMathNoiseTest::RunTest(const FString& Parameters) { const Descent::FNoise3 N(20193u); TestEqual(TEXT("noise at the origin is 0"), N(0.0, 0.0, 0.0), 0.0, 1e-12); TestEqual(TEXT("noise #1"), N(1.5, -2.25, 3.75), 0.6787552083333327, 1e-12); TestEqual(TEXT("noise #2"), N(-10.3, 4.7, 0.2), -0.5025399466666662, 1e-12); TestEqual(TEXT("noise #3"), N(100.123, -50.5, 25.25), 0.6237423224022904, 1e-12); TestEqual(TEXT("noise #4"), N(0.37, 0.81, 0.22), 0.22494970200401815, 1e-12); TestEqual(TEXT("noise #5"), N(7.77, -3.14, 2.71), -0.272774277276282, 1e-12); TestEqual(TEXT("noise #6 (far negative)"), N(-123.456, 78.9, -0.01), 0.377007781620312, 1e-12); const Descent::FNoise3 Other(771u); TestEqual(TEXT("another seed"), Other(1.5, -2.25, 3.75), 0.3389093749999995, 1e-12); // Deterministic across instances, and bounded. const Descent::FNoise3 Twin(20193u); bool bSame = true, bBounded = true; Descent::FRng R(99u); for (int32 I = 0; I < 2000; ++I) { const double X = R.Range(-500.0, 500.0), Y = R.Range(-500.0, 500.0), Z = R.Range(-500.0, 500.0); const double V = N(X, Y, Z); bSame &= (V == Twin(X, Y, Z)); bBounded &= (V >= -1.2 && V <= 1.2); } TestTrue(TEXT("two instances of one seed agree"), bSame); TestTrue(TEXT("noise stays within about -1..1"), bBounded); return true; } #endif // WITH_DEV_AUTOMATION_TESTS