// Copyright Epic Games, Inc. All Rights Reserved. #include "RemoveOccludedTrianglesTool.h" #include "InteractiveToolManager.h" #include "ToolBuilderUtil.h" #include "ToolSetupUtil.h" #include "DynamicMesh3.h" #include "BaseBehaviors/MultiClickSequenceInputBehavior.h" #include "Selection/SelectClickedAction.h" #include "MeshDescriptionToDynamicMesh.h" #include "DynamicMeshToMeshDescription.h" #include "InteractiveGizmoManager.h" #include "AssetGenerationUtil.h" #if WITH_EDITOR #include "Misc/ScopedSlowTask.h" #endif #define LOCTEXT_NAMESPACE "URemoveOccludedTrianglesTool" /* * ToolBuilder */ bool URemoveOccludedTrianglesToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const { return ToolBuilderUtil::CountComponents(SceneState, CanMakeComponentTarget) > 0; } UInteractiveTool* URemoveOccludedTrianglesToolBuilder::BuildTool(const FToolBuilderState& SceneState) const { URemoveOccludedTrianglesTool* NewTool = NewObject(SceneState.ToolManager); TArray Components = ToolBuilderUtil::FindAllComponents(SceneState, CanMakeComponentTarget); check(Components.Num() > 0); TArray> ComponentTargets; for (UActorComponent* ActorComponent : Components) { auto* MeshComponent = Cast(ActorComponent); if ( MeshComponent ) { ComponentTargets.Add(MakeComponentTarget(MeshComponent)); } } NewTool->SetSelection(MoveTemp(ComponentTargets)); NewTool->SetWorld(SceneState.World); NewTool->SetAssetAPI(AssetAPI); return NewTool; } /* * Tool */ URemoveOccludedTrianglesToolProperties::URemoveOccludedTrianglesToolProperties() { } URemoveOccludedTrianglesAdvancedProperties::URemoveOccludedTrianglesAdvancedProperties() { } URemoveOccludedTrianglesTool::URemoveOccludedTrianglesTool() { SetToolDisplayName(LOCTEXT("ProjectToTargetToolName", "Jacketing Tool")); } void URemoveOccludedTrianglesTool::SetWorld(UWorld* World) { this->TargetWorld = World; } void URemoveOccludedTrianglesTool::Setup() { UInteractiveTool::Setup(); // hide input StaticMeshComponent for (TUniquePtr& ComponentTarget : ComponentTargets) { ComponentTarget->SetOwnerVisibility(false); } // find components with the same source asset TargetToPreviewIdx.SetNum(ComponentTargets.Num()); PreviewToTargetIdx.Reset(); for (int ComponentIdx = 0; ComponentIdx < TargetToPreviewIdx.Num(); ComponentIdx++) { TargetToPreviewIdx[ComponentIdx] = -1; } bool bAnyHasSameSource = false; for (int32 ComponentIdx = 0; ComponentIdx < ComponentTargets.Num(); ComponentIdx++) { if (TargetToPreviewIdx[ComponentIdx] >= 0) // already mapped { continue; } int32 NumPreviews = PreviewToTargetIdx.Num(); PreviewToTargetIdx.Add(ComponentIdx); TargetToPreviewIdx[ComponentIdx] = NumPreviews; FPrimitiveComponentTarget* ComponentTarget = ComponentTargets[ComponentIdx].Get(); for (int32 VsIdx = ComponentIdx + 1; VsIdx < ComponentTargets.Num(); VsIdx++) { if (ComponentTarget->HasSameSourceData(*ComponentTargets[VsIdx])) { bAnyHasSameSource = true; TargetToPreviewIdx[VsIdx] = NumPreviews; } } } 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); } BasicProperties = NewObject(this, TEXT("Remove Occluded Triangle Settings")); AdvancedProperties = NewObject(this, TEXT("Advanced Settings")); // initialize our properties AddToolPropertySource(BasicProperties); AddToolPropertySource(AdvancedProperties); // initialize the PreviewMesh+BackgroundCompute object SetupPreviews(); 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::SetupPreviews() { int32 NumTargets = ComponentTargets.Num(); int32 NumPreviews = PreviewToTargetIdx.Num(); CombinedMeshTrees = MakeShared(); #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 OriginalDynamicMeshes.SetNum(NumPreviews); PreviewToCopyIdx.Reset(); PreviewToCopyIdx.SetNum(NumPreviews); for (int32 TargetIdx = 0; TargetIdx < NumTargets; TargetIdx++) { EnterProgressFrame(1); int PreviewIdx = TargetToPreviewIdx[TargetIdx]; bool bHasConverted = OriginalDynamicMeshes[PreviewIdx].IsValid(); if (!bHasConverted) { URemoveOccludedTrianglesOperatorFactory *OpFactory = NewObject(); OpFactory->Tool = this; OpFactory->PreviewIdx = PreviewIdx; OriginalDynamicMeshes[PreviewIdx] = MakeShared(); FMeshDescriptionToDynamicMesh Converter; Converter.Convert(ComponentTargets[TargetIdx]->GetMesh(), *OriginalDynamicMeshes[PreviewIdx]); UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(NewObject(OpFactory, "Preview")); Preview->Setup(this->TargetWorld, OpFactory); Preview->PreviewMesh->SetTangentsMode(EDynamicMeshTangentCalcType::AutoCalculated); FComponentMaterialSet MaterialSet; ComponentTargets[TargetIdx]->GetMaterialSet(MaterialSet); Preview->ConfigureMaterials(MaterialSet.Materials, ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()) ); Preview->PreviewMesh->SetTransform(ComponentTargets[TargetIdx]->GetWorldTransform()); Preview->PreviewMesh->UpdatePreview(OriginalDynamicMeshes[PreviewIdx].Get()); Preview->SetVisibility(true); } 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(this)); PreviewMesh->CreateInWorld(this->TargetWorld, ComponentTargets[TargetIdx]->GetWorldTransform()); PreviewToCopyIdx[PreviewIdx].Add(CopyIdx); PreviewMesh->UpdatePreview(OriginalDynamicMeshes[PreviewIdx].Get()); FComponentMaterialSet MaterialSet; ComponentTargets[TargetIdx]->GetMaterialSet(MaterialSet); PreviewMesh->SetMaterials(MaterialSet.Materials); PreviewMesh->SetVisible(true); Previews[PreviewIdx]->OnMeshUpdated.AddLambda([this, CopyIdx](UMeshOpPreviewWithBackgroundCompute* Compute) { PreviewCopies[CopyIdx]->UpdatePreview(Compute->PreviewMesh->GetPreviewDynamicMesh()); }); } CombinedMeshTrees->AddMesh(*OriginalDynamicMeshes[PreviewIdx], FTransform3d(ComponentTargets[TargetIdx]->GetWorldTransform())); } CombinedMeshTrees->BuildAcceleration(); for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews) { Preview->InvalidateResult(); } } void URemoveOccludedTrianglesTool::Shutdown(EToolShutdownType ShutdownType) { // Restore (unhide) the source meshes for (TUniquePtr& ComponentTarget : ComponentTargets) { ComponentTarget->SetOwnerVisibility(true); } // clear all the preview copies for (UPreviewMesh* PreviewMesh : PreviewCopies) { PreviewMesh->SetVisible(false); PreviewMesh->Disconnect(); PreviewMesh = nullptr; } PreviewCopies.Empty(); TArray Results; for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews) { Results.Add(Preview->Shutdown()); } if (ShutdownType == EToolShutdownType::Accept) { GenerateAsset(Results); } } void URemoveOccludedTrianglesTool::SetAssetAPI(IAssetGenerationAPI* AssetAPIIn) { this->AssetAPI = AssetAPIIn; } TUniquePtr URemoveOccludedTrianglesOperatorFactory::MakeNewOperator() { TUniquePtr Op = MakeUnique(); Op->NormalOffset = Tool->AdvancedProperties->NormalOffset; 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->ComponentTargets[ComponentIndex]->GetWorldTransform(); Op->OriginalMesh = Tool->OriginalDynamicMeshes[PreviewIdx]; Op->CombinedMeshTrees = Tool->CombinedMeshTrees; Op->bOnlySelfOcclude = Tool->BasicProperties->bOnlySelfOcclude; Op->AddRandomRays = Tool->BasicProperties->AddRandomRays; Op->AddTriangleSamples = Tool->BasicProperties->AddTriangleSamples; Op->MinAreaConnectedComponent = Tool->BasicProperties->MinAreaIsland; Op->MinTriCountConnectedComponent = Tool->BasicProperties->MinTriCountIsland; Op->SetTransform(LocalToWorld); Op->MeshTransforms.Add((FTransform3d)LocalToWorld); for (int32 CopyIdx : Tool->PreviewToCopyIdx[PreviewIdx]) { Op->MeshTransforms.Add((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); } else { PreviewCopies[CopyIdx]->ClearOverrideRenderMaterial(); } } } } #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::HasAccept() const { return true; } bool URemoveOccludedTrianglesTool::CanAccept() const { for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews) { if (!Preview->HaveValidResult()) { return false; } } return true; } void URemoveOccludedTrianglesTool::GenerateAsset(const TArray& Results) { GetToolManager()->BeginUndoTransaction(LOCTEXT("RemoveOccludedTrianglesToolTransactionName", "Remove Occluded Triangles")); check(Results.Num() == Previews.Num()); for (int32 PreviewIdx = 0; PreviewIdx < Previews.Num(); PreviewIdx++) { check(Results[PreviewIdx].Mesh.Get() != nullptr); int ComponentIdx = PreviewToTargetIdx[PreviewIdx]; ComponentTargets[ComponentIdx]->CommitMesh([&Results, &PreviewIdx, this](const FPrimitiveComponentTarget::FCommitParams& CommitParams) { FDynamicMeshToMeshDescription Converter; Converter.Convert(Results[PreviewIdx].Mesh.Get(), *CommitParams.MeshDescription); }); } GetToolManager()->EndUndoTransaction(); } #undef LOCTEXT_NAMESPACE