Compare commits
2 Commits
9877c1b6de
...
93b9c7d927
| Author | SHA1 | Date | |
|---|---|---|---|
| 93b9c7d927 | |||
| 050ea4eb2f |
BIN
Unreal/Content/Cubeezrzea.uasset
Normal file
BIN
Unreal/Content/Cubeezrzea.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Unreal/Content/PS_MasterCatalog.uasset
Normal file
BIN
Unreal/Content/PS_MasterCatalog.uasset
Normal file
Binary file not shown.
BIN
Unreal/Content/PS_PropsCatalog.uasset
Normal file
BIN
Unreal/Content/PS_PropsCatalog.uasset
Normal file
Binary file not shown.
BIN
Unreal/Content/PS_SplineEnnemy.uasset
Normal file
BIN
Unreal/Content/PS_SplineEnnemy.uasset
Normal file
Binary file not shown.
BIN
Unreal/Content/PS_ToolsCatalog.uasset
Normal file
BIN
Unreal/Content/PS_ToolsCatalog.uasset
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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