Android build settings + metaxr

This commit is contained in:
2025-05-14 14:00:02 +03:00
parent 6a2bb7475e
commit d5aa21f55c
594 changed files with 200530 additions and 2 deletions
@@ -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",
});
}
}
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);
}
@@ -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();
};
@@ -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;
};