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

View File

@@ -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

View File

@@ -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();
});
});
}

View File

@@ -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)

View File

@@ -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();
});
}

View File

@@ -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();
});
});
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View 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();
};

File diff suppressed because it is too large Load Diff

View File

@@ -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();
};

View File

@@ -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;
};

View File

@@ -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;
};