You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1610 lines
57 KiB
C++
1610 lines
57 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ZoneShapeComponentVisualizer.h"
|
|
#include "CoreMinimal.h"
|
|
#include "Algo/AnyOf.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Commands/InputChord.h"
|
|
#include "Framework/Commands/Commands.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "SceneView.h"
|
|
#include "Settings/LevelEditorViewportSettings.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Editor.h"
|
|
#include "EditorViewportClient.h"
|
|
#include "EditorViewportCommands.h"
|
|
#include "LevelEditorActions.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "ActorEditorUtils.h"
|
|
#include "ZoneGraphSubsystem.h"
|
|
#include "ZoneGraphSettings.h"
|
|
#include "ZoneShapeComponent.h"
|
|
#include "ZoneShapeUtilities.h"
|
|
#include "ZoneGraphRenderingUtilities.h"
|
|
#include "BezierUtilities.h"
|
|
|
|
// Uncomment to draw additional rotation debug visualizations.
|
|
// #define ZONEGRAPH_DEBUG_ROTATIONS
|
|
|
|
IMPLEMENT_HIT_PROXY(HZoneShapeVisProxy, HComponentVisProxy);
|
|
IMPLEMENT_HIT_PROXY(HZoneShapePointProxy, HZoneShapeVisProxy);
|
|
IMPLEMENT_HIT_PROXY(HZoneShapeSegmentProxy, HZoneShapeVisProxy);
|
|
IMPLEMENT_HIT_PROXY(HZoneShapeControlPointProxy, HZoneShapeVisProxy);
|
|
|
|
#define LOCTEXT_NAMESPACE "ZoneShapeComponentVisualizer"
|
|
|
|
/** Define commands for the shape component visualizer */
|
|
class FZoneShapeComponentVisualizerCommands : public TCommands<FZoneShapeComponentVisualizerCommands>
|
|
{
|
|
public:
|
|
FZoneShapeComponentVisualizerCommands() : TCommands <FZoneShapeComponentVisualizerCommands>
|
|
(
|
|
"ZoneShapeComponentVisualizer", // Context name for fast lookup
|
|
LOCTEXT("ZoneShapeComponentVisualizer", "Zone Shape Component Visualizer"), // Localized context name for displaying
|
|
FName(), // Parent
|
|
FAppStyle::GetAppStyleSetName()
|
|
)
|
|
{
|
|
}
|
|
|
|
virtual void RegisterCommands() override
|
|
{
|
|
UI_COMMAND(DeletePoint, "Delete Point(s)", "Delete the currently selected shape points.", EUserInterfaceActionType::Button, FInputChord(EKeys::Delete));
|
|
UI_COMMAND(DuplicatePoint, "Duplicate Point(s)", "Duplicate the currently selected shape points.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(AddPoint, "Add Point Here", "Add a new shape point at the cursor location.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(SelectAll, "Select All Points", "Select all shape points.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(SetPointToSharp, "Sharp", "Set point to Sharp type", EUserInterfaceActionType::RadioButton, FInputChord());
|
|
UI_COMMAND(SetPointToBezier, "Bezier", "Set point to Bezier type", EUserInterfaceActionType::RadioButton, FInputChord());
|
|
UI_COMMAND(SetPointToAutoBezier, "Auto Bezier", "Set point to Auto Bezier type", EUserInterfaceActionType::RadioButton, FInputChord());
|
|
UI_COMMAND(SetPointToLaneSegment, "Lane Segment", "Set point to Lane Segment type", EUserInterfaceActionType::RadioButton, FInputChord());
|
|
UI_COMMAND(FocusViewportToSelection, "Focus Selected", "Moves the camera in front of the selection", EUserInterfaceActionType::Button, FInputChord(EKeys::F));
|
|
}
|
|
|
|
public:
|
|
TSharedPtr<FUICommandInfo> DeletePoint;
|
|
TSharedPtr<FUICommandInfo> DuplicatePoint;
|
|
TSharedPtr<FUICommandInfo> AddPoint;
|
|
TSharedPtr<FUICommandInfo> SelectAll;
|
|
TSharedPtr<FUICommandInfo> SetPointToSharp;
|
|
TSharedPtr<FUICommandInfo> SetPointToBezier;
|
|
TSharedPtr<FUICommandInfo> SetPointToAutoBezier;
|
|
TSharedPtr<FUICommandInfo> SetPointToLaneSegment;
|
|
TSharedPtr<FUICommandInfo> FocusViewportToSelection;
|
|
};
|
|
|
|
FZoneShapeComponentVisualizer::FZoneShapeComponentVisualizer()
|
|
: FComponentVisualizer()
|
|
, bAllowDuplication(true)
|
|
, DuplicateAccumulatedDrag(FVector::ZeroVector)
|
|
, bControlPointPositionCaptured(false)
|
|
, ControlPointPosition(FVector::ZeroVector)
|
|
{
|
|
FZoneShapeComponentVisualizerCommands::Register();
|
|
|
|
ShapeComponentVisualizerActions = MakeShareable(new FUICommandList);
|
|
|
|
ShapePointsProperty = FindFProperty<FProperty>(UZoneShapeComponent::StaticClass(), TEXT("Points")); //Can't use GET_MEMBER_NAME_CHECKED(UZoneShapeComponent, Points)) on private members :(
|
|
|
|
SelectionState = NewObject<UZoneShapeComponentVisualizerSelectionState>(GetTransientPackage(), TEXT("ZoneShapeSelectionState"), RF_Transactional);
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::OnRegister()
|
|
{
|
|
const auto& Commands = FZoneShapeComponentVisualizerCommands::Get();
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.DeletePoint,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnDeletePoint),
|
|
FCanExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::CanDeletePoint));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.DuplicatePoint,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnDuplicatePoint),
|
|
FCanExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::IsPointSelectionValid));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.AddPoint,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnAddPointToSegment),
|
|
FCanExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::CanAddPointToSegment));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.SelectAll,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnSelectAllPoints),
|
|
FCanExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::CanSelectAllPoints));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.SetPointToSharp,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnSetPointType, FZoneShapePointType::Sharp),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FZoneShapeComponentVisualizer::IsPointTypeSet, FZoneShapePointType::Sharp));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.SetPointToBezier,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnSetPointType, FZoneShapePointType::Bezier),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FZoneShapeComponentVisualizer::IsPointTypeSet, FZoneShapePointType::Bezier));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.SetPointToAutoBezier,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnSetPointType, FZoneShapePointType::AutoBezier),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FZoneShapeComponentVisualizer::IsPointTypeSet, FZoneShapePointType::AutoBezier));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.SetPointToLaneSegment,
|
|
FExecuteAction::CreateSP(this, &FZoneShapeComponentVisualizer::OnSetPointType, FZoneShapePointType::LaneProfile),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FZoneShapeComponentVisualizer::IsPointTypeSet, FZoneShapePointType::LaneProfile));
|
|
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
Commands.FocusViewportToSelection,
|
|
FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::ExecuteExecCommand, FString(TEXT("CAMERA ALIGN ACTIVEVIEWPORTONLY")))
|
|
);
|
|
|
|
bool bAlign = false;
|
|
bool bUseLineTrace = false;
|
|
bool bUseBounds = false;
|
|
bool bUsePivot = false;
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
FLevelEditorCommands::Get().SnapToFloor,
|
|
FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SnapToFloor_Clicked, bAlign, bUseLineTrace, bUseBounds, bUsePivot),
|
|
FCanExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::ActorSelected_CanExecute)
|
|
);
|
|
|
|
bAlign = true;
|
|
bUseLineTrace = false;
|
|
bUseBounds = false;
|
|
bUsePivot = false;
|
|
ShapeComponentVisualizerActions->MapAction(
|
|
FLevelEditorCommands::Get().AlignToFloor,
|
|
FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::SnapToFloor_Clicked, bAlign, bUseLineTrace, bUseBounds, bUsePivot),
|
|
FCanExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::ActorSelected_CanExecute)
|
|
);
|
|
}
|
|
|
|
FZoneShapeComponentVisualizer::~FZoneShapeComponentVisualizer()
|
|
{
|
|
FZoneShapeComponentVisualizerCommands::Unregister();
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::AddReferencedObjects(FReferenceCollector& Collector)
|
|
{
|
|
if (SelectionState)
|
|
{
|
|
Collector.AddReferencedObject(SelectionState);
|
|
}
|
|
}
|
|
void FZoneShapeComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
|
|
{
|
|
const UZoneShapeComponent* ShapeComp = Cast<const UZoneShapeComponent>(Component);
|
|
if (!ShapeComp)
|
|
{
|
|
return;
|
|
}
|
|
const FMatrix LocalToWorld = ShapeComp->GetComponentTransform().ToMatrixWithScale();
|
|
|
|
// Distance culling.
|
|
float ShapeMaxDrawDistance = MAX_flt;
|
|
if (const UZoneGraphSettings* ZoneGraphSettings = GetDefault<UZoneGraphSettings>())
|
|
{
|
|
ShapeMaxDrawDistance = ZoneGraphSettings->GetShapeMaxDrawDistance();
|
|
}
|
|
const float MaxDrawDistanceSqr = FMath::Square(ShapeMaxDrawDistance);
|
|
|
|
// Taking into account the min and maximum drawing distance
|
|
const FBoxSphereBounds ShapeBounds = ShapeComp->CalcBounds(ShapeComp->GetComponentTransform());
|
|
const float DistanceSqr = FVector::DistSquared(ShapeBounds.Origin, View->ViewMatrices.GetViewOrigin());
|
|
if (DistanceSqr > MaxDrawDistanceSqr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UZoneShapeComponent* EditedShapeComp = GetEditedShapeComponent();
|
|
const bool bIsActiveComponent = Component == EditedShapeComp;
|
|
|
|
constexpr FColor NormalColor = FColor(255, 255, 255, 255);
|
|
constexpr FColor SelectedColor = FColor(211, 93, 0, 255);
|
|
constexpr FColor TangentColor = SelectedColor;
|
|
|
|
const float GrabHandleSize = GetDefault<ULevelEditorViewportSettings>()->SelectedSplinePointSizeAdjustment + (bIsActiveComponent ? 10.0f : 0.0f);
|
|
|
|
static constexpr float DepthBias = 0.0001f; // Little bias helps to make the lines visible when directly on top of geometry.
|
|
static constexpr float HandlesDepthBias = 0.0002f; // A bit more than in the shape drawing, so that we get drawn on top
|
|
static constexpr float LaneLineThickness = 2.0f;
|
|
static constexpr float BoundaryLineThickness = 0.0f;
|
|
|
|
TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
check(SelectionState);
|
|
|
|
// Lanes
|
|
FZoneGraphStorage Zone;
|
|
if (UZoneGraphSubsystem* ZoneGraph = UWorld::GetSubsystem<UZoneGraphSubsystem>(ShapeComp->GetWorld()))
|
|
{
|
|
ZoneGraph->GetBuilder().BuildSingleShape(*ShapeComp, FMatrix::Identity, Zone);
|
|
Zone.DataHandle = FZoneGraphDataHandle(0xffff, 0xffff); // Give a valid handle so that the drawing happens correctly.
|
|
}
|
|
|
|
TConstArrayView<FZoneShapeConnector> Connectors = ShapeComp->GetShapeConnectors();
|
|
TConstArrayView<FZoneShapeConnection> Connections = ShapeComp->GetConnectedShapes();
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
constexpr int32 ZoneIndex = 0; // We have only one zone in the storage, created above.
|
|
constexpr bool bDrawDetails = true;
|
|
const float ShapeAlpha = bIsActiveComponent ? 1.0f : 0.5f;
|
|
UE::ZoneGraph::RenderingUtilities::FLaneHighlight LaneHighlight;
|
|
|
|
// Highlight lanes that emanate from the selected point.
|
|
if (bIsActiveComponent && ShapePoints.Num() > 0 && SelectionState->GetSelectedPoints().Num() > 0)
|
|
{
|
|
const int32 LastPointIndex = SelectionState->GetLastPointIndexSelected();
|
|
if (ShapePoints.IsValidIndex(LastPointIndex))
|
|
{
|
|
const FZoneShapePoint& Point = ShapePoints[LastPointIndex];
|
|
if (Point.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
LaneHighlight.Position = LocalToWorld.TransformPosition(Point.Position);
|
|
LaneHighlight.Rotation = LocalToWorld.ToQuat() * Point.Rotation.Quaternion();
|
|
LaneHighlight.Width = Point.TangentLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Draw boundary
|
|
UE::ZoneGraph::RenderingUtilities::DrawZoneBoundary(Zone, ZoneIndex, PDI, LocalToWorld, BoundaryLineThickness, DepthBias, ShapeAlpha);
|
|
|
|
// Draw Lanes
|
|
PDI->SetHitProxy(new HZoneShapeVisProxy(Component));
|
|
UE::ZoneGraph::RenderingUtilities::DrawZoneLanes(Zone, ZoneIndex, PDI, LocalToWorld, LaneLineThickness, DepthBias, ShapeAlpha, bDrawDetails, LaneHighlight);
|
|
|
|
// Draw connectors
|
|
for (int32 i = 0; i < Connectors.Num(); i++)
|
|
{
|
|
const FZoneShapeConnector& Connector = Connectors[i];
|
|
const FZoneShapeConnection* Connection = i < Connections.Num() ? &Connections[i] : nullptr;
|
|
PDI->SetHitProxy(new HZoneShapePointProxy(Component, Connector.PointIndex));
|
|
UE::ZoneGraph::RenderingUtilities::DrawZoneShapeConnector(Connector, Connection, PDI, LocalToWorld, DepthBias);
|
|
}
|
|
|
|
// Segments
|
|
if (ShapePoints.Num() > 1)
|
|
{
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
int StartIdx = ShapeComp->IsShapeClosed() ? (NumPoints - 1) : 0;
|
|
int Idx = ShapeComp->IsShapeClosed() ? 0 : 1;
|
|
|
|
TArray<FVector> CurvePoints;
|
|
|
|
while (Idx < NumPoints)
|
|
{
|
|
const FZoneShapePoint& StartPoint = ShapePoints[StartIdx];
|
|
const FZoneShapePoint& EndPoint = ShapePoints[Idx];
|
|
|
|
FVector StartPosition(ForceInitToZero), StartControlPoint(ForceInitToZero), EndControlPoint(ForceInitToZero), EndPosition(ForceInitToZero);
|
|
UE::ZoneShape::Utilities::GetCubicBezierPointsFromShapeSegment(StartPoint, EndPoint, LocalToWorld, StartPosition, StartControlPoint, EndControlPoint, EndPosition);
|
|
|
|
PDI->SetHitProxy(new HZoneShapeSegmentProxy(Component, StartIdx));
|
|
const FColor Color = (ShapeComp == EditedShapeComp && StartIdx == SelectionState->GetSelectedSegmentIndex()) ? SelectedColor : NormalColor;
|
|
|
|
// TODO: Make this a setting or property on shape
|
|
static constexpr float TessTolerance = 5.0f;
|
|
CurvePoints.Reset();
|
|
|
|
if (StartPoint.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
CurvePoints.Add(LocalToWorld.TransformPosition(StartPoint.Position));
|
|
}
|
|
|
|
CurvePoints.Add(StartPosition);
|
|
UE::CubicBezier::Tessellate(CurvePoints, StartPosition, StartControlPoint, EndControlPoint, EndPosition, TessTolerance);
|
|
|
|
if (EndPoint.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
CurvePoints.Add(LocalToWorld.TransformPosition(EndPoint.Position));
|
|
}
|
|
|
|
for (int32 i = 0; i < CurvePoints.Num() - 1; i++)
|
|
{
|
|
PDI->DrawLine(CurvePoints[i], CurvePoints[i + 1], Color, SDPG_Foreground, BoundaryLineThickness, HandlesDepthBias, true);
|
|
}
|
|
|
|
StartIdx = Idx;
|
|
Idx++;
|
|
}
|
|
}
|
|
|
|
// Draw handles on selected shapes
|
|
if (bIsActiveComponent)
|
|
{
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
|
|
if (NumPoints == 0 && SelectionState->GetSelectedPoints().Num() > 0)
|
|
{
|
|
ChangeSelectionState(INDEX_NONE, false);
|
|
}
|
|
else
|
|
{
|
|
const TSet<int32> SelectedPointsCopy = SelectionState->GetSelectedPoints();
|
|
for (int32 SelectedPoint : SelectedPointsCopy)
|
|
{
|
|
check(SelectedPoint >= 0);
|
|
if (SelectedPoint >= NumPoints)
|
|
{
|
|
// Catch any keys that might not exist anymore due to the underlying component changing.
|
|
ChangeSelectionState(SelectedPoint, true);
|
|
continue;
|
|
}
|
|
|
|
const FZoneShapePoint& Point = ShapePoints[SelectedPoint];
|
|
|
|
if (Point.Type == FZoneShapePointType::Bezier || Point.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
const float TangentHandleSize = 8.0f + GetDefault<ULevelEditorViewportSettings>()->SplineTangentHandleSizeAdjustment;
|
|
|
|
const FVector Position = LocalToWorld.TransformPosition(Point.Position);
|
|
const FVector InControlPoint = LocalToWorld.TransformPosition(Point.GetInControlPoint());
|
|
const FVector OutControlPoint = LocalToWorld.TransformPosition(Point.GetOutControlPoint());
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
|
|
PDI->DrawLine(Position, InControlPoint, TangentColor, SDPG_Foreground, 0.0, HandlesDepthBias);
|
|
PDI->DrawLine(Position, OutControlPoint, TangentColor, SDPG_Foreground, 0.0, HandlesDepthBias);
|
|
|
|
PDI->SetHitProxy(new HZoneShapeControlPointProxy(Component, SelectedPoint, true));
|
|
PDI->DrawPoint(InControlPoint, TangentColor, TangentHandleSize, SDPG_Foreground);
|
|
|
|
PDI->SetHitProxy(new HZoneShapeControlPointProxy(Component, SelectedPoint, false));
|
|
PDI->DrawPoint(OutControlPoint, TangentColor, TangentHandleSize, SDPG_Foreground);
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Points
|
|
for (int32 i = 0; i < ShapePoints.Num(); i++)
|
|
{
|
|
const FVector Point = LocalToWorld.TransformPosition(ShapePoints[i].Position);
|
|
const FColor Color = (ShapeComp == EditedShapeComp && SelectionState->GetSelectedPoints().Contains(i)) ? SelectedColor : NormalColor;
|
|
PDI->SetHitProxy(new HZoneShapePointProxy(Component, i));
|
|
PDI->DrawPoint(Point, Color, GrabHandleSize, SDPG_Foreground);
|
|
|
|
#ifdef ZONEGRAPH_DEBUG_ROTATIONS
|
|
const FRotator& Rot = ShapePoints[i].Rotation;
|
|
const FVector Forward = LocalToWorld.TransformVector(Rot.RotateVector(FVector::ForwardVector));
|
|
const FVector Side = LocalToWorld.TransformVector(Rot.RotateVector(FVector::RightVector));
|
|
const FVector Up = LocalToWorld.TransformVector(Rot.RotateVector(FVector::UpVector));
|
|
PDI->DrawLine(Point, Point + Forward * 40.0f, FColor::Red, SDPG_Foreground, 4.0f, HandlesDepthBias, true);
|
|
PDI->DrawLine(Point, Point + Side * 40.0f, FColor::Green, SDPG_Foreground, 4.0f, HandlesDepthBias, true);
|
|
PDI->DrawLine(Point, Point + Up * 40.0f, FColor::Blue, SDPG_Foreground, 4.0f, HandlesDepthBias, true);
|
|
#endif
|
|
}
|
|
|
|
PDI->SetHitProxy(nullptr);
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::ChangeSelectionState(int32 Index, bool bIsCtrlHeld) const
|
|
{
|
|
check(SelectionState);
|
|
SelectionState->Modify();
|
|
|
|
TSet<int32>& SelectedPoints = SelectionState->ModifySelectedPoints();
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
SelectedPoints.Empty();
|
|
SelectionState->SetLastPointIndexSelected(INDEX_NONE);
|
|
}
|
|
else if (!bIsCtrlHeld)
|
|
{
|
|
SelectedPoints.Empty();
|
|
SelectedPoints.Add(Index);
|
|
SelectionState->SetLastPointIndexSelected(Index);
|
|
}
|
|
else
|
|
{
|
|
// Add or remove from selection if Ctrl is held
|
|
if (SelectedPoints.Contains(Index))
|
|
{
|
|
// If already in selection, toggle it off
|
|
SelectedPoints.Remove(Index);
|
|
|
|
if (SelectionState->GetLastPointIndexSelected() == Index)
|
|
{
|
|
if (SelectedPoints.Num() == 0)
|
|
{
|
|
// Last key selected: clear last key index selected
|
|
SelectionState->SetLastPointIndexSelected(INDEX_NONE);
|
|
}
|
|
else
|
|
{
|
|
// Arbitrarily set last key index selected to first member of the set (so that it is valid)
|
|
SelectionState->SetLastPointIndexSelected(*SelectedPoints.CreateConstIterator());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Add to selection
|
|
SelectedPoints.Add(Index);
|
|
SelectionState->SetLastPointIndexSelected(Index);
|
|
}
|
|
}
|
|
}
|
|
|
|
const UZoneShapeComponent* FZoneShapeComponentVisualizer::UpdateSelectedShapeComponent(const HComponentVisProxy* VisProxy)
|
|
{
|
|
check(SelectionState);
|
|
const UZoneShapeComponent* NewShapeComp = CastChecked<const UZoneShapeComponent>(VisProxy->Component.Get());
|
|
check(NewShapeComp);
|
|
|
|
AActor* OldShapeOwningActor = SelectionState->GetShapePropertyPath().GetParentOwningActor();
|
|
UZoneShapeComponent* OldShapeComp = GetEditedShapeComponent();
|
|
|
|
const FComponentPropertyPath NewShapePropertyPath(NewShapeComp);
|
|
SelectionState->SetShapePropertyPath(NewShapePropertyPath);
|
|
|
|
AActor* NewShapeOwningActor = NewShapePropertyPath.GetParentOwningActor();
|
|
|
|
if (NewShapePropertyPath.IsValid())
|
|
{
|
|
if (OldShapeOwningActor != NewShapeOwningActor || OldShapeComp != NewShapeComp)
|
|
{
|
|
// Reset selection state if we are selecting a different actor to the one previously selected
|
|
ChangeSelectionState(INDEX_NONE, false);
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
}
|
|
|
|
if (OldShapeComp != NewShapeComp)
|
|
{
|
|
bIsSelectingComponent = true; // Prevent the selection from clearing our own selection state.
|
|
GEditor->SelectNone(/*bNoteSelectionChange*/true, /*bDeselectBSPSurfs*/true);
|
|
GEditor->SelectActor(NewShapeOwningActor, /*bInSelected*/false, /*bNotify*/true);
|
|
GEditor->SelectComponent(const_cast<UZoneShapeComponent*>(NewShapeComp), /*bInSelected*/true, /*bNotify*/true);
|
|
bIsSelectingComponent = false;
|
|
}
|
|
|
|
return NewShapeComp;
|
|
}
|
|
SelectionState->SetShapePropertyPath(FComponentPropertyPath());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
bool FZoneShapeComponentVisualizer::GetLastSelectedPointRotation(FQuat& OutRotation) const
|
|
{
|
|
bool bResult = false;
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
if (ShapePoints.IsValidIndex(LastPointIndexSelected))
|
|
{
|
|
check(SelectionState->GetSelectedPoints().Contains(LastPointIndexSelected));
|
|
OutRotation = ShapeComp->GetComponentTransform().GetRotation() * ShapePoints[LastPointIndexSelected].Rotation.Quaternion();
|
|
bResult = true;
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
|
|
{
|
|
if (VisProxy && VisProxy->Component.IsValid())
|
|
{
|
|
if (VisProxy->IsA(HZoneShapePointProxy::StaticGetType()))
|
|
{
|
|
// Control point clicked
|
|
const FScopedTransaction Transaction(LOCTEXT("SelectShapePoint", "Select Shape Point"));
|
|
|
|
SelectionState->Modify();
|
|
|
|
if (UpdateSelectedShapeComponent(VisProxy))
|
|
{
|
|
const HZoneShapePointProxy* PointProxy = static_cast<HZoneShapePointProxy*>(VisProxy);
|
|
// Modify the selection state, unless right-clicking on an already selected key
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
if (Click.GetKey() != EKeys::RightMouseButton || !SelectedPoints.Contains(PointProxy->PointIndex))
|
|
{
|
|
ChangeSelectionState(PointProxy->PointIndex, InViewportClient->IsCtrlPressed());
|
|
}
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
|
|
if (SelectionState->GetLastPointIndexSelected() == INDEX_NONE)
|
|
{
|
|
SelectionState->SetShapePropertyPath(FComponentPropertyPath());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else if (VisProxy->IsA(HZoneShapeSegmentProxy::StaticGetType()))
|
|
{
|
|
// Shape segment clicked
|
|
const FScopedTransaction Transaction(LOCTEXT("SelectShapeSegment", "Select Shape Segment"));
|
|
SelectionState->Modify();
|
|
|
|
if (const UZoneShapeComponent* ShapeComp = UpdateSelectedShapeComponent(VisProxy))
|
|
{
|
|
const FTransform& LocalToWorld = ShapeComp->GetComponentTransform();
|
|
const HZoneShapeSegmentProxy* SegmentProxy = static_cast<HZoneShapeSegmentProxy*>(VisProxy);
|
|
|
|
// Find nearest point on shape.
|
|
ChangeSelectionState(INDEX_NONE, false);
|
|
SelectionState->SetSelectedSegmentIndex(SegmentProxy->SegmentIndex);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
|
|
const int32 NumPoints = ShapeComp->GetNumPoints();
|
|
const int32 StartIndex = SegmentProxy->SegmentIndex;
|
|
const int32 EndIndex = (SegmentProxy->SegmentIndex + 1) % NumPoints;
|
|
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
|
|
FVector StartPosition(0), StartControlPoint(0), EndControlPoint(0), EndPosition(0);
|
|
UE::ZoneShape::Utilities::GetCubicBezierPointsFromShapeSegment(ShapePoints[StartIndex], ShapePoints[EndIndex], LocalToWorld.ToMatrixWithScale(), StartPosition, StartControlPoint, EndControlPoint, EndPosition);
|
|
|
|
const FVector RaySegStart = Click.GetOrigin();
|
|
const FVector RaySegEnd = Click.GetOrigin() + Click.GetDirection() * 50000.0f;
|
|
|
|
FVector ClosestPoint;
|
|
float ClosestT = 0.0f;
|
|
|
|
UE::CubicBezier::SegmentClosestPointApproximate(RaySegStart, RaySegEnd, StartPosition, StartControlPoint, EndControlPoint, EndPosition, ClosestPoint, ClosestT);
|
|
|
|
SelectionState->SetSelectedSegmentPoint(ClosestPoint);
|
|
SelectionState->SetSelectedSegmentT(ClosestT);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else if (VisProxy->IsA(HZoneShapeControlPointProxy::StaticGetType()))
|
|
{
|
|
// Shape segment clicked
|
|
const FScopedTransaction Transaction(LOCTEXT("SelectShapeSegment", "Select Shape Segment"));
|
|
SelectionState->Modify();
|
|
|
|
if (UpdateSelectedShapeComponent(VisProxy))
|
|
{
|
|
// Tangent handle clicked
|
|
const HZoneShapeControlPointProxy* ControlPointProxy = static_cast<HZoneShapeControlPointProxy*>(VisProxy);
|
|
|
|
// Note: don't change key selection when a tangent handle is clicked
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(ControlPointProxy->PointIndex);
|
|
SelectionState->SetSelectedControlPointType(ControlPointProxy->bInControlPoint ? FZoneShapeControlPointType::In : FZoneShapeControlPointType::Out);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else if (VisProxy->IsA(HZoneShapeVisProxy::StaticGetType()))
|
|
{
|
|
// Control point clicked
|
|
const FScopedTransaction Transaction(LOCTEXT("SelectShape", "Select Shape"));
|
|
|
|
SelectionState->Modify();
|
|
|
|
if (UpdateSelectedShapeComponent(VisProxy))
|
|
{
|
|
ChangeSelectionState(INDEX_NONE, false);
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
UZoneShapeComponent* FZoneShapeComponentVisualizer::GetEditedShapeComponent() const
|
|
{
|
|
check(SelectionState);
|
|
return Cast<UZoneShapeComponent>(SelectionState->GetShapePropertyPath().GetComponent());
|
|
}
|
|
|
|
UActorComponent* FZoneShapeComponentVisualizer::GetEditedComponent() const
|
|
{
|
|
return Cast<UActorComponent>(GetEditedShapeComponent());
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
|
|
{
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
|
|
if (SelectionState->GetSelectedControlPoint() != INDEX_NONE)
|
|
{
|
|
// If control point index is set, use that
|
|
if (bControlPointPositionCaptured)
|
|
{
|
|
OutLocation = ShapeComp->GetComponentTransform().TransformPosition(ControlPointPosition);
|
|
}
|
|
else
|
|
{
|
|
check(SelectionState->GetSelectedControlPoint() < ShapePoints.Num());
|
|
const FZoneShapePoint& Point = ShapePoints[SelectionState->GetSelectedControlPoint()];
|
|
if (SelectionState->GetSelectedControlPointType() == FZoneShapeControlPointType::Out)
|
|
{
|
|
OutLocation = ShapeComp->GetComponentTransform().TransformPosition(Point.GetOutControlPoint());
|
|
}
|
|
else
|
|
{
|
|
OutLocation = ShapeComp->GetComponentTransform().TransformPosition(Point.GetInControlPoint());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if (SelectionState->GetSelectedSegmentIndex() != INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
else if (SelectionState->GetLastPointIndexSelected() != INDEX_NONE)
|
|
{
|
|
// Otherwise use the last key index set
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
check(LastPointIndexSelected >= 0);
|
|
if (LastPointIndexSelected < ShapePoints.Num())
|
|
{
|
|
check(SelectionState->GetSelectedPoints().Contains(LastPointIndexSelected));
|
|
const FZoneShapePoint& Point = ShapePoints[LastPointIndexSelected];
|
|
OutLocation = ShapeComp->GetComponentTransform().TransformPosition(Point.Position);
|
|
OutLocation += DuplicateAccumulatedDrag;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::GetCustomInputCoordinateSystem(const FEditorViewportClient* ViewportClient, FMatrix& OutMatrix) const
|
|
{
|
|
bool bResult = false;
|
|
if (bHasCachedRotation)
|
|
{
|
|
OutMatrix = FRotationMatrix::Make(CachedRotation);
|
|
bResult = true;
|
|
}
|
|
else
|
|
{
|
|
if (ViewportClient->GetWidgetCoordSystemSpace() == COORD_Local || ViewportClient->GetWidgetMode() == UE::Widget::WM_Rotate)
|
|
{
|
|
FQuat Rotation = FQuat::Identity;
|
|
if (GetLastSelectedPointRotation(Rotation))
|
|
{
|
|
OutMatrix = FRotationMatrix::Make(Rotation);
|
|
bResult = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::IsVisualizingArchetype() const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
return (ShapeComp && ShapeComp->GetOwner() && FActorEditorUtils::IsAPreviewOrInactiveActor(ShapeComp->GetOwner()));
|
|
}
|
|
|
|
|
|
bool FZoneShapeComponentVisualizer::IsAnySelectedPointIndexOutOfRange(const UZoneShapeComponent& Comp) const
|
|
{
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
const int32 NumPoints = Comp.GetNumPoints();
|
|
return Algo::AnyOf(SelectedPoints, [NumPoints](int32 Index) { return Index >= NumPoints; });
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::IsSinglePointSelected() const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
return (ShapeComp != nullptr &&
|
|
SelectedPoints.Num() == 1 &&
|
|
SelectionState->GetLastPointIndexSelected() != INDEX_NONE);
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale)
|
|
{
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
|
|
if (IsAnySelectedPointIndexOutOfRange(*ShapeComp))
|
|
{
|
|
// Something external has changed the number of shape points, meaning that the cached selected keys are no longer valid
|
|
EndEditing();
|
|
return false;
|
|
}
|
|
|
|
if (SelectionState->GetSelectedControlPoint() != INDEX_NONE)
|
|
{
|
|
return TransformSelectedControlPoint(DeltaTranslate);
|
|
}
|
|
else if (SelectionState->GetSelectedPoints().Num() > 0)
|
|
{
|
|
if (ViewportClient->IsAltPressed())
|
|
{
|
|
if (ViewportClient->GetWidgetMode() == UE::Widget::WM_Translate && ViewportClient->GetCurrentWidgetAxis() != EAxisList::None)
|
|
{
|
|
if (bAllowDuplication)
|
|
{
|
|
static const float DuplicationDeadZoneSqr = FMath::Square(10.0f);
|
|
|
|
DuplicateAccumulatedDrag += DeltaTranslate;
|
|
if (DuplicateAccumulatedDrag.SizeSquared() >= DuplicationDeadZoneSqr)
|
|
{
|
|
DuplicatePointForAltDrag(DuplicateAccumulatedDrag);
|
|
DuplicateAccumulatedDrag = FVector::ZeroVector;
|
|
bAllowDuplication = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return TransformSelectedPoints(ViewportClient, DeltaTranslate, DeltaRotate, DeltaScale);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return TransformSelectedPoints(ViewportClient, DeltaTranslate, DeltaRotate, DeltaScale);
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::TransformSelectedControlPoint(const FVector& DeltaTranslate)
|
|
{
|
|
if (UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
check(SelectionState->GetSelectedControlPoint() != INDEX_NONE);
|
|
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
check(SelectionState->GetSelectedControlPoint() < NumPoints);
|
|
|
|
if (!DeltaTranslate.IsZero())
|
|
{
|
|
ShapeComp->Modify();
|
|
|
|
if (!bControlPointPositionCaptured)
|
|
{
|
|
// We capture the control point position on first update and use that as the gizmo position.
|
|
// That allows us to constrain the handle locations as needed, and have the gizmo follow the user input.
|
|
bControlPointPositionCaptured = true;
|
|
|
|
const FZoneShapePoint& EditedPoint = ShapePoints[SelectionState->GetSelectedControlPoint()];
|
|
if (EditedPoint.Type == FZoneShapePointType::Bezier || EditedPoint.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
if (SelectionState->GetSelectedControlPointType() == FZoneShapeControlPointType::Out)
|
|
{
|
|
ControlPointPosition = EditedPoint.GetOutControlPoint();
|
|
}
|
|
else
|
|
{
|
|
ControlPointPosition = EditedPoint.GetInControlPoint();
|
|
}
|
|
}
|
|
}
|
|
|
|
ControlPointPosition += ShapeComp->GetComponentTransform().InverseTransformVector(DeltaTranslate);
|
|
|
|
FZoneShapePoint& EditedPoint = ShapePoints[SelectionState->GetSelectedControlPoint()];
|
|
|
|
if (EditedPoint.Type == FZoneShapePointType::Bezier || EditedPoint.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
// Note: Lane control points will get adjusted to fit the lane profile in UpdateShape() below.
|
|
if (SelectionState->GetSelectedControlPointType() == FZoneShapeControlPointType::Out)
|
|
{
|
|
EditedPoint.SetOutControlPoint(ControlPointPosition);
|
|
}
|
|
else
|
|
{
|
|
EditedPoint.SetInControlPoint(ControlPointPosition);
|
|
}
|
|
}
|
|
}
|
|
|
|
ShapeComp->UpdateShape();
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::TransformSelectedPoints(const FEditorViewportClient* ViewportClient, const FVector& DeltaTranslate, const FRotator& DeltaRotate, const FVector& DeltaScale) const
|
|
{
|
|
if (UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
check(SelectionState->GetLastPointIndexSelected() != INDEX_NONE);
|
|
check(SelectionState->GetLastPointIndexSelected() >= 0);
|
|
check(SelectionState->GetLastPointIndexSelected() < NumPoints);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
check(SelectedPoints.Num() > 0);
|
|
check(SelectedPoints.Contains(LastPointIndexSelected));
|
|
|
|
ShapeComp->Modify();
|
|
|
|
for (const int32 SelectedIndex : SelectedPoints)
|
|
{
|
|
check(SelectedIndex >= 0);
|
|
check(SelectedIndex < NumPoints);
|
|
|
|
FZoneShapePoint& EditedPoint = ShapePoints[SelectedIndex];
|
|
|
|
if (!DeltaTranslate.IsZero())
|
|
{
|
|
const FVector LocalDelta = ShapeComp->GetComponentTransform().InverseTransformVector(DeltaTranslate);
|
|
EditedPoint.Position += LocalDelta;
|
|
}
|
|
|
|
if (!DeltaRotate.IsZero())
|
|
{
|
|
FQuat NewRot = ShapeComp->GetComponentTransform().GetRotation() * EditedPoint.Rotation.Quaternion(); // convert local-space rotation to world-space
|
|
NewRot = DeltaRotate.Quaternion() * NewRot; // apply world-space rotation
|
|
NewRot = ShapeComp->GetComponentTransform().GetRotation().Inverse() * NewRot; // convert world-space rotation to local-space
|
|
EditedPoint.Rotation = NewRot.Rotator();
|
|
}
|
|
|
|
if (DeltaScale.X != 0.0f)
|
|
{
|
|
if (EditedPoint.Type == FZoneShapePointType::Bezier)
|
|
{
|
|
EditedPoint.TangentLength *= (1.0f + DeltaScale.X);
|
|
}
|
|
}
|
|
}
|
|
|
|
ShapeComp->UpdateShape();
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
|
|
{
|
|
bool bHandled = false;
|
|
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
if (ShapeComp != nullptr && IsAnySelectedPointIndexOutOfRange(*ShapeComp))
|
|
{
|
|
// Something external has changed the number of shape points, meaning that the cached selected keys are no longer valid
|
|
EndEditing();
|
|
return false;
|
|
}
|
|
|
|
if (Key == EKeys::LeftMouseButton && Event == IE_Released)
|
|
{
|
|
// Reset duplication on LMB release
|
|
bAllowDuplication = true;
|
|
DuplicateAccumulatedDrag = FVector::ZeroVector;
|
|
|
|
bControlPointPositionCaptured = false;
|
|
ControlPointPosition = FVector::ZeroVector;
|
|
|
|
bHasCachedRotation = false;
|
|
CachedRotation = FQuat::Identity;
|
|
}
|
|
|
|
if (Key == EKeys::LeftMouseButton && Event == IE_Pressed)
|
|
{
|
|
bHasCachedRotation = false;
|
|
CachedRotation = FQuat::Identity;
|
|
|
|
// Cache the widget rotation when mouse is pressed down to avoid feedback effects during gizmo interaction.
|
|
if (ViewportClient->GetWidgetCoordSystemSpace() == COORD_Local || ViewportClient->GetWidgetMode() == UE::Widget::WM_Rotate)
|
|
{
|
|
bHasCachedRotation = GetLastSelectedPointRotation(CachedRotation);
|
|
}
|
|
}
|
|
|
|
if (Event == IE_Pressed)
|
|
{
|
|
bHandled = ShapeComponentVisualizerActions->ProcessCommandBindings(Key, FSlateApplication::Get().GetModifierKeys(), false);
|
|
}
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::HandleBoxSelect(const FBox& InBox, FEditorViewportClient* InViewportClient, FViewport* InViewport)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("HandleBoxSelect", "Box Select Shape Points"));
|
|
check(SelectionState);
|
|
SelectionState->Modify();
|
|
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
bool bSelectionChanged = false;
|
|
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
const FTransform& LocalToWorld = ShapeComp->GetComponentTransform();
|
|
|
|
// Shape control point selection always uses transparent box selection.
|
|
for (int32 Idx = 0; Idx < NumPoints; Idx++)
|
|
{
|
|
const FVector WorldPos = LocalToWorld.TransformPosition(ShapePoints[Idx].Position);
|
|
if (InBox.IsInside(WorldPos))
|
|
{
|
|
ChangeSelectionState(Idx, true);
|
|
bSelectionChanged = true;
|
|
}
|
|
}
|
|
|
|
if (bSelectionChanged)
|
|
{
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::HandleFrustumSelect(const FConvexVolume& InFrustum, FEditorViewportClient* InViewportClient, FViewport* InViewport)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("HandleFrustumSelect", "Frustum Select Shape Points"));
|
|
check(SelectionState);
|
|
SelectionState->Modify();
|
|
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
bool bSelectionChanged = false;
|
|
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
const FTransform& LocalToWorld = ShapeComp->GetComponentTransform();
|
|
|
|
// Shape control point selection always uses transparent box selection.
|
|
for (int32 Idx = 0; Idx < NumPoints; Idx++)
|
|
{
|
|
const FVector WorldPos = LocalToWorld.TransformPosition(ShapePoints[Idx].Position);
|
|
if (InFrustum.IntersectPoint(WorldPos))
|
|
{
|
|
ChangeSelectionState(Idx, true);
|
|
bSelectionChanged = true;
|
|
}
|
|
}
|
|
|
|
if (bSelectionChanged)
|
|
{
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::HasFocusOnSelectionBoundingBox(FBox& OutBoundingBox)
|
|
{
|
|
OutBoundingBox.Init();
|
|
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
|
|
if (SelectedPoints.Num() > 0)
|
|
{
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
const FTransform& LocalToWorld = ShapeComp->GetComponentTransform();
|
|
|
|
// Shape control point selection always uses transparent box selection.
|
|
for (const int32 Idx : SelectedPoints)
|
|
{
|
|
check(Idx >= 0);
|
|
check(Idx < NumPoints);
|
|
const FVector WorldPos = LocalToWorld.TransformPosition(ShapePoints[Idx].Position);
|
|
OutBoundingBox += WorldPos;
|
|
}
|
|
|
|
OutBoundingBox = OutBoundingBox.ExpandBy(50.f);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::HandleSnapTo(const bool bInAlign, const bool bInUseLineTrace, const bool bInUseBounds, const bool bInUsePivot, AActor* InDestination)
|
|
{
|
|
// Does not handle Snap/Align Pivot, Snap/Align Bottom Control Points or Snap/Align to Actor.
|
|
if (bInUsePivot || bInUseBounds || InDestination)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Note: value of bInUseLineTrace is ignored as we always line trace from control points.
|
|
if (UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
if (SelectedPoints.Num() > 0)
|
|
{
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
|
|
check(SelectionState->GetLastPointIndexSelected() != INDEX_NONE);
|
|
check(SelectionState->GetLastPointIndexSelected() >= 0);
|
|
check(SelectionState->GetLastPointIndexSelected() < NumPoints);
|
|
check(SelectedPoints.Contains(SelectionState->GetLastPointIndexSelected()));
|
|
|
|
ShapeComp->Modify();
|
|
|
|
bool bMovedKey = false;
|
|
|
|
// Shape control point selection always uses transparent box selection.
|
|
for (int32 Idx : SelectedPoints)
|
|
{
|
|
check(Idx >= 0);
|
|
check(Idx < NumPoints);
|
|
|
|
FVector Direction = FVector(0.f, 0.f, -1.f);
|
|
|
|
FZoneShapePoint& EditedPoint = ShapePoints[Idx];
|
|
|
|
FHitResult Hit(1.0f);
|
|
FCollisionQueryParams Params(SCENE_QUERY_STAT(MoveShapePointToTrace), true);
|
|
|
|
// Find key position in world space
|
|
const FVector CurrentWorldPos = ShapeComp->GetComponentTransform().TransformPosition(EditedPoint.Position);
|
|
|
|
if (ShapeComp->GetWorld()->LineTraceSingleByChannel(Hit, CurrentWorldPos, CurrentWorldPos + Direction * WORLD_MAX, ECC_WorldStatic, Params))
|
|
{
|
|
// Convert back to local space
|
|
EditedPoint.Position = ShapeComp->GetComponentTransform().InverseTransformPosition(Hit.Location);
|
|
|
|
if (bInAlign && EditedPoint.Type == FZoneShapePointType::Bezier)
|
|
{
|
|
// Get delta rotation between up vector and hit normal
|
|
FQuat DeltaRotate = FQuat::FindBetweenNormals(FVector::UpVector, Hit.Normal);
|
|
|
|
// Rotate tangent according to delta rotation
|
|
const FVector WorldPosition = ShapeComp->GetComponentTransform().TransformPosition(EditedPoint.Position);
|
|
const FVector WorldInControlPoint = ShapeComp->GetComponentTransform().TransformPosition(EditedPoint.GetInControlPoint());
|
|
const FVector WorldTangent = WorldInControlPoint - WorldPosition;
|
|
FVector NewTangent = DeltaRotate.RotateVector(WorldTangent);
|
|
NewTangent = ShapeComp->GetComponentTransform().InverseTransformVector(NewTangent);
|
|
EditedPoint.SetInControlPoint(EditedPoint.Position + NewTangent);
|
|
}
|
|
|
|
bMovedKey = true;
|
|
}
|
|
}
|
|
|
|
if (bMovedKey)
|
|
{
|
|
ShapeComp->UpdateShape();
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::EndEditing()
|
|
{
|
|
// Ignore if there is an undo/redo operation in progress
|
|
if (GIsTransacting)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Ignore if this happens during selection.
|
|
if (bIsSelectingComponent)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(SelectionState);
|
|
SelectionState->Modify();
|
|
if (GetEditedShapeComponent())
|
|
{
|
|
ChangeSelectionState(INDEX_NONE, false);
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
}
|
|
SelectionState->SetShapePropertyPath(FComponentPropertyPath());
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::OnDuplicatePoint() const
|
|
{
|
|
DuplicateSelectedPoints();
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::CanAddPointToSegment() const
|
|
{
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
const int32 SelectedSegmentIndex = SelectionState->GetSelectedSegmentIndex();
|
|
return (SelectedSegmentIndex != INDEX_NONE && SelectedSegmentIndex >= 0 && SelectedSegmentIndex < ShapeComp->GetNumPoints());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::OnAddPointToSegment() const
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("AddShapePoint", "Add Shape Point"));
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(ShapeComp != nullptr);
|
|
const int32 SelectedSegmentIndex = SelectionState->GetSelectedSegmentIndex();
|
|
check(SelectionState);
|
|
check(SelectedSegmentIndex != INDEX_NONE);
|
|
check(SelectedSegmentIndex >= 0);
|
|
check(SelectedSegmentIndex < ShapeComp->GetNumSegments());
|
|
|
|
SelectionState->Modify();
|
|
|
|
SplitSegment(SelectionState->GetSelectedSegmentIndex(), SelectionState->GetSelectedSegmentT());
|
|
|
|
SelectionState->SetSelectedSegmentPoint(FVector::ZeroVector);
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::DuplicateSelectedPoints(const FVector& WorldOffset, bool bInsertAfter) const
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("DuplicatePoint", "Duplicate Point"));
|
|
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(ShapeComp != nullptr);
|
|
check(SelectionState);
|
|
TSet<int32>& SelectedPoints = SelectionState->ModifySelectedPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
check(LastPointIndexSelected != INDEX_NONE);
|
|
check(LastPointIndexSelected >= 0);
|
|
check(LastPointIndexSelected < ShapeComp->GetNumPoints());
|
|
check(SelectedPoints.Num() > 0);
|
|
check(SelectedPoints.Contains(LastPointIndexSelected));
|
|
|
|
SelectionState->Modify();
|
|
|
|
ShapeComp->Modify();
|
|
if (AActor* Owner = ShapeComp->GetOwner())
|
|
{
|
|
Owner->Modify();
|
|
}
|
|
|
|
TArray<int32> SelectedPointsSorted;
|
|
for (int32 SelectedIndex : SelectedPoints)
|
|
{
|
|
SelectedPointsSorted.Add(SelectedIndex);
|
|
}
|
|
SelectedPointsSorted.Sort([](int32 A, int32 B) { return A < B; });
|
|
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
|
|
// Make copies of the points and adjust them based on the requested offset.
|
|
const FVector LocalOffset = ShapeComp->GetComponentTransform().InverseTransformVector(WorldOffset);
|
|
TArray<FZoneShapePoint> SelectedPointsCopy;
|
|
for (const int32 SelectedIndex : SelectedPointsSorted)
|
|
{
|
|
FZoneShapePoint& Point = SelectedPointsCopy.Add_GetRef(ShapePoints[SelectedIndex]);
|
|
Point.Position += LocalOffset;
|
|
}
|
|
|
|
SelectedPoints.Empty();
|
|
|
|
// The offset is incremented each time a point to make sure that the following points are inserted at after their copies too.
|
|
int32 Offset = bInsertAfter ? 1 : 0;
|
|
for (int32 i = 0; i < SelectedPointsSorted.Num(); i++)
|
|
{
|
|
// Add new point
|
|
const int32 SelectedIndex = SelectedPointsSorted[i];
|
|
const FZoneShapePoint& Point = SelectedPointsCopy[i];
|
|
const int32 InsertIndex = SelectedIndex + Offset;
|
|
check(InsertIndex <= ShapePoints.Num());
|
|
ShapePoints.Insert(Point, InsertIndex);
|
|
|
|
// Adjust selection
|
|
if (LastPointIndexSelected == SelectedIndex)
|
|
{
|
|
SelectionState->SetLastPointIndexSelected(InsertIndex);
|
|
}
|
|
SelectedPoints.Add(InsertIndex);
|
|
|
|
Offset++;
|
|
}
|
|
|
|
ShapeComp->UpdateShape();
|
|
|
|
// Unset tangent handle selection
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::DuplicatePointForAltDrag(const FVector& InDrag) const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(ShapeComp != nullptr);
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
const int32 NumPoints = ShapeComp->GetNumPoints();
|
|
check(LastPointIndexSelected != INDEX_NONE);
|
|
check(LastPointIndexSelected >= 0);
|
|
check(LastPointIndexSelected < NumPoints);
|
|
check(SelectedPoints.Contains(LastPointIndexSelected));
|
|
|
|
// Calculate approximate tangent around the current point.
|
|
int32 PrevIndex = 0;
|
|
int32 NextIndex = 0;
|
|
if (ShapeComp->IsShapeClosed())
|
|
{
|
|
PrevIndex = (LastPointIndexSelected + NumPoints - 1) % NumPoints;
|
|
NextIndex = (LastPointIndexSelected + 1) % NumPoints;
|
|
}
|
|
else
|
|
{
|
|
PrevIndex = FMath::Max(0, LastPointIndexSelected - 1);
|
|
NextIndex = FMath::Min(LastPointIndexSelected + 1, NumPoints - 1);
|
|
}
|
|
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
const FVector PrevPoint = ShapePoints[PrevIndex].Position;
|
|
const FVector NextPoint = ShapePoints[NextIndex].Position;
|
|
const FVector TangentDir = (NextPoint - PrevPoint).GetSafeNormal();
|
|
|
|
// Detect where to insert the point based on if we're dragging towards the next point or previous point.
|
|
const bool bInsertAfter = FVector::DotProduct(TangentDir, InDrag) > 0.0f;
|
|
|
|
DuplicateSelectedPoints(InDrag, bInsertAfter);
|
|
|
|
return true;
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::SplitSegment(const int32 InSegmentIndex, const float SegmentSplitT) const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
|
|
check(ShapeComp != nullptr);
|
|
check(InSegmentIndex != INDEX_NONE);
|
|
check(InSegmentIndex >= 0);
|
|
check(InSegmentIndex < ShapeComp->GetNumSegments());
|
|
|
|
ShapeComp->Modify();
|
|
if (AActor* Owner = ShapeComp->GetOwner())
|
|
{
|
|
Owner->Modify();
|
|
}
|
|
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
const int32 NumPoints = ShapePoints.Num();
|
|
const int32 StartPointIdx = InSegmentIndex;
|
|
const int32 EndPointIdx = (InSegmentIndex + 1) % NumPoints;
|
|
const FZoneShapePoint& StartPoint = ShapePoints[StartPointIdx];
|
|
const FZoneShapePoint& EndPoint = ShapePoints[EndPointIdx];
|
|
|
|
FVector StartPosition(ForceInitToZero), StartControlPoint(ForceInitToZero), EndControlPoint(ForceInitToZero), EndPosition(ForceInitToZero);
|
|
UE::ZoneShape::Utilities::GetCubicBezierPointsFromShapeSegment(StartPoint, EndPoint, FMatrix::Identity, StartPosition, StartControlPoint, EndControlPoint, EndPosition);
|
|
|
|
FZoneShapePoint NewPoint;
|
|
NewPoint.Position = UE::CubicBezier::Eval(StartPosition, StartControlPoint, EndControlPoint, EndPosition, SegmentSplitT);
|
|
|
|
|
|
// Set new point type based on neighbors
|
|
if (StartPoint.Type == FZoneShapePointType::AutoBezier || EndPoint.Type == FZoneShapePointType::AutoBezier)
|
|
{
|
|
// Auto bezier handles will be updated in UpdateShape()
|
|
NewPoint.Type = FZoneShapePointType::AutoBezier;
|
|
}
|
|
else if (StartPoint.Type == FZoneShapePointType::Bezier || EndPoint.Type == FZoneShapePointType::Bezier)
|
|
{
|
|
// Initial Bezier handles are created below, after insert.
|
|
NewPoint.Type = FZoneShapePointType::Bezier;
|
|
}
|
|
else
|
|
{
|
|
NewPoint.Type = FZoneShapePointType::Sharp;
|
|
NewPoint.TangentLength = 0.0f;
|
|
}
|
|
|
|
const int NewPointIndex = InSegmentIndex + 1;
|
|
|
|
ShapePoints.Insert(NewPoint, NewPointIndex);
|
|
|
|
// Create sane default tangent for Bezier points.
|
|
if (NewPoint.Type == FZoneShapePointType::Bezier)
|
|
{
|
|
ShapeComp->UpdatePointRotationAndTangent(NewPointIndex);
|
|
}
|
|
|
|
// Set selection to new point
|
|
ChangeSelectionState(NewPointIndex, false);
|
|
|
|
ShapeComp->UpdateShape();
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::OnDeletePoint() const
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("DeletePoint", "Delete Points"));
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(ShapeComp != nullptr);
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
check(LastPointIndexSelected != INDEX_NONE);
|
|
check(LastPointIndexSelected >= 0);
|
|
check(LastPointIndexSelected < ShapeComp->GetNumPoints());
|
|
check(SelectedPoints.Num() > 0);
|
|
check(SelectedPoints.Contains(LastPointIndexSelected));
|
|
|
|
ShapeComp->Modify();
|
|
if (AActor* Owner = ShapeComp->GetOwner())
|
|
{
|
|
Owner->Modify();
|
|
}
|
|
|
|
// Get a sorted list of all the selected indices, highest to lowest
|
|
TArray<int32> SelectedPointsSorted;
|
|
for (int32 SelectedIndex : SelectedPoints)
|
|
{
|
|
SelectedPointsSorted.Add(SelectedIndex);
|
|
}
|
|
SelectedPointsSorted.Sort([](int32 A, int32 B) { return A > B; });
|
|
|
|
// Delete selected keys from list, highest index first
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
for (const int32 SelectedIndex : SelectedPointsSorted)
|
|
{
|
|
if (ShapePoints.Num() <= 2)
|
|
{
|
|
// Keep at least 2 points
|
|
break;
|
|
}
|
|
|
|
ShapePoints.RemoveAt(SelectedIndex);
|
|
}
|
|
|
|
// Clear selection
|
|
ChangeSelectionState(INDEX_NONE, false);
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
|
|
ShapeComp->UpdateShape();
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
|
|
bool FZoneShapeComponentVisualizer::CanDeletePoint() const
|
|
{
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
return (ShapeComp != nullptr &&
|
|
SelectedPoints.Num() > 0 &&
|
|
SelectedPoints.Num() != ShapeComp->GetNumPoints() &&
|
|
LastPointIndexSelected != INDEX_NONE);
|
|
}
|
|
|
|
|
|
bool FZoneShapeComponentVisualizer::IsPointSelectionValid() const
|
|
{
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
const int32 LastPointIndexSelected = SelectionState->GetLastPointIndexSelected();
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
return (ShapeComp != nullptr &&
|
|
SelectedPoints.Num() > 0 &&
|
|
LastPointIndexSelected != INDEX_NONE);
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::OnSetPointType(FZoneShapePointType NewType) const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(ShapeComp != nullptr);
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("SetPointType", "Set Point Type"));
|
|
|
|
ShapeComp->Modify();
|
|
if (AActor* Owner = ShapeComp->GetOwner())
|
|
{
|
|
Owner->Modify();
|
|
}
|
|
|
|
TArray<FZoneShapePoint>& ShapePoints = ShapeComp->GetMutablePoints();
|
|
|
|
for (const int32 SelectedIndex : SelectedPoints)
|
|
{
|
|
check(SelectedIndex >= 0);
|
|
check(SelectedIndex < ShapePoints.Num());
|
|
|
|
FZoneShapePoint& Point = ShapePoints[SelectedIndex];
|
|
if (Point.Type != NewType)
|
|
{
|
|
const FZoneShapePointType OldType = Point.Type;
|
|
Point.Type = NewType;
|
|
if (Point.Type == FZoneShapePointType::Sharp)
|
|
{
|
|
Point.TangentLength = 0.0f;
|
|
}
|
|
else if (OldType == FZoneShapePointType::Sharp)
|
|
{
|
|
if (Point.Type == FZoneShapePointType::Bezier || Point.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
// Initialize bezier points with auto tangents.
|
|
ShapeComp->UpdatePointRotationAndTangent(SelectedIndex);
|
|
}
|
|
}
|
|
else if (OldType == FZoneShapePointType::LaneProfile && Point.Type != FZoneShapePointType::LaneProfile)
|
|
{
|
|
// Change forward to point along tangent.
|
|
Point.Rotation.Yaw -= 90.0f;
|
|
}
|
|
else if (OldType != FZoneShapePointType::LaneProfile && Point.Type == FZoneShapePointType::LaneProfile)
|
|
{
|
|
// Change forward to point inside the shape.
|
|
Point.Rotation.Yaw += 90.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
ShapeComp->UpdateShape();
|
|
NotifyPropertyModified(ShapeComp, ShapePointsProperty);
|
|
|
|
GEditor->RedrawLevelEditingViewports(true);
|
|
}
|
|
|
|
|
|
bool FZoneShapeComponentVisualizer::IsPointTypeSet(FZoneShapePointType Type) const
|
|
{
|
|
if (IsPointSelectionValid())
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
check(ShapeComp != nullptr);
|
|
check(SelectionState);
|
|
const TSet<int32>& SelectedPoints = SelectionState->GetSelectedPoints();
|
|
|
|
const TConstArrayView<FZoneShapePoint> ShapePoints = ShapeComp->GetPoints();
|
|
|
|
for (const int32 SelectedIndex : SelectedPoints)
|
|
{
|
|
check(SelectedIndex >= 0);
|
|
check(SelectedIndex < ShapePoints.Num());
|
|
if (ShapePoints[SelectedIndex].Type == Type)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::OnSelectAllPoints() const
|
|
{
|
|
if (const UZoneShapeComponent* ShapeComp = GetEditedShapeComponent())
|
|
{
|
|
check(SelectionState);
|
|
TSet<int32>& SelectedPoints = SelectionState->ModifySelectedPoints();
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("SelectAllPoints", "Select All Points"));
|
|
|
|
SelectionState->Modify();
|
|
SelectedPoints.Empty();
|
|
|
|
// Shape control point selection always uses transparent box selection.
|
|
const int32 NumPoints = ShapeComp->GetNumPoints();
|
|
for (int32 Idx = 0; Idx < NumPoints; Idx++)
|
|
{
|
|
SelectedPoints.Add(Idx);
|
|
}
|
|
|
|
SelectionState->SetLastPointIndexSelected(NumPoints - 1);
|
|
SelectionState->SetSelectedSegmentIndex(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPoint(INDEX_NONE);
|
|
SelectionState->SetSelectedControlPointType(FZoneShapeControlPointType::None);
|
|
}
|
|
}
|
|
|
|
bool FZoneShapeComponentVisualizer::CanSelectAllPoints() const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
return (ShapeComp != nullptr);
|
|
}
|
|
|
|
TSharedPtr<SWidget> FZoneShapeComponentVisualizer::GenerateContextMenu() const
|
|
{
|
|
check(SelectionState);
|
|
|
|
FMenuBuilder MenuBuilder(true, ShapeComponentVisualizerActions);
|
|
|
|
MenuBuilder.BeginSection("ShapePointEdit", LOCTEXT("ShapePoint", "Shape Point"));
|
|
{
|
|
if (SelectionState->GetSelectedSegmentIndex() != INDEX_NONE)
|
|
{
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().AddPoint);
|
|
}
|
|
else if (SelectionState->GetLastPointIndexSelected() != INDEX_NONE)
|
|
{
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().DeletePoint);
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().DuplicatePoint);
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().SelectAll);
|
|
|
|
MenuBuilder.AddSubMenu(
|
|
LOCTEXT("ShapePointType", "Point Type"),
|
|
LOCTEXT("ShapePointTypeTooltip", "Define the type of the point."),
|
|
FNewMenuDelegate::CreateSP(this, &FZoneShapeComponentVisualizer::GenerateShapePointTypeSubMenu));
|
|
}
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
MenuBuilder.BeginSection("Transform");
|
|
{
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().FocusViewportToSelection);
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
TSharedPtr<SWidget> MenuWidget = MenuBuilder.MakeWidget();
|
|
return MenuWidget;
|
|
}
|
|
|
|
void FZoneShapeComponentVisualizer::GenerateShapePointTypeSubMenu(FMenuBuilder& MenuBuilder) const
|
|
{
|
|
UZoneShapeComponent* ShapeComp = GetEditedShapeComponent();
|
|
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().SetPointToSharp);
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().SetPointToBezier);
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().SetPointToAutoBezier);
|
|
if (ShapeComp && ShapeComp->GetShapeType() == FZoneShapeType::Polygon)
|
|
{
|
|
MenuBuilder.AddMenuEntry(FZoneShapeComponentVisualizerCommands::Get().SetPointToLaneSegment);
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|