You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb Brooke.Hubert, Ryan.Schmidt #jira UE-110597, UE-110273 #rnx #ROBOMERGE-SOURCE: CL 15740999 in //UE5/Release-5.0-EarlyAccess/... #ROBOMERGE-BOT: STARSHIP (Release-5.0-EarlyAccess -> Main) (v781-15675533) [CL 15741006 by semion piskarev in ue5-main branch]
617 lines
20 KiB
C++
617 lines
20 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "PlaneCutTool.h"
|
|
#include "InteractiveToolManager.h"
|
|
#include "ToolBuilderUtil.h"
|
|
|
|
#include "ToolSetupUtil.h"
|
|
|
|
#include "DynamicMesh3.h"
|
|
#include "DynamicMeshTriangleAttribute.h"
|
|
#include "DynamicMeshEditor.h"
|
|
#include "BaseBehaviors/MultiClickSequenceInputBehavior.h"
|
|
#include "BaseBehaviors/KeyAsModifierInputBehavior.h"
|
|
#include "Selection/SelectClickedAction.h"
|
|
|
|
#include "MeshDescriptionToDynamicMesh.h"
|
|
#include "DynamicMeshToMeshDescription.h"
|
|
|
|
#include "InteractiveGizmoManager.h"
|
|
|
|
#include "BaseGizmos/GizmoComponents.h"
|
|
#include "BaseGizmos/TransformGizmo.h"
|
|
|
|
#include "Drawing/MeshDebugDrawing.h"
|
|
#include "AssetGenerationUtil.h"
|
|
|
|
#include "Changes/ToolCommandChangeSequence.h"
|
|
|
|
#include "CuttingOps/PlaneCutOp.h"
|
|
|
|
#include "Misc/MessageDialog.h"
|
|
|
|
#include "TargetInterfaces/MaterialProvider.h"
|
|
#include "TargetInterfaces/MeshDescriptionCommitter.h"
|
|
#include "TargetInterfaces/MeshDescriptionProvider.h"
|
|
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
|
|
#include "TargetInterfaces/AssetBackedTarget.h"
|
|
#include "ToolTargetManager.h"
|
|
|
|
#include "ExplicitUseGeometryMathTypes.h" // using UE::Geometry::(math types)
|
|
using namespace UE::Geometry;
|
|
|
|
#define LOCTEXT_NAMESPACE "UPlaneCutTool"
|
|
|
|
/*
|
|
* ToolBuilder
|
|
*/
|
|
|
|
const FToolTargetTypeRequirements& UPlaneCutToolBuilder::GetTargetRequirements() const
|
|
{
|
|
static FToolTargetTypeRequirements TypeRequirements({
|
|
UMaterialProvider::StaticClass(),
|
|
UMeshDescriptionCommitter::StaticClass(),
|
|
UMeshDescriptionProvider::StaticClass(),
|
|
UPrimitiveComponentBackedTarget::StaticClass(),
|
|
UAssetBackedTarget::StaticClass()
|
|
});
|
|
return TypeRequirements;
|
|
}
|
|
|
|
bool UPlaneCutToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return AssetAPI != nullptr && SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()) > 0;
|
|
}
|
|
|
|
UInteractiveTool* UPlaneCutToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
UPlaneCutTool* NewTool = NewObject<UPlaneCutTool>(SceneState.ToolManager);
|
|
|
|
TArray<TObjectPtr<UToolTarget>> Targets = SceneState.TargetManager->BuildAllSelectedTargetable(SceneState, GetTargetRequirements());
|
|
NewTool->SetTargets(MoveTemp(Targets));
|
|
NewTool->SetWorld(SceneState.World);
|
|
NewTool->SetAssetAPI(AssetAPI);
|
|
|
|
return NewTool;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Tool
|
|
*/
|
|
|
|
UPlaneCutToolProperties::UPlaneCutToolProperties() :
|
|
bKeepBothHalves(false),
|
|
SpacingBetweenHalves(1),
|
|
bFillCutHole(true),
|
|
bShowPreview(true),
|
|
bFillSpans(false)
|
|
{
|
|
}
|
|
|
|
UPlaneCutTool::UPlaneCutTool()
|
|
{
|
|
CutPlaneOrigin = FVector::ZeroVector;
|
|
CutPlaneOrientation = FQuat::Identity;
|
|
}
|
|
|
|
void UPlaneCutTool::SetWorld(UWorld* World)
|
|
{
|
|
this->TargetWorld = World;
|
|
}
|
|
|
|
void UPlaneCutTool::Setup()
|
|
{
|
|
UInteractiveTool::Setup();
|
|
|
|
// add modifier button for snapping
|
|
UKeyAsModifierInputBehavior* SnapToggleBehavior = NewObject<UKeyAsModifierInputBehavior>();
|
|
SnapToggleBehavior->Initialize(this, IgnoreSnappingModifier, FInputDeviceState::IsShiftKeyDown);
|
|
AddInputBehavior(SnapToggleBehavior);
|
|
|
|
// hide input StaticMeshComponents
|
|
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(false);
|
|
}
|
|
|
|
TArray<int32> MapToFirstOccurrences;
|
|
bool bAnyHaveSameSource = GetMapToSharedSourceData(MapToFirstOccurrences);
|
|
|
|
if (bAnyHaveSameSource)
|
|
{
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("PlaneCutMultipleAssetWithSameSource", "WARNING: Multiple meshes in your selection use the same source asset! Plane cuts apply to the source asset, and this tool will not duplicate assets for you, so the tool typically cannot give a correct result in this case. Please consider exiting the tool and duplicating the source assets."),
|
|
EToolMessageLevel::UserWarning);
|
|
}
|
|
|
|
// Convert input mesh descriptions to dynamic mesh
|
|
for (int Idx = 0; Idx < Targets.Num(); Idx++)
|
|
{
|
|
IMeshDescriptionProvider* TargetMesh = TargetMeshProviderInterface(Idx);
|
|
FDynamicMesh3* OriginalDynamicMesh = new FDynamicMesh3;
|
|
FMeshDescriptionToDynamicMesh Converter;
|
|
Converter.Convert(TargetMesh->GetMeshDescription(), *OriginalDynamicMesh);
|
|
OriginalDynamicMesh->EnableAttributes();
|
|
TDynamicMeshScalarTriangleAttribute<int>* SubObjectIDs = new TDynamicMeshScalarTriangleAttribute<int>(OriginalDynamicMesh);
|
|
SubObjectIDs->Initialize(0);
|
|
OriginalDynamicMesh->Attributes()->AttachAttribute(FPlaneCutOp::ObjectIndexAttribute, SubObjectIDs);
|
|
|
|
/// fill in the MeshesToCut array
|
|
UDynamicMeshReplacementChangeTarget* Target = MeshesToCut.Add_GetRef(NewObject<UDynamicMeshReplacementChangeTarget>());
|
|
// store a UV scale based on the original mesh bounds (we don't want to recompute this between cuts b/c we want consistent UV scale)
|
|
MeshUVScaleFactor.Add(1.0 / OriginalDynamicMesh->GetBounds().MaxDim());
|
|
|
|
// Set callbacks so previews are invalidated on undo/redo changing the meshes
|
|
Target->SetMesh(TSharedPtr<const FDynamicMesh3, ESPMode::ThreadSafe>(OriginalDynamicMesh));
|
|
Target->OnMeshChanged.AddLambda([this, Idx]() { Previews[Idx]->InvalidateResult(); });
|
|
}
|
|
|
|
// click to set plane behavior
|
|
FSelectClickedAction* SetPlaneAction = new FSelectClickedAction();
|
|
SetPlaneAction->World = this->TargetWorld;
|
|
|
|
// Include the original components even though we made them invisible, since we want
|
|
// to be able to reposition the plane onto the original mesh.
|
|
for (int Idx = 0; Idx < Targets.Num(); Idx++)
|
|
{
|
|
SetPlaneAction->InvisibleComponentsToHitTest.Add(TargetComponentInterface(Idx)->GetOwnerComponent());
|
|
}
|
|
|
|
SetPlaneAction->OnClickedPositionFunc = [this](const FHitResult& Hit)
|
|
{
|
|
SetCutPlaneFromWorldPos(Hit.ImpactPoint, Hit.ImpactNormal, false);
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
};
|
|
SetPointInWorldConnector = SetPlaneAction;
|
|
|
|
USingleClickInputBehavior* ClickToSetPlaneBehavior = NewObject<USingleClickInputBehavior>();
|
|
ClickToSetPlaneBehavior->ModifierCheckFunc = FInputDeviceState::IsCtrlKeyDown;
|
|
ClickToSetPlaneBehavior->Initialize(SetPointInWorldConnector);
|
|
AddInputBehavior(ClickToSetPlaneBehavior);
|
|
|
|
// create proxy and gizmo (but don't attach yet)
|
|
UInteractiveGizmoManager* GizmoManager = GetToolManager()->GetPairedGizmoManager();
|
|
PlaneTransformProxy = NewObject<UTransformProxy>(this);
|
|
PlaneTransformGizmo = GizmoManager->CreateCustomTransformGizmo(
|
|
ETransformGizmoSubElements::StandardTranslateRotate, this);
|
|
|
|
// initialize our properties
|
|
BasicProperties = NewObject<UPlaneCutToolProperties>(this, TEXT("Plane Cut Settings"));
|
|
BasicProperties->RestoreProperties(this);
|
|
AddToolPropertySource(BasicProperties);
|
|
|
|
AcceptProperties = NewObject<UAcceptOutputProperties>(this, TEXT("Tool Accept Output Settings"));
|
|
AcceptProperties->RestoreProperties(this);
|
|
AddToolPropertySource(AcceptProperties);
|
|
|
|
ToolPropertyObjects.Add(this);
|
|
|
|
// initialize the PreviewMesh+BackgroundCompute object
|
|
SetupPreviews();
|
|
|
|
// set initial cut plane (also attaches gizmo/proxy)
|
|
FBox CombinedBounds; CombinedBounds.Init();
|
|
for (int Idx = 0; Idx < Targets.Num(); Idx++)
|
|
{
|
|
FVector ComponentOrigin, ComponentExtents;
|
|
TargetComponentInterface(Idx)->GetOwnerActor()->GetActorBounds(false, ComponentOrigin, ComponentExtents);
|
|
CombinedBounds += FBox::BuildAABB(ComponentOrigin, ComponentExtents);
|
|
}
|
|
SetCutPlaneFromWorldPos(CombinedBounds.GetCenter(), FVector::UpVector, true);
|
|
// hook up callback so further changes trigger recut
|
|
PlaneTransformProxy->OnTransformChanged.AddUObject(this, &UPlaneCutTool::TransformChanged);
|
|
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
|
|
SetToolDisplayName(LOCTEXT("ToolName", "Plane Cut"));
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartPlaneCutTool", "Press 'A' or use the Cut button to cut the mesh without leaving the tool. When grid snapping is enabled, you can toggle snapping with the shift key."),
|
|
EToolMessageLevel::UserNotification);
|
|
}
|
|
|
|
|
|
|
|
|
|
void UPlaneCutTool::RegisterActions(FInteractiveToolActionSet& ActionSet)
|
|
{
|
|
ActionSet.RegisterAction(this, (int32)EStandardToolActions::BaseClientDefinedActionID + 101,
|
|
TEXT("Do Plane Cut"),
|
|
LOCTEXT("DoPlaneCut", "Do Plane Cut"),
|
|
LOCTEXT("DoPlaneCutTooltip", "Cut the mesh with the current cutting plane, without exiting the tool"),
|
|
EModifierKey::None, EKeys::A,
|
|
[this]() { this->Cut(); } );
|
|
}
|
|
|
|
|
|
|
|
|
|
void UPlaneCutTool::SetupPreviews()
|
|
{
|
|
int32 CurrentNumPreview = Previews.Num();
|
|
int32 NumSourceMeshes = MeshesToCut.Num();
|
|
int32 TargetNumPreview = NumSourceMeshes;
|
|
for (int32 PreviewIdx = CurrentNumPreview; PreviewIdx < TargetNumPreview; PreviewIdx++)
|
|
{
|
|
UPlaneCutOperatorFactory *CutSide = NewObject<UPlaneCutOperatorFactory>();
|
|
CutSide->CutTool = this;
|
|
CutSide->ComponentIndex = PreviewIdx;
|
|
UMeshOpPreviewWithBackgroundCompute* Preview = Previews.Add_GetRef(NewObject<UMeshOpPreviewWithBackgroundCompute>(CutSide, "Preview"));
|
|
Preview->Setup(this->TargetWorld, CutSide);
|
|
Preview->PreviewMesh->SetTangentsMode(EDynamicMeshTangentCalcType::AutoCalculated);
|
|
|
|
FComponentMaterialSet MaterialSet;
|
|
TargetMaterialInterface(PreviewIdx)->GetMaterialSet(MaterialSet);
|
|
Preview->ConfigureMaterials(MaterialSet.Materials,
|
|
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager())
|
|
);
|
|
|
|
// set initial preview to un-processed mesh, so stuff doesn't just disappear if the first cut takes a while
|
|
Preview->PreviewMesh->UpdatePreview(MeshesToCut[PreviewIdx]->GetMesh().Get());
|
|
Preview->PreviewMesh->SetTransform(TargetComponentInterface(PreviewIdx)->GetWorldTransform());
|
|
Preview->SetVisibility(BasicProperties->bShowPreview);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UPlaneCutTool::Cut()
|
|
{
|
|
if (!CanAccept())
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
|
|
TUniquePtr<FToolCommandChangeSequence> ChangeSeq = MakeUnique<FToolCommandChangeSequence>();
|
|
|
|
TArray<FDynamicMeshOpResult> Results;
|
|
for (int Idx = 0, N = MeshesToCut.Num(); Idx < N; Idx++)
|
|
{
|
|
UMeshOpPreviewWithBackgroundCompute* Preview = Previews[Idx];
|
|
TUniquePtr<FDynamicMesh3> ResultMesh = Preview->PreviewMesh->ExtractPreviewMesh();
|
|
ChangeSeq->AppendChange(MeshesToCut[Idx], MeshesToCut[Idx]->ReplaceMesh(
|
|
TSharedPtr<const FDynamicMesh3, ESPMode::ThreadSafe>(ResultMesh.Release()))
|
|
);
|
|
}
|
|
|
|
// emit combined change sequence
|
|
GetToolManager()->EmitObjectChange(this, MoveTemp(ChangeSeq), LOCTEXT("MeshPlaneCut", "Cut Mesh with Plane"));
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void UPlaneCutTool::Shutdown(EToolShutdownType ShutdownType)
|
|
{
|
|
BasicProperties->SaveProperties(this);
|
|
AcceptProperties->SaveProperties(this);
|
|
|
|
// Restore (unhide) the source meshes
|
|
for (int Idx = 0; Idx < Targets.Num(); Idx++)
|
|
{
|
|
TargetComponentInterface(Idx)->SetOwnerVisibility(true);
|
|
}
|
|
|
|
TArray<FDynamicMeshOpResult> Results;
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Results.Emplace(Preview->Shutdown());
|
|
}
|
|
if (ShutdownType == EToolShutdownType::Accept)
|
|
{
|
|
GenerateAsset(Results);
|
|
}
|
|
|
|
if (SetPointInWorldConnector != nullptr)
|
|
{
|
|
delete SetPointInWorldConnector;
|
|
}
|
|
UInteractiveGizmoManager* GizmoManager = GetToolManager()->GetPairedGizmoManager();
|
|
GizmoManager->DestroyAllGizmosByOwner(this);
|
|
}
|
|
|
|
void UPlaneCutTool::SetAssetAPI(IAssetGenerationAPI* AssetAPIIn)
|
|
{
|
|
this->AssetAPI = AssetAPIIn;
|
|
}
|
|
|
|
TUniquePtr<FDynamicMeshOperator> UPlaneCutOperatorFactory::MakeNewOperator()
|
|
{
|
|
TUniquePtr<FPlaneCutOp> CutOp = MakeUnique<FPlaneCutOp>();
|
|
CutOp->bFillCutHole = CutTool->BasicProperties->bFillCutHole;
|
|
CutOp->bFillSpans = CutTool->BasicProperties->bFillSpans;
|
|
|
|
FTransform LocalToWorld = CutTool->TargetComponentInterface(ComponentIndex)->GetWorldTransform();
|
|
CutOp->SetTransform(LocalToWorld);
|
|
// for all plane computation, change LocalToWorld to not have any zero scale dims
|
|
FVector LocalToWorldScale = LocalToWorld.GetScale3D();
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
float DimScale = FMathf::Abs(LocalToWorldScale[i]);
|
|
float Tolerance = KINDA_SMALL_NUMBER;
|
|
if (DimScale < Tolerance)
|
|
{
|
|
LocalToWorldScale[i] = Tolerance * FMathf::SignNonZero(LocalToWorldScale[i]);
|
|
}
|
|
}
|
|
LocalToWorld.SetScale3D(LocalToWorldScale);
|
|
FTransform WorldToLocal = LocalToWorld.Inverse();
|
|
FVector LocalOrigin = WorldToLocal.TransformPosition(CutTool->CutPlaneOrigin);
|
|
FVector WorldNormal = CutTool->CutPlaneOrientation.GetAxisZ();
|
|
UE::Geometry::FTransform3d W2LForNormal(WorldToLocal);
|
|
FVector LocalNormal = (FVector)W2LForNormal.TransformNormal(WorldNormal);
|
|
FVector BackTransformed = LocalToWorld.TransformVector(LocalNormal);
|
|
float NormalScaleFactor = FVector::DotProduct(BackTransformed, WorldNormal);
|
|
if (NormalScaleFactor >= FLT_MIN)
|
|
{
|
|
NormalScaleFactor = 1.0 / NormalScaleFactor;
|
|
}
|
|
CutOp->LocalPlaneOrigin = LocalOrigin;
|
|
CutOp->LocalPlaneNormal = LocalNormal;
|
|
CutOp->OriginalMesh = CutTool->MeshesToCut[ComponentIndex]->GetMesh();
|
|
CutOp->bKeepBothHalves = CutTool->BasicProperties->bKeepBothHalves;
|
|
CutOp->CutPlaneLocalThickness = CutTool->BasicProperties->SpacingBetweenHalves * NormalScaleFactor;
|
|
CutOp->UVScaleFactor = CutTool->MeshUVScaleFactor[ComponentIndex];
|
|
|
|
|
|
return CutOp;
|
|
}
|
|
|
|
|
|
|
|
void UPlaneCutTool::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
FViewCameraState RenderCameraState = RenderAPI->GetCameraState();
|
|
FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface();
|
|
FColor GridColor(128, 128, 128, 32);
|
|
float GridThickness = 0.5f*RenderCameraState.GetPDIScalingFactor();
|
|
int NumGridLines = 10;
|
|
|
|
FFrame3f DrawFrame(CutPlaneOrigin, CutPlaneOrientation);
|
|
MeshDebugDraw::DrawSimpleFixedScreenAreaGrid(RenderCameraState, DrawFrame, NumGridLines, 45.0, GridThickness, GridColor, false, PDI, FTransform::Identity);
|
|
}
|
|
|
|
void UPlaneCutTool::OnTick(float DeltaTime)
|
|
{
|
|
if (PlaneTransformGizmo != nullptr)
|
|
{
|
|
PlaneTransformGizmo->bSnapToWorldGrid =
|
|
BasicProperties->bSnapToWorldGrid && bIgnoreSnappingToggle == false;
|
|
}
|
|
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->Tick(DeltaTime);
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void UPlaneCutTool::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void UPlaneCutTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
|
|
{
|
|
if (Property && (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UPlaneCutToolProperties, bShowPreview)))
|
|
{
|
|
for (int Idx = 0; Idx < Targets.Num(); Idx++)
|
|
{
|
|
TargetComponentInterface(Idx)->SetOwnerVisibility(!BasicProperties->bShowPreview);
|
|
}
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->SetVisibility(BasicProperties->bShowPreview);
|
|
}
|
|
}
|
|
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
|
|
|
|
void UPlaneCutTool::OnUpdateModifierState(int ModifierID, bool bIsOn)
|
|
{
|
|
if (ModifierID == IgnoreSnappingModifier)
|
|
{
|
|
bIgnoreSnappingToggle = bIsOn;
|
|
}
|
|
}
|
|
|
|
|
|
void UPlaneCutTool::TransformChanged(UTransformProxy* Proxy, FTransform Transform)
|
|
{
|
|
CutPlaneOrientation = Transform.GetRotation();
|
|
CutPlaneOrigin = (FVector)Transform.GetTranslation();
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
|
|
|
|
void UPlaneCutTool::SetCutPlaneFromWorldPos(const FVector& Position, const FVector& Normal, bool bIsInitializing)
|
|
{
|
|
CutPlaneOrigin = Position;
|
|
|
|
FFrame3f CutPlane(Position, Normal);
|
|
CutPlaneOrientation = (FQuat)CutPlane.Rotation;
|
|
|
|
PlaneTransformGizmo->SetActiveTarget(PlaneTransformProxy, GetToolManager());
|
|
if (bIsInitializing)
|
|
{
|
|
PlaneTransformGizmo->ReinitializeGizmoTransform(CutPlane.ToFTransform());
|
|
}
|
|
else
|
|
{
|
|
PlaneTransformGizmo->SetNewGizmoTransform(CutPlane.ToFTransform());
|
|
}
|
|
}
|
|
|
|
bool UPlaneCutTool::CanAccept() const
|
|
{
|
|
for (UMeshOpPreviewWithBackgroundCompute* Preview : Previews)
|
|
{
|
|
if (!Preview->HaveValidResult())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return Super::CanAccept();
|
|
}
|
|
|
|
|
|
void UPlaneCutTool::GenerateAsset(const TArray<FDynamicMeshOpResult>& Results)
|
|
{
|
|
if (Results.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetToolManager()->BeginUndoTransaction(LOCTEXT("PlaneCutToolTransactionName", "Plane Cut Tool"));
|
|
|
|
|
|
// currently in-place replaces the first half, and adds a new actor for the second half (if it was generated)
|
|
// TODO: options to support other choices re what should be a new actor
|
|
|
|
ensure(Results.Num() > 0);
|
|
int32 NumSourceMeshes = MeshesToCut.Num();
|
|
TArray<TArray<FDynamicMesh3>> AllSplitMeshes; AllSplitMeshes.SetNum(NumSourceMeshes);
|
|
|
|
// build a selection change starting w/ the original selection (used if objects are added below)
|
|
FSelectedOjectsChangeList NewSelection;
|
|
NewSelection.ModificationType = ESelectedObjectsModificationType::Replace;
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < NumSourceMeshes; OrigMeshIdx++)
|
|
{
|
|
IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(OrigMeshIdx);
|
|
NewSelection.Actors.Add(TargetComponent->GetOwnerActor());
|
|
}
|
|
|
|
// check if we entirely cut away any meshes
|
|
bool bWantDestroy = false;
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < NumSourceMeshes; OrigMeshIdx++)
|
|
{
|
|
bWantDestroy = bWantDestroy || (Results[OrigMeshIdx].Mesh->TriangleCount() == 0);
|
|
}
|
|
// if so ask user what to do
|
|
if (bWantDestroy)
|
|
{
|
|
FText Title = LOCTEXT("PlaneCutDestroyTitle", "Delete mesh components?");
|
|
EAppReturnType::Type Ret = FMessageDialog::Open(EAppMsgType::YesNo,
|
|
LOCTEXT("PlaneCutDestroyQuestion", "Plane cuts have entirely cut away some meshes. Actually destroy these mesh components?"), &Title);
|
|
if (Ret == EAppReturnType::No)
|
|
{
|
|
bWantDestroy = false; // quell destructive urge
|
|
}
|
|
}
|
|
|
|
bool bNeedToAdd = false; // will be set to true if any mesh will be partly split out into a new generated asset
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < NumSourceMeshes; OrigMeshIdx++)
|
|
{
|
|
FDynamicMesh3* UseMesh = Results[OrigMeshIdx].Mesh.Get();
|
|
check(UseMesh != nullptr);
|
|
|
|
if (UseMesh->TriangleCount() == 0)
|
|
{
|
|
if (bWantDestroy)
|
|
{
|
|
TargetComponentInterface(OrigMeshIdx)->GetOwnerComponent()->DestroyComponent();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (AcceptProperties->bExportSeparatedPiecesAsNewMeshAssets)
|
|
{
|
|
TDynamicMeshScalarTriangleAttribute<int>* SubMeshIDs =
|
|
static_cast<TDynamicMeshScalarTriangleAttribute<int>*>(UseMesh->Attributes()->GetAttachedAttribute(
|
|
FPlaneCutOp::ObjectIndexAttribute));
|
|
TArray<FDynamicMesh3>& SplitMeshes = AllSplitMeshes[OrigMeshIdx];
|
|
bool bWasSplit = FDynamicMeshEditor::SplitMesh(UseMesh, SplitMeshes, [SubMeshIDs](int TID)
|
|
{
|
|
return SubMeshIDs->GetValue(TID);
|
|
}
|
|
);
|
|
if (bWasSplit)
|
|
{
|
|
// split mesh did something but has no meshes in the output array??
|
|
if (!ensure(SplitMeshes.Num() > 0))
|
|
{
|
|
continue;
|
|
}
|
|
bNeedToAdd = bNeedToAdd || (SplitMeshes.Num() > 1);
|
|
UseMesh = &SplitMeshes[0];
|
|
}
|
|
}
|
|
|
|
TargetMeshCommitterInterface(OrigMeshIdx)->CommitMeshDescription([&UseMesh](const IMeshDescriptionCommitter::FCommitterParams& CommitParams)
|
|
{
|
|
FDynamicMeshToMeshDescription Converter;
|
|
Converter.Convert(UseMesh, *CommitParams.MeshDescriptionOut);
|
|
});
|
|
}
|
|
|
|
if (bNeedToAdd)
|
|
{
|
|
for (int OrigMeshIdx = 0; OrigMeshIdx < NumSourceMeshes; OrigMeshIdx++)
|
|
{
|
|
TArray<FDynamicMesh3>& SplitMeshes = AllSplitMeshes[OrigMeshIdx];
|
|
if (SplitMeshes.Num() < 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// build array of materials from the original
|
|
TArray<UMaterialInterface*> Materials;
|
|
IMaterialProvider* TargetMaterial = TargetMaterialInterface(OrigMeshIdx);
|
|
for (int MaterialIdx = 0, NumMaterials = TargetMaterial->GetNumMaterials(); MaterialIdx < NumMaterials; MaterialIdx++)
|
|
{
|
|
Materials.Add(TargetMaterial->GetMaterial(MaterialIdx));
|
|
}
|
|
|
|
// add all the additional meshes
|
|
for (int AddMeshIdx = 1; AddMeshIdx < SplitMeshes.Num(); AddMeshIdx++)
|
|
{
|
|
AActor* NewActor = AssetGenerationUtil::GenerateStaticMeshActor(
|
|
AssetAPI, TargetWorld,
|
|
&SplitMeshes[AddMeshIdx], Results[OrigMeshIdx].Transform, TEXT("PlaneCutOtherPart"), Materials);
|
|
if (NewActor != nullptr)
|
|
{
|
|
NewSelection.Actors.Add(NewActor);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NewSelection.Actors.Num() > 0)
|
|
{
|
|
GetToolManager()->RequestSelectionChange(NewSelection);
|
|
}
|
|
}
|
|
|
|
|
|
GetToolManager()->EndUndoTransaction();
|
|
}
|
|
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE
|