Files
UnrealEngineUWP/Engine/Plugins/Experimental/MeshModelingToolset/Source/MeshModelingTools/Public/GroupEdgeInsertionTool.h

247 lines
7.8 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "BaseBehaviors/BehaviorTargetInterfaces.h"
#include "ModelingOperators.h" //IDynamicMeshOperatorFactory
#include "InteractiveTool.h" //UInteractiveToolPropertySet
#include "InteractiveToolBuilder.h" //UInteractiveToolBuilder
#include "InteractiveToolChange.h" //FToolCommandChange
#include "MeshOpPreviewHelpers.h" //FDynamicMeshOpResult
#include "Operations/GroupEdgeInserter.h"
#include "Selection/GroupTopologySelector.h"
#include "SingleSelectionTool.h"
#include "ToolDataVisualizer.h"
#include "BaseTools/SingleSelectionMeshEditingTool.h"
#include "GroupEdgeInsertionTool.generated.h"
PREDECLARE_USE_GEOMETRY_CLASS(FDynamicMeshChange);
using UE::Geometry::FGroupEdgeInserter;
using UE::Geometry::FDynamicMesh3;
UCLASS()
class MESHMODELINGTOOLS_API UGroupEdgeInsertionToolBuilder : public USingleSelectionMeshEditingToolBuilder
{
GENERATED_BODY()
public:
virtual USingleSelectionMeshEditingTool* CreateNewTool(const FToolBuilderState& SceneState) const override;
};
UENUM()
enum class EGroupEdgeInsertionMode
{
/** Existing groups will be deleted and new triangles will be created for the new groups.
Keeps topology simple but breaks non-planar groups. */
Retriangulate,
/** Keeps existing triangles and cuts them to create a new path. May result in fragmented triangles over time.*/
PlaneCut
};
UCLASS()
class MESHMODELINGTOOLS_API UGroupEdgeInsertionProperties : public UInteractiveToolPropertySet
{
GENERATED_BODY()
public:
/** Determines how group edges are added to the geometry */
UPROPERTY(EditAnywhere, Category = InsertEdge)
EGroupEdgeInsertionMode InsertionMode = EGroupEdgeInsertionMode::Retriangulate;
UPROPERTY(EditAnywhere, Category = InsertEdge)
bool bWireframe = true;
/** How close a new loop edge needs to pass next to an existing vertex to use that vertex rather than creating a new one (used for plane cut). */
UPROPERTY(EditAnywhere, Category = InsertEdgeLoop, AdvancedDisplay)
double VertexTolerance = 0.001;
};
UCLASS()
class MESHMODELINGTOOLS_API UGroupEdgeInsertionOperatorFactory : public UObject, public UE::Geometry::IDynamicMeshOperatorFactory
{
GENERATED_BODY()
public:
// IDynamicMeshOperatorFactory API
virtual TUniquePtr<UE::Geometry::FDynamicMeshOperator> MakeNewOperator() override;
UPROPERTY()
UGroupEdgeInsertionTool* Tool;
};
/** Tool for inserting group edges into polygons of the mesh. */
UCLASS()
class MESHMODELINGTOOLS_API UGroupEdgeInsertionTool : public USingleSelectionMeshEditingTool, public IHoverBehaviorTarget, public IClickBehaviorTarget
{
GENERATED_BODY()
enum class EToolState
{
GettingStart,
GettingEnd,
WaitingForInsertComplete
};
using FRay3d = UE::Geometry::FRay3d;
public:
friend class UGroupEdgeInsertionOperatorFactory;
friend class FGroupEdgeInsertionFirstPointChange;
friend class FGroupEdgeInsertionChange;
UGroupEdgeInsertionTool() {};
virtual void Setup() override;
virtual void Shutdown(EToolShutdownType ShutdownType) override;
virtual void OnTick(float DeltaTime) override;
virtual void Render(IToolsContextRenderAPI* RenderAPI) override;
virtual bool HasCancel() const override { return true; }
virtual bool HasAccept() const override { return true; }
virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) override;
// IHoverBehaviorTarget
virtual FInputRayHit BeginHoverSequenceHitTest(const FInputDeviceRay& PressPos) override;
virtual void OnBeginHover(const FInputDeviceRay& DevicePos) override {}
virtual bool OnUpdateHover(const FInputDeviceRay& DevicePos) override;
virtual void OnEndHover() override;
// IClickBehaviorTarget
virtual FInputRayHit IsHitByClick(const FInputDeviceRay& ClickPos) override;
virtual void OnClicked(const FInputDeviceRay& ClickPos) override;
protected:
UPROPERTY()
UGroupEdgeInsertionProperties* Settings = nullptr;
UPROPERTY()
UMeshOpPreviewWithBackgroundCompute* Preview;
TSharedPtr<FDynamicMesh3, ESPMode::ThreadSafe> CurrentMesh;
TSharedPtr<FGroupTopology, ESPMode::ThreadSafe> CurrentTopology;
UE::Geometry::FDynamicMeshAABBTree3 MeshSpatial;
FGroupTopologySelector TopologySelector;
TArray<TPair<FVector3d, FVector3d>> PreviewEdges;
TArray<FVector3d> PreviewPoints;
FViewCameraState CameraState;
FToolDataVisualizer ExistingEdgesRenderer;
FToolDataVisualizer PreviewEdgeRenderer;
FGroupTopologySelector::FSelectionSettings TopologySelectorSettings;
// Inputs from user interaction:
FGroupEdgeInserter::FGroupEdgeSplitPoint StartPoint;
int32 StartTopologyID = FDynamicMesh3::InvalidID;
bool bStartIsCorner = false;
FGroupEdgeInserter::FGroupEdgeSplitPoint EndPoint;
int32 EndTopologyID = FDynamicMesh3::InvalidID;
bool bEndIsCorner = false;
int32 CommonGroupID = FDynamicMesh3::InvalidID;
int32 CommonBoundaryIndex = FDynamicMesh3::InvalidID;
// State control:
EToolState ToolState = EToolState::GettingStart;
bool bShowingBaseMesh = false;
bool bLastComputeSucceeded = false;
TSharedPtr<FGroupTopology, ESPMode::ThreadSafe> LatestOpTopologyResult;
TSharedPtr<TSet<int32>, ESPMode::ThreadSafe> LatestOpChangedTids;
int32 CurrentChangeStamp = 0;
void SetupPreview();
bool TopologyHitTest(const FRay& WorldRay, FVector3d& RayPositionOut, FRay3d* LocalRayOut = nullptr);
bool GetHoveredItem(const FRay& WorldRay, FGroupEdgeInserter::FGroupEdgeSplitPoint& PointOut,
int32& TopologyElementIDOut, bool& bIsCornerOut, FVector3d& PositionOut, FRay3d* LocalRayOut = nullptr);
void ConditionallyUpdatePreview(const FGroupEdgeInserter::FGroupEdgeSplitPoint& NewEndPoint,
int32 NewEndTopologyID, bool bNewEndIsCorner, int32 NewCommonGroupID, int32 NewBoundaryIndex);
void ClearPreview(bool bClearDrawnElements = true, bool bForce = false);
void GetCornerTangent(int32 CornerID, int32 GroupID, int32 BoundaryIndex, FVector3d& TangentOut);
/**
* Expires the tool-associated changes in the undo/redo stack. The ComponentTarget
* changes will stay (we want this).
*/
inline void ExpireChanges()
{
++CurrentChangeStamp;
}
};
/**
* This should get emitted when selecting the first point in an edge insertion so that we can undo it.
*/
class MESHMODELINGTOOLS_API FGroupEdgeInsertionFirstPointChange : public FToolCommandChange
{
public:
FGroupEdgeInsertionFirstPointChange(int32 CurrentChangeStamp)
: ChangeStamp(CurrentChangeStamp)
{};
virtual void Apply(UObject* Object) override {};
virtual void Revert(UObject* Object) override;
virtual bool HasExpired(UObject* Object) const override
{
UGroupEdgeInsertionTool* Tool = Cast<UGroupEdgeInsertionTool>(Object);
return bHaveDoneUndo || Tool->CurrentChangeStamp != ChangeStamp
|| Tool->ToolState != UGroupEdgeInsertionTool::EToolState::GettingEnd;
// TODO: this is a bit of a hack in that we should probably have a separate stamp
// for expiring these instead of letting the tool state help (they expire after
// each new insertion unlike the other changes, which expire on tool close).
}
virtual FString ToString() const override
{
return TEXT("FGroupEdgeInsertionFirstPointChange");
}
protected:
int32 ChangeStamp;
bool bHaveDoneUndo = false;
};
/**
* Wraps a FDynamicMeshChange so that it can be expired and so that other data
* structures in the tool can be updated.
*/
class MESHMODELINGTOOLS_API FGroupEdgeInsertionChange : public FToolCommandChange
{
public:
FGroupEdgeInsertionChange(TUniquePtr<FDynamicMeshChange> MeshChangeIn, int32 CurrentChangeStamp)
: MeshChange(MoveTemp(MeshChangeIn))
, ChangeStamp(CurrentChangeStamp)
{};
virtual void Apply(UObject* Object) override;
virtual void Revert(UObject* Object) override;
virtual bool HasExpired(UObject* Object) const override
{
return Cast<UGroupEdgeInsertionTool>(Object)->CurrentChangeStamp != ChangeStamp;
}
virtual FString ToString() const override
{
return TEXT("FGroupEdgeInsertionChange");
}
protected:
TUniquePtr<FDynamicMeshChange> MeshChange;
int32 ChangeStamp;
};