Refactor spline system with IPS_Editor_PointPlaceable interface
Architecture: - New IPS_Editor_PointPlaceable interface for any actor using click-to-place- points placement. Decouples the placement system from SplineActor so future actors (patrol routes, area polygons) can reuse the same flow. - SplineActor implements the interface. BP subclasses with SpawnableComponent appear in the catalog and automatically enter placement mode when spawned. - PlayerController methods renamed: BeginPointPlacement/FinishPointPlacement/ CancelPointPlacement (generic, interface-driven). Features added: - SplineColor, SplineSelectedColor, SplineTag as UPROPERTY for BP overrides - UpdateSplineMaterials() for runtime color changes - Tube visualization (8-segment cylinder) replacing cross-ribbon - Spline editing mode with Edit/Done buttons in notification bar - Delete spline point (Del key or Delete Point button, min 2 points) - Insert point by clicking on tube mesh in edit mode - Snap spline point to ground (End key) - Append points to existing spline (S key or catalog when spline selected) - Notification bar: contextual messages and buttons per mode - Auto-generated M_PS_Editor_SplineLine material (unlit opaque, depth tested) - Material swap: opaque when deselected, no-depth-test when selected - Serialization of SplineColor/SplineTag in CustomProperties Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,7 @@
|
||||
#include "PS_Editor_Gizmo.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
#include "PS_Editor_ConfirmDialog.h"
|
||||
|
||||
@@ -576,10 +577,10 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for spline point handle hit → select point, move gizmo to it (no drag yet)
|
||||
// Check for spline point handle hit → select point, move gizmo to it (only in SplineEditing mode)
|
||||
if (APS_Editor_PlayerController* EdPC = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (EdPC->GetEditorMode() == EPS_Editor_EditorMode::Normal)
|
||||
if (EdPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM = EdPC->GetSelectionManager())
|
||||
{
|
||||
@@ -626,6 +627,62 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
}
|
||||
}
|
||||
|
||||
// In SplineEditing mode: click on spline tube mesh → insert a CP
|
||||
if (APS_Editor_PlayerController* EdPC2 = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (EdPC2->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
{
|
||||
if (UPS_Editor_SelectionManager* SM2 = EdPC2->GetSelectionManager())
|
||||
{
|
||||
FHitResult LineHit;
|
||||
FCollisionQueryParams LineParams;
|
||||
LineParams.AddIgnoredActor(this);
|
||||
const FVector TraceEnd = RayOrigin + RayDir * 100000.0f;
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(LineHit, RayOrigin, TraceEnd, ECC_Visibility, LineParams))
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM2->GetSelectedActors())
|
||||
{
|
||||
APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get());
|
||||
if (!Spline) continue;
|
||||
|
||||
if (Spline->IsSplineMesh(LineHit.GetComponent()))
|
||||
{
|
||||
const FVector HitPos = LineHit.ImpactPoint;
|
||||
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
const int32 InsertIdx = Spline->FindInsertIndex(HitPos);
|
||||
Spline->InsertPoint(InsertIdx, HitPos);
|
||||
|
||||
if (UPS_Editor_UndoManager* UM = EdPC2->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Insert spline point");
|
||||
UM->RecordAction(Action);
|
||||
}
|
||||
|
||||
ActiveSplinePointIndex = InsertIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
Spline->HighlightHandle(InsertIdx);
|
||||
|
||||
if (APS_Editor_Gizmo* G = SM2->GetGizmo())
|
||||
{
|
||||
G->SetActorLocation(Spline->GetPointLocation(InsertIdx));
|
||||
}
|
||||
SM2->SetTransformMode(EPS_Editor_TransformMode::Translate);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Inserted spline point at index %d"), InsertIdx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clicking elsewhere resets spline point selection
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
@@ -757,7 +814,7 @@ void APS_Editor_Pawn::HandleRightClickStarted(const FInputActionValue& Value)
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplinePlacement();
|
||||
EditorPC->FinishPointPlacement();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -830,6 +887,50 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
||||
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
|
||||
APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController());
|
||||
if (!EditorPC) return;
|
||||
|
||||
// In SplineEditing mode with a point selected: delete that point
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing
|
||||
&& ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get());
|
||||
if (Spline && Spline->GetNumPoints() > 2)
|
||||
{
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
Spline->RemovePoint(ActiveSplinePointIndex);
|
||||
|
||||
if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Delete spline point");
|
||||
UM->RecordAction(Action);
|
||||
}
|
||||
|
||||
// Deselect the point
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
Spline->ClearHandleHighlight();
|
||||
|
||||
// Move gizmo back to centroid
|
||||
if (UPS_Editor_SelectionManager* SM2 = EditorPC->GetSelectionManager())
|
||||
{
|
||||
if (APS_Editor_Gizmo* G = SM2->GetGizmo())
|
||||
{
|
||||
G->SetActorLocation(Spline->GetCentroid());
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Deleted spline point, %d remaining"), Spline->GetNumPoints());
|
||||
}
|
||||
else if (Spline)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Cannot delete point, minimum 2 points required"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager();
|
||||
if (!SM || !SM->IsAnythingSelected()) return;
|
||||
|
||||
@@ -904,6 +1005,50 @@ void APS_Editor_Pawn::HandleSnapToGround(const FInputActionValue& Value)
|
||||
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
// If a spline point is selected, snap that point to ground
|
||||
if (ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get()))
|
||||
{
|
||||
const FVector PointLoc = Spline->GetPointLocation(ActiveSplinePointIndex);
|
||||
const FVector TraceStart = PointLoc + FVector(0.0f, 0.0f, 500.0f);
|
||||
const FVector TraceEnd = PointLoc - FVector(0.0f, 0.0f, 10000.0f);
|
||||
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Params;
|
||||
Params.AddIgnoredActor(Spline);
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, Params))
|
||||
{
|
||||
// Store state for undo
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
|
||||
Spline->MovePoint(ActiveSplinePointIndex, Hit.ImpactPoint + FVector(0.0f, 0.0f, 5.0f));
|
||||
|
||||
// Update gizmo position
|
||||
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
|
||||
{
|
||||
if (APS_Editor_Gizmo* Gizmo = SM->GetGizmo())
|
||||
{
|
||||
Gizmo->SetActorLocation(Spline->GetPointLocation(ActiveSplinePointIndex));
|
||||
}
|
||||
}
|
||||
|
||||
// Record undo
|
||||
if (UPS_Editor_UndoManager* UM = EditorPC->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Snap spline point to ground");
|
||||
UM->RecordAction(Action);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
|
||||
{
|
||||
SM->SnapSelectionToGround();
|
||||
@@ -954,11 +1099,25 @@ void APS_Editor_Pawn::HandleSplineMode(const FInputActionValue& Value)
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplinePlacement();
|
||||
EditorPC->FinishPointPlacement();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPC->BeginSplinePlacement();
|
||||
// If a spline is selected, append points to it instead of creating a new one
|
||||
if (UPS_Editor_SelectionManager* SM = EditorPC->GetSelectionManager())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
{
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
EditorPC->BeginPointPlacementAppend(Spline);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorPC->BeginPointPlacement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -969,7 +1128,7 @@ void APS_Editor_Pawn::HandleConfirm(const FInputActionValue& Value)
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplinePlacement();
|
||||
EditorPC->FinishPointPlacement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -980,7 +1139,11 @@ void APS_Editor_Pawn::HandleCancel(const FInputActionValue& Value)
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->CancelSplinePlacement();
|
||||
EditorPC->CancelPointPlacement();
|
||||
}
|
||||
else if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,12 @@ public:
|
||||
/** Fired when a left-click is confirmed (mouse up without exceeding drag threshold). */
|
||||
FPS_Editor_OnClick OnEditorClick;
|
||||
|
||||
/** Delete handler (public so UI buttons can call it). */
|
||||
void HandleDelete(const FInputActionValue& Value);
|
||||
|
||||
/** Currently selected spline point index (-1 = none). Used by UI for Delete Point button. */
|
||||
int32 ActiveSplinePointIndex = -1;
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
@@ -192,7 +198,6 @@ private:
|
||||
TObjectPtr<UInputAction> IA_Cancel;
|
||||
|
||||
// ---- Spline point drag state ----
|
||||
int32 ActiveSplinePointIndex = -1;
|
||||
TWeakObjectPtr<AActor> DraggedSplineActor;
|
||||
TArray<FVector> SplinePointDragInitialState;
|
||||
FVector SplinePointDragPlaneOrigin;
|
||||
@@ -227,7 +232,6 @@ private:
|
||||
void HandleTranslateMode(const FInputActionValue& Value);
|
||||
void HandleRotateMode(const FInputActionValue& Value);
|
||||
void HandleScaleMode(const FInputActionValue& Value);
|
||||
void HandleDelete(const FInputActionValue& Value);
|
||||
void HandleSnapToGround(const FInputActionValue& Value);
|
||||
void HandleUndo(const FInputActionValue& Value);
|
||||
void HandleRedo(const FInputActionValue& Value);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "PS_Editor_Gizmo.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
|
||||
APS_Editor_PlayerController::APS_Editor_PlayerController()
|
||||
{
|
||||
@@ -81,70 +82,165 @@ void APS_Editor_PlayerController::OnPossess(APawn* InPawn)
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::BeginSplinePlacement()
|
||||
APS_Editor_SplineActor* APS_Editor_PlayerController::GetActiveSplineActor() const
|
||||
{
|
||||
return Cast<APS_Editor_SplineActor>(ActivePlacementActor);
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::BeginPointPlacement(TSubclassOf<AActor> InActorClass)
|
||||
{
|
||||
if (SelectionManager)
|
||||
{
|
||||
SelectionManager->ClearSelection();
|
||||
}
|
||||
ActiveSplineActor = nullptr;
|
||||
ActivePlacementActor = nullptr;
|
||||
PlacementActorClass = InActorClass ? InActorClass : TSubclassOf<AActor>(APS_Editor_SplineActor::StaticClass());
|
||||
bAppendingToActor = false;
|
||||
AppendInitialPoints.Empty();
|
||||
EditorMode = EPS_Editor_EditorMode::SplinePlacement;
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline placement mode started"));
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Point placement mode started (class: %s)"), *PlacementActorClass->GetName());
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::FinishSplinePlacement()
|
||||
void APS_Editor_PlayerController::BeginPointPlacementAppend(AActor* ExistingActor)
|
||||
{
|
||||
if (!ActiveSplineActor || ActiveSplineActor->GetNumPoints() < 2)
|
||||
if (!ExistingActor || !ExistingActor->Implements<UPS_Editor_PointPlaceable>()) return;
|
||||
|
||||
ActivePlacementActor = ExistingActor;
|
||||
bAppendingToActor = true;
|
||||
EditorMode = EPS_Editor_EditorMode::SplinePlacement;
|
||||
|
||||
// Store initial state for undo (spline-specific)
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ExistingActor))
|
||||
{
|
||||
CancelSplinePlacement();
|
||||
AppendInitialPoints = Spline->GetAllPointLocations();
|
||||
Spline->SetHandlesVisible(true);
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Appending points to %s"), *ExistingActor->GetName());
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::FinishPointPlacement()
|
||||
{
|
||||
IPS_Editor_PointPlaceable* Placeable = Cast<IPS_Editor_PointPlaceable>(ActivePlacementActor);
|
||||
if (!ActivePlacementActor || !Placeable || Placeable->GetCurrentPointCount() < Placeable->GetMinimumPoints())
|
||||
{
|
||||
CancelPointPlacement();
|
||||
return;
|
||||
}
|
||||
|
||||
// Hide handles after placement (shown again on selection)
|
||||
ActiveSplineActor->SetHandlesVisible(false);
|
||||
Placeable->OnPlacementFinished();
|
||||
|
||||
// Track in SpawnManager — CRITICAL for outliner, save/load, and scene clear
|
||||
if (SpawnManager)
|
||||
if (bAppendingToActor)
|
||||
{
|
||||
SpawnManager->TrackSpawnedActor(ActiveSplineActor);
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline tracked by SpawnManager (total: %d)"), SpawnManager->GetSpawnedActors().Num());
|
||||
// Appending: record undo for the added points
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
if (UndoManager)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->OldPoints = AppendInitialPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = FString::Printf(TEXT("Add %d point(s)"),
|
||||
Spline->GetNumPoints() - AppendInitialPoints.Num());
|
||||
UndoManager->RecordAction(Action);
|
||||
}
|
||||
}
|
||||
if (SelectionManager)
|
||||
{
|
||||
SelectionManager->ClearSelection();
|
||||
SelectionManager->SelectActor(ActivePlacementActor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Error, TEXT("PS_Editor: SpawnManager is null! Spline will NOT be tracked."));
|
||||
// New actor: track and record spawn undo
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
}
|
||||
|
||||
if (SpawnManager)
|
||||
{
|
||||
SpawnManager->TrackSpawnedActor(ActivePlacementActor);
|
||||
}
|
||||
|
||||
// Record spawn action for undo
|
||||
if (UndoManager)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SpawnAction> Action = MakeShared<FPS_Editor_SpawnAction>();
|
||||
Action->Actors.Add(ActiveSplineActor);
|
||||
Action->Description = FString::Printf(TEXT("Create spline (%d points)"), ActiveSplineActor->GetNumPoints());
|
||||
Action->Actors.Add(ActivePlacementActor);
|
||||
Action->Description = FString::Printf(TEXT("Create %s (%d points)"),
|
||||
*ActivePlacementActor->GetClass()->GetName(), Placeable->GetCurrentPointCount());
|
||||
UndoManager->RecordAction(Action);
|
||||
}
|
||||
|
||||
// Select the new spline
|
||||
if (SelectionManager)
|
||||
{
|
||||
SelectionManager->SelectActor(ActiveSplineActor);
|
||||
SelectionManager->SelectActor(ActivePlacementActor);
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline placed with %d points, class=%s"),
|
||||
ActiveSplineActor->GetNumPoints(), *ActiveSplineActor->GetClass()->GetPathName());
|
||||
|
||||
ActiveSplineActor = nullptr;
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Placement finished (%d points)"), Placeable->GetCurrentPointCount());
|
||||
ActivePlacementActor = nullptr;
|
||||
bAppendingToActor = false;
|
||||
AppendInitialPoints.Empty();
|
||||
EditorMode = EPS_Editor_EditorMode::Normal;
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::CancelSplinePlacement()
|
||||
void APS_Editor_PlayerController::CancelPointPlacement()
|
||||
{
|
||||
if (ActiveSplineActor)
|
||||
if (ActivePlacementActor)
|
||||
{
|
||||
ActiveSplineActor->Destroy();
|
||||
ActiveSplineActor = nullptr;
|
||||
IPS_Editor_PointPlaceable* Placeable = Cast<IPS_Editor_PointPlaceable>(ActivePlacementActor);
|
||||
if (Placeable) Placeable->OnPlacementCancelled();
|
||||
|
||||
if (bAppendingToActor)
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
Spline->SetAllPointLocations(AppendInitialPoints);
|
||||
Spline->SetHandlesVisible(false);
|
||||
}
|
||||
if (SelectionManager)
|
||||
{
|
||||
SelectionManager->ClearSelection();
|
||||
SelectionManager->SelectActor(ActivePlacementActor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivePlacementActor->Destroy();
|
||||
}
|
||||
ActivePlacementActor = nullptr;
|
||||
}
|
||||
bAppendingToActor = false;
|
||||
AppendInitialPoints.Empty();
|
||||
EditorMode = EPS_Editor_EditorMode::Normal;
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline placement cancelled"));
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Placement cancelled"));
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::BeginSplineEditing(APS_Editor_SplineActor* Spline)
|
||||
{
|
||||
if (!Spline) return;
|
||||
|
||||
ActivePlacementActor = Spline;
|
||||
EditorMode = EPS_Editor_EditorMode::SplineEditing;
|
||||
Spline->SetHandlesVisible(true);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline editing mode started (%d points)"), Spline->GetNumPoints());
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::FinishSplineEditing()
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->ClearHandleHighlight();
|
||||
}
|
||||
ActivePlacementActor = nullptr;
|
||||
EditorMode = EPS_Editor_EditorMode::Normal;
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline editing mode ended"));
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bAdditive)
|
||||
@@ -159,33 +255,34 @@ void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bA
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Params;
|
||||
Params.AddIgnoredActor(GetPawn());
|
||||
if (ActiveSplineActor) Params.AddIgnoredActor(ActiveSplineActor);
|
||||
if (ActivePlacementActor) Params.AddIgnoredActor(ActivePlacementActor);
|
||||
|
||||
const FVector End = WorldOrigin + WorldDirection * 100000.0f;
|
||||
FVector ClickWorldPos;
|
||||
if (GetWorld()->LineTraceSingleByChannel(Hit, WorldOrigin, End, ECC_Visibility, Params))
|
||||
{
|
||||
ClickWorldPos = Hit.ImpactPoint;
|
||||
ClickWorldPos = Hit.ImpactPoint + FVector(0.0f, 0.0f, 5.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClickWorldPos = WorldOrigin + WorldDirection * 1000.0f;
|
||||
}
|
||||
|
||||
if (!ActiveSplineActor)
|
||||
if (!ActivePlacementActor)
|
||||
{
|
||||
// First click: spawn the spline actor
|
||||
// First click: spawn the actor
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
ActiveSplineActor = GetWorld()->SpawnActor<APS_Editor_SplineActor>(
|
||||
APS_Editor_SplineActor::StaticClass(), ClickWorldPos, FRotator::ZeroRotator, SpawnParams);
|
||||
UClass* ClassToSpawn = PlacementActorClass ? PlacementActorClass.Get() : APS_Editor_SplineActor::StaticClass();
|
||||
ActivePlacementActor = GetWorld()->SpawnActor<AActor>(ClassToSpawn, ClickWorldPos, FRotator::ZeroRotator, SpawnParams);
|
||||
}
|
||||
|
||||
if (ActiveSplineActor)
|
||||
// Add point via the interface
|
||||
if (IPS_Editor_PointPlaceable* Placeable = Cast<IPS_Editor_PointPlaceable>(ActivePlacementActor))
|
||||
{
|
||||
ActiveSplineActor->AddPoint(ClickWorldPos);
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Spline point %d added at %s"),
|
||||
ActiveSplineActor->GetNumPoints(), *ClickWorldPos.ToString());
|
||||
Placeable->AddPlacementPoint(ClickWorldPos);
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Point %d added at %s"),
|
||||
Placeable->GetCurrentPointCount(), *ClickWorldPos.ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class UPS_Editor_SpawnManager;
|
||||
class UPS_Editor_SpawnCatalog;
|
||||
class UPS_Editor_SceneSerializer;
|
||||
class APS_Editor_SplineActor;
|
||||
class IPS_Editor_PointPlaceable;
|
||||
|
||||
/**
|
||||
* Player controller for PS_Editor runtime editing.
|
||||
@@ -45,17 +46,29 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
EPS_Editor_EditorMode GetEditorMode() const { return EditorMode; }
|
||||
|
||||
/** Enter spline placement mode. Clicks will add points. */
|
||||
void BeginSplinePlacement();
|
||||
/** Enter point-placement mode for any actor implementing IPS_Editor_PointPlaceable. */
|
||||
void BeginPointPlacement(TSubclassOf<AActor> InActorClass = nullptr);
|
||||
|
||||
/** Finish current spline (min 2 points required). Returns to Normal mode. */
|
||||
void FinishSplinePlacement();
|
||||
/** Enter point-placement mode appending to an existing placeable actor. */
|
||||
void BeginPointPlacementAppend(AActor* ExistingActor);
|
||||
|
||||
/** Cancel current spline placement (destroys in-progress spline). */
|
||||
void CancelSplinePlacement();
|
||||
/** Finish current placement (min points required). Returns to Normal mode. */
|
||||
void FinishPointPlacement();
|
||||
|
||||
/** Get the spline currently being placed (nullptr if not in SplinePlacement mode). */
|
||||
APS_Editor_SplineActor* GetActiveSplineActor() const { return ActiveSplineActor; }
|
||||
/** Cancel current placement (destroys in-progress actor or restores state). */
|
||||
void CancelPointPlacement();
|
||||
|
||||
/** Get the actor currently being placed/edited (nullptr if not in placement mode). */
|
||||
AActor* GetActivePlacementActor() const { return ActivePlacementActor; }
|
||||
|
||||
// Legacy convenience (for spline-specific code like point editing)
|
||||
APS_Editor_SplineActor* GetActiveSplineActor() const;
|
||||
|
||||
/** Enter spline point editing mode for a selected spline. */
|
||||
void BeginSplineEditing(APS_Editor_SplineActor* Spline);
|
||||
|
||||
/** Exit spline editing mode. */
|
||||
void FinishSplineEditing();
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
@@ -77,7 +90,17 @@ private:
|
||||
EPS_Editor_EditorMode EditorMode = EPS_Editor_EditorMode::Normal;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<APS_Editor_SplineActor> ActiveSplineActor;
|
||||
TObjectPtr<AActor> ActivePlacementActor;
|
||||
|
||||
/** Class to use when spawning the placement actor. */
|
||||
UPROPERTY()
|
||||
TSubclassOf<AActor> PlacementActorClass;
|
||||
|
||||
/** True when appending to an existing actor (vs creating new). */
|
||||
bool bAppendingToActor = false;
|
||||
|
||||
/** Snapshot of spline points before append, for undo. */
|
||||
TArray<FVector> AppendInitialPoints;
|
||||
|
||||
void HandleEditorClick(FVector2D ScreenPos, bool bAdditive);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Interface.h"
|
||||
#include "PS_Editor_PointPlaceable.generated.h"
|
||||
|
||||
/**
|
||||
* Interface for actors that use a click-to-place-points placement flow.
|
||||
* Implement this on any actor class to get automatic placement mode
|
||||
* when spawned from the PS_Editor catalogue.
|
||||
*
|
||||
* Examples: splines, patrol routes, area polygons, cable paths...
|
||||
*/
|
||||
UINTERFACE(MinimalAPI, BlueprintType)
|
||||
class UPS_Editor_PointPlaceable : public UInterface
|
||||
{
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
class PS_EDITOR_API IPS_Editor_PointPlaceable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Called for each click during placement mode. */
|
||||
virtual void AddPlacementPoint(const FVector& WorldPosition) = 0;
|
||||
|
||||
/** Minimum number of points required. Below this, finishing = cancelling. */
|
||||
virtual int32 GetMinimumPoints() const { return 2; }
|
||||
|
||||
/** Current number of placed points. */
|
||||
virtual int32 GetCurrentPointCount() const = 0;
|
||||
|
||||
/** Called when placement is finished (enough points placed). */
|
||||
virtual void OnPlacementFinished() {}
|
||||
|
||||
/** Called when placement is cancelled (actor will be destroyed). */
|
||||
virtual void OnPlacementCancelled() {}
|
||||
};
|
||||
@@ -186,10 +186,9 @@ void UPS_Editor_SelectionManager::SelectActor(AActor* Actor)
|
||||
UpdateGizmo();
|
||||
OnSelectionChanged.Broadcast();
|
||||
|
||||
// Show spline handles and mark selected
|
||||
// Mark spline as selected (handles shown only in SplineEditing mode, not here)
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
{
|
||||
Spline->SetHandlesVisible(true);
|
||||
Spline->SetSelected(true);
|
||||
}
|
||||
|
||||
@@ -214,13 +213,25 @@ void UPS_Editor_SelectionManager::DeselectActor(AActor* Actor)
|
||||
UpdateGizmo();
|
||||
OnSelectionChanged.Broadcast();
|
||||
|
||||
// Hide spline handles and unmark
|
||||
// Unmark spline
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->SetSelected(false);
|
||||
}
|
||||
|
||||
// Exit spline editing mode if the edited spline is deselected
|
||||
if (APlayerController* PC = OwnerPC.Get())
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing && EditorPC->GetActivePlacementActor() == Actor)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Deselected %s"), *Actor->GetName());
|
||||
}
|
||||
}
|
||||
@@ -244,6 +255,25 @@ void UPS_Editor_SelectionManager::ClearSelection()
|
||||
if (AActor* Actor = Weak.Get())
|
||||
{
|
||||
SetActorHighlight(Actor, false);
|
||||
|
||||
// Spline-specific cleanup
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Actor))
|
||||
{
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->SetSelected(false);
|
||||
}
|
||||
|
||||
// Exit spline editing if needed
|
||||
if (APlayerController* PC = OwnerPC.Get())
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,5 +39,6 @@ UENUM(BlueprintType)
|
||||
enum class EPS_Editor_EditorMode : uint8
|
||||
{
|
||||
Normal, // Standard select/transform behavior
|
||||
SplinePlacement // Clicks add points to current spline
|
||||
SplinePlacement, // Clicks add points to current spline
|
||||
SplineEditing // Editing individual points of a selected spline
|
||||
};
|
||||
|
||||
@@ -46,18 +46,24 @@ void APS_Editor_SplineActor::BeginPlay()
|
||||
UMaterialInterface* SplineLineMat = LoadObject<UMaterialInterface>(
|
||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine.M_PS_Editor_SplineLine"));
|
||||
|
||||
// Spline line: normal depth-tested material (orange)
|
||||
if (!SplineLineMat)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: M_PS_Editor_SplineLine not found, trying without double name..."));
|
||||
SplineLineMat = LoadObject<UMaterialInterface>(nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine"));
|
||||
}
|
||||
|
||||
// Spline line: normal depth-tested material (uses SplineColor property)
|
||||
if (SplineLineMat)
|
||||
{
|
||||
SplineMat = UMaterialInstanceDynamic::Create(SplineLineMat, this);
|
||||
SplineMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 0.4f, 0.0f));
|
||||
SplineMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
}
|
||||
|
||||
// Spline line selected: no depth test (on top, bright orange)
|
||||
// Spline line selected: no depth test (uses SplineSelectedColor property)
|
||||
if (GizmoMat)
|
||||
{
|
||||
SplineMatSelected = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
SplineMatSelected->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 0.6f, 0.0f));
|
||||
SplineMatSelected->SetVectorParameterValue(TEXT("Color"), SplineSelectedColor);
|
||||
|
||||
// Handles always use gizmo material (on top, clickable)
|
||||
HandleMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
@@ -67,7 +73,7 @@ void APS_Editor_SplineActor::BeginPlay()
|
||||
HandleHighlightMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 1.0f, 0.0f)); // Yellow
|
||||
|
||||
CenterCubeMat = UMaterialInstanceDynamic::Create(GizmoMat, this);
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), FLinearColor(1.0f, 0.4f, 0.0f)); // Orange
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
}
|
||||
|
||||
// Create center cube (selection handle for whole spline)
|
||||
@@ -189,70 +195,54 @@ void APS_Editor_SplineActor::UpdateVisualization()
|
||||
|
||||
const float SplineLength = SplineComp->GetSplineLength();
|
||||
const int32 NumSamples = FMath::Max(2, FMath::CeilToInt(SplineLength / SampleInterval));
|
||||
const int32 Segs = TubeSegments;
|
||||
|
||||
TArray<FVector> Vertices;
|
||||
TArray<int32> Triangles;
|
||||
TArray<FVector> Normals;
|
||||
TArray<FVector2D> UVs;
|
||||
|
||||
// 4 vertices per sample: 2 for vertical ribbon + 2 for horizontal ribbon (cross-section)
|
||||
Vertices.Reserve(NumSamples * 4);
|
||||
Normals.Reserve(NumSamples * 4);
|
||||
UVs.Reserve(NumSamples * 4);
|
||||
// TubeSegments vertices per ring, NumSamples rings
|
||||
Vertices.Reserve(NumSamples * Segs);
|
||||
Normals.Reserve(NumSamples * Segs);
|
||||
UVs.Reserve(NumSamples * Segs);
|
||||
|
||||
for (int32 i = 0; i < NumSamples; i++)
|
||||
{
|
||||
const float Distance = (static_cast<float>(i) / (NumSamples - 1)) * SplineLength;
|
||||
const FVector Location = SplineComp->GetLocationAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
const FVector Right = SplineComp->GetRightVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
const FVector Up = FVector(0.0f, 0.0f, LineHalfWidth);
|
||||
const FVector SplineRight = SplineComp->GetRightVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
const FVector SplineUp = SplineComp->GetUpVectorAtDistanceAlongSpline(Distance, ESplineCoordinateSpace::Local);
|
||||
|
||||
const float U = static_cast<float>(i) / (NumSamples - 1);
|
||||
|
||||
// Vertical ribbon (visible from sides)
|
||||
Vertices.Add(Location - Up);
|
||||
Vertices.Add(Location + Up);
|
||||
Normals.Add(Right);
|
||||
Normals.Add(Right);
|
||||
UVs.Add(FVector2D(U, 0.0f));
|
||||
UVs.Add(FVector2D(U, 1.0f));
|
||||
for (int32 j = 0; j < Segs; j++)
|
||||
{
|
||||
const float Angle = (static_cast<float>(j) / Segs) * 2.0f * PI;
|
||||
const FVector Normal = FMath::Cos(Angle) * SplineRight + FMath::Sin(Angle) * SplineUp;
|
||||
const FVector VertPos = Location + Normal * TubeRadius;
|
||||
|
||||
// Horizontal ribbon (visible from above/below)
|
||||
Vertices.Add(Location - Right * LineHalfWidth);
|
||||
Vertices.Add(Location + Right * LineHalfWidth);
|
||||
Normals.Add(FVector::UpVector);
|
||||
Normals.Add(FVector::UpVector);
|
||||
UVs.Add(FVector2D(U, 0.0f));
|
||||
UVs.Add(FVector2D(U, 1.0f));
|
||||
Vertices.Add(VertPos);
|
||||
Normals.Add(Normal);
|
||||
UVs.Add(FVector2D(U, static_cast<float>(j) / Segs));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate triangles for both ribbons (double-sided)
|
||||
Triangles.Reserve((NumSamples - 1) * 24);
|
||||
// Generate triangles connecting consecutive rings
|
||||
Triangles.Reserve((NumSamples - 1) * Segs * 6);
|
||||
for (int32 i = 0; i < NumSamples - 1; i++)
|
||||
{
|
||||
// Vertical ribbon indices
|
||||
const int32 V0 = i * 4;
|
||||
const int32 V1 = i * 4 + 1;
|
||||
const int32 V2 = (i + 1) * 4;
|
||||
const int32 V3 = (i + 1) * 4 + 1;
|
||||
for (int32 j = 0; j < Segs; j++)
|
||||
{
|
||||
const int32 NextJ = (j + 1) % Segs;
|
||||
const int32 A = i * Segs + j;
|
||||
const int32 B = i * Segs + NextJ;
|
||||
const int32 C = (i + 1) * Segs + j;
|
||||
const int32 D = (i + 1) * Segs + NextJ;
|
||||
|
||||
// Horizontal ribbon indices
|
||||
const int32 H0 = i * 4 + 2;
|
||||
const int32 H1 = i * 4 + 3;
|
||||
const int32 H2 = (i + 1) * 4 + 2;
|
||||
const int32 H3 = (i + 1) * 4 + 3;
|
||||
|
||||
// Vertical: front + back
|
||||
Triangles.Add(V0); Triangles.Add(V2); Triangles.Add(V1);
|
||||
Triangles.Add(V1); Triangles.Add(V2); Triangles.Add(V3);
|
||||
Triangles.Add(V0); Triangles.Add(V1); Triangles.Add(V2);
|
||||
Triangles.Add(V1); Triangles.Add(V3); Triangles.Add(V2);
|
||||
|
||||
// Horizontal: front + back
|
||||
Triangles.Add(H0); Triangles.Add(H2); Triangles.Add(H1);
|
||||
Triangles.Add(H1); Triangles.Add(H2); Triangles.Add(H3);
|
||||
Triangles.Add(H0); Triangles.Add(H1); Triangles.Add(H2);
|
||||
Triangles.Add(H1); Triangles.Add(H3); Triangles.Add(H2);
|
||||
Triangles.Add(A); Triangles.Add(C); Triangles.Add(B);
|
||||
Triangles.Add(B); Triangles.Add(C); Triangles.Add(D);
|
||||
}
|
||||
}
|
||||
|
||||
SplineMeshComp->ClearAllMeshSections();
|
||||
@@ -416,17 +406,21 @@ void APS_Editor_SplineActor::SetSelected(bool bSelected)
|
||||
if (SplineMeshComp)
|
||||
{
|
||||
UMaterialInstanceDynamic* Mat = bSelected ? SplineMatSelected : SplineMat;
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: SetSelected(%s) - SplineMat=%s, SplineMatSelected=%s, Using=%s"),
|
||||
bSelected ? TEXT("true") : TEXT("false"),
|
||||
SplineMat ? TEXT("valid") : TEXT("NULL"),
|
||||
SplineMatSelected ? TEXT("valid") : TEXT("NULL"),
|
||||
Mat ? TEXT("valid") : TEXT("NULL"));
|
||||
if (Mat)
|
||||
{
|
||||
SplineMeshComp->SetMaterial(0, Mat);
|
||||
}
|
||||
}
|
||||
|
||||
// Center cube: bright when selected
|
||||
// Center cube: selected color when selected, normal color otherwise
|
||||
if (CenterCubeMat)
|
||||
{
|
||||
const FLinearColor Color = bSelected ? FLinearColor(1.0f, 0.8f, 0.0f) : FLinearColor(1.0f, 0.4f, 0.0f);
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), Color);
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), bSelected ? SplineSelectedColor : SplineColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,6 +429,50 @@ bool APS_Editor_SplineActor::IsCenterCube(const UPrimitiveComponent* Comp) const
|
||||
return Comp && Comp == CenterCube;
|
||||
}
|
||||
|
||||
void APS_Editor_SplineActor::UpdateSplineMaterials()
|
||||
{
|
||||
if (SplineMat)
|
||||
{
|
||||
SplineMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
}
|
||||
if (SplineMatSelected)
|
||||
{
|
||||
SplineMatSelected->SetVectorParameterValue(TEXT("Color"), SplineSelectedColor);
|
||||
}
|
||||
if (CenterCubeMat)
|
||||
{
|
||||
CenterCubeMat->SetVectorParameterValue(TEXT("Color"), SplineColor);
|
||||
}
|
||||
}
|
||||
|
||||
bool APS_Editor_SplineActor::IsSplineMesh(const UPrimitiveComponent* Comp) const
|
||||
{
|
||||
return Comp && Comp == SplineMeshComp;
|
||||
}
|
||||
|
||||
int32 APS_Editor_SplineActor::FindInsertIndex(const FVector& WorldLocation) const
|
||||
{
|
||||
if (!SplineComp || GetNumPoints() < 2) return GetNumPoints();
|
||||
|
||||
const float InputKey = SplineComp->FindInputKeyClosestToWorldLocation(WorldLocation);
|
||||
// Insert after the floor of the input key (between that point and the next)
|
||||
return FMath::Clamp(FMath::FloorToInt(InputKey) + 1, 1, GetNumPoints());
|
||||
}
|
||||
|
||||
void APS_Editor_SplineActor::InsertPoint(int32 Index, const FVector& WorldPosition)
|
||||
{
|
||||
if (!SplineComp) return;
|
||||
|
||||
Index = FMath::Clamp(Index, 0, GetNumPoints());
|
||||
|
||||
const FVector LocalPos = GetActorTransform().InverseTransformPosition(WorldPosition);
|
||||
|
||||
// Use AddSplinePointAtIndex via setting all points (USplineComponent doesn't have a direct insert-at-index in all versions)
|
||||
TArray<FVector> Points = GetAllPointLocations();
|
||||
Points.Insert(WorldPosition, Index);
|
||||
SetAllPointLocations(Points);
|
||||
}
|
||||
|
||||
FVector APS_Editor_SplineActor::GetCentroid() const
|
||||
{
|
||||
const int32 Num = GetNumPoints();
|
||||
@@ -462,6 +500,14 @@ void APS_Editor_SplineActor::ExportToCustomProperties(TMap<FString, FString>& Ou
|
||||
FString::Printf(TEXT("SplinePoint_%d"), i),
|
||||
FString::Printf(TEXT("X=%.4f Y=%.4f Z=%.4f"), Loc.X, Loc.Y, Loc.Z));
|
||||
}
|
||||
|
||||
// Save color and tag
|
||||
OutProperties.Add(TEXT("SplineColor"), SplineColor.ToString());
|
||||
OutProperties.Add(TEXT("SplineSelectedColor"), SplineSelectedColor.ToString());
|
||||
if (!SplineTag.IsEmpty())
|
||||
{
|
||||
OutProperties.Add(TEXT("SplineTag"), SplineTag);
|
||||
}
|
||||
}
|
||||
|
||||
void APS_Editor_SplineActor::ImportFromCustomProperties(const TMap<FString, FString>& Properties)
|
||||
@@ -490,4 +536,19 @@ void APS_Editor_SplineActor::ImportFromCustomProperties(const TMap<FString, FStr
|
||||
{
|
||||
SetAllPointLocations(Points);
|
||||
}
|
||||
|
||||
// Restore color and tag
|
||||
if (const FString* ColorStr = Properties.Find(TEXT("SplineColor")))
|
||||
{
|
||||
SplineColor.InitFromString(*ColorStr);
|
||||
}
|
||||
if (const FString* SelColorStr = Properties.Find(TEXT("SplineSelectedColor")))
|
||||
{
|
||||
SplineSelectedColor.InitFromString(*SelColorStr);
|
||||
}
|
||||
if (const FString* TagStr = Properties.Find(TEXT("SplineTag")))
|
||||
{
|
||||
SplineTag = *TagStr;
|
||||
}
|
||||
UpdateSplineMaterials();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "PS_Editor_SplineActor.generated.h"
|
||||
|
||||
class USplineComponent;
|
||||
@@ -12,13 +13,34 @@ class UProceduralMeshComponent;
|
||||
* Renders as a visible quad strip with clickable point handles.
|
||||
*/
|
||||
UCLASS()
|
||||
class PS_EDITOR_API APS_Editor_SplineActor : public AActor
|
||||
class PS_EDITOR_API APS_Editor_SplineActor : public AActor, public IPS_Editor_PointPlaceable
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
APS_Editor_SplineActor();
|
||||
|
||||
// ---- IPS_Editor_PointPlaceable interface ----
|
||||
virtual void AddPlacementPoint(const FVector& WorldPosition) override { AddPoint(WorldPosition); }
|
||||
virtual int32 GetMinimumPoints() const override { return 2; }
|
||||
virtual int32 GetCurrentPointCount() const override { return GetNumPoints(); }
|
||||
|
||||
/** Get the underlying USplineComponent (for AI path following, etc.). */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Spline")
|
||||
USplineComponent* GetSplineComponent() const { return SplineComp; }
|
||||
|
||||
/** Spline display color (override in Blueprint subclasses for variants). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline")
|
||||
FLinearColor SplineColor = FLinearColor(1.0f, 0.4f, 0.0f); // Orange default
|
||||
|
||||
/** Spline selected color (brighter version). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline")
|
||||
FLinearColor SplineSelectedColor = FLinearColor(1.0f, 0.6f, 0.0f);
|
||||
|
||||
/** Custom tag for gameplay identification (e.g. "Enemy", "Civilian", "Patrol"). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Spline")
|
||||
FString SplineTag;
|
||||
|
||||
// ---- Point manipulation ----
|
||||
|
||||
/** Add a point at the end of the spline (world space). */
|
||||
@@ -47,6 +69,10 @@ public:
|
||||
/** Regenerate the line mesh and reposition all handles. */
|
||||
void UpdateVisualization();
|
||||
|
||||
/** Re-apply SplineColor to materials (call after changing SplineColor at runtime). */
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor|Spline")
|
||||
void UpdateSplineMaterials();
|
||||
|
||||
// ---- Handle interaction ----
|
||||
|
||||
/** Find which point handle is under the cursor ray. Returns -1 if none. */
|
||||
@@ -69,6 +95,15 @@ public:
|
||||
|
||||
// ---- Serialization ----
|
||||
|
||||
/** Returns true if the given component is the spline line mesh. */
|
||||
bool IsSplineMesh(const UPrimitiveComponent* Comp) const;
|
||||
|
||||
/** Find the best insertion index for a new point at the given world location. */
|
||||
int32 FindInsertIndex(const FVector& WorldLocation) const;
|
||||
|
||||
/** Insert a point at a specific index. */
|
||||
void InsertPoint(int32 Index, const FVector& WorldPosition);
|
||||
|
||||
/** Get the centroid of all spline points (world space). Used as pivot for gizmo. */
|
||||
FVector GetCentroid() const;
|
||||
|
||||
@@ -122,8 +157,9 @@ private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> CenterCubeMat;
|
||||
|
||||
/** Line rendering settings. */
|
||||
float LineHalfWidth = 2.0f;
|
||||
/** Tube rendering settings. */
|
||||
float TubeRadius = 2.0f;
|
||||
int32 TubeSegments = 8;
|
||||
float SampleInterval = 10.0f;
|
||||
float HandleScale = 0.12f;
|
||||
float CenterCubeScale = 0.10f;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
#include "PS_Editor_PointPlaceable.h"
|
||||
#include "Widgets/SBoxPanel.h"
|
||||
#include "Widgets/Text/STextBlock.h"
|
||||
#include "Widgets/Layout/SBorder.h"
|
||||
@@ -116,21 +118,100 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
BuildOutliner()
|
||||
]
|
||||
]
|
||||
// Bottom help bar
|
||||
// Bottom notification / help bar
|
||||
+ SOverlay::Slot()
|
||||
.HAlign(HAlign_Center)
|
||||
.VAlign(VAlign_Bottom)
|
||||
.Padding(16.0f)
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderBackgroundColor(FLinearColor(0.05f, 0.05f, 0.05f, 0.75f))
|
||||
.BorderBackgroundColor(FLinearColor(0.05f, 0.05f, 0.05f, 0.85f))
|
||||
.Padding(FMargin(16.0f, 6.0f))
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("LMB: Select/Pan | RMB: Fly | Alt+LMB/MMB: Orbit | Z/E/R: Translate/Rotate/Scale | End: Snap | Del: Delete")))
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot().FillWidth(1.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(HelpBarText, STextBlock)
|
||||
.Text(FText::FromString(TEXT("LMB: Select/Pan | RMB: Fly | Alt+LMB/MMB: Orbit | Z/E/R: Translate/Rotate/Scale | End: Snap | Del: Delete | S: Spline")))
|
||||
.ColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f, 1.0f))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(12.0f, 0.0f, 0.0f, 0.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(SplineEditButton, SButton)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
else if (UPS_Editor_SelectionManager* SelMgr = SelectionManager.Get())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SelMgr->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
{
|
||||
EditorPC->BeginSplineEditing(Spline);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Edit Spline")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
|
||||
.ColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(8.0f, 0.0f, 0.0f, 0.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(SplineDeletePointButton, SButton)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
// Simulate Delete key press for the selected spline point
|
||||
if (APS_Editor_Pawn* Pawn = Cast<APS_Editor_Pawn>(GetOwningPlayerPawn()))
|
||||
{
|
||||
Pawn->HandleDelete(FInputActionValue());
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Delete Point")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
|
||||
.ColorAndOpacity(FLinearColor(1.0f, 0.3f, 0.2f))
|
||||
]
|
||||
]
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(8.0f, 0.0f, 0.0f, 0.0f).VAlign(VAlign_Center)
|
||||
[
|
||||
SAssignNew(SplineFinishButton, SButton)
|
||||
.Visibility(EVisibility::Collapsed)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
EditorPC->FinishPointPlacement();
|
||||
else if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Done")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 10))
|
||||
.ColorAndOpacity(FLinearColor(0.3f, 0.9f, 0.3f))
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -250,11 +331,11 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildToolbar()
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplinePlacement();
|
||||
EditorPC->FinishPointPlacement();
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorPC->BeginSplinePlacement();
|
||||
EditorPC->BeginPointPlacement();
|
||||
}
|
||||
}
|
||||
return FReply::Handled();
|
||||
@@ -673,11 +754,25 @@ void UPS_Editor_MainWidget::PopulateSpawnCatalog()
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this, EntryIndex]() -> FReply
|
||||
{
|
||||
if (UPS_Editor_SpawnManager* Mgr = SpawnManager.Get())
|
||||
UPS_Editor_SpawnManager* Mgr = SpawnManager.Get();
|
||||
if (!Mgr) return FReply::Handled();
|
||||
|
||||
// Check if this actor implements IPS_Editor_PointPlaceable → enter placement mode
|
||||
const TArray<FPS_Editor_ResolvedEntry>& AllEntries = Mgr->GetResolvedEntries();
|
||||
if (AllEntries.IsValidIndex(EntryIndex) && AllEntries[EntryIndex].ActorClass)
|
||||
{
|
||||
Mgr->SpawnFromCatalog(EntryIndex);
|
||||
if (AllEntries[EntryIndex].ActorClass->ImplementsInterface(UPS_Editor_PointPlaceable::StaticClass()))
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
EditorPC->BeginPointPlacement(AllEntries[EntryIndex].ActorClass);
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
}
|
||||
|
||||
Mgr->SpawnFromCatalog(EntryIndex);
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(SBox)
|
||||
@@ -688,6 +783,74 @@ void UPS_Editor_MainWidget::PopulateSpawnCatalog()
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
// ---- Tools section (built-in tools) ----
|
||||
SpawnListContainer->AddSlot()
|
||||
.AutoHeight()
|
||||
.Padding(0.0f, 10.0f, 0.0f, 2.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Tools")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 9))
|
||||
.ColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f))
|
||||
];
|
||||
|
||||
// Spline tool
|
||||
SpawnListContainer->AddSlot()
|
||||
.AutoHeight()
|
||||
.Padding(4.0f, 4.0f)
|
||||
[
|
||||
SNew(SButton)
|
||||
.OnClicked_Lambda([this]() -> FReply
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishPointPlacement();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a spline is selected, append to it
|
||||
if (UPS_Editor_SelectionManager* SelMgr = SelectionManager.Get())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SelMgr->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
{
|
||||
EditorPC->BeginPointPlacementAppend(Spline);
|
||||
return FReply::Handled();
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorPC->BeginPointPlacement();
|
||||
}
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(SBox)
|
||||
.MinDesiredWidth(88.0f)
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("~")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Bold", 28))
|
||||
.ColorAndOpacity(FLinearColor(1.0f, 0.4f, 0.0f))
|
||||
]
|
||||
+ SVerticalBox::Slot().AutoHeight().HAlign(HAlign_Center).Padding(0.0f, 2.0f, 0.0f, 0.0f)
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("Spline")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 9))
|
||||
.Justification(ETextJustify::Center)
|
||||
.ColorAndOpacity(FLinearColor::White)
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
void UPS_Editor_MainWidget::NativeTick(const FGeometry& MyGeometry, float InDeltaTime)
|
||||
@@ -708,21 +871,20 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
return;
|
||||
}
|
||||
|
||||
// Update mode text
|
||||
if (ModeText.IsValid())
|
||||
{
|
||||
// Check for spline placement mode first
|
||||
bool bSplineMode = false;
|
||||
// Update mode text + help bar based on editor mode
|
||||
EPS_Editor_EditorMode CurrentEditorMode = EPS_Editor_EditorMode::Normal;
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
ModeText->SetText(FText::FromString(TEXT("Spline Placement (click to add, RMB/Enter to finish, Esc to cancel)")));
|
||||
bSplineMode = true;
|
||||
}
|
||||
CurrentEditorMode = EditorPC->GetEditorMode();
|
||||
}
|
||||
|
||||
if (!bSplineMode)
|
||||
if (ModeText.IsValid())
|
||||
{
|
||||
if (CurrentEditorMode == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
ModeText->SetText(FText::FromString(TEXT("Spline Placement")));
|
||||
}
|
||||
else
|
||||
{
|
||||
FString ModeStr;
|
||||
switch (SM->GetTransformMode())
|
||||
@@ -735,6 +897,80 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
}
|
||||
}
|
||||
|
||||
// Determine if a spline is selected (for Edit button)
|
||||
bool bSplineSelected = false;
|
||||
if (CurrentEditorMode == EPS_Editor_EditorMode::Normal && SM->IsAnythingSelected())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||
{
|
||||
if (Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
{
|
||||
bSplineSelected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update help bar text and button visibility
|
||||
if (CurrentEditorMode == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
if (HelpBarText.IsValid())
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("SPLINE: LMB to add points | RMB or Enter to finish | Escape to cancel")));
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Visible);
|
||||
}
|
||||
else if (CurrentEditorMode == EPS_Editor_EditorMode::SplineEditing)
|
||||
{
|
||||
// Check if a specific point is selected (for Delete Point button)
|
||||
bool bPointSelected = false;
|
||||
if (APS_Editor_Pawn* Pawn = Cast<APS_Editor_Pawn>(GetOwningPlayerPawn()))
|
||||
{
|
||||
bPointSelected = (Pawn->ActiveSplinePointIndex >= 0);
|
||||
}
|
||||
|
||||
if (HelpBarText.IsValid())
|
||||
{
|
||||
if (bPointSelected)
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("EDIT SPLINE: Drag gizmo to move point | Del: delete point | Click line to insert | Escape: done")));
|
||||
}
|
||||
else
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("EDIT SPLINE: Click handle to select point | Click line to insert | End: snap to ground | Escape: done")));
|
||||
}
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(bPointSelected ? EVisibility::Visible : EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Visible);
|
||||
}
|
||||
else if (bSplineSelected)
|
||||
{
|
||||
if (HelpBarText.IsValid())
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("Spline selected — click Edit to modify points | S: add points")));
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(0.8f, 0.8f, 0.5f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Visible);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HelpBarText.IsValid())
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("LMB: Select/Pan | RMB: Fly | Alt+LMB/MMB: Orbit | Z/E/R: Translate/Rotate/Scale | End: Snap | Del: Delete | S: Spline")));
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(0.6f, 0.6f, 0.6f));
|
||||
}
|
||||
if (SplineEditButton.IsValid()) SplineEditButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineDeletePointButton.IsValid()) SplineDeletePointButton->SetVisibility(EVisibility::Collapsed);
|
||||
if (SplineFinishButton.IsValid()) SplineFinishButton->SetVisibility(EVisibility::Collapsed);
|
||||
}
|
||||
|
||||
// Update space button text
|
||||
if (SpaceText.IsValid())
|
||||
{
|
||||
|
||||
@@ -87,6 +87,12 @@ private:
|
||||
TSharedPtr<SVerticalBox> OutlinerListContainer;
|
||||
int32 LastOutlinerActorCount = -1;
|
||||
|
||||
// Bottom notification bar
|
||||
TSharedPtr<STextBlock> HelpBarText;
|
||||
TSharedPtr<SWidget> SplineFinishButton;
|
||||
TSharedPtr<SWidget> SplineEditButton;
|
||||
TSharedPtr<SWidget> SplineDeletePointButton;
|
||||
|
||||
// ---- Helpers ----
|
||||
TSharedRef<SWidget> BuildToolbar();
|
||||
TSharedRef<SWidget> BuildPropertiesPanel();
|
||||
|
||||
Reference in New Issue
Block a user