diff --git a/.gitignore b/.gitignore index 90f003227c6f..8312d98c66fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +build /*build* /*/*/*/build /*/*/*/VC* diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_conic_traits_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_conic_traits_2.h index fd0215a1a1e4..0aa3bfcc318d 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_conic_traits_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_conic_traits_2.h @@ -2496,7 +2496,7 @@ class Arr_conic_traits_2 { double min_dist = -1; Integer aux_coeffs[6]; for (int k = 1; k <= 2; ++k) { - // Get the integer coefficients of the k'th auxiliary conic curve. + // Get the integer coefficients of the k-th auxiliary conic curve. aux_rat_coeffs[0] = (k == 1) ? r_1 : r_2; aux_rat_coeffs[1] = (k == 1) ? s_1 : s_2; aux_rat_coeffs[2] = (k == 1) ? t_1 : t_2; @@ -2512,7 +2512,7 @@ class Arr_conic_traits_2 { (CGAL::sign(aux_coeffs[2]) == ZERO)) ? 1 : 2; // Compute the x- and y-coordinates of intersection points of the base - // conic and the k'th auxiliary conic. + // conic and the k-th auxiliary conic. int n_xs = compute_resultant_roots(*nt_traits, base_coeffs[0], base_coeffs[1], base_coeffs[2], diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_cache.h b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_cache.h index 15723d2fa35f..c68675731d41 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_cache.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_cache.h @@ -590,7 +590,7 @@ bool _Bezier_cache::_intersection_params // Consruct the bivariate polynomial that corresponds to Equation I. // Note that we represent a bivariate polynomial as a vector of univariate - // polynomials, whose i'th entry corresponds to the coefficient of t^i, + // polynomials, whose i-th entry corresponds to the coefficient of t^i, // which is in turn a polynomial it s. const int degX_2 = nt_traits.degree (polyX_2); std::vector coeffsX_st (degX_2 < 0 ? 1 : (degX_2 + 1)); @@ -657,7 +657,7 @@ void _Bezier_cache::_self_intersection_params // Consruct the bivariate polynomial that corresponds to Equation I. // Note that we represent a bivariate polynomial as a vector of univariate - // polynomials, whose i'th entry corresponds to the coefficient of t^i, + // polynomials, whose i-th entry corresponds to the coefficient of t^i, // which is in turn a polynomial it s. const int degX = nt_traits.degree (polyX); CGAL_assertion(degX > 0); @@ -771,7 +771,7 @@ _Bezier_cache::_compute_resultant if (nt_traits.degree (mat[i][i]) < 0) { // If the current diagonal value is a zero polynomial, try to replace - // the current i'th row with a row with a higher index k, such that + // the current i-th row with a row with a higher index k, such that // mat[k][i] is not a zero polynomial. found_row = false; @@ -786,7 +786,7 @@ _Bezier_cache::_compute_resultant if (found_row) { - // Swap the i'th and the k'th rows (note that we start from the i'th + // Swap the i-th and the k-th rows (note that we start from the i-th // column, because the first i entries in every row with index i or // higher should be zero by now). for (j = i; j < dim; j++) @@ -808,7 +808,7 @@ _Bezier_cache::_compute_resultant } } - // Zero the whole i'th column of the following rows. + // Zero the whole i-th column of the following rows. for (k = i + 1; k < dim; k++) { if (nt_traits.degree (mat[k][i]) >= 0) @@ -821,7 +821,7 @@ _Bezier_cache::_compute_resultant mat[k][j] = mat[k][j] * mat[i][i] - mat[i][j] * value; } - // We multiplied the current row by the i'th diagonal entry, thus + // We multiplied the current row by the i-th diagonal entry, thus // multiplying the determinant value by it. We therefore increment // the exponent of mat[i][i] in the normalization factor. exp_fact[i] = exp_fact[i] + 1; diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_curve_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_curve_2.h index 119ad9a5226b..07ff45e8766f 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_curve_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Bezier_curve_2.h @@ -411,7 +411,7 @@ class _Bezier_curve_2 : } /*! - * Get the i'th control point. + * Get the i-th control point. * \pre i must be between 0 and n - 1, where n is the number of control * points. */ @@ -709,7 +709,7 @@ void _Bezier_curve_2_repx(); py = pts_begin->y(); - // By simplifying (1 - t)^(n-k) we obtain that the k'th expression of + // By simplifying (1 - t)^(n-k) we obtain that the k-th expression of // the above sum is given by: // // n-k diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_arc_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_arc_2.h index 08126475b5a0..6b3d06f19e77 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_arc_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_arc_2.h @@ -572,7 +572,7 @@ class Conic_arc_2 { int k; for (k = 1; k <= 2; ++k) { - // Get the integer coefficients of the k'th auxiliary conic curve. + // Get the integer coefficients of the k-th auxiliary conic curve. aux_rat_coeffs[0] = (k == 1) ? r_1 : r_2; aux_rat_coeffs[1] = (k == 1) ? s_1 : s_2; aux_rat_coeffs[2] = (k == 1) ? t_1 : t_2; @@ -593,7 +593,7 @@ class Conic_arc_2 { } // Compute the x- and y-coordinates of intersection points of the base - // conic and the k'th auxiliary conic. + // conic and the k-th auxiliary conic. n_xs = compute_resultant_roots(nt_traits, base_coeffs[0], base_coeffs[1], base_coeffs[2], diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_x_monotone_arc_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_x_monotone_arc_2.h index 6d2bf119bb94..ee0d241dad76 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_x_monotone_arc_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/Conic_x_monotone_arc_2.h @@ -1078,7 +1078,7 @@ class Conic_x_monotone_arc_2 : public ConicArc { } public: - /*! Obtain the i'th order derivative by x of the conic at the point p=(x,y). + /*! Obtain the i-th order derivative by x of the conic at the point p=(x,y). * \param p The point where we derive. * \param i The order of the derivatives (either 1, 2 or 3). * \param slope_numer The numerator of the slope. @@ -1187,7 +1187,7 @@ class Conic_x_monotone_arc_2 : public ConicArc { CGAL_error(); } - /*! Obtain the i'th order derivative by y of the conic at the point p=(x,y). + /*! Obtain the i-th order derivative by y of the conic at the point p=(x,y). * \param p The point where we derive. * \param i The order of the derivatives (either 1, 2 or 3). * \param slope_numer The numerator of the slope. diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/de_Casteljau_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/de_Casteljau_2.h index caf0e8eacfc0..0db81ef24840 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/de_Casteljau_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_geometry_traits/de_Casteljau_2.h @@ -77,7 +77,7 @@ bisect_control_polygon_2(InputIterator ctrl_pts_begin, while (last_index > 0) { // Construct (m - 1) control points from the m point we currently have, - // where the new i'th point is the midpoint between p[i] and p[i + 1]. + // where the new i-th point is the midpoint between p[i] and p[i + 1]. for (i = 0; i < last_index; ++i) vec[i] = midpoint(vec[i], vec[i + 1]); @@ -134,7 +134,7 @@ typename InputIterator::value_type point_on_Bezier_curve_2 while (last_index > 0) { // Construct (m - 1) control points from the m point we currently have, - // where the new i'th point is given by: (1 - t0)*p[i] + t0*p[i + 1]. + // where the new i-th point is given by: (1 - t0)*p[i] + t0*p[i + 1]. for (i = 0; i < last_index; ++i) { vec[i] = _Point_2(comp_t0*vec[i].x() + t0*vec[i + 1].x(), @@ -197,7 +197,7 @@ typename InputIterator::value_type de_Casteljau_2 while (last_index > 0) { // Construct (m - 1) control points from the m point we currently have, - // where the new i'th point is given by: (1 - t0)*p[i] + t0*p[i + 1]. + // where the new i-th point is given by: (1 - t0)*p[i] + t0*p[i + 1]. for (i = 0; i < last_index; ++i) { vec[i] = _Point_2(comp_t0*vec[i].x() + t0*vec[i + 1].x(), diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h index c216a07bc9ca..46769a595440 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_basic_traits_2.h @@ -2704,7 +2704,7 @@ class Arr_polycurve_basic_traits_2 { if ((!is_vert(cv[0]) && (comp_x(get_min_v(cv[i]), q) == EQUAL)) || (is_vert(cv[0]) && equal(get_min_v(cv[i]), q))){ - // q is the left endpoint of the i'th subcurve: + // q is the left endpoint of the i-th subcurve: if (to_right) return i; else { // to_left @@ -2722,7 +2722,7 @@ class Arr_polycurve_basic_traits_2 { if ((!is_vert(cv[0]) && (comp_x(get_max_v(cv[i]), q) == EQUAL)) || (is_vert(cv[0]) && equal(get_max_v(cv[i]), q))) { - // q is the right endpoint of the i'th subcurve: + // q is the right endpoint of the i-th subcurve: if (!to_right) return i; else { if (direction == SMALLER) { diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_traits_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_traits_2.h index 488ed21dea79..272d1fc235b3 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_traits_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_polycurve_traits_2.h @@ -605,15 +605,15 @@ class Arr_polycurve_traits_2 : if (dir == SMALLER){ // Check whether the split point is xcv[i]'s source or target. if (equal(max_vertex(xcv[i]), p)) { - // The entire i'th subcurve belongs to xcv1: + // The entire i-th subcurve belongs to xcv1: xcv1.push_back(xcv[i]); } else if (equal(min_vertex(xcv[i]), p)) { - // The entire i'th subcurves belongs to xcv2: + // The entire i-th subcurves belongs to xcv2: xcv2.push_back(xcv[i]); } else { - // The i'th subcurve should be split: The left part(seg1) + // The i-th subcurve should be split: The left part(seg1) // goes to xcv1, and the right part(seg2) goes to xcv2. X_monotone_subcurve_2 seg1, seg2; m_poly_traits.subcurve_traits_2()->split_2_object()(xcv[i], p, diff --git a/Arrangement_on_surface_2/include/CGAL/CORE_algebraic_number_traits.h b/Arrangement_on_surface_2/include/CGAL/CORE_algebraic_number_traits.h index c3e941fdab8e..7f6b84ff782b 100644 --- a/Arrangement_on_surface_2/include/CGAL/CORE_algebraic_number_traits.h +++ b/Arrangement_on_surface_2/include/CGAL/CORE_algebraic_number_traits.h @@ -541,7 +541,7 @@ class CORE_algebraic_number_traits for (i = 1; i <= n_roots; i++) { - // Get the i'th real-valued root. + // Get the i-th real-valued root. *oi = rootOf(poly, i); ++oi; } @@ -603,7 +603,7 @@ class CORE_algebraic_number_traits for (i = 0; i < root_intervals.size(); i++) { - // Get the i'th real-valued root. + // Get the i-th real-valued root. *oi = rootOf(poly, root_intervals[i]); ++oi; } diff --git a/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_construction_event_base.h b/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_construction_event_base.h index 89e916f59a18..25d2c172ced7 100644 --- a/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_construction_event_base.h +++ b/Arrangement_on_surface_2/include/CGAL/Surface_sweep_2/Arr_construction_event_base.h @@ -197,11 +197,11 @@ class Arr_construction_event_base : void init_subcurve_in_arrangement_flags(size_t n) { m_isCurveInArr.resize(n, false); } - /*! Check if the i'th subcurve is in the arrangement. */ + /*! Check if the i-th subcurve is in the arrangement. */ bool is_subcurve_in_arrangement(unsigned int i) const { return (m_isCurveInArr[i]); } - /*! Set the flag indicating whether the i'th subcurve is in the arrangement. + /*! Set the flag indicating whether the i-th subcurve is in the arrangement. */ void set_subcurve_in_arrangement(unsigned int i, bool flag) { m_isCurveInArr[i] = flag; } diff --git a/BGL/doc/BGL/BGL.txt b/BGL/doc/BGL/BGL.txt index cf7e8a739031..cc92527243a6 100644 --- a/BGL/doc/BGL/BGL.txt +++ b/BGL/doc/BGL/BGL.txt @@ -585,7 +585,7 @@ so that they virtually become border edges when exploring a seam mesh w The input mesh is referred to as underlying mesh of the seam mesh. We denote `tm` and `sm` the underlying mesh and the seam mesh respectively. -Figure \cgalFigureRef{fig_Seam_mesh_1} shows an example of mesh on which two +\cgalFigureRef{fig_Seam_mesh_1} shows an example of mesh on which two edges, defined by the halfedge pairs `h2-h3` and `h6-h7`, are marked as seams. The introduction of virtual borders modifies the elementary \bgl graph traversal operations: when we circulate around the target of `h7` in the underlying mesh, @@ -599,7 +599,7 @@ A seam mesh with two seam edges `(h2, h3)` and `(h6, h7)`. \cgalFigureEnd A vertex of the underlying mesh may correspond to multiple vertices in the seam mesh. -For example in Figure \cgalFigureRef{fig_Seam_mesh_1}, the target of `h7` corresponds to two +For example in \cgalFigureRef{fig_Seam_mesh_1}, the target of `h7` corresponds to two vertices in the seam mesh, on either side of the virtual border created by the seam edges. For this reason, a vertex `v` of the seam mesh is internally represented as a halfedge `h` of the seam mesh. To obtain a canonical definition, the halfedge `h` is defined as the halfedge @@ -611,7 +611,7 @@ For vertices `v` in the underlying mesh that are not on a seam edge, we choose \subsubsection BGLSeamMeshTraversal Seam Mesh Traversal Using the function `next(halfedge_descriptor, FaceGraph)`, we can walk around a face but also around -a border of a mesh. For the seam mesh `sm` from Figure \cgalFigureRef{fig_Seam_mesh_1}, +a border of a mesh. For the seam mesh `sm` from \cgalFigureRef{fig_Seam_mesh_1}, we have `opposite(h2, sm) == h3*`, and it holds that `face(h3*, sm) == null_face()`. We can walk along this virtual border: starting at `h3*` and repeatedly calling `next(..,sm)`, we will traverse `h6*`, `h7*`, `h2*`, before reaching `h3*` again. diff --git a/CGAL_Core/include/CGAL/CORE/poly/Poly.tcc b/CGAL_Core/include/CGAL/CORE/poly/Poly.tcc index 8e95f92189c4..75884c7d5f9b 100644 --- a/CGAL_Core/include/CGAL/CORE/poly/Poly.tcc +++ b/CGAL_Core/include/CGAL/CORE/poly/Poly.tcc @@ -382,7 +382,7 @@ int Polynomial::getTrueDegree() const { return -1; // Zero polynomial } -//get i'th Coeff. We check whether i is not greater than the +//get i-th Coeff. We check whether i is not greater than the // true degree and if not then return coeff[i] o/w 0 template NT Polynomial::getCoeffi(int i) const { diff --git a/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h b/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h index 0492e2acc6af..e02f4f91eb56 100644 --- a/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h +++ b/CGAL_ImageIO/include/CGAL/IO/read_vtk_image_data.h @@ -88,11 +88,10 @@ read_vtk_image_data(vtkImageData* vtk_image, Image_3::Own owning = Image_3::OWN_ // If there is more than a scalar per point, vtk_image->data is not immediately // interpretable in Image_3->data CGAL_assertion(owning == Image_3::OWN_THE_DATA || cn == 1); - - CGAL_assertion(vtk_image->GetPointData()->GetScalars()->GetNumberOfTuples() == dims[0]*dims[1]*dims[2]); + CGAL_assertion(vtk_image->GetPointData()->GetScalars()->GetNumberOfTuples() == static_cast(image->xdim*image->ydim*image->zdim)); if(owning == Image_3::OWN_THE_DATA) { - int dims_n = dims[0]*dims[1]*dims[2]; + std::size_t dims_n = image->xdim*image->ydim*image->zdim; image->data = ::ImageIO_alloc(dims_n * image->wdim); // std::cerr << "GetNumberOfTuples() = " << vtk_image->GetPointData()->GetScalars()->GetNumberOfTuples() << "\n" @@ -110,7 +109,7 @@ read_vtk_image_data(vtkImageData* vtk_image, Image_3::Own owning = Image_3::OWN_ char* src = static_cast(vtk_image->GetPointData()->GetScalars()->GetVoidPointer(0)); char* dest = static_cast(image->data); - for(int i=0; iwdim because we casted to char* and not the actual data type memcpy(dest + image->wdim*i, src + cn*image->wdim*i, image->wdim); diff --git a/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--XCriticalPoints.h b/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--XCriticalPoints.h index b0dd39cd6915..07be44cd4fe3 100644 --- a/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--XCriticalPoints.h +++ b/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--XCriticalPoints.h @@ -25,7 +25,7 @@ operator()(const AlgebraicKernelForSpheres::Polynomial_for_spheres_2_3 &p, OutputIterator res); /*! -Computes the `i`th `x`-critical point of polynomial `p`. +Computes the `i`-th `x`-critical point of polynomial `p`. */ template < class OutputIterator > AlgebraicKernelForSpheres::Root_for_spheres_2_3 diff --git a/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--YCriticalPoints.h b/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--YCriticalPoints.h index 9fd4f5445df8..cd7569ddb0f3 100644 --- a/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--YCriticalPoints.h +++ b/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--YCriticalPoints.h @@ -25,7 +25,7 @@ operator()(const AlgebraicKernelForSpheres::Polynomial_for_spheres_2_3 &p, OutputIterator res); /*! -Computes the `i`th `y`-critical point of polynomial `p`. +Computes the `i`-th `y`-critical point of polynomial `p`. */ template < class OutputIterator > AlgebraicKernelForSpheres::Root_for_spheres_2_3 diff --git a/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--ZCriticalPoints.h b/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--ZCriticalPoints.h index b589dc78525f..99baff709ad9 100644 --- a/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--ZCriticalPoints.h +++ b/Circular_kernel_3/doc/Circular_kernel_3/Concepts/AlgebraicKernelForSpheres--ZCriticalPoints.h @@ -25,7 +25,7 @@ operator()(const AlgebraicKernelForSpheres::Polynomial_for_spheres_2_3 &p, OutputIterator res); /*! -Computes the `i`th `z`-critical point of polynomial `p`. +Computes the `i`-th `z`-critical point of polynomial `p`. */ template < class OutputIterator > AlgebraicKernelForSpheres::Root_for_spheres_2_3 diff --git a/Classification/doc/Classification/Classification.txt b/Classification/doc/Classification/Classification.txt index 48c7531b362b..2c5a4e3b2d1d 100644 --- a/Classification/doc/Classification/Classification.txt +++ b/Classification/doc/Classification/Classification.txt @@ -10,7 +10,7 @@ This component implements the algorithm described in \cgalCite{cgal:lm-clscm-12} \section Classification_Organization Package Organization -%Classification of data sets is achieved as follows (see Figure \cgalFigureRef{Classification_organization_fig}): +%Classification of data sets is achieved as follows (see \cgalFigureRef{Classification_organization_fig}): - some analysis is performed on the input data set; - features are computed based on this analysis; @@ -314,7 +314,7 @@ Though it is possible to set them up one by one, \cgal also provides a method [t - the same mechanism is repeated until all features' ranges have been tested. Weights are only changed one by one, the other ones are kept to the values that gave the latest best score. -This usually converges to a satisfying solution (see Figure \cgalFigureRef{Classification_trainer_fig}). The number of trials is user defined, set to 300 by default. Using at least 10 times the number of features is advised (for example, at least 300 iterations if 30 features are used). If the solution is not satisfying, more inliers can be selected, for example, in a region that the user identifies as misclassified with the current configuration. The training algorithm keeps, as initialization, the best weights found at the previous round and carries on trying new weights by taking new inliers into account. +This usually converges to a satisfying solution (see \cgalFigureRef{Classification_trainer_fig}). The number of trials is user defined, set to 300 by default. Using at least 10 times the number of features is advised (for example, at least 300 iterations if 30 features are used). If the solution is not satisfying, more inliers can be selected, for example, in a region that the user identifies as misclassified with the current configuration. The training algorithm keeps, as initialization, the best weights found at the previous round and carries on trying new weights by taking new inliers into account. \cgalFigureBegin{Classification_trainer_fig,classif_training.png} Example of evolution of the mean intersection-over-union. The purple curve is the score computed at the current iteration, green curve is the best score found so far. @@ -322,7 +322,7 @@ Example of evolution of the mean intersection-over-union. The purple curve is th \subsubsection Classification_sowf_result Result -Figure \cgalFigureRef{Classification_sowf_result_fig} shows an example of output on a defect-laden point set. The accuracy on this example is 0.97 with a mean intersection-over-union of 0.85 (see section \ref Classification_evaluation). +\cgalFigureRef{Classification_sowf_result_fig} shows an example of output on a defect-laden point set. The accuracy on this example is 0.97 with a mean intersection-over-union of 0.85 (see section \ref Classification_evaluation). \cgalFigureBegin{Classification_sowf_result_fig,noise_outliers.png} Example of classification on a point set with medium noise and outliers (left: input, right: output). _Ground_ is orange, _roofs_ are pink, _vegetation_ is green. Outliers are classified with an additional label _outlier_ in black. @@ -398,7 +398,7 @@ smoothing by providing a model of `CGAL::Classification::NeighborQuery`. - `CGAL::Classification::classify_with_graphcut()`: this method offers the best quality but requires longer computation time (see -Figure \cgalFigureRef{Classification_image}, bottom-right). The +\cgalFigureRef{Classification_image}, bottom-right). The total energy that is minimized is the sum of the partial data term \f$E_{di}(x_i)\f$ and of a pairwise interaction energy defined by the standard Potts model \cgalCite{cgal:l-mrfmi-09} : diff --git a/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_utility.h b/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_utility.h index b7ca3d12b42e..7891d9280da9 100644 --- a/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_utility.h +++ b/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_utility.h @@ -159,7 +159,7 @@ namespace CGAL }; //count the number of time a given type have been found - //within a tuple, until reaching position the k'th type of the tuple. + //within a tuple, until reaching position the k-th type of the tuple. //dim is the total size of the tuple template ::value-1> @@ -191,7 +191,7 @@ namespace CGAL }; //count the number of time a type different from Type have been found - //within a tuple, until reaching position the k'th type of the tuple. + //within a tuple, until reaching position the k-th type of the tuple. //dim is the total size of the tuple template ::value-1> @@ -339,7 +339,7 @@ namespace CGAL }; //Same as Foreach_static excepted that Functor - //is called for case k only if the k'th type in the tuple + //is called for case k only if the k-th type in the tuple //is different from Void. Note that to the converse of Foreach_static //Functor are called from n =0 to k template @@ -365,7 +365,7 @@ namespace CGAL }; //Same as Foreach_static_restricted excepted that Functor - //is called for case k only if the k'th type in the tuple + //is called for case k only if the k-th type in the tuple //is different from Void and k!=j. template struct Foreach_static_restricted_except; diff --git a/Data/data/images/torus_gray_image.vti b/Data/data/images/torus_gray_image.vti new file mode 100644 index 000000000000..7a165899fcad --- /dev/null +++ b/Data/data/images/torus_gray_image.vti @@ -0,0 +1,15 @@ + + + + + + + + + + + + + _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8ODChxMvbvx00EMXfXTS+7/xr95D2kfZS6in1ZTqKrWF/CrHiLNijfVmzvnc+S6cV7POT5pcM9etZ3P4L981ycZsa5TN/7JG8w6f1ssm71w3a3Nr7azHIbWyi7r9HPsc+lOMnrMtXd177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530FvXbR9tLipP2FOpqtaX6So0hz8o14q2YY92Zez5/DOL23wa2yFbWr47LrZpln22pGe+zGuyQxvfqlbWzRvN+jqva/RDPnrEl/tZ4c/Qc/VV8ud+maDTmy3R177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvU7yzBflqMsK+SO9TXakx1llpDvpVzxF2xp81/87ZxwLJgXfMUw6zfGwZW10L31s8+vLhOGucPdv4+Ov3q65g67PPY8/UN0fXddbHsww/js7J/xcE3fJCu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnqL+q0HZwr21faW9lfipzpbraneUnPIu3KP+CsGWYfmovHAZL02rL1jWs8rv6iVGcdTHvo6Wtb9d9LW85g1cdm1y+O459+LMQcsjrVbFkWt4xelq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peon41onMV68P+2h7TPsteQ72t5lR3qT3kXzlIHBaLrEdz0rhge659vbS+b3nquzS3l967Pm54pCqmly1JWq/t8nr0nzQjPu01PdYvnRqDNr+aru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056i/qdqzlbcr7ijMF6sde03xJLxFW1p/pLDSIPy0XisZhkXZqbxgfjxTO2pDm8sWxtGk/jO3zz7HjglWkx4OEpUbn8mTjw7bFxyfNjYvHoJ9LVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Rv7NF+dEZk7rJWYP9tvVj32Xvof5Wg6rD1CLysZwkLotN1qc5apywWs/jZyyJM26cH0+cXRk7j5mcNHb43cj4v9zQqI7K0a3fwJjfbkC6uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oukt6ne+6ozROVvKl4NqpTMH+257T+vJHkQdLt6qx9Qk8rLcJD6LUSluV89V44X5lY1v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15NjXEyOI++bFNvTpOj188R4d/PE7Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikN6/f3kH9rIZUR6kl5FM5RVwVW6wvc8znzFf9PSfHE9unZHH7hcG59Wr97pybm9sHVLKd3HlqfLb3lGi/dVIctH5iPPLYhFj+/Ph48edxcesx4+K46uPiL1vGZlf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvXn98qM9hDpaLameUlPIq3KL+CrGWGfmms+bT/FZvJ7+XC6GFY+qiBbn5Mb7pXZTsvEctv/E2LxwfKZx1OIx8cWk0fHcyEOi3fxRcXrtUfHTxyOzq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5PevH57SPsoewn1tJpSXaW2kF/lGHFWrLHezDmfO98Hvphbny+0r8jmqvVsDm+aMTE6nDo+Rh4zNtN04WGjou+FB8f93wyPr248KPosGxZX1h4WE94Zml3de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Ob120fbS4qT9hTqarWl+kqNIc/KNeKtmGPdmXs+fwzi9te7psZhZ06JhxdPilNmTMjG79UzR2fj+80hI6LmPyrr4quGxNRrB0XZjojHn02xo3qKC54rz67uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9OevP6nSXYT4sR9lVyh/pajanOUmvIt3KOuCv2WH/moHHAMqrSrxhm/f64a0z03Dkqjnl3RDaufzp7cNxzdsQRawfG3lN+F30P7x9bPy2N//xSEovvK8mu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnrz+q0HZwr21faW9lfipzpbraneUnPIu3KP+CsGWYfmovHAZL1efPqYbD0fdvTwbBw7PRnR8IcBmbbfNSiJkqK+0Xp9r5i4Z8/497bucc7l3bOre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk968fjWicxXrw/7aHtM+y15Dva3mVHepPeRfOUgcFousR3PSuGDrtWpkto6fGjY4m9tflx0QA58qjo/n9M60Ptamawwb2Sl+7N4hTt/ULkbMbJdd3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUf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14g56jB7V/s3exjxWa5Sz6lp5HVz2HoW46xzc9140zxo2y45f4eOxdW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0W/76Cdkzg3832c76TUTXKH83ln1M5pnVWqt51ZObeRf+3f1ST2cWKTel5NK26rbeR3OU6cN7etd3PeuNN++YyfFlf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvT+M/w/r/4f4/0P+r/b6r9rr/2rf/1X7/r/az3+q/fyv2s9/q/38v9q//6n27/+q/fvfav/+v9p//1Htv/+p9t9/Vfvv/6r995/V/vvfav/9d7X//r/a//6j2v/+p9r//qva//6v2v/+s9r//rea//0fdgChuXic7dv5o9VV9f9xTXMeSnNMKzWHHMr6qIWarz7OllFqqGkfJSXTnKicp5xQS9IQJxQEARllEhBRkHm6TPde7sQMd8CpUtNURPN7Hu/T+f4TR355836/917r+Tp777XW3ufczTb7/N+7M0/OD14/J0N/ekkmf3Btdnvttpzc697cf+5f0m3Ow9ly3aMZOKxPeu73TA786cCsPWJIjp07PHP2HJXdDhmb+R+8kDvun5j6VZPyy42T02/tK9n1kanZc9dpebPb9Dz75xk5+J6Z+f45s3L2pll56bbZuapldg7aeU7O229OcXXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9Hv/9csuCQvvtE9z5x5R96+pUfGXPdgnt6tVxovfjxPdnk6R2/sn11/OjhjzxmWvjs/n5vvGZOPx72QVf0m5rUzXsprL72cYf+YkrfffDU3jZ+et86YmZaxszL2zdl5eeOcfGnd3DzWf17e6TQ/d4+dn8u2XJArjlmQrU4rX9177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvT7PDwbMOuO3Pl2j9R9vWdmv98r3bs/kW0f65vV3Z7N2a3P5fWdR6TTO6Py7x7jsnbZhGIcxg96OTcfNjX73jitGK9PfzOrGM9N98/NKcvm5fn35mfMWwvSZ2pN7rhyYb794cIcecmiTBm1KBNXLMqgN8pX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70V/eaEz8W7Oef3zA7dHsnFuz6ZDZf3S+tl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yM9ylDgtVlmv5qxxw95r6Ac5fOj7xTj36jYpe42dkGfXv5kdpryeQZe/lr6d44ure8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96KfvFQjahedubo3M2ccf4in6qv7EXtx+xJrDe1qTisRpGn5SrxWsyybs1d40fDdj2nFOP7szPeKrResvereeDOV3J53Uv58TkvFVf3nnuvnfb66c8Oe+yyzw9//PKPo6ixS1z4cOLFjZ8Oeuiij056K/p91+C83ZmzOFnU+efVF2dQzmHMpaK+7yrvy4q9Sak+t/7UaWoV+VrOErfFLuvXHDaOtEy/6c1Mu29cbt32lbzZ7cV0q3su42ufzfeOfba4uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oumt6Lc/Uhs4d3f2rG5yBil+OotyHuNMwr7cHLM/s0dRp6tVrUexSd6Wu8RvMcw6NpeNJ03G+fC7RmX7qSOztPWJ9L98RA69+7Hi6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ot93Tr538d2D/bOawTmss0jnceprcdXZhP25nCP/mnvqdTWrus36lL/lMHFcLLOezWnjSltzv6cLzZ+aOizL/zY0WwYMKa7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeiv6nZP47kl+FCOcw9tLOo9VSziXU1+pt51RiLf2qvZr8rG63ZxUv6lhrFe5TDwX06xrc9v40tj3mYfzxxGD88/T783LY+8uru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056K/rtDX3/pkb0PYzvItTP8qd9lVjifE6N4ZxG7Wm/bs8qDtu7yM9qWHWcuSqfW7/iuthmfZvjxpnWm7e9K80dt+fnt/+luLr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrp/e/5V4/yfs/3cPaM9s++k3Au72xaXW2PIb86q3JeI+aow+xd7d/sYcRnuUo9p6aR181h61mMs87NdeNN872n35QNI68pru4991477fXTnx322GWfH/745R9HUYOVuPDhxIsbPx300EUfnfRW9PsO2jmJczPfx/lOSt0kdzifd0btnNZZpXrbmZVzG/nX/l1NYh8nNqnn1bTittpGfpfjxHlz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0UcnvRX9vnPyvYvvHuyf1QzOYZ1FOo9TX4urzibsz+Uc+dfcU6+rWdVt1qf8LYeJ42KZ9WxOG1fa7rrj5ULz4596JjdMfTIPDn+iuLr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpreh3TuK7J/lRjHAOby/pPFYt4VxOfaXedkYh3tqr2q/Jx+p2c1L9poaxXuUy8VxMs67NbeNLY93ap7L7iuHJ3cPyk7aHi6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3op+e0Pfv6kRfQ/juwj1s/xpXyWWOJ9TYzinUXvar9uzisP2LvKzGlYdZ67K59avuC62Wd/muHGmdb/jh+aurz2QT7x7b3F177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100vvf869+5f2e7+HsGe2ffSfhXN7ZtLraHkN+dVblvEbMUYfZu9q/2cOIz3KVek5NI6+bw9azGGedm+vGm+Yj7x6Uh3reXlzde+69dtrrpz877LHLPj/88cs/jqIGK3Hhw4kXN3466KGLPjrprej3HbRzEudmvo/znZS6Se5wPu+M2jmts0r1tjMr5zbyr/27msQ+TmxSz6tpxW21jfwux4nz5rb1bs4bd9q/Meba4urec++1014//dlhj132+eGPX/5x4MGFr+D8TvlsHT8d9NBFH530fjz+H6//j+P/x/m/2uu/aq//q33/V+37/2o//6n2879qP/+t9vP/av/+p9q//6v273+r/fv/av/9R7X//qfaf/9V7b//q/bff1b773+r/fff1f77/2r/+49q//ufav/7r2r/+79q//vPav/732r+9/8BtmHeGHic7duJv53j1Tfwc87eZ9po6dPHVBJzhUoVj0a1WZfUPEQoYigab9CoVlFaszRFJASJIIakqqZEESI8CCJERCSmEBEic8gkORmP5OTd3/u238/7P+zk8/G53fe91m/9fvu6rrXWde19amo2/dvu9Dvj5cX3xV/aPxQbWh6PvS98OubdPDp+fPzLUf/K2Hh42vjo8uA7sc/278X4wz6K6/eYFp+98lmcu9XMGL3T7Oi1bG60u35BXDL1q2hdvig6T1sSo/oti//dbHn0P3NFHPL3lphw1cqYdsyqWNGyKk67bHVs/v7qGN+4JtZutya7uvfce3bs+fGH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L9q747YS77/s0h21G4pxfol712OlNX5HMc4oadh8eTH2uDRtXbvvuX76s2PPD3/88g8HHlz4eOCDF3544os3/nTQQxd9dNJb1q9uVjuqn9QQ8qhcIp6KKdaVueX9wtjtgXyaq8Tz7xdj6cvFuHbXdB0br0NKPGeMSseXlhF3FWP3acU4a99ijBpcjAX/KkTv10rj9kwheixNW9fue66f/uzY88PfIV/NJzjw4MLHAx+88MMT3yROzUnzAj100UcnvWX99g7qZzWkOkotIZ/KKUlcPTRd7+aY9wzrpZ2LMf2KNG5vHZyuV+t3Zm06t+eUuI27oRhXHFKa4z1LWpaU1sgFhfhNthBbexXiszX5+PayfExdmrau3fdcP/3ZseeHP375n/lVfIALHw988MIPT3zxxp8Oeuiij056y/rlR3sIdbRaUj2lppBX5RbxVYyxzsw17xum+Cxen5JNY9jgYd2x/L103LYeno7zCbMLMaO+kGgceVY+rhiRjy1v5OLoe3Jx98xcHHRZ2rp233P99GfHnh/++OUfDjy48PHABy/88MQXb/zpoIcu+uikt6zfHtI+yl5CPa2mVFepLeRXOUacFWusN3POe4c9LJeuz2IpZ5mr1rM53HNBIY4eW4jVd6SaG2/PxS11pZr2j9nodXY2Jrdk46E+2bhjayZpXbvvuX76s2PPD3/88g8HHlz4eOCDF3544os3/nTQQxd9dNL7/wcApX20vaQ4aU+hrlZbqq/UGPKsXCPeijnWnbnn/eMgbj/zbAn7tGJk/rsQ609Ox/upcflkfK96Mxu7dWfjmAcycWdjJm59pityF3XFzINKMTWbtq7d91w//dmx54c/fvmHAw8ufDzwwQs/PPHFG3866KGLPjrpLct3lmA/LUbYV8kd6ms1pjpLrSHfyjnirthz4Vd52zjgMrKEK4ZZv8uuKtVCz+biH1vTcX3v9NIed2FXrN1U2uef0BmT3+qIA37UEQcdkP7+Revafc/1058de37445d/OPDgwscDH7zwwxNfvPGngx666KOT3rJ+68GZgn21vaX9lfipzlZrqrfUHPKu3CP+ikHWobloPHCyXs86NZ+s5/l3pOP9jUe7YvafUs1DS74HH9Ue/Za0xZqD2qL3DqW9/EetSevafc/1058de37445d/OPDgwscDH7zwwxNfvPGngx666KOT3rJ+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7VnFYXsX+VkNq44zV+Xzav2WcV1ss77NceNM6+qFW+ak4zfPj/s3q67uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oelv61UP2R76Hs2e0f/adhHN5Z9PqansM+dVZlfMaMUcdZu9q/2YPIz7LVeo5NY28bg5bz2KcdW6uG2+aP7TLyKz3542qq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pe/63/N6rPeZyb+T7Od1LqJrnD+bwzaue0zirV286snNvIv/bvahL7OLFJPa+mFbfVNvK7HCfOm9vWuzlv3Gm/dd6I6urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uik9+3xf3v9vx3/387/7V7/tXv93+77v3bf/7f7+U+7n/+1+/lvu5//t/v3P+3+/V+7f//b7t//t/vvP9r99z/t/vuvdv/9X7v//rPdf//b7r//bvff/7f733+0+9//tPvff7X73/+1+99/tvvf/779r73/LR6/ce577j2ZeuYWOebFrXLGjO0yf3B03jV2TL7w0M65bf6u2fKXu+XMd03MvjtMzo9GTs1jD03LbqfOyD0bdWTCMbOy5KedmfKX2TnqhTn5+l/n5lc3deXDi7vTPbEn567syX5dvTlweW8Of7E3O83uy6uf7st+i/py5On11b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvS7//H3PpFpk5c+s8fcZ26b12dEb9cEzOOWVsrtxtXM6+cHwOeGFi9txhSr6907Scs256Fl9f5sO9ZpX7t878ZNSczN17bpYd0ZXew7tz8od7csOo3sy4t1z7h/flwhf6cvf8/nzttv6sfO9A1t9rIC+dMJBXzhnI+8+vr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056W/p9Hp6t+O/tctqzo7Nig50y7vmxWXvluMzLhHRcOil/f2JKrl83LY8+OyM33tCRHx1Uj/d35s3N4q+WtcA13dV43XJpbzWeq3r6M//R/pxfxvBv/X0gZ+8zmCUrBvPJf5Q5fPxQvrfnUHY8dCj/dVh9de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQ1el77/rz6Cl35zwuXi32y475c4Ju+TYf43LnMsnZPakyfnOoqm56tLp+fGKmdnkjFnZfmh29nlgTvqaXfnEuWUNtLrcZ/1Pb35xb1++uaLWvN8Og/nYtYN559yh3HLxUH6zfnLAR5ODzk6uuSlZ77HkrheTT66tr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056W/qtC3PD56PN10btlnc+OiGN4ydnweNT88DYGbm4uyPFhM5s/1w55l+fm302785vj6jH+9bb+qo5+7vLBnLwgnp81/5iKPP3qDUdOHY49x03nN2vL/eqzwxn2daNjJrVyLpGI0s/UF/de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Lb0iw3Whznic9L2T2dPTseW07LjYTPy5NKOPPHNziw8Zk5GjavnuvUoZt19Rn+1fj940mA1h43bqouSgenD2e7i4bxn47LGP7D87wdljf+HRjbZuJlvl/u5VxrN/Owj5X523/rq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLelX3wUI6wTc8XnpY+4uu/qjrz7kc58+4451XoTj8Vnsennuw7kP2fX4331G0N5/fpyzu4/nPX/Nlyxv3ZruV/drpmPHNjMy+X+buTqZl54opkH1yuy/L1FNtixyDE711f3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvS39coQ4KVZYL+aMz03fff/RWeWcDe/qyjeP78m1b/TmnQf0V3Hbutz6yqGcdUZyx7x6bm9xYiMn/KNRjetllzTzk6eblcYb9yiy7+FFbj2tyMeXFhnzvSJ/vKTIFpfXV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Lf3ypFwhXooZ1o254/NjY8EfunLqd3uq/PzU9/uruLz+5kPZ4r117LJejc+iZxr51SH1+BnPP32kyPglRS6+uMjCVUXOfbDIlY8VyVNFFq8psvs/ikx8ob6699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466W3pVyvIl3KGuCl2WD/mkM+RrcXvr2OcOGz97f2J5PDhen0v+HGjWr/r39DMDtuU8/igIjd/s9bw9d/X2i54tciodUX+Vv73wzeK3L+2yB6vleP7an1177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100tvSr15SM8ibcof4KYZYR+aSz5PN/5g6WOXrHy9JFZ/FbXPy/CXNPPpSsxonc3jE7UVW/bXI/FeK3FlqWfF6qe3FIns+WeRHjxT51a/LcS3b/OXmIitvrK/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oelv61YzqJrVDld/LHCKOiiXWkznlc2Vbfj7vrOE0P1PHtjtX1Ot74sIiP76hyLrHy3j2aj2Oq18uckvJ/cI9Rfb6UZHvlOwHnliu8UOKfKPUMOZ99fm3q3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peln51s9pR/aSGkEflEvFUTLGuzC2fLx+dRzWqXFXF894iT36jyLX31uvYeB1XjteKx+txpaX4tyKz5xRZukGR9z/czC+va2bGt5p584xmJpxWX9177r122uunPzvsHffWfOKHP375x4EHFz6ceHHjp4Oeqj4o9dFJb0u/vYP6WQ2pjlJLyKdyirgqtlhf5pjPma+X7m7me/PquL3rI/V6tX6vebqey6tKtg/tU3JvWuRnt5Xjs6hcI93N/P71RsbdVdY7FzXy1a82cs5p9dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0u//GgPoY5WS6qn1BTyqtwivoox1pm55vPmU3wWr898vY5hQ38o8url9biN27KoxvO0vZu5/Nla4/tmNrLi0eHs+t3hnHj4cH6953CObtRX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70t/faQ9lH2EuppNaW6Sm0hv8ox4qxYY72Zcz53vhtv1Ovz0AfruWo9m8OT5jfzxScaGXForXmfTw/npmfKWvj0ZOqs5OQXh/L/fjWU22+ur+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056W/rto+0lxUl7CnW12lJ9pcaQZ+Ua8VbMse7MPZ8/BnH72TOL3DWxyEE/bOY9uzWr8fvrk/X4XvXdZPZ6ySlHDeXO5wez6ozBHNw3mB9uUsbU1weqq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peln5nCfbTYoR9ldyhvlZjqrPUGvKtnCPuij0XvJW3jQOW95V+xTDr97ndy1rozOFsvKoe500nl3vcwwaz0cqBvLzTQE7+Xn8+3+zP0Rv156GX+qqre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96WfuvBmYJ9tb2l/ZX4qc5Wa6q31Bzyrtwj/opB1qG5aDwwWa9LJzSq9XzXIfV4f3HhYH7y5VpzSttDW/elZ1FvNhzZmxm39+QbV/VUV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Lf1qROcq1of9tT1mtf/ev67n1ZzqLrWH/CsHicNikfVoThoXbG92DVfr+E+jh6q5/fmH+jO8si+HXdZbaT34ge6sv2N3rl7alc0aXVm9W31177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100tvS71zN2ZLzFWcM1ou9pv2WWCKuqj3VX2oQeVguEo/FJOvS3DQ+GIf+Vc/1oz7XX42n8b39S90Zd2BXfvDJudnj1DnZ9vHZGVo0O0d8qr6699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+OuihqzqbKXXS29LvbFF+rM6YyrrJWYP9tvVj32Xvof5Wg6rD1CLysZwkLotN1qc5apywWs/zbu7NG3N6Mv6ariztm1tpHH1VZ95x9azMeq4jb57Qka9+uL6699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466W3pd77qjNE5m3zpvMWZg323vaf1ZA+iDhdv1WNqEnlZbhKfxSjr1Fw1XpjPHNFTzW3j+vH7O/NM/6y8+uDM/OXiGdn/lulZMWt6xr6zvrr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGr0lfqpLelX33snNXccN7mzEn+VEvYf9uD2odZX+pxMUddpjaRn+UocVqssl7NWeOG/dzGnFzQ6KzGecuFM7Lt/dPyx8Om5qxPTsm9V07O9OMmV1f3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvS394qEaUb3szNG5mznj/EU+VV/Zi9qP2ZNYb2pTcViNIk/LVeK1mGXdmrvGj4avrJtZje8Ny6dWWm9cf1IeXDkhw0+PzzXfH19d3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfS29PuuwXm7M2dxUv3s/M0ZlHMYc6mq71+s92X2Jupz60+dplaRr+UscVvssn7NYeNIy1FzpmbNTZOyx/ET8tmFu2Xk07vm357aJVecs0t1de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNLb0m9/pDZw7u7sWd3kDFL8dBblPMaZhH25OWZ/Zo+iTlerWo9ik7wtd4nfYph1bC4bT5qM8/L+cdnk4bE54bmdct2VY7LjwJjq6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3pd93Tr538d2D/bOawTmss0jnceprcdXZhP25nCP/mnvqdTWrus36lL/lMHFcLLOezWnjSts7dhhbad7k4dF5tW9UNrh0++rq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLel3zmJ757kRzHCOby9pPNYtYRzOfWVetsZRbUfK/eq9mvysbrdnFS/qWGsV7lMPBfTrGtz2/jSeNUvR+V9d2ybhy7YOofev1V1de95/dmMrdrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Lb02xv6/k2N6HsY30Won+VP+yqxxPmcGsM5jdrTft2eVRy2d5Gf1bDqOHNVPrd+xXWxzfqu5/joSuvuL2+Zd7y4eXp+ull1de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNL7v/FvUb3f8z2cPaP9s+8knMs7m1ZX22PIr86qnNeIOeowe1f7N3sY8VmuUs+paeR1c9h6FuOsc3PdeNP8wAUj86WDNq6u7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnpb+n0H7ZzEuZnv43wnpW6SO5zPO6N2TuusUr3tzMq5jfxr/64msY8Tm9TzalpxW20jv8tx4ry5bb2b88ad9g+sHlFd3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfS+Pf5vr/+34//b+b/d6792r//bff/X7vv/dj//affzv3Y//2338/92//6n3b//a/fvf9v9+/92//1Hu//+p91//9Xuv/9r999/tvvvf9v999/t/vv/dv/7j3b/+592//uvdv/7v3b/+892//vfdv73/wEP83dQeJzt24e/XVWZPvDQpEyEX8RgQkJ6SHK5uclNub08955zxGFEg4pIcURBpAiEIgwloCKCiKK0SIb6U4IiLdSICShBcWgjgiNdiiAMTQEFQsmc796cv+Iknw+fzV57ve/zPHut9b7vWvvcESPW/zv2i1vk7pYtM/P2Uflq2+icdOCY7H3MuGyw14Qs3npyfnXx1Gy1yfb51tDM7LJbS67asTWPbN2WSavn5L+q7ZlyxbwseX1+ZkxcmINmd+R7kzrzu7c6s9PNXZm/T3e+/4/ufOrwnuzxcE++0tabbQ/tzevn92aXlb058Nfl1b12z/XTnx17fvjjl3848ODCxwMfvPDDE1+88aeDHrroo5Pehn7/P/bpUWk9aHSeXTMmHS+PyzYvTMjpv5ycn+09Lac9sn12nz0rH9tth5y7x+yc3jUnx706N7/8zrxcsW5+rt11YeZ9ryNnXtaZjuVdOfa07ly3a09aNu/NNst785+z+/Lbi/vynRH9uXHn/qw7tT9/v7Y/r97Vn6EHyqt77Z7rpz879vzwxy//cODBhY8HPnjhhye+eONPBz100UcnvQ393oe25U+OyQkzx+eyTMykHabkn89NS+W4GWl9elb+Ork113a15ZGZc7PytfZctawc7x99syPH/rYzO7/YVYzX6qd7ivFcdWRfvrhtf86+vD/nbD+Q084YyJJnB7Jr62D+54uDOf+UwYy5cDBnXFpe3Wv3XD/92bHnhz9++YcDDy58PPDBCz888cUbfzrooYs+Oult6DcnvBfPJn1+Ym7/0pQc0jM97c/MyJx9W/Kjla352dNtuebZufnAmnkZfeyCfPKDHek4oTOfubcr3eu6c8KHenPL5n0569m+gvOndhvIJ14eyHuLB7P6icH890Cy++nJnncmV76VvL3tUH7TNpTPLCyv7rV7rp/+7Njzwx+//MOBBxc+HvjghR+e+OKNPx300EUfnfQ29FsX5ob3o8+pu07Pe2Nnpn9FSxaPn51795qTi49oz+CX5ufDLQuz9O6OfPLfunLfZeV43zKinNsP/KU/e109UIzjP98dzBdPLrXuvtdQ7rpqKNVXh/LkjOGcuWg4ow8ezltLhvODk8qre+2e66c/O/b88Mcv/3DgwYWPBz544YcnvnjjTwc9dNFHJ70N/WKD9WGOeE/6Pn5nS1o/MTtjLp2Tv/y+PU/+YX6+dsXCbPOFcq5bj2LWHWv6ivW7400DxRw2br98POk+YChbPzGUzWvDufW84dzy5+EcPKaSTWuVnLtfJa8uqeSm71ZS+UF5da/dc/30Z8eeH/745R8OPLjw8cAHL/zwxBdv/Omghy766KS3oV98FCOsE3PF+2Ijri5a156NtlmQpRuW61w8Fp/Fppv+vT9fP7Qc78s7kzdeTVadM5R1k0vur783nFM/Xcm/nVfJ3++ra1tXycuTq/nv3mr+c+dqNvhcNV/ds7y61+65fvqzY88Pf/zyDwceXPh44IMXfnjiizf+dNBDF3100tvQL0eIk2KF9WLOeG9sd2ldUOScDTfuylkrunNVZ2/eW9pXxG3rctRzgzllTbLmm+XcHnnDcI5oLcf10qcquXZ6qXHlydUsWl7NLbdU86nfVzP20Woefqqakc+UV/faPddPf3bs+eGPX/7hwIMLHw988MIPT3zxxp8Oeuiij056G/rlSblCvBQzrBtzx/vj49AxXVnyYHeRn595rK+Iy+/tNJiRO6eIXdar8TlmRiV3XFCOs/F8/LvVTF5VzcVPVPO1t6s5Y8tafjqulr5ptRw7q5Zaay1TZ5dX99o9109/duz54Y9f/uHAgwsfD3zwwg9PfPHGnw566KKPTnob+tUK8qWcIW6KHdaPOeQ98nXcN3qLmCMOW387n5V85fhyfS9+ZbhYv+terWTMLtXcu6yam/9Qaj79I6W28+bXsk1XLU/V/7uis5Z7FtZ1L6hlxfzy6l675/rpz449P/zxyz8ceHDh44EPXvjhiS/e+NNBD1300UlvQ796Sc0gb8od4qcYYh2ZS94nn0d+ZaDI19esShGfxW1z8pxVlTw8txwnc3jdiFpWTarli/NqWVPXsryjrq2tlh2n1nLVNrX8botapmxQyxNrq7nxzWpxda/dc/30Z8eeH/745R8OPLjw8cAHL/zwxBdv/Omghy766KS3oV/NqG5SO8ifcog4KpZYT+aU98q3/HzWHUMZuKiMbbc/WynW6dTrq7nmtWreHF/LBvPLcfxVe8n75c1q2emlapbWue9+Q32NX1DN9+satv16Ne3Hl1f32j3XT3927Pnhj1/+4cCDCx8PfPDCD0988cafDnrooo9Oehv61c1qR/WTGkIelUvEUzHFujK3vF8Ycy4fLnOVeP61ap6+p5qrNy/XsfE6pD5ey8eX40vL4CXVzF1czQ9SzdDoan7zt0pa/lgftzWVTL2lvLrX7rl++rNjzw9/h7w/n+DAgwsfD3zwwg9PfIs4tbTMC/TQRR+d9Db02zuon9WQ6ii1hHwqpxRxdadyvZtj3jOsv3+gmvO/WcbtiduU69X6vWJ6ObdX1bl97IxqLvvX+hwfUdeysr5GjqjkwY5KJm5cyTaPD+fbvx3O6beUV/faPddPf3bs+eGPX/7hwIMLHw988MIPT3zxxp8Oeuiij056G/rlR3sIdbRaUj2lppBX5RbxVYyxzsw17xum+Cxen9xRxrCeMbW8/kw5bhM/UY7zid+rZPnMSqExBw3nsm2HM+HBoRy1fCh3njKUg5aUV/faPddPf3bs+eGPX/7hwIMLHw988MIPT3zxxp8Oeuiij056G/rtIe2j7CXU02pKdZXaQn6VY8RZscZ6M+e8d9j9neX6/EI9Z5mr1rM5PP3iSo6aXMmIC0vNi84fys0z6jXtr5OZByfHtiV/2DS5be1gcXWv3XP99GfHnh/++OUfDjy48PHABy/88MQXb/zpoIcu+uikt6HfPtpeUpy0p1BXqy3VV2oMeVauEW/FHOvO3PP+cRC3n7u9jr1PNXu8UMlme5fj/eTU4WJ8f/ZgMqc3Oe7ywazZYTC/XDOQvY4ayBUfrcfUjvLqXrvn+unPjj0//PHLPxx4cOHjgQ9e+OGJL97400EPXfTRSW9Dv7ME+2kxwr5K7lBfqzHVWWoN+VbOEXfFnvPez9vGAZfUccUw6/eFb9VroduH8oG3y3Hd4sv1Pe6lA9nkH/V9/h79OfbRvhxwQl8Oqvbl/rnl1b12z/XTnx17fvjjl3848ODCxwMfvPDDE1+88aeDHrroo5Pehn7rwZmCfbW9pf2V+KnOVmuqt9Qc8q7cI/6KQdahuWg8cLJef/Cl4WI9335BOd5HXz+Qa28rNffVffcs6s3ClT3ZcMeetGxQ38s/311c3Wv3XD/92bHnhz9++YcDDy58PPDBCz888cUbfzrooYs+Oult6FcjOlexPuyv7THts+w11NtqTnWX2kP+lYPEYbHIejQnjQtubx0+VKzjxz47WMzt/bfuT98/evPlv5Ra9/pgd9bt1pWf/74zWyzpzK17l1f32j3XT3927Pnhj1/+4cCDCx8PfPDCD0988cafDnrooo9Oehv6nas5W3K+4ozBerHXtN8SS8RVtaf6Sw0iD8tF4rGYZF2am8YHx56ewWIOH/STcryN769/1ZVJ53XmorM7Ulu9MFuPX5ielQuy/7kLiqt77Z7rpz879vzwxy//cODBhY8HPnjhhye+eONPBz100UcnvQ39zhblR2dM6iZnDfbb1o99l72H+lstpg5Ti8jHcpK4LDZZn+aoccLVeq6s7ckbi7sz5cXO/OCojkLjNs/Pz7vPz8vslnlZe217Tjmtvbi61+65fvqzY88Pf/zyDwceXPh44IMXfnjiizf+dNBDF3100tvQ73zVGaNzNvnSeYszB/tue0/ryR5EHS7eqsfUJPKy3CQ+i1HWqblqvHA+ebC7mNvG9dMjF+S5o+fl9S3b88QTc7LrO21ZfnBbtusvr+61e66f/uzY88Mfv/zDgQcXPh744IUfnvjijT8d9NBV6KvrpLehv6iPl5dz33mbMyf5Uy1h/20Pah+25v13K+aoy9Qm8rMcJU6LVdarOWvccP/+koVZtmR+Mc5bXj8nHxrZlocvbc2pZ++QO59ryayrWoqre+2e66c/O/b88Mcv/3DgwYWPBz544YcnvnjjTwc9dNFHJ70N/eKhGlG97MzRuZs54/xFPlVf2Yvaj9mTWG9qU3FYjSJPy1XitZhl3Zq7xo+Gk7vai/G97uHWQusvBmbl3n/MSN/0Gbnyse2Lq3vtnuunPzv2/PDHL/9w4MGFj0dRY19S7pnwxBdv/Omghy766KS3od+3BuftzpzFSfWz8zdnUM5hzKWivm8r93HF3mRlue7VaWoV+VrOErfFLuvXHDaOtBy4uDXPvzUrtRUzsu/107PZ9Gn50rSpueyuKcXVvXbP9dOfHXt++OOXfzjw4MLHAx+88MMTX7zxp4Meuuijk96GfvsjtYFzd2fP6qbirLIeP51FOY9xJmFfbo7Zn9mjqNPVqtaj2CRvy13itxhmHZvLxpMm47zs6GnZdPSUHN4yKdc8NyFj/mNCcXWv3XP99GfHnh/++OUfDjy48PHABy/88MQXb/zpoIcu+uikt6HfNyffXXx7sH9WMziHdRbpPE59La46m7A/l3PkX3NPva5mVbdZn/K3HCaOi2XWszltXGl757OTC82bjh6f14/aNiOeHltc3Wv3XD/92bHnhz9++YcDDy58PPDBCz888cUb/+JMtVbWQvTRSW9Dv3MS357kRzHCOby9pPNYtYRzOfWVetsZRbEfq+9V7dfkY3W7Oal+U8NYr3KZeC6mWdfmtvGl8fJNxiUbjskDD43OF0aOLq7utZfvZkrRnx17fvjjl3848ODCxwMfvPDDE1+88aeDHrroo5Pehn57Q9/f1Ii+w/gWoX6WP+2rxBLnc2oM5zRqT/t1e1Zx2N5FflbDquPMVfnc+hXXxTbru5zj4wuttfYP553Zo7Lg9a2Kq3vtnuunPzv2/PDHL/9w4MGFjwc+eOGHJ754408HPXTRRye9Df3qIfsj3+HsGe2ffZNwLu9sWl1tjyG/OqtyXiPmqMPsXe3f7GHEZ7lKPaemkdfNYetZjLPOzXXjTfM9D43M15dtUVzda/dcP/3ZseeHP375hwMPLnw88MELPzzxxRt/Ouihiz466W3o9w3aOYlzM9/jfJNSN8kdzuedUTundVap3nZm5dxG/rV/V5PYx4lN6nk1rbittpHf5Thx3ty23s154057Zd3GxdW9ds/1058de37445d/OPDgwscDH7zwwxNfvPGngx666KOT3vXjv379r4//6/N/s9d/zV7/N/v+r9n3/81+/tPs53/Nfv7b7Of/zf79p9m//zX7999m//7f7L//aPbf/zT777+a/fd/zf77z2b//W+z//672X//3+x//9Hsf//T7H//1ex//9fsf//Z7H//u/5fc/97aY8tMjBtyyxbPSp/njE6/9xnTP5w+LgcueuEPDVycuYtm5pvvDc9b/TMzJpFLZk01JpPfLAtZ904J70D7Tnn0nl55aX5OW/Mwjy2fUfWje1Mz2udWXVdVy7dqzsbvNKd2w/qyd339+ShGb05ff/e7Hdub9as6M2jN5dX99o9109/duz54Y9f/uHAgwsfD3zwwg9PfPHGnw566KKPTnob+v3/6Y+OyoX7js7nVo/JT/86Lqf+ZULeu35yxu0xLe88sH3u2n5Wbl60Qzb7zOy81z4nL70wN7NPmpcJb83PtE8uzE++3ZGNL+nMTy/qyosndWf7T/bkgg17c+pFvfngjL50L+vL22/1ZeaO/Tnsm/3Z54r+fPk3/bnunvLqXrvn+unPjj0//PHLPxx4cOHjgQ9e+OGJL97400EPXfTRSW9Dv/ehbcwjY/K3yeMztmdizpw2Jfs/NS03HDkjFz06K7uPa830eW3Zecrc7PBSvaY6uxzvLY7vyEu3dubWZ7qK8ZrzWE8xnm2H9OX+rfuz6U/6s9mkgbx76kBe+fNA7pg+mI/tOZgtvzGY7y4dzIYXlVf32j3XT3927Pnhj1/+4cCDCx8PfPDCD0988cafDnrooo9Oehv6zQnvxbOzPluvDfaakifmT8+PH5+RSz7fki2ubc24x9oy5Ym5OWbVvJxyxILc9oGO/Ozozvz2jq78/M3u/O1fejN3o75s8kRfwfn2RQP59V8HsvjAwcx5eDBDXfU9c72Ouades098LTnkw0PpnDmUO9rKq3vtnuunPzv2/PDHL/9w4MGFjwc+eOGHJ754408HPXTRRye9Df3Whbnh/eiz9hPTs3jrmbn68pY8NXp28tk5+dDB7bl2z/k5ZWp9zH/bkduqXalcUo53+9reYs7u+Hh/7v3pQDGO+78xmPtPKLXevetQ+n86lJteGMqn6zX8xjvV9wX7Deego4ez0ZLy6l675/rpz449P/zxyz8ceHDh44EPXvjhiS/e+NNBD1300UlvQ7/YYH2YI96Tvotub8mFO87Ody+ck8/e2Z5P3z0/f710YU79XDnXrUcxq3t1X7F+f3HNQDGHjVvbg8nPvzSUbz80lOMGhtN+Vv2/h4bz51H1vetAJZvvXcm+R1cy6+RKbvhOeXWv3XP99GfHnh/++OUfDjy48PHABy/88MQXb/zpoIcu+uikt6FffBQjrBNzxftiI67e9mZ7jtpqQTZ/Z2Gx3sRj8VlsavlcvXbZvxzv7eqv+4AX69rPGMph40ruX3lzOGs/XsnqMyvZ565Kjn2zkr3HVTO0sJoP7ljNEbvU9/afKa/utXuun/7s2PPDH7/8w4EHFz4e+OCFH5744o0/HfTQRR+d9Db0yxHipFhhvZgz3hvbNdMXFDnnyHc7s8nPuzO5vTeLf9hXxG3r8qQnB7N2VbLw+HJun3jVcJ6ZXo7rRx6tZNrEUmPLidWsuaia9pXV3P5f1Zz+x2o+Xt/zn/h4eXWv3XP99GfHnh/++OUfDjy48PHABy/88MQXb/zpoIcu+uikt6FfnpQrxEsxw7oxd7w/Pp4c1ZVX7usu8vNuf+or4vJh1cGc+NEUsct6NT4vTKqkZ2k5zsZz0cnVnH1DNVs/XM1fX6/v8zerZdzoWq7arpaXptRy0/Razt2+vLrX7rl++rNjzw9//PIPBx5c+Hjggxd+eOKLN/500EMXfXTS29CvVpAv5QxxU+ywfswh75Gvl44tY5w4bP3dWt+jPfS1cn0/9dxwsX4Pe7GS03aqz+Ozq5l9d6n5vf9Xahs5u5ZT22vZdV4tE+rXwTl13bPL8y9X9xPef66f/uzY88Mfv/zDgQcXPh744IUfnvjijT8d9NBFH530NvSrl9QM8qbcIX6KIdaRueR98vns3gNFvp5yY4r4LG6bk5veWMnOLeU4mcOHr62mbdta7t+hlo66nrFz69pm1vKL8bVMquvp2biWs+t9PlXXNfPV8upeu+f66c+OPT/88cs/HHhw4eOBD1744Ykv3vjTQQ9d9NFJb0O/mlHdpHYo8vvUMs6LJdaTOeW98i0/b/Lroaz4URnbOp6oFOv03CurmfJSNQduU8sRreU4zqvznlPnvfeGtax6tprN76zmrquqmbu0mhF1DacfU82Pv1Ze3Wv3XD/92bHnhz9++YcDDy58PPDBCz888cUbfzrooYs+Oult6Fc3qx3VT2oIeVQuEU/FFOvK3PJ+YVzy4+EiVxXx/NBqdr2jmskblevYeD1Z5zumztO40rJiWTX//4BqNuqp5votq+n830ouuLc+bqsrOeem8upeu+f66c+OPT/8Pfn+fIIDDy58PPDBCz888cUbfzroKeqDuj466W3ot3dQP6sh1VFqCflUThFXxRbryxzznmHtM6KaLY8v4/YPtyrXq/U7cWI5t9vq3G4+tZqxlfocX1sfnxX1NXJwJTvNreSH79brnT8N561bh/PeTeXVvXbP9dOfHXt++OOX/wnvxwe48PHABy/88MQXb/zpoIcu+uikt6FffrSHUEerJdVTagp5VW4RX8UY68xc875his/i9Rvvx7IrR9Wy35/LcTtzx3Kc//7tSsZMqRQar913OGM/XNd531Ceu6he431jKI8fVV7da/dcP/3ZseeHP375hwMPLnw88MELPzzxxRt/Ouihiz466W3ot4e0j7KXUE+rKdVVagv5VY4RZ8Ua682c895hXzO3XJ/3bVor5qr1bA4vPa+S57et5PClpebbzhlK6+Sh3HVzsmy/5MUZSXWDZMHrg8XVvXbP9dOfHXt++OOXfzjw4MLHAx+88MMTX7zxp4Meuuijk96Gfvtoe0lx0p5CXa22VF+pMeRZuUa8FXOsO3PP+8dB3N7zljr256u55y917N3L8f70dsPF+I77Q3LJguTlHw9mYX1/N3v1QO49dCATMpAN5pZX99o9109/duz54Y9f/uHAgwsfD3zwwg9PfPHGnw566KKPTnob+p0l2E+LEfZVcof6Wo2pzlJryLdyjrgr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7q9k4UeVfDC0mq2nV/O1nmou6i+v7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnob+sVHMcI6MVd8XvqIq1ddOzO73tuSD25oLdabeCw+i03rDe7Jc5uU473a8r5sd3kydLP+7Ph4qXmbayt5ZZVqrvh2NVsdV833rq1m88ermfS3aj76opp/W6OW+wbWiqt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3oZ+OUKcFCusF3PG56bv0hdaipyz803teXdhR9Zc3pkdNu8u4rZ1uf+v+vLKocmI2eXc3mevSh5+oVKM6wpnVjPor9VC45D2WpYuqGXYD2q5+t9rWfTzWlrPrGWfs8ure8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96GfnlSrhAvxQzrxtzx+bHx5/vb8/RJHUV+zmndRVze8fPe7PNFXxG7rFfj88TTlYyZW46f8ezoqeXoA2r5/PRaHllay1t31LLKQ7Wc82QtTz1by69fqGXxi+XVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Dv1pBvpQzxE2xw/oxh3yObD01q4xx4rD191+15M6p5fp+4NeVYv3ueHk1P1yplklb1rLB8aWGN+4rtX34ai0HL6+l961aVq9fJ7xR1/1aLQNfK6/uV//yvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfQ29KuX1Azyptwhfooh1pG55PNk8y8b9hb5eq0DU8RncducfO+AatpeLsfbHN7p+lqGPlbLba/UMrKuZ6U369pequXSJ2pZo65nzG31ca236arrWvfK8urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6FfzahuUjsU+b2eQ8RRscR6Mqd8rmzLz+8e0Z/ztilj28hzy/W9eM9a1lpSy/YP1+PZq+U4Dq9zb1jn3vyWWpZcVMsHdfYb96qv8bm1LO+ur/mZtZw6tby699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466W3oVzerHdVPagh5VC4RT8UU68rc8vnycfJOZQ4r4vn4OtviWta8tVzHxuvPdd4VHy7HlZbzt6vllFG1vPN+NRfeU80ml1ZzwgnVzFtUzTEHlVf3nnuvnfb66c8O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fGQW3Tom7105Lo/tt30mvTopm+w9NSu/Nz0X3TgzKy5vyeIjZuf8Tcvx/vLajuzb35nWg8pxvmLvnmI8L3+hXiOe35cvjevPSd/vzxfWHcj+CwbSdfVAfvboQE7710DeGjGYL44aLK7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnroKvTtWX4GDf3mhM/Fu/cfGpNlj43LLjdtnxH7TM5mf5iaL7fMyNl7z8qFC1rzfPecvP5KW9rO6Mi2f5+bznRl1IndOeDMnlx5Wm9OXFBq7nigP3PqdemTqwZyxR6Dublet/S9V89pw0M575ih/Pn8oVyztMx/ru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056G/qtC3PD56PNUfdtnyfPm5xxU6Zltwtn5KaHZuX05+s577G69ivqY16t+7ujM/8xphzvqxb2FnP2p5/tz+CkchwfO34wtbcGC039Dw3lxonDmXL4cO6+bDgn3DOctU8P54+vDef4N8qre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96GfrHB+jBHfE7a3j48LRvfPSNvjWzJPTvMzl2fqNdVEzqy9rdzi7lnPYpZP+rpK9bvtFnlnDZul39mKKOeqNdtuw/nxeXDuWqTSq7avZKdz63k+eWVnPx4Jb97rZJL3q1k0nrV4urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6FffBQjrBNzxeelj7jaduLsrDq7LSefVK5z8Vh8Fpsu/W1fDnq2vxiXc24YzO8PH8oVGw7nqe+Vmh89oZIjV1bSskk1v/14NS+cUM1vvlfNzTdXc+pd1TzzQDU7P1he3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfQ29MsR4qRYYb2YMz43fduvbityzrMnd+bEKd25oL7XeHKjviJuW5ev7TuYI3vqdfraoWLuvjK9kt2vLsf1m3tV853FpcbFb1XTPrqWqzpq6dihln/uWstte9XyymfLq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pehn55Uq4QL8UM68bc8fmxseu5nTlgp+4iP987v6+Iy0/dPpBX7hwsYpf1anz2uaySa0eU42c8b3+3mnXaajljj1r2PK6W475ey9n1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49mx54c/fvmHAw8ufDzwwQs/PPHFG3866KGLPjrp/U/8m5Sd93wP58zo/Ow7Cffy7qbV1c4Y8qu7Kvc1Yo46zNnV+c0ZRnyWq9Rzahp53Rq2n8U4+9xaN980XzlgU9tXv/0wfXrXrt8449mx54c/fvmHAw8ufDzwwQs/PPHFG3866KGLPjrp/U/9f052z+PezPdxvpNSN8kd7ufdUbundVep3nZn5d5G/nV+V5M4x4lN6nk1rbittpHf5Thx3tq236158077zOM3pE/v2vUbZzw79vzwxy//cODBhY8HPnjhhye+eONPBz100UcnvTvmf8f+3xH/d+T/cq//yr3+L/fzX7mf/8v9/qfc7//K/f633O//y/37n3L//q/cv/8t9+//y/33H+X++59y//1Xuf/+r9x//1nuv/8t999/l/vv/8v97z/K/e9/yv3vv8r97//K/e8/y/3vf8v53/8DHRBcDnic7duHm1XVucdxEASBQcoAMwPD9Dll5owtWLCdV2MMUWNu1MSIJYJca4jYLqISsUSjURETY48aLrZEIdYoGM21RCQxNojlaiygoNcKogQld3/25uSfOPA8Ptu991rv+/2dtdb7vmudM716bfr37ikryhf3+6C85/g15SWD15e//J9e8fJzfeLMe/vFqhMGxPh1g+Jnh28ZG64ZGk/PHx75m0bEQT8aFdeOrI89b2iI6/uOibX7NcZN08fG8p82Rb8ZzbHHAS3x2PDW+N3C1thi/7ZY8nRbvLBde7xxQXv84s/tMfWz9nh6aEe83dCRXt177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvT7/87pa8r7XLi+/MPG3jF/774x++v9Y/MRA6PtwZroM25IPP/TYfGn+bUx5J6RsfmculizT0OMe2t0dP6gMbrvGhu/XdEUg3q1xPwNLbH6rdbouast5k5qj9kb2mPEBR1RXtcRmx3SGVvf0hmnv9kZxw/KxY86cvFQKbu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466a3o93l41rxz7/h8Vt9ouaZ/XHPewDhxj8Gx6IUhccsuw+PIM0ZEac6oOOic+thuv9GRXz0mHYdhrzbFmqaWeHKvbJx32DUb1+3/2hGvnNwZW/bJxZCzc9F3ZS7W7p6PZ87Px3ceysfIN/Jxxdp8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c++1014//dlhj132+eGPX/5xpDV2woUPJ17c+Omghy766KS3ot93Dc7bnTmLk+pn52/OoJzDmEtpfX9hti9L9yZJfW79qdPUKvK1nCVui13WrzlsHGl5+6kRcdQBw+ORAUPjtZot45yZNfHqWYOipWNQenXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9Fvf6Q2cO7u7Fnd5AxS/HQW5TzGmYR9uTlmf2aPok5Xq1qPYpO8LXeJ32KYdWwuG0+ajHPtszUxa+rA+L9zt4jiHv1jzrP90qt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3op+3zn53sV3D/bPagbnsM4incepr8VVZxP253KO/GvuqdfVrOo261P+lsPEcbHMejanjSttp80fkGqeNbVvTP3bZjFjl83Sq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pein7nJL57kh/FCOfw9pLOY9USzuXUV+ptZxTirb2q/Zp8rG43J9VvahjrVS4Tz8U069rcNr40th/RJx6a2CuunfJlecHif5Zd3XuefTYD0/b66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvRX99oa+f1Mj+h7GdxHqZ/nTvkoscT6nxnBOo/a0X7dnFYftXeRnNaw6zlyVz61fcV1ss76zOd431X5W7bry+zWry4de83F6de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0a8esj/yPZw9o/2z7yScyzubVlfbY8ivzqqc14g56jB7V/s3exjxWa5Sz6lp5HVz2HoW46xzc91403zplPfKq1esSK/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeiv6fQftnMS5me/jfCelbpI7nM87o3ZO66xSve3MyrmN/Gv/riaxjxOb1PNqWnFbbSO/y3HivLltvZvzxp32GfNfSa/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9OejeN/6b1vyn+b8r/1V7/VXv9X+37v2rf/1f7+U+1n/9V+/lvtZ//V/v3P9X+/V+1f/9b7d//V/vvP6r99z/V/vuvav/9X7X//rPaf/9b7b//rvbf/1f7339U+9//VPvff1X73/9V+99/Vvvf/276V93/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 + + diff --git a/Documentation/doc/Documentation/packages.txt b/Documentation/doc/Documentation/packages.txt index 7002020a1a07..aee12eb95ebd 100644 --- a/Documentation/doc/Documentation/packages.txt +++ b/Documentation/doc/Documentation/packages.txt @@ -93,6 +93,7 @@ \package_listing{Skin_surface_3} \package_listing{SMDS_3} \package_listing{Mesh_3} +\package_listing{Isosurfacing_3} \package_listing{Tetrahedral_remeshing} \package_listing{Periodic_3_mesh_3} \package_listing{Alpha_wrap_3} diff --git a/Documentation/doc/biblio/cgal_manual.bib b/Documentation/doc/biblio/cgal_manual.bib index 51dfb1090046..c15b34e6d084 100644 --- a/Documentation/doc/biblio/cgal_manual.bib +++ b/Documentation/doc/biblio/cgal_manual.bib @@ -387,6 +387,12 @@ @inproceedings{cgal:cad-vsa-04 organization={ACM} } +@techreport{ cgal:c-mcctci-95, + title={Marching Cubes 33: Construction of Topologically Correct Isosurfaces}, + author={Chernyaev, Evgeni}, + year={1995} +} + @inproceedings{cgal::c-mssbo-04, author={Chen, L.}, title={{Mesh Smoothing Schemes based on Optimal Delaunay Triangulations}}, @@ -643,6 +649,17 @@ @article{ cgal:dds-scs-09 , year = 2009 } +@article{cgal:dljjaw-sisp-15, + title={A survey on Implicit Surface Polygonization}, + author={De Ara{\'u}jo, Bruno Rodrigues and Lopes, Daniel S and Jepp, Pauline and Jorge, Joaquim A and Wyvill, Brian}, + journal={ACM Computing Surveys (CSUR)}, + volume={47}, + number={4}, + pages={1--39}, + year={2015}, + publisher={ACM New York, NY, USA} +} + @article{cgal:dfg-cvtaa-99t, title={{Centroidal Voronoi Tessellations: Applications and Algorithms}}, author={Du, Q. and Faber, V. and Gunzburger, M.}, @@ -909,6 +926,17 @@ @article{ cgal:f-mvc-03 pages = "19--27", year = "2003"} +@inproceedings{ cgal:g-ctcmi-16, + title={Construction of Topologically Correct and Manifold Isosurfaces}, + author={Grosso, Roberto}, + booktitle={Computer Graphics Forum}, + volume={35}, + number={5}, + pages={187--196}, + year={2016}, + organization={Wiley Online Library} +} + @inproceedings{ cgal:g-frseb-99 ,author = "B. G{\"a}rtner" ,title = "Fast and robust smallest enclosing balls" @@ -1248,6 +1276,14 @@ @book{ cgal:j-csl-99 ,update = "01.06 hoffmann" } +@inproceedings{ cgal:jlsw-dchd-02, + title={Dual contouring of Hermite Data}, + author={Ju, Tao and Losasso, Frank and Schaefer, Scott and Warren, Joe}, + booktitle={Proceedings of the 29th annual conference on Computer graphics and interactive techniques}, + pages={339--346}, + year={2002} +} + @incollection{ cgal:k-dat-96 ,author = "Keffer, T." ,title = "The Design and Architecture of {T}ools.h{\tt ++}" diff --git a/Filtered_kernel/include/CGAL/Lazy.h b/Filtered_kernel/include/CGAL/Lazy.h index 2f1201ee239a..d4b11ac45a1f 100644 --- a/Filtered_kernel/include/CGAL/Lazy.h +++ b/Filtered_kernel/include/CGAL/Lazy.h @@ -1369,7 +1369,7 @@ CGAL_Kernel_obj(Point_3) } -// This functor selects the i'th element in a vector of Object's +// This functor selects the i-th element in a vector of Object's // and casts it to what is in the Object template @@ -1412,7 +1412,7 @@ struct Ith { } }; -// This functor selects the i'th element in a vector of T2's +// This functor selects the i-th element in a vector of T2's template struct Ith_for_intersection { typedef T2 result_type; @@ -1430,7 +1430,7 @@ struct Ith_for_intersection { } }; -// This functor selects the i'th element in a vector of T2's +// This functor selects the i-th element in a vector of T2's template struct Ith_for_intersection_with_variant { typedef T2 result_type; diff --git a/Installation/include/CGAL/license/Isosurfacing_3.h b/Installation/include/CGAL/license/Isosurfacing_3.h new file mode 100644 index 000000000000..345fae13a820 --- /dev/null +++ b/Installation/include/CGAL/license/Isosurfacing_3.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 GeometryFactory SARL (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Andreas Fabri +// +// Warning: this file is generated, see include/CGAL/licence/README.md + +#ifndef CGAL_LICENSE_ISOSURFACING_3_H +#define CGAL_LICENSE_ISOSURFACING_3_H + +#include +#include + +#ifdef CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE + +# if CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +# if defined(CGAL_LICENSE_WARNING) + + CGAL_pragma_warning("Your commercial license for CGAL does not cover " + "this release of the 3D Isosurfacing package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the 3D Isosurfacing package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL 3D Isosurfacing package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL 3D Isosurfacing package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_ISOSURFACING_3_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_ISOSURFACING_3_H diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index a66f7cdfea7f..cdf61882fd68 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -25,6 +25,7 @@ Hyperbolic_triangulation_2 2D Hyperbolic Delaunay Triangulations Inscribed_areas Inscribed Areas Interpolation 2D and Surface Function Interpolation Interval_skip_list Interval Skip List +Isosurfacing_3 3D Isosurfacing Jet_fitting_3 Estimation of Local Differential Properties of Point-Sampled Surfaces Matrix_search Monotone and Sorted Matrix Search Mesh_2 2D Conforming Triangulations and Meshes diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/CMakeLists.txt b/Isosurfacing_3/benchmark/Isosurfacing_3/CMakeLists.txt new file mode 100644 index 000000000000..b1ecd2b02d3f --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/CMakeLists.txt @@ -0,0 +1,36 @@ +# Created by the script cgal_create_cmake_script +# This is the CMake script for compiling a CGAL application. + +cmake_minimum_required(VERSION 3.1...3.23) +project( Isosurfacing_3_benchmark ) + +find_package(CGAL REQUIRED) + +find_package(Eigen3 3.1.0 QUIET) #(3.1.0 or greater) +include(CGAL_Eigen3_support) + +find_package(TBB) +include(CGAL_TBB_support) + +if(TARGET CGAL::Eigen3_support) + create_single_source_cgal_program("benchmark.cpp" ) + create_single_source_cgal_program("contouring_seq_vs_parallel_implicit.cpp" ) + create_single_source_cgal_program("contouring_seq_vs_parallel_image.cpp" ) + + target_compile_definitions(benchmark PUBLIC ${SCENARIO}) + target_compile_definitions(benchmark PUBLIC ${KERNEL}) + target_compile_definitions(benchmark PUBLIC ${ALGO}) + target_compile_definitions(benchmark PUBLIC ${TAG}) + + target_link_libraries(benchmark PRIVATE CGAL::Eigen3_support) + target_link_libraries(contouring_seq_vs_parallel_implicit PRIVATE CGAL::Eigen3_support) + target_link_libraries(contouring_seq_vs_parallel_image PRIVATE CGAL::Eigen3_support) + + if(TARGET CGAL::TBB_support) + target_link_libraries(benchmark PUBLIC CGAL::TBB_support) + target_link_libraries(contouring_seq_vs_parallel_implicit PUBLIC CGAL::TBB_support) + target_link_libraries(contouring_seq_vs_parallel_image PUBLIC CGAL::TBB_support) + endif() +else() + message(STATUS "NOTICE: Some benchmarks use Eigen, and will not be compiled.") +endif() diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark.cpp b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark.cpp new file mode 100644 index 000000000000..a2d1e2a73fa9 --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark.cpp @@ -0,0 +1,350 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884L +#endif + +template +struct Sphere_value +{ + using FT = typename GeomTraits::FT; + using Point = typename GeomTraits::Point_3; + + FT operator()(const Point& point) const + { + return CGAL::approximate_sqrt((point - CGAL::ORIGIN).squared_length()); + } +}; + +template +struct Sphere_gradient +{ + using FT = typename GeomTraits::FT; + using Point = typename GeomTraits::Point_3; + using Vector = typename GeomTraits::Vector_3; + + Vector operator()(const Point& point) const + { + Vector g = point - CGAL::ORIGIN; + return g / CGAL::approximate_sqrt(g.squared_length()); + } +}; + +template +struct Implicit_sphere +{ + using FT = typename GeomTraits::FT; + using Vector = typename GeomTraits::Vector_3; + + using Grid = CGAL::Isosurfacing::Cartesian_grid_3; + using Values = CGAL::Isosurfacing::Value_function_3; + using Gradients = CGAL::Isosurfacing::Gradient_function_3; + using Domain = CGAL::Isosurfacing::internal::Isosurfacing_domain_3; + + Implicit_sphere(const std::size_t N) + : res(2. / N, 2. / N, 2. / N), + grid { CGAL::Bbox_3 {-1, -1, -1, 1, 1, 1}, CGAL::make_array(N, N, N) }, + values { Sphere_value{}, grid }, + gradients { Sphere_gradient{}, grid } + { } + + Domain domain() const + { + return { grid, values, gradients }; + } + + FT iso() const + { + return 0.8; + } + +private: + Vector res; + Grid grid; + Values values; + Gradients gradients; +}; + +template +struct IWPValue +{ + using FT = typename GeomTraits::FT; + using Point = typename GeomTraits::Point_3; + + FT operator()(const Point& point) const + { + const FT alpha = 5.01; + // const FT alpha = 1.01; + + const FT x = alpha * (point.x() + 1) * M_PI; + const FT y = alpha * (point.y() + 1) * M_PI; + const FT z = alpha * (point.z() + 1) * M_PI; + + return cos(x)*cos(y) + cos(y)*cos(z) + cos(z)*cos(x) - cos(x)*cos(y)*cos(z); // isovalue = 0 + } +}; + +template +struct IWPGradient +{ + using FT = typename GeomTraits::FT; + using Point = typename GeomTraits::Point_3; + using Vector = typename GeomTraits::Vector_3; + + Vector operator()(const Point& point) const + { + const FT alpha = 5.01; + // const FT alpha = 1.01; + + const FT x = alpha * (point.x() + 1) * M_PI; + const FT y = alpha * (point.y() + 1) * M_PI; + const FT z = alpha * (point.z() + 1) * M_PI; + + const FT gx = M_PI * alpha * sin(x) * (cos(y) * (cos(z) - 1.0) - cos(z)); + const FT gy = M_PI * alpha * sin(y) * (cos(x) * (cos(z) - 1.0) - cos(z)); + const FT gz = M_PI * alpha * sin(z) * (cos(x) * (cos(y) - 1.0) - cos(y)); + + return { gx, gy, gz }; + } +}; + +template +struct Implicit_iwp +{ + using FT = typename GeomTraits::FT; + using Vector = typename GeomTraits::Vector_3; + + using Grid = CGAL::Isosurfacing::Cartesian_grid_3; + using Values = CGAL::Isosurfacing::Value_function_3; + using Gradients = CGAL::Isosurfacing::Gradient_function_3; + using Domain = CGAL::Isosurfacing::internal::Isosurfacing_domain_3; + + Implicit_iwp(const std::size_t N) + : res(2. / N, 2. / N, 2. / N), + grid { CGAL::Bbox_3{-1, -1, -1, 1, 1, 1}, CGAL::make_array(N, N, N) }, + values { IWPValue{}, grid }, + gradients { IWPGradient{}, grid } + { } + + Domain domain() const + { + return { grid, values, gradients }; + } + + FT iso() const + { + return 0.; + } + +private: + Vector res; + Grid grid; + Values values; + Gradients gradients; +}; + +template +struct Grid_sphere +{ + using Grid = CGAL::Isosurfacing::Cartesian_grid_3; + using Values = CGAL::Isosurfacing::Interpolated_discrete_values_3; + using Gradients = CGAL::Isosurfacing::Interpolated_discrete_gradients_3; + using Domain = CGAL::Isosurfacing::internal::Isosurfacing_domain_3; + + using FT = typename GeomTraits::FT; + using Point = typename GeomTraits::Point_3; + + Grid_sphere(const std::size_t N) + : grid { CGAL::Bbox_3{-1., -1., -1., 1., 1., 1.}, + CGAL::make_array(N, N, N) }, + values { grid }, + gradients { grid } + { + const Sphere_value sphere_val; + const Sphere_gradient sphere_grad; + const FT resolution = 2.0 / N; + + for(std::size_t x = 0; x < grid.xdim(); x++) + { + const FT xp = x * resolution - 1.0; + for(std::size_t y = 0; y < grid.ydim(); y++) + { + const FT yp = y * resolution - 1.0; + for(std::size_t z = 0; z < grid.zdim(); z++) + { + const FT zp = z * resolution - 1.0; + + values(x, y, z) = sphere_val(Point(xp, yp, zp)); + gradients(x, y, z) = sphere_grad(Point(xp, yp, zp)); + } + } + } + } + + Domain domain() const + { + return { grid, values, gradients }; + } + + typename GeomTraits::FT iso() const + { + return 0.8; + } + +private: + Grid grid; + Values values; + Gradients gradients; +}; + +template +struct Skull_image +{ + using FT = typename GeomTraits::FT; + using Grid = CGAL::Isosurfacing::Cartesian_grid_3; + using Values = CGAL::Isosurfacing::Interpolated_discrete_values_3; + using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + using Domain = CGAL::Isosurfacing::internal::Isosurfacing_domain_3; + + Skull_image(const std::size_t N) + : grid { }, + values { grid } + { + const std::string fname = CGAL::data_file_path("images/skull_2.9.inr"); + CGAL::Image_3 image; + if(!image.read(fname)) + std::cerr << "Error: Cannot read file " << fname << std::endl; + + Grid grid; + Values values { grid }; + if(!CGAL::Isosurfacing::IO::convert_image_to_grid(image, grid, values)) + std::cerr << "Error: Cannot convert image to Cartesian grid" << std::endl; + } + + Domain domain() const + { + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + Gradients gradients { values, step }; + return { grid, values, gradients }; + } + + typename GeomTraits::FT iso() const + { + return 2.9; + } + +private: + Grid grid; + Values values; +}; + +int main(int argc, char* argv[]) +{ + std::size_t N = 100; + + const int argc_check = argc - 1; + + for(int i=1; i; +#elif defined KERNEL_SIMPLE_CARTESIAN_FLOAT + std::cout << "KERNEL_SIMPLE_CARTESIAN_FLOAT" << std::endl; + using Kernel = CGAL::Simple_cartesian; +#elif defined KERNEL_CARTESIAN_DOUBLE + std::cout << "KERNEL_CARTESIAN_DOUBLE" << std::endl; + using Kernel = CGAL::Cartesian; +#elif defined KERNEL_EPIC + std::cout << "KERNEL_EPIC" << std::endl; + using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +#else + std::cout << "no kernel selected!" << std::endl; + using Kernel = CGAL::Simple_cartesian; +#endif + + using Point = Kernel::Point_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + +#if defined SCENARIO_GRID_SPHERE + std::cout << "SCENARIO_GRID_SPHERE" << std::endl; + auto scenario = Grid_sphere(N); +#elif defined SCENARIO_IMPLICIT_SPHERE + std::cout << "SCENARIO_IMPLICIT_SPHERE" << std::endl; + auto scenario = Implicit_sphere(N); +#elif defined SCENARIO_IMPLICIT_IWP + std::cout << "SCENARIO_IMPLICIT_IWP" << std::endl; + auto scenario = Implicit_iwp(N); +#elif defined SCENARIO_SKULL_IMAGE + std::cout << "SCENARIO_SKULL_IMAGE" << std::endl; + auto scenario = Skull_image(N); +#else + std::cout << "no scenario selected!" << std::endl; + auto scenario = Implicit_sphere(N); +#endif + + Point_range points; + Polygon_range polygons; + + CGAL::Real_timer timer; + timer.start(); + +#if defined TAG_PARALLEL + std::cout << "TAG_PARALLEL" << std::endl; + using Tag = CGAL::Parallel_tag; +#elif defined TAG_SEQUENTIAL + std::cout << "TAG_SEQUENTIAL" << std::endl; + using Tag = CGAL::Sequential_tag; +#else + std::cout << "no tag selected!" << std::endl; + using Tag = CGAL::Sequential_tag; +#endif + +#if defined ALGO_MARCHING_CUBES + std::cout << "ALGO_MARCHING_CUBES" << std::endl; + CGAL::Isosurfacing::marching_cubes(scenario.domain(), scenario.iso(), points, polygons); +#elif defined ALGO_DUAL_CONTOURING + std::cout << "ALGO_DUAL_CONTOURING" << std::endl; + CGAL::Isosurfacing::dual_contouring(scenario.domain(), scenario.iso(), points, polygons); +#else + std::cout << "no algorithm selected!" << std::endl; + CGAL::Isosurfacing::marching_cubes(scenario.domain(), scenario.iso(), points, polygons); +#endif + + timer.stop(); + + if(points.size() > std::numeric_limits::max() - 2) + std::cout << "This should never print and only prevents optimizations" << std::endl; + + std::cout << "internal timer: " << timer.time() << std::endl; + std::cout << "internal polygons: " << polygons.size() << std::endl; + std::cout << "internal points: " << points.size() << std::endl; +} diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_size.py b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_size.py new file mode 100644 index 000000000000..962b10edd434 --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_size.py @@ -0,0 +1,42 @@ +from benchmark_util import * + +# KERNEL_SIMPLE_CARTESIAN_DOUBLE +# KERNEL_SIMPLE_CARTESIAN_FLOAT +# KERNEL_CARTESIAN_DOUBLE +# KERNEL_EPIC +kernel = "KERNEL_CARTESIAN_DOUBLE" + +# SCENARIO_GRID_SPHERE +# SCENARIO_IMPLICIT_SPHERE +# SCENARIO_IMPLICIT_IWP +# SCENARIO_SKULL_IMAGE +scenario = "SCENARIO_SKULL_IMAGE" + +# TAG_SEQUENTIAL +# TAG_PARALLEL +tag = "TAG_PARALLEL" + +# ALGO_MARCHING_CUBES +# ALGO_DUAL_CONTOURING +algorithm = "ALGO_DUAL_CONTOURING" +threads = 1 +exponent = 1.2 +min_cells = 100000 +max_cells = 100000000 + +build(scenario, kernel, algorithm, tag) + +data = [] + +c = min_cells +while c < max_cells: + n = int(c ** (1.0 / 3.0)) + + res = execute(n, threads, 5) + data.append([scenario, kernel, algorithm, tag, threads, int(c), res["time"], res["polygons"], res["points"]]) + + c *= exponent + +df = pd.DataFrame(data, columns=["scenario", "kernel", "algorithm", "tag", "threads", "cells", "time", "polygons", "points"]) + +df.to_csv("benchmark_size.csv") diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_threads.py b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_threads.py new file mode 100644 index 000000000000..e53c26236ed1 --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_threads.py @@ -0,0 +1,23 @@ +from benchmark_util import * + +scenario = "SCENARIO_IMPLICIT_IWP" +kernel = "KERNEL_SIMPLE_CARTESIAN_DOUBLE" +algorithm = "ALGO_MARCHING_CUBES" +tag = "TAG_PARALLEL" +min_threads = 1 +max_threads = 26 +n = 500 +cells = n ** 3 + +build(scenario, kernel, algorithm, tag) + +data = [] + +for t in range(min_threads, max_threads + 1): + res = execute(n, t, times=5) + + data.append([scenario, kernel, algorithm, tag, t, cells, res["time"], res["bandwidth"]]) + +df = pd.DataFrame(data, columns=["scenario", "kernel", "algorithm", "tag", "threads", "cells", "time", "bandwidth"]) + +df.to_csv("benchmark_threads.csv") diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_util.py b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_util.py new file mode 100644 index 000000000000..8bdc615aca0f --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/benchmark_util.py @@ -0,0 +1,73 @@ +import os +import sys +import re +import subprocess +import pandas as pd + +def print_stream(stream): + while True: + line = stream.readline() + if not line: + break + print(line, end="") + +def run(cmd, output=True): + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + exit_code = process.wait() + if output: + print_stream(process.stdout) + if exit_code != 0: + print_stream(process.stderr) + sys.exit(exit_code) + return process + +def build(scenario, kernel, algorithm, tag): + run(["cmake", "-E", "make_directory", "build"]) + run(["cmake", "-B", "build", "-DCMAKE_BUILD_TYPE=Release", "-DSCENARIO=" + scenario, "-DKERNEL=" + kernel, "-DALGO=" + algorithm, "-DTAG=" + tag, "-DCGAL_DIR=../../../"]) + run(["make", "-C", "build"]) + +def execute(n, threads, times=1): + measurements = {"time" : 0, "polygons" : 0, "points" : 0, "bandwidth" : 0, "transfer" : 0, "performance" : 0, "clock" : 0, "intensity" : 0} + + for i in range(times): + process = run(["likwid-perfctr", "-g", "MEM_DP", "-C", "S0:0-" + str(threads - 1), "./build/benchmark", "-N", str(n)], False) + + for line in process.stdout.readlines(): + print(line, end='') + + m = re.search(r'internal timer:\s*(\d+)', line) + if m is not None: + measurements["time"] += int(m.group(1)) + + m = re.search(r'internal polygons:\s*(\d+)', line) + if m is not None: + measurements["polygons"] += int(m.group(1)) + + m = re.search(r'internal points:\s*(\d+)', line) + if m is not None: + measurements["points"] += int(m.group(1)) + + m = re.search(r'Memory bandwidth.*\s+(\d+(\.\d+)?) \|\s*$', line) + if m is not None: + measurements["bandwidth"] += float(m.group(1)) + + m = re.search(r'Memory data volume.*\s+(\d+(\.\d+)?) \|\s*$', line) + if m is not None: + measurements["transfer"] += float(m.group(1)) + + m = re.search(r'DP.*\s+(\d+(\.\d+)?) \|\s*$', line) + if m is not None: + measurements["performance"] += float(m.group(1)) + + m = re.search(r'Clock.*\s+(\d+(\.\d+)?) \|\s*$', line) + if m is not None: + measurements["clock"] += float(m.group(1)) + + m = re.search(r'Operational intensity.*\s+(\d+(\.\d+)?) \|\s*$', line) + if m is not None: + measurements["intensity"] += float(m.group(1)) + + for item in measurements.items(): + measurements[item[0]] = item[1] / times + + return measurements \ No newline at end of file diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/contouring_seq_vs_parallel_image.cpp b/Isosurfacing_3/benchmark/Isosurfacing_3/contouring_seq_vs_parallel_image.cpp new file mode 100644 index 000000000000..8085431820f4 --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/contouring_seq_vs_parallel_image.cpp @@ -0,0 +1,156 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Interpolated_discrete_values_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +int main(int argc, char** argv) +{ + const std::string fname = (argc > 1) ? argv[1] : CGAL::data_file_path("images/skull_2.9.inr"); + const FT isovalue = (argc > 2) ? std::stod(argv[2]) : - 2.9; + + // load volumetric image from a file + CGAL::Image_3 image; + if(!image.read(fname)) + { + std::cerr << "Error: Cannot read image file " << fname << std::endl; + return EXIT_FAILURE; + } + + // convert image to a Cartesian grid + Grid grid; + Values values { grid }; + if(!IS::IO::convert_image_to_grid(image, grid, values)) + { + std::cerr << "Error: Cannot convert image to Cartesian grid" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + Gradients gradients { values, step }; + + const bool triangulate_faces = false; + + // DC sequential + { + using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + Domain domain { grid, values, gradients }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Sequential)" << std::endl; + IS::dual_contouring( + domain, isovalue, points, triangles, CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_image_sequential.off", points, triangles); + } + + // DC parallel + { + using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + Domain domain { grid, values, gradients }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Parallel)" << std::endl; + IS::dual_contouring( + domain, isovalue, points, triangles, CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_image_parallel.off", points, triangles); + } + + // MC Sequential + { + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + Domain domain { grid, values }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Marching Cubes (Sequential)" << std::endl; + IS::marching_cubes(domain, isovalue, points, triangles); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_image_sequential.off", points, triangles); + } + + // MC parallel + { + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + Domain domain { grid, values }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Marching Cubes (Parallel)" << std::endl; + IS::marching_cubes(domain, isovalue, points, triangles); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_image_parallel.off", points, triangles); + } + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/contouring_seq_vs_parallel_implicit.cpp b/Isosurfacing_3/benchmark/Isosurfacing_3/contouring_seq_vs_parallel_implicit.cpp new file mode 100644 index 000000000000..b5948907efe5 --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/contouring_seq_vs_parallel_implicit.cpp @@ -0,0 +1,208 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +auto implicit_function = [](const Point& q) -> FT +{ + auto cyl = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,0,1), 0.5, q); }; + auto cube = [](const Point& q) { return IS::Shapes::box(Point(-0.5,-0.5,-0.5), Point(0.5,0.5,0.5), q); }; + auto cyl_and_cube = [&](const Point& q) { return IS::Shapes::shape_union(cyl, cube, q); }; + + auto sphere = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0.5), 0.75, q); }; + return IS::Shapes::shape_difference(cyl_and_cube, sphere, q); +}; + +int main(int argc, char** argv) +{ + int num_threads = tbb::this_task_arena::max_concurrency(); + std::cout << "Number of TBB threads: " << num_threads << std::endl; + + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.; + std::cout << "Isovalue: " << isovalue << std::endl; + + // create bounding box and grid + const CGAL::Bbox_3 bbox = {-2., -2., -2., 2., 2., 2.}; + Grid grid { bbox, CGAL::make_array(500, 500, 500) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + // fill up values and gradients + Values values { implicit_function, grid }; + + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + Gradients gradients { values, step }; + + const bool triangulate_faces = false; + + // DC parallel + { + using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + Domain domain { grid, values, gradients }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Parallel)" << std::endl; + IS::dual_contouring( + domain, isovalue, points, triangles, CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_implicit_parallel.off", points, triangles); + } + + // DC sequential + { + using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + Domain domain { grid, values, gradients }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Sequential)" << std::endl; + IS::dual_contouring( + domain, isovalue, points, triangles, CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_implicit_sequential.off", points, triangles); + } + + // TMC parallel + { + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + Domain domain { grid, values }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- TC Marching Cubes (Parallel)" << std::endl; + IS::marching_cubes(domain, isovalue, points, triangles); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("toco_marching_cubes_implicit_parallel.off", points, triangles); + } + + // TMC Sequential + { + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + Domain domain { grid, values }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- TC Marching Cubes (Sequential)" << std::endl; + IS::marching_cubes(domain, isovalue, points, triangles); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("toco_marching_cubes_implicit_sequential.off", points, triangles); + } + + // MC parallel + { + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + Domain domain { grid, values }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Marching Cubes (Parallel)" << std::endl; + IS::marching_cubes(domain, isovalue, points, triangles, + CGAL::parameters::use_topologically_correct_marching_cubes(false)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_implicit_parallel.off", points, triangles); + } + + // MC Sequential + { + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + Domain domain { grid, values }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Marching Cubes (Sequential)" << std::endl; + IS::marching_cubes(domain, isovalue, points, triangles, + CGAL::parameters::use_topologically_correct_marching_cubes(false)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_implicit_sequential.off", points, triangles); + } + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/graphs.py b/Isosurfacing_3/benchmark/Isosurfacing_3/graphs.py new file mode 100644 index 000000000000..c2794f76cc9b --- /dev/null +++ b/Isosurfacing_3/benchmark/Isosurfacing_3/graphs.py @@ -0,0 +1,87 @@ + +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + + +def save_svg(file): + fig = plt.gcf() + fig.set_size_inches((10, 5), forward=False) + plt.savefig(file, bbox_inches="tight") + + +def add_threads_graph(data, label): + x = data["threads"] + y = data["cells"] / data["time"] / 10 ** 3 + plt.plot(x, y, label=label) + plt.legend() + + +def add_size_graph(data, label): + x = data["cells"] + y = data["cells"] / data["time"] / 10 ** 3 + plt.plot(x, y, label=label) + plt.legend() + + +def add_triangle_graph(data, label, factor): + x = data["cells"] + y = data["polygons"] * factor + plt.plot(x, y, label=label) + plt.legend() + + +def plot_graph(file, name, log, ylabel, xlabel): + plt.title(name) + plt.xlabel(xlabel) + plt.ylabel(ylabel) + if log: + plt.xscale("log") + plt.gca().yaxis.grid(color='#cccccc') + plt.gca().xaxis.grid(color='#cccccc') + plt.ylim(ymin=0) + save_svg(file) + plt.show() + + +latex_export = True +if latex_export: + #plt.rcParams["svg.fonttype"] = "none" + plt.rcParams["axes.unicode_minus"] = False + plt.rcParams['font.size'] = "17" + + +data = pd.read_csv("implicit_iwp_mc_1.csv") +add_threads_graph(data, "MC") + +#data = pd.read_csv("threads_grid.csv") +#add_threads_graph(data, "grid") + +xt = np.arange(0, min(max(data["threads"]), 9) + 0.1, 2) +if max(data["threads"]) > 10: + print("more") + xt = np.concatenate((xt, np.arange(10, max(data["threads"]) + 0.1, 2))) + +yt = np.arange(0, 20 + 0.1, 2) + +print(xt) +plt.xticks(xt) +plt.yticks(yt) +plot_graph("perf_threads.svg", "", False, "performance [10^3 cubes/s]", "cores") + + +data = pd.read_csv("size_iwp_mc.csv") +add_size_graph(data, "MC") + +data = pd.read_csv("size_iwp_dc.csv") +add_size_graph(data, "DC") + +plot_graph("perf_size.svg", "", False, "performance [10^3 cubes/s]", "cells") + +data = pd.read_csv("size_iwp_mc.csv") +add_triangle_graph(data, "MC", 1) + +data = pd.read_csv("size_iwp_dc.csv") +add_triangle_graph(data, "DC", 1) + +plot_graph("triangles_size.svg", "", False, "triangles", "cells") diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingDomainWithGradient_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingDomainWithGradient_3.h new file mode 100644 index 000000000000..6091f27fef9a --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingDomainWithGradient_3.h @@ -0,0 +1,52 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +\cgalRefines{IsosurfacingDomain_3} + +\brief The concept `IsosurfacingDomainWithGradient_3` describes the set of requirements to be +fulfilled by any class used as input data for some isosurfacing algorithms. + +This concept refines `IsosurfacingDomain_3` to add a `gradient()` function which is used +by isosurfacing domains to query the domain for the gradient of the values field +at a 3D query point (not necessarily a vertex) in space. + +\cgalHasModelsBegin +\cgalHasModels{CGAL::Isosurfacing::Dual_contouring_domain_3} +\cgalHasModelsEnd +*/ +class IsosurfacingDomainWithGradient_3 +{ +public: + /// \name Types + /// @{ + + /*! + The geometric traits type. + Must be a model of `IsosurfacingTraits_3`. + */ + typedef unspecified_type Geom_traits; + + /*! + The 3D point type. + */ + typedef Geom_traits::Point_3 Point_3; + + /*! + The 3D vector type. + */ + typedef Geom_traits::Vector_3 Vector_3; + + /// @} + + /// \name Operations + /// @{ + + /*! + returns the gradient at the position `p` + */ + Vector_3 gradient(const Point_3& p) const; + + /// @} +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingDomain_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingDomain_3.h new file mode 100644 index 000000000000..fdf0572bcbd0 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingDomain_3.h @@ -0,0 +1,185 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +\brief The concept `IsosurfacingDomain_3` describes the set of requirements to be +fulfilled by any class used as input data for isosurfacing algorithms. + +A model of the concept `IsosurfacingDomain_3` provides a partition of the Euclidean space in cells, +and a scalar field defined over the whole partition. +The isosurfacing algorithms traverse these cells and query the domain class +at the vertices of each cell, using the functions `point()` and `value()`. + +\cgalHasModelsBegin +\cgalHasModels{CGAL::Isosurfacing::Marching_cubes_domain_3} +\cgalHasModels{CGAL::Isosurfacing::Dual_contouring_domain_3} +\cgalHasModelsEnd + +\sa `IsosurfacingDomainWithGradient_3` +*/ +class IsosurfacingDomain_3 +{ +public: + /// \name Types + /// @{ + + /*! + The geometric traits type. + Must be a model of `IsosurfacingTraits_3`. + */ + typedef unspecified_type Geom_traits; + + /*! + The scalar type. + */ + typedef Geom_traits::FT FT; + + /*! + The 3D point type. + */ + typedef Geom_traits::Point_3 Point_3; + + /*! + A descriptor that uniquely identifies a vertex. + Must be a model of the concepts `Descriptor` and `Hashable`. + */ + typedef unspecified_type vertex_descriptor; + + /*! + A descriptor that uniquely identifies an edge. + Must be a model of the concept `Descriptor` and `Hashable`. + */ + typedef unspecified_type edge_descriptor; + + /*! + A descriptor that uniquely identifies a cell. + Must be a model of the concepts `Descriptor` and `Hashable`. + */ + typedef unspecified_type cell_descriptor; + + /*! + A container for the two vertices of an edge. + Must be a model of the concept `RandomAccessContainer` of size `2` whose value type is `vertex_descriptor`. + */ + typedef unspecified_type Edge_vertices; + + /*! + A container for the cells incident to an edge. + Must be a model of the concept `ForwardRange` whose value type is `cell_descriptor`. + */ + typedef unspecified_type Cells_incident_to_edge; + + /*! + A container for the vertices of a cell. + Must be a model of the concept `ForwardRange` whose value type is `vertex_descriptor`. + */ + typedef unspecified_type Cell_vertices; + + /*! + A container for the edges of a cell. + Must be a model of the concept `ForwardRange` whose value type is `edge_descriptor`. + */ + typedef unspecified_type Cell_edges; + + + /// @} + + /// \name Operations + /// @{ + + /*! + returns the geometric traits. + */ + Geom_traits geom_traits(); + + /*! + returns the 3D position of the vertex `v`. + */ + Point_3 point(const vertex_descriptor& v) const; + + /*! + returns the value of the values field at the point `p`. + */ + FT value(const Point_3& p) const; + + /*! + returns the value of the values field at the vertex `v`. + */ + FT value(const vertex_descriptor& v) const; + + /*! + returns the two vertices incident to the edge `e`. + */ + Edge_vertices incident_vertices(const edge_descriptor& e) const; + + /*! + returns all the cells incident to the edge `e`, in a geometrically ordered manner around the edge. + */ + Cells_incident_to_edge incident_cells(const edge_descriptor& e) const; + + /*! + returns all the vertices of the cell `c`. + */ + Cell_vertices cell_vertices(const cell_descriptor& c) const; + + /*! + returns all the edges of the cell `c`. + */ + Cell_edges cell_edges(const cell_descriptor& c) const; + + /*! + iterates over all vertices, and calls the functor `f` on each one. + + \tparam ConcurrencyTag decides if the vertices are iterated sequentially or in parallel. + Can be either `CGAL::Sequential_tag`, `CGAL::Parallel_if_available_tag`, or `CGAL::Parallel_tag`. + \tparam Functor must implement `void operator()(const vertex_descriptor& vertex)` + + \param f the functor called on every vertex + */ + template + void for_each_vertex(Functor& f) const; + + /*! + iterates over all edges, and calls the functor `f` on each one. + + \tparam ConcurrencyTag decides if the edges are iterated sequentially or in parallel. + Can be either `CGAL::Sequential_tag`, `CGAL::Parallel_if_available_tag`, or `CGAL::Parallel_tag`. + \tparam Functor must implement `void operator()(const edge_descriptor& edge)`. + + \param f the functor called on every edge + */ + template + void for_each_edge(Functor& f) const; + + /*! + iterates over all cells, and calls the functor `f` on each one. + + \tparam ConcurrencyTag decides if the cells are iterated sequentially or in parallel. + Can be either `CGAL::Sequential_tag`, `CGAL::Parallel_if_available_tag`, or `CGAL::Parallel_tag`. + \tparam Functor must implement `void operator()(const cell_descriptor& cell)`. + + \param f the functor called on every cell + */ + template + void for_each_cell(Functor& f) const; + + /*! + Constructs the intersection - if it exists - between an edge and an isosurface. + + \param p_0 the geometric position of the first vertex of the edge + \param p_1 the geometric position of the second vertex of the edge + \param val_0 the value at the first vertex of the edge + \param val_1 the value at the second vertex of the edge + \param isovalue the isovalue defining the isosurface with which we seek an intersection + \param p the intersection point, if it exists + + \returns `true` if the intersection point exists, `false` otherwise + */ + bool construct_intersection(const Point_3& p_0, const Point_3& p_1, + const FT val_0, const FT val_1, + const FT isovalue, + Point_3& p) const; + + /// @} +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingEdgeIntersectionOracle_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingEdgeIntersectionOracle_3.h new file mode 100644 index 000000000000..4e23184d6900 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingEdgeIntersectionOracle_3.h @@ -0,0 +1,44 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +\cgalRefines{DefaultConstructible, Assignable} + +The concept `IsosurfacingEdgeIntersectionOracle_3` describes the requirements for the edge-isosurface +intersection oracle template parameter of the domain classes +`CGAL::Isosurfacing::Marching_cubes_domain_3` and +`CGAL::Isosurfacing::Dual_contouring_domain_3`. + +\cgalHasModelsBegin +\cgalHasModels{CGAL::Isosurfacing::Dichotomy_edge_intersection} +\cgalHasModels{CGAL::Isosurfacing::Linear_interpolation_edge_intersection} +\cgalHasModelsEnd +*/ +class IsosurfacingEdgeIntersectionOracle_3 +{ +public: + /*! + * \brief computes the intersection point between an edge and the isosurface. + * + * \tparam Domain must be a model of `IsosurfacingDomain_3` + * + * \param p_0 the geometric position of the first vertex of the edge + * \param p_1 the geometric position of the second vertex of the edge + * \param val_0 the value at the first vertex of the edge + * \param val_1 the value at the second vertex of the edge + * \param domain the isosurfacing domain + * \param isovalue the isovalue defining the isosurface with which we seek an intersection + * \param p the intersection point, if it exists + * + * \return `true` if the intersection point exists, `false` otherwise + */ + template + bool operator()(const typename Domain::Geom_traits::Point_3& p_0, + const typename Domain::Geom_traits::Point_3& p_1, + const typename Domain::Geom_traits::FT val_0, + const typename Domain::Geom_traits::FT val_1, + const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + typename Domain::Geom_traits::Point_3& p) const; +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingGradientField_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingGradientField_3.h new file mode 100644 index 000000000000..f17108756eac --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingGradientField_3.h @@ -0,0 +1,38 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +The concept `IsosurfacingGradientField_3` describes the set of requirements to be fulfilled +by the gradient field template parameter of the domain class `CGAL::Isosurfacing::Dual_contouring_domain_3`. + +Gradient fields must be continuous and defined over the geometric span of the +space partitioning data structure (also known as "partition") being used. + +\cgalHasModelsBegin +\cgalHasModels{CGAL::Isosurfacing::Gradient_function_3} +\cgalHasModels{CGAL::Isosurfacing::Finite_difference_gradient_3} +\cgalHasModels{CGAL::Isosurfacing::Interpolated_discrete_gradients_3} +\cgalHasModelsEnd + +\sa `IsosurfacingTraits_3` +\sa `IsosurfacingValueField_3` +*/ +class IsosurfacingGradientField_3 +{ +public: + /*! + * The 3D point type. + */ + typedef unspecified_type Point_3; + + /*! + * The 3D vector type. + */ + typedef unspecified_type Vector_3; + + /*! + returns the gradient at the position `p` + */ + Vector_3 operator()(const Point_3& p) const; +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingInterpolationScheme_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingInterpolationScheme_3.h new file mode 100644 index 000000000000..c7d34860dd7a --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingInterpolationScheme_3.h @@ -0,0 +1,58 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +\cgalRefines{DefaultConstructible, Assignable} + +The concept `IsosurfacingInterpolationScheme_3` describes the set of requirements to be fulfilled +by the interpolation scheme template parameter of the domain classes `CGAL::Isosurfacing::Interpolated_discrete_values_3` and `CGAL::Isosurfacing::Interpolated_discrete_gradients_3`. + +\cgalHasModelsBegin +\cgalHasModels{CGAL::Isosurfacing::Trilinear_interpolation} +\cgalHasModelsEnd +*/ +class IsosurfacingInterpolationScheme_3 +{ +public: + /*! + * The geometric traits type. + * Must be a model of `IsosurfacingTraits_3`, equal to that of the grid. + */ + typedef unspecified_type Geom_traits; + + /*! + * The scalar type. + */ + typedef unspecified_type FT; + + /*! + * The 3D point type. + */ + typedef unspecified_type Point_3; + + /*! + * The 3D vector type. + */ + typedef unspecified_type Vector_3; + + /*! + * \brief interpolates the value of the value field at the point `p` using values defined by `vr` + * over the grid `g`. + * + * \tparam Grid must be `CGAL::Isosurfacing::Cartesian_grid_3` + * \tparam ValueRange must be a model of `RandomAccessRange` with `FT` as value type + */ + template + FT interpolate_values(const Point_3& p, const Grid& g, const ValueRange& vr) const; + + /*! + * \brief interpolates the gradient of the gradient field at the point `p` + * using gradients defined by `gr` over the grid `g`. + * + * \tparam Grid must be `CGAL::Isosurfacing::Cartesian_grid_3` + * \tparam GradientRange must be a model of `RandomAccessRange` with `Vector_3` as value type + */ + template + Vector_3 interpolate_gradients(const Point_3& p, const Grid& g, const GradientRange& gr) const; +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingPartition_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingPartition_3.h new file mode 100644 index 000000000000..6ddedee6c0fb --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingPartition_3.h @@ -0,0 +1,33 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +The concept `IsosurfacingPartition_3` describes the set of requirements to be fulfilled +by the partition template parameter of the domain classes `CGAL::Isosurfacing::Marching_cubes_domain_3` +and `CGAL::Isosurfacing::Dual_contouring_domain_3`. + +A 3D partition is a space partitioning data structure that provides a discrete representation +of a subset of 3D space. + +A partial specialization of `CGAL::Isosurfacing::partition_traits` must be provided for all models. +This is similar to graph traits in \ref PkgBGL. + +\cgalHasModelsBegin +\cgalHasModels{`CGAL::Isosurfacing::Cartesian_grid_3`} +\cgalHasModelsEnd +*/ +class IsosurfacingPartition_3 +{ +public: + /*! + * The geometric traits type. + * Must be a model of `IsosurfacingTraits_3`. + */ + typedef unspecified_type Geom_traits; + + /*! + * \returns the geometric traits. + */ + Geom_traits geom_traits(); +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingTraits_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingTraits_3.h new file mode 100644 index 000000000000..d1aec7ee26c8 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingTraits_3.h @@ -0,0 +1,165 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +\cgalRefines{DefaultConstructible, Assignable} + +The concept `IsosurfacingTraits_3` describes the set of requirements to be +fulfilled by the traits class of a model of `IsosurfacingDomain_3`. + +\cgalHasModelsBegin +\cgalHasModelsBare{All models of the concept `Kernel`} +\cgalHasModelsEnd +*/ +class IsosurfacingTraits_3 +{ +public: + /// \name Types + /// @{ + + /*! + The scalar type. + Must be a model of `FieldNumberType`. + */ + typedef unspecified_type FT; + + /*! + The 3D point type. + Must be a model of `Kernel::Point_3`. + */ + typedef unspecified_type Point_3; + + /*! + The 3D vector type. + Must be a model of `Kernel::Vector_3`. + */ + typedef unspecified_type Vector_3; + + /*! + The 3D cuboid type. + Must be a model of `Kernel::IsoCuboid_3` + */ + typedef unspecified_type IsoCuboid_3; + + /*! + A construction object that must provide the function operators: + + `FT operator()(Point_3 p)` + + and + + `FT operator()(Vector_3 p)` + + which return the \f$ x\f$-coordinate of the point and the vector, respectively. + */ + typedef unspecified_type Compute_x_3; + + /*! + A construction object that must provide the function operators: + + `FT operator()(Point_3 p)` + + and + + `FT operator()(Vector_3 p)` + + which return the \f$ y\f$-coordinate of the point and the vector, respectively. + */ + typedef unspecified_type Compute_y_3; + + /*! + A construction object that must provide the function operators: + + `FT operator()(Point_3 p)` + + and + + `FT operator()(Vector_3 p)` + + which return the \f$ z\f$-coordinate of the point and the vector, respectively. + */ + typedef unspecified_type Compute_z_3; + + /*! + A construction object that must provide the function operator: + + `Point_3 operator()(FT x, FT y, FT z)` + + which constructs a 3D point from its three coordinates. + */ + typedef unspecified_type Construct_point_3; + + /*! + A construction object that must provide the function operator: + + `Vector_3 operator()(FT x, FT y, FT z)` + + which constructs a 3D vector from its three coordinates. + */ + typedef unspecified_type Construct_vector_3; + + /*! + A construction object that must provide the function operator: + + `IsoCuboid_3 operator()(Point_3 p, Point_3 q)` + + which constructs an iso-oriented cuboid with diagonal opposite vertices `p` and `q` + such that `p` is the lexicographically smallest point in the cuboid. + */ + typedef unspecified_type Construct_iso_cuboid_3; + + /*! + A construction object that must provide the function operator: + + `Point_3 operator()(IsoCuboid_3 c, int i)` + + which returns the i-th vertex of an iso-cuboid `c`. See `Kernel::ConstructVertex_3` + for the order of vertices. + */ + typedef unspecified_type Construct_vertex_3; + + /// @} + + /// \name Operations + /// The following functions give access to the predicate and construction objects: + /// @{ + + /*! + + */ + Compute_x_3 compute_x_3_object(); + + /*! + + */ + Compute_y_3 compute_y_3_object(); + + /*! + + */ + Compute_z_3 compute_z_3_object(); + + /*! + + */ + Construct_point_3 construct_point_3_object(); + + /*! + + */ + Construct_vector_3 construct_vector_3_object(); + + /*! + + */ + Construct_iso_cuboid_3 construct_iso_cuboid_3_object(); + + /*! + + */ + Construct_vertex_3 construct_vertex_3_object(); + + /// @} + +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingValueField_3.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingValueField_3.h new file mode 100644 index 000000000000..39f9eda0a59b --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingValueField_3.h @@ -0,0 +1,47 @@ +/*! +\ingroup PkgIsosurfacing3Concepts + +\cgalConcept + +The concept `IsosurfacingValueField_3` describes the set of requirements to be fulfilled +by the value field template parameter of the domain classes `CGAL::Isosurfacing::Marching_cubes_domain_3` and `CGAL::Isosurfacing::Dual_contouring_domain_3`. + +Value fields must be continuous and defined over the geometric span of the +space partitioning data structure (also known as "partition") being used. + +\cgalHasModelsBegin +\cgalHasModels{CGAL::Isosurfacing::Value_function_3} +\cgalHasModels{CGAL::Isosurfacing::Interpolated_discrete_values_3} +\cgalHasModelsEnd + +\sa `IsosurfacingTraits_3` +\sa `IsosurfacingGradientField_3` +*/ +class IsosurfacingValueField_3 +{ +public: + /*! + * The scalar type. + */ + typedef unspecified_type FT; + + /*! + * The 3D point type. + */ + typedef unspecified_type Point_3; + + /*! + * A descriptor that uniquely identifies a vertex (see `IsosurfacingPartition_3`). + */ + typedef unspecified_type vertex_descriptor; + + /*! + returns the value of the value field at the point `p`. + */ + FT operator()(const Point_3& p); + + /*! + returns the value of the value field at the vertex `v`. + */ + FT operator()(const vertex_descriptor& v); +}; diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Concepts/partition_traits.h b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/partition_traits.h new file mode 100644 index 000000000000..e6d0941c8d6b --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/partition_traits.h @@ -0,0 +1,126 @@ +namespace CGAL { +namespace Isosurfacing { + +/*! + * \ingroup PkgIsosurfacing3Concepts + * + * \relates IsosurfacingPartition_3 + * + * The class `partition_traits` is the API compatibility layer between a model of `IsosurfacingPartition_3` + * and the isosurfacing domain classes `CGAL::Isosurfacing::Marching_cubes_domain_3` and + * `CGAL::Isosurfacing::Dual_contouring_domain_3`. + * + * For each model of `IsosurfacingPartition_3`, a partial specialization of `partition_traits` must be provided, + * providing the types and functions listed below. Such a partial specialization is provided + * for `CGAL::Isosurfacing::Cartesian_grid_3`. + */ +template +class partition_traits +{ +public: + /*! + * A vertex descriptor corresponds to a unique vertex in an abstract partition instance. + */ + typedef unspecified_type vertex_descriptor; + + /*! + * An edge descriptor corresponds to a unique edge in an abstract partition instance. + */ + typedef unspecified_type edge_descriptor; + + /*! + * A cell descriptor corresponds to a unique edge in an abstract partition instance. + */ + typedef unspecified_type cell_descriptor; + + /*! + * A container for the two vertices of an edge. + * Must be a model of `RandomAccessContainer` whose `value_type` must be `vertex_descriptor`. + */ + typedef unspecified_type Edge_vertices; + + /*! + * A container for the cells incident to an edge. + * Must be a model of `ForwardRange` whose `value_type` must be `cell_descriptor`. + */ + typedef unspecified_type Cells_incident_to_edge; + + /*! + * A container for the vertices of a cell. + * Must be a model of `ForwardRange` whose `value_type` must be `vertex_descriptor`. + */ + typedef unspecified_type Cell_vertices; + + /*! + * A container for the edges of a cell. + * Must be a model of `ForwardRange` whose `value_type` must be `edge_descriptor`. + */ + typedef unspecified_type Cell_edges; + + /*! + * \returns the 3D position of the vertex `v`. + */ + static Point_3 point(const vertex_descriptor& v, const IsosurfacingPartition_3& partition); + + /*! + * \returns the two vertices incident to the edge `e`. + */ + static Edge_vertices incident_vertices(const edge_descriptor& e, const IsosurfacingPartition_3& partition); + + /*! + * \returns all the cells incident to the edge `e`, in a geometrically ordered manner around the edge. + */ + static Cells_incident_to_edge incident_cells(const edge_descriptor& e, const IsosurfacingPartition_3& partition); + + /*! + * \returns all the vertices of the cell `c`. + */ + static Cell_vertices cell_vertices(const cell_descriptor& c, const IsosurfacingPartition_3& partition); + + /*! + * \returns all the edges of the cell `c`. + */ + static Cell_edges cell_edges(const cell_descriptor& c, const IsosurfacingPartition_3& partition); + + /*! + * iterates over all vertices, and calls the functor `f` on each one. + * + * \tparam ConcurrencyTag decides if the vertices are iterated sequentially or in parallel. + * Can be either `CGAL::Sequential_tag`, `CGAL::Parallel_if_available_tag`, or `CGAL::Parallel_tag`. + * \tparam Functor must implement `void operator()(const vertex_descriptor& vertex)` + * + * \param f the functor called on every vertex + * \param partition the partition whose vertices are being iterated over + */ + template + static void for_each_vertex(Functor& f, const IsosurfacingPartition_3& partition); + + /*! + * iterates over all edges, and calls the functor `f` on each one. + * + * \tparam ConcurrencyTag decides if the edges are iterated sequentially or in parallel. + * Can be either `CGAL::Sequential_tag`, `CGAL::Parallel_if_available_tag`, or `CGAL::Parallel_tag`. + * \tparam Functor must implement `void operator()(const edge_descriptor& edge)`. + * + * \param f the functor called on every edge + * \param partition the partition whose edges are being iterated over + */ + template + static void for_each_edge(Functor& f, const IsosurfacingPartition_3& partition); + + /*! + * iterates over all cells, and calls the functor `f` on each one. + * + * \tparam ConcurrencyTag decides if the cells are iterated sequentially or in parallel. + * Can be either `CGAL::Sequential_tag`, `CGAL::Parallel_if_available_tag`, or `CGAL::Parallel_tag`. + * \tparam Functor must implement `void operator()(const cell_descriptor& cell)`. + * + * \param f the functor called on every cell + * \param partition the partition whose cells are being iterated over + */ + template + static void for_each_cell(Functor& f, const IsosurfacingPartition_3& partition); +}; + +} // namespace Isosurfacing +} // namespace CGAL diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Doxyfile.in b/Isosurfacing_3/doc/Isosurfacing_3/Doxyfile.in new file mode 100644 index 000000000000..edd2ed8a9ef6 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Doxyfile.in @@ -0,0 +1,11 @@ +@INCLUDE = ${CGAL_DOC_PACKAGE_DEFAULTS} + +PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - 3D Isosurfacing" + +HTML_EXTRA_FILES = ${CGAL_PACKAGE_DOC_DIR}/fig/isosurfacing_teaser.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/MC_cases.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/MC_DC.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/MC_DC_open.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/DC.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/isosurfacing_inrimage.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/MC_DC_performance.png diff --git a/Isosurfacing_3/doc/Isosurfacing_3/Isosurfacing_3.txt b/Isosurfacing_3/doc/Isosurfacing_3/Isosurfacing_3.txt new file mode 100644 index 000000000000..39ecf725370d --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Isosurfacing_3.txt @@ -0,0 +1,327 @@ +namespace CGAL { +/*! + +\mainpage User Manual +\anchor Chapter_Isosurfacing3 + +\cgalAutoToc +\author Mael Rouxel-Labbé, Julian Stahl, Daniel Zint, and Pierre Alliez + +\cgalFigureAnchor{IsosurfacingTeaser} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingTeaser} +Generating a surface from a 3D gray level image using Marching Cubes (3D input image: qim.dk) +\cgalFigureCaptionEnd + +\section SecIsoSurfacingIntroduction Introduction + +Given a scalar field, an isosurface is defined as the locus of points where the scalar field +has a given constant value; in other words, it is a level set. +This constant value is referred to as "isovalue", and, for well-behaved scalar fields, +the level set forms a surface. +"Isosurfacing", also known as "isosurface extraction" or "contouring", is the process of constructing +the isosurface corresponding to a given scalar field and isovalue. +This process is often needed for volume visualization and the simulation of physical phenomena. + +This \cgal package provides methods to extract isosurfaces from 3D scalar fields. +These contouring techniques rely on the discretization of the 3D space to construct +an approximate representation of the isosurface. +The 3D scalar field can be described through various representations: an implicit function, +an interpolated set of discrete sampling values, etc. (see \ref SecIsosurfacingExamples). The isovalue is user-defined. +The output is a polygon soup, made either of triangles or quads depending on the method, +and may consist of a single connected component, or multiple, disjoint components. +Note that due to the inherent approximate nature of these discrete methods, parts of the "true" +isosurface may be missing from the output, and the output may contain artifacts that are not present +in the true isosurface. + +\section SecIsosurfacingMethods Isosurfacing Methods + +The scientific literature abounds with algorithms for extracting isosurfaces, each coming +with different properties for the output and requirements for the input \cgalCite{cgal:dljjaw-sisp-15}. +This package offers the following methods + +
    +
  • \ref SubSecMarchingCubes : a simple and efficient method that generates a triangle mesh, with almost no guarantees.
  • +
  • \ref SubSecTMC : an extension to Marching Cubes that provides additional guarantees for the output.
  • +
  • \ref SubSecDualContouring : a method that generates a polygon mesh, with focus on sharp features recovery.
  • +
+ +\subsection SubSecMarchingCubes Marching Cubes (MC) + +Marching Cubes (MC) \cgalCite{LC87} uses a volumetric grid, i.e., a 3D iso-cuboid partitioned into hexahedral cells. +All cells of the grid are processed individually using values of the scalar field at the grid corners. +Each cell corner is assigned a sign (+/-) to indicate whether its scalar field value is above +or below the user-defined isovalue. +A vertex is created for each grid edge with a sign change, i.e., where the edge intersects the isosurface. +More specifically, the vertex position is computed via linear interpolation of +the scalar field values evaluated at the cell corners forming the edge. +These vertices are connected to form triangles within the cell, depending on the configuration +of signs at the cell corners. \cgalFigureRef{IsosurfacingMCCases} illustrates the configurations in 2D. +In 3D, there is no less than 33 cases (not shown) \cgalCite{cgal:c-mcctci-95}. + +\cgalFigureAnchor{IsosurfacingMCCases} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingMCCases} +Examples of some configurations for 2D Marching Cubes. +\cgalFigureCaptionEnd + +The implementation within \cgal is generic in the sense that it can process any grid-like data structure +that consists of hexahedral cells. When the hexahedral grid is a conforming grid (meaning that the +intersection of two hexahedral cells is a face, and edge, or a vertex), the Marching Cubes algorithm +generates as output a surface triangle mesh that is almost always combinatorially 2-manifold, +but can also produce cracks in the surface. + +If the mesh is 2-manifold and the isosurface does not intersect the domain boundary, then the output mesh is watertight. +As the Marching Cubes algorithm uses linear interpolation of the sampled scalar field along the grid edges, +it can miss details or components that are not captured by the sampling of the scalar field. + +Compared to other meshing approaches such as Delaunay refinement, Marching Cubes is much faster, +but often tends to generate more triangles for an equivalent desired sizing field. In addition, +the quality of the elements if generally poor, with many needles / caps shaped faces. + +Furthermore, Marching Cubes does not preserve the sharp features present in the isovalue of the input scalar field +(see \cgalFigureRef{IsosurfacingMCDC}). + +\subsection SubSecTMC Topologically Correct Marching Cubes (TMC) + +Topologically Correct Marching Cubes is an extension to the Marching Cubes algorithm +which provides additional guarantees for the output \cgalCite{cgal:g-ctcmi-16}. +It generates as output a mesh that is homeomorphic to the trilinear interpolant of the input scalar +field inside each cube. This means that the output mesh can accurately represent small complex features. +For example, a tunnel of the isosurface within a single cell is topologically resolved. +To achieve this, the algorithm can insert additional vertices within cells. +Furthermore, the mesh is guaranteed to be 2-manifold and watertight, as long as the isosurface +does not intersect the domain boundaries. [and the input is 2-manifold?] + +\cgalFigureAnchor{IsosurfacingMCTMC} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingMCTMC} +MC vs TMC [todo] +\cgalFigureCaptionEnd + +\subsection SubSecDualContouring Dual Contouring (DC) + +%Dual Contouring (DC) \cgalCite{cgal:jlsw-dchd-02} is a method that does not generate vertices +on the grid edges, but within cells. Next, a face is created for each edge that intersects the isosurface +by connecting the vertices of the incident cells. For a uniform hexahedral grid, this results +into a quadrilateral surface mesh. %Dual Contouring can deal with any domain but guarantees neither +a 2-manifold nor a watertight mesh. On the other hand it generates fewer faces and higher quality +faces than Marching Cubes, in general. Finally, its main advantage over Marching Cubes is its ability +to recover sharp creases and corners. + +\cgalFigureAnchor{IsosurfacingMCDC} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingMCDC} +Comparison between a mesh of a CSG shape generated by Marching Cubes (left) and %Dual Contouring (right). +\cgalFigureCaptionEnd + +In addition to the scalar field, %Dual Contouring requires knowledge of the gradient of said scalar field. + +The \cgal implementation uses a point positioning strategy based on *Quadric Error Metrics*: for +a cell, the position of the vertex is computed by minimizing the error to the sum of the quadrics +defined at each edge-isosurface intersection. +Using this strategy, the position can in fact lie outside the cell, which is a desirable property +to improve the odds of recovering sharp features, but it might also create self-intersections. +Users can choose to constrain the placement to lie within the cell. + +By default, %Dual Contouring creates quads, but using edge-isosurface intersections, +one can "star" these quads to create four triangles. This is the default behavior in \cgal, +and it can be changed in the named parameters. + +\subsection SubSecIsosurfacingComparison Comparisons + +The following table summarizes the differences between the algorithms in terms of constraints +over the input 3D domain, the facets of the output surface mesh, and the properties +of the output surface mesh. + +
+| Algorithm | Facets | 2-Manifold | Watertight* | Topologically correct | Recovery of Sharp Features | +| ---- | ---- | ---- | ---- | ---- | ---- | + MC | Triangles | no | no | no | no | + TMC | Triangles | yes | yes | yes | no | + DC | Polygons | no | no | no | yes (not guaranteed) | +
+ +(* assuming the isosurface does not exit the specified bounding box of the input 3D domain) + +Note that the output mesh has boundaries when the isosurface intersects the domain boundaries, +regardless of the method (see \cgalFigureRef{IsosurfacingOpen}). + +\cgalFigureAnchor{IsosurfacingOpen} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingOpen} +Outputs of Marching Cubes (left) and %Dual Contouring (right) for an implicit sphere +of radius `1.1` and a domain of size `2x2x2`, both centered at the origin. +Output meshes can have boundaries when the isosurface intersects the domain boundary. +\cgalFigureCaptionEnd + +\section SecInterface Interface + +The following functions are the main entry points to the isosurfacing algorithms: +
    +
  • Marching Cubes
  • : `CGAL::Isosurfacing::marching_cubes()`, using the named parameter: `use_topologically_correct_marching_cubes` set to `false`; +
  • Topologically Correct Marching Cubes
  • : `CGAL::Isosurfacing::marching_cubes()`; +
  • %Dual Contouring
  • : `CGAL::Isosurfacing::dual_contouring()`. +
+ +These free functions share the same signature: + +\code{.cpp} +template +void ...(const Domain& domain, + const typename Domain::FT isovalue, + PointRange& points, + PolygonRange& polygons); +\endcode + +The input (space partition, scalar field, gradient field) is provided in the form of a `domain`, +see \ref SubSecIsosurfacingDomains for a complete description. + +The `isovalue` scalar parameter is the value that defines the isosurface being approximated. + +The output discrete surface is provided in the form of a polygon soup, which is stored into +two containers: `points` and `polygons`. Depending on the algorithm, the polygon soup may +store either unorganized polygons with no relationship to one another (no connectivity between them) +or polygons sharing points (the same point in adjacent polygons will be the same point in the point range). + +The isosurfacing algorithms can run either sequentially in one thread or in parallel (multithread). +The template parameter `ConcurrencyTag` is used to specify how the algorithm is executed. +To enable parallelism, \cgal must be linked with the Intel TBB library (see the CMakeLists.txt files in the examples folder). + +\subsection SubSecIsosurfacingDomains Domains + +A domain is an object that provides functions to access the partition of the 3D volume, +the scalar field, and, optionally, the gradient field at a given position. +These requirements are described through two concepts: `IsosurfacingDomain_3` and `IsosurfacingDomainWithGradient_3`. + +Two domains, `CGAL::Isosurfacing::Marching_cubes_domain_3` and `CGAL::Isosurfacing::Dual_contouring_domain_3`, +are provided as the respective default class models that fulfill the requirements of the concepts. +Both these domain models have template parameters enabling the user to customize the domain: +- Partition: this must be a class that describes the partition of the 3D volume into cells. + The most basic example of such class is `CGAL::Isosurfacing::Cartesian_grid_3`, but users + can pass their own partition, provided it meets the requirements described + by the concept `IsosurfacingPartition_3`. +- ValueField: this must be a class that provides the scalar field at the vertices of the partition. + A few classes are provided, such as `CGAL::Isosurfacing::Value_function_3` and + `CGAL::Isosurfacing::Interpolated_discrete_values_3`. Users can pass their own value class, + provided it meets the requirements described by the concept `IsosurfacingValueField_3`. +- GradientField: (`CGAL::Isosurfacing::Dual_contouring_domain_3` only) this must be a class that provides the gradient + of the scalar field at the vertices of the partition. + A few classes are provided by default, such as + `CGAL::Isosurfacing::Finite_difference_gradient_3` and + `CGAL::Isosurfacing::Interpolated_discrete_gradients_3`. + Users can pass their own gradient class, + provided it meets the requirements described + by the concept `IsosurfacingGradientField_3`. +- EdgeIntersectionOracle: this must be a class that provides a function to compute the intersection + between an edge and the isosurface. The default is linear interpolation + for `CGAL::Isosurfacing::Marching_cubes_domain_3`, and a dichotomy + for `CGAL::Isosurfacing::Dual_contouring_domain_3`. This parameter should + be adjusted depending on how your value field is defined: there is for + example no point doing a dichotomy in Dual Contouring if the value field is defined + through linear interpolation. Users can pass their own edge intersection + oracle, provided it meets the requirements described by the concept + `IsosurfacingEdgeIntersectionOracle_3`. + +\section SecPerformance Performance + +Due to their cell-based nature, the isosurfacing algorithms are well-suited for parallel execution. + +\cgalFigureAnchor{IsosurfacingPerf} +
+ +
+ +\section SecIsosurfacingExamples Examples + +The first two examples are very basic examples for Marching Cubes and %Dual Contouring. +Afterwards, the focus is shifted from the method to the type of input data, and examples +run both methods on different types of input data. + +\subsection SubSecMCExample Marching Cubes + +The following example illustrates a basic run of the Marching Cubes algorithm, and in particular +the free function to create a domain from a %Cartesian grid, and the named parameter +that enable the user to switch beetween Marching Cubes and Topologically Correct Marching Cubes. + +\cgalExample{Isosurfacing_3/marching_cubes.cpp} + +\subsection SubSecDCExample Dual Contouring + +The following example illustrates a basic run of the %Dual Contouring algorithm, and in particular +the free function to create a domain from a %Cartesian grid, and the named parameters +that enable triangulating (or not) the output, and to constrain the vertex placement within the cell. + +\cgalExample{Isosurfacing_3/dual_contouring.cpp} + +\cgalFigureAnchor{IsosurfacingDC} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingDC} +Results of the %Dual Contouring algorithm: untriangulated (left column) or triangulated (right column), +unconstrained placement (top row) or constrained placement (bottom row). +\cgalFigureCaptionEnd + +\subsection SubSecImplicitDataExample Implicit Data + +The following example shows the usage of Marching Cubes and %Dual Contouring algorithms to extract +an isosurface. The domain is an implicit domain that describes the unit sphere +by the distance to its center (set to the origin) as an implicit field. + +\cgalExample{Isosurfacing_3/contouring_implicit_data.cpp} + +\subsection SubSecDiscreteDataExample Discrete Data + +In the following example, the input data is sampled at the vertices of a grid, and interpolated. + +\cgalExample{Isosurfacing_3/contouring_discrete_data.cpp} + +\subsection SubSecImageDataExample 3D Image + +The following example shows how to load data from an `Image_3`, and generate an isosurface +from this voxel data. + +\cgalExample{Isosurfacing_3/contouring_inrimage.cpp} + +\cgalFigureAnchor{IsosurfacingDCEx} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingDCEx} +Results of the Topologically Correct Marching Cubes algorithm for different isovalues (1, 2, and 2.9) +on the skull model. +\cgalFigureCaptionEnd + +\subsection SubSecOffsetDataExample Offset Mesh + +The following example illustrates how to generate a mesh approximating a signed offset to an input +closed surface mesh. The input mesh is stored into an `AABB_tree` data structure to provide fast +distance queries. Via the `Side_of_triangle_mesh` functor, the sign of the distance field is made +negative inside the mesh. + +\cgalExample{Isosurfacing_3/contouring_mesh_offset.cpp} + +\section SecIsosurfacingHistory Design and Implementation History + +The development of this package started during the 2022 Google Summer of Code, with the contribution +of Julian Stahl, mentored by Daniel Zint and Pierre Alliez, providing a first implementation +of Marching Cubes, Topologically Correct Marching Cubes, and %Dual Contouring. Marching Cubes tables +were provided by Roberto Grosso (FAU Erlangen-Nürnberg). Mael Rouxel-Labbé worked on improving +the initial %Dual Contouring implementation, and on the first version of the package. + +*/ +} /* namespace CGAL */ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/PackageDescription.txt b/Isosurfacing_3/doc/Isosurfacing_3/PackageDescription.txt new file mode 100644 index 000000000000..20c42cc3bc09 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/PackageDescription.txt @@ -0,0 +1,101 @@ +/// \defgroup PkgIsosurfacing3Ref 3D Isosurfacing Reference +/// \defgroup PkgIsosurfacing3Concepts Concepts +/// \ingroup PkgIsosurfacing3Ref + +/// \defgroup IS_Partitions_grp Space Partitioning Data Structures +/// \ingroup PkgIsosurfacing3Ref +/// +/// This group encapsulates classes that represent a spatial discretization of space, +/// which will be the scaffolding for the construction of the isosurface. + +/// \defgroup IS_Partitions_helpers_grp Space Partitioning Data Structures Helpers +/// \ingroup IS_Partitions_grp + +/// \defgroup IS_Fields_helpers_grp Value and Gradient Fields Helpers +/// \ingroup PkgIsosurfacing3Ref +/// +/// The following classes and functions are parameters or template parameters of value and gradient fields. + +/// \defgroup IS_Fields_grp Value and Gradient Fields +/// \ingroup PkgIsosurfacing3Ref +/// +/// The following classes represent the data that defines the isosurface. + +/// \defgroup IS_Domains_grp Isosurfacing Domains +/// \ingroup PkgIsosurfacing3Ref +/// +/// This group encapsulates the classes that can be used to represent a complete domain (partition +/// and fields), to be used by the isosurfacing methods of this package. + +/// \defgroup IS_Domain_helpers_grp Isosurfacing Domain Helpers +/// \ingroup IS_Domains_grp + +/// \defgroup IS_Methods_grp Isosurfacing Methods +/// \ingroup PkgIsosurfacing3Ref + +/// \defgroup IS_IO_functions_grp I/O Functions +/// \ingroup PkgIsosurfacing3Ref + +/*! +\addtogroup PkgIsosurfacing3Ref +\cgalPkgDescriptionBegin{3D Isosurfacing,PkgIsosurfacing3} +\cgalPkgPicture{isosurfacing3_ico.png} +\cgalPkgSummaryBegin +\cgalPkgAuthor{Mael Rouxel-Labbé, Julian Stahl, Daniel Zint, and Pierre Alliez} +\cgalPkgDesc{This package implements several grid-based isosurfacing algorithms (Marching Cubes, +its topologically correct variant, and Dual Contouring) that enable generating meshes +from value and gradient fields. The methods are generic with respect to the definition +of the grid and the fields, and all methods offer parallel implementations. +The output is a polygon soup (i.e., a container of point coordinates and indexed faces).} +\cgalPkgManuals{Chapter_Isosurfacing3,PkgIsosurfacing3Ref} +\cgalPkgSummaryEnd +\cgalPkgShortInfoBegin +\cgalPkgSince{6.1} +\cgalPkgBib{cgal:sz-mc} +\cgalPkgLicense{\ref licensesGPL "GPL"} +\cgalPkgDemo{Polyhedron demo,polyhedron_3.zip} +\cgalPkgShortInfoEnd +\cgalPkgDescriptionEnd + +\cgalClassifedRefPages + +\cgalCRPSection{Concepts} +- `IsosurfacingTraits_3` +- `IsosurfacingPartition_3` +- `IsosurfacingValueField_3` +- `IsosurfacingGradientField_3` +- `IsosurfacingInterpolationScheme_3` +- `IsosurfacingEdgeIntersectionOracle_3` +- `IsosurfacingDomain_3` +- `IsosurfacingDomainWithGradient_3` + +\cgalCRPSection{Space Partitioning Data Structures} +- `CGAL::Isosurfacing::Cartesian_grid_3` + +\cgalCRPSection{Value and Gradient Fields} +- `CGAL::Isosurfacing::Value_function_3` +- `CGAL::Isosurfacing::Gradient_function_3` +- `CGAL::Isosurfacing::Finite_difference_gradient_3` +- `CGAL::Isosurfacing::Interpolated_discrete_values_3` +- `CGAL::Isosurfacing::Interpolated_discrete_gradients_3` +- `CGAL::Isosurfacing::Trilinear_interpolation` + +\cgalCRPSection{Isosurfacing Domains Helpers} +- `CGAL::Isosurfacing::Dichotomy_edge_intersection` +- `CGAL::Isosurfacing::Linear_interpolation_edge_intersection` + +\cgalCRPSection{Isosurfacing Domains} +- `CGAL::Isosurfacing::Marching_cubes_domain_3` +- `CGAL::Isosurfacing::Dual_contouring_domain_3` +- `CGAL::Isosurfacing::create_marching_cubes_domain_3()` +- `CGAL::Isosurfacing::create_dual_contouring_domain_3()` + +\cgalCRPSection{Isosurfacing Methods} + +- `CGAL::Isosurfacing::marching_cubes()` +- `CGAL::Isosurfacing::dual_contouring()` + +\cgalCRPSection{I/O} +- `CGAL::Isosurfacing::IO::convert_image_to_grid()` +- `CGAL::Isosurfacing::IO::convert_grid_to_image()` +*/ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/dependencies b/Isosurfacing_3/doc/Isosurfacing_3/dependencies new file mode 100644 index 000000000000..7c1108554d09 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/dependencies @@ -0,0 +1,10 @@ +Manual +Kernel_23 +BGL +STL_Extension +Algebraic_foundations +Circulator +Stream_support +AABB_tree +Polygon_mesh_processing +Mesh_3 diff --git a/Isosurfacing_3/doc/Isosurfacing_3/examples.txt b/Isosurfacing_3/doc/Isosurfacing_3/examples.txt new file mode 100644 index 000000000000..d6fdba91deb0 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/examples.txt @@ -0,0 +1,9 @@ +/*! +\example Isosurfacing_3/contouring_discrete_data.cpp +\example Isosurfacing_3/contouring_inrimage.cpp +\example Isosurfacing_3/contouring_implicit_data.cpp +\example Isosurfacing_3/contouring_mesh_offset.cpp +\example Isosurfacing_3/contouring_vtk_image.cpp +\example Isosurfacing_3/dual_contouring.cpp +\example Isosurfacing_3/marching_cubes.cpp +*/ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/DC.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/DC.png new file mode 100644 index 000000000000..a6ce7a256258 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/DC.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC.png new file mode 100644 index 000000000000..9fcf36cb7102 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC_open.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC_open.png new file mode 100644 index 000000000000..3985b616f1be Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC_open.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC_performance.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC_performance.png new file mode 100644 index 000000000000..e7d51cbcb647 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_DC_performance.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_cases.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_cases.png new file mode 100644 index 000000000000..f98bf21d501c Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_cases.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/cross.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/cross.png new file mode 100644 index 000000000000..59ed724936c8 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/cross.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/cross.pptx b/Isosurfacing_3/doc/Isosurfacing_3/fig/cross.pptx new file mode 100644 index 000000000000..d7bad6d16516 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/cross.pptx differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing3_ico.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing3_ico.png new file mode 100644 index 000000000000..a140ed0bdf1b Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing3_ico.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_inrimage.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_inrimage.png new file mode 100644 index 000000000000..c9e10672b802 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_inrimage.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_teaser.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_teaser.png new file mode 100644 index 000000000000..f2d88f5717e8 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_teaser.png differ diff --git a/Isosurfacing_3/doc/Isosurfacing_3/fig/iwp_dc.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/iwp_dc.png new file mode 100644 index 000000000000..35db0e294926 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/iwp_dc.png differ diff --git a/Isosurfacing_3/examples/Isosurfacing_3/CMakeLists.txt b/Isosurfacing_3/examples/Isosurfacing_3/CMakeLists.txt new file mode 100644 index 000000000000..8bafad1db22b --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/CMakeLists.txt @@ -0,0 +1,90 @@ +# Created by the script cgal_create_cmake_script +# This is the CMake script for compiling a CGAL application. + +cmake_minimum_required(VERSION 3.1...3.23) +project( Isosurfacing_3_Examples ) + +find_package(CGAL REQUIRED COMPONENTS ImageIO) + +find_package(Eigen3 3.1.0 QUIET) #(3.1.0 or greater) +include(CGAL_Eigen3_support) + +find_package(TBB QUIET) +include(CGAL_TBB_support) + +find_package(VTK QUIET COMPONENTS vtkImagingGeneral vtkIOImage vtkIOXML vtkIOXML vtkIOMINC vtkIOLegacy NO_MODULE) +if(VTK_FOUND) + if(VTK_USE_FILE) + include(${VTK_USE_FILE}) + endif() + if("${VTK_VERSION_MAJOR}" GREATER "5" OR VTK_VERSION VERSION_GREATER 5) + message(STATUS "VTK was found") + if(TARGET VTK::ImagingGeneral AND TARGET VTK::IOImage AND TARGET VTK::IOXML AND VTK::IOMINC AND TARGET VTK::IOLegacy) + set(VTK_LIBRARIES VTK::ImagingGeneral VTK::IOImage VTK::IOXML VTK::IOLegacy) + endif() + else() + message(STATUS "NOTICE: VTK version 6.0 or greater is required") + endif() +else() + message(STATUS "NOTICE: VTK was not found") +endif() + +create_single_source_cgal_program("marching_cubes.cpp") + +# undocumented +create_single_source_cgal_program("marching_cubes_strategies.cpp") + +if(TARGET CGAL::Eigen3_support) + create_single_source_cgal_program("dual_contouring.cpp") + create_single_source_cgal_program("contouring_discrete_data.cpp") + create_single_source_cgal_program("contouring_inrimage.cpp") + create_single_source_cgal_program("contouring_implicit_data.cpp") + create_single_source_cgal_program("contouring_mesh_offset.cpp") + +find_package(OpenVDB CONFIG) +if(TARGET OpenVDB::openvdb) + create_single_source_cgal_program("marching_cubes_openvdb.cpp") + target_link_libraries(marching_cubes_openvdb + PUBLIC OpenVDB::openvdb + PRIVATE OpenVDB::openvdb) +endif() + + target_link_libraries(dual_contouring PRIVATE CGAL::Eigen3_support) + target_link_libraries(contouring_discrete_data PRIVATE CGAL::Eigen3_support) + target_link_libraries(contouring_inrimage PRIVATE CGAL::Eigen3_support) + target_link_libraries(contouring_implicit_data PRIVATE CGAL::Eigen3_support) + target_link_libraries(contouring_mesh_offset PRIVATE CGAL::Eigen3_support) + target_link_libraries(marching_cubes_openvdb PRIVATE CGAL::Eigen3_support) + + if(TARGET CGAL::TBB_support) + target_link_libraries(dual_contouring PRIVATE CGAL::TBB_support) + target_link_libraries(contouring_discrete_data PRIVATE CGAL::TBB_support) + target_link_libraries(contouring_inrimage PRIVATE CGAL::TBB_support) + target_link_libraries(contouring_implicit_data PRIVATE CGAL::TBB_support) + target_link_libraries(contouring_mesh_offset PRIVATE CGAL::TBB_support) + endif() + +else() + message(STATUS "NOTICE: Some examples use Eigen, and will not be compiled.") +endif() + +if(TARGET CGAL::TBB_support) + target_link_libraries(marching_cubes PRIVATE CGAL::TBB_support) + target_link_libraries(marching_cubes_strategies PRIVATE CGAL::TBB_support) +endif() + +if(TARGET CGAL::CGAL_ImageIO) + if(TARGET CGAL::Eigen3_support) + if(VTK_FOUND AND ("${VTK_VERSION_MAJOR}" GREATER "5" OR VTK_VERSION VERSION_GREATER 5)) + create_single_source_cgal_program("contouring_vtk_image.cpp") + target_link_libraries(contouring_vtk_image PRIVATE CGAL::Eigen3_support + CGAL::CGAL_ImageIO + ${VTK_LIBRARIES}) + if(TARGET CGAL::TBB_support) + target_link_libraries(contouring_vtk_image PRIVATE CGAL::TBB_support) + endif() # TBB + endif() # VTK + endif() # Eigen +else() # ImageIO + message(STATUS "NOTICE: Some examples need the CGAL_ImageIO library, and will not be compiled.") +endif() diff --git a/Isosurfacing_3/examples/Isosurfacing_3/contouring_discrete_data.cpp b/Isosurfacing_3/examples/Isosurfacing_3/contouring_discrete_data.cpp new file mode 100644 index 000000000000..af61c6a44c87 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_discrete_data.cpp @@ -0,0 +1,129 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Interpolated_discrete_values_3; +using Gradients = CGAL::Isosurfacing::Interpolated_discrete_gradients_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +void run_marching_cubes(const Grid& grid, + const FT isovalue) +{ + using Domain = IS::Marching_cubes_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes with isovalue = " << isovalue << std::endl; + + // fill up values + Values values { grid }; + + for(std::size_t i=0; i(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_discrete.off", points, triangles); +} + +void run_dual_contouring(const Grid& grid, + const FT isovalue) +{ + using Domain = IS::Dual_contouring_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + // fill up values and gradients + Values values { grid }; + Gradients gradients { grid }; + + for(std::size_t i=0; i(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_discrete.off", points, triangles); +} + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.8; + + // create bounding box and grid + const CGAL::Bbox_3 bbox { -1., -1., -1., 1., 1., 1. }; + Grid grid { bbox, CGAL::make_array(30, 30, 30) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + run_marching_cubes(grid, isovalue); + + run_dual_contouring(grid, isovalue); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/contouring_implicit_data.cpp b/Isosurfacing_3/examples/Isosurfacing_3/contouring_implicit_data.cpp new file mode 100644 index 000000000000..fb9728dd000a --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_implicit_data.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +// using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Gradient_function_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +// --- +const FT alpha = 5.01; + +auto iwp_value = [](const Point& point) +{ + const FT x = alpha * (point.x() + FT(1.0)) * CGAL_PI; + const FT y = alpha * (point.y() + FT(1.0)) * CGAL_PI; + const FT z = alpha * (point.z() + FT(1.0)) * CGAL_PI; + return cos(x)*cos(y) + cos(y)*cos(z) + cos(z)*cos(x) - cos(x)*cos(y)*cos(z); // isovalue = 0 +}; +auto iwp_gradient = [](const Point& point) +{ + const FT x = alpha * (point.x() + FT(1.0)) * CGAL_PI; + const FT y = alpha * (point.y() + FT(1.0)) * CGAL_PI; + const FT z = alpha * (point.z() + FT(1.0)) * CGAL_PI; + + const FT gx = CGAL_PI * alpha * sin(x) * (cos(y) * (cos(z) - FT(1.0)) - cos(z)); + const FT gy = CGAL_PI * alpha * sin(y) * (cos(x) * (cos(z) - FT(1.0)) - cos(z)); + const FT gz = CGAL_PI * alpha * sin(z) * (cos(x) * (cos(y) - FT(1.0)) - cos(y)); + return Vector(gx, gy, gz); +}; + +void run_marching_cubes(const Grid& grid, + const FT isovalue) +{ + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes with isovalue = " << isovalue << std::endl; + + CGAL::Real_timer timer; + timer.start(); + + // fill up values + Values values { iwp_value, grid }; + Domain domain { grid, values }; + + // output containers + Point_range points; + Polygon_range triangles; + + // run Marching Cubes + CGAL::Isosurfacing::marching_cubes(domain, isovalue, points, triangles); + + timer.stop(); + + std::cout << "Output #vertices (MC): " << points.size() << std::endl; + std::cout << "Output #triangles (MC): " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_implicit.off", points, triangles); +} + +void run_dual_contouring(const Grid& grid, + const FT isovalue) +{ + using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + CGAL::Real_timer timer; + timer.start(); + + // fill up values and gradients + Values values { iwp_value, grid }; + Gradients gradients { iwp_gradient, grid }; + Domain domain { grid, values, gradients }; + + // output containers + Point_range points; + Polygon_range triangles; + + // run Dual Contouring + CGAL::Isosurfacing::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(true)); + + timer.stop(); + + std::cout << "Output #vertices (DC): " << points.size() << std::endl; + std::cout << "Output #triangles (DC): " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_implicit.off", points, triangles); +} + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.; + + const CGAL::Bbox_3 bbox{-1, -1, -1, 1, 1, 1}; + const FT step = 0.02; + const Vector spacing { step, step, step }; + Grid grid { bbox, spacing }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + run_marching_cubes(grid, isovalue); + + run_dual_contouring(grid, isovalue); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/contouring_inrimage.cpp b/Isosurfacing_3/examples/Isosurfacing_3/contouring_inrimage.cpp new file mode 100644 index 000000000000..25588f00182e --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_inrimage.cpp @@ -0,0 +1,116 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Interpolated_discrete_values_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +void run_marching_cubes(const Grid& grid, + const FT isovalue, + const Values& values) +{ + using Domain = IS::Marching_cubes_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes with isovalue = " << isovalue << std::endl; + + // fill up values + + // create a domain from the grid + Domain domain { grid, values }; + + // prepare collections for the output indexed soup + Point_range points; + Polygon_range triangles; + + // execute marching cubes + IS::marching_cubes(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + + // save output indexed mesh to a file, in the OFF format + CGAL::IO::write_polygon_soup("marching_cubes_inrimage.off", points, triangles); +} + +void run_dual_contouring(const Grid& grid, + const FT isovalue, + const Values& values) +{ + using Domain = IS::Dual_contouring_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + // fill up values and gradients + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; // finite difference step + Gradients gradients { values, step }; + Domain domain { grid, values, gradients }; + + Point_range points; + Polygon_range triangles; + + // run dual contouring isosurfacing + IS::dual_contouring(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_inrimage.off", points, triangles); +} + +int main(int argc, char* argv[]) +{ + const std::string fname = (argc > 1) ? argv[1] : CGAL::data_file_path("images/skull_2.9.inr"); + const FT isovalue = (argc > 2) ? std::stod(argv[2]) : - 2.9; + + // load volumetric image from a file + CGAL::Image_3 image; + if(!image.read(fname)) + { + std::cerr << "Error: Cannot read image file " << fname << std::endl; + return EXIT_FAILURE; + } + + // convert image to a Cartesian grid + Grid grid; + Values values { grid }; // 'values' keeps a reference to the grid + if(!IS::IO::convert_image_to_grid(image, grid, values)) + { + std::cerr << "Error: Cannot convert image to Cartesian grid" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + run_marching_cubes(grid, isovalue, values); + + run_dual_contouring(grid, isovalue, values); + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/contouring_mesh_offset.cpp b/Isosurfacing_3/examples/Isosurfacing_3/contouring_mesh_offset.cpp new file mode 100644 index 000000000000..5408e38b9c54 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_mesh_offset.cpp @@ -0,0 +1,177 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Mesh = CGAL::Surface_mesh; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +struct Offset_oracle +{ + using Primitive = CGAL::AABB_face_graph_triangle_primitive; + using Traits = CGAL::AABB_traits; + using Tree = CGAL::AABB_tree; + +private: + const bool is_closed; + const Tree tree; + CGAL::Side_of_triangle_mesh sotm; + +public: + Offset_oracle(const Mesh& mesh) + : is_closed(CGAL::is_closed(mesh)), tree(mesh.faces_begin(), mesh.faces_end(), mesh), sotm(mesh) + { } + + FT distance(const Point& p) const + { + const Point cp = tree.closest_point(p); + FT d = sqrt((p - cp).squared_length()); + + if(is_closed && sotm(p) == (CGAL::ON_BOUNDED_SIDE)) + d *= -1; + + return d; + } +}; + +void run_marching_cubes(const Grid& grid, + const FT offset_value, + const Offset_oracle& offset_oracle) +{ + using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes with offset value = " << offset_value << std::endl; + + // fill up values + auto mesh_distance = [&offset_oracle](const Point& p) { return offset_oracle.distance(p); }; + Values values { mesh_distance, grid }; + Domain domain { grid, values }; + + Point_range points; + Polygon_range triangles; + + // run marching cubes + std::cout << "Running Marching Cubes with isovalue = " << offset_value << std::endl; + CGAL::Isosurfacing::marching_cubes(domain, offset_value, points, triangles); + + std::cout << "Output #vertices (MC): " << points.size() << std::endl; + std::cout << "Output #triangles (MC): " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_offsets.off", points, triangles); +} + +void run_dual_contouring(const Grid& grid, + const FT offset_value, + const Offset_oracle& offset_oracle) +{ + using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring with offset value = " << offset_value << std::endl; + + // fill up values and gradients + auto mesh_distance = [&offset_oracle](const Point& p) { return offset_oracle.distance(p); }; + + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + + Values values { mesh_distance, grid }; + Gradients gradients { values, step }; + Domain domain { grid, values, gradients }; + + // output containers + Point_range points; + Polygon_range triangles; + + // run dual contouring + std::cout << "Running Dual Contouring with isovalue = " << offset_value << std::endl; + CGAL::Isosurfacing::dual_contouring( + domain, offset_value, points, triangles, + CGAL::parameters::do_not_triangulate_faces(true)); + + std::cout << "Output #vertices (DC): " << points.size() << std::endl; + std::cout << "Output #triangles (DC): " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_mesh_offset.off", points, triangles); +} + +int main(int argc, char** argv) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/cross.off"); + const FT offset_value = (argc > 2) ? std::stod(argv[2]) : 0.2; + + if(offset_value < 0) + { + std::cerr << "Offset value must be positive" << std::endl; + return EXIT_FAILURE; + } + + Mesh mesh; + if(!CGAL::IO::read_polygon_mesh(filename, mesh) || is_empty(mesh)) + { + std::cerr << "Could not read input mesh" << std::endl; + return EXIT_FAILURE; + } + + if(CGAL::is_closed(mesh)) + std::cout << "Input mesh is closed - using signed distance offset" << std::endl; + else + std::cout << "Input mesh is not closed - using unsigned distance offset" << std::endl; + + // construct loose bounding box from input mesh + CGAL::Bbox_3 bbox = CGAL::Polygon_mesh_processing::bbox(mesh); + + const FT diag_length = sqrt(CGAL::square(bbox.xmax() - bbox.xmin()) + + CGAL::square(bbox.ymax() - bbox.ymin()) + + CGAL::square(bbox.zmax() - bbox.zmin())); + const FT loose_offset = offset_value + 0.1 * diag_length; + + Vector aabb_increase_vec = Vector(loose_offset, loose_offset, loose_offset); + bbox += (Point(bbox.xmax(), bbox.ymax(), bbox.zmax()) + aabb_increase_vec).bbox(); + bbox += (Point(bbox.xmin(), bbox.ymin(), bbox.zmin()) - aabb_increase_vec).bbox(); + + const int nv = 15; + + Grid grid { bbox, CGAL::make_array(nv, nv, nv) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + Offset_oracle offset_oracle(mesh); + + run_marching_cubes(grid, offset_value, offset_oracle); + + run_dual_contouring(grid, offset_value, offset_oracle); + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/contouring_vtk_image.cpp b/Isosurfacing_3/examples/Isosurfacing_3/contouring_vtk_image.cpp new file mode 100644 index 000000000000..f36c48e8aff3 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_vtk_image.cpp @@ -0,0 +1,145 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Interpolated_discrete_values_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +void run_marching_cubes(const Grid& grid, + const FT isovalue, + const Values& values) +{ + using Domain = IS::Marching_cubes_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes with isovalue = " << isovalue << std::endl; + + // fill up values + + // create a domain from the grid + Domain domain { grid, values }; + + // prepare collections for the output indexed soup + Point_range points; + Polygon_range triangles; + + // execute marching cubes + IS::marching_cubes(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + + // save output indexed mesh to a file, in the OFF format + CGAL::IO::write_polygon_soup("marching_cubes_vtk_image.off", points, triangles); +} + +void run_dual_contouring(const Grid& grid, + const FT isovalue, + const Values& values) +{ + using Domain = IS::Dual_contouring_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + // fill up values and gradients + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; // finite difference step + Gradients gradients { values, step }; + Domain domain { grid, values, gradients }; + + Point_range points; + Polygon_range triangles; + + // run dual contouring isosurfacing + IS::dual_contouring(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_vtk_image.off", points, triangles); +} + +template +void run(const char* filename, + const FT isovalue) +{ + vtkNew reader; + reader->SetFileName(filename); + reader->Update(); + CGAL::Image_3 image = CGAL::IO::read_vtk_image_data(reader->GetOutput()); + + // convert image to a Cartesian grid + Grid grid; + Values values { grid }; // 'values' keeps a reference to the grid + if(!IS::IO::convert_image_to_grid(image, grid, values)) + { + std::cerr << "Error: Cannot convert image to Cartesian grid" << std::endl; + return; + } + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + run_marching_cubes(grid, isovalue, values); + + run_dual_contouring(grid, isovalue, values); +} + +int main(int argc, char* argv[]) +{ + const std::string fname = (argc>1) ? argv[1] : CGAL::data_file_path("images/torus_gray_image.vti"); + + const char* filename = fname.c_str(); + const FT isovalue = (argc > 2) ? std::stod(argv[2]) : 3; + + const std::string ext = CGAL::IO::internal::get_file_extension(filename); + if(ext == "mhd" || ext == "mha") + run(filename, isovalue); + else if(ext == "vti") + run(filename, isovalue); + else if(ext == "tif") + run(filename, isovalue); + else if(ext == "nrrd") + run(filename, isovalue); + else if(ext == "mnc") + run(filename, isovalue); + else + { + std::cerr << "Error: Unsupported file format" << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/dual_contouring.cpp b/Isosurfacing_3/examples/Isosurfacing_3/dual_contouring.cpp new file mode 100644 index 000000000000..07c250117e02 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/dual_contouring.cpp @@ -0,0 +1,79 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Gradient_function_3; +using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +// https://www-sop.inria.fr/galaad/surface/ +auto devil_value = [](const Point& point) +{ + const FT x = point.x(), y = point.y(), z = point.z(); + return x*x*x*x + 2*x*x*z*z - 0.36*x*x - y*y*y*y + 0.25*y*y + z*z*z*z; +}; + +auto devil_gradient = [](const Point& point) +{ + const FT x = point.x(), y = point.y(), z = point.z(); + + const FT gx = 4*x*x*x + 4*x*z*z - 0.72*x; + const FT gy = -4*y*y*y + 0.5*y; + const FT gz = 4*x*x*z + 4*z*z*z; + Vector g(gx, gy, gz); + return g / std::sqrt(gx*gx + gy*gy + gz*gz); +}; + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.; + + // create bounding box and grid + const CGAL::Bbox_3 bbox { -1, -1, -1, 1, 1, 1 }; + Grid grid { bbox, CGAL::make_array(50, 50, 50) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + Values values { devil_value, grid }; + Gradients gradients { devil_gradient, grid }; + + // Below is equivalent to: + // Domain domain { grid, values, gradients }; + Domain domain = CGAL::Isosurfacing::create_dual_contouring_domain_3(grid, values, gradients); + + Point_range points; + Polygon_range triangles; + + // run dual contouring isosurfacing + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + CGAL::Isosurfacing::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(true) + .constrain_to_cell(false)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring.off", points, triangles); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes.cpp b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes.cpp new file mode 100644 index 000000000000..2ca5984a58af --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes.cpp @@ -0,0 +1,63 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.8; + + // create bounding box and grid + const CGAL::Bbox_3 bbox { -1., -1., -1., 1., 1., 1. }; + Grid grid { bbox, CGAL::make_array(30, 30, 30) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + // fill up values + auto sphere_value_fn = [](const Point& p) -> FT + { + return sqrt(p.x()*p.x() + p.y()*p.y() + p.z()*p.z()); + }; + + Values values { sphere_value_fn, grid }; + + // Below is equivalent to: + // Domain domain { grid, values }; + Domain domain = CGAL::Isosurfacing::create_marching_cubes_domain_3(grid, values); + + Point_range points; + Polygon_range triangles; + + // run marching cubes isosurfacing + std::cout << "Running Marching Cubes with isovalue = " << isovalue << std::endl; + CGAL::Isosurfacing::marching_cubes(domain, isovalue, points, triangles, + CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes.off", points, triangles); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes_openvdb.cpp b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes_openvdb.cpp new file mode 100644 index 000000000000..25c93de74711 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes_openvdb.cpp @@ -0,0 +1,425 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +class OpenVDB_Edge_intersection +{ +public: + OpenVDB_Edge_intersection(openvdb::FloatGrid::Ptr grid, + const float isovalue) + : _grid(grid), + _intersector(*grid, isovalue) + {} + +public: + template // == Isosurfacing_domain_3 or similar + bool operator()(const typename Domain::Geom_traits::Point_3& p_0, + const typename Domain::Geom_traits::Point_3& p_1, + const typename Domain::Geom_traits::FT /*val_0*/, + const typename Domain::Geom_traits::FT /*val_1*/, + const Domain& /*domain*/, + const typename Domain::Geom_traits::FT/* isovalue*/, + typename Domain::Geom_traits::Point_3& p) const + { + const openvdb::Vec3R eye(p_0.x(), p_0.y(), p_0.z()); + const openvdb::Vec3R direction = openvdb::Vec3R(p_1.x() - p_0.x(), + p_1.y() - p_0.y(), + p_1.z() - p_0.z()); + const openvdb::math::Ray ray(eye, direction.unit()); + openvdb::Vec3R I; + openvdb::Vec3R normal; + openvdb::Real t; + if (_intersector.intersectsWS(ray, I, normal, t) && t > 0.0) + { + // Only consider intersections within the segment bounds. + const openvdb::Vec3R vI = I - eye; + if (vI.lengthSqr() <= direction.lengthSqr()) + { + p = typename Domain::Geom_traits::Point_3(I.x(), I.y(), I.z()); + return true; + } + } + return false; + } + +private: + openvdb::FloatGrid::Ptr _grid; + openvdb::tools::LevelSetRayIntersector _intersector; +}; + +class OpenVDB_partition +{ +public: + using Geom_traits = Kernel; + + OpenVDB_partition(openvdb::FloatGrid::Ptr grid) + : _grid(grid) + , _gt() + {} + + const Geom_traits& geom_traits() const { return _gt; } + + openvdb::FloatGrid::Ptr grid() const { return _grid; } + +private: + openvdb::FloatGrid::Ptr _grid; + Geom_traits _gt; +}; + + +namespace CGAL { +namespace Isosurfacing { + +template<> +struct partition_traits +{ +public: + using vertex_descriptor = openvdb::Coord; //indices (i,j,k) + // identifies a cell by its corner vertex with the smallest (i, j, k) index + using cell_descriptor = openvdb::Coord; + // identifies an edge by its starting vertex (i, j, k) and the direction x -> 0, y -> 1, z -> 2 + struct edge_descriptor + { + vertex_descriptor source; + int direction; + + bool operator==(const edge_descriptor& other) const { + return source == other.source && direction == other.direction; + } + }; + + static constexpr Cell_type CELL_TYPE = CUBICAL_CELL; + static constexpr std::size_t VERTICES_PER_CELL = 8; + static constexpr std::size_t EDGES_PER_CELL = 12; + + using Edge_vertices = std::array; + using Cells_incident_to_edge = std::array; + using Cell_vertices = std::array; + using Cell_edges = std::array; + + static Point point(const vertex_descriptor& v, + const OpenVDB_partition& partition) + { + const auto& grid = partition.grid(); + const openvdb::Vec3R vr = grid->indexToWorld(v); + return Point(vr.x(), vr.y(), vr.z()); + } + + static Edge_vertices incident_vertices(const edge_descriptor& e, const OpenVDB_partition&) + { + vertex_descriptor src = e.source;// start vertex + vertex_descriptor tgt = src;// end vertex + tgt[e.direction] += 1;// one position further in the direction of the edge + return {src, tgt}; + } + + static Cells_incident_to_edge incident_cells(const edge_descriptor& e, const OpenVDB_partition&) + { + // lookup the neighbor cells relative to the edge + const int local = internal::Cube_table::edge_store_index[e.direction]; + auto neighbors = internal::Cube_table::edge_to_voxel_neighbor[local]; + + Cells_incident_to_edge cite; + for (std::size_t i = 0; i < 4; ++i) { //for each cell + cite[i] = cell_descriptor(e.source.x() + neighbors[i][0], + e.source.y() + neighbors[i][1], + e.source.z() + neighbors[i][2]); + } + return cite; + } + + static Cell_vertices cell_vertices(const cell_descriptor& c, const OpenVDB_partition&) + { + Cell_vertices cv; + for (std::size_t i = 0; i < cv.size(); ++i) + { //for each vertex in cell_vertices (8) + cv[i] = vertex_descriptor( + c.x() + internal::Cube_table::local_vertex_position[i][0], + c.y() + internal::Cube_table::local_vertex_position[i][1], + c.z() + internal::Cube_table::local_vertex_position[i][2]); + } + return cv; + } + + static Cell_edges cell_edges(const cell_descriptor& c, const OpenVDB_partition&) + { + Cell_edges ce; + for (std::size_t i = 0; i < ce.size(); ++i) //for each edge + { + // lookup the relative edge indices and offset them by the cell position + ce[i] = edge_descriptor{ + vertex_descriptor(c.x() + internal::Cube_table::global_edge_id[i][0], + c.y() + internal::Cube_table::global_edge_id[i][1], + c.z() + internal::Cube_table::global_edge_id[i][2]), + internal::Cube_table::global_edge_id[i][3]}; + } + return ce; + } + + template + static void for_each_vertex(Functor& f, + const OpenVDB_partition& partition, + ConcurrencyTag) + { + const auto& grid = partition.grid(); + for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) { + f(iter.getCoord()); + } + } + + template + static void for_each_edge(Functor& f, + const OpenVDB_partition& partition, + ConcurrencyTag) + { + const auto grid = partition.grid(); + for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) + { + // all three edges starting at vertex (i, j, k) + f(edge_descriptor{iter.getCoord(), 0}); + f(edge_descriptor{iter.getCoord(), 1}); + f(edge_descriptor{iter.getCoord(), 2}); + } + } + + template + static void for_each_cell(Functor& f, + const OpenVDB_partition& partition, + ConcurrencyTag) + { + const auto grid = partition.grid(); + for (openvdb::FloatGrid::ValueOnCIter iter = grid->cbeginValueOn(); iter.test(); ++iter) { + f(iter.getCoord()); + } + } +}; + +} +} + +namespace std{ + template<> + struct hash::edge_descriptor> + { + using edge_descriptor + = CGAL::Isosurfacing::partition_traits::edge_descriptor; + using vertex_descriptor + = CGAL::Isosurfacing::partition_traits::vertex_descriptor; + + auto operator()(const edge_descriptor& e) const -> size_t + { + return std::hash{}(e.source) ^ std::hash{}(e.direction); + } + }; +} // namespace std + +class OpenVDB_value_field +{ +public: + using FT = FT; + using Point_3 = Point; + using vertex_descriptor = openvdb::Coord; + + OpenVDB_value_field(openvdb::FloatGrid::Ptr grid) + : _grid(grid) + , _accessor(grid->getAccessor()) + {} + + FT operator()(const Point_3& p) const + { + // calculate coordinates of min index + openvdb::Vec3d ijk_p = _grid->worldToIndex(openvdb::Vec3d(p.x(), p.y(), p.z())); + openvdb::Coord ijk(static_cast(ijk_p.x()), + static_cast(ijk_p.y()), + static_cast(ijk_p.z())); + + openvdb::Coord ijk_z (ijk[0] + 0, ijk[1] + 0, ijk[2] + 1); + openvdb::Coord ijk_y (ijk[0] + 0, ijk[1] + 1, ijk[2] + 0); + openvdb::Coord ijk_yz (ijk[0] + 0, ijk[1] + 1, ijk[2] + 1); + openvdb::Coord ijk_x (ijk[0] + 1, ijk[1] + 0, ijk[2] + 0); + openvdb::Coord ijk_xz (ijk[0] + 1, ijk[1] + 0, ijk[2] + 1); + openvdb::Coord ijk_xy (ijk[0] + 1, ijk[1] + 1, ijk[2] + 0); + openvdb::Coord ijk_xyz(ijk[0] + 1, ijk[1] + 1, ijk[2] + 1); + + auto min_p = _grid->indexToWorld(ijk); + auto ijk_z_p = _grid->indexToWorld(ijk_z); + auto ijk_y_p = _grid->indexToWorld(ijk_y); + auto ijk_yz_p = _grid->indexToWorld(ijk_yz); + auto ijk_x_p = _grid->indexToWorld(ijk_x); + auto ijk_xz_p = _grid->indexToWorld(ijk_xz); + auto ijk_xy_p = _grid->indexToWorld(ijk_xy); + auto ijk_xyz_p = _grid->indexToWorld(ijk_xyz); + + std::cout << "p: " << p << std::endl; + std::cout << "ijk_p: " << ijk_p << std::endl; + std::cout << "min_p: " << min_p << std::endl; + std::cout << _grid->indexToWorld(ijk) << std::endl; + std::cout << _grid->indexToWorld(ijk_z) << std::endl; + std::cout << _grid->indexToWorld(ijk_y) << std::endl; + std::cout << _grid->indexToWorld(ijk_yz) << std::endl; + std::cout << _grid->indexToWorld(ijk_x) << std::endl; + std::cout << _grid->indexToWorld(ijk_xz) << std::endl; + std::cout << _grid->indexToWorld(ijk_xy) << std::endl; + std::cout << _grid->indexToWorld(ijk_xyz) << std::endl; + + // interpolation factors between 0 and 1 + const FT f_i = CGAL::abs((p.x() - min_p.x()) / (ijk_x_p.x() - min_p.x())); // @fixme? negative voxels + const FT f_j = CGAL::abs((p.y() - min_p.y()) / (ijk_y_p.y() - min_p.y())); + const FT f_k = CGAL::abs((p.z() - min_p.z()) / (ijk_z_p.z() - min_p.z())); + std::cout << "f_i = " << f_i << std::endl; + std::cout << "f_j = " << f_j << std::endl; + std::cout << "f_k = " << f_k << std::endl; + CGAL_assertion(f_i >= 0 && f_j >= 0 && f_k >= 0 && f_i <= 1. && f_j <= 1. && f_k <= 1.); + + // read the value at all 8 corner points + const FT g000 = _accessor.getValue(ijk); + const FT g001 = _accessor.getValue(ijk_z); + const FT g010 = _accessor.getValue(ijk_y); + const FT g011 = _accessor.getValue(ijk_yz); + const FT g100 = _accessor.getValue(ijk_x); + const FT g101 = _accessor.getValue(ijk_xz); + const FT g110 = _accessor.getValue(ijk_xy); + const FT g111 = _accessor.getValue(ijk_xyz); + + // interpolate along all axes by weighting the corner points + const FT lambda000 = (FT(1) - f_i) * (FT(1) - f_j) * (FT(1) - f_k); + const FT lambda001 = (FT(1) - f_i) * (FT(1) - f_j) * f_k; + const FT lambda010 = (FT(1) - f_i) * f_j * (FT(1) - f_k); + const FT lambda011 = (FT(1) - f_i) * f_j * f_k; + const FT lambda100 = f_i * (FT(1) - f_j) * (FT(1) - f_k); + const FT lambda101 = f_i * (FT(1) - f_j) * f_k; + const FT lambda110 = f_i * f_j * (FT(1) - f_k); + const FT lambda111 = f_i * f_j * f_k; + + // add weighted corners + return g000 * lambda000 + g001 * lambda001 + + g010 * lambda010 + g011 * lambda011 + + g100 * lambda100 + g101 * lambda101 + + g110 * lambda110 + g111 * lambda111; + } + + FT operator()(const vertex_descriptor& v) const + { + return _accessor.getValue(v); + } + +private: + openvdb::FloatGrid::Ptr _grid; + openvdb::FloatGrid::Accessor _accessor; +}; + +using Partition = OpenVDB_partition; +using Values = OpenVDB_value_field; +using Edge_intersection = OpenVDB_Edge_intersection; + +void run_marching_cubes(openvdb::FloatGrid::Ptr grid, + const float isovalue, + const bool use_tcm) +{ + using Domain = IS::Marching_cubes_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes with isovalue = " << isovalue + << ", tcm = " << std::boolalpha << use_tcm << std::endl; + + Partition partition(grid); + Values values(grid); + Edge_intersection intersection_oracle(grid, isovalue); + Domain domain = IS::create_marching_cubes_domain_3(partition, values, intersection_oracle); + + // run marching cubes isosurfacing + Point_range points; + Polygon_range triangles; + + IS::marching_cubes(domain, isovalue, points, triangles, + CGAL::parameters::use_topologically_correct_marching_cubes(use_tcm)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + const std::string outfile = use_tcm + ? "marching_cubes_openvdb_tcm.off" + : "marching_cubes_openvdb.off"; + CGAL::IO::write_polygon_soup(outfile, points, triangles); +} + +void run_dual_contouring(openvdb::FloatGrid::Ptr grid, + const FT isovalue) +{ + using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + using Domain = IS::Dual_contouring_domain_3; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + // fill up values and gradients + Partition partition(grid); + Values values(grid); + const FT step = grid->voxelSize().length() * 0.01; // finite difference step + Gradients gradients{ values, step }; + Edge_intersection intersection_oracle(grid, isovalue); + + Domain domain = IS::create_dual_contouring_domain_3(partition, values, gradients, intersection_oracle); + + Point_range points; + Polygon_range triangles; + + // run dual contouring isosurfacing + IS::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(true)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_openvdb.off", points, triangles); +} + + +int main(int argc, char** argv) +{ + const std::string input_filename = argv[1]; + + std::ifstream ifs(input_filename, std::ios::binary); + if (!ifs) + return EXIT_FAILURE; + + openvdb::initialize(); + openvdb::io::Stream stream(ifs); + openvdb::GridPtrVecPtr grids = stream.getGrids(); + openvdb::FloatGrid::Ptr grid = openvdb::gridPtrCast(grids->front()); + + run_marching_cubes(grid, 0./*isovalue*/, true/*topologically correct MC*/); + run_marching_cubes(grid, 0./*isovalue*/, false/*topologically correct MC*/); + run_dual_contouring(grid, 0./*isovalue*/); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes_strategies.cpp b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes_strategies.cpp new file mode 100644 index 000000000000..aad37317e9f1 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes_strategies.cpp @@ -0,0 +1,91 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.8; + + // create bounding box and grid + const CGAL::Bbox_3 bbox { -1., -1., -1., 1., 1., 1. }; + Grid grid { bbox, CGAL::make_array(30, 30, 30) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + // fill up values + auto sphere_value_fn = [](const Point& p) -> FT + { + return sqrt(p.x()*p.x() + p.y()*p.y() + p.z()*p.z()); + }; + + Values values { sphere_value_fn, grid }; + Domain domain { grid, values }; + + // MC base version + { + Point_range points; + Polygon_range triangles; + + CGAL::Real_timer timer; + timer.start(); + + // run marching cubes isosurfacing + std::cout << "Running Marching Cubes with isovalue = " << isovalue << std::endl; + CGAL::Isosurfacing::marching_cubes(domain, isovalue, points, triangles, + CGAL::parameters::use_topologically_correct_marching_cubes(false)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes.off", points, triangles); + } + + // MC topologically correct version + { + Point_range points; + Polygon_range triangles; + + CGAL::Real_timer timer; + timer.start(); + + // run marching cubes isosurfacing + std::cout << "Running Marching Cubes (TMC) with isovalue = " << isovalue << std::endl; + CGAL::Isosurfacing::marching_cubes(domain, isovalue, points, triangles, + CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("marching_cubes_TMC.off", points, triangles); + } + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Cartesian_grid_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Cartesian_grid_3.h new file mode 100644 index 000000000000..71d98f141e9c --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Cartesian_grid_3.h @@ -0,0 +1,405 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_CARTESIAN_GRID_3_H +#define CGAL_ISOSURFACING_3_CARTESIAN_GRID_3_H + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Partitions_helpers_grp + * + * A policy to choose whether grid vertex positions should be cached, or recomputed at each access. + * + * \tparam Tag a tag that is either `Tag_true` (positions are cached) or `Tag_false` (positions are not cached). + */ +template +struct Grid_vertex_memory_policy : public Tag { }; + +/** + * \ingroup IS_Partitions_helpers_grp + * + * A convenience alias for the policy that caches grid vertex positions. + */ +using Cache_positions = Grid_vertex_memory_policy; + +/** + * \ingroup IS_Partitions_helpers_grp + * + * A convenience alias for the policy that does not cache grid vertex positions. + */ +using Do_not_cache_positions = Grid_vertex_memory_policy; + +namespace internal { + +template +struct Cartesian_grid_position +{ + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + using Iso_cuboid_3 = typename GeomTraits::Iso_cuboid_3; + + Cartesian_grid_position() { } // just for compilation + + Cartesian_grid_position(const Iso_cuboid_3& /*span*/, + const std::array& /*dims*/, + const Vector_3& /*spacing*/) + { } + + template + Point_3 operator()(const std::size_t i, + const std::size_t j, + const std::size_t k, + const Grid& g) const + { + typename GeomTraits::Compute_x_3 x_coord = g.geom_traits().compute_x_3_object(); + typename GeomTraits::Compute_y_3 y_coord = g.geom_traits().compute_y_3_object(); + typename GeomTraits::Compute_z_3 z_coord = g.geom_traits().compute_z_3_object(); + typename GeomTraits::Construct_point_3 point = g.geom_traits().construct_point_3_object(); + typename GeomTraits::Construct_vertex_3 vertex = g.geom_traits().construct_vertex_3_object(); + + const Point_3& min_p = vertex(g.span(), 0); + return point(x_coord(min_p) + i * g.spacing()[0], + y_coord(min_p) + j * g.spacing()[1], + z_coord(min_p) + k * g.spacing()[2]); + } +}; + +template +struct Cartesian_grid_position +{ + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + using Iso_cuboid_3 = typename GeomTraits::Iso_cuboid_3; + + std::vector m_points; + + Cartesian_grid_position() { } // just for compilation + + Cartesian_grid_position(const Iso_cuboid_3& span, + const std::array& dims, + const Vector_3& spacing) + { + m_points.reserve(dims[0] * dims[1] * dims[2]); + for(std::size_t k=0; k + const Point_3& operator()(const std::size_t i, + const std::size_t j, + const std::size_t k, + const Grid& g) const + { + const std::size_t linear_index = g.linear_index(i, j, k); + CGAL_precondition(linear_index < m_points.size()); + return m_points[linear_index]; + } +}; + +} // namespace internal + +/** + * \ingroup IS_Partitions_grp + * + * \cgalModels{IsosurfacingPartition_3} + * + * \brief The class `Cartesian_grid_3` represents a 3D %Cartesian grid, that is the partition of + * an iso-cuboid into identical iso-cuboidal cells. + * + * The class `Cartesian_grid_3` is one of the possible space partitioning data structures + * that can be used along with value and gradient fields to make up a domain. + * + * \tparam GeomTraits must be a model of `IsosurfacingTraits_3`. + * \tparam MemoryPolicy whether the geometric positions of the grid vertices are stored or not. + * Possible values are `CGAL::Isosurfacing::Cache_positions` and `CGAL::Isosurfacing::Do_not_cache_positions`. + * + * \sa `CGAL::Isosurfacing::Marching_cubes_domain_3()` + * \sa `CGAL::Isosurfacing::Dual_contouring_domain_3()` + */ +template +class Cartesian_grid_3 +{ +public: + using Geom_traits = GeomTraits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + using Iso_cuboid_3 = typename Geom_traits::Iso_cuboid_3; + + using Positioner = internal::Cartesian_grid_position; + +private: + Iso_cuboid_3 m_span; + std::array m_dims; + Vector_3 m_spacing; + + Positioner m_positioner; + Geom_traits m_gt; + +private: + void initialize_spacing() + { + typename Geom_traits::Compute_x_3 x_coord = m_gt.compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = m_gt.compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = m_gt.compute_z_3_object(); + typename Geom_traits::Construct_vector_3 vector = m_gt.construct_vector_3_object(); + typename Geom_traits::Construct_vertex_3 vertex = m_gt.construct_vertex_3_object(); + + // calculate grid spacing + const Point_3& min_p = vertex(m_span, 0); + const Point_3& max_p = vertex(m_span, 7); + const FT x_span = x_coord(max_p) - x_coord(min_p); + const FT y_span = y_coord(max_p) - y_coord(min_p); + const FT z_span = z_coord(max_p) - z_coord(min_p); + + const FT d_x = x_span / (m_dims[0] - 1); + const FT d_y = y_span / (m_dims[1] - 1); + const FT d_z = z_span / (m_dims[2] - 1); + + m_spacing = vector(d_x, d_y, d_z); + + m_positioner = Positioner { m_span, m_dims, m_spacing }; + } + + void initialize_dimensions() + { + typename Geom_traits::Compute_x_3 x_coord = m_gt.compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = m_gt.compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = m_gt.compute_z_3_object(); + typename Geom_traits::Construct_vertex_3 vertex = m_gt.construct_vertex_3_object(); + + const Point_3& min_p = vertex(m_span, 0); + const Point_3& max_p = vertex(m_span, 7); + const FT x_span = x_coord(max_p) - x_coord(min_p); + const FT y_span = y_coord(max_p) - y_coord(min_p); + const FT z_span = z_coord(max_p) - z_coord(min_p); + + m_dims[0] = static_cast(std::ceil(x_span / m_spacing[0])) + 1; + m_dims[1] = static_cast(std::ceil(y_span / m_spacing[1])) + 1; + m_dims[2] = static_cast(std::ceil(z_span / m_spacing[2])) + 1; + + m_positioner = Positioner { m_span, m_dims, m_spacing }; + } + +public: + /*! + * \brief Default constructor + */ + Cartesian_grid_3() + : m_span{Point_3{0, 0, 0}, Point_3{0, 0, 0}}, + m_dims{2, 2, 2}, + m_spacing{0, 0, 0}, + m_gt{Geom_traits()} + { } + + /** + * \brief creates a %Cartesian grid with `xdim * ydim * zdim` grid vertices. + * + * The grid covers the space described by an iso-cuboid. + * + * \param span the geometric span of the grid + * \param dimensions the number of grid vertices in the `x`, `y`, and `z` directions + * \param gt the geometric traits + * + * \pre all dimensions are (strictly) positive. + */ + Cartesian_grid_3(const Iso_cuboid_3& span, + const std::array& dimensions, + const Geom_traits& gt = Geom_traits()) + : m_span{span}, + m_dims{dimensions}, + m_gt{gt} + { + initialize_spacing(); + } + + /** + * \brief creates a %Cartesian grid with `xdim * ydim * zdim` grid vertices. + * + * The grid covers the space described by an iso-cuboid, + * itself described through two diagonal corners. + * + * \param p the lexicographically smallest corner of the iso-cuboid + * \param q the lexicographically largest corner of the iso-cuboid + * \param dimensions the number of grid vertices in the `x`, `y`, and `z` directions + * \param gt the geometric traits + * + * \pre `p` is lexicographically (strictly) smaller than `q` + * \pre all dimensions are (strictly) positive. + */ + Cartesian_grid_3(const Point_3& p, const Point_3& q, + const std::array& dimensions, + const Geom_traits& gt = Geom_traits()) + : Cartesian_grid_3{Iso_cuboid_3{p, q}, dimensions, gt} + { } + + /** + * \brief creates a %Cartesian grid using a prescribed grid step. + * + * The grid covers the space described by an iso-cuboid. + * + * \param span the geometric span of the grid + * \param spacing the dimension of the paving cell, in the `x`, `y`, and `z` directions + * \param gt the geometric traits + * + * \pre the diagonal of `span` has length a multiple of `spacing` + */ + Cartesian_grid_3(const Iso_cuboid_3& span, + const Vector_3& spacing, + const Geom_traits& gt = Geom_traits()) + : m_span{span}, + m_spacing{spacing}, + m_gt{gt} + { + initialize_dimensions(); + } + + /** + * \brief creates a %Cartesian grid using a prescribed grid step. + * + * The grid covers the space described by an iso-cuboid, itself described through two diagonal corners. + * + * \param p the lexicographically smallest corner of the iso-cuboid + * \param q the lexicographically largest corner of the iso-cuboid + * \param spacing the dimension of the paving cell, in the `x`, `y`, and `z` directions, respectively. + * \param gt the geometric traits + * + * \pre `p` is lexicographically (strictly) smaller than `q` + * \pre the diagonal of the iso-cuboid has length a multiple of `spacing` + */ + Cartesian_grid_3(const Point_3& p, const Point_3& q, + const Vector_3& spacing, + const Geom_traits& gt = Geom_traits()) + : Cartesian_grid_3{Iso_cuboid_3{p, q}, spacing, gt} + { } + +public: + /** + * returns the geometric traits class + */ + const Geom_traits& geom_traits() const + { + return m_gt; + } + + /** + * returns an iso-cuboid representing the geometric span of the %Cartesian grid + */ + const Iso_cuboid_3& span() const { return m_span; } + + /** + * returns the number of grid vertices in the `x` direction + */ + std::size_t xdim() const { return m_dims[0]; } + + /** + * returns the number of grid vertices in the `y` direction + */ + std::size_t ydim() const { return m_dims[1]; } + + /** + * returns the number of grid vertices in the `z` direction + */ + std::size_t zdim() const { return m_dims[2]; } + + /** + * returns the spacing of the %Cartesian grid, that is a vector whose coordinates are + * the grid steps in the `x`, `y`, and `z` directions, respectively + */ + const Vector_3& spacing() const { return m_spacing; } + +public: + /** + * \brief returns the index of a grid cell given its indices. + */ + std::size_t linear_index(const std::size_t i, + const std::size_t j, + const std::size_t k) const + { + CGAL_precondition(i < m_dims[0] && j < m_dims[1] && k < m_dims[2]); + return (k * m_dims[1] + j) * m_dims[0] + i; + } + +public: + /** + * \brief returns the index of the grid cell that contains a given point. + * + * \param p the point to be located + * + * \pre `p` is inside the grid. + */ + std::array index(const Point_3& p) const + { + typename Geom_traits::Compute_x_3 x_coord = m_gt.compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = m_gt.compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = m_gt.compute_z_3_object(); + typename Geom_traits::Construct_vertex_3 vertex = m_gt.construct_vertex_3_object(); + + const Point_3& min_p = vertex(m_span, 0); + std::size_t i = std::size_t((x_coord(p) - x_coord(min_p)) / x_coord(m_spacing)); + std::size_t j = std::size_t((y_coord(p) - y_coord(min_p)) / y_coord(m_spacing)); + std::size_t k = std::size_t((z_coord(p) - z_coord(min_p)) / z_coord(m_spacing)); + + if(i == xdim() - 1) + --i; + if(j == ydim() - 1) + --j; + if(k == zdim() - 1) + --k; + + return {i, j, k}; + } + + // Geometry +public: + /** + * \brief returns the geometric position of the grid vertex described by a set of indices. + * + * Depending on the value of the template parameter `cache_points`, positions might not be stored + * but calculated on-the-fly. + * + * \param i the index in the `x` direction + * \param j the index in the `y` direction + * \param k the index in the `z` direction + * + * \pre `i < xdim()` and `j < ydim()` and `k < zdim()` + */ + decltype(auto) /*Point_3*/ point(const std::size_t i, + const std::size_t j, + const std::size_t k) const + { + return m_positioner(i, j, k, *this); + } +}; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_CARTESIAN_GRID_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Dual_contouring_domain_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Dual_contouring_domain_3.h new file mode 100644 index 000000000000..134fe0af0501 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Dual_contouring_domain_3.h @@ -0,0 +1,110 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_DUAL_CONTOURING_DOMAIN_3_H +#define CGAL_ISOSURFACING_3_DUAL_CONTOURING_DOMAIN_3_H + +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Domains_grp + * + * \cgalModels{IsosurfacingDomainWithGradient_3} + * + * \brief A domain that can be used as input in the %Dual Contouring algorithm. + * + * \details This class is essentially a wrapper around the different bricks provided by its + * template parameters: `Partition` provides the spatial partitioning, `ValueField` and `GradientField` + * the values and gradients that define the isosurface. The optional template parameter + * `EdgeIntersectionOracle` gives control over the method used to compute edge-isosurface intersection points. + * + * \tparam Partition must be a model of `IsosurfacingPartition_3` + * \tparam ValueField must be a model of `IsosurfacingValueField_3` + * \tparam GradientField must be a model of `IsosurfacingGradientField_3` + * \tparam EdgeIntersectionOracle must be a model of `IsosurfacingEdgeIntersectionOracle_3` + * + * \sa `CGAL::Isosurfacing::dual_contouring()` + * \sa `CGAL::Isosurfacing::Marching_cubes_domain_3()` + */ +template +class Dual_contouring_domain_3 +#ifndef DOXYGEN_RUNNING + : public internal::Isosurfacing_domain_3 +#endif +{ +private: + using Base = internal::Isosurfacing_domain_3; + +public: + /** + * \brief constructs a domain that can be used with the %Dual Contouring algorithm. + * + * \param partition the space partitioning data structure + * \param values a continuous field of scalar values, defined over the geometric span of `partition` + * \param gradients a continuous field of normalized vectors, defined over the geometric span of `partition` + * \param intersection_oracle the oracle for edge-isosurface intersection computation + * + * \warning the domain class keeps a reference to the `partition`, `values` and `gradients` objects. + * As such, users must ensure that the lifetimes of these objects exceed that of the domain object. + */ + Dual_contouring_domain_3(const Partition& partition, + const ValueField& values, + const GradientField& gradients, + const EdgeIntersectionOracle& intersection_oracle = EdgeIntersectionOracle()) + : Base(partition, values, gradients, intersection_oracle) + { } +}; + +/** + * \ingroup IS_Domains_grp + * + * \brief creates a new instance of a domain that can be used with the %Dual Contouring algorithm. + * + * \tparam Partition must be a model of `IsosurfacingPartition_3` + * \tparam ValueField must be a model of `IsosurfacingValueField_3` + * \tparam GradientField must be a model of `IsosurfacingGradientField_3` + * \tparam EdgeIntersectionOracle must be a model of `IsosurfacingEdgeIntersectionOracle_3` + * + * \param partition the space partitioning data structure + * \param values a continuous field of scalar values, defined over the geometric span of `partition` + * \param gradients a continuous field of normalized vectors, defined over the geometric span of `partition` + * \param intersection_oracle the oracle for edge-isosurface intersection computation + * + * \warning the domain class keeps a reference to the `partition`, `values` and `gradients` objects. + * As such, users must ensure that the lifetimes of these objects exceed that of the domain object. + */ +template +Dual_contouring_domain_3 +create_dual_contouring_domain_3(const Partition& partition, + const ValueField& values, + const GradientField& gradients, + const EdgeIntersectionOracle& intersection_oracle = EdgeIntersectionOracle()) +{ + return { partition, values, gradients, intersection_oracle }; +} + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_DUAL_CONTOURING_DOMAIN_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Finite_difference_gradient_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Finite_difference_gradient_3.h new file mode 100644 index 000000000000..89fb1506bac4 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Finite_difference_gradient_3.h @@ -0,0 +1,112 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_FINITE_DIFFERENCE_GRADIENT_3_H +#define CGAL_ISOSURFACING_3_FINITE_DIFFERENCE_GRADIENT_3_H + +#include + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Fields_grp + * + * \cgalModels{IsosurfacingGradientField_3} + * + * \brief Class template for a gradient that is calculated using finite differences. + * + * \details This gradient function evaluates a value function at six points that are + * a given distance `delta` away from the queried point along the %Cartesian axes. + * + * \tparam GeomTraits must be a model of `IsosurfacingTraits_3`. + */ +template +class Finite_difference_gradient_3 +{ +public: + using Geom_traits = GeomTraits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + +private: + const std::function m_function; + const FT m_delta, m_half_step_inv; + + GeomTraits m_gt; + +public: + /** + * \brief creates a new instance of this gradient class. + * + * \tparam ValueFunction must be a model of `IsosurfacingValueField_3`. + * + * \param function the function giving the scalar value at each point + * \param delta the distance between samples for calculating the finite differences + * \param gt the geometric traits class + */ + template + Finite_difference_gradient_3(const ValueFunction& function, + const FT delta, + const Geom_traits& gt = Geom_traits()) + : m_function{function}, + m_delta{delta}, + m_half_step_inv{FT{1} / (FT{2} * m_delta)}, + m_gt{gt} + { } + + /** + * \brief evaluates the gradient at a point in 3D space. + * + * \param p the position at which the gradient is computed + */ + Vector_3 operator()(const Point_3& p) const + { + typename Geom_traits::Compute_x_3 x_coord = m_gt.compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = m_gt.compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = m_gt.compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = m_gt.construct_point_3_object(); + typename Geom_traits::Construct_vector_3 vector = m_gt.construct_vector_3_object(); + + // compute the gradient by sampling the function with finite differences + // at six points with distance delta around the query point + const FT x = x_coord(p), y = y_coord(p), z = z_coord(p); + + const Point_3 p0 = point(x + m_delta, y, z); + const Point_3 p1 = point(x - m_delta, y, z); + const Point_3 p2 = point(x, y + m_delta, z); + const Point_3 p3 = point(x, y - m_delta, z); + const Point_3 p4 = point(x, y, z + m_delta); + const Point_3 p5 = point(x, y, z - m_delta); + + const FT gx = (m_function(p0) - m_function(p1)) * m_half_step_inv; + const FT gy = (m_function(p2) - m_function(p3)) * m_half_step_inv; + const FT gz = (m_function(p4) - m_function(p5)) * m_half_step_inv; + + const FT n = CGAL::approximate_sqrt(CGAL::square(gx) + CGAL::square(gy) + CGAL::square(gz)); + + if(is_zero(n)) + return vector(0,0,0); + + return vector(gx / n, gy / n, gz / n); + } +}; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_FINITE_DIFFERENCE_GRADIENT_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Gradient_function_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Gradient_function_3.h new file mode 100644 index 000000000000..b5fc998093d9 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Gradient_function_3.h @@ -0,0 +1,90 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_GRADIENT_FUNCTION_3_H +#define CGAL_ISOSURFACING_3_GRADIENT_FUNCTION_3_H + +#include + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Fields_grp + * + * \cgalModels{IsosurfacingGradientField_3} + * + * \brief The class `Gradient_function_3` represents a field of vectors computed + * using a user-provided unary function. + * + * \tparam Partition must be a model of `IsosurfacingPartition_3` + * + * \sa `CGAL::Isosurfacing::Dual_contouring_domain_3` + */ +template +class Gradient_function_3 +{ +public: + using Geom_traits = typename Partition::Geom_traits; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using PT = partition_traits; + using vertex_descriptor = typename PT::vertex_descriptor; + +private: + std::function m_fn; + const Partition& m_partition; + +public: + /** + * \brief constructs a field of gradients using a gradient function and a partition. + * + * \tparam Function must provide the following function signature: + * `Vector_3 operator()(const %Point_3&) const` + * + * \param fn the function providing gradients + * \param partition the space partitioning data structure + */ + template + Gradient_function_3(const Function& fn, + const Partition& partition) + : m_fn{fn}, + m_partition{partition} + { } + +public: + /** + * \brief evaluates the function at the point `p`. + */ + Vector_3 operator()(const Point_3& p) const + { + return m_fn(p); + } + + /** + * \brief evaluates the function at the vertex `v`. + */ + const Vector_3& operator()(const vertex_descriptor& v) const + { + return this->operator()(PT::point(v, m_partition)); + } +}; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_GRADIENT_FUNCTION_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/IO/Image_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/IO/Image_3.h new file mode 100644 index 000000000000..5694252dfd42 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/IO/Image_3.h @@ -0,0 +1,159 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_IO_IMAGE_3_H +#define CGAL_ISOSURFACING_3_IO_IMAGE_3_H + +#include + +#include +#include + +#include + +namespace CGAL { +namespace Isosurfacing { +namespace IO { + +/** + * \ingroup IS_IO_functions_grp + * + * \brief extracts geometry and values from a `CGAL::Image_3`. + * + * The dimensions and geometric span are read from the image. The values stored + * in the image must be of type `Geom_traits::FT` or implicitly convertible to it. + * + * \tparam Grid must be `CGAL::Isosurfacing::Cartesian_grid_3` whose `GeomTraits` is a model of `IsosurfacingTraits_3` + * \tparam Values must be `CGAL::Isosurfacing::Interpolated_discrete_values_3` + * + * \param image the image providing the data + * \param grid the grid + * \param values the values + */ +// We need to have the API pass us an existing grid / values pair because the values +// usually keep a reference to the grid. +template +bool convert_image_to_grid(const CGAL::Image_3& image, + Grid& grid, + Values& values) +{ + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + using Iso_cuboid_3 = typename Geom_traits::Iso_cuboid_3; + + typename Geom_traits::Construct_point_3 point = grid.geom_traits().construct_point_3_object(); + typename Geom_traits::Construct_iso_cuboid_3 iso_cuboid = grid.geom_traits().construct_iso_cuboid_3_object(); + + // compute span + const FT max_x = image.tx() + (image.xdim() - 1) * image.vx(); + const FT max_y = image.ty() + (image.ydim() - 1) * image.vy(); + const FT max_z = image.tz() + (image.zdim() - 1) * image.vz(); + Iso_cuboid_3 span = iso_cuboid(point(image.tx(), image.ty(), image.tz()), + point(max_x, max_y, max_z)); + + // get spacing + // std::array spacing = make_array(image.vx(), image.vy(), image.vz()); + + grid = Grid { span, CGAL::make_array(image.xdim(), image.ydim(), image.zdim()) }; + + // copy values + for(std::size_t x=0; x` with `GeomTraits` + * a model of `IsosurfacingTraits_3` + * + * \param grid the space partitioning data structure + * \param values the field of values + */ +template +CGAL::Image_3 convert_grid_to_image(const Grid& grid, + const Values& values) +{ + using Geom_traits = typename Grid::Geom_traits; + + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + + const Geom_traits& gt = grid.geom_traits(); + typename Geom_traits::Compute_x_3 x_coord = gt.compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = gt.compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = gt.compute_z_3_object(); + typename Geom_traits::Construct_vertex_3 vertex = gt.construct_vertex_3_object(); + + // select number type + WORD_KIND wordkind; + if(std::is_floating_point::value) // @fixme seems meaningless given that vx vy vz are doubles + wordkind = WK_FLOAT; + else + wordkind = WK_FIXED; + + // select signed or unsigned + SIGN sign; + if(std::is_signed::value) + sign = SGN_SIGNED; + else + sign = SGN_UNSIGNED; + + // get spacing + const double vx = CGAL::to_double(grid.spacing()[0]); + const double vy = CGAL::to_double(grid.spacing()[1]); + const double vz = CGAL::to_double(grid.spacing()[2]); + + // create image + _image* im = _createImage(grid.xdim(), grid.ydim(), grid.zdim(), + 1, // vectorial dimension + vx, vy, vz, // voxel size + sizeof(FT), // image word size in bytes + wordkind, // image word kind WK_FIXED, WK_FLOAT, WK_UNKNOWN + sign); // image word sign + + // error handling + if(im == nullptr || im->data == nullptr) + throw std::bad_alloc(); // @todo idk? + + // set min coordinates + const Point_3& min_p = vertex(grid.span(), 0); + im->tx = float(CGAL::to_double(x_coord(min_p))); + im->ty = float(CGAL::to_double(y_coord(min_p))); + im->tz = float(CGAL::to_double(z_coord(min_p))); + + // copy data + FT* data = static_cast(im->data); // @fixme what compatibility with non trivial FTs? + for(std::size_t x=0; x + +#include + +#include +#include +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +template +class Cartesian_grid_3; + +namespace IO { + +template +bool write_OBJ(std::ostream& out, + const Cartesian_grid_3& grid, + const NamedParameters& np = parameters::default_values()) +{ + using Point_3 = typename GeomTraits::Point_3; + + typename GeomTraits::Compute_x_3 x_coord = grid.geom_traits().compute_x_3_object(); + typename GeomTraits::Compute_y_3 y_coord = grid.geom_traits().compute_y_3_object(); + typename GeomTraits::Compute_z_3 z_coord = grid.geom_traits().compute_z_3_object(); + typename GeomTraits::Construct_vertex_3 vertex = grid.geom_traits().construct_vertex_3_object(); + + ::CGAL::IO::set_ascii_mode(out); // obj is ASCII only + + set_stream_precision_from_NP(out, np); + + if(out.fail()) + return false; + + // write vertices + for(std::size_t x=0; x +bool write_OBJ(const std::string& fname, + const Cartesian_grid_3& grid, + const NamedParameters& np = parameters::default_values()) +{ + std::ofstream os(fname); + CGAL::IO::set_mode(os, CGAL::IO::ASCII); + + return write_OBJ(os, grid, np); +} + +} // namespace IO +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_IMAGE_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Interpolated_discrete_gradients_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Interpolated_discrete_gradients_3.h new file mode 100644 index 000000000000..bb00ae716557 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Interpolated_discrete_gradients_3.h @@ -0,0 +1,127 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_INTERPOLATED_DISCRETE_GRADIENTS_3_H +#define CGAL_ISOSURFACING_3_INTERPOLATED_DISCRETE_GRADIENTS_3_H + +#include + +#include +#include +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Fields_grp + * + * \cgalModels{IsosurfacingValueField_3} + * + * \brief Class template for a gradient field that is computed using discrete values and interpolation. + * + * \tparam Grid must be `CGAL::Isosurfacing::Cartesian_grid_3`, with `GeomTraits` a model of `IsosurfacingTraits_3` + * \tparam InterpolationScheme must be a model of `IsosurfacingInterpolationScheme_3` + */ +template > +class Interpolated_discrete_gradients_3 +{ + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using vertex_descriptor = typename partition_traits::vertex_descriptor; + +private: + const Grid& m_grid; + const InterpolationScheme m_interpolation; + + std::vector m_gradients; + +public: + Interpolated_discrete_gradients_3(const Grid& grid, + const InterpolationScheme& interpolation = InterpolationScheme()) + : m_grid{grid}, + m_interpolation{interpolation} + { + // pre-allocate memory + const std::size_t nv = grid.xdim() * grid.ydim() * grid.zdim(); + m_gradients.resize(nv); + } + + /// computes (using finite difference) and stores gradients at the vertices of the grid. + /// \tparam ValueField must be a model of `IsosurfacingValueField_3` + /// \param values a field of values whose gradient are being computed + template + void compute_discrete_gradients(const ValueField& values) + { + const FT step = CGAL::approximate_sqrt(m_grid.spacing().squared_length()) * 0.01; // finite difference step + Finite_difference_gradient_3 g(values, step); + + for(std::size_t i=0; i + +#include + +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Fields_grp + * + * \cgalModels{IsosurfacingValueField_3} + * + * \brief Class template for a field of values that are calculated using discrete values and interpolation. + * + * \tparam Grid must be `CGAL::Isosurfacing::Cartesian_grid_3`, with `GeomTraits` a model of `IsosurfacingTraits_3` + * \tparam InterpolationScheme must be a model of `IsosurfacingInterpolationScheme_3` + */ +template > +class Interpolated_discrete_values_3 +{ + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + + using vertex_descriptor = typename partition_traits::vertex_descriptor; + +private: + const Grid& m_grid; + const InterpolationScheme m_interpolation; + + std::vector m_values; + +public: + Interpolated_discrete_values_3(const Grid& grid, + const InterpolationScheme& interpolation = InterpolationScheme()) + : m_grid{grid}, + m_interpolation{interpolation} + { + // pre-allocate memory + const std::size_t nv = grid.xdim() * grid.ydim() * grid.zdim(); + m_values.resize(nv); + } + +public: + /** + * \brief returns the scalar value stored at the grid vertex described by a set of indices. + * + * \note This function can be used to set the value at a grid vertex. + * + * \param i the index in the `x` direction + * \param j the index in the `y` direction + * \param k the index in the `z` direction + */ + FT& operator()(const std::size_t i, + const std::size_t j, + const std::size_t k) + { + const std::size_t id = m_grid.linear_index(i, j, k); + if(id >= m_values.size()) + m_values.resize(id + 1); + return m_values[id]; + } + + /** + * \brief returns the scalar value stored at the grid vertex described by a set of indices. + * + * \param i the index in the `x` direction + * \param j the index in the `y` direction + * \param k the index in the `z` direction + * + * \pre `i < xdim()` and `j < ydim()` and `k < zdim()` + */ + FT operator()(const std::size_t i, + const std::size_t j, + const std::size_t k) const + { + CGAL_precondition(i < m_grid.xdim() && j < m_grid.ydim() && k < m_grid.zdim()); + return m_values[m_grid.linear_index(i, j, k)]; + } + + /*! + * returns the value at vertex `v`. + */ + FT operator()(const vertex_descriptor& v) const + { + return this->operator()(v[0], v[1], v[2]); + } + + /*! + * returns the value at point `p`. + */ + FT operator()(const Point_3& p) const + { + return m_interpolation.interpolate_values(p, m_grid, m_values); + } +}; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERPOLATED_DISCRETE_VALUES_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Marching_cubes_domain_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Marching_cubes_domain_3.h new file mode 100644 index 000000000000..11008340f417 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Marching_cubes_domain_3.h @@ -0,0 +1,102 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_MARCHING_CUBES_DOMAIN_3_H +#define CGAL_ISOSURFACING_3_MARCHING_CUBES_DOMAIN_3_H + +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Domains_grp + * + * \cgalModels{IsosurfacingDomain_3} + * + * \brief A domain that can be used with the Marching Cubes algorithm. + * + * \details This class is essentially wrapper around the different bricks provided by its + * template parameters: `Partition` provides the spatial partitioning, `ValueField` + * the values that define the isosurface. The optional template parameter + * `EdgeIntersectionOracle` gives control over the method used to compute edge-isosurface intersection points. + * + * \tparam Partition must be a model of `IsosurfacingPartition_3` + * \tparam ValueField must be a model of `IsosurfacingValueField_3` + * \tparam EdgeIntersectionOracle must be a model of `IsosurfacingEdgeIntersectionOracle_3` + * + * \sa `CGAL::Isosurfacing::marching_cubes()` + * \sa `CGAL::Isosurfacing::Dual_contouring_domain_3` + */ +template +class Marching_cubes_domain_3 +#ifndef DOXYGEN_RUNNING + : public internal::Isosurfacing_domain_3 +#endif +{ +private: + using Base = internal::Isosurfacing_domain_3; + +public: + /** + * \brief constructs a domain that can be used with the Marching Cubes algorithm. + * + * \param partition the space partitioning data structure + * \param values a continuous field of scalar values, defined over the geometric span of `partition` + * \param intersection_oracle the oracle for edge-isosurface intersection computation + * + * \warning the domain class keeps a reference to the `partition`, `values` and `gradients` objects. + * As such, users must ensure that the lifetimes of these objects exceed that of the domain object. + */ + Marching_cubes_domain_3(const Partition& partition, + const ValueField& values, + const EdgeIntersectionOracle& intersection_oracle = EdgeIntersectionOracle()) + : Base(partition, values, intersection_oracle) + { } +}; + +/** + * \ingroup IS_Domains_grp + * + * \brief creates a new instance of a domain that can be used with the Marching Cubes algorithm. + * + * \tparam Partition must be a model of `IsosurfacingPartition_3` + * \tparam ValueField must be a model of `IsosurfacingValueField_3` + * \tparam EdgeIntersectionOracle must be a model of `IsosurfacingEdgeIntersectionOracle_3` + * + * \param partition the space partitioning data structure + * \param values a continuous field of scalar values, defined over the geometric span of `partition` + * \param intersection_oracle the oracle for edge-isosurface intersection computation + * + * \warning the domain class keeps a reference to the `partition`, `values` and `gradients` objects. + * As such, users must ensure that the lifetimes of these objects exceed that of the domain object. + */ +template +Marching_cubes_domain_3 +create_marching_cubes_domain_3(const Partition& partition, + const ValueField& values, + const EdgeIntersectionOracle& intersection_oracle = EdgeIntersectionOracle()) +{ + return { partition, values, intersection_oracle }; +} + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_MARCHING_CUBES_DOMAIN_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/Value_function_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Value_function_3.h new file mode 100644 index 000000000000..d1f429fd8cb5 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Value_function_3.h @@ -0,0 +1,91 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_VALUE_FUNCTION_3_H +#define CGAL_ISOSURFACING_3_VALUE_FUNCTION_3_H + +#include + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Fields_grp + * + * \cgalModels{IsosurfacingValueField_3} + * + * \brief The class `Value_function_3` represents a field of scalars computed + * using a user-provided unary function. + * + * \tparam Partition must be a model of `IsosurfacingPartition_3` + * + * \sa `CGAL::Isosurfacing::Marching_cubes_domain_3` + * \sa `CGAL::Isosurfacing::Dual_contouring_domain_3` + */ +template +class Value_function_3 +{ +public: + using Geom_traits = typename Partition::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + + using PT = partition_traits; + using vertex_descriptor = typename PT::vertex_descriptor; + +private: + std::function m_fn; + const Partition& m_partition; + +public: + /** + * \brief constructs a field of values using a value function and a partition. + * + * \tparam Function must provide the following function signature: + * `FT operator()(const %Point_3&) const` + * + * \param fn the function providing values + * \param partition the space partitioning data structure + */ + template + Value_function_3(const Function& fn, + const Partition& partition) + : m_fn{fn}, + m_partition{partition} + { } + +public: + /** + * \brief evaluates the function at the point `p` + */ + FT operator()(const Point_3& p) const + { + return m_fn(p); + } + + /** + * \brief evaluates the function at the vertex `v` + */ + FT operator()(const vertex_descriptor& v) const + { + return this->operator()(PT::point(v, m_partition)); + } +}; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_VALUE_FUNCTION_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/dual_contouring_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/dual_contouring_3.h new file mode 100644 index 000000000000..883707eeb0e6 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/dual_contouring_3.h @@ -0,0 +1,95 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Daniel Zint +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_DUAL_CONTOURING_3_H +#define CGAL_ISOSURFACING_3_DUAL_CONTOURING_3_H + +#include + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Methods_grp + * + * \brief creates a polygon soup that discretizes an isosurface using the %Dual Contouring algorithm. + * + * \details The point placement strategy within each cell of the space partition is based on + * Quadric Error Metrics ("QEM", or "QEF" in %Dual Contouring-related works). + * + * \tparam ConcurrencyTag enables sequential versus parallel algorithm. + * Possible values are `Sequential_tag`, `Parallel_if_available_tag`, or `Parallel_tag`. + * \tparam Domain must be a model of `IsosurfacingDomainWithGradient_3`. + * \tparam PointRange must be a model of the concept `AssociativeContainer` + * whose value type can be constructed from the point type of the domain. + * \tparam PolygonRange must be a model of the concepts `RandomAccessContainer` and `BackInsertionSequence` + * whose value type is itself a model of the concepts `RandomAccessContainer` + * and `BackInsertionSequence` whose value type is `std::size_t`. + * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" + * + * \param domain the domain providing the spatial partition and the values and gradient data + * \param isovalue the value defining the isosurface + * \param points the points of the polygons in the created polygon soup + * \param polygons each element in the vector describes a polygon using the indices of the points in `points` + * \param np optional \ref bgl_namedparameters "Named Parameters" described below + * + * \cgalNamedParamsBegin + * \cgalParamNBegin{constrain_to_cell} + * \cgalParamDescription{whether to constrain the vertex position to the geometrical space of its cell} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false`} + * \cgalParamExtra{Constraining the vertex to its dual cell guarantees that the resulting + * surface is a without self-intersections (non-manifoldness aside). Oppositely, + * an unconstrained positioning strategy might produce better looking surfaces + * near sharp features (ridges, corners), at the cost of possible self-intersections.} + * \cgalParamNEnd + * + * \cgalParamNBegin{do_not_triangulate_faces} + * \cgalParamDescription{If `true`, the output will contain quadrilaterals. + * If `false`, the output will contain triangles.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`false` (faces are triangulated)} + * \cgalParamExtra{Triangulating faces is done by inserting the intersection between an edge and + * the isosurface, and linking it to the dual points of the cells incident to the edge. + * If `constrain_to_cell` is set to `false`, triangulation faces can result in additional + * self-intersections. An alternative that has worse approximation but is less likely + * to produce self-intersections is to use the function + * `CGAL::Polygon_mesh_processing::triangulate_faces()`.} + * \cgalParamNEnd + * \cgalNamedParamsEnd + * + * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` + */ +template +void dual_contouring(const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + PointRange& points, + PolygonRange& polygons, + const NamedParameters& np = parameters::default_values()) +{ + internal::Dual_contourer contourer; + contourer(domain, isovalue, points, polygons, np); +} + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_DUAL_CONTOURING_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/edge_intersection_oracles_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/edge_intersection_oracles_3.h new file mode 100644 index 000000000000..f7cd7392eaa1 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/edge_intersection_oracles_3.h @@ -0,0 +1,208 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_EDGE_INTERSECTION_ORACLES_3_H +#define CGAL_ISOSURFACING_3_EDGE_INTERSECTION_ORACLES_3_H + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Domain_helpers_grp + * + * \cgalModels{IsosurfacingEdgeIntersectionOracle_3} + * + * \brief The class `Dichotomy_edge_intersection` uses a dichotomy to find the intersection point + * between an edge and the isosurface. + * + * This class is for example suitable to be used as the `EdgeIntersectionOracle` template parameter of isosurfacing + * domain classes when values are computed using an implicit function. + * It is however not optimal when the values are interpolated from discrete values + * since the intersection can be computed analytically in this case. + * + * \sa `CGAL::Isosurfacing::Linear_interpolation_edge_intersection` + * \sa `CGAL::Isosurfacing::Marching_cubes_domain_3` + * \sa `CGAL::Isosurfacing::Dual_contouring_domain_3` + */ +struct Dichotomy_edge_intersection +{ + unsigned int m_max_iterations = 10; + double m_relative_eps = 1e-7; + + /*! + * \brief computes the intersection point between an edge and the isosurface. + * + * \tparam Domain must be a model of `IsosurfacingDomain_3` + * + * \param p_0 the geometric position of the first vertex of the edge + * \param p_1 the geometric position of the second vertex of the edge + * \param val_0 the value at the first vertex of the edge + * \param val_1 the value at the second vertex of the edge + * \param domain the isosurfacing domain + * \param isovalue the isovalue defining the isosurface with which we seek an intersection + * \param p the intersection point, if it exists + * + * \return `true` if the intersection point exists, `false` otherwise + */ + template // == Isosurfacing_domain_3 or similar + bool operator()(const typename Domain::Geom_traits::Point_3& p_0, + const typename Domain::Geom_traits::Point_3& p_1, + const typename Domain::Geom_traits::FT val_0, + const typename Domain::Geom_traits::FT val_1, + const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + typename Domain::Geom_traits::Point_3& p) const + { + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = domain.geom_traits().construct_point_3_object(); + + const bool sl = (val_0 <= isovalue); + const bool sr = (val_1 <= isovalue); + + if(sl == sr) + return false; + + Point_3 pl = p_0; + Point_3 pr = p_1; + + unsigned int dichotomy_iterations = m_max_iterations, iter = 0; + const FT eps = (std::max)(FT(m_relative_eps), std::abs(isovalue) * FT(m_relative_eps)); + do + { + p = point((x_coord(pl) + x_coord(pr)) / FT(2), + (y_coord(pl) + y_coord(pr)) / FT(2), + (z_coord(pl) + z_coord(pr)) / FT(2)); + + const FT val_p = domain.value(p); + const bool sp = (val_p <= isovalue); + + if(sl == sp) + pl = p; + else if(sp == sr) + pr = p; + else + break; + + if(std::abs(val_p - isovalue) < eps) + return true; + } + while(++iter < dichotomy_iterations); + + + return true; + } +}; + +/** + * \ingroup IS_Domain_helpers_grp + * + * \cgalModels{IsosurfacingEdgeIntersectionOracle_3} + * + * \brief The class `Linear_interpolation_edge_intersection` uses linear interpolation + * to find the intersection point between an edge and the isosurface. + * + * This class is for example suitable when interpolated discrete values are being used. + * + * \sa `CGAL::Isosurfacing::Dichotomy_edge_intersection` + * \sa `CGAL::Isosurfacing::Marching_cubes_domain_3` + * \sa `CGAL::Isosurfacing::Dual_contouring_domain_3` + * \sa `CGAL::Isosurfacing::Interpolated_discrete_values_3` + */ +struct Linear_interpolation_edge_intersection +{ + /*! + * \brief computes the intersection point between an edge and the isosurface. + * + * \tparam Domain must be a model of `IsosurfacingDomain_3` + * + * \param p_0 the geometric position of the first vertex of the edge + * \param p_1 the geometric position of the second vertex of the edge + * \param val_0 the value at the first vertex of the edge + * \param val_1 the value at the second vertex of the edge + * \param domain the isosurfacing domain + * \param isovalue the isovalue defining the isosurface with which we seek an intersection + * \param p the intersection point, if it exists + * + * \return `true` if the intersection point exists, `false` otherwise + */ + template + bool operator()(const typename Domain::Geom_traits::Point_3& p_0, + const typename Domain::Geom_traits::Point_3& p_1, + const typename Domain::Geom_traits::FT val_0, + const typename Domain::Geom_traits::FT val_1, + const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + typename Domain::Geom_traits::Point_3& p) const + { + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = domain.geom_traits().construct_point_3_object(); + + if((val_0 <= isovalue) == (val_1 <= isovalue)) + return false; + + const FT den = val_0 - val_1; + const FT u = is_zero(den) ? 0.5 : (val_0 - isovalue) / den; + p = point((FT(1) - u) * x_coord(p_0) + u * x_coord(p_1), + (FT(1) - u) * y_coord(p_0) + u * y_coord(p_1), + (FT(1) - u) * z_coord(p_0) + u * z_coord(p_1)); + + return true; + } +}; + +#ifndef DOXYGEN_RUNNING +/* + * \ingroup IS_Domain_helpers_grp + * + * \cgalModels{IsosurfacingEdgeIntersectionOracle_3} + * + * \brief The class `Ray_marching_edge_intersection` uses ray marching to find the intersection point + * between an edge and the isosurface. + * + * This class is suitable when the values stem from a signed distance function. + */ +// +// @todo this is for the case where we know domain.value is an SDF +// then we can do better than a dichotomy +// Take code from the AW3 sharp branch +struct Ray_marching_edge_intersection +{ + template // == Isosurfacing_domain_3 or similar + bool operator()(const typename Domain::Geom_traits::Point_3& p_0, + const typename Domain::Geom_traits::Point_3& p_1, + const typename Domain::Geom_traits::FT val_0, + const typename Domain::Geom_traits::FT val_1, + const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + typename Domain::Geom_traits::Point_3& p) const; +}; +#endif // DOXYGEN_RUNNING + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_EDGE_INTERSECTION_ORACLES_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Cell_type.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Cell_type.h new file mode 100644 index 000000000000..5ee49b209602 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Cell_type.h @@ -0,0 +1,34 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_DOMAIN_CELL_TYPE_H +#define CGAL_ISOSURFACING_3_INTERNAL_DOMAIN_CELL_TYPE_H + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +// Was supposed to check if an algorithm can handle a specific domain. Not used right now. +using Cell_type = std::size_t; + +static constexpr Cell_type ANY_CELL = (std::numeric_limits::max)(); + +static constexpr Cell_type POLYHEDRAL_CELL = (std::size_t(1) << 0); +static constexpr Cell_type TETRAHEDRAL_CELL = (std::size_t(1) << 1); +static constexpr Cell_type CUBICAL_CELL = (std::size_t(1) << 2); + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_DOMAIN_CELL_TYPE_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Isosurfacing_domain_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Isosurfacing_domain_3.h new file mode 100644 index 000000000000..9eb701372651 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Isosurfacing_domain_3.h @@ -0,0 +1,175 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_ISOSURFACING_DOMAIN_3_H +#define CGAL_ISOSURFACING_3_INTERNAL_ISOSURFACING_DOMAIN_3_H + +#include + +#include +#include +#include + +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +// This class is pretty much just the concatenation of the following classes: +// - Partition: Space partitioning data structure, e.g. Cartesian grid, octree, ... +// - Values: values over the 3D space +// - Gradients: gradients over the 3D space +// - Oracle: edge-isosurface intersection computation +template +class Isosurfacing_domain_3 +{ +public: + using Edge_intersection_oracle = IntersectionOracle; + + using Geom_traits = typename Partition::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using PT = CGAL::Isosurfacing::partition_traits; + + using vertex_descriptor = typename PT::vertex_descriptor; + using edge_descriptor = typename PT::edge_descriptor; + using cell_descriptor = typename PT::cell_descriptor; + + using Edge_vertices = typename PT::Edge_vertices; + using Cells_incident_to_edge = typename PT::Cells_incident_to_edge; + using Cell_vertices = typename PT::Cell_vertices; + using Cell_edges = typename PT::Cell_edges; + + static constexpr Cell_type CELL_TYPE = PT::CELL_TYPE; + static constexpr std::size_t VERTICES_PER_CELL = PT::VERTICES_PER_CELL; + static constexpr std::size_t EDGES_PER_CELL = PT::EDGES_PER_CELL; + +private: + const Partition& m_partition; + const ValueField& m_values; + const GradientField& m_gradients; + const IntersectionOracle m_intersection_oracle; + +public: + Isosurfacing_domain_3(const Partition& partition, + const ValueField& values, + const GradientField& gradients, + const IntersectionOracle& intersection_oracle = IntersectionOracle()) + : m_partition{partition}, + m_values{values}, + m_gradients{gradients}, + m_intersection_oracle{intersection_oracle} + { } + + const Geom_traits& geom_traits() const + { + return m_partition.geom_traits(); + } + + const Edge_intersection_oracle& intersection_oracle() const + { + return m_intersection_oracle; + } + +public: + // The following functions are dispatching to the partition_traits' static functions. + + // returns the position of vertex `v` + decltype(auto) /*Point_3*/ point(const vertex_descriptor& v) const + { + return PT::point(v, m_partition); + } + + // returns the value of the function at vertex `v` + decltype(auto) /*FT*/ value(const vertex_descriptor& v) const + { + return m_values(v); + } + + // returns the value of the function at point `p` + decltype(auto) /*FT*/ value(const Point_3& p) const + { + return m_values(p); + } + + // returns the gradient at point `p` + decltype(auto) /*Vector_3*/ gradient(const Point_3& p) const + { + return m_gradients(p); + } + + // returns a container with the two vertices incident to the edge `e` + decltype(auto) /*Edge_vertices*/ incident_vertices(const edge_descriptor& e) const + { + return PT::incident_vertices(e, m_partition); + } + + // returns a container with all cells incident to the edge `e` + decltype(auto) /*Cells_incident_to_edge*/ incident_cells(const edge_descriptor& e) const + { + return PT::incident_cells(e, m_partition); + } + + // returns a container with all vertices of the cell `c` + decltype(auto) /*Cell_vertices*/ cell_vertices(const cell_descriptor& c) const + { + return PT::cell_vertices(c, m_partition); + } + + // returns a container with all edges of the cell `c` + decltype(auto) /*Cell_edges*/ cell_edges(const cell_descriptor& c) const + { + return PT::cell_edges(c, m_partition); + } + + // iterates over all vertices `v`, calling `f(v)` on each of them + template + void for_each_vertex(Functor& f) const + { + PT::for_each_vertex(f, m_partition, ConcurrencyTag{}); + } + + // iterates over all edges `e`, calling `f(e)` on each of them + template + void for_each_edge(Functor& f) const + { + PT::for_each_edge(f, m_partition, ConcurrencyTag{}); + } + + // iterates over all cells `c`, calling `f(c)` on each of them + template + void for_each_cell(Functor& f) const + { + PT::for_each_cell(f, m_partition, ConcurrencyTag{}); + } + + // finds the intersection of the isosurface with the edge `e` (if any) + bool construct_intersection(const Point_3& p_0, const Point_3& p_1, + const FT val_0, const FT val_1, + const FT isovalue, + Point_3& p) const + { + return m_intersection_oracle(p_0, p_1, val_0, val_1, *this, isovalue, p); + } +}; + +} // namespace internal +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_ISOSURFACING_DOMAIN_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Octree_wrapper.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Octree_wrapper.h new file mode 100644 index 000000000000..dbd9aeee92da --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Octree_wrapper.h @@ -0,0 +1,627 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Daniel Zint +// Julian Stahl + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_OCTREE_WRAPPER_H +#define CGAL_ISOSURFACING_3_INTERNAL_OCTREE_WRAPPER_H + +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +// this is to be able to specialize std::hash +struct OW_Edge_handle : public std::tuple +{ + using std::tuple::tuple; // inherit constructors +}; + +} // namespace internal +} // namespace Isosurfacing +} // namespace CGAL + +namespace std { + +template <> +struct hash +{ + std::size_t operator()(const CGAL::Isosurfacing::internal::OW_Edge_handle& e) const + { + std::size_t seed = 0; + boost::hash_combine(seed, std::get<0>(e)); + boost::hash_combine(seed, std::get<1>(e)); + return seed; + } +}; + +} // namespace std + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +template +class Octree_wrapper +{ + /* + * Naming convention from "A parallel dual marching cubes approach to quad + * only surface reconstruction - Grosso & Zint" + * + * ^ y + * | + * v2------e2------v3 + * /| /| + * e11| e10| + * / e3 / e1 + * v6------e6------v7 | + * | | | | + * | v0------e0--|---v1 --> x + * e7 / e5 / + * | e8 | e9 + * |/ |/ + * v4------e4------v5 + * / + * < z + */ + +public: + using Geom_traits = GeomTraits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using Octree = CGAL::Octree >; + + using Vertex_handle = std::size_t; + using Edge_handle = OW_Edge_handle; + using Voxel_handle = std::size_t; + + using Node_index = typename Octree::Node_index; + using Uniform_coords = typename Octree::Global_coordinates; // coordinates on max depth level + +private: + std::size_t max_depth_ = 0; + + FT offset_x_; + FT offset_y_; + FT offset_z_; + + // @todo should be iso_cuboid_3 if we mirrored the other domains, but we'll see + // once CGAL::Octree has been updated and this wrapper isn't required anymore. + CGAL::Bbox_3 bbox_; + + std::size_t dim_ = 1; + + FT hx_ = 0; + + std::vector point_range_; + Octree octree_; + GeomTraits gt_; + + // std::set leaf_node_uniform_coordinates_; + std::vector leaf_voxels_; + std::vector leaf_edges_; + std::vector leaf_vertices_; + +public: + Octree_wrapper(const CGAL::Bbox_3& bbox) + : offset_x_(bbox.xmin()), + offset_y_(bbox.ymin()), + offset_z_(bbox.zmin()), + bbox_(bbox), + point_range_({{bbox.xmin(), bbox.ymin(), bbox.zmin()}, + {bbox.xmax(), bbox.ymax(), bbox.zmax()}}), + octree_(point_range_) + { } + + template + void refine(const Split_predicate& split_predicate) + { + namespace Tables = internal::Cube_table; + + octree_.refine(split_predicate); + + max_depth_ = octree_.depth(); + dim_ = std::size_t(1) << max_depth_; + hx_ = bbox_.x_span() / dim_; + + // store leaf elements in sets + initialize value maps + std::set leaf_voxels_set; + std::set leaf_edges_set; + std::set leaf_vertices_set; + for(Node_index node_index : octree_.traverse(CGAL::Orthtrees::Leaves_traversal(octree_))) + { + const Uniform_coords& coords_uniform = uniform_coordinates(node_index); + // write all leaf nodes in a set + leaf_voxels_set.insert(lex_index(coords_uniform[0], coords_uniform[1], coords_uniform[2], max_depth_)); + + // init vertex values + for(int i=0; i= dim_ || y >= dim_ || z >= dim_) + { + are_all_voxels_leafs = false; + break; + } + + const Node_index n = get_node(x, y, z); + if(octree_.depth(n) > depth) + { + are_all_voxels_leafs = false; + break; + } + } + + if(are_all_voxels_leafs) + { + // add to leaf edge set + std::size_t e_gl = e_glIndex(edge_voxels[0][3], + coords_global[0], coords_global[1], coords_global[2], + depth); + leaf_edges_set.insert({e_gl, depth}); + } + } + } + + leaf_voxels_ = std::vector{leaf_voxels_set.begin(), leaf_voxels_set.end()}; + leaf_edges_ = std::vector{leaf_edges_set.begin(), leaf_edges_set.end()}; + leaf_vertices_ = std::vector{leaf_vertices_set.begin(), leaf_vertices_set.end()}; + } + + const Octree& octree() const + { + return octree_; + } + + const Geom_traits& geom_traits() const + { + return gt_; + } + + std::size_t dim() const + { + return dim_; + } + + FT hx() const + { + return hx_; + } + + FT offset_x() const + { + return offset_x_; + } + + FT offset_y() const + { + return offset_y_; + } + + FT offset_z() const + { + return offset_z_; + } + + std::size_t max_depth() const + { + return max_depth_; + } + + const std::vector& leaf_edges() const + { + return leaf_edges_; + } + + const std::vector& leaf_vertices() const + { + return leaf_vertices_; + } + + const std::vector& leaf_voxels() const + { + return leaf_voxels_; + } + + std::size_t depth_factor(const std::size_t depth) const + { + return std::size_t(1) << (max_depth_ - depth); + } + + Uniform_coords uniform_coordinates(Node_index node_index) const + { + Uniform_coords coords = octree_.global_coordinates(node_index); + const std::size_t df = depth_factor(octree_.depth(node_index)); + for(int i=0; i<3; ++i) + coords[i] *= static_cast(df); + + return coords; + } + + std::array node_points(Node_index node_index) const + { + const Uniform_coords& coords = octree_.global_coordinates(node_index); + const std::size_t df = depth_factor(octree_.depth(node_index)); + + const FT x0 = offset_x_ + coords[0] * df * hx_; + const FT y0 = offset_y_ + coords[1] * df * hx_; + const FT z0 = offset_z_ + coords[2] * df * hx_; + const FT x1 = offset_x_ + (coords[0] + 1) * df * hx_; + const FT y1 = offset_y_ + (coords[1] + 1) * df * hx_; + const FT z1 = offset_z_ + (coords[2] + 1) * df * hx_; + + std::array points; + points[0] = {x0, y0, z0}; + points[1] = {x1, y0, z0}; + points[2] = {x0, y1, z0}; + points[3] = {x1, y1, z0}; + + points[4] = {x0, y0, z1}; + points[5] = {x1, y0, z1}; + points[6] = {x0, y1, z1}; + points[7] = {x1, y1, z1}; + + return points; + } + + Point_3 point(const Uniform_coords& vertex_coordinates) const + { + const FT x0 = offset_x_ + vertex_coordinates[0] * hx_; + const FT y0 = offset_y_ + vertex_coordinates[1] * hx_; + const FT z0 = offset_z_ + vertex_coordinates[2] * hx_; + + return { x0, y0, z0 }; + } + + Point_3 point(const Vertex_handle& v) const + { + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(v, max_depth_); + + const FT x0 = offset_x_ + i * hx_; + const FT y0 = offset_y_ + j * hx_; + const FT z0 = offset_z_ + k * hx_; + + return { x0, y0, z0 }; + } + + Uniform_coords vertex_uniform_coordinates(Node_index node_index, + const typename Octree::Local_coordinates local_coords) const + { + const Uniform_coords& node_coords = octree_.global_coordinates(node_index); + Uniform_coords v_coords = node_coords; + for(int i=0; i<3; ++i) + v_coords[i] += std::size_t(local_coords[i]); + + const std::size_t df = depth_factor(octree_.depth(node_index)); + for(int i=0; i<3; ++i) + v_coords[i] *= static_cast(df); + + return v_coords; + } + + Node_index get_node(const std::size_t i, + const std::size_t j, + const std::size_t k) const + { + Node_index node_index = octree_.root(); + const std::size_t x = i; + const std::size_t y = j; + const std::size_t z = k; + + while(!octree_.is_leaf(node_index)) + { + std::size_t dist_to_max = max_depth_ - octree_.depth(node_index) - 1; + typename Octree::Local_coordinates loc; + if(x & (std::size_t(1) << dist_to_max)) + loc[0] = true; + + if(y & (std::size_t(1) << dist_to_max)) + loc[1] = true; + + if(z & (std::size_t(1) << dist_to_max)) + loc[2] = true; + + node_index = octree_.child(node_index, loc.to_ulong()); + } + + return node_index; + } + + Node_index get_node(const std::size_t lex_index) const + { + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(lex_index, max_depth_); + return get_node(i, j, k); + } + + std::size_t lex_index(const std::size_t i, + const std::size_t j, + const std::size_t k, + const std::size_t depth) const + { + std::size_t dim = (std::size_t(1) << depth) + 1; + return k * dim * dim + j * dim + i; + } + + std::size_t i_index(const std::size_t lex_index, + const std::size_t depth) const + { + std::size_t dim = (std::size_t(1) << depth) + 1; + return lex_index % dim; + } + + std::size_t j_index(const std::size_t lex_index, + const std::size_t depth) const + { + std::size_t dim = (std::size_t(1) << depth) + 1; + return ((lex_index / dim) % dim); + } + + std::size_t k_index(const std::size_t lex_index, + const std::size_t depth) const + { + std::size_t dim = (std::size_t(1) << depth) + 1; + return (lex_index / (dim * dim)); + } + + std::tuple ijk_index(const std::size_t lex_index, + const std::size_t depth) const + { + const std::size_t dim = (std::size_t(1) << depth) + 1; + return std::make_tuple(lex_index % dim, (lex_index / dim) % dim, lex_index / (dim * dim)); + } + + // computes unique edge global index. + // \param e local edge index + // \param i_idx i-index of cell + // \param j_idx j-index of cell + // \param k_idx k-index of cell + // \param depth of cell + std::size_t e_glIndex(const std::size_t e, + const std::size_t i_idx, + const std::size_t j_idx, + const std::size_t k_idx, + const std::size_t depth) const + { + const unsigned long long gei_pattern_ = 670526590282893600ull; + const size_t i = i_idx + (size_t)((gei_pattern_ >> 5 * e) & 1); // global_edge_id[eg][0]; + const size_t j = j_idx + (size_t)((gei_pattern_ >> (5 * e + 1)) & 1); // global_edge_id[eg][1]; + const size_t k = k_idx + (size_t)((gei_pattern_ >> (5 * e + 2)) & 1); // global_edge_id[eg][2]; + const size_t offs = (size_t)((gei_pattern_ >> (5 * e + 3)) & 3); + + return (3 * lex_index(i, j, k, depth) + offs); + } + + // std::array voxel_values(const Voxel_handle& vox) const + // { + // namespace Tables = internal::Cube_table; + // + // std::size_t i, j, k; + // std::tie(i, j, k) = ijk_index(vox, max_depth_); + // Node node = get_node(i, j, k); + // const auto& df = depth_factor(node.depth()); + // + // std::array v; + // for(int v_id=0; v_id s; + // std::transform(v.begin(), v.end(), s.begin(), [this](const auto& e) { return this->vertex_values_.at(e); }); + // + // return s; + // } + + std::array voxel_edges(const Voxel_handle& vox) const + { + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(vox, max_depth_); + Node_index node_index = get_node(i, j, k); + + const Uniform_coords& coords_global = octree_.global_coordinates(node_index); + const std::size_t depth = octree_.depth(node_index); + + std::array edges; + for(std::size_t e_id=0; e_id voxel_vertices(const Voxel_handle& vox) const + { + namespace Tables = internal::Cube_table; + + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(vox, max_depth_); + Node_index node_index = get_node(i, j, k); + const std::size_t df = depth_factor(octree_.depth(node_index)); + + std::array v; + for(int v_id=0; v_id voxel_gradients(const Voxel_handle& vox) const + // { + // namespace Tables = internal::Cube_table; + // + // std::size_t i, j, k; + // std::tie(i, j, k) = ijk_index(vox, max_depth_); + // Node node = get_node(i, j, k); + // const auto& df = depth_factor(node.depth()); + // + // std::array v; + // for(int v_id=0; v_id s; + // std::transform(v.begin(), v.end(), s.begin(), [this](const auto& e) { return this->vertex_gradients_.at(e); }); + // + // return s; + // } + + std::array voxel_vertex_positions(const Voxel_handle& vox) const + { + Node_index node_index = get_node(vox); + return node_points(node_index); + } + +/* + // returns the values at the incident two vertices. + // Vertices are sorted in ascending order. + std::array edge_values(const Edge_handle& e_id) const + { + namespace Tables = internal::Cube_table; + + std::size_t e_global_id, depth; + std::tie(e_global_id, depth) = e_id; + const std::size_t df = depth_factor(depth); + + const size_t v0_lex_index = e_global_id / 3; + std::size_t i0, j0, k0; + std::tie(i0, j0, k0) = ijk_index(v0_lex_index, depth); + + // v1 + const std::size_t e_local_index = Tables::edge_store_index[e_global_id % 3]; + const int* v1_local = Tables::local_vertex_position[Tables::edge_to_vertex[e_local_index][1]]; + + const std::size_t i1 = i0 + v1_local[0]; + const std::size_t j1 = j0 + v1_local[1]; + const std::size_t k1 = k0 + v1_local[2]; + + const auto v0 = lex_index(df * i0, df * j0, df * k0, max_depth_); + const auto v1 = lex_index(df * i1, df * j1, df * k1, max_depth_); + + return { value(v0), value(v1) }; + } +*/ + + std::array edge_vertices(const Edge_handle& e_id) const + { + namespace Tables = internal::Cube_table; + + std::size_t e_global_id, depth; + std::tie(e_global_id, depth) = static_cast&>(e_id); + const std::size_t df = depth_factor(depth); + + const size_t v0_lex_index = e_global_id / 3; + std::size_t i0, j0, k0; + std::tie(i0, j0, k0) = ijk_index(v0_lex_index, depth); + + // v1 + const std::size_t e_local_index = Tables::edge_store_index[e_global_id % 3]; + const int* v1_local = Tables::local_vertex_position[Tables::edge_to_vertex[e_local_index][1]]; + + const std::size_t i1 = i0 + v1_local[0]; + const std::size_t j1 = j0 + v1_local[1]; + const std::size_t k1 = k0 + v1_local[2]; + + const std::size_t v0 = lex_index(df * i0, df * j0, df * k0, max_depth_); + const std::size_t v1 = lex_index(df * i1, df * j1, df * k1, max_depth_); + + return { v0, v1 }; + } + + /// returns the 4 voxels incident to an edge. If an edge has only three incident + /// voxels, one will appear twice. The voxels are given with the uniform + /// lexicographical index. + std::array edge_voxels(const Edge_handle& e_id) const + { + namespace Tables = internal::Cube_table; + + std::size_t e_global_id, depth; + std::tie(e_global_id, depth) = static_cast&>(e_id); + const std::size_t e_local_index = Tables::edge_store_index[e_global_id % 3]; + + const std::size_t df = depth_factor(depth); + + const size_t v0_lex_index = e_global_id / 3; + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(v0_lex_index, depth); + i *= df; + j *= df; + k *= df; + + const auto& voxel_neighbors = Tables::edge_to_voxel_neighbor[e_local_index]; + Node_index n0 = get_node(i + voxel_neighbors[0][0], j + voxel_neighbors[0][1], k + voxel_neighbors[0][2]); + Node_index n1 = get_node(i + voxel_neighbors[1][0], j + voxel_neighbors[1][1], k + voxel_neighbors[1][2]); + Node_index n2 = get_node(i + voxel_neighbors[2][0], j + voxel_neighbors[2][1], k + voxel_neighbors[2][2]); + Node_index n3 = get_node(i + voxel_neighbors[3][0], j + voxel_neighbors[3][1], k + voxel_neighbors[3][2]); + + const Uniform_coords n0_uniform_coords = uniform_coordinates(n0); + const Uniform_coords n1_uniform_coords = uniform_coordinates(n1); + const Uniform_coords n2_uniform_coords = uniform_coordinates(n2); + const Uniform_coords n3_uniform_coords = uniform_coordinates(n3); + + std::size_t n0_lex = lex_index(n0_uniform_coords[0], n0_uniform_coords[1], n0_uniform_coords[2], max_depth_); + std::size_t n1_lex = lex_index(n1_uniform_coords[0], n1_uniform_coords[1], n1_uniform_coords[2], max_depth_); + std::size_t n2_lex = lex_index(n2_uniform_coords[0], n2_uniform_coords[1], n2_uniform_coords[2], max_depth_); + std::size_t n3_lex = lex_index(n3_uniform_coords[0], n3_uniform_coords[1], n3_uniform_coords[2], max_depth_); + + return { n0_lex, n1_lex, n2_lex, n3_lex }; + } +}; + +} // namespace internal +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_OCTREE_WRAPPER_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/dual_contouring_functors.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/dual_contouring_functors.h new file mode 100644 index 000000000000..d53522dafe06 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/dual_contouring_functors.h @@ -0,0 +1,660 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Daniel Zint +// Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_DUAL_CONTOURING_FUNCTORS_H +#define CGAL_ISOSURFACING_3_INTERNAL_DUAL_CONTOURING_FUNCTORS_H + +#include + +#include +#include +#include +#include + +#ifdef CGAL_EIGEN3_ENABLED +#include +#include + +#include +#endif + +#ifdef CGAL_LINKED_WITH_TBB +#if TBB_INTERFACE_VERSION < 12010 && !defined(TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS) +#define CGAL_HAS_DEFINED_TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS +#define TBB_PREVIEW_CONCURRENT_ORDERED_CONTAINERS 1 +#endif +#include +#endif +#include +#include +#include +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +template +bool cell_position_QEM(const typename Domain::cell_descriptor& c, + const Domain& domain, + const bool constrain_to_cell, + const EdgeToPointIDMap& edge_to_point_id, + const PointRange& edge_points, + const GradientRange& edge_gradients, + typename Domain::Geom_traits::Point_3& p) +{ + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using Eigen_vector_3 = Eigen_vector; + using Eigen_matrix_3 = Eigen_matrix; + using Eigen_vector_x = Eigen_vector; + using Eigen_matrix_x = Eigen_matrix; + + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = domain.geom_traits().construct_point_3_object(); + + // compute edge intersections + std::vector cell_edge_intersections; + std::vector cell_edge_intersection_normals; + + for(const auto& edge : domain.cell_edges(c)) + { + const auto it = edge_to_point_id.find(edge); + if(it == edge_to_point_id.end()) + continue; + + cell_edge_intersections.push_back(edge_points[it->second]); + cell_edge_intersection_normals.push_back(edge_gradients[it->second]); + } + + const std::size_t en = cell_edge_intersections.size(); + if(en == 0) + return false; + +#ifdef CGAL_ISOSURFACING_3_DC_FUNCTORS_DEBUG + std::cout << "Points and normals: " << std::endl; + for(std::size_t i=0; i::max)(); // @todo domain.span() + x_max = y_max = z_max = std::numeric_limits::lowest(); + FT x(0), y(0), z(0); + + typename Domain::Cell_vertices vertices = domain.cell_vertices(c); + for(const auto& v : vertices) + { + const Point_3& cp = domain.point(v); + if(constrain_to_cell) + { + x_min = (std::min)(x_min, x_coord(cp)); + y_min = (std::min)(y_min, y_coord(cp)); + z_min = (std::min)(z_min, z_coord(cp)); + + x_max = (std::max)(x_max, x_coord(cp)); + y_max = (std::max)(y_max, y_coord(cp)); + z_max = (std::max)(z_max, z_coord(cp)); + } + } + + for(const auto& ep : cell_edge_intersections) + { + x += x_coord(ep); + y += y_coord(ep); + z += z_coord(ep); + } + + Point_3 com = point(x / FT(en), y / FT(en), z / FT(en)); + +#ifdef CGAL_ISOSURFACING_3_DC_FUNCTORS_DEBUG + std::cout << "cell: " << x_min << " " << y_min << " " << z_min << " " << x_max << " " << y_max << " " << z_max << std::endl; + std::cout << "COM " << com << std::endl; +#endif + + // SVD QEM + Eigen_matrix_3 A; + A.setZero(); + Eigen_vector_3 rhs; + rhs.setZero(); + for(std::size_t i=0; i etc., + // so the double conversion is not implicit + Eigen_matrix_3 A_k = typename Eigen_matrix_3::EigenType(n_k * n_k.transpose()); + Eigen_vector_3 b_k; + b_k = d_k * n_k; + A += A_k; + rhs += b_k; + } + + Eigen::JacobiSVD svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV); + + // Ju's paper, "Dual Contouring of Hermite Data": 1e-1 + // Lindstrom's paper, "Out-of-Core Simplification of Large Polygonal Models": 1e-3 + svd.setThreshold(1e-3); + + Eigen_vector_3 x_hat; + x_hat << x_coord(com), y_coord(com), z_coord(com); + + // Lindstrom formula for QEM new position for singular matrices + Eigen_vector_x v_svd; + v_svd = x_hat + svd.solve(rhs - A * x_hat); + + + if(constrain_to_cell) + { + // @todo clamping back doesn't necessarily yield the optimal position within the cell + v_svd[0] = std::clamp(v_svd[0], x_min, x_max); + v_svd[1] = std::clamp(v_svd[1], y_min, y_max); + v_svd[2] = std::clamp(v_svd[2], z_min, z_max); + } + + p = point(v_svd[0], v_svd[1], v_svd[2]); + +#ifdef CGAL_ISOSURFACING_3_DC_FUNCTORS_DEBUG + std::cout << "CGAL QEM POINT: " << v_svd[0] << " " << v_svd[1] << " " << v_svd[2] << std::endl; + std::cout << "CGAL clamped QEM POINT: " << p[0] << " " << p[1] << " " << p[2] << std::endl; + std::cout << "--" << std::endl; +#endif + + return true; +} + +template +void generate_face(const typename Domain::edge_descriptor& e, + const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + const bool do_not_triangulate_faces, + const EdgeToPointIDMap& edge_to_point_id, + const CellToPointIDMap& cell_to_point_id, + std::mutex& mutex, + PolygonRange& polygons) +{ + using FT = typename Domain::Geom_traits::FT; + + using cell_descriptor = typename Domain::cell_descriptor; + + const auto& vertices = domain.incident_vertices(e); + + // @todo this check could be avoided for QEM: active edges are in `edge_to_point_id` + const FT val_0 = domain.value(vertices[0]); + const FT val_1 = domain.value(vertices[1]); + if((val_0 <= isovalue) == (val_1 <= isovalue)) + return; + + std::vector vertex_ids; + + const auto& icells = domain.incident_cells(e); + for(const cell_descriptor& c : icells) + { + auto it = cell_to_point_id.find(c); + if(it == cell_to_point_id.end()) + continue; + + vertex_ids.push_back(it->second); + } + + if(vertex_ids.size() < 3) + return; + + if(val_0 > val_1) + std::reverse(vertex_ids.begin(), vertex_ids.end()); + + // @todo? filter degenerate faces? + + if(do_not_triangulate_faces) + { + std::lock_guard lock(mutex); + polygons.emplace_back(); + CGAL::internal::resize(polygons.back(), vertex_ids.size()); + std::copy(vertex_ids.begin(), vertex_ids.end(), std::begin(polygons.back())); + } + else + { + auto it = edge_to_point_id.find(e); + if(it == edge_to_point_id.end()) + { + CGAL_assertion(false); + return; + } + const std::size_t ei = it->second; + + std::lock_guard lock(mutex); + for(std::size_t i=0; i +class Dual_contourer; + +template +class Dual_contourer +{ + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using vertex_descriptor = typename Domain::vertex_descriptor; + using edge_descriptor = typename Domain::edge_descriptor; + using cell_descriptor = typename Domain::cell_descriptor; + + std::mutex m_mutex; + +public: + template + void operator()(const Domain& domain, + const FT isovalue, + PointRange& points, + PolygonRange& polygons, + const NamedParameters& np = parameters::default_values()) + { + using parameters::choose_parameter; + using parameters::get_parameter; + + // Otherwise the `edge_to_point_id` map might be messed up + CGAL_precondition(points.empty()); + CGAL_precondition(polygons.empty()); + + const bool constrain_to_cell = choose_parameter(get_parameter(np, internal_np::constrain_to_cell), false); + + const bool do_not_triangulate_faces = + choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), false); + + using Edge_to_point_ID_map = std::unordered_map; + using Cell_to_point_ID_map = std::unordered_map; + + Edge_to_point_ID_map edge_to_point_id; + Cell_to_point_ID_map cell_to_point_id; + + std::vector edge_points; + std::vector edge_gradients; + + // --------------------------------------------------------------------------------------------- + // construct the intersection of the surface at active edges + auto edge_positioner = [&](const edge_descriptor& e) + { + const auto& evs = domain.incident_vertices(e); + const vertex_descriptor& v0 = evs[0]; + const vertex_descriptor& v1 = evs[1]; + const Point_3& p0 = domain.point(v0); + const Point_3& p1 = domain.point(v1); + const FT val0 = domain.value(v0); + const FT val1 = domain.value(v1); + + Point_3 p; + bool res = domain.construct_intersection(p0, p1, val0, val1, isovalue, p); + if(!res) + return; + + const Vector_3 g = domain.gradient(p); + + std::lock_guard lock(m_mutex); + edge_to_point_id[e] = edge_points.size(); + edge_points.push_back(p); + edge_gradients.push_back(g); + }; + domain.template for_each_edge(edge_positioner); + +#ifdef CGAL_ISOSURFACING_3_DC_FUNCTORS_DEBUG + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + + std::ofstream out_active_edges("active_edges.polylines"); + for(const auto& ei : edge_to_point_id) + { + const edge_descriptor& e = ei.first; + const auto& evs = domain.incident_vertices(e); + const vertex_descriptor& v0 = evs[0]; + const vertex_descriptor& v1 = evs[1]; + const Point_3& p0 = domain.point(v0); + const Point_3& p1 = domain.point(v1); + + out_active_edges << "2 " << x_coord(p0) << " " << y_coord(p0) << " " << z_coord(p0) << " " << x_coord(p1) << " " << y_coord(p1) << " " << z_coord(p1) << std::endl; + } + + std::ofstream out_edge_intersections("edge_intersections.polylines"); + for(const auto& ei : edge_to_point_id) + { + const Point_3& p = edge_points.at(ei.second); + const Vector_3& g = edge_gradients.at(ei.second); + + out_edge_intersections << "2 " << x_coord(p) << " " << y_coord(p) << " " << z_coord(p) << " " << x_coord(p) + x_coord(g) << " " << y_coord(p) + y_coord(g) << " " << z_coord(p) + z_coord(g) << std::endl; + } +#endif + + if(!do_not_triangulate_faces) + points.insert(points.end(), edge_points.begin(), edge_points.end()); + + // --------------------------------------------------------------------------------------------- + // create a vertex for each cell that has at least one active edge + auto cell_positioner = [&](const cell_descriptor& c) + { + Point_3 p; + if(cell_position_QEM(c, domain, constrain_to_cell, edge_to_point_id, + edge_points, edge_gradients, p)) + { + std::lock_guard lock(m_mutex); // @todo useless if sequential + cell_to_point_id[c] = points.size(); + points.push_back(p); + } + }; + domain.template for_each_cell(cell_positioner); + + // --------------------------------------------------------------------------------------------- + // connect vertices around edges to form faces + auto face_generator = [&](const edge_descriptor& e) + { + generate_face(e, domain, isovalue, do_not_triangulate_faces, + edge_to_point_id, cell_to_point_id, m_mutex, polygons); + }; + domain.template for_each_edge(face_generator); + +#ifdef CGAL_ISOSURFACING_3_DC_FUNCTORS_DEBUG + std::cout << points.size() << " points" << std::endl; + std::cout << polygons.size() << " polygons" << std::endl; +#endif + } +}; + +template +class Dual_contourer +{ + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using vertex_descriptor = typename Domain::vertex_descriptor; + using edge_descriptor = typename Domain::edge_descriptor; + using cell_descriptor = typename Domain::cell_descriptor; + + std::mutex m_mutex; + +public: + template + void operator()(const Domain& domain, + const typename Geom_traits::FT isovalue, + PointRange& points, + PolygonRange& polygons, + const NamedParameters& np = parameters::default_values()) + { + using parameters::choose_parameter; + using parameters::get_parameter; + + // Otherwise the `edge_to_point_id` map might be messed up + CGAL_precondition(points.empty()); + CGAL_precondition(polygons.empty()); + + bool do_not_triangulate_faces = + choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), false); + + using Edge_to_point_ID_map = std::unordered_map; + using Cell_to_point_ID_map = std::unordered_map; + + Edge_to_point_ID_map edge_to_point_id; + Cell_to_point_ID_map cell_to_point_id; + + std::vector edge_points; + + // --------------------------------------------------------------------------------------------- + auto edge_positioner = [&](const edge_descriptor& e) + { + const auto& evs = domain.incident_vertices(e); + const vertex_descriptor& v0 = evs[0]; + const vertex_descriptor& v1 = evs[1]; + const Point_3& p0 = domain.point(v0); + const Point_3& p1 = domain.point(v1); + const FT val0 = domain.value(v0); + const FT val1 = domain.value(v1); + + Point_3 p; + bool res = domain.construct_intersection(p0, p1, val0, val1, isovalue, p); + if(!res) + return; + + std::lock_guard lock(m_mutex); + edge_to_point_id[e] = edge_points.size(); + edge_points.push_back(p); + }; + domain.template for_each_edge(edge_positioner); + + if(!do_not_triangulate_faces) + points.insert(points.end(), edge_points.begin(), edge_points.end()); + + // --------------------------------------------------------------------------------------------- + auto cell_positioner = [&](const cell_descriptor& c) + { + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = domain.geom_traits().construct_point_3_object(); + + // compute edge intersections + std::vector edge_intersections; + for(const edge_descriptor& e : domain.cell_edges(c)) + { + const auto it = edge_to_point_id.find(e); + if(it == edge_to_point_id.end()) + continue; + + edge_intersections.push_back(edge_points[it->second]); // @todo could avoid copying + } + + const std::size_t en = edge_intersections.size(); + if(en == 0) + return; + + FT x = 0, y = 0, z = 0; + for(const Point_3& p : edge_intersections) + { + x += x_coord(p); + y += y_coord(p); + z += z_coord(p); + } + + const Point_3 p = point(x / FT(en), y / FT(en), z / FT(en)); + + std::lock_guard lock(m_mutex); + cell_to_point_id[c] = points.size(); + points.push_back(p); + }; + domain.template for_each_cell(cell_positioner); + + // --------------------------------------------------------------------------------------------- + auto face_generator = [&](const edge_descriptor& e) + { + generate_face(e, domain, isovalue, do_not_triangulate_faces, + edge_to_point_id, cell_to_point_id, m_mutex, polygons); + }; + domain.template for_each_edge(face_generator); + } +}; + +template +class Dual_contourer +{ + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + using vertex_descriptor = typename Domain::vertex_descriptor; + using edge_descriptor = typename Domain::edge_descriptor; + using cell_descriptor = typename Domain::cell_descriptor; + + std::mutex m_mutex; + +public: + template + void operator()(const Domain& domain, + const FT isovalue, + PointRange& points, + PolygonRange& polygons, + const NamedParameters& np = parameters::default_values()) + { + using parameters::choose_parameter; + using parameters::get_parameter; + + // Otherwise the `edge_to_point_id` map might be messed up + CGAL_precondition(points.empty()); + CGAL_precondition(polygons.empty()); + + bool do_not_triangulate_faces = + choose_parameter(get_parameter(np, internal_np::do_not_triangulate_faces), false); + + using Edge_to_point_ID_map = std::unordered_map; + using Cell_to_point_ID_map = std::unordered_map; + + Edge_to_point_ID_map edge_to_point_id; + Cell_to_point_ID_map cell_to_point_id; + + std::vector edge_points; + + // --------------------------------------------------------------------------------------------- + auto edge_positioner = [&](const edge_descriptor& e) + { + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = domain.geom_traits().construct_point_3_object(); + + const auto& evs = domain.incident_vertices(e); + const vertex_descriptor& v0 = evs[0]; + const vertex_descriptor& v1 = evs[1]; + + const FT val_0 = domain.value(v0); + const FT val_1 = domain.value(v1); + if((val_0 <= isovalue) == (val_1 <= isovalue)) + return; + + Point_3 p = point((x_coord(domain.point(v0)) + x_coord(domain.point(v1))) / FT(2), + (y_coord(domain.point(v0)) + y_coord(domain.point(v1))) / FT(2), + (z_coord(domain.point(v0)) + z_coord(domain.point(v1))) / FT(2)); + + std::lock_guard lock(m_mutex); + edge_to_point_id[e] = edge_points.size(); + edge_points.push_back(p); + }; + domain.template for_each_edge(edge_positioner); + + if(!do_not_triangulate_faces) + points.insert(points.end(), edge_points.begin(), edge_points.end()); + + // --------------------------------------------------------------------------------------------- + auto cell_positioner = [&](const cell_descriptor& c) + { + typename Geom_traits::Compute_x_3 x_coord = domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = domain.geom_traits().construct_point_3_object(); + + typename Domain::Cell_vertices vertices = domain.cell_vertices(c); + const std::size_t cn = vertices.size(); + + bool all_smaller = true; + bool all_greater = true; + for(const auto& v : vertices) + { + const bool b = (domain.value(v) <= isovalue); + all_smaller = all_smaller && b; + all_greater = all_greater && !b; + } + + if(all_smaller || all_greater) + return; + + FT x(0), y(0), z(0); + for(const auto& v : vertices) + { + const Point_3& cp = domain.point(v); + x += x_coord(cp); + y += y_coord(cp); + z += z_coord(cp); + } + + // set point to cell center + Point_3 p = point(x / cn, y / cn, z / cn); + + std::lock_guard lock(m_mutex); + cell_to_point_id[c] = points.size(); + points.push_back(p); + }; + domain.template for_each_cell(cell_positioner); + + // --------------------------------------------------------------------------------------------- + auto face_generator = [&](const edge_descriptor& e) + { + generate_face(e, domain, isovalue, do_not_triangulate_faces, + edge_to_point_id, cell_to_point_id, m_mutex, polygons); + }; + domain.template for_each_edge(face_generator); + } +}; + +} // namespace internal +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_DUAL_CONTOURING_FUNCTORS_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/implicit_shapes_helper.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/implicit_shapes_helper.h new file mode 100644 index 000000000000..24c918a6d7f9 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/implicit_shapes_helper.h @@ -0,0 +1,185 @@ +// Copyright (c) 2024 INRIA Sophia-Antipolis (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Mael Rouxel-Labbé + +// This file is only used by the testsuite and examples + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_IMPLICIT_SHAPES_HELPER_H +#define CGAL_ISOSURFACING_3_INTERNAL_IMPLICIT_SHAPES_HELPER_H + +#include + +#include // quiet the CI in case this file gets compiled alone + +namespace CGAL { +namespace Isosurfacing { +namespace Shapes { + +// Shapes are defined at isovalue 0 + +// c is the center +// r the radius +template +typename K::FT +sphere(const typename K::Point_3& c, + const typename K::FT r, + const typename K::Point_3& q) +{ + return CGAL::approximate_sqrt(CGAL::squared_distance(c, q)) - r; +} + +template +typename K::FT +box(const typename K::Point_3& b, + const typename K::Point_3& t, + const typename K::Point_3& q) +{ + typename K::Point_3 c = CGAL::midpoint(b, t); + typename K::Iso_cuboid_3 ic(b, t); + bool inside = ic.has_on_bounded_side(q); + typename K::FT d = 0; + if(inside) + { + d = (std::min)({CGAL::abs(q.x() - b.x()), CGAL::abs(q.x() - t.x()), + CGAL::abs(q.y() - b.y()), CGAL::abs(q.y() - t.y()), + CGAL::abs(q.z() - b.z()), CGAL::abs(q.z() - t.z())}); + } + else + { + for(int i=0; i<3; ++i) + d += (CGAL::abs(q[i] - c[i]) > (c[i] - b[i]) ? CGAL::square(q[i] - c[i]) : 0); + d = CGAL::approximate_sqrt(d); + } + + return inside ? - d : d; +} + +// template +// typename K::FT +// disk(const typename K::Point_3& c, +// const typename K::Vector_3& n, +// const typename K::FT r, +// const typename K::Point_3& q) +// { +// typename K::Plane_3 pl(c, n); +// typename K::Point_3 pq = pl.projection(q); + +// typename K::FT sq_dpl = CGAL::squared_distance(q, pl); + +// if(CGAL::squared_distance(pq, c) < CGAL::square(r)) +// return CGAL::approximate_sqrt(sq_dpl); +// else +// return CGAL::approximate_sqrt(CGAL::square(CGAL::approximate_sqrt(CGAL::squared_distance(pq, c)) - r) + sq_dpl); +// } + + + +// p is the center of the base disk +// q is the center of the top disk +template +typename K::FT +infinite_cylinder(const typename K::Point_3& b, + const typename K::Vector_3& n, + const typename K::FT r, + const typename K::Point_3& q) +{ + typename K::Plane_3 pl(b, n); + typename K::Point_3 pq = pl.projection(q); + return CGAL::approximate_sqrt(CGAL::squared_distance(pq, b)) - r; +} + + +// c is the center of the torus +// n is the normal of the plane containing all centers of the tube +// r is the small radius +// R is the large radius +template +typename K::FT +torus(const typename K::Point_3& c, + const typename K::Vector_3& n, + const typename K::FT r, + const typename K::FT R, + const typename K::Point_3& q) +{ + typename K::Vector_3 w (c, q); + typename K::Plane_3 pl(c, n); + typename K::Point_3 pq = pl.projection(q); + typename K::FT d = CGAL::approximate_sqrt(CGAL::squared_distance(pq, c)) - R; + typename K::FT h = CGAL::abs(CGAL::scalar_product(w, n)); + + return CGAL::approximate_sqrt(CGAL::square(d) + CGAL::square(h)) - r; +} + +template +typename K::FT +torus_ridge(const typename K::Point_3& c, + const typename K::Vector_3& n, + const typename K::FT r, + const typename K::FT R, + const typename K::Point_3& q) +{ + typename K::Vector_3 w (c, q); + typename K::Plane_3 pl(c, n); + typename K::Point_3 pq = pl.projection(q); + typename K::FT d = CGAL::abs(CGAL::approximate_sqrt(CGAL::squared_distance(pq, c)) - R) - r; + return d + CGAL::squared_distance(q, pl); +} + +template +typename K::FT +inverted_torus(const typename K::Point_3& c, + const typename K::Vector_3& n, + const typename K::FT r, + const typename K::FT R, + const typename K::Point_3& q) +{ + typename K::Vector_3 w (c, q); + typename K::Plane_3 pl(c, n); + typename K::Point_3 pq = pl.projection(q); + typename K::FT d = CGAL::abs(CGAL::approximate_sqrt(CGAL::squared_distance(pq, c)) - R) - r; + return d - CGAL::squared_distance(q, pl); +} + +///////////////////////////////////////////////////////////////// + +template +typename K::FT +shape_union(const S1& s1, const S2& s2, const typename K::Point_3& q) +{ + return std::min(s1(q), s2(q)); +} + +template +typename K::FT +shape_difference(const S1& s1, const S2& s2, const typename K::Point_3& q) +{ + return std::max(s1(q), -s2(q)); +} + +template +typename K::FT +shape_intersection(const S1& s1, const S2& s2, const typename K::Point_3& q) +{ + return std::max(s1(q), s2(q)); +} + +template +typename K::FT +shape_symmetric_difference(const S1& s1, const S2& s2, const typename K::Point_3& q) +{ + return std::max(-std::min(s1(q), s2(q)), std::max(s1(q), s2(q))); +} + + +} // namespace Shapes +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_IMPLICIT_SHAPES_HELPER_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/marching_cubes_functors.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/marching_cubes_functors.h new file mode 100644 index 000000000000..1c6dd8e53c16 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/marching_cubes_functors.h @@ -0,0 +1,316 @@ +// Copyright (c) 2020 INRIA Sophia-Antipolis (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: ( GPL-3.0-or-later OR LicenseRef-Commercial ) AND MIT +// +// Author(s) : Julian Stahl +// +// This file incorporates work covered by the following copyright and permission notice: +// +// MIT License +// +// Copyright (c) 2020 Roberto Grosso +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// The code below uses the version of +// https://github.com/rogrosso/tmc available on 15th of September 2022. +// + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_MARCHING_CUBES_FUNCTORS_H +#define CGAL_ISOSURFACING_3_INTERNAL_MARCHING_CUBES_FUNCTORS_H + +#include + +#include + +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#else +#include +#endif + +#include +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +// Interpolate linearly between two vertex positions v0, v1 with values d0 and d1 according to the isovalue +template +typename GeomTraits::Point_3 vertex_interpolation(const typename GeomTraits::Point_3& p0, + const typename GeomTraits::Point_3& p1, + const typename GeomTraits::FT d0, + const typename GeomTraits::FT d1, + const typename GeomTraits::FT isovalue, + const GeomTraits& gt) +{ + using FT = typename GeomTraits::FT; + + typename GeomTraits::Compute_x_3 x_coord = gt.compute_x_3_object(); + typename GeomTraits::Compute_y_3 y_coord = gt.compute_y_3_object(); + typename GeomTraits::Compute_z_3 z_coord = gt.compute_z_3_object(); + typename GeomTraits::Construct_point_3 point = gt.construct_point_3_object(); + + FT mu = FT(0); + + // @todo, technically we should be using the edge intersection oracle here, but there is a nuance + // between MC and DC on the handling of edges that have val0 = val1 = isovalue: in MC we assume + // the isosurface is in the middle, in DC we assume the isosurface is not intersecting the edge. + // In the oracle, we follow DC right now. Could put a Boolean parameter, but it's ugly. + + // don't divide by 0 + if(abs(d1 - d0) < 0.000001) // @fixme hardcoded bound + mu = FT(0.5); // if both points have the same value, assume isolevel is in the middle + else + mu = (isovalue - d0) / (d1 - d0); + + CGAL_assertion(mu >= FT(0.0) || mu <= FT(1.0)); + + // linear interpolation + return point(x_coord(p1) * mu + x_coord(p0) * (FT(1) - mu), + y_coord(p1) * mu + y_coord(p0) * (FT(1) - mu), + z_coord(p1) * mu + z_coord(p0) * (FT(1) - mu)); +} + +// retrieves the values of a cell and return the lookup index +// if the cell is completely above or below the isovalue, corner points are not computed +template +std::size_t get_cell_corners(const Domain& domain, + const typename Domain::cell_descriptor& cell, + const typename Domain::Geom_traits::FT isovalue, + Corners& corners, + Values& values) +{ + using vertex_descriptor = typename Domain::vertex_descriptor; + + const auto& vertices = domain.cell_vertices(cell); + + // collect function values and build index + std::size_t v_id = 0; + std::bitset index = 0; + for(const vertex_descriptor& v : vertices) + { + values[v_id] = domain.value(v); + if(values[v_id] >= isovalue) + index.set(v_id); + + ++v_id; + } + + if(index.all() || index.none()) // nothing's happening in this cell + return static_cast(index.to_ullong()); + + v_id = 0; + for(const vertex_descriptor& v : vertices) + corners[v_id++] = domain.point(v); + + return static_cast(index.to_ullong()); +} + +// creates the vertices on the edges of one cell +template +void MC_construct_vertices(const typename Domain::cell_descriptor& cell, + const std::size_t i_case, + const Corners& corners, + const Values& values, + const typename Domain::Geom_traits::FT isovalue, + const Domain& domain, + Vertices& vertices) +{ + using Cell_edges = typename Domain::Cell_edges; + using edge_descriptor = typename Domain::edge_descriptor; + + const Cell_edges& cell_edges = domain.cell_edges(cell); + + // compute for this case the vertices + std::size_t flag = 1; + std::size_t e_id = 0; + + for(const edge_descriptor& e : cell_edges) + { + CGAL_USE(e); + + if(flag & Cube_table::intersected_edges[i_case]) + { + // generate vertex here, do not care at this point if vertex already exists + const int v0 = Cube_table::edge_to_vertex[e_id][0]; + const int v1 = Cube_table::edge_to_vertex[e_id][1]; + + vertices[e_id] = vertex_interpolation(corners[v0], corners[v1], + values[v0], values[v1], + isovalue, domain.geom_traits()); + } + + flag <<= 1; + ++e_id; + } +} + +// connects the vertices of one cell to form triangles +template +void MC_construct_triangles(const std::size_t i_case, + const Vertices& vertices, + TriangleList& triangles) +{ + // construct triangles + for(int t=0; t<16; t+=3) + { + const std::size_t t_index = i_case * 16 + t; + + // if(e_tris_list[t_index] == 0x7f) + if(Cube_table::triangle_cases[t_index] == -1) + break; + + // @todo move more of this stuff into the table + const int eg0 = Cube_table::triangle_cases[t_index + 0]; + const int eg1 = Cube_table::triangle_cases[t_index + 1]; + const int eg2 = Cube_table::triangle_cases[t_index + 2]; + + // insert new triangle in list +#ifdef CGAL_LINKED_WITH_TBB + auto& tris = triangles.local(); +#else + auto& tris = triangles; +#endif + + tris.push_back({vertices[eg0], vertices[eg1], vertices[eg2]}); + } +} + +template +void triangles_to_polygon_soup(const TriangleRange& triangles, + PointRange& points, + PolygonRange& polygons) +{ +#ifdef CGAL_LINKED_WITH_TBB + for(const auto& triangle_list : triangles) + { +#else + const auto& triangle_list = triangles; +#endif + + for(const auto& triangle : triangle_list) + { + const std::size_t id = points.size(); + + points.push_back(triangle[0]); + points.push_back(triangle[1]); + points.push_back(triangle[2]); + + // simply use increasing indices + polygons.push_back({id + 2, id + 1, id + 0}); + + // just a safeguard against arrays of the wrong size + CGAL_assertion(polygons.back().size() == 3); + } + +#ifdef CGAL_LINKED_WITH_TBB + } +#endif +} + +// Marching Cubes implemented as a functor that runs on every cell of the grid +template +class Marching_cubes_3 +{ +public: + using Domain = Domain_; + + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + + using cell_descriptor = typename Domain::cell_descriptor; + +#ifdef CGAL_LINKED_WITH_TBB + using Triangles = tbb::enumerable_thread_specific>>; +#else + using Triangles = std::vector >; +#endif + +private: + const Domain& m_domain; + const FT m_isovalue; + + Triangles m_triangles; + +public: + // creates a Marching Cubes functor for a domain and isovalue + Marching_cubes_3(const Domain& domain, + const FT isovalue) + : m_domain(domain), + m_isovalue(isovalue) + { } + + // returns the created triangle list + Triangles& triangles() + { + return m_triangles; + } + +public: + // computes one cell + void operator()(const cell_descriptor& cell) + { + CGAL_precondition(m_domain.cell_vertices(cell).size() == 8); + CGAL_precondition(m_domain.cell_edges(cell).size() == 12); + + // @todo for SDFs, we could query at the center of the voxel an early exit + + constexpr std::size_t vpc = Domain::VERTICES_PER_CELL; + + std::array values; + std::array corners; + const std::size_t i_case = get_cell_corners(m_domain, cell, m_isovalue, corners, values); + + // skip empty / full cells + constexpr std::size_t ones = (1 << vpc) - 1; + if((i_case & ones) == ones || // all bits set + (i_case & ones) == 0) // no bits set + return; + + std::array vertices; + MC_construct_vertices(cell, i_case, corners, values, m_isovalue, m_domain, vertices); + + MC_construct_triangles(i_case, vertices, m_triangles); + } +}; + +} // namespace internal +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_MARCHING_CUBES_FUNCTORS_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits.h new file mode 100644 index 000000000000..baff7a8e5834 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits.h @@ -0,0 +1,26 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_H +#define CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_H + +#include + +namespace CGAL { +namespace Isosurfacing { + +template +struct partition_traits; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Cartesian_grid.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Cartesian_grid.h new file mode 100644 index 000000000000..4268f6d95410 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Cartesian_grid.h @@ -0,0 +1,295 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_CARTESIAN_GRID_3_H +#define CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_CARTESIAN_GRID_3_H + +#include + +#include +#include +#include + +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#include +#endif // CGAL_LINKED_WITH_TBB + +#include + +#include + +namespace CGAL { +namespace Isosurfacing { + +template +class Cartesian_grid_3; + +template +struct partition_traits; + +struct CG_Edge_descriptor : public std::array { }; +struct CG_Cell_descriptor : public std::array { }; + +template +struct partition_traits > +{ + using Grid = Cartesian_grid_3; + + // identifies a vertex by its (i, j, k) indices + using vertex_descriptor = std::array; + + // identifies an edge by its starting vertex (i, j, k) and the direction x -> 0, y -> 1, z -> 2 + using edge_descriptor = CG_Edge_descriptor; + + // identifies a cell by its corner vertex with the smallest (i, j, k) index + using cell_descriptor = CG_Cell_descriptor; + + static constexpr Cell_type CELL_TYPE = CUBICAL_CELL; + static constexpr std::size_t VERTICES_PER_CELL = 8; + static constexpr std::size_t EDGES_PER_CELL = 12; + + using Edge_vertices = std::array; + using Cells_incident_to_edge = std::array; + using Cell_vertices = std::array; + using Cell_edges = std::array; + + static decltype(auto) /*Point_3*/ point(const vertex_descriptor& v, + const Grid& g) + { + return g.point(v[0], v[1], v[2]); + } + + // returns a container with the two vertices incident to edge e + static Edge_vertices incident_vertices(const edge_descriptor& e, + const Grid&) + { + Edge_vertices ev; + ev[0] = { e[0], e[1], e[2] }; // start vertex + ev[1] = { e[0], e[1], e[2] }; // end vertex + ev[1][e[3]] += 1; // one position further in the direction of the edge + return ev; + } + + // returns a container with all cells incident to edge e + static Cells_incident_to_edge incident_cells(const edge_descriptor& e, + const Grid&) + { + // lookup the neighbor cells relative to the edge + const int local = internal::Cube_table::edge_store_index[e[3]]; + auto neighbors = internal::Cube_table::edge_to_voxel_neighbor[local]; + + Cells_incident_to_edge cite; + for(std::size_t i=0; i + static void for_each_vertex(Functor& f, + const Grid& g, + const CGAL::Sequential_tag) + { + for(std::size_t i=0; i + static void for_each_edge(Functor& f, + const Grid& g, + const CGAL::Sequential_tag) + { + for(std::size_t i=0; i + static void for_each_cell(Functor& f, + const Grid& g, + const CGAL::Sequential_tag) + { + for(std::size_t i=0; i + static void for_each_vertex(Functor& f, + const Grid& g, + const CGAL::Parallel_tag) + { + const std::size_t sj = g.ydim(); + const std::size_t sk = g.zdim(); + + // for now only parallelize outer loop + auto iterator = [&f, sj, sk](const tbb::blocked_range& r) + { + for(std::size_t i=r.begin(); i!=r.end(); ++i) + for(std::size_t j=0; j(0, g.xdim()), iterator); + } + + // iterates in parallel over all edges e calling f(e) on every one + template + static void for_each_edge(Functor& f, + const Grid& g, + const CGAL::Parallel_tag) + { + const std::size_t sj = g.ydim(); + const std::size_t sk = g.zdim(); + + // for now only parallelize outer loop + auto iterator = [&f, sj, sk](const tbb::blocked_range& r) + { + for(std::size_t i=r.begin(); i != r.end(); ++i) { + for(std::size_t j=0; j(0, g.xdim() - 1), iterator); + } + + // iterates in parallel over all cells c calling f(c) on every one + template + static void for_each_cell(Functor& f, + const Grid& g, + const CGAL::Parallel_tag) + { + // for now only parallelize outer loop + auto iterator = [&f](const tbb::blocked_range3d& r) + { + const std::size_t i_begin = r.pages().begin(); + const std::size_t i_end = r.pages().end(); + const std::size_t j_begin = r.rows().begin(); + const std::size_t j_end = r.rows().end(); + const std::size_t k_begin = r.cols().begin(); + const std::size_t k_end = r.cols().end(); + + for(std::size_t i = i_begin; i != i_end; ++i) + for(std::size_t j = j_begin; j != j_end; ++j) + for(std::size_t k = k_begin; k != k_end; ++k) + f({i, j, k}); + }; + + tbb::blocked_range3d range(0, g.xdim() - 1, 0, g.ydim() - 1, 0, g.zdim() - 1); + tbb::parallel_for(range, iterator); + } + #endif // CGAL_LINKED_WITH_TBB +}; + +} // namespace Isosurfacing +} // namespace CGAL + +namespace std { + +template <> +struct hash +{ + std::size_t operator()(const CGAL::Isosurfacing::CG_Edge_descriptor& e) const + { + std::size_t seed = 0; + boost::hash_combine(seed, e[0]); + boost::hash_combine(seed, e[1]); + boost::hash_combine(seed, e[2]); + boost::hash_combine(seed, e[3]); + return seed; + } +}; + +template <> +struct hash +{ + std::size_t operator()(const CGAL::Isosurfacing::CG_Cell_descriptor& e) const + { + std::size_t seed = 0; + boost::hash_combine(seed, e[0]); + boost::hash_combine(seed, e[1]); + boost::hash_combine(seed, e[2]); + return seed; + } +}; + +} // namespace std + +#endif // CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_CARTESIAN_GRID_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Octree.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Octree.h new file mode 100644 index 000000000000..29ad8bbfbd9c --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Octree.h @@ -0,0 +1,172 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_OCTREE_H +#define CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_OCTREE_H + +#include + +#include +#include + +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#endif // CGAL_LINKED_WITH_TBB + +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +template +class Octree_wrapper; + +} // namespace internal + +template +struct partition_traits; + +template +struct partition_traits > +{ + using Octree = internal::Octree_wrapper; + +public: + using vertex_descriptor = typename Octree::Vertex_handle; + using edge_descriptor = typename Octree::Edge_handle; + using cell_descriptor = typename Octree::Voxel_handle; + + static constexpr Cell_type CELL_TYPE = CUBICAL_CELL; + static constexpr std::size_t VERTICES_PER_CELL = 8; + static constexpr std::size_t EDGES_PER_CELL = 12; + + using Edge_vertices = std::array; + using Cells_incident_to_edge = std::array; // @todo: not always 4 + using Cell_vertices = std::array; + using Cell_edges = std::array; + +public: + static decltype(auto) /*Point_3*/ point(const vertex_descriptor& v, + const Octree& o) + { + return o.point(v); + } + + static Edge_vertices incident_vertices(const edge_descriptor& e, + const Octree& o) + { + return o.edge_vertices(e); + } + + static Cells_incident_to_edge incident_cells(const edge_descriptor& e, + const Octree& o) + { + return o.edge_voxels(e); + } + + static Cell_vertices cell_vertices(const cell_descriptor& c, + const Octree& o) + { + return o.voxel_vertices(c); + } + + static Cell_edges cell_edges(const cell_descriptor& c, + const Octree& o) + { + return o.voxel_edges(c); + } + + template + static void for_each_vertex(Functor& f, + const Octree& o, + const CGAL::Sequential_tag) + { + for(const vertex_descriptor& v : o.leaf_vertices()) + f(v); + } + + template + static void for_each_edge(Functor& f, + const Octree& o, + Sequential_tag) + { + for(const edge_descriptor& e : o.leaf_edges()) + f(e); + } + + template + static void for_each_cell(Functor& f, + const Octree& o, + CGAL::Sequential_tag) + { + for(const cell_descriptor& v : o.leaf_voxels()) + f(v); + } + +#ifdef CGAL_LINKED_WITH_TBB + template + static void for_each_vertex(Functor& f, + const Octree& o, + Parallel_tag) + { + const auto& vertices = o.leaf_vertices(); + + auto iterator = [&](const tbb::blocked_range& r) + { + for(std::size_t i = r.begin(); i != r.end(); ++i) + f(vertices[i]); + }; + + tbb::parallel_for(tbb::blocked_range(0, vertices.size()), iterator); + } + + template + static void for_each_edge(Functor& f, + const Octree& o, + Parallel_tag) + { + const auto& edges = o.leaf_edges(); + + auto iterator = [&](const tbb::blocked_range& r) + { + for(std::size_t i = r.begin(); i != r.end(); ++i) + f(edges[i]); + }; + + tbb::parallel_for(tbb::blocked_range(0, edges.size()), iterator); + } + + template + static void for_each_cell(Functor& f, + const Octree& o, + Parallel_tag) + { + const auto& cells = o.leaf_voxels(); + + auto iterator = [&](const tbb::blocked_range& r) + { + for(std::size_t i = r.begin(); i != r.end(); ++i) + f(cells[i]); + }; + + tbb::parallel_for(tbb::blocked_range(0, cells.size()), iterator); + } +#endif // CGAL_LINKED_WITH_TBB +}; + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_OCTREE_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/tables.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/tables.h new file mode 100644 index 000000000000..9b0771ffcf7b --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/tables.h @@ -0,0 +1,687 @@ +// Copyright (c) 2020 INRIA Sophia-Antipolis (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: ( GPL-3.0-or-later OR LicenseRef-Commercial ) AND MIT +// +// Author(s) : Julian Stahl +// +// This file incorporates work covered by the following copyright and permission notice: +// +// MIT License +// +// Copyright (c) 2020 Roberto Grosso +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// The code below uses the version of +// https://github.com/rogrosso/tmc available on 15th of September 2022. +// + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_TABLES_H +#define CGAL_ISOSURFACING_3_INTERNAL_TABLES_H + +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { +namespace Cube_table { +/* + * Naming convention from "A parallel dual marching cubes approach + * to quad only surface reconstruction - Grosso & Zint" + * + * ^ y + * | + * v2------e2------v3 + * /| /| + * e11| e10| + * / e3 / e1 + * v6------e6------v7 | + * | | | | + * | v0------e0--|---v1 --> x + * e7 / e5 / + * | e8 | e9 + * |/ |/ + * v4------e4------v5 + * / + * < z + */ + +constexpr int N_VERTICES = 8; +constexpr int N_EDGES = 12; + +// This table iterates around an edge of a voxel in positive direction, starting from the given voxel (0,0,0). The +// iteration is described in coordinates relative to the given voxel. The last number is the local edge index. +constexpr int edge_to_voxel_neighbor[N_EDGES][4][4] = +{ + {{0, 0, 0, 0}, {0, -1, 0, 2}, {0, -1, -1, 6}, {0, 0, -1, 4}}, // e0 + {{0, 0, 0, 1}, {1, 0, 0, 3}, {1, 0, -1, 7}, {0, 0, -1, 5}}, // e1 + {{0, 0, 0, 2}, {0, 0, -1, 6}, {0, 1, -1, 4}, {0, 1, 0, 0}}, // e2 + {{0, 0, 0, 3}, {0, 0, -1, 7}, {-1, 0, -1, 5}, {-1, 0, 0, 1}}, // e3 + {{0, 0, 0, 4}, {0, 0, 1, 0}, {0, -1, 1, 2}, {0, -1, 0, 6}}, // e4 + {{0, 0, 0, 5}, {0, 0, 1, 1}, {1, 0, 1, 3}, {1, 0, 0, 7}}, // e5 + {{0, 0, 0, 6}, {0, 1, 0, 4}, {0, 1, 1, 0}, {0, 0, 1, 2}}, // e6 + {{0, 0, 0, 7}, {-1, 0, 0, 5}, {-1, 0, 1, 1}, {0, 0, 1, 3}}, // e7 + {{0, 0, 0, 8}, {-1, 0, 0, 9}, {-1, -1, 0, 10}, {0, -1, 0, 11}}, // e8 + {{0, 0, 0, 9}, {0, -1, 0, 10}, {1, -1, 0, 11}, {1, 0, 0, 8}}, // e9 + {{0, 0, 0, 10}, {1, 0, 0, 11}, {1, 1, 0, 8}, {0, 1, 0, 9}}, // e10 + {{0, 0, 0, 11}, {0, 1, 0, 8}, {-1, 1, 0, 9}, {-1, 0, 0, 10}} // e11 +}; + +/* The global edge index consists of the lexicographical index of the v0 vertex of a voxel, and an index that + * represents the axis. This table maps from the axis index to the local edge index: 0 = x-axis --> 0 1 = y-axis --> + * 3 2 = z-axis --> 8 + */ +constexpr int edge_store_index[3] = {0, 3, 8}; + +// The local vertex indices of an edge. The indices are sorted by axis direction. +constexpr int edge_to_vertex[N_EDGES][2] = +{ + {0, 1}, // e0 + {1, 3}, // e1 + {2, 3}, // e2 + {0, 2}, // e3 + {4, 5}, // e4 + {5, 7}, // e5 + {6, 7}, // e6 + {4, 6}, // e7 + {0, 4}, // e8 + {1, 5}, // e9 + {3, 7}, // e10 + {2, 6} // e11 +}; + +// The local vertex coordinates within a voxel. +constexpr int local_vertex_position[N_VERTICES][3] = +{ + {0, 0, 0}, // v0 + {1, 0, 0}, // v1 + {0, 1, 0}, // v2 + {1, 1, 0}, // v3 + {0, 0, 1}, // v4 + {1, 0, 1}, // v5 + {0, 1, 1}, // v6 + {1, 1, 1} // v7 +}; + +// Edges are uniquely characterized by the two end vertices, which have a unique vertex id +// the end vertices of the edge are computed in the cell by giving the indices (i,j,k). +// These indices are obtained from the cell index by adding 0 or 1 to i, j or k respectively +// Example: edge 0: (i,j,k) - (i+1,j,k) +// edge 1: (i+1,j,k) - (i+1,j+1,k) +// The first 3 indices are for the first vertex and the second 3 for the second vertex. +// there are 12 edges, assign to each vertex three edges, the global edge numbering +// consist of 3*global_vertex_id + edge_offset. +constexpr int global_edge_id[][4] = {{0, 0, 0, 0}, {1, 0, 0, 1}, {0, 1, 0, 0}, {0, 0, 0, 1}, + {0, 0, 1, 0}, {1, 0, 1, 1}, {0, 1, 1, 0}, {0, 0, 1, 1}, + {0, 0, 0, 2}, {1, 0, 0, 2}, {1, 1, 0, 2}, {0, 1, 0, 2}}; + +// probably a list without errors +// indicates which edges has to be intersected +constexpr int intersected_edges[256] = +{ + 0, 265, 515, 778, 2060, 2309, 2575, 2822, 1030, 1295, 1541, 1804, 3082, 3331, 3593, 3840, 400, 153, 915, + 666, 2460, 2197, 2975, 2710, 1430, 1183, 1941, 1692, 3482, 3219, 3993, 3728, 560, 825, 51, 314, 2620, 2869, + 2111, 2358, 1590, 1855, 1077, 1340, 3642, 3891, 3129, 3376, 928, 681, 419, 170, 2988, 2725, 2479, 2214, 1958, + 1711, 1445, 1196, 4010, 3747, 3497, 3232, 2240, 2505, 2755, 3018, 204, 453, 719, 966, 3270, 3535, 3781, 4044, + 1226, 1475, 1737, 1984, 2384, 2137, 2899, 2650, 348, 85, 863, 598, 3414, 3167, 3925, 3676, 1370, 1107, 1881, + 1616, 2800, 3065, 2291, 2554, 764, 1013, 255, 502, 3830, 4095, 3317, 3580, 1786, 2035, 1273, 1520, 2912, 2665, + 2403, 2154, 876, 613, 367, 102, 3942, 3695, 3429, 3180, 1898, 1635, 1385, 1120, 1120, 1385, 1635, 1898, 3180, + 3429, 3695, 3942, 102, 367, 613, 876, 2154, 2403, 2665, 2912, 1520, 1273, 2035, 1786, 3580, 3317, 4095, 3830, + 502, 255, 1013, 764, 2554, 2291, 3065, 2800, 1616, 1881, 1107, 1370, 3676, 3925, 3167, 3414, 598, 863, 85, + 348, 2650, 2899, 2137, 2384, 1984, 1737, 1475, 1226, 4044, 3781, 3535, 3270, 966, 719, 453, 204, 3018, 2755, + 2505, 2240, 3232, 3497, 3747, 4010, 1196, 1445, 1711, 1958, 2214, 2479, 2725, 2988, 170, 419, 681, 928, 3376, + 3129, 3891, 3642, 1340, 1077, 1855, 1590, 2358, 2111, 2869, 2620, 314, 51, 825, 560, 3728, 3993, 3219, 3482, + 1692, 1941, 1183, 1430, 2710, 2975, 2197, 2460, 666, 915, 153, 400, 3840, 3593, 3331, 3082, 1804, 1541, 1295, + 1030, 2822, 2575, 2309, 2060, 778, 515, 265, 0 +}; + +// list of triangles for Marching Cubes case t, position at t*16 + tri +constexpr int triangle_cases[4096] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 0 <-> mc: 0, class rep: 0 + 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 1 <-> mc: 1, class rep: 1 + 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 2 <-> mc: 2, class rep: 1 + 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 3 <-> mc: 3, class rep: 3 + 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 4 <-> mc: 8, class rep: 1 + 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 5 <-> mc: 9, class rep: 3 + 1, 0, 9, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 6 <-> mc: 10, class rep: 6 + 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1, // quitte: 7 <-> mc: 11, class rep: 7 + 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 8 <-> mc: 4, class rep: 1 + 0, 3, 8, 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 9 <-> mc: 5, class rep: 6 + 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 10 <-> mc: 6, class rep: 3 + 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 11 <-> mc: 7, class rep: 7 + 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 12 <-> mc: 12, class rep: 3 + 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 13 <-> mc: 13, class rep: 7 + 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 14 <-> mc: 14, class rep: 7 + 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 15 <-> mc: 15, class rep: 15 + 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 16 <-> mc: 16, class rep: 1 + 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 17 <-> mc: 17, class rep: 3 + 0, 9, 1, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 18 <-> mc: 18, class rep: 6 + 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 19 <-> mc: 19, class rep: 7 + 8, 7, 4, 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 20 <-> mc: 24, class rep: 6 + 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1, // quitte: 21 <-> mc: 25, class rep: 7 + 9, 1, 0, 8, 7, 4, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 22 <-> mc: 26, class rep: 22 + 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1, // quitte: 23 <-> mc: 27, class rep: 23 + 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 24 <-> mc: 20, class rep: 24 + 3, 7, 4, 3, 4, 0, 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, // quitte: 25 <-> mc: 21, class rep: 25 + 9, 10, 2, 9, 2, 0, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 26 <-> mc: 22, class rep: 25 + 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1, // quitte: 27 <-> mc: 23, class rep: 27 + 3, 1, 10, 3, 10, 11, 7, 4, 8, -1, -1, -1, -1, -1, -1, -1, // quitte: 28 <-> mc: 28, class rep: 25 + 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1, // quitte: 29 <-> mc: 29, class rep: 29 + 4, 8, 7, 9, 11, 0, 9, 10, 11, 11, 3, 0, -1, -1, -1, -1, // quitte: 30 <-> mc: 30, class rep: 30 + 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 31 <-> mc: 31, class rep: 7 + 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 32 <-> mc: 32, class rep: 1 + 9, 4, 5, 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 33 <-> mc: 33, class rep: 6 + 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 34 <-> mc: 34, class rep: 3 + 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1, // quitte: 35 <-> mc: 35, class rep: 7 + 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 36 <-> mc: 40, class rep: 24 + 0, 2, 11, 0, 11, 8, 4, 5, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 37 <-> mc: 41, class rep: 25 + 0, 4, 5, 0, 5, 1, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 38 <-> mc: 42, class rep: 25 + 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1, // quitte: 39 <-> mc: 43, class rep: 29 + 1, 10, 2, 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 40 <-> mc: 36, class rep: 6 + 3, 8, 0, 1, 10, 2, 4, 5, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 41 <-> mc: 37, class rep: 22 + 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1, // quitte: 42 <-> mc: 38, class rep: 7 + 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1, // quitte: 43 <-> mc: 39, class rep: 23 + 10, 11, 3, 10, 3, 1, 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, // quitte: 44 <-> mc: 44, class rep: 25 + 4, 5, 9, 0, 1, 8, 8, 1, 10, 8, 10, 11, -1, -1, -1, -1, // quitte: 45 <-> mc: 45, class rep: 30 + 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1, // quitte: 46 <-> mc: 46, class rep: 27 + 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, // quitte: 47 <-> mc: 47, class rep: 7 + 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 48 <-> mc: 48, class rep: 3 + 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 49 <-> mc: 49, class rep: 7 + 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1, // quitte: 50 <-> mc: 50, class rep: 7 + 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 51 <-> mc: 51, class rep: 15 + 7, 5, 9, 7, 9, 8, 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 52 <-> mc: 56, class rep: 25 + 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1, // quitte: 53 <-> mc: 57, class rep: 27 + 2, 11, 3, 0, 8, 1, 1, 8, 7, 1, 7, 5, -1, -1, -1, -1, // quitte: 54 <-> mc: 58, class rep: 30 + 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1, // quitte: 55 <-> mc: 59, class rep: 7 + 9, 8, 7, 9, 7, 5, 10, 2, 1, -1, -1, -1, -1, -1, -1, -1, // quitte: 56 <-> mc: 52, class rep: 25 + 10, 2, 1, 9, 0, 5, 5, 0, 3, 5, 3, 7, -1, -1, -1, -1, // quitte: 57 <-> mc: 53, class rep: 30 + 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1, // quitte: 58 <-> mc: 54, class rep: 29 + 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, // quitte: 59 <-> mc: 55, class rep: 7 + 9, 8, 5, 8, 7, 5, 10, 3, 1, 10, 11, 3, -1, -1, -1, -1, // quitte: 60 <-> mc: 60, class rep: 60 + 5, 0, 7, 5, 9, 0, 7, 0, 11, 1, 10, 0, 11, 0, 10, -1, // quitte: 61 <-> mc: 61, class rep: 25 + 11, 0, 10, 11, 3, 0, 10, 0, 5, 8, 7, 0, 5, 0, 7, -1, // quitte: 62 <-> mc: 62, class rep: 25 + 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 63 <-> mc: 63, class rep: 3 + 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 64 <-> mc: 128, class rep: 1 + 3, 8, 0, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 65 <-> mc: 129, class rep: 6 + 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 66 <-> mc: 130, class rep: 24 + 8, 9, 1, 8, 1, 3, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 67 <-> mc: 131, class rep: 25 + 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 68 <-> mc: 136, class rep: 3 + 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1, // quitte: 69 <-> mc: 137, class rep: 7 + 2, 6, 7, 2, 7, 3, 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, // quitte: 70 <-> mc: 138, class rep: 25 + 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1, // quitte: 71 <-> mc: 139, class rep: 27 + 10, 2, 1, 6, 7, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 72 <-> mc: 132, class rep: 6 + 1, 10, 2, 3, 8, 0, 6, 7, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 73 <-> mc: 133, class rep: 22 + 2, 0, 9, 2, 9, 10, 6, 7, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 74 <-> mc: 134, class rep: 25 + 6, 7, 11, 2, 3, 10, 10, 3, 8, 10, 8, 9, -1, -1, -1, -1, // quitte: 75 <-> mc: 135, class rep: 30 + 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 76 <-> mc: 140, class rep: 7 + 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1, // quitte: 77 <-> mc: 141, class rep: 23 + 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1, // quitte: 78 <-> mc: 142, class rep: 29 + 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 79 <-> mc: 143, class rep: 7 + 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 80 <-> mc: 144, class rep: 3 + 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 81 <-> mc: 145, class rep: 7 + 8, 11, 6, 8, 6, 4, 9, 1, 0, -1, -1, -1, -1, -1, -1, -1, // quitte: 82 <-> mc: 146, class rep: 25 + 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1, // quitte: 83 <-> mc: 147, class rep: 29 + 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 84 <-> mc: 152, class rep: 7 + 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 85 <-> mc: 153, class rep: 15 + 1, 0, 9, 2, 4, 3, 2, 6, 4, 4, 8, 3, -1, -1, -1, -1, // quitte: 86 <-> mc: 154, class rep: 30 + 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 87 <-> mc: 155, class rep: 7 + 6, 4, 8, 6, 8, 11, 2, 1, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 88 <-> mc: 148, class rep: 25 + 1, 10, 2, 3, 11, 0, 0, 11, 6, 0, 6, 4, -1, -1, -1, -1, // quitte: 89 <-> mc: 149, class rep: 30 + 4, 8, 11, 4, 11, 6, 0, 9, 2, 2, 9, 10, -1, -1, -1, -1, // quitte: 90 <-> mc: 150, class rep: 60 + 10, 3, 9, 10, 2, 3, 9, 3, 4, 11, 6, 3, 4, 3, 6, -1, // quitte: 91 <-> mc: 151, class rep: 25 + 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1, // quitte: 92 <-> mc: 156, class rep: 27 + 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1, // quitte: 93 <-> mc: 157, class rep: 7 + 4, 3, 6, 4, 8, 3, 6, 3, 10, 0, 9, 3, 10, 3, 9, -1, // quitte: 94 <-> mc: 158, class rep: 25 + 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 95 <-> mc: 159, class rep: 3 + 4, 5, 9, 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 96 <-> mc: 160, class rep: 6 + 0, 3, 8, 4, 5, 9, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 97 <-> mc: 161, class rep: 22 + 5, 1, 0, 5, 0, 4, 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 98 <-> mc: 162, class rep: 25 + 11, 6, 7, 8, 4, 3, 3, 4, 5, 3, 5, 1, -1, -1, -1, -1, // quitte: 99 <-> mc: 163, class rep: 30 + 7, 3, 2, 7, 2, 6, 5, 9, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 100 <-> mc: 168, class rep: 25 + 9, 4, 5, 0, 6, 8, 0, 2, 6, 6, 7, 8, -1, -1, -1, -1, // quitte: 101 <-> mc: 169, class rep: 30 + 3, 2, 6, 3, 6, 7, 1, 0, 5, 5, 0, 4, -1, -1, -1, -1, // quitte: 102 <-> mc: 170, class rep: 60 + 6, 8, 2, 6, 7, 8, 2, 8, 1, 4, 5, 8, 1, 8, 5, -1, // quitte: 103 <-> mc: 171, class rep: 25 + 9, 4, 5, 10, 2, 1, 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 104 <-> mc: 164, class rep: 22 + 6, 7, 11, 1, 10, 2, 0, 3, 8, 4, 5, 9, -1, -1, -1, -1, // quitte: 105 <-> mc: 165, class rep: 105 + 7, 11, 6, 5, 10, 4, 4, 10, 2, 4, 2, 0, -1, -1, -1, -1, // quitte: 106 <-> mc: 166, class rep: 30 + 3, 8, 4, 3, 4, 5, 3, 5, 2, 10, 2, 5, 11, 6, 7, -1, // quitte: 107 <-> mc: 167, class rep: 22 + 9, 4, 5, 10, 6, 1, 1, 6, 7, 1, 7, 3, -1, -1, -1, -1, // quitte: 108 <-> mc: 172, class rep: 30 + 1, 10, 6, 1, 6, 7, 1, 7, 0, 8, 0, 7, 9, 4, 5, -1, // quitte: 109 <-> mc: 173, class rep: 22 + 4, 10, 0, 4, 5, 10, 0, 10, 3, 6, 7, 10, 3, 10, 7, -1, // quitte: 110 <-> mc: 174, class rep: 25 + 7, 10, 6, 7, 8, 10, 5, 10, 4, 4, 10, 8, -1, -1, -1, -1, // quitte: 111 <-> mc: 175, class rep: 6 + 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1, // quitte: 112 <-> mc: 176, class rep: 7 + 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1, // quitte: 113 <-> mc: 177, class rep: 23 + 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1, // quitte: 114 <-> mc: 178, class rep: 27 + 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 115 <-> mc: 179, class rep: 7 + 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1, // quitte: 116 <-> mc: 184, class rep: 29 + 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 117 <-> mc: 185, class rep: 7 + 1, 8, 5, 1, 0, 8, 5, 8, 6, 3, 2, 8, 6, 8, 2, -1, // quitte: 118 <-> mc: 186, class rep: 25 + 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 119 <-> mc: 187, class rep: 3 + 1, 10, 2, 9, 11, 5, 9, 8, 11, 11, 6, 5, -1, -1, -1, -1, // quitte: 120 <-> mc: 180, class rep: 30 + 0, 3, 11, 0, 11, 6, 0, 6, 9, 5, 9, 6, 1, 10, 2, -1, // quitte: 121 <-> mc: 181, class rep: 22 + 11, 5, 8, 11, 6, 5, 8, 5, 0, 10, 2, 5, 0, 5, 2, -1, // quitte: 122 <-> mc: 182, class rep: 25 + 6, 3, 11, 6, 5, 3, 2, 3, 10, 10, 3, 5, -1, -1, -1, -1, // quitte: 123 <-> mc: 183, class rep: 6 + 1, 6, 3, 1, 10, 6, 3, 6, 8, 5, 9, 6, 8, 6, 9, -1, // quitte: 124 <-> mc: 188, class rep: 25 + 10, 0, 1, 10, 6, 0, 9, 0, 5, 5, 0, 6, -1, -1, -1, -1, // quitte: 125 <-> mc: 189, class rep: 6 + 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 126 <-> mc: 190, class rep: 24 + 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 127 <-> mc: 191, class rep: 1 + 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 128 <-> mc: 64, class rep: 1 + 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 129 <-> mc: 65, class rep: 24 + 9, 1, 0, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 130 <-> mc: 66, class rep: 6 + 1, 3, 8, 1, 8, 9, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 131 <-> mc: 67, class rep: 25 + 2, 11, 3, 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 132 <-> mc: 72, class rep: 6 + 11, 8, 0, 11, 0, 2, 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 133 <-> mc: 73, class rep: 25 + 0, 9, 1, 2, 11, 3, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 134 <-> mc: 74, class rep: 22 + 5, 6, 10, 1, 2, 9, 9, 2, 11, 9, 11, 8, -1, -1, -1, -1, // quitte: 135 <-> mc: 75, class rep: 30 + 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 136 <-> mc: 68, class rep: 3 + 1, 5, 6, 1, 6, 2, 3, 8, 0, -1, -1, -1, -1, -1, -1, -1, // quitte: 137 <-> mc: 69, class rep: 25 + 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1, // quitte: 138 <-> mc: 70, class rep: 7 + 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1, // quitte: 139 <-> mc: 71, class rep: 29 + 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1, // quitte: 140 <-> mc: 76, class rep: 7 + 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1, // quitte: 141 <-> mc: 77, class rep: 27 + 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1, // quitte: 142 <-> mc: 78, class rep: 23 + 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 143 <-> mc: 79, class rep: 7 + 5, 6, 10, 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 144 <-> mc: 80, class rep: 6 + 4, 0, 3, 4, 3, 7, 6, 10, 5, -1, -1, -1, -1, -1, -1, -1, // quitte: 145 <-> mc: 81, class rep: 25 + 1, 0, 9, 5, 6, 10, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 146 <-> mc: 82, class rep: 22 + 10, 5, 6, 1, 7, 9, 1, 3, 7, 7, 4, 9, -1, -1, -1, -1, // quitte: 147 <-> mc: 83, class rep: 30 + 3, 2, 11, 7, 4, 8, 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 148 <-> mc: 88, class rep: 22 + 5, 6, 10, 4, 2, 7, 4, 0, 2, 2, 11, 7, -1, -1, -1, -1, // quitte: 149 <-> mc: 89, class rep: 30 + 0, 9, 1, 4, 8, 7, 2, 11, 3, 5, 6, 10, -1, -1, -1, -1, // quitte: 150 <-> mc: 90, class rep: 105 + 9, 1, 2, 9, 2, 11, 9, 11, 4, 7, 4, 11, 5, 6, 10, -1, // quitte: 151 <-> mc: 91, class rep: 22 + 6, 2, 1, 6, 1, 5, 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 152 <-> mc: 84, class rep: 25 + 1, 5, 2, 5, 6, 2, 3, 4, 0, 3, 7, 4, -1, -1, -1, -1, // quitte: 153 <-> mc: 85, class rep: 60 + 8, 7, 4, 9, 5, 0, 0, 5, 6, 0, 6, 2, -1, -1, -1, -1, // quitte: 154 <-> mc: 86, class rep: 30 + 7, 9, 3, 7, 4, 9, 3, 9, 2, 5, 6, 9, 2, 9, 6, -1, // quitte: 155 <-> mc: 87, class rep: 25 + 8, 7, 4, 3, 5, 11, 3, 1, 5, 5, 6, 11, -1, -1, -1, -1, // quitte: 156 <-> mc: 92, class rep: 30 + 5, 11, 1, 5, 6, 11, 1, 11, 0, 7, 4, 11, 0, 11, 4, -1, // quitte: 157 <-> mc: 93, class rep: 25 + 0, 9, 5, 0, 5, 6, 0, 6, 3, 11, 3, 6, 8, 7, 4, -1, // quitte: 158 <-> mc: 94, class rep: 22 + 6, 9, 5, 6, 11, 9, 4, 9, 7, 7, 9, 11, -1, -1, -1, -1, // quitte: 159 <-> mc: 95, class rep: 6 + 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 160 <-> mc: 96, class rep: 3 + 4, 6, 10, 4, 10, 9, 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, // quitte: 161 <-> mc: 97, class rep: 25 + 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 162 <-> mc: 98, class rep: 7 + 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1, // quitte: 163 <-> mc: 99, class rep: 27 + 10, 9, 4, 10, 4, 6, 11, 3, 2, -1, -1, -1, -1, -1, -1, -1, // quitte: 164 <-> mc: 104, class rep: 25 + 0, 2, 8, 2, 11, 8, 4, 10, 9, 4, 6, 10, -1, -1, -1, -1, // quitte: 165 <-> mc: 105, class rep: 60 + 3, 2, 11, 0, 6, 1, 0, 4, 6, 6, 10, 1, -1, -1, -1, -1, // quitte: 166 <-> mc: 106, class rep: 30 + 6, 1, 4, 6, 10, 1, 4, 1, 8, 2, 11, 1, 8, 1, 11, -1, // quitte: 167 <-> mc: 107, class rep: 25 + 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 168 <-> mc: 100, class rep: 7 + 3, 8, 0, 1, 9, 2, 2, 9, 4, 2, 4, 6, -1, -1, -1, -1, // quitte: 169 <-> mc: 101, class rep: 30 + 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 170 <-> mc: 102, class rep: 15 + 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, // quitte: 171 <-> mc: 103, class rep: 7 + 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1, // quitte: 172 <-> mc: 108, class rep: 29 + 8, 1, 11, 8, 0, 1, 11, 1, 6, 9, 4, 1, 6, 1, 4, -1, // quitte: 173 <-> mc: 109, class rep: 25 + 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1, // quitte: 174 <-> mc: 110, class rep: 7 + 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 175 <-> mc: 111, class rep: 3 + 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 176 <-> mc: 112, class rep: 7 + 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1, // quitte: 177 <-> mc: 113, class rep: 29 + 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1, // quitte: 178 <-> mc: 114, class rep: 23 + 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 179 <-> mc: 115, class rep: 7 + 2, 11, 3, 10, 8, 6, 10, 9, 8, 8, 7, 6, -1, -1, -1, -1, // quitte: 180 <-> mc: 120, class rep: 30 + 2, 7, 0, 2, 11, 7, 0, 7, 9, 6, 10, 7, 9, 7, 10, -1, // quitte: 181 <-> mc: 121, class rep: 25 + 1, 0, 8, 1, 8, 7, 1, 7, 10, 6, 10, 7, 2, 11, 3, -1, // quitte: 182 <-> mc: 122, class rep: 22 + 11, 1, 2, 11, 7, 1, 10, 1, 6, 6, 1, 7, -1, -1, -1, -1, // quitte: 183 <-> mc: 123, class rep: 6 + 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1, // quitte: 184 <-> mc: 116, class rep: 27 + 2, 9, 6, 2, 1, 9, 6, 9, 7, 0, 3, 9, 7, 9, 3, -1, // quitte: 185 <-> mc: 117, class rep: 25 + 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1, // quitte: 186 <-> mc: 118, class rep: 7 + 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 187 <-> mc: 119, class rep: 3 + 8, 6, 9, 8, 7, 6, 9, 6, 1, 11, 3, 6, 1, 6, 3, -1, // quitte: 188 <-> mc: 124, class rep: 25 + 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 189 <-> mc: 125, class rep: 24 + 7, 0, 8, 7, 6, 0, 3, 0, 11, 11, 0, 6, -1, -1, -1, -1, // quitte: 190 <-> mc: 126, class rep: 6 + 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 191 <-> mc: 127, class rep: 1 + 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 192 <-> mc: 192, class rep: 3 + 11, 10, 5, 11, 5, 7, 8, 0, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 193 <-> mc: 193, class rep: 25 + 5, 7, 11, 5, 11, 10, 1, 0, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 194 <-> mc: 194, class rep: 25 + 10, 5, 7, 10, 7, 11, 9, 1, 8, 8, 1, 3, -1, -1, -1, -1, // quitte: 195 <-> mc: 195, class rep: 60 + 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 196 <-> mc: 200, class rep: 7 + 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1, // quitte: 197 <-> mc: 201, class rep: 29 + 9, 1, 0, 5, 3, 10, 5, 7, 3, 3, 2, 10, -1, -1, -1, -1, // quitte: 198 <-> mc: 202, class rep: 30 + 9, 2, 8, 9, 1, 2, 8, 2, 7, 10, 5, 2, 7, 2, 5, -1, // quitte: 199 <-> mc: 203, class rep: 25 + 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1, // quitte: 200 <-> mc: 196, class rep: 7 + 0, 3, 8, 1, 7, 2, 1, 5, 7, 7, 11, 2, -1, -1, -1, -1, // quitte: 201 <-> mc: 197, class rep: 30 + 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1, // quitte: 202 <-> mc: 198, class rep: 27 + 7, 2, 5, 7, 11, 2, 5, 2, 9, 3, 8, 2, 9, 2, 8, -1, // quitte: 203 <-> mc: 199, class rep: 25 + 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 204 <-> mc: 204, class rep: 15 + 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1, // quitte: 205 <-> mc: 205, class rep: 7 + 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1, // quitte: 206 <-> mc: 206, class rep: 7 + 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 207 <-> mc: 207, class rep: 3 + 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 208 <-> mc: 208, class rep: 7 + 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1, // quitte: 209 <-> mc: 209, class rep: 27 + 0, 9, 1, 8, 10, 4, 8, 11, 10, 10, 5, 4, -1, -1, -1, -1, // quitte: 210 <-> mc: 210, class rep: 30 + 10, 4, 11, 10, 5, 4, 11, 4, 3, 9, 1, 4, 3, 4, 1, -1, // quitte: 211 <-> mc: 211, class rep: 25 + 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1, // quitte: 212 <-> mc: 216, class rep: 23 + 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1, // quitte: 213 <-> mc: 217, class rep: 7 + 3, 2, 10, 3, 10, 5, 3, 5, 8, 4, 8, 5, 0, 9, 1, -1, // quitte: 214 <-> mc: 218, class rep: 22 + 5, 2, 10, 5, 4, 2, 1, 2, 9, 9, 2, 4, -1, -1, -1, -1, // quitte: 215 <-> mc: 219, class rep: 6 + 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1, // quitte: 216 <-> mc: 212, class rep: 29 + 0, 11, 4, 0, 3, 11, 4, 11, 5, 2, 1, 11, 5, 11, 1, -1, // quitte: 217 <-> mc: 213, class rep: 25 + 0, 5, 2, 0, 9, 5, 2, 5, 11, 4, 8, 5, 11, 5, 8, -1, // quitte: 218 <-> mc: 214, class rep: 25 + 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 219 <-> mc: 215, class rep: 24 + 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1, // quitte: 220 <-> mc: 220, class rep: 7 + 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 221 <-> mc: 221, class rep: 3 + 8, 5, 4, 8, 3, 5, 9, 5, 0, 0, 5, 3, -1, -1, -1, -1, // quitte: 222 <-> mc: 222, class rep: 6 + 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 223 <-> mc: 223, class rep: 1 + 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 224 <-> mc: 224, class rep: 7 + 0, 3, 8, 4, 7, 9, 9, 7, 11, 9, 11, 10, -1, -1, -1, -1, // quitte: 225 <-> mc: 225, class rep: 30 + 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1, // quitte: 226 <-> mc: 226, class rep: 29 + 3, 4, 1, 3, 8, 4, 1, 4, 10, 7, 11, 4, 10, 4, 11, -1, // quitte: 227 <-> mc: 227, class rep: 25 + 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1, // quitte: 228 <-> mc: 232, class rep: 27 + 9, 7, 10, 9, 4, 7, 10, 7, 2, 8, 0, 7, 2, 7, 0, -1, // quitte: 229 <-> mc: 233, class rep: 25 + 3, 10, 7, 3, 2, 10, 7, 10, 4, 1, 0, 10, 4, 10, 0, -1, // quitte: 230 <-> mc: 234, class rep: 25 + 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 231 <-> mc: 235, class rep: 24 + 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1, // quitte: 232 <-> mc: 228, class rep: 23 + 9, 4, 7, 9, 7, 11, 9, 11, 1, 2, 1, 11, 0, 3, 8, -1, // quitte: 233 <-> mc: 229, class rep: 22 + 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1, // quitte: 234 <-> mc: 230, class rep: 7 + 11, 4, 7, 11, 2, 4, 8, 4, 3, 3, 4, 2, -1, -1, -1, -1, // quitte: 235 <-> mc: 231, class rep: 6 + 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1, // quitte: 236 <-> mc: 236, class rep: 7 + 4, 1, 9, 4, 7, 1, 0, 1, 8, 8, 1, 7, -1, -1, -1, -1, // quitte: 237 <-> mc: 237, class rep: 6 + 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 238 <-> mc: 238, class rep: 3 + 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 239 <-> mc: 239, class rep: 1 + 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 240 <-> mc: 240, class rep: 15 + 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1, // quitte: 241 <-> mc: 241, class rep: 7 + 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1, // quitte: 242 <-> mc: 242, class rep: 7 + 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 243 <-> mc: 243, class rep: 3 + 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1, // quitte: 244 <-> mc: 248, class rep: 7 + 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 245 <-> mc: 249, class rep: 3 + 2, 8, 3, 2, 10, 8, 0, 8, 1, 1, 8, 10, -1, -1, -1, -1, // quitte: 246 <-> mc: 250, class rep: 6 + 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 247 <-> mc: 251, class rep: 1 + 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1, // quitte: 248 <-> mc: 244, class rep: 7 + 3, 9, 0, 3, 11, 9, 1, 9, 2, 2, 9, 11, -1, -1, -1, -1, // quitte: 249 <-> mc: 245, class rep: 6 + 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 250 <-> mc: 246, class rep: 3 + 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 251 <-> mc: 247, class rep: 1 + 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 252 <-> mc: 252, class rep: 3 + 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 253 <-> mc: 253, class rep: 1 + 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // quitte: 254 <-> mc: 254, class rep: 1 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 // quitte: 255 <-> mc: 255, class rep: 0 +}; + +// list of ambiguous cases +constexpr int t_ambig[256] = +{ + 0, // quitte: 0 <-> mc: 0, class representative: 0 + 1, // quitte: 1 <-> mc: 1, class representative: 1 + 2, // quitte: 2 <-> mc: 2, class representative: 1 + 3, // quitte: 3 <-> mc: 3, class representative: 3 + 4, // quitte: 4 <-> mc: 8, class representative: 1 + 5, // quitte: 5 <-> mc: 9, class representative: 3 + 105, // quitte: 6 <-> mc: 10, class representative: 6 + 7, // quitte: 7 <-> mc: 11, class representative: 7 + 8, // quitte: 8 <-> mc: 4, class representative: 1 + 105, // quitte: 9 <-> mc: 5, class representative: 6 + 10, // quitte: 10 <-> mc: 6, class representative: 3 + 11, // quitte: 11 <-> mc: 7, class representative: 7 + 12, // quitte: 12 <-> mc: 12, class representative: 3 + 13, // quitte: 13 <-> mc: 13, class representative: 7 + 14, // quitte: 14 <-> mc: 14, class representative: 7 + 15, // quitte: 15 <-> mc: 15, class representative: 15 + 16, // quitte: 16 <-> mc: 16, class representative: 1 + 17, // quitte: 17 <-> mc: 17, class representative: 3 + 105, // quitte: 18 <-> mc: 18, class representative: 6 + 19, // quitte: 19 <-> mc: 19, class representative: 7 + 105, // quitte: 20 <-> mc: 24, class representative: 6 + 21, // quitte: 21 <-> mc: 25, class representative: 7 + 105, // quitte: 22 <-> mc: 26, class representative: 22 + 23, // quitte: 23 <-> mc: 27, class representative: 23 + 105, // quitte: 24 <-> mc: 20, class representative: 24 + 105, // quitte: 25 <-> mc: 21, class representative: 25 + 105, // quitte: 26 <-> mc: 22, class representative: 25 + 27, // quitte: 27 <-> mc: 23, class representative: 27 + 105, // quitte: 28 <-> mc: 28, class representative: 25 + 29, // quitte: 29 <-> mc: 29, class representative: 29 + 105, // quitte: 30 <-> mc: 30, class representative: 30 + 31, // quitte: 31 <-> mc: 31, class representative: 7 + 32, // quitte: 32 <-> mc: 32, class representative: 1 + 105, // quitte: 33 <-> mc: 33, class representative: 6 + 34, // quitte: 34 <-> mc: 34, class representative: 3 + 35, // quitte: 35 <-> mc: 35, class representative: 7 + 105, // quitte: 36 <-> mc: 40, class representative: 24 + 105, // quitte: 37 <-> mc: 41, class representative: 25 + 105, // quitte: 38 <-> mc: 42, class representative: 25 + 39, // quitte: 39 <-> mc: 43, class representative: 29 + 105, // quitte: 40 <-> mc: 36, class representative: 6 + 105, // quitte: 41 <-> mc: 37, class representative: 22 + 42, // quitte: 42 <-> mc: 38, class representative: 7 + 43, // quitte: 43 <-> mc: 39, class representative: 23 + 105, // quitte: 44 <-> mc: 44, class representative: 25 + 105, // quitte: 45 <-> mc: 45, class representative: 30 + 46, // quitte: 46 <-> mc: 46, class representative: 27 + 47, // quitte: 47 <-> mc: 47, class representative: 7 + 48, // quitte: 48 <-> mc: 48, class representative: 3 + 49, // quitte: 49 <-> mc: 49, class representative: 7 + 50, // quitte: 50 <-> mc: 50, class representative: 7 + 51, // quitte: 51 <-> mc: 51, class representative: 15 + 105, // quitte: 52 <-> mc: 56, class representative: 25 + 53, // quitte: 53 <-> mc: 57, class representative: 27 + 105, // quitte: 54 <-> mc: 58, class representative: 30 + 55, // quitte: 55 <-> mc: 59, class representative: 7 + 105, // quitte: 56 <-> mc: 52, class representative: 25 + 105, // quitte: 57 <-> mc: 53, class representative: 30 + 58, // quitte: 58 <-> mc: 54, class representative: 29 + 59, // quitte: 59 <-> mc: 55, class representative: 7 + 105, // quitte: 60 <-> mc: 60, class representative: 60 + 105, // quitte: 61 <-> mc: 61, class representative: 25 + 105, // quitte: 62 <-> mc: 62, class representative: 25 + 63, // quitte: 63 <-> mc: 63, class representative: 3 + 64, // quitte: 64 <-> mc: 128, class representative: 1 + 105, // quitte: 65 <-> mc: 129, class representative: 6 + 105, // quitte: 66 <-> mc: 130, class representative: 24 + 105, // quitte: 67 <-> mc: 131, class representative: 25 + 68, // quitte: 68 <-> mc: 136, class representative: 3 + 69, // quitte: 69 <-> mc: 137, class representative: 7 + 105, // quitte: 70 <-> mc: 138, class representative: 25 + 71, // quitte: 71 <-> mc: 139, class representative: 27 + 105, // quitte: 72 <-> mc: 132, class representative: 6 + 105, // quitte: 73 <-> mc: 133, class representative: 22 + 105, // quitte: 74 <-> mc: 134, class representative: 25 + 105, // quitte: 75 <-> mc: 135, class representative: 30 + 76, // quitte: 76 <-> mc: 140, class representative: 7 + 77, // quitte: 77 <-> mc: 141, class representative: 23 + 78, // quitte: 78 <-> mc: 142, class representative: 29 + 79, // quitte: 79 <-> mc: 143, class representative: 7 + 80, // quitte: 80 <-> mc: 144, class representative: 3 + 81, // quitte: 81 <-> mc: 145, class representative: 7 + 105, // quitte: 82 <-> mc: 146, class representative: 25 + 83, // quitte: 83 <-> mc: 147, class representative: 29 + 84, // quitte: 84 <-> mc: 152, class representative: 7 + 85, // quitte: 85 <-> mc: 153, class representative: 15 + 105, // quitte: 86 <-> mc: 154, class representative: 30 + 87, // quitte: 87 <-> mc: 155, class representative: 7 + 105, // quitte: 88 <-> mc: 148, class representative: 25 + 105, // quitte: 89 <-> mc: 149, class representative: 30 + 105, // quitte: 90 <-> mc: 150, class representative: 60 + 105, // quitte: 91 <-> mc: 151, class representative: 25 + 92, // quitte: 92 <-> mc: 156, class representative: 27 + 93, // quitte: 93 <-> mc: 157, class representative: 7 + 105, // quitte: 94 <-> mc: 158, class representative: 25 + 95, // quitte: 95 <-> mc: 159, class representative: 3 + 105, // quitte: 96 <-> mc: 160, class representative: 6 + 105, // quitte: 97 <-> mc: 161, class representative: 22 + 105, // quitte: 98 <-> mc: 162, class representative: 25 + 105, // quitte: 99 <-> mc: 163, class representative: 30 + 105, // quitte: 100 <-> mc: 168, class representative: 25 + 105, // quitte: 101 <-> mc: 169, class representative: 30 + 105, // quitte: 102 <-> mc: 170, class representative: 60 + 105, // quitte: 103 <-> mc: 171, class representative: 25 + 105, // quitte: 104 <-> mc: 164, class representative: 22 + 105, // quitte: 105 <-> mc: 165, class representative: 105 + 105, // quitte: 106 <-> mc: 166, class representative: 30 + 105, // quitte: 107 <-> mc: 167, class representative: 22 + 105, // quitte: 108 <-> mc: 172, class representative: 30 + 105, // quitte: 109 <-> mc: 173, class representative: 22 + 105, // quitte: 110 <-> mc: 174, class representative: 25 + 105, // quitte: 111 <-> mc: 175, class representative: 6 + 112, // quitte: 112 <-> mc: 176, class representative: 7 + 113, // quitte: 113 <-> mc: 177, class representative: 23 + 114, // quitte: 114 <-> mc: 178, class representative: 27 + 115, // quitte: 115 <-> mc: 179, class representative: 7 + 116, // quitte: 116 <-> mc: 184, class representative: 29 + 117, // quitte: 117 <-> mc: 185, class representative: 7 + 105, // quitte: 118 <-> mc: 186, class representative: 25 + 119, // quitte: 119 <-> mc: 187, class representative: 3 + 105, // quitte: 120 <-> mc: 180, class representative: 30 + 105, // quitte: 121 <-> mc: 181, class representative: 22 + 105, // quitte: 122 <-> mc: 182, class representative: 25 + 105, // quitte: 123 <-> mc: 183, class representative: 6 + 105, // quitte: 124 <-> mc: 188, class representative: 25 + 105, // quitte: 125 <-> mc: 189, class representative: 6 + 105, // quitte: 126 <-> mc: 190, class representative: 24 + 127, // quitte: 127 <-> mc: 191, class representative: 1 + 128, // quitte: 128 <-> mc: 64, class representative: 1 + 105, // quitte: 129 <-> mc: 65, class representative: 24 + 105, // quitte: 130 <-> mc: 66, class representative: 6 + 105, // quitte: 131 <-> mc: 67, class representative: 25 + 105, // quitte: 132 <-> mc: 72, class representative: 6 + 105, // quitte: 133 <-> mc: 73, class representative: 25 + 105, // quitte: 134 <-> mc: 74, class representative: 22 + 105, // quitte: 135 <-> mc: 75, class representative: 30 + 136, // quitte: 136 <-> mc: 68, class representative: 3 + 105, // quitte: 137 <-> mc: 69, class representative: 25 + 138, // quitte: 138 <-> mc: 70, class representative: 7 + 139, // quitte: 139 <-> mc: 71, class representative: 29 + 140, // quitte: 140 <-> mc: 76, class representative: 7 + 141, // quitte: 141 <-> mc: 77, class representative: 27 + 142, // quitte: 142 <-> mc: 78, class representative: 23 + 143, // quitte: 143 <-> mc: 79, class representative: 7 + 105, // quitte: 144 <-> mc: 80, class representative: 6 + 105, // quitte: 145 <-> mc: 81, class representative: 25 + 105, // quitte: 146 <-> mc: 82, class representative: 22 + 105, // quitte: 147 <-> mc: 83, class representative: 30 + 105, // quitte: 148 <-> mc: 88, class representative: 22 + 105, // quitte: 149 <-> mc: 89, class representative: 30 + 105, // quitte: 150 <-> mc: 90, class representative: 105 + 105, // quitte: 151 <-> mc: 91, class representative: 22 + 105, // quitte: 152 <-> mc: 84, class representative: 25 + 105, // quitte: 153 <-> mc: 85, class representative: 60 + 105, // quitte: 154 <-> mc: 86, class representative: 30 + 105, // quitte: 155 <-> mc: 87, class representative: 25 + 105, // quitte: 156 <-> mc: 92, class representative: 30 + 105, // quitte: 157 <-> mc: 93, class representative: 25 + 105, // quitte: 158 <-> mc: 94, class representative: 22 + 105, // quitte: 159 <-> mc: 95, class representative: 6 + 160, // quitte: 160 <-> mc: 96, class representative: 3 + 105, // quitte: 161 <-> mc: 97, class representative: 25 + 162, // quitte: 162 <-> mc: 98, class representative: 7 + 163, // quitte: 163 <-> mc: 99, class representative: 27 + 105, // quitte: 164 <-> mc: 104, class representative: 25 + 105, // quitte: 165 <-> mc: 105, class representative: 60 + 105, // quitte: 166 <-> mc: 106, class representative: 30 + 105, // quitte: 167 <-> mc: 107, class representative: 25 + 168, // quitte: 168 <-> mc: 100, class representative: 7 + 105, // quitte: 169 <-> mc: 101, class representative: 30 + 170, // quitte: 170 <-> mc: 102, class representative: 15 + 171, // quitte: 171 <-> mc: 103, class representative: 7 + 172, // quitte: 172 <-> mc: 108, class representative: 29 + 105, // quitte: 173 <-> mc: 109, class representative: 25 + 174, // quitte: 174 <-> mc: 110, class representative: 7 + 175, // quitte: 175 <-> mc: 111, class representative: 3 + 176, // quitte: 176 <-> mc: 112, class representative: 7 + 177, // quitte: 177 <-> mc: 113, class representative: 29 + 178, // quitte: 178 <-> mc: 114, class representative: 23 + 179, // quitte: 179 <-> mc: 115, class representative: 7 + 105, // quitte: 180 <-> mc: 120, class representative: 30 + 105, // quitte: 181 <-> mc: 121, class representative: 25 + 105, // quitte: 182 <-> mc: 122, class representative: 22 + 105, // quitte: 183 <-> mc: 123, class representative: 6 + 184, // quitte: 184 <-> mc: 116, class representative: 27 + 105, // quitte: 185 <-> mc: 117, class representative: 25 + 186, // quitte: 186 <-> mc: 118, class representative: 7 + 187, // quitte: 187 <-> mc: 119, class representative: 3 + 105, // quitte: 188 <-> mc: 124, class representative: 25 + 105, // quitte: 189 <-> mc: 125, class representative: 24 + 105, // quitte: 190 <-> mc: 126, class representative: 6 + 191, // quitte: 191 <-> mc: 127, class representative: 1 + 192, // quitte: 192 <-> mc: 192, class representative: 3 + 105, // quitte: 193 <-> mc: 193, class representative: 25 + 105, // quitte: 194 <-> mc: 194, class representative: 25 + 105, // quitte: 195 <-> mc: 195, class representative: 60 + 196, // quitte: 196 <-> mc: 200, class representative: 7 + 197, // quitte: 197 <-> mc: 201, class representative: 29 + 105, // quitte: 198 <-> mc: 202, class representative: 30 + 105, // quitte: 199 <-> mc: 203, class representative: 25 + 200, // quitte: 200 <-> mc: 196, class representative: 7 + 105, // quitte: 201 <-> mc: 197, class representative: 30 + 202, // quitte: 202 <-> mc: 198, class representative: 27 + 105, // quitte: 203 <-> mc: 199, class representative: 25 + 204, // quitte: 204 <-> mc: 204, class representative: 15 + 205, // quitte: 205 <-> mc: 205, class representative: 7 + 206, // quitte: 206 <-> mc: 206, class representative: 7 + 207, // quitte: 207 <-> mc: 207, class representative: 3 + 208, // quitte: 208 <-> mc: 208, class representative: 7 + 209, // quitte: 209 <-> mc: 209, class representative: 27 + 105, // quitte: 210 <-> mc: 210, class representative: 30 + 105, // quitte: 211 <-> mc: 211, class representative: 25 + 212, // quitte: 212 <-> mc: 216, class representative: 23 + 213, // quitte: 213 <-> mc: 217, class representative: 7 + 105, // quitte: 214 <-> mc: 218, class representative: 22 + 105, // quitte: 215 <-> mc: 219, class representative: 6 + 216, // quitte: 216 <-> mc: 212, class representative: 29 + 105, // quitte: 217 <-> mc: 213, class representative: 25 + 105, // quitte: 218 <-> mc: 214, class representative: 25 + 105, // quitte: 219 <-> mc: 215, class representative: 24 + 220, // quitte: 220 <-> mc: 220, class representative: 7 + 221, // quitte: 221 <-> mc: 221, class representative: 3 + 105, // quitte: 222 <-> mc: 222, class representative: 6 + 223, // quitte: 223 <-> mc: 223, class representative: 1 + 224, // quitte: 224 <-> mc: 224, class representative: 7 + 105, // quitte: 225 <-> mc: 225, class representative: 30 + 226, // quitte: 226 <-> mc: 226, class representative: 29 + 105, // quitte: 227 <-> mc: 227, class representative: 25 + 228, // quitte: 228 <-> mc: 232, class representative: 27 + 105, // quitte: 229 <-> mc: 233, class representative: 25 + 105, // quitte: 230 <-> mc: 234, class representative: 25 + 105, // quitte: 231 <-> mc: 235, class representative: 24 + 232, // quitte: 232 <-> mc: 228, class representative: 23 + 105, // quitte: 233 <-> mc: 229, class representative: 22 + 234, // quitte: 234 <-> mc: 230, class representative: 7 + 105, // quitte: 235 <-> mc: 231, class representative: 6 + 236, // quitte: 236 <-> mc: 236, class representative: 7 + 105, // quitte: 237 <-> mc: 237, class representative: 6 + 238, // quitte: 238 <-> mc: 238, class representative: 3 + 239, // quitte: 239 <-> mc: 239, class representative: 1 + 240, // quitte: 240 <-> mc: 240, class representative: 15 + 241, // quitte: 241 <-> mc: 241, class representative: 7 + 242, // quitte: 242 <-> mc: 242, class representative: 7 + 243, // quitte: 243 <-> mc: 243, class representative: 3 + 244, // quitte: 244 <-> mc: 248, class representative: 7 + 245, // quitte: 245 <-> mc: 249, class representative: 3 + 105, // quitte: 246 <-> mc: 250, class representative: 6 + 247, // quitte: 247 <-> mc: 251, class representative: 1 + 248, // quitte: 248 <-> mc: 244, class representative: 7 + 105, // quitte: 249 <-> mc: 245, class representative: 6 + 250, // quitte: 250 <-> mc: 246, class representative: 3 + 251, // quitte: 251 <-> mc: 247, class representative: 1 + 252, // quitte: 252 <-> mc: 252, class representative: 3 + 253, // quitte: 253 <-> mc: 253, class representative: 1 + 254, // quitte: 254 <-> mc: 254, class representative: 1 + 255 // quitte: 255 <-> mc: 255, class representative: 0 +}; + +} // namespace Cube_table +} // namespace internal +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_TABLES_H \ No newline at end of file diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/topologically_correct_marching_cubes_functors.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/topologically_correct_marching_cubes_functors.h new file mode 100644 index 000000000000..a80e25dc6aa8 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/topologically_correct_marching_cubes_functors.h @@ -0,0 +1,1253 @@ +// Copyright (c) 2020 INRIA Sophia-Antipolis (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: ( GPL-3.0-or-later OR LicenseRef-Commercial ) AND MIT +// +// Author(s) : Julian Stahl +// +// This file incorporates work covered by the following copyright and permission notice: +// +// MIT License +// +// Copyright (c) 2020 Roberto Grosso +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// +// The code below uses the version of +// https://github.com/rogrosso/tmc available on 15th of September 2022. +// + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_TMC_FUNCTORS_H +#define CGAL_ISOSURFACING_3_INTERNAL_TMC_FUNCTORS_H + +#include + +#include +#include + +#ifdef CGAL_LINKED_WITH_TBB +# include +# include +#endif + +#include +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +template +class TMC_functor +{ +private: + using Domain = Domain_; + using Point_range = PointRange; + using Polygon_range = PolygonRange; + + using Geom_traits = typename Domain::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + + using edge_descriptor = typename Domain::edge_descriptor; + using cell_descriptor = typename Domain::cell_descriptor; + + using Point_index = std::size_t; + using Edge_index = std::array; + +#ifdef CGAL_LINKED_WITH_TBB + struct Hash_compare + { + static size_t hash(const Edge_index& key) + { + std::size_t res = 17; + res = res * 31 + std::hash()(key[0]); + res = res * 31 + std::hash()(key[1]); + res = res * 31 + std::hash()(key[2]); + res = res * 31 + std::hash()(key[3]); + return res; + } + + static bool equal(const Edge_index& key1, const Edge_index& key2) + { + return key1[0] == key2[0] && key1[1] == key2[1] && key1[2] == key2[2] && key1[3] == key2[3]; + } + }; +#else + struct Hash + { + std::size_t operator()(const Edge_index& key) const + { + std::size_t res = 17; + res = res * 31 + std::hash()(key[0]); + res = res * 31 + std::hash()(key[1]); + res = res * 31 + std::hash()(key[2]); + res = res * 31 + std::hash()(key[3]); + return res; + } + }; +#endif + +private: + const Domain& m_domain; + FT m_isovalue; + +#ifdef CGAL_LINKED_WITH_TBB + std::atomic m_point_counter; + + tbb::concurrent_vector m_points; + tbb::concurrent_vector > m_triangles; + + using Edge_point_map = tbb::concurrent_hash_map; + Edge_point_map m_edges; +#else + Point_index m_point_counter; + + std::vector m_points; + std::vector > m_triangles; + + std::unordered_map m_edges; +#endif + +public: + TMC_functor(const Domain& domain, + const FT isovalue) + : m_domain(domain), + m_isovalue(isovalue), + m_point_counter(0) + { } + + void operator()(const cell_descriptor& cell) + { + std::array values; + std::array corners; + const std::size_t i_case = get_cell_corners(m_domain, cell, m_isovalue, corners, values); + + // skip empty / full cells + constexpr std::size_t ones = (1 << 8) - 1; + if((i_case & ones) == ones || // all bits set + (i_case & ones) == 0) // no bits set + return; + + // this is the only difference to the default Marching Cubes + const int tcm = Cube_table::t_ambig[i_case]; + if(tcm == 105) + { + if(p_slice(cell, m_isovalue, corners, values, i_case)) + return; +#ifdef CGAL_ISOSURFACING_3_MC_FUNCTORS_DEBUG + else + std::cerr << "WARNING: the result might not be topologically correct" << std::endl; +#endif + } + + std::array vertices; + MC_construct_vertices(cell, i_case, corners, values, m_isovalue, m_domain, vertices); + + // @todo improve triangle generation + + // construct triangles + for(int t=0; t<16; t+=3) + { + const std::size_t t_index = i_case * 16 + t; + + // if(e_tris_list[t_index] == 0x7f) + if(Cube_table::triangle_cases[t_index] == -1) + break; + + // @todo move more of this stuff into the table + const int eg0 = Cube_table::triangle_cases[t_index + 0]; + const int eg1 = Cube_table::triangle_cases[t_index + 1]; + const int eg2 = Cube_table::triangle_cases[t_index + 2]; + + // insert new triangle into list + const Point_index p0 = add_point(vertices[eg0], compute_edge_index(cell, eg0)); + const Point_index p1 = add_point(vertices[eg1], compute_edge_index(cell, eg1)); + const Point_index p2 = add_point(vertices[eg2], compute_edge_index(cell, eg2)); + + add_triangle(p2, p1, p0); + } + } + + // returns the created triangle list + template + void to_triangle_soup(PR& points, TR& triangles) const + { + points.insert(points.begin(), m_points.begin(), m_points.end()); + for (const auto& tri : m_triangles) { + triangles.push_back({ tri[0], tri[1], tri[2] }); + + // just a safeguard against arrays of the wrong size + CGAL_assertion(triangles.back().size() == 3); + } + } + +private: + Edge_index compute_edge_index(const cell_descriptor& cell, int edge) + { + // edge is in 0 - 11 + + // there are 12 edges, assign to each vertex three edges, the global edge numbering + // consists of 3*global_vertex_id + edge_offset. + const unsigned long long gei_pattern_ = 670526590282893600ull; + + // the edge global index is given by the vertex global index + the edge offset + const std::size_t shift = 5 * edge; + const std::size_t ix = cell[0] + ((gei_pattern_ >> shift) & 1); // global_edge_id[edge][0]; + const std::size_t iy = cell[1] + ((gei_pattern_ >> (shift + 1)) & 1); // global_edge_id[edge][1]; + const std::size_t iz = cell[2] + ((gei_pattern_ >> (shift + 2)) & 1); // global_edge_id[edge][2]; + const std::size_t off_val = ((gei_pattern_ >> (shift + 3)) & 3); + + return { ix, iy, iz, off_val }; + } + + bool find_point(const Edge_index& e, Point_index& i) + { +#ifdef CGAL_LINKED_WITH_TBB + typename Edge_point_map::const_accessor acc; + if (m_edges.find(acc, e)) + { + i = acc->second; + return true; + } +#else + auto it = m_edges.find(e); + if (it != m_edges.end()) + { + i = it->second; + return true; + } +#endif + return false; + } + + Point_index add_point(const Point_3& p, const Edge_index& e) + { + +#ifdef CGAL_LINKED_WITH_TBB + typename Edge_point_map::accessor acc; + if (!m_edges.insert(acc, e)) + return acc->second; + + const Point_index i = m_point_counter++; + acc->second = i; + acc.release(); + + m_points.grow_to_at_least(i + 1); +#else + const Point_index i = m_point_counter; + auto res = m_edges.insert({e, i}); + if (!res.second) + return res.first->second; + + ++m_point_counter; + m_points.resize(i + 1); +#endif + + m_points[i] = p; + + return i; + } + + Point_index add_point_unchecked(const Point_3& p) + { + const Point_index i = m_point_counter++; + +#ifdef CGAL_LINKED_WITH_TBB + m_points.grow_to_at_least(i + 1); +#else + m_points.resize(i + 1); +#endif + m_points[i] = p; + + return i; + } + + void add_triangle(const Point_index p0, + const Point_index p1, + const Point_index p2) + { + m_triangles.push_back({p0, p1, p2}); + } + + bool p_slice(const cell_descriptor& cell, + const FT i0, + const std::array& corners, + const std::array& values, + const std::size_t i_case) + { + typename Geom_traits::Compute_x_3 x_coord = m_domain.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = m_domain.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = m_domain.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_point_3 point = m_domain.geom_traits().construct_point_3_object(); + + // code edge end vertices for each of the 12 edges + const unsigned char l_edges_[12] = {16, 49, 50, 32, 84, 117, 118, 100, 64, 81, 115, 98}; + auto get_edge_vertex = [](const int e, unsigned int& v0, unsigned int& v1, const unsigned char l_edges_[12]) + { + v0 = (unsigned int)(l_edges_[e] & 0xF); + v1 = (unsigned int)(l_edges_[e] >> 4) & 0xF; + }; + + // A hexahedron has twelve edges, save the intersection of the isosurface with the edge + // save global edge and global vertex index of isosurface + std::array vertices; + + // save local coordinate along the edge of intersection point + std::vector ecoord(12, FT(0)); + + // collect vertices + unsigned short flag{1}; + for(int eg = 0; eg < 12; ++eg) + { + if(flag & Cube_table::intersected_edges[i_case]) + { + // generate vertex here, do not care at this point if vertex already exists + unsigned int v0, v1; + get_edge_vertex(eg, v0, v1, l_edges_); + + // @todo use the domain's interpolation scheme? + FT l = (i0 - values[v0]) / (values[v1] - values[v0]); + ecoord[eg] = l; + + // interpolate vertex + const FT px = (FT(1) - l) * x_coord(corners[v0]) + l * x_coord(corners[v1]); + const FT py = (FT(1) - l) * y_coord(corners[v0]) + l * y_coord(corners[v1]); + const FT pz = (FT(1) - l) * z_coord(corners[v0]) + l * z_coord(corners[v1]); + + // add vertex and insert to map + vertices[eg] = add_point(point(px, py, pz), compute_edge_index(cell, eg)); + } + + // next edge + flag <<= 1; + } + + // compute oriented contours + // + // A contour consists of segment at the faces connecting the intersection of the + // isosurface with the edges. For each edge, we store the edge to which the segment + // is outgoing and the edge from which the segment in coming. Therefore, a contour + // can be reconstructed by connecting the edges in the direction of the outgoing. + // The contour is oriented in such a way that the positive vertices are outside. + // 1. build segments + // 2. connect segments + // build up segments + // set segments map + unsigned char segm_[12] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + auto set_segm = [](const int e, const int pos, const int val, unsigned char segm_[12]) + { + if(pos == 0) + { + segm_[e] &= 0xF0; + segm_[e] |= (unsigned char)val & 0xF; + } + else if(pos == 1) + { + segm_[e] &= 0xF; + segm_[e] |= val << 4; + } + }; + + auto get_segm = [](const int e, const int pos, unsigned char segm_[12]) -> int + { + if(pos == 0) + return int(segm_[e] & 0xF); + else + return int((segm_[e] >> 4) & 0xF); + }; + + auto is_segm_set = [](const int e, unsigned char segm_[12]) { return (segm_[e] != 0xFF); }; + auto unset_segm = [](const int e, unsigned char segm_[12]) { segm_[e] = 0xFF; }; + + // In order to compute oriented segments, the hexahedron must be flattened. + // The inside of the faces of the hexahedron must be all on the same + // side of the flattened hexahedron. This requires changing the order of + // the edges when reading from the faces + // code edges at face + // unsigned short face_e_[6] = { 12816, 30292, 33936, 46754, 34739, 38305 }; + std::array e_face_{{291, 18277, 18696, 10859, 33719, 38305}}; + + // code vertices at face + // unsigned short face_v_[6] = { 12816, 30292, 21520, 30258, 25632, 30001 }; + std::array v_face_{{12576, 25717, 5380, 29538, 8292, 30001}}; + + // reading edge from face + auto get_face_e = [e_face_](const int f, const int e) { return ((e_face_[f] >> (4 * e)) & 0xF); }; + auto get_face_v = [v_face_](const int f, const int e) { return ((v_face_[f] >> (4 * e)) & 0xF); }; + + // compute oriented segments using the isoline scheme at the faces + const unsigned int BIT_1 = 1; + const unsigned int BIT_2 = 2; + const unsigned int BIT_3 = 4; + const unsigned int BIT_4 = 8; + auto asymptotic_decider = [](const FT f0, const FT f1, const FT f2, const FT f3) -> FT + { + return (f0 * f3 - f1 * f2) / (f0 + f3 - f1 - f2); + }; + + std::vector f_flag(6, false); + for(int f=0; f<6; ++f) + { + // classify face + unsigned int f_case = 0; + unsigned int v0 = get_face_v(f, 0); + unsigned int v1 = get_face_v(f, 1); + unsigned int v2 = get_face_v(f, 2); + unsigned int v3 = get_face_v(f, 3); + unsigned int e0 = get_face_e(f, 0); + unsigned int e1 = get_face_e(f, 1); + unsigned int e2 = get_face_e(f, 2); + unsigned int e3 = get_face_e(f, 3); + FT f0 = values[v0]; + FT f1 = values[v1]; + FT f2 = values[v2]; + FT f3 = values[v3]; + if(f0 >= i0) f_case |= BIT_1; + if(f1 >= i0) f_case |= BIT_2; + if(f2 >= i0) f_case |= BIT_3; + if(f3 >= i0) f_case |= BIT_4; + + switch (f_case) + { + case 1: + set_segm(e0, 0, e3, segm_); + set_segm(e3, 1, e0, segm_); + break; + case 2: + set_segm(e1, 0, e0, segm_); + set_segm(e0, 1, e1, segm_); + break; + case 3: + set_segm(e1, 0, e3, segm_); + set_segm(e3, 1, e1, segm_); + break; + case 4: + set_segm(e3, 0, e2, segm_); + set_segm(e2, 1, e3, segm_); + break; + case 5: + set_segm(e0, 0, e2, segm_); + set_segm(e2, 1, e0, segm_); + break; + case 6: + { + const FT val = asymptotic_decider(f0, f1, f2, f3); + if(val > i0) + { + set_segm(e3, 0, e0, segm_); + set_segm(e0, 1, e3, segm_); + set_segm(e1, 0, e2, segm_); + set_segm(e2, 1, e1, segm_); + } + else if(val < i0) + { + set_segm(e1, 0, e0, segm_); + set_segm(e0, 1, e1, segm_); + set_segm(e3, 0, e2, segm_); + set_segm(e2, 1, e3, segm_); + } + else + { + f_flag[f] = true; + // singular case val == i0, there are no asymptotes + // check if there is a reasonable triangulation of the face + const unsigned short e_flag = 0x218; + const unsigned short bit_1 = 0x1; + const unsigned short bit_2 = 0x2; + FT ec0 = ecoord[e0]; + FT ec1 = ecoord[e1]; + FT ec2 = ecoord[e2]; + FT ec3 = ecoord[e3]; + + if((e_flag >> (f * 2)) & bit_1) + { + ec0 = FT(1) - ec0; + ec2 = FT(1) - ec2; + } + + if((e_flag >> (f * 2)) & bit_2) + { + ec1 = FT(1) - ec1; + ec3 = FT(1) - ec3; + } + + if(ec1 < ec3 && ec0 > ec2) + { + set_segm(e1, 0, e0, segm_); + set_segm(e0, 1, e1, segm_); + set_segm(e3, 0, e2, segm_); + set_segm(e2, 1, e3, segm_); + } + else if(ec1 > ec3 && ec0 < ec2) + { + set_segm(e3, 0, e0, segm_); + set_segm(e0, 1, e3, segm_); + set_segm(e1, 0, e2, segm_); + set_segm(e2, 1, e1, segm_); + } + else + { + // std::cerr << "ERROR: can't correctly triangulate cell's face\n"; + return false; + } + } + } + break; + case 7: + set_segm(e1, 0, e2, segm_); + set_segm(e2, 1, e1, segm_); + break; + case 8: + set_segm(e2, 0, e1, segm_); + set_segm(e1, 1, e2, segm_); + break; + case 9: + { + const FT val = asymptotic_decider(f0, f1, f2, f3); + if(val > i0) + { + set_segm(e0, 0, e1, segm_); + set_segm(e1, 1, e0, segm_); + set_segm(e2, 0, e3, segm_); + set_segm(e3, 1, e2, segm_); + } + else if(val < i0) + { + set_segm(e0, 0, e3, segm_); + set_segm(e3, 1, e0, segm_); + set_segm(e2, 0, e1, segm_); + set_segm(e1, 1, e2, segm_); + } + else + { + f_flag[f] = true; + // singular case val == i0, there are no asymptotes + // check if there is a reasonable triangulation of the face + const unsigned short e_flag = 0x218; + const unsigned short bit_1 = 0x1; + const unsigned short bit_2 = 0x2; + FT ec0 = ecoord[e0]; + FT ec1 = ecoord[e1]; + FT ec2 = ecoord[e2]; + FT ec3 = ecoord[e3]; + + if((e_flag >> (f * 2)) & bit_1) + { + ec0 = FT(1) - ec0; + ec2 = FT(1) - ec2; + } + + if((e_flag >> (f * 2)) & bit_2) + { + ec1 = FT(1) - ec1; + ec3 = FT(1) - ec3; + } + + if(ec1 < ec3 && ec0 > ec2) + { + set_segm(e0, 0, e1, segm_); + set_segm(e1, 1, e0, segm_); + set_segm(e2, 0, e3, segm_); + set_segm(e3, 1, e2, segm_); + } + else if(ec1 > ec3 && ec0 < ec2) + { + set_segm(e0, 0, e3, segm_); + set_segm(e3, 1, e0, segm_); + set_segm(e2, 0, e1, segm_); + set_segm(e1, 1, e2, segm_); + } + else + { + // std::cerr << "ERROR: can't correctly triangulate cell's face\n"; + return false; + } + } + } + break; + case 10: + set_segm(e2, 0, e0, segm_); + set_segm(e0, 1, e2, segm_); + break; + case 11: + set_segm(e2, 0, e3, segm_); + set_segm(e3, 1, e2, segm_); + break; + case 12: + set_segm(e3, 0, e1, segm_); + set_segm(e1, 1, e3, segm_); + break; + case 13: + set_segm(e0, 0, e1, segm_); + set_segm(e1, 1, e0, segm_); + break; + case 14: + set_segm(e3, 0, e0, segm_); + set_segm(e0, 1, e3, segm_); + break; + default: + break; + } + } + + // connect oriented segments into oriented contours + // + // closed contours are coded in 64 bit unsigned long long + // 1) Each entry has 4 bits + // 2) The first 4 entries are reserved for the size of the contours + // 3) The next 12 entries are the indices of the edges constituting the contorus + // The indices are numbers from 0 to 12 + unsigned long long c_ = 0xFFFFFFFFFFFF0000; + + // in the 4 first bits store size of contours + auto get_cnt_size = [](const int cnt, unsigned long long& c_) -> size_t + { + return size_t((c_ & (0xF << 4 * cnt)) >> 4 * cnt); + }; + + auto set_cnt_size = [](const int cnt, const int size, unsigned long long& c_) + { + // unset contour size + c_ &= ~(0xF << 4 * cnt); + c_ |= (size << 4 * cnt); + }; + + // set corresponging edge + auto set_c = [](const int cnt, const int pos, const int val, unsigned long long& c_) + { + const unsigned int mask[4] = {0x0, 0xF, 0xFF, 0xFFF}; + const unsigned int c_sz = c_ & mask[cnt]; + const unsigned int e = 16 + 4 * ((c_sz & 0xF) + ((c_sz & 0xF0) >> 4) + ((c_sz & 0xF00) >> 8) + pos); + c_ &= ~(((unsigned long long)0xF) << e); + c_ |= (((unsigned long long)val) << e); + }; + + // read edge from contour + auto get_c = [](const int cnt, const int pos, unsigned long long c_) -> int + { + const unsigned int mask[4] = {0x0, 0xF, 0xFF, 0xFFF}; + const unsigned int c_sz = (unsigned int)(c_ & mask[cnt]); + const unsigned int e = 16 + 4 * ((c_sz & 0xF) + ((c_sz & 0xF0) >> 4) + ((c_sz & 0xF00) >> 8) + pos); + return int((c_ >> e) & 0xF); + }; + + // connect oriented contours + unsigned int cnt_ = 0; + for(unsigned int e=0; e<12; ++e) + { + if(is_segm_set(e, segm_)) + { + unsigned int eTo = get_segm(e, 0, segm_); + unsigned int eIn = get_segm(e, 1, segm_); + unsigned int eStart = e; + unsigned int pos = 0; + set_c(cnt_, pos, eStart, c_); + + while(eTo != eStart) + { + pos = pos + 1; + set_c(cnt_, pos, eTo, c_); + eIn = eTo; + eTo = get_segm(eIn, 0, segm_); + unset_segm(eIn, segm_); + } + + // set contour length + set_cnt_size(cnt_, pos + 1, c_); + + // update number of contours + cnt_ = cnt_ + 1; + } + } + + // compute intersection of opposite faces + // + // It is sufficient to compute a pair of solutions for one face + // The other solutions are obtained by evaluating the equations + // for the common variable + FT ui[2]{}; + FT vi[2]{}; + FT wi[2]{}; + unsigned char q_sol = 0; + const FT a = (values[0] - values[1]) * (-values[6] + values[7] + values[4] - values[5]) - + (values[4] - values[5]) * (-values[2] + values[3] + values[0] - values[1]); + const FT b = (i0 - values[0]) * (-values[6] + values[7] + values[4] - values[5]) + + (values[0] - values[1]) * (values[6] - values[4]) - + (i0 - values[4]) * (-values[2] + values[3] + values[0] - values[1]) - + (values[4] - values[5]) * (values[2] - values[0]); + const FT c = (i0 - values[0]) * (values[6] - values[4]) - (i0 - values[4]) * (values[2] - values[0]); + + FT d = b * b - FT(4) * a * c; + if(d > 0) + { + d = sqrt(d); + + // compute u-coord of solutions + ui[0] = (-b - d) / (FT(2) * a); + ui[1] = (-b + d) / (FT(2) * a); + + // compute v-coord of solutions + FT g1 = values[0] * (FT(1) - ui[0]) + values[1] * ui[0]; + FT g2 = values[2] * (FT(1) - ui[0]) + values[3] * ui[0]; + vi[0] = (i0 - g1) / (g2 - g1); + if(std::isnan(vi[0]) || std::isinf(vi[0])) + vi[0] = FT(-1); + + g1 = values[0] * (FT(1) - ui[1]) + values[1] * ui[1]; + g2 = values[2] * (FT(1) - ui[1]) + values[3] * ui[1]; + vi[1] = (i0 - g1) / (g2 - g1); + if(std::isnan(vi[1]) || std::isinf(vi[1])) + vi[1] = FT(-1); + + // compute w-coordinates of solutions + g1 = values[0] * (FT(1) - ui[0]) + values[1] * ui[0]; + g2 = values[4] * (FT(1) - ui[0]) + values[5] * ui[0]; + wi[0] = (i0 - g1) / (g2 - g1); + if (std::isnan(wi[0]) || std::isinf(wi[0])) wi[0] = FT(-1); + g1 = values[0] * (FT(1) - ui[1]) + values[1] * ui[1]; + g2 = values[4] * (FT(1) - ui[1]) + values[5] * ui[1]; + wi[1] = (i0 - g1) / (g2 - g1); + if(std::isnan(wi[1]) || std::isinf(wi[1])) + wi[1] = FT(-1); + + // correct values for roots of quadratic equations + // in case the asymptotic decider has failed + if(f_flag[0]) { // face 1, w = 0; + if(wi[0] < wi[1]) + wi[0] = FT(0); + else + wi[1] = FT(0); + } + + if(f_flag[1]) { // face 2, w = 1 + if(wi[0] > wi[1]) + wi[1] = FT(1); + else + wi[1] = FT(1); + } + + if(f_flag[2]) { // face 3, v = 0 + if(vi[0] < vi[1]) + vi[0] = FT(0); + else + vi[1] = FT(0); + } + + if(f_flag[3]) { // face 4, v = 1 + if(vi[0] > vi[1]) + vi[0] = FT(1); + else + vi[1] = FT(1); + } + + if(f_flag[4]) { // face 5, u = 0 + if(ui[0] < ui[1]) + ui[0] = FT(0); + else + ui[1] = FT(0); + } + + if(f_flag[5]) { // face 6, u = 1 + if(ui[0] > ui[1]) + ui[0] = FT(1); + else + ui[1] = FT(1); + } + + // check solution intervals + if(FT(0) < ui[0] && ui[0] < FT(1)) + q_sol |= 1; + + if(0 < ui[1] && ui[1] < 1) + q_sol |= 2; + + if(0 < vi[0] && vi[0] < 1) + q_sol |= 4; + + if(0 < vi[1] && vi[1] < 1) + q_sol |= 8; + + if(0 < wi[0] && wi[0] < 1) + q_sol |= 16; + + if(0 < wi[1] && wi[1] < 1) + q_sol |= 32; + } + + // counts the number of set bits + auto numberOfSetBits = [](const unsigned char n) + { + // C or C++: use uint32_t + unsigned int b = (unsigned int)(n); + b = b - ((b >> 1) & 0x55555555); + b = (b & 0x33333333) + ((b >> 2) & 0x33333333); + return (((b + (b >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; + }; + + // compute the number of solutions to the quadratic equation for a given face + auto nrQSolFace = [](const unsigned int f, const unsigned char n) + { + unsigned int nr = 0; + switch (f) + { + case 0: + if((n & 0x5) == 0x5) nr = nr + 1; + if((n & 0xA) == 0xA) nr = nr + 1; + break; + case 1: + if((n & 0x11) == 0x11) nr = nr + 1; + if((n & 0x22) == 0x22) nr = nr + 1; + break; + case 2: + if((n & 0x18) == 0x18) nr = nr + 1; + if((n & 0x24) == 0x24) nr = nr + 1; + break; + } + return nr; + }; + + // triangulate contours + // + // if all bits are set, then there are three pairs of nontrivial solutions + // to the quadratic equations. In this case, there is a tunnel or a contour + // with 12 vertices. If there are three contours, then there is a tunnel and + // one of the contorus with only three vertices is not part of it. + if(numberOfSetBits(q_sol) == 6) + { + // there are at most three contours + // Possible cases: + // 1) a single contour with 12 vertices + // 2) two contours which build a tunnel + // 3) three contours, one has only 3 vertices and does not belong to the tunnel + + // construct the six vertices of the inner hexagon + FT hvt[6][3]; + hvt[0][0] = ui[0]; + hvt[0][1] = vi[0]; + hvt[0][2] = wi[0]; + hvt[1][0] = ui[0]; + hvt[1][1] = vi[0]; + hvt[1][2] = wi[1]; + hvt[2][0] = ui[1]; + hvt[2][1] = vi[0]; + hvt[2][2] = wi[1]; + hvt[3][0] = ui[1]; + hvt[3][1] = vi[1]; + hvt[3][2] = wi[1]; + hvt[4][0] = ui[1]; + hvt[4][1] = vi[1]; + hvt[4][2] = wi[0]; + hvt[5][0] = ui[0]; + hvt[5][1] = vi[1]; + hvt[5][2] = wi[0]; + + // construct vertices at intersections with the edges + auto e_vert = [&ecoord](const int e, const int i) -> FT + { + const unsigned int l_coord[3]{1324855, 5299420, 16733440}; + const unsigned char flag = (l_coord[i] >> (2 * e)) & 3; + if(flag == 3) + return ecoord[e]; + else + return FT(flag); + }; + + // if there are three contours, then there is a tunnel and one + // of the contours is not part of it. + unsigned char _not_tunnel = 0xF; + if(cnt_ == 3) + { + // loop over the contours + // triangulate the contour which is not part of + // the tunnel + const FT uc_min = (ui[0] < ui[1]) ? ui[0] : ui[1]; + const FT uc_max = (ui[0] < ui[1]) ? ui[1] : ui[0]; + for(int t=0; t < (int)cnt_; ++t) + { + if(get_cnt_size(t, c_) == 3) + { + FT umin(2); + FT umax(-2); + const unsigned int e0 = get_c(t, 0, c_); + const unsigned int e1 = get_c(t, 1, c_); + const unsigned int e2 = get_c(t, 2, c_); + const FT u_e0 = e_vert(e0, 0); + const FT u_e1 = e_vert(e1, 0); + const FT u_e2 = e_vert(e2, 0); + umin = (u_e0 < umin) ? u_e0 : umin; + umin = (u_e1 < umin) ? u_e1 : umin; + umin = (u_e2 < umin) ? u_e2 : umin; + umax = (u_e0 > umax) ? u_e0 : umax; + umax = (u_e1 > umax) ? u_e1 : umax; + umax = (u_e2 > umax) ? u_e1 : umax; + if(uc_min > umax || uc_max < umin) + { + // this contour is not part of the tunnel + _not_tunnel = t; + + add_triangle(vertices[e0], vertices[e1], vertices[e2]); + } + } + } + } + + // compute vertices of inner hexagon, save new vertices in list and compute and keep + // global vertices index to build triangle connectivity later on. + Point_index tg_idx[6]; + for(int i=0; i<6; ++i) + { + const FT u = hvt[i][0]; + const FT v = hvt[i][1]; + const FT w = hvt[i][2]; + const FT px = (FT(1) - w) * ((FT(1) - v) * (x_coord(corners[0]) + u * (x_coord(corners[1]) - x_coord(corners[0]))) + + v * (x_coord(corners[2]) + u * (x_coord(corners[3]) - x_coord(corners[2])))) + + w * ((FT(1) - v) * (x_coord(corners[4]) + u * (x_coord(corners[5]) - x_coord(corners[4]))) + + v * (x_coord(corners[6]) + u * (x_coord(corners[7]) - x_coord(corners[6])))); + const FT py = (FT(1) - w) * ((FT(1) - v) * (y_coord(corners[0]) + u * (y_coord(corners[1]) - y_coord(corners[0]))) + + v * (y_coord(corners[2]) + u * (y_coord(corners[3]) - y_coord(corners[2])))) + + w * ((FT(1) - v) * (y_coord(corners[4]) + u * (y_coord(corners[5]) - y_coord(corners[4]))) + + v * (y_coord(corners[6]) + u * (y_coord(corners[7]) - y_coord(corners[6])))); + const FT pz = (FT(1) - w) * ((FT(1) - v) * (z_coord(corners[0]) + u * (z_coord(corners[1]) - z_coord(corners[0]))) + + v * (z_coord(corners[2]) + u * (z_coord(corners[3]) - z_coord(corners[2])))) + + w * ((FT(1) - v) * (z_coord(corners[4]) + u * (z_coord(corners[5]) - z_coord(corners[4]))) + + v * (z_coord(corners[6]) + u * (z_coord(corners[7]) - z_coord(corners[6])))); + + tg_idx[i] = add_point_unchecked(point(px, py, pz)); + } + + // triangulate contours with inner hexagon + unsigned char tcon_[12]; + for(int i=0; i<(int)cnt_; ++i) + { + if(_not_tunnel != i) + { + // contour belongs to tunnel + const int cnt_sz = int(get_cnt_size(i, c_)); + for(int r=0; r::max)(); + unsigned int ci = get_c(i, r, c_); + const FT u_edge = e_vert(ci, 0); + const FT v_edge = e_vert(ci, 1); + const FT w_edge = e_vert(ci, 2); + for(int s=0; s<6; ++s) + { + const FT uval = u_edge - hvt[s][0]; + const FT vval = v_edge - hvt[s][1]; + const FT wval = w_edge - hvt[s][2]; + FT val = uval * uval + vval * vval + wval * wval; + if(dist > val) + { + index = s; + dist = val; + } + } + + tcon_[ci] = (unsigned char)(index); + } + + // correspondence between vertices found + // create triangles + // needs some functions + auto distanceRingIntsModulo = [](const int d1, const int d2) + { + const int r = (d1 - d2) < 0 ? d2 - d1 : d1 - d2; + return (r > 2 ? 6 - r : r); + }; + + auto midpointRingIntModulo = [](const int d1, const int d2) + { + const int dmax = (d1 > d2) ? d1 : d2; + const int dmin = (d1 < d2) ? d1 : d2; + return ((dmax + 2) % 6 == dmin) ? (dmax + 1) % 6 : (dmax + dmin) / 2; + }; + + for(int r=0; r> 1) & 1)}, + {(uchar)((q_sol >> 2) & 1), (uchar)((q_sol >> 3) & 1)}, + {(uchar)((q_sol >> 4) & 1), (uchar)((q_sol >> 5) & 1)}}; + + const unsigned char fc1 = fs[0][0] * fs[1][0] + fs[0][1] * fs[1][1]; + const unsigned char fc2 = fs[0][0] * fs[2][0] + fs[0][1] * fs[2][1]; + const unsigned char fc3 = fs[1][0] * fs[2][1] + fs[1][1] * fs[2][0]; + const unsigned char c_faces = fc1 + fc2 + fc3; + FT ucoord{}; + FT vcoord{}; + FT wcoord{}; + switch(c_faces) + { + case 2: + { + if(fc1 == 0) + { + ucoord = fs[0][0] * ui[0] + fs[0][1] * ui[1]; + vcoord = fs[1][0] * vi[0] + fs[1][1] * vi[1]; + wcoord = fs[1][0] * wi[1] + fs[1][1] * wi[0]; + } + else if(fc2 == 0) + { + ucoord = fs[0][0] * ui[0] + fs[0][1] * ui[1]; + vcoord = fs[0][0] * vi[0] + fs[0][1] * vi[1]; + wcoord = fs[0][0] * wi[1] + fs[0][1] * wi[0]; + } + else if(fc3 == 0) + { + ucoord = fs[1][0] * ui[0] + fs[1][1] * ui[1]; + vcoord = fs[1][0] * vi[0] + fs[1][1] * vi[1]; + wcoord = fs[1][0] * wi[0] + fs[1][1] * wi[1]; + } + } + break; + case 3: + { + ucoord = (fs[0][0] * ui[0] + fs[0][1] * ui[1]) / (fs[0][0] + fs[0][1]); + vcoord = (fs[1][0] * vi[0] + fs[1][1] * vi[1]) / (fs[1][0] + fs[1][1]); + wcoord = (fs[2][0] * wi[0] + fs[2][1] * wi[1]) / (fs[2][0] + fs[2][1]); + } + break; + case 4: + { + const unsigned char nr_u = fs[0][0] + fs[0][1]; + const unsigned char nr_v = fs[1][0] + fs[1][1]; + const unsigned char nr_w = fs[2][0] + fs[2][1]; + if(nr_w == 1) + { + ucoord = fs[2][0] * ui[0] + fs[2][1] * ui[1]; + vcoord = fs[2][1] * vi[0] + fs[2][0] * vi[1]; + wcoord = fs[2][0] * wi[0] + fs[2][1] * wi[1]; + } + else if(nr_v == 1) + { + ucoord = fs[1][0] * ui[0] + fs[1][1] * ui[1]; + vcoord = fs[1][0] * vi[0] + fs[1][1] * vi[1]; + wcoord = fs[1][1] * wi[0] + fs[1][0] * wi[1]; + } + else if(nr_u == 1) + { + ucoord = fs[0][0] * ui[0] + fs[0][1] * ui[1]; + vcoord = fs[0][0] * vi[0] + fs[0][1] * vi[1]; + wcoord = fs[0][0] * wi[0] + fs[0][1] * wi[1]; + } + } + break; + } // switch(c_faces) + + // create inner vertex + const FT px = (FT(1) - wcoord) * ((FT(1) - vcoord) * (x_coord(corners[0]) + ucoord * (x_coord(corners[1]) - x_coord(corners[0]))) + + vcoord * (x_coord(corners[2]) + ucoord * (x_coord(corners[3]) - x_coord(corners[2])))) + + wcoord * ((FT(1) - vcoord) * (x_coord(corners[4]) + ucoord * (x_coord(corners[5]) - x_coord(corners[4]))) + + vcoord * (x_coord(corners[6]) + ucoord * (x_coord(corners[7]) - x_coord(corners[6])))); + const FT py = (FT(1) - wcoord) * ((FT(1) - vcoord) * (y_coord(corners[0]) + ucoord * (y_coord(corners[1]) - y_coord(corners[0]))) + + vcoord * (y_coord(corners[2]) + ucoord * (y_coord(corners[3]) - y_coord(corners[2])))) + + wcoord * ((FT(1) - vcoord) * (y_coord(corners[4]) + ucoord * (y_coord(corners[5]) - y_coord(corners[4]))) + + vcoord * (y_coord(corners[6]) + ucoord * (y_coord(corners[7]) - y_coord(corners[6])))); + const FT pz = (FT(1) - wcoord) * ((FT(1) - vcoord) * (z_coord(corners[0]) + ucoord * (z_coord(corners[1]) - z_coord(corners[0]))) + + vcoord * (z_coord(corners[2]) + ucoord * (z_coord(corners[3]) - z_coord(corners[2])))) + + wcoord * ((FT(1) - vcoord) * (z_coord(corners[4]) + ucoord * (z_coord(corners[5]) - z_coord(corners[4]))) + + vcoord * (z_coord(corners[6]) + ucoord * (z_coord(corners[7]) - z_coord(corners[6])))); + + bool pt_used = false; + Point_index g_index = 0; + + // loop over the contours + for(int i=0; i + +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +/*! + * \ingroup IS_Fields_helpers_grp + * + * \cgalModels{IsosurfacingInterpolationScheme_3} + * + * The class `Trilinear_interpolation` is the standard interpolation scheme + * to define continuous fields of scalar values and gradients from data defined + * only at the vertices of a %Cartesian grid. + * + * \tparam Grid must be `CGAL::Isosurfacing::Cartesian_grid_3`, with `GeomTraits` + * a model of `IsosurfacingTraits_3` + */ +template +class Trilinear_interpolation +{ +public: + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + using Iso_cuboid_3 = typename Geom_traits::Iso_cuboid_3; + +public: + /*! + * \brief interpolates the values at a given point using trilinear interpolation. + * + * \param p the point at which values are interpolated + * \param g the grid + * \param values the continuous field of scalar values, defined over the geometric span of `g` + */ + FT interpolate_values(const Point_3& p, + const Grid& g, + const std::vector& values) const +{ + typename Geom_traits::Compute_x_3 x_coord = g.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = g.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = g.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_vertex_3 vertex = g.geom_traits().construct_vertex_3_object(); + + // trilinear interpolation of stored values + const Iso_cuboid_3& span = g.span(); + const Vector_3& spacing = g.spacing(); + + // calculate min index including border case + const Point_3& min_p = vertex(span, 0); + std::size_t i = std::size_t((x_coord(p) - x_coord(min_p)) / x_coord(spacing)); + std::size_t j = std::size_t((y_coord(p) - y_coord(min_p)) / y_coord(spacing)); + std::size_t k = std::size_t((z_coord(p) - z_coord(min_p)) / z_coord(spacing)); + + if(i == g.xdim() - 1) + --i; + if(j == g.ydim() - 1) + --j; + if(k == g.zdim() - 1) + --k; + + // calculate coordinates of min index + const FT min_x = x_coord(min_p) + i * spacing[0]; + const FT min_y = y_coord(min_p) + j * spacing[1]; + const FT min_z = z_coord(min_p) + k * spacing[2]; + + // interpolation factors between 0 and 1 + const FT f_i = std::clamp((x_coord(p) - min_x) / spacing[0], 0, 1); + const FT f_j = std::clamp((y_coord(p) - min_y) / spacing[1], 0, 1); + const FT f_k = std::clamp((z_coord(p) - min_z) / spacing[2], 0, 1); + + // read the value at all 8 corner points + const FT g000 = values[g.linear_index(i + 0, j + 0, k + 0)]; + const FT g001 = values[g.linear_index(i + 0, j + 0, k + 1)]; + const FT g010 = values[g.linear_index(i + 0, j + 1, k + 0)]; + const FT g011 = values[g.linear_index(i + 0, j + 1, k + 1)]; + const FT g100 = values[g.linear_index(i + 1, j + 0, k + 0)]; + const FT g101 = values[g.linear_index(i + 1, j + 0, k + 1)]; + const FT g110 = values[g.linear_index(i + 1, j + 1, k + 0)]; + const FT g111 = values[g.linear_index(i + 1, j + 1, k + 1)]; + + // interpolate along all axes by weighting the corner points + const FT lambda000 = (FT(1) - f_i) * (FT(1) - f_j) * (FT(1) - f_k); + const FT lambda001 = (FT(1) - f_i) * (FT(1) - f_j) * f_k; + const FT lambda010 = (FT(1) - f_i) * f_j * (FT(1) - f_k); + const FT lambda011 = (FT(1) - f_i) * f_j * f_k; + const FT lambda100 = f_i * (FT(1) - f_j) * (FT(1) - f_k); + const FT lambda101 = f_i * (FT(1) - f_j) * f_k; + const FT lambda110 = f_i * f_j * (FT(1) - f_k); + const FT lambda111 = f_i * f_j * f_k; + + // add weighted corners + return g000 * lambda000 + g001 * lambda001 + + g010 * lambda010 + g011 * lambda011 + + g100 * lambda100 + g101 * lambda101 + + g110 * lambda110 + g111 * lambda111; + } + + /*! + * \brief interpolates the gradients at a given point using trilinear interpolation. + * + * \param p the point at which to interpolate the gradients + * \param g the grid + * \param gradients the continuous field of vector values, defined over the geometric span of `g` + */ + Vector_3 interpolate_gradients(const Point_3& p, + const Grid& g, + const std::vector& gradients) const + { + typename Geom_traits::Compute_x_3 x_coord = g.geom_traits().compute_x_3_object(); + typename Geom_traits::Compute_y_3 y_coord = g.geom_traits().compute_y_3_object(); + typename Geom_traits::Compute_z_3 z_coord = g.geom_traits().compute_z_3_object(); + typename Geom_traits::Construct_vector_3 vector = g.geom_traits().construct_vector_3_object(); + typename Geom_traits::Construct_vertex_3 vertex = g.geom_traits().construct_vertex_3_object(); + + // trilinear interpolation of stored gradients + const Iso_cuboid_3& span = g.span(); + const Vector_3& spacing = g.spacing(); + + // calculate min index including border case + const Point_3& min_p = vertex(span, 0); + std::size_t i = static_cast((x_coord(p) - x_coord(min_p)) / x_coord(spacing)); + std::size_t j = static_cast((y_coord(p) - y_coord(min_p)) / y_coord(spacing)); + std::size_t k = static_cast((z_coord(p) - z_coord(min_p)) / z_coord(spacing)); + + if(i == g.xdim() - 1) // dim is the point number + --i; + if(j == g.ydim() - 1) + --j; + if(k == g.zdim() - 1) + --k; + + // calculate coordinates of min index + const FT min_x = i * spacing[0] + x_coord(min_p); + const FT min_y = j * spacing[1] + y_coord(min_p); + const FT min_z = k * spacing[2] + z_coord(min_p); + + // interpolation factors between 0 and 1 + const FT f_i = std::clamp((x_coord(p) - min_x) / spacing[0], 0, 1); + const FT f_j = std::clamp((y_coord(p) - min_y) / spacing[1], 0, 1); + const FT f_k = std::clamp((z_coord(p) - min_z) / spacing[2], 0, 1); + + // read the value at all 8 corner points + const Vector_3& g000 = gradients[g.linear_index(i + 0, j + 0, k + 0)]; + const Vector_3& g001 = gradients[g.linear_index(i + 0, j + 0, k + 1)]; + const Vector_3& g010 = gradients[g.linear_index(i + 0, j + 1, k + 0)]; + const Vector_3& g011 = gradients[g.linear_index(i + 0, j + 1, k + 1)]; + const Vector_3& g100 = gradients[g.linear_index(i + 1, j + 0, k + 0)]; + const Vector_3& g101 = gradients[g.linear_index(i + 1, j + 0, k + 1)]; + const Vector_3& g110 = gradients[g.linear_index(i + 1, j + 1, k + 0)]; + const Vector_3& g111 = gradients[g.linear_index(i + 1, j + 1, k + 1)]; + + // interpolate along all axes by weighting the corner points + const FT lambda000 = (FT(1) - f_i) * (FT(1) - f_j) * (FT(1) - f_k); + const FT lambda001 = (FT(1) - f_i) * (FT(1) - f_j) * f_k; + const FT lambda010 = (FT(1) - f_i) * f_j * (FT(1) - f_k); + const FT lambda011 = (FT(1) - f_i) * f_j * f_k; + const FT lambda100 = f_i * (FT(1) - f_j) * (FT(1) - f_k); + const FT lambda101 = f_i * (FT(1) - f_j) * f_k; + const FT lambda110 = f_i * f_j * (FT(1) - f_k); + const FT lambda111 = f_i * f_j * f_k; + + // add weighted corners + return vector(x_coord(g000) * lambda000 + x_coord(g001) * lambda001 + + x_coord(g010) * lambda010 + x_coord(g011) * lambda011 + + x_coord(g100) * lambda100 + x_coord(g101) * lambda101 + + x_coord(g110) * lambda110 + x_coord(g111) * lambda111, + y_coord(g000) * lambda000 + y_coord(g001) * lambda001 + + y_coord(g010) * lambda010 + y_coord(g011) * lambda011 + + y_coord(g100) * lambda100 + y_coord(g101) * lambda101 + + y_coord(g110) * lambda110 + y_coord(g111) * lambda111, + z_coord(g000) * lambda000 + z_coord(g001) * lambda001 + + z_coord(g010) * lambda010 + z_coord(g011) * lambda011 + + z_coord(g100) * lambda100 + z_coord(g101) * lambda101 + + z_coord(g110) * lambda110 + z_coord(g111) * lambda111); + } +}; + +#ifndef DOXYGEN_RUNNING +// [undocumented] +// +// \ingroup IS_Fields_helpers_grp +// +// This can be used for example when we have implicit functions for data (values & gradients), +// but use an interpolated values field as to store data. +template +class Function_evaluation +{ + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + using Point_3 = typename Geom_traits::Point_3; + using Vector_3 = typename Geom_traits::Vector_3; + + std::function m_value_fn; + std::function m_gradient_fn; + +public: + template + Function_evaluation(const ValueFunction& value_fn, + const GradientFunction& gradient_fn = [](const Point_3&) -> Vector_3 { return CGAL::NULL_VECTOR; }) + : m_value_fn{value_fn}, + m_gradient_fn{gradient_fn} + { } + +public: + FT interpolate_values(const Point_3& p, const Grid&, const std::vector&) const + { + return m_value_fn(p); + } + + Vector_3 interpolate_gradients(const Point_3& p, const Grid&, const std::vector&) const + { + return m_gradient_fn(p); + } +}; +#endif + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_INTERNAL_INTERPOLATION_SCHEMES_3_H diff --git a/Isosurfacing_3/include/CGAL/Isosurfacing_3/marching_cubes_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/marching_cubes_3.h new file mode 100644 index 000000000000..676577bef8b0 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/marching_cubes_3.h @@ -0,0 +1,94 @@ +// Copyright (c) 2022-2024 INRIA Sophia-Antipolis (France), GeometryFactory (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Julian Stahl +// Mael Rouxel-Labbé + +#ifndef CGAL_ISOSURFACING_3_MARCHING_CUBES_3_H +#define CGAL_ISOSURFACING_3_MARCHING_CUBES_3_H + +#include + +#include +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { + +/** + * \ingroup IS_Methods_grp + * + * \brief creates a triangle soup that represents an isosurface generated by the Marching Cubes algorithm. + * + * \tparam ConcurrencyTag enables sequential versus parallel algorithm. + * Possible values are `Sequential_tag`, `Parallel_if_available_tag`, or `Parallel_tag`. + * \tparam Domain must be a model of `IsosurfacingDomain_3`. + * \tparam PointRange must be a model of the concepts `RandomAccessContainer` and `BackInsertionSequence` + * whose value type can be constructed from the point type of the domain. + * \tparam TriangleRange must be a model of the concepts `RandomAccessContainer` and `BackInsertionSequence` + * whose value type is itself a model of the concepts `RandomAccessContainer` + * and `BackInsertionSequence` whose value type is `std::size_t`. + * \tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" + * + * \param domain the domain providing the spatial partition and the data + * \param isovalue the value defining the isosurface + * \param points the points of the triangles in the created indexed face set + * \param triangles each element in the vector describes a triangle using the indices of the points in `points` + * \param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below + * + * \cgalNamedParamsBegin + * \cgalParamNBegin{use_topologically_correct_marching_cubes} + * \cgalParamDescription{whether the topologically correct variant of Marching Cubes \cgalCite{cgal:g-ctcmi-16} should be used.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`true`} + * \cgalParamNEnd + * \cgalNamedParamsEnd + * + * \sa `CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh()` + */ +template +void marching_cubes(const Domain& domain, + const typename Domain::Geom_traits::FT isovalue, + PointRange& points, + TriangleRange& triangles, + const NamedParameters& np = parameters::default_values()) +{ + using parameters::choose_parameter; + using parameters::get_parameter; + + const bool use_tmc = choose_parameter(get_parameter(np, internal_np::use_topologically_correct_marching_cubes), true); + + if(use_tmc) + { + internal::TMC_functor functor(domain, isovalue); + domain.template for_each_cell(functor); + functor.to_triangle_soup(points, triangles); + } + else + { + // run marching cubes + internal::Marching_cubes_3 functor(domain, isovalue); + domain.template for_each_cell(functor); + + // copy the result to points and triangles + internal::triangles_to_polygon_soup(functor.triangles(), points, triangles); + } +} + +} // namespace Isosurfacing +} // namespace CGAL + +#endif // CGAL_ISOSURFACING_3_MARCHING_CUBES_3_H diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/copyright b/Isosurfacing_3/package_info/Isosurfacing_3/copyright new file mode 100644 index 000000000000..fd4b0243d94d --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/copyright @@ -0,0 +1 @@ +Inria Sophia-Antipolis (France) diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/dependencies b/Isosurfacing_3/package_info/Isosurfacing_3/dependencies new file mode 100644 index 000000000000..144ff7db0577 --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/dependencies @@ -0,0 +1,15 @@ +Algebraic_foundations +BGL +CGAL_ImageIO +Circulator +Installation +Interval_support +Isosurfacing_3 +Kernel_23 +Modular_arithmetic +Number_types +Profiling_tools +Property_map +STL_Extension +Solver_interface +Stream_support diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/description.txt b/Isosurfacing_3/package_info/Isosurfacing_3/description.txt new file mode 100644 index 000000000000..aa18e64751ab --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/description.txt @@ -0,0 +1 @@ +The 3D Isosurfacing package provides several isosurfacing algorithms (marching cubes, dual contouring) to generate triangle and quadrangle surface meshes from an input 3D domain and a user-defined isovalue. \ No newline at end of file diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/license.txt b/Isosurfacing_3/package_info/Isosurfacing_3/license.txt new file mode 100644 index 000000000000..23559ebbbaf3 --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/license.txt @@ -0,0 +1,2 @@ +GPL (v3 or later) +GPL (v3 or later) AND MIT/X11 (BSD like) diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/long_description.txt b/Isosurfacing_3/package_info/Isosurfacing_3/long_description.txt new file mode 100644 index 000000000000..feb2f41e98e2 --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/long_description.txt @@ -0,0 +1,5 @@ +This component takes a 3D domain as input and a user-specified isovalue, and generates a surface mesh +approximating the specified isovalue. The meshing algorithm can be selected among two isosurfacing algorithms: +marching cubes and dual contouring. Two variants of the marching cubes algorithm are offererd: +with or without topological guarantees. The domain can be created from an explicit grid, +an implicit grid or a volumetric image. diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/maintainer b/Isosurfacing_3/package_info/Isosurfacing_3/maintainer new file mode 100644 index 000000000000..c2585469932e --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/maintainer @@ -0,0 +1 @@ +Pierre Alliez diff --git a/Isosurfacing_3/test/Isosurfacing_3/CMakeLists.txt b/Isosurfacing_3/test/Isosurfacing_3/CMakeLists.txt new file mode 100644 index 000000000000..c367b057d6f1 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/CMakeLists.txt @@ -0,0 +1,48 @@ +cmake_minimum_required(VERSION 3.1...3.23) + +project(Isosurfacing_3_Tests) + +find_package(CGAL REQUIRED) + +find_package(Eigen3 3.1.0 QUIET) #(3.1.0 or greater) +include(CGAL_Eigen3_support) + +find_package(TBB QUIET) +include(CGAL_TBB_support) + +create_single_source_cgal_program("test_marching_cubes.cpp") + +if(TARGET CGAL::Eigen3_support) + create_single_source_cgal_program("test_isosurfacing_concepts.cpp") + create_single_source_cgal_program("test_dual_contouring.cpp") + + target_link_libraries(test_isosurfacing_concepts PRIVATE CGAL::Eigen3_support) + target_link_libraries(test_dual_contouring PRIVATE CGAL::Eigen3_support) + + if(TARGET CGAL::TBB_support) + target_link_libraries(test_isosurfacing_concepts PRIVATE CGAL::Eigen3_support) + target_link_libraries(test_dual_contouring PRIVATE CGAL::TBB_support) + endif() + + #examples to be moved in example when reading to be documented + create_single_source_cgal_program("dual_contouring_octree.cpp") + create_single_source_cgal_program("dual_contouring_strategies.cpp") + create_single_source_cgal_program("dual_contouring_intersection_oracles.cpp") + + target_link_libraries(dual_contouring_octree PRIVATE CGAL::Eigen3_support) + target_link_libraries(dual_contouring_strategies PRIVATE CGAL::Eigen3_support) + target_link_libraries(dual_contouring_intersection_oracles PRIVATE CGAL::Eigen3_support) + + if(TARGET CGAL::TBB_support) + target_link_libraries(dual_contouring_octree PRIVATE CGAL::TBB_support) + target_link_libraries(dual_contouring_strategies PRIVATE CGAL::TBB_support) + target_link_libraries(dual_contouring_intersection_oracles PRIVATE CGAL::TBB_support) + endif() + +else() + message(STATUS "NOTICE: tests require the Eigen library, and will not be compiled.") +endif() + +if(TARGET CGAL::TBB_support) + target_link_libraries(test_marching_cubes PRIVATE CGAL::TBB_support) +endif() diff --git a/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_intersection_oracles.cpp b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_intersection_oracles.cpp new file mode 100644 index 000000000000..2861d651a55a --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_intersection_oracles.cpp @@ -0,0 +1,113 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +auto implicit_function = [](const Point& q) -> FT +{ + auto cyl = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,0,1), 0.5, q); }; + auto cube = [](const Point& q) { return IS::Shapes::box(Point(-0.5,-0.5,-0.5), Point(0.5,0.5,0.5), q); }; + auto cyl_and_cube = [&](const Point& q) { return IS::Shapes::shape_union(cyl, cube, q); }; + + auto sphere = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0.5), 0.75, q); }; + return IS::Shapes::shape_difference(cyl_and_cube, sphere, q); +}; + +// The example shows that this is a bad choice to use a linear interpolant +// because the implicit function gives much more information than just using the values at grid vertices. +// @todo implement the SDF interpolant and show that it's as good as the dichotomy, but faster +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.; + std::cout << "Isovalue: " << isovalue << std::endl; + + // create bounding box and grid + const CGAL::Bbox_3 bbox = {-2., -2., -2., 2., 2., 2.}; + Grid grid { bbox, CGAL::make_array(50, 50, 50) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + + // fill up values and gradients + Values values { implicit_function, grid }; + Gradients gradients { values, step }; + + const bool triangulate_faces = false; + + // Compute edge intersections with dichotomy + { + using Domain = IS::Dual_contouring_domain_3; + Domain domain { grid, values, gradients }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Dichotomy)" << std::endl; + IS::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring-dichotomy_oracle.off", points, triangles); + } + + // Compute edge intersections with linear interpolation + { + using Domain = IS::Dual_contouring_domain_3; + Domain domain { grid, values, gradients }; + + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Linear Interpolation)" << std::endl; + IS::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring-linear_oracle.off", points, triangles); + } + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_octree.cpp b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_octree.cpp new file mode 100644 index 000000000000..51ed1c4ac5e8 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_octree.cpp @@ -0,0 +1,135 @@ +#include + +// undocumented +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Vector = typename Kernel::Vector_3; +using Point = typename Kernel::Point_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +using Octree_wrapper = CGAL::Isosurfacing::internal::Octree_wrapper; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Gradient_function_3; +using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + +// Refine one of the octant +struct Refine_one_eighth +{ + using Octree = Octree_wrapper::Octree; + + std::size_t min_depth_; + std::size_t max_depth_; + + std::size_t octree_dim_; + + Refine_one_eighth(std::size_t min_depth, + std::size_t max_depth) + : min_depth_(min_depth), + max_depth_(max_depth) + { + octree_dim_ = std::size_t(1) << max_depth_; + } + + Octree_wrapper::Uniform_coords uniform_coordinates(const Octree::Node_index& node_index, const Octree& octree) const + { + auto coords = octree.global_coordinates(node_index); + const std::size_t depth_factor = std::size_t(1) << (max_depth_ - octree.depth(node_index)); + for(int i=0; i < 3; ++i) + coords[i] *= uint32_t(depth_factor); + + return coords; + } + + bool operator()(const Octree::Node_index& ni, const Octree& octree) const + { + if(octree.depth(ni) < min_depth_) + return true; + + if(octree.depth(ni) == max_depth_) + return false; + + auto leaf_coords = uniform_coordinates(ni, octree); + + if(leaf_coords[0] >= octree_dim_ / 2) + return false; + + if(leaf_coords[1] >= octree_dim_ / 2) + return false; + + if(leaf_coords[2] >= octree_dim_ / 2) + return false; + + return true; + } +}; + +auto sphere_function = [](const Point& p) -> FT +{ + return std::sqrt(p.x()*p.x() + p.y()*p.y() + p.z()*p.z()); +}; + +auto sphere_gradient = [](const Point& p) -> Vector +{ + const Vector g = p - CGAL::ORIGIN; + return g / std::sqrt(g.squared_length()); +}; + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.8; + + const CGAL::Bbox_3 bbox{-1., -1., -1., 1., 1., 1.}; + Octree_wrapper octree_wrap { bbox }; + + Refine_one_eighth split_predicate(3, 5); + octree_wrap.refine(split_predicate); + + std::ofstream oo("octree.polylines.txt"); + oo.precision(17); + octree_wrap.octree().dump_to_polylines(oo); + + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + CGAL::Real_timer timer; + timer.start(); + + // fill up values and gradients + Values values { sphere_function, octree_wrap }; + Gradients gradients { sphere_gradient, octree_wrap }; + Domain domain { octree_wrap, values, gradients }; + + // output containers + Point_range points; + Polygon_range triangles; + + // run Dual Contouring + CGAL::Isosurfacing::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(true)); + + timer.stop(); + + std::cout << "Output #vertices (DC): " << points.size() << std::endl; + std::cout << "Output #triangles (DC): " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_octree.off", points, triangles); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_strategies.cpp b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_strategies.cpp new file mode 100644 index 000000000000..58e91a8271cd --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_strategies.cpp @@ -0,0 +1,156 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point = typename Kernel::Point_3; +using Vector = typename Kernel::Vector_3; + +using Grid = CGAL::Isosurfacing::Cartesian_grid_3; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Finite_difference_gradient_3; +using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +namespace IS = CGAL::Isosurfacing; + +auto implicit_function = [](const Point& q) -> FT +{ + auto cyl = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,0,1), 0.5, q); }; + auto cube = [](const Point& q) { return IS::Shapes::box(Point(-0.5,-0.5,-0.5), Point(0.5,0.5,0.5), q); }; + auto cyl_and_cube = [&](const Point& q) { return IS::Shapes::shape_union(cyl, cube, q); }; + + auto sphere = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0.5), 0.75, q); }; + return IS::Shapes::shape_difference(cyl_and_cube, sphere, q); +}; + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.; + std::cout << "Isovalue: " << isovalue << std::endl; + + // create bounding box and grid + const CGAL::Bbox_3 bbox = {-2., -2., -2., 2., 2., 2.}; + Grid grid { bbox, CGAL::make_array(50, 50, 50) }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + + // fill up values and gradients + Values values { implicit_function, grid }; + Gradients gradients { values, step }; + Domain domain { grid, values, gradients }; + + const bool triangulate_faces = false; + + // unconstrained QEM Placement strategy (default) + { + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (QEM - unconstrained)" << std::endl; + IS::internal::Dual_contourer contourer; + contourer(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_QEM-unconstrained.off", points, triangles); + } + + // constrained QEM Placement strategy + { + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (QEM - constrained)" << std::endl; + IS::internal::Dual_contourer contourer; + contourer(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(!triangulate_faces) + .constrain_to_cell(true)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_QEM-constrained.off", points, triangles); + } + + + // Centroid of Edge Intersections strategy + { + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Centroid of Edge Intersections)" << std::endl; + IS::internal::Dual_contourer contourer; + contourer(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_CEI.off", points, triangles); + } + + // Cell Center strategy + { + CGAL::Real_timer timer; + timer.start(); + + Point_range points; + Polygon_range triangles; + + std::cout << "--- Dual Contouring (Cell Center)" << std::endl; + IS::internal::Dual_contourer contourer; + contourer(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(!triangulate_faces)); + + timer.stop(); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #triangles: " << triangles.size() << std::endl; + std::cout << "Elapsed time: " << timer.time() << " seconds" << std::endl; + CGAL::IO::write_polygon_soup("dual_contouring_CC.off", points, triangles); + } + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/test_dual_contouring.cpp b/Isosurfacing_3/test/Isosurfacing_3/test_dual_contouring.cpp new file mode 100644 index 000000000000..c37cda9130e0 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_dual_contouring.cpp @@ -0,0 +1,202 @@ +#include "test_util.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace IS = CGAL::Isosurfacing; + +template +void test_cube() +{ + using FT = typename K::FT; + using Point = typename K::Point_3; + using Vector = typename K::Vector_3; + + using Mesh = CGAL::Surface_mesh; + using Grid = IS::Cartesian_grid_3; + using Values = IS::Value_function_3; + using Gradients = IS::Finite_difference_gradient_3; + using Domain = IS::Dual_contouring_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + using Mesh = CGAL::Surface_mesh; + +auto implicit_function = [](const Point& q) -> FT +{ + return IS::Shapes::box(Point(-1,-1,-1), Point(1,1,1), q); + + // --- + auto cyl = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,0,1), 0.5, q); }; + auto cube = [](const Point& q) { return IS::Shapes::box(Point(-0.5,-0.5,-0.5), Point(0.5,0.5,0.5), q); }; + auto cyl_and_cube = [&](const Point& q) { return IS::Shapes::shape_union(cyl, cube, q); }; + + auto sphere = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0.5), 1, q); }; + return IS::Shapes::shape_difference(cyl_and_cube, sphere, q); + + // --- + auto box_1 = [](const Point& q) { return IS::Shapes::box(Point(0,0,0), Point(1,1,1), q); }; + auto box_2 = [](const Point& q) { return IS::Shapes::box(Point(0.5,0.5,0.5), Point(1.5,1.5,1.5), q); }; + return IS::Shapes::shape_union(box_1, box_2, q); + + // --- + return IS::Shapes::box(Point(0,0,0), Point(1,2,3), q); + + // --- + return IS::Shapes::torus(Point(0,0,0), Vector(0,0,1), 0.25, 1, q); + + // --- + return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(1,1,1), 1, q); + + // --- + auto sphere_1 = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0), 1, q); }; + auto sphere_2 = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0.5), 1, q); }; + return IS::Shapes::shape_union(sphere_1, sphere_2, q); +}; + + const CGAL::Bbox_3 bbox = {-2., -2., -2., 2., 2., 2.}; + const Vector spacing { 0.05, 0.05, 0.05 }; + const FT isovalue = 0; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring (Implicit function) with isovalue = " << isovalue << std::endl; + std::cout << "Kernel: " << typeid(K).name() << std::endl; + + Grid grid { bbox, spacing }; + Values values {implicit_function , grid }; + Gradients gradients { values, 0.01 * spacing[0] }; + Domain domain { grid, values, gradients }; + + Point_range points; + Polygon_range polygons; + IS::dual_contouring(domain, isovalue, points, polygons, CGAL::parameters::do_not_triangulate_faces(true)); // if you change that, change the array + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #polygons: " << polygons.size() << std::endl; + + std::cout << "Checking emptiness..." << std::endl; + assert(points.size() && polygons.size()); + std::cout << "Checking for duplicate points..." << std::endl; + assert(!has_duplicate_points(points, polygons)); + std::cout << "Checking for duplicate polygons..." << std::endl; + assert(!has_duplicate_polygons(points, polygons)); + std::cout << "Checking for isolated vertices..." << std::endl; + assert(!has_isolated_vertices(points, polygons)); + std::cout << "Checking if the soup is a mesh..." << std::endl; + assert(CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(polygons)); + + std::cout << "Create the mesh..." << std::endl; + Mesh mesh; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, polygons, mesh); + + std::cout << "Triangulate the mesh..." << std::endl; + CGAL::Polygon_mesh_processing::triangulate_faces(mesh); + + std::cout << "Write the polygon mesh..." << std::endl; + CGAL::IO::write_polygon_mesh("DC_implicit_function.off", mesh, CGAL::parameters::stream_precision(17)); + + std::cout << "Check manifoldness..." << std::endl; + assert(is_manifold(mesh)); + + std::cout << "Check for degenerate faces..." << std::endl; + assert(!has_degenerate_faces(mesh)); + + std::cout << "Volume: " << CGAL::Polygon_mesh_processing::volume(mesh) << std::endl; + assert(CGAL::abs(CGAL::Polygon_mesh_processing::volume(mesh) - FT(8)) < 1e-2); +} + +template +void test_image() +{ + using FT = typename K::FT; + using Point = typename K::Point_3; + + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Gradients = IS::Finite_difference_gradient_3; + using Domain = IS::Dual_contouring_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + const std::string fname = CGAL::data_file_path("images/skull_2.9.inr"); + const FT isovalue = 2.9; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Dual Contouring (Image) with isovalue = " << isovalue << std::endl; + std::cout << "Kernel: " << typeid(K).name() << std::endl; + + CGAL::Image_3 image; + if(!image.read(fname)) + { + std::cerr << "Error: Cannot read file " << fname << std::endl; + assert(false); + return; + } + + // convert image to a Cartesian grid + Grid grid; + Values values { grid }; // 'values' keeps a reference to the grid + if(!IS::IO::convert_image_to_grid(image, grid, values)) + { + std::cerr << "Error: Cannot convert image to Cartesian grid" << std::endl; + assert(false); + return; + } + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + const FT step = CGAL::approximate_sqrt(grid.spacing().squared_length()) * 0.01; + + // fill up values and gradients + Gradients gradients { values, step }; + Domain domain { grid, values, gradients }; + + // run dual contouring isosurfacing + Point_range points; + Polygon_range polygons; + IS::dual_contouring(domain, isovalue, points, polygons, CGAL::parameters::do_not_triangulate_faces(true)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #polygons: " << polygons.size() << std::endl; + + assert(points.size() && polygons.size()); + assert(!has_duplicate_points(points, polygons)); + assert(!has_duplicate_polygons(points, polygons)); + assert(!has_isolated_vertices(points, polygons)); +} + +int main(int, char**) +{ + test_cube >(); + test_image >(); + + test_cube(); + test_image(); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/test_isosurfacing_concepts.cpp b/Isosurfacing_3/test/Isosurfacing_3/test_isosurfacing_concepts.cpp new file mode 100644 index 000000000000..a9094351ad16 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_isosurfacing_concepts.cpp @@ -0,0 +1,144 @@ +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace IS = CGAL::Isosurfacing; + +template +struct Traits +{ + using FT = typename K::FT; + using Point_3 = typename K::Point_3; + using Vector_3 = typename K::Vector_3; + using Iso_cuboid_3 = typename K::Iso_cuboid_3; + + struct Compute_x_3 + { + FT operator()(const Point_3&) const { return 0; } + FT operator()(const Vector_3&) const { return 0; } + }; + + struct Compute_y_3 + { + FT operator()(const Point_3&) const { return 0; } + FT operator()(const Vector_3&) const { return 0; } + }; + + struct Compute_z_3 + { + FT operator()(const Point_3&) const { return 0; } + FT operator()(const Vector_3&) const { return 0; } + }; + + struct Construct_point_3 + { + Point_3 operator()(FT, FT, FT) const { return CGAL::ORIGIN; } + }; + + struct Construct_vector_3 + { + Vector_3 operator()(FT, FT, FT) const { return CGAL::NULL_VECTOR; } + }; + + struct Construct_iso_cuboid_3 + { + Iso_cuboid_3 operator()(FT, FT, FT, FT, FT, FT) const { return Iso_cuboid_3(); } + }; + + struct Construct_vertex_3 + { + Point_3 operator()(const Iso_cuboid_3&, int) const { return CGAL::ORIGIN; } + }; + + Compute_x_3 compute_x_3_object() { return Compute_x_3(); } + Compute_y_3 compute_y_3_object() { return Compute_y_3();} + Compute_z_3 compute_z_3_object() { return Compute_z_3(); } + Construct_point_3 construct_point_3_object() { return Construct_point_3();} + Construct_vector_3 construct_vector_3_object() { return Construct_vector_3(); } + Construct_iso_cuboid_3 construct_iso_cuboid_3_object() { return Construct_iso_cuboid_3(); } + Construct_vertex_3 construct_vertex_3_object() { return Construct_vertex_3(); } +}; + +template +struct MC_Domain +{ + typedef Traits Geom_traits; + typedef typename Geom_traits::FT FT; + typedef typename Geom_traits::Point_3 Point_3; + typedef std::size_t vertex_descriptor; + typedef std::size_t edge_descriptor; + typedef std::size_t cell_descriptor; + typedef std::vector Edge_vertices; + typedef std::vector Cells_incident_to_edge; + typedef std::vector Cell_vertices; + typedef std::vector Cell_edges; + + Geom_traits geom_traits() const { return Geom_traits(); } + + Point_3 point(const vertex_descriptor& /*v*/) const { return CGAL::ORIGIN; } + + FT value(const Point_3& /*p*/) const { return 0; } + FT value(const vertex_descriptor& /*v*/) const { return 0; } + + Edge_vertices incident_vertices(const edge_descriptor& /*e*/) const { return {}; } + Cells_incident_to_edge incident_cells(const edge_descriptor& /*e*/) const { return {}; } + Cell_vertices cell_vertices(const cell_descriptor& /*c*/) const { return {}; } + Cell_edges cell_edges(const cell_descriptor& /*c*/) const { return {}; } + + template + void for_each_vertex(Functor& /*f*/) const { } + template + void for_each_edge(Functor& /*f*/) const { } + template + void for_each_cell(Functor& /*f*/) const { } + + bool construct_intersection(const Point_3&, const Point_3& /*p_1*/, + const FT /*v_0*/, const FT /*v_1*/, + const FT /*isovalue*/, + Point_3& /*intersection*/) const { return false; } +}; + +template +struct DC_Domain + : public MC_Domain +{ + typedef Traits Geom_traits; + typedef typename Geom_traits::Point_3 Point_3; + typedef typename Geom_traits::Vector_3 Vector_3; + + Vector_3 gradient(const Point_3& /*p*/) const { return CGAL::NULL_VECTOR; } +}; + +template +void test() +{ + using Point_3 = typename K::Point_3; + + MC_Domain mc_domain; + DC_Domain dc_domain; + + std::vector points; + std::vector > faces; + + IS::marching_cubes(mc_domain, 0, points, faces); + + IS::dual_contouring(dc_domain, 0, points, faces); +} + +int main(int, char**) +{ + test >(); + test(); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/test_marching_cubes.cpp b/Isosurfacing_3/test/Isosurfacing_3/test_marching_cubes.cpp new file mode 100644 index 000000000000..332583562530 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_marching_cubes.cpp @@ -0,0 +1,155 @@ +#include "test_util.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using K = CGAL::Simple_cartesian; +using FT = typename K::FT; +using Point = typename K::Point_3; +using Vector = typename K::Vector_3; + +using Point_range = std::vector; +using Triangle_range = std::vector >; + +using Mesh = CGAL::Surface_mesh; + +#define CGAL_TESTUISTE_ISOSURFACING_OUTPUT + +namespace IS = CGAL::Isosurfacing; + +struct Sphere_function +{ + FT operator()(const Point& p) const + { + return sqrt(p.x()*p.x() + p.y()*p.y() + p.z()*p.z()); + } +}; + +void test_implicit_sphere() +{ + using Grid = IS::Cartesian_grid_3; + using Values = IS::Value_function_3; + using Domain = IS::Marching_cubes_domain_3; + + const CGAL::Bbox_3 bbox {-1., -1., -1., 1., 1., 1.}; + const Vector spacing { 0.1, 0.1, 0.1 }; + const FT isovalue = 0.8; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes (Implicit sphere) with isovalue = " << isovalue << std::endl; + std::cout << "Kernel: " << typeid(K).name() << std::endl; + + Grid grid { bbox, spacing }; + Values values { Sphere_function(), grid }; + Domain domain { grid, values }; + + Point_range points; + Triangle_range triangles; + IS::marching_cubes(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #polygons: " << triangles.size() << std::endl; + +#ifdef CGAL_TESTUISTE_ISOSURFACING_OUTPUT + CGAL::IO::write_polygon_soup("MC_implicit_sphere.off", points, triangles); +#endif + + assert(points.size() && triangles.size()); + assert(!has_duplicate_points(points, triangles)); + assert(!has_duplicate_polygons(points, triangles)); + assert(!has_isolated_vertices(points, triangles)); + + assert(CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles)); + Mesh m; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, triangles, m); + + assert(is_manifold(m)); + assert(!has_degenerate_faces(m)); +} + +void test_grid_sphere(const std::size_t n) +{ + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Domain = IS::Marching_cubes_domain_3; + + const CGAL::Bbox_3 bbox = {-1., -1., -1., 1., 1., 1.}; + const Vector spacing(2. / (n - 1), 2. / (2 * (n - 1)), 2. / (3*(n - 1))); + const FT isovalue = 0.777; + + std::cout << "\n ---- " << std::endl; + std::cout << "Running Marching Cubes (Implicit sphere) with n = " << n << std::endl; + std::cout << "Kernel: " << typeid(K).name() << std::endl; + + Grid grid { bbox, spacing }; + + std::cout << "Span: " << grid.span() << std::endl; + std::cout << "Cell dimensions: " << grid.spacing()[0] << " " << grid.spacing()[1] << " " << grid.spacing()[2] << std::endl; + std::cout << "Cell #: " << grid.xdim() << ", " << grid.ydim() << ", " << grid.zdim() << std::endl; + + Values values { grid }; + Sphere_function sphere_function; + for(std::size_t x=0; x(domain, isovalue, points, triangles); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #polygons: " << triangles.size() << std::endl; + +#ifdef CGAL_TESTUISTE_ISOSURFACING_OUTPUT + const std::string test_name = "test_grid_sphere(" + std::to_string(n) + ")"; + CGAL::IO::write_polygon_soup(test_name + ".off", points, triangles); +#endif + + assert(!has_duplicate_points(points, triangles)); + assert(!has_duplicate_polygons(points, triangles)); + assert(!has_isolated_vertices(points, triangles)); + + assert(CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles)); + Mesh m; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, triangles, m); + + assert(is_manifold(m)); + assert(!has_degenerate_faces(m)); +} + +int main(int, char**) +{ + test_implicit_sphere(); + test_grid_sphere(2); + test_grid_sphere(3); + test_grid_sphere(10); + test_grid_sphere(50); + test_grid_sphere(100); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/test_util.h b/Isosurfacing_3/test/Isosurfacing_3/test_util.h new file mode 100644 index 000000000000..2c127a112758 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_util.h @@ -0,0 +1,58 @@ +#ifndef CGAL_ISOSURFACING_TEST_UTIL_H +#define CGAL_ISOSURFACING_TEST_UTIL_H + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +template +bool has_duplicate_points(PointRange points, PolygonRange polygons) // intentional copies +{ + return CGAL::Polygon_mesh_processing::merge_duplicate_points_in_polygon_soup(points, polygons) != 0; +} + +template +bool has_duplicate_polygons(PointRange points, PolygonRange polygons) +{ + return CGAL::Polygon_mesh_processing::merge_duplicate_polygons_in_polygon_soup(points, polygons) != 0; +} + +template +bool has_isolated_vertices(PointRange points, PolygonRange polygons) +{ + return CGAL::Polygon_mesh_processing::remove_isolated_points_in_polygon_soup(points, polygons) != 0; +} + +template +bool is_manifold(PolygonMesh& pmesh) +{ + return CGAL::Polygon_mesh_processing::duplicate_non_manifold_vertices(pmesh, CGAL::parameters::dry_run(true)) == 0; +} + +template +bool has_degenerate_faces(PolygonMesh& pmesh) +{ + std::set::face_descriptor> dfs; + CGAL::Polygon_mesh_processing::degenerate_faces(pmesh, std::inserter(dfs, dfs.begin())); + return !dfs.empty(); +} + +// template +// bool check_mesh_distance(const PolygonMesh& m0, const PolygonMesh& m1) +// { +// auto dist = CGAL::Polygon_mesh_processing::approximate_Hausdorff_distance( +// m0, m1, CGAL::parameters::number_of_points_per_area_unit(4000)); +// std::cout << dist << std::endl; +// return true; +// } + +#endif // CGAL_ISOSURFACING_TEST_UTIL_H diff --git a/Kernel_23/doc/Kernel_23/CGAL/Iso_cuboid_3.h b/Kernel_23/doc/Kernel_23/CGAL/Iso_cuboid_3.h index c2606d52caca..6ca223f77c60 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Iso_cuboid_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Iso_cuboid_3.h @@ -99,7 +99,7 @@ Test for inequality. bool operator!=(const Iso_cuboid_3 &c2) const; /*! -returns the i'th vertex modulo 8 of `c`. +returns the i-th vertex modulo 8 of `c`. starting with the lower left vertex. \cgalEpicExact */ diff --git a/Kernel_23/doc/Kernel_23/CGAL/Iso_rectangle_2.h b/Kernel_23/doc/Kernel_23/CGAL/Iso_rectangle_2.h index 602cdc2386ea..4876de765149 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Iso_rectangle_2.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Iso_rectangle_2.h @@ -95,7 +95,7 @@ Test for inequality. bool operator!=(const Iso_rectangle_2 &r2) const; /*! -returns the i'th vertex modulo 4 of `r` in counterclockwise order, +returns the i-th vertex modulo 4 of `r` in counterclockwise order, starting with the lower left vertex. \cgalEpicExact */ @@ -144,7 +144,7 @@ returns the \f$ y\f$ coordinate of upper right vertex of `r`. Kernel::FT ymax() const; /*! -returns the `i`'th %Cartesian coordinate of the +returns the `i`-th %Cartesian coordinate of the lower left vertex of `r`. \pre `0 <= i <= 1`. @@ -153,7 +153,7 @@ lower left vertex of `r`. Kernel::FT min_coord(int i) const; /*! -returns the `i`'th %Cartesian coordinate of the +returns the `i`-th %Cartesian coordinate of the upper right vertex of `r`. \pre `0 <= i <= 1`. diff --git a/Kernel_23/doc/Kernel_23/CGAL/Point_2.h b/Kernel_23/doc/Kernel_23/CGAL/Point_2.h index a0a6315a867b..4af9a71a2b2a 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Point_2.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Point_2.h @@ -163,13 +163,13 @@ Kernel::FT y() const; /// @{ /*! -returns the i'th homogeneous coordinate of `p`. +returns the i-th homogeneous coordinate of `p`. \pre `0 <= i <= 2`. */ Kernel::RT homogeneous(int i) const; /*! -returns the i'th %Cartesian coordinate of `p`. +returns the i-th %Cartesian coordinate of `p`. \pre `0 <= i <= 1`. \cgalEpicExact diff --git a/Kernel_23/doc/Kernel_23/CGAL/Point_3.h b/Kernel_23/doc/Kernel_23/CGAL/Point_3.h index d22c3589164a..e0ec7c20c3de 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Point_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Point_3.h @@ -160,13 +160,13 @@ Kernel::FT z() const; /// @{ /*! -returns the i'th homogeneous coordinate of `p`. +returns the i-th homogeneous coordinate of `p`. \pre `0 <= i <= 3`. */ Kernel::RT homogeneous(int i) const; /*! -returns the i'th %Cartesian coordinate of `p`. +returns the i-th %Cartesian coordinate of `p`. \pre `0 <= i <= 2`. \cgalEpicExact diff --git a/Kernel_23/doc/Kernel_23/CGAL/Tetrahedron_3.h b/Kernel_23/doc/Kernel_23/CGAL/Tetrahedron_3.h index 6af609250b87..8405593d7700 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Tetrahedron_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Tetrahedron_3.h @@ -54,7 +54,7 @@ Test for inequality. bool operator!=(const Tetrahedron_3 &t2) const; /*! -returns the i'th vertex modulo 4 of `t`. +returns the i-th vertex modulo 4 of `t`. \cgalEpicExact */ Point_3 vertex(int i) const; diff --git a/Kernel_23/doc/Kernel_23/CGAL/Triangle_2.h b/Kernel_23/doc/Kernel_23/CGAL/Triangle_2.h index 11a9c5162698..08ec687bb82c 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Triangle_2.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Triangle_2.h @@ -49,7 +49,7 @@ Test for inequality. bool operator!=(const Triangle_2 &t2) const; /*! -returns the i'th vertex modulo 3 of `t`. +returns the i-th vertex modulo 3 of `t`. \cgalEpicExact */ Point_2 vertex(int i) const; diff --git a/Kernel_23/doc/Kernel_23/CGAL/Triangle_3.h b/Kernel_23/doc/Kernel_23/CGAL/Triangle_3.h index 4efcfac0752f..00755f7cc205 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Triangle_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Triangle_3.h @@ -44,7 +44,7 @@ Test for inequality. bool operator!=(const Triangle_3 &t2) const; /*! -returns the i'th vertex modulo 3 of `t`. +returns the i-th vertex modulo 3 of `t`. \cgalEpicExact */ Point_3 vertex(int i) const; diff --git a/Kernel_23/doc/Kernel_23/CGAL/Vector_2.h b/Kernel_23/doc/Kernel_23/CGAL/Vector_2.h index 5f77fcd9c845..8a2236104b02 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Vector_2.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Vector_2.h @@ -128,13 +128,13 @@ Kernel::FT y() const; /*! -returns the i'th homogeneous coordinate of `v`. +returns the i-th homogeneous coordinate of `v`. \pre `0 <= i <= 2`. */ Kernel::RT homogeneous(int i) const; /*! -returns the i'th %Cartesian coordinate of `v`. +returns the i-th %Cartesian coordinate of `v`. \pre `0 <= i <= 1`. \cgalEpicExact diff --git a/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h b/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h index 84c61818845f..a4566f5b6bb2 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Vector_3.h @@ -141,13 +141,13 @@ Kernel::FT z() const; /// @{ /*! -returns the i'th homogeneous coordinate of `v`. +returns the i-th homogeneous coordinate of `v`. \pre `0 <= i <= 3`. */ Kernel::RT homogeneous(int i) const; /*! -returns the i'th %Cartesian coordinate of `v`. +returns the i-th %Cartesian coordinate of `v`. \pre `0 <= i <= 2` \cgalEpicExact diff --git a/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_2.h b/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_2.h index f06e42222d31..6cd66c28188e 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_2.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_2.h @@ -153,13 +153,13 @@ class Weighted_point_2 /// @{ /*! - returns the i'th homogeneous coordinate of `p`. + returns the i-th homogeneous coordinate of `p`. \pre `0 <= i <= 2` */ Kernel::RT homogeneous(int i) const; /*! - returns the i'th %Cartesian coordinate of `p`. + returns the i-th %Cartesian coordinate of `p`. \pre `0 <= i <= 1` \cgalEpicExact diff --git a/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_3.h b/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_3.h index 8a442b9fb8f1..b97f9d48fcf3 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Weighted_point_3.h @@ -165,13 +165,13 @@ class Weighted_point_3 /// @{ /*! - returns the i'th homogeneous coordinate of `p`. + returns the i-th homogeneous coordinate of `p`. \pre `0 <= i <= 3` */ Kernel::RT homogeneous(int i) const; /*! - returns the i'th %Cartesian coordinate of `p`. + returns the i-th %Cartesian coordinate of `p`. \pre `0 <= i <= 2` \cgalEpicExact diff --git a/Kernel_23/doc/Kernel_23/Concepts/FunctionObjectConcepts.h b/Kernel_23/doc/Kernel_23/Concepts/FunctionObjectConcepts.h index 7e4e66615233..0517937218b1 100644 --- a/Kernel_23/doc/Kernel_23/Concepts/FunctionObjectConcepts.h +++ b/Kernel_23/doc/Kernel_23/Concepts/FunctionObjectConcepts.h @@ -3983,7 +3983,7 @@ class ConstructCartesianConstIterator_2 { /*! - returns an iterator on the 0'th %Cartesian coordinate of `p`. + returns an iterator on the zeroth %Cartesian coordinate of `p`. */ Kernel::Cartesian_const_iterator_2 operator()(const Kernel::Point_2 &p); @@ -3995,7 +3995,7 @@ class ConstructCartesianConstIterator_2 { &p, int); /*! - returns an iterator on the 0'th %Cartesian coordinate of `v`. + returns an iterator on the zeroth %Cartesian coordinate of `v`. */ Kernel::Cartesian_const_iterator_2 operator()(const Kernel::Vector_2 &v); @@ -4028,7 +4028,7 @@ class ConstructCartesianConstIterator_3 { /// @{ /*! - returns an iterator on the 0'th %Cartesian coordinate of `p`. + returns an iterator on the zeroth %Cartesian coordinate of `p`. */ Kernel::Cartesian_const_iterator_3 operator()(const Kernel::Point_3 &p); @@ -4040,7 +4040,7 @@ class ConstructCartesianConstIterator_3 { &p, int); /*! - returns an iterator on the 0'th %Cartesian coordinate of `v`. + returns an iterator on the zeroth %Cartesian coordinate of `v`. */ Kernel::Cartesian_const_iterator_3 operator()(const Kernel::Vector_3 &v); @@ -7036,7 +7036,7 @@ class ConstructVector_2 { /*! introduces a null vector. */ - Kernel::Vector_2 operator()(const Null_vector &NULL_VECTOR); + Kernel::Vector_2 operator()(const CGAL::Null_vector &v); /// @} @@ -7095,7 +7095,7 @@ class ConstructVector_3 { /*! introduces a null vector. */ - Kernel::Vector_3 operator()(const Null_vector &NULL_VECTOR); + Kernel::Vector_3 operator()(const CGAL::Null_vector &v); /// @} @@ -7130,7 +7130,7 @@ class ConstructVertex_2 { &s, int i); /*! - returns the i'th vertex of + returns the i-th vertex of `r` in counterclockwise order, starting with the lower left vertex. The parameter `i` is taken modulo 4. */ @@ -7138,7 +7138,7 @@ class ConstructVertex_2 { Kernel::Iso_rectangle_2 &r, int i); /*! - returns the i'th vertex of `t`. The parameter + returns the i-th vertex of `t`. The parameter `i` is taken modulo 3. */ Kernel::Point_2 operator()(const Kernel::Triangle_2 @@ -7152,9 +7152,6 @@ class ConstructVertex_2 { \ingroup PkgKernel23ConceptsFunctionObjects \cgalConcept -\image html IsoCuboid.png -\image latex IsoCuboid.png - \cgalRefines{AdaptableBinaryFunction} \sa `CGAL::Iso_cuboid_3` @@ -7179,23 +7176,25 @@ class ConstructVertex_3 { &s, int i); /*! - returns the i'th vertex of + returns the i-th vertex of `c`, as indicated in the figure below. The parameter `i` is taken modulo 8. + \image html IsoCuboid.png + \image latex IsoCuboid.png */ Kernel::Point_3 operator()(const Kernel::Iso_cuboid_3 &c, int i); /*! - returns the i'th vertex of `t`. The parameter + returns the i-th vertex of `t`. The parameter `i` is taken modulo 3. */ Kernel::Point_3 operator()(const Kernel::Triangle_3 &t, int i); /*! - returns the i'th vertex of + returns the i-th vertex of `t`. The parameter `i` is taken modulo 4. */ Kernel::Point_3 operator()(const diff --git a/Kernel_d/doc/Kernel_d/CGAL/Epeck_d.h b/Kernel_d/doc/Kernel_d/CGAL/Epeck_d.h index 9ae8ff346fd9..69f98bcdcc4b 100644 --- a/Kernel_d/doc/Kernel_d/CGAL/Epeck_d.h +++ b/Kernel_d/doc/Kernel_d/CGAL/Epeck_d.h @@ -70,7 +70,7 @@ Point_d(double x0, double x1, ...); template Point_d(ForwardIterator first, ForwardIterator end); -/*! returns the i'th coordinate of a point. +/*! returns the i-th coordinate of a point. \pre `i` is non-negative and less than the dimension. */ double operator[](int i)const; diff --git a/Kernel_d/doc/Kernel_d/CGAL/Epick_d.h b/Kernel_d/doc/Kernel_d/CGAL/Epick_d.h index b5e67064e9bc..b8ae4ea6acfd 100644 --- a/Kernel_d/doc/Kernel_d/CGAL/Epick_d.h +++ b/Kernel_d/doc/Kernel_d/CGAL/Epick_d.h @@ -59,7 +59,7 @@ Point_d(double x0, double x1, ...); template Point_d(InputIterator first, InputIterator end); -/*! returns the i'th coordinate of a point. +/*! returns the i-th coordinate of a point. \pre `i` is non-negative and less than the dimension. */ double operator[](int i)const; diff --git a/Kernel_d/doc/Kernel_d/Concepts/Kernel--ConstructCartesianConstIterator_d.h b/Kernel_d/doc/Kernel_d/Concepts/Kernel--ConstructCartesianConstIterator_d.h index b2585622b90a..95997df7ae70 100644 --- a/Kernel_d/doc/Kernel_d/Concepts/Kernel--ConstructCartesianConstIterator_d.h +++ b/Kernel_d/doc/Kernel_d/Concepts/Kernel--ConstructCartesianConstIterator_d.h @@ -16,7 +16,7 @@ class Kernel_d::ConstructCartesianConstIterator_d { /// @{ /*! -returns an iterator on the 0'th %Cartesian coordinate of `p`. +returns an iterator on the zeroth %Cartesian coordinate of `p`. */ Kernel_d::Cartesian_const_iterator_d operator()(const Kernel_d::Point_d &p); diff --git a/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Surface_mesh.h b/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Surface_mesh.h index 295b1c8ba2b2..6315897ba22b 100644 --- a/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Surface_mesh.h +++ b/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Surface_mesh.h @@ -1031,14 +1031,14 @@ class Surface_mesh } - /// returns the \c i'th halfedge of edge \c e. \c i has to be 0 or 1. + /// returns the \c i-th halfedge of edge \c e. \c i has to be 0 or 1. Halfedge halfedge(Edge e, unsigned int i) const { assert(i<=1); return Halfedge((e.idx() << 1) + i); } - /// returns the \c i'th vertex of edge \c e. \c i has to be 0 or 1. + /// returns the \c i-th vertex of edge \c e. \c i has to be 0 or 1. Vertex vertex(Edge e, unsigned int i) const { assert(i<=1); diff --git a/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Vector.h b/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Vector.h index 21aa768dbfb4..0d20266d85b4 100644 --- a/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Vector.h +++ b/Linear_cell_complex/benchmark/Linear_cell_complex_2/surface_mesh/Vector.h @@ -122,14 +122,14 @@ class Vector return data_; } - /// get i'th element read-write + /// get i-th element read-write Scalar& operator[](unsigned int i) { assert(i - Node_index descendant(Node_index node, Indices... indices) { + Node_index descendant(Node_index node, Indices... indices) const { return recursive_descendant(node, indices...); } @@ -940,7 +940,7 @@ class Orthtree { \return the index of the specified node */ template - Node_index node(Indices... indices) { + Node_index node(Indices... indices) const { return descendant(root(), indices...); } @@ -1261,10 +1261,10 @@ class Orthtree { private: // functions : - Node_index recursive_descendant(Node_index node, std::size_t i) { return child(node, i); } + Node_index recursive_descendant(Node_index node, std::size_t i) const { return child(node, i); } template - Node_index recursive_descendant(Node_index node, std::size_t i, Indices... remaining_indices) { + Node_index recursive_descendant(Node_index node, std::size_t i, Indices... remaining_indices) const { return recursive_descendant(child(node, i), remaining_indices...); } @@ -1401,15 +1401,8 @@ class Orthtree { public: /// \cond SKIP_IN_MANUAL - void dump_to_polylines(std::ostream& os) const { - for (const auto n: traverse()) - if (is_leaf(n)) { - Bbox box = bbox(n); - dump_box_to_polylines(box, os); - } - } - - void dump_box_to_polylines(const Bbox_2& box, std::ostream& os) const { + template + void dump_box_to_polylines(const Iso_rectangle_2& box, std::ostream& os) const { // dump in 3D for visualisation os << "5 " << box.xmin() << " " << box.ymin() << " 0 " @@ -1419,7 +1412,8 @@ class Orthtree { << box.xmin() << " " << box.ymin() << " 0" << std::endl; } - void dump_box_to_polylines(const Bbox_3& box, std::ostream& os) const { + template + void dump_box_to_polylines(const Iso_cuboid_3& box, std::ostream& os) const { // Back face os << "5 " << box.xmin() << " " << box.ymin() << " " << box.zmin() << " " @@ -1451,6 +1445,14 @@ class Orthtree { << box.xmax() << " " << box.ymax() << " " << box.zmax() << std::endl; } + void dump_to_polylines(std::ostream& os) const { + for (Node_index n: traverse(Orthtrees::Preorder_traversal(*this))) + if (is_leaf(n)) { + Bbox box = bbox(n); + dump_box_to_polylines(box, os); + } + } + std::string to_string(Node_index node) { std::stringstream stream; internal::print_orthtree_node(stream, node, *this); diff --git a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt index e532de3e7288..f61081efece7 100644 --- a/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt +++ b/Polygon_mesh_processing/doc/Polygon_mesh_processing/Polygon_mesh_processing.txt @@ -125,7 +125,7 @@ provided: a uniform and a curvature-adaptive sizing field. With `CGAL::Polygon_m all triangle edges are targeted to have equal lengths. With `CGAL::Polygon_mesh_processing::Adaptive_sizing_field`, triangle edge lengths depend on the local curvature -- shorter edges appear in regions with a higher curvature and vice versa. The outline of the adaptive sizing field algorithm is available in \cgalCite{dunyach2013curvRemesh}. The distinction between uniform and adaptive -sizing fields is depicted in figure \cgalFigureRef{uniform_and_adaptive}. +sizing fields is depicted in \cgalFigureRef{uniform_and_adaptive}. As the number of iterations increases, the mesh tends to be smoother and closer to the target edge length. @@ -602,7 +602,7 @@ reports all pairs of intersecting triangles. \subsubsection SIExample Self Intersections Example The following example illustrates the detection of self intersection in the `pig.off` mesh. -The detected self-intersection is illustrated on Figure \cgalFigureRef{SelfIntersections}. +The detected self-intersection is illustrated on \cgalFigureRef{SelfIntersections}. \cgalExample{Polygon_mesh_processing/self_intersections_example.cpp} diff --git a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/tangential_relaxation.h b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/tangential_relaxation.h index 145d5a24135f..a1604092424d 100644 --- a/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/tangential_relaxation.h +++ b/Polygon_mesh_processing/include/CGAL/Polygon_mesh_processing/tangential_relaxation.h @@ -112,7 +112,7 @@ struct Allow_all_moves{ * \cgalParamNBegin{allow_move_functor} * \cgalParamDescription{A function object used to determinate if a vertex move should be allowed or not} * \cgalParamType{Unary functor that provides `bool operator()(vertex_descriptor v, Point_3 src, Point_3 tgt)` returning `true` -* if the vertex `v` can be moved from `src` to `tgt`; `Point_3` being the value type of the vertex point map } +* if the vertex `v` can be moved from `src` to `tgt`; `%Point_3` being the value type of the vertex point map } * \cgalParamDefault{If not provided, all moves are allowed.} * \cgalParamNEnd * diff --git a/Polygon_mesh_processing/include/CGAL/Polyhedral_envelope.h b/Polygon_mesh_processing/include/CGAL/Polyhedral_envelope.h index 7a377a935c94..0a4d2441570f 100644 --- a/Polygon_mesh_processing/include/CGAL/Polyhedral_envelope.h +++ b/Polygon_mesh_processing/include/CGAL/Polyhedral_envelope.h @@ -176,7 +176,7 @@ struct Polyhedral_envelope { std::array triangle; std::array etriangle; ePlane_3 eplane; - std::array elines; // the i'th line is opposite to vertex i + std::array elines; // the i-th line is opposite to vertex i ePoint_3 n; // triangle[0] offsetted by the triangle normal }; diff --git a/Polygon_mesh_processing/include/CGAL/Side_of_triangle_mesh.h b/Polygon_mesh_processing/include/CGAL/Side_of_triangle_mesh.h index eff54edea7c2..d59eacba60e6 100644 --- a/Polygon_mesh_processing/include/CGAL/Side_of_triangle_mesh.h +++ b/Polygon_mesh_processing/include/CGAL/Side_of_triangle_mesh.h @@ -141,7 +141,6 @@ class Side_of_triangle_mesh #endif { CGAL_assertion(CGAL::is_triangle_mesh(tmesh)); - CGAL_assertion(CGAL::is_closed(tmesh)); box = Polygon_mesh_processing::bbox(tmesh, parameters::vertex_point_map(vpmap)); } @@ -171,6 +170,7 @@ class Side_of_triangle_mesh const GeomTraits& gt = GeomTraits()) : ray_functor(gt.construct_ray_3_object()) , vector_functor(gt.construct_vector_3_object()) + , tm_ptr(nullptr) , own_tree(false) #ifdef CGAL_HAS_THREADS , atomic_tree_ptr(&tree) @@ -182,9 +182,9 @@ class Side_of_triangle_mesh } /** - * Constructor moving an instance of Side_of_triangle_mesh to a new memory + * Constructor moving an instance of `Side_of_triangle_mesh` to a new memory * location with minimal memory copy. - * @param other The instance to be moved + * @param other the instance to be moved */ Side_of_triangle_mesh(Side_of_triangle_mesh&& other) { @@ -202,10 +202,10 @@ class Side_of_triangle_mesh public: /** - * Assign operator moving an instance of Side_of_triangle_mesh to this + * Assign operator moving an instance of `Side_of_triangle_mesh` to this * location with minimal memory copy. - * @param other The instance to be moved - * @return A reference to this + * @param other the instance to be moved + * @return a reference to `this` */ Side_of_triangle_mesh& operator=(Side_of_triangle_mesh&& other) { @@ -233,6 +233,8 @@ class Side_of_triangle_mesh */ Bounded_side operator()(const Point& point) const { + CGAL_assertion(tm_ptr == nullptr || CGAL::is_closed(*tm_ptr)); + if(point.x() < box.xmin() || point.x() > box.xmax() || point.y() < box.ymin() @@ -276,6 +278,8 @@ class Side_of_triangle_mesh template Bounded_side operator()(const typename K2::Point_3& point, const K2& k2) const { + CGAL_assertion(tm_ptr == nullptr || CGAL::is_closed(*tm_ptr)); + if(point.x() < box.xmin() || point.x() > box.xmax() || point.y() < box.ymin() diff --git a/Property_map/include/CGAL/property_map.h b/Property_map/include/CGAL/property_map.h index 1154ab87b42a..e9868ef6e076 100644 --- a/Property_map/include/CGAL/property_map.h +++ b/Property_map/include/CGAL/property_map.h @@ -725,7 +725,7 @@ make_cartesian_converter_property_map(Vpm vpm) /// \ingroup PkgPropertyMapRef /// A property map with `std::size_t` as key-type that can be used -/// to access the i'th element in a container with random access. +/// to access the i-th element in a container with random access. /// \cgalModels{LvaluePropertyMap constness being than of `Container`.} template class Random_access_property_map diff --git a/SMDS_3/doc/SMDS_3/SMDS_3.txt b/SMDS_3/doc/SMDS_3/SMDS_3.txt index 7fcd35d73366..dff08599e10e 100644 --- a/SMDS_3/doc/SMDS_3/SMDS_3.txt +++ b/SMDS_3/doc/SMDS_3/SMDS_3.txt @@ -34,7 +34,7 @@ of the support 3D triangulation, per dimension from 0 to 3: - corner vertices (0D), - feature edges (1D), - surface facets (2D), -- domain cells (3D). See Figure \cgalFigureRef{figure_c3t3_cells}. +- domain cells (3D). See \cgalFigureRef{figure_c3t3_cells}. The concept `MeshComplex_3InTriangulation_3` is a data structure devised to represent these three-dimensional complexes embedded in a `Triangulation_3`. diff --git a/STL_Extension/include/CGAL/Container_helper.h b/STL_Extension/include/CGAL/Container_helper.h index 18a98dfa456b..9c12ed2a8d8c 100644 --- a/STL_Extension/include/CGAL/Container_helper.h +++ b/STL_Extension/include/CGAL/Container_helper.h @@ -18,8 +18,9 @@ #include #include +#include #include -#include +#include namespace CGAL { namespace internal { @@ -29,24 +30,24 @@ CGAL_GENERATE_MEMBER_DETECTOR(resize); // Typical container template -void resize(Container& c, std::size_t size, +void resize(Container& c, + const std::size_t size, std::enable_if_t::value>* = nullptr) { c.resize(size); } -// Container without a resize() function, but with a size() function (e.g. an array) -template -void resize(Container& CGAL_assertion_code(array), std::size_t CGAL_assertion_code(size), - std::enable_if_t::value && - has_size::value >* = nullptr) +template +void resize(const std::array& CGAL_assertion_code(array), + const std::size_t CGAL_assertion_code(size)) { CGAL_assertion(array.size() == size); } // A class with neither resize() nor size(), can't enforce size (it better be correct!) template -void resize(Container&, std::size_t, +void resize(const Container&, + const std::size_t, std::enable_if_t< !(has_resize::value || has_size::value)>* = nullptr) @@ -59,7 +60,8 @@ CGAL_GENERATE_MEMBER_DETECTOR(reserve); // Container with 'reserve' template -void reserve(Container& c, std::size_t size, +void reserve(Container& c, + const std::size_t size, std::enable_if_t::value>* = nullptr) { c.reserve(size); @@ -67,7 +69,8 @@ void reserve(Container& c, std::size_t size, // Container with no 'reserve' template -void reserve(Container&, std::size_t, +void reserve(const Container&, + const std::size_t, std::enable_if_t::value>* = nullptr) { } diff --git a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h index 9b172f5b4c16..f4d9cb5f3742 100644 --- a/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h +++ b/STL_Extension/include/CGAL/STL_Extension/internal/parameters_interface.h @@ -239,6 +239,10 @@ CGAL_add_named_parameter(optimize_anchor_location_t, optimize_anchor_location, o CGAL_add_named_parameter(optimize_boundary_anchor_location_t, optimize_boundary_anchor_location, optimize_boundary_anchor_location) CGAL_add_named_parameter(pca_plane_t, pca_plane, pca_plane) +// List of named parameters used in Isosurfacing_3 +CGAL_add_named_parameter(use_topologically_correct_marching_cubes_t, use_topologically_correct_marching_cubes, use_topologically_correct_marching_cubes) +CGAL_add_named_parameter(constrain_to_cell_t, constrain_to_cell, constrain_to_cell) + // tetrahedral remeshing parameters CGAL_add_named_parameter(remesh_boundaries_t, remesh_boundaries, remesh_boundaries) CGAL_add_named_parameter(cell_selector_t, cell_selector, cell_is_selected_map) diff --git a/Shape_detection/doc/Shape_detection/Shape_detection.txt b/Shape_detection/doc/Shape_detection/Shape_detection.txt index c51a84aad197..d58d86eeaccb 100644 --- a/Shape_detection/doc/Shape_detection/Shape_detection.txt +++ b/Shape_detection/doc/Shape_detection/Shape_detection.txt @@ -19,7 +19,7 @@ This \cgal component implements two algorithms for shape detection: \section Shape_detection_RANSAC Efficient RANSAC -From an unstructured point set with unoriented normals, this algorithm detects a set of shapes (see Figure \cgalFigureRef{Efficient_RANSAC_overview}). +From an unstructured point set with unoriented normals, this algorithm detects a set of shapes (see \cgalFigureRef{Efficient_RANSAC_overview}). Five types of primitive shapes are provided by this package: plane, sphere, cylinder, cone, and torus. Other primitive shapes can be easily added by the user (see Section \ref Shape_detection_RANSACExample_with_custom_shapes). @@ -67,7 +67,7 @@ A high value of `epsilon` leads to the detection of fewer large shapes and hence A low value of `epsilon` yields a more detailed detection, but may lead to either lower coverage or over-segmentation. Over-segmentation translates into detection of fragmented shapes when `epsilon` is within or below the noise level. When the input point set is made of free-form parts, a higher tolerance `epsilon` enables to detect more primitive shapes that approximate some of the free-form surfaces. -The impact of this parameter is depicted by Figure \cgalFigureRef{Efficient_RANSAC_parameter_epsilon_variation}. Its impact on performance is evaluated in Section \ref Shape_detection_RANSACPerformance. +The impact of this parameter is depicted by \cgalFigureRef{Efficient_RANSAC_parameter_epsilon_variation}. Its impact on performance is evaluated in Section \ref Shape_detection_RANSACPerformance. \cgalFigureBegin{Efficient_RANSAC_parameter_epsilon_variation, Efficient_RANSAC/epsilon_variation2.png} Impact of the epsilon parameter over the levels of detail of the detection. @@ -84,7 +84,7 @@ the points covered by a shape are mapped to a 2D parameter space chosen to minim This 2D parameter space is discretized using a regular grid, and a connected component search is performed to identify the largest cluster. The parameter `cluster_epsilon` defines the spacing between two cells of the regular grid, so that two points separated by a distance of at most \f$2\sqrt{2}\f$ `cluster_epsilon` are considered adjacent. For non-developable shapes, the connected components are identified by computing a neighboring graph in 3D and walking in the graph. -The impact of the parameter `cluster_epsilon` is depicted in Figure \cgalFigureRef{Efficient_RANSAC_parameter_connectivity}. +The impact of the parameter `cluster_epsilon` is depicted in \cgalFigureRef{Efficient_RANSAC_parameter_connectivity}. \cgalFigureBegin{Efficient_RANSAC_parameter_connectivity, Efficient_RANSAC/varying_connectivity.png} The parameter `cluster_epsilon` controls the connectivity of the points covered by a detected shape. @@ -207,10 +207,10 @@ which is used by the example \ref Shape_detection/efficient_RANSAC_with_custom_s The running time and detection performance of the Efficient RANSAC depend on the chosen parameters. A selective error tolerance parameter leads to higher running time and fewer shapes, as many shape candidates are generated to find the largest shape. -We plot the detection performance against the `epsilon` error tolerance parameter for detecting planes in a complex scene with 5M points (see Figure \cgalFigureRef{Efficient_RANSAC_performance_epsilon}). +We plot the detection performance against the `epsilon` error tolerance parameter for detecting planes in a complex scene with 5M points (see \cgalFigureRef{Efficient_RANSAC_performance_epsilon}). The `probability` parameter controls the endurance when searching for the largest candidate at each iteration. It barely impacts the number of detected shapes, has a moderate impact on the size of the detected shapes, and increases the running time. -We plot the performance against the `probability` parameter (see Figure \cgalFigureRef{Efficient_RANSAC_performance_probability}). +We plot the performance against the `probability` parameter (see \cgalFigureRef{Efficient_RANSAC_performance_probability}). \cgalFigureBegin{Efficient_RANSAC_performance_epsilon, Efficient_RANSAC/epsilon_graph.png} The graph depicts the number of detected shapes (purple) and the coverage (green), that is the ratio assignedPoints / totalPoints, against the `epsilon` tolerance parameter. @@ -358,7 +358,7 @@ The right choice of the parameters above plays an important role in producing a For example, if we consider the fuzzy sphere neighborhood, when `sphere_radius` is too large, we have fewer regions, and the details are not clearly separated. Meanwhile, if `sphere_radius` is too small, we produce more regions, and the point set may be over-segmented. Consider a 2D map of an intersection of streets in a city as -in Figure \cgalFigureRef{Region_growing_parameter_sphere_radius_variation}. Each region is painted with +in \cgalFigureRef{Region_growing_parameter_sphere_radius_variation}. Each region is painted with a unique color. As `sphere_radius` increases, the details become less clear. When `sphere_radius` = 0.3 (c), the best visual result is produced. @@ -379,7 +379,7 @@ The first two parameters are used by the functions `RegionType::is_part_of_regio while the third parameter is used by the function `RegionType::is_valid_region()` from the `RegionType` concept. The right choice of `maximum_distance` and `maximum_angle` parameters is also very important. -For example, Figure \cgalFigureRef{Region_growing_parameter_max_angle_variation} shows that the +For example, \cgalFigureRef{Region_growing_parameter_max_angle_variation} shows that the roof top of the house can be distinguished as two planes (painted in blue and dark red) when `maximum_angle` is strict enough (c), or it can be recognized as only one plane (painted in pale yellow) in the other case (b). @@ -403,7 +403,7 @@ Typical usage of the Region Growing component consists of five steps: -# Postprocess. Given a 2D point set, we detect 2D lines using the fuzzy sphere neighborhood. We then color all points -from the found regions and save them in a file (see Figure \cgalFigureRef{Region_growing_on_point_set_2}). +from the found regions and save them in a file (see \cgalFigureRef{Region_growing_on_point_set_2}). A point set with normals is stored in `std::vector`. \cgalExample{Shape_detection/region_growing_lines_on_point_set_2.cpp} @@ -417,7 +417,7 @@ The following example shows a similar example, this time detecting circles inste \paragraph Shape_detection_RegionGrowingPoints_example_3D_planes Detecting 3D Planes If we are given a 3D point set, then the example below shows how to detect 3D planes using the K nearest neighbors search. -We color all points from the found regions and save them in a file (see Figure \cgalFigureRef{Region_growing_on_point_set_3}). +We color all points from the found regions and save them in a file (see \cgalFigureRef{Region_growing_on_point_set_3}). The example also shows how to retrieve all points, which are not assigned to any region, and how to use a custom output iterator. A point set with normals is stored in `CGAL::Point_set_3`. @@ -484,7 +484,7 @@ In particular, it provides - `CGAL::Shape_detection::Polygon_mesh::Least_squares_plane_fit_region` class that fits a 3D plane to the vertices of all mesh faces, which have been added to the region so far, and controls the quality of this fit. -This component accepts any model of the concept `FaceListGraph` as a polygon mesh. Figure \cgalFigureRef{Region_growing_on_surface_mesh} +This component accepts any model of the concept `FaceListGraph` as a polygon mesh. \cgalFigureRef{Region_growing_on_surface_mesh} gives an \ref Shape_detection_RegionGrowingMesh_examples "example" of the region growing algorithm for detecting 3D planes on `CGAL::Surface_mesh`. @@ -573,7 +573,7 @@ The Efficient RANSAC algorithm is very quick, however, since it is not determini Instead, the region growing algorithm usually takes longer to complete, but it may provide better quality output in the presence of large scenes with numerous small details. Since it iterates throughout all items of the scene, there are fewer chances to miss a shape. In addition, it is deterministic (for a given input and a given set of parameters, it always returns the same output, -whereas the Efficient RANSAC algorithm is randomized and so the output may vary at each run), see Figure \cgalFigureRef{Shape_detection_comparison}. +whereas the Efficient RANSAC algorithm is randomized and so the output may vary at each run), see \cgalFigureRef{Shape_detection_comparison}. \cgalFigureBegin{Shape_detection_comparison, Efficient_RANSAC/comparison.png} Comparison of the Efficient RANSAC and region growing algorithms. diff --git a/Solver_interface/doc/Solver_interface/Concepts/SvdTraits.h b/Solver_interface/doc/Solver_interface/Concepts/SvdTraits.h index dfad56236b81..b22173cb08b4 100644 --- a/Solver_interface/doc/Solver_interface/Concepts/SvdTraits.h +++ b/Solver_interface/doc/Solver_interface/Concepts/SvdTraits.h @@ -71,12 +71,12 @@ class SvdTraits::Vector size_t size(); /*! - Return the `i`th entry, `i` from `0` to `size()-1`. + Return the `i`-th entry, `i` from `0` to `size()-1`. */ FT operator()(size_t i); /*! - Set the `i`'th entry to `value`. + Set the `i`-th entry to `value`. */ void set(size_t i, const FT value); diff --git a/Spatial_searching/benchmark/Spatial_searching/include/nanoflann.hpp b/Spatial_searching/benchmark/Spatial_searching/include/nanoflann.hpp index 3dd0b3998928..69d372a53d66 100644 --- a/Spatial_searching/benchmark/Spatial_searching/include/nanoflann.hpp +++ b/Spatial_searching/benchmark/Spatial_searching/include/nanoflann.hpp @@ -712,7 +712,7 @@ namespace nanoflann * // Must return the Euclidean (L2) distance between the vector "p1[0:size-1]" and the data point with index "idx_p2" stored in the class: * inline DistanceType kdtree_distance(const T *p1, const size_t idx_p2,size_t size) const { ... } * - * // Must return the dim'th component of the idx'th point in the class: + * // Must return the dim-th component of the idx-th point in the class: * inline T kdtree_get_pt(const size_t idx, int dim) const { ... } * * // Optional bounding-box computation: return false to default to a standard bbox computation loop. @@ -1470,7 +1470,7 @@ namespace nanoflann return s; } - // Returns the dim'th component of the idx'th point in the class: + // Returns the dim-th component of the idx-th point in the class: inline num_t kdtree_get_pt(const size_t idx, int dim) const { return m_data_matrix.coeff(idx,dim); } diff --git a/Spatial_searching/benchmark/Spatial_searching/nanoflan.cpp b/Spatial_searching/benchmark/Spatial_searching/nanoflan.cpp index db5f5c4a94da..ebc93192bf84 100644 --- a/Spatial_searching/benchmark/Spatial_searching/nanoflan.cpp +++ b/Spatial_searching/benchmark/Spatial_searching/nanoflan.cpp @@ -47,7 +47,7 @@ struct PointCloud return d0*d0+d1*d1+d2*d2; } - // Returns the dim'th component of the idx'th point in the class: + // Returns the dim-th component of the idx-th point in the class: // Since this is inlined and the "dim" argument is typically an immediate value, the // "if/else's" are actually solved at compile time. inline T kdtree_get_pt(const size_t idx, int dim) const diff --git a/Spatial_searching/benchmark/Spatial_searching/nn3nanoflan.cpp b/Spatial_searching/benchmark/Spatial_searching/nn3nanoflan.cpp index 8822bae99927..abcf23f739c2 100644 --- a/Spatial_searching/benchmark/Spatial_searching/nn3nanoflan.cpp +++ b/Spatial_searching/benchmark/Spatial_searching/nn3nanoflan.cpp @@ -51,7 +51,7 @@ struct PointCloud return d0*d0+d1*d1+d2*d2; } - // Returns the dim'th component of the idx'th point in the class: + // Returns the dim-th component of the idx-th point in the class: // Since this is inlined and the "dim" argument is typically an immediate value, the // "if/else's" are actually solved at compile time. inline T kdtree_get_pt(const size_t idx, int dim) const diff --git a/Spatial_searching/doc/Spatial_searching/CGAL/Plane_separator.h b/Spatial_searching/doc/Spatial_searching/CGAL/Plane_separator.h index e206f669fc0a..a0fa65e7a71e 100644 --- a/Spatial_searching/doc/Spatial_searching/CGAL/Plane_separator.h +++ b/Spatial_searching/doc/Spatial_searching/CGAL/Plane_separator.h @@ -7,7 +7,7 @@ The class `Plane_separator` implements a plane separator, i.e., a hyperplane that is used to separate two half spaces. This hyperplane is defined by a cutting dimension `d` and a cutting -value `v` as `xd=v`, where `v` denotes the `d`th coordinate value. +value `v` as `xd=v`, where `v` denotes the `d`-th coordinate value. \cgalModels{SpatialSeparator} @@ -21,7 +21,7 @@ class Plane_separator { /*! Constructs a separator that separates two half spaces by a hyperplane -defined by `xd=v`, where `v` denotes the `d`th coordinate value. +defined by `xd=v`, where `v` denotes the `d`-th coordinate value. */ Plane_separator(int d, FT v); diff --git a/Spatial_searching/doc/Spatial_searching/Spatial_searching.txt b/Spatial_searching/doc/Spatial_searching/Spatial_searching.txt index b6f061060699..f92b88f171cb 100644 --- a/Spatial_searching/doc/Spatial_searching/Spatial_searching.txt +++ b/Spatial_searching/doc/Spatial_searching/Spatial_searching.txt @@ -62,7 +62,7 @@ computation has to be re-invoked for a larger number of neighbors, thereby performing redundant computations. Therefore, Hjaltason and Samet \cgalCite{hs-rsd-95} introduced incremental nearest neighbor searching in the sense that having obtained the `k` nearest -neighbors, the `k + 1`th neighbor can be obtained without having +neighbors, the `k + 1`-th neighbor can be obtained without having to calculate the `k + 1` nearest neighbor from scratch. Spatial searching typically consists of a preprocessing phase and a @@ -85,7 +85,7 @@ neighbor exactly, one of these objects may be returned as the approximate nearest/furthest neighbor. I.e., given some non-negative constant \f$ \epsilon\f$ the distance of an object returned as an approximate `k`-nearest neighbor must not be larger than -\f$ (1+\epsilon)r\f$, where \f$ r\f$ denotes the distance to the real `k`th +\f$ (1+\epsilon)r\f$, where \f$ r\f$ denotes the distance to the real `k`-th nearest neighbor. Similar the distance of an approximate `k`-furthest neighbor must not be smaller than \f$ r/(1+\epsilon)\f$. Obviously, for \f$ \epsilon=0\f$ we get the exact result, and the larger \f$ \epsilon\f$ is, @@ -495,13 +495,13 @@ rectangles. Data points are only stored in the leaf nodes of the tree, not in the internal nodes. Friedmann, Bentley and Finkel \cgalCite{fbf-afbml-77} described the -standard search algorithm to find the `k`th nearest neighbor by +standard search algorithm to find the `k`-th nearest neighbor by searching a `kd` tree recursively. When encountering a node of the tree, the algorithm first visits the child that is closest to the query point. On return, if the rectangle containing the other child lies within 1/ (1+\f$ \epsilon\f$) times the -distance to the `k`th nearest neighbors so far, then the other child +distance to the `k`-th nearest neighbors so far, then the other child is visited recursively. Priority search \cgalCite{am-annqf-93} visits the nodes in increasing order of distance from the queue with help of a priority queue. The search stops when the distance of the query point diff --git a/Surface_mesh/doc/Surface_mesh/Surface_mesh.txt b/Surface_mesh/doc/Surface_mesh/Surface_mesh.txt index b7be50a395fc..670449c2ee65 100644 --- a/Surface_mesh/doc/Surface_mesh/Surface_mesh.txt +++ b/Surface_mesh/doc/Surface_mesh/Surface_mesh.txt @@ -408,7 +408,7 @@ of points to OpenGL. We use a \em freelist for removed elements. This mean when a vertex gets removed and later `add_vertex` is called, the memory of the removed element is reused. -This especially means that the n'th inserted element has not necessarily the index `n-1`, +This especially means that the n-th inserted element has not necessarily the index `n-1`, and when iterating over elements they will not be enumerated in the insertion order. diff --git a/Surface_mesh/include/CGAL/Surface_mesh/Properties.h b/Surface_mesh/include/CGAL/Surface_mesh/Properties.h index a81dd63d530a..bed414b80b18 100644 --- a/Surface_mesh/include/CGAL/Surface_mesh/Properties.h +++ b/Surface_mesh/include/CGAL/Surface_mesh/Properties.h @@ -187,14 +187,14 @@ class Property_array : public Base_property_array return &data_[0]; } - /// Access the i'th element. No range check is performed! + /// Access the i-th element. No range check is performed! reference operator[](std::size_t _idx) { CGAL_assertion( _idx < data_.size() ); return data_[_idx]; } - /// Const access to the i'th element. No range check is performed! + /// Const access to the i-th element. No range check is performed! const_reference operator[](std::size_t _idx) const { CGAL_assertion( _idx < data_.size()); diff --git a/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h b/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h index 559aec265f09..ef1db14fb003 100644 --- a/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h +++ b/Surface_mesh/include/CGAL/Surface_mesh/Surface_mesh.h @@ -1827,7 +1827,7 @@ class Surface_mesh return opposite(prev(h)); } - /// returns the i'th vertex of edge `e`, for `i=0` or `1`. + /// returns the i-th vertex of edge `e`, for `i=0` or `1`. Vertex_index vertex(Edge_index e, unsigned int i) const { CGAL_assertion(i<=1); @@ -1856,7 +1856,7 @@ class Surface_mesh return Halfedge_index(e.halfedge()); } - /// returns the i'th halfedge of edge `e`, for `i=0` or `1`. + /// returns the i-th halfedge of edge `e`, for `i=0` or `1`. Halfedge_index halfedge(Edge_index e, unsigned int i) const { CGAL_assertion(i<=1); diff --git a/Surface_mesh_approximation/doc/Surface_mesh_approximation/Surface_mesh_approximation.txt b/Surface_mesh_approximation/doc/Surface_mesh_approximation/Surface_mesh_approximation.txt index e2ea8e5b1b51..e7ff50e17cf0 100644 --- a/Surface_mesh_approximation/doc/Surface_mesh_approximation/Surface_mesh_approximation.txt +++ b/Surface_mesh_approximation/doc/Surface_mesh_approximation/Surface_mesh_approximation.txt @@ -29,7 +29,7 @@ A class interface is also provided for advanced users, in which a series of plia \section sma_overview Overview -The package contains 3 main components: approximation algorithm, pliant operators and meshing as shown in Figure \cgalFigureRef{workflow}. +The package contains 3 main components: approximation algorithm, pliant operators and meshing as shown in \cgalFigureRef{workflow}. \cgalFigureBegin{workflow, workflow.svg} From left to right are 3 components of the approximation package: approximation algorithm (left), optional pliant operations (middle) and meshing (right). @@ -37,11 +37,11 @@ From left to right are 3 components of the approximation package: approximation \subsection sma_approximation Approximation -The left part of Figure \cgalFigureRef{workflow} depicts the workflow of the approximation algorithm. +The left part of \cgalFigureRef{workflow} depicts the workflow of the approximation algorithm. \subsubsection sma_clustering Clustering Iteration -Figure \cgalFigureRef{iterations} depicts several Lloyd \cgalCite{cgal:l-lsqp-82} clustering iterations on the plane-sphere model with planar proxies and the \f$ \mathcal{L}^{2,1} \f$ metric. We plot the fitting error against each iteration. After 8 iterations, the error barely changes. Based on this observation, we consider that the clustering converges if the error change between the current and previous iteration is lower than a user-specified threshold (indicated by two green dash lines). +\cgalFigureRef{iterations} depicts several Lloyd \cgalCite{cgal:l-lsqp-82} clustering iterations on the plane-sphere model with planar proxies and the \f$ \mathcal{L}^{2,1} \f$ metric. We plot the fitting error against each iteration. After 8 iterations, the error barely changes. Based on this observation, we consider that the clustering converges if the error change between the current and previous iteration is lower than a user-specified threshold (indicated by two green dash lines). \cgalFigureBegin{iterations, iterations.jpg} Discrete Lloyd iterations on the plane-sphere model with planar proxies and the \f$ \mathcal{L}^{2,1} \f$ metric: (left) random seeding of 6 proxies; (center) after one iteration; (right) after 8 iterations, the regions settle. The red lines depict the proxy normals drawn at the seed faces. @@ -66,7 +66,7 @@ and the remaining error is added to the next proxy error in order to keep the to \f[ E'_{k+1} = (E_k - N_k * E_{avg}) + E_{k+1}. \f] -Figure \cgalFigureRef{seeding_method} depicts different seeding methods. Random initialization randomly selects a set of input triangle faces as proxy seeds. While it is very fast, the subsequent clustering process can be entrapped in a bad local minimum, especially on shapes with regions surrounded by sharp creases (left closeup). Incremental initialization adds the proxies one by one at the most distorted region. It can thus be slow due to the large number of interleaved relaxation iterations. Hierarchical initialization (selected by default) repeatedly doubles the number of proxies in a hierarchical refinement sequence, so as to generate clustering regions with evenly distributed fitting errors. Time consumption is typically in-between the former two. Statistics and comparisons are available in Section \ref sma_perf. +\cgalFigureRef{seeding_method} depicts different seeding methods. Random initialization randomly selects a set of input triangle faces as proxy seeds. While it is very fast, the subsequent clustering process can be entrapped in a bad local minimum, especially on shapes with regions surrounded by sharp creases (left closeup). Incremental initialization adds the proxies one by one at the most distorted region. It can thus be slow due to the large number of interleaved relaxation iterations. Hierarchical initialization (selected by default) repeatedly doubles the number of proxies in a hierarchical refinement sequence, so as to generate clustering regions with evenly distributed fitting errors. Time consumption is typically in-between the former two. Statistics and comparisons are available in Section \ref sma_perf. \cgalFigureBegin{seeding_method, seeding_method.jpg} Different seeding methods on the sphere-cube model. From left to right: initial partition (\f$ \mathcal{L}^{2,1} \f$ metrics and 20 proxies), add 5 proxy seeds (red faces) with random, incremental and hierarchical methods respectively. @@ -78,7 +78,7 @@ Different seeding methods on the sphere-cube model. From left to right: initial To determine when to stop adding more proxies, we can specify either the maximum number of proxies required to approximate the geometry or the minimum error drop percentage with respect to the very first partition. More specifically, we can decide: - Maximum number of proxies. Adding proxies until the specified number is met. - Minimum error drop. Starting from the very first partition (with one proxy per connected component) with fitting error \f$ \hat E \f$, the algorithm adds proxies until the approximation error drops below the specified percentage \f$ target\_drop * \hat E \f$. -As depicted by Figure \cgalFigureRef{nb_proxies}, specifying a minimum error drop of 10% (yellow dash lines) as stopping criterion, yields 12 proxies on the plane-sphere model. When both criteria are provided, the first criterion met stops the seeding. Different seeding examples are depicted by Figure \cgalFigureRef{meshing}. +As depicted by \cgalFigureRef{nb_proxies}, specifying a minimum error drop of 10% (yellow dash lines) as stopping criterion, yields 12 proxies on the plane-sphere model. When both criteria are provided, the first criterion met stops the seeding. Different seeding examples are depicted by \cgalFigureRef{meshing}. To balance between performance and speed, we recommend using hierarchical seeding and specifying both stopping criteria. @@ -92,14 +92,14 @@ Using different number of proxies to approximate the plane-sphere model. From le For interactive use, the approach can yield better approximations of the geometry via adding/removing proxies and tunneling out of local minima via additional operations: - Merging. Merging two adjacent regions. - Splitting. Splitting one specified region into smaller ones to reduce the error. By default bisection is performed but N-section is also possible. We first select the request number of face seeds from the specified region, then perform the refitting process confined to the region. -- Adding. Adding one or more proxies to further reduce the approximation error. As for the seeding process, addition can be performed incrementally or hierarchically, as shown in Figure \cgalFigureRef{seeding_method}. +- Adding. Adding one or more proxies to further reduce the approximation error. As for the seeding process, addition can be performed incrementally or hierarchically, as shown in \cgalFigureRef{seeding_method}. - Teleporting. A teleportation operator is a combination of merging and adding proxies: merging the pair of adjacent regions and adding a proxy seed to the worst region. More specifically, the pair of regions whose merging realizes the smallest error after merging and local re-fitting, is selected for merging. In practice, the teleport operation can temporarily either decrease or increase the total approximation error. We provide an optional heuristic to evaluate if the teleportation is worth it by verifying whether the error increase induced by a (simulated) deletion is smaller than half of the error of the worst region. If this test fails, no teleportation is judged necessary. \cgalFigureBegin{operations, operations.jpg} Operations on the sphere-cube union model. Upper row: merging (9 proxies) and the reverse bisection splitting (10 partitions) on the confined region. Lower row: one teleportation operation of merging and adding a face seed, the sphere is approximated with one more proxy. \cgalFigureEnd -As depicted in Figure \cgalFigureRef{operations}, teleportation provides a means to relocate a local minimum region entrapped in the planar part (left) to the most needed regions on the sphere (right). In \ref sma_example3, the class interface is used to control the approximation process through the aforementioned operations. +As depicted in \cgalFigureRef{operations}, teleportation provides a means to relocate a local minimum region entrapped in the planar part (left) to the most needed regions on the sphere (right). In \ref sma_example3, the class interface is used to control the approximation process through the aforementioned operations. \subsection sma_meshing Meshing @@ -115,9 +115,9 @@ A vertex is selected as a basic anchor if it is: \subsubsection sma_anchors_subdivision Subdivision Anchors -Walking along the boundary of a proxy region (counterclockwise), a chord is a sequence of halfedges connecting two anchors. One cluster boundary cycle may consist of several chords. A connected region with holes may yield several boundary cycles (Figure \cgalFigureRef{operations}, planar part before teleportation). +Walking along the boundary of a proxy region (counterclockwise), a chord is a sequence of halfedges connecting two anchors. One cluster boundary cycle may consist of several chords. A connected region with holes may yield several boundary cycles (\cgalFigureRef{operations}, planar part before teleportation). -In order to approximate complex boundaries well, more anchors are generated by recursive chord subdivision (Figure \cgalFigureRef{chord}). An anchor \f$ \mathbf{c} \f$ is added at the furthest vertex of a chord \f$ (\mathbf{a}, \mathbf{b}) \f$, split it into \f$ (\mathbf{a}, \mathbf{c}) \f$ and \f$ (\mathbf{c}, \mathbf{b}) \f$, if the distance \f$ d = \Vert \mathbf{c} , (\mathbf{a}, \mathbf{b}) \Vert \f$ is beyond certain threshold. To make \f$ d \f$ independent to the scale of the input: +In order to approximate complex boundaries well, more anchors are generated by recursive chord subdivision (\cgalFigureRef{chord}). An anchor \f$ \mathbf{c} \f$ is added at the furthest vertex of a chord \f$ (\mathbf{a}, \mathbf{b}) \f$, split it into \f$ (\mathbf{a}, \mathbf{c}) \f$ and \f$ (\mathbf{c}, \mathbf{b}) \f$, if the distance \f$ d = \Vert \mathbf{c} , (\mathbf{a}, \mathbf{b}) \Vert \f$ is beyond certain threshold. To make \f$ d \f$ independent to the scale of the input: \f[ d = d / input\_mesh\_average\_edge\_length. \f] Optionally, \f$ d \f$ can be measured as the ratio of the chord length: \f[ d = d / \Vert(\mathbf{a}, \mathbf{b})\Vert. \f] @@ -129,7 +129,7 @@ Varying the chord error. From left to right: clustering partition, and meshing w \subsubsection sma_anchors_additional Additional Anchors -For a boundary cycle without any anchor such as the hole depicted Figure \cgalFigureRef{operations}, we first add a starting anchor to the boundary. We then subdivide this circular chord to ensure that every boundary cycle has at least 2 anchors (i.e., every chord is connecting 2 different anchors, Figure \cgalFigureRef{anchors}). Finally, we add additional anchors to ensure that at least three anchor vertices are generated on every boundary cycle. +For a boundary cycle without any anchor such as the hole depicted \cgalFigureRef{operations}, we first add a starting anchor to the boundary. We then subdivide this circular chord to ensure that every boundary cycle has at least 2 anchors (i.e., every chord is connecting 2 different anchors, \cgalFigureRef{anchors}). Finally, we add additional anchors to ensure that at least three anchor vertices are generated on every boundary cycle. \cgalFigureBegin{anchors, anchors.jpg} Adding anchors. From left to right: starting from a partition (grey) with a hole (white) and two encircled regions (green and blue), we add a starting anchor (orange disk) to the boundary cycle (red dash line) without any anchor (2nd), subdivide the circular chord (3rd, the number indicates the level of recursion) and add anchors to the boundary cycle with less than 2 anchors (4th, red dash lines). @@ -139,7 +139,7 @@ Adding anchors. From left to right: starting from a partition (grey) with a hole With the anchors defined, their chord connection graph forms a general polygon mesh. Because of non-flat, concave polygon or polygons with holes, we need to triangulate this initial polygon mesh. The triangulation is generated by computing a discrete variant of a constrained 2D Delaunay triangulation, where distances are measured on the input triangle mesh. -The first image of Figure \cgalFigureRef{triangulation} depicts how the Delaunay triangulation of set of points (colored disks) is deduced from its dual Voronoi diagram (colored regions separated by blue lines) by connecting the points indicated by the vertices (red circles) where 3 Voronoi cells meet. In an analogous manner, we construct discrete Voronoi cells from which the triangulation is extracted. +The first image of \cgalFigureRef{triangulation} depicts how the Delaunay triangulation of set of points (colored disks) is deduced from its dual Voronoi diagram (colored regions separated by blue lines) by connecting the points indicated by the vertices (red circles) where 3 Voronoi cells meet. In an analogous manner, we construct discrete Voronoi cells from which the triangulation is extracted. In a first step, we start a flooding of the interior of the region, coloring the vertices according to their closest anchor vertex. We then only flood the boundary of a region so that every vertex on it is colored depending on the closest anchor vertex. This enforces the constrained edges by forcing the boundary to be in it. @@ -169,7 +169,7 @@ Free function with \ref bgl_namedparameters options. - `CGAL::Surface_mesh_approximation::approximate_triangle_mesh()`: given a triangle mesh, approximate the geometry with default \f$ \mathcal{L}^{2,1} \f$ metric. Class interface: -- `CGAL::Variational_shape_approximation`: allowing more customization of the proxy, metric and approximation process. As shown in Figure \cgalFigureRef{workflow}, typical calling order of the approximation and meshing process is: +- `CGAL::Variational_shape_approximation`: allowing more customization of the proxy, metric and approximation process. As shown in \cgalFigureRef{workflow}, typical calling order of the approximation and meshing process is: - \link CGAL::Variational_shape_approximation::initialize_seeds initialize seeds \endlink - \link CGAL::Variational_shape_approximation::run run clustering iterations \endlink - \link CGAL::Variational_shape_approximation::extract_mesh extract mesh \endlink @@ -209,7 +209,7 @@ The class interface `CGAL::Variational_shape_approximation` offers a flexible me Comparison of different error metrics on the bear model, with 200 proxies and hierarchical seeding. From left to right: \f$ \mathcal{L}^{2,1} \f$ metric, \f$ \mathcal{L}^2 \f$ metric and custom compact metric. \cgalFigureEnd -The following example defines a point-wise proxy to yield an isotropic approximation. The output mesh is depicted in Figure \cgalFigureRef{vsa_metric_comparison}. +The following example defines a point-wise proxy to yield an isotropic approximation. The output mesh is depicted in \cgalFigureRef{vsa_metric_comparison}. \cgalExample{Surface_mesh_approximation/vsa_isotropic_metric_example.cpp} diff --git a/Surface_mesh_deformation/doc/Surface_mesh_deformation/Concepts/DeformationClosestRotationTraits_3.h b/Surface_mesh_deformation/doc/Surface_mesh_deformation/Concepts/DeformationClosestRotationTraits_3.h index 5a8a704883d4..e89c30f0815d 100644 --- a/Surface_mesh_deformation/doc/Surface_mesh_deformation/Concepts/DeformationClosestRotationTraits_3.h +++ b/Surface_mesh_deformation/doc/Surface_mesh_deformation/Concepts/DeformationClosestRotationTraits_3.h @@ -49,7 +49,7 @@ class DeformationClosestRotationTraits_3{ /// Returns the vector (x,y,z) Vector vector(double x, double y, double z); - /// Returns `i`th coefficient of a vector + /// Returns `i`-th coefficient of a vector double vector_coordinate(const Vector& v, int i); /// Computes a rotation matrix close to `m` and places it into `R` diff --git a/Surface_mesh_parameterization/doc/Surface_mesh_parameterization/Surface_mesh_parameterization.txt b/Surface_mesh_parameterization/doc/Surface_mesh_parameterization/Surface_mesh_parameterization.txt index 3aae43c9c421..5919a1aada71 100644 --- a/Surface_mesh_parameterization/doc/Surface_mesh_parameterization/Surface_mesh_parameterization.txt +++ b/Surface_mesh_parameterization/doc/Surface_mesh_parameterization/Surface_mesh_parameterization.txt @@ -93,7 +93,7 @@ using the `Surface_mesh` built-in property mechanism. \cgalExample{Surface_mesh_parameterization/simple_parameterization.cpp} -Figure \cgalFigureRef{Surface_mesh_parameterizationfigsimple} illustrates the input and output +\cgalFigureRef{Surface_mesh_parameterizationfigsimple} illustrates the input and output of this program. \cgalFigureAnchor{Surface_mesh_parameterizationfigsimple} @@ -179,9 +179,9 @@ depending on the type of border parameterization that is required: fixed border, free border, and borderless. Illustrations of the different methods are obtained with the same input model, -the hand model, shown in Figure \cgalFigureRef{Surface_mesh_parameterizationfigbase}. +the hand model, shown in \cgalFigureRef{Surface_mesh_parameterizationfigbase}. The hand is a topological sphere, and a `Seam_mesh` is used to cut it into -a topological disk. In Figure \cgalFigureRef{Surface_mesh_parameterizationfigbase}, +a topological disk. In \cgalFigureRef{Surface_mesh_parameterizationfigbase}, the seam is traced in red and the four cones used for Orbifold Tutte Parameterization (Section \ref Surface_mesh_parameterizationOrbi) are marked with green dots. diff --git a/Surface_mesh_shortest_path/doc/Surface_mesh_shortest_path/Concepts/SurfaceMeshShortestPathTraits.h b/Surface_mesh_shortest_path/doc/Surface_mesh_shortest_path/Concepts/SurfaceMeshShortestPathTraits.h index a0324ab28ed5..f85508a09fd6 100644 --- a/Surface_mesh_shortest_path/doc/Surface_mesh_shortest_path/Concepts/SurfaceMeshShortestPathTraits.h +++ b/Surface_mesh_shortest_path/doc/Surface_mesh_shortest_path/Concepts/SurfaceMeshShortestPathTraits.h @@ -114,7 +114,7 @@ class SurfaceMeshShortestPathTraits `Point_2 operator()(Segment_2 s, int i)` to return the source or target of `s`, depending on whether `i` is 0 or 1 respectively, and `Point_2 operator()(Triangle_2 t, int i)` - to return the `i`th vertex of `t`. + to return the `i`-th vertex of `t`. */ typedef unspecified_type Construct_vertex_2; @@ -186,7 +186,7 @@ class SurfaceMeshShortestPathTraits `Point_3 operator()(Segment_3 s, int i)` to return the source or target of `s`, depending on whether `i` is 0 or 1 respectively, and `Point_3 operator()(Triangle_3 t, int i)` - to return the `i`th vertex of `t`. + to return the `i`-th vertex of `t`. */ typedef unspecified_type Construct_vertex_3; @@ -231,9 +231,9 @@ class SurfaceMeshShortestPathTraits Function object type that provides `Triangle_2 operator()(Triangle_3 t, std::size_t i, Segment_2 base)` which computes a 2-dimensional projection of t that preserves edge lengths, - such that the `i`th edge of the projection of `t` is incident to `base`. + such that the `i`-th edge of the projection of `t` is incident to `base`. - \pre The length of the `i`th edge of `t` is equal to the length of `base` + \pre The length of the `i`-th edge of `t` is equal to the length of `base` */ typedef unspecified_type Construct_triangle_3_along_segment_2_flattening; @@ -258,7 +258,7 @@ class SurfaceMeshShortestPathTraits /*! Function object type that provides `FT operator(Barycentric_coordinates b, std::size_t i)` - to get the `i`th weight of barycentric coordinates `b`. + to get the `i`-th weight of barycentric coordinates `b`. */ typedef unspecified_type Construct_barycentric_coordinates_weight; diff --git a/Surface_mesher/doc/Surface_mesher/Concepts/SurfaceMeshTriangulation_3.h b/Surface_mesher/doc/Surface_mesher/Concepts/SurfaceMeshTriangulation_3.h index a8411edcbbd5..eb277fce941d 100644 --- a/Surface_mesher/doc/Surface_mesher/Concepts/SurfaceMeshTriangulation_3.h +++ b/Surface_mesher/doc/Surface_mesher/Concepts/SurfaceMeshTriangulation_3.h @@ -273,7 +273,7 @@ Returns the same facet seen from the other adjacent cell. Facet mirror_facet(Facet f) const; /*! -Return the indexes of the `j`th vertex of the facet of a cell +Return the indexes of the `j`-th vertex of the facet of a cell opposite to vertex `i`. */ int vertex_triple_index(const int i, const int j); diff --git a/TDS_3/doc/TDS_3/CGAL/Triangulation_utils_3.h b/TDS_3/doc/TDS_3/CGAL/Triangulation_utils_3.h index f78421c4a46b..bc42a6f850a0 100644 --- a/TDS_3/doc/TDS_3/CGAL/Triangulation_utils_3.h +++ b/TDS_3/doc/TDS_3/CGAL/Triangulation_utils_3.h @@ -43,7 +43,7 @@ opposite to this neighbor `n`. (see \cgalFigureRef{Triangulation3figutils}). static unsigned int next_around_edge(unsigned int i, unsigned int j); /*! -In dimension 3, index of the `j`'th vertex in counterclockwise order on the face opposite to vertex with `i` of the cell. +In dimension 3, index of the `j`-th vertex in counterclockwise order on the face opposite to vertex with `i` of the cell. \pre `( i < 4 ) && ( j < 3 )`. */ static int vertex_triple_index(const int i, const int j); diff --git a/Tetrahedral_remeshing/doc/Tetrahedral_remeshing/Tetrahedral_remeshing.txt b/Tetrahedral_remeshing/doc/Tetrahedral_remeshing/Tetrahedral_remeshing.txt index a2e1c0fb1368..e468439d5337 100644 --- a/Tetrahedral_remeshing/doc/Tetrahedral_remeshing/Tetrahedral_remeshing.txt +++ b/Tetrahedral_remeshing/doc/Tetrahedral_remeshing/Tetrahedral_remeshing.txt @@ -37,7 +37,7 @@ preserve the input topology of the geometric complex. The tetrahedral remeshing algorithm improves the quality of dihedral angles, while targeting the user-defined uniform sizing field and preserving the -topology of the feature complex, as highlighted by Figure \cgalFigureRef{Remesh_liver}. +topology of the feature complex, as highlighted by \cgalFigureRef{Remesh_liver}. Experimental evidence shows that a higher number of remeshing iterations leads to a mesh with a improved fidelity to the sizing criterion, diff --git a/Three/include/CGAL/Three/Primitive_container.h b/Three/include/CGAL/Three/Primitive_container.h index 613af38b7e9c..8a22c7fc639e 100644 --- a/Three/include/CGAL/Three/Primitive_container.h +++ b/Three/include/CGAL/Three/Primitive_container.h @@ -178,7 +178,7 @@ class DEMO_FRAMEWORK_EXPORT Primitive_container //! void setVbos(std::vector); //! - //! \brief setVbo sets the `vbo_id`th `Vbo` of this container to `vbo`. + //! \brief setVbo sets the `vbo_id`-th `Vbo` of this container to `vbo`. //! \param vbo_id //! \param vbo //! @@ -199,7 +199,7 @@ class DEMO_FRAMEWORK_EXPORT Primitive_container //! Vao* getVao(Viewer_interface* viewer)const; //! - //! \brief getVbo returns the `id`th Vbo of this container. + //! \brief getVbo returns the `id`-th Vbo of this container. //! Vbo *getVbo(std::size_t id)const; //! diff --git a/Three/include/CGAL/Three/Scene_interface.h b/Three/include/CGAL/Three/Scene_interface.h index 64441fe197db..652bfa0b3922 100644 --- a/Three/include/CGAL/Three/Scene_interface.h +++ b/Three/include/CGAL/Three/Scene_interface.h @@ -102,7 +102,7 @@ class Scene_interface { //! \brief The number of items //!@returns the number of items in the scene. virtual int numberOfEntries() const = 0; - //!\brief The `id`th item. + //!\brief The `id`-th item. //! @returns the item with the specified index. virtual CGAL::Three::Scene_item* item(Item_id id) const = 0; //!\brief The id of `item` @@ -131,7 +131,7 @@ class Scene_interface { virtual double len_diagonal() const = 0; public: - //! Updates the information about the `i`th item in the + //! Updates the information about the `i`-th item in the //! Geometric Objects list and redraws the scene. virtual void itemChanged(Item_id i) = 0; //! Updates the information about `item` in the diff --git a/Three/include/CGAL/Three/Scene_item_rendering_helper.h b/Three/include/CGAL/Three/Scene_item_rendering_helper.h index 83ee418e4054..2db399b23465 100644 --- a/Three/include/CGAL/Three/Scene_item_rendering_helper.h +++ b/Three/include/CGAL/Three/Scene_item_rendering_helper.h @@ -94,20 +94,20 @@ class DEMO_FRAMEWORK_EXPORT Scene_item_rendering_helper //! @returns the item's bounding box. Scene_item::Bbox bbox()const override; //! - //! \brief getTriangleContainer returns the `id`th `Triangle_container`. + //! \brief getTriangleContainer returns the `id`-th `Triangle_container`. //! CGAL::Three::Triangle_container* getTriangleContainer(std::size_t id) const; //! - //! \brief getEdgeContainer returns the `id`th `Edge_container`. + //! \brief getEdgeContainer returns the `id`-th `Edge_container`. //! CGAL::Three::Edge_container* getEdgeContainer(std::size_t id) const; //! - //! \brief getPointContainer returns the `id`th `Point_container`. + //! \brief getPointContainer returns the `id`-th `Point_container`. //! CGAL::Three::Point_container* getPointContainer(std::size_t id) const; //! - //! \brief setTriangleContainer sets the `id`th `Triangle_container` to `tc`. + //! \brief setTriangleContainer sets the `id`-th `Triangle_container` to `tc`. //! //! If `id` is bigger than the current size of the container vector, this vector is //! resized accordingly. This means that for optimisation reasons, containers should be created @@ -117,7 +117,7 @@ class DEMO_FRAMEWORK_EXPORT Scene_item_rendering_helper Triangle_container* tc); //! - //! \brief setEdgeContainer sets the `id`th `Edge_container` to `tc`. + //! \brief setEdgeContainer sets the `id`-th `Edge_container` to `tc`. //! //! If `id` is bigger than the current size of the container vector, this vector is //! resized accordingly. This means that for optimisation reasons, containers should be created @@ -127,7 +127,7 @@ class DEMO_FRAMEWORK_EXPORT Scene_item_rendering_helper Edge_container* tc); //! - //! \brief setPointContainer sets the `id`th `Point_container` to `tc`. + //! \brief setPointContainer sets the `id`-th `Point_container` to `tc`. //! //! If `id` is bigger than the current size of the container vector, this vector is //! resized accordingly. This means that for optimisation reasons, containers should be created diff --git a/Three/include/CGAL/Three/Viewer_interface.h b/Three/include/CGAL/Three/Viewer_interface.h index 2bdba0aadd39..a9f61b288f02 100644 --- a/Three/include/CGAL/Three/Viewer_interface.h +++ b/Three/include/CGAL/Three/Viewer_interface.h @@ -223,7 +223,7 @@ class VIEWER_EXPORT Viewer_interface : public CGAL::QGLViewer{ //! Customizable from the MainWindow or the SubViewer menu. virtual float total_pass() = 0; Q_SIGNALS: - //!Emit this to signal that the `id`th item has been picked. + //!Emit this to signal that the `id`-th item has been picked. void selected(int id); //!Emit this to require a contextual menu to appear at `global_pos`. void requestContextMenu(QPoint global_pos); diff --git a/Triangulation/doc/Triangulation/Concepts/TriangulationDataStructure.h b/Triangulation/doc/Triangulation/Concepts/TriangulationDataStructure.h index 6ab5522df502..197a362532ed 100644 --- a/Triangulation/doc/Triangulation/Concepts/TriangulationDataStructure.h +++ b/Triangulation/doc/Triangulation/Concepts/TriangulationDataStructure.h @@ -480,7 +480,7 @@ The indexing of the vertices in the full cell is such that, if `f` was a full cell of maximal dimension in the initial complex, then `(f,v)`, in this order, is the corresponding full cell in the updated triangulation. A handle to `v` is returned -(see Figure \cgalFigureRef{triangulationfiginsertincreasedim}). +(see \cgalFigureRef{triangulationfiginsertincreasedim}). \pre If the current dimension is -2 (empty triangulation), then `star` can be omitted (it is ignored), otherwise @@ -541,7 +541,7 @@ void clear(); /*! Contracts the `Face f` to a single vertex. Returns a handle to that vertex -(see Figure \cgalFigureRef{triangulationfigcollapseface}). +(see \cgalFigureRef{triangulationfigcollapseface}). \pre The boundary of the full cells incident to `f` is a topological sphere of dimension `tds`.`current_dimension()`-1). @@ -561,7 +561,7 @@ This method does exactly the opposite of full cells not containing `star` are removed, full cells containing `star` but not `v` lose vertex `star`, full cells containing `star` and `v` lose vertex `v` -(see Figure \cgalFigureRef{triangulationfiginsertincreasedim}). +(see \cgalFigureRef{triangulationfiginsertincreasedim}). \pre All cells contain either `star` or `v`. Edge `star-v` exists in the triangulation and `current_dimension() != -2`. diff --git a/Triangulation/doc/Triangulation/Triangulation.txt b/Triangulation/doc/Triangulation/Triangulation.txt index 2e7c8e538bd8..8fe83b3fdd50 100644 --- a/Triangulation/doc/Triangulation/Triangulation.txt +++ b/Triangulation/doc/Triangulation/Triangulation.txt @@ -163,7 +163,7 @@ neighbors. Its vertices and neighbors are indexed from \f$ 0\f$ to \f$ d \f$. Th of its neighbors have the following meaning: the \f$ i\f$-th neighbor of \f$ \sigma\f$ is the unique neighbor of \f$ \sigma\f$ that does not contain the \f$ i\f$-th vertex of \f$ \sigma\f$; in other words, it is the neighbor of \f$ \sigma\f$ opposite to -the \f$ i\f$-th vertex of \f$ \sigma\f$ (Figure \cgalFigureRef{triangulationfigfullcell}). +the \f$ i\f$-th vertex of \f$ \sigma\f$ (\cgalFigureRef{triangulationfigfullcell}). The vertices and full cells of the triangulations are accessed through `handles` and `iterators`. A handle is a model of the @@ -183,7 +183,7 @@ to \f$ f \f$. \if READY_TO_PUBLISH \cgalAdvanced The index of a full cell \f$ c\f$ in the \f$ i\f$-th neighbor of \f$ c\f$ is called the \f$ i\f$-th mirror-index of -\f$ c\f$ (Figure \cgalFigureRef{triangulationfigfullcell}). Mirror indices are +\f$ c\f$ (\cgalFigureRef{triangulationfigfullcell}). Mirror indices are often needed for maintaining the triangulation data structure. Thus, it might be desirable, for performance reasons, to store the mirror indices alongside the references to the vertices and neighbors in a diff --git a/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt b/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt index 17ae1a6e24bd..f04b331b629f 100644 --- a/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt +++ b/Triangulation_2/doc/Triangulation_2/Triangulation_2.txt @@ -1055,7 +1055,7 @@ one can easily obtain the input constraints that induce `e`. \subsection Subsection_Edges_and_Constraints Edges, Constrained Edges, Constraints, and Subconstraints All triangulation classes define the type `Edge` as `typedef std::pair Edge`. -For a pair `(fh,i)` it is the edge of the face `*fh`, which is opposite to the `i`'th vertex. +For a pair `(fh,i)` it is the edge of the face `*fh`, which is opposite to the `i`-th vertex. A constrained edge `e` is an edge of a constrained triangulation `ct`, for which `ct.is_constrained(e)` returns `true`. diff --git a/Triangulation_3/doc/Triangulation_3/Triangulation_3.txt b/Triangulation_3/doc/Triangulation_3/Triangulation_3.txt index 84daca88505f..5eaa7e91874a 100644 --- a/Triangulation_3/doc/Triangulation_3/Triangulation_3.txt +++ b/Triangulation_3/doc/Triangulation_3/Triangulation_3.txt @@ -807,7 +807,7 @@ in \cgalCite{msri52:liu-snoeyink-05}. \subsection Triangulation_3ParallelPerformance Parallel Performance -Figure \cgalFigureRef{Triangulation3figparallelspeedup} shows insertion +\cgalFigureRef{Triangulation3figparallelspeedup} shows insertion and removal speed-ups obtained using the parallel version of the triangulation algorithms of \cgal 4.5. The machine used is a PC running Windows 7 64-bits with two 6-core diff --git a/Visibility_2/doc/Visibility_2/visibility_2.txt b/Visibility_2/doc/Visibility_2/visibility_2.txt index d0dcdc09c49b..e4ab65a512d6 100644 --- a/Visibility_2/doc/Visibility_2/visibility_2.txt +++ b/Visibility_2/doc/Visibility_2/visibility_2.txt @@ -87,7 +87,7 @@ Where \f$ n \f$ denotes the number of vertices of \f$ f \f$ and \f$ h \f$ the nu Example representing a cathedral. \cgalFigureEnd -The left hand side of Figure \cgalFigureRef{cathedral-fig} depicts the outer boundary of a cathedral, +The left hand side of \cgalFigureRef{cathedral-fig} depicts the outer boundary of a cathedral, which is a simple polygon with 565 vertices. The right hand side shows the cathedral also with its inner pillars, which is a polygon (with holes) with 1153 vertices.