Files
2025-05-18 13:04:45 +08:00

463 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Math/IntVector.h"
#include "TriangleTypes.h"
#include "SegmentTypes.h"
#include "BoxTypes.h"
#include "DynamicMesh/InfoTypes.h"
#define UE_API DYNAMICMESH_API
namespace UE
{
namespace Geometry
{
/**
* FGeoSelectionID provides a pair of 32-bit unsigned integers that can
* be packed into a 64-bit unsigned integer for use with FGeometrySelection.
* This is generally intended to be used to encode a mesh geometry ID
* (eg TriangleID, EdgeID, VertexID) combined with a "Topology ID",
* eg something like a Face Group ID. However none of this is enforced
* and so a caller can use these two integers for any purpose.
*
* Note that since the ints are unsigned, IndexConstants::InvalidID
* is not directly representable (-1 will become positive 0xFFFFFFFF).
*/
struct FGeoSelectionID
{
/** Topology ID, stored in upper 32 bits when packed into 64-bits */
uint32 TopologyID;
/** Geometry ID, stored in lower 32 bits when packed into 64-bits */
uint32 GeometryID;
FGeoSelectionID()
{
GeometryID = 0;
TopologyID = 0;
}
/**
* Initialize the TopologyID and GeometryID with the given values
*/
explicit FGeoSelectionID(uint32 GeometryIDIn, uint32 TopologyIDIn = 0)
{
GeometryID = GeometryIDIn;
TopologyID = TopologyIDIn;
}
/**
* Initialize the TopologyID and GeometryID by unpacking the 64-bit packed EncodedID
*/
explicit FGeoSelectionID(uint64 EncodedID)
{
GeometryID = EncodedID & 0x00000000FFFFFFFF;
TopologyID = (EncodedID & 0xFFFFFFFF00000000) >> 32;
}
/**
* @return the packed 64-bit representation of [TopologyID, GeometryID]
*/
uint64 Encoded() const
{
return ((uint64)TopologyID << 32) | (uint64)GeometryID;
}
/**
* @return a FGeoSelectionID initialized with the given TriangleID, and a TopologyID of 0
*/
static FGeoSelectionID MeshTriangle(int32 TriangleID)
{
return FGeoSelectionID((uint32)TriangleID, 0);
}
/**
* @return a FGeoSelectionID initialized with the given VertexID, and a TopologyID of 0
*/
static FGeoSelectionID MeshVertex(int32 VertexID)
{
return FGeoSelectionID((uint32)VertexID, 0);
}
/**
* @return a FGeoSelectionID initialized with the given 32-bit encoding of the MeshTriEdgeID, and a TopologyID of 0
*/
static FGeoSelectionID MeshEdge(UE::Geometry::FMeshTriEdgeID EdgeKey)
{
return FGeoSelectionID(EdgeKey.Encoded());
}
/**
* @return a FGeoSelectionID initialized with the given TriangleID and GroupID used as the TopologyID
*/
static FGeoSelectionID GroupFace(int32 TriangleID, int32 GroupID)
{
return FGeoSelectionID((uint32)TriangleID, (uint32)GroupID);
}
friend uint32 GetTypeHash(const FGeoSelectionID& TopoKey)
{
return ::GetTypeHash(TopoKey.Encoded());
}
};
/**
* Type of selected Elements in a FGeometrySelection
*/
enum class EGeometryElementType
{
/** Mesh Vertices, Polygroup Corners, ... */
Vertex = 1,
/** Mesh Edges, Polygroup Edges, ... */
Edge = 2,
/** Mesh Triangles, Polygroup Faces, ... */
Face = 4
};
/**
* Type of selected Topology in a FGeometrySelection.
*/
enum class EGeometryTopologyType
{
Triangle = 1,
Polygroup = 2
};
/**
* FGeometrySelection represents a subset of geometric elements of a larger
* object, for example a Mesh (currently the only use case). The main selection
* is represented via 64-bit unsigned integers. The integers are stored in a TSet
* for efficient unique adds and removals. No assumptions are made about the values,
* they could be (eg) mesh indices, IDs of some type, or even pointer values.
*/
struct FGeometrySelection
{
/** Type of geometric element represented by this selection, if applicable */
EGeometryElementType ElementType = EGeometryElementType::Face;
/** Type of geometric topology this selection is defined relative to, if applicable */
EGeometryTopologyType TopologyType = EGeometryTopologyType::Triangle;
/** Set of selected items/elements */
TSet<uint64> Selection;
/**
* @return true if Selection is empty
*/
bool IsEmpty() const
{
return Selection.Num() == 0;
}
/**
* @return number of elements in Selection
*/
int32 Num() const
{
return Selection.Num();
}
/**
* Clear the Selection (may not release memory)
*/
void Reset()
{
Selection.Reset();
}
/**
* Initialize the Element and Topology types for this Selection
*/
void InitializeTypes(EGeometryElementType ElementTypeIn, EGeometryTopologyType TopologyTypeIn)
{
ElementType = ElementTypeIn;
TopologyType = TopologyTypeIn;
}
/**
* Initialize the Element and Topology types for this Selection based on another Selection
*/
void InitializeTypes(const FGeometrySelection& FromSelection)
{
ElementType = FromSelection.ElementType;
TopologyType = FromSelection.TopologyType;
}
/**
* @return true if the two selections have the same type
*/
bool IsSameType(const FGeometrySelection& OtherSelection) const
{
return ElementType == OtherSelection.ElementType && TopologyType == OtherSelection.TopologyType;
}
};
/**
* FGeometrySelectionHitQueryConfig defines the desired settings for a "hit query" on
* selected meshes/objects.
*/
struct FGeometrySelectionHitQueryConfig
{
/** Type of object topology to query */
EGeometryTopologyType TopologyType = EGeometryTopologyType::Polygroup;
/** Type of object topological element to query */
EGeometryElementType ElementType = EGeometryElementType::Face;
/** If true, only "visible" elements are considered, otherwise query may return obscured elements (eg, hidden edges occluded by mesh surface) */
bool bOnlyVisible = true;
/**
* When false, triangles that are facing away from the camera are not considered.
* This can be useful when working with inside-out meshes, where we usually want to select the farther, visible wall.
*/
bool bHitBackFaces = false;
};
/**
* Type of change, relative to a FGeometrySelection
*/
enum class EGeometrySelectionChangeType
{
/** Elements Added to Selection */
Add,
/** Elements Removed from Selection */
Remove,
/** Selected Elements replaced with a new set of Selected Elements */
Replace
};
/**
* FGeometrySelectionUpdateConfig is passed to various Selection Editing functions/classes
* to indicate what type of change should be applied to a FGeometrySelection
* (based on additional parameters, generally)
*/
struct FGeometrySelectionUpdateConfig
{
EGeometrySelectionChangeType ChangeType = EGeometrySelectionChangeType::Add;
};
/**
* FGeometrySelectionDelta represents a change to the set of elements in a FGeometrySelection.
* The delta is ordered, ie if the Delta was to be re-applied, the Removed elements should
* be removed before the Added elements are added.
*
* (Currently there is no way to swap the order)
*/
struct FGeometrySelectionDelta
{
/**
* Elements removed from a FGeometrySelection during some selection edit
*/
TArray<uint64> Removed;
/**
* Elements added to a FGeometrySelection during some selection edit
*/
TArray<uint64> Added;
/** @return true if the Delta is empty (nothing Removed or Added) */
bool IsEmpty() const { return Removed.Num() == 0 && Added.Num() == 0; }
};
/**
* 3D Bounding information for a FGeometrySelection
*/
struct FGeometrySelectionBounds
{
UE::Geometry::FAxisAlignedBox3d WorldBounds = UE::Geometry::FAxisAlignedBox3d::Empty();
};
/**
* 3D Geometry representing a FGeometrySelection, for example
* suitable for passing to rendering code, etc
*/
struct FGeometrySelectionElements
{
TArray<UE::Geometry::FTriangle3d> Triangles;
TArray<UE::Geometry::FSegment3d> Segments;
TArray<FVector3d> Points;
void Reset()
{
Triangles.Reset();
Segments.Reset();
Points.Reset();
}
};
struct FGeometrySelectionUpdateResult
{
bool bSelectionMissed = false;
bool bSelectionModified = false;
FGeometrySelectionDelta SelectionDelta;
};
/**
* FGeometrySelectionEditor is a utility/helper class used for modifying
* a FGeometrySelection. The various functions can be used to add or remove
* to the Selection, while also tracking what changed, returned via
* FGeometrySelectionDelta structs.
*
* In some cases (eg Polygroup selections on faces and edges), only the TopologyIDs
* are unique, and the same TopologyID may be paired with an arbitrary TriangleID/EdgeID.
* FGeometrySelection is currently not aware of this distinction, however it means when
* (for example) adding to a selection, a new uint64 ID should only be added if the
* TopologyID is unique, ie the ElementID bit should be ignored. FGeometrySelectionEditor can
* do this, by enabling TopologyIDFiltering in the various setup/config functions.
*
* Note, however, this means that FGeometrySelectionEditor.IsSelected() must be used
* to determine whether an ID is selected, rather than Selection.Contains()
*
*/
class FGeometrySelectionEditor
{
public:
/**
* Initialize the Editor with the given Selection. The
* TargetSelectionIn must live longer than the FGeometrySelectionEditor
*/
UE_API void Initialize(
FGeometrySelection* TargetSelectionIn,
const FGeometrySelectionHitQueryConfig& QueryConfigIn,
bool bEnableTopologyIDFiltering);
/**
* Initialize the Editor with the given Selection. The
* TargetSelectionIn must live longer than the FGeometrySelectionEditor
*/
UE_API void Initialize(
FGeometrySelection* TargetSelectionIn,
bool bEnableTopologyIDFiltering);
/** @return the Element Type of the Target Selection */
EGeometryElementType GetElementType() const { return TargetSelection->ElementType; }
/** @return the Topology Type of the Target Selection */
EGeometryTopologyType GetTopologyType() const { return TargetSelection->TopologyType; }
/** @return the active configuration for this Selection Editor, eg what type of element/topology is being selected. This is redundant w/ the above fields. */
const FGeometrySelectionHitQueryConfig& GetQueryConfig() const { return QueryConfig; }
bool GetIsTopologyIDFilteringEnabled() const { return bEnableTopologyIDFiltering; }
/**
* Update the active QueryConfig for this SelectionEditor. This is necessary to keep it in sync w/ the TargetSelection, if
* the ElementType or TopologyType are modified. This perhaps should be revisited as they basically
* always need to be the same...
*/
UE_API void UpdateQueryConfig(const FGeometrySelectionHitQueryConfig& NewConfig, bool bEnableTopologyIDFilteringIn);
/** @return true if the given ID is currently selected in the Target Selection */
UE_API bool IsSelected(uint64 ID) const;
/** Clear the Target Selection and return change information in DeltaOut */
UE_API void ClearSelection(FGeometrySelectionDelta& DeltaOut);
/** Remove ID from Selection */
UE_API bool RemoveFromSelection(uint64 ID);
/** Access the Selection object this Editor is modifying */
const FGeometrySelection& GetSelection() const { return *TargetSelection; }
/** Add the items to the Target Selection */
bool Select(uint64 ID)
{
if (IsSelected(ID) == false)
{
TargetSelection->Selection.Add(ID);
return true;
}
return false;
}
/** Add the items in the List to the Target Selection and return change information in DeltaOut */
template<typename ListType>
bool Select(const ListType& List, FGeometrySelectionDelta& DeltaOut)
{
int32 NumAdded = 0;
for (uint64 ID : List)
{
if (IsSelected(ID) == false)
{
TargetSelection->Selection.Add(ID);
DeltaOut.Added.Add(ID);
NumAdded++;
}
}
return (NumAdded > 0);
}
/** Remove the items in the List from the Target Selection and return change information in DeltaOut */
template<typename ListType>
bool Deselect(const ListType& List, FGeometrySelectionDelta& DeltaOut)
{
int32 TotalRemoved = 0;
for (uint64 ID : List)
{
if (RemoveFromSelection(ID))
{
DeltaOut.Removed.Add(ID);
TotalRemoved++;
}
}
return (TotalRemoved > 0);
}
/** Replace the current set of selected items with those in the NewSelection, and return change information in DeltaOut */
UE_API bool Replace(const FGeometrySelection& NewSelection, FGeometrySelectionDelta& DeltaOut);
protected:
FGeometrySelection* TargetSelection = nullptr;
FGeometrySelectionHitQueryConfig QueryConfig;
bool bEnableTopologyIDFiltering = false;
UE_API uint64 RemapToExistingTopologyID(uint64 ID, bool& bFoundOut) const;
};
/**
* FGeometrySelectionPreview is a combined FGeometrySelection and FGeometrySelectionEditor.
* The purpose of this class is to support things like hover-highlighting, where the Selection
* system needs to be queried but the result is going to be discarded. FGeometrySelectionPreview can
* only be constructed based on a parent FGeometrySelectionEditor, from which it derives it's
* configuration, which is then how the rest of the system knows what geometry to provide as a preview/etc
*/
class FGeometrySelectionPreview : public FGeometrySelectionEditor
{
public:
FGeometrySelection PreviewSelection;
FGeometrySelectionPreview(const FGeometrySelectionEditor& ActiveEditor)
{
PreviewSelection.InitializeTypes(ActiveEditor.GetSelection());
Initialize(&PreviewSelection, ActiveEditor.GetQueryConfig(), ActiveEditor.GetIsTopologyIDFilteringEnabled());
}
bool IsEmpty() const { return PreviewSelection.IsEmpty(); }
};
} // end namespace UE::Geometry
} // end namespace UE
#undef UE_API