//########################################################################## //# # //# CLOUDCOMPARE WRAPPER: PoissonReconLib # //# # //# This program is free software; you can redistribute it and/or modify # //# it under the terms of the GNU General Public License as published by # //# the Free Software Foundation; version 2 or later of the License. # //# # //# This program is distributed in the hope that it will be useful, # //# but WITHOUT ANY WARRANTY; without even the implied warranty of # //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # //# GNU General Public License for more details. # //# # //# COPYRIGHT: Daniel Girardeau-Montaut # //# # //########################################################################## #include "PoissonReconLib.h" //PoissonRecon #include "../Src/FEMTree.h" //Local #include "PointData.h" //System #include namespace { // The order of the B-Spline used to splat in data for color interpolation constexpr int DATA_DEGREE = 0; // The order of the B-Spline used to splat in the weights for density estimation constexpr int WEIGHT_DEGREE = 2; // The order of the B-Spline used to splat in the normals for constructing the Laplacian constraints constexpr int NORMAL_DEGREE = 2; // The default finite-element degree constexpr int DEFAULT_FEM_DEGREE = 1; // The dimension of the system constexpr int DIMENSION = 3; inline float ComputeNorm(const float vec[3]) { return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]); } inline double ComputeNorm(const double vec[3]) { return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]); } } int PoissonReconLib::Parameters::GetMaxThreadCount() { #ifdef WITH_OPENMP return omp_get_num_procs(); #else return std::thread::hardware_concurrency(); #endif } PoissonReconLib::Parameters::Parameters() : threads(GetMaxThreadCount()) { } template class Vertex : public PointData<_Real> { public: typedef _Real Real; Vertex(const Point& point) : PointData() , point(point) , w(0) {} Vertex(const Point& point, const PointData& data, double _w = 0.0) : PointData(data.normal, data.color) , point(point) , w(_w) {} Vertex() : Vertex(Point(0, 0, 0)) {} Vertex& operator *= (Real s) { PointData::operator *= (s); point *= s; w *= s; return *this; } Vertex& operator /= (Real s) { PointData::operator *= (1 / s); point /= s; w /= s; return *this; } Vertex& operator+=(const Vertex& p) { PointData::operator += (p); point += p.point; w += p.w; return *this; } public: Point point; double w; }; template class PointStream : public InputPointStreamWithData > { public: PointStream(const PoissonReconLib::ICloud& _cloud) : cloud(_cloud), xform(nullptr), currentIndex(0) {} void reset(void) override { currentIndex = 0; } bool nextPoint(Point& p, PointData& d) override { if (currentIndex >= cloud.size()) { return false; } cloud.getPoint(currentIndex, p.coords); if (xform != nullptr) { p = (*xform) * p; } if (cloud.hasNormals()) { cloud.getNormal(currentIndex, d.normal); } else { d.normal[0] = d.normal[1] = d.normal[2]; } if (cloud.hasColors()) { cloud.getColor(currentIndex, d.color); } else { d.color[0] = d.color[1] = d.color[2]; } currentIndex++; return true; } public: const PoissonReconLib::ICloud& cloud; XForm* xform; size_t currentIndex; }; template struct FEMTreeProfiler { FEMTree& tree; double t; FEMTreeProfiler(FEMTree& t) : tree(t) {} void start(void) { t = Time(), FEMTree::ResetLocalMemoryUsage(); } void dumpOutput(const char* header) const { FEMTree::MemoryUsage(); //if (header) { // utility::LogDebug("{} {} (s), {} (MB) / {} (MB) / {} (MB)", header, // Time() - t, // FEMTree::LocalMemoryUsage(), // FEMTree::MaxMemoryUsage(), // MemoryInfo::PeakMemoryUsageMB()); //} //else { // utility::LogDebug("{} (s), {} (MB) / {} (MB) / {} (MB)", Time() - t, // FEMTree::LocalMemoryUsage(), // FEMTree::MaxMemoryUsage(), // MemoryInfo::PeakMemoryUsageMB()); //} } }; template XForm GetBoundingBoxXForm( const Point& min, const Point& max, Real scaleFactor) { Point center = (max + min) / 2; Real scale = max[0] - min[0]; for (unsigned int d = 1; d < Dim; d++) { scale = std::max(scale, max[d] - min[d]); } scale *= scaleFactor; for (unsigned int i = 0; i < Dim; i++) { center[i] -= scale / 2; } XForm tXForm = XForm::Identity(), sXForm = XForm::Identity(); for (unsigned int i = 0; i < Dim; i++) { sXForm(i, i) = static_cast(1. / scale), tXForm(Dim, i) = -center[i]; } return sXForm * tXForm; } template XForm GetBoundingBoxXForm( const Point& min, const Point& max, Real width, Real scaleFactor, int& depth) { // Get the target resolution (along the largest dimension) Real resolution = (max[0] - min[0]) / width; for (unsigned int d = 1; d < Dim; d++) { resolution = std::max(resolution, (max[d] - min[d]) / width); } resolution *= scaleFactor; depth = 0; while ((1 << depth) < resolution) { depth++; } Point center = (max + min) / 2; Real scale = (1 << depth) * width; for (unsigned int i = 0; i < Dim; i++) { center[i] -= scale / 2; } XForm tXForm = XForm::Identity(); XForm sXForm = XForm::Identity(); for (unsigned int i = 0; i < Dim; i++) { sXForm(i, i) = static_cast(1.0 / scale); tXForm(Dim, i) = -center[i]; } return sXForm * tXForm; } template XForm GetPointXForm( InputPointStream& stream, Real width, Real scaleFactor, int& depth) { Point min, max; stream.boundingBox(min, max); return GetBoundingBoxXForm(min, max, width, scaleFactor, depth); } template XForm GetPointXForm( InputPointStream& stream, Real scaleFactor) { Point min, max; stream.boundingBox(min, max); return GetBoundingBoxXForm(min, max, scaleFactor); } template struct ConstraintDual { Real target, weight; ConstraintDual(Real t, Real w) : target(t), weight(w) {} CumulativeDerivativeValues operator()(const Point& p) const { return CumulativeDerivativeValues(target * weight); }; }; template struct SystemDual { SystemDual(Real w) : weight(w) {} CumulativeDerivativeValues operator()(const Point& p, const CumulativeDerivativeValues& dValues) const { return dValues * weight; }; CumulativeDerivativeValues operator()( const Point& p, const CumulativeDerivativeValues& dValues) const { return dValues * weight; }; Real weight; }; template struct SystemDual { typedef double Real; SystemDual(Real w) : weight(w) {} CumulativeDerivativeValues operator()( const Point& p, const CumulativeDerivativeValues& dValues) const { return dValues * weight; }; Real weight; }; template void ExtractMesh( const PoissonReconLib::Parameters& params, UIntPack, std::tuple, FEMTree& tree, const DenseNodeData>& solution, Real isoValue, const std::vector::PointSample>* samples, std::vector< PointData >* sampleData, const typename FEMTree::template DensityEstimator* density, const SetVertexFunction& SetVertex, XForm iXForm, PoissonReconLib::IMesh& out_mesh) { static const int Dim = sizeof...(FEMSigs); typedef UIntPack Sigs; static const unsigned int DataSig = FEMDegreeAndBType::Signature; const bool non_manifold = true; const bool polygon_mesh = false; CoredVectorMeshData mesh; if (samples && sampleData) { typedef typename FEMTree::template DensityEstimator DensityEstimator; SparseNodeData< ProjectiveData, Real>, IsotropicUIntPack> _sampleData = tree.template setMultiDepthDataField( *samples, *sampleData, (DensityEstimator*)nullptr); for (const RegularTreeNode* n = tree.tree().nextNode(); n; n = tree.tree().nextNode(n)) { ProjectiveData, Real>* color = _sampleData(n); if (color) (*color) *= static_cast(pow(params.colorPullFactor, tree.depth(n))); } IsoSurfaceExtractor::template Extract< PointData >(Sigs(), UIntPack(), UIntPack(), tree, density, &_sampleData, solution, isoValue, mesh, SetVertex, !params.linearFit, !non_manifold, polygon_mesh, false); } else { IsoSurfaceExtractor::template Extract< PointData >(Sigs(), UIntPack(), UIntPack(), tree, density, nullptr, solution, isoValue, mesh, SetVertex, !params.linearFit, !non_manifold, polygon_mesh, false); } mesh.resetIterator(); for (size_t vidx = 0; vidx < mesh.outOfCorePointCount(); ++vidx) { Vertex v; mesh.nextOutOfCorePoint(v); v.point = iXForm * v.point; out_mesh.addVertex(v.point.coords); if (sampleData) { //out_mesh.addNormal(v.normal); out_mesh.addColor(v.color); } if (params.density) { out_mesh.addDensity(v.w); } } for (size_t tidx = 0; tidx < mesh.polygonCount(); ++tidx) { std::vector> triangle; mesh.nextPolygon(triangle); if (triangle.size() == 3) { out_mesh.addTriangle(triangle[0].idx, triangle[1].idx, triangle[2].idx); } else { assert(false); } } } template static bool Execute(PointStream& pointStream, PoissonReconLib::IMesh& out_mesh, const PoissonReconLib::Parameters& params, UIntPack ) { static const int Dim = sizeof...(FEMSigs); typedef UIntPack Sigs; typedef UIntPack::Degree...> Degrees; typedef UIntPack::BType, 1>::BType>::Signature...> NormalSigs; typedef typename FEMTree::template DensityEstimator DensityEstimator; typedef typename FEMTree::template InterpolationInfo InterpolationInfo; // Compute scaling transformation (and optionally the depth) int depth = params.depth; XForm xForm = XForm::Identity(); { if (params.finestCellWidth > 0) { Real scaleFactor = static_cast(params.scale > 0 ? params.scale : 1.0); xForm = GetPointXForm(pointStream, params.finestCellWidth, scaleFactor, depth) * xForm; //warning: depth may change! } else if (params.scale > 0) { xForm = GetPointXForm(pointStream, static_cast(params.scale)) * xForm; } pointStream.xform = &xForm; } if (depth < 2) { //depth should be greater than 2 assert(false); return false; } //default parameters const int solve_depth = depth; const bool exact_interpolation = false; const Real target_value = static_cast(0.5); // Read in the samples (and color data) FEMTree tree(MEMORY_ALLOCATOR_BLOCK_SIZE); typedef std::vector::PointSample> SampleSet; typedef std::vector< PointData > SampleDataSet; std::unique_ptr samples; std::unique_ptr sampleData; try { samples.reset(new SampleSet); sampleData.reset(new SampleDataSet); if (params.normalConfidence > 0) { auto ProcessDataWithConfidence = [&](const Point& p, PointData& d) { Real l = ComputeNorm(d.normal); if (std::isnan(l) || l == 0) return static_cast(-1.0); return static_cast(pow(l, params.normalConfidence)); }; FEMTreeInitializer::template Initialize< PointData >(tree.spaceRoot(), pointStream, depth, *samples, *sampleData, true, tree.nodeAllocators[0], tree.initializer(), ProcessDataWithConfidence); } else { auto ProcessData = [](const Point& p, PointData& d) { Real l = ComputeNorm(d.normal); if (std::isnan(l) || l == 0) return static_cast(-1.0); d.normal[0] /= l; d.normal[1] /= l; d.normal[2] /= l; return static_cast(1.0); }; FEMTreeInitializer::template Initialize< PointData >(tree.spaceRoot(), pointStream, solve_depth, *samples, *sampleData, true, tree.nodeAllocators[0], tree.initializer(), ProcessData); } } catch (std::exception e) { return false; } DenseNodeData solution; std::unique_ptr density; SparseNodeData, NormalSigs>* normalInfo = nullptr; Real pointWeightSum = 0; { tree.resetNodeIndices(); // Get the kernel density estimator { int kernelDepth = solve_depth - 2; assert(kernelDepth >= 0); density.reset(tree.template setDensityEstimator(*samples, kernelDepth, params.samplesPerNode, 1)); } // Transform the Hermite samples into a vector field { normalInfo = new SparseNodeData, NormalSigs>(); if (params.normalConfidenceBias > 0) { std::function, Point&, Real&)> ConversionAndBiasFunction = [&](PointData in, Point& out, Real& bias) { // Point n = in.template data<0>(); Point n(in.normal[0], in.normal[1], in.normal[2]); Real l = static_cast(Length(n)); // It is possible that the samples have non-zero normals but there are two co-located samples with negative normals... if (l == 0) return false; out = n / l; bias = static_cast(log(l) * params.normalConfidenceBias / log(1 << (Dim - 1))); return true; }; *normalInfo = tree.setDataField(NormalSigs(), *samples, *sampleData, density.get(), pointWeightSum, ConversionAndBiasFunction); } else { std::function, Point&)> ConversionFunction = [](PointData in, Point& out) { Point n(in.normal[0], in.normal[1], in.normal[2]); Real l = static_cast(Length(n)); // It is possible that the samples have non-zero normals but there are two co-located samples with negative normals... if (l == 0) return false; out = n / l; return true; }; *normalInfo = tree.setDataField(NormalSigs(), *samples, *sampleData, density.get(), pointWeightSum, ConversionFunction); } auto InvertNormal = [&](unsigned int, size_t i) { (*normalInfo)[i] *= static_cast(-1.0); }; ThreadPool::Parallel_for(0, normalInfo->size(), InvertNormal); } if (!params.density) { density.reset(); } if (!params.withColors || params.colorPullFactor == 0) { sampleData.reset(); } // Trim the tree and prepare for multigrid { constexpr int MAX_DEGREE = NORMAL_DEGREE > Degrees::Max() ? NORMAL_DEGREE : Degrees::Max(); tree.template finalizeForMultigrid( params.fullDepth, typename FEMTree::template HasNormalDataFunctor(*normalInfo), normalInfo, density.get() ); } // Add the FEM constraints DenseNodeData constraints; { constraints = tree.initDenseNodeData(Sigs()); typename FEMIntegrator::template Constraint, NormalSigs, IsotropicUIntPack, Dim> F; unsigned int derivatives2[Dim]; for (unsigned int d = 0; d < Dim; d++) derivatives2[d] = 0; typedef IsotropicUIntPack Derivatives1; typedef IsotropicUIntPack Derivatives2; for (unsigned int d = 0; d < Dim; d++) { unsigned int derivatives1[Dim]; for (unsigned int dd = 0; dd < Dim; dd++) derivatives1[dd] = (dd == d ? 1 : 0); F.weights[d][TensorDerivatives::Index(derivatives1)][TensorDerivatives::Index(derivatives2)] = 1; } tree.addFEMConstraints(F, *normalInfo, constraints, solve_depth); } // Free up the normal info if (normalInfo) { delete normalInfo; normalInfo = nullptr; } // Add the interpolation constraints InterpolationInfo* iInfo = nullptr; if (params.pointWeight > 0) { if (exact_interpolation) { iInfo = FEMTree::template InitializeExactPointInterpolationInfo( tree, *samples, ConstraintDual(target_value, static_cast(params.pointWeight) * pointWeightSum), SystemDual(static_cast(params.pointWeight) * pointWeightSum), true, 0); } else { iInfo = FEMTree::template InitializeApproximatePointInterpolationInfo( tree, *samples, ConstraintDual(target_value, static_cast(params.pointWeight) * pointWeightSum), SystemDual(static_cast(params.pointWeight) * pointWeightSum), true, 1); } tree.addInterpolationConstraints(constraints, solve_depth, *iInfo); } // Solve the linear system { typename FEMTree::SolverInfo sInfo; { sInfo.cgDepth = 0; sInfo.cascadic = true; sInfo.vCycles = 1; sInfo.iters = params.iters; sInfo.cgAccuracy = params.cgAccuracy; sInfo.verbose = false; sInfo.showResidual = false; sInfo.showGlobalResidual = SHOW_GLOBAL_RESIDUAL_NONE; sInfo.sliceBlockSize = 1; sInfo.baseDepth = params.baseDepth; sInfo.baseVCycles = params.baseVCycles; } typename FEMIntegrator::template System > F({ 0.0, 1.0 }); solution = tree.solveSystem(Sigs(), F, constraints, solve_depth,sInfo, iInfo); } // Free up the interpolation info if (iInfo) { delete iInfo; iInfo = nullptr; } } Real isoValue = 0; { double valueSum = 0, weightSum = 0; typename FEMTree::template MultiThreadedEvaluator evaluator(&tree, solution); std::vector valueSums(ThreadPool::NumThreads(), 0); std::vector weightSums(ThreadPool::NumThreads(), 0); auto func = [&](unsigned int thread, size_t j) { const ProjectiveData, Real>& sample = (*samples)[j].sample; if (sample.weight > 0) { weightSums[thread] += sample.weight; valueSums[thread] += evaluator.values(sample.data / sample.weight, thread, (*samples)[j].node)[0] * sample.weight; } }; ThreadPool::Parallel_for( 0, samples->size(), func); for (size_t t = 0; t < valueSums.size(); t++) { valueSum += valueSums[t]; weightSum += weightSums[t]; } isoValue = static_cast(valueSum / weightSum); if (!params.withColors || params.colorPullFactor == 0) { samples.reset(); } } auto SetVertex = [] (Vertex& v, Point p, double w, PointData d) { v = Vertex(p, d, w); }; ExtractMesh, Real>(params, UIntPack(), std::tuple(), tree, solution, isoValue, samples.get(), sampleData.get(), density.get(), SetVertex, xForm.inverse(), out_mesh); return true; } bool PoissonReconLib::Reconstruct( const Parameters& params, const ICloud& inCloud, IMesh& outMesh ) { if (!inCloud.hasNormals()) { //we need normals return false; } #ifdef WITH_OPENMP ThreadPool::Init((ThreadPool::ParallelType)(int)ThreadPool::OPEN_MP, params.threads); #else ThreadPool::Init((ThreadPool::ParallelType)(int)ThreadPool::THREAD_POOL, params.threads); #endif PointStream pointStream(inCloud); bool success = false; switch (params.boundary) { case Parameters::FREE: typedef IsotropicUIntPack::Signature> FEMSigsFree; success = Execute(pointStream, outMesh, params, FEMSigsFree()); break; case Parameters::DIRICHLET: typedef IsotropicUIntPack::Signature> FEMSigsDirichlet; success = Execute(pointStream, outMesh, params, FEMSigsDirichlet()); break; case Parameters::NEUMANN: typedef IsotropicUIntPack::Signature> FEMSigsNeumann; success = Execute(pointStream, outMesh, params, FEMSigsNeumann()); break; default: assert(false); break; } ThreadPool::Terminate(); return success; } bool PoissonReconLib::Reconstruct( const Parameters& params, const ICloud& inCloud, IMesh& outMesh ) { if (!inCloud.hasNormals()) { //we need normals return false; } #ifdef WITH_OPENMP ThreadPool::Init((ThreadPool::ParallelType)(int)ThreadPool::OPEN_MP, std::thread::hardware_concurrency()); #else ThreadPool::Init((ThreadPool::ParallelType)(int)ThreadPool::THREAD_POOL, std::thread::hardware_concurrency()); #endif PointStream pointStream(inCloud); bool success = false; switch (params.boundary) { case Parameters::FREE: typedef IsotropicUIntPack::Signature> FEMSigsFree; success = Execute(pointStream, outMesh, params, FEMSigsFree()); break; case Parameters::DIRICHLET: typedef IsotropicUIntPack::Signature> FEMSigsDirichlet; success = Execute(pointStream, outMesh, params, FEMSigsDirichlet()); break; case Parameters::NEUMANN: typedef IsotropicUIntPack::Signature> FEMSigsNeumann; success = Execute(pointStream, outMesh, params, FEMSigsNeumann()); break; default: assert(false); break; } ThreadPool::Terminate(); return success; }