From 861c15c1938575695592d53875de2e0f4e850bfe Mon Sep 17 00:00:00 2001 From: "j.foucher" Date: Sun, 12 Apr 2026 11:06:04 +0200 Subject: [PATCH] Unify spline creation/editing into single seamless mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- Unreal/Content/test.umap | Bin 54794 -> 54794 bytes .../Content/NewPS_Editor_SpawnCatalog.uasset | Bin 1998 -> 0 bytes .../Content/PS_Editor_PlayerController.uasset | Bin 20728 -> 21043 bytes .../PS_Editor/Content/PS_MasterCatalog.uasset | Bin 0 -> 1813 bytes .../PS_Editor/Content/PS_PropsCatalog.uasset | Bin 0 -> 1938 bytes .../PS_Editor/Content/PS_SplineEnnemy.uasset | Bin 0 -> 23407 bytes .../PS_Editor/Content/PS_ToolsCatalog.uasset | Bin 0 -> 1840 bytes .../PS_Editor/GameMode/PS_Editor_Pawn.cpp | 219 +++++++++++++----- .../GameMode/PS_Editor_PlayerController.cpp | 208 ++++++++--------- .../GameMode/PS_Editor_PlayerController.h | 16 +- .../Selection/PS_Editor_SelectionManager.cpp | 4 +- .../Selection/PS_Editor_SelectionTypes.h | 3 +- .../PS_Editor/Spawn/PS_Editor_SpawnCatalog.h | 16 ++ .../Spawn/PS_Editor_SpawnCatalogFactory.cpp | 19 ++ .../Spawn/PS_Editor_SpawnCatalogFactory.h | 13 ++ .../Spawn/PS_Editor_SpawnManager.cpp | 27 +-- .../PS_Editor/Spawn/PS_Editor_SpawnManager.h | 12 +- .../Spline/PS_Editor_SplineActor.cpp | 29 ++- .../PS_Editor/Spline/PS_Editor_SplineActor.h | 8 + .../UI/Widgets/PS_Editor_MainWidget.cpp | 112 +-------- 20 files changed, 358 insertions(+), 328 deletions(-) delete mode 100644 Unreal/Plugins/PS_Editor/Content/NewPS_Editor_SpawnCatalog.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/PS_MasterCatalog.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/PS_PropsCatalog.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/PS_SplineEnnemy.uasset create mode 100644 Unreal/Plugins/PS_Editor/Content/PS_ToolsCatalog.uasset diff --git a/Unreal/Content/test.umap b/Unreal/Content/test.umap index 17beaffac203855f461e26a9929f62fec7bc09d6..ef3cdbec48c1f74875fdd8394c62e0cde0a0f5b7 100644 GIT binary patch delta 137 zcmeBL!`!uodBXulfrB^C=d9V4;k=SH%en2sB-PCq7$sA=jC2jm^^7d_j4U>PPTM0T z(BLv@seN{Jz{jtBzsmRVlx^PM_?%lH>Ti4KR5in6d^-PmYG159J9&Sj=H~moS7ayO pt7e-VKbM`yA;E!HvOlQPI@O_Ua$~0On#*R#+88D$DFEio}JFgY$UFthV! zyc!TNY~_ba3NCW#hI2^8#l;V^zk%lq5FES1n5b^{QC#fCbx_I&T$8_nDYM^=)EybG zBXr7zs(QfdrMV_+DTzSW!7~%e`F}umlYwaylW(aV5HJ8h{r`V4z*qoa;(C-vz_Sdj F{wZP8M1=qV diff --git a/Unreal/Plugins/PS_Editor/Content/NewPS_Editor_SpawnCatalog.uasset b/Unreal/Plugins/PS_Editor/Content/NewPS_Editor_SpawnCatalog.uasset deleted file mode 100644 index f78e2568f3b40f0f8890bb5993aa8ffcaca50ef1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1998 zcmah~O>9h26h8Wc*1u{cqQpX~s#7DTI-yPG=T!uwI=W#|&-7w+I&bpkwKYwMXoO`O ziV77%Vj&_TEG$}ypTvULkXTsINV>2f^%E&|zW3gpz7Cz9nfva&-}$}gocrG6?zICy z$H&L_=Moj>5PjnrG=sA{O3mJymMdoJ*s!DVQgq7(Q2Nn8p6#=OVrTxs*vbR zZ<@miwKE=24K=16Vlkp(9!J<3+om=)zKZ8f?i%k##pm^GP^yE~!J44o7j%35O0~B> z=&SbzScIsLg`wohBjUfgFV45HQl2Y$)bi+W%~|zN^?&Fx2?!?N>z^ zI6qMEAm^SNCxIOVlFz?9>RHj6vzT`k6-|n#qEWozckCjAIR~(6xlnjTCLW`!R}8Cj2!AsIR48 zo6VpSH}t4TQvZ*aC;2E8(&BMTUn@A?qh@izr z>I^cPQF$0l(fv2QWkt*kYX^;82abq{kq&3y_B+1{z%mcWp*%eHylw#iLvMG;t*6Ul zs~Mg+a+W?z-0C~E1j2Tj0-7$GEXVY7TV)+rgRC4Gokx4&ey1?hAa^MZpSXW^83R){ z@rKLSnvg3y9hBU9rg-7RK5gmBx*K9CaCtE%--Y>w&uY<%AQUc>kbX(1A_#c_{pOZ} zOOT_ZC{u2X<&X!^|K!k0lBDi*a=1DmryN?`Lairi4T7xSTv;y)duhH*)C-|2Bv-vwG73^8+WWeby_*qJxy|3I{@!RN3CK$>t zUCt7+z0)1$O0OpO8eW&^7hf_d41asPB__;fXNczV2=Qg^5Y~+d@?IKt+#8q4Et8zI zqbXdL5o*$)N840AWxB$X7*=la$hoTrV`f`w2H^{-x|9H z=0^rvCk{Ny#f VEC=YIj`uaY^Ii;=ueUMC{{@dPl#c)a diff --git a/Unreal/Plugins/PS_Editor/Content/PS_Editor_PlayerController.uasset b/Unreal/Plugins/PS_Editor/Content/PS_Editor_PlayerController.uasset index 8459e9fa09cf93cb5f6f31174379755518b4935c..55c6b3c58723d0078e6e632381edf5da938ce1a8 100644 GIT binary patch delta 1674 zcmb7EZ%kWN6u-B0tgUVL$}108>$=IG!UQYOF)@@xl_x4ZBAr-K$tt>!> zk|c1fS1^l^b5KgFgQYq`^yuAhAfyoWBKnRZLXbpWMxDm*L;TKsHMLgfywfpv>xJ*U zbDu4o?)rUlHFd^67tg!;P2&ewDDqjI1kBJ-jjfoF^&q;NWF3XI^B7`zJ^u#8_V$uC z-Rx3lqMeOIRw|lL?lW6 zK2=@^KjkMbPHv|qxGb`z&Y@3(4KS(t5t8aofpy(Gd~)nqEIbnR#g50xReya3vg(h; zWLs!(d?b;y^~J^`;i!9vR4dP`GSiwjs5pOUFcOR8ra!Qpe+|sq8cN`x_8}anw0kRY z5>Z^TW+&!OMPfA%HOT5LbO6+PD~`2#tL|>(H{8r&Nb5{F z$OuC=vRC1X-m(#2#f+mYj+GiY3tZNza$VMDu5)JkIxKS!Diki579_CAV5bpy+Q2Ik46J}l2=v2E1j<`Qz+7Ob$01n2 zE54S1mqZ|oKy|yw_zQs(P|Nd**A9w6l(*BTU>1Q=KTcl(OT1kO%RSbjenz&d0wUjU zw9`0D8+pas0T>l_!ewI%JqBwCye~1T3(G2Q!>*JHKm4IZxhc_?_%g#E zo9OKN#~@tDD^?`Jg+e>+fG-eO42msig#;vohv6NeRym7L0nBR5a7Wma{~kI)M`m}? z?(*&I?YQx#2|P_+%&v78Z)1h~Alzbs>rGvBKbRa-)DLUEBDmyGQ!h+AczPIpGnkzm z^+AoZ3{1^NiKAgJl!`6kKM~n$bx$JgVZBf-bU9y?`l5PRb@sxdvjm=KxghH@>`@KP zu4m~!$hwR2+p!+8W2#wQ&rFp4(C=={G`7xA@O#4WXxq12xH~tvQi?dTBOvWyXsyh= z;{J$IG`&MG@8L38PYI=Mutz9@8G{-)Zy8v8NB&3E{>8{kSI=RD6Su@49oUnhRS3ExrS{`OWcXtH4)4{dwNqUH# U8?*%W;fbaK&9LI+;FEy;AMysSW&i*H delta 1595 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 a2Tvop&E&4tapSLx8%O>4(fP~)@c$qB1o)-^ literal 0 HcmV?d00001 diff --git a/Unreal/Plugins/PS_Editor/Content/PS_ToolsCatalog.uasset b/Unreal/Plugins/PS_Editor/Content/PS_ToolsCatalog.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9e56d44175aaf3790a4348669a25d1124fcdc27a GIT binary patch literal 1840 zcmah~O-NKx6h8iyX%d$^( zh??k=ThR!x8&?zNpfnatnT`BNNvYtpmpQH0@{;mmq1fiM*{n9FP+BQe7U2$E0PN_D z$ADSn@y7lc3Y2IP`5RobxLiY7wwX`qmHorbgMCA8>G6l&`S}L}HT^Sh&t^0YUL$|X zLkCH~zj)|9&=#A9yS%&xmo2Z3`IxA(e4E9l+W|gLRjgyjAf2*W3Z9dYkpQrSeJ~TY zaG83OOvRk1Tag1y(SlGt@=;1et;??|5>w&U@2SgpK&X}d3>M;ErexyAEqdF;R#qc> zMW1v@)FjzoE4DK=AbJ_3X~CX!a^fRx^aR8%KP3#p=yYEWoe+)YlQ%jb@yo;0)LxH# zPCMCrfqAv?na&)4^k)Z|G^N9&w!-bD)eVUNPu7GeT|uqtN#D7$5Bm)szR&iOKJvO8zNtyCj_F}hupTQI z>%u6Vs0^p=hLB?Lzla2y5)sXTQ?%a_`t34|YxzN!ZmNV0^b&=caA!hCuXK~ z;o6CNp4fzbi*C)~mT;kUO6CAz%dJT5q7u~0(tBU?Cee=d!2sK+V|1A$T;@SVAP#N1 zkqCx_;sT6RECIT{!xu6i+UP_?m&Ph;y@&sCk>y^{4yr+f8UK?!`+sn ZFJ4pdeu^+r=lil#@ss1ZRzpYr{{g^Ic6$H- literal 0 HcmV?d00001 diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp index 0cadbe7..b988290 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_Pawn.cpp @@ -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(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 CandidateSplines; if (UPS_Editor_SelectionManager* SM = EdPC->GetSelectionManager()) { for (const TWeakObjectPtr& Weak : SM->GetSelectedActors()) { - if (APS_Editor_SplineActor* Spline = Cast(Weak.Get())) + if (APS_Editor_SplineActor* S = Cast(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(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(GetController())) { - if (EdPC2->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing) + if (EdPC2->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement) { + // Collect candidate splines: selected + active placement actor + TArray TubeCandidates; if (UPS_Editor_SelectionManager* SM2 = EdPC2->GetSelectionManager()) + { + for (const TWeakObjectPtr& Weak : SM2->GetSelectedActors()) + { + if (APS_Editor_SplineActor* S = Cast(Weak.Get())) + TubeCandidates.AddUnique(S); + } + } + if (APS_Editor_SplineActor* ActiveSpline = Cast(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& Weak : SM2->GetSelectedActors()) + for (APS_Editor_SplineActor* Spline : TubeCandidates) { - APS_Editor_SplineActor* Spline = Cast(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(GetController())) + { + if (EdPC3->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement) + { + if (APS_Editor_SplineActor* Spline = Cast(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 OldPoints = Spline->GetAllPointLocations(); + Spline->AddPoint(ClickPos); + + if (UPS_Editor_UndoManager* UM = EdPC3->GetUndoManager()) + { + TSharedPtr Action = MakeShared(); + 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(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(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(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(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 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& Weak : SM->GetSelectedActors()) { - if (APS_Editor_SplineActor* Spline = Cast(Weak.Get())) + if (AActor* Actor = Weak.Get()) { - ActiveSplinePointIndex = -1; - DraggedSplineActor = nullptr; - EditorPC->BeginPointPlacementAppend(Spline); - return; + if (Actor->Implements()) + { + 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(); } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp index 51783e7..5b1b617 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.cpp @@ -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(this); - // Load spawn catalog and create spawn manager + // Create spawn manager and load catalogs from MasterCatalog SpawnManager = NewObject(this); - UPS_Editor_SpawnCatalog* LoadedCatalog = SpawnCatalogAsset.LoadSynchronous(); - SpawnManager->Initialize(this, LoadedCatalog); + SpawnManager->Initialize(this); + + if (UPS_Editor_MasterCatalog* Master = MasterCatalogAsset.LoadSynchronous()) + { + for (const TSoftObjectPtr& 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(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(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(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(ActivePlacementActor)) - { - Spline->SetHandlesVisible(false); - if (UndoManager) - { - TSharedPtr Action = MakeShared(); - 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(ActivePlacementActor)) - { - Spline->SetHandlesVisible(false); - } - - if (SpawnManager) - { - SpawnManager->TrackSpawnedActor(ActivePlacementActor); - } - - if (UndoManager) - { - TSharedPtr Action = MakeShared(); - 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(ActivePlacementActor); - if (Placeable) Placeable->OnPlacementCancelled(); - - if (bAppendingToActor) - { - if (APS_Editor_SplineActor* Spline = Cast(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(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(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 Action = MakeShared(); + 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(ActivePlacementActor)) + // Check if we clicked on the existing spline tube → insert instead of append + if (APS_Editor_SplineActor* Spline = Cast(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(ActivePlacementActor)) + { + TArray OldPoints = Spline->GetAllPointLocations(); + Spline->AddPoint(ClickWorldPos); + + if (UndoManager) + { + TSharedPtr Action = MakeShared(); + 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(ActivePlacementActor)) { Placeable->AddPlacementPoint(ClickWorldPos); - UE_LOG(LogTemp, Log, TEXT("PS_Editor: Point %d added at %s"), - Placeable->GetCurrentPointCount(), *ClickWorldPos.ToString()); } return; } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h index c3a3b5e..1d80762 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/GameMode/PS_Editor_PlayerController.h @@ -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 SpawnCatalogAsset; + TSoftObjectPtr 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; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp index 935269f..6e6da03 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionManager.cpp @@ -225,7 +225,7 @@ void UPS_Editor_SelectionManager::DeselectActor(AActor* Actor) { if (APS_Editor_PlayerController* EditorPC = Cast(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(PC)) { - if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing) + if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement) { EditorPC->FinishSplineEditing(); } diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionTypes.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionTypes.h index 9752d07..6fe7f7b 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionTypes.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Selection/PS_Editor_SelectionTypes.h @@ -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.) }; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h index c546003..b89a627 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalog.h @@ -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> Catalogs; +}; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp index 7e0759c..dd78432 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.cpp @@ -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(InParent, InClass, InName, Flags); +} + +FText UPS_Editor_MasterCatalogFactory::GetDisplayName() const +{ + return LOCTEXT("MasterCatalogDisplayName", "PS Editor Master Catalog"); +} + #undef LOCTEXT_NAMESPACE #endif diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h index 1074972..8f3f523 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnCatalogFactory.h @@ -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 diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp index 22fe3e4..12faf7e 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.cpp @@ -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) diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h index 9b29f18..15c929c 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spawn/PS_Editor_SpawnManager.h @@ -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 OwnerPC; - UPROPERTY() - TObjectPtr Catalog; - UPROPERTY() TArray ResolvedEntries; @@ -80,8 +80,6 @@ private: UPROPERTY() TArray> SpawnedActors; - /** Resolve all catalog entries (load classes, read SpawnableComponent metadata). */ - void ResolveCatalog(); /** Compute spawn location in front of the camera. */ FVector ComputeSpawnLocation() const; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.cpp index 85842e1..1d6d417 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.cpp @@ -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(TEXT("HandleRoot")); HandleRoot->SetupAttachment(Root); + + // Spawnable: makes this actor appear in the catalogue + SpawnableComp = CreateDefaultSubobject(TEXT("PS_Editor_Spawnable")); + SpawnableComp->DisplayName = TEXT("Spline"); + SpawnableComp->Category = TEXT("Tools"); + + // Editable: exposes SplineColor and SplineTag in the runtime property panel + EditableComp = CreateDefaultSubobject(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& 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(), TArray(), false); + SplineMeshComp->CreateMeshSection(0, Vertices, Triangles, Normals, UVs, TArray(), TArray(), 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 diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.h b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.h index 1c6aa77..3928c99 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.h +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/Spline/PS_Editor_SplineActor.h @@ -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 CenterCubeMat; + UPROPERTY(VisibleAnywhere) + TObjectPtr SpawnableComp; + + UPROPERTY(VisibleAnywhere) + TObjectPtr EditableComp; + /** Tube rendering settings. */ float TubeRadius = 2.0f; int32 TubeSegments = 8; diff --git a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp index 16bc331..c9301be 100644 --- a/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp +++ b/Unreal/Plugins/PS_Editor/Source/PS_Editor/UI/Widgets/PS_Editor_MainWidget.cpp @@ -144,7 +144,7 @@ TSharedRef UPS_Editor_MainWidget::RebuildWidget() { if (APS_Editor_PlayerController* EditorPC = Cast(GetOwningPlayer())) { - if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplineEditing) + if (EditorPC->GetEditorMode() == EPS_Editor_EditorMode::SplinePlacement) { EditorPC->FinishSplineEditing(); } @@ -199,7 +199,7 @@ TSharedRef 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 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(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(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& Weak : SelMgr->GetSelectedActors()) - { - if (APS_Editor_SplineActor* Spline = Cast(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(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)); }