339 lines
10 KiB
C++
339 lines
10 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "DynamicMesh/DynamicMesh3.h"
|
|
#include "DynamicMesh/DynamicMeshAttributeSet.h"
|
|
#include "DynamicMesh/DynamicMeshAABBTree3.h"
|
|
|
|
#define UE_API DYNAMICMESH_API
|
|
|
|
namespace UE
|
|
{
|
|
namespace Geometry
|
|
{
|
|
|
|
/**
|
|
* Wrapper around a Mesh and UV Overlay that provides UVs triangles as vertices.
|
|
* This allows building a TMeshAABBTree3 for the UV mesh
|
|
*/
|
|
struct FDynamicMeshUVMesh
|
|
{
|
|
const FDynamicMesh3* Mesh = nullptr;
|
|
const FDynamicMeshUVOverlay* UV = nullptr;
|
|
|
|
inline bool IsTriangle(int32 TriangleIndex) const
|
|
{
|
|
return UV->IsSetTriangle(TriangleIndex);
|
|
}
|
|
|
|
inline bool IsVertex(int32 VertexIndex) const
|
|
{
|
|
return UV->IsElement(VertexIndex);
|
|
}
|
|
|
|
inline int32 MaxTriangleID() const
|
|
{
|
|
return Mesh->MaxTriangleID();
|
|
}
|
|
|
|
inline int32 TriangleCount() const
|
|
{
|
|
return Mesh->TriangleCount();
|
|
}
|
|
|
|
inline int32 MaxVertexID() const
|
|
{
|
|
return UV->MaxElementID();
|
|
}
|
|
|
|
inline int32 VertexCount() const
|
|
{
|
|
return UV->ElementCount();
|
|
}
|
|
|
|
inline uint64 GetChangeStamp() const
|
|
{
|
|
return Mesh->GetChangeStamp();
|
|
}
|
|
|
|
inline FIndex3i GetTriangle(int32 TriangleIndex) const
|
|
{
|
|
return UV->GetTriangle(TriangleIndex);
|
|
}
|
|
|
|
inline FVector3d GetVertex(int32 ElementIndex) const
|
|
{
|
|
FVector2f Elem = UV->GetElement(ElementIndex);
|
|
return FVector3d(Elem.X, Elem.Y, 0);
|
|
}
|
|
|
|
template<typename VecType>
|
|
inline void GetTriVertices(int32 TriangleIndex, VecType& V0, VecType& V1, VecType& V2) const
|
|
{
|
|
FIndex3i TriIndices = UV->GetTriangle(TriangleIndex);
|
|
V0 = GetVertex(TriIndices.A);
|
|
V1 = GetVertex(TriIndices.B);
|
|
V2 = GetVertex(TriIndices.C);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Information about a UV sample
|
|
*/
|
|
struct FMeshUVSampleInfo
|
|
{
|
|
// Triangle containing the sample
|
|
int32 TriangleIndex = IndexConstants::InvalidID;
|
|
|
|
// 3D vertices
|
|
FIndex3i MeshVertices;
|
|
// 3D triangle
|
|
FTriangle3d Triangle3D { FVector3d::ZeroVector, FVector3d::ZeroVector, FVector3d::ZeroVector };
|
|
|
|
// UV overlay vertices
|
|
FIndex3i UVVertices;
|
|
// 2D triangle
|
|
FTriangle2d TriangleUV { FVector2d(0.0,0.0), FVector2d(0.0,0.0), FVector2d(0.0,0.0) };
|
|
|
|
// barycentric coords in triangle
|
|
FVector3d BaryCoords = FVector3d::ZeroVector;
|
|
// surface point (lying in Triangle3D)
|
|
FVector3d SurfacePoint = FVector3d::ZeroVector;
|
|
};
|
|
|
|
|
|
/** Types of query that FMeshSurfaceUVSampler/TMeshSurfaceUVSampler supports */
|
|
enum class EMeshSurfaceSamplerQueryType : uint8
|
|
{
|
|
/** Query with arbitrary UV value */
|
|
UVOnly,
|
|
/** Query with given TriangleID and UV that is assumed to lie within that Triangle */
|
|
TriangleAndUV
|
|
};
|
|
|
|
// Hacky base class to avoid 8 bytes of padding after the vtable
|
|
class FMeshSurfaceUVSamplerFixLayout
|
|
{
|
|
public:
|
|
virtual ~FMeshSurfaceUVSamplerFixLayout() = default;
|
|
};
|
|
|
|
/**
|
|
* FMeshSurfaceUVSampler computes FMeshUVSampleInfo's on a given mesh at given UV-space positions, this info can then
|
|
* be used by an external function to compute some application-specific sample.
|
|
*
|
|
* Note: This class is similar to TMeshSurfaceUVSampler (the "old" class) but provides a more convenient API. This class
|
|
* computes the sample info and that's it. The old class computes the sample info internally but also immediately
|
|
* forwards it to an application-specific sampling function which must be provided as a callback and hence has a fixed
|
|
* signature and requires that the sample type is passed as a template parameter. Also the old class is more awkward to
|
|
* use when sampling multiple things, see the documentation of TMeshSurfaceUVSampler for more details.
|
|
*/
|
|
class FMeshSurfaceUVSampler : public FMeshSurfaceUVSamplerFixLayout
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Initialize the sampler.
|
|
* @param Mesh Mesh to sample
|
|
* @param UVOverlay UV overlay of Mesh
|
|
* @param QueryType Type of query functions we will call. Some queries are simpler and do not require spatial data structures
|
|
*/
|
|
UE_API virtual void Initialize(const FDynamicMesh3* Mesh, const FDynamicMeshUVOverlay* UVOverlay, EMeshSurfaceSamplerQueryType QueryType);
|
|
|
|
/**
|
|
* Fill in the given SampleInfo for the given UV location.
|
|
* @pre The Initialize function must have been called with EMeshSurfaceSamplerQueryType::UVOnly
|
|
* @return If the query location is not on a triangle in the Mesh then return false (SampleInfo will be invalid)
|
|
*/
|
|
UE_API virtual bool QuerySampleInfo(const FVector2d& UV, FMeshUVSampleInfo& SampleInfo);
|
|
|
|
/**
|
|
* Fill in the given SampleInfo for the given UV location in the given UVTriangleID.
|
|
* @return If the query location is not on a triangle in the Mesh then return false (SampleInfo will be invalid)
|
|
*/
|
|
UE_API virtual bool QuerySampleInfo(int32 UVTriangleID, const FVector2d& UV, FMeshUVSampleInfo& SampleInfo);
|
|
|
|
protected:
|
|
|
|
const FDynamicMesh3* Mesh = nullptr;
|
|
const FDynamicMeshUVOverlay* UVOverlay = nullptr;
|
|
EMeshSurfaceSamplerQueryType QueryType = EMeshSurfaceSamplerQueryType::TriangleAndUV;
|
|
|
|
bool bUVMeshSpatialInitialized = false;
|
|
|
|
// BV tree for finding triangle for a given UV. Not always initialized.
|
|
FDynamicMeshUVMesh UVMeshAdapter;
|
|
TMeshAABBTree3<FDynamicMeshUVMesh> UVMeshSpatial;
|
|
void InitializeUVMeshSpatial();
|
|
};
|
|
|
|
/**
|
|
* Consider using FMeshSurfaceUVSampler instead, it is more flexible and easier to understand!
|
|
*
|
|
* TMeshSurfaceUVSampler computes point samples of the given SampleType at positions on the mesh
|
|
* based on UV-space positions. The standard use case for this class is to compute samples used
|
|
* in building Normal Maps, AO Maps, etc.
|
|
*
|
|
* Note that for UVOnly sample type, an internal UV-space BVTree will be constructed, and each
|
|
* sample will query that to find the UV/3D correspondence. If you already know the TriangleID,
|
|
* you can use the TriangleAndUV type to avoid the BVTree construction and queries.
|
|
*
|
|
* Note that if you need to sample multiple things, rather than building up an uber-SampleType, you
|
|
* can first compute a sample with SampleType=FMeshUVSampleInfo to find the correspondence information,
|
|
* and then construction additional samplers of type EMeshSurfaceSamplerQueryType::TriangleAndUV,
|
|
* and call CachedSampleUV(), to avoid expensive BVTree constructions and UV-to-3D recalculation.
|
|
*/
|
|
template<typename SampleType>
|
|
class TMeshSurfaceUVSampler : public FMeshSurfaceUVSamplerFixLayout
|
|
{
|
|
public:
|
|
|
|
/**
|
|
* Configure the sampler.
|
|
* @param MeshIn mesh to sample
|
|
* @param UVOverlayIn UV overlay of MeshIn to sample
|
|
* @param QueryTypeIn type of query functions we will call. Some queries are simpler and do not require spatial data structures.
|
|
* @param ZeroValueIn the value that is returned if a sample cannot be found
|
|
* @param SampleValueFunctionIn This function is called to compute the sample at a given UV location. SampleInfo provides the necessary UV/3D correspondence data.
|
|
*/
|
|
virtual void Initialize(
|
|
const FDynamicMesh3* MeshIn,
|
|
const FDynamicMeshUVOverlay* UVOverlayIn,
|
|
EMeshSurfaceSamplerQueryType QueryTypeIn,
|
|
SampleType ZeroValueIn,
|
|
TUniqueFunction<void(const FMeshUVSampleInfo& SampleInfo, SampleType& SampleValueOut)> SampleValueFunctionIn)
|
|
{
|
|
this->Mesh = MeshIn;
|
|
this->UVOverlay = UVOverlayIn;
|
|
this->ZeroValue = ZeroValueIn;
|
|
this->ValueFunction = MoveTemp(SampleValueFunctionIn);
|
|
|
|
// initialize spatial data structure if we need it
|
|
QueryType = QueryTypeIn;
|
|
if (QueryType == EMeshSurfaceSamplerQueryType::UVOnly)
|
|
{
|
|
InitializeBVTree();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Compute a sample at the given UV location
|
|
* @return true if valid sample was computed
|
|
*/
|
|
virtual bool SampleUV(const FVector2d& UV, SampleType& ResultOut);
|
|
|
|
/**
|
|
* Compute a sample at the given UV location in the given Triangle
|
|
* @return true if valid sample was computed
|
|
*/
|
|
virtual bool SampleUV(int32 UVTriangleID, const FVector2d& UV, SampleType& ResultOut);
|
|
|
|
/**
|
|
* Compute a sample at the given UV/3D location specified by CachedSampleInfo, which presumably was produced by previous calls to SampleUV()
|
|
* @return true if valid sample was computed
|
|
*/
|
|
virtual bool CachedSampleUV(const FMeshUVSampleInfo& CachedSampleInfo, SampleType& ResultOut);
|
|
|
|
protected:
|
|
EMeshSurfaceSamplerQueryType QueryType = EMeshSurfaceSamplerQueryType::TriangleAndUV;
|
|
bool bUVSpatialValid = false;
|
|
const FDynamicMesh3* Mesh = nullptr;
|
|
const FDynamicMeshUVOverlay* UVOverlay = nullptr;
|
|
|
|
TUniqueFunction<void(const FMeshUVSampleInfo& SampleInfo, SampleType& SampleValueOut)> ValueFunction;
|
|
|
|
SampleType ZeroValue;
|
|
|
|
// BV tree for finding triangle for a given UV. Not always initialized.
|
|
FDynamicMeshUVMesh UVMeshAdapter;
|
|
TMeshAABBTree3<FDynamicMeshUVMesh> UVBVTree;
|
|
void InitializeBVTree();
|
|
};
|
|
|
|
|
|
|
|
template<typename SampleType>
|
|
void TMeshSurfaceUVSampler<SampleType>::InitializeBVTree()
|
|
{
|
|
if (bUVSpatialValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
check(UVOverlay);
|
|
|
|
UVMeshAdapter.Mesh = Mesh;
|
|
UVMeshAdapter.UV = UVOverlay;
|
|
UVBVTree.SetMesh(&UVMeshAdapter, true);
|
|
|
|
bUVSpatialValid = true;
|
|
}
|
|
|
|
|
|
|
|
template<typename SampleType>
|
|
bool TMeshSurfaceUVSampler<SampleType>::SampleUV(const FVector2d& UV, SampleType& ResultOut)
|
|
{
|
|
check(QueryType == EMeshSurfaceSamplerQueryType::UVOnly);
|
|
check(bUVSpatialValid);
|
|
|
|
FRay3d HitRay(FVector3d(UV.X, UV.Y, 100.0), -FVector3d::UnitZ());
|
|
const int32 UVTriangleID = UVBVTree.FindNearestHitTriangle(HitRay);
|
|
|
|
return SampleUV(UVTriangleID, UV, ResultOut);
|
|
}
|
|
|
|
|
|
|
|
|
|
template<typename SampleType>
|
|
bool TMeshSurfaceUVSampler<SampleType>::SampleUV(int32 UVTriangleID, const FVector2d& UV, SampleType& ResultOut)
|
|
{
|
|
check(QueryType == EMeshSurfaceSamplerQueryType::TriangleAndUV);
|
|
|
|
FMeshUVSampleInfo Sample;
|
|
|
|
Sample.TriangleIndex = UVTriangleID;
|
|
if (Mesh->IsTriangle(Sample.TriangleIndex) == false)
|
|
{
|
|
ResultOut = ZeroValue;
|
|
return false;
|
|
}
|
|
check(UVOverlay->IsSetTriangle(Sample.TriangleIndex));
|
|
|
|
Sample.MeshVertices = Mesh->GetTriangle(Sample.TriangleIndex);
|
|
Sample.Triangle3D = FTriangle3d(
|
|
Mesh->GetVertex(Sample.MeshVertices.A),
|
|
Mesh->GetVertex(Sample.MeshVertices.B),
|
|
Mesh->GetVertex(Sample.MeshVertices.C));
|
|
|
|
Sample.UVVertices = UVOverlay->GetTriangle(Sample.TriangleIndex);
|
|
Sample.TriangleUV = FTriangle2d(
|
|
(FVector2d)UVOverlay->GetElement(Sample.UVVertices.A),
|
|
(FVector2d)UVOverlay->GetElement(Sample.UVVertices.B),
|
|
(FVector2d)UVOverlay->GetElement(Sample.UVVertices.C));
|
|
|
|
Sample.BaryCoords = Sample.TriangleUV.GetBarycentricCoords(UV);
|
|
Sample.SurfacePoint = Mesh->GetTriBaryPoint(Sample.TriangleIndex, Sample.BaryCoords.X, Sample.BaryCoords.Y, Sample.BaryCoords.Z);
|
|
|
|
ValueFunction(Sample, ResultOut);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
template<typename SampleType>
|
|
bool TMeshSurfaceUVSampler<SampleType>::CachedSampleUV(const FMeshUVSampleInfo& CachedSampleInfo, SampleType& ResultOut)
|
|
{
|
|
ValueFunction(CachedSampleInfo, ResultOut);
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
} // end namespace UE::Geometry
|
|
} // end namespace UE
|
|
|
|
#undef UE_API |