Files
2025-05-18 13:04:45 +08:00

301 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "BaseBehaviors/BehaviorTargetInterfaces.h"
#include "InteractiveTool.h"
#include "InteractiveToolBuilder.h"
#include "InteractiveToolChange.h"
#include "ToolContextInterfaces.h" // FViewCameraState
#include "TransactionUtil.h"
#include "DrawSplineTool.generated.h"
class AActor;
class APreviewGeometryActor;
class UConstructionPlaneMechanic;
class USingleClickOrDragInputBehavior;
class USplineComponent;
class UWorld;
UENUM()
enum class EDrawSplineDrawMode : uint8
{
// Click to place a point and then drag to set its tangent. Clicking without
// dragging will create sharp corners.
TangentDrag,
// Click and drag new points, with the tangent set automatically
ClickAutoTangent,
// Drag to place multiple points, with spacing controlled by Min Point Spacing
FreeDraw,
};
UENUM()
enum class ESplineOffsetMethod : uint8
{
// Spline points will be offset along the normal direction of the clicked surface
HitNormal,
// Spline points will be offset along a manually-chosen direction
Custom
};
UENUM()
enum class EDrawSplineOutputMode : uint8
{
// Create a new empty actor with the spline inside it
EmptyActor,
// Attach the spline to an existing actor, or replace a spline inside that
// actor if Existing Spline Index To Replace is valid.
ExistingActor,
// Create the blueprint specified by Blueprint To Create, and either attach
// the spline to that, or replace an existing spline if Existing Spline Index
// To Replace is valid.
CreateBlueprint,
//~ The original implementation of this option was too unwieldy, but there is some way to
//~ implement it using FComponentReference to make a nice spline component picker...
//SelectedComponent,
};
UENUM()
enum class EDrawSplineUpVectorMode : uint8
{
// Pick the first up vector based on the hit normal, and then align subsequent
// up vectors with the previous ones.
AlignToPrevious,
// Base the up vector off the hit normal.
UseHitNormal,
};
UCLASS()
class MESHMODELINGTOOLSEDITORONLYEXP_API UDrawSplineToolProperties : public UInteractiveToolPropertySet
{
GENERATED_BODY()
public:
//~ This is TransientToolProperty because while it's useful to toggle after drawing a spline,
//~ it's confusing when you first start the tool and are drawing a spline.
/**
* Determines whether the created spline is a loop. This can be toggled using "Closed Loop" in
* the detail panel after spline creation.
*/
UPROPERTY(EditAnywhere, Category = Spline, meta = (TransientToolProperty))
bool bLoop = false;
/**
* Determines how the resulting spline is emitted on tool accept.
*/
UPROPERTY(EditAnywhere, Category = Spline)
EDrawSplineOutputMode OutputMode = EDrawSplineOutputMode::EmptyActor;
/**
* Actor to attach to when Output Mode is "Existing Actor"
*/
UPROPERTY(EditAnywhere, Category = Spline, meta = (TransientToolProperty,
EditCondition = "OutputMode == EDrawSplineOutputMode::ExistingActor", EditConditionHides))
TWeakObjectPtr<AActor> TargetActor = nullptr;
/**
* Blueprint to create when Output Mode is "Create Blueprint"
*/
UPROPERTY(EditAnywhere, Category = Spline, meta = (
EditCondition = "OutputMode == EDrawSplineOutputMode::CreateBlueprint", EditConditionHides))
TWeakObjectPtr<UBlueprint> BlueprintToCreate;
/**
* When attaching to an existing actor or creating a blueprint, controls whether the drawn
* spline replaces one of the target's existing components or gets added as a new one (if
* the index is out of bounds).
*/
UPROPERTY(EditAnywhere, Category = Spline, meta = (ClampMin = -1, UIMax = 10, EditConditionHides,
EditCondition = "OutputMode == EDrawSplineOutputMode::ExistingActor || OutputMode == EDrawSplineOutputMode::CreateBlueprint"))
int32 ExistingSplineIndexToReplace = 0;
/**
* How the spline is drawn in the tool.
*/
UPROPERTY(EditAnywhere, Category = Spline)
EDrawSplineDrawMode DrawMode = EDrawSplineDrawMode::TangentDrag;
/** Point spacing when Draw Mode is "Free Draw" */
UPROPERTY(EditAnywhere, Category = Spline, meta = (ClampMin = 0, UIMax = 1000,
EditCondition = "DrawMode == EDrawSplineDrawMode::FreeDraw", EditConditionHides))
double MinPointSpacing = 200;
/** How far to offset spline points from the clicked surface, along the surface normal */
UPROPERTY(EditAnywhere, Category = Spline, meta = (UIMin = 0, UIMax = 100))
double ClickOffset = 0;
/** How to choose the direction to offset points from the clicked surface */
UPROPERTY(EditAnywhere, Category = Spline, meta = (EditCondition = "ClickOffset > 0", EditConditionHides))
ESplineOffsetMethod OffsetMethod = ESplineOffsetMethod::HitNormal;
/** Manually-specified click offset direction. Note: Will be normalized. If it is a zero vector, a default Up vector will be used instead. */
UPROPERTY(EditAnywhere, Category = Spline, meta = (EditCondition = "ClickOffset > 0 && OffsetMethod == ESplineOffsetMethod::Custom", EditConditionHides))
FVector OffsetDirection = FVector::UpVector;
/**
* When nonzero, allows a visualization of the rotation of the spline. Can be controlled
* in the detail panel after creation via "Scale Visualization Width" property.
*/
UPROPERTY(EditAnywhere, Category = Spline, AdvancedDisplay, meta = (ClampMin = 0, UIMax = 100))
double FrameVisualizationWidth = 0;
/**
* How the spline rotation is set. It is suggested to use a nonzero FrameVisualizationWidth to
* see the effects.
*/
UPROPERTY(EditAnywhere, Category = Spline, AdvancedDisplay)
EDrawSplineUpVectorMode UpVectorMode = EDrawSplineUpVectorMode::UseHitNormal;
/**
* When modifying existing actors, whether the result should be previewed using a copy
* of that actor (rather than just drawing the spline by itself). Could be toggled off
* if something about duplicating the given actor is problematic.
*/
UPROPERTY(EditAnywhere, Category = Spline, AdvancedDisplay, meta=(
EditCondition = "OutputMode != EDrawSplineOutputMode::EmptyActor"))
bool bPreviewUsingActorCopy = true;
/** Whether to place spline points on the surface of objects in the world */
UPROPERTY(EditAnywhere, Category = RaycastTargets, meta = (DisplayName = "World Objects"))
bool bHitWorld = true;
/** Whether to place spline points on a custom, user-adjustable plane */
UPROPERTY(EditAnywhere, Category = RaycastTargets, meta = (DisplayName = "Custom Plane"))
bool bHitCustomPlane = false;
/** Whether to place spline points on a plane through the origin aligned with the Z axis in perspective views, or facing the camera in othographic views */
UPROPERTY(EditAnywhere, Category = RaycastTargets, meta = (DisplayName = "Ground Planes"))
bool bHitGroundPlanes = true;
/**
* If modifying a blueprint actor, whether to run the construction script while dragging
* or only at the end of a drag. Can be toggled off for expensive construction scripts.
*/
UPROPERTY(EditAnywhere, Category = Spline, AdvancedDisplay, meta = (
EditCondition = "OutputMode != EDrawSplineOutputMode::EmptyActor"))
bool bRerunConstructionScriptOnDrag = true;
};
UCLASS()
class MESHMODELINGTOOLSEDITORONLYEXP_API UDrawSplineTool : public UInteractiveTool
, public IClickBehaviorTarget
, public IClickDragBehaviorTarget
{
GENERATED_BODY()
public:
virtual void SetSelectedActor(AActor* Actor);
virtual void SetWorld(UWorld* World);
virtual UWorld* GetTargetWorld() { return TargetWorld.Get(); }
// UInteractiveTool
virtual void Setup() override;
virtual void Shutdown(EToolShutdownType ShutdownType) override;
virtual void OnTick(float DeltaTime) override;
virtual bool HasCancel() const override { return true; }
virtual bool HasAccept() const override { return true; }
virtual bool CanAccept() const override;
virtual void Render(IToolsContextRenderAPI* RenderAPI) override;
virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override;
// IClickBehaviorTarget
virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos);
virtual void OnClicked(const FInputDeviceRay& ClickPos);
// IClickDragBehaviorTarget
virtual FInputRayHit CanBeginClickDragSequence(const FInputDeviceRay& PressPos);
virtual void OnClickPress(const FInputDeviceRay& PressPos);
virtual void OnClickDrag(const FInputDeviceRay& DragPos);
virtual void OnClickRelease(const FInputDeviceRay& ReleasePos);
virtual void OnTerminateDragSequence();
private:
UPROPERTY()
TObjectPtr<UDrawSplineToolProperties> Settings = nullptr;
UPROPERTY()
TObjectPtr<USingleClickOrDragInputBehavior> ClickOrDragBehavior = nullptr;
UPROPERTY()
TObjectPtr<UConstructionPlaneMechanic> PlaneMechanic = nullptr;
TWeakObjectPtr<UWorld> TargetWorld = nullptr;
// This is only used to initialize TargetActor in the settings object
TWeakObjectPtr<AActor> StartupSelectedActor = nullptr;
// The preview actor is either a APreviewGeometryActor with a spline, or a duplicate of
// some target blueprint actor so that we can see the effects of the drawn spline immediately.
UPROPERTY()
TObjectPtr<AActor> PreviewActor = nullptr;
// Used for recapturing the spline when rerunning construction scripts
int32 SplineRecaptureIndex = 0;
// This is the spline we add points to. It points to a component nested somewhere under
// PreviewRootActor.
TWeakObjectPtr<USplineComponent> WorkingSpline = nullptr;
bool bDrawTangentForLastPoint = false;
bool bFreeDrawPlacedPreviewPoint = false;
int32 FreeDrawStrokeStartIndex = 0;
bool Raycast(const FRay& WorldRay, FVector3d& HitLocationOut, FVector3d& HitNormalOut, double& HitTOut);
void AddSplinePoint(const FVector3d& HitLocation, const FVector3d& UpVector);
FVector3d GetUpVectorToUse(const FVector3d& HitLocation, const FVector3d& HitNormal, int32 NumSplinePointsBeforehand);
void TransitionOutputMode();
void GenerateAsset();
// Used to restore visibility of previous actor when switching to a different one, and to avoid switching
// target actors if the new value is invalid (e.g., user clicked the preview actor with the actor picker)
UPROPERTY()
TObjectPtr<AActor> PreviousTargetActor = nullptr;
int32 TargetActorWatcherID = -1;
bool PreviousTargetActorVisibility = true;
// Used to restore visibility of previous spline when switching to a different one
TWeakObjectPtr<USplineComponent> HiddenSpline = nullptr;
bool bPreviousSplineVisibility = true;
bool bNeedToRerunConstructionScript = false;
FViewCameraState CameraState;
private:
UE::TransactionUtil::FLongTransactionTracker LongTransactions;
public:
// Helper class for making undo/redo transactions, to avoid friending all the variations.
class FSplineChange : public FToolCommandChange
{
public:
// These pass the working spline to the overloads below
virtual void Apply(UObject* Object) override;
virtual void Revert(UObject* Object) override;
protected:
virtual void Apply(USplineComponent& Spline) = 0;
virtual void Revert(USplineComponent& Spline) = 0;
};
};
UCLASS(Transient)
class MESHMODELINGTOOLSEDITORONLYEXP_API UDrawSplineToolBuilder : public UInteractiveToolBuilder
{
GENERATED_BODY()
public:
virtual bool CanBuildTool(const FToolBuilderState& SceneState) const override;
virtual UInteractiveTool* BuildTool(const FToolBuilderState& SceneState) const override;
};