Files
UnrealEngine/Engine/Plugins/Editor/ProxyLODPlugin/Source/ProxyLOD/Private/ProxyLODVolume.cpp
2025-05-18 13:04:45 +08:00

595 lines
18 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ProxyLODVolume.h"
#include "ProxyLODMeshAttrTransfer.h"
#include "ProxyLODMeshConvertUtils.h"
#include "ProxyLODMeshSDFConversions.h"
#include "ProxyLODMeshTypes.h"
#include "ProxyLODMeshUtilities.h"
THIRD_PARTY_INCLUDES_START
#pragma warning(push)
#pragma warning(disable: 4146)
#include <openvdb/openvdb.h>
#include <openvdb/tools/Interpolation.h> // for Spatial Query
#include <openvdb/tools/MeshToVolume.h> // for MeshToVolume
#include <openvdb/tools/VolumeToMesh.h> // for VolumeToMesh
#include <openvdb/tools/Composite.h> // for CSG operations
#pragma warning(pop)
THIRD_PARTY_INCLUDES_END
#include "MeshDescription.h"
typedef openvdb::math::Transform OpenVDBTransform;
class FProxyLODVolumeImpl : public IProxyLODVolume
{
public:
FProxyLODVolumeImpl()
: VoxelSize(0.0)
{
}
~FProxyLODVolumeImpl()
{
SDFVolume.reset();
SrcPolyIndexGrid.reset();
Sampler.reset();
}
bool Initialize(const TArray<FMeshMergeData>& Geometry, float Accuracy)
{
FMeshDescriptionArrayAdapter SrcGeometryAdapter(Geometry);
return Initialize(SrcGeometryAdapter, Accuracy);
}
bool Initialize(const TArray<FInstancedMeshMergeData>& Geometry, float Accuracy)
{
FMeshDescriptionArrayAdapter SrcGeometryAdapter(Geometry);
return Initialize(SrcGeometryAdapter, Accuracy);
}
virtual double GetVoxelSize() const override
{
return VoxelSize;
}
virtual FVector3i GetBBoxSize() const override
{
if (SDFVolume == nullptr)
{
return FVector3i(0,0,0);
}
openvdb::math::Coord VolumeBBoxSize = SDFVolume->evalActiveVoxelDim();
return FVector3i(VolumeBBoxSize.x(), VolumeBBoxSize.y(), VolumeBBoxSize.z());
}
virtual void CloseGaps(const double GapRadius, const int32 MaxDilations) override
{
ProxyLOD::CloseGaps(SDFVolume, GapRadius, MaxDilations);
}
virtual float QueryDistance(const FVector& Point) const override
{
return Sampler->wsSample(openvdb::Vec3R(Point.X, Point.Y, Point.Z));
}
virtual void ConvertToRawMesh(FMeshDescription& OutRawMesh) const override
{
// Mesh types that will be shared by various stages.
FAOSMesh AOSMeshedVolume;
ProxyLOD::SDFVolumeToMesh(SDFVolume, 0.0, 0.0, AOSMeshedVolume);
ProxyLOD::ConvertMesh(AOSMeshedVolume, OutRawMesh);
}
void ExpandNarrowBand(float ExteriorWidth, float InteriorWidth) override
{
using namespace openvdb::tools;
FMeshDescription RawMesh;
FStaticMeshAttributes(RawMesh).Register();
ConvertToRawMesh(RawMesh);
FMeshDescriptionAdapter MeshAdapter(RawMesh, SDFVolume->transform());
openvdb::FloatGrid::Ptr NewSDFVolume;
openvdb::Int32Grid::Ptr NewSrcPolyIndexGrid;
try
{
NewSrcPolyIndexGrid = openvdb::Int32Grid::create();
NewSDFVolume = openvdb::tools::meshToVolume<openvdb::FloatGrid>(MeshAdapter, MeshAdapter.GetTransform(), ExteriorWidth / VoxelSize, InteriorWidth / VoxelSize, 0, NewSrcPolyIndexGrid.get());
SDFVolume = NewSDFVolume;
SrcPolyIndexGrid = NewSrcPolyIndexGrid;
// reduce memory footprint, increase the spareness.
openvdb::tools::pruneLevelSet(SDFVolume->tree(), float(ExteriorWidth / VoxelSize), float(-InteriorWidth / VoxelSize));
}
catch (std::bad_alloc&)
{
NewSDFVolume.reset();
NewSrcPolyIndexGrid.reset();
return;
}
Sampler.reset(new openvdb::tools::GridSampler<openvdb::FloatGrid, openvdb::tools::PointSampler>(*SDFVolume));
}
private:
bool Initialize(FMeshDescriptionArrayAdapter& InSrcGeometryAdapter, float Accuracy)
{
OpenVDBTransform::Ptr XForm = OpenVDBTransform::createLinearTransform(Accuracy);
InSrcGeometryAdapter.SetTransform(XForm);
VoxelSize = InSrcGeometryAdapter.GetTransform().voxelSize()[0];
SrcPolyIndexGrid = openvdb::Int32Grid::create();
if (!ProxyLOD::MeshToSDFVolume(InSrcGeometryAdapter, InSrcGeometryAdapter.GetTransform(), SDFVolume, SrcPolyIndexGrid.get()))
{
SrcPolyIndexGrid.reset();
return false;
}
Sampler.reset(new openvdb::tools::GridSampler<openvdb::FloatGrid, openvdb::tools::PointSampler>(*SDFVolume));
return true;
}
openvdb::FloatGrid::Ptr SDFVolume;
openvdb::Int32Grid::Ptr SrcPolyIndexGrid;
openvdb::tools::GridSampler<openvdb::FloatGrid, openvdb::tools::PointSampler>::Ptr Sampler;
double VoxelSize;
};
int32 IProxyLODVolume::FVector3i::MinIndex() const
{
return (int32)openvdb::math::MinIndex(openvdb::math::Coord(X, Y, X));
}
TUniquePtr<IProxyLODVolume> IProxyLODVolume::CreateSDFVolumeFromMeshArray(const TArray<FMeshMergeData>& Geometry, float Step)
{
TUniquePtr<FProxyLODVolumeImpl> Volume = MakeUnique<FProxyLODVolumeImpl>();
if (Volume == nullptr || !Volume->Initialize(Geometry, Step))
{
return nullptr;
}
return Volume;
}
TUniquePtr<IProxyLODVolume> IProxyLODVolume::CreateSDFVolumeFromMeshArray(const TArray<FInstancedMeshMergeData>& Geometry, float Step)
{
TUniquePtr<FProxyLODVolumeImpl> Volume = MakeUnique<FProxyLODVolumeImpl>();
if (Volume == nullptr || !Volume->Initialize(Geometry, Step))
{
return nullptr;
}
return Volume;
}
class FPolygonSoup
{
public:
FPolygonSoup(const TArray<FMeshDescriptionAdapter>& AdapterArray, double VoxelSize);
// Total number of polygons
size_t polygonCount() const { return NumPolys; }
// Total number of points (vertex locations)
size_t pointCount() const { return NumVerts; }
// Vertex count for polygon n: currently FMeshDescription is just triangles.
size_t vertexCount(size_t n) const { return 3; }
// Return position pos in local grid index space for polygon n and vertex v
void getIndexSpacePoint(size_t FaceNumber, size_t CornerNumber, openvdb::Vec3d& pos) const
{
int32 AdapterIdx = PolyIdxToAdapter[FaceNumber];
int32 Offset = AdapterToOffset[AdapterIdx];
Adapters[AdapterIdx].getIndexSpacePoint(FaceNumber - Offset, CornerNumber, pos);
}
const OpenVDBTransform& Transform() const { return *Xform; }
private:
FPolygonSoup();
OpenVDBTransform::Ptr Xform;
TArray<FMeshDescriptionAdapter> Adapters;
TArray<size_t> AdapterToOffset;
TArray<int32> PolyIdxToAdapter;
size_t NumPolys;
size_t NumVerts;
};
FPolygonSoup::FPolygonSoup(const TArray<FMeshDescriptionAdapter>& AdapterArray, double VoxelSize)
{
Xform = OpenVDBTransform::createLinearTransform(VoxelSize);
int32 NumMeshes = AdapterArray.Num();
Adapters.Reserve(NumMeshes);
NumPolys = 0;
NumVerts = 0;
for (auto& Adapter : AdapterArray)
{
Adapters.Add(Adapter);
NumPolys += Adapter.polygonCount();
NumVerts += Adapter.pointCount();
}
AdapterToOffset.AddZeroed(NumMeshes + 1);
for (int32 i = 1; i < NumMeshes + 1; ++i)
{
AdapterToOffset[i] = AdapterToOffset[i-1] + AdapterArray[i - 1].polygonCount();
}
PolyIdxToAdapter.Reserve(NumPolys);
for (int32 i = 0; i < NumMeshes; ++i)
{
int32 np = AdapterArray[i].polygonCount();
for (int32 j = 0; j < np; ++j)
{
PolyIdxToAdapter.Add(i);
}
}
}
class FVoxelBasedCSGImpl : public IVoxelBasedCSG
{
public:
FVoxelBasedCSGImpl()
{
openvdb::initialize();
double VoxelSize = 0.1;
XForm = OpenVDBTransform::createLinearTransform(VoxelSize);
}
FVoxelBasedCSGImpl(double VoxelSize)
{
openvdb::initialize();
XForm = OpenVDBTransform::createLinearTransform(VoxelSize);
}
virtual ~FVoxelBasedCSGImpl()
{
XForm.reset();
}
double GetVoxelSize() const override
{
return XForm->voxelSize()[0];
}
void SetVoxelSize(double VoxelSize) override
{
XForm = OpenVDBTransform::createLinearTransform(VoxelSize);
}
// Note this will become very slow if the isosurface is more than 3 voxel widths from 0.
// the tool that calls this should clamp the isosurface.
virtual FVector ComputeUnion(const TArray<IVoxelBasedCSG::FPlacedMesh>& PlacedMeshArray, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override
{
FInterrupter NullInterrupter;
FVector AverageTranslation;
ComputeUnion(NullInterrupter, PlacedMeshArray, ResultMesh, AverageTranslation, Adaptivity, IsoSurface);
return AverageTranslation;
}
virtual bool ComputeUnion(IVoxelBasedCSG::FInterrupter& Interrupter, const TArray<IVoxelBasedCSG::FPlacedMesh>& PlacedMeshArray, FMeshDescription& ResultMesh, FVector& AverageTranslation, double Adaptivity, double IsoSurface) const override
{
// convert IsoSurface units to voxels
const double VoxelSize = GetVoxelSize();
const double IsoSurfaceInVoxels = IsoSurface / VoxelSize;
const double ExteriorVoxelWidth = FMath::Max( 2., IsoSurfaceInVoxels + 1.);
const double InteriorVoxelWidth = -FMath::Min(-2., IsoSurfaceInVoxels - 1);
AverageTranslation = FVector(0.f, 0.f, 0.f);
const int32 NumMeshes = PlacedMeshArray.Num();
if (NumMeshes == 0)
{
return false;
}
// Find the average translation of all the meshes.
for (int32 i = 0; i < NumMeshes; ++i)
{
AverageTranslation += PlacedMeshArray[i].Transform.GetTranslation();
}
AverageTranslation /= (float)NumMeshes;
// Target grid to hold the union in SDF form.
//openvdb::FloatGrid::Ptr SDFUnionVolume = openvdb::FloatGrid::create(ExteriorVoxelWidth /*background value */);
//SDFUnionVolume->setTransform(XForm);
// Fill the SDFUnionVolume
FMatrix44f LocalToVoxel = FMatrix44f::Identity;
LocalToVoxel.M[0][0] = VoxelSize;
LocalToVoxel.M[1][1] = VoxelSize;
LocalToVoxel.M[2][2] = VoxelSize;
TArray<FMeshDescriptionAdapter> Adapters;
for (int32 i = 0, I = PlacedMeshArray.Num(); i < I; ++i)
{
const FPlacedMesh& PlacedMesh = PlacedMeshArray[i];
const FMeshDescription* MeshPtr = PlacedMesh.Mesh;
if (MeshPtr)
{
// Get the transform relative to the average
FTransform MeshTransform = PlacedMesh.Transform;
MeshTransform.AddToTranslation(-AverageTranslation);
FMatrix44f TransformMatrix = FMatrix44f(MeshTransform.ToMatrixWithScale().Inverse()); // LWC_TODO: Precision loss
TransformMatrix = LocalToVoxel * TransformMatrix;
float* data = &TransformMatrix.M[0][0];
openvdb::math::Mat4<float> VDBMatFloat(data);
openvdb::Mat4R VDBMatDouble(VDBMatFloat);
// NB: rounding errors in the inverse may have resulted in error in this col.
// openvdb explicitly checks this matrix row to insure the tranform is affine and will throw
VDBMatDouble.setCol(3, openvdb::Vec4R(0, 0, 0, 1));
OpenVDBTransform::Ptr LocalXForm = OpenVDBTransform::createLinearTransform(VDBMatDouble);
// Create a wrapper with OpenVDB semantics.
FMeshDescriptionAdapter MeshAdapter(*MeshPtr, *LocalXForm);
Adapters.Add(MeshAdapter);
}
}
FPolygonSoup PolySoup(Adapters, VoxelSize);
openvdb::FloatGrid::Ptr SDFUnionVolume = openvdb::tools::meshToVolume<openvdb::FloatGrid>(Interrupter, PolySoup, PolySoup.Transform(), ExteriorVoxelWidth, InteriorVoxelWidth);
const bool bSuccess = !Interrupter.wasInterrupted();
if (bSuccess)
{
// Convert the SDFUnionVolume to a mesh
ConvertSDFToMesh(SDFUnionVolume, Adaptivity, IsoSurface, ResultMesh);
}
return bSuccess;
}
virtual FVector ComputeDifference(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override
{
IVoxelBasedCSG::FInterrupter NullInterrupter;
FVector AverageTranslation;
ComputeDifference(NullInterrupter, PlacedMeshA, PlacedMeshB, ResultMesh, AverageTranslation, Adaptivity, IsoSurface);
return AverageTranslation;
}
virtual bool ComputeDifference(IVoxelBasedCSG::FInterrupter& Interrupter, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, FVector& AverageTranslation, double Adaptivity, double IsoSurface) const override
{
// make SDFs
openvdb::FloatGrid::Ptr SDFVolumeA;
openvdb::FloatGrid::Ptr SDFVolumeB;
bool bSuccess = GenerateVolumes(Interrupter, IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB, AverageTranslation);
bSuccess = bSuccess && !Interrupter.wasInterrupted();
if (bSuccess)
{
// create the difference - result stored in volume A
openvdb::tools::csgDifference(*SDFVolumeA, *SDFVolumeB);
}
// convert the result
bSuccess = bSuccess && !Interrupter.wasInterrupted();
if (bSuccess)
{
ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh);
}
return bSuccess;
}
virtual FVector ComputeIntersection(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override
{
IVoxelBasedCSG::FInterrupter NullInterrupter;
FVector AverageTranslation;
ComputeIntersection(NullInterrupter, PlacedMeshA, PlacedMeshB, ResultMesh, AverageTranslation, Adaptivity, IsoSurface);
return AverageTranslation;
}
virtual bool ComputeIntersection(IVoxelBasedCSG::FInterrupter& Interrupter, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, FVector& AverageTranslation, double Adaptivity, double IsoSurface) const override
{
// make SDFs
openvdb::FloatGrid::Ptr SDFVolumeA;
openvdb::FloatGrid::Ptr SDFVolumeB;
bool bSuccess = GenerateVolumes(Interrupter, IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB, AverageTranslation);
bSuccess = bSuccess && !Interrupter.wasInterrupted();
if (bSuccess)
{
// create the difference - result stored in volume A
openvdb::tools::csgIntersection(*SDFVolumeA, *SDFVolumeB);
}
// convert the result
bSuccess = bSuccess && !Interrupter.wasInterrupted();
if (bSuccess)
{
// convert the result
ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh);
}
return bSuccess;
}
virtual FVector ComputeUnion(const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, double Adaptivity, double IsoSurface) const override
{
FInterrupter NullInterrupter;
FVector AverageTranslation;
ComputeUnion(NullInterrupter, PlacedMeshA, PlacedMeshB, ResultMesh, AverageTranslation, Adaptivity, IsoSurface);
return AverageTranslation;
}
virtual bool ComputeUnion(IVoxelBasedCSG::FInterrupter& Interrupter, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, FMeshDescription& ResultMesh, FVector& AverageTranslation, double Adaptivity, double IsoSurface) const override
{
// make SDFs
openvdb::FloatGrid::Ptr SDFVolumeA;
openvdb::FloatGrid::Ptr SDFVolumeB;
bool bSuccess = GenerateVolumes(Interrupter, IsoSurface, PlacedMeshA, PlacedMeshB, SDFVolumeA, SDFVolumeB, AverageTranslation);
bSuccess = bSuccess && !Interrupter.wasInterrupted();
if (bSuccess)
{
// create the difference - result stored in volume A
openvdb::tools::csgUnion(*SDFVolumeA, *SDFVolumeB);
}
// convert the result
bSuccess = bSuccess && !Interrupter.wasInterrupted();
if (bSuccess)
{
ConvertSDFToMesh(SDFVolumeA, Adaptivity, IsoSurface, ResultMesh);
}
return bSuccess;
}
private:
void ConvertSDFToMesh(openvdb::FloatGrid::Ptr SDFVolume, double Adaptivity, double IsoSurface, FMeshDescription& ResultMesh) const
{
// Convert the SDFUnionVolume to a mesh
FAOSMesh AOSMeshedVolume;
ProxyLOD::ExtractIsosurfaceWithNormals(SDFVolume, IsoSurface, Adaptivity, AOSMeshedVolume); // QUestion should IsoSurface be worldspace?
// Evidently this conversion code makes certain assumptions about the FMeshDescription. that were introduced when it was converted from FRawMesh...
ProxyLOD::ConvertMesh(AOSMeshedVolume, ResultMesh);
}
bool GenerateVolumes(IVoxelBasedCSG::FInterrupter& Interrupter, const double IsoSurface, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, openvdb::FloatGrid::Ptr& VolumeA, openvdb::FloatGrid::Ptr& VolumeB, FVector& AverageTranslation) const
{
const FMeshDescription& MeshA = *PlacedMeshA.Mesh;
const FMeshDescription& MeshB = *PlacedMeshB.Mesh;
const double VoxelSize = GetVoxelSize();
const double IsoSurfaceInVoxels = IsoSurface / VoxelSize;
const double ExteriorVoxelWidth = FMath::Max(2., IsoSurfaceInVoxels + 1.);
const double InteriorVoxelWidth = -FMath::Min(-2., IsoSurfaceInVoxels - 1);
// The average translation of the two meshes.
AverageTranslation = PlacedMeshA.Transform.GetTranslation() + PlacedMeshB.Transform.GetTranslation();
AverageTranslation *= 0.5f;
// Fill the SDFUnionVolume
FMatrix LocalToVoxel = FMatrix::Identity;
LocalToVoxel.M[0][0] = VoxelSize;
LocalToVoxel.M[1][1] = VoxelSize;
LocalToVoxel.M[2][2] = VoxelSize;
auto TransformGenerator = [&LocalToVoxel, &AverageTranslation](const FPlacedMesh& PlacedMesh)->openvdb::Mat4R
{
FTransform MeshXForm = PlacedMesh.Transform;
MeshXForm.AddToTranslation(-AverageTranslation);
FMatrix TransformMatrix = MeshXForm.ToMatrixWithScale().Inverse();
TransformMatrix = LocalToVoxel * TransformMatrix;
double* data = &TransformMatrix.M[0][0];
openvdb::Mat4R VDBMatDouble(data);
// NB: rounding errors in the inverse may have resulted in error in this col.
// openvdb explicitly checks this matrix row to insure the transform is affine and will throw
VDBMatDouble.setCol(3, openvdb::Vec4R(0, 0, 0, 1));
return VDBMatDouble;
};
openvdb::Mat4R XFormA = TransformGenerator(PlacedMeshA);
OpenVDBTransform::Ptr VDBXFormA = OpenVDBTransform::createLinearTransform(XFormA);
openvdb::Mat4R XFormB = TransformGenerator(PlacedMeshB);
OpenVDBTransform::Ptr VDBXFormB = OpenVDBTransform::createLinearTransform(XFormB);
// Create adapters that understand the openVDB semantics.
FMeshDescriptionAdapter AdapterA(MeshA, *VDBXFormA);
FMeshDescriptionAdapter AdapterB(MeshB, *VDBXFormB);
// target transform
OpenVDBTransform::Ptr TargetXForm = OpenVDBTransform::createLinearTransform(VoxelSize);
// make SDFs
openvdb::FloatGrid::Ptr SDFVolumeA = openvdb::tools::meshToVolume<openvdb::FloatGrid>(Interrupter, AdapterA, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth);
openvdb::FloatGrid::Ptr SDFVolumeB = openvdb::tools::meshToVolume<openvdb::FloatGrid>(Interrupter, AdapterB, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth);
VolumeA = SDFVolumeA;
VolumeB = SDFVolumeB;
const bool bSuccess = !Interrupter.wasInterrupted();
return bSuccess;
}
FVector GenerateVolumes(const double IsoSurface, const FPlacedMesh& PlacedMeshA, const FPlacedMesh& PlacedMeshB, openvdb::FloatGrid::Ptr& VolumeA, openvdb::FloatGrid::Ptr& VolumeB) const
{
FVector AverageTranslation;
IVoxelBasedCSG::FInterrupter NullInterrupter;
GenerateVolumes(NullInterrupter, IsoSurface, PlacedMeshA, PlacedMeshB, VolumeA, VolumeB, AverageTranslation);
return AverageTranslation;
}
OpenVDBTransform::Ptr XForm;
};
TUniquePtr<IVoxelBasedCSG> IVoxelBasedCSG::CreateCSGTool(float VoxelSize)
{
TUniquePtr<FVoxelBasedCSGImpl> CSGTool = MakeUnique<FVoxelBasedCSGImpl>();
if (CSGTool == nullptr)
{
return nullptr;
}
else
{
CSGTool->SetVoxelSize(VoxelSize);
}
return CSGTool;
}