Gizmo follow, cooker-friendly assets, and proper ground snapping
Gizmo usability: - Refresh the gizmo every tick while simulation is running so it glues to animated / AI-driven / timeline-driven actors instead of freezing at the pose they had when selected - Clear the pawn's active sub-element drag state on FinishPointPlacement / CancelPointPlacement — previously, after clicking Done on a spline, the gizmo jumped to the center cube but the internal ActiveSplinePointIndex stayed on the last clicked CP, so dragging the gizmo moved that CP instead of the whole actor - Expose UpdateGizmo() publicly on the SelectionManager so the PlayerController Tick can drive it Package cooking (fix gray gizmos / missing outline in packaged builds): - Convert every runtime LoadObject<> soft-string path into a UPROPERTY TObjectPtr<> filled via ConstructorHelpers::FObjectFinder at CDO construction time. Hard refs are picked up by the cooker so plugin Content assets (M_PS_Editor_Gizmo, M_PS_Editor_SelectionOutline, M_PS_Editor_SplineLine, and the engine basic shapes the spline actor uses) are reliably staged - Touches APS_Editor_Gizmo, APS_Editor_Pawn (outline post-process material), APS_Editor_SplineActor (gizmo material, spline line material, center cube mesh, handle sphere mesh) Ground placement (PS_Editor_GroundSnap): - New PS_Editor_GroundSnap utility centralising placement logic for catalogue spawns, End-key snap-to-ground, and spline CP placement - ProjectCameraRayToGround: aim down from a point in front of the camera when the camera ray misses (no more "looking at sky spawns object in mid-air"); classify the primary hit by normal to re-cast from below a ceiling / step back from a wall so the object lands on the floor underneath / at the foot of the surface rather than embedded in it - SnapActorToGround: start the downward ray from INSIDE the actor's volume (pivot + small buffer), ignoring the actor itself — avoids the "raised a bit high, pressed End, teleports to upper story" bug where starting above the actor's bbox could cross the current room's ceiling and find the upper floor as the first floor-like surface - Uses GetActorBounds(bOnlyCollidingComponents=true) with a fallback so characters snap from their capsule bounds rather than bbox including non-colliding meshes / attached weapons / child actors that used to lift pawns into the air - Multi-hit trace + normal.Z >= 0.3 filter skips walls, ceilings, and backfaces when finding the first true floor below Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
#include "Camera/CameraComponent.h"
|
#include "Camera/CameraComponent.h"
|
||||||
#include "GameFramework/SpringArmComponent.h"
|
#include "GameFramework/SpringArmComponent.h"
|
||||||
#include "Components/PostProcessComponent.h"
|
#include "Components/PostProcessComponent.h"
|
||||||
|
#include "Materials/MaterialInterface.h"
|
||||||
|
#include "UObject/ConstructorHelpers.h"
|
||||||
#include "EnhancedInputComponent.h"
|
#include "EnhancedInputComponent.h"
|
||||||
#include "EnhancedInputSubsystems.h"
|
#include "EnhancedInputSubsystems.h"
|
||||||
#include "InputAction.h"
|
#include "InputAction.h"
|
||||||
@@ -10,6 +12,7 @@
|
|||||||
#include "InputTriggers.h"
|
#include "InputTriggers.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
#include "PS_Editor_SelectionManager.h"
|
#include "PS_Editor_SelectionManager.h"
|
||||||
|
#include "PS_Editor_GroundSnap.h"
|
||||||
#include "PS_Editor_PlayerController.h"
|
#include "PS_Editor_PlayerController.h"
|
||||||
#include "PS_Editor_Gizmo.h"
|
#include "PS_Editor_Gizmo.h"
|
||||||
#include "PS_Editor_UndoManager.h"
|
#include "PS_Editor_UndoManager.h"
|
||||||
@@ -43,24 +46,27 @@ APS_Editor_Pawn::APS_Editor_Pawn()
|
|||||||
OutlinePostProcess = CreateDefaultSubobject<UPostProcessComponent>(TEXT("OutlinePostProcess"));
|
OutlinePostProcess = CreateDefaultSubobject<UPostProcessComponent>(TEXT("OutlinePostProcess"));
|
||||||
OutlinePostProcess->SetupAttachment(RootScene);
|
OutlinePostProcess->SetupAttachment(RootScene);
|
||||||
OutlinePostProcess->bUnbound = true; // Apply to entire scene
|
OutlinePostProcess->bUnbound = true; // Apply to entire scene
|
||||||
|
|
||||||
|
// Hard-ref the outline material at CDO-construction time so the cooker follows it into
|
||||||
|
// packaged builds. Replaces the old runtime LoadObject<> soft-string path.
|
||||||
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> OutlineMatFinder(
|
||||||
|
TEXT("/PS_Editor/M_PS_Editor_SelectionOutline.M_PS_Editor_SelectionOutline"));
|
||||||
|
if (OutlineMatFinder.Succeeded())
|
||||||
|
{
|
||||||
|
OutlineMaterial = OutlineMatFinder.Object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APS_Editor_Pawn::BeginPlay()
|
void APS_Editor_Pawn::BeginPlay()
|
||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
// Load outline post-process material
|
// OutlineMaterial is resolved in the ctor via ConstructorHelpers (hard cook ref).
|
||||||
UMaterialInterface* OutlineMat = LoadObject<UMaterialInterface>(
|
if (OutlineMaterial && OutlinePostProcess)
|
||||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_SelectionOutline.M_PS_Editor_SelectionOutline"));
|
|
||||||
if (OutlineMat && OutlinePostProcess)
|
|
||||||
{
|
{
|
||||||
OutlinePostProcess->Settings.WeightedBlendables.Array.Add(FWeightedBlendable(1.0f, OutlineMat));
|
OutlinePostProcess->Settings.WeightedBlendables.Array.Add(FWeightedBlendable(1.0f, OutlineMaterial));
|
||||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Selection outline material loaded"));
|
|
||||||
}
|
}
|
||||||
|
else if (!OutlineMaterial)
|
||||||
// TODO: Spline occluded PP material — disabled for now, depth comparison unreliable
|
|
||||||
// Consider translucent second-pass approach instead of post-process
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Outline material /PS_Editor/M_PS_Editor_SelectionOutline not found. "
|
UE_LOG(LogTemp, Warning, TEXT("PS_Editor: Outline material /PS_Editor/M_PS_Editor_SelectionOutline not found. "
|
||||||
"Create a Post Process material in Plugins/PS_Editor/Content/"));
|
"Create a Post Process material in Plugins/PS_Editor/Content/"));
|
||||||
@@ -769,22 +775,18 @@ void APS_Editor_Pawn::HandleLeftClickStarted(const FInputActionValue& Value)
|
|||||||
{
|
{
|
||||||
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(EdPC3->GetActivePlacementActor()))
|
if (IPS_Editor_SplineEditable* Spline = Cast<IPS_Editor_SplineEditable>(EdPC3->GetActivePlacementActor()))
|
||||||
{
|
{
|
||||||
// Raycast to get world click position
|
// Project the click ray onto ground. Uses the shared helper so that a click
|
||||||
FHitResult AddHit;
|
// pointing at the sky still lands the control point on the ground below the
|
||||||
FCollisionQueryParams AddParams;
|
// camera's forward vector (instead of floating 1000 units ahead).
|
||||||
AddParams.AddIgnoredActor(this);
|
TArray<AActor*> Ignored;
|
||||||
AddParams.AddIgnoredActor(Cast<AActor>(Spline));
|
Ignored.Add(this);
|
||||||
const FVector AddEnd = RayOrigin + RayDir * 100000.0f;
|
if (AActor* SplineActor = Cast<AActor>(Spline)) Ignored.Add(SplineActor);
|
||||||
|
|
||||||
FVector ClickPos;
|
FVector ClickPos = PS_Editor_GroundSnap::ProjectCameraRayToGround(
|
||||||
if (GetWorld()->LineTraceSingleByChannel(AddHit, RayOrigin, AddEnd, ECC_Camera, AddParams))
|
GetWorld(), RayOrigin, RayDir, Ignored);
|
||||||
{
|
// Lift the CP slightly above the surface so the tube mesh renders cleanly
|
||||||
ClickPos = AddHit.ImpactPoint + FVector(0.0f, 0.0f, 5.0f);
|
// (otherwise Z-fighting with the ground).
|
||||||
}
|
ClickPos.Z += 5.0f;
|
||||||
else
|
|
||||||
{
|
|
||||||
ClickPos = RayOrigin + RayDir * 1000.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
TArray<FVector> OldPoints = Spline->GetAllPointLocations();
|
||||||
Spline->AddPoint(ClickPos);
|
Spline->AddPoint(ClickPos);
|
||||||
@@ -1023,6 +1025,12 @@ void APS_Editor_Pawn::HandleScaleMode(const FInputActionValue& Value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APS_Editor_Pawn::ClearActiveSubElementDrag()
|
||||||
|
{
|
||||||
|
ActiveSplinePointIndex = -1;
|
||||||
|
DraggedSplineActor = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
void APS_Editor_Pawn::HandleDelete(const FInputActionValue& Value)
|
||||||
{
|
{
|
||||||
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
|
if (CameraMode != EPS_Editor_CameraMode::Idle) return;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ class USpringArmComponent;
|
|||||||
class UPostProcessComponent;
|
class UPostProcessComponent;
|
||||||
class UInputAction;
|
class UInputAction;
|
||||||
class UInputMappingContext;
|
class UInputMappingContext;
|
||||||
|
class UMaterialInterface;
|
||||||
|
|
||||||
DECLARE_DELEGATE_TwoParams(FPS_Editor_OnClick, FVector2D /*ScreenPos*/, bool /*bAdditive*/);
|
DECLARE_DELEGATE_TwoParams(FPS_Editor_OnClick, FVector2D /*ScreenPos*/, bool /*bAdditive*/);
|
||||||
|
|
||||||
@@ -68,6 +69,11 @@ public:
|
|||||||
/** Currently selected spline point index (-1 = none). Used by UI for Delete Point button. */
|
/** Currently selected spline point index (-1 = none). Used by UI for Delete Point button. */
|
||||||
int32 ActiveSplinePointIndex = -1;
|
int32 ActiveSplinePointIndex = -1;
|
||||||
|
|
||||||
|
/** Clear any active sub-element drag state (spline point / ChildMovable index + target actor).
|
||||||
|
* Call this when leaving placement / edit mode so a subsequent gizmo drag on the newly
|
||||||
|
* selected parent actor moves the whole actor instead of the last clicked point. */
|
||||||
|
void ClearActiveSubElementDrag();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void BeginPlay() override;
|
virtual void BeginPlay() override;
|
||||||
|
|
||||||
@@ -85,6 +91,12 @@ protected:
|
|||||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
|
||||||
TObjectPtr<UPostProcessComponent> OutlinePostProcess;
|
TObjectPtr<UPostProcessComponent> OutlinePostProcess;
|
||||||
|
|
||||||
|
/** Selection outline post-process material (from /PS_Editor/M_PS_Editor_SelectionOutline).
|
||||||
|
* Hard-ref so the cooker picks it up in packaged builds. Resolved via ConstructorHelpers
|
||||||
|
* in the ctor. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UMaterialInterface> OutlineMaterial;
|
||||||
|
|
||||||
// ---- Camera Settings ----
|
// ---- Camera Settings ----
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Fly")
|
||||||
|
|||||||
@@ -186,6 +186,17 @@ void APS_Editor_PlayerController::Tick(float DeltaTime)
|
|||||||
{
|
{
|
||||||
SpawnManager->TickEditorMode(DeltaTime);
|
SpawnManager->TickEditorMode(DeltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the gizmo glued to its selection every frame while simulating — otherwise
|
||||||
|
// animated actors (AI walking, timeline-driven properties, ragdoll) leave the gizmo
|
||||||
|
// behind at the pose it had when the actor was last selected / dragged. Outside of
|
||||||
|
// simulation the selection doesn't move on its own so the normal mutation-driven
|
||||||
|
// UpdateGizmo calls (click / drag / undo) are enough — and skipping this avoids any
|
||||||
|
// feedback with the pawn's in-progress gizmo drag.
|
||||||
|
if (bSimulating && SelectionManager && SelectionManager->IsAnythingSelected())
|
||||||
|
{
|
||||||
|
SelectionManager->UpdateGizmo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APS_Editor_PlayerController::OnPossess(APawn* InPawn)
|
void APS_Editor_PlayerController::OnPossess(APawn* InPawn)
|
||||||
@@ -554,6 +565,14 @@ void APS_Editor_PlayerController::FinishPointPlacement()
|
|||||||
IPS_Editor_ChildMovable::Execute_ClearChildHighlight(PlacementActor);
|
IPS_Editor_ChildMovable::Execute_ClearChildHighlight(PlacementActor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear the pawn's sub-element drag state (last clicked spline point / child index).
|
||||||
|
// Without this, grabbing the gizmo right after Done would move the last clicked point
|
||||||
|
// instead of the whole actor, because the drag logic keys on ActiveSplinePointIndex.
|
||||||
|
if (APS_Editor_Pawn* EditorPawn = Cast<APS_Editor_Pawn>(GetPawn()))
|
||||||
|
{
|
||||||
|
EditorPawn->ClearActiveSubElementDrag();
|
||||||
|
}
|
||||||
|
|
||||||
if (PlacementActor && SelectionManager)
|
if (PlacementActor && SelectionManager)
|
||||||
{
|
{
|
||||||
SelectionManager->ClearSelection();
|
SelectionManager->ClearSelection();
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ APS_Editor_Gizmo::APS_Editor_Gizmo()
|
|||||||
{
|
{
|
||||||
PrimaryActorTick.bCanEverTick = true;
|
PrimaryActorTick.bCanEverTick = true;
|
||||||
|
|
||||||
|
// Hard-ref the gizmo base material at CDO-construction time so the cooker follows it
|
||||||
|
// into packaged builds. Replaces the old runtime LoadObject<> soft-string path.
|
||||||
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> GizmoBaseMatFinder(
|
||||||
|
TEXT("/PS_Editor/M_PS_Editor_Gizmo.M_PS_Editor_Gizmo"));
|
||||||
|
if (GizmoBaseMatFinder.Succeeded())
|
||||||
|
{
|
||||||
|
GizmoBaseMaterial = GizmoBaseMatFinder.Object;
|
||||||
|
}
|
||||||
|
|
||||||
GizmoRoot = CreateDefaultSubobject<USceneComponent>(TEXT("GizmoRoot"));
|
GizmoRoot = CreateDefaultSubobject<USceneComponent>(TEXT("GizmoRoot"));
|
||||||
SetRootComponent(GizmoRoot);
|
SetRootComponent(GizmoRoot);
|
||||||
|
|
||||||
@@ -92,9 +101,8 @@ void APS_Editor_Gizmo::BeginPlay()
|
|||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
// Load gizmo material
|
// GizmoBaseMaterial is resolved in the ctor via ConstructorHelpers (hard cook ref).
|
||||||
UMaterialInterface* GizmoBaseMat = LoadObject<UMaterialInterface>(
|
UMaterialInterface* GizmoBaseMat = GizmoBaseMaterial.Get();
|
||||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_Gizmo.M_PS_Editor_Gizmo"));
|
|
||||||
|
|
||||||
auto MakeColorMat = [&](const FLinearColor& Color) -> UMaterialInstanceDynamic*
|
auto MakeColorMat = [&](const FLinearColor& Color) -> UMaterialInstanceDynamic*
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
class UStaticMeshComponent;
|
class UStaticMeshComponent;
|
||||||
class UProceduralMeshComponent;
|
class UProceduralMeshComponent;
|
||||||
class UMaterialInstanceDynamic;
|
class UMaterialInstanceDynamic;
|
||||||
|
class UMaterialInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom runtime transform gizmo for PS_Editor.
|
* Custom runtime transform gizmo for PS_Editor.
|
||||||
@@ -109,6 +110,12 @@ private:
|
|||||||
TObjectPtr<UStaticMeshComponent> ScaleCube_Center;
|
TObjectPtr<UStaticMeshComponent> ScaleCube_Center;
|
||||||
|
|
||||||
// ---- Materials ----
|
// ---- Materials ----
|
||||||
|
|
||||||
|
/** Base gizmo material (from /PS_Editor/M_PS_Editor_Gizmo). Hard-ref so the cooker
|
||||||
|
* picks it up in packaged builds. Resolved via ConstructorHelpers in the ctor. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UMaterialInterface> GizmoBaseMaterial;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UMaterialInstanceDynamic> Mat_X;
|
TObjectPtr<UMaterialInstanceDynamic> Mat_X;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
|
|||||||
@@ -0,0 +1,195 @@
|
|||||||
|
#include "PS_Editor_GroundSnap.h"
|
||||||
|
#include "PS_Editor_SpawnableComponent.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "CollisionQueryParams.h"
|
||||||
|
|
||||||
|
namespace PS_Editor_GroundSnap
|
||||||
|
{
|
||||||
|
// Normal.Z threshold that classifies a hit surface as "floor-like" (roughly walkable
|
||||||
|
// slope — cos(72°) ≈ 0.3). Below this, we treat the surface as a wall / ceiling /
|
||||||
|
// overhang and keep searching.
|
||||||
|
static constexpr float GFloorNormalThreshold = 0.3f;
|
||||||
|
|
||||||
|
/** Single downward trace that returns the first floor-like hit. Returns false if
|
||||||
|
* nothing below qualifies (all walls, ceiling-like geometry, etc.). */
|
||||||
|
static bool FindFloorBelow(UWorld* World, const FVector& Above, float Distance,
|
||||||
|
const FCollisionQueryParams& Params, FVector& OutImpact)
|
||||||
|
{
|
||||||
|
TArray<FHitResult> Hits;
|
||||||
|
if (!World->LineTraceMultiByChannel(Hits, Above, Above - FVector::UpVector * Distance,
|
||||||
|
ECC_Visibility, Params))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const FHitResult& H : Hits)
|
||||||
|
{
|
||||||
|
if (H.ImpactNormal.Z >= GFloorNormalThreshold)
|
||||||
|
{
|
||||||
|
OutImpact = H.ImpactPoint;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FVector ProjectCameraRayToGround(UWorld* World,
|
||||||
|
const FVector& Start,
|
||||||
|
const FVector& Direction,
|
||||||
|
const TArray<AActor*>& IgnoreActors,
|
||||||
|
float MaxCameraDistance,
|
||||||
|
float DefaultDistance,
|
||||||
|
float DownTraceDistance)
|
||||||
|
{
|
||||||
|
if (!World)
|
||||||
|
{
|
||||||
|
return Start + Direction * DefaultDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
FCollisionQueryParams Params(SCENE_QUERY_STAT(PS_Editor_GroundSnap), false);
|
||||||
|
for (AActor* A : IgnoreActors)
|
||||||
|
{
|
||||||
|
if (A) Params.AddIgnoredActor(A);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Primary camera-aligned trace.
|
||||||
|
FHitResult Hit;
|
||||||
|
if (World->LineTraceSingleByChannel(Hit, Start,
|
||||||
|
Start + Direction.GetSafeNormal() * MaxCameraDistance,
|
||||||
|
ECC_Visibility, Params))
|
||||||
|
{
|
||||||
|
const float NormalZ = Hit.ImpactNormal.Z;
|
||||||
|
|
||||||
|
// Ceiling / overhang: normal points DOWN. Re-cast downward from a point just
|
||||||
|
// BELOW the ceiling impact (not on the surface itself — avoids re-hitting the
|
||||||
|
// backface due to floating-point slop).
|
||||||
|
if (NormalZ <= -0.3f)
|
||||||
|
{
|
||||||
|
const FVector StartBelowCeiling = Hit.ImpactPoint - FVector::UpVector * 2.0f;
|
||||||
|
FVector FloorImpact;
|
||||||
|
if (FindFloorBelow(World, StartBelowCeiling, DownTraceDistance, Params, FloorImpact))
|
||||||
|
{
|
||||||
|
return FloorImpact;
|
||||||
|
}
|
||||||
|
return Hit.ImpactPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wall: roughly horizontal normal. Step BACK along the wall normal (away from
|
||||||
|
// the surface) by a small distance so the placed object doesn't end up with its
|
||||||
|
// bbox half-buried in the wall, then cast down to find the floor at the foot
|
||||||
|
// of the wall. Without this, clicking on a wall drops the object inside it.
|
||||||
|
if (NormalZ < 0.3f) // not floor-like either
|
||||||
|
{
|
||||||
|
constexpr float WallStandoff = 30.0f;
|
||||||
|
const FVector StartAwayFromWall = Hit.ImpactPoint + Hit.ImpactNormal * WallStandoff;
|
||||||
|
FVector FloorImpact;
|
||||||
|
if (FindFloorBelow(World, StartAwayFromWall, DownTraceDistance, Params, FloorImpact))
|
||||||
|
{
|
||||||
|
return FloorImpact;
|
||||||
|
}
|
||||||
|
// No floor below — at least return the stood-off point so we're not inside
|
||||||
|
// the wall.
|
||||||
|
return StartAwayFromWall;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Floor-like: use impact directly.
|
||||||
|
return Hit.ImpactPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Camera missed. Aim DOWN from a fallback point in front of the camera so the
|
||||||
|
// object still lands on ground instead of hovering in the sky.
|
||||||
|
const FVector FallbackTarget = Start + Direction.GetSafeNormal() * DefaultDistance;
|
||||||
|
FVector FloorImpact;
|
||||||
|
if (FindFloorBelow(World, FallbackTarget, DownTraceDistance, Params, FloorImpact))
|
||||||
|
{
|
||||||
|
return FloorImpact;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Nothing at all — give up and return the in-front point.
|
||||||
|
return FallbackTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SnapActorToGround(AActor* Actor,
|
||||||
|
FVector& OutLocation,
|
||||||
|
float OverheadRaise,
|
||||||
|
float MaxDistance,
|
||||||
|
const TArray<AActor*>& ExtraIgnored)
|
||||||
|
{
|
||||||
|
if (!Actor || !Actor->GetWorld())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Measure the bbox bottom relative to the pivot. Use COLLIDING components only so
|
||||||
|
// a Character's capsule drives the calculation — not its skeletal mesh (often with
|
||||||
|
// collision disabled) or attached weapons / child actors whose meshes may extend
|
||||||
|
// below the capsule and artificially lift the pawn by that overflow.
|
||||||
|
// Falls back to the full actor bounds if no colliding component is found (e.g. a
|
||||||
|
// pure-mesh prop without collision — rare but possible).
|
||||||
|
FVector BoundsOrigin, BoundsExtent;
|
||||||
|
Actor->GetActorBounds(/*bOnlyCollidingComponents*/ true, BoundsOrigin, BoundsExtent);
|
||||||
|
if (BoundsExtent.IsNearlyZero())
|
||||||
|
{
|
||||||
|
Actor->GetActorBounds(false, BoundsOrigin, BoundsExtent);
|
||||||
|
}
|
||||||
|
const float ActorZ = Actor->GetActorLocation().Z;
|
||||||
|
const float BBoxBottomZ = BoundsOrigin.Z - BoundsExtent.Z;
|
||||||
|
const float BBoxTopZ = BoundsOrigin.Z + BoundsExtent.Z;
|
||||||
|
const float PivotAboveBottom = ActorZ - BBoxBottomZ;
|
||||||
|
|
||||||
|
float ExtraOffset = 0.0f;
|
||||||
|
if (UPS_Editor_SpawnableComponent* SpawnComp = Actor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
||||||
|
{
|
||||||
|
ExtraOffset = SpawnComp->GroundOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start INSIDE the actor (at its pivot + a small buffer), not above its bbox top.
|
||||||
|
// Why: if we started above the bbox top and the actor was raised even slightly into
|
||||||
|
// a ceiling, the ray would cross the ceiling and the FIRST floor-like hit (filtered
|
||||||
|
// by normal.Z >= 0.3) would be the CEILING'S TOP FACE — i.e. the floor of the
|
||||||
|
// upper story. That made "End" teleport the actor up to the next story when the
|
||||||
|
// user only nudged it a bit too high.
|
||||||
|
//
|
||||||
|
// Starting at the pivot keeps the ray origin within the actor's own volume (pivot
|
||||||
|
// is always inside or at the edge of the bbox for normal meshes / characters). The
|
||||||
|
// actor is in the ignore list, so the trace passes straight through its own body
|
||||||
|
// and finds the first floor-like surface UNDERNEATH the actor — i.e. the floor of
|
||||||
|
// its current room.
|
||||||
|
//
|
||||||
|
// OverheadRaise is unused now (kept for API compat); the 2.0f buffer just avoids
|
||||||
|
// a degenerate "ray starts exactly on ground surface" edge case when the pivot
|
||||||
|
// coincides with the ground.
|
||||||
|
(void)BBoxTopZ; (void)OverheadRaise;
|
||||||
|
const FVector Start = Actor->GetActorLocation() + FVector(0.f, 0.f, 2.0f);
|
||||||
|
const FVector End = Start - FVector::UpVector * MaxDistance;
|
||||||
|
|
||||||
|
FCollisionQueryParams Params(SCENE_QUERY_STAT(PS_Editor_SnapActorToGround), false);
|
||||||
|
Params.AddIgnoredActor(Actor);
|
||||||
|
for (AActor* A : ExtraIgnored)
|
||||||
|
{
|
||||||
|
if (A) Params.AddIgnoredActor(A);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-hit trace + filter by normal so we only snap to floor-like surfaces.
|
||||||
|
// Accepts slopes up to ~72 degrees (normal.Z > 0.3). Rejects walls, ceilings,
|
||||||
|
// overhangs, and any backface we might cross on the way down.
|
||||||
|
TArray<FHitResult> Hits;
|
||||||
|
if (!Actor->GetWorld()->LineTraceMultiByChannel(Hits, Start, End, ECC_Visibility, Params))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr float FloorNormalThreshold = 0.3f; // cos(~72 deg)
|
||||||
|
for (const FHitResult& H : Hits)
|
||||||
|
{
|
||||||
|
if (H.ImpactNormal.Z >= FloorNormalThreshold)
|
||||||
|
{
|
||||||
|
OutLocation = Actor->GetActorLocation();
|
||||||
|
OutLocation.Z = H.ImpactPoint.Z + PivotAboveBottom + ExtraOffset;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Trace hit stuff but nothing floor-like — don't move the actor.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
|
||||||
|
class AActor;
|
||||||
|
class UWorld;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ground-placement helpers used across catalogue spawn, End-key snap, and spline point
|
||||||
|
* placement. The goal is that every object the user drops / snaps ends up on the ground
|
||||||
|
* by default, even when the camera is pointing at the sky.
|
||||||
|
*/
|
||||||
|
namespace PS_Editor_GroundSnap
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Project a camera-style ray onto ground.
|
||||||
|
*
|
||||||
|
* Sequence:
|
||||||
|
* 1. Line-trace from Start along Direction for up to MaxCameraDistance.
|
||||||
|
* - Hit a surface whose normal points UP / sideways -> return ImpactPoint.
|
||||||
|
* - Hit a surface whose normal points DOWN (ceiling, overhang) -> re-cast straight
|
||||||
|
* down from the impact point so the object lands on the floor underneath the
|
||||||
|
* overhead surface instead of glued to the ceiling.
|
||||||
|
* 2. Camera ray missed entirely (user aiming at the sky). Fall back to a point at
|
||||||
|
* DefaultDistance in front of the camera, then cast DOWN from there for
|
||||||
|
* DownTraceDistance. If THAT hits, return the downward impact.
|
||||||
|
* 3. Still nothing (infinite drop) -> return the in-front fallback unmodified so the
|
||||||
|
* caller at least gets a point in world space instead of a zero vector.
|
||||||
|
*
|
||||||
|
* @param IgnoreActors Actors the line trace must skip (typically the player pawn and,
|
||||||
|
* for re-snapping an existing actor, the actor itself — otherwise
|
||||||
|
* the trace hits its own mesh and nothing moves).
|
||||||
|
*/
|
||||||
|
PS_EDITOR_API FVector ProjectCameraRayToGround(UWorld* World,
|
||||||
|
const FVector& Start,
|
||||||
|
const FVector& Direction,
|
||||||
|
const TArray<AActor*>& IgnoreActors = {},
|
||||||
|
float MaxCameraDistance = 20000.0f,
|
||||||
|
float DefaultDistance = 1500.0f,
|
||||||
|
float DownTraceDistance = 50000.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a world position such that Actor's bounding-box bottom lands on the ground
|
||||||
|
* directly under its current XY position. Optionally adds a SpawnableComponent's
|
||||||
|
* GroundOffset for props that look better floating slightly (rugs, characters, etc.).
|
||||||
|
*
|
||||||
|
* Properly handles pivots not at bbox center (e.g. characters with pivot at feet) by
|
||||||
|
* measuring the pivot-to-bbox-bottom delta from GetActorBounds.
|
||||||
|
*
|
||||||
|
* The downward ray starts AT the actor's pivot (+ small buffer), not above its bbox.
|
||||||
|
* This keeps the trace origin within the current room (the pivot is always inside or
|
||||||
|
* on the edge of the actor's own volume) so the snap lands on the floor the actor is
|
||||||
|
* currently in, even when the actor has been nudged slightly into a ceiling. A multi-
|
||||||
|
* hit trace + a normal.Z >= 0.3 floor filter skips walls, ceilings, and backfaces
|
||||||
|
* encountered on the way down.
|
||||||
|
*
|
||||||
|
* OverheadRaise is kept for API compatibility but is no longer used — starting above
|
||||||
|
* the bbox was exactly what caused "End teleports to the upper story" bugs.
|
||||||
|
*
|
||||||
|
* @return true + OutLocation filled when ground was found; false when nothing is below.
|
||||||
|
*/
|
||||||
|
PS_EDITOR_API bool SnapActorToGround(AActor* Actor,
|
||||||
|
FVector& OutLocation,
|
||||||
|
float OverheadRaise = 50.0f,
|
||||||
|
float MaxDistance = 50000.0f,
|
||||||
|
const TArray<AActor*>& ExtraIgnored = {});
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "PS_Editor_UndoManager.h"
|
#include "PS_Editor_UndoManager.h"
|
||||||
#include "PS_Editor_PlayerController.h"
|
#include "PS_Editor_PlayerController.h"
|
||||||
#include "PS_Editor_SpawnManager.h"
|
#include "PS_Editor_SpawnManager.h"
|
||||||
|
#include "PS_Editor_GroundSnap.h"
|
||||||
#include "PS_Editor_EditableComponent.h"
|
#include "PS_Editor_EditableComponent.h"
|
||||||
#include "PS_Editor_SpawnableComponent.h"
|
#include "PS_Editor_SpawnableComponent.h"
|
||||||
#include "PS_Editor_SplineActor.h"
|
#include "PS_Editor_SplineActor.h"
|
||||||
@@ -448,6 +449,11 @@ void UPS_Editor_SelectionManager::SnapSelectionToGround()
|
|||||||
TSharedPtr<FPS_Editor_TransformAction> UndoAction = MakeShared<FPS_Editor_TransformAction>();
|
TSharedPtr<FPS_Editor_TransformAction> UndoAction = MakeShared<FPS_Editor_TransformAction>();
|
||||||
UndoAction->Description = FString::Printf(TEXT("Snap to ground %d actor(s)"), SelectedActors.Num());
|
UndoAction->Description = FString::Printf(TEXT("Snap to ground %d actor(s)"), SelectedActors.Num());
|
||||||
|
|
||||||
|
// Extra ignores shared across every actor in the batch.
|
||||||
|
TArray<AActor*> ExtraIgnored;
|
||||||
|
if (APawn* Pwn = PC->GetPawn()) ExtraIgnored.Add(Pwn);
|
||||||
|
if (Gizmo) ExtraIgnored.Add(Gizmo);
|
||||||
|
|
||||||
for (const TWeakObjectPtr<AActor>& Weak : SelectedActors)
|
for (const TWeakObjectPtr<AActor>& Weak : SelectedActors)
|
||||||
{
|
{
|
||||||
AActor* Actor = Weak.Get();
|
AActor* Actor = Weak.Get();
|
||||||
@@ -456,36 +462,11 @@ void UPS_Editor_SelectionManager::SnapSelectionToGround()
|
|||||||
UndoAction->Actors.Add(Actor);
|
UndoAction->Actors.Add(Actor);
|
||||||
UndoAction->OldTransforms.Add(Actor->GetActorTransform());
|
UndoAction->OldTransforms.Add(Actor->GetActorTransform());
|
||||||
|
|
||||||
FVector Origin, Extent;
|
FVector NewLocation;
|
||||||
Actor->GetActorBounds(false, Origin, Extent);
|
if (PS_Editor_GroundSnap::SnapActorToGround(Actor, NewLocation,
|
||||||
float BottomOffset = Extent.Z;
|
/*OverheadRaise*/ 50.0f, /*MaxDistance*/ 50000.0f, ExtraIgnored))
|
||||||
|
|
||||||
// Add custom ground offset from SpawnableComponent if available
|
|
||||||
if (UPS_Editor_SpawnableComponent* SpawnComp = Actor->FindComponentByClass<UPS_Editor_SpawnableComponent>())
|
|
||||||
{
|
{
|
||||||
BottomOffset += SpawnComp->GroundOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FVector Start = Actor->GetActorLocation();
|
|
||||||
const FVector End = Start - FVector::UpVector * 100000.0f;
|
|
||||||
|
|
||||||
FHitResult Hit;
|
|
||||||
FCollisionQueryParams Params;
|
|
||||||
Params.AddIgnoredActor(Actor);
|
|
||||||
Params.AddIgnoredActor(PC->GetPawn());
|
|
||||||
if (Gizmo) Params.AddIgnoredActor(Gizmo);
|
|
||||||
|
|
||||||
if (Actor->GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Camera, Params))
|
|
||||||
{
|
|
||||||
FVector NewLocation = Hit.ImpactPoint;
|
|
||||||
NewLocation.Z += BottomOffset;
|
|
||||||
Actor->SetActorLocation(NewLocation);
|
Actor->SetActorLocation(NewLocation);
|
||||||
|
|
||||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor: SnapToGround '%s' -> Hit '%s' (%s) at Z=%.1f, BottomOffset=%.1f, NewZ=%.1f"),
|
|
||||||
*Actor->GetName(),
|
|
||||||
*Hit.GetActor()->GetName(),
|
|
||||||
*Hit.GetComponent()->GetName(),
|
|
||||||
Hit.ImpactPoint.Z, BottomOffset, NewLocation.Z);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -106,6 +106,11 @@ public:
|
|||||||
|
|
||||||
FPS_Editor_OnSelectionChanged OnSelectionChanged;
|
FPS_Editor_OnSelectionChanged OnSelectionChanged;
|
||||||
|
|
||||||
|
/** Update gizmo visibility, position, orientation, and axis constraints from the
|
||||||
|
* current selection. Called on every selection mutation, and per-tick during
|
||||||
|
* simulation so the gizmo tracks animated pawns. */
|
||||||
|
void UpdateGizmo();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TWeakObjectPtr<APlayerController> OwnerPC;
|
TWeakObjectPtr<APlayerController> OwnerPC;
|
||||||
@@ -127,7 +132,4 @@ private:
|
|||||||
|
|
||||||
/** Clean up any stale weak pointers in the selection array. */
|
/** Clean up any stale weak pointers in the selection array. */
|
||||||
void CleanupStaleReferences();
|
void CleanupStaleReferences();
|
||||||
|
|
||||||
/** Update gizmo visibility and position based on current selection. */
|
|
||||||
void UpdateGizmo();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "PS_Editor_UndoManager.h"
|
#include "PS_Editor_UndoManager.h"
|
||||||
#include "PS_Editor_PlayerController.h"
|
#include "PS_Editor_PlayerController.h"
|
||||||
#include "PS_Editor_TimelineSubsystem.h"
|
#include "PS_Editor_TimelineSubsystem.h"
|
||||||
|
#include "PS_Editor_GroundSnap.h"
|
||||||
#include "AIController.h"
|
#include "AIController.h"
|
||||||
#include "BrainComponent.h"
|
#include "BrainComponent.h"
|
||||||
#include "GameFramework/PlayerController.h"
|
#include "GameFramework/PlayerController.h"
|
||||||
@@ -159,27 +160,19 @@ AActor* UPS_Editor_SpawnManager::SpawnFromCatalogAtLocation(int32 Index, FVector
|
|||||||
SpawnedActors.Add(SpawnedActor);
|
SpawnedActors.Add(SpawnedActor);
|
||||||
RegisterSpawnedActorId(PC->GetWorld(), SpawnedActor);
|
RegisterSpawnedActorId(PC->GetWorld(), SpawnedActor);
|
||||||
|
|
||||||
// Auto-snap to ground if configured
|
// Auto-snap to ground if configured — uses the shared helper so it handles pivot
|
||||||
|
// positions (characters with pivot at feet) + ignores the fresh actor itself.
|
||||||
UPS_Editor_SpawnableComponent* SpawnComp = SpawnedActor->FindComponentByClass<UPS_Editor_SpawnableComponent>();
|
UPS_Editor_SpawnableComponent* SpawnComp = SpawnedActor->FindComponentByClass<UPS_Editor_SpawnableComponent>();
|
||||||
if (SpawnComp && SpawnComp->bAutoSnapToGround && PC->GetWorld())
|
if (SpawnComp && SpawnComp->bAutoSnapToGround && PC->GetWorld())
|
||||||
{
|
{
|
||||||
FVector Origin, Extent;
|
TArray<AActor*> ExtraIgnored;
|
||||||
SpawnedActor->GetActorBounds(false, Origin, Extent);
|
if (APawn* Pwn = PC->GetPawn()) ExtraIgnored.Add(Pwn);
|
||||||
float BottomOffset = Extent.Z + SpawnComp->GroundOffset;
|
|
||||||
|
|
||||||
const FVector Start = SpawnedActor->GetActorLocation();
|
FVector SnappedLoc;
|
||||||
const FVector End = Start - FVector::UpVector * 100000.0f;
|
if (PS_Editor_GroundSnap::SnapActorToGround(SpawnedActor, SnappedLoc,
|
||||||
|
/*OverheadRaise*/ 50.0f, /*MaxDistance*/ 50000.0f, ExtraIgnored))
|
||||||
FHitResult Hit;
|
|
||||||
FCollisionQueryParams SnapParams;
|
|
||||||
SnapParams.AddIgnoredActor(SpawnedActor);
|
|
||||||
SnapParams.AddIgnoredActor(PC->GetPawn());
|
|
||||||
|
|
||||||
if (PC->GetWorld()->LineTraceSingleByChannel(Hit, Start, End, ECC_Camera, SnapParams))
|
|
||||||
{
|
{
|
||||||
FVector NewLoc = Hit.ImpactPoint;
|
SpawnedActor->SetActorLocation(SnappedLoc);
|
||||||
NewLoc.Z += BottomOffset;
|
|
||||||
SpawnedActor->SetActorLocation(NewLoc);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,23 +266,14 @@ FVector UPS_Editor_SpawnManager::ComputeSpawnLocation() const
|
|||||||
FRotator CameraRotation;
|
FRotator CameraRotation;
|
||||||
PC->GetPlayerViewPoint(CameraLocation, CameraRotation);
|
PC->GetPlayerViewPoint(CameraLocation, CameraRotation);
|
||||||
|
|
||||||
const FVector Direction = CameraRotation.Vector();
|
// Unified placement: camera ray first, and if the camera is pointing at the sky / empty
|
||||||
const float MaxDistance = 5000.0f;
|
// space, fall back to a downward trace from a point in front of the camera so the spawn
|
||||||
const float DefaultDistance = 500.0f;
|
// still lands on ground. Avoids the "aim up, object hovers in the sky" bug.
|
||||||
const float SurfaceOffset = 5.0f;
|
TArray<AActor*> Ignored;
|
||||||
|
if (APawn* Pwn = PC->GetPawn()) Ignored.Add(Pwn);
|
||||||
|
|
||||||
FHitResult Hit;
|
return PS_Editor_GroundSnap::ProjectCameraRayToGround(PC->GetWorld(), CameraLocation,
|
||||||
FCollisionQueryParams Params;
|
CameraRotation.Vector(), Ignored);
|
||||||
Params.AddIgnoredActor(PC->GetPawn());
|
|
||||||
|
|
||||||
if (PC->GetWorld()->LineTraceSingleByChannel(Hit, CameraLocation, CameraLocation + Direction * MaxDistance, ECC_Visibility, Params))
|
|
||||||
{
|
|
||||||
// Hit something — place at impact point, slightly offset along the surface normal
|
|
||||||
return Hit.ImpactPoint + Hit.ImpactNormal * SurfaceOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nothing hit — fallback to default distance
|
|
||||||
return CameraLocation + Direction * DefaultDistance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AActor* UPS_Editor_SpawnManager::SpawnActorDirect(UClass* ActorClass, FVector Location, FRotator Rotation, FVector Scale)
|
AActor* UPS_Editor_SpawnManager::SpawnActorDirect(UClass* ActorClass, FVector Location, FRotator Rotation, FVector Scale)
|
||||||
|
|||||||
@@ -5,7 +5,9 @@
|
|||||||
#include "ProceduralMeshComponent.h"
|
#include "ProceduralMeshComponent.h"
|
||||||
#include "Components/StaticMeshComponent.h"
|
#include "Components/StaticMeshComponent.h"
|
||||||
#include "Engine/StaticMesh.h"
|
#include "Engine/StaticMesh.h"
|
||||||
|
#include "Materials/MaterialInterface.h"
|
||||||
#include "Materials/MaterialInstanceDynamic.h"
|
#include "Materials/MaterialInstanceDynamic.h"
|
||||||
|
#include "UObject/ConstructorHelpers.h"
|
||||||
|
|
||||||
APS_Editor_SplineActor::APS_Editor_SplineActor()
|
APS_Editor_SplineActor::APS_Editor_SplineActor()
|
||||||
{
|
{
|
||||||
@@ -50,25 +52,46 @@ APS_Editor_SplineActor::APS_Editor_SplineActor()
|
|||||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineColor"))});
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineColor"))});
|
||||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineSelectedColor"))});
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineSelectedColor"))});
|
||||||
EditableComp->EditableProperties.Add({FName(TEXT("SplineTag"))});
|
EditableComp->EditableProperties.Add({FName(TEXT("SplineTag"))});
|
||||||
|
|
||||||
|
// Hard-ref plugin + engine assets at CDO-construction time so the cooker follows them
|
||||||
|
// into packaged builds. Replaces the old runtime LoadObject<> soft-string paths.
|
||||||
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> GizmoBaseMatFinder(
|
||||||
|
TEXT("/PS_Editor/M_PS_Editor_Gizmo.M_PS_Editor_Gizmo"));
|
||||||
|
if (GizmoBaseMatFinder.Succeeded())
|
||||||
|
{
|
||||||
|
GizmoBaseMaterial = GizmoBaseMatFinder.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConstructorHelpers::FObjectFinder<UMaterialInterface> SplineLineMatFinder(
|
||||||
|
TEXT("/PS_Editor/M_PS_Editor_SplineLine.M_PS_Editor_SplineLine"));
|
||||||
|
if (SplineLineMatFinder.Succeeded())
|
||||||
|
{
|
||||||
|
SplineLineMaterial = SplineLineMatFinder.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeFinder(
|
||||||
|
TEXT("/Engine/BasicShapes/Cube.Cube"));
|
||||||
|
if (CubeFinder.Succeeded())
|
||||||
|
{
|
||||||
|
CenterCubeMesh = CubeFinder.Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ConstructorHelpers::FObjectFinder<UStaticMesh> SphereFinder(
|
||||||
|
TEXT("/Engine/BasicShapes/Sphere.Sphere"));
|
||||||
|
if (SphereFinder.Succeeded())
|
||||||
|
{
|
||||||
|
HandleSphereMesh = SphereFinder.Object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APS_Editor_SplineActor::BeginPlay()
|
void APS_Editor_SplineActor::BeginPlay()
|
||||||
{
|
{
|
||||||
Super::BeginPlay();
|
Super::BeginPlay();
|
||||||
|
|
||||||
// Gizmo material (no depth test, renders on top) for handles and selected state
|
// GizmoBaseMaterial / SplineLineMaterial / meshes are resolved in the ctor via
|
||||||
UMaterialInterface* GizmoMat = LoadObject<UMaterialInterface>(
|
// ConstructorHelpers — hard cook references, available here.
|
||||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_Gizmo.M_PS_Editor_Gizmo"));
|
UMaterialInterface* GizmoMat = GizmoBaseMaterial.Get();
|
||||||
|
UMaterialInterface* SplineLineMat = SplineLineMaterial.Get();
|
||||||
// Auto-generated opaque unlit material (depth tested) for spline line
|
|
||||||
UMaterialInterface* SplineLineMat = LoadObject<UMaterialInterface>(
|
|
||||||
nullptr, TEXT("/PS_Editor/M_PS_Editor_SplineLine.M_PS_Editor_SplineLine"));
|
|
||||||
|
|
||||||
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)
|
// Spline line: normal depth-tested material (uses SplineColor property)
|
||||||
if (SplineLineMat)
|
if (SplineLineMat)
|
||||||
@@ -102,10 +125,9 @@ void APS_Editor_SplineActor::BeginPlay()
|
|||||||
CenterCube = NewObject<UStaticMeshComponent>(this);
|
CenterCube = NewObject<UStaticMeshComponent>(this);
|
||||||
CenterCube->SetupAttachment(GetRootComponent());
|
CenterCube->SetupAttachment(GetRootComponent());
|
||||||
CenterCube->RegisterComponent();
|
CenterCube->RegisterComponent();
|
||||||
UStaticMesh* CubeMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Cube.Cube"));
|
if (CenterCubeMesh)
|
||||||
if (CubeMesh)
|
|
||||||
{
|
{
|
||||||
CenterCube->SetStaticMesh(CubeMesh);
|
CenterCube->SetStaticMesh(CenterCubeMesh);
|
||||||
}
|
}
|
||||||
CenterCube->SetWorldScale3D(FVector(CenterCubeScale));
|
CenterCube->SetWorldScale3D(FVector(CenterCubeScale));
|
||||||
CenterCube->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
CenterCube->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
|
||||||
@@ -310,11 +332,10 @@ UStaticMeshComponent* APS_Editor_SplineActor::CreatePointHandle(const FVector& W
|
|||||||
Handle->SetupAttachment(HandleRoot);
|
Handle->SetupAttachment(HandleRoot);
|
||||||
Handle->RegisterComponent();
|
Handle->RegisterComponent();
|
||||||
|
|
||||||
// Load sphere mesh
|
// HandleSphereMesh resolved via ConstructorHelpers (hard cook ref).
|
||||||
UStaticMesh* SphereMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Sphere.Sphere"));
|
if (HandleSphereMesh)
|
||||||
if (SphereMesh)
|
|
||||||
{
|
{
|
||||||
Handle->SetStaticMesh(SphereMesh);
|
Handle->SetStaticMesh(HandleSphereMesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle->SetWorldLocation(WorldPosition);
|
Handle->SetWorldLocation(WorldPosition);
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ class USplineComponent;
|
|||||||
class UProceduralMeshComponent;
|
class UProceduralMeshComponent;
|
||||||
class UPS_Editor_SpawnableComponent;
|
class UPS_Editor_SpawnableComponent;
|
||||||
class UPS_Editor_EditableComponent;
|
class UPS_Editor_EditableComponent;
|
||||||
|
class UMaterialInterface;
|
||||||
|
class UStaticMesh;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Runtime spline actor for path/route editing in PS_Editor.
|
* Runtime spline actor for path/route editing in PS_Editor.
|
||||||
@@ -166,6 +168,23 @@ private:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
TObjectPtr<UMaterialInstanceDynamic> CenterCubeMat;
|
TObjectPtr<UMaterialInstanceDynamic> CenterCubeMat;
|
||||||
|
|
||||||
|
// ---- Hard-ref source assets (cooked into packaged builds via ConstructorHelpers) ----
|
||||||
|
|
||||||
|
/** Gizmo base material for spline handles / selected state (renders on top). */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UMaterialInterface> GizmoBaseMaterial;
|
||||||
|
|
||||||
|
/** Depth-tested material for the normal spline line. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UMaterialInterface> SplineLineMaterial;
|
||||||
|
|
||||||
|
/** Engine basic shapes used for the center cube and point handles. */
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UStaticMesh> CenterCubeMesh;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TObjectPtr<UStaticMesh> HandleSphereMesh;
|
||||||
|
|
||||||
UPROPERTY(VisibleAnywhere)
|
UPROPERTY(VisibleAnywhere)
|
||||||
TObjectPtr<UPS_Editor_SpawnableComponent> SpawnableComp;
|
TObjectPtr<UPS_Editor_SpawnableComponent> SpawnableComp;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user