Unify spline creation/editing into single seamless mode
Major UX simplification: - Removed SplineEditing mode — single SplinePlacement mode handles both creation and editing with identical behavior - Actor spawned and tracked on first click (no "finish" step needed) - Each point add/insert/move has individual undo - Right-click always = camera fly (no special handling during creation) - Escape/Done just exits edit mode, nothing to "finish" Catalog system: - MasterCatalog DataAsset groups multiple SpawnCatalogs (Tools, Characters, Props) - PlayerController references one MasterCatalog instead of single catalog - SpawnManager.AddCatalog() loads sub-catalogs incrementally - Factory for MasterCatalog creation in Content Browser Spline polish: - SplineActor has built-in SpawnableComponent + EditableComponent - SplineColor/SplineSelectedColor/SplineTag as UPROPERTY for BP variants - All spline point operations use World coordinate space (fixes snap-to-ground) - Snap-to-ground ignores gizmo actor (was hitting gizmo guide lines) - Tube mesh collision forced with UpdateBounds after CreateMeshSection - Tube insert and handle selection include active placement actor (not just selected) - Notification bar shows contextual Delete Point button when CP selected 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.
BIN
Unreal/Plugins/PS_Editor/Content/PS_MasterCatalog.uasset
Normal file
BIN
Unreal/Plugins/PS_Editor/Content/PS_MasterCatalog.uasset
Normal file
Binary file not shown.
BIN
Unreal/Plugins/PS_Editor/Content/PS_PropsCatalog.uasset
Normal file
BIN
Unreal/Plugins/PS_Editor/Content/PS_PropsCatalog.uasset
Normal file
Binary file not shown.
BIN
Unreal/Plugins/PS_Editor/Content/PS_SplineEnnemy.uasset
Normal file
BIN
Unreal/Plugins/PS_Editor/Content/PS_SplineEnnemy.uasset
Normal file
Binary file not shown.
BIN
Unreal/Plugins/PS_Editor/Content/PS_ToolsCatalog.uasset
Normal file
BIN
Unreal/Plugins/PS_Editor/Content/PS_ToolsCatalog.uasset
Normal file
Binary file not shown.
@@ -577,62 +577,93 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for spline point handle hit → select point, move gizmo to it (only in SplineEditing mode)
|
||||
// Check for spline point handle hit → select point, move gizmo to it (SplineEditing or SplinePlacement)
|
||||
if (APS_Editor_PlayerController* EdPC = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (EdPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
const EPS_Editor_EditorMode Mode = EdPC->GetEditorMode();
|
||||
if (Mode == EPS_Editor_EditorMode::SplinePlacement || Mode == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
// Collect candidate splines: selected actors + active placement actor
|
||||
TArray<APS_Editor_SplineActor*> CandidateSplines;
|
||||
if (UPS_Editor_SelectionManager* SM = EdPC->GetSelectionManager())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
if (APS_Editor_SplineActor* S = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
{
|
||||
// First check: did we hit the center cube? If so, skip handle detection
|
||||
// (let the click fall through to normal selection → gizmo at actor pivot)
|
||||
FHitResult SplineHit;
|
||||
FCollisionQueryParams SplineParams;
|
||||
SplineParams.AddIgnoredActor(this);
|
||||
const FVector TraceEnd = RayOrigin + RayDir * 100000.0f;
|
||||
if (GetWorld()->LineTraceSingleByChannel(SplineHit, RayOrigin, TraceEnd, ECC_Visibility, SplineParams))
|
||||
CandidateSplines.AddUnique(S);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (APS_Editor_SplineActor* ActiveSpline = Cast<APS_Editor_SplineActor>(EdPC->GetActivePlacementActor()))
|
||||
{
|
||||
CandidateSplines.AddUnique(ActiveSpline);
|
||||
}
|
||||
|
||||
for (APS_Editor_SplineActor* Spline : CandidateSplines)
|
||||
{
|
||||
// Center cube check (skip in SplinePlacement — no center cube interaction during creation)
|
||||
if (Mode == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
FHitResult SplineHit;
|
||||
FCollisionQueryParams SplineParams;
|
||||
SplineParams.AddIgnoredActor(this);
|
||||
const FVector TraceEnd = RayOrigin + RayDir * 100000.0f;
|
||||
if (GetWorld()->LineTraceSingleByChannel(SplineHit, RayOrigin, TraceEnd, ECC_Visibility, SplineParams))
|
||||
{
|
||||
if (Spline->IsCenterCube(SplineHit.GetComponent()))
|
||||
{
|
||||
if (Spline->IsCenterCube(SplineHit.GetComponent()))
|
||||
{
|
||||
// Center cube hit → reset point selection, let normal flow handle it
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
Spline->ClearHandleHighlight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const int32 HandleIdx = Spline->GetHandleIndexUnderCursor(RayOrigin, RayDir);
|
||||
if (HandleIdx >= 0)
|
||||
{
|
||||
ActiveSplinePointIndex = HandleIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
Spline->HighlightHandle(HandleIdx);
|
||||
|
||||
if (APS_Editor_Gizmo* Gizmo = SM->GetGizmo())
|
||||
{
|
||||
Gizmo->SetActorLocation(Spline->GetPointLocation(HandleIdx));
|
||||
}
|
||||
|
||||
SM->SetTransformMode(EPS_Editor_TransformMode::Translate);
|
||||
return;
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
Spline->ClearHandleHighlight();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int32 HandleIdx = Spline->GetHandleIndexUnderCursor(RayOrigin, RayDir);
|
||||
if (HandleIdx >= 0)
|
||||
{
|
||||
ActiveSplinePointIndex = HandleIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
Spline->HighlightHandle(HandleIdx);
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM = EdPC->GetSelectionManager())
|
||||
{
|
||||
if (APS_Editor_Gizmo* Gizmo = SM->GetGizmo())
|
||||
{
|
||||
// Force gizmo active at this point (even if spline not in SelectionManager yet)
|
||||
Gizmo->SetGizmoActive(true, Spline->GetPointLocation(HandleIdx));
|
||||
}
|
||||
SM->SetTransformMode(EPS_Editor_TransformMode::Translate);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In SplineEditing mode: click on spline tube mesh → insert a CP
|
||||
// In SplinePlacement 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 (EdPC2->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
// Collect candidate splines: selected + active placement actor
|
||||
TArray<APS_Editor_SplineActor*> TubeCandidates;
|
||||
if (UPS_Editor_SelectionManager* SM2 = EdPC2->GetSelectionManager())
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM2->GetSelectedActors())
|
||||
{
|
||||
if (APS_Editor_SplineActor* S = Cast<APS_Editor_SplineActor>(Weak.Get()))
|
||||
TubeCandidates.AddUnique(S);
|
||||
}
|
||||
}
|
||||
if (APS_Editor_SplineActor* ActiveSpline = Cast<APS_Editor_SplineActor>(EdPC2->GetActivePlacementActor()))
|
||||
{
|
||||
TubeCandidates.AddUnique(ActiveSpline);
|
||||
}
|
||||
|
||||
if (TubeCandidates.Num() > 0)
|
||||
{
|
||||
FHitResult LineHit;
|
||||
FCollisionQueryParams LineParams;
|
||||
@@ -641,10 +672,8 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(LineHit, RayOrigin, TraceEnd, ECC_Visibility, LineParams))
|
||||
{
|
||||
for (const TWeakObjectPtr<AActor>& Weak : SM2->GetSelectedActors())
|
||||
for (APS_Editor_SplineActor* Spline : TubeCandidates)
|
||||
{
|
||||
APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(Weak.Get());
|
||||
if (!Spline) continue;
|
||||
|
||||
if (Spline->IsSplineMesh(LineHit.GetComponent()))
|
||||
{
|
||||
@@ -668,11 +697,14 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
DraggedSplineActor = Spline;
|
||||
Spline->HighlightHandle(InsertIdx);
|
||||
|
||||
if (APS_Editor_Gizmo* G = SM2->GetGizmo())
|
||||
if (UPS_Editor_SelectionManager* InsertSM = EdPC2->GetSelectionManager())
|
||||
{
|
||||
G->SetActorLocation(Spline->GetPointLocation(InsertIdx));
|
||||
if (APS_Editor_Gizmo* G = InsertSM->GetGizmo())
|
||||
{
|
||||
G->SetGizmoActive(true, Spline->GetPointLocation(InsertIdx));
|
||||
}
|
||||
InsertSM->SetTransformMode(EPS_Editor_TransformMode::Translate);
|
||||
}
|
||||
SM2->SetTransformMode(EPS_Editor_TransformMode::Translate);
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Inserted spline point at index %d"), InsertIdx);
|
||||
return;
|
||||
@@ -683,6 +715,63 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
||||
}
|
||||
}
|
||||
|
||||
// In SplineEditing mode: click outside tube/handles → add point at end
|
||||
if (APS_Editor_PlayerController* EdPC3 = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (EdPC3->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(EdPC3->GetActivePlacementActor()))
|
||||
{
|
||||
// Raycast to get world click position
|
||||
FHitResult AddHit;
|
||||
FCollisionQueryParams AddParams;
|
||||
AddParams.AddIgnoredActor(this);
|
||||
AddParams.AddIgnoredActor(Spline);
|
||||
const FVector AddEnd = RayOrigin + RayDir * 100000.0f;
|
||||
|
||||
FVector ClickPos;
|
||||
if (GetWorld()->LineTraceSingleByChannel(AddHit, RayOrigin, AddEnd, ECC_Visibility, AddParams))
|
||||
{
|
||||
ClickPos = AddHit.ImpactPoint + FVector(0.0f, 0.0f, 5.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
ClickPos = RayOrigin + RayDir * 1000.0f;
|
||||
}
|
||||
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
Spline->AddPoint(ClickPos);
|
||||
|
||||
if (UPS_Editor_UndoManager* UM = EdPC3->GetUndoManager())
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Add spline point");
|
||||
UM->RecordAction(Action);
|
||||
}
|
||||
|
||||
// Select the new point
|
||||
const int32 NewIdx = Spline->GetNumPoints() - 1;
|
||||
ActiveSplinePointIndex = NewIdx;
|
||||
DraggedSplineActor = Spline;
|
||||
Spline->HighlightHandle(NewIdx);
|
||||
|
||||
if (UPS_Editor_SelectionManager* SM3 = EdPC3->GetSelectionManager())
|
||||
{
|
||||
if (APS_Editor_Gizmo* G = SM3->GetGizmo())
|
||||
{
|
||||
G->SetActorLocation(Spline->GetPointLocation(NewIdx));
|
||||
}
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Added spline point at end (index %d)"), NewIdx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clicking elsewhere resets spline point selection
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
@@ -809,15 +898,6 @@ void APS_Editor_Pawn::HandleLeftClickCompleted(const FInputActionValue& Value)
|
||||
|
||||
void APS_Editor_Pawn::HandleRightClickStarted(const FInputActionValue& Value)
|
||||
{
|
||||
// In spline placement mode, right-click finishes the spline
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetController()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishPointPlacement();
|
||||
return;
|
||||
}
|
||||
}
|
||||
SetCameraMode(EPS_Editor_CameraMode::Fly);
|
||||
}
|
||||
|
||||
@@ -889,7 +969,7 @@ void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
||||
if (!EditorPC) return;
|
||||
|
||||
// In SplineEditing mode with a point selected: delete that point
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement
|
||||
&& ActiveSplinePointIndex >= 0 && DraggedSplineActor.IsValid())
|
||||
{
|
||||
APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(DraggedSplineActor.Get());
|
||||
@@ -1005,21 +1085,34 @@ 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 a spline point is selected (in edit or placement mode), 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 TraceStart = PointLoc + FVector(0.0f, 0.0f, 1.0f); // Just above current position
|
||||
const FVector TraceEnd = PointLoc - FVector(0.0f, 0.0f, 10000.0f);
|
||||
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Params;
|
||||
Params.AddIgnoredActor(Spline);
|
||||
Params.AddIgnoredActor(this);
|
||||
if (UPS_Editor_SelectionManager* SnapSM = EditorPC->GetSelectionManager())
|
||||
{
|
||||
if (APS_Editor_Gizmo* SnapGizmo = SnapSM->GetGizmo())
|
||||
{
|
||||
Params.AddIgnoredActor(SnapGizmo);
|
||||
}
|
||||
}
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(Hit, TraceStart, TraceEnd, ECC_Visibility, Params))
|
||||
{
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: SnapToGround - PointLoc=%s, HitZ=%.2f, HitActor=%s, HitComp=%s"),
|
||||
*PointLoc.ToString(), Hit.ImpactPoint.Z,
|
||||
Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("null"),
|
||||
Hit.GetComponent() ? *Hit.GetComponent()->GetName() : TEXT("null"));
|
||||
|
||||
// Store state for undo
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
|
||||
@@ -1103,21 +1196,23 @@ void APS_Editor_Pawn::HandleSplineMode(const FInputActionValue& Value)
|
||||
}
|
||||
else
|
||||
{
|
||||
// If a spline is selected, append points to it instead of creating a new one
|
||||
// S key only appends to a selected point-placeable actor (no new creation from shortcut)
|
||||
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()))
|
||||
if (AActor* Actor = Weak.Get())
|
||||
{
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
EditorPC->BeginPointPlacementAppend(Spline);
|
||||
return;
|
||||
if (Actor->Implements<UPS_Editor_PointPlaceable>())
|
||||
{
|
||||
ActiveSplinePointIndex = -1;
|
||||
DraggedSplineActor = nullptr;
|
||||
EditorPC->BeginPointPlacementAppend(Actor);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorPC->BeginPointPlacement();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1141,7 +1236,7 @@ void APS_Editor_Pawn::HandleCancel(const FInputActionValue& Value)
|
||||
{
|
||||
EditorPC->CancelPointPlacement();
|
||||
}
|
||||
else if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
else if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "PS_Editor_SelectionManager.h"
|
||||
#include "PS_Editor_UndoManager.h"
|
||||
#include "PS_Editor_SpawnManager.h"
|
||||
#include "PS_Editor_SpawnCatalog.h"
|
||||
#include "PS_Editor_SceneSerializer.h"
|
||||
#include "PS_Editor_Gizmo.h"
|
||||
#include "PS_Editor_Pawn.h"
|
||||
@@ -26,10 +27,26 @@ void APS_Editor_PlayerController::BeginPlay()
|
||||
|
||||
UndoManager = NewObject<UPS_Editor_UndoManager>(this);
|
||||
|
||||
// Load spawn catalog and create spawn manager
|
||||
// Create spawn manager and load catalogs from MasterCatalog
|
||||
SpawnManager = NewObject<UPS_Editor_SpawnManager>(this);
|
||||
UPS_Editor_SpawnCatalog* LoadedCatalog = SpawnCatalogAsset.LoadSynchronous();
|
||||
SpawnManager->Initialize(this, LoadedCatalog);
|
||||
SpawnManager->Initialize(this);
|
||||
|
||||
if (UPS_Editor_MasterCatalog* Master = MasterCatalogAsset.LoadSynchronous())
|
||||
{
|
||||
for (const TSoftObjectPtr<UPS_Editor_SpawnCatalog>& CatalogRef : Master->Catalogs)
|
||||
{
|
||||
if (UPS_Editor_SpawnCatalog* Catalog = CatalogRef.LoadSynchronous())
|
||||
{
|
||||
SpawnManager->AddCatalog(Catalog);
|
||||
}
|
||||
}
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: MasterCatalog loaded with %d sub-catalogs, %d total entries"),
|
||||
Master->Catalogs.Num(), SpawnManager->GetResolvedEntries().Num());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: No MasterCatalog assigned."));
|
||||
}
|
||||
|
||||
SceneSerializer = NewObject<UPS_Editor_SceneSerializer>(this);
|
||||
SceneSerializer->SetSelectionManager(SelectionManager);
|
||||
@@ -121,67 +138,21 @@ void APS_Editor_PlayerController::BeginPointPlacementAppend(AActor* ExistingActo
|
||||
|
||||
void APS_Editor_PlayerController::FinishPointPlacement()
|
||||
{
|
||||
IPS_Editor_PointPlaceable* Placeable = Cast<IPS_Editor_PointPlaceable>(ActivePlacementActor);
|
||||
if (!ActivePlacementActor || !Placeable || Placeable->GetCurrentPointCount() < Placeable->GetMinimumPoints())
|
||||
// Just exit the mode — actor is already tracked, all edits already have undo
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
CancelPointPlacement();
|
||||
return;
|
||||
Spline->SetHandlesVisible(false);
|
||||
Spline->ClearHandleHighlight();
|
||||
}
|
||||
|
||||
Placeable->OnPlacementFinished();
|
||||
|
||||
if (bAppendingToActor)
|
||||
if (ActivePlacementActor && SelectionManager)
|
||||
{
|
||||
// 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
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
|
||||
if (UndoManager)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SpawnAction> Action = MakeShared<FPS_Editor_SpawnAction>();
|
||||
Action->Actors.Add(ActivePlacementActor);
|
||||
Action->Description = FString::Printf(TEXT("Create %s (%d points)"),
|
||||
*ActivePlacementActor->GetClass()->GetName(), Placeable->GetCurrentPointCount());
|
||||
UndoManager->RecordAction(Action);
|
||||
}
|
||||
|
||||
if (SelectionManager)
|
||||
{
|
||||
SelectionManager->SelectActor(ActivePlacementActor);
|
||||
}
|
||||
SelectionManager->ClearSelection();
|
||||
SelectionManager->SelectActor(ActivePlacementActor);
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Placement finished (%d points)"), Placeable->GetCurrentPointCount());
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Exited placement mode"));
|
||||
|
||||
ActivePlacementActor = nullptr;
|
||||
bAppendingToActor = false;
|
||||
AppendInitialPoints.Empty();
|
||||
@@ -190,57 +161,8 @@ void APS_Editor_PlayerController::FinishPointPlacement()
|
||||
|
||||
void APS_Editor_PlayerController::CancelPointPlacement()
|
||||
{
|
||||
if (ActivePlacementActor)
|
||||
{
|
||||
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: 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"));
|
||||
// Same as finish — just exit mode. Undo handles reverting if needed.
|
||||
FinishPointPlacement();
|
||||
}
|
||||
|
||||
void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bAdditive)
|
||||
@@ -270,19 +192,77 @@ void APS_Editor_PlayerController::HandleEditorClick(FVector2D ScreenPos, bool bA
|
||||
|
||||
if (!ActivePlacementActor)
|
||||
{
|
||||
// First click: spawn the actor
|
||||
// First click: spawn the actor, track immediately, record undo
|
||||
FActorSpawnParameters SpawnParams;
|
||||
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||
UClass* ClassToSpawn = PlacementActorClass ? PlacementActorClass.Get() : APS_Editor_SplineActor::StaticClass();
|
||||
ActivePlacementActor = GetWorld()->SpawnActor<AActor>(ClassToSpawn, ClickWorldPos, FRotator::ZeroRotator, SpawnParams);
|
||||
|
||||
if (ActivePlacementActor)
|
||||
{
|
||||
// Track immediately (for outliner, save/load, scene clear)
|
||||
if (SpawnManager)
|
||||
{
|
||||
SpawnManager->TrackSpawnedActor(ActivePlacementActor);
|
||||
}
|
||||
|
||||
// Record spawn undo immediately
|
||||
if (UndoManager)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SpawnAction> Action = MakeShared<FPS_Editor_SpawnAction>();
|
||||
Action->Actors.Add(ActivePlacementActor);
|
||||
Action->Description = TEXT("Create placeable actor");
|
||||
UndoManager->RecordAction(Action);
|
||||
}
|
||||
|
||||
// Now we're "appending" to the tracked actor
|
||||
bAppendingToActor = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Add point via the interface
|
||||
if (IPS_Editor_PointPlaceable* Placeable = Cast<IPS_Editor_PointPlaceable>(ActivePlacementActor))
|
||||
// Check if we clicked on the existing spline tube → insert instead of append
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
if (Spline->GetNumPoints() >= 2)
|
||||
{
|
||||
// Raycast to check if we hit the tube mesh
|
||||
FHitResult TubeHit;
|
||||
FCollisionQueryParams TubeParams;
|
||||
TubeParams.AddIgnoredActor(GetPawn());
|
||||
const FVector TubeEnd = WorldOrigin + WorldDirection * 100000.0f;
|
||||
|
||||
if (GetWorld()->LineTraceSingleByChannel(TubeHit, WorldOrigin, TubeEnd, ECC_Visibility, TubeParams))
|
||||
{
|
||||
if (Spline->IsSplineMesh(TubeHit.GetComponent()))
|
||||
{
|
||||
const int32 InsertIdx = Spline->FindInsertIndex(TubeHit.ImpactPoint);
|
||||
Spline->InsertPoint(InsertIdx, TubeHit.ImpactPoint + FVector(0.0f, 0.0f, 5.0f));
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Inserted point at index %d during placement"), InsertIdx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default: add point at end via the interface (with undo)
|
||||
if (APS_Editor_SplineActor* Spline = Cast<APS_Editor_SplineActor>(ActivePlacementActor))
|
||||
{
|
||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||
Spline->AddPoint(ClickWorldPos);
|
||||
|
||||
if (UndoManager)
|
||||
{
|
||||
TSharedPtr<FPS_Editor_SplinePointAction> Action = MakeShared<FPS_Editor_SplinePointAction>();
|
||||
Action->SplineActor = Spline;
|
||||
Action->OldPoints = OldPoints;
|
||||
Action->NewPoints = Spline->GetAllPointLocations();
|
||||
Action->Description = TEXT("Add spline point");
|
||||
UndoManager->RecordAction(Action);
|
||||
}
|
||||
}
|
||||
else if (IPS_Editor_PointPlaceable* Placeable = Cast<IPS_Editor_PointPlaceable>(ActivePlacementActor))
|
||||
{
|
||||
Placeable->AddPlacementPoint(ClickWorldPos);
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Point %d added at %s"),
|
||||
Placeable->GetCurrentPointCount(), *ClickWorldPos.ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ class UPS_Editor_SelectionManager;
|
||||
class UPS_Editor_UndoManager;
|
||||
class UPS_Editor_SpawnManager;
|
||||
class UPS_Editor_SpawnCatalog;
|
||||
class UPS_Editor_MasterCatalog;
|
||||
class UPS_Editor_SceneSerializer;
|
||||
class APS_Editor_SplineActor;
|
||||
class IPS_Editor_PointPlaceable;
|
||||
@@ -37,9 +38,9 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "PS Editor")
|
||||
UPS_Editor_SceneSerializer* GetSceneSerializer() const { return SceneSerializer; }
|
||||
|
||||
/** The spawn catalog DataAsset to load. Set this in the Blueprint or World Settings. */
|
||||
/** Master catalog grouping all sub-catalogs (Tools, Characters, Props...). */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||
TSoftObjectPtr<UPS_Editor_SpawnCatalog> SpawnCatalogAsset;
|
||||
TSoftObjectPtr<UPS_Editor_MasterCatalog> MasterCatalogAsset;
|
||||
|
||||
// ---- Editor Mode (Normal / SplinePlacement) ----
|
||||
|
||||
@@ -61,14 +62,17 @@ public:
|
||||
/** Get the actor currently being placed/edited (nullptr if not in placement mode). */
|
||||
AActor* GetActivePlacementActor() const { return ActivePlacementActor; }
|
||||
|
||||
/** True if we're appending to an existing actor (editing), false if creating new. */
|
||||
bool IsAppendingToActor() const { return bAppendingToActor; }
|
||||
|
||||
// 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);
|
||||
/** Convenience: enter point placement for an existing spline. */
|
||||
void BeginSplineEditing(AActor* Spline) { BeginPointPlacementAppend(Spline); }
|
||||
|
||||
/** Exit spline editing mode. */
|
||||
void FinishSplineEditing();
|
||||
/** Convenience: exit spline editing. */
|
||||
void FinishSplineEditing() { FinishPointPlacement(); }
|
||||
|
||||
protected:
|
||||
virtual void BeginPlay() override;
|
||||
|
||||
@@ -225,7 +225,7 @@ void UPS_Editor_SelectionManager::DeselectActor(AActor* Actor)
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing && EditorPC->GetActivePlacementActor() == Actor)
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement && EditorPC->GetActivePlacementActor() == Actor)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
@@ -268,7 +268,7 @@ void UPS_Editor_SelectionManager::ClearSelection()
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(PC))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,5 @@ UENUM(BlueprintType)
|
||||
enum class EPS_Editor_EditorMode : uint8
|
||||
{
|
||||
Normal, // Standard select/transform behavior
|
||||
SplinePlacement, // Clicks add points to current spline
|
||||
SplineEditing // Editing individual points of a selected spline
|
||||
SplinePlacement // Creating or editing a point-placeable actor (spline, etc.)
|
||||
};
|
||||
|
||||
@@ -43,3 +43,19 @@ public:
|
||||
void CaptureAllThumbnails();
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
* Master catalog that groups multiple SpawnCatalogs.
|
||||
* Assign this to the PlayerController. Each sub-catalog can represent a domain
|
||||
* (Tools, Characters, Props, Vehicles...).
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class PS_EDITOR_API UPS_Editor_MasterCatalog : public UPrimaryDataAsset
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Sub-catalogs to load. Each one contributes its entries to the runtime spawn UI. */
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PS Editor")
|
||||
TArray<TSoftObjectPtr<UPS_Editor_SpawnCatalog>> Catalogs;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,25 @@ FText UPS_Editor_SpawnCatalogFactory::GetDisplayName() const
|
||||
return LOCTEXT("SpawnCatalogDisplayName", "PS Editor Spawn Catalog");
|
||||
}
|
||||
|
||||
// ---- Master Catalog Factory ----
|
||||
|
||||
UPS_Editor_MasterCatalogFactory::UPS_Editor_MasterCatalogFactory()
|
||||
{
|
||||
SupportedClass = UPS_Editor_MasterCatalog::StaticClass();
|
||||
bCreateNew = true;
|
||||
bEditAfterNew = true;
|
||||
}
|
||||
|
||||
UObject* UPS_Editor_MasterCatalogFactory::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
|
||||
{
|
||||
return NewObject<UPS_Editor_MasterCatalog>(InParent, InClass, InName, Flags);
|
||||
}
|
||||
|
||||
FText UPS_Editor_MasterCatalogFactory::GetDisplayName() const
|
||||
{
|
||||
return LOCTEXT("MasterCatalogDisplayName", "PS Editor Master Catalog");
|
||||
}
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,4 +19,17 @@ public:
|
||||
virtual FText GetDisplayName() const override;
|
||||
};
|
||||
|
||||
UCLASS(HideCategories = Object)
|
||||
class UPS_Editor_MasterCatalogFactory : public UFactory
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPS_Editor_MasterCatalogFactory();
|
||||
|
||||
virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
|
||||
virtual bool ShouldShowInNewMenu() const override { return true; }
|
||||
virtual FText GetDisplayName() const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -5,32 +5,18 @@
|
||||
#include "GameFramework/PlayerController.h"
|
||||
#include "Engine/World.h"
|
||||
|
||||
void UPS_Editor_SpawnManager::Initialize(APlayerController* InOwnerPC, UPS_Editor_SpawnCatalog* InCatalog)
|
||||
void UPS_Editor_SpawnManager::Initialize(APlayerController* InOwnerPC)
|
||||
{
|
||||
OwnerPC = InOwnerPC;
|
||||
Catalog = InCatalog;
|
||||
|
||||
if (Catalog)
|
||||
{
|
||||
ResolveCatalog();
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: SpawnCatalog loaded with %d entries"), ResolvedEntries.Num());
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: No SpawnCatalog assigned. Spawn catalogue will be empty."));
|
||||
}
|
||||
}
|
||||
|
||||
void UPS_Editor_SpawnManager::ResolveCatalog()
|
||||
void UPS_Editor_SpawnManager::AddCatalog(UPS_Editor_SpawnCatalog* InCatalog)
|
||||
{
|
||||
ResolvedEntries.Empty();
|
||||
if (!InCatalog) return;
|
||||
|
||||
if (!Catalog)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const int32 BeforeCount = ResolvedEntries.Num();
|
||||
|
||||
for (const FPS_Editor_SpawnEntry& Entry : Catalog->Entries)
|
||||
for (const FPS_Editor_SpawnEntry& Entry : InCatalog->Entries)
|
||||
{
|
||||
// Synchronously load the class
|
||||
UClass* LoadedClass = Entry.ActorClass.LoadSynchronous();
|
||||
@@ -84,6 +70,9 @@ void UPS_Editor_SpawnManager::ResolveCatalog()
|
||||
|
||||
ResolvedEntries.Add(Resolved);
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Added catalog with %d entries (total: %d)"),
|
||||
ResolvedEntries.Num() - BeforeCount, ResolvedEntries.Num());
|
||||
}
|
||||
|
||||
AActor* UPS_Editor_SpawnManager::SpawnFromCatalog(int32 Index)
|
||||
|
||||
@@ -36,8 +36,11 @@ class PS_EDITOR_API UPS_Editor_SpawnManager : public UObject
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Initialize with owning controller and load catalog. */
|
||||
void Initialize(APlayerController* InOwnerPC, UPS_Editor_SpawnCatalog* InCatalog);
|
||||
/** Initialize with owning controller. */
|
||||
void Initialize(APlayerController* InOwnerPC);
|
||||
|
||||
/** Load and add a catalog. Can be called multiple times for multiple catalogs. */
|
||||
void AddCatalog(UPS_Editor_SpawnCatalog* InCatalog);
|
||||
|
||||
/** Spawn the actor at the given catalog index, positioned in front of the camera. Returns the spawned actor. */
|
||||
AActor* SpawnFromCatalog(int32 Index);
|
||||
@@ -70,9 +73,6 @@ private:
|
||||
UPROPERTY()
|
||||
TWeakObjectPtr<APlayerController> OwnerPC;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UPS_Editor_SpawnCatalog> Catalog;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FPS_Editor_ResolvedEntry> ResolvedEntries;
|
||||
|
||||
@@ -80,8 +80,6 @@ private:
|
||||
UPROPERTY()
|
||||
TArray<TWeakObjectPtr<AActor>> SpawnedActors;
|
||||
|
||||
/** Resolve all catalog entries (load classes, read SpawnableComponent metadata). */
|
||||
void ResolveCatalog();
|
||||
|
||||
/** Compute spawn location in front of the camera. */
|
||||
FVector ComputeSpawnLocation() const;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "PS_Editor_SplineActor.h"
|
||||
#include "PS_Editor_SpawnableComponent.h"
|
||||
#include "PS_Editor_EditableComponent.h"
|
||||
#include "Components/SplineComponent.h"
|
||||
#include "ProceduralMeshComponent.h"
|
||||
#include "Components/StaticMeshComponent.h"
|
||||
@@ -32,6 +34,17 @@ APS_Editor_SplineActor::APS_Editor_SplineActor()
|
||||
// Handle parent
|
||||
HandleRoot = CreateDefaultSubobject<USceneComponent>(TEXT("HandleRoot"));
|
||||
HandleRoot->SetupAttachment(Root);
|
||||
|
||||
// Spawnable: makes this actor appear in the catalogue
|
||||
SpawnableComp = CreateDefaultSubobject<UPS_Editor_SpawnableComponent>(TEXT("PS_Editor_Spawnable"));
|
||||
SpawnableComp->DisplayName = TEXT("Spline");
|
||||
SpawnableComp->Category = TEXT("Tools");
|
||||
|
||||
// Editable: exposes SplineColor and SplineTag in the runtime property panel
|
||||
EditableComp = CreateDefaultSubobject<UPS_Editor_EditableComponent>(TEXT("PS_Editor_Editable"));
|
||||
EditableComp->EditableProperties.Add(FName(TEXT("SplineColor")));
|
||||
EditableComp->EditableProperties.Add(FName(TEXT("SplineSelectedColor")));
|
||||
EditableComp->EditableProperties.Add(FName(TEXT("SplineTag")));
|
||||
}
|
||||
|
||||
void APS_Editor_SplineActor::BeginPlay()
|
||||
@@ -100,9 +113,7 @@ void APS_Editor_SplineActor::BeginPlay()
|
||||
|
||||
void APS_Editor_SplineActor::AddPoint(const FVector& WorldPosition)
|
||||
{
|
||||
// Convert world to local space
|
||||
const FVector LocalPos = GetActorTransform().InverseTransformPosition(WorldPosition);
|
||||
SplineComp->AddSplinePoint(LocalPos, ESplineCoordinateSpace::Local, true);
|
||||
SplineComp->AddSplinePoint(WorldPosition, ESplineCoordinateSpace::World, true);
|
||||
|
||||
// Create handle
|
||||
UStaticMeshComponent* Handle = CreatePointHandle(WorldPosition);
|
||||
@@ -115,8 +126,7 @@ void APS_Editor_SplineActor::MovePoint(int32 Index, const FVector& NewWorldPosit
|
||||
{
|
||||
if (Index < 0 || Index >= GetNumPoints()) return;
|
||||
|
||||
const FVector LocalPos = GetActorTransform().InverseTransformPosition(NewWorldPosition);
|
||||
SplineComp->SetLocationAtSplinePoint(Index, LocalPos, ESplineCoordinateSpace::Local, true);
|
||||
SplineComp->SetLocationAtSplinePoint(Index, NewWorldPosition, ESplineCoordinateSpace::World, true);
|
||||
|
||||
if (PointHandles.IsValidIndex(Index) && PointHandles[Index])
|
||||
{
|
||||
@@ -174,8 +184,7 @@ void APS_Editor_SplineActor::SetAllPointLocations(const TArray<FVector>& Points)
|
||||
SplineComp->ClearSplinePoints(false);
|
||||
for (const FVector& WorldPos : Points)
|
||||
{
|
||||
const FVector LocalPos = GetActorTransform().InverseTransformPosition(WorldPos);
|
||||
SplineComp->AddSplinePoint(LocalPos, ESplineCoordinateSpace::Local, false);
|
||||
SplineComp->AddSplinePoint(WorldPos, ESplineCoordinateSpace::World, false);
|
||||
}
|
||||
SplineComp->UpdateSpline();
|
||||
|
||||
@@ -246,7 +255,11 @@ void APS_Editor_SplineActor::UpdateVisualization()
|
||||
}
|
||||
|
||||
SplineMeshComp->ClearAllMeshSections();
|
||||
SplineMeshComp->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, TArray<FColor>(), TArray<FProcMeshTangent>(), false);
|
||||
SplineMeshComp->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, TArray<FColor>(), TArray<FProcMeshTangent>(), true);
|
||||
|
||||
// Force collision and bounds update immediately
|
||||
SplineMeshComp->UpdateBounds();
|
||||
SplineMeshComp->MarkRenderStateDirty();
|
||||
|
||||
// Apply the current material (normal or selected)
|
||||
// Check if handles are visible as a proxy for "selected" state
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
class USplineComponent;
|
||||
class UProceduralMeshComponent;
|
||||
class UPS_Editor_SpawnableComponent;
|
||||
class UPS_Editor_EditableComponent;
|
||||
|
||||
/**
|
||||
* Runtime spline actor for path/route editing in PS_Editor.
|
||||
@@ -157,6 +159,12 @@ private:
|
||||
UPROPERTY()
|
||||
TObjectPtr<UMaterialInstanceDynamic> CenterCubeMat;
|
||||
|
||||
UPROPERTY(VisibleAnywhere)
|
||||
TObjectPtr<UPS_Editor_SpawnableComponent> SpawnableComp;
|
||||
|
||||
UPROPERTY(VisibleAnywhere)
|
||||
TObjectPtr<UPS_Editor_EditableComponent> EditableComp;
|
||||
|
||||
/** Tube rendering settings. */
|
||||
float TubeRadius = 2.0f;
|
||||
int32 TubeSegments = 8;
|
||||
|
||||
@@ -144,7 +144,7 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
{
|
||||
if (APS_Editor_PlayerController* EditorPC = Cast<APS_Editor_PlayerController>(GetOwningPlayer()))
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
{
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
@@ -199,7 +199,7 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::RebuildWidget()
|
||||
{
|
||||
if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
EditorPC->FinishPointPlacement();
|
||||
else if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing)
|
||||
else if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement)
|
||||
EditorPC->FinishSplineEditing();
|
||||
}
|
||||
return FReply::Handled();
|
||||
@@ -321,31 +321,6 @@ TSharedRef<SWidget> UPS_Editor_MainWidget::BuildToolbar()
|
||||
})
|
||||
]
|
||||
]
|
||||
// New Spline button
|
||||
+ SHorizontalBox::Slot().AutoWidth().Padding(8.0f, 0.0f, 2.0f, 0.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
|
||||
{
|
||||
EditorPC->BeginPointPlacement();
|
||||
}
|
||||
}
|
||||
return FReply::Handled();
|
||||
})
|
||||
[
|
||||
SNew(STextBlock)
|
||||
.Text(FText::FromString(TEXT("New Spline (S)")))
|
||||
.Font(FCoreStyle::GetDefaultFontStyle("Regular", 10))
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -784,73 +759,6 @@ 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)
|
||||
@@ -914,18 +822,6 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
// 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()))
|
||||
{
|
||||
@@ -936,11 +832,11 @@ void UPS_Editor_MainWidget::RefreshPropertiesFromSelection()
|
||||
{
|
||||
if (bPointSelected)
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("EDIT SPLINE: Drag gizmo to move point | Del: delete point | Click line to insert | Escape: done")));
|
||||
HelpBarText->SetText(FText::FromString(TEXT("SPLINE: Drag gizmo to move | Del: delete point | Click line: insert | Click outside: add | Esc/Done: finish")));
|
||||
}
|
||||
else
|
||||
{
|
||||
HelpBarText->SetText(FText::FromString(TEXT("EDIT SPLINE: Click handle to select point | Click line to insert | End: snap to ground | Escape: done")));
|
||||
HelpBarText->SetText(FText::FromString(TEXT("SPLINE: Click to add points | Click handle to select | Click line to insert | End: snap | Esc/Done: finish")));
|
||||
}
|
||||
HelpBarText->SetColorAndOpacity(FLinearColor(1.0f, 0.6f, 0.0f));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user