// 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(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(this, TEXT("Mesh Normals Settings")); BasicProperties->RestoreProperties(this); AddToolPropertySource(BasicProperties); AdvancedProperties = NewObject(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(); OpFactory->Tool = this; OpFactory->ComponentIndex = PreviewIdx; OriginalDynamicMeshes[PreviewIdx] = MakeShared(); FMeshDescriptionToDynamicMesh Converter; Converter.Convert(UE::ToolTarget::GetMeshDescription(Targets[PreviewIdx]), *OriginalDynamicMeshes[PreviewIdx]); UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(NewObject(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 Results; for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews) { Results.Add(Preview->Shutdown()); } if (ShutdownType == EToolShutdownType::Accept) { GenerateAsset(Results); } } TUniquePtr UEditNormalsOperatorFactory::MakeNewOperator() { TUniquePtr NormalsOp = MakeUnique(); 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& 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(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