Android build settings + metaxr
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "DistanceMapTestData.h"
|
||||
#include "Editor.h"
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
#include "Engine/CanvasRenderTarget2D.h"
|
||||
#include "HAL/PlatformFileManager.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "MRUtilityKitDistanceMapGenerator.h"
|
||||
#include "MRUtilityKitSubsystem.h"
|
||||
#include "TestHelper.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "TextureResource.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
|
||||
static void WriteTGA(const FString& FilePath, const TArray<FColor>& Pixels, int32 Width, int32 Height)
|
||||
{
|
||||
// Create file
|
||||
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenWrite(*FilePath);
|
||||
if (FileHandle)
|
||||
{
|
||||
// TGA File Header
|
||||
uint8 TGAHeader[18] = {};
|
||||
TGAHeader[2] = 2; // Uncompressed Type
|
||||
TGAHeader[12] = Width & 0xFF;
|
||||
TGAHeader[13] = (Width >> 8) & 0xFF;
|
||||
TGAHeader[14] = Height & 0xFF;
|
||||
TGAHeader[15] = (Height >> 8) & 0xFF;
|
||||
TGAHeader[16] = 32; // Bits per Pixel
|
||||
TGAHeader[17] = 0x20; // Top-Down, Non-Interlaced
|
||||
// Write TGA Header
|
||||
FileHandle->Write(TGAHeader, sizeof(TGAHeader));
|
||||
// Write Pixels
|
||||
for (int32 i = 0; i < Width * Height; i++)
|
||||
{
|
||||
const FColor& Pixel = Pixels[i];
|
||||
uint8 BGRA[4] = { Pixel.B, Pixel.G, Pixel.R, Pixel.A };
|
||||
FileHandle->Write(BGRA, sizeof(BGRA));
|
||||
}
|
||||
|
||||
// Close the file
|
||||
delete FileHandle;
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Failed to open file for writing: %s"), *FilePath);
|
||||
}
|
||||
}
|
||||
|
||||
static TArray<FColor> ReadTGAFromMemory(const uint8* Data, size_t DataSize)
|
||||
{
|
||||
TArray<FColor> Pixels;
|
||||
|
||||
// Read TGA Header
|
||||
uint8 TGAHeader[18];
|
||||
memcpy(TGAHeader, Data, sizeof(TGAHeader));
|
||||
// Get image dimensions from header
|
||||
const int32 Width = TGAHeader[12] | (TGAHeader[13] << 8);
|
||||
const int32 Height = TGAHeader[14] | (TGAHeader[15] << 8);
|
||||
|
||||
if (Width * Height > DataSize)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Resize pixel array
|
||||
Pixels.SetNumUninitialized(Width * Height);
|
||||
// Read Pixels
|
||||
uint32 Offset = sizeof(TGAHeader);
|
||||
for (int32 i = 0; i < Width * Height; i++)
|
||||
{
|
||||
uint8 BGRA[4];
|
||||
memcpy(BGRA, Data + Offset, sizeof(BGRA));
|
||||
Offset += sizeof(BGRA);
|
||||
// Convert from BGRA to RGBA
|
||||
Pixels[i] = FColor(BGRA[2], BGRA[1], BGRA[0], BGRA[3]);
|
||||
}
|
||||
|
||||
return Pixels;
|
||||
}
|
||||
|
||||
static TArray<FColor> ReadTGAFromFile(const FString& FilePath)
|
||||
{
|
||||
IFileHandle* FileHandle = FPlatformFileManager::Get().GetPlatformFile().OpenRead(*FilePath);
|
||||
TArray<uint8_t> Data;
|
||||
Data.SetNum(FileHandle->Size());
|
||||
FileHandle->Read(Data.GetData(), Data.Num());
|
||||
return ReadTGAFromMemory(Data.GetData(), Data.Num());
|
||||
}
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
BEGIN_DEFINE_SPEC(FMRUKDistanceMapSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
|
||||
#else
|
||||
BEGIN_DEFINE_SPEC(FMRUKDistanceMapSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
|
||||
#endif
|
||||
END_DEFINE_SPEC(FMRUKDistanceMapSpec)
|
||||
|
||||
void FMRUKDistanceMapSpec::Define()
|
||||
{
|
||||
Describe(TEXT("Distance map"), [this] {
|
||||
BeforeEach([this]() {
|
||||
// Load map
|
||||
const auto ContentDir = FPaths::ProjectContentDir();
|
||||
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
|
||||
StartPIE(true);
|
||||
});
|
||||
|
||||
BeforeEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (!GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session starts
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
It(TEXT("Capture distance map"), [this] {
|
||||
const auto World = GEditor->GetPIEWorldContext()->World();
|
||||
const auto GameInstance = World->GetGameInstance();
|
||||
UMRUKSubsystem* Subsystem = GameInstance->GetSubsystem<UMRUKSubsystem>();
|
||||
Subsystem->LoadSceneFromJsonString(ExampleRoomJson);
|
||||
|
||||
// Create and setup distance map generator
|
||||
const FActorSpawnParameters Params{};
|
||||
AMRUKDistanceMapGenerator* DistanceMapGenerator = World->SpawnActor<AMRUKDistanceMapGenerator>(Params);
|
||||
DistanceMapGenerator->SetActorLocation(FVector(0.0, 0.0, 200.0));
|
||||
DistanceMapGenerator->SetActorRotation(FRotator::MakeFromEuler(FVector(0.0, -90.0, 0.0)));
|
||||
|
||||
UCanvasRenderTarget2D* RenderTarget = Cast<UCanvasRenderTarget2D>(DistanceMapGenerator->CaptureDistanceMap());
|
||||
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
|
||||
// Create an array to store the pixel data
|
||||
TArray<FColor> PixelData;
|
||||
// Read the pixel data from the Render Target
|
||||
ENQUEUE_RENDER_COMMAND(ReadSurfaceCommand)
|
||||
(
|
||||
[RenderTargetResource, &PixelData](FRHICommandListImmediate& RHICmdList) {
|
||||
const FTextureRHIRef Texture2DRHI = RenderTargetResource->GetRenderTargetTexture();
|
||||
RHICmdList.ReadSurfaceData(
|
||||
Texture2DRHI,
|
||||
FIntRect(0, 0, Texture2DRHI->GetSizeX(), Texture2DRHI->GetSizeY()),
|
||||
PixelData,
|
||||
FReadSurfaceDataFlags());
|
||||
});
|
||||
// Wait for the rendering thread to finish executing the command
|
||||
FlushRenderingCommands();
|
||||
|
||||
// Compare result
|
||||
|
||||
const TArray<FColor> ExpectedPixels = ReadTGAFromMemory(DistanceMapTestData, sizeof(DistanceMapTestData));
|
||||
|
||||
bool Success = true;
|
||||
if (!TestEqual(TEXT("Pixel count matches"), PixelData.Num(), ExpectedPixels.Num()))
|
||||
{
|
||||
Success = false;
|
||||
}
|
||||
for (int32 I = 0; I < PixelData.Num() && Success; ++I)
|
||||
{
|
||||
if (!TestEqual(TEXT("R channel matches"), PixelData[I].R, ExpectedPixels[I].R))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
if (!TestEqual(TEXT("G channel matches"), PixelData[I].G, ExpectedPixels[I].G))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
if (!TestEqual(TEXT("B channel matches"), PixelData[I].B, ExpectedPixels[I].B))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
if (!TestEqual(TEXT("A channel matches"), PixelData[I].A, ExpectedPixels[I].A))
|
||||
{
|
||||
Success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Success)
|
||||
{
|
||||
const FString& TestResultsPath = FPaths::Combine(FPaths::ProjectIntermediateDir(), "TestResults");
|
||||
|
||||
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
||||
if (!PlatformFile.DirectoryExists(*TestResultsPath))
|
||||
{
|
||||
PlatformFile.CreateDirectory(*TestResultsPath);
|
||||
}
|
||||
|
||||
const FString& ActualPixelsFilename = FPaths::CreateTempFilename(*TestResultsPath, TEXT("DistanceMapTest_ActualPixels"), TEXT(".tga"));
|
||||
const FString& ExpectedPixelsFilename = FPaths::CreateTempFilename(*TestResultsPath, TEXT("DistanceMapTest_ExpectedPixels"), TEXT(".tga"));
|
||||
// The resulting binary data can be converted to C array with the tool found here https://github.com/AntumDeluge/bin2header
|
||||
WriteTGA(ActualPixelsFilename, PixelData, RenderTarget->SizeX, RenderTarget->SizeY);
|
||||
WriteTGA(ExpectedPixelsFilename, PixelData, RenderTarget->SizeX, RenderTarget->SizeY);
|
||||
UE_LOG(LogTemp, Warning, TEXT("Expected pixels have been saved as tga file in %s"), *ExpectedPixelsFilename);
|
||||
UE_LOG(LogTemp, Warning, TEXT("Actual pixels have been saved as tga file in %s"), *ActualPixelsFilename);
|
||||
}
|
||||
});
|
||||
|
||||
// Caution: Order of these statements is important
|
||||
|
||||
AfterEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session ends
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
AfterEach([]() {
|
||||
// Request end of play session
|
||||
GUnrealEd->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitEditor.h"
|
||||
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "MRUtilityKitGridSliceResizerVisualization.h"
|
||||
#include "MRUtilityKitTelemetry.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FMRUKEditorModule"
|
||||
|
||||
DEFINE_LOG_CATEGORY(LogMRUKEditor);
|
||||
|
||||
void FMRUKEditorModule::StartupModule()
|
||||
{
|
||||
if (GUnrealEd)
|
||||
{
|
||||
const auto ResizerVisualizer = MakeShared<FMRUKGridSliceResizerVisualizer>();
|
||||
GUnrealEd->RegisterComponentVisualizer(UMRUKGridSliceResizerComponent::StaticClass()->GetFName(), ResizerVisualizer);
|
||||
ResizerVisualizer->OnRegister();
|
||||
}
|
||||
}
|
||||
|
||||
void FMRUKEditorModule::ShutdownModule()
|
||||
{
|
||||
if (GUnrealEd)
|
||||
{
|
||||
GUnrealEd->UnregisterComponentVisualizer(UMRUKGridSliceResizerComponent::StaticClass()->GetFName());
|
||||
}
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FMRUKEditorModule, MRUtilityKitEditor)
|
||||
@@ -0,0 +1,165 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "MRUtilityKitGeometry.h"
|
||||
#include "TestHelper.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
#include "Editor.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
double CalculateTriangleArea(const FVector2D& P1, const FVector2D& P2, const FVector2D& P3)
|
||||
{
|
||||
return (P1.X * (P2.Y - P3.Y) + P2.X * (P3.Y - P1.Y) + P3.X * (P1.Y - P2.Y)) / 2.0f;
|
||||
}
|
||||
|
||||
// Use the triangulated area as a proxy to ensure the triangulation worked as expected
|
||||
double CalculateTriangulatedArea(const TArray<FVector2D>& Vertices, const TArray<int32>& Indices)
|
||||
{
|
||||
double Area = 0.0;
|
||||
for (int i = 0; i < Indices.Num(); i += 3)
|
||||
{
|
||||
const FVector2D& P1 = Vertices[Indices[i]];
|
||||
const FVector2D& P2 = Vertices[Indices[i + 1]];
|
||||
const FVector2D& P3 = Vertices[Indices[i + 2]];
|
||||
const double TriangleArea = CalculateTriangleArea(P1, P2, P3);
|
||||
check(TriangleArea >= 0.0);
|
||||
Area += TriangleArea;
|
||||
}
|
||||
|
||||
return Area;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
BEGIN_DEFINE_SPEC(FMRUKGeometrySpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
|
||||
#else
|
||||
BEGIN_DEFINE_SPEC(FMRUKGeometrySpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
|
||||
#endif
|
||||
|
||||
void SetupMRUKSubsystem();
|
||||
void TeardownMRUKSubsystem();
|
||||
END_DEFINE_SPEC(FMRUKGeometrySpec)
|
||||
|
||||
void FMRUKGeometrySpec::SetupMRUKSubsystem()
|
||||
{
|
||||
BeforeEach([this]() {
|
||||
// Load map and start play in editor
|
||||
const auto ContentDir = FPaths::ProjectContentDir();
|
||||
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
|
||||
StartPIE(true);
|
||||
});
|
||||
|
||||
BeforeEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (!GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session starts
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void FMRUKGeometrySpec::TeardownMRUKSubsystem()
|
||||
{
|
||||
// Caution: Order of these statements is important
|
||||
|
||||
AfterEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session ends
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
AfterEach([]() {
|
||||
// Request end of play session
|
||||
GUnrealEd->RequestEndPlayMap();
|
||||
});
|
||||
}
|
||||
|
||||
void FMRUKGeometrySpec::Define()
|
||||
{
|
||||
Describe(TEXT("Triangulation"), [this] {
|
||||
SetupMRUKSubsystem();
|
||||
|
||||
It(TEXT("Triangulate quad"), [this] {
|
||||
const TArray<FVector2f> TestPolygon = { { 0.0f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon({ TestPolygon }, Vertices, Indices);
|
||||
TestEqual(TEXT("Correct number of indices"), 6, Indices.Num());
|
||||
TestEqual(TEXT("Correct area triangulated"), 1.0, CalculateTriangulatedArea(Vertices, Indices));
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate quad with hole"), [this] {
|
||||
const TArray<TArray<FVector2f>> Polygons = { { { 0.0f, 0.0f }, { 2.0f, 0.0f }, { 2.0f, 2.0f }, { 0.0f, 2.0f } }, { { 0.5f, 0.5f }, { 0.5f, 1.5f }, { 1.5f, 1.5f }, { 1.5f, 0.5f } } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon(Polygons, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 24);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 3.0);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate quad with four holes"), [this] {
|
||||
TArray<TArray<FVector2f>> Polygons = { { { 0.0f, 0.0f }, { 4.0f, 0.0f }, { 4.0f, 4.0f }, { 0.0f, 4.0f } } };
|
||||
for (int32 I = 0; I < 4; ++I)
|
||||
{
|
||||
const FVector2f Offset(0.5 + 2.0 * (I / 2), 0.5 + 2.0 * (I % 2));
|
||||
Polygons.Push({ Offset + FVector2f(0.0, 0.0), Offset + FVector2f(0.0, 1.0), Offset + FVector2f(1.0, 1.0), Offset + FVector2f(1.0, 0.0) });
|
||||
}
|
||||
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon(Polygons, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 66);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 12.0);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate quad with two close holes"), [this] {
|
||||
const TArray<TArray<FVector2f>> Polygons = {
|
||||
{
|
||||
{ 101.985214, 113.8258 },
|
||||
{ -101.985214, 113.8258 },
|
||||
{ -101.985214, -113.8258 },
|
||||
{ 101.985214, -113.8258 },
|
||||
},
|
||||
{ { 18.395055731633885, 9.0596833 }, { -72.518264268366110, 9.0596833 }, { -72.518264268366110, 67.2252527 }, { 18.395055731633885, 67.2252527 } },
|
||||
{ { 18.395055731633885, -53.4203167 }, { -72.518264268366110, -53.4203167 }, { -72.518264268366110, 4.7452569 }, { 18.395055731633885, 4.7452569 } },
|
||||
};
|
||||
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon(Polygons, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 42);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 35858.143857, 0.001);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate LShape"), [this] {
|
||||
const TArray<FVector2f> TestPolygon = { { 0.0, 0.0 }, { 2.0, 0.0 }, { 2.0, 2.0 }, { 1.0, 2.0 }, { 1.0, 1.0 }, { 0.0, 1.0 } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon({ TestPolygon }, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 12);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 3.0);
|
||||
});
|
||||
|
||||
It(TEXT("Triangulate CShape"), [this] {
|
||||
const TArray<FVector2f> TestPolygon = { { 0.0, 0.0 }, { 2.0, 0.0 }, { 2.0, 1.0 }, { 1.0, 1.0 }, { 1.0, 2.0 }, { 2.0, 2.0 }, { 2.0, 3.0 }, { 0.0, 3.0 } };
|
||||
TArray<FVector2D> Vertices;
|
||||
TArray<int32> Indices;
|
||||
MRUKTriangulatePolygon({ TestPolygon }, Vertices, Indices);
|
||||
|
||||
TestEqual(TEXT("Correct number of indices"), Indices.Num(), 18);
|
||||
TestEqual(TEXT("Correct area triangulated"), CalculateTriangulatedArea(Vertices, Indices), 5.0);
|
||||
});
|
||||
|
||||
TeardownMRUKSubsystem();
|
||||
});
|
||||
}
|
||||
+194
@@ -0,0 +1,194 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "Editor/UnrealEdEngine.h"
|
||||
#include "Editor.h"
|
||||
#include "GridSliceResizerTestData.h"
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "Misc/AutomationTest.h"
|
||||
#include "Misc/EngineVersionComparison.h"
|
||||
#include "TestHelper.h"
|
||||
#include "Tests/AutomationEditorCommon.h"
|
||||
#include "UnrealEdGlobals.h"
|
||||
|
||||
#if UE_VERSION_OLDER_THAN(5, 5, 0)
|
||||
BEGIN_DEFINE_SPEC(FMRUKGridSliceResizerSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags::ApplicationContextMask)
|
||||
#else
|
||||
BEGIN_DEFINE_SPEC(FMRUKGridSliceResizerSpec, TEXT("MR Utility Kit"), EAutomationTestFlags::ProductFilter | EAutomationTestFlags_ApplicationContextMask)
|
||||
#endif
|
||||
END_DEFINE_SPEC(FMRUKGridSliceResizerSpec)
|
||||
|
||||
void FMRUKGridSliceResizerSpec::Define()
|
||||
{
|
||||
Describe(TEXT("Grid Slice Resizer"), [this] {
|
||||
BeforeEach([this]() {
|
||||
// Load map
|
||||
const auto ContentDir = FPaths::ProjectContentDir();
|
||||
FAutomationEditorCommonUtils::LoadMap(ContentDir + "/Common/Maps/TestLevel.umap");
|
||||
StartPIE(true);
|
||||
});
|
||||
|
||||
BeforeEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (!GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session starts
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
It(TEXT("Slice mesh"), [this] {
|
||||
const auto World = GEditor->GetPIEWorldContext()->World();
|
||||
const FActorSpawnParameters Params{};
|
||||
AMeshResizer* Resizer = World->SpawnActor<AMeshResizer>(Params);
|
||||
|
||||
UMRUKGridSliceResizerComponent* ResizerComponent = Resizer->GridSliceResizerComponent;
|
||||
|
||||
struct FTestData
|
||||
{
|
||||
FVector Scale;
|
||||
uint8 ScaleCenterMode;
|
||||
double BorderXNegative;
|
||||
double BorderXPositive;
|
||||
double BorderYNegative;
|
||||
double BorderYPositive;
|
||||
double BorderZNegative;
|
||||
double BorderZPositive;
|
||||
FVector SlicerPivotOffset;
|
||||
const TArray<FVector>& ExpectedPositions;
|
||||
};
|
||||
|
||||
TArray TestDataContainer = {
|
||||
// Test without pivot offset
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 0.0),
|
||||
ExpectedPositionsPivotCenter,
|
||||
},
|
||||
// Test with pivot offset
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, -40.0),
|
||||
ExpectedPositionsPivotOffset,
|
||||
},
|
||||
// Test with pivot outside of bounding box
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, -160.0),
|
||||
ExpectedPositionsPivotOutside,
|
||||
},
|
||||
// Test scaled down with pivot offset
|
||||
FTestData{
|
||||
FVector(0.2, 0.2, 0.2),
|
||||
(uint8)EMRUKScaleCenterMode::XAxis | (uint8)EMRUKScaleCenterMode::YAxis | (uint8)EMRUKScaleCenterMode::ZAxis,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 20.0),
|
||||
ExpectedPositionsScaledDown,
|
||||
},
|
||||
// Test without center scale
|
||||
FTestData{
|
||||
FVector(2.0, 2.0, 2.0),
|
||||
(uint8)EMRUKScaleCenterMode::None,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 0.0),
|
||||
ExpectedPositionsCenterNotScaled,
|
||||
},
|
||||
// Test without center scale but scaled down
|
||||
FTestData{
|
||||
FVector(0.2, 0.2, 0.2),
|
||||
(uint8)EMRUKScaleCenterMode::None,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
0.8,
|
||||
FVector(0.0, 0.0, 0.0),
|
||||
ExpectedPositionsScaledDownCenterNotScaled,
|
||||
}
|
||||
};
|
||||
|
||||
constexpr double Tolerance = 0.001;
|
||||
|
||||
for (const FTestData& TestData : TestDataContainer)
|
||||
{
|
||||
ResizerComponent->ScaleCenterMode = TestData.ScaleCenterMode;
|
||||
ResizerComponent->BorderXNegative = TestData.BorderXNegative;
|
||||
ResizerComponent->BorderXPositive = TestData.BorderXPositive;
|
||||
ResizerComponent->BorderYNegative = TestData.BorderYNegative;
|
||||
ResizerComponent->BorderYPositive = TestData.BorderYPositive;
|
||||
ResizerComponent->BorderZNegative = TestData.BorderZNegative;
|
||||
ResizerComponent->BorderZPositive = TestData.BorderZPositive;
|
||||
ResizerComponent->SlicerPivotOffset = TestData.SlicerPivotOffset;
|
||||
|
||||
Resizer->SetActorScale3D(TestData.Scale);
|
||||
Resizer->GridSliceResizerComponent->SliceMesh();
|
||||
|
||||
const FProcMeshSection* MeshSection = Resizer->GridSliceResizerComponent->ProcMesh->GetProcMeshSection(0);
|
||||
if (!TestNotNull(TEXT("Mesh section is not null"), MeshSection))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const TArray<uint32>& Indices = MeshSection->ProcIndexBuffer;
|
||||
const TArray<FProcMeshVertex>& Vertices = MeshSection->ProcVertexBuffer;
|
||||
|
||||
TestEqual(TEXT("Triangles count matches"), Indices.Num(), ExpectedTriangles.Num());
|
||||
for (int32 I = 0; I < Indices.Num(); ++I)
|
||||
{
|
||||
TestEqual(TEXT("Index matches"), Indices[I], ExpectedTriangles[I]);
|
||||
}
|
||||
|
||||
TestEqual(TEXT("Positions count matches"), Vertices.Num(), TestData.ExpectedPositions.Num());
|
||||
for (int32 I = 0; I < Vertices.Num(); ++I)
|
||||
{
|
||||
TestEqual(TEXT("Position matches"), Vertices[I].Position, TestData.ExpectedPositions[I], Tolerance);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Caution: Order of these statements is important
|
||||
|
||||
AfterEach(EAsyncExecution::ThreadPool, []() {
|
||||
while (GEditor->IsPlayingSessionInEditor())
|
||||
{
|
||||
// Wait until play session ends
|
||||
FGenericPlatformProcess::Yield();
|
||||
}
|
||||
});
|
||||
|
||||
AfterEach([]() {
|
||||
// Request end of play session
|
||||
GUnrealEd->RequestEndPlayMap();
|
||||
});
|
||||
});
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MRUtilityKitGridSliceResizerVisualization.h"
|
||||
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "SceneManagement.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
void DrawSliceBorder(FPrimitiveDrawInterface* PDI, FVector Scale, FVector Pivot, FVector ScaledPivot, const UProceduralMeshComponent* ProcMesh, const FVector& Size, double BorderPos, double BorderNeg, int32 Axis)
|
||||
{
|
||||
// Scaled bounding box of the mesh that gets displayed
|
||||
FBox BBox = ProcMesh->GetLocalBounds().GetBox();
|
||||
BBox.Min *= Scale;
|
||||
BBox.Max *= Scale;
|
||||
|
||||
const FVector InvSize = FVector(1.0, 1.0, 1.0) / Scale;
|
||||
// Unscaled bounding box
|
||||
const FBox BBoxOriginal = FBox(BBox.Min * InvSize, BBox.Max * InvSize);
|
||||
|
||||
double Positive = BBox.Max[Axis] - (BBoxOriginal.Max[Axis] - (FMath::Abs(BBoxOriginal.Max[Axis] - Pivot[Axis]) * BorderPos + Pivot[Axis]));
|
||||
if (Positive + Pivot[Axis] < 0.0)
|
||||
{
|
||||
// Clamp to the bounding box in case the bounding box is smaller than the stubs
|
||||
Positive = BBox.Max[Axis];
|
||||
}
|
||||
if (Pivot[Axis] > BBox.Max[Axis])
|
||||
{
|
||||
// Clamp to the pivot if the pivot is outside of the bounding box
|
||||
Positive = FMath::Min(ScaledPivot[Axis], Positive);
|
||||
}
|
||||
|
||||
double Negative = BBox.Min[Axis] - (BBoxOriginal.Min[Axis] - (-FMath::Abs(BBoxOriginal.Min[Axis] - Pivot[Axis]) * BorderNeg + Pivot[Axis]));
|
||||
if (Negative - Pivot[Axis] > 0.0)
|
||||
{
|
||||
// Clamp to the bounding box in case the bounding box is smaller than the stubs
|
||||
Negative = BBox.Min[Axis];
|
||||
}
|
||||
if (Pivot[Axis] < BBox.Min[Axis])
|
||||
{
|
||||
// Clamp to the pivot if the pivot is outside of the bounding box
|
||||
Negative = FMath::Max(ScaledPivot[Axis], Negative);
|
||||
}
|
||||
|
||||
double PosNeg[2] = { Positive, Negative };
|
||||
for (int32 J = 0; J < 2; ++J)
|
||||
{
|
||||
FVector Max;
|
||||
FVector Min;
|
||||
for (int32 I = 0; I < 3; ++I)
|
||||
{
|
||||
if (I == Axis)
|
||||
{
|
||||
Min[I] = PosNeg[J];
|
||||
Max[I] = PosNeg[J];
|
||||
}
|
||||
else
|
||||
{
|
||||
Min[I] = BBox.Min[I];
|
||||
Max[I] = BBox.Max[I];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix visualization for in editor playing. Meaning applying the world transform.
|
||||
const FBox Box(Min, Max);
|
||||
DrawWireBox(PDI, Box, FLinearColor(1.0f, 0.0f, 0.0f), SDPG_Foreground, 0.2f);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FMRUKGridSliceResizerVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
|
||||
{
|
||||
const UMRUKGridSliceResizerComponent* Resizer = Cast<const UMRUKGridSliceResizerComponent>(Component);
|
||||
if (!Resizer || !Resizer->GetOwner())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const FVector Size = Resizer->GetOwner()->GetActorScale();
|
||||
const FVector Location = Resizer->GetComponentLocation();
|
||||
const FVector Pivot = Location + Resizer->SlicerPivotOffset;
|
||||
const FVector ScaledPivot = Location + Resizer->SlicerPivotOffset * Size;
|
||||
|
||||
if (Resizer->bDebugDrawPivot)
|
||||
{
|
||||
// Draw pivot
|
||||
DrawWireSphere(PDI, ScaledPivot, FLinearColor(0.0f, 1.0f, 1.0f), 2.0, 16, SDPG_Foreground, 0.5f);
|
||||
}
|
||||
|
||||
if (const auto ProcMesh = Resizer->GetOwner()->GetComponentByClass<UProceduralMeshComponent>())
|
||||
{
|
||||
if (Resizer->bDebugDrawBorderX)
|
||||
{
|
||||
DrawSliceBorder(PDI, Size, Pivot, ScaledPivot, ProcMesh, Size, Resizer->BorderXPositive, Resizer->BorderXNegative, 0);
|
||||
}
|
||||
if (Resizer->bDebugDrawBorderY)
|
||||
{
|
||||
DrawSliceBorder(PDI, Size, Pivot, ScaledPivot, ProcMesh, Size, Resizer->BorderYPositive, Resizer->BorderYNegative, 1);
|
||||
}
|
||||
if (Resizer->bDebugDrawBorderZ)
|
||||
{
|
||||
DrawSliceBorder(PDI, Size, Pivot, ScaledPivot, ProcMesh, Size, Resizer->BorderZPositive, Resizer->BorderZNegative, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#include "MeshActor.h"
|
||||
#include "UObject/ConstructorHelpers.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
#include "Engine/StaticMesh.h"
|
||||
|
||||
AMeshActor::AMeshActor()
|
||||
{
|
||||
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeFinder(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'"));
|
||||
Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
|
||||
if (CubeFinder.Succeeded())
|
||||
{
|
||||
Mesh->SetStaticMesh(CubeFinder.Object);
|
||||
}
|
||||
SetRootComponent(Mesh);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MeshActor.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class AMeshActor : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UStaticMeshComponent* Mesh;
|
||||
|
||||
public:
|
||||
AMeshActor();
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "MRUtilityKitAnchor.h"
|
||||
#include "MRUtilityKitGridSliceResizer.h"
|
||||
#include "TestHelper.generated.h"
|
||||
|
||||
bool StartPIE(bool bSimulateInEditor);
|
||||
|
||||
UCLASS()
|
||||
class URoomAndAnchorObserver : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
TArray<AMRUKAnchor*> AnchorsCreated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKAnchor*> AnchorsUpdated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKAnchor*> AnchorsRemoved;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<AMRUKRoom*> RoomsCreated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKRoom*> RoomsUpdated;
|
||||
UPROPERTY()
|
||||
TArray<AMRUKRoom*> RoomsRemoved;
|
||||
|
||||
UFUNCTION()
|
||||
void OnAnchorCreated(AMRUKAnchor* Anchor);
|
||||
UFUNCTION()
|
||||
void OnAnchorUpdated(AMRUKAnchor* Anchor);
|
||||
UFUNCTION()
|
||||
void OnAnchorRemoved(AMRUKAnchor* Anchor);
|
||||
UFUNCTION()
|
||||
void OnRoomCreated(AMRUKRoom* Room);
|
||||
UFUNCTION()
|
||||
void OnRoomUpdated(AMRUKRoom* Room);
|
||||
UFUNCTION()
|
||||
void OnRoomRemoved(AMRUKRoom* Room);
|
||||
|
||||
void Clear();
|
||||
};
|
||||
|
||||
extern const TCHAR* ExampleRoomJson;
|
||||
extern const TCHAR* ExampleRoomFurnitureAddedJson;
|
||||
extern const TCHAR* ExampleRoomMoreFurnitureAddedJson;
|
||||
extern const TCHAR* ExampleRoomFurnitureModifiedJson;
|
||||
extern const TCHAR* ExampleOtherRoomJson;
|
||||
|
||||
UCLASS()
|
||||
class AMeshResizer : public AActor
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
UMRUKGridSliceResizerComponent* GridSliceResizerComponent;
|
||||
|
||||
AMeshResizer();
|
||||
};
|
||||
Reference in New Issue
Block a user