Tooling
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user