Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolsetExp/Source/MeshModelingToolsExp/Private/EditNormalsTool.cpp
tyson brochu afd2b3b5c6 Fix crash when accepting Normal tool on a mesh with degenerate triangles
#jira UE-144819
#rb jimmy.andrews
#preflight 625ee1653e0f6f80adb59027

#ROBOMERGE-AUTHOR: tyson.brochu
#ROBOMERGE-SOURCE: CL 19811381 in //UE5/Release-5.0/... via CL 19816562
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v939-19570697)

[CL 19819524 by tyson brochu in ue5-main branch]
2022-04-19 18:10:11 -04:00

290 lines
8.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditNormalsTool.h"
#include "InteractiveToolManager.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "ModelingToolTargetUtil.h"
#include "DynamicMesh/DynamicMesh3.h"
#include "BaseBehaviors/MultiClickSequenceInputBehavior.h"
#include "Selection/SelectClickedAction.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "DynamicMeshToMeshDescription.h"
#include "InteractiveGizmoManager.h"
#include "AssetUtils/MeshDescriptionUtil.h"
#include "Engine/StaticMesh.h"
#include "Components/StaticMeshComponent.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "ModelingToolTargetUtil.h"
using namespace UE::Geometry;
#define LOCTEXT_NAMESPACE "UEditNormalsTool"
/*
* ToolBuilder
*/
UMultiSelectionMeshEditingTool* UEditNormalsToolBuilder::CreateNewTool(const FToolBuilderState& SceneState) const
{
return NewObject<UEditNormalsTool>(SceneState.ToolManager);
}
/*
* Tool
*/
UEditNormalsToolProperties::UEditNormalsToolProperties()
{
bFixInconsistentNormals = false;
bInvertNormals = false;
bRecomputeNormals = true;
NormalCalculationMethod = ENormalCalculationMethod::AreaAngleWeighting;
SplitNormalMethod = ESplitNormalMethod::UseExistingTopology;
SharpEdgeAngleThreshold = 60;
bAllowSharpVertices = false;
}
UEditNormalsAdvancedProperties::UEditNormalsAdvancedProperties()
{
}
UEditNormalsTool::UEditNormalsTool()
{
}
void UEditNormalsTool::Setup()
{
UInteractiveTool::Setup();
// hide input StaticMeshComponent
for (int32 ComponentIdx = 0, NumTargets = Targets.Num(); ComponentIdx < NumTargets; ComponentIdx++)
{
UE::ToolTarget::HideSourceObject(Targets[ComponentIdx]);
}
BasicProperties = NewObject<UEditNormalsToolProperties>(this, TEXT("Mesh Normals Settings"));
BasicProperties->RestoreProperties(this);
AddToolPropertySource(BasicProperties);
AdvancedProperties = NewObject<UEditNormalsAdvancedProperties>(this, TEXT("Advanced Settings"));
AdvancedProperties->RestoreProperties(this);
AddToolPropertySource(AdvancedProperties);
// initialize the PreviewMesh+BackgroundCompute object
UpdateNumPreviews();
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->InvalidateResult();
}
SetToolDisplayName(LOCTEXT("ToolName", "Edit Normals"));
GetToolManager()->DisplayMessage(
LOCTEXT("OnStartTool", "Configure or Recalculate Normals on a Mesh (disables autogenerated Normals)"),
EToolMessageLevel::UserNotification);
}
void UEditNormalsTool::UpdateNumPreviews()
{
int32 CurrentNumPreview = Previews.Num();
int32 TargetNumPreview = Targets.Num();
if (TargetNumPreview < CurrentNumPreview)
{
for (int32 PreviewIdx = CurrentNumPreview - 1; PreviewIdx >= TargetNumPreview; PreviewIdx--)
{
Previews[PreviewIdx]->Cancel();
}
Previews.SetNum(TargetNumPreview);
OriginalDynamicMeshes.SetNum(TargetNumPreview);
}
else
{
OriginalDynamicMeshes.SetNum(TargetNumPreview);
for (int32 PreviewIdx = CurrentNumPreview; PreviewIdx < TargetNumPreview; PreviewIdx++)
{
UEditNormalsOperatorFactory *OpFactory = NewObject<UEditNormalsOperatorFactory>();
OpFactory->Tool = this;
OpFactory->ComponentIndex = PreviewIdx;
OriginalDynamicMeshes[PreviewIdx] = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
FMeshDescriptionToDynamicMesh Converter;
Converter.Convert(UE::ToolTarget::GetMeshDescription(Targets[PreviewIdx]), *OriginalDynamicMeshes[PreviewIdx]);
UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(NewObject<UMeshOpPreviewWithBackgroundCompute>(OpFactory, "Preview"));
Preview->Setup(GetTargetWorld(), OpFactory);
ToolSetupUtil::ApplyRenderingConfigurationToPreview(Preview->PreviewMesh, Targets[PreviewIdx]);
Preview->PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated);
const FComponentMaterialSet MaterialSet = UE::ToolTarget::GetMaterialSet(Targets[PreviewIdx]);
Preview->ConfigureMaterials(MaterialSet.Materials,
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager())
);
Preview->SetVisibility(true);
}
}
}
void UEditNormalsTool::OnShutdown(EToolShutdownType ShutdownType)
{
BasicProperties->SaveProperties(this);
AdvancedProperties->SaveProperties(this);
// Restore (unhide) the source meshes
for (int32 ComponentIdx = 0, NumTargets = Targets.Num(); ComponentIdx < NumTargets; ComponentIdx++)
{
UE::ToolTarget::ShowSourceObject(Targets[ComponentIdx]);
}
TArray<FDynamicMeshOpResult> Results;
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Results.Add(Preview->Shutdown());
}
if (ShutdownType == EToolShutdownType::Accept)
{
GenerateAsset(Results);
}
}
TUniquePtr<FDynamicMeshOperator> UEditNormalsOperatorFactory::MakeNewOperator()
{
TUniquePtr<FEditNormalsOp> NormalsOp = MakeUnique<FEditNormalsOp>();
NormalsOp->bFixInconsistentNormals = Tool->BasicProperties->bFixInconsistentNormals;
NormalsOp->bInvertNormals = Tool->BasicProperties->bInvertNormals;
NormalsOp->bRecomputeNormals = Tool->BasicProperties->bRecomputeNormals;
NormalsOp->SplitNormalMethod = Tool->BasicProperties->SplitNormalMethod;
NormalsOp->bAllowSharpVertices = Tool->BasicProperties->bAllowSharpVertices;
NormalsOp->NormalCalculationMethod = Tool->BasicProperties->NormalCalculationMethod;
NormalsOp->NormalSplitThreshold = Tool->BasicProperties->SharpEdgeAngleThreshold;
const FTransform LocalToWorld = (FTransform) UE::ToolTarget::GetLocalToWorldTransform(Tool->Targets[ComponentIndex]);
NormalsOp->OriginalMesh = Tool->OriginalDynamicMeshes[ComponentIndex];
NormalsOp->SetTransform(LocalToWorld);
return NormalsOp;
}
void UEditNormalsTool::Render(IToolsContextRenderAPI* RenderAPI)
{
}
void UEditNormalsTool::OnTick(float DeltaTime)
{
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->Tick(DeltaTime);
}
}
#if WITH_EDITOR
void UEditNormalsTool::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
UpdateNumPreviews();
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->InvalidateResult();
}
}
#endif
void UEditNormalsTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
UpdateNumPreviews();
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
Preview->InvalidateResult();
}
}
bool UEditNormalsTool::HasAccept() const
{
return true;
}
bool UEditNormalsTool::CanAccept() const
{
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
{
if (!Preview->HaveValidResult())
{
return false;
}
}
return Super::CanAccept();
}
void UEditNormalsTool::GenerateAsset(const TArray<FDynamicMeshOpResult>& Results)
{
GetToolManager()->BeginUndoTransaction(LOCTEXT("EditNormalsToolTransactionName", "Edit Normals Tool"));
check(Results.Num() == Targets.Num());
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
// disable auto-generated normals StaticMesh build setting
UStaticMeshComponent* StaticMeshComponent = Cast<UStaticMeshComponent>(UE::ToolTarget::GetTargetComponent(Targets[ComponentIdx]));
if (StaticMeshComponent != nullptr)
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (ensure(StaticMesh != nullptr))
{
StaticMesh->Modify();
UE::MeshDescription::FStaticMeshBuildSettingChange SettingsChange;
SettingsChange.AutoGeneratedNormals = UE::MeshDescription::EBuildSettingBoolChange::Disable;
UE::MeshDescription::ConfigureBuildSettings(StaticMesh, 0, SettingsChange);
}
}
const FDynamicMesh3* NewDynamicMesh = Results[ComponentIdx].Mesh.Get();
if (NewDynamicMesh)
{
if (bool bTopologyChanged = BasicProperties->WillTopologyChange())
{
// Tool may have changed the topology of the normal overlay (according to the specified tool properties), so we can't simply update the target mesh.
// Passing in bTopologyChanged = true will trigger the slower Convert function rather than the fast Update function.
UE::ToolTarget::CommitMeshDescriptionUpdateViaDynamicMesh(Targets[ComponentIdx], *NewDynamicMesh, bTopologyChanged);
}
else
{
// The tool didn't change the overlay topology so there's a chance we can do a fast path Update of the normal attributes.
// This function will still check if there is a mismatch between the dynamic mesh and target mesh in terms of triangles/vertices, and
// if so it will do the full conversion.
constexpr bool bUpdateTangents = false;
UE::ToolTarget::CommitDynamicMeshNormalsUpdate(Targets[ComponentIdx], NewDynamicMesh, bUpdateTangents);
}
}
}
GetToolManager()->EndUndoTransaction();
}
#undef LOCTEXT_NAMESPACE