You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
332 lines
11 KiB
C++
332 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RemeshMeshTool.h"
|
|
#include "ComponentSourceInterfaces.h"
|
|
#include "InteractiveToolManager.h"
|
|
#include "ToolBuilderUtil.h"
|
|
|
|
#include "Util/ColorConstants.h"
|
|
#include "ToolSetupUtil.h"
|
|
|
|
#include "DynamicMesh3.h"
|
|
|
|
#include "MeshDescriptionToDynamicMesh.h"
|
|
#include "DynamicMeshToMeshDescription.h"
|
|
|
|
#include "AssetGenerationUtil.h"
|
|
|
|
#include "SceneManagement.h" // for FPrimitiveDrawInterface
|
|
|
|
#include "TargetInterfaces/MaterialProvider.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 "URemeshMeshTool"
|
|
|
|
/*
|
|
* ToolBuilder
|
|
*/
|
|
const FToolTargetTypeRequirements& URemeshMeshToolBuilder::GetTargetRequirements() const
|
|
{
|
|
static FToolTargetTypeRequirements TypeRequirements({
|
|
UMaterialProvider::StaticClass(),
|
|
UMeshDescriptionCommitter::StaticClass(),
|
|
UMeshDescriptionProvider::StaticClass(),
|
|
UPrimitiveComponentBackedTarget::StaticClass()
|
|
});
|
|
return TypeRequirements;
|
|
}
|
|
|
|
bool URemeshMeshToolBuilder::CanBuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
return SceneState.TargetManager->CountSelectedAndTargetable(SceneState, GetTargetRequirements()) == 1;
|
|
}
|
|
|
|
UInteractiveTool* URemeshMeshToolBuilder::BuildTool(const FToolBuilderState& SceneState) const
|
|
{
|
|
URemeshMeshTool* NewTool = NewObject<URemeshMeshTool>(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
|
|
*/
|
|
URemeshMeshToolProperties::URemeshMeshToolProperties()
|
|
{
|
|
TargetTriangleCount = 5000;
|
|
SmoothingStrength = 0.25;
|
|
RemeshIterations = 20;
|
|
MaxRemeshIterations = 20;
|
|
ExtraProjectionIterations = 5;
|
|
bDiscardAttributes = false;
|
|
RemeshType = ERemeshType::Standard;
|
|
SmoothingType = ERemeshSmoothingType::MeanValue;
|
|
bPreserveSharpEdges = true;
|
|
bShowWireframe = true;
|
|
bShowGroupColors = false;
|
|
|
|
TargetEdgeLength = 5.0;
|
|
bFlips = true;
|
|
bSplits = true;
|
|
bCollapses = true;
|
|
bReproject = true;
|
|
bPreventNormalFlips = true;
|
|
bUseTargetEdgeLength = false;
|
|
}
|
|
|
|
URemeshMeshTool::URemeshMeshTool(const FObjectInitializer&)
|
|
{
|
|
BasicProperties = CreateDefaultSubobject<URemeshMeshToolProperties>(TEXT("RemeshProperties"));
|
|
// CreateDefaultSubobject automatically sets RF_Transactional flag, we need to clear it so that undo/redo doesn't affect tool properties
|
|
BasicProperties->ClearFlags(RF_Transactional);
|
|
}
|
|
|
|
void URemeshMeshTool::SetWorld(UWorld* World)
|
|
{
|
|
this->TargetWorld = World;
|
|
}
|
|
|
|
void URemeshMeshTool::SetAssetAPI(IToolsContextAssetAPI* AssetAPIIn)
|
|
{
|
|
this->AssetAPI = AssetAPIIn;
|
|
}
|
|
|
|
void URemeshMeshTool::Setup()
|
|
{
|
|
UInteractiveTool::Setup();
|
|
|
|
check(BasicProperties);
|
|
BasicProperties->RestoreProperties(this);
|
|
MeshStatisticsProperties = NewObject<UMeshStatisticsProperties>(this);
|
|
|
|
check(Targets.Num() > 0);
|
|
check(Targets[0]);
|
|
IPrimitiveComponentBackedTarget* TargetComponent = TargetComponentInterface(0);
|
|
IMeshDescriptionProvider* TargetMeshProvider = TargetMeshProviderInterface(0);
|
|
IMaterialProvider* TargetMaterial = TargetMaterialInterface(0);
|
|
|
|
// hide component and create + show preview
|
|
TargetComponent->SetOwnerVisibility(false);
|
|
Preview = NewObject<UMeshOpPreviewWithBackgroundCompute>(this, "Preview");
|
|
Preview->Setup(this->TargetWorld, this);
|
|
Preview->OnMeshUpdated.AddLambda([this](UMeshOpPreviewWithBackgroundCompute* Compute)
|
|
{
|
|
MeshStatisticsProperties->Update(*Compute->PreviewMesh->GetPreviewDynamicMesh());
|
|
});
|
|
FComponentMaterialSet MaterialSet;
|
|
TargetMaterial->GetMaterialSet(MaterialSet);
|
|
Preview->ConfigureMaterials( MaterialSet.Materials,
|
|
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager())
|
|
);
|
|
Preview->PreviewMesh->EnableWireframe(BasicProperties->bShowWireframe);
|
|
|
|
BasicProperties->WatchProperty(BasicProperties->bShowGroupColors,
|
|
[this](bool bNewValue) { UpdateVisualization();});
|
|
BasicProperties->WatchProperty(BasicProperties->bShowWireframe,
|
|
[this](bool bNewValue) { UpdateVisualization();});
|
|
|
|
OriginalMesh = MakeShared<FDynamicMesh3, ESPMode::ThreadSafe>();
|
|
FMeshDescriptionToDynamicMesh Converter;
|
|
Converter.Convert(TargetMeshProvider->GetMeshDescription(), *OriginalMesh);
|
|
|
|
Preview->PreviewMesh->SetTransform(TargetComponent->GetWorldTransform());
|
|
Preview->PreviewMesh->SetTangentsMode(EDynamicMeshTangentCalcType::AutoCalculated);
|
|
Preview->PreviewMesh->UpdatePreview(OriginalMesh.Get());
|
|
|
|
OriginalMeshSpatial = MakeShared<FDynamicMeshAABBTree3, ESPMode::ThreadSafe>(OriginalMesh.Get(), true);
|
|
|
|
// calculate initial mesh area (no utility fn yet)
|
|
// TODO: will need to change to account for component transform's Scale3D
|
|
InitialMeshArea = 0;
|
|
for (int tid : OriginalMesh->TriangleIndicesItr())
|
|
{
|
|
InitialMeshArea += OriginalMesh->GetTriArea(tid);
|
|
}
|
|
|
|
// set properties defaults
|
|
|
|
// arbitrary threshold of 5000 tris seems reasonable?
|
|
BasicProperties->TargetTriangleCount = (OriginalMesh->TriangleCount() < 5000) ? 5000 : OriginalMesh->TriangleCount();
|
|
BasicProperties->TargetEdgeLength = CalculateTargetEdgeLength(BasicProperties->TargetTriangleCount);
|
|
|
|
// add properties to GUI
|
|
AddToolPropertySource(BasicProperties);
|
|
AddToolPropertySource(MeshStatisticsProperties);
|
|
|
|
Preview->InvalidateResult();
|
|
|
|
SetToolDisplayName(LOCTEXT("ToolName", "Remesh"));
|
|
GetToolManager()->DisplayMessage(
|
|
LOCTEXT("OnStartTool", "Retriangulate the selected Mesh. Use the Boundary Constraints to preserve mesh borders. Enable Discard Attributes to ignore UV/Normal Seams. "),
|
|
EToolMessageLevel::UserNotification);
|
|
}
|
|
|
|
void URemeshMeshTool::Shutdown(EToolShutdownType ShutdownType)
|
|
{
|
|
BasicProperties->SaveProperties(this);
|
|
for (int ComponentIdx = 0; ComponentIdx < Targets.Num(); ComponentIdx++)
|
|
{
|
|
TargetComponentInterface(ComponentIdx)->SetOwnerVisibility(true);
|
|
}
|
|
FDynamicMeshOpResult Result = Preview->Shutdown();
|
|
if (ShutdownType == EToolShutdownType::Accept)
|
|
{
|
|
GenerateAsset(Result);
|
|
}
|
|
}
|
|
|
|
void URemeshMeshTool::OnTick(float DeltaTime)
|
|
{
|
|
Preview->Tick(DeltaTime);
|
|
}
|
|
|
|
TUniquePtr<FDynamicMeshOperator> URemeshMeshTool::MakeNewOperator()
|
|
{
|
|
TUniquePtr<FRemeshMeshOp> Op = MakeUnique<FRemeshMeshOp>();
|
|
|
|
Op->RemeshType = BasicProperties->RemeshType;
|
|
|
|
if (!BasicProperties->bUseTargetEdgeLength)
|
|
{
|
|
Op->TargetEdgeLength = CalculateTargetEdgeLength(BasicProperties->TargetTriangleCount);
|
|
}
|
|
else
|
|
{
|
|
Op->TargetEdgeLength = BasicProperties->TargetEdgeLength;
|
|
}
|
|
|
|
Op->bCollapses = BasicProperties->bCollapses;
|
|
Op->bDiscardAttributes = BasicProperties->bDiscardAttributes;
|
|
Op->bFlips = BasicProperties->bFlips;
|
|
Op->bPreserveSharpEdges = BasicProperties->bPreserveSharpEdges;
|
|
Op->MeshBoundaryConstraint = (EEdgeRefineFlags)BasicProperties->MeshBoundaryConstraint;
|
|
Op->GroupBoundaryConstraint = (EEdgeRefineFlags)BasicProperties->GroupBoundaryConstraint;
|
|
Op->MaterialBoundaryConstraint = (EEdgeRefineFlags)BasicProperties->MaterialBoundaryConstraint;
|
|
Op->bPreventNormalFlips = BasicProperties->bPreventNormalFlips;
|
|
Op->bReproject = BasicProperties->bReproject;
|
|
Op->bSplits = BasicProperties->bSplits;
|
|
Op->RemeshIterations = BasicProperties->RemeshIterations;
|
|
Op->MaxRemeshIterations = BasicProperties->MaxRemeshIterations;
|
|
Op->ExtraProjectionIterations = BasicProperties->ExtraProjectionIterations;
|
|
Op->SmoothingStrength = BasicProperties->SmoothingStrength;
|
|
Op->SmoothingType = BasicProperties->SmoothingType;
|
|
|
|
check(Targets.Num() > 0);
|
|
FTransform LocalToWorld = TargetComponentInterface(0)->GetWorldTransform();
|
|
Op->SetTransform(LocalToWorld);
|
|
|
|
Op->OriginalMesh = OriginalMesh;
|
|
Op->OriginalMeshSpatial = OriginalMeshSpatial;
|
|
|
|
Op->ProjectionTarget = nullptr;
|
|
Op->ProjectionTargetSpatial = nullptr;
|
|
|
|
return Op;
|
|
}
|
|
|
|
void URemeshMeshTool::Render(IToolsContextRenderAPI* RenderAPI)
|
|
{
|
|
FPrimitiveDrawInterface* PDI = RenderAPI->GetPrimitiveDrawInterface();
|
|
FTransform Transform = TargetComponentInterface(0)->GetWorldTransform();
|
|
|
|
if (BasicProperties->bShowConstraintEdges)
|
|
{
|
|
FColor LineColor(255, 0, 0);
|
|
const FDynamicMesh3* TargetMesh = Preview->PreviewMesh->GetPreviewDynamicMesh();
|
|
if (TargetMesh && TargetMesh->HasAttributes())
|
|
{
|
|
float PDIScale = RenderAPI->GetCameraState().GetPDIScalingFactor();
|
|
const FDynamicMeshUVOverlay* UVOverlay = TargetMesh->Attributes()->PrimaryUV();
|
|
for (int eid : TargetMesh->EdgeIndicesItr())
|
|
{
|
|
if (UVOverlay->IsSeamEdge(eid))
|
|
{
|
|
FVector3d A, B;
|
|
TargetMesh->GetEdgeV(eid, A, B);
|
|
PDI->DrawLine(Transform.TransformPosition((FVector)A), Transform.TransformPosition((FVector)B),
|
|
LineColor, 0, 2.0 * PDIScale, 1.0f, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void URemeshMeshTool::OnPropertyModified(UObject* PropertySet, FProperty* Property)
|
|
{
|
|
if ( Property )
|
|
{
|
|
if ( ( Property->GetFName() == GET_MEMBER_NAME_CHECKED(URemeshMeshToolProperties, bShowWireframe) ) ||
|
|
( Property->GetFName() == GET_MEMBER_NAME_CHECKED(URemeshMeshToolProperties, bShowGroupColors) ) )
|
|
{
|
|
//UpdateVisualization();
|
|
}
|
|
else
|
|
{
|
|
Preview->InvalidateResult();
|
|
}
|
|
}
|
|
}
|
|
|
|
void URemeshMeshTool::UpdateVisualization()
|
|
{
|
|
Preview->PreviewMesh->EnableWireframe(BasicProperties->bShowWireframe);
|
|
FComponentMaterialSet MaterialSet;
|
|
if (BasicProperties->bShowGroupColors)
|
|
{
|
|
MaterialSet.Materials = {ToolSetupUtil::GetSelectionMaterial(GetToolManager())};
|
|
Preview->PreviewMesh->SetTriangleColorFunction([this](const FDynamicMesh3* Mesh, int TriangleID)
|
|
{
|
|
return LinearColors::SelectFColor(Mesh->GetTriangleGroup(TriangleID));
|
|
},
|
|
UPreviewMesh::ERenderUpdateMode::FastUpdate);
|
|
}
|
|
else
|
|
{
|
|
TargetMaterialInterface(0)->GetMaterialSet(MaterialSet);
|
|
Preview->PreviewMesh->ClearTriangleColorFunction(UPreviewMesh::ERenderUpdateMode::FastUpdate);
|
|
}
|
|
Preview->ConfigureMaterials(MaterialSet.Materials,
|
|
ToolSetupUtil::GetDefaultWorkingMaterial(GetToolManager()));
|
|
}
|
|
|
|
double URemeshMeshTool::CalculateTargetEdgeLength(int TargetTriCount)
|
|
{
|
|
double TargetTriArea = InitialMeshArea / (double)TargetTriCount;
|
|
double EdgeLen = TriangleUtil::EquilateralEdgeLengthForArea(TargetTriArea);
|
|
return (double)FMath::RoundToInt(EdgeLen*100.0) / 100.0;
|
|
}
|
|
|
|
bool URemeshMeshTool::CanAccept() const
|
|
{
|
|
return Super::CanAccept() && Preview->HaveValidResult();
|
|
}
|
|
|
|
void URemeshMeshTool::GenerateAsset(const FDynamicMeshOpResult& Result)
|
|
{
|
|
GetToolManager()->BeginUndoTransaction(LOCTEXT("RemeshMeshToolTransactionName", "Remesh Mesh"));
|
|
|
|
check(Result.Mesh.Get() != nullptr);
|
|
TargetMeshCommitterInterface(0)->CommitMeshDescription([&Result](const IMeshDescriptionCommitter::FCommitterParams& CommitParams)
|
|
{
|
|
FDynamicMeshToMeshDescription Converter;
|
|
|
|
// full conversion if normal topology changed or faces were inverted
|
|
Converter.Convert(Result.Mesh.Get(), *CommitParams.MeshDescriptionOut);
|
|
});
|
|
|
|
GetToolManager()->EndUndoTransaction();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|