117#include "DGtal/base/Common.h"
118#include "DGtal/shapes/SurfaceMesh.h"
120#include "DGtal/geometry/meshes/CorrectedNormalCurrentComputer.h"
122#include "DGtal/helpers/Shortcuts.h"
123#include "DGtal/helpers/ShortcutsGeometry.h"
124#include "DGtal/io/writers/SurfaceMeshWriter.h"
125#include "DGtal/io/colormaps/GradientColorMap.h"
126#include "DGtal/io/colormaps/QuantifiedColorMap.h"
140void usage(
int argc,
char* argv[] )
142 using namespace DGtal;
145 std::cout <<
"Usage: " << std::endl
146 <<
"\t" << argv[ 0 ] <<
" <P> <B> <h> <R> <mode>" << std::endl
148 <<
"Computation of principal curvatures and directions on" << std::endl
149 <<
"a digitized implicit shape using constant or " << std::endl
150 <<
"interpolated corrected curvature measures (based " << std::endl
151 <<
"on the theory of corrected normal currents)." << std::endl
152 <<
"- builds the surface mesh from polynomial <P>" << std::endl
153 <<
"- <B> defines the digitization space size [-B,B]^3" << std::endl
154 <<
"- <h> is the gridstep digitization" << std::endl
155 <<
"- <R> is the radius of the measuring balls" << std::endl
156 <<
"- <mode> is either Const for constant corrected normal" << std::endl
157 <<
" vector field or Interp for interpolated corrected" << std::endl
158 <<
" normal vector field." << std::endl
159 <<
"It produces several OBJ files to display principal " << std::endl
160 <<
"curvatures and directions estimations: `example-cnc-K1.obj`" << std::endl
161 <<
"`example-cnc-K2.obj`, `example-cnc-D1.obj`, and" << std::endl
162 <<
"`example-cnc-D2.obj` as well as associated MTL files." << std::endl;
163 std::cout <<
"You may either write your own polynomial as 3*x^2*y-z^2*x*y+1" << std::endl
164 <<
"or use a predefined polynomial in the following list:" << std::endl;
165 auto L = SH::getPolynomialList();
166 for (
const auto& p : L )
167 std::cout << p.first <<
" : " << p.second << std::endl;
170int main(
int argc,
char* argv[] )
178 using namespace DGtal;
185 std::string poly = argv[ 1 ];
186 const double B = argc > 2 ? atof( argv[ 2 ] ) : 1.0;
187 const double h = argc > 3 ? atof( argv[ 3 ] ) : 1.0;
188 const double R = argc > 4 ? atof( argv[ 4 ] ) : 2.0;
189 std::string mode = argc > 5 ? argv[ 5 ] :
"Const";
190 bool interpolated = mode ==
"Interp";
192 std::cout <<
"Using vertex-*Interpolated* Corrected Normal Current" << std::endl;
194 std::cout <<
"Using face-*Constant* Corrected Normal Current" << std::endl;
197 auto params = SH::defaultParameters() | SHG::defaultParameters();
198 params(
"t-ring", 3 )(
"surfaceTraversal",
"Default" );
199 params(
"polynomial", poly )(
"gridstep", h );
200 params(
"minAABB", -B )(
"maxAABB", B );
201 params(
"offset", 3.0 );
202 auto shape = SH::makeImplicitShape3D( params );
203 auto K = SH::getKSpace( params );
204 auto dshape = SH::makeDigitizedImplicitShape3D( shape, params );
205 auto bimage = SH::makeBinaryImage( dshape, params );
206 if ( bimage ==
nullptr )
208 trace.
error() <<
"Unable to read polynomial <"
209 << poly.c_str() <<
">" << std::endl;
212 auto sembedder = SH::getSCellEmbedder(
K );
213 auto embedder = SH::getCellEmbedder(
K );
214 auto surface = SH::makeDigitalSurface( bimage,
K, params );
215 auto surfels = SH::getSurfelRange( surface, params );
216 trace.
info() <<
"- surface has " << surfels.size()<<
" surfels." << std::endl;
221 std::vector< SM::Vertices > faces;
223 auto pointels = SH::getPointelRange( c2i, surface );
224 auto vertices = SH::RealPoints( pointels.size() );
225 std::transform( pointels.cbegin(), pointels.cend(), vertices.begin(),
226 [&] (
const SH::Cell& c) { return h * embedder( c ); } );
227 for (
auto&& surfel : *surface )
229 const auto primal_surfel_vtcs = SH::getPointelRange(
K, surfel );
231 for (
auto&& primal_vtx : primal_surfel_vtcs )
232 face.push_back( c2i[ primal_vtx ] );
233 faces.push_back( face );
235 smesh.init( vertices.cbegin(), vertices.cend(),
236 faces.cbegin(), faces.cend() );
237 trace.
info() << smesh << std::endl;
241 auto exp_K1 = SHG::getFirstPrincipalCurvatures ( shape,
K, surfels, params );
242 auto exp_K2 = SHG::getSecondPrincipalCurvatures( shape,
K, surfels, params );
243 auto exp_D1 = SHG::getFirstPrincipalDirections ( shape,
K, surfels, params );
244 auto exp_D2 = SHG::getSecondPrincipalDirections( shape,
K, surfels, params );
251 auto face_normals = SHG::getCTrivialNormalVectors( surface, surfels, params );
254 smesh.setFaceNormals( face_normals.cbegin(), face_normals.cend() );
257 if ( interpolated ) smesh.computeVertexNormalsFromFaceNormals();
259 auto mu0 = cnc.computeMu0();
260 auto muXY = cnc.computeMuXY();
266 std::vector< double > K1( smesh.nbFaces() );
267 std::vector< double > K2( smesh.nbFaces() );
268 std::vector< RealVector > D1( smesh.nbFaces() );
269 std::vector< RealVector > D2( smesh.nbFaces() );
270 for (
auto f = 0; f < smesh.nbFaces(); ++f )
272 const auto b = smesh.faceCentroid( f );
273 const auto N = smesh.faceNormals()[ f ];
274 const auto area = mu0 .measure( b, R, f );
275 const auto M = muXY.measure( b, R, f );
276 std::tie( K1[ f ], K2[ f ], D1[ f ], D2[ f ] )
277 = cnc.principalCurvatures( area, M, N );
282 auto exp_K1_min_max = std::minmax_element( exp_K1.cbegin(), exp_K1.cend() );
283 auto exp_K2_min_max = std::minmax_element( exp_K2.cbegin(), exp_K2.cend() );
284 auto K1_min_max = std::minmax_element( K1.cbegin(), K1.cend() );
285 auto K2_min_max = std::minmax_element( K2.cbegin(), K2.cend() );
286 std::cout <<
"Expected K1 curvatures:"
287 <<
" min=" << *exp_K1_min_max.first <<
" max=" << *exp_K1_min_max.second
289 std::cout <<
"Computed k1 curvatures:"
290 <<
" min=" << *K1_min_max.first <<
" max=" << *K1_min_max.second
292 std::cout <<
"Expected k2 curvatures:"
293 <<
" min=" << *exp_K2_min_max.first <<
" max=" << *exp_K2_min_max.second
295 std::cout <<
"Computed k2 curvatures:"
296 <<
" min=" << *K2_min_max.first <<
" max=" << *K2_min_max.second
298 const auto error_K1 = SHG::getScalarsAbsoluteDifference( K1, exp_K1 );
299 const auto stat_error_K1 = SHG::getStatistic( error_K1 );
300 const auto error_K1_l2 = SHG::getScalarsNormL2( K1, exp_K1 );
301 trace.
info() <<
"|K1-K1_CNC|_oo = " << stat_error_K1.max() << std::endl;
302 trace.
info() <<
"|K1-K1_CNC|_2 = " << error_K1_l2 << std::endl;
303 const auto error_K2 = SHG::getScalarsAbsoluteDifference( K2, exp_K2 );
304 const auto stat_error_K2 = SHG::getStatistic( error_K2 );
305 const auto error_K2_l2 = SHG::getScalarsNormL2( K2, exp_K2 );
306 trace.
info() <<
"|K2-K2_CNC|_oo = " << stat_error_K2.max() << std::endl;
307 trace.
info() <<
"|K2-K2_CNC|_2 = " << error_K2_l2 << std::endl;
312 smesh.vertexNormals() = SH::RealVectors();
313 smesh.faceNormals() = SH::RealVectors();
315 const double Kmax = std::max( fabs( *exp_K1_min_max.first ),
316 fabs( *exp_K2_min_max.second ) );
317 const auto colormapK1 = makeQuantifiedColorMap(
makeColorMap( -Kmax, Kmax ) );
318 const auto colormapK2 = makeQuantifiedColorMap(
makeColorMap( -Kmax, Kmax ) );
319 auto colorsK1 = SMW::Colors( smesh.nbFaces() );
320 auto colorsK2 = SMW::Colors( smesh.nbFaces() );
321 for (
auto i = 0; i < smesh.nbFaces(); i++ )
323 colorsK1[ i ] = colormapK1( K1[ i ] );
324 colorsK2[ i ] = colormapK2( K2[ i ] );
326 SMW::writeOBJ(
"example-cnc-K1", smesh, colorsK1 );
327 SMW::writeOBJ(
"example-cnc-K2", smesh, colorsK2 );
328 const auto avg_e = smesh.averageEdgeLength();
329 SH::RealPoints positions( smesh.nbFaces() );
330 for (
auto f = 0; f < positions.size(); ++f )
332 D1[ f ] *= smesh.localWindow( f );
333 positions[ f ] = smesh.faceCentroid( f ) - 0.5 * D1[ f ];
335 SH::saveVectorFieldOBJ( positions, D1, 0.05 * avg_e, SH::Colors(),
337 SH::Color::Black, SH::Color( 0, 128, 0 ) );
338 for (
auto f = 0; f < positions.size(); ++f )
340 D2[ f ] *= smesh.localWindow( f );
341 positions[ f ] = smesh.faceCentroid( f ) - 0.5 * D2[ f ];
343 SH::saveVectorFieldOBJ( positions, D2, 0.05 * avg_e, SH::Colors(),
345 SH::Color::Black, SH::Color(128, 0,128 ) );
Structure representing an RGB triple with alpha component.
Aim: This class template may be used to (linearly) convert scalar values in a given range into a colo...
void addColor(const Color &color)
Aim: This class is used to simplify shape and surface creation. With it, you can create new shapes an...
Aim: This class is used to simplify shape and surface creation. With it, you can create new shapes an...
DGtal::GradientColorMap< double > makeColorMap(double min_value, double max_value)
[curvature-measures-Includes]
Z3i this namespace gathers the standard of types for 3D imagery.
DGtal is the top-level namespace which contains all DGtal functions and types.
Aim: Utility class to compute curvature measures induced by (1) a corrected normal current defined by...
Aim: An helper class for writing mesh file formats (Waverfront OBJ at this point) and creating a Surf...
Aim: Represents an embedded mesh as faces and a list of vertices. Vertices may be shared among faces ...