├── .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 | [](https://youtu.be/WRqO93rEy6s)
28 |
29 | ### Mean curvature estimation
30 |
31 |
32 | ### Principale curvatures estimation
33 |
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 |
--------------------------------------------------------------------------------