// Copyright Epic Games, Inc. All Rights Reserved. #include "EditNormalsTool.h" #include "InteractiveToolManager.h" #include "ToolBuilderUtil.h" #include "ToolSetupUtil.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/Classes/Engine/StaticMesh.h" #include "Engine/Classes/Components/StaticMeshComponent.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 "UEditNormalsTool" /* * ToolBuilder */ const FToolTargetTypeRequirements& UEditNormalsToolBuilder::GetTargetRequirements() const { static FToolTargetTypeRequirements TypeRequirements({ UMeshDescriptionCommitter::StaticClass(), UMeshDescriptionProvider::StaticClass(), UPrimitiveComponentBackedTarget::StaticClass() }); return TypeRequirements; } bool UEditNormalsToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { return SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()) > 0; } UInteractiveTool* UEditNormalsToolBuilder::BuildTool(const FToolBuilderState& SceneState) const { UEditNormalsTool* NewTool = NewObject(SceneState.ToolManager); TArray> Targets = SceneState.TargetManager->BuildAllSelectedTargetable(SceneState, GetTargetRequirements()); NewTool->SetTargets(MoveTemp(Targets)); NewTool->SetWorld(SceneState.World); return NewTool; } /* * 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::SetWorld(UWorld* World) { this->TargetWorld = World; } void UEditNormalsTool::Setup() { UInteractiveTool::Setup(); // hide input StaticMeshComponent for (int32 ComponentIdx = 0, NumTargets = Targets.Num(); ComponentIdx < NumTargets; ComponentIdx++) { IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(ComponentIdx); TargetComponent->SetOwnerVisibility(false); } 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(TargetMeshProviderInterface(PreviewIdx)->GetMeshDescription(), *OriginalDynamicMeshes[PreviewIdx]); UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(NewObject(OpFactory, "Preview")); Preview->Setup(this->TargetWorld, OpFactory); Preview->PreviewMesh->SetTangentsMode(EDynamicMeshComponentTangentsMode::AutoCalculated); FComponentMaterialSet MaterialSet; TargetMaterialInterface(PreviewIdx)->GetMaterialSet(MaterialSet); Preview->ConfigureMaterials(MaterialSet.Materials, ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()) ); Preview->SetVisibility(true); } } } void UEditNormalsTool::Shutdown(EToolShutdownType ShutdownType) { BasicProperties->SaveProperties(this); AdvancedProperties->SaveProperties(this); // Restore (unhide) the source meshes for (int32 ComponentIdx = 0, NumTargets = Targets.Num(); ComponentIdx < NumTargets; ComponentIdx++) { IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(ComponentIdx); TargetComponent->SetOwnerVisibility(true); } 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; FTransform LocalToWorld = Tool->TargetComponentInterface(ComponentIndex)->GetWorldTransform(); 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(TargetComponentInterface(ComponentIdx)->GetOwnerComponent()); 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); } } check(Results[ComponentIdx].Mesh.Get() != nullptr); TargetMeshCommitterInterface(ComponentIdx)->CommitMeshDescription([&Results, &ComponentIdx, this](const IMeshDescriptionCommitter::FCommitterParams& CommitParams) { FDynamicMeshToMeshDescription Converter; if (BasicProperties->WillTopologyChange() || !FDynamicMeshToMeshDescription::HaveMatchingElementCounts(Results[ComponentIdx].Mesh.Get(), CommitParams.MeshDescriptionOut, false, true)) { // full conversion if normal topology changed or faces were inverted Converter.Convert(Results[ComponentIdx].Mesh.Get(), *CommitParams.MeshDescriptionOut); } else { // otherwise just copy attributes const bool bUpdateNormals = true; const bool bCopyOverlayTangents = false; const bool bCopyOverlayUVs = false; Converter.UpdateAttributes(Results[ComponentIdx].Mesh.Get(), *CommitParams.MeshDescriptionOut, bUpdateNormals, bCopyOverlayTangents, bCopyOverlayUVs); } }); } GetToolManager()->EndUndoTransaction(); } #undef LOCTEXT_NAMESPACE