// 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 #include // for Spatial Query #include // for MeshToVolume #include // for VolumeToMesh #include // 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& Geometry, float Accuracy) { FMeshDescriptionArrayAdapter SrcGeometryAdapter(Geometry); return Initialize(SrcGeometryAdapter, Accuracy); } bool Initialize(const TArray& 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(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(*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(*SDFVolume)); return true; } openvdb::FloatGrid::Ptr SDFVolume; openvdb::Int32Grid::Ptr SrcPolyIndexGrid; openvdb::tools::GridSampler::Ptr Sampler; double VoxelSize; }; int32 IProxyLODVolume::FVector3i::MinIndex() const { return (int32)openvdb::math::MinIndex(openvdb::math::Coord(X, Y, X)); } TUniquePtr IProxyLODVolume::CreateSDFVolumeFromMeshArray(const TArray& Geometry, float Step) { TUniquePtr Volume = MakeUnique(); if (Volume == nullptr || !Volume->Initialize(Geometry, Step)) { return nullptr; } return Volume; } TUniquePtr IProxyLODVolume::CreateSDFVolumeFromMeshArray(const TArray& Geometry, float Step) { TUniquePtr Volume = MakeUnique(); if (Volume == nullptr || !Volume->Initialize(Geometry, Step)) { return nullptr; } return Volume; } class FPolygonSoup { public: FPolygonSoup(const TArray& 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 Adapters; TArray AdapterToOffset; TArray PolyIdxToAdapter; size_t NumPolys; size_t NumVerts; }; FPolygonSoup::FPolygonSoup(const TArray& 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& 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& 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 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 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(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(Interrupter, AdapterA, *TargetXForm, ExteriorVoxelWidth, InteriorVoxelWidth); openvdb::FloatGrid::Ptr SDFVolumeB = openvdb::tools::meshToVolume(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::CreateCSGTool(float VoxelSize) { TUniquePtr CSGTool = MakeUnique(); if (CSGTool == nullptr) { return nullptr; } else { CSGTool->SetVoxelSize(VoxelSize); } return CSGTool; }