Giant_Beast_2025/Plugins/MetaXR/Source/MRUtilityKit/Private/MRUtilityKitGuardianSpawner.cpp

236 lines
6.9 KiB
C++

// Copyright (c) Meta Platforms, Inc. and affiliates.
#include "MRUtilityKitGuardianSpawner.h"
#include "Engine/GameInstance.h"
#include "Engine/GameEngine.h"
#include "IXRTrackingSystem.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "MRUtilityKitAnchor.h"
#include "MRUtilityKitGuardian.h"
#include "MRUtilityKitRoom.h"
#include "MRUtilityKitSubsystem.h"
#include "MRUtilityKitTelemetry.h"
AMRUKGuardianSpawner::AMRUKGuardianSpawner()
{
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
}
void AMRUKGuardianSpawner::SetGuardianMaterial(UMaterialInstance* Material)
{
if (!Material)
{
return;
}
GuardianMaterial = Material;
DynamicGuardianMaterial = UMaterialInstanceDynamic::Create(GuardianMaterial, this);
DynamicGuardianMaterial->SetVectorParameterValue(TEXT("WallScale"), FVector(GridDensity));
// Recreate guardian meshes
TArray<AMRUKRoom*> Rooms;
SpawnedGuardians.GetKeys(Rooms);
for (AMRUKRoom* Room : Rooms)
{
SpawnGuardians(Room);
}
}
void AMRUKGuardianSpawner::SpawnGuardians(AMRUKRoom* Room)
{
if (!IsValid(Room))
{
UE_LOG(LogMRUK, Warning, TEXT("Can not spawn Guardians for a room that is a nullptr"));
return;
}
// Remove guardians that are already in this room
DestroyGuardians(Room);
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
Subsystem->OnRoomUpdated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomUpdated);
Subsystem->OnRoomRemoved.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomRemoved);
const auto SpawnGuardian = [this](AMRUKAnchor* Anchor, const TArray<FMRUKPlaneUV>& PlaneUVAdjustments) {
// Create guardian actor
const auto GuardianActor = GetWorld()->SpawnActor<AMRUKGuardian>();
GuardianActor->AttachToComponent(Anchor->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
GuardianActor->SetActorHiddenInGame(IsHidden());
// Generate procedural mesh
const auto ProceduralMesh = NewObject<UProceduralMeshComponent>(GuardianActor, TEXT("GuardianMesh"));
Anchor->GenerateProceduralAnchorMesh(ProceduralMesh, PlaneUVAdjustments, {}, true, false, 0.01);
ProceduralMesh->SetMaterial(0, DynamicGuardianMaterial);
GuardianActor->CreateGuardian(ProceduralMesh);
return GuardianActor;
};
TArray<AMRUKGuardian*> SpawnedActors;
// Attach procedural meshes to the walls first because they are connected.
TArray<FMRUKAnchorWithPlaneUVs> AnchorsWithPlaneUVs;
const TArray<FMRUKTexCoordModes> WallTextureCoordinateModes = { { EMRUKCoordModeU::Metric, EMRUKCoordModeV::Metric } };
Room->ComputeWallMeshUVAdjustments(WallTextureCoordinateModes, AnchorsWithPlaneUVs);
for (const auto& [Anchor, PlaneUVs] : AnchorsWithPlaneUVs)
{
SpawnedActors.Push(SpawnGuardian(Anchor, PlaneUVs));
}
// Attach procedural meshes to the rest of the anchors. The walls have already meshes applied
// because of the first step and will therefore be ignored by this code automatically.
for (const auto& Anchor : Room->AllAnchors)
{
if (!Anchor || Anchor == Room->FloorAnchor || Anchor == Room->CeilingAnchor || Room->IsWallAnchor(Anchor))
{
continue;
}
SpawnedActors.Push(SpawnGuardian(Anchor, {}));
}
SpawnedGuardians.Add(Room, SpawnedActors);
}
void AMRUKGuardianSpawner::SetGridDensity(double Density)
{
GridDensity = Density;
if (DynamicGuardianMaterial)
{
DynamicGuardianMaterial->SetVectorParameterValue(TEXT("WallScale"), FVector(GridDensity));
}
}
void AMRUKGuardianSpawner::Tick(float DeltaSeconds)
{
if (!DynamicGuardianMaterial)
{
return;
}
if (EnableFade)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
const auto CurrentRoom = Subsystem->GetCurrentRoom();
if (!CurrentRoom)
{
return;
}
FQuat HeadsetOrientation;
FVector HeadsetPosition(0.f);
GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HeadsetOrientation, HeadsetPosition);
FVector SurfacePosition = FVector::ZeroVector;
double SurfaceDistance = 0.0;
FMRUKLabelFilter LabelFilter;
LabelFilter.ExcludedLabels = { FMRUKLabels::Ceiling, FMRUKLabels::Floor };
CurrentRoom->TryGetClosestSurfacePosition(HeadsetPosition, SurfacePosition, SurfaceDistance, LabelFilter);
const auto WorldToMeters = GetWorldSettings()->WorldToMeters;
const auto GuardianFade = FMath::Clamp(1.0 - ((SurfaceDistance / WorldToMeters) / GuardianDistance), 0.0, 1.0);
DynamicGuardianMaterial->SetScalarParameterValue(TEXT("Fade"), GuardianFade);
}
}
void AMRUKGuardianSpawner::BeginPlay()
{
Super::BeginPlay();
SetGuardianMaterial(GuardianMaterial);
OculusXRTelemetry::TScopedMarker<MRUKTelemetry::FLoadGuardianMarker> Event(static_cast<int>(GetTypeHash(this)));
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
if (Subsystem->SceneLoadStatus == EMRUKInitStatus::Complete)
{
if (AMRUKRoom* CurrentRoom = Subsystem->GetCurrentRoom())
{
SpawnGuardians(CurrentRoom);
}
}
else
{
// Only listen for the room created event in case no current room was available yet
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomCreated);
}
}
else if (SpawnMode == EMRUKSpawnMode::AllRooms)
{
const auto Subsystem = GetGameInstance()->GetSubsystem<UMRUKSubsystem>();
for (auto Room : Subsystem->Rooms)
{
SpawnGuardians(Room);
}
// Listen for new rooms that get created
Subsystem->OnRoomCreated.AddUniqueDynamic(this, &AMRUKGuardianSpawner::OnRoomCreated);
}
}
#if WITH_EDITOR
void AMRUKGuardianSpawner::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
const auto PropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.Property->GetFName() : NAME_None;
if (PropertyName == GET_MEMBER_NAME_CHECKED(AMRUKGuardianSpawner, GridDensity))
{
SetGridDensity(GridDensity);
}
else if (PropertyName == GET_MEMBER_NAME_CHECKED(AMRUKGuardianSpawner, GuardianMaterial))
{
SetGuardianMaterial(GuardianMaterial);
}
Super::PostEditChangeProperty(PropertyChangedEvent);
}
#endif
void AMRUKGuardianSpawner::OnRoomCreated(AMRUKRoom* Room)
{
if (SpawnMode == EMRUKSpawnMode::CurrentRoomOnly && GetGameInstance()->GetSubsystem<UMRUKSubsystem>()->GetCurrentRoom() != Room)
{
// Skip this room if it is not the current room
return;
}
SpawnGuardians(Room);
}
void AMRUKGuardianSpawner::OnRoomUpdated(AMRUKRoom* Room)
{
if (!SpawnedGuardians.Find(Room))
{
// A room was updated that we don't care about. If we are in current room only mode
// we only want to update the one room we created
return;
}
SpawnGuardians(Room);
}
void AMRUKGuardianSpawner::OnRoomRemoved(AMRUKRoom* Room)
{
DestroyGuardians(Room);
}
void AMRUKGuardianSpawner::DestroyGuardians(AMRUKRoom* Room)
{
if (TArray<AMRUKGuardian*>* Actors = SpawnedGuardians.Find(Room))
{
for (AActor* Actor : *Actors)
{
if (IsValid(Actor))
{
Actor->Destroy();
}
}
Actors->Empty();
SpawnedGuardians.Remove(Room);
}
}