// Headless tests of the look (DESIGN_UE.md sections 7.1, 7.5): Descent.Look.SurfaceLight, .LightPoolSelect, .LampSetup #include "Tests/DescentTestCommon.h" #include "Core/DescentMath.h" #include "Core/Tuning/DescentTune_Look.h" #include "Render/DescentEnvironment.h" #include "Render/DescentLamps.h" #include "Render/DescentLightPool.h" #include "Components/SpotLightComponent.h" #if WITH_DEV_AUTOMATION_TESTS IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentLookSurfaceLightTest, "Descent.Look.SurfaceLight", DESCENT_TEST_FLAGS) bool FDescentLookSurfaceLightTest::RunTest(const FString& Parameters) { // Section 2.7: 0.36 at the start (Z -30), 0.05 at Z -165, under 0.01 below Z -300, clamped to 1 above the rim. TestTrue(TEXT("L(-30) is about 0.36"), FMath::Abs(ADescentEnvironment::SurfaceLightAt(-30.0) - 0.36) < 0.01); TestTrue(TEXT("L(-165) is about 0.05"), FMath::Abs(ADescentEnvironment::SurfaceLightAt(-165.0) - 0.05) < 0.01); TestTrue(TEXT("L(-300) is under 0.01"), ADescentEnvironment::SurfaceLightAt(-300.0) < 0.01); TestEqual(TEXT("L(+100) is clamped to 1"), ADescentEnvironment::SurfaceLightAt(100.0), 1.0); TestEqual(TEXT("the surface sun is off below L 0.02: Z -225 and deeper"), ADescentEnvironment::SurfaceLightAt(-230.0) < Descent::Tune::Look::SunHiddenBelowL, true); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentLookLightPoolTest, "Descent.Look.LightPoolSelect", DESCENT_TEST_FLAGS) bool FDescentLookLightPoolTest::RunTest(const FString& Parameters) { // 40 requests along a line away from the camera, one far out of range: the pool keeps the 24 best that score under 9. TArray Reqs; for (int32 I = 0; I < 40; ++I) { FDescentLightRequest R; R.PositionUU = FVector(Descent::MetresToUU(2.0 * (I + 1)), 0, 0); // 2, 4, ... 80 m R.RadiusM = 10.f; R.Priority = 0.f; Reqs.Add(R); } TArray Win; UDescentLightPool::Select(Reqs, FVector::ZeroVector, 24, Descent::Tune::Look::LightPoolMaxScore, Win); // Score = (d/10)^2 < 9 => d < 30 m: requests at 2..28 m qualify (14 of them), fewer than the 24 slots. TestEqual(TEXT("only the requests whose score is under 9 are kept"), Win.Num(), 14); TestEqual(TEXT("the nearest wins first"), Win.Num() > 0 ? Win[0] : -1, 0); // Priority lifts a far request over a near one. Reqs[19].Priority = 8.f; // 40 m away: score 16 - 8 = 8 < 9 UDescentLightPool::Select(Reqs, FVector::ZeroVector, 24, Descent::Tune::Look::LightPoolMaxScore, Win); TestEqual(TEXT("a priority boost lets a far light in"), Win.Contains(19), true); // More good requests than slots: exactly 24 are kept. TArray Many; for (int32 I = 0; I < 60; ++I) { FDescentLightRequest R; R.PositionUU = FVector(Descent::MetresToUU(0.5 * (I + 1)), 0, 0); R.RadiusM = 20.f; Many.Add(R); } UDescentLightPool::Select(Many, FVector::ZeroVector, 24, Descent::Tune::Look::LightPoolMaxScore, Win); TestEqual(TEXT("the pool holds 24 lights"), Win.Num(), 24); TestEqual(TEXT("and they are the 24 nearest"), Win.Last(), 23); return true; } IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDescentLookLampSetupTest, "Descent.Look.LampSetup", DESCENT_TEST_FLAGS) bool FDescentLookLampSetupTest::RunTest(const FString& Parameters) { USpotLightComponent* L = NewObject(GetTransientPackage()); Descent::Look::ConfigureSpot(L, Descent::Look::ELamp::TenderHeadlamp); TestFalse(TEXT("no inverse-square falloff (section 7.5)"), static_cast(L->bUseInverseSquaredFalloff)); TestEqual(TEXT("falloff exponent 3"), L->LightFalloffExponent, 3.f); TestTrue(TEXT("Tender headlamp radius 90 m"), FMath::IsNearlyEqual(L->AttenuationRadius, 9000.f, 1.f)); TestTrue(TEXT("cone 14.5 / 26.4 degrees"), FMath::IsNearlyEqual(L->InnerConeAngle, 14.5f, 0.01f) && FMath::IsNearlyEqual(L->OuterConeAngle, 26.4f, 0.01f)); TestTrue(TEXT("start intensity 3.2"), FMath::IsNearlyEqual(L->Intensity, 3.2f, 0.001f)); Descent::Look::ConfigureSpot(L, Descent::Look::ELamp::Flashlight, 0.5); TestTrue(TEXT("flashlight radius 55 m, intensity 2.0 x scale 0.5"), FMath::IsNearlyEqual(L->AttenuationRadius, 5500.f, 1.f) && FMath::IsNearlyEqual(L->Intensity, 1.0f, 0.001f)); return true; } #endif // WITH_DEV_AUTOMATION_TESTS