Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolsetExp/Source/MeshModelingToolsExp/Private/MeshSelectionTool.cpp
jimmy andrews 433388510b fix polygroup colors not rendering due to incorrect material in subdivide, tri select, and materials tools
#jira UE-209227
#jira UE-209211
#rb lonnie.li

[CL 32184271 by jimmy andrews in ue5-main branch]
2024-03-12 13:11:58 -04:00

1831 lines
53 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshSelectionTool.h"
#include "InteractiveToolManager.h"
#include "ToolBuilderUtil.h"
#include "Drawing/MeshDebugDrawing.h"
#include "DynamicMeshEditor.h"
#include "DynamicMesh/DynamicMeshChangeTracker.h"
#include "Changes/ToolCommandChangeSequence.h"
#include "Changes/MeshChange.h"
#include "Util/ColorConstants.h"
#include "Selections/MeshConnectedComponents.h"
#include "MeshRegionBoundaryLoops.h"
#include "DynamicMesh/MeshIndexUtil.h"
#include "ModelingObjectsCreationAPI.h"
#include "ToolSetupUtil.h"
#include "Selections/MeshConnectedComponents.h"
#include "Selections/MeshFaceSelection.h"
#include "ModelingToolTargetUtil.h"
#include "Properties/MeshStatisticsProperties.h"
#include "Drawing/MeshElementsVisualizer.h"
#include "Properties/MeshUVChannelProperties.h"
#include "PropertySets/PolygroupLayersProperties.h"
#include "Polygroups/PolygroupUtil.h"
#include "Selection/StoredMeshSelectionUtil.h"
#include "Selections/GeometrySelectionUtil.h"
#include "Algo/MaxElement.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MeshSelectionTool)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UMeshSelectionTool"
/*
* ToolBuilder
*/
UMeshSurfacePointTool* UMeshSelectionToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
UMeshSelectionTool* SelectionTool = NewObject<UMeshSelectionTool>(SceneState.ToolManager);
SelectionTool->SetWorld(SceneState.World);
return SelectionTool;
}
void UMeshSelectionToolBuilder::InitializeNewTool(UMeshSurfacePointTool* Tool, const FToolBuilderState& SceneState) const
{
UMeshSurfacePointMeshEditingToolBuilder::InitializeNewTool(Tool, SceneState);
if (UMeshSelectionTool* SelectionTool = Cast<UMeshSelectionTool>(Tool))
{
UE::Geometry::FGeometrySelection Selection;
if (UE::Geometry::GetCurrentGeometrySelectionForTarget(SceneState, SelectionTool->GetTarget(), Selection))
{
SelectionTool->SetGeometrySelection(MoveTemp(Selection));
}
}
}
/*
* Properties
*/
void UMeshSelectionToolActionPropertySet::PostAction(EMeshSelectionToolActions Action)
{
if (ParentTool.IsValid())
{
ParentTool->RequestAction(Action);
}
}
/*
* Tool
*/
UMeshSelectionTool::UMeshSelectionTool()
{
}
void UMeshSelectionTool::SetWorld(UWorld* World)
{
this->TargetWorld = World;
}
void UMeshSelectionTool::Setup()
{
UDynamicMeshBrushTool::Setup();
// hide strength and falloff
BrushProperties->bShowStrength = BrushProperties->bShowFalloff = false;
BrushProperties->RestoreProperties(this);
SelectionProps = NewObject<UMeshSelectionToolProperties>(this);
SelectionProps->RestoreProperties(this);
AddToolPropertySource(SelectionProps);
PolygroupLayerProperties = NewObject<UPolygroupLayersProperties>(this);
PolygroupLayerProperties->RestoreProperties(this);
PreviewMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh) { PolygroupLayerProperties->InitializeGroupLayers(&ReadMesh); });
PolygroupLayerProperties->WatchProperty(PolygroupLayerProperties->ActiveGroupLayer, [&](FName) { UpdateActiveGroupLayer(); bColorsUpdatePending = true; bFullMeshInvalidationPending = true; });
AddToolPropertySource(PolygroupLayerProperties);
UpdateActiveGroupLayer();
UVChannelProperties = NewObject<UMeshUVChannelProperties>(this);
UVChannelProperties->RestoreProperties(this);
PreviewMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh) { UVChannelProperties->Initialize(&ReadMesh, false); });
UVChannelProperties->ValidateSelection(true);
UVChannelProperties->WatchProperty(UVChannelProperties->UVChannel, [this](const FString& NewValue) { CacheUVIslandIDs(); bColorsUpdatePending = true; bFullMeshInvalidationPending = true; });
AddToolPropertySource(UVChannelProperties);
// we could probably calculate this on-demand but we need to do it before making any mesh changes? or update?
CacheUVIslandIDs();
AddSubclassPropertySets();
SelectionActions = NewObject<UMeshSelectionEditActions>(this);
SelectionActions->Initialize(this);
AddToolPropertySource(SelectionActions);
EditActions = CreateEditActions();
AddToolPropertySource(EditActions);
// set autocalculated tangents
PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated);
// disable shadows
PreviewMesh->SetShadowsEnabled(false);
// configure secondary render material
UMaterialInterface* SelectionMaterial = ToolSetupUtil::GetSelectionMaterial(FLinearColor(0.9f, 0.1f, 0.1f), GetToolManager());
if (SelectionMaterial != nullptr)
{
PreviewMesh->SetSecondaryRenderMaterial(SelectionMaterial);
}
// enable secondary triangle buffers
PreviewMesh->EnableSecondaryTriangleBuffers(
[this](const FDynamicMesh3* Mesh, int32 TriangleID)
{
return SelectedTriangles[TriangleID] ? true : false;
});
// enable auto-chunking of mesh into separate render buffers, so that partial updates can be done during selection paint
PreviewMesh->SetEnableRenderMeshDecomposition(true);
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
SelectedVertices = TBitArray<>(false, Mesh->MaxVertexID());
SelectedTriangles = TBitArray<>(false, Mesh->MaxTriangleID());
this->Selection = NewObject<UMeshSelectionSet>(this);
Selection->GetOnModified().AddLambda([this](USelectionSet* SelectionObj)
{
OnExternalSelectionChange();
});
// rebuild octree if mesh changes
PreviewMesh->GetOnMeshChanged().AddLambda([this]() {
bOctreeValid = false;
bFullMeshInvalidationPending = true;
bColorsUpdatePending = true;
CacheUVIslandIDs();
UpdateActiveGroupLayer();
});
SelectionProps->WatchProperty(SelectionProps->FaceColorMode,
[this](EMeshFacesColorMode NewValue)
{
bColorsUpdatePending = true; UpdateVisualization(false);
});
bColorsUpdatePending = (SelectionProps->FaceColorMode != EMeshFacesColorMode::None);
MeshElementsDisplay = NewObject<UMeshElementsVisualizer>(this);
MeshElementsDisplay->CreateInWorld(PreviewMesh->GetWorld(), PreviewMesh->GetTransform());
if (ensure(MeshElementsDisplay->Settings))
{
MeshElementsDisplay->Settings->bShowWireframe = true;
MeshElementsDisplay->Settings->bShowUVSeams = false;
MeshElementsDisplay->Settings->bShowNormalSeams = false;
MeshElementsDisplay->Settings->bShowColorSeams = false;
MeshElementsDisplay->Settings->RestoreProperties(this, TEXT("MeshSelection"));
AddToolPropertySource(MeshElementsDisplay->Settings);
}
MeshElementsDisplay->SetMeshAccessFunction([this](UMeshElementsVisualizer::ProcessDynamicMeshFunc ProcessFunc) {
PreviewMesh->ProcessMesh(ProcessFunc);
});
MeshStatisticsProperties = NewObject<UMeshStatisticsProperties>(this);
AddToolPropertySource(MeshStatisticsProperties);
// initialize our selection from input selection, if available
if (InputGeometrySelection.IsEmpty() == false)
{
TArray<int32> AddFaces;
UE::Geometry::EnumerateSelectionTriangles(InputGeometrySelection, *Mesh,
[&](int32 TriangleID) { AddFaces.Add(TriangleID); });
Selection->AddIndices(EMeshSelectionElementType::Face, AddFaces);
OnExternalSelectionChange();
}
RecalculateBrushRadius();
UpdateVisualization(true);
SetToolDisplayName(LOCTEXT("ToolName", "Select Triangles"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartMeshSelectionTool", "This Tool allows you to modify the mesh based on a triangle selection. [Q] cyles through Selection Mode. [A] cycles through Face Color modes. [ and ] change brush size, < and > grow/shrink selection."),
EToolMessageLevel::UserNotification);
}
UMeshSelectionToolActionPropertySet* UMeshSelectionTool::CreateEditActions()
{
UMeshSelectionMeshEditActions* Actions = NewObject<UMeshSelectionMeshEditActions>(this);
Actions->Initialize(this);
return Actions;
}
void UMeshSelectionTool::ApplyShutdownAction(EToolShutdownType ShutdownType)
{
if (ShutdownType == EToolShutdownType::Accept)
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("MeshSelectionToolTransactionName", "Edit Mesh"));
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (bHaveModifiedMesh )
{
FDynamicMesh3 ResultMesh(*PreviewMesh->GetMesh());
if (ResultMesh.IsCompact() == false)
{
FCompactMaps CompactMaps;
ResultMesh.CompactInPlace(&CompactMaps);
for (int32& tid : SelectedFaces)
{
tid = CompactMaps.GetTriangleMapping(tid);
}
}
UE::ToolTarget::CommitDynamicMeshUpdate(Target, ResultMesh, true);
}
if (SelectionType == EMeshSelectionElementType::Face)
{
if ( SelectedFaces.Num() > 0 )
{
FGeometrySelection OutputSelection;
for ( int32 tid : SelectedFaces )
{
OutputSelection.Selection.Add( FGeoSelectionID::MeshTriangle(tid).Encoded() );
}
UE::Geometry::SetToolOutputGeometrySelectionForTarget(this, GetTarget(), OutputSelection);
}
}
GetToolManager()->EndUndoTransaction();
}
else if (ShutdownType == EToolShutdownType::Cancel)
{
for (AActor* Spawned : SpawnedActors)
{
Spawned->Destroy();
}
}
}
void UMeshSelectionTool::OnShutdown(EToolShutdownType ShutdownType)
{
SelectionProps->SaveProperties(this);
BrushProperties->SaveProperties(this);
UVChannelProperties->SaveProperties(this);
PolygroupLayerProperties->SaveProperties(this);
if (ensure(MeshElementsDisplay->Settings))
{
MeshElementsDisplay->Settings->SaveProperties(this, TEXT("MeshSelection"));
}
MeshElementsDisplay->Disconnect();
ApplyShutdownAction(ShutdownType);
}
void UMeshSelectionTool::SetGeometrySelection(UE::Geometry::FGeometrySelection&& SelectionIn)
{
InputGeometrySelection = SelectionIn;
}
void UMeshSelectionTool::RegisterActions(FInteractiveToolActionSet& ActionSet)
{
UDynamicMeshBrushTool::RegisterActions(ActionSet);
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 50,
TEXT("TriSelectIncreaseSize"),
LOCTEXT("TriSelectIncreaseSize", "Increase Size"),
LOCTEXT("TriSelectIncreaseSizeTooltip", "Increase Brush Size"),
EModifierKey::None, EKeys::D,
[this]() { IncreaseBrushSizeAction(); });
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 51,
TEXT("TriSelectDecreaseSize"),
LOCTEXT("TriSelectDecreaseSize", "Decrease Size"),
LOCTEXT("TriSelectDecreaseSizeTooltip", "Decrease Brush Size"),
EModifierKey::None, EKeys::S,
[this]() { DecreaseBrushSizeAction(); });
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 1,
TEXT("MeshSelectionToolDelete"),
LOCTEXT("MeshSelectionToolDelete", "Delete"),
LOCTEXT("MeshSelectionToolDeleteTooltip", "Delete Selected Elements"),
EModifierKey::None, EKeys::Delete,
[this]() { DeleteSelectedTriangles(); });
#if WITH_EDITOR // enum HasMetaData() is not available at runtime
ActionSet.RegisterAction(this, (int32)EMeshSelectionToolActions::CycleSelectionMode,
TEXT("CycleSelectionMode"),
LOCTEXT("CycleSelectionMode", "Cycle Selection Mode"),
LOCTEXT("CycleSelectionModeTooltip", "Cycle through selection modes"),
EModifierKey::None, EKeys::Q,
[this]() {
const UEnum* SelectionModeEnum = StaticEnum<EMeshSelectionToolPrimaryMode>();
check(SelectionModeEnum);
int32 NumEnum = SelectionModeEnum->NumEnums() - 1;
do {
SelectionProps->SelectionMode = (EMeshSelectionToolPrimaryMode)(((int32)SelectionProps->SelectionMode + 1) % NumEnum);
} while (SelectionModeEnum->HasMetaData(TEXT("Hidden"), (int32)SelectionProps->SelectionMode));
}
);
ActionSet.RegisterAction(this, (int32)EMeshSelectionToolActions::CycleViewMode,
TEXT("CycleViewMode"),
LOCTEXT("CycleViewMode", "Cycle View Mode"),
LOCTEXT("CycleViewModeTooltip", "Cycle through face coloring modes"),
EModifierKey::None, EKeys::A,
[this]() {
const UEnum* ViewModeEnum = StaticEnum<EMeshFacesColorMode>();
check(ViewModeEnum);
int32 NumEnum = ViewModeEnum->NumEnums() - 1;
do {
SelectionProps->FaceColorMode = (EMeshFacesColorMode)(((int32)SelectionProps->FaceColorMode + 1) % NumEnum);
} while (ViewModeEnum->HasMetaData(TEXT("Hidden"), (int32)SelectionProps->FaceColorMode));
}
);
#endif
ActionSet.RegisterAction(this, (int32)EMeshSelectionToolActions::ShrinkSelection,
TEXT("ShrinkSelection"),
LOCTEXT("ShrinkSelection", "Shrink Selection"),
LOCTEXT("ShrinkSelectionTooltip", "Shrink selection"),
EModifierKey::Shift, EKeys::Comma,
[this]() { GrowShrinkSelection(false); });
ActionSet.RegisterAction(this, (int32)EMeshSelectionToolActions::GrowSelection,
TEXT("GrowSelection"),
LOCTEXT("GrowSelection", "Grow Selection"),
LOCTEXT("GrowSelectionTooltip", "Grow selection"),
EModifierKey::Shift, EKeys::Period,
[this]() { GrowShrinkSelection(true); });
ActionSet.RegisterAction(this, (int32)EMeshSelectionToolActions::OptimizeSelection,
TEXT("OptimizeSelection"),
LOCTEXT("OptimizeSelection", "Optimize Selection"),
LOCTEXT("OptimizeSelectionTooltip", "Optimize selection"),
EModifierKey::None, EKeys::O,
[this]() { OptimizeSelection(); });
ActionSet.RegisterAction(this, (int32)EMeshSelectionToolActions::SmoothBoundary,
TEXT("SmoothBoundary"),
LOCTEXT("SmoothBoundary", "Smooth Boundary"),
LOCTEXT("SmoothBoundaryTooltip", "Smooth Boundary"),
EModifierKey::None, EKeys::B,
[this]() { SmoothSelectionBoundary(); });
}
void UMeshSelectionTool::OnExternalSelectionChange()
{
int32 NumTriangles = SelectedTriangles.Num();
TSet<int32> AllModifiedTriangles;
for (int32 k = 0; k < NumTriangles; ++k)
{
if (SelectedTriangles[k])
{
AllModifiedTriangles.Add(k);
}
}
SelectedVertices.SetRange(0, SelectedVertices.Num(), false);
SelectedTriangles.SetRange(0, SelectedTriangles.Num(), false);
if (SelectionType == EMeshSelectionElementType::Vertex)
{
ensure(false); // not supported
for (int VertIdx : Selection->Vertices)
{
SelectedVertices[VertIdx] = true;
}
}
else if (SelectionType == EMeshSelectionElementType::Face)
{
for (int FaceIdx : Selection->Faces)
{
SelectedTriangles[FaceIdx] = true;
AllModifiedTriangles.Add(FaceIdx);
}
// todo: more efficient for rendering update to compute the delta set here...
OnRegionHighlightUpdated(AllModifiedTriangles);
}
}
bool UMeshSelectionTool::HitTest(const FRay& Ray, FHitResult& OutHit)
{
bool bHit = UDynamicMeshBrushTool::HitTest(Ray, OutHit);
if (bHit && SelectionProps->bHitBackFaces == false)
{
const FDynamicMesh3* SourceMesh = PreviewMesh->GetPreviewDynamicMesh();
FVector3d Normal, Centroid;
double Area;
SourceMesh->GetTriInfo(OutHit.FaceIndex, Normal, Area, Centroid);
FViewCameraState StateOut;
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(StateOut);
FTransform3d LocalToWorld = UE::ToolTarget::GetLocalToWorldTransform(Target);
FVector3d LocalEyePosition(LocalToWorld.InverseTransformPosition(StateOut.Position));
if (Normal.Dot((Centroid - LocalEyePosition)) > 0)
{
bHit = false;
}
}
return bHit;
}
void UMeshSelectionTool::OnBeginDrag(const FRay& WorldRay)
{
UDynamicMeshBrushTool::OnBeginDrag(WorldRay);
PreviewBrushROI.Reset();
if (IsInBrushStroke())
{
bInRemoveStroke = GetShiftToggle();
BeginChange(bInRemoveStroke == false);
StartStamp = UBaseBrushTool::LastBrushStamp;
LastStamp = StartStamp;
bStampPending = true;
LongTransactions.Open(LOCTEXT("MeshSelectionChange", "Mesh Selection"), GetToolManager());
}
}
void UMeshSelectionTool::OnUpdateDrag(const FRay& WorldRay)
{
UDynamicMeshBrushTool::OnUpdateDrag(WorldRay);
if (IsInBrushStroke())
{
LastStamp = UBaseBrushTool::LastBrushStamp;
bStampPending = true;
}
}
TUniquePtr<FDynamicMeshOctree3>& UMeshSelectionTool::GetOctree()
{
if (bOctreeValid == false)
{
Octree = MakeUnique<FDynamicMeshOctree3>();
Octree->Initialize(PreviewMesh->GetPreviewDynamicMesh());
bOctreeValid = true;
}
return Octree;
}
void UMeshSelectionTool::CalculateVertexROI(const FBrushStampData& Stamp, TArray<int>& VertexROI)
{
FTransform3d Transform = UE::ToolTarget::GetLocalToWorldTransform(Target);
FVector3d StampPosLocal = Transform.InverseTransformPosition((FVector3d)Stamp.WorldPosition);
// TODO: need dynamic vertex hash table!
float Radius = GetCurrentBrushRadiusLocal();
float RadiusSqr = Radius * Radius;
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
for (int VertIdx : Mesh->VertexIndicesItr())
{
FVector3d Position = Mesh->GetVertex(VertIdx);
if ((Position - StampPosLocal).SquaredLength() < RadiusSqr)
{
VertexROI.Add(VertIdx);
}
}
}
void UMeshSelectionTool::CalculateTriangleROI(const FBrushStampData& Stamp, TArray<int>& TriangleROI)
{
FTransform3d Transform = UE::ToolTarget::GetLocalToWorldTransform(Target);
FVector3d StampPosLocal = Transform.InverseTransformPosition((FVector3d)Stamp.WorldPosition);
// always select first triangle
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
float Radius = GetCurrentBrushRadiusLocal();
float RadiusSqr = Radius * Radius;
if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::VolumetricBrush)
{
if (Mesh->IsTriangle(Stamp.HitResult.FaceIndex))
{
TriangleROI.Add(Stamp.HitResult.FaceIndex);
}
FAxisAlignedBox3d Bounds(StampPosLocal- Radius*FVector3d::One(), StampPosLocal+ Radius*FVector3d::One());
TemporaryBuffer.Reset();
GetOctree()->RangeQuery(Bounds, TemporaryBuffer);
for (int32 TriIdx : TemporaryBuffer)
{
FVector3d Position = Mesh->GetTriCentroid(TriIdx);
if ((Position - StampPosLocal).SquaredLength() < RadiusSqr)
{
TriangleROI.Add(TriIdx);
}
}
}
else
{
TArray<int32> StartROI;
StartROI.Add(Stamp.HitResult.FaceIndex);
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, TriangleROI, &TemporaryBuffer, &TemporarySet,
[Mesh, RadiusSqr, StampPosLocal](int t1, int t2) { return (Mesh->GetTriCentroid(t2) - StampPosLocal).SquaredLength() < RadiusSqr; });
}
}
static void UpdateList(TArray<int>& List, int Value, bool bAdd)
{
if (bAdd)
{
List.Add(Value);
}
else
{
List.RemoveSwap(Value);
}
}
void UMeshSelectionTool::ApplyStamp(const FBrushStampData& Stamp)
{
IndexBuf.Reset();
bool bDesiredValue = bInRemoveStroke ? false : true;
if (SelectionType == EMeshSelectionElementType::Face)
{
CalculateTriangleROI(Stamp, IndexBuf);
UpdateFaceSelection(Stamp, IndexBuf);
}
else
{
CalculateVertexROI(Stamp, IndexBuf);
for (int VertIdx : IndexBuf)
{
if (SelectedVertices[VertIdx] != bDesiredValue)
{
SelectedVertices[VertIdx] = bDesiredValue;
UpdateList(Selection->Vertices, VertIdx, bDesiredValue);
if (ActiveSelectionChange != nullptr)
{
ActiveSelectionChange->Add(VertIdx);
}
}
}
ensure(false); // not supported yet
}
}
void UMeshSelectionTool::UpdateFaceSelection(const FBrushStampData& Stamp, const TArray<int>& TriangleROI)
{
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
const TArray<int>* UseROI = &TriangleROI;
TArray<int> LocalROI;
if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::AllConnected)
{
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, TriangleROI, LocalROI, &TemporaryBuffer, &TemporarySet);
UseROI = &LocalROI;
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::AllInGroup)
{
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, TriangleROI, LocalROI, &TemporaryBuffer, &TemporarySet,
[&](int t1, int t2) { return ActiveGroupSet->GetTriangleGroup(t1) == ActiveGroupSet->GetTriangleGroup(t2); } );
UseROI = &LocalROI;
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::ByMaterial)
{
const FDynamicMeshMaterialAttribute* MaterialIDs = Mesh->Attributes()->GetMaterialID();
// If material IDs are missing, this logic will be skipped and selection will fall back to brush selection
if (MaterialIDs)
{
TArray<int32> StartROI;
StartROI.Add(Stamp.HitResult.FaceIndex);
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, LocalROI, &TemporaryBuffer, &TemporarySet,
[Mesh, MaterialIDs](int t1, int t2) { return MaterialIDs->GetValue(t1) == MaterialIDs->GetValue(t2); });
UseROI = &LocalROI;
}
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::ByMaterialAll)
{
const FDynamicMeshMaterialAttribute* MaterialIDs = Mesh->Attributes()->GetMaterialID();
// If material IDs are missing, this logic will be skipped and selection will fall back to brush selection
if (MaterialIDs)
{
int32 HitMaterialID = MaterialIDs->GetValue(Stamp.HitResult.FaceIndex);
for (int32 TID : Mesh->TriangleIndicesItr())
{
if (MaterialIDs->GetValue(TID) == HitMaterialID)
{
LocalROI.Add(TID);
}
}
UseROI = &LocalROI;
}
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::ByUVIsland)
{
TArray<int32> StartROI;
StartROI.Add(Stamp.HitResult.FaceIndex);
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, LocalROI, &TemporaryBuffer, &TemporarySet,
[&](int t1, int t2) { return TriangleToUVIsland[t1] == TriangleToUVIsland[t2]; });
UseROI = &LocalROI;
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::AllWithinAngle)
{
TArray<int32> StartROI;
StartROI.Add(Stamp.HitResult.FaceIndex);
FVector3d StartNormal = Mesh->GetTriNormal(StartROI[0]);
int AngleTol = SelectionProps->AngleTolerance;
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, LocalROI, &TemporaryBuffer, &TemporarySet,
[Mesh, AngleTol, StartNormal](int t1, int t2) { return UE::Geometry::AngleD(Mesh->GetTriNormal(t2), StartNormal) < AngleTol; });
UseROI = &LocalROI;
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::AngleFiltered)
{
TSet<int32> BrushROI(TriangleROI);
TArray<int32> StartROI;
StartROI.Add(Stamp.HitResult.FaceIndex);
FVector3d StartNormal = Mesh->GetTriNormal(StartROI[0]);
int AngleTol = SelectionProps->AngleTolerance;
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, LocalROI, &TemporaryBuffer, &TemporarySet,
[Mesh, AngleTol, StartNormal, &BrushROI](int t1, int t2) { return BrushROI.Contains(t2) && UE::Geometry::AngleD(Mesh->GetTriNormal(t2), StartNormal) < AngleTol; });
UseROI = &LocalROI;
}
else if (SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::Visible)
{
FViewCameraState StateOut;
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(StateOut);
FTransform3d LocalToWorld = UE::ToolTarget::GetLocalToWorldTransform(Target);
FVector3d LocalEyePosition(LocalToWorld.InverseTransformPosition(StateOut.Position));
for (int tid : TriangleROI)
{
FVector3d Centroid = Mesh->GetTriCentroid(tid);
int HitTID = GetOctree()->FindNearestHitObject(FRay3d(LocalEyePosition, UE::Geometry::Normalized(Centroid - LocalEyePosition)));
if (HitTID == tid)
{
LocalROI.Add(HitTID);
}
}
UseROI = &LocalROI;
}
// separate paths for add and remove because we can't efficiently remove from Selection->Faces TArray
if (bInRemoveStroke == false)
{
for (int TriIdx : *UseROI)
{
if (SelectedTriangles[TriIdx] == false)
{
SelectedTriangles[TriIdx] = true;
Selection->Faces.Add(TriIdx);
if (ActiveSelectionChange != nullptr)
{
ActiveSelectionChange->Add(TriIdx);
}
}
}
}
else
{
bool bModified = false;
TArray<int32> CurSelection = Selection->Faces;
for (int TriIdx : *UseROI)
{
if (SelectedTriangles[TriIdx] == true)
{
SelectedTriangles[TriIdx] = false;
bModified = true;
if (ActiveSelectionChange != nullptr)
{
ActiveSelectionChange->Add(TriIdx);
}
}
}
// rebuild selection
if (bModified)
{
Selection->Faces.Reset();
for (int32 tid : CurSelection)
{
if (SelectedTriangles[tid])
{
Selection->Faces.Add(tid);
}
}
}
}
OnRegionHighlightUpdated(*UseROI);
}
void UMeshSelectionTool::OnEndDrag(const FRay& Ray)
{
UDynamicMeshBrushTool::OnEndDrag(Ray);
bInRemoveStroke = false;
bStampPending = false;
// close change record
TUniquePtr<FToolCommandChange> Change = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(Change), LOCTEXT("MeshSelectionChange", "Mesh Selection"));
LongTransactions.Close(GetToolManager());
}
bool UMeshSelectionTool::OnUpdateHover(const FInputDeviceRay& DevicePos)
{
UDynamicMeshBrushTool::OnUpdateHover(DevicePos);
// todo get rid of this redundant hit test!
FHitResult OutHit;
if ( UDynamicMeshBrushTool::HitTest(DevicePos.WorldRay, OutHit) )
{
PreviewBrushROI.Reset();
if (SelectionType == EMeshSelectionElementType::Face)
{
CalculateTriangleROI(LastBrushStamp, PreviewBrushROI);
}
else
{
CalculateVertexROI(LastBrushStamp, PreviewBrushROI);
}
}
return true;
}
FBox UMeshSelectionTool::GetWorldSpaceFocusBox()
{
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() > 0)
{
FAxisAlignedBox3d Bounds = FAxisAlignedBox3d::Empty();
const FDynamicMesh3* Mesh = PreviewMesh->GetMesh();
FTransform3d Transform(PreviewMesh->GetTransform());
for (int32 tid : SelectedFaces)
{
FIndex3i Tri = Mesh->GetTriangle(tid);
for (int32 j = 0; j < 3; ++j)
{
Bounds.Contain(Transform.TransformPosition(Mesh->GetVertex(Tri[j])));
}
}
if (Bounds.MaxDim() > FMathf::ZeroTolerance)
{
return (FBox)Bounds;
}
}
return PreviewMesh->GetActor()->GetComponentsBoundingBox();
}
bool UMeshSelectionTool::SupportsNestedCancelCommand()
{
return true;
}
bool UMeshSelectionTool::CanCurrentlyNestedCancel()
{
return Selection && (Selection->Faces.Num() > 0);
}
bool UMeshSelectionTool::ExecuteNestedCancelCommand()
{
if (CanCurrentlyNestedCancel())
{
// Only actually clear if there's no in-progress change; this just means escape does nothing in the middle of a mouse drag
// (We could make esc undo the active change and cancel the drag in this case; not clear if that is worthwhile. In most similar situations, escape just exits the tool.)
if (!ActiveSelectionChange)
{
ClearSelection();
}
return true;
}
return false;
}
void UMeshSelectionTool::OnRegionHighlightUpdated()
{
PreviewMesh->NotifyDeferredEditCompleted(UPreviewMesh::ERenderUpdateMode::FastUpdate, EMeshRenderAttributeFlags::SecondaryIndexBuffers, false);
}
void UMeshSelectionTool::OnRegionHighlightUpdated(const TArray<int32>& Triangles)
{
PreviewMesh->NotifyRegionDeferredEditCompleted(Triangles, EMeshRenderAttributeFlags::SecondaryIndexBuffers);
}
void UMeshSelectionTool::OnRegionHighlightUpdated(const TSet<int32>& Triangles)
{
PreviewMesh->NotifyRegionDeferredEditCompleted(Triangles, EMeshRenderAttributeFlags::SecondaryIndexBuffers);
}
void UMeshSelectionTool::UpdateVisualization(bool bSelectionModified)
{
check(SelectionType == EMeshSelectionElementType::Face); // only face selection supported so far
SetToolPropertySourceEnabled(UVChannelProperties,
SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::ByUVIsland ||
SelectionProps->FaceColorMode == EMeshFacesColorMode::ByUVIsland);
if (bFullMeshInvalidationPending)
{
PreviewMesh->ProcessMesh([&](const FDynamicMesh3& ReadMesh)
{
MeshStatisticsProperties->Update(ReadMesh);
});
MeshElementsDisplay->NotifyMeshChanged();
bFullMeshInvalidationPending = false;
}
// force an update of renderbuffers
if (bSelectionModified)
{
PreviewMesh->NotifyDeferredEditCompleted(UPreviewMesh::ERenderUpdateMode::FullUpdate, EMeshRenderAttributeFlags::AllVertexAttribs, true);
}
if (bColorsUpdatePending)
{
if (SelectionProps->FaceColorMode != EMeshFacesColorMode::None)
{
PreviewMesh->SetOverrideRenderMaterial(ToolSetupUtil::GetVertexColorMaterial(GetToolManager()));
PreviewMesh->SetTriangleColorFunction([this](const FDynamicMesh3* Mesh, int TriangleID)
{
return GetCurrentFaceColor(Mesh, TriangleID);
},
UPreviewMesh::ERenderUpdateMode::FastUpdate);
}
else
{
PreviewMesh->ClearOverrideRenderMaterial();
PreviewMesh->ClearTriangleColorFunction(UPreviewMesh::ERenderUpdateMode::FastUpdate);
}
bColorsUpdatePending = false;
}
}
FColor UMeshSelectionTool::GetCurrentFaceColor(const FDynamicMesh3* Mesh, int TriangleID)
{
if (SelectionProps->FaceColorMode == EMeshFacesColorMode::ByGroup)
{
return LinearColors::SelectFColor(ActiveGroupSet->GetTriangleGroup(TriangleID));
}
else if (SelectionProps->FaceColorMode == EMeshFacesColorMode::ByMaterialID)
{
return LinearColors::SelectFColor( Mesh->Attributes()->GetMaterialID()->GetValue(TriangleID) );
}
else if (SelectionProps->FaceColorMode == EMeshFacesColorMode::ByUVIsland)
{
return LinearColors::SelectFColor(TriangleToUVIsland[TriangleID]);
}
return FColor::Red;
}
void UMeshSelectionTool::CacheUVIslandIDs()
{
const FDynamicMesh3* Mesh = PreviewMesh->GetMesh();
FMeshConnectedComponents Components(Mesh);
TriangleToUVIsland.SetNum(Mesh->MaxTriangleID());
int32 ActiveUVLayer = UVChannelProperties->GetSelectedChannelIndex(true);
const FDynamicMeshUVOverlay* UV = Mesh->Attributes()->GetUVLayer(ActiveUVLayer);
Components.FindConnectedTriangles([&](int32 TriIdx0, int32 TriIdx1)
{
return UV->AreTrianglesConnected(TriIdx0, TriIdx1);
});
int32 NumComponents = Components.Num();
for (int32 ci = 0; ci < NumComponents; ++ci)
{
for (int32 TriIdx : Components.GetComponent(ci).Indices)
{
TriangleToUVIsland[TriIdx] = ci;
}
}
}
void UMeshSelectionTool::UpdateActiveGroupLayer()
{
// todo: need this to be const
const FDynamicMesh3* SourceMesh = PreviewMesh->GetMesh();
if (PolygroupLayerProperties->HasSelectedPolygroup() == false)
{
ActiveGroupSet = MakeShared<UE::Geometry::FPolygroupSet, ESPMode::ThreadSafe>(SourceMesh);
}
else
{
FName SelectedName = PolygroupLayerProperties->ActiveGroupLayer;
const FDynamicMeshPolygroupAttribute* FoundAttrib = UE::Geometry::FindPolygroupLayerByName(*SourceMesh, SelectedName);
ensureMsgf(FoundAttrib, TEXT("Selected Attribute Not Found! Falling back to Default group layer."));
ActiveGroupSet = MakeShared<UE::Geometry::FPolygroupSet, ESPMode::ThreadSafe>(SourceMesh, FoundAttrib);
}
}
void UMeshSelectionTool::Render(IToolsContextRenderAPI* RenderAPI)
{
UDynamicMeshBrushTool::Render(RenderAPI);
FTransform WorldTransform = (FTransform)UE::ToolTarget::GetLocalToWorldTransform(Target);
const FDynamicMesh3* Mesh = PreviewMesh->GetMesh();
if (SelectionProps->bShowPoints)
{
float PDIScale = RenderAPI->GetCameraState().GetPDIScalingFactor();
if (SelectionType == EMeshSelectionElementType::Vertex)
{
MeshDebugDraw::DrawVertices(Mesh, Selection->Vertices,
12.0f*PDIScale, FColor::Orange, RenderAPI->GetPrimitiveDrawInterface(), WorldTransform);
MeshDebugDraw::DrawVertices(Mesh, PreviewBrushROI,
8.0f*PDIScale, FColor(40, 200, 40), RenderAPI->GetPrimitiveDrawInterface(), WorldTransform);
}
else
{
MeshDebugDraw::DrawTriCentroids(Mesh, PreviewBrushROI,
4.0f*PDIScale, FColor(40, 200, 40), RenderAPI->GetPrimitiveDrawInterface(), WorldTransform);
}
}
}
void UMeshSelectionTool::OnTick(float DeltaTime)
{
if (bStampPending)
{
ApplyStamp(LastStamp);
bStampPending = false;
}
if (bHavePendingAction)
{
ApplyAction(PendingAction);
bHavePendingAction = false;
PendingAction = EMeshSelectionToolActions::NoAction;
}
if (bFullMeshInvalidationPending)
{
UpdateVisualization(false);
}
MeshElementsDisplay->OnTick(DeltaTime);
}
void UMeshSelectionTool::BeginChange(bool bAdding)
{
check(ActiveSelectionChange == nullptr);
ActiveSelectionChange = new FMeshSelectionChangeBuilder(SelectionType, bAdding);
}
void UMeshSelectionTool::CancelChange()
{
if (ActiveSelectionChange != nullptr)
{
delete ActiveSelectionChange;
ActiveSelectionChange = nullptr;
}
}
TUniquePtr<FToolCommandChange> UMeshSelectionTool::EndChange()
{
check(ActiveSelectionChange);
if (ActiveSelectionChange != nullptr)
{
TUniquePtr<FMeshSelectionChange> Result = MoveTemp(ActiveSelectionChange->Change);
delete ActiveSelectionChange;
ActiveSelectionChange = nullptr;
return Result;
}
return TUniquePtr<FMeshSelectionChange>();
}
void UMeshSelectionTool::RequestAction(EMeshSelectionToolActions ActionType)
{
if (bHavePendingAction)
{
return;
}
PendingAction = ActionType;
bHavePendingAction = true;
}
void UMeshSelectionTool::ApplyAction(EMeshSelectionToolActions ActionType)
{
switch (ActionType)
{
case EMeshSelectionToolActions::SelectAll:
SelectAll();
break;
case EMeshSelectionToolActions::SelectAllByMaterial:
SelectAllByMaterial();
break;
case EMeshSelectionToolActions::ClearSelection:
ClearSelection();
break;
case EMeshSelectionToolActions::InvertSelection:
InvertSelection();
break;
case EMeshSelectionToolActions::GrowSelection:
GrowShrinkSelection(true);
break;
case EMeshSelectionToolActions::ShrinkSelection:
GrowShrinkSelection(false);
break;
case EMeshSelectionToolActions::SelectLargestComponentByArea:
SelectLargestComponent(true);
break;
case EMeshSelectionToolActions::SelectLargestComponentByTriCount:
SelectLargestComponent(false);
break;
case EMeshSelectionToolActions::OptimizeSelection:
OptimizeSelection();
break;
case EMeshSelectionToolActions::ExpandToConnected:
ExpandToConnected();
break;
case EMeshSelectionToolActions::DeleteSelected:
DeleteSelectedTriangles();
break;
case EMeshSelectionToolActions::DisconnectSelected:
DisconnectSelectedTriangles();
break;
case EMeshSelectionToolActions::SmoothBoundary:
SmoothSelectionBoundary();
break;
case EMeshSelectionToolActions::SeparateSelected:
SeparateSelectedTriangles(true);
break;
case EMeshSelectionToolActions::DuplicateSelected:
SeparateSelectedTriangles(false);
break;
case EMeshSelectionToolActions::FlipSelected:
FlipSelectedTriangles();
break;
case EMeshSelectionToolActions::CreateGroup:
AssignNewGroupToSelectedTriangles();
break;
}
}
void UMeshSelectionTool::SelectAll()
{
BeginChange(true);
TArray<int32> AddFaces;
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
for (int tid : Mesh->TriangleIndicesItr())
{
if (SelectedTriangles[tid] == false)
{
AddFaces.Add(tid);
}
}
ActiveSelectionChange->Add(AddFaces);
Selection->AddIndices(EMeshSelectionElementType::Face, AddFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("SelectAll", "Select All"));
OnExternalSelectionChange();
}
void UMeshSelectionTool::SelectAllByMaterial()
{
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
if (Selection->Faces.Num() == 0|| Mesh->HasAttributes() == false || Mesh->Attributes()->HasMaterialID() == false)
{
SelectAll();
return;
}
BeginChange(true);
TSet<int32> SelectedMaterialIDs;
const FDynamicMeshMaterialAttribute* MaterialIDs = Mesh->Attributes()->GetMaterialID();
for (int tid : Mesh->TriangleIndicesItr())
{
if ( SelectedTriangles[tid] )
{
SelectedMaterialIDs.Add( MaterialIDs->GetValue(tid) );
}
}
TArray<int32> AddFaces;
for (int tid : Mesh->TriangleIndicesItr())
{
if ( SelectedTriangles[tid] == false && SelectedMaterialIDs.Contains(MaterialIDs->GetValue(tid)) )
{
AddFaces.Add(tid);
}
}
ActiveSelectionChange->Add(AddFaces);
Selection->AddIndices(EMeshSelectionElementType::Face, AddFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("ExpandToMaterial", "Select Materials"));
OnExternalSelectionChange();
}
void UMeshSelectionTool::ClearSelection()
{
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
BeginChange(false);
ActiveSelectionChange->Add(SelectedFaces);
Selection->RemoveIndices(EMeshSelectionElementType::Face, SelectedFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("ClearSelection", "Clear Selection"));
OnExternalSelectionChange();
}
void UMeshSelectionTool::InvertSelection()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
TArray<int32> InvertedFaces;
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
for (int tid : Mesh->TriangleIndicesItr())
{
if (SelectedTriangles[tid] == false)
{
InvertedFaces.Add(tid);
}
}
GetToolManager()->BeginUndoTransaction(LOCTEXT("InvertSelection", "Invert Selection"));
// clear current selection
BeginChange(false);
ActiveSelectionChange->Add(SelectedFaces);
Selection->RemoveIndices(EMeshSelectionElementType::Face, SelectedFaces);
TUniquePtr<FToolCommandChange> ClearChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(ClearChange), LOCTEXT("InvertSelection", "Invert Selection"));
// add inverted selection
BeginChange(true);
ActiveSelectionChange->Add(InvertedFaces);
Selection->AddIndices(EMeshSelectionElementType::Face, InvertedFaces);
TUniquePtr<FToolCommandChange> AddChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(AddChange), LOCTEXT("InvertSelection", "Invert Selection"));
GetToolManager()->EndUndoTransaction();
OnExternalSelectionChange();
}
void UMeshSelectionTool::GrowShrinkSelection(bool bGrow)
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
TArray<int32> Vertices;
UE::Geometry::TriangleToVertexIDs(Mesh, SelectedFaces, Vertices);
TSet<int32> ChangeFaces;
for (int vid : Vertices)
{
int OutCount = 0;
for (int tid : Mesh->VtxTrianglesItr(vid))
{
if (SelectedTriangles[tid] == false)
{
OutCount++;
}
}
if (OutCount == 0)
{
continue;
}
for (int tid : Mesh->VtxTrianglesItr(vid))
{
if ( (bGrow && SelectedTriangles[tid] == false) || (bGrow == false && SelectedTriangles[tid]) )
{
ChangeFaces.Add(tid);
}
}
}
if( SelectionProps->SelectionMode == EMeshSelectionToolPrimaryMode::AllInGroup )
{
TSet<int32> AdjacentFaces{ChangeFaces};
TSet<int32> AdjacentGroups{};
ChangeFaces.Empty();
for ( int32 TID : AdjacentFaces )
{
AdjacentGroups.Add(ActiveGroupSet->GetTriangleGroup(TID));
}
for ( int32 TID : Mesh->TriangleIndicesItr() )
{
if ( AdjacentGroups.Contains(ActiveGroupSet->GetTriangleGroup(TID)) )
{
ChangeFaces.Add(TID);
}
}
}
if (ChangeFaces.Num() == 0)
{
return;
}
BeginChange(bGrow);
ActiveSelectionChange->Add(ChangeFaces);
if (bGrow)
{
Selection->AddIndices(EMeshSelectionElementType::Face, ChangeFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("GrowSelection", "Grow Selection"));
}
else
{
Selection->RemoveIndices(EMeshSelectionElementType::Face, ChangeFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("ShrinkSelection", "Shrink Selection"));
}
OnExternalSelectionChange();
}
void UMeshSelectionTool::ExpandToConnected()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
TArray<int32> Queue(SelectedFaces);
TSet<int32> AddFaces;
while (Queue.Num() > 0)
{
int32 CurTri = Queue.Pop(EAllowShrinking::No);
FIndex3i NbrTris = Mesh->GetTriNeighbourTris(CurTri);
for (int j = 0; j < 3; ++j)
{
int32 tid = NbrTris[j];
if (tid != FDynamicMesh3::InvalidID && SelectedTriangles[tid] == false && AddFaces.Contains(tid) == false)
{
AddFaces.Add(tid);
Queue.Add(tid);
}
}
}
if (AddFaces.Num() == 0)
{
return;
}
BeginChange(true);
ActiveSelectionChange->Add(AddFaces);
Selection->AddIndices(EMeshSelectionElementType::Face, AddFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("ExpandToConnected", "Expand Selection"));
OnExternalSelectionChange();
}
void UMeshSelectionTool::SelectLargestComponent(bool bWeightByArea)
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
// each component gets its own group id
FMeshConnectedComponents Components(Mesh);
Components.FindConnectedTriangles(SelectedFaces);
if (Components.Num() == 0) // no triangles?
{
ClearSelection();
return;
}
int BestComponent = 0;
FMeshConnectedComponents::FComponent* MaxComponent = Algo::MaxElementBy(Components, [bWeightByArea, &Mesh](const FMeshConnectedComponents::FComponent& Component)
{
if (bWeightByArea)
{
double AreaSum = 0;
for (int TID : Component.Indices)
{
AreaSum += Mesh->GetTriArea(TID);
}
return AreaSum;
}
else
{
return (double)Component.Indices.Num();
}
});
BeginChange(false);
for (FMeshConnectedComponents::FComponent& Component : Components)
{
if (&Component != MaxComponent)
{
ActiveSelectionChange->Add(Component.Indices);
Selection->RemoveIndices(EMeshSelectionElementType::Face, Component.Indices);
}
}
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(SelectionChange), LOCTEXT("SelectLargestComponentByArea", "Select Largest Component By Area"));
OnExternalSelectionChange();
}
void UMeshSelectionTool::OptimizeSelection()
{
check(SelectionType == EMeshSelectionElementType::Face);
if (Selection->Faces.Num() == 0)
{
return;
}
const FDynamicMesh3* Mesh = PreviewMesh->GetPreviewDynamicMesh();
FMeshFaceSelection FaceSelection(Mesh);
TSet<int> OriginalSelection(Selection->Faces);
FaceSelection.Select(Selection->Faces);
FaceSelection.LocalOptimize(true);
GetToolManager()->BeginUndoTransaction(LOCTEXT("OptimizeSelection", "Optimize Selection"));
// remove faces from the current selection that are not in the optimized one
BeginChange(false);
for (int32 FaceSelIdx = Selection->Faces.Num() - 1; FaceSelIdx >= 0; FaceSelIdx--)
{
int32 TID = Selection->Faces[FaceSelIdx];
if (!FaceSelection.IsSelected(TID))
{
Selection->Faces.RemoveAtSwap(FaceSelIdx, EAllowShrinking::No);
ActiveSelectionChange->Add(TID);
}
}
Selection->NotifySelectionSetModified();
TUniquePtr<FToolCommandChange> DeselectChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(DeselectChange), LOCTEXT("OptimizeSelection", "Optimize Selection"));
// add faces from the optimized selection to the current selection, if they were not in the original
BeginChange(true);
Selection->Faces.Reserve(FaceSelection.Num());
for (int32 TID : FaceSelection.AsSet())
{
if (!OriginalSelection.Contains(TID))
{
ActiveSelectionChange->Add(TID);
Selection->Faces.Add(TID);
}
}
Selection->NotifySelectionSetModified();
check(Selection->Faces.Num() == FaceSelection.Num());
TUniquePtr<FToolCommandChange> AddChange = EndChange();
GetToolManager()->EmitObjectChange(Selection, MoveTemp(AddChange), LOCTEXT("OptimizeSelection", "Optimize Selection"));
GetToolManager()->EndUndoTransaction();
OnExternalSelectionChange();
}
void UMeshSelectionTool::DeleteSelectedTriangles()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
if (SelectedFaces.Num() >= PreviewMesh->GetMesh()->TriangleCount())
{
return;
}
TUniquePtr<FToolCommandChangeSequence> ChangeSeq = MakeUnique<FToolCommandChangeSequence>();
// clear current selection
BeginChange(false);
for (int tid : SelectedFaces)
{
ActiveSelectionChange->Add(tid);
}
Selection->RemoveIndices(EMeshSelectionElementType::Face, SelectedFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
ChangeSeq->AppendChange(Selection, MoveTemp(SelectionChange));
// delete triangles and emit delete triangles change
TUniquePtr<FMeshChange> MeshChange = PreviewMesh->TrackedEditMesh(
[&SelectedFaces](FDynamicMesh3& Mesh, FDynamicMeshChangeTracker& ChangeTracker)
{
FDynamicMeshEditor Editor(&Mesh);
Editor.RemoveTriangles(SelectedFaces, true, [&ChangeTracker](int TriangleID) { ChangeTracker.SaveTriangle(TriangleID, true); });
});
ChangeSeq->AppendChange(PreviewMesh, MoveTemp(MeshChange));
// emit combined change sequence
GetToolManager()->EmitObjectChange(this, MoveTemp(ChangeSeq), LOCTEXT("MeshSelectionToolDeleteFaces", "Delete Faces"));
bFullMeshInvalidationPending = true;
OnExternalSelectionChange();
bHaveModifiedMesh = true;
bOctreeValid = false;
}
void UMeshSelectionTool::DisconnectSelectedTriangles()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
TUniquePtr<FToolCommandChangeSequence> ChangeSeq = MakeUnique<FToolCommandChangeSequence>();
// split out selected triangles and emit triangle change
TUniquePtr<FMeshChange> MeshChange = PreviewMesh->TrackedEditMesh(
[&SelectedFaces](FDynamicMesh3& Mesh, FDynamicMeshChangeTracker& ChangeTracker)
{
// save vertices and triangles that are on the boundary of the selection
FMeshRegionBoundaryLoops BoundaryLoops(&Mesh, SelectedFaces);
for (const FEdgeLoop& Loop : BoundaryLoops.Loops)
{
// include the whole one-ring in case the disconnect creates bowties that need to be split
ChangeTracker.SaveVertexOneRingTriangles(Loop.Vertices, true);
}
FDynamicMeshEditor Editor(&Mesh);
Editor.DisconnectTriangles(SelectedFaces);
});
ChangeSeq->AppendChange(PreviewMesh, MoveTemp(MeshChange));
// emit combined change sequence
GetToolManager()->EmitObjectChange(this, MoveTemp(ChangeSeq), LOCTEXT("MeshSelectionToolDisconnectFaces", "Disconnect Faces"));
bFullMeshInvalidationPending = true;
bHaveModifiedMesh = true;
}
void UMeshSelectionTool::SeparateSelectedTriangles(bool bDeleteSelected)
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
const FDynamicMesh3* SourceMesh = PreviewMesh->GetPreviewDynamicMesh();
if (SelectedFaces.Num() == SourceMesh->TriangleCount())
{
return; // don't separate entire mesh
}
// extract copy of triangles
FDynamicMesh3 SeparatedMesh;
SeparatedMesh.EnableTriangleGroups();
SeparatedMesh.EnableAttributes();
SeparatedMesh.Attributes()->EnableMatchingAttributes(*SourceMesh->Attributes());
FDynamicMeshEditor Editor(&SeparatedMesh);
FMeshIndexMappings Mappings; FDynamicMeshEditResult EditResult;
Editor.AppendTriangles(SourceMesh, SelectedFaces, Mappings, EditResult);
// emit new asset
FTransform3d Transform(PreviewMesh->GetTransform());
GetToolManager()->BeginUndoTransaction(
(bDeleteSelected) ? LOCTEXT("MeshSelectionToolSeparate", "Separate") : LOCTEXT("MeshSelectionToolDuplicate", "Duplicate") );
// build array of materials from the original
FComponentMaterialSet MaterialSet = UE::ToolTarget::GetMaterialSet(Target);
TArray<UMaterialInterface*> Materials = MaterialSet.Materials;
AActor* TargetActor = UE::ToolTarget::GetTargetActor(Target);
FString AssetName = TargetActor->GetActorNameOrLabel();
FCreateMeshObjectParams NewMeshObjectParams;
NewMeshObjectParams.TargetWorld = TargetWorld;
NewMeshObjectParams.Transform = (FTransform)Transform;
NewMeshObjectParams.BaseName = (AssetName.IsEmpty()) ? TEXT("Submesh") : (AssetName + TEXT("_Submesh"));
NewMeshObjectParams.Materials = Materials;
NewMeshObjectParams.SetMesh(&SeparatedMesh);
FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams));
if (Result.IsOK() && Result.NewActor != nullptr)
{
SpawnedActors.Add(Result.NewActor);
}
GetToolManager()->EndUndoTransaction();
// delete selected triangles from this mesh
if (bDeleteSelected)
{
DeleteSelectedTriangles();
}
// Currently have to mark the mesh as 'modified' so we can Accept, because if we Cancel,
// Actor created by duplicate operation will be rolled back
bHaveModifiedMesh = true;
}
void UMeshSelectionTool::FlipSelectedTriangles()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
TUniquePtr<FToolCommandChangeSequence> ChangeSeq = MakeUnique<FToolCommandChangeSequence>();
// clear current selection
BeginChange(false);
for (int tid : SelectedFaces)
{
ActiveSelectionChange->Add(tid);
}
Selection->RemoveIndices(EMeshSelectionElementType::Face, SelectedFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
ChangeSeq->AppendChange(Selection, MoveTemp(SelectionChange));
// flip normals
TUniquePtr<FMeshChange> MeshChange = PreviewMesh->TrackedEditMesh(
[&SelectedFaces](FDynamicMesh3& Mesh, FDynamicMeshChangeTracker& ChangeTracker)
{
for (int TID : SelectedFaces)
{
ChangeTracker.SaveTriangle(TID, true);
}
FDynamicMeshEditor Editor(&Mesh);
Editor.ReverseTriangleOrientations(SelectedFaces, true);
});
ChangeSeq->AppendChange(PreviewMesh, MoveTemp(MeshChange));
// emit combined change sequence
GetToolManager()->EmitObjectChange(this, MoveTemp(ChangeSeq), LOCTEXT("MeshSelectionToolFlipFaces", "Flip Face Orientations"));
bHaveModifiedMesh = true;
}
void UMeshSelectionTool::AssignNewGroupToSelectedTriangles()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
TUniquePtr<FToolCommandChangeSequence> ChangeSeq = MakeUnique<FToolCommandChangeSequence>();
// clear current selection
BeginChange(false);
for (int tid : SelectedFaces)
{
ActiveSelectionChange->Add(tid);
}
Selection->RemoveIndices(EMeshSelectionElementType::Face, SelectedFaces);
TUniquePtr<FToolCommandChange> SelectionChange = EndChange();
ChangeSeq->AppendChange(Selection, MoveTemp(SelectionChange));
// assign new groups to triangles
// note: using an FMeshChange is kind of overkill here
TUniquePtr<FMeshChange> MeshChange = PreviewMesh->TrackedEditMesh(
[&SelectedFaces, this](FDynamicMesh3& Mesh, FDynamicMeshChangeTracker& ChangeTracker)
{
// each component gets its own group id
FMeshConnectedComponents Components(&Mesh);
Components.FindConnectedTriangles(SelectedFaces);
for (FMeshConnectedComponents::FComponent& Component : Components)
{
int NewGroupID = Mesh.AllocateTriangleGroup();
for (int tid : Component.Indices)
{
ChangeTracker.SaveTriangle(tid, true);
ActiveGroupSet->SetGroup(tid, NewGroupID, Mesh);
}
}
});
ChangeSeq->AppendChange(PreviewMesh, MoveTemp(MeshChange));
// emit combined change sequence
GetToolManager()->EmitObjectChange(this, MoveTemp(ChangeSeq), LOCTEXT("MeshSelectionToolCreateGroup", "Create Polygroup"));
OnExternalSelectionChange();
bHaveModifiedMesh = true;
}
void UMeshSelectionTool::SmoothSelectionBoundary()
{
check(SelectionType == EMeshSelectionElementType::Face);
TArray<int32> SelectedFaces = Selection->GetElements(EMeshSelectionElementType::Face);
if (SelectedFaces.Num() == 0)
{
return;
}
FMeshRegionBoundaryLoops BoundaryLoops(PreviewMesh->GetMesh(), SelectedFaces, true);
if (BoundaryLoops.Loops.Num() == 0)
{
return;
}
TUniquePtr<FToolCommandChangeSequence> ChangeSeq = MakeUnique<FToolCommandChangeSequence>();
TUniquePtr<FMeshChange> MeshChange = PreviewMesh->TrackedEditMesh(
[&BoundaryLoops, this](FDynamicMesh3& Mesh, FDynamicMeshChangeTracker& ChangeTracker)
{
constexpr double Alpha = 0.75;
TMap<int, FVector3d> NewLoopPositions;
for (const FEdgeLoop& Loop : BoundaryLoops.Loops)
{
int NumLoopVertices = Loop.GetVertexCount();
for (int LoopVertexIndex = 0; LoopVertexIndex < NumLoopVertices; ++LoopVertexIndex)
{
const FVector3d PrevPoint = Loop.GetPrevVertex(LoopVertexIndex);
const FVector3d NextPoint = Loop.GetNextVertex(LoopVertexIndex);
const FVector3d Avg = 0.5 * (PrevPoint + NextPoint);
const FVector3d CurrPoint = Loop.GetVertex(LoopVertexIndex);
const FVector3d NewCurrPoint = (1.0 - Alpha) * CurrPoint + Alpha * Avg;
const int VertexIndex = Loop.Vertices[LoopVertexIndex];
// TODO: Reproject to original surface?
// TODO: Fix the UVs
NewLoopPositions.Add(VertexIndex, NewCurrPoint);
}
}
for (const TPair<int, FVector3d>& NewVert : NewLoopPositions)
{
ChangeTracker.SaveVertexOneRingTriangles(NewVert.Key, true);
}
PreviewMesh->EditMesh([NewLoopPositions](FDynamicMesh3& Mesh)
{
for (const TPair<int, FVector3d>& NewVert : NewLoopPositions)
{
Mesh.SetVertex(NewVert.Key, NewVert.Value);
}
});
});
ChangeSeq->AppendChange(PreviewMesh, MoveTemp(MeshChange));
GetToolManager()->EmitObjectChange(this, MoveTemp(ChangeSeq), LOCTEXT("MeshSelectionToolSmoothBoundary", "Smooth Selection Boundary"));
OnExternalSelectionChange();
bHaveModifiedMesh = true;
}
#undef LOCTEXT_NAMESPACE