Android build settings + metaxr
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class MRUtilityKitEditor : ModuleRules
|
||||
{
|
||||
public MRUtilityKitEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
bUseUnity = true;
|
||||
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
});
|
||||
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"OculusXRHMD",
|
||||
"OculusXRAnchors",
|
||||
"OculusXRScene",
|
||||
"Json",
|
||||
"UnrealEd",
|
||||
"RHI",
|
||||
"RenderCore",
|
||||
"ProceduralMeshComponent",
|
||||
"MRUtilityKit",
|
||||
});
|
||||
}
|
||||
}
|
||||
87391
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/DistanceMapTestData.h
Normal file
87391
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/DistanceMapTestData.h
Normal file
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();
|
||||
});
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
18
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/MeshActor.h
Normal file
18
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/MeshActor.h
Normal file
@@ -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();
|
||||
};
|
||||
2674
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/TestHelper.cpp
Normal file
2674
Plugins/MetaXR/Source/MRUtilityKitEditor/Private/TestHelper.cpp
Normal file
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();
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
DECLARE_LOG_CATEGORY_EXTERN(LogMRUKEditor, Log, All);
|
||||
|
||||
class FMRUKEditorModule : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
/** IModuleInterface implementation */
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ComponentVisualizer.h"
|
||||
|
||||
class MRUTILITYKITEDITOR_API FMRUKGridSliceResizerVisualizer : public FComponentVisualizer
|
||||
{
|
||||
private:
|
||||
virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
|
||||
};
|
||||
Reference in New Issue
Block a user