This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
@@ -0,0 +1,168 @@
#include "Misc/AutomationTest.h"
#include "World/WorldMapProjection.h"
#if WITH_DEV_AUTOMATION_TESTS
namespace
{
// L_World as RawContent/World/Region.json describes it: 14 x 7 tiles of 2550 quads at 200 cm, straddling
// the origin, the whole cylinder. The numbers are duplicated here on purpose - a test that read the
// manifest would pass when both it and the manifest were wrong together.
FWorldMapProjection MakeWorldProjection()
{
FWorldMapProjection Projection;
Projection.WidthM = 71400.0;
Projection.HeightM = 35700.0;
Projection.CentreM = FVector2D::ZeroVector;
Projection.bWrapsX = true;
Projection.ElevationMinM = -1024.0;
Projection.ElevationMaxM = 6144.0;
Projection.SeaLevelM = 0.0;
return Projection;
}
constexpr double Tolerance = 1e-9;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FWorldMapProjectionCornersTest, "Salty.Core.WorldMap.Projection.CornersAndCentre",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
bool FWorldMapProjectionCornersTest::RunTest(const FString& Parameters)
{
const FWorldMapProjection Projection = MakeWorldProjection();
// The world straddles the origin, so the centre of the art is the origin and the corners are half the
// extent away. In centimetres, because that is the unit every call site is in.
const FVector2D Centre = Projection.WorldToNormalised(FVector2D(0.0, 0.0));
TestNearlyEqual(TEXT("world origin is the middle of the map in U"), Centre.X, 0.5, Tolerance);
TestNearlyEqual(TEXT("world origin is the middle of the map in V"), Centre.Y, 0.5, Tolerance);
const FVector2D TopLeft = Projection.WorldToNormalised(FVector2D(-3570000.0, -1785000.0));
TestNearlyEqual(TEXT("west edge is U 0"), TopLeft.X, 0.0, Tolerance);
TestNearlyEqual(TEXT("north edge is V 0"), TopLeft.Y, 0.0, Tolerance);
const FVector2D BottomRight = Projection.WorldToNormalised(FVector2D(3570000.0, 1785000.0));
TestNearlyEqual(TEXT("east edge is U 1"), BottomRight.X, 1.0, Tolerance);
TestNearlyEqual(TEXT("south edge is V 1"), BottomRight.Y, 1.0, Tolerance);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FWorldMapProjectionRoundTripTest, "Salty.Core.WorldMap.Projection.RoundTrips",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
bool FWorldMapProjectionRoundTripTest::RunTest(const FString& Parameters)
{
const FWorldMapProjection Projection = MakeWorldProjection();
// Arbitrary places, including outside the map, which is legal: a marker off the edge is off the edge and
// the transform is not the thing that decides what to do about it.
const TArray<FVector2D> Places = {
FVector2D(0.0, 0.0),
FVector2D(1234567.0, -890123.0),
FVector2D(-3569999.0, 1784999.0),
FVector2D(9000000.0, 4000000.0),
};
for (const FVector2D& Place : Places)
{
const FVector2D Back = Projection.NormalisedToWorldCm(Projection.WorldToNormalised(Place));
TestNearlyEqual(*FString::Printf(TEXT("X round trips at (%.0f, %.0f)"), Place.X, Place.Y), Back.X, Place.X, 1e-6);
TestNearlyEqual(*FString::Printf(TEXT("Y round trips at (%.0f, %.0f)"), Place.X, Place.Y), Back.Y, Place.Y, 1e-6);
}
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FWorldMapProjectionWrapTest, "Salty.Core.WorldMap.Projection.WrapsWestwardPastTheSeam",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
bool FWorldMapProjectionWrapTest::RunTest(const FString& Parameters)
{
const FWorldMapProjection Projection = MakeWorldProjection();
// The case that motivated WrapU: panning west off the seam. FMath::Fmod would leave these negative and the
// map would sample nothing at all.
TestNearlyEqual(TEXT("just west of the seam comes back to just east of it"), Projection.WrapU(-0.02), 0.98, Tolerance);
TestNearlyEqual(TEXT("a whole turn west is the seam"), Projection.WrapU(-1.0), 0.0, Tolerance);
TestNearlyEqual(TEXT("two and a bit turns east"), Projection.WrapU(2.25), 0.25, Tolerance);
TestNearlyEqual(TEXT("inside the map is untouched"), Projection.WrapU(0.5), 0.5, Tolerance);
// Half open: 1.0 is the same meridian as 0.0 and must not be a second copy of it, or the seam column is
// drawn twice at some zoom levels and shimmers.
TestNearlyEqual(TEXT("U 1 is U 0"), Projection.WrapU(1.0), 0.0, Tolerance);
// And a map that is a window rather than a whole cylinder has edges, which stay edges.
FWorldMapProjection Window = MakeWorldProjection();
Window.bWrapsX = false;
TestNearlyEqual(TEXT("a window does not wrap"), Window.WrapU(-0.02), -0.02, Tolerance);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FWorldMapProjectionShortestDeltaTest, "Salty.Core.WorldMap.Projection.ShortestWayRound",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
bool FWorldMapProjectionShortestDeltaTest::RunTest(const FString& Parameters)
{
const FWorldMapProjection Projection = MakeWorldProjection();
// Either side of the seam is close, not almost a whole world apart. This is what keeps a marker at U 0.99
// drawn next to a view centred on U 0.01 rather than off the far edge.
TestNearlyEqual(TEXT("across the seam eastward"), Projection.ShortestDeltaU(0.99, 0.01), 0.02, Tolerance);
TestNearlyEqual(TEXT("across the seam westward"), Projection.ShortestDeltaU(0.01, 0.99), -0.02, Tolerance);
TestNearlyEqual(TEXT("ordinary distance is unchanged"), Projection.ShortestDeltaU(0.20, 0.35), 0.15, Tolerance);
FWorldMapProjection Window = MakeWorldProjection();
Window.bWrapsX = false;
TestNearlyEqual(TEXT("a window goes the long way because there is no short way"),
Window.ShortestDeltaU(0.99, 0.01), -0.98, Tolerance);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FWorldMapProjectionDistanceTest, "Salty.Core.WorldMap.Projection.DistanceInMetres",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
bool FWorldMapProjectionDistanceTest::RunTest(const FString& Parameters)
{
const FWorldMapProjection Projection = MakeWorldProjection();
// A kilometre east of the origin is a kilometre, in the unit a player would be told.
TestNearlyEqual(TEXT("a kilometre east"),
Projection.DistanceM(FVector2D(0.0, 0.0), FVector2D(100000.0, 0.0)), 1000.0, 1e-6);
// Two places a hundred metres either side of the seam are two hundred metres apart, not 71.2 km.
// The map is 71 400 m wide and straddles the origin, so its edges are at +-35 700 m: a hundred metres
// inside each of them is +-35 600 m, which is +-3 560 000 cm.
const double JustWest = -3560000.0;
const double JustEast = 3560000.0;
TestNearlyEqual(TEXT("either side of the seam is 200 m, not most of a world"),
Projection.DistanceM(FVector2D(JustWest, 0.0), FVector2D(JustEast, 0.0)), 200.0, 1e-6);
// And on a map that does not wrap those same two places really are most of a world apart.
FWorldMapProjection Window = MakeWorldProjection();
Window.bWrapsX = false;
TestNearlyEqual(TEXT("without a seam to cross it is the long way"),
Window.DistanceM(FVector2D(JustWest, 0.0), FVector2D(JustEast, 0.0)), 71200.0, 1e-6);
return true;
}
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FWorldMapProjectionInvalidTest, "Salty.Core.WorldMap.Projection.UnsetIsInvalidAndSafe",
EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
bool FWorldMapProjectionInvalidTest::RunTest(const FString& Parameters)
{
// A definition asset that was never filled in is the normal state of a new one, and asking it where a place
// is must not divide by zero - the widget checks IsValid and draws nothing, rather than NaNs.
const FWorldMapProjection Empty;
TestFalse(TEXT("a default projection is not valid"), Empty.IsValid());
TestEqual(TEXT("and maps everything to the origin rather than to NaN"),
Empty.WorldToNormalised(FVector2D(1234.0, 5678.0)), FVector2D::ZeroVector);
TestEqual(TEXT("both ways"), Empty.NormalisedToWorldCm(FVector2D(0.5, 0.5)), FVector2D::ZeroVector);
return true;
}
#endif // WITH_DEV_AUTOMATION_TESTS