// Copyright IHY. Vertical Partition Streaming plugin. #include "VerticalPartitionManager.h" #include "VerticalPartitionDebugComponent.h" #include "VerticalPartitionRuntimeSubsystem.h" #include "VerticalPartitionDescriptor.h" #include "VerticalPartitionVolume.h" #include "VerticalPartitionStatics.h" #include "VerticalPartitionLog.h" #include "EngineUtils.h" // TActorIterator #include "GameFramework/Pawn.h" #include "GameFramework/PlayerController.h" #include "Camera/PlayerCameraManager.h" #include "Engine/World.h" AVerticalPartitionManager::AVerticalPartitionManager() { PrimaryActorTick.bCanEverTick = true; PrimaryActorTick.bStartWithTickEnabled = true; DebugComponent = CreateDefaultSubobject(TEXT("DebugComponent")); } void AVerticalPartitionManager::BeginPlay() { Super::BeginPlay(); UWorld* World = GetWorld(); // Resolve a volume if none was assigned. if (!Volume && World) { for (TActorIterator It(World); It; ++It) { Volume = *It; break; } } // Resolve the descriptor: explicit -> volume's -> none. UVerticalPartitionDescriptor* Desc = Descriptor.LoadSynchronous(); if (!Desc && Volume) { Desc = Volume->Descriptor.LoadSynchronous(); } if (!Desc) { UE_LOG(LogVerticalPartition, Warning, TEXT("[VP] Manager found no descriptor. Build the partition first. Streaming disabled.")); SetActorTickEnabled(false); return; } const FBox Bounds = Volume ? Volume->GetVolumeBounds() : Desc->VolumeBounds; if (UVerticalPartitionRuntimeSubsystem* Sub = UVerticalPartitionRuntimeSubsystem::Get(this)) { Sub->RegisterDescriptor(Desc, Bounds); } bSnapNextUpdate = true; } void AVerticalPartitionManager::EndPlay(const EEndPlayReason::Type EndPlayReason) { if (UVerticalPartitionRuntimeSubsystem* Sub = UVerticalPartitionRuntimeSubsystem::Get(this)) { Sub->UnregisterDescriptor(); } Super::EndPlay(EndPlayReason); } APawn* AVerticalPartitionManager::ResolveTrackedPawn() { if (TrackedPawn.IsValid()) { return TrackedPawn.Get(); } if (UWorld* World = GetWorld()) { if (APlayerController* PC = World->GetFirstPlayerController()) { return PC->GetPawn(); } } return nullptr; } bool AVerticalPartitionManager::BuildContext(FVerticalStreamingContext& OutCtx, float DeltaSeconds, bool& bOutTeleport) { bOutTeleport = false; APawn* Pawn = ResolveTrackedPawn(); UVerticalPartitionRuntimeSubsystem* Sub = UVerticalPartitionRuntimeSubsystem::Get(this); if (!Pawn || !Sub || !Sub->IsReady()) { return false; } const FVerticalPartitionSettings& S = Sub->GetSettings(); const FBox Bounds = Sub->GetVolumeBounds(); const FVector Pos = Pawn->GetActorLocation(); // Velocity (smoothed) + teleport detection. if (bHavePrevPos && DeltaSeconds > KINDA_SMALL_NUMBER) { const FVector RawVel = (Pos - PrevTrackedPos) / DeltaSeconds; SmoothedVelocity = FMath::Lerp(SmoothedVelocity, RawVel, 0.5f); const float Jump = FMath::Abs(Pos.Z - PrevTrackedPos.Z); if (Jump > S.CellHeight * 3.f) { bOutTeleport = true; SmoothedVelocity = FVector::ZeroVector; } } PrevTrackedPos = Pos; bHavePrevPos = true; OutCtx.PlayerWorldZ = Pos.Z; OutCtx.PlayerVelocityZ = SmoothedVelocity.Z; OutCtx.PlayerCellZ = UVerticalPartitionStatics::GetCellZFromWorldZ(Pos.Z, Bounds.Min.Z, S.CellHeight); OutCtx.bUseXY = S.bUseXYSubCells; if (S.bUseXYSubCells) { OutCtx.PlayerCellX = UVerticalPartitionStatics::GetCellXFromWorldX(Pos.X, Bounds.Min.X, S.XYCellSize); OutCtx.PlayerCellY = UVerticalPartitionStatics::GetCellYFromWorldY(Pos.Y, Bounds.Min.Y, S.XYCellSize); } if (const APlayerController* PC = Cast(Pawn->GetController())) { if (PC->PlayerCameraManager) { OutCtx.CameraPitchDeg = PC->PlayerCameraManager->GetCameraRotation().Pitch; } } return true; } void AVerticalPartitionManager::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); UVerticalPartitionRuntimeSubsystem* Sub = UVerticalPartitionRuntimeSubsystem::Get(this); if (!Sub || !Sub->IsReady()) { return; } UpdateAccumulator += DeltaSeconds; const float Interval = FMath::Max(0.01f, Sub->GetSettings().UpdateInterval); if (!bSnapNextUpdate && UpdateAccumulator < Interval) { return; } const float Elapsed = UpdateAccumulator; UpdateAccumulator = 0.f; FVerticalStreamingContext Ctx; bool bTeleport = false; if (!BuildContext(Ctx, Elapsed, bTeleport)) { return; } const bool bSnap = bSnapNextUpdate || bTeleport; bSnapNextUpdate = false; Sub->UpdateStreaming(Ctx, Elapsed, bSnap); } void AVerticalPartitionManager::TeleportToCell(int32 Z) { APawn* Pawn = ResolveTrackedPawn(); UVerticalPartitionRuntimeSubsystem* Sub = UVerticalPartitionRuntimeSubsystem::Get(this); if (!Pawn || !Sub || !Sub->IsReady()) { return; } const FVerticalPartitionSettings& S = Sub->GetSettings(); const FBox Bounds = Sub->GetVolumeBounds(); const FVector Center = Bounds.GetCenter(); const float TargetZ = Bounds.Min.Z + (Z + 0.5f) * S.CellHeight; Pawn->TeleportTo(FVector(Center.X, Center.Y, TargetZ), Pawn->GetActorRotation()); bSnapNextUpdate = true; UE_LOG(LogVerticalPartition, Log, TEXT("[VP] Teleported tracked pawn to cell Z=%d (worldZ=%.0f)"), Z, TargetZ); }