Files
UnrealEngine/Engine/Plugins/Runtime/MeshModelingToolset/Source/ModelingComponents/Private/Selection/GeometrySelectionManager.cpp
2025-05-18 13:04:45 +08:00

1880 lines
62 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Selection/GeometrySelectionManager.h"
#include "CoreGlobals.h" // for GIsTransacting
#include "Engine/Engine.h"
#include "Selection/DynamicMeshSelector.h"
#include "Selection/ToolSelectionUtil.h"
#include "Selection/SelectionEditInteractiveCommand.h"
#include "InteractiveToolsContext.h"
#include "InteractiveToolManager.h"
#include "ToolContextInterfaces.h"
#include "ToolDataVisualizer.h"
#include "ToolSetupUtil.h"
#include "Drawing/PreviewGeometryActor.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "Selections/GeometrySelectionUtil.h"
#include "Util/ColorConstants.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GeometrySelectionManager)
static TAutoConsoleVariable<int32> CVarGeometrySelectionManager_FullSelectionHoverHighlights(
TEXT("modeling.Selection.FullHoverHighlights"),
1,
TEXT("Use full selection hover highlights instead of simplified highlights")
);
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UGeometrySelectionManager"
void UGeometrySelectionManager::Initialize( UInteractiveToolsContext* ToolsContextIn, IToolsContextTransactionsAPI* TransactionsAPIIn )
{
ToolsContext = ToolsContextIn;
TransactionsAPI = TransactionsAPIIn;
PreviewGeometry = NewObject<UPreviewGeometry>(this);
}
void UGeometrySelectionManager::RegisterSelectorFactory(TUniquePtr<IGeometrySelectorFactory> Factory)
{
Factories.Add(MoveTemp(Factory));
ResetTargetCache();
}
void UGeometrySelectionManager::Shutdown()
{
DiscardSavedSelection();
OnSelectionModified.Clear();
ToolsContext = nullptr;
TransactionsAPI = nullptr;
DisconnectPreviewGeometry();
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
SleepOrShutdownTarget(Target, false);
}
ResetTargetCache();
ActiveTargetReferences.Reset();
ActiveTargetMap.Reset();
UpdateSelectionRenderCacheOnTargetChange();
}
bool UGeometrySelectionManager::HasBeenShutDown() const
{
return (ToolsContext == nullptr);
}
class FGeometrySelectionManager_SelectionTypeChange : public FToolCommandChange
{
public:
EGeometryElementType FromElementType = EGeometryElementType::Face;
EGeometryElementType ToElementType = EGeometryElementType::Face;
UGeometrySelectionManager::EMeshTopologyMode FromTopologyMode = UGeometrySelectionManager::EMeshTopologyMode::None;
UGeometrySelectionManager::EMeshTopologyMode ToTopologyMode = UGeometrySelectionManager::EMeshTopologyMode::None;
/** Makes the change to the object */
virtual void Apply(UObject* Object) override
{
// do the (default) red selectable lines/verts need to be rebuilt?
// - ex: when moving from object mode to tri/vert/edge mode, or moving between vert and edge/face mode,
// or between triangle and polygroup topology
bool bRebuildSelectable = false;
UGeometrySelectionManager* GeoSelectionManager = CastChecked<UGeometrySelectionManager>(Object);
// removes existing Line/Point/Triangle Sets when moving between vertex and face/edge modes during redo
if (
(((ToElementType == EGeometryElementType::Vertex) && (FromElementType == EGeometryElementType::Edge || FromElementType == EGeometryElementType::Face))
|| ((ToElementType == EGeometryElementType::Edge || ToElementType == EGeometryElementType::Face) && (FromElementType == EGeometryElementType::Vertex)))
&& (ToTopologyMode != UGeometrySelectionManager::EMeshTopologyMode::None)
)
{
GeoSelectionManager->RemoveAllSets();
bRebuildSelectable = true;
}
if (FromTopologyMode != ToTopologyMode)
{
// when changing to Object mode, lines or verts need to be cleared
if (ToTopologyMode == UGeometrySelectionManager::EMeshTopologyMode::None)
{
GeoSelectionManager->RemoveAllSets();
}
// in all other cases of changing topology modes, the lines/verts need to be rebuilt
// uses a flag to preserve order of Removing Sets->Setting Element Type/Topo Mode -> Rebuild (when applicable for each step)
else
{
bRebuildSelectable = true;
}
GeoSelectionManager->SetMeshTopologyModeInternal(ToTopologyMode);
}
if (FromElementType != ToElementType)
{
GeoSelectionManager->SetSelectionElementTypeInternal(ToElementType);
}
// if applicable, rebuilds lines/verts
if (bRebuildSelectable)
{
GeoSelectionManager->RebuildSelectable();
}
}
/** Reverts change to the object */
virtual void Revert(UObject* Object) override
{
// do the (default) red selectable lines/verts need to be rebuilt?
// - ex: when moving from object mode to tri/vert/edge mode, or moving between vert and edge/face mode
// or between triangle and polygroup topology
bool bRebuildSelectable = false;
UGeometrySelectionManager* GeoSelectionManager = CastChecked<UGeometrySelectionManager>(Object);
// removes existing Line/Point/Triangle Sets when moving between vertex and face/edge modes during undo
if (
(((ToElementType == EGeometryElementType::Vertex) && (FromElementType == EGeometryElementType::Edge || FromElementType == EGeometryElementType::Face))
|| ((ToElementType == EGeometryElementType::Edge || ToElementType == EGeometryElementType::Face) && (FromElementType == EGeometryElementType::Vertex)))
&& (FromTopologyMode != UGeometrySelectionManager::EMeshTopologyMode::None)
)
{
GeoSelectionManager->RemoveAllSets();
bRebuildSelectable = true;
}
if (FromTopologyMode != ToTopologyMode)
{
// when changing to Object mode, lines or verts need to be cleared
if (FromTopologyMode == UGeometrySelectionManager::EMeshTopologyMode::None)
{
GeoSelectionManager->RemoveAllSets();
}
// in all other cases of changing topology modes, the lines/verts need to be rebuilt
// uses a flag to preserve order of Removing Sets->Setting Element Type/Topo Mode -> Rebuild (when applicable for each step)
else
{
bRebuildSelectable = true;
}
GeoSelectionManager->SetMeshTopologyModeInternal(FromTopologyMode);
}
if (FromElementType != ToElementType)
{
GeoSelectionManager->SetSelectionElementTypeInternal(FromElementType);
}
// if applicable, rebuilds lines/verts
if (bRebuildSelectable)
{
GeoSelectionManager->RebuildSelectable();
}
}
/** Describes this change (for debugging) */
virtual FString ToString() const override { return TEXT("FGeometrySelectionManager_SelectionTypeChange"); }
virtual bool HasExpired(UObject* Object) const override
{
UGeometrySelectionManager* Manager = Cast<UGeometrySelectionManager>(Object);
return (Manager == nullptr || IsValid(Manager) == false || Manager->HasBeenShutDown());
}
};
void UGeometrySelectionManager::SetSelectionElementTypeInternal(EGeometryElementType NewElementType)
{
if (SelectionElementType != NewElementType)
{
SelectionElementType = NewElementType;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
Target->Selection.ElementType = SelectionElementType;
bool bEnableTopologyFilter = (Target->Selection.TopologyType == EGeometryTopologyType::Polygroup && Target->Selection.ElementType != EGeometryElementType::Vertex);
Target->SelectionEditor->UpdateQueryConfig(GetCurrentSelectionQueryConfig(), bEnableTopologyFilter);
}
MarkRenderCachesDirty(false);
ClearActivePreview();
}
}
void UGeometrySelectionManager::SetSelectionElementType(EGeometryElementType NewElementType)
{
if (SelectionElementType != NewElementType)
{
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("ChangeElementType", "Selection Type"));
if (HasSelection())
{
ClearSelection();
}
// We have to undo/redo the change to the selection type because if we want to 'undo' this later and restore
// the current selection, we need the active element type to be correct. Note that it goes *after* the Clear
// so that when we undo, we change to the correct type before we restore
TUniquePtr<FGeometrySelectionManager_SelectionTypeChange> TypeChange = MakeUnique<FGeometrySelectionManager_SelectionTypeChange>();
TypeChange->FromElementType = SelectionElementType;
TypeChange->ToElementType = NewElementType;
TypeChange->FromTopologyMode = TypeChange->ToTopologyMode = MeshTopologyMode; // no-op
GetTransactionsAPI()->AppendChange(this, MoveTemp(TypeChange), LOCTEXT("ChangeElementType", "Selection Type"));
SetSelectionElementTypeInternal(NewElementType);
GetTransactionsAPI()->EndUndoTransaction();
}
}
void UGeometrySelectionManager::SetMeshTopologyModeInternal(EMeshTopologyMode NewTopologyMode)
{
if (MeshTopologyMode != NewTopologyMode)
{
MeshTopologyMode = NewTopologyMode;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
Target->Selection.TopologyType = GetSelectionTopologyType();
bool bEnableTopologyFilter = (Target->Selection.TopologyType == EGeometryTopologyType::Polygroup && Target->Selection.ElementType != EGeometryElementType::Vertex);
Target->SelectionEditor->UpdateQueryConfig(GetCurrentSelectionQueryConfig(), bEnableTopologyFilter);
}
MarkRenderCachesDirty();
ClearActivePreview();
}
}
void UGeometrySelectionManager::SetMeshTopologyMode(EMeshTopologyMode NewTopologyMode)
{
if (MeshTopologyMode != NewTopologyMode)
{
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("ChangeSelectionMode", "Selection Mode"));
if (HasSelection())
{
ClearSelection();
}
// We have to undo/redo the change to the selection type because if we want to 'undo' this later and restore
// the current selection, we need the active element type to be correct. Note that it goes *after* the Clear
// so that when we undo, we change to the correct type before we restore
TUniquePtr<FGeometrySelectionManager_SelectionTypeChange> TypeChange = MakeUnique<FGeometrySelectionManager_SelectionTypeChange>();
TypeChange->FromTopologyMode = MeshTopologyMode;
TypeChange->ToTopologyMode = NewTopologyMode;
TypeChange->FromElementType = TypeChange->ToElementType = SelectionElementType; // no-op
GetTransactionsAPI()->AppendChange(this, MoveTemp(TypeChange),LOCTEXT("ChangeSelectionMode", "Selection Mode"));
SetMeshTopologyModeInternal(NewTopologyMode);
GetTransactionsAPI()->EndUndoTransaction();
}
}
void UGeometrySelectionManager::RebuildSelectable() const
{
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
CreateOrUpdateAllSets(CachedSelectableRenderElements[k], UnselectedParams);
}
}
void UGeometrySelectionManager::SetMeshSelectionTypeAndMode(EGeometryElementType NewElementType, EMeshTopologyMode NewTopologyMode, bool bConvertSelection)
{
if (MeshTopologyMode != NewTopologyMode || SelectionElementType != NewElementType)
{
bool bHasSelection = HasSelection();
// If we're converting selections, save the old one; we will re-add it after changing the mode
TArray<FGeometrySelection> OldTypeSelections;
if (bHasSelection && bConvertSelection)
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
OldTypeSelections.Add(Target->Selection);
}
}
ClearSelection();
// clear preview geometry sets when in Object selection mode
if (NewTopologyMode == EMeshTopologyMode::None && PreviewGeometry)
{
RemoveAllSets();
}
// removes existing Line/Point/Triangle Sets when moving between vertex and face/edge modes
bool bRebuildSelectable = false;
if (
(((NewElementType == EGeometryElementType::Vertex) && (SelectionElementType == EGeometryElementType::Edge || SelectionElementType == EGeometryElementType::Face))
|| ((NewElementType == EGeometryElementType::Edge || NewElementType == EGeometryElementType::Face) && (SelectionElementType == EGeometryElementType::Vertex)))
&& (NewTopologyMode != EMeshTopologyMode::None)
) // ensure lines not rebuilt when changing to object mode
{
RemoveAllSets();
bRebuildSelectable = true;
}
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("ChangeElementMethod", "Change Selection Method"));
// We have to undo/redo the change to the selection type because if we want to 'undo' this later and restore
// the current selection, we need the active element type to be correct. Note that it goes *after* the Clear
// so that when we undo, we change to the correct type before we restore
TUniquePtr<FGeometrySelectionManager_SelectionTypeChange> TypeChange = MakeUnique<FGeometrySelectionManager_SelectionTypeChange>();
TypeChange->FromElementType = SelectionElementType;
TypeChange->ToElementType = NewElementType;
TypeChange->FromTopologyMode = MeshTopologyMode;
TypeChange->ToTopologyMode = NewTopologyMode;
GetTransactionsAPI()->AppendChange(this, MoveTemp(TypeChange), LOCTEXT("ChangeElementMethod", "Change Selection Method"));
SetSelectionElementTypeInternal(NewElementType);
SetMeshTopologyModeInternal(NewTopologyMode);
if (bRebuildSelectable)
{
RebuildSelectable();
}
if (bHasSelection && bConvertSelection && ensure(ActiveTargetReferences.Num() == OldTypeSelections.Num()))
{
for (int32 TargetIdx = 0; TargetIdx < ActiveTargetReferences.Num(); ++TargetIdx)
{
// Add back the old selection, converted to the new mode/type
TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[TargetIdx];
FGeometrySelection InitialSelection = Target->Selection;
FGeometrySelectionDelta AfterDelta;
Target->Selector->UpdateSelectionFromSelection(OldTypeSelections[TargetIdx], true, *Target->SelectionEditor, FGeometrySelectionUpdateConfig{EGeometrySelectionChangeType::Replace}, &AfterDelta);
if (!AfterDelta.IsEmpty())
{
TUniquePtr<FGeometrySelectionReplaceChange> NewSelectionChange = MakeUnique<FGeometrySelectionReplaceChange>();
NewSelectionChange->Identifier = Target->TargetIdentifier;
NewSelectionChange->After = Target->Selection;
NewSelectionChange->Before = InitialSelection;
GetTransactionsAPI()->AppendChange(this, MoveTemp(NewSelectionChange), LOCTEXT("ConvertSelection", "Convert Selection"));
}
}
}
GetTransactionsAPI()->EndUndoTransaction();
OnSelectionModified.Broadcast();
}
}
EGeometryTopologyType UGeometrySelectionManager::GetSelectionTopologyType() const
{
if (MeshTopologyMode == EMeshTopologyMode::Polygroup)
{
return EGeometryTopologyType::Polygroup;
}
else
{
return EGeometryTopologyType::Triangle;
}
}
FGeometrySelectionHitQueryConfig UGeometrySelectionManager::GetCurrentSelectionQueryConfig() const
{
FGeometrySelectionHitQueryConfig HitQueryConfig;
HitQueryConfig.TopologyType = GetSelectionTopologyType();
HitQueryConfig.ElementType = GetSelectionElementType();
HitQueryConfig.bOnlyVisible = true;
HitQueryConfig.bHitBackFaces = GetHitBackFaces();
return HitQueryConfig;
}
void UGeometrySelectionManager::SetHitBackFaces(const bool bInHitBackFaces)
{
bHitBackFaces = bInHitBackFaces;
for (const TSharedPtr<FGeometrySelectionTarget>& Target : ActiveTargetReferences)
{
FGeometrySelectionHitQueryConfig TargetQueryConfig = Target->SelectionEditor->GetQueryConfig();
TargetQueryConfig.bHitBackFaces = bHitBackFaces;
Target->SelectionEditor->UpdateQueryConfig(TargetQueryConfig, Target->SelectionEditor->GetIsTopologyIDFilteringEnabled());
}
}
bool UGeometrySelectionManager::HasSelection() const
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->Selection.IsEmpty() == false)
{
return true;
}
}
return false;
}
void UGeometrySelectionManager::GetActiveSelectionInfo(EGeometryTopologyType& TopologyTypeOut, EGeometryElementType& ElementTypeOut, int& NumTargetsOut, bool& bIsEmpty) const
{
FGeometrySelectionHitQueryConfig Config = GetCurrentSelectionQueryConfig();
TopologyTypeOut = Config.TopologyType;
ElementTypeOut = Config.ElementType;
NumTargetsOut = ActiveTargetReferences.Num();
bIsEmpty = (NumTargetsOut == 0) || ActiveTargetReferences[0]->Selection.IsEmpty();
}
class FGeometrySelectionManager_ActiveTargetsChange : public FToolCommandChange
{
public:
TArray<FGeometryIdentifier> TargetsBefore;
TArray<FGeometryIdentifier> TargetsAfter;
virtual void Apply(UObject* Object) override
{
CastChecked<UGeometrySelectionManager>(Object)->SetTargetsOnUndoRedo(TargetsAfter);
}
virtual void Revert(UObject* Object) override
{
CastChecked<UGeometrySelectionManager>(Object)->SetTargetsOnUndoRedo(TargetsBefore);
}
virtual FString ToString() const override { return TEXT("FGeometrySelectionManager_ActiveTargetsChange"); }
virtual bool HasExpired(UObject* Object) const override
{
UGeometrySelectionManager* Manager = Cast<UGeometrySelectionManager>(Object);
return (Manager == nullptr || IsValid(Manager) == false || Manager->HasBeenShutDown());
}
};
class FGeometrySelectionManager_TargetLockStateChange : public FToolCommandChange
{
public:
FGeometryIdentifier TargetIdentifier;
bool bToState;
virtual void Apply(UObject* Object) override
{
CastChecked<UGeometrySelectionManager>(Object)->SetTargetLockStateOnUndoRedo(TargetIdentifier, bToState);
}
virtual void Revert(UObject* Object) override
{
CastChecked<UGeometrySelectionManager>(Object)->SetTargetLockStateOnUndoRedo(TargetIdentifier, !bToState);
}
virtual FString ToString() const override { return TEXT("FGeometrySelectionManager_TargetLockStateChange"); }
virtual bool HasExpired(UObject* Object) const override
{
UGeometrySelectionManager* Manager = Cast<UGeometrySelectionManager>(Object);
return (Manager == nullptr || IsValid(Manager) == false || Manager->HasBeenShutDown());
}
};
bool UGeometrySelectionManager::HasActiveTargets() const
{
return (ActiveTargetReferences.Num() > 0);
}
bool UGeometrySelectionManager::ValidateSelectionState() const
{
for (const TSharedPtr<FGeometrySelectionTarget>& Target : ActiveTargetReferences)
{
if (!Target.IsValid())
{
return false;
}
// if we have a stale target/selection object, selection state is not valid
// Note: it is ok for the object to be explicitly null, just not stale
if (Target->SelectionIdentifer.TargetObject.IsStale())
{
return false;
}
if (Target->TargetIdentifier.TargetObject.IsStale())
{
return false;
}
}
return true;
}
void UGeometrySelectionManager::ClearActiveTargets()
{
// generally at this point is it too late to clear the selection, because it will emit an
// undo that cannot be redone later, because on redo the Targets will not exist yet
// (one possibility would be to emit separate changes for when the target set is modified?? would that work w/ delete?? )
ensure(HasSelection() == false);
DiscardSavedSelection();
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
SleepOrShutdownTarget(Target, false);
}
ActiveTargetReferences.Reset();
ActiveTargetMap.Reset();
UpdateSelectionRenderCacheOnTargetChange();
OnSelectionModified.Broadcast();
}
bool UGeometrySelectionManager::AddActiveTarget(FGeometryIdentifier TargetIdentifier)
{
if (ActiveTargetMap.Contains(TargetIdentifier))
{
return false;
}
// need to have a selector factory that can build for this target
const IGeometrySelectorFactory* UseFactory = nullptr;
for (const TUniquePtr<IGeometrySelectorFactory>& Factory : Factories)
{
if (Factory->CanBuildForTarget(TargetIdentifier))
{
UseFactory = Factory.Get();
break;
}
}
if (UseFactory == nullptr)
{
return false;
}
TSharedPtr<FGeometrySelectionTarget> SelectionTarget = GetCachedTarget(TargetIdentifier, UseFactory);
if (SelectionTarget.IsValid() == false)
{
return false;
}
ActiveTargetMap.Add(TargetIdentifier, SelectionTarget);
ActiveTargetReferences.Add(SelectionTarget);
SelectionTarget->OnGeometryModifiedHandle =
SelectionTarget->Selector->GetOnGeometryModifed().AddUObject(this, &UGeometrySelectionManager::OnTargetGeometryModified);
UpdateSelectionRenderCacheOnTargetChange();
return true;
}
void UGeometrySelectionManager::SynchronizeActiveTargets(
const TArray<FGeometryIdentifier>& DesiredActiveSet,
TFunctionRef<void()> WillChangeActiveTargetsCallback)
{
TArray<FGeometryIdentifier> Before = GetCurrentTargetIdentifiers();
// currently only support single selection
if (DesiredActiveSet.Num() == 1)
{
// if we do not already have this target, select it
if ( ActiveTargetMap.Contains(DesiredActiveSet[0]) == false )
{
WillChangeActiveTargetsCallback();
ClearActiveTargets();
AddActiveTarget(DesiredActiveSet[0]);
}
}
else
{
WillChangeActiveTargetsCallback();
ClearActiveTargets();
}
TArray<FGeometryIdentifier> After = GetCurrentTargetIdentifiers();
if (Before != After)
{
TUniquePtr<FGeometrySelectionManager_ActiveTargetsChange> Change = MakeUnique<FGeometrySelectionManager_ActiveTargetsChange>();
Change->TargetsBefore = Before;
Change->TargetsAfter = After;
GetTransactionsAPI()->AppendChange(this, MoveTemp(Change), LOCTEXT("Change Targets", "Change Targets"));
}
}
bool UGeometrySelectionManager::GetAnyCurrentTargetsLockable() const
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->Selector->IsLockable())
{
return true;
}
}
return false;
}
bool UGeometrySelectionManager::GetAnyCurrentTargetsLocked() const
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->Selector->IsLockable() && Target->Selector->IsLocked())
{
return true;
}
}
return false;
}
void UGeometrySelectionManager::SetCurrentTargetsLockState(bool bLocked)
{
bool bInTransaction = false;
bool bLockStateModified = false;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->Selector->IsLockable() && Target->Selector->IsLocked() != bLocked)
{
Target->Selector->SetLockedState(bLocked);
bLockStateModified = true;
if (!bInTransaction)
{
GetTransactionsAPI()->BeginUndoTransaction(
(bLocked) ? LOCTEXT("Lock Target", "Lock Target") : LOCTEXT("Unlock Target", "Unlock Target"));
bInTransaction = true;
}
TUniquePtr<FGeometrySelectionManager_TargetLockStateChange> Change = MakeUnique<FGeometrySelectionManager_TargetLockStateChange>();
Change->TargetIdentifier = Target->TargetIdentifier;
Change->bToState = bLocked;
GetTransactionsAPI()->AppendChange(this, MoveTemp(Change),
(bLocked) ? LOCTEXT("Lock Target", "Lock Target") : LOCTEXT("Unlock Target", "Unlock Target"));
}
}
if (bLockStateModified)
{
ClearSelection();
}
if (bInTransaction)
{
GetTransactionsAPI()->EndUndoTransaction();
}
}
void UGeometrySelectionManager::SetTargetLockStateOnUndoRedo(FGeometryIdentifier TargetIdentifier, bool bLocked)
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->TargetIdentifier == TargetIdentifier)
{
Target->Selector->SetLockedState(bLocked);
}
}
}
TArray<FGeometryIdentifier> UGeometrySelectionManager::GetCurrentTargetIdentifiers() const
{
TArray<FGeometryIdentifier> Result;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
Result.Add(Target->TargetIdentifier);
}
return Result;
}
void UGeometrySelectionManager::SetTargetsOnUndoRedo(TArray<FGeometryIdentifier> NewTargets)
{
ClearActiveTargets();
for (FGeometryIdentifier Identifier : NewTargets)
{
AddActiveTarget(Identifier);
}
}
void UGeometrySelectionManager::SleepOrShutdownTarget(TSharedPtr<FGeometrySelectionTarget> Target, bool bForceShutdown)
{
if (Target->Selector->SupportsSleep() && bForceShutdown == false)
{
if (Target->Selector->Sleep())
{
return;
}
}
// if target cannot sleep or if sleeping failed, make sure it is not in the target
// cache so that we do not try to restore it later
TargetCache.Remove(Target->TargetIdentifier);
Target->Selector->GetOnGeometryModifed().Remove(Target->OnGeometryModifiedHandle);
Target->Selector->Shutdown();
}
TSharedPtr<UGeometrySelectionManager::FGeometrySelectionTarget> UGeometrySelectionManager::GetCachedTarget(FGeometryIdentifier TargetIdentifier, const IGeometrySelectorFactory* UseFactory)
{
if (TargetCache.Contains(TargetIdentifier))
{
TSharedPtr<FGeometrySelectionTarget> FoundTarget = TargetCache[TargetIdentifier];
FoundTarget->Selection.Reset();
bool bRestored = FoundTarget->Selector->Restore();
if (bRestored)
{
// ensure these are current, as they may have changed while Target was asleep
FoundTarget->Selection.ElementType = GetSelectionElementType();
FoundTarget->Selection.TopologyType = GetSelectionTopologyType();
bool bEnableTopologyFilter = (FoundTarget->Selection.TopologyType == EGeometryTopologyType::Polygroup && FoundTarget->Selection.ElementType != EGeometryElementType::Vertex);
FoundTarget->SelectionEditor->UpdateQueryConfig(GetCurrentSelectionQueryConfig(), bEnableTopologyFilter);
return FoundTarget;
}
else
{
// if restore failed, something is wrong w/ TargetCache, remove this Target
TargetCache.Remove(TargetIdentifier);
}
}
// if we are in a situation where we don't have a cache, currently we need the Factory to exist?
if (UseFactory == nullptr)
{
return nullptr;
}
// selector has to be built properly
TUniquePtr<IGeometrySelector> Selector = UseFactory->BuildForTarget(TargetIdentifier);
if (Selector.IsValid() == false)
{
return nullptr;
}
TSharedPtr<FGeometrySelectionTarget> SelectionTarget = MakeShared<FGeometrySelectionTarget>();
SelectionTarget->Selector = MoveTemp(Selector);
SelectionTarget->TargetIdentifier = TargetIdentifier;
SelectionTarget->SelectionIdentifer = SelectionTarget->Selector->GetIdentifier();
SelectionTarget->Selection.ElementType = GetSelectionElementType();
SelectionTarget->Selection.TopologyType = GetSelectionTopologyType();
SelectionTarget->SelectionEditor = MakeUnique<FGeometrySelectionEditor>();
FGeometrySelectionHitQueryConfig HitQueryConfig = GetCurrentSelectionQueryConfig();
bool bEnableTopologyFilter = (HitQueryConfig.TopologyType == EGeometryTopologyType::Polygroup && HitQueryConfig.ElementType != EGeometryElementType::Vertex);
SelectionTarget->SelectionEditor->Initialize(&SelectionTarget->Selection, HitQueryConfig, bEnableTopologyFilter);
if (SelectionTarget->Selector->SupportsSleep())
{
TargetCache.Add(TargetIdentifier, SelectionTarget);
}
return SelectionTarget;
}
void UGeometrySelectionManager::ResetTargetCache()
{
// SleepOrShutdownTarget may modify TargetCache
TArray<TSharedPtr<FGeometrySelectionTarget>> ToShutdown;
TargetCache.GenerateValueArray(ToShutdown);
for (TSharedPtr<FGeometrySelectionTarget> Target : ToShutdown)
{
SleepOrShutdownTarget(Target, true);
}
TargetCache.Reset();
}
bool UGeometrySelectionManager::RayHitTest(
const FRay3d& WorldRay,
FInputRayHit& HitResultOut )
{
HitResultOut = FInputRayHit();
if (ActiveTargetReferences.Num() == 0)
{
return false;
}
IGeometrySelector::FWorldRayQueryInfo RayQueryInfo;
RayQueryInfo.WorldRay = WorldRay;
IToolsContextQueriesAPI* QueryAPI = this->ToolsContext->ToolManager->GetContextQueriesAPI();
QueryAPI->GetCurrentViewState(RayQueryInfo.CameraState);
// currently only going to support one object, not sure how to support more yet...
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
FGeometrySelectionHitQueryConfig HitQueryConfig = GetCurrentSelectionQueryConfig();
bool bHit = Target->Selector->RayHitTest(RayQueryInfo, HitQueryConfig, HitResultOut);
if (bHit)
{
HitResultOut.HitOwner = ActiveTargetReferences[0].Get();
HitResultOut.HitObject = (ActiveTargetReferences[0]->TargetIdentifier.TargetType == FGeometryIdentifier::ETargetType::PrimitiveComponent) ?
ActiveTargetReferences[0]->TargetIdentifier.TargetObject : nullptr;
}
// currently only going to support one object, not sure how to support more yet...
return bHit;
}
void UGeometrySelectionManager::ClearSelection(bool bSaveSelectionBeforeClear)
{
if (!HasSelection())
{
return;
}
if (bSaveSelectionBeforeClear)
{
SaveCurrentSelection();
}
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("ClearSelection", "Clear Selection"));
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
FGeometrySelectionDelta ClearDelta;
Target->SelectionEditor->ClearSelection(ClearDelta);
if (ClearDelta.IsEmpty() == false)
{
TUniquePtr<FGeometrySelectionDeltaChange> ClearChange = MakeUnique<FGeometrySelectionDeltaChange>();
ClearChange->Identifier = Target->TargetIdentifier;
ClearChange->Delta = MoveTemp(ClearDelta);
GetTransactionsAPI()->AppendChange(this, MoveTemp(ClearChange), LOCTEXT("ClearSelection", "Clear Selection"));
}
}
GetTransactionsAPI()->EndUndoTransaction();
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
void UGeometrySelectionManager::UpdateSelectionViaRaycast(
const FRay3d& WorldRay,
const FGeometrySelectionUpdateConfig& UpdateConfig,
FGeometrySelectionUpdateResult& ResultOut
)
{
ResultOut.bSelectionModified = false;
if (ActiveTargetReferences.Num() == 0)
{
return;
}
// currently only going to support one object, not sure how to support more yet...
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
IGeometrySelector::FWorldRayQueryInfo RayQueryInfo;
RayQueryInfo.WorldRay = WorldRay;
IToolsContextQueriesAPI* QueryAPI = this->ToolsContext->ToolManager->GetContextQueriesAPI();
QueryAPI->GetCurrentViewState(RayQueryInfo.CameraState);
Target->Selector->UpdateSelectionViaRaycast(
RayQueryInfo, *Target->SelectionEditor, UpdateConfig, ResultOut
);
if (ResultOut.bSelectionModified)
{
TUniquePtr<FGeometrySelectionDeltaChange> DeltaChange = MakeUnique<FGeometrySelectionDeltaChange>();
DeltaChange->Identifier = Target->TargetIdentifier;
DeltaChange->Delta = ResultOut.SelectionDelta;
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("UpdateSelectionViaRaycast", "Change Selection"));
GetTransactionsAPI()->AppendChange(this, MoveTemp(DeltaChange), LOCTEXT("UpdateSelectionViaRaycast", "Change Selection"));
GetTransactionsAPI()->EndUndoTransaction();
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
else if (ResultOut.bSelectionMissed && UpdateConfig.ChangeType == EGeometrySelectionChangeType::Replace)
{
ClearSelection();
}
}
void UGeometrySelectionManager::UpdateSelectionViaConvex(
const FConvexVolume& ConvexVolume,
const FGeometrySelectionUpdateConfig& UpdateConfig,
FGeometrySelectionUpdateResult& ResultOut )
{
ResultOut.bSelectionModified = false;
if (ActiveTargetReferences.Num() == 0)
{
return;
}
// currently only going to support one object, not sure how to support more yet...
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
IGeometrySelector::FWorldShapeQueryInfo ShapeQueryInfo;
ShapeQueryInfo.Convex = ConvexVolume;
IToolsContextQueriesAPI* QueryAPI = this->ToolsContext->ToolManager->GetContextQueriesAPI();
QueryAPI->GetCurrentViewState(ShapeQueryInfo.CameraState);
Target->Selector->UpdateSelectionViaShape(
ShapeQueryInfo, *Target->SelectionEditor, UpdateConfig, ResultOut );
if (ResultOut.bSelectionModified)
{
TUniquePtr<FGeometrySelectionDeltaChange> DeltaChange = MakeUnique<FGeometrySelectionDeltaChange>();
DeltaChange->Identifier = Target->TargetIdentifier;
DeltaChange->Delta = ResultOut.SelectionDelta;
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("UpdateSelectionViaConvex", "Change Selection"));
GetTransactionsAPI()->AppendChange(this, MoveTemp(DeltaChange), LOCTEXT("UpdateSelectionViaConvex", "Change Selection"));
GetTransactionsAPI()->EndUndoTransaction();
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
else if (ResultOut.bSelectionMissed && UpdateConfig.ChangeType == EGeometrySelectionChangeType::Replace)
{
ClearSelection();
}
}
bool UGeometrySelectionManager::CanBeginTrackedSelectionChange() const
{
return ActiveTargetReferences.Num() > 0 && bInTrackedSelectionChange == false;
}
bool UGeometrySelectionManager::BeginTrackedSelectionChange(FGeometrySelectionUpdateConfig UpdateConfig, bool bClearOnBegin)
{
if (ensureMsgf(CanBeginTrackedSelectionChange(), TEXT("Cannot begin Selection Change - validate CanBeginTrackedSelectionChange() before calling BeginTrackedSelectionChange()")) == false)
{
return false;
}
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("ChangeSelection", "Change Selection"));
bInTrackedSelectionChange = true;
// currently only going to support one object, not sure how to support more yet...
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
ActiveTrackedUpdateConfig = UpdateConfig;
bSelectionModifiedDuringTrackedChange = false;
// if we are doing a Replace selection, we want to clear on initialization...
InitialTrackedDelta = FGeometrySelectionDelta();
if (bClearOnBegin)
{
Target->SelectionEditor->ClearSelection(InitialTrackedDelta);
bSelectionModifiedDuringTrackedChange = true;
}
ActiveTrackedSelection = Target->Selection;
ActiveTrackedDelta = FGeometrySelectionDelta();
if (bClearOnBegin && InitialTrackedDelta.IsEmpty() == false)
{
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
return true;
}
void UGeometrySelectionManager::AccumulateSelectionUpdate_Raycast(
const FRay3d& WorldRay,
FGeometrySelectionUpdateResult& ResultOut )
{
if (!ensure(bInTrackedSelectionChange)) return;
// currently only going to support one object, not sure how to support more yet...
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
IGeometrySelector::FWorldRayQueryInfo RayQueryInfo;
RayQueryInfo.WorldRay = WorldRay;
IToolsContextQueriesAPI* QueryAPI = this->ToolsContext->ToolManager->GetContextQueriesAPI();
QueryAPI->GetCurrentViewState(RayQueryInfo.CameraState);
Target->Selector->UpdateSelectionViaRaycast(
RayQueryInfo, *Target->SelectionEditor, ActiveTrackedUpdateConfig, ResultOut );
if (ResultOut.bSelectionModified)
{
bSelectionModifiedDuringTrackedChange = true;
ActiveTrackedDelta.Added.Append( ResultOut.SelectionDelta.Added );
ActiveTrackedDelta.Removed.Append( ResultOut.SelectionDelta.Removed );
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
}
void UGeometrySelectionManager::EndTrackedSelectionChange()
{
if ( ensure(bInTrackedSelectionChange) )
{
if (bSelectionModifiedDuringTrackedChange)
{
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
if (InitialTrackedDelta.IsEmpty() == false)
{
TUniquePtr<FGeometrySelectionDeltaChange> InitialDeltaChange = MakeUnique<FGeometrySelectionDeltaChange>();
InitialDeltaChange->Identifier = Target->TargetIdentifier;
InitialDeltaChange->Delta = MoveTemp(InitialTrackedDelta);
GetTransactionsAPI()->AppendChange(this, MoveTemp(InitialDeltaChange), LOCTEXT("ChangeSelection", "Change Selection"));
}
if (ActiveTrackedDelta.IsEmpty() == false)
{
TUniquePtr<FGeometrySelectionDeltaChange> AccumDeltaChange = MakeUnique<FGeometrySelectionDeltaChange>();
AccumDeltaChange->Identifier = Target->TargetIdentifier;
AccumDeltaChange->Delta = MoveTemp(ActiveTrackedDelta);
GetTransactionsAPI()->AppendChange(this, MoveTemp(AccumDeltaChange), LOCTEXT("ChangeSelection", "Change Selection"));
}
}
GetTransactionsAPI()->EndUndoTransaction();
bInTrackedSelectionChange = false;
}
}
bool UGeometrySelectionManager::SetSelectionForComponent(UPrimitiveComponent* Component, const FGeometrySelection& NewSelection)
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->TargetIdentifier.TargetObject == Component)
{
FGeometrySelection InitialSelection = Target->Selection;
FGeometrySelectionDelta AfterDelta;
Target->Selector->UpdateSelectionFromSelection(
NewSelection, true, *Target->SelectionEditor,
FGeometrySelectionUpdateConfig{EGeometrySelectionChangeType::Replace}, &AfterDelta);
if ( AfterDelta.IsEmpty() == false )
{
TUniquePtr<FGeometrySelectionReplaceChange> NewSelectionChange = MakeUnique<FGeometrySelectionReplaceChange>();
NewSelectionChange->Identifier = Target->TargetIdentifier; //Target->Selector->GetIdentifier();
NewSelectionChange->After = Target->Selection;
NewSelectionChange->Before = InitialSelection;
GetTransactionsAPI()->AppendChange(this, MoveTemp(NewSelectionChange), LOCTEXT("NewSelection", "New Selection"));
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
return true;
}
}
return false;
}
void UGeometrySelectionManager::SaveCurrentSelection()
{
SavedSelection.Reset();
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
SavedSelection.Targets.Add(Target->TargetIdentifier.TargetObject);
SavedSelection.Selections.Add(Target->Selection);
}
}
bool UGeometrySelectionManager::RestoreSavedSelection()
{
#if WITH_EDITORONLY_DATA
// Cannot update the selection if we're already in a transaction (can happen e.g. when we undo out of a tool)
if (GIsTransacting)
{
DiscardSavedSelection();
return false;
}
#endif
check(SavedSelection.Targets.Num() == SavedSelection.Selections.Num());
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("RestoreSelection", "Restore Selection"));
bool bSuccess = true;
for (int32 TargetIdx = 0; TargetIdx < SavedSelection.Targets.Num(); ++TargetIdx)
{
if (!SavedSelection.Targets[TargetIdx].IsValid())
{
bSuccess = false;
continue;
}
const FGeometrySelection& NewSelection = SavedSelection.Selections[TargetIdx];
bool bFound = false;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (SavedSelection.Targets[TargetIdx] == Target->TargetIdentifier.TargetObject)
{
FGeometrySelection InitialSelection = Target->Selection;
FGeometrySelectionDelta AfterDelta;
Target->Selector->UpdateSelectionFromSelection(
NewSelection, true, *Target->SelectionEditor,
FGeometrySelectionUpdateConfig{ EGeometrySelectionChangeType::Replace }, &AfterDelta);
if (AfterDelta.IsEmpty() == false)
{
TUniquePtr<FGeometrySelectionReplaceChange> NewSelectionChange = MakeUnique<FGeometrySelectionReplaceChange>();
NewSelectionChange->Identifier = Target->TargetIdentifier;
NewSelectionChange->After = Target->Selection;
NewSelectionChange->Before = InitialSelection;
GetTransactionsAPI()->AppendChange(this, MoveTemp(NewSelectionChange), LOCTEXT("RestoreSelection", "Restore Selection"));
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
bFound = true;
break;
}
}
if (!bFound)
{
bSuccess = false;
}
}
GetTransactionsAPI()->EndUndoTransaction();
DiscardSavedSelection();
return bSuccess;
}
void UGeometrySelectionManager::DiscardSavedSelection()
{
SavedSelection.Empty();
}
bool UGeometrySelectionManager::HasSavedSelection()
{
return !SavedSelection.Selections.IsEmpty();
}
bool UGeometrySelectionManager::UpdateSelectionPreviewViaRaycast(
const FRay3d& WorldRay )
{
if (ActiveTargetReferences.Num() == 0)
{
return false;
}
// currently only going to support one object, not sure how to support more yet...
FGeometrySelectionTarget* Target = ActiveTargetReferences[0].Get();
IGeometrySelector::FWorldRayQueryInfo RayQueryInfo;
RayQueryInfo.WorldRay = WorldRay;
IToolsContextQueriesAPI* QueryAPI = this->ToolsContext->ToolManager->GetContextQueriesAPI();
QueryAPI->GetCurrentViewState(RayQueryInfo.CameraState);
FGeometrySelectionPreview NewPreview( *(Target->SelectionEditor) );
Target->Selector->GetSelectionPreviewForRaycast( RayQueryInfo, NewPreview );
if ( ! UE::Geometry::AreSelectionsIdentical(NewPreview.PreviewSelection, ActivePreviewSelection) )
{
ActivePreviewSelection = MoveTemp(NewPreview.PreviewSelection);
// Initialize [Un]SelectedActivePreviewSelection(s) so that they are of the correct Topology and Geometry type, then clear them
SelectedActivePreviewSelection = MoveTemp(NewPreview.PreviewSelection);
UnselectedActivePreviewSelection = MoveTemp(NewPreview.PreviewSelection);
SelectedActivePreviewSelection.Reset();
UnselectedActivePreviewSelection.Reset();
if (MeshTopologyMode == EMeshTopologyMode::Polygroup)
{
// Get all polygroup IDs in current preview selection
TSet<uint32> SelectedGroupIDs;
for (const uint64 ID : Target->Selection.Selection)
{
SelectedGroupIDs.Add(FGeoSelectionID(ID).TopologyID);
}
// Get GroupID of active preview selection (hovered items)
for (const uint64 ID : ActivePreviewSelection.Selection)
{
const uint32 TopoID = FGeoSelectionID(ID).TopologyID;
// add to selection according to if an element with the GroupID is already selected
if (SelectedGroupIDs.Contains(TopoID))
{
SelectedActivePreviewSelection.Selection.Add(ID);
}
else
{
UnselectedActivePreviewSelection.Selection.Add(ID);
}
}
}
// Triangle Topology mode is more straightforward
else if (MeshTopologyMode == EMeshTopologyMode::Triangle)
{
for (const uint64 ID : ActivePreviewSelection.Selection)
{
if (Target->Selection.Selection.Contains(ID))
{
SelectedActivePreviewSelection.Selection.Add(ID);
}
else
{
UnselectedActivePreviewSelection.Selection.Add(ID);
}
}
}
CachedSelectedPreviewRenderElements.Reset();
CachedUnselectedPreviewRenderElements.Reset();
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::PreviewCachesDirty;
RemoveSets(HoverOverSelectedParams.Identifiers);
RemoveSets(HoverOverUnselectedParams.Identifiers);
}
return (ActivePreviewSelection.IsEmpty() == false);
}
void UGeometrySelectionManager::ClearSelectionPreview()
{
ClearActivePreview();
}
bool UGeometrySelectionManager::GetSelectionBounds(FGeometrySelectionBounds& BoundsOut) const
{
BoundsOut = FGeometrySelectionBounds();
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
Target->Selector->AccumulateSelectionBounds(Target->Selection, BoundsOut, true);
}
return (BoundsOut.WorldBounds.IsEmpty() == false);
}
void UGeometrySelectionManager::GetSelectionWorldFrame(FFrame3d& SelectionFrame) const
{
SelectionFrame = FFrame3d();
if (HasSelection())
{
// only handling this case for now
//if (ActiveTargetReferences.Num() == 1)
TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[0];
Target->Selector->GetSelectionFrame(Target->Selection, SelectionFrame, true);
}
}
void UGeometrySelectionManager::GetTargetWorldFrame(FFrame3d& SelectionFrame) const
{
SelectionFrame = FFrame3d();
if (HasSelection())
{
// only handling one target for now
//if (ActiveTargetReferences.Num() == 1)
TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[0];
Target->Selector->GetTargetFrame(Target->Selection, SelectionFrame);
}
}
bool UGeometrySelectionManager::HasSelectionForComponent(UPrimitiveComponent* Component) const
{
if (HasSelection())
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->TargetIdentifier.TargetObject == Component)
{
return Target->Selection.IsEmpty();
}
}
}
return false;
}
bool UGeometrySelectionManager::GetSelectionForComponent(UPrimitiveComponent* Component, FGeometrySelection& SelectionOut) const
{
if (HasSelection())
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->TargetIdentifier.TargetObject == Component)
{
SelectionOut = Target->Selection;
return ! Target->Selection.IsEmpty();
}
}
}
return false;
}
bool UGeometrySelectionManager::BeginTransformation()
{
if (!ensure(IsInActiveTransformation() == false))
{
return false;
}
if (HasSelection() == false)
{
return false;
}
bool bHaveTransformers = false;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
IGeometrySelectionTransformer* Transformer = Target->Selector->InitializeTransformation(Target->Selection);
if (Transformer != nullptr)
{
Transformer->BeginTransform(Target->Selection);
ActiveTransformations.Add(Transformer);
bHaveTransformers = true;
}
else
{
ActiveTransformations.Add(nullptr);
}
}
if (!bHaveTransformers)
{
ActiveTransformations.Reset();
return false;
}
return true;
}
void UGeometrySelectionManager::UpdateTransformation(
TFunctionRef<FVector3d(int32 VertexID, const FVector3d&, const FTransform&)> PositionTransformFunc )
{
if ( ! ensure(IsInActiveTransformation()) ) return;
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
if (ActiveTransformations[k] != nullptr)
{
ActiveTransformations[k]->UpdateTransform(PositionTransformFunc);
}
}
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::SelectionCachesDirty;
}
void UGeometrySelectionManager::EndTransformation()
{
if ( ! ensure(IsInActiveTransformation()) ) return;
GetTransactionsAPI()->BeginUndoTransaction(LOCTEXT("EndTransformation", "Transform Selection"));
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
if (ActiveTransformations[k] != nullptr)
{
ActiveTransformations[k]->EndTransform(GetTransactionsAPI());
ActiveTargetReferences[k]->Selector->ShutdownTransformation(ActiveTransformations[k]);
}
}
ActiveTransformations.Reset();
GetTransactionsAPI()->EndUndoTransaction();
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::SelectionCachesDirty;
}
bool UGeometrySelectionManager::CanExecuteSelectionCommand(UGeometrySelectionEditCommand* Command)
{
if (SelectionArguments == nullptr)
{
SelectionArguments = NewObject<UGeometrySelectionEditCommandArguments>();
}
bool bCanExecute = true;
bool bHaveSelections = false;
ProcessActiveSelections([&](FGeometrySelectionHandle Handle)
{
SelectionArguments->SelectionHandle = Handle;
SelectionArguments->SetTransactionsAPI(TransactionsAPI);
bCanExecute = bCanExecute && Command->CanExecuteCommand(SelectionArguments);
bHaveSelections = true;
});
return ( bHaveSelections || (Command->AllowEmptySelection() && HasActiveTargets() && MeshTopologyMode != EMeshTopologyMode::None ) ) && bCanExecute;
}
void UGeometrySelectionManager::ExecuteSelectionCommand(UGeometrySelectionEditCommand* Command)
{
if (SelectionArguments == nullptr)
{
SelectionArguments = NewObject<UGeometrySelectionEditCommandArguments>();
}
// open transaction to wrap the entire set of Commands and selection changes
FText CommandText = Command->GetCommandShortString();
GetTransactionsAPI()->BeginUndoTransaction(CommandText);
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->Selection.IsEmpty() && Command->AllowEmptySelection() == false) continue;
// TODO: can use Command->IsModifySelectionCommand() to check if this is a command that only affects selection
// and not geometry. In that case we can skip the intermediate clear-selection and emit a single change.
// When initially executing the command, we do not clear the selection, because we pass it to the command.
// However, when we later *undo* any changes emitted by the command, we need to restore the selection aftewards.
// So we emit a clearing change here, so that undo un-clears.
// When we later Redo, it is also necessary to Clear as otherwise an invalid Selection might hang around.
// Note that this must happen *before* the Command. The Command will not be re-executed, only its emitted Changes,
// so it will not be holding onto the active Selection on Redo later
// (if that becomes necessary, this sequence of changes will need to become more complicated....)
TUniquePtr<FGeometrySelectionReplaceChange> ClearChange = MakeUnique<FGeometrySelectionReplaceChange>();
ClearChange->Identifier = Target->TargetIdentifier; //Target->Selector->GetIdentifier();
ClearChange->Before = Target->Selection;
ClearChange->After.InitializeTypes(ClearChange->Before);
GetTransactionsAPI()->AppendChange(this, MoveTemp(ClearChange), LOCTEXT("ClearSelection", "Clear Selection"));
// q: we could clear the selection here, and pass the Handle a copy. Perhaps safer?
UInteractiveCommandResult* ResultPtr = nullptr;
SelectionArguments->SelectionHandle = FGeometrySelectionHandle{ Target->Selector->GetIdentifier(), &Target->Selection, Target->Selector.Get() };
SelectionArguments->ElementType = SelectionElementType;
SelectionArguments->TopologyMode = GetSelectionTopologyType();
SelectionArguments->SetTransactionsAPI(TransactionsAPI);
Command->ExecuteCommand(SelectionArguments, &ResultPtr);
// actually clear selection after executing command.
FGeometrySelectionDelta ClearDelta;
Target->SelectionEditor->ClearSelection(ClearDelta);
// if selection returned a result, and it was a non-empty selection, select it
if ( UGeometrySelectionEditCommandResult* SelectionResult = Cast<UGeometrySelectionEditCommandResult>(ResultPtr) )
{
if (SelectionResult->OutputSelection.IsEmpty() == false )
{
FGeometrySelectionDelta AfterDelta;
Target->Selector->UpdateSelectionFromSelection(
SelectionResult->OutputSelection, true, *Target->SelectionEditor,
FGeometrySelectionUpdateConfig{EGeometrySelectionChangeType::Add}, &AfterDelta);
if (Target->Selection.IsEmpty() == false)
{
TUniquePtr<FGeometrySelectionReplaceChange> NewSelectionChange = MakeUnique<FGeometrySelectionReplaceChange>();
NewSelectionChange->Identifier = Target->TargetIdentifier; //Target->Selector->GetIdentifier();
NewSelectionChange->After = Target->Selection;
NewSelectionChange->Before.InitializeTypes(Target->Selection);
GetTransactionsAPI()->AppendChange(this, MoveTemp(NewSelectionChange), LOCTEXT("NewSelection", "New Selection"));
}
}
}
}
GetTransactionsAPI()->EndUndoTransaction();
// assume marking render caches (except selectable) is true for now
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
void UGeometrySelectionManager::ProcessActiveSelections(TFunctionRef<void(FGeometrySelectionHandle)> ProcessFunc)
{
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
if (Target->Selection.IsEmpty() == false)
{
FGeometrySelectionHandle Handle;
Handle.Selection = & Target->Selection;
Handle.Identifier = Target->Selector->GetIdentifier();
Handle.Selector = Target->Selector.Get();
ProcessFunc(Handle);
}
}
}
void UGeometrySelectionManager::ApplyChange(IGeometrySelectionChange* Change)
{
// We should not get here because selection changes should have been expired.
if ( ! ensure(HasBeenShutDown() == false) )
{
return;
}
FGeometryIdentifier Identifer = Change->GetIdentifier();
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
if (ActiveTargetReferences[k]->TargetIdentifier == Identifer)
{
FGeometrySelectionDelta ApplyDelta;
Change->ApplyChange( ActiveTargetReferences[k]->SelectionEditor.Get(), ApplyDelta);
if (ApplyDelta.IsEmpty() == false)
{
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
break;
}
}
}
void UGeometrySelectionManager::RevertChange(IGeometrySelectionChange* Change)
{
// We should not get here because selection changes should have been expired.
if ( ! ensure(HasBeenShutDown() == false) )
{
return;
}
FGeometryIdentifier Identifer = Change->GetIdentifier();
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
if (ActiveTargetReferences[k]->TargetIdentifier == Identifer)
{
FGeometrySelectionDelta RevertDelta;
Change->RevertChange( ActiveTargetReferences[k]->SelectionEditor.Get(), RevertDelta);
if (RevertDelta.IsEmpty() == false)
{
MarkRenderCachesDirty(false);
OnSelectionModified.Broadcast();
}
break;
}
}
}
void UGeometrySelectionManager::OnTargetGeometryModified(IGeometrySelector* Selector)
{
CachedSelectableRenderElements.Reset();
CachedSelectableRenderElements.SetNum(ActiveTargetReferences.Num());
RemoveSets(UnselectedParams.Identifiers);
MarkRenderCachesDirty();
ClearActivePreview();
}
void UGeometrySelectionManager::UpdateSelectionRenderCacheOnTargetChange()
{
CachedSelectionRenderElements.Reset();
CachedSelectableRenderElements.Reset();
CachedSelectionRenderElements.SetNum(ActiveTargetReferences.Num());
CachedSelectableRenderElements.SetNum(ActiveTargetReferences.Num());
RemoveSets(SelectedParams.Identifiers);
RemoveSets(UnselectedParams.Identifiers);
MarkRenderCachesDirty();
ClearActivePreview();
}
void UGeometrySelectionManager::MarkRenderCachesDirty(bool bMarkSelectableDirty)
{
if (bMarkSelectableDirty)
{
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::UnselectedCachesDirty;
}
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::PreviewCachesDirty | EEnumerateRenderCachesDirtyFlags::SelectionCachesDirty;
}
void UGeometrySelectionManager::RebuildSelectionRenderCaches()
{
RebuildSelectionRenderCache();
RebuildSelectableRenderCache();
RebuildPreviewRenderCache();
}
void UGeometrySelectionManager::ClearActivePreview()
{
ActivePreviewSelection.Reset();
SelectedActivePreviewSelection.Reset();
UnselectedActivePreviewSelection.Reset();
CachedSelectedPreviewRenderElements.Reset();
CachedUnselectedPreviewRenderElements.Reset();
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::PreviewCachesDirty;
RemoveSets(HoverOverSelectedParams.Identifiers);
RemoveSets(HoverOverUnselectedParams.Identifiers);
}
void UGeometrySelectionManager::RemoveAllSets() const
{
if(!ensure(PreviewGeometry))
{
return;
}
PreviewGeometry->RemoveAllLineSets();
PreviewGeometry->RemoveAllPointSets();
PreviewGeometry->RemoveAllTriangleSets();
}
void UGeometrySelectionManager::RemoveSets(const TArrayView<FString>& SetIdentifiers) const
{
if (PreviewGeometry)
{
PreviewGeometry->RemoveLineSet(SetIdentifiers[1]);
PreviewGeometry->RemovePointSet(SetIdentifiers[0]);
PreviewGeometry->RemoveTriangleSet(SetIdentifiers[2]);
}
}
void UGeometrySelectionManager::CreateOrUpdateAllSets(const FGeometrySelectionElements& Elements, const FMeshElementSelectionParams SelectionParams) const
{
UWorld* World = GetWorld();
if (World && !PreviewGeometry->ParentActor)
{
PreviewGeometry->CreateInWorld(World, FTransform::Identity);
}
if(SelectionElementType == EGeometryElementType::Edge || SelectionElementType == EGeometryElementType::Face)
{
PreviewGeometry->CreateOrUpdateLineSet(SelectionParams.Identifiers[1], Elements.Segments.Num(), [&](int32 j, TArray<FRenderableLine>& LinesOut)
{
const FSegment3d Seg = Elements.Segments[j];
LinesOut.Add(FRenderableLine(Seg.StartPoint(), Seg.EndPoint(), SelectionParams.Color, SelectionParams.LineThickness, SelectionParams.DepthBias));
}, 1);
PreviewGeometry->CreateOrUpdateTriangleSet(SelectionParams.Identifiers[2], Elements.Triangles.Num(), [&](int32 k, TArray<FRenderableTriangle>& TrianglesOut)
{
const FTriangle3d Triangle = Elements.Triangles[k];
const FVector3d Normal = Triangle.Normal();
FRenderableTriangleVertex A(Triangle.V[0], FVector2D(0,0), Normal, SelectionParams.Color);
FRenderableTriangleVertex B(Triangle.V[1], FVector2D(1,0), Normal, SelectionParams.Color);
FRenderableTriangleVertex C(Triangle.V[2], FVector2D(1,1), Normal, SelectionParams.Color);
TrianglesOut.Add(FRenderableTriangle(SelectionParams.SelectionFillColor, A, B, C));
},1 );
}
else if (SelectionElementType == EGeometryElementType::Vertex)
{
PreviewGeometry->CreateOrUpdatePointSet(SelectionParams.Identifiers[0], Elements.Points.Num(), [&](int32 k, TArray<FRenderablePoint>& PointsOut)
{
const FVector3d Point = Elements.Points[k];
PointsOut.Add(FRenderablePoint(Point, SelectionParams.Color, SelectionParams.PointSize, SelectionParams.DepthBias));
});
}
}
void UGeometrySelectionManager::RebuildSelectionRenderCache()
{
if ((RenderCachesDirtyFlags & EEnumerateRenderCachesDirtyFlags::SelectionCachesDirty) == EEnumerateRenderCachesDirtyFlags::None)
{
return;
}
check(ActiveTargetReferences.Num() == CachedSelectionRenderElements.Num());
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[k];
FGeometrySelectionElements& SelectionElements = CachedSelectionRenderElements[k];
SelectionElements.Reset();
Target->Selector->AccumulateSelectionElements(Target->Selection, SelectionElements, true, EEnumerateSelectionMapping::Default);
CreateOrUpdateAllSets(SelectionElements, SelectedParams);
}
RenderCachesDirtyFlags &= ~EEnumerateRenderCachesDirtyFlags::SelectionCachesDirty;
}
void UGeometrySelectionManager::RebuildSelectableRenderCache()
{
if ((RenderCachesDirtyFlags & EEnumerateRenderCachesDirtyFlags::UnselectedCachesDirty) == EEnumerateRenderCachesDirtyFlags::None || MeshTopologyMode == EMeshTopologyMode::None)
{
return;
}
check(ActiveTargetReferences.Num() == CachedSelectionRenderElements.Num());
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[k];
FGeometrySelectionElements& AllElements = CachedSelectableRenderElements[k];
AllElements.Reset();
Target->Selector->AccumulateElementsFromPredicate(AllElements, true, false, MeshTopologyMode == EMeshTopologyMode::Polygroup, [Target, this](EGeometryElementType Type, FGeoSelectionID ID)
{
// Selectable faces are not displayed directly, just implicitly via displayed edges.
if (Type == EGeometryElementType::Face)
{
return false;
}
return true;
});
CreateOrUpdateAllSets(AllElements, UnselectedParams);
}
RenderCachesDirtyFlags &= ~EEnumerateRenderCachesDirtyFlags::UnselectedCachesDirty;
}
void UGeometrySelectionManager::RebuildPreviewRenderCache()
{
if ((RenderCachesDirtyFlags & EEnumerateRenderCachesDirtyFlags::PreviewCachesDirty) == EEnumerateRenderCachesDirtyFlags::None || ActiveTargetReferences.Num() == 0)
{
return;
}
// defaults to off/false; when off, will show outlines and fill color when hovering. When on/true, will only show outlines
const TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[0];
if (ActivePreviewSelection.IsEmpty() == false)
{
EEnumerateSelectionMapping MappingFlags = EEnumerateSelectionMapping::Default | EEnumerateSelectionMapping::FacesToEdges;
if (CVarGeometrySelectionManager_FullSelectionHoverHighlights.GetValueOnGameThread() == 0)
{
// Unset FacesToFaces flag if full hover highlights are disabled
MappingFlags &= ~EEnumerateSelectionMapping::FacesToFaces;
}
Target->Selector->AccumulateSelectionElements(SelectedActivePreviewSelection, CachedSelectedPreviewRenderElements, true, MappingFlags);
Target->Selector->AccumulateSelectionElements(UnselectedActivePreviewSelection, CachedUnselectedPreviewRenderElements, true, MappingFlags);
CreateOrUpdateAllSets(CachedSelectedPreviewRenderElements, HoverOverSelectedParams);
CreateOrUpdateAllSets(CachedUnselectedPreviewRenderElements, HoverOverUnselectedParams);
}
RenderCachesDirtyFlags &= ~EEnumerateRenderCachesDirtyFlags::PreviewCachesDirty;
}
void UGeometrySelectionManager::DebugPrintSelection()
{
if (ActiveTargetReferences.Num() == 0)
{
UE_LOG(LogGeometry, Warning, TEXT("[SelectionManager] No Active Selection"));
return;
}
int32 NumSelected = 0;
for (TSharedPtr<FGeometrySelectionTarget> Target : ActiveTargetReferences)
{
NumSelected += Target->Selection.Num();
}
UE_LOG(LogGeometry, Warning, TEXT("[SelectionManager] %d selected items in %d active targets"), NumSelected, ActiveTargetReferences.Num());
}
void UGeometrySelectionManager::DebugRender(IToolsContextRenderAPI* RenderAPI)
{
// disable selection during xform to avoid overhead
if (IsInActiveTransformation())
{
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
{
if (ActiveTransformations[k] != nullptr)
{
ActiveTransformations[k]->PreviewRender(RenderAPI);
}
}
return;
}
if (!this->ToolsContext->ToolManager->HasAnyActiveTool())
{
RebuildSelectionRenderCaches();
}
else
{
// disables PreviewGeometry when in a tool
RemoveAllSets();
RenderCachesDirtyFlags |= EEnumerateRenderCachesDirtyFlags::UnselectedCachesDirty;
}
}
void UGeometrySelectionManager::SetSelectionColors(const FLinearColor UnselectedCol, const FLinearColor HoverOverSelectedCol, const FLinearColor HoverOverUnselectedCol, const FLinearColor GeometrySelectedCol)
{
UnselectedParams.Color = UnselectedCol.ToFColor(true);
HoverOverSelectedParams.Color = HoverOverSelectedCol.ToFColor(true);
HoverOverUnselectedParams.Color = HoverOverUnselectedCol.ToFColor(true);
SelectedParams.Color = GeometrySelectedCol.ToFColor(true);
// on initial set up of Materials used for selection colors material (typically when entering modeling mode)
if (UnselectedParams.SelectionFillColor == nullptr) // if one is null, they all will be
{
auto SetMaterial = [this] (FMeshElementSelectionParams& Params, const FLinearColor Color)
{
Params.SelectionFillColor =
ToolSetupUtil::GetCustomTwoSidedDepthOffsetMaterial(this->ToolsContext->ToolManager, Color, Params.DepthBias, Color.A);
};
SetMaterial(UnselectedParams, UnselectedCol);
SetMaterial(HoverOverUnselectedParams, HoverOverUnselectedCol);
SetMaterial(SelectedParams, GeometrySelectedCol);
// to avoid flickering, this version of GetCustomTwoSidedDeptOffsetMaterial (without opacity parameter) must be called for HoverOverSelected
HoverOverSelectedParams.SelectionFillColor =
ToolSetupUtil::GetCustomTwoSidedDepthOffsetMaterial(this->ToolsContext->ToolManager, HoverOverSelectedCol, HoverOverSelectedParams.DepthBias);
}
// setting colors after initialization of Materials (typically using color customization in editor preferences)
else
{
auto SetColorAndOpacity = [](const FMeshElementSelectionParams& Params, const FLinearColor Color)
{
Params.SelectionFillColor->SetScalarParameterValue("Opacity", Color.A); // no effect for HoverOverSelected
Params.SelectionFillColor->SetVectorParameterValue("Color", Color);
};
SetColorAndOpacity(UnselectedParams, UnselectedCol);
SetColorAndOpacity(HoverOverSelectedParams, HoverOverSelectedCol);
SetColorAndOpacity(HoverOverUnselectedParams, HoverOverUnselectedCol);
SetColorAndOpacity(SelectedParams, GeometrySelectedCol);
}
// ensures that when color is changed in Editor Preferences, colors are immediately updated in the UI
auto UpdateAllSetsColor = [this](const FMeshElementSelectionParams& Params)
{
UPointSetComponent* PointSet = PreviewGeometry->FindPointSet(Params.Identifiers[0]);
ULineSetComponent* LineSet = PreviewGeometry->FindLineSet(Params.Identifiers[1]);
UTriangleSetComponent* TriSet = PreviewGeometry->FindTriangleSet(Params.Identifiers[2]);
if (PointSet) { PointSet->SetAllPointsColor(Params.Color); }
if (LineSet) { LineSet->SetAllLinesColor(Params.Color); }
if (TriSet) { TriSet->SetAllTrianglesColor(Params.Color); }
};
UpdateAllSetsColor(UnselectedParams);
UpdateAllSetsColor(HoverOverSelectedParams);
UpdateAllSetsColor(HoverOverUnselectedParams);
UpdateAllSetsColor(SelectedParams);
}
void UGeometrySelectionManager::DisconnectPreviewGeometry()
{
if (PreviewGeometry)
{
RemoveAllSets();
PreviewGeometry->Disconnect();
PreviewGeometry = nullptr;
}
}
#undef LOCTEXT_NAMESPACE