// Copyright (c) Meta Platforms, Inc. and affiliates. #include "MRUtilityKitDestructibleMesh.h" #include "Engine/GameInstance.h" #include "MRUtilityKitBPLibrary.h" #include "MRUtilityKitSubsystem.h" #include "MRUtilityKitAnchor.h" #include "MRUtilityKitRoom.h" #include "MRUtilityKitTelemetry.h" #include "OculusXRTelemetry.h" #include "Tasks/Task.h" constexpr const char* RESERVED_MESH_SEGMENT_TAG = "ReservedMeshSegment"; UMRUKDestructibleMeshComponent::UMRUKDestructibleMeshComponent(const FObjectInitializer& ObjectInitializer) : UProceduralMeshComponent(ObjectInitializer) { PrimaryComponentTick.bCanEverTick = true; } void UMRUKDestructibleMeshComponent::SegmentMesh(const TArray& MeshPositions, const TArray& MeshIndices, const TArray& SegmentationPoints) { TaskResult = UE::Tasks::Launch(UE_SOURCE_LOCATION, [this, MeshPositions, MeshIndices, SegmentationPoints]() { TArray Segments; FMRUKMeshSegment ReservedMeshSegment; const FVector ReservedMin(ReservedTop, -1.0, -1.0); const FVector ReservedMax(ReservedBottom, -1.0, -1.0); UMRUKBPLibrary::CreateMeshSegmentation(MeshPositions, MeshIndices, SegmentationPoints, ReservedMin, ReservedMax, Segments, ReservedMeshSegment); return TPair, FMRUKMeshSegment>{ MoveTemp(Segments), MoveTemp(ReservedMeshSegment) }; }); SetComponentTickEnabled(true); } void UMRUKDestructibleMeshComponent::BeginPlay() { Super::BeginPlay(); SetComponentTickEnabled(false); } void UMRUKDestructibleMeshComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) { Super::TickComponent(DeltaTime, TickType, ThisTickFunction); if (!TaskResult.IsCompleted()) { return; } const auto& [MeshSegments, ReservedMeshSegment] = TaskResult.GetResult(); for (int32 i = 0; i < MeshSegments.Num(); ++i) { const auto& [Positions, Indices] = MeshSegments[i]; const FString ProcMeshName = FString::Printf(TEXT("DestructibleMeshSegment%d"), i); const auto ProcMesh = NewObject(GetOwner(), *ProcMeshName); const FAttachmentTransformRules TransformRules{ EAttachmentRule::KeepRelative, false }; ProcMesh->AttachToComponent(GetOwner()->GetRootComponent(), TransformRules); ProcMesh->RegisterComponent(); ProcMesh->ComponentTags.AddUnique(TEXT("DestructibleMeshSegment")); GetOwner()->AddInstanceComponent(ProcMesh); ProcMesh->CreateMeshSection(0, Positions, Indices, {}, {}, {}, {}, true); if (GlobalMeshMaterial) { ProcMesh->SetMaterial(0, GlobalMeshMaterial); } } if (ReservedMeshSegment.Indices.Num() > 0) { const auto ProcMesh = NewObject(GetOwner(), TEXT("ReservedMeshSegment")); const FAttachmentTransformRules TransformRules{ EAttachmentRule::KeepRelative, false }; ProcMesh->AttachToComponent(GetOwner()->GetRootComponent(), TransformRules); ProcMesh->RegisterComponent(); ProcMesh->ComponentTags.AddUnique(RESERVED_MESH_SEGMENT_TAG); GetOwner()->AddInstanceComponent(ProcMesh); ProcMesh->CreateMeshSection(0, ReservedMeshSegment.Positions, ReservedMeshSegment.Indices, {}, {}, {}, {}, true); if (GlobalMeshMaterial) { ProcMesh->SetMaterial(0, GlobalMeshMaterial); } } SetComponentTickEnabled(false); OnMeshesGenerated.Broadcast(); } AMRUKDestructibleGlobalMesh::AMRUKDestructibleGlobalMesh() { DestructibleMeshComponent = CreateDefaultSubobject(TEXT("DestructibleMesh")); SetRootComponent(DestructibleMeshComponent); } void AMRUKDestructibleGlobalMesh::CreateDestructibleMesh(AMRUKRoom* Room) { const UMRUKSubsystem* MRUKSubsystem = GetWorld()->GetGameInstance()->GetSubsystem(); if (!Room) { Room = MRUKSubsystem->GetCurrentRoom(); } if (!Room) { UE_LOG(LogMRUK, Warning, TEXT("Could not find a room for the destructible mesh")); return; } if (!Room->GlobalMeshAnchor) { UE_LOG(LogMRUK, Warning, TEXT("No global mesh available for creating a destructible mesh")); return; } const AMRUKAnchor* GlobalMesh = Room->GlobalMeshAnchor; UProceduralMeshComponent* GlobalProcMesh = Cast(GlobalMesh->GetComponentByClass(UProceduralMeshComponent::StaticClass())); if (!GlobalProcMesh) { Room->LoadGlobalMeshFromDevice(); } if (!GlobalProcMesh) { UE_LOG(LogMRUK, Warning, TEXT("Could not load a triangle mesh from the global mesh")); return; } // Attach to the global mesh const FAttachmentTransformRules AttachmentTransformRules{ EAttachmentRule::KeepRelative, false }; AttachToActor(Room->GlobalMeshAnchor, AttachmentTransformRules); // Get global mesh data ensure(GlobalProcMesh); ensure(GlobalProcMesh->ComponentHasTag("GlobalMesh")); FProcMeshSection* ProcMeshSection = GlobalProcMesh->GetProcMeshSection(0); TArray MeshPositions; MeshPositions.SetNum(ProcMeshSection->ProcVertexBuffer.Num()); for (int32 i = 0; i < ProcMeshSection->ProcVertexBuffer.Num(); ++i) { MeshPositions[i] = ProcMeshSection->ProcVertexBuffer[i].Position * GetWorldSettings()->WorldToMeters; } const TArray& MeshIndices = ProcMeshSection->ProcIndexBuffer; TArray SegmentationPointsWS = UMRUKBPLibrary::ComputeRoomBoxGrid(Room, MaxPointsCount, PointsPerUnitX, PointsPerUnitY); TArray SegmentationPointsLS; SegmentationPointsLS.SetNum(SegmentationPointsWS.Num()); const FTransform T = GlobalMesh->GetActorTransform().Inverse(); for (int32 i = 0; i < SegmentationPointsWS.Num(); ++i) { SegmentationPointsLS[i] = T.TransformPosition(SegmentationPointsWS[i]); } DestructibleMeshComponent->SegmentMesh(MeshPositions, MeshIndices, SegmentationPointsLS); } void AMRUKDestructibleGlobalMesh::RemoveGlobalMeshSegment(UPrimitiveComponent* Mesh) { if (!Mesh->ComponentTags.Contains(RESERVED_MESH_SEGMENT_TAG)) { // Only remove mesh segments that are allowed to be destroyed Mesh->DestroyComponent(); } } void AMRUKDestructibleGlobalMeshSpawner::BeginPlay() { Super::BeginPlay(); OculusXRTelemetry::TScopedMarker Event(static_cast(GetTypeHash(this))); if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly) { const auto Subsystem = GetGameInstance()->GetSubsystem(); if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete) { AddDestructibleGlobalMesh(Subsystem->GetCurrentRoom()); } else { // Only listen for the room created event in case no current room was available yet Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated); // Remove destructible meshes as soon as the room gets removed Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved); } } else if (SpawnMode == EMRUKSpawnMode::AllRooms) { const auto Subsystem = GetGameInstance()->GetSubsystem(); for (auto Room : Subsystem->Rooms) { AddDestructibleGlobalMesh(Room); } // Listen for new rooms that get created Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated); // Remove destructible meshes as soon as the room gets removed Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved); } } void AMRUKDestructibleGlobalMeshSpawner::OnRoomCreated(AMRUKRoom* Room) { if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem()->GetCurrentRoom() != Room) { // Skip this room if it is not the current room return; } AddDestructibleGlobalMesh(Room); } void AMRUKDestructibleGlobalMeshSpawner::OnRoomRemoved(AMRUKRoom* Room) { if (!IsValid(Room)) { return; } if (AMRUKDestructibleGlobalMesh** Mesh = SpawnedMeshes.Find(Room)) { (*Mesh)->Destroy(); SpawnedMeshes.Remove(Room); } } AMRUKDestructibleGlobalMesh* AMRUKDestructibleGlobalMeshSpawner::FindDestructibleMeshForRoom(AMRUKRoom* Room) { if (AMRUKDestructibleGlobalMesh** Mesh = SpawnedMeshes.Find(Room)) { return *Mesh; } return nullptr; } AMRUKDestructibleGlobalMesh* AMRUKDestructibleGlobalMeshSpawner::AddDestructibleGlobalMesh(AMRUKRoom* Room) { if (SpawnedMeshes.Contains(Room)) { return SpawnedMeshes[Room]; } AMRUKDestructibleGlobalMesh* Mesh = GetWorld()->SpawnActor(); Mesh->PointsPerUnitX = PointsPerUnitX; Mesh->PointsPerUnitY = PointsPerUnitY; Mesh->MaxPointsCount = MaxPointsCount; Mesh->DestructibleMeshComponent->GlobalMeshMaterial = GlobalMeshMaterial; Mesh->DestructibleMeshComponent->ReservedBottom = ReservedBottom; Mesh->DestructibleMeshComponent->ReservedTop = ReservedTop; Mesh->CreateDestructibleMesh(); SpawnedMeshes.Add(Room, Mesh); return Mesh; }