Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Private/RemoveOccludedTrianglesTool.cpp

550 lines
18 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RemoveOccludedTrianglesTool.h"
#include "InteractiveToolManager.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "Polygroups/PolygroupUtil.h"
#include "BaseBehaviors/MultiClickSequenceInputBehavior.h"
#include "Selection/SelectClickedAction.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "DynamicMeshToMeshDescription.h"
#include "InteractiveGizmoManager.h"
#include "Misc/MessageDialog.h"
#if WITH_EDITOR
#include "Misc/ScopedSlowTask.h"
#endif
#include "TargetInterfaces/MaterialProvider.h"
#include "TargetInterfaces/MeshDescriptionCommitter.h"
#include "TargetInterfaces/MeshDescriptionProvider.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "ToolTargetManager.h"
#include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types)
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "URemoveOccludedTrianglesTool"
/*
* ToolBuilder
*/
const FToolTargetTypeRequirements& URemoveOccludedTrianglesToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UMaterialProvider::StaticClass(),
UMeshDescriptionCommitter::StaticClass(),
UMeshDescriptionProvider::StaticClass(),
UPrimitiveComponentBackedTarget::StaticClass()
});
return TypeRequirements;
}
bool URemoveOccludedTrianglesToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
return SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()) > 0;
}
UInteractiveTool* URemoveOccludedTrianglesToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
{
URemoveOccludedTrianglesTool* NewTool = NewObject<URemoveOccludedTrianglesTool>(SceneState.ToolManager);
TArray<TObjectPtr<UToolTarget>> Targets = SceneState.TargetManager->BuildAllSelectedTargetable(SceneState, GetTargetRequirements());
NewTool->SetTargets(MoveTemp(Targets));
NewTool->SetWorld(SceneState.World);
return NewTool;
}
/*
* Tool
*/
URemoveOccludedTrianglesToolProperties::URemoveOccludedTrianglesToolProperties()
{
}
URemoveOccludedTrianglesAdvancedProperties::URemoveOccludedTrianglesAdvancedProperties()
{
}
URemoveOccludedTrianglesTool::URemoveOccludedTrianglesTool()
{
SetToolDisplayName(LOCTEXT("ProjectToTargetToolName", "Jacket"));
}
void URemoveOccludedTrianglesTool::SetWorld(UWorld* World)
{
this->TargetWorld = World;
}
void URemoveOccludedTrianglesTool::Setup()
{
UInteractiveTool::Setup();
// hide input StaticMeshComponent
for (int Idx = 0; Idx < Targets.Num(); Idx++)
{
TargetComponentInterface(Idx)->SetOwnerVisibility(false);
}
// find components with the same source asset
TArray<int32> MapToFirstOccurrences;
bool bAnyHasSameSource = GetMapToSharedSourceData(MapToFirstOccurrences);
TargetToPreviewIdx.SetNum(Targets.Num());
PreviewToTargetIdx.Reset();
for (int32 ComponentIdx = 0; ComponentIdx < MapToFirstOccurrences.Num(); ComponentIdx++)
{
if (MapToFirstOccurrences[ComponentIdx] == ComponentIdx)
{
int32 NumPreviews = PreviewToTargetIdx.Num();
PreviewToTargetIdx.Add(ComponentIdx);
TargetToPreviewIdx[ComponentIdx] = NumPreviews;
}
else
{
TargetToPreviewIdx[ComponentIdx] = TargetToPreviewIdx[MapToFirstOccurrences[ComponentIdx]];
}
}
if (bAnyHasSameSource)
{
GetToolManager()->DisplayMessage(
LOCTEXT("JacketingMultipleAssetWithSameSource", "WARNING: Multiple meshes in your selection use the same source asset! Triangles will be conservatively removed from these meshes only when they are occluded in every selected instance."),
EToolMessageLevel::UserWarning);
}
// initialize the PreviewMesh+BackgroundCompute object
SetupPreviews();
BasicProperties = NewObject<URemoveOccludedTrianglesToolProperties>(this, TEXT("Remove Occluded Triangle Settings"));
AdvancedProperties = NewObject<URemoveOccludedTrianglesAdvancedProperties>(this, TEXT("Advanced Settings"));
MakePolygroupLayerProperties();
BasicProperties->WatchProperty(BasicProperties->Action,
[this](EOccludedAction Action) { SetToolPropertySourceEnabled(PolygroupLayersProperties, Action == EOccludedAction::SetNewGroup); }
);
// initialize our properties
AddToolPropertySource(BasicProperties);
AddToolPropertySource(PolygroupLayersProperties);
AddToolPropertySource(AdvancedProperties);
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->InvalidateResult();
}
GetToolManager()->DisplayMessage(
LOCTEXT("RemoveOccludedTrianglesToolDescription", "Remove triangles that are fully contained within the selected Meshes, and hence cannot be visible with opaque shading."),
EToolMessageLevel::UserNotification);
}
void URemoveOccludedTrianglesTool::MakePolygroupLayerProperties()
{
PolygroupLayersProperties = NewObject<UPolygroupLayersProperties>(this, TEXT("Polygroup Layer"));
auto GetGroupLayerNames = [](const FDynamicMesh3& Mesh)
{
TSet<FName> Names;
if (Mesh.Attributes())
{
for (int32 LayerIdx = 0; LayerIdx < Mesh.Attributes()->NumPolygroupLayers(); LayerIdx++)
{
FName Name = Mesh.Attributes()->GetPolygroupLayer(LayerIdx)->GetName();
Names.Add(Name);
}
}
return Names;
};
check(OriginalDynamicMeshes.Num() > 0);
TSet<FName> CommonLayerNames = GetGroupLayerNames(*OriginalDynamicMeshes[0]);
for (int32 Idx = 1; Idx < OriginalDynamicMeshes.Num(); Idx++)
{
CommonLayerNames = CommonLayerNames.Intersect(GetGroupLayerNames(*OriginalDynamicMeshes[Idx]));
}
PolygroupLayersProperties->InitializeGroupLayers(CommonLayerNames);
}
void URemoveOccludedTrianglesTool::SetupPreviews()
{
int32 NumTargets = Targets.Num();
int32 NumPreviews = PreviewToTargetIdx.Num();
#if WITH_EDITOR
static const FText SlowTaskText = LOCTEXT("RemoveOccludedTrianglesInit", "Building mesh occlusion data...");
FScopedSlowTask SlowTask(NumTargets, SlowTaskText);
SlowTask.MakeDialog();
// Declare progress shortcut lambdas
auto EnterProgressFrame = [&SlowTask](float Progress)
{
SlowTask.EnterProgressFrame(Progress);
};
#else
auto EnterProgressFrame = [](float Progress) {};
#endif
// create a "magic pink" secondary material to mark occluded faces (to be used if we are setting a new triangle group instead of removing faces)
UMaterialInterface* OccludedMaterial = ToolSetupUtil::GetSelectionMaterial(FLinearColor(0.9f, 0.1f, 0.9f), GetToolManager());
OccludedGroupIDs.Init(-1, NumPreviews);
OccludedGroupLayers.Init(-1, NumPreviews);
OccluderTrees.SetNum(NumTargets);
OccluderWindings.SetNum(NumTargets);
OccluderTransforms.SetNum(NumTargets);
OriginalDynamicMeshes.SetNum(NumPreviews);
PreviewToCopyIdx.Reset(); PreviewToCopyIdx.SetNum(NumPreviews);
for (int32 TargetIdx = 0; TargetIdx < NumTargets; TargetIdx++)
{
EnterProgressFrame(1);
int PreviewIdx = TargetToPreviewIdx[TargetIdx];
// used to choose which triangles need to use the special "OccludedMaterial" secondary material, when the Occluded Action == SetNewGroup
auto IsOccludedGroupFn = [this, PreviewIdx](const FDynamicMesh3* Mesh, int32 TriangleID)
{
int GroupID = OccludedGroupIDs[PreviewIdx];
if (GroupID >= 0)
{
int LayerIndex = OccludedGroupLayers[PreviewIdx];
if (LayerIndex < 0)
{
return Mesh->GetTriangleGroup(TriangleID) == GroupID;
}
else
{
check(Mesh->HasAttributes() && Mesh->Attributes()->NumPolygroupLayers() > LayerIndex);
return Mesh->Attributes()->GetPolygroupLayer(LayerIndex)->GetValue(TriangleID) == GroupID;
}
}
return false;
};
bool bHasConverted = OriginalDynamicMeshes[PreviewIdx].IsValid();
if (!bHasConverted)
{
URemoveOccludedTrianglesOperatorFactory *OpFactory = NewObject<URemoveOccludedTrianglesOperatorFactory>();
OpFactory->Tool = this;
OpFactory->PreviewIdx = PreviewIdx;
OriginalDynamicMeshes[PreviewIdx] = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
FMeshDescriptionToDynamicMesh Converter;
Converter.Convert(TargetMeshProviderInterface(TargetIdx)->GetMeshDescription(), *OriginalDynamicMeshes[PreviewIdx]);
UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(NewObject<UMeshOpPreviewWithBackgroundCompute>(OpFactory, "Preview"));
Preview->Setup(this->TargetWorld, OpFactory);
ModelingComponents: Clean up DynamicMeshComponent API. Update Component and Proxy handling of Tangents to use Attribute Overlay if available. Update affected Tools and also convert most of the affected Tools to use UE::ToolTarget helper functions. - Add UE::ToolTarget::CommitMaterialSetUpdate() and ::CommitDynamicMeshUpdate(). ::GetDynamicMeshCopy() can now return tangents if requested. - Add IMeshDescriptionProvider::CalculateAutoGeneratedAttributes(). Default implementation does nothing, UStaticMeshComponentToolTarget implementation initializes auto-generated MeshDescription attributes. Used in ::GetDynamicMeshCopy() to get tangents (but requires a MeshDescription copy). - Clean up handling of Tangents in Simple/OctreeDynamicMeshComponent. Add local MakeTangentsFunc() to generate the Tangents lambda, handle different cases and no-tangents fallbacks consistently. - UDynamicMesh: add optional info arguments to EditMesh() and ChangeInfo struct. Add support for deferring change events from Edit funcs. - Remove UBaseDynamicMeshComponent::InitializeMesh(), ::Bake() APIs, and add ::SetMesh(). Implement in Simple/Octree implementations, update all Tools that used those APIs. - Add USimpleDynamicMeshComponent::ProcessMesh(), EditMesh(). These are now the preferred ways to read/write mesh. - Update USimpleDynamicMeshComponent tangents handling. Externally-computed tangents are now taken directly from the FDynamicMesh3 attribute set. Autogenerated tangents are still computed and stored in an internal FMeshTangentsf, but this is no longer exposed for external updates. - Remove UPreviewMesh pass-through functions for Tangents access, InitializeMesh() and Bake(). Add ProcessMesh() - Update all affected Tools. In most cases these Tools have also been converted to use ModelingToolTargetUtil functions, instead of direct ToolTarget interface casting. #rb none #rnx #jira none #preflight 60c3e71d3e1b3c00015668af [CL 16650666 by Ryan Schmidt in ue5-main branch]
2021-06-11 22:39:18 -04:00
Preview->PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated);
FComponentMaterialSet MaterialSet;
TargetMaterialInterface(TargetIdx)->GetMaterialSet(MaterialSet);
Preview->ConfigureMaterials(MaterialSet.Materials,
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager())
);
Preview->PreviewMesh->SetTransform(TargetComponentInterface(TargetIdx)->GetWorldTransform());
Preview->PreviewMesh->UpdatePreview(OriginalDynamicMeshes[PreviewIdx].Get());
Preview->SetVisibility(true);
OccluderTrees[TargetIdx] = MakeShared<FDynamicMeshAABBTree3, ESPMode::ThreadSafe>(OriginalDynamicMeshes[PreviewIdx].Get());
OccluderWindings[TargetIdx] = MakeShared<TFastWindingTree<FDynamicMesh3>, ESPMode::ThreadSafe>(OccluderTrees[TargetIdx].Get());
OccluderTransforms[TargetIdx] = (UE::Geometry::FTransform3d)TargetComponentInterface(TargetIdx)->GetWorldTransform();
// configure secondary render material
Preview->SecondaryMaterial = OccludedMaterial;
// Set occluded layer index and group IDs
Previews[PreviewIdx]->OnOpCompleted.AddLambda([this, PreviewIdx](const FDynamicMeshOperator* UncastOp) {
const FRemoveOccludedTrianglesOp* Op = static_cast<const FRemoveOccludedTrianglesOp*>(UncastOp);
OccludedGroupIDs[PreviewIdx] = Op->CreatedGroupID;
OccludedGroupLayers[PreviewIdx] = Op->CreatedGroupLayerIndex;
});
// enable secondary triangle buffers
Preview->PreviewMesh->EnableSecondaryTriangleBuffers(MoveTemp(IsOccludedGroupFn));
}
else
{
// already did the conversion for a full UMeshOpPreviewWithBackgroundCompute -- just make a light version of that and hook it up to copy the other's work
int CopyIdx = PreviewCopies.Num();
UPreviewMesh* PreviewMesh = PreviewCopies.Add_GetRef(NewObject<UPreviewMesh>(this));
PreviewMesh->CreateInWorld(this->TargetWorld, TargetComponentInterface(TargetIdx)->GetWorldTransform());
PreviewToCopyIdx[PreviewIdx].Add(CopyIdx);
PreviewMesh->UpdatePreview(OriginalDynamicMeshes[PreviewIdx].Get());
FComponentMaterialSet MaterialSet;
TargetMaterialInterface(TargetIdx)->GetMaterialSet(MaterialSet);
PreviewMesh->SetMaterials(MaterialSet.Materials);
PreviewMesh->SetVisible(true);
OccluderTrees[TargetIdx] = OccluderTrees[PreviewToTargetIdx[PreviewIdx]];
OccluderWindings[TargetIdx] = OccluderWindings[PreviewToTargetIdx[PreviewIdx]];
OccluderTransforms[TargetIdx] = (UE::Geometry::FTransform3d)TargetComponentInterface(TargetIdx)->GetWorldTransform();
PreviewMesh->SetSecondaryRenderMaterial(OccludedMaterial);
PreviewMesh->EnableSecondaryTriangleBuffers(MoveTemp(IsOccludedGroupFn));
Previews[PreviewIdx]->OnMeshUpdated.AddLambda([this, CopyIdx](UMeshOpPreviewWithBackgroundCompute* Compute) {
PreviewCopies[CopyIdx]->UpdatePreview(Compute->PreviewMesh->GetPreviewDynamicMesh());
});
}
}
}
void URemoveOccludedTrianglesTool::Shutdown(EToolShutdownType ShutdownType)
{
if (ShutdownType == EToolShutdownType::Accept && AreAllTargetsValid() == false)
{
UE_LOG(LogTemp, Error, TEXT("Tool Target has become Invalid (possibly it has been Force Deleted). Aborting Tool."));
ShutdownType = EToolShutdownType::Cancel;
}
// Restore (unhide) the source meshes
for (int ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(true);
}
// clear all the preview copies
for (UPreviewMesh* PreviewMesh : PreviewCopies)
{
PreviewMesh->SetVisible(false);
PreviewMesh->Disconnect();
PreviewMesh = nullptr;
}
PreviewCopies.Empty();
TArray<FDynamicMeshOpResult> Results;
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Results.Add(Preview->Shutdown());
}
if (ShutdownType == EToolShutdownType::Accept)
{
GenerateAsset(Results);
}
}
TUniquePtr<FDynamicMeshOperator> URemoveOccludedTrianglesOperatorFactory::MakeNewOperator()
{
TUniquePtr<FRemoveOccludedTrianglesOp> Op = MakeUnique<FRemoveOccludedTrianglesOp>();
Op->NormalOffset = Tool->AdvancedProperties->NormalOffset;
Op->bSetTriangleGroupInsteadOfRemoving = Tool->BasicProperties->Action == EOccludedAction::SetNewGroup;
Op->ActiveGroupLayer = Tool->PolygroupLayersProperties->ActiveGroupLayer;
Op->bActiveGroupLayerIsDefault = !Tool->PolygroupLayersProperties->HasSelectedPolygroup();
switch (Tool->BasicProperties->OcclusionTestMethod)
{
case EOcclusionCalculationUIMode::GeneralizedWindingNumber:
Op->InsideMode = EOcclusionCalculationMode::FastWindingNumber;
break;
case EOcclusionCalculationUIMode::RaycastOcclusionSamples:
Op->InsideMode = EOcclusionCalculationMode::SimpleOcclusionTest;
break;
default:
ensure(false); // all cases should be handled
}
switch (Tool->BasicProperties->TriangleSampling)
{
case EOcclusionTriangleSamplingUIMode::Vertices:
Op->TriangleSamplingMethod = EOcclusionTriangleSampling::Vertices;
break;
// Centroids sampling not exposed in UI for now
// case EOcclusionTriangleSamplingUIMode::Centroids:
// Op->TriangleSamplingMethod = EOcclusionTriangleSampling::Centroids;
// break;
case EOcclusionTriangleSamplingUIMode::VerticesAndCentroids:
Op->TriangleSamplingMethod = EOcclusionTriangleSampling::VerticesAndCentroids;
break;
default:
ensure(false);
}
Op->WindingIsoValue = Tool->BasicProperties->WindingIsoValue;
int ComponentIndex = Tool->PreviewToTargetIdx[PreviewIdx];
FTransform LocalToWorld = Tool->TargetComponentInterface(ComponentIndex)->GetWorldTransform();
Op->OriginalMesh = Tool->OriginalDynamicMeshes[PreviewIdx];
if (Tool->BasicProperties->bOnlySelfOcclude)
{
int32 TargetIdx = Tool->PreviewToTargetIdx[PreviewIdx];
Op->OccluderTrees.Add(Tool->OccluderTrees[TargetIdx]);
Op->OccluderWindings.Add(Tool->OccluderWindings[TargetIdx]);
Op->OccluderTransforms.Add(UE::Geometry::FTransform3d::Identity());
}
else
{
Op->OccluderTrees = Tool->OccluderTrees;
Op->OccluderWindings = Tool->OccluderWindings;
Op->OccluderTransforms = Tool->OccluderTransforms;
}
Op->AddRandomRays = Tool->BasicProperties->AddRandomRays;
Op->AddTriangleSamples = Tool->BasicProperties->AddTriangleSamples;
Op->ShrinkRemoval = Tool->BasicProperties->ShrinkRemoval;
Op->MinAreaConnectedComponent = Tool->BasicProperties->MinAreaIsland;
Op->MinTriCountConnectedComponent = Tool->BasicProperties->MinTriCountIsland;
Op->SetTransform(LocalToWorld);
Op->MeshTransforms.Add((UE::Geometry::FTransform3d)LocalToWorld);
for (int32 CopyIdx : Tool->PreviewToCopyIdx[PreviewIdx])
{
Op->MeshTransforms.Add((UE::Geometry::FTransform3d)Tool->PreviewCopies[CopyIdx]->GetTransform());
}
return Op;
}
void URemoveOccludedTrianglesTool::OnTick(float DeltaTime)
{
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->Tick(DeltaTime);
}
// copy working material state to corresponding copies
for (int PreviewIdx = 0; PreviewIdx < Previews.Num(); PreviewIdx++)
{
UMeshOpPreviewWithBackgroundCompute* Preview = Previews[PreviewIdx];
bool bIsWorking = Preview->IsUsingWorkingMaterial();
for (int CopyIdx : PreviewToCopyIdx[PreviewIdx])
{
if (bIsWorking)
{
PreviewCopies[CopyIdx]->SetOverrideRenderMaterial(Preview->WorkingMaterial);
PreviewCopies[CopyIdx]->ClearSecondaryRenderMaterial();
}
else
{
PreviewCopies[CopyIdx]->ClearOverrideRenderMaterial();
PreviewCopies[CopyIdx]->SetSecondaryRenderMaterial(Preview->SecondaryMaterial);
}
}
}
}
#if WITH_EDITOR
void URemoveOccludedTrianglesTool::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->InvalidateResult();
}
}
#endif
void URemoveOccludedTrianglesTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->InvalidateResult();
}
}
bool URemoveOccludedTrianglesTool::CanAccept() const
{
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
if (!Preview->HaveValidResult())
{
return false;
}
}
return Super::CanAccept();
}
void URemoveOccludedTrianglesTool::GenerateAsset(const TArray<FDynamicMeshOpResult>& Results)
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("RemoveOccludedTrianglesToolTransactionName", "Remove Occluded Triangles"));
check(Results.Num() == Previews.Num());
// check if we entirely remove away any meshes
bool bWantDestroy = false;
for (int32 PreviewIdx = 0; PreviewIdx < Previews.Num(); PreviewIdx++)
{
bWantDestroy = bWantDestroy || (Results[PreviewIdx].Mesh.Get()->TriangleCount() == 0);
}
// if so ask user what to do
if (bWantDestroy)
{
FText Title = LOCTEXT("RemoveOccludedDestroyTitle", "Delete mesh components?");
EAppReturnType::Type Ret = FMessageDialog::Open(EAppMsgType::YesNo,
LOCTEXT("RemoveOccludedDestroyQuestion", "Jacketing has removed all triangles from some meshes. Actually destroy these mesh components?"), &Title);
if (Ret == EAppReturnType::No)
{
bWantDestroy = false;
}
}
for (int32 PreviewIdx = 0; PreviewIdx < Previews.Num(); PreviewIdx++)
{
check(Results[PreviewIdx].Mesh.Get() != nullptr);
int ComponentIdx = PreviewToTargetIdx[PreviewIdx];
if (Results[PreviewIdx].Mesh.Get()->TriangleCount() == 0)
{
if (bWantDestroy)
{
for (int TargetIdx = 0; TargetIdx < Targets.Num(); TargetIdx++)
{
if (TargetToPreviewIdx[TargetIdx] == PreviewIdx)
{
TargetComponentInterface(TargetIdx)->GetOwnerComponent()->DestroyComponent();
}
}
}
continue;
}
TargetMeshCommitterInterface(ComponentIdx)->CommitMeshDescription([&Results, &PreviewIdx, this](const IMeshDescriptionCommitter::FCommitterParams& CommitParams)
{
FDynamicMeshToMeshDescription Converter;
Converter.Convert(Results[PreviewIdx].Mesh.Get(), *CommitParams.MeshDescriptionOut);
});
}
GetToolManager()->EndUndoTransaction();
}
#undef LOCTEXT_NAMESPACE