├── .gitignore ├── .gitmodules ├── LICENSE ├── .github └── workflows │ ├── msbuild.yml │ └── cmake.yml ├── README.md ├── CMakeLists.txt └── src ├── poncaAdapters.hpp ├── polyscopeSlicer.hpp └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *build* 2 | *~ 3 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/ponca"] 2 | path = external/ponca 3 | url = https://github.com/poncateam/ponca.git 4 | [submodule "external/polyscope"] 5 | path = external/polyscope 6 | url = https://github.com/nmwsharp/polyscope.git 7 | [submodule "external/libigl"] 8 | path = external/libigl 9 | url = https://github.com/libigl/libigl.git 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Ponca Development Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/msbuild.yml: -------------------------------------------------------------------------------- 1 | name: MSbuild 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: seanmiddleditch/gha-setup-ninja@master 19 | - name: Add msbuild to PATH 20 | uses: seanmiddleditch/gha-setup-vsdevenv@master 21 | 22 | - uses: actions/checkout@v3 23 | with: 24 | submodules: 'recursive' 25 | 26 | - name: Configure CMake 27 | # Configure CMake for MSbuild in a 'build' subdirectory. 28 | # `CMAKE_BUILD_TYPE` is not required if you are using a multi-configuration generators 29 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 30 | run: | 31 | cmake -B ${{github.workspace}}/build -GNinja -DCMAKE_CXX_COMPILER=cl.exe -DCMAKE_C_COMPILER=cl.exe 32 | 33 | - name: Build 34 | # Build your program with the given configuration 35 | run: cmake --build ${{github.workspace}}/build --parallel --config ${{env.BUILD_TYPE}} 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | strategy: 16 | matrix: 17 | os: ['windows-latest', 'ubuntu-latest', 'macos-latest'] 18 | fail-fast: false 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Install Polyscope dependencies (ubuntu) 23 | run: sudo apt-get update && sudo apt-get install -y xorg-dev libglu1-mesa-dev freeglut3-dev mesa-common-dev 24 | if: matrix.os == 'ubuntu-latest' 25 | 26 | - uses: actions/checkout@v3 27 | with: 28 | submodules: 'recursive' 29 | 30 | - name: Configure CMake 31 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 32 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 33 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 34 | 35 | - name: Build 36 | # Build your program with the given configuration 37 | run: cmake --build ${{github.workspace}}/build --parallel --config ${{env.BUILD_TYPE}} 38 | 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # poncascope 2 | Application demonstrating how easily to combine: 3 | - Ponca: for POiNt Cloud Analysis and acceleration structures (kdtree) [https://github.com/poncateam/ponca] 4 | - libIGL: for data loading [https://github.com/libigl/libigl] 5 | - Polyscope: for the GUI [https://github.com/nmwsharp/polyscope] 6 | 7 | With the current version, you will be able to: 8 | - compute and visualise differential quantities (normal vectors, mean curvature, principal curvature), 9 | - compare several differential estimators based on Moving Least Squares reconstruction, 10 | - play with reconstruction parameters, control timings, and more... 11 | 12 | Computations are all done using Ponca on polyscope datastructures (see code for more details on data biding). Spatial queries are accelerated using Ponca Kdtree. 13 | 14 | ## Compilation instructions 15 | ```bash 16 | git clone https://github.com/poncateam/poncascope.git # Fetch repository 17 | cd poncascope 18 | git submodule update --recursive --init # Get dependencies: Polyscope, Ponca 19 | mkdir build && cd build # Goto to compilation directory 20 | cmake ../ -DCMAKE_BUILD_TYPE=Release # Configure in release mode 21 | make # Compile 22 | ``` 23 | 24 | ## Gallery 25 | 26 | ### Main features 27 | [![Alt text](https://user-images.githubusercontent.com/6310221/134690163-f8ea4965-2e6c-4a84-9caa-d553fbe4e40c.png)](https://youtu.be/WRqO93rEy6s) 28 | 29 | ### Mean curvature estimation 30 | image 31 | 32 | ### Principale curvatures estimation 33 | image 34 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project("Poncascope") 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | # Maybe stop from CMAKEing in the wrong place 9 | if (CMAKE_BINARY_DIR STREQUAL CMAKE_SOURCE_DIR) 10 | message(FATAL_ERROR "Source and build directories cannot be the same. Go use the /build directory.") 11 | endif() 12 | 13 | ### Configure output locations 14 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 15 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 16 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 17 | 18 | ### Compiler options 19 | set( CMAKE_EXPORT_COMPILE_COMMANDS 1 ) # Emit a compile flags file to support completion engines 20 | 21 | # Add ponca 22 | message("\n\n == CMAKE add Ponca\n") 23 | set( PONCA_CONFIGURE_EXAMPLES CACHE BOOL OFF) 24 | set( PONCA_CONFIGURE_TESTS CACHE BOOL OFF) 25 | set( PONCA_CONFIGURE_DOC CACHE BOOL OFF) 26 | add_subdirectory("external/ponca") 27 | 28 | # Check if Eigen is used from a package or from Ponca submodules 29 | find_package(Eigen3 QUIET) 30 | set(Eigen_Deps "") 31 | if( NOT Eigen3_FOUND ) # Should use Ponca submodule 32 | message( "Polyscope should use Eigen from ${EIGEN3_INCLUDE_DIRS}" ) 33 | include_directories(${EIGEN3_INCLUDE_DIRS}) 34 | else() 35 | set(Eigen_Deps Eigen3::Eigen) 36 | endif() 37 | 38 | # Add polyscope 39 | message("\n\n == CMAKE recursively building Polyscope\n") 40 | add_subdirectory("external/polyscope") 41 | 42 | # Move assets to binary folder 43 | add_custom_target( poncascope-copyassets 44 | COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/assets/" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets" 45 | COMMENT "Install assets to binary folder") 46 | 47 | # Find OpenMP 48 | find_package(OpenMP) 49 | set(OpenMP_link_libraries ) 50 | if(OpenMP_CXX_FOUND) 51 | set(OpenMP_link_libraries OpenMP::OpenMP_CXX) 52 | message("OpenMP found") 53 | endif() 54 | 55 | # Create an executable 56 | add_executable( poncascope 57 | src/poncaAdapters.hpp 58 | src/polyscopeSlicer.hpp 59 | src/main.cpp 60 | ) 61 | 62 | add_dependencies( poncascope poncascope-copyassets) 63 | 64 | # Include settings 65 | target_include_directories(poncascope PUBLIC 66 | "${CMAKE_CURRENT_SOURCE_DIR}/external/libigl/include" 67 | "${CMAKE_CURRENT_SOURCE_DIR}/external/ponca/") 68 | 69 | # Link settings 70 | target_link_libraries(poncascope polyscope ${Eigen_Deps} ${OpenMP_link_libraries}) 71 | 72 | 73 | # Fix potential bug on windows (appears with VSCode, but not with VS) 74 | # Moves bin to project/bin instead of project/bin/BuidType/ 75 | set_target_properties(poncascope PROPERTIES RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_RUNTIME_OUTPUT_DIRECTORY}>) 76 | 77 | # Fix compilation error with MSVC 78 | if (MSVC) 79 | target_compile_options(poncascope PRIVATE /bigobj) 80 | endif () 81 | 82 | -------------------------------------------------------------------------------- /src/poncaAdapters.hpp: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2021 Ponca Development Group 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | /// \file This file contains structures mapping Polyscope data representation to Ponca concepts 24 | /// \author Nicolas Mellado 25 | 26 | #include 27 | 28 | 29 | /// Map a block to a Ponca point 30 | template 31 | struct BlockPointAdapter { 32 | public: 33 | enum {Dim = 3}; 34 | using Scalar = _Scalar; 35 | using VectorType = Eigen::Matrix; 36 | using MatrixType = Eigen::Matrix; 37 | 38 | using InternalType = typename Eigen::Block::ConstTransposeReturnType; 39 | 40 | /// \brief Map a vector as ponca Point 41 | PONCA_MULTIARCH inline BlockPointAdapter(InternalType v, InternalType n) 42 | : m_pos (v), m_nor (n) {} 43 | 44 | PONCA_MULTIARCH inline InternalType pos() const { return m_pos; } 45 | PONCA_MULTIARCH inline InternalType normal() const { return m_nor; } 46 | 47 | private: 48 | InternalType m_pos; 49 | InternalType m_nor; 50 | }; 51 | 52 | 53 | 54 | template 55 | void 56 | buildKdTree(const Eigen::MatrixXd& cloudV, const Eigen::MatrixXd& cloudN, KdTreeType& tree){ 57 | std::vector ids(cloudV.rows()); 58 | std::iota(ids.begin(), ids.end(), 0); 59 | 60 | using VN = std::pair; 61 | 62 | // Build KdTree: do not copy coordinate but rather store Eigen::Block 63 | tree.buildWithSampling(VN(cloudV, cloudN), 64 | ids, 65 | [](VN bufs, typename KdTreeType::PointContainer &out) { 66 | int s = bufs.first.rows(); 67 | out.reserve(s); 68 | for (int i = 0; i != s; ++i) 69 | out.push_back(typename KdTreeType::DataPoint(bufs.first.row(i).transpose(), 70 | bufs.second.row(i).transpose())); 71 | }); 72 | } -------------------------------------------------------------------------------- /src/polyscopeSlicer.hpp: -------------------------------------------------------------------------------- 1 | //MIT License 2 | // 3 | //Copyright (c) 2023 Ponca Development Group 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | // 12 | //The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | //SOFTWARE. 22 | 23 | /// \file This file contains a slicer of the implicit function as a Polyscope surface mesh 24 | /// \author David Coeurjolly 25 | 26 | #pragma once 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | 33 | /** 34 | * Create and register a polyscope surface mesh that slices a given implicit function. 35 | * This function uses a regular grid and evaluates the implicit function at the grid vertex positions. 36 | * 37 | * The implicit function could be a C/C++ function or any lambda taking as input a Point and returning a double. 38 | * 39 | * Note: if openmp is available, the implicit function is evaluated in parallel. 40 | * 41 | * @param name the name of the slicer 42 | * @param implicit the implicit function that maps points (type Point) to scalars. 43 | * @param lowerBound the bbox lower bound of the implicit function domain. 44 | * @param upperBound the bbox upper bound of the implicit function domain. 45 | * @param nbSteps step size for the regular grid construction (eg 256) 46 | * @param axis the axis to slice {0,1,2}. 47 | * @param slice the relative position of the slice in [0,1] 48 | * 49 | * @return a pointer to the polyscope surface mesh object 50 | */ 51 | template 52 | polyscope::SurfaceMesh* registerRegularSlicer(const std::string &name, 53 | const Functor &implicit, 54 | const Point &lowerBound, 55 | const Point &upperBound, 56 | size_t nbSteps, 57 | size_t axis, 58 | float slice=0.0) 59 | { 60 | size_t sliceid = static_cast(std::floor(slice*nbSteps)); 61 | 62 | auto dim1 = (axis+1)%3; 63 | auto dim2 = (axis+2)%3; 64 | 65 | double du = (upperBound[dim1]-lowerBound[dim1])/(double)nbSteps; 66 | double dv = (upperBound[dim2]-lowerBound[dim2])/(double)nbSteps; 67 | double dw = (upperBound[axis]-lowerBound[axis])/(double)nbSteps; 68 | 69 | double u = lowerBound[dim1]; 70 | double v = lowerBound[dim2]; 71 | double w = lowerBound[axis] + sliceid*dw; 72 | 73 | Point p; 74 | Point vu,vv; 75 | switch (axis) { 76 | case 0: p=Point(w,u,v); vu=Point(0,du,0); vv=Point(0,0,dv);break; 77 | case 1: p=Point(u,w,v); vu=Point(du,0,0); vv=Point(0,0,dv);break; 78 | case 2: p=Point(u,v,w); vu=Point(du,0,0); vv=Point(0,dv,0);break; 79 | } 80 | 81 | std::vector vertices(nbSteps*nbSteps); 82 | std::vector values(nbSteps*nbSteps); 83 | std::vector> faces; 84 | faces.reserve(nbSteps*nbSteps); 85 | std::array face; 86 | 87 | //Regular grid construction 88 | for(size_t id=0; id < nbSteps*nbSteps; ++id) 89 | { 90 | auto i = id % nbSteps; 91 | auto j = id / nbSteps; 92 | p = lowerBound + i*vu + j*vv; 93 | p[axis] += sliceid*dw; 94 | vertices[id] = p; 95 | face = { id, id+1, id+1+nbSteps, id+nbSteps }; 96 | if (((i+1) < nbSteps) && ((j+1)addVertexScalarQuantity("values",values)->setEnabled(true); 108 | return psm; 109 | } 110 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "polyscope/polyscope.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "polyscope/point_cloud.h" 7 | 8 | #include 9 | #include 10 | #include "poncaAdapters.hpp" 11 | #include "polyscopeSlicer.hpp" 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | // Types definition 18 | using Scalar = double; 19 | using VectorType = Eigen::Vector; 20 | using PPAdapter = BlockPointAdapter; 21 | using KdTree = Ponca::KdTreeSparse; 22 | using KnnGraph = Ponca::KnnGraph; 23 | using SmoothWeightFunc = Ponca::DistWeightFunc >; 24 | //using SmoothWeightFunc = Ponca::DistWeightFunc >; 25 | 26 | // Variables 27 | Eigen::MatrixXd cloudV, cloudN; 28 | KdTree tree; 29 | KnnGraph* knnGraph {nullptr}; 30 | polyscope::PointCloud* cloud = nullptr; 31 | 32 | // Options for algorithms 33 | int iVertexSource = 7; /// < id of the selected point 34 | int kNN = 10; /// < neighborhood size (knn) 35 | float NSize = 0.1; /// < neighborhood size (euclidean) 36 | int mlsIter = 3; /// < number of moving least squares iterations 37 | Scalar pointRadius = 0.005; /// < display radius of the point cloud 38 | bool useKnnGraph = false; /// < use k-neighbor graph instead of kdtree 39 | 40 | 41 | 42 | // Slicer 43 | float slice = 0.f; 44 | int axis = 0; 45 | bool isHDSlicer=false; 46 | VectorType lower, upper; 47 | 48 | 49 | /// Convenience function measuring and printing the processing time of F 50 | template 51 | void measureTime( const std::string &actionName, Functor F ){ 52 | using namespace std::literals; // enables the usage of 24h instead of e.g. std::chrono::hours(24) 53 | 54 | const std::chrono::time_point start = 55 | std::chrono::steady_clock::now(); 56 | F(); // run process 57 | const auto end = std::chrono::steady_clock::now(); 58 | std::cout << actionName << " in " << (end - start) / 1ms << "ms.\n"; 59 | } 60 | 61 | template 62 | void processRangeNeighbors(int i, Functor f){ 63 | if(useKnnGraph) 64 | for (int j : knnGraph->range_neighbors(i, NSize)){ 65 | f(j); 66 | } 67 | else 68 | for (int j : tree.range_neighbors(i, NSize)){ 69 | f(j); 70 | } 71 | } 72 | 73 | /// Show in polyscope the euclidean neighborhood of the selected point (iVertexSource), with smooth weighting function 74 | void colorizeEuclideanNeighborhood() { 75 | int nvert = tree.samples().size(); 76 | Eigen::VectorXd closest ( nvert ); 77 | closest.setZero(); 78 | 79 | delete knnGraph; 80 | knnGraph = new KnnGraph (tree, kNN); 81 | 82 | SmoothWeightFunc w(VectorType::Zero(), NSize ); 83 | 84 | closest(iVertexSource) = 2; 85 | const auto &p = tree.points()[iVertexSource]; 86 | processRangeNeighbors(iVertexSource, [w,p,&closest](int j){ 87 | const auto &q = tree.points()[j]; 88 | closest(j) = w.w( q.pos() - p.pos(), q ).first; 89 | }); 90 | 91 | cloud->addScalarQuantity( "range neighborhood", closest); 92 | } 93 | 94 | /// Recompute K-Neighbor graph 95 | void recomputeKnnGraph() { 96 | if(useKnnGraph) { 97 | measureTime("[Ponca] Build KnnGraph", []() { 98 | delete knnGraph; 99 | knnGraph = new KnnGraph(tree, kNN); 100 | }); 101 | } 102 | } 103 | 104 | /// Show in polyscope the knn neighborhood of the selected point (iVertexSource) 105 | void colorizeKnn() { 106 | int nvert = tree.samples().size(); 107 | Eigen::VectorXd closest ( nvert ); 108 | closest.setZero(); 109 | 110 | closest(iVertexSource) = 2; 111 | processRangeNeighbors(iVertexSource, [&closest](int j){ 112 | closest(j) = 1; 113 | }); 114 | cloud->addScalarQuantity( "knn neighborhood", closest); 115 | } 116 | 117 | /// Generic processing function: traverse point cloud, compute fitting, and use functor to process fitting output 118 | /// \note Functor is called only if fit is stable 119 | template 120 | void processPointCloud(const typename FitT::Scalar t, Functor f){ 121 | #pragma omp parallel for 122 | for (int i = 0; i < tree.samples().size(); ++i) { 123 | VectorType pos = tree.points()[i].pos(); 124 | 125 | for( int mm = 0; mm < mlsIter; ++mm) { 126 | FitT fit; 127 | fit.setWeightFunc({pos, t}); 128 | fit.init(); 129 | 130 | processRangeNeighbors(i, [&fit](int j){ 131 | fit.addNeighbor(tree.points()[j]); 132 | }); 133 | 134 | if (fit.finalize() == Ponca::STABLE){ 135 | pos = fit.project( pos ); 136 | if ( mm == mlsIter -1 ) // last mls step, calling functor 137 | f(i, fit, pos); 138 | } 139 | else { 140 | std::cerr << "Warning: fit " << i << " is not stable" << std::endl; 141 | break; 142 | } 143 | } 144 | } 145 | } 146 | 147 | /// Generic processing function: traverse point cloud and compute mean, first and second curvatures + their direction 148 | /// \tparam FitT Defines the type of estimator used for computation 149 | template 150 | void estimateDifferentialQuantities_impl(const std::string& name) { 151 | int nvert = tree.samples().size(); 152 | Eigen::VectorXd mean ( nvert ), kmin ( nvert ), kmax ( nvert ); 153 | Eigen::MatrixXd normal( nvert, 3 ), dmin( nvert, 3 ), dmax( nvert, 3 ), proj( nvert, 3 ); 154 | 155 | measureTime( "[Ponca] Compute differential quantities using " + name, 156 | [&mean, &kmin, &kmax, &normal, &dmin, &dmax, &proj]() { 157 | processPointCloud(NSize, 158 | [&mean, &kmin, &kmax, &normal, &dmin, &dmax, &proj] 159 | ( int i, const FitT& fit, const VectorType& mlsPos){ 160 | 161 | mean(i) = fit.kMean(); 162 | kmax(i) = fit.kmax(); 163 | kmin(i) = fit.kmin(); 164 | 165 | normal.row( i ) = fit.primitiveGradient(); 166 | dmin.row( i ) = fit.kminDirection(); 167 | dmax.row( i ) = fit.kmaxDirection(); 168 | 169 | proj.row( i ) = mlsPos - tree.points()[i].pos(); 170 | }); 171 | }); 172 | 173 | measureTime( "[Polyscope] Update differential quantities", 174 | [&name, &mean, &kmin, &kmax, &normal, &dmin, &dmax, &proj]() { 175 | cloud->addScalarQuantity(name + " - Mean Curvature", mean)->setMapRange({-10,10}); 176 | cloud->addScalarQuantity(name + " - K1", kmin)->setMapRange({-10,10}); 177 | cloud->addScalarQuantity(name + " - K2", kmax)->setMapRange({-10,10}); 178 | 179 | cloud->addVectorQuantity(name + " - normal", normal)->setVectorLengthScale( 180 | Scalar(2) * pointRadius); 181 | cloud->addVectorQuantity(name + " - K1 direction", dmin)->setVectorLengthScale( 182 | Scalar(2) * pointRadius); 183 | cloud->addVectorQuantity(name + " - K2 direction", dmax)->setVectorLengthScale( 184 | Scalar(2) * pointRadius); 185 | cloud->addVectorQuantity(name + " - projection", proj, polyscope::VectorType::AMBIENT); 186 | }); 187 | } 188 | 189 | using FitDry = Ponca::Basket; 190 | 191 | using FitPlane = Ponca::Basket; 192 | using FitPlaneDiff = Ponca::BasketDiff< 193 | FitPlane, 194 | Ponca::DiffType::FitSpaceDer, 195 | Ponca::CovariancePlaneDer, 196 | Ponca::CurvatureEstimatorBase, Ponca::NormalDerivativesCurvatureEstimator>; 197 | 198 | using FitAPSS = Ponca::Basket; 199 | using FitAPSSDiff = Ponca::BasketDiff< 200 | FitAPSS, 201 | Ponca::DiffType::FitSpaceDer, 202 | Ponca::OrientedSphereDer, 203 | Ponca::CurvatureEstimatorBase, Ponca::NormalDerivativesCurvatureEstimator>; 204 | 205 | using FitASO = FitAPSS; 206 | using FitASODiff = Ponca::BasketDiff< 207 | FitASO, 208 | Ponca::DiffType::FitSpaceDer, 209 | Ponca::OrientedSphereDer, Ponca::MlsSphereFitDer, 210 | Ponca::CurvatureEstimatorBase, Ponca::NormalDerivativesCurvatureEstimator>; 211 | 212 | /// Compute curvature using Covariance Plane fitting 213 | /// \see estimateDifferentialQuantities_impl 214 | void estimateDifferentialQuantitiesWithPlane() { 215 | estimateDifferentialQuantities_impl("PSS"); 216 | } 217 | 218 | /// Compute curvature using APSS 219 | /// \see estimateDifferentialQuantities_impl 220 | inline void estimateDifferentialQuantitiesWithAPSS() { 221 | estimateDifferentialQuantities_impl("APSS"); 222 | } 223 | 224 | /// Compute curvature using Algebraic Shape Operator 225 | /// \see estimateDifferentialQuantities_impl 226 | inline void estimateDifferentialQuantitiesWithASO() { 227 | estimateDifferentialQuantities_impl("ASO"); 228 | } 229 | 230 | /// Dry run: loop over all vertices + run MLS loops without computation 231 | /// This function is useful to monitor the KdTree performances 232 | inline void mlsDryRun() { 233 | measureTime( "[Ponca] Dry run MLS ", []() { 234 | processPointCloud( NSize, [](int, const FitDry&, const VectorType& ){ }); 235 | }); 236 | } 237 | 238 | ///Evaluate scalar field for generic FitType. 239 | ///// \tparam FitT Defines the type of estimator used for computation 240 | template 241 | Scalar evalScalarField_impl(const VectorType& input_pos) 242 | { 243 | VectorType current_pos = input_pos; 244 | Scalar current_value = std::numeric_limits::max(); 245 | for(int mm = 0; mm < mlsIter; ++mm) 246 | { 247 | FitT fit; 248 | fit.setWeightFunc({current_pos, NSize}); // weighting function using current pos (not input pos) 249 | auto res = fit.computeWithIds(tree.range_neighbors(current_pos, NSize), tree.points()); 250 | if(res == Ponca::STABLE) { 251 | current_pos = fit.project(input_pos); // always project input pos 252 | current_value = isSigned ? fit.potential(input_pos) : std::abs(fit.potential(input_pos)); 253 | // current_gradient = fit.primitiveGradient(input_pos); 254 | } else { 255 | // not enough neighbors (if far from the point cloud) 256 | return .0;//std::numeric_limits::max(); 257 | } 258 | } 259 | return current_value; 260 | } 261 | 262 | /// Define Polyscope callbacks 263 | void callback() { 264 | 265 | ImGui::PushItemWidth(100); 266 | 267 | ImGui::Text("Neighborhood collection"); 268 | ImGui::SameLine(); 269 | if(ImGui::Checkbox("Use KnnGraph", &useKnnGraph)) recomputeKnnGraph(); 270 | 271 | if(ImGui::InputInt("k-neighborhood size", &kNN)) recomputeKnnGraph(); 272 | ImGui::InputFloat("neighborhood size", &NSize); 273 | ImGui::InputInt("source vertex", &iVertexSource); 274 | ImGui::InputInt("Nb MLS Iterations", &mlsIter); 275 | ImGui::SameLine(); 276 | if (ImGui::Button("show knn")) colorizeKnn(); 277 | ImGui::SameLine(); 278 | if (ImGui::Button("show euclidean nei")) colorizeEuclideanNeighborhood(); 279 | 280 | ImGui::Separator(); 281 | 282 | ImGui::Text("Differential estimators"); 283 | if (ImGui::Button("Dry Run")) mlsDryRun(); 284 | ImGui::SameLine(); 285 | if (ImGui::Button("Plane (PCA)")) estimateDifferentialQuantitiesWithPlane(); 286 | ImGui::SameLine(); 287 | if (ImGui::Button("APSS")) estimateDifferentialQuantitiesWithAPSS(); 288 | ImGui::SameLine(); 289 | if (ImGui::Button("ASO")) estimateDifferentialQuantitiesWithASO(); 290 | 291 | ImGui::Separator(); 292 | 293 | ImGui::Text("Implicit function slicer"); 294 | ImGui::SliderFloat("Slice", &slice, 0, 1.0); ImGui::SameLine(); 295 | ImGui::Checkbox("HD", &isHDSlicer); 296 | ImGui::RadioButton("X axis", &axis, 0); ImGui::SameLine(); 297 | ImGui::RadioButton("Y axis", &axis, 1); ImGui::SameLine(); 298 | ImGui::RadioButton("Z axis", &axis, 2); 299 | const char* items[] = { "ASO", "APSS", "PSS"}; 300 | static int item_current = 0; 301 | ImGui::Combo("Fit function", &item_current, items, IM_ARRAYSIZE(items)); 302 | if (ImGui::Button("Update")) 303 | { 304 | switch(item_current) 305 | { 306 | case 0: registerRegularSlicer("slicer", evalScalarField_impl,lower, upper, isHDSlicer?1024:256, axis, slice); break; 307 | case 1: registerRegularSlicer("slicer", evalScalarField_impl,lower, upper, isHDSlicer?1024:256, axis, slice); break; 308 | case 2: registerRegularSlicer("slicer", evalScalarField_impl,lower, upper, isHDSlicer?1024:256, axis, slice); break; 309 | } 310 | } 311 | ImGui::SameLine(); 312 | ImGui::PopItemWidth(); 313 | } 314 | 315 | int main(int argc, char **argv) { 316 | // Options 317 | polyscope::options::autocenterStructures = false; 318 | polyscope::options::programName = "poncascope"; 319 | polyscope::view::windowWidth = 1024; 320 | polyscope::view::windowHeight = 1024; 321 | 322 | // Initialize polyscope 323 | polyscope::init(); 324 | 325 | measureTime( "[libIGL] Load Armadillo", []() 326 | // For convenience: use libIGL to load a mesh, and store only the vertices location and normal vector 327 | { 328 | std::string filename = "assets/armadillo.obj"; 329 | Eigen::MatrixXi meshF; 330 | igl::readOBJ(filename, cloudV, meshF); 331 | igl::per_vertex_normals(cloudV, meshF, cloudN); 332 | } ); 333 | 334 | // Check if normals have been properly loaded 335 | int nbUnitNormal = cloudN.rowwise().squaredNorm().sum(); 336 | if ( nbUnitNormal != cloudV.rows() ) { 337 | std::cerr << "[libIGL] An error occurred when computing the normal vectors from the mesh. Aborting..." 338 | << std::endl; 339 | return EXIT_FAILURE; 340 | } 341 | 342 | //Bounding Box (used in the slicer) 343 | lower = cloudV.colwise().minCoeff(); 344 | upper = cloudV.colwise().maxCoeff(); 345 | 346 | // Build Ponca KdTree 347 | measureTime( "[Ponca] Build KdTree", []() { 348 | buildKdTree(cloudV, cloudN, tree); 349 | }); 350 | 351 | // Register the point cloud with Polyscope 352 | std::cout << "Starting polyscope... " << std::endl; 353 | cloud = polyscope::registerPointCloud("cloud", cloudV); 354 | cloud->setPointRadius(pointRadius); 355 | 356 | // Add the callback 357 | polyscope::state::userCallback = callback; 358 | 359 | // Show the gui 360 | polyscope::show(); 361 | 362 | delete knnGraph; 363 | return EXIT_SUCCESS; 364 | } 365 | --------------------------------------------------------------------------------