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

1563 lines
48 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MeshGroupPaintTool.h"
#include "Engine/Engine.h"
#include "Engine/World.h"
#include "InteractiveToolManager.h"
#include "InteractiveGizmoManager.h"
#include "Drawing/MeshElementsVisualizer.h"
#include "Async/ParallelFor.h"
#include "Async/Async.h"
#include "SceneView.h"
#include "ToolSetupUtil.h"
#include "ModelingToolTargetUtil.h"
#include "MeshWeights.h"
#include "DynamicMesh/MeshNormals.h"
#include "DynamicMesh/MeshIndexUtil.h"
#include "Util/BufferUtil.h"
#include "Util/ColorConstants.h"
#include "Selections/MeshConnectedComponents.h"
#include "Selections/MeshFaceSelection.h"
#include "Selections/MeshVertexSelection.h"
#include "Polygroups/PolygroupUtil.h"
#include "Polygon2.h"
#include "Changes/MeshVertexChange.h"
#include "Changes/MeshPolygroupChange.h"
#include "Changes/BasicChanges.h"
#include "Sculpting/MeshGroupPaintBrushOps.h"
#include "Sculpting/StampFalloffs.h"
#include "Sculpting/MeshSculptUtil.h"
#include "CanvasTypes.h"
#include "CanvasItem.h"
#include "Engine/Engine.h" // for GEngine->GetSmallFont()
#include "SceneView.h"
#include "ToolTargetManager.h"
#include "ToolBuilderUtil.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MeshGroupPaintTool)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UMeshGroupPaintTool"
namespace
{
// probably should be something defined for the whole tool framework...
#if WITH_EDITOR
static EAsyncExecution GroupPaintToolAsyncExecTarget = EAsyncExecution::LargeThreadPool;
#else
static EAsyncExecution GroupPaintToolAsyncExecTarget = EAsyncExecution::ThreadPool;
#endif
}
/*
* ToolBuilder
*/
UMeshSurfacePointTool* UMeshGroupPaintToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
UMeshGroupPaintTool* SculptTool = NewObject<UMeshGroupPaintTool>(SceneState.ToolManager);
SculptTool->SetWorld(SceneState.World);
return SculptTool;
}
bool UMeshGroupPaintToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
return UMeshSurfacePointMeshEditingToolBuilder::CanBuildTool(SceneState) &&
SceneState.TargetManager->CountSelectedAndTargetableWithPredicate(SceneState, GetTargetRequirements(),
[](UActorComponent& Component) { return !ToolBuilderUtil::IsVolume(Component); }) >= 1;
}
/*
* Properties
*/
void UMeshGroupPaintToolActionPropertySet::PostAction(EMeshGroupPaintToolActions Action)
{
if (ParentTool.IsValid())
{
ParentTool->RequestAction(Action);
}
}
/*
* Tool
*/
void UMeshGroupPaintTool::Setup()
{
UMeshSculptToolBase::Setup();
SetToolDisplayName(LOCTEXT("ToolName", "Paint PolyGroups"));
// create dynamic mesh component to use for live preview
FActorSpawnParameters SpawnInfo;
PreviewMeshActor = TargetWorld->SpawnActor<AInternalToolFrameworkActor>(FVector::ZeroVector, FRotator::ZeroRotator, SpawnInfo);
DynamicMeshComponent = NewObject<UDynamicMeshComponent>(PreviewMeshActor);
InitializeSculptMeshComponent(DynamicMeshComponent, PreviewMeshActor);
// assign materials
FComponentMaterialSet MaterialSet = UE::ToolTarget::GetMaterialSet(Target);
for (int k = 0; k < MaterialSet.Materials.Num(); ++k)
{
DynamicMeshComponent->SetMaterial(k, MaterialSet.Materials[k]);
}
DynamicMeshComponent->SetInvalidateProxyOnChangeEnabled(false);
OnDynamicMeshComponentChangedHandle = DynamicMeshComponent->OnMeshChanged.AddUObject(this, &UMeshGroupPaintTool::OnDynamicMeshComponentChanged);
FDynamicMesh3* Mesh = GetSculptMesh();
Mesh->EnableVertexColors(FVector3f::One());
FAxisAlignedBox3d Bounds = Mesh->GetBounds(true);
TFuture<void> PrecomputeFuture = Async(GroupPaintToolAsyncExecTarget, [&]()
{
PrecomputeFilterData();
});
TFuture<void> OctreeFuture = Async(GroupPaintToolAsyncExecTarget, [&]()
{
// initialize dynamic octree
if (Mesh->TriangleCount() > 100000)
{
Octree.RootDimension = Bounds.MaxDim() / 10.0;
Octree.SetMaxTreeDepth(4);
}
else
{
Octree.RootDimension = Bounds.MaxDim();
Octree.SetMaxTreeDepth(8);
}
Octree.Initialize(Mesh);
//Octree.CheckValidity(EValidityCheckFailMode::Check, true, true);
//FDynamicMeshOctree3::FStatistics Stats;
//Octree.ComputeStatistics(Stats);
//UE_LOG(LogTemp, Warning, TEXT("Octree Stats: %s"), *Stats.ToString());
});
// initialize render decomposition
TUniquePtr<FMeshRenderDecomposition> Decomp = MakeUnique<FMeshRenderDecomposition>();
FMeshRenderDecomposition::BuildChunkedDecomposition(Mesh, &MaterialSet, *Decomp);
Decomp->BuildAssociations(Mesh);
//UE_LOG(LogTemp, Warning, TEXT("Decomposition has %d groups"), Decomp->Num());
DynamicMeshComponent->SetExternalDecomposition(MoveTemp(Decomp));
// initialize brush radius range interval, brush properties
UMeshSculptToolBase::InitializeBrushSizeRange(Bounds);
// Set up control points mechanic
PolyLassoMechanic = NewObject<UPolyLassoMarqueeMechanic>(this);
PolyLassoMechanic->Setup(this);
PolyLassoMechanic->SetIsEnabled(false);
PolyLassoMechanic->SpacingTolerance = 10.0f;
PolyLassoMechanic->OnDrawPolyLassoFinished.AddUObject(this, &UMeshGroupPaintTool::OnPolyLassoFinished);
PolygroupLayerProperties = NewObject<UPolygroupLayersProperties>(this);
PolygroupLayerProperties->RestoreProperties(this, TEXT("MeshGroupPaintTool"));
PolygroupLayerProperties->InitializeGroupLayers(GetSculptMesh());
PolygroupLayerProperties->WatchProperty(PolygroupLayerProperties->ActiveGroupLayer, [&](FName) { OnSelectedGroupLayerChanged(); });
UpdateActiveGroupLayer();
AddToolPropertySource(PolygroupLayerProperties);
// initialize other properties
FilterProperties = NewObject<UGroupPaintBrushFilterProperties>(this);
FilterProperties->WatchProperty(FilterProperties->SubToolType,
[this](EMeshGroupPaintInteractionType NewType) { UpdateSubToolType(NewType); });
FilterProperties->WatchProperty(FilterProperties->BrushSize,
[this](float NewSize) { UMeshSculptToolBase::BrushProperties->BrushSize.AdaptiveSize = NewSize; CalculateBrushRadius(); });
FilterProperties->WatchProperty(FilterProperties->bHitBackFaces,
[this](bool bNewValue) { UMeshSculptToolBase::BrushProperties->bHitBackFaces = bNewValue; });
FilterProperties->RestoreProperties(this);
FilterProperties->BrushSize = UMeshSculptToolBase::BrushProperties->BrushSize.AdaptiveSize;
FilterProperties->bHitBackFaces = UMeshSculptToolBase::BrushProperties->bHitBackFaces;
FilterProperties->SetGroup = ActiveGroupSet->MaxGroupID;
AddToolPropertySource(FilterProperties);
InitializeIndicator();
// initialize our properties
AddToolPropertySource(UMeshSculptToolBase::BrushProperties);
UMeshSculptToolBase::BrushProperties->bShowPerBrushProps = false;
UMeshSculptToolBase::BrushProperties->bShowFalloff = false;
UMeshSculptToolBase::BrushProperties->bShowLazyness = false;
CalculateBrushRadius();
PaintBrushOpProperties = NewObject<UGroupPaintBrushOpProps>(this);
RegisterBrushType((int32)EMeshGroupPaintBrushType::Paint, LOCTEXT("Paint", "Paint"),
MakeUnique<FLambdaMeshSculptBrushOpFactory>([this]() { return MakeUnique<FGroupPaintBrushOp>(); }),
PaintBrushOpProperties);
// secondary brushes
EraseBrushOpProperties = NewObject<UGroupEraseBrushOpProps>(this);
EraseBrushOpProperties->GetCurrentGroupLambda = [this]() { return PaintBrushOpProperties->GetGroup(); };
RegisterSecondaryBrushType((int32)EMeshGroupPaintBrushType::Erase, LOCTEXT("Erase", "Erase"),
MakeUnique<TBasicMeshSculptBrushOpFactory<FGroupEraseBrushOp>>(),
EraseBrushOpProperties);
AddToolPropertySource(UMeshSculptToolBase::ViewProperties);
AddToolPropertySource(UMeshSculptToolBase::GizmoProperties);
SetToolPropertySourceEnabled(UMeshSculptToolBase::GizmoProperties, false);
// register watchers
FilterProperties->WatchProperty( FilterProperties->PrimaryBrushType,
[this](EMeshGroupPaintBrushType NewType) { UpdateBrushType(NewType); });
// must call before updating brush type so that we register all brush properties?
UMeshSculptToolBase::OnCompleteSetup();
UpdateBrushType(FilterProperties->PrimaryBrushType);
SetActiveSecondaryBrushType((int32)EMeshGroupPaintBrushType::Erase);
FreezeActions = NewObject<UMeshGroupPaintToolFreezeActions>(this);
FreezeActions->Initialize(this);
AddToolPropertySource(FreezeActions);
MeshElementsDisplay = NewObject<UMeshElementsVisualizer>(this);
MeshElementsDisplay->CreateInWorld(DynamicMeshComponent->GetWorld(), DynamicMeshComponent->GetComponentTransform());
if (ensure(MeshElementsDisplay->Settings))
{
MeshElementsDisplay->Settings->RestoreProperties(this, TEXT("MeshGroupPaintTool"));
AddToolPropertySource(MeshElementsDisplay->Settings);
}
MeshElementsDisplay->SetMeshAccessFunction([this](UMeshElementsVisualizer::ProcessDynamicMeshFunc ProcessFunc) {
ProcessFunc(*GetSculptMesh());
});
// force colors update... ?
DynamicMeshComponent->SetTriangleColorFunction([this](const FDynamicMesh3* Mesh, int TriangleID)
{
return GetColorForGroup(ActiveGroupSet->GetGroup(TriangleID));
});
// disable view properties
SetViewPropertiesEnabled(false);
UpdateMaterialMode(EMeshEditingMaterialModes::VertexColor);
UpdateWireframeVisibility(false);
UpdateFlatShadingSetting(true);
// configure panels
UpdateSubToolType(FilterProperties->SubToolType);
PrecomputeFuture.Wait();
OctreeFuture.Wait();
}
void UMeshGroupPaintTool::Shutdown(EToolShutdownType ShutdownType)
{
if (DynamicMeshComponent != nullptr)
{
DynamicMeshComponent->OnMeshChanged.Remove(OnDynamicMeshComponentChangedHandle);
}
if (ensure(MeshElementsDisplay->Settings))
{
MeshElementsDisplay->Settings->SaveProperties(this, TEXT("MeshGroupPaintTool"));
}
MeshElementsDisplay->Disconnect();
FilterProperties->SaveProperties(this);
PolygroupLayerProperties->SaveProperties(this, TEXT("MeshGroupPaintTool"));
if (PreviewMeshActor != nullptr)
{
PreviewMeshActor->Destroy();
PreviewMeshActor = nullptr;
}
UMeshSculptToolBase::Shutdown(ShutdownType);
}
void UMeshGroupPaintTool::CommitResult(UBaseDynamicMeshComponent* Component, bool bModifiedTopology)
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("GroupPaintToolTransactionName", "Paint Groups"));
Component->ProcessMesh([&](const FDynamicMesh3& CurMesh)
{
UE::ToolTarget::CommitDynamicMeshUpdate(Target, CurMesh, true);
});
GetToolManager()->EndUndoTransaction();
}
void UMeshGroupPaintTool::RegisterActions(FInteractiveToolActionSet& ActionSet)
{
UMeshSculptToolBase::RegisterActions(ActionSet);
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 500,
TEXT("PickGroupColorUnderCursor"),
LOCTEXT("PickGroupColorUnderCursor", "Pick PolyGroup"),
LOCTEXT("PickGroupColorUnderCursorTooltip", "Switch the active PolyGroup to the group currently under the cursor"),
EModifierKey::Shift, EKeys::G,
[this]() { bPendingPickGroup = true; });
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 501,
TEXT("ToggleFrozenGroup"),
LOCTEXT("ToggleFrozenGroup", "Toggle Group Frozen State"),
LOCTEXT("ToggleFrozenGroupTooltip", "Toggle Group Frozen State"),
EModifierKey::Shift, EKeys::F,
[this]() { bPendingToggleFreezeGroup = true; });
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 502,
TEXT("CreateNewGroup"),
LOCTEXT("CreateNewGroup", "New Group"),
LOCTEXT("CreateNewGroupTooltip", "Allocate a new Polygroup and set as Current"),
EModifierKey::Shift, EKeys::Q,
[this]() { AllocateNewGroupAndSetAsCurrentAction(); });
};
TUniquePtr<FMeshSculptBrushOp>& UMeshGroupPaintTool::GetActiveBrushOp()
{
if (GetInEraseStroke())
{
return SecondaryBrushOp;
}
else
{
return PrimaryBrushOp;
}
}
void UMeshGroupPaintTool::IncreaseBrushRadiusAction()
{
Super::IncreaseBrushRadiusAction();
FilterProperties->BrushSize = BrushProperties->BrushSize.AdaptiveSize;
NotifyOfPropertyChangeByTool(FilterProperties);
}
void UMeshGroupPaintTool::DecreaseBrushRadiusAction()
{
Super::DecreaseBrushRadiusAction();
FilterProperties->BrushSize = BrushProperties->BrushSize.AdaptiveSize;
NotifyOfPropertyChangeByTool(FilterProperties);
}
void UMeshGroupPaintTool::IncreaseBrushRadiusSmallStepAction()
{
Super::IncreaseBrushRadiusSmallStepAction();
FilterProperties->BrushSize = BrushProperties->BrushSize.AdaptiveSize;
NotifyOfPropertyChangeByTool(FilterProperties);
}
void UMeshGroupPaintTool::DecreaseBrushRadiusSmallStepAction()
{
Super::DecreaseBrushRadiusSmallStepAction();
FilterProperties->BrushSize = BrushProperties->BrushSize.AdaptiveSize;
NotifyOfPropertyChangeByTool(FilterProperties);
}
bool UMeshGroupPaintTool::IsInBrushSubMode() const
{
return FilterProperties->SubToolType == EMeshGroupPaintInteractionType::Brush
|| FilterProperties->SubToolType == EMeshGroupPaintInteractionType::Fill
|| FilterProperties->SubToolType == EMeshGroupPaintInteractionType::GroupFill;
}
void UMeshGroupPaintTool::OnBeginStroke(const FRay& WorldRay)
{
UpdateBrushPosition(WorldRay);
if (PaintBrushOpProperties)
{
PaintBrushOpProperties->Group = FilterProperties->SetGroup;
PaintBrushOpProperties->bOnlyPaintUngrouped = FilterProperties->bOnlySetUngrouped;
}
if (EraseBrushOpProperties)
{
EraseBrushOpProperties->Group = FilterProperties->EraseGroup;
EraseBrushOpProperties->bOnlyEraseCurrent = FilterProperties->bOnlyEraseCurrent;
}
// initialize first "Last Stamp", so that we can assume all stamps in stroke have a valid previous stamp
LastStamp.WorldFrame = GetBrushFrameWorld();
LastStamp.LocalFrame = GetBrushFrameLocal();
LastStamp.Radius = GetCurrentBrushRadius();
LastStamp.Falloff = GetCurrentBrushFalloff();
LastStamp.Direction = GetInInvertStroke() ? -1.0 : 1.0;
LastStamp.Depth = GetCurrentBrushDepth();
LastStamp.Power = GetActivePressure() * GetCurrentBrushStrength();
LastStamp.TimeStamp = FDateTime::Now();
FSculptBrushOptions SculptOptions;
//SculptOptions.bPreserveUVFlow = false; // FilterProperties->bPreserveUVFlow;
SculptOptions.ConstantReferencePlane = GetCurrentStrokeReferencePlane();
TUniquePtr<FMeshSculptBrushOp>& UseBrushOp = GetActiveBrushOp();
UseBrushOp->ConfigureOptions(SculptOptions);
UseBrushOp->BeginStroke(GetSculptMesh(), LastStamp, VertexROI);
AccumulatedTriangleROI.Reset();
// begin change here? or wait for first stamp?
BeginChange();
}
void UMeshGroupPaintTool::OnEndStroke()
{
GetActiveBrushOp()->EndStroke(GetSculptMesh(), LastStamp, VertexROI);
// close change record
EndChange();
}
void UMeshGroupPaintTool::OnCancelStroke()
{
GetActiveBrushOp()->CancelStroke();
ActiveGroupEditBuilder.Reset();
bDrawGroupsDataValid = false;
}
void UMeshGroupPaintTool::UpdateROI(const FSculptBrushStamp& BrushStamp)
{
SCOPE_CYCLE_COUNTER(GroupPaintTool_UpdateROI);
int32 SetGroupID = GetInEraseStroke() ? FilterProperties->EraseGroup : FilterProperties->SetGroup;
const FVector3d& BrushPos = BrushStamp.LocalFrame.Origin;
const FDynamicMesh3* Mesh = GetSculptMesh();
float RadiusSqr = GetCurrentBrushRadius() * GetCurrentBrushRadius();
FAxisAlignedBox3d BrushBox(
BrushPos - GetCurrentBrushRadius() * FVector3d::One(),
BrushPos + GetCurrentBrushRadius() * FVector3d::One());
TriangleROI.Reset();
int32 CenterTID = GetBrushTriangleID();
if (Mesh->IsTriangle(CenterTID))
{
TriangleROI.Add(CenterTID);
}
FVector3d CenterNormal = Mesh->IsTriangle(CenterTID) ? TriNormals[CenterTID] : FVector3d::One(); // One so that normal check always passes
bool bVolumetric = (FilterProperties->BrushAreaMode == EMeshGroupPaintBrushAreaType::Volumetric);
bool bUseAngleThreshold = (bVolumetric == false) && (FilterProperties->AngleThreshold < 180.0f);
double DotAngleThreshold = FMathd::Cos(FilterProperties->AngleThreshold * FMathd::DegToRad);
bool bStopAtUVSeams = FilterProperties->bUVSeams;
bool bStopAtNormalSeams = FilterProperties->bNormalSeams;
auto CheckEdgeCriteria = [&](int32 t1, int32 t2) -> bool
{
if (bUseAngleThreshold == false || CenterNormal.Dot(TriNormals[t2]) > DotAngleThreshold)
{
int32 eid = Mesh->FindEdgeFromTriPair(t1, t2);
if (bStopAtUVSeams == false || UVSeamEdges[eid] == false)
{
if (bStopAtNormalSeams == false || NormalSeamEdges[eid] == false)
{
return true;
}
}
}
return false;
};
bool bFill = (FilterProperties->SubToolType == EMeshGroupPaintInteractionType::Fill);
bool bGroupFill = (FilterProperties->SubToolType == EMeshGroupPaintInteractionType::GroupFill);
if (bVolumetric)
{
Octree.RangeQuery(BrushBox,
[&](int TriIdx) {
if ((Mesh->GetTriCentroid(TriIdx) - BrushPos).SquaredLength() < RadiusSqr)
{
TriangleROI.Add(TriIdx);
}
});
}
else
{
if (Mesh->IsTriangle(CenterTID))
{
TArray<int32> StartROI;
StartROI.Add(CenterTID);
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, TriangleROI, &TempROIBuffer,
[&](int t1, int t2)
{
if ((Mesh->GetTriCentroid(t2) - BrushPos).SquaredLength() < RadiusSqr)
{
return CheckEdgeCriteria(t1, t2);
}
return false;
});
}
}
if (bFill)
{
TArray<int32> StartROI;
for (int32 tid : TriangleROI)
{
StartROI.Add(tid);
}
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, TriangleROI, &TempROIBuffer,
[&](int t1, int t2)
{
return CheckEdgeCriteria(t1, t2);
});
}
else if (bGroupFill)
{
TArray<int32> StartROI;
TSet<int32> FillGroups;
for (int32 tid : TriangleROI)
{
if (ActiveGroupSet->GetGroup(tid) != SetGroupID)
{
StartROI.Add(tid);
FillGroups.Add(ActiveGroupSet->GetGroup(tid));
}
}
FMeshConnectedComponents::GrowToConnectedTriangles(Mesh, StartROI, TriangleROI, &TempROIBuffer,
[&](int t1, int t2)
{
return (FillGroups.Contains(ActiveGroupSet->GetGroup(t2)));
});
}
// apply visibility filter
if (FilterProperties->VisibilityFilter != EMeshGroupPaintVisibilityType::None)
{
TArray<int32> ResultBuffer;
ApplyVisibilityFilter(TriangleROI, TempROIBuffer, ResultBuffer);
}
// construct ROI vertex set
VertexSetBuffer.Reset();
for (int32 tid : TriangleROI)
{
FIndex3i Tri = Mesh->GetTriangle(tid);
VertexSetBuffer.Add(Tri.A); VertexSetBuffer.Add(Tri.B); VertexSetBuffer.Add(Tri.C);
}
VertexROI.SetNum(0, EAllowShrinking::No);
BufferUtil::AppendElements(VertexROI, VertexSetBuffer);
// construct ROI triangle and group buffers
ROITriangleBuffer.Reserve(TriangleROI.Num());
ROITriangleBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : TriangleROI)
{
ROITriangleBuffer.Add(tid);
}
ROIGroupBuffer.SetNum(ROITriangleBuffer.Num(), EAllowShrinking::No);
}
bool UMeshGroupPaintTool::UpdateStampPosition(const FRay& WorldRay)
{
CalculateBrushRadius();
TUniquePtr<FMeshSculptBrushOp>& UseBrushOp = GetActiveBrushOp();
ESculptBrushOpTargetType TargetType = UseBrushOp->GetBrushTargetType();
switch (TargetType)
{
case ESculptBrushOpTargetType::SculptMesh:
case ESculptBrushOpTargetType::TargetMesh:
UpdateBrushPositionOnSculptMesh(WorldRay, true);
break;
case ESculptBrushOpTargetType::ActivePlane:
check(false);
UpdateBrushPositionOnActivePlane(WorldRay);
break;
}
if (UseBrushOp->GetAlignStampToView())
{
AlignBrushToView();
}
CurrentStamp = LastStamp;
CurrentStamp.DeltaTime = FMathd::Min((FDateTime::Now() - LastStamp.TimeStamp).GetTotalSeconds(), 1.0);
CurrentStamp.WorldFrame = GetBrushFrameWorld();
CurrentStamp.LocalFrame = GetBrushFrameLocal();
CurrentStamp.Power = GetActivePressure() * GetCurrentBrushStrength();
CurrentStamp.PrevLocalFrame = LastStamp.LocalFrame;
CurrentStamp.PrevWorldFrame = LastStamp.WorldFrame;
FVector3d MoveDelta = CurrentStamp.LocalFrame.Origin - CurrentStamp.PrevLocalFrame.Origin;
if (UseBrushOp->IgnoreZeroMovements() && MoveDelta.SquaredLength() < FMathd::ZeroTolerance)
{
return false;
}
return true;
}
bool UMeshGroupPaintTool::ApplyStamp()
{
SCOPE_CYCLE_COUNTER(GroupPaintToolApplyStamp);
TUniquePtr<FMeshSculptBrushOp>& UseBrushOp = GetActiveBrushOp();
// yuck
FMeshTriangleGroupEditBrushOp* GroupBrushOp = (FMeshTriangleGroupEditBrushOp*)UseBrushOp.Get();
FDynamicMesh3* Mesh = GetSculptMesh();
GroupBrushOp->ApplyStampByTriangles(Mesh, CurrentStamp, ROITriangleBuffer, ROIGroupBuffer);
bool bUpdated = SyncMeshWithGroupBuffer(Mesh);
LastStamp = CurrentStamp;
LastStamp.TimeStamp = FDateTime::Now();
return bUpdated;
}
bool UMeshGroupPaintTool::SyncMeshWithGroupBuffer(FDynamicMesh3* Mesh)
{
int NumModified = 0;
const int32 NumT = ROITriangleBuffer.Num();
// change update could be async here if we collected array of <idx,orig,new> and dispatched independenlty
for ( int32 k = 0; k < NumT; ++k)
{
int TriIdx = ROITriangleBuffer[k];
int32 CurGroupID = ActiveGroupSet->GetGroup(TriIdx);
if (FrozenGroups.Contains(CurGroupID)) // skip frozen groups
{
continue;
}
if (ROIGroupBuffer[k] != CurGroupID)
{
ActiveGroupEditBuilder->SaveTriangle(TriIdx, CurGroupID, ROIGroupBuffer[k]);
ActiveGroupSet->SetGroup(TriIdx, ROIGroupBuffer[k], *Mesh);
//ActiveVertexChange->UpdateVertexColor(VertIdx, OrigColor, NewColor);
NumModified++;
}
}
return (NumModified > 0);
}
void UMeshGroupPaintTool::OnPolyLassoFinished(const FCameraPolyLasso& Lasso, bool bCanceled)
{
// construct polyline
TArray<FVector2D> Polyline = Lasso.Polyline;
int32 N = Polyline.Num();
if (N < 2)
{
return;
}
// Try to clip polyline to be closed, or closed-enough for winding evaluation to work.
// If that returns false, the polyline is "too open". In that case we will extend
// outwards from the endpoints and then try to create a closed very large polygon
if (UPolyLassoMarqueeMechanic::ApproximateSelfClipPolyline(Polyline) == false)
{
FVector2d StartDirOut = UE::Geometry::Normalized(Polyline[0] - Polyline[1]);
FLine2d StartLine(Polyline[0], StartDirOut);
FVector2d EndDirOut = UE::Geometry::Normalized(Polyline[N - 1] - Polyline[N - 2]);
FLine2d EndLine(Polyline[N - 1], EndDirOut);
// if we did not intersect, we are in ambiguous territory. Check if a segment along either end-direction
// intersects the polyline. If it does, we have something like a spiral and will be OK.
// If not, make a closed polygon by interpolating outwards from each endpoint, and then in perp-directions.
FPolygon2d Polygon(Polyline);
float PerpSign = Polygon.IsClockwise() ? -1.0 : 1.0;
Polyline.Insert(StartLine.PointAt(10000.0f), 0);
Polyline.Insert(Polyline[0] + 1000 * PerpSign * UE::Geometry::PerpCW(StartDirOut), 0);
Polyline.Add(EndLine.PointAt(10000.0f));
Polyline.Add(Polyline.Last() + 1000 * PerpSign * UE::Geometry::PerpCW(EndDirOut));
FVector2d StartPos = Polyline[0];
Polyline.Add(StartPos); // close polyline (cannot use Polyline[0] in case Add resizes!)
}
N = Polyline.Num();
// project each mesh vertex to view plane and evaluate winding integral of polyline
const FDynamicMesh3* Mesh = GetSculptMesh();
TempROIBuffer.SetNum(Mesh->MaxVertexID());
ParallelFor(Mesh->MaxVertexID(), [&](int32 vid)
{
if (Mesh->IsVertex(vid))
{
FVector3d WorldPos = CurTargetTransform.TransformPosition(Mesh->GetVertex(vid));
FVector2d PlanePos = (FVector2d)Lasso.GetProjectedPoint((FVector)WorldPos);
double WindingSum = 0;
FVector2d a = Polyline[0] - PlanePos, b = FVector2d::Zero();
for (int32 i = 1; i < N; ++i)
{
b = Polyline[i] - PlanePos;
WindingSum += (double)FMathd::Atan2(a.X*b.Y - a.Y*b.X, a.X*b.X + a.Y*b.Y);
a = b;
}
WindingSum /= FMathd::TwoPi;
bool bInside = FMathd::Abs(WindingSum) > 0.3;
TempROIBuffer[vid] = bInside ? 1 : 0;
}
else
{
TempROIBuffer[vid] = -1;
}
});
// convert to vertex selection, and then select fully-enclosed faces
FMeshVertexSelection VertexSelection(Mesh);
VertexSelection.SelectByVertexID([&](int32 vid) { return TempROIBuffer[vid] == 1; });
FMeshFaceSelection FaceSelection(Mesh, VertexSelection, FilterProperties->MinTriVertCount);
if (FaceSelection.Num() == 0)
{
return;
}
int32 SetGroupID = GetInEraseStroke() ? FilterProperties->EraseGroup : FilterProperties->SetGroup;
SetTrianglesToGroupID(FaceSelection.AsSet(), SetGroupID, GetInEraseStroke());
}
void UMeshGroupPaintTool::SetTrianglesToGroupID(const TSet<int32>& Triangles, int32 ToGroupID, bool bIsErase)
{
BeginChange();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Triangles)
{
int32 CurGroupID = ActiveGroupSet->GetGroup(tid);
if (CurGroupID == ToGroupID || FrozenGroups.Contains(CurGroupID)) // skip frozen groups
{
continue;
}
if (bIsErase == false && FilterProperties->bOnlySetUngrouped && CurGroupID != 0)
{
continue;
}
if (bIsErase && FilterProperties->bOnlyEraseCurrent && CurGroupID != FilterProperties->SetGroup)
{
continue;
}
TempROIBuffer.Add(tid);
}
if (HaveVisibilityFilter())
{
TArray<int32> VisibleTriangles;
VisibleTriangles.Reserve(TempROIBuffer.Num());
ApplyVisibilityFilter(TempROIBuffer, VisibleTriangles);
TempROIBuffer = MoveTemp(VisibleTriangles);
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
for (int32 tid : TempROIBuffer)
{
ActiveGroupSet->SetGroup(tid, ToGroupID, *GetSculptMesh());
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
EndChange();
}
bool UMeshGroupPaintTool::HaveVisibilityFilter() const
{
return FilterProperties->VisibilityFilter != EMeshGroupPaintVisibilityType::None;
}
void UMeshGroupPaintTool::ApplyVisibilityFilter(TSet<int32>& Triangles, TArray<int32>& ROIBuffer, TArray<int32>& OutputBuffer)
{
ROIBuffer.SetNum(0, EAllowShrinking::No);
ROIBuffer.Reserve(Triangles.Num());
for (int32 tid : Triangles)
{
ROIBuffer.Add(tid);
}
OutputBuffer.Reset();
ApplyVisibilityFilter(TempROIBuffer, OutputBuffer);
Triangles.Reset();
for (int32 tid : OutputBuffer)
{
TriangleROI.Add(tid);
}
}
void UMeshGroupPaintTool::ApplyVisibilityFilter(const TArray<int32>& Triangles, TArray<int32>& VisibleTriangles)
{
if (!HaveVisibilityFilter())
{
VisibleTriangles = Triangles;
return;
}
FViewCameraState StateOut;
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(StateOut);
FTransform3d LocalToWorld = UE::ToolTarget::GetLocalToWorldTransform(Target);
FVector3d LocalEyePosition(LocalToWorld.InverseTransformPosition(StateOut.Position));
const FDynamicMesh3* Mesh = GetSculptMesh();
int32 NumTriangles = Triangles.Num();
VisibilityFilterBuffer.SetNum(NumTriangles, EAllowShrinking::No);
ParallelFor(NumTriangles, [&](int32 idx)
{
VisibilityFilterBuffer[idx] = true;
FVector3d Centroid = Mesh->GetTriCentroid(Triangles[idx]);
FVector3d FaceNormal = Mesh->GetTriNormal(Triangles[idx]);
if (FaceNormal.Dot((Centroid - LocalEyePosition)) > 0)
{
VisibilityFilterBuffer[idx] = false;
}
if (FilterProperties->VisibilityFilter == EMeshGroupPaintVisibilityType::Unoccluded)
{
int32 HitTID = Octree.FindNearestHitObject(FRay3d(LocalEyePosition, UE::Geometry::Normalized(Centroid - LocalEyePosition)));
if (HitTID != Triangles[idx])
{
VisibilityFilterBuffer[idx] = false;
}
}
});
VisibleTriangles.Reset();
for (int32 k = 0; k < NumTriangles; ++k)
{
if (VisibilityFilterBuffer[k])
{
VisibleTriangles.Add(Triangles[k]);
}
}
}
int32 UMeshGroupPaintTool::FindHitSculptMeshTriangleConst(const FRay3d& LocalRay) const
{
if (!IsInBrushSubMode())
{
return IndexConstants::InvalidID;
}
if (GetBrushCanHitBackFaces())
{
return Octree.FindNearestHitObject(LocalRay);
}
else
{
const FDynamicMesh3* Mesh = GetSculptMesh();
FViewCameraState StateOut;
GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(StateOut);
FVector3d LocalEyePosition(CurTargetTransform.InverseTransformPosition((FVector3d)StateOut.Position));
int HitTID = Octree.FindNearestHitObject(LocalRay,
[this, Mesh, &LocalEyePosition](int TriangleID) {
FVector3d Normal, Centroid;
double Area;
Mesh->GetTriInfo(TriangleID, Normal, Area, Centroid);
return Normal.Dot((Centroid - LocalEyePosition)) < 0;
});
return HitTID;
}
}
int32 UMeshGroupPaintTool::FindHitTargetMeshTriangleConst(const FRay3d& LocalRay) const
{
check(false);
return IndexConstants::InvalidID;
}
bool UMeshGroupPaintTool::UpdateBrushPosition(const FRay& WorldRay)
{
TUniquePtr<FMeshSculptBrushOp>& UseBrushOp = GetActiveBrushOp();
bool bHit = false;
ESculptBrushOpTargetType TargetType = UseBrushOp->GetBrushTargetType();
switch (TargetType)
{
case ESculptBrushOpTargetType::SculptMesh:
case ESculptBrushOpTargetType::TargetMesh:
bHit = UpdateBrushPositionOnSculptMesh(WorldRay, false);
break;
case ESculptBrushOpTargetType::ActivePlane:
check(false);
bHit = UpdateBrushPositionOnSculptMesh(WorldRay, false);
break;
}
if (bHit && UseBrushOp->GetAlignStampToView())
{
AlignBrushToView();
}
return bHit;
}
bool UMeshGroupPaintTool::OnUpdateHover(const FInputDeviceRay& DevicePos)
{
PendingStampType = FilterProperties->PrimaryBrushType;
if(ensure(InStroke() == false))
{
UpdateBrushPosition(DevicePos.WorldRay);
}
return true;
}
void UMeshGroupPaintTool::DrawHUD(FCanvas* Canvas, IToolsContextRenderAPI* RenderAPI)
{
if (PolyLassoMechanic)
{
// because the actual group change is deferred until mouse release, color the lasso to let the user know whether it will erase
PolyLassoMechanic->LineColor = GetInEraseStroke() ? FLinearColor::Red : FLinearColor::Green;
PolyLassoMechanic->DrawHUD(Canvas, RenderAPI);
}
float DPIScale = Canvas->GetDPIScale();
UFont* UseFont = GEngine->GetSmallFont();
FViewCameraState CamState = RenderAPI->GetCameraState();
const FSceneView* SceneView = RenderAPI->GetSceneView();
FVector3d LocalEyePosition(CurTargetTransform.InverseTransformPosition((FVector3d)CamState.Position));
FDynamicMesh3* Mesh = GetSculptMesh();
// draw the group number for the group under the cursor
if (FilterProperties->bShowHitGroup)
{
int CursorHitTID = Octree.FindNearestHitObject(FRay3d(LocalEyePosition, Normalized(HoverStamp.LocalFrame.Origin - LocalEyePosition)));
if (Mesh->IsTriangle(CursorHitTID))
{
int32 GroupID = ActiveGroupSet->GetTriangleGroup(CursorHitTID);
FVector2D CursorPixelPos;
SceneView->WorldToPixel(HoverStamp.WorldFrame.Origin, CursorPixelPos);
FString CursorString = FString::Printf(TEXT("%d"), GroupID);
Canvas->DrawShadowedString(CursorPixelPos.X / (double)DPIScale, CursorPixelPos.Y / (double)DPIScale, *CursorString, UseFont, FLinearColor::White);
}
}
// draw the group number for all groups
if (FilterProperties->bShowAllGroups)
{
if (bDrawGroupsDataValid == false)
{
GroupVisualizationCache.UpdateGroupInfo_ConnectedComponents(*Mesh, *ActiveGroupSet);
bDrawGroupsDataValid = true;
}
for (const FGroupVisualizationCache::FGroupInfo& GroupInfo : GroupVisualizationCache)
{
FRay3d EyeRay;
EyeRay.Origin = LocalEyePosition;
EyeRay.Direction = Normalized(GroupInfo.Center - LocalEyePosition);
int HitTID = Octree.FindNearestHitObject(EyeRay);
if (HitTID == GroupInfo.CenterTris.A || HitTID == GroupInfo.CenterTris.B)
{
FVector2D PixelPos;
SceneView->WorldToPixel(CurTargetTransform.TransformPosition(GroupInfo.Center), PixelPos);
FString String = FString::Printf(TEXT("%d"), GroupInfo.GroupID);
Canvas->DrawShadowedString(PixelPos.X / (double)DPIScale, PixelPos.Y / (double)DPIScale, *String, UseFont, FLinearColor::White);
}
}
}
}
void UMeshGroupPaintTool::OnTick(float DeltaTime)
{
UMeshSculptToolBase::OnTick(DeltaTime);
MeshElementsDisplay->OnTick(DeltaTime);
bool bIsLasso = (FilterProperties->SubToolType == EMeshGroupPaintInteractionType::PolyLasso);
PolyLassoMechanic->SetIsEnabled(bIsLasso);
ConfigureIndicator(FilterProperties->BrushAreaMode == EMeshGroupPaintBrushAreaType::Volumetric);
SetIndicatorVisibility(bIsLasso == false);
if (bHavePendingAction)
{
ApplyAction(PendingAction);
bHavePendingAction = false;
PendingAction = EMeshGroupPaintToolActions::NoAction;
}
SCOPE_CYCLE_COUNTER(GroupPaintToolTick);
// process the undo update
if (bUndoUpdatePending)
{
// wait for updates
WaitForPendingUndoRedoUpdate();
// post rendering update
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(AccumulatedTriangleROI, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
bDrawGroupsDataValid = false;
// ignore stamp and wait for next tick to do anything else
bUndoUpdatePending = false;
return;
}
if (bPendingPickGroup || bPendingToggleFreezeGroup)
{
if (GetBrushTriangleID() >= 0 && IsStampPending() == false )
{
if (GetSculptMesh()->IsTriangle(GetBrushTriangleID()))
{
int32 HitGroupID = ActiveGroupSet->GetGroup(GetBrushTriangleID());
if (bPendingPickGroup)
{
FilterProperties->SetGroup = HitGroupID;
NotifyOfPropertyChangeByTool(FilterProperties);
}
else if (bPendingToggleFreezeGroup)
{
ToggleFrozenGroup(HitGroupID);
}
}
}
bPendingPickGroup = bPendingToggleFreezeGroup = false;
}
auto ExecuteStampOperation = [this](int StampIndex, const FRay& StampRay)
{
SCOPE_CYCLE_COUNTER(GroupPaintTool_Tick_ApplyStampBlock);
// update sculpt ROI
UpdateROI(CurrentStamp);
// append updated ROI to modified region (async)
TFuture<void> AccumulateROI = Async(GroupPaintToolAsyncExecTarget, [&]()
{
AccumulatedTriangleROI.Append(TriangleROI);
});
// apply the stamp
bool bGroupsModified = ApplyStamp();
if (bGroupsModified)
{
SCOPE_CYCLE_COUNTER(GroupPaintTool_Tick_UpdateMeshBlock);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TriangleROI, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
}
// we don't really need to wait for these to happen to end Tick()...
AccumulateROI.Wait();
};
if (IsInBrushSubMode())
{
ProcessPerTickStamps(
[this](const FRay& StampRay) -> bool {
return UpdateStampPosition(StampRay);
}, ExecuteStampOperation);
}
}
void UMeshGroupPaintTool::AllocateNewGroupAndSetAsCurrentAction()
{
FilterProperties->SetGroup = ActiveGroupSet->MaxGroupID;
NotifyOfPropertyChangeByTool(FilterProperties);
}
FColor UMeshGroupPaintTool::GetColorForGroup(int32 GroupID)
{
FColor Color = LinearColors::SelectFColor(GroupID);
if (FrozenGroups.Contains(GroupID))
{
int32 GrayValue = (Color.R + Color.G + Color.B) / 3;
Color.R = Color.G = Color.B = FMath::Clamp(GrayValue, 0, 255);
}
return Color;
}
void UMeshGroupPaintTool::ToggleFrozenGroup(int32 FreezeGroupID)
{
if (FreezeGroupID == 0) return;
TArray<int32> InitialFrozenGroups = FrozenGroups;
if (FrozenGroups.Contains(FreezeGroupID))
{
FrozenGroups.Remove(FreezeGroupID);
}
else
{
FrozenGroups.Add(FreezeGroupID);
}
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Mesh->TriangleIndicesItr())
{
int32 TriGroupID = ActiveGroupSet->GetGroup(tid);
if (TriGroupID == FreezeGroupID)
{
TempROIBuffer.Add(tid);
}
}
EmitFrozenGroupsChange(InitialFrozenGroups, FrozenGroups, LOCTEXT("ToggleFrozenGroupChange", "Toggle Frozen Group"));
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
}
void UMeshGroupPaintTool::FreezeOtherGroups(int32 KeepGroupID)
{
TArray<int32> InitialFrozenGroups = FrozenGroups;
FrozenGroups.Reset();
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Mesh->TriangleIndicesItr())
{
int32 GroupID = ActiveGroupSet->GetGroup(tid);
if ( GroupID != 0 && GroupID != KeepGroupID)
{
FrozenGroups.AddUnique(ActiveGroupSet->GetGroup(tid));
TempROIBuffer.Add(tid);
}
}
EmitFrozenGroupsChange(InitialFrozenGroups, FrozenGroups, LOCTEXT("FreezeOtherGroups", "Freeze Other Groups"));
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
}
void UMeshGroupPaintTool::ClearAllFrozenGroups()
{
TArray<int32> InitialFrozenGroups = FrozenGroups;
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Mesh->TriangleIndicesItr())
{
if ( FrozenGroups.Contains(ActiveGroupSet->GetGroup(tid)) )
{
TempROIBuffer.Add(tid);
}
}
FrozenGroups.Reset();
EmitFrozenGroupsChange(InitialFrozenGroups, FrozenGroups, LOCTEXT("ClearAllFrozenGroups", "Clear Frozen Groups"));
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
}
void UMeshGroupPaintTool::EmitFrozenGroupsChange(const TArray<int32>& FromGroups, const TArray<int32>& ToGroups, const FText& ChangeText)
{
if (FromGroups != ToGroups)
{
TUniquePtr<TSimpleValueLambdaChange<TArray<int32>>> FrozenGroupsChange = MakeUnique<TSimpleValueLambdaChange<TArray<int32>>>();
FrozenGroupsChange->FromValue = FromGroups;
FrozenGroupsChange->ToValue = ToGroups;
FrozenGroupsChange->ValueChangeFunc = [this](UObject*, const TArray<int32>& FromGroups, const TArray<int32>& ToGroups, bool)
{
FrozenGroups = ToGroups;
DynamicMeshComponent->FastNotifyVertexAttributesUpdated(EMeshRenderAttributeFlags::VertexColors);
};
GetToolManager()->EmitObjectChange(this, MoveTemp(FrozenGroupsChange), ChangeText);
}
}
void UMeshGroupPaintTool::GrowCurrentGroupAction()
{
BeginChange();
int32 CurrentGroupID = FilterProperties->SetGroup;
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
FMeshFaceSelection InitialSelection(Mesh);
InitialSelection.Select([&](int32 tid) { return ActiveGroupSet->GetGroup(tid) == CurrentGroupID; });
FMeshFaceSelection ExpandSelection(InitialSelection);
ExpandSelection.ExpandToOneRingNeighbours([&](int32 tid) { return FrozenGroups.Contains(ActiveGroupSet->GetGroup(tid)) == false; });
TempROIBuffer.SetNum(0, EAllowShrinking::No);
ExpandSelection.SetDifference(InitialSelection, TempROIBuffer);
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
for (int32 tid : TempROIBuffer)
{
ActiveGroupSet->SetGroup(tid, CurrentGroupID, *GetSculptMesh());
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
EndChange();
}
void UMeshGroupPaintTool::ShrinkCurrentGroupAction()
{
BeginChange();
int32 CurrentGroupID = FilterProperties->SetGroup;
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
FMeshFaceSelection InitialSelection(Mesh);
InitialSelection.Select([&](int32 tid) { return ActiveGroupSet->GetGroup(tid) == CurrentGroupID; });
FMeshFaceSelection ContractSelection(InitialSelection);
ContractSelection.ContractBorderByOneRingNeighbours();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
InitialSelection.SetDifference(ContractSelection, TempROIBuffer);
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
for (int32 tid : TempROIBuffer)
{
// todo: could probably guess boundary groups here...
ActiveGroupSet->SetGroup(tid, 0, *GetSculptMesh());
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
EndChange();
}
void UMeshGroupPaintTool::ClearCurrentGroupAction()
{
BeginChange();
int32 CurrentGroupID = FilterProperties->SetGroup;
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Mesh->TriangleIndicesItr())
{
if (ActiveGroupSet->GetGroup(tid) == CurrentGroupID)
{
TempROIBuffer.Add(tid);
}
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
for (int32 tid : TempROIBuffer)
{
ActiveGroupSet->SetGroup(tid, 0, *GetSculptMesh());
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
EndChange();
}
void UMeshGroupPaintTool::FloodFillCurrentGroupAction()
{
BeginChange();
int32 SetGroupID = FilterProperties->SetGroup;
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Mesh->TriangleIndicesItr())
{
int32 GroupID = ActiveGroupSet->GetGroup(tid);
if (GroupID == 0 && GroupID != SetGroupID && FrozenGroups.Contains(GroupID) == false)
{
TempROIBuffer.Add(tid);
}
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
for (int32 tid : TempROIBuffer)
{
ActiveGroupSet->SetGroup(tid, SetGroupID, *GetSculptMesh());
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
EndChange();
}
void UMeshGroupPaintTool::ClearAllGroupsAction()
{
BeginChange();
const FDynamicMesh3* Mesh = DynamicMeshComponent->GetMesh();
TempROIBuffer.SetNum(0, EAllowShrinking::No);
for (int32 tid : Mesh->TriangleIndicesItr())
{
if (ActiveGroupSet->GetGroup(tid) != 0)
{
TempROIBuffer.Add(tid);
}
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
for (int32 tid : TempROIBuffer)
{
ActiveGroupSet->SetGroup(tid, 0, *GetSculptMesh());
}
ActiveGroupEditBuilder->SaveTriangles(TempROIBuffer);
DynamicMeshComponent->FastNotifyTriangleVerticesUpdated(TempROIBuffer, EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
EndChange();
}
//
// Change Tracking
//
void UMeshGroupPaintTool::BeginChange()
{
check(ActiveGroupEditBuilder == nullptr);
ActiveGroupEditBuilder = MakeUnique<FDynamicMeshGroupEditBuilder>(ActiveGroupSet.Get());
LongTransactions.Open(LOCTEXT("GroupPaintChange", "Group Stroke"), GetToolManager());
}
void UMeshGroupPaintTool::EndChange()
{
check(ActiveGroupEditBuilder);
TUniquePtr<FDynamicMeshGroupEdit> EditResult = ActiveGroupEditBuilder->ExtractResult();
ActiveGroupEditBuilder = nullptr;
TUniquePtr<TWrappedToolCommandChange<FMeshPolygroupChange>> NewChange = MakeUnique<TWrappedToolCommandChange<FMeshPolygroupChange>>();
NewChange->WrappedChange = MakeUnique<FMeshPolygroupChange>(MoveTemp(EditResult));
NewChange->BeforeModify = [this](bool bRevert)
{
this->WaitForPendingUndoRedoUpdate();
};
GetToolManager()->EmitObjectChange(DynamicMeshComponent, MoveTemp(NewChange), LOCTEXT("GroupPaintChange", "Group Stroke"));
LongTransactions.Close(GetToolManager());
// debug groups are invalid now
bDrawGroupsDataValid = false;
}
void UMeshGroupPaintTool::WaitForPendingUndoRedoUpdate()
{
if (bUndoUpdatePending)
{
bUndoUpdatePending = false;
}
}
void UMeshGroupPaintTool::OnDynamicMeshComponentChanged()
{
// update octree
FDynamicMesh3* Mesh = GetSculptMesh();
// make sure any previous async computations are done, and update the undo ROI
if (bUndoUpdatePending)
{
// we should never hit this anymore, because of pre-change calling WaitForPendingUndoRedoUpdate()
WaitForPendingUndoRedoUpdate();
// TODO: do we need to read from mesh change here??
//UE::Geometry::VertexToTriangleOneRing(Mesh, Change->Vertices, AccumulatedTriangleROI);
}
else
{
// TODO: do we need to read from mesh change here??
//UE::Geometry::VertexToTriangleOneRing(Mesh, Change->Vertices, AccumulatedTriangleROI);
}
// note that we have a pending update
bUndoUpdatePending = true;
}
void UMeshGroupPaintTool::PrecomputeFilterData()
{
const FDynamicMesh3* Mesh = GetSculptMesh();
TriNormals.SetNum(Mesh->MaxTriangleID());
ParallelFor(Mesh->MaxTriangleID(), [&](int32 tid)
{
if (Mesh->IsTriangle(tid))
{
TriNormals[tid] = Mesh->GetTriNormal(tid);
}
});
const FDynamicMeshNormalOverlay* Normals = Mesh->Attributes()->PrimaryNormals();
const FDynamicMeshUVOverlay* UVs = Mesh->Attributes()->PrimaryUV();
UVSeamEdges.SetNum(Mesh->MaxEdgeID());
NormalSeamEdges.SetNum(Mesh->MaxEdgeID());
ParallelFor(Mesh->MaxEdgeID(), [&](int32 eid)
{
if (Mesh->IsEdge(eid))
{
UVSeamEdges[eid] = UVs->IsSeamEdge(eid);
NormalSeamEdges[eid] = Normals->IsSeamEdge(eid);
}
});
}
void UMeshGroupPaintTool::OnSelectedGroupLayerChanged()
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("ChangeActiveGroupLayer", "Change Polygroup Layer"));
TArray<int32> InitialFrozenGroups = FrozenGroups;
int32 ActiveLayerIndex = (ActiveGroupSet) ? ActiveGroupSet->GetPolygroupIndex() : -1;
UpdateActiveGroupLayer();
int32 NewLayerIndex = (ActiveGroupSet) ? ActiveGroupSet->GetPolygroupIndex() : -1;
if (ActiveLayerIndex != NewLayerIndex)
{
// clear frozen groups
EmitFrozenGroupsChange(InitialFrozenGroups, FrozenGroups, LOCTEXT("ClearAllFrozenGroups", "Clear Frozen Groups"));
TUniquePtr<TSimpleValueLambdaChange<int32>> GroupLayerChange = MakeUnique<TSimpleValueLambdaChange<int32>>();
GroupLayerChange->FromValue = ActiveLayerIndex;
GroupLayerChange->ToValue = NewLayerIndex;
GroupLayerChange->ValueChangeFunc = [this](UObject*, int32 FromIndex, int32 ToIndex, bool)
{
this->PolygroupLayerProperties->SetSelectedFromPolygroupIndex(ToIndex);
this->PolygroupLayerProperties->SilentUpdateWatched(); // to prevent OnSelectedGroupLayerChanged() from being called immediately
this->UpdateActiveGroupLayer();
};
GetToolManager()->EmitObjectChange(this, MoveTemp(GroupLayerChange), LOCTEXT("ChangeActiveGroupLayer", "Change Polygroup Layer"));
}
GetToolManager()->EndUndoTransaction();
}
void UMeshGroupPaintTool::UpdateActiveGroupLayer()
{
if (PolygroupLayerProperties->HasSelectedPolygroup() == false)
{
ActiveGroupSet = MakeUnique<UE::Geometry::FPolygroupSet>(GetSculptMesh());
}
else
{
FName SelectedName = PolygroupLayerProperties->ActiveGroupLayer;
FDynamicMeshPolygroupAttribute* FoundAttrib = UE::Geometry::FindPolygroupLayerByName(*GetSculptMesh(), SelectedName);
ensureMsgf(FoundAttrib, TEXT("Selected Attribute Not Found! Falling back to Default group layer."));
ActiveGroupSet = MakeUnique<UE::Geometry::FPolygroupSet>(GetSculptMesh(), FoundAttrib);
}
// need to reset everything here...
FrozenGroups.Reset();
// update colors
DynamicMeshComponent->FastNotifyVertexAttributesUpdated(EMeshRenderAttributeFlags::VertexColors);
GetToolManager()->PostInvalidation();
}
void UMeshGroupPaintTool::UpdateSubToolType(EMeshGroupPaintInteractionType NewType)
{
// Currenly we mirror base-brush properties in UGroupPaintBrushFilterProperties, so we never
// want to show both
//bool bSculptPropsVisible = (NewType == EMeshGroupPaintInteractionType::Brush);
//SetToolPropertySourceEnabled(UMeshSculptToolBase::BrushProperties, bSculptPropsVisible);
SetToolPropertySourceEnabled(UMeshSculptToolBase::BrushProperties, false);
SetToolPropertySourceEnabled(FilterProperties, true);
SetBrushOpPropsVisibility(false);
}
void UMeshGroupPaintTool::UpdateBrushType(EMeshGroupPaintBrushType BrushType)
{
static const FText BaseMessage = LOCTEXT("OnStartTool", "Hold Shift to Erase. [/] and S/D change Size (+Shift to small-step). Shift+Q for New Group, Shift+G to pick Group, Shift+F to Freeze Group.");
FTextBuilder Builder;
Builder.AppendLine(BaseMessage);
SetActivePrimaryBrushType((int32)BrushType);
SetToolPropertySourceEnabled(GizmoProperties, false);
GetToolManager()->DisplayMessage(Builder.ToText(), EToolMessageLevel::UserNotification);
}
void UMeshGroupPaintTool::RequestAction(EMeshGroupPaintToolActions ActionType)
{
if (!bHavePendingAction)
{
PendingAction = ActionType;
bHavePendingAction = true;
}
}
void UMeshGroupPaintTool::ApplyAction(EMeshGroupPaintToolActions ActionType)
{
switch (ActionType)
{
case EMeshGroupPaintToolActions::ClearFrozen:
ClearAllFrozenGroups();
break;
case EMeshGroupPaintToolActions::FreezeCurrent:
ToggleFrozenGroup(FilterProperties->SetGroup);
break;
case EMeshGroupPaintToolActions::FreezeOthers:
FreezeOtherGroups(FilterProperties->SetGroup);
break;
case EMeshGroupPaintToolActions::GrowCurrent:
GrowCurrentGroupAction();
break;
case EMeshGroupPaintToolActions::ShrinkCurrent:
ShrinkCurrentGroupAction();
break;
case EMeshGroupPaintToolActions::ClearCurrent:
ClearCurrentGroupAction();
break;
case EMeshGroupPaintToolActions::FloodFillCurrent:
FloodFillCurrentGroupAction();
break;
case EMeshGroupPaintToolActions::ClearAll:
ClearAllGroupsAction();
break;
}
}
#undef LOCTEXT_NAMESPACE