// Copyright Epic Games, Inc. All Rights Reserved. #include "LandscapeSplineSelection.h" #include "LandscapeSplineControlPoint.h" #include "LandscapeSplineSegment.h" #include "LandscapeSplinesComponent.h" #include "UnrealEdGlobals.h" #include "Editor/UnrealEdEngine.h" #include "EditorModeManager.h" #include "EditorModes.h" #include "UObject/ConstructorHelpers.h" #include "PropertyEditorModule.h" #include "Modules/ModuleManager.h" #define LOCTEXT_NAMESPACE "LandscapeSplineSelection" ULandscapeSplineSelection::ULandscapeSplineSelection(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void ULandscapeSplineSelection::SelectSegment(ULandscapeSplineSegment* Segment, ESplineNavigationFlags Flags) { // UpdateProperties, AddToSelection, SegmentModeFlags are only valid flags check(Flags == ESplineNavigationFlags::None || !EnumHasAnyFlags(Flags, ~(ESplineNavigationFlags::UpdatePropertiesWindows | ESplineNavigationFlags::AddToSelection | ESplineNavigationFlags::SegmentSelectModeEnabled))); // Do nothing if the given point is already selected if (IsSegmentSelected(Segment)) { check(Segment->IsSplineSelected()); return; } // Before entering segment mode, save the control point mode for persistence if (SelectedSplineSegments.Num() == 0) { ControlPointWidgetMode = GLevelEditorModeTools().GetWidgetMode(); } // Clear all previous selection if (!EnumHasAllFlags(Flags, ESplineNavigationFlags::AddToSelection)) { ClearSelection(); } Modify(false); SelectedSplineSegments.Add(Segment); SelectNavigationSegment(Segment); Segment->Modify(false); Segment->SetSplineSelected(true); GLevelEditorModeTools().SetWidgetMode(UE::Widget::WM_Scale); if (EnumHasAllFlags(Flags, ESplineNavigationFlags::UpdatePropertiesWindows)) { UpdatePropertiesWindows(); } } void ULandscapeSplineSelection::SelectControlPoint(ULandscapeSplineControlPoint* ControlPoint, ESplineNavigationFlags Flags) { // UpdateProperties, AddToSelection, ControlPointSelect are only valid flags check(Flags == ESplineNavigationFlags::None || !EnumHasAnyFlags(Flags, ~(ESplineNavigationFlags::UpdatePropertiesWindows | ESplineNavigationFlags::AddToSelection | ESplineNavigationFlags::ControlPointSelectModeEnabled))); // Do nothing if the given point is already selected if (IsControlPointSelected(ControlPoint)) { check(ControlPoint->IsSplineSelected()); return; } // When switching from segment mode, restore the last used control point mode if (SelectedSplineSegments.Num() > 0 && ControlPointWidgetMode.IsSet()) { GLevelEditorModeTools().SetWidgetMode(ControlPointWidgetMode.GetValue()); } // Clear all previous selection if (!EnumHasAllFlags(Flags, ESplineNavigationFlags::AddToSelection)) { ClearSelection(); } Modify(false); SelectedSplineControlPoints.Add(ControlPoint); SelectNavigationControlPoint(ControlPoint); ControlPoint->Modify(false); ControlPoint->SetSplineSelected(true); if (EnumHasAllFlags(Flags, ESplineNavigationFlags::UpdatePropertiesWindows)) { UpdatePropertiesWindows(); } } void ULandscapeSplineSelection::ClearSelectedControlPoints() { for (ULandscapeSplineControlPoint* ControlPoint : SelectedSplineControlPoints) { check(ControlPoint->IsSplineSelected()); ControlPoint->Modify(false); ControlPoint->SetSplineSelected(false); } Modify(false); SelectedSplineControlPoints.Empty(); } void ULandscapeSplineSelection::ClearSelectedSegments() { for (ULandscapeSplineSegment* Segment : SelectedSplineSegments) { check(Segment->IsSplineSelected()); Segment->Modify(false); Segment->SetSplineSelected(false); } Modify(false); SelectedSplineSegments.Empty(); } void ULandscapeSplineSelection::ClearSelection() { ClearSelectedControlPoints(); ClearSelectedSegments(); UpdatePropertiesWindows(); } void ULandscapeSplineSelection::DeselectControlPoint(ULandscapeSplineControlPoint* ControlPoint, ESplineNavigationFlags Flags) { // UpdateProperties is only valid flag check(Flags == ESplineNavigationFlags::None || !EnumHasAnyFlags(Flags, ~(ESplineNavigationFlags::UpdatePropertiesWindows))); check(ControlPoint->IsSplineSelected()); ControlPoint->Modify(false); ControlPoint->SetSplineSelected(false); Modify(false); SelectedSplineControlPoints.Remove(ControlPoint); if (EnumHasAllFlags(Flags, ESplineNavigationFlags::UpdatePropertiesWindows)) { UpdatePropertiesWindows(); } } void ULandscapeSplineSelection::DeSelectSegment(ULandscapeSplineSegment* Segment, ESplineNavigationFlags Flags) { // UpdateProperties is only valid flag check(Flags == ESplineNavigationFlags::None || !EnumHasAnyFlags(Flags, ~(ESplineNavigationFlags::UpdatePropertiesWindows))); check(Segment->IsSplineSelected()); Segment->Modify(false); Segment->SetSplineSelected(false); Modify(false); SelectedSplineSegments.Remove(Segment); if (EnumHasAllFlags(Flags, ESplineNavigationFlags::UpdatePropertiesWindows)) { UpdatePropertiesWindows(); } } void ULandscapeSplineSelection::SelectConnected() { TArray ControlPointsToProcess = ObjectPtrDecay(SelectedSplineControlPoints); while (ControlPointsToProcess.Num() > 0) { const ULandscapeSplineControlPoint* ControlPoint = ControlPointsToProcess.Pop(); for (const FLandscapeSplineConnection& Connection : ControlPoint->ConnectedSegments) { ULandscapeSplineControlPoint* OtherEnd = Connection.GetFarConnection().ControlPoint; if (!OtherEnd->IsSplineSelected()) { SelectControlPoint(OtherEnd, ESplineNavigationFlags::AddToSelection); ControlPointsToProcess.Add(OtherEnd); } } } TArray SegmentsToProcess = ObjectPtrDecay(SelectedSplineSegments); while (SegmentsToProcess.Num() > 0) { const ULandscapeSplineSegment* Segment = SegmentsToProcess.Pop(); for (const FLandscapeSplineSegmentConnection& SegmentConnection : Segment->Connections) { for (const FLandscapeSplineConnection& Connection : SegmentConnection.ControlPoint->ConnectedSegments) { if (Connection.Segment != Segment && !Connection.Segment->IsSplineSelected()) { SelectSegment(Connection.Segment, ESplineNavigationFlags::AddToSelection); SegmentsToProcess.Add(Connection.Segment); } } } } } void ULandscapeSplineSelection::SelectAllSplineSegments(const ULandscapeInfo& InLandscapeInfo) { TArray> SplineActors(InLandscapeInfo.GetSplineActors()); for (TScriptInterface SplineActor : SplineActors) { if (ULandscapeSplinesComponent* SplineComponent = SplineActor->GetSplinesComponent()) { SplineComponent->ForEachControlPoint([this](ULandscapeSplineControlPoint* ControlPoint) { for (const FLandscapeSplineConnection& Connection : ControlPoint->ConnectedSegments) { SelectSegment(Connection.Segment, ESplineNavigationFlags::AddToSelection); } }); } } } void ULandscapeSplineSelection::SelectAllControlPoints(const ULandscapeInfo& InLandscapeInfo) { TArray> SplineActors(InLandscapeInfo.GetSplineActors()); for (TScriptInterface SplineActor : SplineActors) { if (ULandscapeSplinesComponent* SplineComponent = SplineActor->GetSplinesComponent()) { SplineComponent->ForEachControlPoint([this](ULandscapeSplineControlPoint* ControlPoint) { SelectControlPoint(ControlPoint, ESplineNavigationFlags::AddToSelection); }); } } } void ULandscapeSplineSelection::SelectAdjacentControlPoints() { for (const ULandscapeSplineSegment* Segment : SelectedSplineSegments) { SelectControlPoint(Segment->Connections[0].ControlPoint, ESplineNavigationFlags::AddToSelection); SelectControlPoint(Segment->Connections[1].ControlPoint, ESplineNavigationFlags::AddToSelection); } } void ULandscapeSplineSelection::SelectAdjacentSegments() { for (ULandscapeSplineControlPoint* ControlPoint : SelectedSplineControlPoints) { for (const FLandscapeSplineConnection& Connection : ControlPoint->ConnectedSegments) { SelectSegment(Connection.Segment, ESplineNavigationFlags::AddToSelection); } } } const bool ULandscapeSplineSelection::IsSegmentSelected(ULandscapeSplineSegment* Segment) const { return SelectedSplineSegments.Contains(Segment); } const bool ULandscapeSplineSelection::IsControlPointSelected(ULandscapeSplineControlPoint* ControlPoint) const { return SelectedSplineControlPoints.Contains(ControlPoint); } void ULandscapeSplineSelection::UpdatePropertiesWindows() const { TArray Objects; Objects.Reset(SelectedSplineControlPoints.Num() + SelectedSplineSegments.Num()); Algo::Copy(SelectedSplineControlPoints, Objects); Algo::Copy(SelectedSplineSegments, Objects); FPropertyEditorModule& PropertyModule = FModuleManager::Get().LoadModuleChecked(TEXT("PropertyEditor")); PropertyModule.UpdatePropertyViews(Objects); GUnrealEd->RedrawLevelEditingViewports(); } ULandscapeSplineControlPoint* ULandscapeSplineSelection::GetLinearEndControlPointInternal(ULandscapeSplineControlPoint* SelectedControlPoint) { TSet ControlPointsVisited; TArray ControlPointsToProcess; ControlPointsToProcess.Add(SelectedControlPoint); Modify(false); // Reset the linear path before it gets rebuilt below LinearControlPoints.Empty(); // Control points with zero/multi node connections are not part of a valid linear path if (SelectedControlPoint->ConnectedSegments.IsEmpty() || SelectedControlPoint->ConnectedSegments.Num() > 2) { LinearControlPoints.Add(SelectedControlPoint); return SelectedControlPoint; } while (ControlPointsToProcess.Num() > 0) { ULandscapeSplineControlPoint* ControlPoint = ControlPointsToProcess.Pop(); ControlPointsVisited.Add(ControlPoint); // Store nodes in order of visiting to create the linear path if (!LinearControlPoints.Contains(ControlPoint)) { LinearControlPoints.Add(ControlPoint); } // Valid end points only have one connection if (ControlPoint->ConnectedSegments.Num() == 1 && ControlPoint != SelectedControlPoint) { return ControlPoint; } for (const FLandscapeSplineConnection& Connection : ControlPoint->ConnectedSegments) { ULandscapeSplineControlPoint* OtherEnd = Connection.GetFarConnection().ControlPoint; check(OtherEnd != nullptr); if (OtherEnd->ConnectedSegments.IsEmpty() || OtherEnd->ConnectedSegments.Num() > 2) { continue; } if (!ControlPointsVisited.Contains(OtherEnd)) { ControlPointsToProcess.Add(OtherEnd); } } } return SelectedControlPoint; } void ULandscapeSplineSelection::ResetNavigationPath() { Modify(false); // Clear cached path LinearControlPoints.Empty(); LinearSegments.Empty(); } void ULandscapeSplineSelection::BuildLinearPathFromLastSelectedPointInternal() { Modify(false); // Calculate the last selected point // If a control point is selected, use it directly. Otherwise, if a segment is selected, retrieve the control point from its connection TObjectPtr LastSelectedSegment = SelectedSplineSegments.IsEmpty() ? nullptr : SelectedSplineSegments.Last(); TObjectPtr LastSelectedControlPoint = SelectedSplineControlPoints.IsEmpty() && LastSelectedSegment ? LastSelectedSegment->Connections[0].ControlPoint : SelectedSplineControlPoints.Last(); check(LastSelectedControlPoint != nullptr); LinearControlPoints.Empty(); LinearSegments.Empty(); ULandscapeSplineControlPoint* StartControlPoint = GetLinearEndControlPointInternal(LastSelectedControlPoint); ULandscapeSplineControlPoint* EndControlPoint = GetLinearEndControlPointInternal(StartControlPoint); for (const ULandscapeSplineControlPoint* Point : LinearControlPoints) { for (const FLandscapeSplineConnection& Connection : Point->ConnectedSegments) { if (!LinearSegments.Contains(Connection.Segment)) { LinearSegments.Add(Connection.Segment); } } } } void ULandscapeSplineSelection::SelectNavigationSegment(ULandscapeSplineSegment* Segment) { Modify(false); if (!LinearSegments.Contains(Segment)) { BuildLinearPathFromLastSelectedPointInternal(); } } void ULandscapeSplineSelection::SelectNavigationControlPoint(ULandscapeSplineControlPoint* ControlPoint) { Modify(false); if (!LinearControlPoints.Contains(ControlPoint)) { BuildLinearPathFromLastSelectedPointInternal(); } } bool ULandscapeSplineSelection::IsSelectionValidForNavigation() const { // If any elements in selection are part of different linear paths, disable navigation for (ULandscapeSplineControlPoint* ControlPoint : SelectedSplineControlPoints) { if (!LinearControlPoints.Contains(ControlPoint)) { return false; } } for (ULandscapeSplineSegment* Segment : SelectedSplineSegments) { if (!LinearSegments.Contains(Segment)) { return false; } } return true; } ULandscapeSplineSegment* ULandscapeSplineSelection::GetEndSegmentInLinearPath(ESplineNavigationFlags Flags) const { check(EnumHasAllFlags(Flags, ESplineNavigationFlags::SegmentSelectModeEnabled)); check(FMath::CountBits(static_cast(Flags & ESplineNavigationFlags::DirectionMask)) == 1); if (LinearSegments.IsEmpty()) { return nullptr; } if (EnumHasAllFlags(Flags, ESplineNavigationFlags::DirectionForward)) { return LinearSegments.Top(); } return LinearSegments[0]; } ULandscapeSplineControlPoint* ULandscapeSplineSelection::GetEndControlPointInLinearPath(ESplineNavigationFlags Flags) const { check(EnumHasAllFlags(Flags, ESplineNavigationFlags::ControlPointSelectModeEnabled)); check(FMath::CountBits(static_cast(Flags & ESplineNavigationFlags::SelectModeMask)) == 1); if (LinearControlPoints.IsEmpty()) { return nullptr; } if (EnumHasAllFlags(Flags, ESplineNavigationFlags::DirectionForward)) { return LinearControlPoints.Top(); } return LinearControlPoints[0]; } ULandscapeSplineSegment* ULandscapeSplineSelection::GetAdjacentSegmentInLinearPath(ESplineNavigationFlags Flags) const { check(EnumHasAllFlags(Flags, ESplineNavigationFlags::SegmentSelectModeEnabled)); check(FMath::CountBits(static_cast(Flags & ESplineNavigationFlags::SelectModeMask)) == 1); const int32 LastSelectedSegmentIndex = LinearSegments.Find(SelectedSplineSegments.Last()); const int32 NextIndexOffset = EnumHasAllFlags(Flags, ESplineNavigationFlags::DirectionForward) ? 1 : -1; check(LastSelectedSegmentIndex != INDEX_NONE); // last selected should always be part of the linear path if (LinearSegments.IsValidIndex(LastSelectedSegmentIndex + NextIndexOffset)) { return LinearSegments[LastSelectedSegmentIndex + NextIndexOffset]; } return nullptr; } ULandscapeSplineControlPoint* ULandscapeSplineSelection::GetAdjacentControlPointInPath(ESplineNavigationFlags Flags) const { check(EnumHasAllFlags(Flags, ESplineNavigationFlags::ControlPointSelectModeEnabled)); check(FMath::CountBits(static_cast(Flags & ESplineNavigationFlags::SelectModeMask)) == 1); const int32 LastSelectedControlPointIndex = LinearControlPoints.Find(SelectedSplineControlPoints.Last()); const int32 NextIndexOffset = EnumHasAllFlags(Flags, ESplineNavigationFlags::DirectionForward) ? 1 : -1; check(LastSelectedControlPointIndex != INDEX_NONE); // last selected should always be part of the linear path if (LinearControlPoints.IsValidIndex(LastSelectedControlPointIndex + NextIndexOffset)) { return LinearControlPoints[LastSelectedControlPointIndex + NextIndexOffset]; } return nullptr; } bool ULandscapeSplineSelection::HasAdjacentSegmentInLinearPath(ESplineNavigationFlags Flags) const { check(EnumHasAllFlags(Flags, ESplineNavigationFlags::SegmentSelectModeEnabled)); check(FMath::CountBits(static_cast(Flags & ESplineNavigationFlags::SelectModeMask)) == 1); const ULandscapeSplineSegment* AdjacentSegment = GetAdjacentSegmentInLinearPath(Flags); if (!AdjacentSegment || !IsSelectionValidForNavigation()) { return false; } if (EnumHasAllFlags(Flags, ESplineNavigationFlags::AddToSelection)) { return !AdjacentSegment->IsSplineSelected(); } return true; } bool ULandscapeSplineSelection::HasAdjacentControlPointInLinearPath(ESplineNavigationFlags Flags) const { check(EnumHasAllFlags(Flags, ESplineNavigationFlags::ControlPointSelectModeEnabled)); check(FMath::CountBits(static_cast(Flags & ESplineNavigationFlags::SelectModeMask)) == 1); const ULandscapeSplineControlPoint* AdjacentPoint = GetAdjacentControlPointInPath(Flags); if (!AdjacentPoint || !IsSelectionValidForNavigation()) { return false; } if (EnumHasAllFlags(Flags, ESplineNavigationFlags::AddToSelection)) { return !AdjacentPoint->IsSplineSelected(); } return true; } #undef LOCTEXT_NAMESPACE