Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolset/Source/ModelingComponents/Private/BaseTools/BaseCreateFromSelectedTool.cpp

395 lines
13 KiB
C++
Raw Normal View History

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BaseTools/BaseCreateFromSelectedTool.h"
#include "InteractiveToolManager.h"
#include "ToolBuilderUtil.h"
#include "ToolSetupUtil.h"
#include "Components/DynamicMeshComponent.h"
Gizmos: refactor Modeling Mode gizmo creation out of InteractiveGizmoManager. Editor will use other "default" transform gizmo implementations, and so the UTransformGizmo creation helper functions do not belong in GizmoManager. Instead a UTransformGizmoContextObject now provides this functionality. ModelingToolsEditorMode (and any other modes/systems that want to use these gizmo convenience functions) creates an instance of UTransformGizmoContextObject and registers it with the ContextObjectStore. Calling code can spawn a new UTransformGizmo by looking this object up in the ContextStore and calling it's helper functions. Static versions of the helper functions in the UE::TransformGizmoUtil:: namespace provide a single-line interface that replaces the previous GizmoManager call sites in the MeshModelingTools library. IntervalGizmo is now just registered and unregistered as needed by the MeshSpaceDeformerTool, as this is the only place it is currently used. Previous implementation in InteractiveGizmoManager is left intact as there are a few uses outside of MeshModelingTools that need to be cleaned up before it can be deleted. UTransformGizmo now requires it's Builder to tell it which sub-gizmo identifier strings to pass to the GizmoManager to create axis/plane/rotation sub-gizmos (and the code that registers the Builder must then provide these strings). This cleans up previous explicit references to UInteractiveGizmoManager static strings from UTransformGizmo. #rb Christina.TempelaarL, david.hill #rnx #jira none [CL 16409673 by Ryan Schmidt in ue5-main branch]
2021-05-20 16:39:39 -04:00
#include "BaseGizmos/TransformGizmoUtil.h"
#include "Async/Async.h"
#include "DynamicMesh/MeshNormals.h"
#include "DynamicMesh/MeshTransforms.h"
#include "DynamicMeshToMeshDescription.h"
#include "MeshDescriptionToDynamicMesh.h"
#include "ModelingObjectsCreationAPI.h"
#include "Selection/ToolSelectionUtil.h"
#include "TargetInterfaces/MeshDescriptionCommitter.h"
#include "TargetInterfaces/MeshDescriptionProvider.h"
#include "TargetInterfaces/PrimitiveComponentBackedTarget.h"
#include "TargetInterfaces/MaterialProvider.h"
#include "TargetInterfaces/AssetBackedTarget.h"
#include "ToolTargetManager.h"
ModelingTools: add support for creating Volumes directly from DrawPolygon, DrawRevolve, DrawPolyPath, and AddPrimitive, CombineMeshes, CutMeshWithMesh, PlaneCut, BaseCreateFromSelected Tools. Improve support for Editing volumes, eg handling mesh/volume interactions, and add configurable auto-simplification for volumes to avoid painful Editor hangs. - Move ToolTarget implementations, DynamicMeshToVolume to ModelingComponentsEditorOnly module - move VolumeToDynamicMesh, DynamicMeshProvider/Commiter interfaces to ModelingComponents module - Add UCreateMeshObjectTypeProperties property set to expose mesh/volume options - Add FCreateMeshObjectParams::TypeHintClass to allow AVolume type (or other UClass hints) to be passed to creation APIs - Add UE::ToolTarget::ConfigureCreateMeshObjectParams() util function in ModelingToolTargetUtil, tries to determine output type in a FCreateMeshObjectParams based on input ToolTarget - Add UEditorModelingObjectsCreationAPI::CreateVolume() implementation - Add UEditorModelingObjectsCreationAPI::FilterMaterials() that strips out any internal materials and replaces with WorldGridMaterial. This occurs when (eg) subtracting a Volume from a StaticMesh, because the temporary volume mesh gets assigned internal materials, but the Tools don't know this. Use in EditorModelingObjectsCreationAPI when creating new objects. UStaticMeshComponentToolTarget also does this filtering in ::CommitMaterialSetUpdate(). - Add ::ComponentTypeSupportsCollision() function to ComponentCollisionUtil, use to avoid checks/ensures for Volume targets - Add support for automatic mesh simplification in DynamicMeshToVolume. Add CVar to VolumeDynamicMeshToolTarget.h to control max triangle count (default 500). Apply auto-simplify when creating or updating an AVolume. This prevents the Editor from blocking for long periods on meshes that are too high-res for volumes (even 500 is quite high). - DynamicMeshToVolume now emits polygroup-faces that contain holes (ie multiple boundary loops) as a set of triangles, rather than emitting separate overlapping faces for each boundary loop #rb none #rnx #jira none #preflight 60ba50632c42ea0001cb54c5 [CL 16561742 by Ryan Schmidt in ue5-main branch]
2021-06-04 16:04:03 -04:00
#include "ModelingToolTargetUtil.h"
#define LOCTEXT_NAMESPACE "UBaseCreateFromSelectedTool"
using namespace UE::Geometry;
/*
* ToolBuilder
*/
const FToolTargetTypeRequirements& UBaseCreateFromSelectedToolBuilder::GetTargetRequirements() const
{
static FToolTargetTypeRequirements TypeRequirements({
UMeshDescriptionCommitter::StaticClass(),
UMeshDescriptionProvider::StaticClass(),
UPrimitiveComponentBackedTarget::StaticClass(),
ModelingTools: add support for creating Volumes directly from DrawPolygon, DrawRevolve, DrawPolyPath, and AddPrimitive, CombineMeshes, CutMeshWithMesh, PlaneCut, BaseCreateFromSelected Tools. Improve support for Editing volumes, eg handling mesh/volume interactions, and add configurable auto-simplification for volumes to avoid painful Editor hangs. - Move ToolTarget implementations, DynamicMeshToVolume to ModelingComponentsEditorOnly module - move VolumeToDynamicMesh, DynamicMeshProvider/Commiter interfaces to ModelingComponents module - Add UCreateMeshObjectTypeProperties property set to expose mesh/volume options - Add FCreateMeshObjectParams::TypeHintClass to allow AVolume type (or other UClass hints) to be passed to creation APIs - Add UE::ToolTarget::ConfigureCreateMeshObjectParams() util function in ModelingToolTargetUtil, tries to determine output type in a FCreateMeshObjectParams based on input ToolTarget - Add UEditorModelingObjectsCreationAPI::CreateVolume() implementation - Add UEditorModelingObjectsCreationAPI::FilterMaterials() that strips out any internal materials and replaces with WorldGridMaterial. This occurs when (eg) subtracting a Volume from a StaticMesh, because the temporary volume mesh gets assigned internal materials, but the Tools don't know this. Use in EditorModelingObjectsCreationAPI when creating new objects. UStaticMeshComponentToolTarget also does this filtering in ::CommitMaterialSetUpdate(). - Add ::ComponentTypeSupportsCollision() function to ComponentCollisionUtil, use to avoid checks/ensures for Volume targets - Add support for automatic mesh simplification in DynamicMeshToVolume. Add CVar to VolumeDynamicMeshToolTarget.h to control max triangle count (default 500). Apply auto-simplify when creating or updating an AVolume. This prevents the Editor from blocking for long periods on meshes that are too high-res for volumes (even 500 is quite high). - DynamicMeshToVolume now emits polygroup-faces that contain holes (ie multiple boundary loops) as a set of triangles, rather than emitting separate overlapping faces for each boundary loop #rb none #rnx #jira none #preflight 60ba50632c42ea0001cb54c5 [CL 16561742 by Ryan Schmidt in ue5-main branch]
2021-06-04 16:04:03 -04:00
UMaterialProvider::StaticClass()
});
return TypeRequirements;
}
bool UBaseCreateFromSelectedToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
{
int32 ComponentCount = SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements());
return ComponentCount >= MinComponentsSupported() && (!MaxComponentsSupported().IsSet() || ComponentCount <= MaxComponentsSupported().GetValue());
}
UInteractiveTool* UBaseCreateFromSelectedToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
{
UBaseCreateFromSelectedTool* NewTool = MakeNewToolInstance(SceneState.ToolManager);
TArray<TObjectPtr<UToolTarget>> Targets = SceneState.TargetManager->BuildAllSelectedTargetable(SceneState, GetTargetRequirements());
NewTool->SetTargets(MoveTemp(Targets));
NewTool->SetWorld(SceneState.World);
return NewTool;
}
/*
* Tool
*/
void UBaseCreateFromSelectedTool::Setup()
{
UInteractiveTool::Setup();
// hide input StaticMeshComponents
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(false);
}
// initialize our properties
SetupProperties();
TransformProperties = NewObject<UTransformInputsToolProperties>(this);
TransformProperties->RestoreProperties(this);
AddToolPropertySource(TransformProperties);
OutputTypeProperties = NewObject<UCreateMeshObjectTypeProperties>(this);
OutputTypeProperties->InitializeDefaultWithAuto();
OutputTypeProperties->OutputType = UCreateMeshObjectTypeProperties::AutoIdentifier;
OutputTypeProperties->RestoreProperties(this, TEXT("OutputTypeFromInputTool"));
OutputTypeProperties->WatchProperty(OutputTypeProperties->OutputType, [this](FString) { OutputTypeProperties->UpdatePropertyVisibility(); });
AddToolPropertySource(OutputTypeProperties);
HandleSourcesProperties = NewObject<UBaseCreateFromSelectedHandleSourceProperties>(this);
HandleSourcesProperties->RestoreProperties(this);
AddToolPropertySource(HandleSourcesProperties);
Preview = NewObject<UMeshOpPreviewWithBackgroundCompute>(this);
Preview->Setup(this->TargetWorld, this);
SetPreviewCallbacks();
Preview->OnMeshUpdated.AddLambda(
[this](const UMeshOpPreviewWithBackgroundCompute* UpdatedPreview)
{
UpdateAcceptWarnings(UpdatedPreview->HaveEmptyResult() ? EAcceptWarning::EmptyForbidden : EAcceptWarning::NoWarning);
}
);
SetTransformGizmos();
ConvertInputsAndSetPreviewMaterials(true);
// output name fields
HandleSourcesProperties->OutputName = PrefixWithSourceNameIfSingleSelection(GetCreatedAssetName());
HandleSourcesProperties->WatchProperty(HandleSourcesProperties->WriteOutputTo, [&](EBaseCreateFromSelectedTargetType NewType)
{
SetToolPropertySourceEnabled(OutputTypeProperties, (NewType == EBaseCreateFromSelectedTargetType::NewAsset));
if (NewType == EBaseCreateFromSelectedTargetType::NewAsset)
{
HandleSourcesProperties->OutputAsset = TEXT("");
UpdateGizmoVisibility();
}
else
{
int32 Index = (HandleSourcesProperties->WriteOutputTo == EBaseCreateFromSelectedTargetType::FirstInputAsset) ? 0 : Targets.Num() - 1;
HandleSourcesProperties->OutputAsset = UE::Modeling::GetComponentAssetBaseName(TargetComponentInterface(Index)->GetOwnerComponent(), false);
// Reset the hidden gizmo to its initial position
FTransform ComponentTransform = TargetComponentInterface(Index)->GetWorldTransform();
TransformGizmos[Index]->SetNewGizmoTransform(ComponentTransform, true);
UpdateGizmoVisibility();
}
});
Preview->InvalidateResult();
}
int32 UBaseCreateFromSelectedTool::GetHiddenGizmoIndex() const
{
if (HandleSourcesProperties->WriteOutputTo == EBaseCreateFromSelectedTargetType::NewAsset)
{
return -1;
}
else
{
return (HandleSourcesProperties->WriteOutputTo == EBaseCreateFromSelectedTargetType::FirstInputAsset) ? 0 : Targets.Num() - 1;
}
}
void UBaseCreateFromSelectedTool::SetWorld(UWorld* World)
{
this->TargetWorld = World;
}
void UBaseCreateFromSelectedTool::OnTick(float DeltaTime)
{
for (UTransformGizmo* Gizmo : TransformGizmos)
{
Gizmo->bSnapToWorldGrid = TransformProperties->bSnapToWorldGrid;
}
Preview->Tick(DeltaTime);
}
void UBaseCreateFromSelectedTool::UpdateGizmoVisibility()
{
for (int32 GizmoIndex = 0; GizmoIndex < TransformGizmos.Num(); GizmoIndex++)
{
UTransformGizmo* Gizmo = TransformGizmos[GizmoIndex];
Gizmo->SetVisibility(TransformProperties->bShowTransformUI && GizmoIndex != GetHiddenGizmoIndex());
}
}
void UBaseCreateFromSelectedTool::SetTransformGizmos()
{
UInteractiveGizmoManager* GizmoManager = GetToolManager()->GetPairedGizmoManager();
for (int ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
UTransformProxy* Proxy = TransformProxies.Add_GetRef(NewObject<UTransformProxy>(this));
Gizmos: refactor Modeling Mode gizmo creation out of InteractiveGizmoManager. Editor will use other "default" transform gizmo implementations, and so the UTransformGizmo creation helper functions do not belong in GizmoManager. Instead a UTransformGizmoContextObject now provides this functionality. ModelingToolsEditorMode (and any other modes/systems that want to use these gizmo convenience functions) creates an instance of UTransformGizmoContextObject and registers it with the ContextObjectStore. Calling code can spawn a new UTransformGizmo by looking this object up in the ContextStore and calling it's helper functions. Static versions of the helper functions in the UE::TransformGizmoUtil:: namespace provide a single-line interface that replaces the previous GizmoManager call sites in the MeshModelingTools library. IntervalGizmo is now just registered and unregistered as needed by the MeshSpaceDeformerTool, as this is the only place it is currently used. Previous implementation in InteractiveGizmoManager is left intact as there are a few uses outside of MeshModelingTools that need to be cleaned up before it can be deleted. UTransformGizmo now requires it's Builder to tell it which sub-gizmo identifier strings to pass to the GizmoManager to create axis/plane/rotation sub-gizmos (and the code that registers the Builder must then provide these strings). This cleans up previous explicit references to UInteractiveGizmoManager static strings from UTransformGizmo. #rb Christina.TempelaarL, david.hill #rnx #jira none [CL 16409673 by Ryan Schmidt in ue5-main branch]
2021-05-20 16:39:39 -04:00
UTransformGizmo* Gizmo = TransformGizmos.Add_GetRef(UE::TransformGizmoUtil::Create3AxisTransformGizmo(GizmoManager, this));
Proxy->SetTransform(TargetComponentInterface(ComponentIdx)->GetWorldTransform());
Gizmo->SetActiveTarget(Proxy, GetToolManager());
Proxy->OnTransformChanged.AddUObject(this, &UBaseCreateFromSelectedTool::TransformChanged);
}
UpdateGizmoVisibility();
}
void UBaseCreateFromSelectedTool::TransformChanged(UTransformProxy* Proxy, FTransform Transform)
{
Preview->InvalidateResult();
}
FText UBaseCreateFromSelectedTool::GetActionName() const
{
return LOCTEXT("BaseCreateFromSelectedTool", "Generated Mesh");
}
TArray<UMaterialInterface*> UBaseCreateFromSelectedTool::GetOutputMaterials() const
{
return Preview->StandardMaterials;
}
void UBaseCreateFromSelectedTool::GenerateAsset(const FDynamicMeshOpResult& OpResult)
{
if (OpResult.Mesh.Get() == nullptr) return;
FTransform3d NewTransform;
if (Targets.Num() == 1) // in the single-selection case, shove the result back into the original component space
{
FTransform3d ToSourceComponentSpace = (FTransform3d)TargetComponentInterface(0)->GetWorldTransform().Inverse();
MeshTransforms::ApplyTransform(*OpResult.Mesh, ToSourceComponentSpace);
NewTransform = (FTransform3d)TargetComponentInterface(0)->GetWorldTransform();
}
else // in the multi-selection case, center the pivot for the combined result
{
FVector3d Center = OpResult.Mesh->GetCachedBounds().Center();
double Rescale = OpResult.Transform.GetScale().X;
FTransform3d LocalTransform(-Center * Rescale);
LocalTransform.SetScale(FVector3d(Rescale, Rescale, Rescale));
MeshTransforms::ApplyTransform(*OpResult.Mesh, LocalTransform);
NewTransform = OpResult.Transform;
NewTransform.SetScale(FVector3d::One());
NewTransform.SetTranslation(NewTransform.GetTranslation() + NewTransform.TransformVector(Center * Rescale));
}
// max len explicitly enforced here, would ideally notify user
FString UseBaseName = HandleSourcesProperties->OutputName.Left(250);
if (UseBaseName.IsEmpty())
{
UseBaseName = PrefixWithSourceNameIfSingleSelection(GetCreatedAssetName());
}
FCreateMeshObjectParams NewMeshObjectParams;
NewMeshObjectParams.TargetWorld = TargetWorld;
NewMeshObjectParams.Transform = (FTransform)NewTransform;
NewMeshObjectParams.BaseName = UseBaseName;
NewMeshObjectParams.Materials = GetOutputMaterials();
NewMeshObjectParams.SetMesh(OpResult.Mesh.Get());
if (OutputTypeProperties->OutputType == UCreateMeshObjectTypeProperties::AutoIdentifier)
{
UE::ToolTarget::ConfigureCreateMeshObjectParams(Targets[0], NewMeshObjectParams);
}
else
{
OutputTypeProperties->ConfigureCreateMeshObjectParams(NewMeshObjectParams);
}
FCreateMeshObjectResult Result = UE::Modeling::CreateMeshObject(GetToolManager(), MoveTemp(NewMeshObjectParams));
if (Result.IsOK() && Result.NewActor != nullptr)
{
ToolSelectionUtil::SetNewActorSelection(GetToolManager(), Result.NewActor);
}
}
void UBaseCreateFromSelectedTool::UpdateAsset(const FDynamicMeshOpResult& Result, UToolTarget* UpdateTarget)
{
check(Result.Mesh.Get() != nullptr);
IPrimitiveComponentBackedTarget* TargetComponent = Cast<IPrimitiveComponentBackedTarget>(UpdateTarget);
IMeshDescriptionCommitter* TargetMeshCommitter = Cast<IMeshDescriptionCommitter>(UpdateTarget);
IMaterialProvider* TargetMaterial = Cast<IMaterialProvider>(UpdateTarget);
FTransform3d TargetToWorld = (FTransform3d)TargetComponent->GetWorldTransform();
FTransform3d ResultTransform = Result.Transform;
MeshTransforms::ApplyTransform(*Result.Mesh, ResultTransform);
MeshTransforms::ApplyTransformInverse(*Result.Mesh, TargetToWorld);
TargetMeshCommitter->CommitMeshDescription([&](const IMeshDescriptionCommitter::FCommitterParams& CommitParams)
{
FDynamicMeshToMeshDescription Converter;
Converter.Convert(Result.Mesh.Get(), *CommitParams.MeshDescriptionOut);
});
FComponentMaterialSet MaterialSet;
MaterialSet.Materials = GetOutputMaterials();
TargetMaterial->CommitMaterialSetUpdate(MaterialSet, true);
}
FString UBaseCreateFromSelectedTool::PrefixWithSourceNameIfSingleSelection(const FString& AssetName) const
{
if (Targets.Num() == 1)
{
FString CurName = UE::Modeling::GetComponentAssetBaseName(TargetComponentInterface(0)->GetOwnerComponent());
return FString::Printf(TEXT("%s_%s"), *CurName, *AssetName);
}
else
{
return AssetName;
}
}
void UBaseCreateFromSelectedTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
{
if (Property && (Property->GetFName() == GET_MEMBER_NAME_CHECKED(UTransformInputsToolProperties, bShowTransformUI)))
{
UpdateGizmoVisibility();
}
else if (Property &&
( PropertySet == HandleSourcesProperties
|| Property->GetFName() == GET_MEMBER_NAME_CHECKED(UTransformInputsToolProperties, bSnapToWorldGrid)
))
{
// nothing
}
else
{
Preview->InvalidateResult();
}
}
void UBaseCreateFromSelectedTool::Shutdown(EToolShutdownType ShutdownType)
{
SaveProperties();
HandleSourcesProperties->SaveProperties(this);
OutputTypeProperties->SaveProperties(this, TEXT("OutputTypeFromInputTool"));
TransformProperties->SaveProperties(this);
FDynamicMeshOpResult Result = Preview->Shutdown();
// Restore (unhide) the source meshes
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(true);
}
if (ShutdownType == EToolShutdownType::Accept)
{
GetToolManager()->BeginUndoTransaction(GetActionName());
// Generate the result
AActor* KeepActor = nullptr;
if (HandleSourcesProperties->WriteOutputTo == EBaseCreateFromSelectedTargetType::NewAsset)
{
GenerateAsset(Result);
}
else
{
int32 TargetIndex = (HandleSourcesProperties->WriteOutputTo == EBaseCreateFromSelectedTargetType::FirstInputAsset) ? 0 : (Targets.Num() - 1);
KeepActor = TargetComponentInterface(TargetIndex)->GetOwnerActor();
UpdateAsset(Result, Targets[TargetIndex]);
}
TArray<AActor*> Actors;
for (int32 ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
{
AActor* Actor = TargetComponentInterface(ComponentIdx)->GetOwnerActor();
if (Actor != KeepActor)
{
Actors.Add(Actor);
}
}
HandleSourcesProperties->ApplyMethod(Actors, GetToolManager());
if (KeepActor != nullptr)
{
// select the actor we kept
ToolSelectionUtil::SetNewActorSelection(GetToolManager(), KeepActor);
}
GetToolManager()->EndUndoTransaction();
}
UInteractiveGizmoManager* GizmoManager = GetToolManager()->GetPairedGizmoManager();
GizmoManager->DestroyAllGizmosByOwner(this);
}
bool UBaseCreateFromSelectedTool::CanAccept() const
{
return Super::CanAccept() && Preview->HaveValidNonEmptyResult();
}
#undef LOCTEXT_NAMESPACE