You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Add DynamicMeshSelector::UpdateAfterGeometryEdit API. StaticMeshSelector implementation updates static mesh after an Edit instead of emitting MeshChange on temporary DynamicMesh. Delete and Retriangulate Commands now use this API instead of directly emitting transaction, so now these commands work properly on Static Meshes. FStaticMeshComponentSelectorFactory::CanBuildForTarget now only allows UStaticMeshComponent specifically, filtering out subclasses. This is not ideal but I don't know what else we can do for now, there are many subclasses like ISMC, SplineMeshComponent, etc, that will not work w/ the Selection system. Also now filtering out Engine assets and cooked assets. ModelingToolsEditorMode now listens for blueprint pre-compiles, and when this occurs, clears the active selection and selection targets. This is necessary because if the selection Component was part of a BP, on recompile it is re-instanced and the old pointer goes stale. Possibly can handle this better or at a more granular level, but clearing the selection is safest. This currently results in things not being undoable because the FChange transactions are on the "old" UDynamicMesh that no longer exists. UModelingToolsEditorMode::UpdateSelectionManagerOnEditorSelectionChange now does a more thorough job of inspecting the current Actor/Component selection to find Components that could work w/ the selection system. #rb lonnie.li [CL 26133442 by ryan schmidt in 5.3 branch]
1309 lines
40 KiB
C++
1309 lines
40 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Selection/GeometrySelectionManager.h"
|
|
#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 "Selections/GeometrySelectionUtil.h"
|
|
|
|
#include UE_INLINE_GENERATED_CPP_BY_NAME(GeometrySelectionManager)
|
|
|
|
static TAutoConsoleVariable<int32> CVarGeometrySelectionManager_FullSelectionHoverHighlights(
|
|
TEXT("modeling.Selection.FullHoverHighlights"),
|
|
0,
|
|
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;
|
|
}
|
|
|
|
void UGeometrySelectionManager::RegisterSelectorFactory(TUniquePtr<IGeometrySelectorFactory> Factory)
|
|
{
|
|
Factories.Add(MoveTemp(Factory));
|
|
ResetTargetCache();
|
|
}
|
|
|
|
void UGeometrySelectionManager::Shutdown()
|
|
{
|
|
OnSelectionModified.Clear();
|
|
ToolsContext = nullptr;
|
|
TransactionsAPI = nullptr;
|
|
|
|
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
|
|
{
|
|
if (FromElementType != ToElementType)
|
|
{
|
|
CastChecked<UGeometrySelectionManager>(Object)->SetSelectionElementTypeInternal(ToElementType);
|
|
}
|
|
if (FromTopologyMode != ToTopologyMode)
|
|
{
|
|
CastChecked<UGeometrySelectionManager>(Object)->SetMeshTopologyModeInternal(ToTopologyMode);
|
|
}
|
|
}
|
|
|
|
/** Reverts change to the object */
|
|
virtual void Revert(UObject* Object) override
|
|
{
|
|
if (FromElementType != ToElementType)
|
|
{
|
|
CastChecked<UGeometrySelectionManager>(Object)->SetSelectionElementTypeInternal(FromElementType);
|
|
}
|
|
if (FromTopologyMode != ToTopologyMode)
|
|
{
|
|
CastChecked<UGeometrySelectionManager>(Object)->SetMeshTopologyModeInternal(FromTopologyMode);
|
|
}
|
|
}
|
|
|
|
/** 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
EGeometryTopologyType UGeometrySelectionManager::GetSelectionTopologyType() const
|
|
{
|
|
if (MeshTopologyMode == EMeshTopologyMode::Polygroup)
|
|
{
|
|
return EGeometryTopologyType::Polygroup;
|
|
}
|
|
else
|
|
{
|
|
return EGeometryTopologyType::Triangle;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UE::Geometry::FGeometrySelectionHitQueryConfig UGeometrySelectionManager::GetCurrentSelectionQueryConfig() const
|
|
{
|
|
FGeometrySelectionHitQueryConfig HitQueryConfig;
|
|
HitQueryConfig.TopologyType = GetSelectionTopologyType();
|
|
HitQueryConfig.ElementType = GetSelectionElementType();
|
|
HitQueryConfig.bOnlyVisible = true;
|
|
return HitQueryConfig;
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
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)
|
|
{
|
|
check(HasSelection() == false);
|
|
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()
|
|
{
|
|
if (!HasSelection())
|
|
{
|
|
return;
|
|
}
|
|
|
|
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();
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
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();
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
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();
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
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)
|
|
{
|
|
bSelectionRenderCachesDirty = true;
|
|
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 );
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
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"));
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
OnSelectionModified.Broadcast();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool UGeometrySelectionManager::UpdateSelectionPreviewViaRaycast(
|
|
const FRay3d& WorldRay )
|
|
{
|
|
if (ActiveTargetReferences.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
bool bUseSimplifiedPreviewHighlight = (CVarGeometrySelectionManager_FullSelectionHoverHighlights.GetValueOnGameThread() == 0);
|
|
|
|
// 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);
|
|
CachedPreviewRenderElements.Reset();
|
|
if (ActivePreviewSelection.IsEmpty() == false)
|
|
{
|
|
Target->Selector->AccumulateSelectionElements(ActivePreviewSelection, CachedPreviewRenderElements, true, bUseSimplifiedPreviewHighlight);
|
|
}
|
|
}
|
|
|
|
return (ActivePreviewSelection.IsEmpty() == false);
|
|
}
|
|
|
|
void UGeometrySelectionManager::ClearSelectionPreview()
|
|
{
|
|
ActivePreviewSelection.Selection.Reset();
|
|
CachedPreviewRenderElements.Reset();
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
}
|
|
|
|
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();
|
|
|
|
bSelectionRenderCachesDirty = true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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->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 this is true for now
|
|
bSelectionRenderCachesDirty = true;
|
|
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)
|
|
{
|
|
bSelectionRenderCachesDirty = true;
|
|
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)
|
|
{
|
|
bSelectionRenderCachesDirty = true;
|
|
OnSelectionModified.Broadcast();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void UGeometrySelectionManager::OnTargetGeometryModified(IGeometrySelector* Selector)
|
|
{
|
|
bSelectionRenderCachesDirty = true;
|
|
|
|
ClearActivePreview();
|
|
}
|
|
|
|
|
|
void UGeometrySelectionManager::UpdateSelectionRenderCacheOnTargetChange()
|
|
{
|
|
CachedSelectionRenderElements.Reset();
|
|
CachedSelectionRenderElements.SetNum(ActiveTargetReferences.Num());
|
|
bSelectionRenderCachesDirty = true;
|
|
|
|
ClearActivePreview();
|
|
}
|
|
|
|
void UGeometrySelectionManager::RebuildSelectionRenderCaches()
|
|
{
|
|
if (bSelectionRenderCachesDirty == false)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(ActiveTargetReferences.Num() == CachedSelectionRenderElements.Num());
|
|
for (int32 k = 0; k < ActiveTargetReferences.Num(); ++k)
|
|
{
|
|
TSharedPtr<FGeometrySelectionTarget> Target = ActiveTargetReferences[k];
|
|
|
|
FGeometrySelectionElements& Elements = CachedSelectionRenderElements[k];
|
|
Elements.Reset();
|
|
|
|
Target->Selector->AccumulateSelectionElements(Target->Selection, Elements, true, false);
|
|
}
|
|
|
|
bSelectionRenderCachesDirty = false;
|
|
}
|
|
|
|
|
|
void UGeometrySelectionManager::ClearActivePreview()
|
|
{
|
|
ActivePreviewSelection.Reset();
|
|
CachedPreviewRenderElements.Reset();
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
//const UMaterialInterface* TriangleMaterial = ToolSetupUtil::GetSelectionMaterial(FLinearColor(1.0f, 0, 0, 0.5f), nullptr, 0.5f);
|
|
|
|
RebuildSelectionRenderCaches();
|
|
for ( const FGeometrySelectionElements& Elements : CachedSelectionRenderElements )
|
|
{
|
|
ToolSelectionUtil::DebugRenderGeometrySelectionElements(RenderAPI, Elements, false);
|
|
}
|
|
ToolSelectionUtil::DebugRenderGeometrySelectionElements(RenderAPI, CachedPreviewRenderElements, true);
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|