Compare commits
1 Commits
4a98c6402b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6eca4fc94b |
@@ -0,0 +1,230 @@
|
|||||||
|
#include "PS_Editor_FloorCutManager.h"
|
||||||
|
#include "PS_Editor_MandatoryAnchor.h"
|
||||||
|
#include "PS_Editor_SceneLoader.h"
|
||||||
|
#include "Components/PrimitiveComponent.h"
|
||||||
|
#include "EngineUtils.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
|
||||||
|
bool UPS_Editor_FloorCutManager::ShouldCreateSubsystem(UObject* Outer) const
|
||||||
|
{
|
||||||
|
// Only create on real game worlds — skip preview / inactive worlds.
|
||||||
|
if (const UWorld* World = Cast<UWorld>(Outer))
|
||||||
|
{
|
||||||
|
const EWorldType::Type WT = World->WorldType;
|
||||||
|
return WT == EWorldType::Game || WT == EWorldType::PIE || WT == EWorldType::Editor;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
bEnabled = false;
|
||||||
|
HeightAboveAnchor = 200.f;
|
||||||
|
bAnchorZCached = false;
|
||||||
|
CachedAnchorZ = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::Deinitialize()
|
||||||
|
{
|
||||||
|
// Restore everything we touched, so a world tear-down leaves a clean state.
|
||||||
|
RestoreAll();
|
||||||
|
Snapshots.Empty();
|
||||||
|
Super::Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::SetEnabled(bool bInEnabled)
|
||||||
|
{
|
||||||
|
// Editor-only feature: silently ignore in gameplay mode. The widgets gate calls already,
|
||||||
|
// but this is a belt-and-braces safety so nothing leaks into a SceneLoader-driven runtime.
|
||||||
|
if (!UPS_Editor_SceneLoader::IsInEditorMode())
|
||||||
|
{
|
||||||
|
if (bEnabled)
|
||||||
|
{
|
||||||
|
RestoreAll();
|
||||||
|
bEnabled = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bEnabled == bInEnabled) return;
|
||||||
|
|
||||||
|
bEnabled = bInEnabled;
|
||||||
|
if (bEnabled)
|
||||||
|
{
|
||||||
|
ApplyCutToWorld();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RestoreAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::SetHeightAboveAnchor(float HeightCm)
|
||||||
|
{
|
||||||
|
// Clamp to a sane range matching the UI slider (0–1000 cm).
|
||||||
|
HeightCm = FMath::Clamp(HeightCm, 0.f, 10000.f);
|
||||||
|
if (FMath::IsNearlyEqual(HeightAboveAnchor, HeightCm)) return;
|
||||||
|
|
||||||
|
HeightAboveAnchor = HeightCm;
|
||||||
|
if (bEnabled)
|
||||||
|
{
|
||||||
|
ApplyCutToWorld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float UPS_Editor_FloorCutManager::GetCutWorldZ() const
|
||||||
|
{
|
||||||
|
if (!bAnchorZCached)
|
||||||
|
{
|
||||||
|
// Const-cast acceptable: lazy cache only, no observable mutation of "logical" state.
|
||||||
|
const_cast<UPS_Editor_FloorCutManager*>(this)->ResolveAnchorZ();
|
||||||
|
}
|
||||||
|
return CachedAnchorZ + HeightAboveAnchor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::ReapplyCut()
|
||||||
|
{
|
||||||
|
if (!bEnabled) return;
|
||||||
|
ApplyCutToWorld();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::OnActorSpawned(AActor* Actor)
|
||||||
|
{
|
||||||
|
if (!bEnabled || !Actor) return;
|
||||||
|
|
||||||
|
ResolveAnchorZ();
|
||||||
|
const float CutZ = CachedAnchorZ + HeightAboveAnchor;
|
||||||
|
|
||||||
|
TArray<UPrimitiveComponent*> Prims;
|
||||||
|
Actor->GetComponents<UPrimitiveComponent>(Prims);
|
||||||
|
for (UPrimitiveComponent* P : Prims)
|
||||||
|
{
|
||||||
|
ApplyCutToComponent(P, CutZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::InvalidateAnchorCache()
|
||||||
|
{
|
||||||
|
bAnchorZCached = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UPS_Editor_FloorCutManager::ResolveAnchorZ()
|
||||||
|
{
|
||||||
|
bAnchorZCached = true;
|
||||||
|
CachedAnchorZ = 0.f;
|
||||||
|
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World) return CachedAnchorZ;
|
||||||
|
|
||||||
|
for (TActorIterator<APS_Editor_MandatoryAnchor> It(World); It; ++It)
|
||||||
|
{
|
||||||
|
if (APS_Editor_MandatoryAnchor* Anchor = *It)
|
||||||
|
{
|
||||||
|
CachedAnchorZ = Anchor->GetActorLocation().Z;
|
||||||
|
UE_LOG(LogTemp, Verbose, TEXT("PS_Editor: FloorCut anchor Z = %f"), CachedAnchorZ);
|
||||||
|
return CachedAnchorZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: FloorCut — no APS_Editor_MandatoryAnchor found, using world Z=0 as reference"));
|
||||||
|
return CachedAnchorZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::ApplyCutToWorld()
|
||||||
|
{
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
if (!World) return;
|
||||||
|
|
||||||
|
ResolveAnchorZ();
|
||||||
|
const float CutZ = CachedAnchorZ + HeightAboveAnchor;
|
||||||
|
|
||||||
|
int32 Hidden = 0;
|
||||||
|
int32 Visible = 0;
|
||||||
|
|
||||||
|
for (TActorIterator<AActor> ActorIt(World); ActorIt; ++ActorIt)
|
||||||
|
{
|
||||||
|
AActor* Actor = *ActorIt;
|
||||||
|
if (!Actor) continue;
|
||||||
|
|
||||||
|
// Never hide the anchor itself (it's hidden in game anyway, but defensive).
|
||||||
|
if (Actor->IsA<APS_Editor_MandatoryAnchor>()) continue;
|
||||||
|
|
||||||
|
TArray<UPrimitiveComponent*> Prims;
|
||||||
|
Actor->GetComponents<UPrimitiveComponent>(Prims);
|
||||||
|
for (UPrimitiveComponent* P : Prims)
|
||||||
|
{
|
||||||
|
if (!P) continue;
|
||||||
|
|
||||||
|
const FBoxSphereBounds B = P->Bounds;
|
||||||
|
const float MinZ = B.Origin.Z - B.BoxExtent.Z;
|
||||||
|
if (MinZ > CutZ)
|
||||||
|
{
|
||||||
|
ApplyCutToComponent(P, CutZ);
|
||||||
|
++Hidden;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Component sits at or straddles the plane → must be in its restored state.
|
||||||
|
RestoreComponent(P);
|
||||||
|
++Visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Verbose, TEXT("PS_Editor: FloorCut applied — CutZ=%.1f, hidden=%d, kept=%d, anchor=%.1f, height=%.1f"),
|
||||||
|
CutZ, Hidden, Visible, CachedAnchorZ, HeightAboveAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::ApplyCutToComponent(UPrimitiveComponent* Comp, float CutZ)
|
||||||
|
{
|
||||||
|
if (!Comp) return;
|
||||||
|
|
||||||
|
const FBoxSphereBounds B = Comp->Bounds;
|
||||||
|
const float MinZ = B.Origin.Z - B.BoxExtent.Z;
|
||||||
|
if (MinZ <= CutZ)
|
||||||
|
{
|
||||||
|
// Below the plane — make sure it is in its restored state (caller may not have).
|
||||||
|
RestoreComponent(Comp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot before first mutation, so we can restore the exact original state.
|
||||||
|
TWeakObjectPtr<UPrimitiveComponent> Key(Comp);
|
||||||
|
if (!Snapshots.Contains(Key))
|
||||||
|
{
|
||||||
|
FCutSnapshot Snap;
|
||||||
|
Snap.bWasVisible = Comp->IsVisible();
|
||||||
|
Snap.CollisionEnabled = Comp->GetCollisionEnabled();
|
||||||
|
Snapshots.Add(Key, Snap);
|
||||||
|
}
|
||||||
|
|
||||||
|
Comp->SetVisibility(false, /*bPropagateToChildren=*/false);
|
||||||
|
Comp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::RestoreComponent(UPrimitiveComponent* Comp)
|
||||||
|
{
|
||||||
|
if (!Comp) return;
|
||||||
|
TWeakObjectPtr<UPrimitiveComponent> Key(Comp);
|
||||||
|
const FCutSnapshot* Snap = Snapshots.Find(Key);
|
||||||
|
if (!Snap) return;
|
||||||
|
|
||||||
|
Comp->SetVisibility(Snap->bWasVisible, /*bPropagateToChildren=*/false);
|
||||||
|
Comp->SetCollisionEnabled(Snap->CollisionEnabled);
|
||||||
|
Snapshots.Remove(Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_FloorCutManager::RestoreAll()
|
||||||
|
{
|
||||||
|
for (auto It = Snapshots.CreateIterator(); It; ++It)
|
||||||
|
{
|
||||||
|
UPrimitiveComponent* Comp = It.Key().Get();
|
||||||
|
if (Comp)
|
||||||
|
{
|
||||||
|
Comp->SetVisibility(It.Value().bWasVisible, /*bPropagateToChildren=*/false);
|
||||||
|
Comp->SetCollisionEnabled(It.Value().CollisionEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Snapshots.Empty();
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/WorldSubsystem.h"
|
||||||
|
#include "Engine/EngineTypes.h"
|
||||||
|
#include "UObject/WeakObjectPtr.h"
|
||||||
|
#include "PS_Editor_FloorCutManager.generated.h"
|
||||||
|
|
||||||
|
class UPrimitiveComponent;
|
||||||
|
class APS_Editor_MandatoryAnchor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Editor-only horizontal "floor cut" plane.
|
||||||
|
*
|
||||||
|
* Hides every UPrimitiveComponent whose world-space bounds-min Z is strictly above
|
||||||
|
* AnchorZ + HeightAboveAnchor, by toggling per-component visibility and collision.
|
||||||
|
* The original state is snapshotted before any modification and restored on disable
|
||||||
|
* (or on plugin exit), so the feature is non-destructive.
|
||||||
|
*
|
||||||
|
* Editor-only by design — the gameplay runtime loaded via SceneLoader::LoadScene never
|
||||||
|
* touches this subsystem. UI in the editor widgets is the only consumer.
|
||||||
|
*
|
||||||
|
* Limitations:
|
||||||
|
* - Components straddling the cut plane (minZ <= cut <= maxZ) stay fully visible.
|
||||||
|
* Per-pixel cuts would require modifying master materials, which we explicitly
|
||||||
|
* don't want.
|
||||||
|
* - Anchor Z is sampled once and cached. If the user moves the APS_Editor_MandatoryAnchor
|
||||||
|
* at runtime, call InvalidateAnchorCache() to re-sample.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* - Get via UWorld::GetSubsystem<UPS_Editor_FloorCutManager>(World)
|
||||||
|
* - SetEnabled(true), SetHeightAboveAnchor(200.f)
|
||||||
|
* - OnActorSpawned(NewActor) when SpawnManager spawns a new actor while active
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class PS_EDITOR_API UPS_Editor_FloorCutManager : public UWorldSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
// UWorldSubsystem
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
virtual void Deinitialize() override;
|
||||||
|
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
|
||||||
|
|
||||||
|
/** Enable / disable the cut. On disable, all snapshotted components are restored. */
|
||||||
|
void SetEnabled(bool bInEnabled);
|
||||||
|
bool IsEnabled() const { return bEnabled; }
|
||||||
|
|
||||||
|
/** Set the cut height in centimeters above the MandatoryAnchor. Re-applies immediately
|
||||||
|
* if the cut is enabled. */
|
||||||
|
void SetHeightAboveAnchor(float HeightCm);
|
||||||
|
float GetHeightAboveAnchor() const { return HeightAboveAnchor; }
|
||||||
|
|
||||||
|
/** Computed world Z of the cut plane (AnchorZ + HeightAboveAnchor). Returns
|
||||||
|
* TNumericLimits<float>::Max() if no anchor is found. */
|
||||||
|
float GetCutWorldZ() const;
|
||||||
|
|
||||||
|
/** Force a re-application of the current cut on all current components.
|
||||||
|
* Cheap (no allocation if components haven't moved). */
|
||||||
|
void ReapplyCut();
|
||||||
|
|
||||||
|
/** Called by the SpawnManager whenever it spawns a new actor while the cut is
|
||||||
|
* active, so the new actor's primitives are evaluated against the cut. */
|
||||||
|
void OnActorSpawned(AActor* Actor);
|
||||||
|
|
||||||
|
/** Drop the cached anchor Z (re-sampled on next ReapplyCut). */
|
||||||
|
void InvalidateAnchorCache();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** Per-component snapshot of the state we tweak, so we can restore exactly. */
|
||||||
|
struct FCutSnapshot
|
||||||
|
{
|
||||||
|
bool bWasVisible = true;
|
||||||
|
TEnumAsByte<ECollisionEnabled::Type> CollisionEnabled = ECollisionEnabled::QueryAndPhysics;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Apply the cut to a single primitive (snapshot if needed, then hide). */
|
||||||
|
void ApplyCutToComponent(UPrimitiveComponent* Comp, float CutZ);
|
||||||
|
/** Restore a single primitive from its snapshot, if any. */
|
||||||
|
void RestoreComponent(UPrimitiveComponent* Comp);
|
||||||
|
|
||||||
|
/** Walk the world and apply the cut to every primitive. */
|
||||||
|
void ApplyCutToWorld();
|
||||||
|
/** Restore every snapshotted primitive. */
|
||||||
|
void RestoreAll();
|
||||||
|
|
||||||
|
/** Find the world Z of the first MandatoryAnchor in the world, or fall back to 0.
|
||||||
|
* Result is cached in CachedAnchorZ. */
|
||||||
|
float ResolveAnchorZ();
|
||||||
|
|
||||||
|
bool bEnabled = false;
|
||||||
|
float HeightAboveAnchor = 200.f; // cm — default 2m as agreed with user
|
||||||
|
|
||||||
|
bool bAnchorZCached = false;
|
||||||
|
float CachedAnchorZ = 0.f;
|
||||||
|
|
||||||
|
/** Snapshots of components we have modified, keyed by weak pointer so destroyed
|
||||||
|
* primitives are simply ignored on restore. */
|
||||||
|
TMap<TWeakObjectPtr<UPrimitiveComponent>, FCutSnapshot> Snapshots;
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "PS_Editor_GroundSnap.h"
|
#include "PS_Editor_GroundSnap.h"
|
||||||
#include "PS_Editor_NavUtils.h"
|
#include "PS_Editor_NavUtils.h"
|
||||||
#include "PS_Editor_MandatoryAnchor.h"
|
#include "PS_Editor_MandatoryAnchor.h"
|
||||||
|
#include "PS_Editor_FloorCutManager.h"
|
||||||
#include "AIController.h"
|
#include "AIController.h"
|
||||||
#include "BrainComponent.h"
|
#include "BrainComponent.h"
|
||||||
#include "EngineUtils.h"
|
#include "EngineUtils.h"
|
||||||
@@ -310,6 +311,19 @@ AActor* UPS_Editor_SpawnManager::SpawnActorDirect(UClass* ActorClass, FVector Lo
|
|||||||
SpawnedActors.Add(SpawnedActor);
|
SpawnedActors.Add(SpawnedActor);
|
||||||
RegisterSpawnedActorId(PC->GetWorld(), SpawnedActor);
|
RegisterSpawnedActorId(PC->GetWorld(), SpawnedActor);
|
||||||
|
|
||||||
|
// If the editor's floor-cut is active, evaluate the new actor against it so it doesn't
|
||||||
|
// appear floating above a "hidden" floor.
|
||||||
|
if (UWorld* W = PC->GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
if (FC->IsEnabled())
|
||||||
|
{
|
||||||
|
FC->OnActorSpawned(SpawnedActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SpawnedActor;
|
return SpawnedActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,6 +349,17 @@ void UPS_Editor_SpawnManager::TrackSpawnedActor(AActor* Actor)
|
|||||||
if (APlayerController* PC = OwnerPC.Get())
|
if (APlayerController* PC = OwnerPC.Get())
|
||||||
{
|
{
|
||||||
RegisterSpawnedActorId(PC->GetWorld(), Actor);
|
RegisterSpawnedActorId(PC->GetWorld(), Actor);
|
||||||
|
// Re-evaluate against the floor cut (duplicate / paste paths land here).
|
||||||
|
if (UWorld* W = PC->GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
if (FC->IsEnabled())
|
||||||
|
{
|
||||||
|
FC->OnActorSpawned(Actor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "DesktopPlatformModule.h"
|
#include "DesktopPlatformModule.h"
|
||||||
#include "PS_Editor_GameInstanceSubsystem.h"
|
#include "PS_Editor_GameInstanceSubsystem.h"
|
||||||
#include "PS_Editor_TimelineSubsystem.h"
|
#include "PS_Editor_TimelineSubsystem.h"
|
||||||
|
#include "PS_Editor_FloorCutManager.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
#include "PS_Editor_UIStyleAsset.h"
|
#include "PS_Editor_UIStyleAsset.h"
|
||||||
#include "PS_Editor_UIStyle.h"
|
#include "PS_Editor_UIStyle.h"
|
||||||
@@ -892,6 +893,70 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildToolbar()
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth() [ MakeSeparator() ]
|
||||||
|
// ---- Floor cut: editor-only horizontal plane to hide upper floors ----
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth()
|
||||||
|
[
|
||||||
|
MakeToggleButton(EPS_Editor_Icon::Grid, EPS_Editor_Icon::Grid,
|
||||||
|
TAttribute<FString>::CreateLambda([this]() -> FString
|
||||||
|
{
|
||||||
|
UWorld* W = GetWorld();
|
||||||
|
UPS_Editor_FloorCutManager* FC = W ? W->GetSubsystem<UPS_Editor_FloorCutManager>() : nullptr;
|
||||||
|
return (FC && FC->IsEnabled()) ? TEXT("Floor cut On") : TEXT("Floor cut");
|
||||||
|
}),
|
||||||
|
[this]() -> bool
|
||||||
|
{
|
||||||
|
UWorld* W = GetWorld();
|
||||||
|
UPS_Editor_FloorCutManager* FC = W ? W->GetSubsystem<UPS_Editor_FloorCutManager>() : nullptr;
|
||||||
|
return FC && FC->IsEnabled();
|
||||||
|
},
|
||||||
|
[this]()
|
||||||
|
{
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
FC->SetEnabled(!FC->IsEnabled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(4.0f, 0.0f)
|
||||||
|
[
|
||||||
|
SNew(SBox).MinDesiredWidth(120.0f)
|
||||||
|
[
|
||||||
|
SAssignNew(FloorCutSlider, SSlider)
|
||||||
|
.MinValue(0.0f)
|
||||||
|
.MaxValue(1000.0f)
|
||||||
|
.StepSize(10.0f)
|
||||||
|
.MouseUsesStep(true)
|
||||||
|
.Value(200.0f)
|
||||||
|
.ToolTipText(FText::FromString(TEXT("Cut height above MandatoryAnchor (cm) — range 0..1000, step 10")))
|
||||||
|
.OnValueChanged_Lambda([this](float NewValue)
|
||||||
|
{
|
||||||
|
const float Snapped = FMath::RoundToFloat(NewValue / 10.f) * 10.f;
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
FC->SetHeightAboveAnchor(Snapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FloorCutValueText.IsValid())
|
||||||
|
{
|
||||||
|
FloorCutValueText->SetText(FText::FromString(FString::Printf(TEXT("%.0f cm"), Snapped)));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(2.0f, 0.0f)
|
||||||
|
[
|
||||||
|
SAssignNew(FloorCutValueText, STextBlock)
|
||||||
|
.Text(FText::FromString(TEXT("200 cm")))
|
||||||
|
.Font(FPS_Editor_UIStyle::SmallFont(S))
|
||||||
|
.ColorAndOpacity(FPS_Editor_UIStyle::TextMuted(S))
|
||||||
|
.MinDesiredWidth(48.0f)
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ private:
|
|||||||
TSharedPtr<STextBlock> SnapText;
|
TSharedPtr<STextBlock> SnapText;
|
||||||
TSharedPtr<SEditableTextBox> SnapSizeField;
|
TSharedPtr<SEditableTextBox> SnapSizeField;
|
||||||
|
|
||||||
|
// Floor-cut UI (editor-only horizontal plane that hides primitives above AnchorZ+H)
|
||||||
|
TSharedPtr<STextBlock> FloorCutValueText;
|
||||||
|
TSharedPtr<class SSlider> FloorCutSlider;
|
||||||
|
|
||||||
// Spawn catalog
|
// Spawn catalog
|
||||||
TSharedPtr<SVerticalBox> SpawnListContainer;
|
TSharedPtr<SVerticalBox> SpawnListContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include "DesktopPlatformModule.h"
|
#include "DesktopPlatformModule.h"
|
||||||
#include "PS_Editor_GameInstanceSubsystem.h"
|
#include "PS_Editor_GameInstanceSubsystem.h"
|
||||||
#include "PS_Editor_TimelineSubsystem.h"
|
#include "PS_Editor_TimelineSubsystem.h"
|
||||||
|
#include "PS_Editor_FloorCutManager.h"
|
||||||
#include "Engine/World.h"
|
#include "Engine/World.h"
|
||||||
|
|
||||||
// Forward declaration — the real definition lives further down with the timeline UI block,
|
// Forward declaration — the real definition lives further down with the timeline UI block,
|
||||||
@@ -740,6 +741,69 @@ TSharedRef<SWidget> UPS_Editor_MainWidget_Legacy::BuildToolbar()
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
// ---- Floor cut: editor-only horizontal plane to hide upper floors ----
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth().Padding(16.0f, 0.0f, 2.0f, 0.0f).VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SButton)
|
||||||
|
.ToolTipText(FText::FromString(TEXT("Toggle floor cut. Hides primitives whose bounds-min Z is above (Anchor.Z + Height)")))
|
||||||
|
.OnClicked_Lambda([this]() -> FReply
|
||||||
|
{
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
FC->SetEnabled(!FC->IsEnabled());
|
||||||
|
if (FloorCutToggleText.IsValid())
|
||||||
|
{
|
||||||
|
FloorCutToggleText->SetText(FText::FromString(FC->IsEnabled() ? TEXT("Floor cut: On") : TEXT("Floor cut: Off")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FReply::Handled();
|
||||||
|
})
|
||||||
|
[
|
||||||
|
SAssignNew(FloorCutToggleText, STextBlock)
|
||||||
|
.Text(FText::FromString(TEXT("Floor cut: Off")))
|
||||||
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth().Padding(4.0f, 0.0f).VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SNew(SBox).MinDesiredWidth(120.0f)
|
||||||
|
[
|
||||||
|
SAssignNew(FloorCutSlider, SSlider)
|
||||||
|
.MinValue(0.0f)
|
||||||
|
.MaxValue(1000.0f)
|
||||||
|
.StepSize(10.0f)
|
||||||
|
.MouseUsesStep(true)
|
||||||
|
.Value(200.0f)
|
||||||
|
.ToolTipText(FText::FromString(TEXT("Cut height above MandatoryAnchor (cm) — range 0..1000, step 10")))
|
||||||
|
.OnValueChanged_Lambda([this](float NewValue)
|
||||||
|
{
|
||||||
|
// Snap to 10 cm — SSlider's MouseUsesStep already does this for clicks,
|
||||||
|
// but a drag still emits raw floats, so we re-snap here.
|
||||||
|
const float Snapped = FMath::RoundToFloat(NewValue / 10.f) * 10.f;
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
FC->SetHeightAboveAnchor(Snapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FloorCutValueText.IsValid())
|
||||||
|
{
|
||||||
|
FloorCutValueText->SetText(FText::FromString(FString::Printf(TEXT("%.0f cm"), Snapped)));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot().AutoWidth().Padding(2.0f, 0.0f).VAlign(VAlign_Center)
|
||||||
|
[
|
||||||
|
SAssignNew(FloorCutValueText, STextBlock)
|
||||||
|
.Text(FText::FromString(TEXT("200 cm")))
|
||||||
|
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||||
|
.MinDesiredWidth(48.0f)
|
||||||
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ private:
|
|||||||
TSharedPtr<STextBlock> SnapText;
|
TSharedPtr<STextBlock> SnapText;
|
||||||
TSharedPtr<SEditableTextBox> SnapSizeField;
|
TSharedPtr<SEditableTextBox> SnapSizeField;
|
||||||
|
|
||||||
|
// Floor-cut UI (editor-only horizontal plane that hides primitives above AnchorZ+H)
|
||||||
|
TSharedPtr<STextBlock> FloorCutToggleText;
|
||||||
|
TSharedPtr<class SSlider> FloorCutSlider;
|
||||||
|
TSharedPtr<STextBlock> FloorCutValueText;
|
||||||
|
|
||||||
// Spawn catalog
|
// Spawn catalog
|
||||||
TSharedPtr<SVerticalBox> SpawnListContainer;
|
TSharedPtr<SVerticalBox> SpawnListContainer;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
#include "PS_Editor_SpawnManager.h"
|
#include "PS_Editor_SpawnManager.h"
|
||||||
#include "PS_Editor_SceneSerializer.h"
|
#include "PS_Editor_SceneSerializer.h"
|
||||||
#include "PS_Editor_PlayerController.h"
|
#include "PS_Editor_PlayerController.h"
|
||||||
|
#include "PS_Editor_FloorCutManager.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
|
||||||
void UPS_Editor_MainWidget_UMG::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager)
|
void UPS_Editor_MainWidget_UMG::SetSelectionManager(UPS_Editor_SelectionManager* InSelectionManager)
|
||||||
{
|
{
|
||||||
@@ -161,6 +163,54 @@ void UPS_Editor_MainWidget_UMG::ClearSelection()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------- Floor cut --------------------
|
||||||
|
|
||||||
|
void UPS_Editor_MainWidget_UMG::SetFloorCutEnabled(bool bEnabled)
|
||||||
|
{
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
FC->SetEnabled(bEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UPS_Editor_MainWidget_UMG::SetFloorCutHeightCm(float HeightCm)
|
||||||
|
{
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
FC->SetHeightAboveAnchor(HeightCm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UPS_Editor_MainWidget_UMG::IsFloorCutEnabled() const
|
||||||
|
{
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
return FC->IsEnabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float UPS_Editor_MainWidget_UMG::GetFloorCutHeightCm() const
|
||||||
|
{
|
||||||
|
if (UWorld* W = GetWorld())
|
||||||
|
{
|
||||||
|
if (UPS_Editor_FloorCutManager* FC = W->GetSubsystem<UPS_Editor_FloorCutManager>())
|
||||||
|
{
|
||||||
|
return FC->GetHeightAboveAnchor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 200.f;
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------- Getters --------------------
|
// -------------------- Getters --------------------
|
||||||
|
|
||||||
EPS_Editor_TransformMode UPS_Editor_MainWidget_UMG::GetCurrentTransformMode() const
|
EPS_Editor_TransformMode UPS_Editor_MainWidget_UMG::GetCurrentTransformMode() const
|
||||||
|
|||||||
@@ -70,6 +70,22 @@ public:
|
|||||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Selection")
|
UFUNCTION(BlueprintCallable, Category = "PS Editor|Selection")
|
||||||
void ClearSelection();
|
void ClearSelection();
|
||||||
|
|
||||||
|
// ---- Floor cut (editor-only horizontal plane that hides upper floors) ----
|
||||||
|
|
||||||
|
/** Enable/disable the floor cut. Slider value drives the cut height in cm above the
|
||||||
|
* MandatoryAnchor. */
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "PS Editor|FloorCut")
|
||||||
|
void SetFloorCutEnabled(bool bEnabled);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "PS Editor|FloorCut")
|
||||||
|
void SetFloorCutHeightCm(float HeightCm);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "PS Editor|FloorCut")
|
||||||
|
bool IsFloorCutEnabled() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintPure, Category = "PS Editor|FloorCut")
|
||||||
|
float GetFloorCutHeightCm() const;
|
||||||
|
|
||||||
// ==================== BP-PURE GETTERS ====================
|
// ==================== BP-PURE GETTERS ====================
|
||||||
UFUNCTION(BlueprintPure, Category = "PS Editor|Tools")
|
UFUNCTION(BlueprintPure, Category = "PS Editor|Tools")
|
||||||
EPS_Editor_TransformMode GetCurrentTransformMode() const;
|
EPS_Editor_TransformMode GetCurrentTransformMode() const;
|
||||||
|
|||||||
Reference in New Issue
Block a user