592 lines
18 KiB
C++
592 lines
18 KiB
C++
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
|
|
#include "MRUtilityKitBPLibrary.h"
|
|
|
|
#include "MRUtilityKit.h"
|
|
#include "Generated/MRUtilityKitShared.h"
|
|
#include "MRUtilityKitAnchor.h"
|
|
#include "MRUtilityKitSubsystem.h"
|
|
#include "MRUtilityKitSerializationHelpers.h"
|
|
#include "ProceduralMeshComponent.h"
|
|
#include "VectorUtil.h"
|
|
#include "Engine/World.h"
|
|
#include "Engine/GameInstance.h"
|
|
#include "Engine/Engine.h"
|
|
#include "TextureResource.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
|
|
#include "Engine/Texture2D.h"
|
|
#include "Serialization/JsonReader.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
|
|
namespace
|
|
{
|
|
TArray<FVector> RecalculateNormals(const TArray<FVector>& Vertices, const TArray<uint32>& Triangles)
|
|
{
|
|
TArray<FVector> Normals;
|
|
|
|
// Initialize the normals array with zero vectors
|
|
Normals.Init(FVector::ZeroVector, Vertices.Num());
|
|
|
|
// Iterate through each triangle
|
|
for (int32 TriIndex = 0; TriIndex < Triangles.Num(); TriIndex += 3)
|
|
{
|
|
// Get the vertices of the triangle
|
|
FVector VertexA = Vertices[Triangles[TriIndex]];
|
|
FVector VertexB = Vertices[Triangles[TriIndex + 1]];
|
|
FVector VertexC = Vertices[Triangles[TriIndex + 2]];
|
|
|
|
// Calculate the triangle's normal
|
|
const FVector TriangleNormal = FVector::CrossProduct(VertexC - VertexA, VertexB - VertexA).GetSafeNormal();
|
|
|
|
// Add the triangle's normal to each of its vertices' normals
|
|
Normals[Triangles[TriIndex]] += TriangleNormal;
|
|
Normals[Triangles[TriIndex + 1]] += TriangleNormal;
|
|
Normals[Triangles[TriIndex + 2]] += TriangleNormal;
|
|
}
|
|
|
|
// Normalize the vertex normals
|
|
for (FVector& Normal : Normals)
|
|
{
|
|
if (!Normal.IsNearlyZero())
|
|
{
|
|
Normal.Normalize();
|
|
}
|
|
else
|
|
{
|
|
Normal = FVector::UpVector;
|
|
}
|
|
}
|
|
|
|
return Normals;
|
|
}
|
|
|
|
TArray<FProcMeshTangent> RecalculateTangents(const TArray<FVector>& Normals)
|
|
{
|
|
TArray<FProcMeshTangent> Tangents;
|
|
|
|
// Initialize the tangents array with zero tangents
|
|
Tangents.Init(FProcMeshTangent(0.f, 0.f, 0.f), Normals.Num());
|
|
|
|
// Iterate through each normal
|
|
for (int32 NormalIndex = 0; NormalIndex < Normals.Num(); NormalIndex++)
|
|
{
|
|
const FVector& Normal = Normals[NormalIndex];
|
|
|
|
// Calculate a tangent based on the normal
|
|
FVector TangentX = FVector(1.0f, 0.0f, 0.0f);
|
|
|
|
// Gram-Schmidt orthogonalization
|
|
TangentX -= Normal * FVector::DotProduct(TangentX, Normal);
|
|
if (!TangentX.IsNearlyZero())
|
|
{
|
|
TangentX.Normalize();
|
|
}
|
|
else
|
|
{
|
|
TangentX = FVector::UpVector;
|
|
}
|
|
|
|
// Store the tangent in the array
|
|
Tangents[NormalIndex] = FProcMeshTangent(TangentX, false);
|
|
}
|
|
|
|
return Tangents;
|
|
}
|
|
|
|
void SetScaleRecursivelyAdjustingForRotationInternal(USceneComponent* SceneComponent, const FVector& UnRotatedScale, const FQuat& AccumulatedRotation, const FVector& ParentReciprocalScale)
|
|
{
|
|
if (SceneComponent)
|
|
{
|
|
const auto RelativeRotation = SceneComponent->GetRelativeRotationCache().RotatorToQuat(SceneComponent->GetRelativeRotation());
|
|
const auto Rotation = AccumulatedRotation * RelativeRotation;
|
|
const FVector RotatedXAxis = Rotation.GetAxisX();
|
|
const FVector RotatedYAxis = Rotation.GetAxisY();
|
|
const FVector RotatedZAxis = Rotation.GetAxisZ();
|
|
FVector RotatedScale;
|
|
if (FMath::Abs(RotatedXAxis.X) >= UE_INV_SQRT_2)
|
|
{
|
|
RotatedScale.X = UnRotatedScale.X;
|
|
}
|
|
else if (FMath::Abs(RotatedXAxis.Y) >= UE_INV_SQRT_2)
|
|
{
|
|
RotatedScale.X = UnRotatedScale.Y;
|
|
}
|
|
else
|
|
{
|
|
RotatedScale.X = UnRotatedScale.Z;
|
|
}
|
|
|
|
if (FMath::Abs(RotatedYAxis.X) >= UE_INV_SQRT_2)
|
|
{
|
|
RotatedScale.Y = UnRotatedScale.X;
|
|
}
|
|
else if (FMath::Abs(RotatedYAxis.Y) >= UE_INV_SQRT_2)
|
|
{
|
|
RotatedScale.Y = UnRotatedScale.Y;
|
|
}
|
|
else
|
|
{
|
|
RotatedScale.Y = UnRotatedScale.Z;
|
|
}
|
|
|
|
if (FMath::Abs(RotatedZAxis.X) >= UE_INV_SQRT_2)
|
|
{
|
|
RotatedScale.Z = UnRotatedScale.X;
|
|
}
|
|
else if (FMath::Abs(RotatedZAxis.Y) >= UE_INV_SQRT_2)
|
|
{
|
|
RotatedScale.Z = UnRotatedScale.Y;
|
|
}
|
|
else
|
|
{
|
|
RotatedScale.Z = UnRotatedScale.Z;
|
|
}
|
|
|
|
const FVector OldScale = SceneComponent->GetRelativeScale3D();
|
|
const FVector NewScale = ParentReciprocalScale * RotatedScale * OldScale;
|
|
SceneComponent->SetRelativeScale3D(NewScale);
|
|
const FVector NewParentReciprocalScale = ParentReciprocalScale * (OldScale / NewScale);
|
|
for (auto Child : SceneComponent->GetAttachChildren())
|
|
{
|
|
if (Child)
|
|
{
|
|
SetScaleRecursivelyAdjustingForRotationInternal(Child, UnRotatedScale, Rotation, NewParentReciprocalScale);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<FVector> GeneratePoints(const FTransform& Plane, const FBox2D& PlaneBounds, double PointsPerUnitX, double PointsPerUnitY, double WorldToMeters = 100.0)
|
|
{
|
|
const FVector PlaneRight = Plane.GetRotation().GetRightVector();
|
|
const FVector PlaneUp = Plane.GetRotation().GetUpVector();
|
|
const FVector PlaneSize = FVector(PlaneBounds.GetSize().X, PlaneBounds.GetSize().Y, 0.0);
|
|
const FVector PlaneBottomLeft = Plane.GetLocation() - PlaneRight * PlaneSize.X * 0.5f - PlaneUp * PlaneSize.Y * 0.5f;
|
|
|
|
const int32 PointsX = FMath::Max(FMathf::Ceil(PointsPerUnitX * PlaneSize.X) / WorldToMeters, 1);
|
|
const int32 PointsY = FMath::Max(FMathf::Ceil(PointsPerUnitY * PlaneSize.Y) / WorldToMeters, 1);
|
|
|
|
const FVector2D Stride{ PlaneSize.X / (PointsX + 1), PlaneSize.Y / (PointsY + 1) };
|
|
|
|
TArray<FVector> Points;
|
|
Points.SetNum(PointsX * PointsY);
|
|
|
|
for (int Iy = 0; Iy < PointsY; ++Iy)
|
|
{
|
|
for (int Ix = 0; Ix < PointsX; ++Ix)
|
|
{
|
|
const float Dx = (Ix + 1) * Stride.X;
|
|
const float Dy = (Iy + 1) * Stride.Y;
|
|
const FVector Point = PlaneBottomLeft + Dx * PlaneRight + Dy * PlaneUp;
|
|
Points[Ix + Iy * PointsX] = Point;
|
|
}
|
|
}
|
|
|
|
return Points;
|
|
}
|
|
} // namespace
|
|
|
|
UMRUKLoadFromDevice* UMRUKLoadFromDevice::LoadSceneFromDeviceAsync(const UObject* WorldContext
|
|
)
|
|
{
|
|
// We must have a valid contextual world for this action, so we don't even make it
|
|
// unless we can resolve the UWorld from WorldContext.
|
|
UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
|
|
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Create a new UMyDelayAsyncAction, and store function arguments in it.
|
|
UMRUKLoadFromDevice* NewAction = NewObject<UMRUKLoadFromDevice>();
|
|
NewAction->World = World;
|
|
NewAction->RegisterWithGameInstance(World->GetGameInstance());
|
|
return NewAction;
|
|
}
|
|
|
|
void UMRUKLoadFromDevice::Activate()
|
|
{
|
|
const auto Subsystem = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
|
Subsystem->OnSceneLoaded.AddDynamic(this, &UMRUKLoadFromDevice::OnSceneLoaded);
|
|
|
|
{
|
|
Subsystem->LoadSceneFromDevice();
|
|
}
|
|
}
|
|
|
|
void UMRUKLoadFromDevice::OnSceneLoaded(bool Succeeded)
|
|
{
|
|
const auto Subsystem = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
|
|
Subsystem->OnSceneLoaded.RemoveDynamic(this, &UMRUKLoadFromDevice::OnSceneLoaded);
|
|
if (Succeeded)
|
|
{
|
|
Success.Broadcast();
|
|
}
|
|
else
|
|
{
|
|
Failure.Broadcast();
|
|
}
|
|
SetReadyToDestroy();
|
|
}
|
|
|
|
bool UMRUKBPLibrary::LoadGlobalMeshFromDevice(FOculusXRUInt64 SpaceHandle, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision, const UObject* WorldContext)
|
|
{
|
|
ensure(OutProceduralMesh);
|
|
|
|
const UWorld* World = GEngine->GetWorldFromContextObject(WorldContext, EGetWorldErrorMode::ReturnNull);
|
|
if (!ensureAlwaysMsgf(IsValid(WorldContext), TEXT("World Context was not valid.")))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto RoomLayoutManager = World->GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetRoomLayoutManager();
|
|
const bool LoadResult = RoomLayoutManager->LoadTriangleMesh(SpaceHandle.Value, OutProceduralMesh, LoadCollision);
|
|
if (!LoadResult)
|
|
{
|
|
UE_LOG(LogMRUK, Warning, TEXT("Could not load triangle mesh from layout manager"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UMRUKBPLibrary::LoadGlobalMeshFromJsonString(const FString& JsonString, FOculusXRUUID AnchorUUID, UProceduralMeshComponent* OutProceduralMesh, bool LoadCollision)
|
|
{
|
|
ensure(OutProceduralMesh);
|
|
|
|
TSharedPtr<FJsonValue> JsonValue;
|
|
auto JsonReader = TJsonReaderFactory<>::Create(JsonString);
|
|
if (!FJsonSerializer::Deserialize(JsonReader, JsonValue))
|
|
{
|
|
UE_LOG(LogMRUK, Warning, TEXT("Could not deserialize global mesh JSON data"));
|
|
return false;
|
|
}
|
|
|
|
auto JsonObject = JsonValue->AsObject();
|
|
|
|
// Find room
|
|
auto RoomsJson = JsonObject->GetArrayField(TEXT("Rooms"));
|
|
for (const auto& RoomJson : RoomsJson)
|
|
{
|
|
auto RoomObject = RoomJson->AsObject();
|
|
FOculusXRUUID RoomUUID;
|
|
MRUKDeserialize(*RoomObject->GetField<EJson::None>(TEXT("UUID")), RoomUUID);
|
|
if (RoomUUID == AnchorUUID)
|
|
{
|
|
// Find global mesh anchor
|
|
auto AnchorsJson = RoomObject->GetArrayField(TEXT("Anchors"));
|
|
for (const auto& AnchorJson : AnchorsJson)
|
|
{
|
|
auto AnchorObject = AnchorJson->AsObject();
|
|
if (AnchorObject->HasField(TEXT("GlobalMesh")))
|
|
{
|
|
auto GlobalMeshObject = AnchorObject->GetField<EJson::Object>(TEXT("GlobalMesh"))->AsObject();
|
|
|
|
auto PositionsJson = GlobalMeshObject->GetArrayField(TEXT("Positions"));
|
|
TArray<FVector> Positions;
|
|
Positions.Reserve(PositionsJson.Num());
|
|
for (const auto& PositionJson : PositionsJson)
|
|
{
|
|
FVector Position;
|
|
MRUKDeserialize(*PositionJson, Position);
|
|
Positions.Push(Position);
|
|
}
|
|
|
|
auto IndicesJson = GlobalMeshObject->GetArrayField(TEXT("Indices"));
|
|
TArray<int32> Indices;
|
|
Indices.Reserve(IndicesJson.Num());
|
|
for (const auto& IndexJson : IndicesJson)
|
|
{
|
|
double Index;
|
|
MRUKDeserialize(*IndexJson, Index);
|
|
Indices.Push((int32)Index);
|
|
}
|
|
|
|
TArray<FVector> EmptyNormals;
|
|
TArray<FVector2D> EmptyUV;
|
|
TArray<FColor> EmptyVertexColors;
|
|
TArray<FProcMeshTangent> EmptyTangents;
|
|
OutProceduralMesh->CreateMeshSection(0, Positions, Indices, EmptyNormals, EmptyUV, EmptyVertexColors, EmptyTangents, LoadCollision);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogMRUK, Warning, TEXT("Could not find global mesh in room"));
|
|
|
|
return false;
|
|
}
|
|
|
|
void UMRUKBPLibrary::RecalculateProceduralMeshAndTangents(UProceduralMeshComponent* Mesh)
|
|
{
|
|
if (!IsValid(Mesh))
|
|
return;
|
|
|
|
for (int s = 0; s < Mesh->GetNumSections(); ++s)
|
|
{
|
|
FProcMeshSection* Section = Mesh->GetProcMeshSection(s);
|
|
|
|
// Get vertices of the section
|
|
TArray<FVector> Vertices;
|
|
for (FProcMeshVertex Vertex : Section->ProcVertexBuffer)
|
|
{
|
|
Vertices.Add(Vertex.Position);
|
|
}
|
|
|
|
// Calculate normals and tangents
|
|
TArray<FVector> Normals = RecalculateNormals(Vertices, Section->ProcIndexBuffer);
|
|
TArray<FProcMeshTangent> Tangents = RecalculateTangents(Normals);
|
|
TArray<FVector2D> EmptyUV;
|
|
TArray<FColor> EmptyVertexColors;
|
|
|
|
// Update mesh section
|
|
Mesh->UpdateMeshSection(s, Vertices, Normals, EmptyUV, EmptyVertexColors, Tangents);
|
|
}
|
|
}
|
|
|
|
bool UMRUKBPLibrary::IsUnrealEngineMetaFork()
|
|
{
|
|
#if defined(WITH_OCULUS_BRANCH)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
FVector2D UMRUKBPLibrary::ComputeCentroid(const TArray<FVector2D>& PolygonPoints)
|
|
{
|
|
FVector2D Centroid = FVector2D::ZeroVector;
|
|
|
|
double SignedArea = 0.0;
|
|
for (int32 I = 0; I < PolygonPoints.Num(); ++I)
|
|
{
|
|
const double X0 = PolygonPoints[I].X;
|
|
const double Y0 = PolygonPoints[I].Y;
|
|
const double X1 = PolygonPoints[(I + 1) % PolygonPoints.Num()].X;
|
|
const double Y1 = PolygonPoints[(I + 1) % PolygonPoints.Num()].Y;
|
|
|
|
const double A = X0 * Y1 - X1 * Y0;
|
|
SignedArea += A;
|
|
|
|
Centroid.X += (X0 + X1) * A;
|
|
Centroid.Y += (Y0 + Y1) * A;
|
|
}
|
|
|
|
return Centroid / (6.0 * (SignedArea * 0.5));
|
|
}
|
|
|
|
void UMRUKBPLibrary::SetScaleRecursivelyAdjustingForRotation(USceneComponent* SceneComponent, const FVector& UnRotatedScale)
|
|
{
|
|
SetScaleRecursivelyAdjustingForRotationInternal(SceneComponent, UnRotatedScale, FQuat::Identity, FVector::OneVector);
|
|
}
|
|
|
|
FVector UMRUKBPLibrary::ComputeDirectionAwayFromClosestWall(const AMRUKAnchor* Anchor, int& OutCardinalAxisIndex, const TArray<int> ExcludedAxes)
|
|
{
|
|
double ClosestWallDistance = DBL_MAX;
|
|
FVector AwayFromWall{};
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (ExcludedAxes.Contains(i))
|
|
{
|
|
continue;
|
|
}
|
|
// Shoot a ray along the cardinal directions
|
|
// The "Up" (i.e. Z axis) for anchors typically points away from the facing direction, but it depends
|
|
// entirely on how the user defined the volume in scene capture.
|
|
const auto CardinalAxis = (FQuat::MakeFromEuler({ 0.0, 0.0, 90.0 * i }).RotateVector(Anchor->GetActorUpVector()));
|
|
|
|
for (const auto& WallAnchor : Anchor->Room->WallAnchors)
|
|
{
|
|
if (!WallAnchor)
|
|
{
|
|
continue;
|
|
}
|
|
FMRUKHit Hit{};
|
|
if (!WallAnchor->Raycast(Anchor->GetActorLocation(), CardinalAxis, 0.0, Hit))
|
|
{
|
|
continue;
|
|
}
|
|
const auto DistToWall = FVector::Distance(Hit.HitPosition, Anchor->GetActorLocation());
|
|
if (DistToWall < ClosestWallDistance)
|
|
{
|
|
ClosestWallDistance = DistToWall;
|
|
AwayFromWall = -CardinalAxis;
|
|
OutCardinalAxisIndex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
return AwayFromWall;
|
|
}
|
|
|
|
UTexture2D* UMRUKBPLibrary::ConstructTexture2D(UTextureRenderTarget2D* RenderTarget2D, UObject* Outer, const FString& TexName)
|
|
{
|
|
const auto SizeX = RenderTarget2D->SizeX;
|
|
const auto SizeY = RenderTarget2D->SizeY;
|
|
const auto Tex = UTexture2D::CreateTransient(SizeX, SizeY, RenderTarget2D->GetFormat());
|
|
Tex->AddToRoot();
|
|
Tex->Filter = TF_Bilinear;
|
|
Tex->CompressionSettings = TC_Default;
|
|
Tex->SRGB = 0;
|
|
Tex->UpdateResource();
|
|
|
|
FTextureRenderTargetResource* RenderTargetResource = RenderTarget2D->GameThread_GetRenderTargetResource();
|
|
FReadSurfaceDataFlags ReadSurfaceDataFlags;
|
|
ReadSurfaceDataFlags.SetLinearToGamma(false);
|
|
|
|
TArray<FColor> OutBMP;
|
|
RenderTargetResource->ReadPixels(OutBMP, ReadSurfaceDataFlags);
|
|
|
|
FTexture2DMipMap& Mip = Tex->GetPlatformData()->Mips[0];
|
|
void* Data = Mip.BulkData.Lock(LOCK_READ_WRITE);
|
|
FMemory::Memcpy(Data, OutBMP.GetData(), SizeX * SizeY * 4);
|
|
Mip.BulkData.Unlock();
|
|
|
|
Tex->UpdateResource();
|
|
return Tex;
|
|
}
|
|
|
|
FLinearColor UMRUKBPLibrary::GetMatrixColumn(const FMatrix& Matrix, int32 Index)
|
|
{
|
|
ensure(0 <= Index && Index < 4);
|
|
FLinearColor V;
|
|
V.R = Matrix.M[0][Index];
|
|
V.G = Matrix.M[1][Index];
|
|
V.B = Matrix.M[2][Index];
|
|
V.A = Matrix.M[3][Index];
|
|
return V;
|
|
}
|
|
|
|
TArray<FVector> UMRUKBPLibrary::ComputeRoomBoxGrid(const AMRUKRoom* Room, int32 MaxPointsCount, double PointsPerUnitX, double PointsPerUnitY)
|
|
{
|
|
TArray<FVector> AllPoints;
|
|
|
|
const double WorldToMeters = Room->GetWorld()->GetWorldSettings()->WorldToMeters;
|
|
for (const AMRUKAnchor* WallAnchor : Room->WallAnchors)
|
|
{
|
|
|
|
const auto Points = GeneratePoints(WallAnchor->GetTransform(), WallAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
|
AllPoints.Append(Points);
|
|
}
|
|
|
|
// Generate points between floor and ceiling
|
|
const float DistFloorCeiling = Room->CeilingAnchor->GetTransform().GetLocation().Z - Room->FloorAnchor->GetTransform().GetLocation().Z;
|
|
const int32 PlanesCount = FMath::Max(FMathf::Ceil(PointsPerUnitY * DistFloorCeiling) / WorldToMeters, 1);
|
|
const int32 SpaceBetweenPlanes = DistFloorCeiling / PlanesCount;
|
|
for (int i = 1; i < PlanesCount; ++i)
|
|
{
|
|
FTransform Transform = Room->CeilingAnchor->GetTransform();
|
|
Transform.SetLocation(FVector(Transform.GetLocation().X, Transform.GetLocation().Y, Transform.GetLocation().Z - (SpaceBetweenPlanes * i)));
|
|
const auto Points = GeneratePoints(Transform, Room->CeilingAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
|
AllPoints.Append(Points);
|
|
}
|
|
|
|
const auto CeilingPoints = GeneratePoints(Room->CeilingAnchor->GetTransform(), Room->CeilingAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
|
AllPoints.Append(CeilingPoints);
|
|
|
|
const auto FloorPoints = GeneratePoints(Room->FloorAnchor->GetTransform(), Room->FloorAnchor->PlaneBounds, PointsPerUnitX, PointsPerUnitY, WorldToMeters);
|
|
AllPoints.Append(FloorPoints);
|
|
|
|
if (AllPoints.Num() > MaxPointsCount)
|
|
{
|
|
// Shuffle the array
|
|
AllPoints.Sort([](const FVector& /*Item1*/, const FVector& /*Item2*/) {
|
|
return FMath::FRand() < 0.5f;
|
|
});
|
|
|
|
// Randomly remove some points
|
|
int32 PointsToRemoveCount = AllPoints.Num() - MaxPointsCount;
|
|
while (PointsToRemoveCount > 0)
|
|
{
|
|
AllPoints.Pop();
|
|
--PointsToRemoveCount;
|
|
}
|
|
}
|
|
return AllPoints;
|
|
}
|
|
|
|
void UMRUKBPLibrary::CreateMeshSegmentation(const TArray<FVector>& MeshPositions, const TArray<uint32>& MeshIndices,
|
|
const TArray<FVector>& SegmentationPoints, const FVector& ReservedMin, const FVector& ReservedMax,
|
|
TArray<FMRUKMeshSegment>& OutSegments, FMRUKMeshSegment& OutReservedSegment)
|
|
{
|
|
if (!MRUKShared::GetInstance())
|
|
{
|
|
UE_LOG(LogMRUK, Error, TEXT("MRUK shared library is not available. To use this functionality make sure the library is included"));
|
|
return;
|
|
}
|
|
|
|
TArray<FVector3f> MeshPositionsF;
|
|
MeshPositionsF.Reserve(MeshPositions.Num());
|
|
for (const FVector& V : MeshPositions)
|
|
{
|
|
MeshPositionsF.Add(FVector3f(V));
|
|
}
|
|
|
|
TArray<FVector3f> SegmentationPointsF;
|
|
SegmentationPointsF.Reserve(SegmentationPoints.Num());
|
|
for (const FVector& V : SegmentationPoints)
|
|
{
|
|
SegmentationPointsF.Add(FVector3f(V));
|
|
}
|
|
|
|
MRUKShared::MrukMesh3f* MeshSegmentsF = nullptr;
|
|
uint32_t MeshSegmentsCount = 0;
|
|
|
|
MRUKShared::MrukMesh3f ReservedMeshSegmentF{};
|
|
|
|
const FVector3f ReservedMinF(ReservedMin);
|
|
const FVector3f ReservedMaxF(ReservedMax);
|
|
|
|
MRUKShared::GetInstance()->ComputeMeshSegmentation(MeshPositionsF.GetData(), MeshPositionsF.Num(), MeshIndices.GetData(),
|
|
MeshIndices.Num(), SegmentationPointsF.GetData(), SegmentationPointsF.Num(), ReservedMinF, ReservedMaxF, &MeshSegmentsF,
|
|
&MeshSegmentsCount, &ReservedMeshSegmentF);
|
|
|
|
OutSegments.Reserve(MeshSegmentsCount);
|
|
for (uint32_t i = 0; i < MeshSegmentsCount; ++i)
|
|
{
|
|
const MRUKShared::MrukMesh3f& SegmentF = MeshSegmentsF[i];
|
|
if (SegmentF.numIndices == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FMRUKMeshSegment MeshSegment{};
|
|
MeshSegment.Indices.Reserve(SegmentF.numIndices);
|
|
MeshSegment.Positions.Reserve(SegmentF.numVertices);
|
|
for (uint32_t j = 0; j < SegmentF.numIndices; ++j)
|
|
{
|
|
MeshSegment.Indices.Add(SegmentF.indices[j]);
|
|
}
|
|
for (uint32_t j = 0; j < SegmentF.numVertices; ++j)
|
|
{
|
|
const FVector3f& V = SegmentF.vertices[j];
|
|
MeshSegment.Positions.Add({ V.X, V.Y, V.Z });
|
|
}
|
|
|
|
OutSegments.Emplace(MoveTemp(MeshSegment));
|
|
}
|
|
|
|
if (ReservedMeshSegmentF.numIndices && ReservedMeshSegmentF.numVertices)
|
|
{
|
|
OutReservedSegment.Indices.Reserve(ReservedMeshSegmentF.numIndices);
|
|
OutReservedSegment.Positions.Reserve(ReservedMeshSegmentF.numVertices);
|
|
for (uint32_t j = 0; j < ReservedMeshSegmentF.numIndices; ++j)
|
|
{
|
|
OutReservedSegment.Indices.Add(ReservedMeshSegmentF.indices[j]);
|
|
}
|
|
for (uint32_t j = 0; j < ReservedMeshSegmentF.numVertices; ++j)
|
|
{
|
|
const FVector3f& V = ReservedMeshSegmentF.vertices[j];
|
|
OutReservedSegment.Positions.Add({ V.X, V.Y, V.Z });
|
|
}
|
|
}
|
|
|
|
MRUKShared::GetInstance()->FreeMeshSegmentation(MeshSegmentsF, MeshSegmentsCount, &ReservedMeshSegmentF);
|
|
}
|