Ajout du projet Depths sur Git
This commit is contained in:
+264
@@ -0,0 +1,264 @@
|
||||
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "ProceduralDungeonTypes.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FBoxMinAndMaxTest, "ProceduralDungeon.Types.BoxMinAndMax", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::SmokeFilter)
|
||||
|
||||
bool FBoxMinAndMaxTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Constructor Test
|
||||
{
|
||||
FBoxMinAndMax Box0;
|
||||
FBoxMinAndMax Box1(FIntVector(-1), FIntVector(1));
|
||||
FBoxMinAndMax Box2(FIntVector(3, 2, 1), FIntVector(-1, -2, -3));
|
||||
FBoxMinAndMax Box3(Box2);
|
||||
FBoxMinAndMax Box4(FIntVector(-3, 2, -1), FIntVector(1, -2, 3));
|
||||
FBoxMinAndMax Box5 = FBoxMinAndMax::Invalid;
|
||||
|
||||
TestEqual(TEXT("Default Constructor Min == 0"), Box0.GetMin(), FIntVector(0));
|
||||
TestEqual(TEXT("Default Constructor Max == 1"), Box0.GetMax(), FIntVector(1));
|
||||
TestEqual(TEXT("Constructor (-1,1) Min == -1"), Box1.GetMin(), FIntVector(-1));
|
||||
TestEqual(TEXT("Constructor (-1,1) Max == 1"), Box1.GetMax(), FIntVector(1));
|
||||
TestEqual(TEXT("Constructor ((3,2,1), (-1,-2,-3)) Min == (-1,-2,-3)"), Box2.GetMin(), FIntVector(-1, -2, -3));
|
||||
TestEqual(TEXT("Constructor ((3,2,1), (-1,-2,-3)) Max == (3,2,1)"), Box2.GetMax(), FIntVector(3, 2, 1));
|
||||
TestEqual(TEXT("Copy Constructor of ((3,2,1), (-1,-2,-3)) Min == (-1,-2,-3)"), Box3.GetMin(), FIntVector(-1, -2, -3));
|
||||
TestEqual(TEXT("Copy Constructor of ((3,2,1), (-1,-2,-3)) Max == (3,2,1)"), Box3.GetMax(), FIntVector(3, 2, 1));
|
||||
TestEqual(TEXT("Constructor ((-3,2,-1), (1,-2,3)) Min == (-3,-2,-1)"), Box4.GetMin(), FIntVector(-3, -2, -1));
|
||||
TestEqual(TEXT("Constructor ((-3,2,-1), (1,-2,3)) Max == (1,2,3)"), Box4.GetMax(), FIntVector(1, 2, 3));
|
||||
|
||||
TestEqual(TEXT("Invalid Box Max == 0"), Box5.GetMin(), FIntVector(0));
|
||||
TestEqual(TEXT("Invalid Box Max == 0"), Box5.GetMax(), FIntVector(0));
|
||||
TestFalse(TEXT("Invalid Box is not Valid"), Box5.IsValid());
|
||||
}
|
||||
|
||||
// Size Test
|
||||
{
|
||||
FBoxMinAndMax Box0(FIntVector(0), FIntVector(0));
|
||||
FBoxMinAndMax Box1(FIntVector(0), FIntVector(1));
|
||||
FBoxMinAndMax Box2(FIntVector(0), FIntVector(-1));
|
||||
FBoxMinAndMax Box3(FIntVector(-1), FIntVector(0));
|
||||
FBoxMinAndMax Box4(FIntVector(1), FIntVector(0));
|
||||
FBoxMinAndMax Box5(FIntVector(0, 0, 0), FIntVector(3, 4, 5));
|
||||
FBoxMinAndMax Box6(FIntVector(3, 4, 5), FIntVector(-5, -4, -3));
|
||||
|
||||
TestEqual(TEXT("Box(0, 0).GetSize() == (0,0,0)"), Box0.GetSize(), FIntVector(0));
|
||||
TestEqual(TEXT("Box(0, 1).GetSize() == (1,1,1)"), Box1.GetSize(), FIntVector(1));
|
||||
TestEqual(TEXT("Box(0, -1).GetSize() == (1,1,1)"), Box2.GetSize(), FIntVector(1));
|
||||
TestEqual(TEXT("Box(-1, 0).GetSize() == (1,1,1)"), Box3.GetSize(), FIntVector(1));
|
||||
TestEqual(TEXT("Box(1, 0).GetSize() == (1,1,1)"), Box4.GetSize(), FIntVector(1));
|
||||
TestEqual(TEXT("Box(0, (3, 4, 5)).GetSize() == (3,4,5)"), Box5.GetSize(), FIntVector(3, 4, 5));
|
||||
TestEqual(TEXT("Box((3, 4, 5), (-5, -4, -3)).GetSize() == (8,8,8)"), Box6.GetSize(), FIntVector(8));
|
||||
}
|
||||
|
||||
// Overlap Test
|
||||
{
|
||||
FBoxMinAndMax Box1(FIntVector(-2, -3, -4), FIntVector(2, 3, 4));
|
||||
FBoxMinAndMax Box2(FIntVector(3, 0, 0), FIntVector(5, 5, 5));
|
||||
FBoxMinAndMax Box3(FIntVector(2, 3, 0), FIntVector(5, 5, 5));
|
||||
FBoxMinAndMax Box4(FIntVector(2, 2, 0), FIntVector(5, 5, 5));
|
||||
FBoxMinAndMax Box5(FIntVector(1, 2, 0), FIntVector(5, 5, 5));
|
||||
FBoxMinAndMax Box6(FIntVector(-3, 0, 0), FIntVector(-5, -5, -5));
|
||||
FBoxMinAndMax Box7(FIntVector(-2, -2, 0), FIntVector(-5, -5, -5));
|
||||
FBoxMinAndMax Box8(FIntVector(-1, -2, 0), FIntVector(-5, -5, -5));
|
||||
|
||||
TestTrue(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((-2,-3,-4), (2,3,4)) overlap"), FBoxMinAndMax::Overlap(Box1, Box1));
|
||||
TestFalse(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((3,0,0), (5,5,5)) don't overlap"), FBoxMinAndMax::Overlap(Box1, Box2));
|
||||
TestFalse(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((2,3,0), (5,5,5)) don't overlap"), FBoxMinAndMax::Overlap(Box1, Box3));
|
||||
TestFalse(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((2,2,0), (5,5,5)) don't overlap"), FBoxMinAndMax::Overlap(Box1, Box4));
|
||||
TestTrue(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((1,2,0), (5,5,5)) overlap"), FBoxMinAndMax::Overlap(Box1, Box5));
|
||||
TestFalse(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((-3,0,0), (-5,-5,-5)) don't overlap"), FBoxMinAndMax::Overlap(Box1, Box6));
|
||||
TestFalse(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((-2,-2,0), (-5,-5,-5)) don't overlap"), FBoxMinAndMax::Overlap(Box1, Box7));
|
||||
TestTrue(TEXT("Box((-2,-3,-4), (2,3,4)) and Box((-1,-2,0), (-5,-5,-5)) overlap"), FBoxMinAndMax::Overlap(Box1, Box8));
|
||||
|
||||
FBoxMinAndMax BoxA(FIntVector(0), FIntVector(1));
|
||||
FBoxMinAndMax BoxB(FIntVector(-2), FIntVector(5));
|
||||
TestTrue(TEXT("Box(0, 1) and Box(-2, 5) overlap"), FBoxMinAndMax::Overlap(BoxA, BoxB));
|
||||
}
|
||||
|
||||
// Rotation Test
|
||||
{
|
||||
FBoxMinAndMax Box0(FIntVector(0), FIntVector(1));
|
||||
FBoxMinAndMax RotBox0N = Rotate(Box0, EDoorDirection::North);
|
||||
FBoxMinAndMax RotBox0E = Rotate(Box0, EDoorDirection::East);
|
||||
FBoxMinAndMax RotBox0S = Rotate(Box0, EDoorDirection::South);
|
||||
FBoxMinAndMax RotBox0W = Rotate(Box0, EDoorDirection::West);
|
||||
|
||||
TestEqual(TEXT("Rotate(Box(0,1), N).Min == 0"), RotBox0N.GetMin(), FIntVector(0));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), N).Max == 1"), RotBox0N.GetMax(), FIntVector(1));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), E).Min == 0"), RotBox0E.GetMin(), FIntVector(0));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), E).Max == 1"), RotBox0E.GetMax(), FIntVector(1));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), S).Min == 0"), RotBox0S.GetMin(), FIntVector(0));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), S).Max == 1"), RotBox0S.GetMax(), FIntVector(1));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), W).Min == 0"), RotBox0W.GetMin(), FIntVector(0));
|
||||
TestEqual(TEXT("Rotate(Box(0,1), W).Max == 1"), RotBox0W.GetMax(), FIntVector(1));
|
||||
|
||||
FBoxMinAndMax Box1(FIntVector(-1, 0, -1), FIntVector(3, 1, 1));
|
||||
FBoxMinAndMax RotBox1N = Rotate(Box1, EDoorDirection::North);
|
||||
FBoxMinAndMax RotBox1E = Rotate(Box1, EDoorDirection::East);
|
||||
FBoxMinAndMax RotBox1S = Rotate(Box1, EDoorDirection::South);
|
||||
FBoxMinAndMax RotBox1W = Rotate(Box1, EDoorDirection::West);
|
||||
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), N).Min == (-1,0,-1)"), RotBox1N.GetMin(), FIntVector(-1, 0, -1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), N).Max == (3,1,1)"), RotBox1N.GetMax(), FIntVector(3, 1, 1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), E).Min == (0,-1,-1)"), RotBox1E.GetMin(), FIntVector(0, -1, -1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), E).Max == (1,3,1)"), RotBox1E.GetMax(), FIntVector(1, 3, 1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), S).Min == (-2,0,-1)"), RotBox1S.GetMin(), FIntVector(-2, 0, -1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), S).Max == (2,1,1)"), RotBox1S.GetMax(), FIntVector(2, 1, 1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), W).Min == (0,-2,-1)"), RotBox1W.GetMin(), FIntVector(0, -2, -1));
|
||||
TestEqual(TEXT("Rotate(Box((-1,0,-1), (3,1,1)), W).Max == (1,2,1)"), RotBox1W.GetMax(), FIntVector(1, 2, 1));
|
||||
}
|
||||
|
||||
// Extend Test
|
||||
{
|
||||
FBoxMinAndMax BoxToExtend;
|
||||
FBoxMinAndMax Box0({0, 0, 0}, {1, 1, 1});
|
||||
FBoxMinAndMax Box1 = Box0 + FIntVector(1, 2, 3); // offseted box
|
||||
FBoxMinAndMax Box2({-1, -2, -3}, {4, 5, 6});
|
||||
|
||||
BoxToExtend.Extend(Box0);
|
||||
TestEqual(TEXT("Extend Box Step 1"), BoxToExtend, Box0);
|
||||
|
||||
// Extend the box to contain the provided box
|
||||
BoxToExtend.Extend(Box1);
|
||||
TestEqual(TEXT("Extend Box Step 2"), BoxToExtend, FBoxMinAndMax({0, 0, 0}, {2, 3, 4}));
|
||||
|
||||
BoxToExtend.Extend(Box2);
|
||||
TestEqual(TEXT("Extend Box Step 3"), BoxToExtend, Box2);
|
||||
|
||||
// The extended box should not change when using a box entirely contained in it.
|
||||
BoxToExtend.Extend(Box0);
|
||||
TestEqual(TEXT("Extend Box Step 4"), BoxToExtend, Box2);
|
||||
}
|
||||
|
||||
// IsInside(FBoxMinAndMax) Test
|
||||
{
|
||||
FBoxMinAndMax Bounds(FIntVector(-4, -5, -6), FIntVector(7, 8, 9));
|
||||
FBoxMinAndMax Box(FIntVector(-1, -1, -1), FIntVector(1, 2, 3));
|
||||
|
||||
// Completely inside, no coincident face
|
||||
TestTrue(*FString::Printf(TEXT("Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
|
||||
// Positive X
|
||||
Box += FIntVector(6, 0, 0); // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[X+,A] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(1, 0, 0); // Intersecting
|
||||
TestFalse(*FString::Printf(TEXT("[X+,B] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(1, 0, 0); // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[X+,C] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
|
||||
// Reset
|
||||
Box = FBoxMinAndMax(FIntVector(-1, -1, -1), FIntVector(1, 2, 3));
|
||||
|
||||
// Positive Y
|
||||
Box += FIntVector(0, 6, 0); // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Y+,A] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, 1, 0); // Intersecting
|
||||
TestFalse(*FString::Printf(TEXT("[Y+,B] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, 1, 0); // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Y+,C] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
|
||||
// Reset
|
||||
Box = FBoxMinAndMax(FIntVector(-1, -1, -1), FIntVector(1, 2, 3));
|
||||
|
||||
// Positive Z
|
||||
Box += FIntVector(0, 0, 6); // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Z+,A] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, 0, 1); // Intersecting
|
||||
TestFalse(*FString::Printf(TEXT("[Z+,B] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, 0, 1); // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Z+,C] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
|
||||
// Reset
|
||||
Box = FBoxMinAndMax(FIntVector(-1, -1, -1), FIntVector(1, 2, 3));
|
||||
|
||||
// Negative X
|
||||
Box += FIntVector(-3, 0, 0); // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[X-,A] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(-1, 0, 0); // Intersecting
|
||||
TestFalse(*FString::Printf(TEXT("[X-,B] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(-1, 0, 0); // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[X-,C] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
|
||||
// Reset
|
||||
Box = FBoxMinAndMax(FIntVector(-1, -1, -1), FIntVector(1, 2, 3));
|
||||
|
||||
// Negative Y
|
||||
Box += FIntVector(0, -4, 0); // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Y-,A] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, -2, 0); // Intersecting
|
||||
TestFalse(*FString::Printf(TEXT("[Y-,B] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, -1, 0); // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Y-,C] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
|
||||
// Reset
|
||||
Box = FBoxMinAndMax(FIntVector(-1, -1, -1), FIntVector(1, 2, 3));
|
||||
|
||||
// Negative Z
|
||||
Box += FIntVector(0, 0, -5); // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Z-,A] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, 0, -3); // Intersecting
|
||||
TestFalse(*FString::Printf(TEXT("[Z-,B] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
Box += FIntVector(0, 0, -1); // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Z-,C] Box%s IsInside Bounds%s"), *Box.ToString(), *Bounds.ToString()), Bounds.IsInside(Box));
|
||||
}
|
||||
|
||||
// IsInside(FIntVector) Test
|
||||
{
|
||||
FBoxMinAndMax Bounds(FIntVector(-4, -5, -6), FIntVector(7, 8, 9));
|
||||
FIntVector Cell {0};
|
||||
|
||||
// Completely inside, no coincident face
|
||||
TestTrue(*FString::Printf(TEXT("Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
|
||||
// Positive X
|
||||
Cell = {6, 0, 0}; // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[X+,A] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
Cell = {7, 0, 0}; // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[X+,B] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
|
||||
// Negative X
|
||||
Cell = {-4, 0, 0}; // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[X-,A] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
Cell = {-5, 0, 0}; // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[X-,B] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
|
||||
// Positive Y
|
||||
Cell = {0, 7, 0}; // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Y+,A] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
Cell = {0, 8, 0}; // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Y+,B] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
|
||||
// Negative Y
|
||||
Cell = {0, -5, 0}; // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Y-,A] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
Cell = {0, -6, 0}; // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Y-,B] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
|
||||
// Positive Z
|
||||
Cell = {0, 0, 8}; // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Z+,A] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
Cell = {0, 0, 9}; // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Z+,B] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
|
||||
// Negative Z
|
||||
Cell = {0, 0, -6}; // Inside but with coincident face
|
||||
TestTrue(*FString::Printf(TEXT("[Z-,A] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
Cell = {0, 0, -7}; // Outside but with a coincident face
|
||||
TestFalse(*FString::Printf(TEXT("[Z-,B] Cell(%s) IsInside Bounds%s"), *Cell.ToString(), *Bounds.ToString()), Bounds.IsInside(Cell));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
// Copyright Benoit Pelletier 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "VoxelBounds/VoxelBounds.h"
|
||||
#include "CustomScoreCallbacks.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class UCustomScoreCallback : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UFUNCTION()
|
||||
bool ZeroScore(const FVoxelBoundsConnection& A, const FVoxelBoundsConnection& B, int32& Score)
|
||||
{
|
||||
Score = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
UFUNCTION()
|
||||
bool NeverPass(const FVoxelBoundsConnection& A, const FVoxelBoundsConnection& B, int32& Score)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
// Copyright Benoit Pelletier 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Interfaces/DungeonCustomSerialization.h"
|
||||
#include "Interfaces/DungeonSaveInterface.h"
|
||||
#include "Utils/CompatUtils.h"
|
||||
#include "DungeonSaveClasses.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class USaveTestObject : public UObject, public IDungeonCustomSerialization, public IDungeonSaveInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
//~ Begin IDungeonCustomSerialization Interface
|
||||
virtual bool SerializeObject(FStructuredArchive::FRecord& Record, bool bIsLoading) override
|
||||
{
|
||||
OrderOfExecution += (bIsLoading) ? TEXT("X") : TEXT("C");
|
||||
Record.EnterField(AR_FIELD_NAME("NativeTest")) << TestSerializeObjectFunction;
|
||||
return true;
|
||||
}
|
||||
//~ End IDungeonCustomSerialization Interface
|
||||
|
||||
//~ Begin IDungeonSaveInterface Interface
|
||||
virtual void PreSaveDungeon_Implementation() override
|
||||
{
|
||||
OrderOfExecution += TEXT("A");
|
||||
}
|
||||
|
||||
virtual void DungeonPreSerialize_Implementation(bool bIsLoading) override
|
||||
{
|
||||
OrderOfExecution += (bIsLoading) ? TEXT("W") : TEXT("B");
|
||||
}
|
||||
|
||||
virtual void DungeonPostSerialize_Implementation(bool bIsLoading) override
|
||||
{
|
||||
OrderOfExecution += (bIsLoading) ? TEXT("Y") : TEXT("D");
|
||||
}
|
||||
|
||||
virtual void PostLoadDungeon_Implementation() override
|
||||
{
|
||||
OrderOfExecution += TEXT("Z");
|
||||
}
|
||||
//~ End IDungeonSaveInterface Interface
|
||||
|
||||
public:
|
||||
UPROPERTY(SaveGame)
|
||||
int32 TestSaveGameFlag {0};
|
||||
|
||||
int32 TestSerializeObjectFunction {0};
|
||||
|
||||
UPROPERTY();
|
||||
FString OrderOfExecution {};
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright Benoit Pelletier 2025 - 2026 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RoomConstraints/RoomConstraint.h"
|
||||
#include "RoomConstraintChildClasses.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class UConstraintPass : public URoomConstraint
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual bool Check_Implementation(const UDungeonGraph* Dungeon, const URoomData* RoomData, FIntVector Location, EDoorDirection Direction) const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class UConstraintFail : public URoomConstraint
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual bool Check_Implementation(const UDungeonGraph* Dungeon, const URoomData* RoomData, FIntVector Location, EDoorDirection Direction) const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// Copyright Benoit Pelletier 2024 - 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RoomCustomData.h"
|
||||
#include "RoomCustomDataChildClasses.generated.h"
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class UCustomDataA : public URoomCustomData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class UCustomDataB : public URoomCustomData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
UCLASS(NotBlueprintable, NotBlueprintType, HideDropdown, meta = (HiddenNode))
|
||||
class UCustomDataC : public URoomCustomData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright Benoit Pelletier 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "ProceduralDungeonTypes.h"
|
||||
#include "DoorType.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDoorDefTest, "ProceduralDungeon.Types.DoorDef", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::SmokeFilter)
|
||||
|
||||
bool FDoorDefTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
CREATE_DATA_ASSET(UDoorType, DoorTypeA);
|
||||
CREATE_DATA_ASSET(UDoorType, DoorTypeB);
|
||||
|
||||
// Constructor Test
|
||||
{
|
||||
FDoorDef Door0;
|
||||
FDoorDef Door1(FDoorDef::Invalid);
|
||||
FDoorDef Door2({1, 2, 3}, EDoorDirection::South, DoorTypeA.Get());
|
||||
FDoorDef Door3(Door2);
|
||||
|
||||
TestTrue(TEXT("Default Constructor makes valid door"), Door0.IsValid());
|
||||
TestFalse(TEXT("Copy constructor of invalid door mus makes an invalid door"), Door1.IsValid());
|
||||
|
||||
TestTrue(TEXT("Constructor (1,2,3) South DoorTypeA is valid"), Door2.IsValid());
|
||||
TestEqual(TEXT("Constructor (1,2,3) South DoorTypeA :: Position == (1,2,3)"), Door2.Position, {1, 2, 3});
|
||||
TestEqual(TEXT("Constructor (1,2,3) South DoorTypeA :: Direction == South"), Door2.Direction, EDoorDirection::South);
|
||||
TestEqual(TEXT("Constructor (1,2,3) South DoorTypeA :: Type == DoorTypeA"), Door2.Type, DoorTypeA.Get());
|
||||
|
||||
TestTrue(TEXT("Copy Constructor of valid door must be valid too"), Door3.IsValid());
|
||||
TestEqual(TEXT("Copy Constructor must carry over position"), Door3.Position, Door2.Position);
|
||||
TestEqual(TEXT("Copy Constructor must carry over direction"), Door3.Direction, Door2.Direction);
|
||||
TestEqual(TEXT("Copy Constructor must carry over type"), Door3.Type, Door2.Type);
|
||||
}
|
||||
|
||||
// Compatibility Test
|
||||
{
|
||||
FDoorDef Door0({0, 0, 0}, EDoorDirection::North, DoorTypeA.Get());
|
||||
FDoorDef Door1({1, 2, 3}, EDoorDirection::South, DoorTypeA.Get());
|
||||
FDoorDef Door2({1, 2, 3}, EDoorDirection::South, DoorTypeB.Get());
|
||||
FDoorDef Door3;
|
||||
|
||||
TestTrue(TEXT("Door0 is compatible with Door1"), FDoorDef::AreCompatible(Door0, Door1));
|
||||
TestFalse(TEXT("Door0 is not compatible with Door2"), FDoorDef::AreCompatible(Door0, Door2));
|
||||
TestFalse(TEXT("Door0 is not compatible with Door3"), FDoorDef::AreCompatible(Door0, Door3));
|
||||
TestFalse(TEXT("Door1 is not compatible with Door2"), FDoorDef::AreCompatible(Door1, Door2));
|
||||
TestFalse(TEXT("Door1 is not compatible with Door3"), FDoorDef::AreCompatible(Door1, Door3));
|
||||
TestFalse(TEXT("Door2 is not compatible with Door3"), FDoorDef::AreCompatible(Door2, Door3));
|
||||
}
|
||||
|
||||
// Opposite Test
|
||||
{
|
||||
FDoorDef Door0({1, 2, 3}, EDoorDirection::North, DoorTypeA.Get());
|
||||
FDoorDef Door1 = Door0.GetOpposite();
|
||||
|
||||
TestTrue(TEXT("Opposite door is valid"), Door1.IsValid());
|
||||
TestEqual(TEXT("Opposite of North is South"), Door1.Direction, EDoorDirection::South);
|
||||
TestEqual(TEXT("Opposite cell of (1,2,3)[North] is (2,2,3)"), Door1.Position, {2, 2, 3});
|
||||
TestEqual(TEXT("Opposite type is the same"), Door1.Type, DoorTypeA.Get());
|
||||
TestTrue(TEXT("Opposite door is compatible with original"), FDoorDef::AreCompatible(Door0, Door1));
|
||||
}
|
||||
|
||||
// Transform Test
|
||||
{
|
||||
FDoorDef Door0({1, 2, 3}, EDoorDirection::North, DoorTypeA.Get());
|
||||
FDoorDef TransformedDoor0({-1, 3, 6}, EDoorDirection::East, DoorTypeA.Get());
|
||||
FDoorDef TransformedDoor1({0, 0, 6}, EDoorDirection::South, DoorTypeA.Get());
|
||||
FDoorDef TransformedDoor2({3, 1, 6}, EDoorDirection::West, DoorTypeA.Get());
|
||||
|
||||
TestEqual(TEXT("Transformation {(1, 2, 3), East} is correct"), FDoorDef::Transform(Door0, {1, 2, 3}, EDoorDirection::East), TransformedDoor0);
|
||||
TestEqual(TEXT("Transformation {(1, 2, 3), South} is correct"), FDoorDef::Transform(Door0, {1, 2, 3}, EDoorDirection::South), TransformedDoor1);
|
||||
TestEqual(TEXT("Transformation {(1, 2, 3), West} is correct"), FDoorDef::Transform(Door0, {1, 2, 3}, EDoorDirection::West), TransformedDoor2);
|
||||
|
||||
TestEqual(TEXT("Inverse Transformation {(1, 2, 3), East} is correct"), FDoorDef::InverseTransform(TransformedDoor0, {1, 2, 3}, EDoorDirection::East), Door0);
|
||||
TestEqual(TEXT("Inverse Transformation {(1, 2, 3), South} is correct"), FDoorDef::InverseTransform(TransformedDoor1, {1, 2, 3}, EDoorDirection::South), Door0);
|
||||
TestEqual(TEXT("Inverse Transformation {(1, 2, 3), West} is correct"), FDoorDef::InverseTransform(TransformedDoor2, {1, 2, 3}, EDoorDirection::West), Door0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "ProceduralDungeonTypes.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDoorDirectionTest, "ProceduralDungeon.Types.DoorDirection", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::SmokeFilter)
|
||||
|
||||
bool FDoorDirectionTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Adding directions is correct
|
||||
{
|
||||
TestEqual(TEXT("North + North = North"), EDoorDirection::North + EDoorDirection::North, EDoorDirection::North);
|
||||
TestEqual(TEXT("North + East = East"), EDoorDirection::North + EDoorDirection::East, EDoorDirection::East);
|
||||
TestEqual(TEXT("North + South = South"), EDoorDirection::North + EDoorDirection::South, EDoorDirection::South);
|
||||
TestEqual(TEXT("North + West = West"), EDoorDirection::North + EDoorDirection::West, EDoorDirection::West);
|
||||
|
||||
TestEqual(TEXT("East + North = East"), EDoorDirection::East + EDoorDirection::North, EDoorDirection::East);
|
||||
TestEqual(TEXT("East + East = South"), EDoorDirection::East + EDoorDirection::East, EDoorDirection::South);
|
||||
TestEqual(TEXT("East + South = West"), EDoorDirection::East + EDoorDirection::South, EDoorDirection::West);
|
||||
TestEqual(TEXT("East + West = North"), EDoorDirection::East + EDoorDirection::West, EDoorDirection::North);
|
||||
|
||||
TestEqual(TEXT("South + North = South"), EDoorDirection::South + EDoorDirection::North, EDoorDirection::South);
|
||||
TestEqual(TEXT("South + East = West"), EDoorDirection::South + EDoorDirection::East, EDoorDirection::West);
|
||||
TestEqual(TEXT("South + South = North"), EDoorDirection::South + EDoorDirection::South, EDoorDirection::North);
|
||||
TestEqual(TEXT("South + West = East"), EDoorDirection::South + EDoorDirection::West, EDoorDirection::East);
|
||||
|
||||
TestEqual(TEXT("West + North = West"), EDoorDirection::West + EDoorDirection::North, EDoorDirection::West);
|
||||
TestEqual(TEXT("West + East = North"), EDoorDirection::West + EDoorDirection::East, EDoorDirection::North);
|
||||
TestEqual(TEXT("West + South = East"), EDoorDirection::West + EDoorDirection::South, EDoorDirection::East);
|
||||
TestEqual(TEXT("West + West = South"), EDoorDirection::West + EDoorDirection::West, EDoorDirection::South);
|
||||
}
|
||||
|
||||
// Subtracting directions is correct
|
||||
{
|
||||
TestEqual(TEXT("North - North = North"), EDoorDirection::North - EDoorDirection::North, EDoorDirection::North);
|
||||
TestEqual(TEXT("North - East = West"), EDoorDirection::North - EDoorDirection::East, EDoorDirection::West);
|
||||
TestEqual(TEXT("North - South = South"), EDoorDirection::North - EDoorDirection::South, EDoorDirection::South);
|
||||
TestEqual(TEXT("North - West = East"), EDoorDirection::North - EDoorDirection::West, EDoorDirection::East);
|
||||
|
||||
TestEqual(TEXT("East - North = East"), EDoorDirection::East - EDoorDirection::North, EDoorDirection::East);
|
||||
TestEqual(TEXT("East - East = North"), EDoorDirection::East - EDoorDirection::East, EDoorDirection::North);
|
||||
TestEqual(TEXT("East - South = West"), EDoorDirection::East - EDoorDirection::South, EDoorDirection::West);
|
||||
TestEqual(TEXT("East - West = South"), EDoorDirection::East - EDoorDirection::West, EDoorDirection::South);
|
||||
|
||||
TestEqual(TEXT("South - North = South"), EDoorDirection::South - EDoorDirection::North, EDoorDirection::South);
|
||||
TestEqual(TEXT("South - East = East"), EDoorDirection::South - EDoorDirection::East, EDoorDirection::East);
|
||||
TestEqual(TEXT("South - South = North"), EDoorDirection::South - EDoorDirection::South, EDoorDirection::North);
|
||||
TestEqual(TEXT("South - West = West"), EDoorDirection::South - EDoorDirection::West, EDoorDirection::West);
|
||||
|
||||
TestEqual(TEXT("West - North = West"), EDoorDirection::West - EDoorDirection::North, EDoorDirection::West);
|
||||
TestEqual(TEXT("West - East = South"), EDoorDirection::West - EDoorDirection::East, EDoorDirection::South);
|
||||
TestEqual(TEXT("West - South = East"), EDoorDirection::West - EDoorDirection::South, EDoorDirection::East);
|
||||
TestEqual(TEXT("West - West = North"), EDoorDirection::West - EDoorDirection::West, EDoorDirection::North);
|
||||
}
|
||||
|
||||
// Negating directions are correct
|
||||
{
|
||||
TestEqual(TEXT("-North = North"), -EDoorDirection::North, EDoorDirection::North);
|
||||
TestEqual(TEXT("-East = West"), -EDoorDirection::East, EDoorDirection::West);
|
||||
TestEqual(TEXT("-South = South"), -EDoorDirection::South, EDoorDirection::South);
|
||||
TestEqual(TEXT("-West = East"), -EDoorDirection::West, EDoorDirection::East);
|
||||
}
|
||||
|
||||
// Opposite directions are correct
|
||||
{
|
||||
TestEqual(TEXT("~North = South"), ~EDoorDirection::North, EDoorDirection::South);
|
||||
TestEqual(TEXT("~East = West"), ~EDoorDirection::East, EDoorDirection::West);
|
||||
TestEqual(TEXT("~South = North"), ~EDoorDirection::South, EDoorDirection::North);
|
||||
TestEqual(TEXT("~West = East"), ~EDoorDirection::West, EDoorDirection::East);
|
||||
}
|
||||
|
||||
// Incrementing/decrementing directions are correct
|
||||
{
|
||||
EDoorDirection direction {EDoorDirection::North};
|
||||
TestEqual(TEXT("++North = East"), ++direction, EDoorDirection::East);
|
||||
TestEqual(TEXT("++East = South"), ++direction, EDoorDirection::South);
|
||||
TestEqual(TEXT("++South = West"), ++direction, EDoorDirection::West);
|
||||
TestEqual(TEXT("++West = North"), ++direction, EDoorDirection::North);
|
||||
|
||||
direction = EDoorDirection::North;
|
||||
TestEqual(TEXT("--North = West"), --direction, EDoorDirection::West);
|
||||
TestEqual(TEXT("--West = South"), --direction, EDoorDirection::South);
|
||||
TestEqual(TEXT("--South = East"), --direction, EDoorDirection::East);
|
||||
TestEqual(TEXT("--East = North"), --direction, EDoorDirection::North);
|
||||
}
|
||||
|
||||
// Boolean testing directions
|
||||
{
|
||||
TestFalse(TEXT("!North = false"), !EDoorDirection::North);
|
||||
TestFalse(TEXT("!East = false"), !EDoorDirection::East);
|
||||
TestFalse(TEXT("!South = false"), !EDoorDirection::South);
|
||||
TestFalse(TEXT("!West = false"), !EDoorDirection::West);
|
||||
TestTrue(TEXT("!NbDirection = true"), !EDoorDirection::NbDirection);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright Benoit Pelletier 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "DoorType.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDoorTypeTests, "ProceduralDungeon.Types.DoorType", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
bool FDoorTypeTests::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Test DoorType Compatibility
|
||||
{
|
||||
|
||||
CREATE_DATA_ASSET(UDoorType, TypeA);
|
||||
CREATE_DATA_ASSET(UDoorType, TypeB);
|
||||
CREATE_DATA_ASSET(UDoorType, TypeC);
|
||||
/* Default Compatibility Table:
|
||||
* A B C N
|
||||
* A O X X X
|
||||
* B X O X X
|
||||
* C X X O X
|
||||
* N X X X O
|
||||
*/
|
||||
TestTrue("TypeA should be compatible with itself", UDoorType::AreCompatible(TypeA.Get(), TypeA.Get()));
|
||||
TestFalse("TypeA should not be compatible with TypeB", UDoorType::AreCompatible(TypeA.Get(), TypeB.Get()));
|
||||
TestFalse("TypeA should not be compatible with TypeC", UDoorType::AreCompatible(TypeA.Get(), TypeC.Get()));
|
||||
TestFalse("TypeA should not be compatible with Null", UDoorType::AreCompatible(TypeA.Get(), nullptr));
|
||||
|
||||
TestFalse("TypeB should not be compatible with TypeA", UDoorType::AreCompatible(TypeB.Get(), TypeA.Get()));
|
||||
TestTrue("TypeB should be compatible with itself", UDoorType::AreCompatible(TypeB.Get(), TypeB.Get()));
|
||||
TestFalse("TypeB should not be compatible with TypeC", UDoorType::AreCompatible(TypeB.Get(), TypeC.Get()));
|
||||
TestFalse("TypeB should not be compatible with Null", UDoorType::AreCompatible(TypeB.Get(), nullptr));
|
||||
|
||||
TestFalse("TypeC should not be compatible with TypeA", UDoorType::AreCompatible(TypeC.Get(), TypeA.Get()));
|
||||
TestFalse("TypeC should not be compatible with TypeB", UDoorType::AreCompatible(TypeC.Get(), TypeB.Get()));
|
||||
TestTrue("TypeC should not be compatible with TypeC", UDoorType::AreCompatible(TypeC.Get(), TypeC.Get()));
|
||||
TestFalse("TypeC should not be compatible with Null", UDoorType::AreCompatible(TypeC.Get(), nullptr));
|
||||
|
||||
TestFalse("Null should not be compatible with TypeA", UDoorType::AreCompatible(nullptr, TypeA.Get()));
|
||||
TestFalse("Null should not be compatible with TypeB", UDoorType::AreCompatible(nullptr, TypeB.Get()));
|
||||
TestFalse("Null should not be compatible with TypeC", UDoorType::AreCompatible(nullptr, TypeC.Get()));
|
||||
TestTrue("Null should be compatible with Null", UDoorType::AreCompatible(nullptr, nullptr));
|
||||
|
||||
/* Test Compatibility Table:
|
||||
* A B C N
|
||||
* A O O X X
|
||||
* B O X X X
|
||||
* C X X O X
|
||||
* N X X X O
|
||||
*/
|
||||
TypeA->SetCompatibility({});
|
||||
TypeB->SetCompatibility({ TypeA.Get() });
|
||||
TypeB->SetCompatibleWithItself(false);
|
||||
TypeC->SetCompatibility({});
|
||||
|
||||
TestTrue("TypeA should be compatible with itself", UDoorType::AreCompatible(TypeA.Get(), TypeA.Get()));
|
||||
TestTrue("TypeA should be compatible with TypeB", UDoorType::AreCompatible(TypeA.Get(), TypeB.Get()));
|
||||
TestFalse("TypeA should not be compatible with TypeC", UDoorType::AreCompatible(TypeA.Get(), TypeC.Get()));
|
||||
TestFalse("TypeA should not be compatible with Null", UDoorType::AreCompatible(TypeA.Get(), nullptr));
|
||||
|
||||
TestTrue("TypeB should be compatible with TypeA", UDoorType::AreCompatible(TypeB.Get(), TypeA.Get()));
|
||||
TestFalse("TypeB should not be compatible with itself", UDoorType::AreCompatible(TypeB.Get(), TypeB.Get()));
|
||||
TestFalse("TypeB should not be compatible with TypeC", UDoorType::AreCompatible(TypeB.Get(), TypeC.Get()));
|
||||
TestFalse("TypeB should not be compatible with Null", UDoorType::AreCompatible(TypeB.Get(), nullptr));
|
||||
|
||||
TestFalse("TypeC should not be compatible with TypeA", UDoorType::AreCompatible(TypeC.Get(), TypeA.Get()));
|
||||
TestFalse("TypeC should not be compatible with TypeB", UDoorType::AreCompatible(TypeC.Get(), TypeB.Get()));
|
||||
TestTrue("TypeC should be compatible with TypeC", UDoorType::AreCompatible(TypeC.Get(), TypeC.Get()));
|
||||
TestFalse("TypeC should not be compatible with Null", UDoorType::AreCompatible(TypeC.Get(), nullptr));
|
||||
|
||||
TestFalse("Null should not be compatible with TypeA", UDoorType::AreCompatible(nullptr, TypeA.Get()));
|
||||
TestFalse("Null should not be compatible with TypeB", UDoorType::AreCompatible(nullptr, TypeB.Get()));
|
||||
TestFalse("Null should not be compatible with TypeC", UDoorType::AreCompatible(nullptr, TypeC.Get()));
|
||||
TestTrue("Null should be compatible with Null", UDoorType::AreCompatible(nullptr, nullptr));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "DungeonGraph.h"
|
||||
#include "Room.h"
|
||||
#include "RoomData.h"
|
||||
#include "TestUtils.h"
|
||||
#include "./Classes/RoomConstraintChildClasses.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDungeonGraphTest, "ProceduralDungeon.Types.DungeonGraph", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
#define INIT_TEST(Graph) \
|
||||
TStrongObjectPtr<UDungeonGraph> Graph(NewObject<UDungeonGraph>(GetTransientPackage(), #Graph));
|
||||
|
||||
#define CLEAN_TEST() \
|
||||
Graph->Clear();
|
||||
|
||||
// Utility to create and initialize a room
|
||||
#define CREATE_ROOM(Name, RoomDataPtr) \
|
||||
URoom* Name = NewObject<URoom>(); \
|
||||
Name->Init(RoomDataPtr.Get(), nullptr, Graph->Count()); \
|
||||
Graph->AddRoom(Name);
|
||||
|
||||
// Utility to create room data
|
||||
#define CREATE_ROOM_DATA(Data) \
|
||||
CREATE_DATA_ASSET(URoomData, Data); \
|
||||
Data->Doors.Empty();
|
||||
|
||||
// Utility to create a non-empty path
|
||||
#define DUMMY_PATH(Path) \
|
||||
Path.Empty(); \
|
||||
Path.Add(nullptr);
|
||||
|
||||
#pragma optimize("", off)
|
||||
bool FDungeonGraphTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
{
|
||||
// Creating data assets
|
||||
CREATE_ROOM_DATA(DA_A);
|
||||
CREATE_ROOM_DATA(DA_B);
|
||||
CREATE_ROOM_DATA(DA_C);
|
||||
CREATE_ROOM_DATA(DA_D);
|
||||
|
||||
DA_A->Doors.Add({{0, 0, 0}, EDoorDirection::South});
|
||||
|
||||
DA_B->Doors.Add({{0, 0, 0}, EDoorDirection::East});
|
||||
DA_B->Doors.Add({{0, 0, 0}, EDoorDirection::West});
|
||||
|
||||
DA_C->Doors.Add({{0, 0, 0}, EDoorDirection::North});
|
||||
DA_C->Doors.Add({{0, 0, 0}, EDoorDirection::South});
|
||||
DA_C->Doors.Add({{0, 0, 0}, EDoorDirection::East});
|
||||
DA_C->Doors.Add({{0, 0, 0}, EDoorDirection::West});
|
||||
|
||||
DA_D->BoundingBoxes[0].SetMinAndMax({0, 0, 0}, {1, 1, 2});
|
||||
DA_D->Doors.Add({{0, 0, 0}, EDoorDirection::North});
|
||||
DA_D->Doors.Add({{0, 0, 1}, EDoorDirection::North});
|
||||
|
||||
// Test pathfind
|
||||
{
|
||||
INIT_TEST(Graph);
|
||||
|
||||
// (Rooms are numbered from left to right and top to bottom starting at 0)
|
||||
// A--C-C-!C-C-B
|
||||
// | | |
|
||||
// A-!B B--B-C
|
||||
|
||||
// first line
|
||||
CREATE_ROOM(Room0, DA_A);
|
||||
CREATE_ROOM(Room1, DA_C);
|
||||
CREATE_ROOM(Room2, DA_C);
|
||||
CREATE_ROOM(Room3, DA_C);
|
||||
CREATE_ROOM(Room4, DA_C);
|
||||
CREATE_ROOM(Room5, DA_B);
|
||||
|
||||
// second line
|
||||
CREATE_ROOM(Room6, DA_A);
|
||||
CREATE_ROOM(Room7, DA_B);
|
||||
CREATE_ROOM(Room8, DA_B);
|
||||
CREATE_ROOM(Room9, DA_B);
|
||||
CREATE_ROOM(Room10, DA_C);
|
||||
|
||||
Room3->Lock(true);
|
||||
Room7->Lock(true);
|
||||
|
||||
// first line
|
||||
Graph->Connect(Room0, 0, Room1, 1);
|
||||
Graph->Connect(Room1, 0, Room2, 1);
|
||||
Graph->Connect(Room2, 0, Room3, 1);
|
||||
Graph->Connect(Room3, 0, Room4, 1);
|
||||
Graph->Connect(Room4, 0, Room5, 1);
|
||||
|
||||
// second line
|
||||
Graph->Connect(Room6, 0, Room7, 0);
|
||||
Graph->Connect(Room8, 0, Room9, 1);
|
||||
Graph->Connect(Room9, 0, Room10, 0);
|
||||
|
||||
// transversal
|
||||
Graph->Connect(Room1, 2, Room7, 1);
|
||||
Graph->Connect(Room2, 2, Room8, 1);
|
||||
Graph->Connect(Room4, 2, Room10, 1);
|
||||
|
||||
// Used to test path output for some scenarios
|
||||
TArray<const URoom*> Path;
|
||||
|
||||
// Check graph state
|
||||
TestEqual(TEXT("Graph should have 11 rooms"), 11, Graph->Count());
|
||||
TestEqual(TEXT("Graph should have 2 rooms from data DA_A"), 2, Graph->CountRoomData(DA_A.Get()));
|
||||
TestEqual(TEXT("Graph should have 4 rooms from data DA_B"), 4, Graph->CountRoomData(DA_B.Get()));
|
||||
TestEqual(TEXT("Graph should have 5 rooms from data DA_C"), 5, Graph->CountRoomData(DA_C.Get()));
|
||||
|
||||
// Check path find with null rooms
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("Path should not be found when both rooms are null"), UDungeonGraph::FindPath(nullptr, nullptr, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("Path should not be found when first room is null"), UDungeonGraph::FindPath(nullptr, Room1, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("Path should not be found when second room is null"), UDungeonGraph::FindPath(Room0, nullptr, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
|
||||
// Check path found with same room
|
||||
TestTrue(TEXT("Path should be found when both rooms are same"), UDungeonGraph::FindPath(Room0, Room0, &Path));
|
||||
TestEqual(TEXT("Path should have 1 room"), Path.Num(), 1);
|
||||
TestTrue(TEXT("Path should have Room0"), Path[0] == Room0);
|
||||
TestTrue(TEXT("Path should be found when both rooms are same (even if locked)"), UDungeonGraph::FindPath(Room3, Room3, &Path));
|
||||
TestEqual(TEXT("Path should have 1 room"), Path.Num(), 1);
|
||||
TestTrue(TEXT("Path should have Room3"), Path[0] == Room3);
|
||||
|
||||
// Check no path when one or both rooms are locked
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("No path should be found between Room0 and Room3"), UDungeonGraph::FindPath(Room0, Room3, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("No path should be found between Room3 and Room0"), UDungeonGraph::FindPath(Room3, Room0, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("No path should be found between Room3 and Room7"), UDungeonGraph::FindPath(Room3, Room7, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
|
||||
// output path has rooms in the correct order
|
||||
TestTrue(TEXT("Path should be found between Room0 and Room5"), UDungeonGraph::FindPath(Room0, Room5, &Path));
|
||||
TestEqual(TEXT("Path should have 8 rooms"), Path.Num(), 8);
|
||||
TestTrue(TEXT("Path should not go through Room3"), !Path.Contains(Room3));
|
||||
TestEqual(TEXT("Path's 1st room should be Room0"), Path[0], (const URoom*)Room0);
|
||||
TestTrue(TEXT("Path's 2nd room should be Room1"), Path[1] == Room1);
|
||||
TestTrue(TEXT("Path's 3rd room should be Room2"), Path[2] == Room2);
|
||||
TestTrue(TEXT("Path's 4th room should be Room8"), Path[3] == Room8);
|
||||
TestTrue(TEXT("Path's 5th room should be Room9"), Path[4] == Room9);
|
||||
TestTrue(TEXT("Path's 6th room should be Room10"), Path[5] == Room10);
|
||||
TestTrue(TEXT("Path's 7th room should be Room4"), Path[6] == Room4);
|
||||
TestTrue(TEXT("Path's 8th room should be Room5"), Path[7] == Room5);
|
||||
|
||||
// Check pathfind through locked door (not first nor last)
|
||||
DUMMY_PATH(Path);
|
||||
TestFalse(TEXT("No path should be found between Room0 and Room6"), UDungeonGraph::FindPath(Room0, Room6, &Path));
|
||||
TestEqual(TEXT("Path should be empty"), Path.Num(), 0);
|
||||
TestTrue(TEXT("Path should be found between Room0 and Room6 (when locked rooms allowed)"), UDungeonGraph::FindPath(Room0, Room6, nullptr, /*IgnoreLocked = */ true));
|
||||
|
||||
CLEAN_TEST();
|
||||
}
|
||||
|
||||
// Test Voxel Bounds Conversions
|
||||
{
|
||||
INIT_TEST(Graph);
|
||||
|
||||
// first floor second floor
|
||||
// C - B - A C - C
|
||||
// | |
|
||||
// D D
|
||||
|
||||
CREATE_ROOM(Room0, DA_C);
|
||||
CREATE_ROOM(Room1, DA_B);
|
||||
CREATE_ROOM(Room2, DA_A);
|
||||
CREATE_ROOM(Room3, DA_D);
|
||||
CREATE_ROOM(Room4, DA_C);
|
||||
CREATE_ROOM(Room5, DA_C);
|
||||
|
||||
Room1->Position = {0, 1, 0};
|
||||
Room2->Position = {0, 2, 0};
|
||||
Room2->Direction = EDoorDirection::East;
|
||||
Room3->Position = {-1, 0, 0};
|
||||
Room4->Position = {0, 0, 1};
|
||||
Room5->Position = {0, 1, 1};
|
||||
|
||||
// Room positions are modified manually, so we need to explicitely rebuild bounds
|
||||
Graph->RebuildBounds();
|
||||
|
||||
// Check Room0 voxel bounds conversion
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({0, 0, 0});
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = Room0->GetVoxelBounds();
|
||||
TestEqual(TEXT("Room0 bounds should be as expected"), ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
|
||||
// Check Room1 voxel bounds conversion
|
||||
{
|
||||
FVoxelBounds ExceptedBounds;
|
||||
ExceptedBounds.AddCell({0, 1, 0});
|
||||
ExceptedBounds.SetCellConnection({0, 1, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExceptedBounds.SetCellConnection({0, 1, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExceptedBounds.SetCellConnection({0, 1, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExceptedBounds.SetCellConnection({0, 1, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExceptedBounds.SetCellConnection({0, 1, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExceptedBounds.SetCellConnection({0, 1, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = Room1->GetVoxelBounds();
|
||||
TestEqual(TEXT("Room1 bounds should be as expected"), ConvertedBounds, ExceptedBounds);
|
||||
}
|
||||
|
||||
// Check Room2 voxel bounds conversion
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({0, 2, 0});
|
||||
ExpectedBounds.SetCellConnection({0, 2, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 2, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 2, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 2, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 2, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 2, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = Room2->GetVoxelBounds();
|
||||
TestEqual(TEXT("Room2 bounds should be as expected"), ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
|
||||
// Check Room3 voxel bounds conversion
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({-1, 0, 0});
|
||||
ExpectedBounds.AddCell({-1, 0, 1});
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 1}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 1}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 1}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 1}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 1}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = Room3->GetVoxelBounds();
|
||||
TestEqual(TEXT("Room3 bounds should be as expected"), ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
|
||||
// Check Room4 voxel bounds conversion
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({0, 0, 1});
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = Room4->GetVoxelBounds();
|
||||
TestEqual(TEXT("Room4 bounds should be as expected"), ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
|
||||
// Check Room5 voxel bounds conversion
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({0, 1, 1});
|
||||
ExpectedBounds.SetCellConnection({0, 1, 1}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 1, 1}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 1, 1}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 1, 1}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 1, 1}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 1, 1}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = Room5->GetVoxelBounds();
|
||||
TestEqual(TEXT("Room5 bounds should be as expected"), ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// FilterAndSort Test
|
||||
{
|
||||
INIT_TEST(Graph);
|
||||
|
||||
// first floor second floor
|
||||
// C - B - A C - C
|
||||
// | |
|
||||
// D D
|
||||
|
||||
CREATE_DATA_ASSET(UConstraintPass, Pass);
|
||||
CREATE_DATA_ASSET(UConstraintFail, Fail);
|
||||
|
||||
CREATE_ROOM_DATA(DA_E);
|
||||
DA_E->BoundingBoxes[0].SetMinAndMax({0, 0, 0}, {1, 2, 1});
|
||||
DA_E->Doors.Add({{0, 1, 0}, EDoorDirection::North});
|
||||
DA_E->Constraints.Add(Pass.Get());
|
||||
|
||||
// Same as DA_E but constraint fail
|
||||
CREATE_ROOM_DATA(DA_F);
|
||||
DA_F->BoundingBoxes[0].SetMinAndMax({0, 0, 0}, {1, 2, 1});
|
||||
DA_F->Doors.Add({{0, 1, 0}, EDoorDirection::North});
|
||||
DA_F->Constraints.Add(Fail.Get());
|
||||
|
||||
CREATE_ROOM(Room0, DA_C);
|
||||
CREATE_ROOM(Room1, DA_B);
|
||||
CREATE_ROOM(Room2, DA_A);
|
||||
CREATE_ROOM(Room3, DA_D);
|
||||
CREATE_ROOM(Room4, DA_C);
|
||||
CREATE_ROOM(Room5, DA_C);
|
||||
|
||||
Room1->Position = {0, 1, 0};
|
||||
Room2->Position = {0, 2, 0};
|
||||
Room2->Direction = EDoorDirection::East;
|
||||
Room3->Position = {-1, 0, 0};
|
||||
Room4->Position = {0, 0, 1};
|
||||
Room5->Position = {0, 1, 1};
|
||||
|
||||
// Room positions are modified manually, so we need to explicitely rebuild bounds
|
||||
Graph->RebuildBounds();
|
||||
|
||||
const TArray<URoomData*> RoomList = {DA_A.Get(), DA_D.Get(), DA_E.Get(), DA_F.Get()};
|
||||
TArray<FRoomCandidate> SortedRooms;
|
||||
|
||||
{
|
||||
FDoorDef FromDoor = {{0, 0, 0}, EDoorDirection::North};
|
||||
bool bHasCandidates = Graph->FilterAndSortRooms(RoomList, FromDoor, SortedRooms);
|
||||
TestTrue(TEXT("There should be candidates"), bHasCandidates);
|
||||
TestEqual(TEXT("There should be 4 candidates"), SortedRooms.Num(), 4);
|
||||
TestEqual(TEXT("RoomData D should be the best candidate"), SortedRooms[0].Data, DA_D.Get());
|
||||
TestEqual(TEXT("RoomData D Door index 0 should be the best candidate"), SortedRooms[0].DoorIndex, 0);
|
||||
TestEqual(TEXT("RoomData A should be the worst candidate"), SortedRooms[3].Data, DA_A.Get());
|
||||
|
||||
bool bContainsE = false;
|
||||
bool bContainsF = false;
|
||||
for (const FRoomCandidate& Candidate : SortedRooms)
|
||||
{
|
||||
bContainsE |= (Candidate.Data == DA_E.Get());
|
||||
bContainsF |= (Candidate.Data == DA_F.Get());
|
||||
}
|
||||
|
||||
// DA_E and DA_F are the same, but DA_F has a failing constraint
|
||||
TestTrue(TEXT("RoomData E should be a valid candidate"), bContainsE);
|
||||
TestFalse(TEXT("RoomData F should not be a valid candidate"), bContainsF);
|
||||
}
|
||||
|
||||
{
|
||||
FDoorDef FromDoor = {{0, 1, 1}, EDoorDirection::South};
|
||||
bool bHasCandidates = Graph->FilterAndSortRooms(RoomList, FromDoor, SortedRooms);
|
||||
TestTrue(TEXT("There should be candidates"), bHasCandidates);
|
||||
TestEqual(TEXT("There should be 3 candidates"), SortedRooms.Num(), 3);
|
||||
TestEqual(TEXT("RoomData D should be the best candidate"), SortedRooms[0].Data, DA_D.Get());
|
||||
TestEqual(TEXT("RoomData D DoorIndex 0 should be the best candidate"), SortedRooms[0].DoorIndex, 0);
|
||||
TestEqual(TEXT("RoomData D should be the worst candidate"), SortedRooms[2].Data, DA_D.Get());
|
||||
TestEqual(TEXT("RoomData D DoorIndex 1 should be the worst candidate"), SortedRooms[2].DoorIndex, 1);
|
||||
}
|
||||
|
||||
CLEAN_TEST();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#pragma optimize("", on)
|
||||
|
||||
#undef INIT_TEST
|
||||
#undef CLEAN_TEST
|
||||
#undef CREATE_ROOM
|
||||
#undef CREATE_ROOM_DATA
|
||||
#undef DUMMY_PATH
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright Benoit Pelletier 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "ProceduralDungeonUtils.h"
|
||||
#include "Interfaces/DungeonSaveInterface.h"
|
||||
#include "Interfaces/DungeonCustomSerialization.h"
|
||||
#include "Utils/DungeonSaveUtils.h"
|
||||
#include "Classes/DungeonSaveClasses.h"
|
||||
#include "TestUtils.h"
|
||||
#include "UObject/StrongObjectPtr.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
DEFINE_SPEC(FDungeonSaveSpecs, "ProceduralDungeon.SaveLoad", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
void FDungeonSaveSpecs::Define()
|
||||
{
|
||||
Describe(TEXT("Interfaces"), [this]()
|
||||
{
|
||||
It(TEXT("DungeonSaveInterface"), [this]()
|
||||
{
|
||||
TStrongObjectPtr<USaveTestObject> TestSave(NewObject<USaveTestObject>(GetTransientPackage()));
|
||||
TStrongObjectPtr<USaveTestObject> TestLoad(NewObject<USaveTestObject>(GetTransientPackage()));
|
||||
|
||||
TestSave->TestSaveGameFlag = 5;
|
||||
TestSave->TestSerializeObjectFunction = 8;
|
||||
|
||||
TArray<uint8> SavedData {};
|
||||
SerializeUObject(SavedData, TestSave.Get(), false);
|
||||
|
||||
TestEqual(TEXT("should have correct order of execution"), TestSave->OrderOfExecution, FString(TEXT("BCD")));
|
||||
TestTrue(TEXT("should have written data"), SavedData.Num() > 0);
|
||||
|
||||
SerializeUObject(SavedData, TestLoad.Get(), true);
|
||||
|
||||
TestEqual(TEXT("should have correct order of execution"), TestLoad->OrderOfExecution, FString(TEXT("WXY")));
|
||||
TestEqual(TEXT("should have retrieved data from SaveGame flag"), TestLoad->TestSaveGameFlag, TestSave->TestSaveGameFlag);
|
||||
TestEqual(TEXT("should have retrieved data from SerializedObject function"), TestLoad->TestSerializeObjectFunction, TestSave->TestSerializeObjectFunction);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
// Copyright Benoit Pelletier 2024 - 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "ProceduralDungeonUtils.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDungeonUtilsTest_WeightedMap, "ProceduralDungeon.Utils.WeightedMaps", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::SmokeFilter)
|
||||
|
||||
bool FDungeonUtilsTest_WeightedMap::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Built-in types test
|
||||
{
|
||||
TMap<int, int> WeightedMap = {
|
||||
{1, 0}, // Weight with 0 should never be returned
|
||||
{2, 1}, // The first non-zero weight should be return for index 0
|
||||
{3, 2}, // Weights greater than 1 should be returned for as much indices
|
||||
{4, 1} // The last one should be return when index == total weights minus one
|
||||
}; // Out of bounds index should return default value
|
||||
|
||||
TestEqual(TEXT("Total Weights"), Dungeon::GetTotalWeight(WeightedMap), 4);
|
||||
TestEqual(TEXT("Weighted value at -1"), Dungeon::GetWeightedAt(WeightedMap, -1), 0); // negative index should return default value
|
||||
TestEqual(TEXT("Weighted value at 0"), Dungeon::GetWeightedAt(WeightedMap, 0), 2);
|
||||
TestEqual(TEXT("Weighted value at 1"), Dungeon::GetWeightedAt(WeightedMap, 1), 3);
|
||||
TestEqual(TEXT("Weighted value at 2"), Dungeon::GetWeightedAt(WeightedMap, 2), 3);
|
||||
TestEqual(TEXT("Weighted value at 3"), Dungeon::GetWeightedAt(WeightedMap, 3), 4);
|
||||
TestEqual(TEXT("Weighted value at 4"), Dungeon::GetWeightedAt(WeightedMap, 4), 0); // default int is 0
|
||||
}
|
||||
|
||||
// Pointer test
|
||||
{
|
||||
int a = 1;
|
||||
int b = 2;
|
||||
int c = 3;
|
||||
int d = 4;
|
||||
|
||||
TMap<int*, int> WeightedMap = {
|
||||
{&a, 2},
|
||||
{&b, 0}, // Weight with 0 in middle should be skipped
|
||||
{&c, 1},
|
||||
{&d, 0} // The last one should not be returned if weight is 0
|
||||
};
|
||||
|
||||
TestEqual(TEXT("Total Weights"), Dungeon::GetTotalWeight(WeightedMap), 3);
|
||||
TestEqual(TEXT("Weighted pointer at -1"), Dungeon::GetWeightedAt(WeightedMap, -1), (int*)nullptr);
|
||||
TestEqual(TEXT("Weighted pointer at 0"), Dungeon::GetWeightedAt(WeightedMap, 0), &a);
|
||||
TestEqual(TEXT("Weighted pointer at 1"), Dungeon::GetWeightedAt(WeightedMap, 1), &a);
|
||||
TestEqual(TEXT("Weighted pointer at 2"), Dungeon::GetWeightedAt(WeightedMap, 2), &c);
|
||||
TestEqual(TEXT("Weighted pointer at 3"), Dungeon::GetWeightedAt(WeightedMap, 3), (int*)nullptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FDungeonUtilsTest_Guid2Seed, "ProceduralDungeon.Utils.Guid2Seed.Simple", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::SmokeFilter)
|
||||
|
||||
bool FDungeonUtilsTest_Guid2Seed::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Guid to Random Seed Tests
|
||||
{
|
||||
FGuid Guid1(0x12345678U, 0x9ABCDEF0U, 0x0FEDCBA9U, 0x87654321U);
|
||||
FGuid Guid2(0x08F7E6D5U, 0xC4B3A291U, 0x192A3B4CU, 0x5D6E7F80U);
|
||||
int64 Salt1 = 1;
|
||||
int64 Salt2 = 2;
|
||||
|
||||
TestTrue(TEXT("Same Guid and Salt should return same Seed (1)"), Random::Guid2Seed(Guid1, Salt1) == Random::Guid2Seed(Guid1, Salt1));
|
||||
TestTrue(TEXT("Same Guid and Salt should return same Seed (2)"), Random::Guid2Seed(Guid2, Salt2) == Random::Guid2Seed(Guid2, Salt2));
|
||||
TestTrue(TEXT("Same Guid but different Salt should return different Seeds (1)"), Random::Guid2Seed(Guid1, Salt1) != Random::Guid2Seed(Guid1, Salt2));
|
||||
TestTrue(TEXT("Same Guid but different Salt should return different Seeds (2)"), Random::Guid2Seed(Guid2, Salt1) != Random::Guid2Seed(Guid2, Salt2));
|
||||
TestTrue(TEXT("Different Guid but same Salt should return different Seeds (1)"), Random::Guid2Seed(Guid1, Salt1) != Random::Guid2Seed(Guid2, Salt1));
|
||||
TestTrue(TEXT("Different Guid but same Salt should return different Seeds (2)"), Random::Guid2Seed(Guid1, Salt2) != Random::Guid2Seed(Guid2, Salt2));
|
||||
TestTrue(TEXT("Different Guid and Salt should return different Seeds (1)"), Random::Guid2Seed(Guid1, Salt1) != Random::Guid2Seed(Guid2, Salt2));
|
||||
TestTrue(TEXT("Different Guid and Salt should return different Seeds (2)"), Random::Guid2Seed(Guid1, Salt2) != Random::Guid2Seed(Guid2, Salt1));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
BEGIN_DEFINE_SPEC(FGuid2SeedStatisticalTests, "ProceduralDungeon.Utils.Guid2Seed.Stats", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::EngineFilter)
|
||||
struct FTestParams
|
||||
{
|
||||
FGuid Guid;
|
||||
int32 NbElements;
|
||||
int32 NbSamples;
|
||||
double CriticalValue;
|
||||
};
|
||||
|
||||
using SampleArrayType = int32[];
|
||||
|
||||
static TUniquePtr<SampleArrayType> GenerateSamples(const FTestParams& Params)
|
||||
{
|
||||
TUniquePtr<SampleArrayType> Occurences(new int32[Params.NbElements] {0});
|
||||
for (int32 i = 0; i < Params.NbSamples; ++i)
|
||||
{
|
||||
// Create a random stream with only the salt modified.
|
||||
// This will simulate an actor in a room level generating its first random number
|
||||
// in each of the room instances (where only the room ID changes)
|
||||
FRandomStream RNG(Random::Guid2Seed(Params.Guid, i));
|
||||
Occurences[RNG.RandRange(0, Params.NbElements - 1)]++;
|
||||
}
|
||||
return Occurences;
|
||||
}
|
||||
|
||||
static bool PassChiSquaredTest(const FTestParams& Params, SampleArrayType Samples)
|
||||
{
|
||||
const double Expected = static_cast<double>(Params.NbSamples) / Params.NbElements;
|
||||
double X2 = 0;
|
||||
for (int i = 0; i < Params.NbElements; ++i)
|
||||
{
|
||||
const double Delta = Samples[i] - Expected;
|
||||
X2 += (Delta * Delta) / Expected;
|
||||
}
|
||||
//UE_LOG(LogTemp, Warning, TEXT("Chi Squared result %d: %f / %f"), Params.NbElements, X2, Params.CriticalValue);
|
||||
return X2 < Params.CriticalValue;
|
||||
}
|
||||
END_DEFINE_SPEC(FGuid2SeedStatisticalTests)
|
||||
|
||||
void FGuid2SeedStatisticalTests::Define()
|
||||
{
|
||||
Describe(TEXT("Chi Squared Test"), [this]()
|
||||
{
|
||||
FTestParams Params;
|
||||
Params.NbSamples = 1'000'000'000;
|
||||
|
||||
TArray<FGuid> GuidsToTest = {
|
||||
FGuid(),
|
||||
FGuid(0x12345678U, 0x9ABCDEF0U, 0x0FEDCBA9U, 0x87654321U),
|
||||
FGuid(0x08F7E6D5U, 0xC4B3A291U, 0x192A3B4CU, 0x5D6E7F80U),
|
||||
};
|
||||
|
||||
for (const auto& Guid : GuidsToTest)
|
||||
{
|
||||
Params.Guid = Guid;
|
||||
Describe(FString::Printf(TEXT("with Guid %s"), *Guid.ToString()), [this, Params]() mutable
|
||||
{
|
||||
// The test cases we want to check for each Guid.
|
||||
// First value is the number of elements in the generated samples.
|
||||
// Second value is the critical value for the Chi Squared test (with p-value of 5%)
|
||||
TArray<TTuple<int32, double>> TestCases;
|
||||
TestCases.Add(TTuple<int32, double>(10, 16.91898));
|
||||
TestCases.Add(TTuple<int32, double>(100, 123.22522));
|
||||
TestCases.Add(TTuple<int32, double>(1000, 1073.64265));
|
||||
|
||||
for (const auto& TestCase : TestCases)
|
||||
{
|
||||
Params.NbElements = TestCase.Key;
|
||||
Params.CriticalValue = TestCase.Value;
|
||||
It(FString::Printf(TEXT("with %d elements"), Params.NbElements), [this, Params]()
|
||||
{
|
||||
TUniquePtr<SampleArrayType> Samples = GenerateSamples(Params);
|
||||
TestTrue(TEXT("Pass"), PassChiSquaredTest(Params, Samples.Get()));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -0,0 +1,448 @@
|
||||
// Copyright Benoit Pelletier 2024 - 2026 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "RoomData.h"
|
||||
#include "DoorType.h"
|
||||
#include "./Classes/RoomCustomDataChildClasses.h"
|
||||
#include "./Classes/RoomConstraintChildClasses.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "TestUtils.h"
|
||||
#include "VoxelBounds/VoxelBounds.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FRoomDataTests, "ProceduralDungeon.Types.RoomData", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::EngineFilter)
|
||||
|
||||
// Utility to create room data
|
||||
#define CREATE_ROOM_DATA(Data) \
|
||||
CREATE_DATA_ASSET(URoomData, Data); \
|
||||
Data->Doors.Empty();
|
||||
|
||||
#define ADD_DOOR(ROOM, DOOR_POS, DOOR_DIR, DOOR_TYPE) \
|
||||
{ \
|
||||
FDoorDef Door; \
|
||||
Door.Position = DOOR_POS; \
|
||||
Door.Direction = DOOR_DIR; \
|
||||
Door.Type = DOOR_TYPE; \
|
||||
ROOM->Doors.Add(Door); \
|
||||
}
|
||||
|
||||
bool FRoomDataTests::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Test IsRoomInBounds
|
||||
{
|
||||
// Creating this room data (X is the room origin, v is the door), thus we could test rotated bounds:
|
||||
// 1 +---+---+---+
|
||||
// | | X | |
|
||||
// 0 +---+---+---+
|
||||
// | | | |
|
||||
// -1 +---+---+---+
|
||||
// | | | |
|
||||
// -2 +---+-v-+---+
|
||||
// -1 0 1 2
|
||||
CREATE_ROOM_DATA(RoomData);
|
||||
RoomData->BoundingBoxes[0].SetMinAndMax(FIntVector(-2, -1, -1), FIntVector(1, 2, 2));
|
||||
|
||||
FDoorDef Door;
|
||||
Door.Position = FIntVector(-2, 0, 0);
|
||||
Door.Direction = EDoorDirection::South;
|
||||
RoomData->Doors.Add(Door);
|
||||
|
||||
// If we want to limit the dungeon cells from -2 to 2,
|
||||
// we need to create a box from -2 to 3 (see below).
|
||||
// +---+---+---+---+---+
|
||||
// |-2 |-1 | 0 | 1 | 2 |
|
||||
// +---+---+---+---+---+
|
||||
// -2 -1 0 1 2 3
|
||||
FBoxMinAndMax DungeonBounds({-1000, -2, -1000}, {1000, 3, 1000});
|
||||
|
||||
FBoxMinAndMax RoomBoundsAtDoorLocation = RoomData->GetIntBounds() - Door.Position;
|
||||
|
||||
// Rotated to South
|
||||
{
|
||||
FBoxMinAndMax RotatedRoomBounds = Rotate(RoomBoundsAtDoorLocation, EDoorDirection::North);
|
||||
TestEqual(TEXT("[S] Rotated Room Bounds: ((0, -1, -1), (3, 2, 2))"), RotatedRoomBounds, FBoxMinAndMax({0, -1, -1}, {3, 2, 2}));
|
||||
|
||||
Door.Direction = EDoorDirection::South;
|
||||
Door.Position = {0, 0, 0};
|
||||
TestTrue(TEXT("[S] Inside with no coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Positive Y
|
||||
|
||||
Door.Position = {0, 1, 0};
|
||||
TestTrue(TEXT("[S] Inside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 2, 0};
|
||||
TestFalse(TEXT("[S] Intersecting in Y+"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 4, 0};
|
||||
TestFalse(TEXT("[S] Outside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Negative Y
|
||||
|
||||
Door.Position = {0, -1, 0};
|
||||
TestTrue(TEXT("[S] Inside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -2, 0};
|
||||
TestFalse(TEXT("[S] Intersecting in Y-"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -4, 0};
|
||||
TestFalse(TEXT("[S] Outside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
}
|
||||
|
||||
// Rotated to East
|
||||
{
|
||||
FBoxMinAndMax RotatedRoomBounds = Rotate(RoomBoundsAtDoorLocation, EDoorDirection::West);
|
||||
TestEqual(TEXT("[E] Rotated Room Bounds: ((-1, -2, -1), (2, 1, 2))"), RotatedRoomBounds, FBoxMinAndMax({-1, -2, -1}, {2, 1, 2}));
|
||||
|
||||
Door.Direction = EDoorDirection::East;
|
||||
Door.Position = {0, 1, 0};
|
||||
TestTrue(TEXT("[E] Inside with no coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Positive Y
|
||||
|
||||
Door.Position = {0, 2, 0};
|
||||
TestTrue(TEXT("[E] Inside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 3, 0};
|
||||
TestFalse(TEXT("[E] Intersecting in Y+"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 5, 0};
|
||||
TestFalse(TEXT("[E] Outside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Negative Y
|
||||
|
||||
Door.Position = {0, 0, 0};
|
||||
TestTrue(TEXT("[E] Inside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -1, 0};
|
||||
TestFalse(TEXT("[E] Intersecting in Y-"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -3, 0};
|
||||
TestFalse(TEXT("[E] Outside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
}
|
||||
|
||||
// Rotated to West
|
||||
{
|
||||
FBoxMinAndMax RotatedRoomBounds = Rotate(RoomBoundsAtDoorLocation, EDoorDirection::East);
|
||||
TestEqual(TEXT("[W] Rotated Room Bounds: ((-1, 0, -1), (2, 3, 2))"), RotatedRoomBounds, FBoxMinAndMax({-1, 0, -1}, {2, 3, 2}));
|
||||
|
||||
Door.Direction = EDoorDirection::West;
|
||||
Door.Position = {0, -1, 0};
|
||||
TestTrue(TEXT("[W] Inside with no coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Positive Y
|
||||
|
||||
Door.Position = {0, 0, 0};
|
||||
TestTrue(TEXT("[W] Inside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 1, 0};
|
||||
TestFalse(TEXT("[W] Intersecting in Y+"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 3, 0};
|
||||
TestFalse(TEXT("[W] Outside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Negative Y
|
||||
|
||||
Door.Position = {0, -2, 0};
|
||||
TestTrue(TEXT("[W] Inside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -3, 0};
|
||||
TestFalse(TEXT("[W] Intersecting in Y-"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -5, 0};
|
||||
TestFalse(TEXT("[W] Outside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
}
|
||||
|
||||
// Rotated to North
|
||||
{
|
||||
FBoxMinAndMax RotatedRoomBounds = Rotate(RoomBoundsAtDoorLocation, EDoorDirection::South);
|
||||
TestEqual(TEXT("[N] Rotated Room Bounds: ((-2, -1, -1), (1, 2, 2))"), RotatedRoomBounds, FBoxMinAndMax({-2, -1, -1}, {1, 2, 2}));
|
||||
|
||||
Door.Direction = EDoorDirection::North;
|
||||
Door.Position = {0, 0, 0};
|
||||
TestTrue(TEXT("[N] Inside with no coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Positive Y
|
||||
|
||||
Door.Position = {0, 1, 0};
|
||||
TestTrue(TEXT("[N] Inside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 2, 0};
|
||||
TestFalse(TEXT("[N] Intersecting in Y+"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, 4, 0};
|
||||
TestFalse(TEXT("[N] Outside with Y+ as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
// Negative Y
|
||||
|
||||
Door.Position = {0, -1, 0};
|
||||
TestTrue(TEXT("[N] Inside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -2, 0};
|
||||
TestFalse(TEXT("[N] Intersecting in Y-"), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
|
||||
Door.Position = {0, -4, 0};
|
||||
TestFalse(TEXT("[N] Outside with Y- as coincident face."), RoomData->IsRoomInBounds(DungeonBounds, 0, Door));
|
||||
}
|
||||
}
|
||||
|
||||
// Test HasDoorOfType and variants
|
||||
{
|
||||
CREATE_ROOM_DATA(RoomData);
|
||||
|
||||
CREATE_DATA_ASSET(UDoorType, DoorA);
|
||||
CREATE_DATA_ASSET(UDoorType, DoorB);
|
||||
CREATE_DATA_ASSET(UDoorType, DoorC);
|
||||
CREATE_DATA_ASSET(UDoorType, DoorD);
|
||||
CREATE_DATA_ASSET(UDoorType, DoorE);
|
||||
|
||||
ADD_DOOR(RoomData, FIntVector::ZeroValue, EDoorDirection::North, DoorA.Get());
|
||||
ADD_DOOR(RoomData, FIntVector::ZeroValue, EDoorDirection::South, DoorB.Get());
|
||||
ADD_DOOR(RoomData, FIntVector::ZeroValue, EDoorDirection::East, DoorC.Get());
|
||||
ADD_DOOR(RoomData, FIntVector::ZeroValue, EDoorDirection::West, DoorD.Get());
|
||||
|
||||
// HasDoorOfType
|
||||
{
|
||||
TestTrue("HasDoorOfType(DoorA)", RoomData->HasDoorOfType(DoorA.Get()));
|
||||
TestTrue("HasDoorOfType(DoorB)", RoomData->HasDoorOfType(DoorB.Get()));
|
||||
TestTrue("HasDoorOfType(DoorC)", RoomData->HasDoorOfType(DoorC.Get()));
|
||||
TestTrue("HasDoorOfType(DoorD)", RoomData->HasDoorOfType(DoorD.Get()));
|
||||
TestFalse("HasDoorOfType(DoorE)", RoomData->HasDoorOfType(DoorE.Get()));
|
||||
TestFalse("HasDoorOfType(nullptr)", RoomData->HasDoorOfType(nullptr));
|
||||
}
|
||||
|
||||
// HasAnyDoorOfType
|
||||
{
|
||||
TestTrue("HasAnyDoorOfType({DoorA, DoorB, DoorE, nullptr})", RoomData->HasAnyDoorOfType({DoorA.Get(), DoorB.Get(), DoorE.Get(), nullptr}));
|
||||
TestTrue("HasAnyDoorOfType({DoorC, DoorD})", RoomData->HasAnyDoorOfType({DoorC.Get(), DoorD.Get()}));
|
||||
TestFalse("HasAnyDoorOfType({DoorE, nullptr})", RoomData->HasAnyDoorOfType({DoorE.Get(), nullptr}));
|
||||
TestFalse("HasAnyDoorOfType({})", RoomData->HasAnyDoorOfType({}));
|
||||
}
|
||||
|
||||
// HasAllDoorOfType
|
||||
{
|
||||
TestFalse("HasAllDoorOfType({DoorA, DoorB, DoorE, nullptr})", RoomData->HasAllDoorOfType({DoorA.Get(), DoorB.Get(), DoorE.Get(), nullptr}));
|
||||
TestTrue("HasAllDoorOfType({DoorC, DoorD})", RoomData->HasAllDoorOfType({DoorC.Get(), DoorD.Get()}));
|
||||
TestFalse("HasAllDoorOfType({DoorE, nullptr})", RoomData->HasAllDoorOfType({DoorE.Get(), nullptr}));
|
||||
TestTrue("HasAllDoorOfType({})", RoomData->HasAllDoorOfType({}));
|
||||
}
|
||||
}
|
||||
|
||||
// Test HasCustomData and variants
|
||||
{
|
||||
CREATE_ROOM_DATA(RoomData);
|
||||
|
||||
RoomData->CustomData.Add(UCustomDataA::StaticClass());
|
||||
RoomData->CustomData.Add(UCustomDataB::StaticClass());
|
||||
|
||||
// HasCustomData
|
||||
{
|
||||
TestTrue("HasCustomData(CustomDataA)", RoomData->HasCustomData(UCustomDataA::StaticClass()));
|
||||
TestTrue("HasCustomData(CustomDataB)", RoomData->HasCustomData(UCustomDataB::StaticClass()));
|
||||
TestFalse("HasCustomData(CustomDataC)", RoomData->HasCustomData(UCustomDataC::StaticClass()));
|
||||
TestFalse("HasCustomData(nullptr)", RoomData->HasCustomData(nullptr));
|
||||
}
|
||||
|
||||
// HasAnyCustomData
|
||||
{
|
||||
TestTrue("HasAnyCustomData({CustomDataA, CustomDataB})", RoomData->HasAnyCustomData({UCustomDataA::StaticClass(), UCustomDataB::StaticClass()}));
|
||||
TestTrue("HasAnyCustomData({CustomDataA, CustomDataC})", RoomData->HasAnyCustomData({UCustomDataA::StaticClass(), UCustomDataC::StaticClass()}));
|
||||
TestFalse("HasAnyCustomData({nullptr, CustomDataC})", RoomData->HasAnyCustomData({nullptr, UCustomDataC::StaticClass()}));
|
||||
TestFalse("HasAnyCustomData({})", RoomData->HasAnyCustomData({}));
|
||||
}
|
||||
|
||||
// HasAllCustomData
|
||||
{
|
||||
TestTrue("HasAllCustomData({CustomDataA, CustomDataB})", RoomData->HasAllCustomData({UCustomDataA::StaticClass(), UCustomDataB::StaticClass()}));
|
||||
TestFalse("HasAllCustomData({CustomDataA, CustomDataC})", RoomData->HasAllCustomData({UCustomDataA::StaticClass(), UCustomDataC::StaticClass()}));
|
||||
TestFalse("HasAllCustomData({nullptr, CustomDataC})", RoomData->HasAllCustomData({nullptr, UCustomDataC::StaticClass()}));
|
||||
TestTrue("HasAllCustomData({})", RoomData->HasAllCustomData({}));
|
||||
}
|
||||
}
|
||||
|
||||
// Test Size and Volume
|
||||
{
|
||||
// Should have Size=(1,1,1) and Volume=1
|
||||
CREATE_ROOM_DATA(RoomDataA);
|
||||
RoomDataA->BoundingBoxes[0].SetMinAndMax(FIntVector(0, 1, 0), FIntVector(1, 0, 1));
|
||||
|
||||
// Should have Size=(2,1,1) and Volume=2
|
||||
CREATE_ROOM_DATA(RoomDataB);
|
||||
RoomDataB->BoundingBoxes[0].SetMinAndMax(FIntVector(-1, 1, 0), FIntVector(1, 0, 1));
|
||||
|
||||
// Should have Size=(2,2,1) and Volume=4
|
||||
CREATE_ROOM_DATA(RoomDataC);
|
||||
RoomDataC->BoundingBoxes[0].SetMinAndMax(FIntVector(-1, 1, 0), FIntVector(1, -1, 1));
|
||||
|
||||
// Should have Size=(2,2,2) and Volume=8
|
||||
CREATE_ROOM_DATA(RoomDataD);
|
||||
RoomDataD->BoundingBoxes[0].SetMinAndMax(FIntVector(-1, 1, -1), FIntVector(1, -1, 1));
|
||||
|
||||
// GetSize
|
||||
{
|
||||
TestEqual("RoomDataA->GetSize() == {1,1,1}", RoomDataA->GetSize(), FIntVector {1, 1, 1});
|
||||
TestEqual("RoomDataB->GetSize() == {2,1,1}", RoomDataB->GetSize(), FIntVector {2, 1, 1});
|
||||
TestEqual("RoomDataC->GetSize() == {2,2,1}", RoomDataC->GetSize(), FIntVector {2, 2, 1});
|
||||
TestEqual("RoomDataD->GetSize() == {2,2,2}", RoomDataD->GetSize(), FIntVector {2, 2, 2});
|
||||
}
|
||||
|
||||
// GetVolume
|
||||
{
|
||||
TestEqual("RoomDataA->GetVolume() == 1", RoomDataA->GetVolume(), 1);
|
||||
TestEqual("RoomDataB->GetVolume() == 2", RoomDataB->GetVolume(), 2);
|
||||
TestEqual("RoomDataC->GetVolume() == 4", RoomDataC->GetVolume(), 4);
|
||||
TestEqual("RoomDataD->GetVolume() == 8", RoomDataD->GetVolume(), 8);
|
||||
}
|
||||
}
|
||||
|
||||
// Test GetVoxelBounds
|
||||
{
|
||||
CREATE_ROOM_DATA(RoomDataA);
|
||||
RoomDataA->Doors.Add({{0, 0, 0}, EDoorDirection::North});
|
||||
|
||||
// Should have one cell at (0,0,0) with a door at (0,0,0)[North]
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({0, 0, 0});
|
||||
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = RoomDataA->GetVoxelBounds();
|
||||
TestEqual("RoomDataA->GetVoxelBounds() == ExpectedBounds", ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataB);
|
||||
RoomDataB->BoundingBoxes[0].SetMinAndMax(FIntVector(-1, 0, 0), FIntVector(2, 1, 1));
|
||||
RoomDataB->Doors.Add({{0, 0, 0}, EDoorDirection::West});
|
||||
RoomDataB->Doors.Add({{1, 0, 0}, EDoorDirection::North});
|
||||
|
||||
// Should have 3 cells at (-1,0,0), (0,0,0), (1,0,0) with doors at (0,0,0)[West] and (1,0,0)[North]
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({-1, 0, 0});
|
||||
ExpectedBounds.AddCell({0, 0, 0});
|
||||
ExpectedBounds.AddCell({1, 0, 0});
|
||||
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({-1, 0, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
ExpectedBounds.SetCellConnection({1, 0, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({1, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({1, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({1, 0, 0}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({1, 0, 0}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = RoomDataB->GetVoxelBounds();
|
||||
TestEqual("RoomDataB->GetVoxelBounds() == ExpectedBounds", ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataC);
|
||||
RoomDataC->BoundingBoxes[0].SetMinAndMax(FIntVector(0, 0, -1), FIntVector(1, 1, 2));
|
||||
RoomDataC->Doors.Add({{0, 0, 0}, EDoorDirection::North});
|
||||
RoomDataC->Doors.Add({{0, 0, 1}, EDoorDirection::South});
|
||||
|
||||
// Should have 3 cells at (0,0,-1), (0,0,0), (0,0,1) with doors at (0,0,0)[North] and (0,0,1)[South]
|
||||
{
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell({0, 0, -1});
|
||||
ExpectedBounds.AddCell({0, 0, 0});
|
||||
ExpectedBounds.AddCell({0, 0, 1});
|
||||
|
||||
ExpectedBounds.SetCellConnection({0, 0, -1}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, -1}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, -1}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, -1}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, -1}, FVoxelBounds::EDirection::Down, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 0}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::North, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::West, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::South, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::East, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
ExpectedBounds.SetCellConnection({0, 0, 1}, FVoxelBounds::EDirection::Up, FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall));
|
||||
|
||||
FVoxelBounds ConvertedBounds = RoomDataC->GetVoxelBounds();
|
||||
TestEqual("RoomDataC->GetVoxelBounds() == ExpectedBounds", ConvertedBounds, ExpectedBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Test Room Constraints
|
||||
{
|
||||
#define CHECK_CONSTRAINTS(DATA) URoomData::DoesPassAllConstraints(nullptr, DATA, FIntVector::ZeroValue, EDoorDirection::North)
|
||||
|
||||
TestFalse("null data should fail.", CHECK_CONSTRAINTS(nullptr));
|
||||
|
||||
CREATE_DATA_ASSET(UConstraintPass, Pass);
|
||||
CREATE_DATA_ASSET(UConstraintFail, Fail);
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataA);
|
||||
TestTrue("No constraint should pass.", CHECK_CONSTRAINTS(RoomDataA.Get()));
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataB);
|
||||
RoomDataB->Constraints.Add(Pass.Get());
|
||||
TestTrue("One passing constraint should pass.", CHECK_CONSTRAINTS(RoomDataB.Get()));
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataC);
|
||||
RoomDataC->Constraints.Add(Fail.Get());
|
||||
TestFalse("One failing constraint should fail.", CHECK_CONSTRAINTS(RoomDataC.Get()));
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataD);
|
||||
RoomDataD->Constraints.Add(Pass.Get());
|
||||
RoomDataD->Constraints.Add(Pass.Get());
|
||||
RoomDataD->Constraints.Add(Pass.Get());
|
||||
TestTrue("All passing constraint should pass.", CHECK_CONSTRAINTS(RoomDataD.Get()));
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataE);
|
||||
RoomDataE->Constraints.Add(Fail.Get());
|
||||
RoomDataE->Constraints.Add(Pass.Get());
|
||||
RoomDataE->Constraints.Add(Pass.Get());
|
||||
TestFalse("First failing constraint should fail.", CHECK_CONSTRAINTS(RoomDataE.Get()));
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataF);
|
||||
RoomDataF->Constraints.Add(Pass.Get());
|
||||
RoomDataF->Constraints.Add(Fail.Get());
|
||||
RoomDataF->Constraints.Add(Pass.Get());
|
||||
TestFalse("Second failing constraint should fail.", CHECK_CONSTRAINTS(RoomDataF.Get()));
|
||||
|
||||
CREATE_ROOM_DATA(RoomDataG);
|
||||
RoomDataG->Constraints.Add(Pass.Get());
|
||||
RoomDataG->Constraints.Add(Pass.Get());
|
||||
RoomDataG->Constraints.Add(Fail.Get());
|
||||
TestFalse("Third failing constraint should fail.", CHECK_CONSTRAINTS(RoomDataG.Get()));
|
||||
|
||||
#undef CHECK_CONSTRAINTS
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef CREATE_DATA_ASSET
|
||||
#undef CREATE_ROOM_DATA
|
||||
#undef ADD_DOOR
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright Benoit Pelletier 2024 - 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "UObject/Package.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "UObject/StrongObjectPtr.h"
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
#define FLAG_APPLICATION_CONTEXT EAutomationTestFlags::ApplicationContextMask
|
||||
#else
|
||||
#define FLAG_APPLICATION_CONTEXT EAutomationTestFlags_ApplicationContextMask
|
||||
#endif
|
||||
|
||||
// Utility to create a data asset
|
||||
#define CREATE_DATA_ASSET(VAR_TYPE, VAR_NAME) \
|
||||
TStrongObjectPtr<VAR_TYPE> VAR_NAME(NewObject<VAR_TYPE>(GetTransientPackage(), #VAR_NAME))
|
||||
+522
@@ -0,0 +1,522 @@
|
||||
// Copyright Benoit Pelletier 2025 All Rights Reserved.
|
||||
//
|
||||
// This software is available under different licenses depending on the source from which it was obtained:
|
||||
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
|
||||
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
|
||||
// Please refer to the accompanying LICENSE file for further details.
|
||||
|
||||
#include "CoreTypes.h"
|
||||
#include "Containers/UnrealString.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "ProceduralDungeonTypes.h"
|
||||
#include "VoxelBounds/VoxelBounds.h"
|
||||
#include "Tests/Classes/CustomScoreCallbacks.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
#if WITH_DEV_AUTOMATION_TESTS
|
||||
|
||||
IMPLEMENT_SIMPLE_AUTOMATION_TEST(FVoxelBoundsTest, "ProceduralDungeon.Types.VoxelBounds", FLAG_APPLICATION_CONTEXT | EAutomationTestFlags::SmokeFilter)
|
||||
|
||||
#define SET_CONNECTION(BOUNDS, CELL, DIR, TYPE) \
|
||||
BOUNDS.SetCellConnection(FIntVector CELL, FVoxelBounds::EDirection::DIR, FVoxelBoundsConnection(EVoxelBoundsConnectionType::TYPE));
|
||||
|
||||
bool FVoxelBoundsTest::RunTest(const FString& Parameters)
|
||||
{
|
||||
// Extend Test
|
||||
{
|
||||
FVoxelBounds BoundsA;
|
||||
FVoxelBounds BoundsB;
|
||||
FVoxelBounds BoundsC;
|
||||
|
||||
BoundsA.AddCell(FIntVector(0, -1, 0));
|
||||
BoundsA.AddCell(FIntVector(1, 1, 2));
|
||||
|
||||
BoundsB.AddCell(FIntVector(1, 1, 1));
|
||||
|
||||
TestEqual(TEXT("BoundsA == ({0, -1, 0}, {2, 2, 3})"), BoundsA.GetBounds(), FBoxMinAndMax({0, -1, 0}, {2, 2, 3}));
|
||||
TestEqual(TEXT("BoundsB == ({1, 1, 1}, {2, 2, 2})"), BoundsB.GetBounds(), FBoxMinAndMax({1, 1, 1}, {2, 2, 2}));
|
||||
TestEqual(TEXT("BoundsC == ({0, 0, 0}, {0, 0, 0})"), BoundsC.GetBounds(), FBoxMinAndMax::Invalid);
|
||||
}
|
||||
|
||||
// Comparison Test
|
||||
{
|
||||
FVoxelBounds BoundsA;
|
||||
FVoxelBounds BoundsB;
|
||||
FVoxelBounds BoundsC;
|
||||
FVoxelBounds BoundsD;
|
||||
FVoxelBounds BoundsE;
|
||||
|
||||
BoundsA.AddCell(FIntVector(0, 0, 0));
|
||||
BoundsA.AddCell(FIntVector(1, 1, 1));
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsA, (1, 1, 1), South, Door);
|
||||
|
||||
// Setting the cell connections in a different way is intentional here
|
||||
auto& CellA = BoundsB.AddCell(FIntVector(0, 0, 0));
|
||||
auto& CellB = BoundsB.AddCell(FIntVector(1, 1, 1));
|
||||
CellA[static_cast<uint8>(FVoxelBounds::EDirection::North)] = FVoxelBoundsConnection(EVoxelBoundsConnectionType::Wall);
|
||||
CellB[static_cast<uint8>(FVoxelBounds::EDirection::South)] = FVoxelBoundsConnection(EVoxelBoundsConnectionType::Door);
|
||||
|
||||
BoundsC.AddCell(FIntVector(0, 0, 0));
|
||||
BoundsC.AddCell(FIntVector(1, 1, 1));
|
||||
SET_CONNECTION(BoundsC, (0, 0, 0), North, Wall);
|
||||
|
||||
TestEqual(TEXT("BoundsA == BoundsB"), BoundsA, BoundsB);
|
||||
TestNotEqual(TEXT("BoundsA != BoundsC"), BoundsA, BoundsC);
|
||||
TestTrue(TEXT("BoundsD is not valid"), !BoundsD.IsValid());
|
||||
TestNotEqual(TEXT("BoundsA != BoundsD"), BoundsA, BoundsD);
|
||||
}
|
||||
|
||||
// Transform Test
|
||||
{
|
||||
FVoxelBounds Bounds;
|
||||
Bounds.AddCell(FIntVector(0, 0, 0));
|
||||
Bounds.AddCell(FIntVector(1, 1, 1));
|
||||
SET_CONNECTION(Bounds, (0, 0, 0), North, Wall);
|
||||
SET_CONNECTION(Bounds, (1, 1, 1), South, Door);
|
||||
|
||||
// Offset by 0
|
||||
{
|
||||
TestEqual(TEXT("Bounds + (0,0,0) == OffsetBounds"), Bounds + FIntVector {0, 0, 0}, Bounds);
|
||||
}
|
||||
|
||||
// Simple addition
|
||||
{
|
||||
FVoxelBounds OffsetBounds;
|
||||
OffsetBounds.AddCell(FIntVector(1, -1, 1));
|
||||
OffsetBounds.AddCell(FIntVector(2, 0, 2));
|
||||
SET_CONNECTION(OffsetBounds, (1, -1, 1), North, Wall);
|
||||
SET_CONNECTION(OffsetBounds, (2, 0, 2), South, Door);
|
||||
|
||||
TestEqual(TEXT("Bounds + (1,-1,1) == OffsetBounds"), Bounds + FIntVector {1, -1, 1}, OffsetBounds);
|
||||
}
|
||||
|
||||
// Simple subtraction
|
||||
{
|
||||
FVoxelBounds OffsetBounds;
|
||||
OffsetBounds.AddCell(FIntVector(-1, 1, -1));
|
||||
OffsetBounds.AddCell(FIntVector(0, 2, 0));
|
||||
SET_CONNECTION(OffsetBounds, (-1, 1, -1), North, Wall);
|
||||
SET_CONNECTION(OffsetBounds, (0, 2, 0), South, Door);
|
||||
TestEqual(TEXT("Bounds - (1,-1,1) == OffsetBounds"), Bounds - FIntVector {1, -1, 1}, OffsetBounds);
|
||||
}
|
||||
|
||||
// North rotation
|
||||
{
|
||||
TestEqual(TEXT("Bounds.Rotate(North) == Bounds"), Rotate(Bounds, EDoorDirection::North), Bounds);
|
||||
}
|
||||
|
||||
// East rotation
|
||||
{
|
||||
FVoxelBounds RotatedBounds;
|
||||
RotatedBounds.AddCell(FIntVector(0, 0, 0));
|
||||
RotatedBounds.AddCell(FIntVector(-1, 1, 1));
|
||||
SET_CONNECTION(RotatedBounds, (0, 0, 0), East, Wall);
|
||||
SET_CONNECTION(RotatedBounds, (-1, 1, 1), West, Door);
|
||||
FVoxelBounds NewBounds = Rotate(Bounds, EDoorDirection::East);
|
||||
TestEqual(TEXT("Bounds.Rotate(East) == RotatedBounds"), NewBounds, RotatedBounds);
|
||||
}
|
||||
|
||||
// South rotation
|
||||
{
|
||||
FVoxelBounds RotatedBounds;
|
||||
RotatedBounds.AddCell(FIntVector(0, 0, 0));
|
||||
RotatedBounds.AddCell(FIntVector(-1, -1, 1));
|
||||
SET_CONNECTION(RotatedBounds, (0, 0, 0), South, Wall);
|
||||
SET_CONNECTION(RotatedBounds, (-1, -1, 1), North, Door);
|
||||
TestEqual(TEXT("Bounds.Rotate(South) == RotatedBounds"), Rotate(Bounds, EDoorDirection::South), RotatedBounds);
|
||||
}
|
||||
|
||||
// West rotation
|
||||
{
|
||||
FVoxelBounds RotatedBounds;
|
||||
RotatedBounds.AddCell(FIntVector(0, 0, 0));
|
||||
RotatedBounds.AddCell(FIntVector(1, -1, 1));
|
||||
SET_CONNECTION(RotatedBounds, (0, 0, 0), West, Wall);
|
||||
SET_CONNECTION(RotatedBounds, (1, -1, 1), East, Door);
|
||||
FVoxelBounds NewBounds = Rotate(Bounds, EDoorDirection::West);
|
||||
TestEqual(TEXT("Bounds.Rotate(West) == RotatedBounds"), NewBounds, RotatedBounds);
|
||||
}
|
||||
}
|
||||
|
||||
// Concatenation Test
|
||||
{
|
||||
FVoxelBounds BoundsA;
|
||||
BoundsA.AddCell(FIntVector(0, 0, 0));
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), North, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), East, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), South, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), West, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds BoundsB;
|
||||
BoundsB.AddCell(FIntVector(1, 0, 0));
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), South, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds BoundsC = BoundsA + BoundsB;
|
||||
FVoxelBounds BoundsD = BoundsB + BoundsA;
|
||||
|
||||
TestEqual(TEXT("BoundsC == BoundsD"), BoundsC, BoundsD);
|
||||
TestEqual(TEXT("BoundsC.GetBounds() == BoundsD.GetBounds()"), BoundsC.GetBounds(), BoundsD.GetBounds());
|
||||
|
||||
FVoxelBounds ExpectedBounds;
|
||||
ExpectedBounds.AddCell(FIntVector(0, 0, 0));
|
||||
ExpectedBounds.AddCell(FIntVector(1, 0, 0));
|
||||
SET_CONNECTION(ExpectedBounds, (0, 0, 0), East, Door);
|
||||
SET_CONNECTION(ExpectedBounds, (0, 0, 0), South, Door);
|
||||
SET_CONNECTION(ExpectedBounds, (0, 0, 0), West, Door);
|
||||
SET_CONNECTION(ExpectedBounds, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(ExpectedBounds, (0, 0, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(ExpectedBounds, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(ExpectedBounds, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(ExpectedBounds, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(ExpectedBounds, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(ExpectedBounds, (1, 0, 0), Down, Wall);
|
||||
|
||||
TestEqual(TEXT("BoundsC == ExpectedBounds"), BoundsC, ExpectedBounds);
|
||||
}
|
||||
|
||||
// Subtraction test
|
||||
{
|
||||
FVoxelBounds BoundsA;
|
||||
BoundsA.AddCell(FIntVector(0, 0, 0));
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), North, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), East, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), South, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), West, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds BoundsB;
|
||||
BoundsB.AddCell(FIntVector(1, 0, 0));
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), South, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds BoundsC;
|
||||
BoundsC.AddCell(FIntVector(0, 0, 0));
|
||||
BoundsC.AddCell(FIntVector(1, 0, 0));
|
||||
SET_CONNECTION(BoundsC, (0, 0, 0), East, Door);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 0), South, Door);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 0), West, Door);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsC, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds ExpectedBoundsCMinusA;
|
||||
ExpectedBoundsCMinusA.AddCell(FIntVector(1, 0, 0));
|
||||
SET_CONNECTION(ExpectedBoundsCMinusA, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusA, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusA, (1, 0, 0), South, Door);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusA, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusA, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusA, (1, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds ExpectedBoundsCMinusB;
|
||||
ExpectedBoundsCMinusB.AddCell(FIntVector(0, 0, 0));
|
||||
SET_CONNECTION(ExpectedBoundsCMinusB, (0, 0, 0), North, Wall);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusB, (0, 0, 0), East, Door);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusB, (0, 0, 0), South, Door);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusB, (0, 0, 0), West, Door);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusB, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(ExpectedBoundsCMinusB, (0, 0, 0), Down, Wall);
|
||||
|
||||
FVoxelBounds BoundsCMinusA = BoundsC - BoundsA;
|
||||
FVoxelBounds BoundsCMinusB = BoundsC - BoundsB;
|
||||
|
||||
TestEqual(TEXT("BoundsC - BoundsA == ExpectedBoundsCMinusA"), BoundsCMinusA, ExpectedBoundsCMinusA);
|
||||
TestEqual(TEXT("BoundsC - BoundsB == ExpectedBoundsCMinusB"), BoundsCMinusB, ExpectedBoundsCMinusB);
|
||||
|
||||
// Non commutative
|
||||
FVoxelBounds BoundsAMinusC = BoundsA - BoundsC;
|
||||
FVoxelBounds BoundsBMinusC = BoundsB - BoundsC;
|
||||
|
||||
TestFalse(TEXT("BoundsA - BoundsC is invalid"), BoundsAMinusC.IsValid());
|
||||
TestFalse(TEXT("BoundsB - BoundsC is invalid"), BoundsBMinusC.IsValid());
|
||||
}
|
||||
|
||||
// DoesFitOutside Test
|
||||
{
|
||||
// Bounds of 2x2x2
|
||||
// +---+-o-+ + +---+
|
||||
// |100 110| |111|
|
||||
// +-o-+ + + +-o-+
|
||||
// |010|
|
||||
// + +---+ + + +
|
||||
FVoxelBounds DungeonBounds;
|
||||
DungeonBounds.AddCell(FIntVector(1, 0, 0));
|
||||
DungeonBounds.AddCell(FIntVector(0, 1, 0));
|
||||
DungeonBounds.AddCell(FIntVector(1, 1, 0));
|
||||
DungeonBounds.AddCell(FIntVector(1, 1, 1));
|
||||
|
||||
SET_CONNECTION(DungeonBounds, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 0, 0), South, Door);
|
||||
SET_CONNECTION(DungeonBounds, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 0, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(DungeonBounds, (0, 1, 0), West, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (0, 1, 0), East, Door);
|
||||
SET_CONNECTION(DungeonBounds, (0, 1, 0), South, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (0, 1, 0), Up, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (0, 1, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 0), North, Door);
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 0), East, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 1), North, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 1), East, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 1), South, Door);
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 1), West, Wall);
|
||||
SET_CONNECTION(DungeonBounds, (1, 1, 1), Up, Wall);
|
||||
|
||||
// Bounds that fit in the available space
|
||||
{
|
||||
// Bounds that fits perfectly in the available space
|
||||
// + + +
|
||||
//
|
||||
// +-o-+ +
|
||||
// |000|
|
||||
// +---+ +
|
||||
FVoxelBounds BoundsA;
|
||||
BoundsA.AddCell(FIntVector(0, 0, 0));
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), North, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), South, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Down, Wall);
|
||||
|
||||
// Like BoundsA but with a door facing a wall
|
||||
// + + +
|
||||
//
|
||||
// +-o-+ +
|
||||
// |000o
|
||||
// +---+ +
|
||||
FVoxelBounds BoundsB;
|
||||
BoundsB.AddCell(FIntVector(0, 0, 0));
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), North, Door);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), East, Door);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), South, Wall);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), Down, Wall);
|
||||
|
||||
// Bounds bigger that fits perfectly in the available space
|
||||
// +-o-+ +
|
||||
// |101|
|
||||
// + + +
|
||||
// |001o
|
||||
// +---+ +
|
||||
FVoxelBounds BoundsC;
|
||||
BoundsC.AddCell(FIntVector(0, 0, 1));
|
||||
BoundsC.AddCell(FIntVector(1, 0, 1));
|
||||
|
||||
SET_CONNECTION(BoundsC, (0, 0, 1), East, Door);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 1), South, Wall);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 1), Up, Wall);
|
||||
SET_CONNECTION(BoundsC, (0, 0, 1), Down, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsC, (1, 0, 1), North, Door);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 1), East, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 1), Up, Wall);
|
||||
SET_CONNECTION(BoundsC, (1, 0, 1), Down, Wall);
|
||||
|
||||
// Like BoundsC but with a door facing a wall
|
||||
// +---+ +
|
||||
// |101o
|
||||
// + + +
|
||||
// |001o
|
||||
// +---+ +
|
||||
FVoxelBounds BoundsD;
|
||||
BoundsD.AddCell(FIntVector(0, 0, 1));
|
||||
BoundsD.AddCell(FIntVector(1, 0, 1));
|
||||
|
||||
SET_CONNECTION(BoundsD, (0, 0, 1), East, Door);
|
||||
SET_CONNECTION(BoundsD, (0, 0, 1), South, Wall);
|
||||
SET_CONNECTION(BoundsD, (0, 0, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsD, (0, 0, 1), Up, Wall);
|
||||
SET_CONNECTION(BoundsD, (0, 0, 1), Down, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsD, (1, 0, 1), North, Wall);
|
||||
SET_CONNECTION(BoundsD, (1, 0, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsD, (1, 0, 1), East, Door);
|
||||
SET_CONNECTION(BoundsD, (1, 0, 1), Up, Wall);
|
||||
SET_CONNECTION(BoundsD, (1, 0, 1), Down, Wall);
|
||||
|
||||
// Bounds outside the dungeon bounds
|
||||
// + +---+ + +-o-+
|
||||
// |210| |211|
|
||||
// + +-o-+ + +---+
|
||||
FVoxelBounds BoundsE;
|
||||
BoundsE.AddCell(FIntVector(2, 1, 0));
|
||||
BoundsE.AddCell(FIntVector(2, 1, 1));
|
||||
SET_CONNECTION(BoundsE, (2, 1, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 0), South, Door);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsE, (2, 1, 1), North, Door);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 1), East, Wall);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 1), South, Wall);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsE, (2, 1, 1), Up, Wall);
|
||||
|
||||
// Like BoundsE but with a door facing a wall
|
||||
// + +---+ + +---+
|
||||
// |210| |211|
|
||||
// + +-o-+ + +-o-+
|
||||
FVoxelBounds BoundsF;
|
||||
BoundsF.AddCell(FIntVector(2, 1, 0));
|
||||
BoundsF.AddCell(FIntVector(2, 1, 1));
|
||||
SET_CONNECTION(BoundsF, (2, 1, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 0), South, Door);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 0), Down, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsF, (2, 1, 1), North, Wall);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 1), East, Wall);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 1), South, Door);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsF, (2, 1, 1), Up, Wall);
|
||||
|
||||
// Bounds bigger with a connected door
|
||||
// + + +
|
||||
//
|
||||
// +-o-+-o-+
|
||||
// |001 011|
|
||||
// +---+---+
|
||||
FVoxelBounds BoundsG;
|
||||
BoundsG.AddCell(FIntVector(0, 0, 1));
|
||||
BoundsG.AddCell(FIntVector(0, 1, 1));
|
||||
|
||||
SET_CONNECTION(BoundsG, (0, 0, 1), North, Door);
|
||||
SET_CONNECTION(BoundsG, (0, 0, 1), South, Wall);
|
||||
SET_CONNECTION(BoundsG, (0, 0, 1), West, Wall);
|
||||
SET_CONNECTION(BoundsG, (0, 0, 1), Up, Wall);
|
||||
SET_CONNECTION(BoundsG, (0, 0, 1), Down, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsG, (0, 1, 1), North, Door);
|
||||
SET_CONNECTION(BoundsG, (0, 1, 1), South, Wall);
|
||||
SET_CONNECTION(BoundsG, (0, 1, 1), East, Wall);
|
||||
SET_CONNECTION(BoundsG, (0, 1, 1), Up, Wall);
|
||||
SET_CONNECTION(BoundsG, (0, 1, 1), Down, Wall);
|
||||
|
||||
int32 _ScoreA, _ScoreB, _ScoreC, _ScoreD, _ScoreE, _ScoreF, _ScoreG;
|
||||
TestTrue(TEXT("BoundsA fit in DungeonBounds"), BoundsA.GetCompatibilityScore(DungeonBounds, _ScoreA));
|
||||
TestTrue(TEXT("BoundsB fit in DungeonBounds"), BoundsB.GetCompatibilityScore(DungeonBounds, _ScoreB));
|
||||
TestTrue(TEXT("BoundsC fit in DungeonBounds"), BoundsC.GetCompatibilityScore(DungeonBounds, _ScoreC));
|
||||
TestTrue(TEXT("BoundsD fit in DungeonBounds"), BoundsD.GetCompatibilityScore(DungeonBounds, _ScoreD));
|
||||
TestTrue(TEXT("BoundsE fit in DungeonBounds"), BoundsE.GetCompatibilityScore(DungeonBounds, _ScoreE));
|
||||
TestTrue(TEXT("BoundsF fit in DungeonBounds"), BoundsF.GetCompatibilityScore(DungeonBounds, _ScoreF));
|
||||
TestTrue(TEXT("BoundsG fit in DungeonBounds"), BoundsG.GetCompatibilityScore(DungeonBounds, _ScoreG));
|
||||
|
||||
// Order of fitting: E(G) > A > C(F) > B > D
|
||||
TestTrue(TEXT("BoundsE fit better than BoundsA"), _ScoreE > _ScoreA);
|
||||
TestTrue(TEXT("BoundsA fit better than BoundsC"), _ScoreA > _ScoreC);
|
||||
TestTrue(TEXT("BoundsC fit better than BoundsB"), _ScoreC > _ScoreB);
|
||||
TestTrue(TEXT("BoundsB fit better than BoundsD"), _ScoreB > _ScoreD);
|
||||
TestTrue(TEXT("BoundsG fit equally as BoundsE"), _ScoreG == _ScoreE);
|
||||
TestTrue(TEXT("BoundsC fit equally as BoundsF"), _ScoreC == _ScoreF);
|
||||
}
|
||||
|
||||
// Bounds that do not fit in the available space
|
||||
{
|
||||
// Bounds that does not fit in the available space
|
||||
// +---+ +
|
||||
// |100|
|
||||
// +-o-+ +
|
||||
//
|
||||
// + + +
|
||||
FVoxelBounds BoundsA;
|
||||
BoundsA.AddCell(FIntVector(1, 0, 0));
|
||||
SET_CONNECTION(BoundsA, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsA, (1, 0, 0), North, Door);
|
||||
SET_CONNECTION(BoundsA, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsA, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsA, (1, 0, 0), Down, Wall);
|
||||
|
||||
int32 ScoreA;
|
||||
TestFalse(TEXT("BoundsA does not fit in DungeonBounds"), BoundsA.GetCompatibilityScore(DungeonBounds, ScoreA));
|
||||
|
||||
// Bigger bounds that does not fit in the available space
|
||||
// +---+ +
|
||||
// |100|
|
||||
// + + +
|
||||
// |000|
|
||||
// +-o-+ +
|
||||
FVoxelBounds BoundsB;
|
||||
BoundsB.AddCell(FIntVector(0, 0, 0));
|
||||
BoundsB.AddCell(FIntVector(1, 0, 0));
|
||||
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), South, Door);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsB, (0, 0, 0), Up, Wall);
|
||||
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), North, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsB, (1, 0, 0), Down, Wall);
|
||||
|
||||
int32 ScoreB;
|
||||
TestFalse(TEXT("BoundsB does not fit in DungeonBounds"), BoundsB.GetCompatibilityScore(DungeonBounds, ScoreB));
|
||||
}
|
||||
|
||||
// Custom Score Test
|
||||
{
|
||||
UCustomScoreCallback* CustomCallbacks = NewObject<UCustomScoreCallback>();
|
||||
|
||||
FScoreCallback ZeroScore;
|
||||
ZeroScore.BindDynamic(CustomCallbacks, &UCustomScoreCallback::ZeroScore);
|
||||
|
||||
FScoreCallback NeverPassScore;
|
||||
NeverPassScore.BindDynamic(CustomCallbacks, &UCustomScoreCallback::NeverPass);
|
||||
|
||||
FVoxelBounds BoundsA;
|
||||
BoundsA.AddCell(FIntVector(0, 0, 0));
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), North, Door);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), East, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), South, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), West, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Up, Wall);
|
||||
SET_CONNECTION(BoundsA, (0, 0, 0), Down, Wall);
|
||||
|
||||
int32 ScoreA;
|
||||
TestTrue(TEXT("BoundsA does fit in DungeonBounds with ZeroScore"), BoundsA.GetCompatibilityScore(DungeonBounds, ScoreA, ZeroScore));
|
||||
TestEqual(TEXT("BoundsA ZeroScore should have score of 0"), ScoreA, 0);
|
||||
|
||||
int32 ScoreB;
|
||||
TestFalse(TEXT("BoundsA does not fit in DungeonBounds ith NeverPass"), BoundsA.GetCompatibilityScore(DungeonBounds, ScoreB, NeverPassScore));
|
||||
TestNotEqual(TEXT("BoundsA NeverPass should have score different from ZeroScore"), ScoreA, ScoreB);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#undef SET_CONNECTION
|
||||
|
||||
#endif //WITH_DEV_AUTOMATION_TESTS
|
||||
Reference in New Issue
Block a user