diff --git a/.gitignore b/.gitignore index 93b7a9bfc90..752f07930b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +build /*build* /*/*/*/build /*/*/*/VC* diff --git a/Algebraic_kernel_for_circles/include/CGAL/Root_for_circles_2_2.h b/Algebraic_kernel_for_circles/include/CGAL/Root_for_circles_2_2.h index bd26c95af66..8a5eda359e3 100644 --- a/Algebraic_kernel_for_circles/include/CGAL/Root_for_circles_2_2.h +++ b/Algebraic_kernel_for_circles/include/CGAL/Root_for_circles_2_2.h @@ -46,7 +46,7 @@ class Root_for_circles_2_2 { Root_for_circles_2_2(const Root_of_2& r1, const Root_of_2& r2) : x_(r1), y_(r2) { - // When it is an interval this assertion dont compile + // When it is an interval this assertion doesn't compile //CGAL_assertion((r1.is_rational() || r2.is_rational()) || // (r1.gamma() == r2.gamma())); } diff --git a/Alpha_wrap_3/benchmark/Alpha_wrap_3/Quality/generate_quality_benchmark_charts.py b/Alpha_wrap_3/benchmark/Alpha_wrap_3/Quality/generate_quality_benchmark_charts.py index 0df5ed5e0f4..264dec30935 100644 --- a/Alpha_wrap_3/benchmark/Alpha_wrap_3/Quality/generate_quality_benchmark_charts.py +++ b/Alpha_wrap_3/benchmark/Alpha_wrap_3/Quality/generate_quality_benchmark_charts.py @@ -134,13 +134,13 @@ def main(argv): avg_diff_str = str(format(abs(avg_diff_to_goal), '.2f')) if key == "Mean_Min_Angle_(degree)" or key == "Mean_Max_Angle_(degree)": if avg_diff_to_goal < 0 : - title += "\nIn average we loose " + title += "\nIn average we lose " else : title += "\nIn average we gain " title += avg_diff_str + "° toward 60°" elif key == "Mean_Radius_Ratio" or key == "Mean_Edge_Ratio" or key == "Mean_Aspect_Ratio" : if avg_diff_to_goal < 0 : - title += "\nIn average we loose " + title += "\nIn average we lose " else : title += "\nIn average we gain " title += avg_diff_str + " of ratio toward 1" diff --git a/Apollonius_graph_2/include/CGAL/Apollonius_graph_2/Bounded_side_of_ccw_circle_C2.h b/Apollonius_graph_2/include/CGAL/Apollonius_graph_2/Bounded_side_of_ccw_circle_C2.h index bbef2be6ac0..a6590cde787 100644 --- a/Apollonius_graph_2/include/CGAL/Apollonius_graph_2/Bounded_side_of_ccw_circle_C2.h +++ b/Apollonius_graph_2/include/CGAL/Apollonius_graph_2/Bounded_side_of_ccw_circle_C2.h @@ -77,7 +77,7 @@ private: public: typedef Voronoi_radius_2 Voronoi_radius; - typedef typename K::Bounded_side Bounded_dide; + typedef typename K::Bounded_side Bounded_side; public: template diff --git a/Arrangement_on_surface_2/TODO b/Arrangement_on_surface_2/TODO index 63b5d28a8c1..615c13266cf 100644 --- a/Arrangement_on_surface_2/TODO +++ b/Arrangement_on_surface_2/TODO @@ -164,7 +164,7 @@ or at least issues warnings about returning a reference to temporary variable. Can you prompt the INRIA people to fix this? (I personally think that we should remove this example from our test-suite, - but if the INRIA people believe that it's place is there, they should at + but if the INRIA people believe that its place is there, they should at least properly maintain it ...) - There is a problem with the examples that use CORE on Darwin (platform #10). Other Darwin platforms seem fine. Do we want to investigate? (perhaps it's diff --git a/Arrangement_on_surface_2/demo/Arrangement_on_surface_2/ArrangementPainterOstream.h b/Arrangement_on_surface_2/demo/Arrangement_on_surface_2/ArrangementPainterOstream.h index 33942b6d83a..d983cea19d1 100644 --- a/Arrangement_on_surface_2/demo/Arrangement_on_surface_2/ArrangementPainterOstream.h +++ b/Arrangement_on_surface_2/demo/Arrangement_on_surface_2/ArrangementPainterOstream.h @@ -232,7 +232,7 @@ protected: // methods // Assumes that clippingRect is valid. std::vector< X_monotone_curve_2 > visibleParts( X_monotone_curve_2 curve ); - // keep only the intersection points ie. throw out overlapping curve segments + // keep only the intersection points i.e. throw out overlapping curve segments void filterIntersectionPoints( std::vector< CGAL::Object >& res ); protected: // members diff --git a/Arrangement_on_surface_2/doc/Arrangement_on_surface_2/Arrangement_on_surface_2.txt b/Arrangement_on_surface_2/doc/Arrangement_on_surface_2/Arrangement_on_surface_2.txt index 0287ab3226c..2498396f920 100644 --- a/Arrangement_on_surface_2/doc/Arrangement_on_surface_2/Arrangement_on_surface_2.txt +++ b/Arrangement_on_surface_2/doc/Arrangement_on_surface_2/Arrangement_on_surface_2.txt @@ -1999,7 +1999,7 @@ operates in two-dimensional surfaces (not restricted to the plane), (ii) accepts various families of \f$x\f$-monotone curves (not only line segments), and (iii) handles overlaps. (Observe that the original algorithm did not handle overlaps. Handling overlaps is difficult, -especially for polyline, as two polylines may overlap in more then one +especially for polyline, as two polylines may overlap in more than one connected component.) The generic implementation serves as the foundation of a family of concrete operations described in the rest of this section, such as aggregately constructing an arrangement induced diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_batched_point_location.h b/Arrangement_on_surface_2/include/CGAL/Arr_batched_point_location.h index 85ba6ccc235..fefc8a75748 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_batched_point_location.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_batched_point_location.h @@ -113,7 +113,7 @@ locate(const Arrangement_on_surface_2& arr, * * If the type Bgt2 is the same as the type Gt2, use a reference to Gt2 * to avoid constructing a new one. Otherwise, instantiate a local variable - * of the former and provide the later as a single parameter to the + * of the former and provide the latter as a single parameter to the * constructor. * * Use the form 'A a(*b);' and not ''A a = b;' to handle the case where A has 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 0f63b3c41fa..6d810e11f3c 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 @@ -2500,7 +2500,7 @@ public: 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; @@ -2516,7 +2516,7 @@ public: (CGAL::sign(aux_coeffs[2]) == ZERO)) ? 1 : 2; // Compute the \f$x\f$- and \f$y\f$-coordinates of intersection points - // of the base conic and the k'th auxiliary conic. + // of the base 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 9598456cd59..62d71befe8b 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 // Construct 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 // Construct 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 e63e7eea930..ba4c3e69342 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 @@ -397,7 +397,7 @@ public: return static_cast((this->_rep()._ctrl_pts.size())); } - /*! obtains the i'th control point. + /*! obtains the i-th control point. * \pre i must be between 0 and n - 1, where n is the number of control * points. */ @@ -681,7 +681,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 b0bd7fd018e..d90692e229b 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 @@ public: 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 @@ public: } // 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 c9e17b4d85f..9ec99d99500 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 @@ private: } public: - /*! obtains the i'th order derivative by x of the conic at the point p=(x,y). + /*! obtains 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 @@ public: CGAL_error(); } - /*! obtains the i'th order derivative by y of the conic at the point p=(x,y). + /*! obtains 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 caf0e8eacfc..0db81ef2484 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_overlay_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h index dfa6fb89ffc..4d518c96961 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_overlay_2.h @@ -241,7 +241,7 @@ overlay(const Arrangement_on_surface_2& arr1 * If the type Ovl_gt2 is the same as the type * GeomTraits, use a reference to GeomTraits to avoid constructing a new one. * Otherwise, instantiate a local variable of the former and provide - * the later as a single parameter to the constructor. + * the latter as a single parameter to the constructor. * * Use the form 'A a(*b);' and not ''A a = b;' to handle the case where A has * only an implicit constructor, (which takes *b as a parameter). diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_point_location/Trapezoidal_decomposition_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_point_location/Trapezoidal_decomposition_2.h index 82b554cb6a0..3390ef3d585 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_point_location/Trapezoidal_decomposition_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_point_location/Trapezoidal_decomposition_2.h @@ -1992,10 +1992,10 @@ public: determines according to pre defined conditions whether the current Trapezoidal_decomposition_2 needs update Postconditions: - The output is true iff the depth of the Trapezoidal Tree is more then + The output is true iff the depth of the Trapezoidal Tree is more than DepthThreshold times log of the X_curve count or the Trapezoidal Tree's size - is more then SizeThreshold times the log of the last count. + is more than SizeThreshold times the log of the last count. */ bool set_with_guarantees(bool u) { 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 7bf3b5da9cc..94173b2e083 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 @@ -2625,7 +2625,7 @@ protected: if ((! is_vert(cv[0]) && (cmp_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 @@ -2642,7 +2642,7 @@ protected: if ((! is_vert(cv[0]) && (cmp_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 8c78432ad12..51f47742df8 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 @@ -598,15 +598,15 @@ public: 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/Arr_topology_traits/Arr_spherical_topology_traits_2_impl.h b/Arrangement_on_surface_2/include/CGAL/Arr_topology_traits/Arr_spherical_topology_traits_2_impl.h index 8dea4d1afb5..a2beb87aac2 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_topology_traits/Arr_spherical_topology_traits_2_impl.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_topology_traits/Arr_spherical_topology_traits_2_impl.h @@ -281,7 +281,7 @@ is_in_face(const Face* f, const Point_2& p, const Vertex* v) const // ----------- // cv1 coincide with the identification curve. In this case we // consider the identification to be on the right. All (interior) - // points are smaller then the right boundary. + // points are smaller than the right boundary. rc1 = SMALLER; rc2 = cmp_x_pt_ce(p, cv2, ARR_MAX_END); } @@ -294,7 +294,7 @@ is_in_face(const Face* f, const Point_2& p, const Vertex* v) const // ----------- // cv2 coincide with the identification curve. In this case we // consider the identification to be on the left. All (interior) - // points are larger then the left boundary. + // points are larger than the left boundary. rc1 = cmp_x_pt_ce(p, cv1, ARR_MAX_END); rc2 = LARGER; } diff --git a/Arrangement_on_surface_2/include/CGAL/Arr_vertical_decomposition_2.h b/Arrangement_on_surface_2/include/CGAL/Arr_vertical_decomposition_2.h index 099f44192ee..6a53d0d4ee3 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arr_vertical_decomposition_2.h +++ b/Arrangement_on_surface_2/include/CGAL/Arr_vertical_decomposition_2.h @@ -117,7 +117,7 @@ decompose(const Arrangement_on_surface_2& arr, * * If the type Vgt2 is the same as the type Gt2, use a * reference to Gt2 to avoid constructing a new one. Otherwise, - * instantiate a local variable of the former and provide the later as a + * instantiate a local variable of the former and provide the latter as a * single parameter to the constructor. * * Use the form 'A a(*b);' and not ''A a = b;' to handle the case where A has diff --git a/Arrangement_on_surface_2/include/CGAL/Arrangement_2/Arrangement_on_surface_2_global.h b/Arrangement_on_surface_2/include/CGAL/Arrangement_2/Arrangement_on_surface_2_global.h index 1436405e4d8..6c826feec2a 100644 --- a/Arrangement_on_surface_2/include/CGAL/Arrangement_2/Arrangement_on_surface_2_global.h +++ b/Arrangement_on_surface_2/include/CGAL/Arrangement_2/Arrangement_on_surface_2_global.h @@ -264,7 +264,7 @@ insert_empty(Arrangement_on_surface_2& arr, * If the type C_visitor::Geometry_traits_2 is the same as the type * GeometryTraits_2, use a reference to GeometryTraits_2 to avoid constructing * a new one. Otherwise, instantiate a local variable of the former and - * provide the later as a single parameter to the constructor. + * provide the latter as a single parameter to the constructor. * * Use the form 'A a(*b);' and not ''A a = b;' to handle the case where A has * only an implicit constructor, (which takes *b as a parameter). @@ -319,7 +319,7 @@ void insert_empty(Arrangement_on_surface_2& * If the type C_visitor::Geometry_traits_2 is the same as the type * GeometryTraits_2, use a reference to GeometryTraits_2 to avoid constructing * a new one. Otherwise, instantiate a local variable of the former and - * provide the later as a single parameter to the constructor. + * provide the latter as a single parameter to the constructor. * * Use the form 'A a(*b);' and not ''A a = b;' to handle the case where A has * only an implicit constructor, (which takes *b as a parameter). @@ -372,7 +372,7 @@ void insert_non_empty(Arrangement_on_surface_2 bool IO_base_test::read_segment(InputStream_& is, Subcurve_2& seg) { - //we dont need to check this type as it has already been checked in the + //we don't need to check this type as it has already been checked in the //IO_test.h char type; is >> type; @@ -600,7 +600,7 @@ template bool IO_base_test::read_xsegment(InputStream_& is, X_monotone_subcurve_2& xseg) { - //we dont need to check this type as it has already been checked in the + //we don't need to check this type as it has already been checked in the //IO_test.h char type; is >> type; diff --git a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/construction_test_suite_generator.cpp b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/construction_test_suite_generator.cpp index 8dfb636ebd3..a4a9993955a 100644 --- a/Arrangement_on_surface_2/test/Arrangement_on_surface_2/construction_test_suite_generator.cpp +++ b/Arrangement_on_surface_2/test/Arrangement_on_surface_2/construction_test_suite_generator.cpp @@ -116,7 +116,7 @@ int main(int argc, char* argv[]) std::cout << argv[2] << " was generated successfully" - << ", dont forget to add it to test_construction.cmd" + << ", don't forget to add it to test_construction.cmd" << std::endl; return 0; } diff --git a/BGL/doc/BGL/BGL.txt b/BGL/doc/BGL/BGL.txt index c4db7311c6d..1f1a3dd1c97 100644 --- a/BGL/doc/BGL/BGL.txt +++ b/BGL/doc/BGL/BGL.txt @@ -586,7 +586,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, @@ -600,7 +600,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 @@ -612,7 +612,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/BGL/doc/BGL/Doxyfile.in b/BGL/doc/BGL/Doxyfile.in index f37650c983f..549cdf37c1b 100644 --- a/BGL/doc/BGL/Doxyfile.in +++ b/BGL/doc/BGL/Doxyfile.in @@ -19,7 +19,8 @@ INPUT += ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/IO/polygon_mesh_io.h \ ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_graph.h \ ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/METIS/partition_dual_graph.h \ ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/alpha_expansion_graphcut.h \ - ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/graph_traits_inheritance_macros.h + ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/graph_traits_inheritance_macros.h \ + ${CGAL_PACKAGE_INCLUDE_DIR}/CGAL/boost/graph/dijkstra_shortest_path.h EXAMPLE_PATH += ${CGAL_Surface_mesh_skeletonization_EXAMPLE_DIR} \ diff --git a/BGL/doc/BGL/PackageDescription.txt b/BGL/doc/BGL/PackageDescription.txt index 39d72cd330d..93687510006 100644 --- a/BGL/doc/BGL/PackageDescription.txt +++ b/BGL/doc/BGL/PackageDescription.txt @@ -467,6 +467,9 @@ the requirement for traversal of all faces in a graph. /// \defgroup PkgBGLPartition Partitioning Operations /// \ingroup PkgBGLRef +/// \defgroup PkgBGLTraversal Graph Traversal +/// \ingroup PkgBGLRef + /// \defgroup PkgBGLIOFct I/O Functions /// \ingroup PkgBGLRef @@ -586,6 +589,12 @@ Methods to split a mesh into subdomains, using the library implementation. */ +/*! +\addtogroup PkgBGLTraversal + +Methods to traverse a graph, for example to find the shortest path between two vertices. +*/ + /*! \addtogroup PkgBGLIOFct @@ -762,6 +771,9 @@ user might encounter. \cgalCRPSection{Conversion Functions} - `CGAL::split_graph_into_polylines()` +\cgalCRPSection{Graph Traversal} +- `CGAL::dijkstra_shortest_path()` + \cgalCRPSection{Graph Adaptors} - `CGAL::Dual` - `CGAL::Face_filtered_graph` diff --git a/BGL/doc/BGL/examples.txt b/BGL/doc/BGL/examples.txt index 622a2395b73..19b883ae360 100644 --- a/BGL/doc/BGL/examples.txt +++ b/BGL/doc/BGL/examples.txt @@ -22,6 +22,7 @@ \example BGL_surface_mesh/prim.cpp \example BGL_surface_mesh/gwdwg.cpp \example BGL_surface_mesh/seam_mesh.cpp +\example BGL_surface_mesh/shortest_path.cpp \example BGL_surface_mesh/write_inp.cpp \example BGL_surface_mesh/surface_mesh_dual.cpp \example BGL_surface_mesh/connected_components.cpp diff --git a/BGL/examples/BGL_OpenMesh/CMakeLists.txt b/BGL/examples/BGL_OpenMesh/CMakeLists.txt index c56f97d03d7..eb05799c966 100644 --- a/BGL/examples/BGL_OpenMesh/CMakeLists.txt +++ b/BGL/examples/BGL_OpenMesh/CMakeLists.txt @@ -7,7 +7,7 @@ project(BGL_OpenMesh_Examples) # CGAL and its components find_package(CGAL REQUIRED) -find_package(OpenMesh) +find_package(OpenMesh QUIET) if(OpenMesh_FOUND) include(CGAL_OpenMesh_support) create_single_source_cgal_program("TriMesh.cpp") diff --git a/BGL/examples/BGL_surface_mesh/CMakeLists.txt b/BGL/examples/BGL_surface_mesh/CMakeLists.txt index 87982059ae9..7346e63d71e 100644 --- a/BGL/examples/BGL_surface_mesh/CMakeLists.txt +++ b/BGL/examples/BGL_surface_mesh/CMakeLists.txt @@ -9,6 +9,7 @@ create_single_source_cgal_program("seam_mesh.cpp") create_single_source_cgal_program("write_inp.cpp") create_single_source_cgal_program("surface_mesh_dual.cpp") create_single_source_cgal_program("connected_components.cpp") +create_single_source_cgal_program("shortest_path.cpp") find_package(METIS QUIET) include(CGAL_METIS_support) diff --git a/BGL/examples/BGL_surface_mesh/shortest_path.cpp b/BGL/examples/BGL_surface_mesh/shortest_path.cpp new file mode 100644 index 00000000000..3b6bc4c1613 --- /dev/null +++ b/BGL/examples/BGL_surface_mesh/shortest_path.cpp @@ -0,0 +1,83 @@ +#include +#include +#include + +#include +#include + + +#include +#include +#include +#include +#include + +using K = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point = K::Point_3; +using Mesh = CGAL::Surface_mesh; + +using vertex_descriptor = boost::graph_traits::vertex_descriptor; +using edge_descriptor = boost::graph_traits::edge_descriptor; +using halfedge_descriptor = boost::graph_traits::halfedge_descriptor; + +namespace params = CGAL::parameters; + + +// Example main +int main(int argc, char** argv) +{ + const std::string filename = (argc > 1) ? argv[1] : CGAL::data_file_path("meshes/elephant.off"); + + // Try building a surface_mesh + Mesh sm; + bool ok = CGAL::IO::read_polygon_mesh(filename, sm); + if (!ok || !sm.is_valid() || sm.is_empty()) + { + std::cerr << "Error: Invalid facegraph" << std::endl; + std::cerr << "Filename = " << filename << std::endl; + return EXIT_FAILURE; + } + + const std::size_t i0 = 0; + const std::size_t i1 = num_vertices(sm) / 2; + + + // Get the vertex descriptors of the source and target vertices + const vertex_descriptor vs = *vertices(sm).first; + vertex_descriptor vt; + std::size_t vid = 0; + for (const vertex_descriptor v : vertices(sm)) + { + if (vid++ == i1) + { + vt = v; + break; + } + } + + std::list halfedge_sequence; + CGAL::dijkstra_shortest_path(vs, vt, sm, + std::back_inserter(halfedge_sequence)); + + assert(source(halfedge_sequence.front(), sm)==vs); + assert(target(halfedge_sequence.back(), sm)==vt); + + // dump + std::cout << "Shortest path between vertices " << i0 << " and " << i1 + << " is made of " << halfedge_sequence.size() << " halfedges." << std::endl; + + // Get the property map of the points of the mesh + auto vpmap = get(CGAL::vertex_point, sm); + + std::ofstream out("shortest_path.polylines.txt"); + out << halfedge_sequence.size()+1 << " " << get(vpmap, source(halfedge_sequence.front(),sm)); + for (const halfedge_descriptor he : halfedge_sequence) + { + const vertex_descriptor v = target(he, sm); + out << " " << get(vpmap, v); + } + out << std::endl; + out.close(); + + return EXIT_SUCCESS; +} diff --git a/BGL/include/CGAL/boost/graph/dijkstra_shortest_path.h b/BGL/include/CGAL/boost/graph/dijkstra_shortest_path.h new file mode 100644 index 00000000000..8bc03f9fb48 --- /dev/null +++ b/BGL/include/CGAL/boost/graph/dijkstra_shortest_path.h @@ -0,0 +1,160 @@ +// Copyright (c) 2025 GeometryFactory (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) : Jane Tournois, Andreas Fabri +// + +#ifndef CGAL_BOOST_GRAPH_DIJKSTRA_SHORTEST_PATH_H +#define CGAL_BOOST_GRAPH_DIJKSTRA_SHORTEST_PATH_H + +#include + +#include +#include + +#include +#include + +#include +#include + +namespace CGAL { +namespace internal { + + /// An exception used while catching a throw that stops Dijkstra's algorithm + /// once the shortest path to a target has been found. + class Dijkstra_end_exception : public std::exception + { + const char* what() const throw () + { + return "Dijkstra shortest path: reached the target vertex."; + } + }; + + /// Visitor to stop Dijkstra's algorithm once the given target turns 'BLACK', + /// that is when the target has been examined through all its incident edges and + /// the shortest path is thus known. + template + class Stop_at_target_Dijkstra_visitor : public boost::default_dijkstra_visitor + { + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using edge_descriptor = typename boost::graph_traits::edge_descriptor; + + public: + vertex_descriptor destination_vd; + VertexEdgeMap& relaxed_edges; + + Stop_at_target_Dijkstra_visitor(vertex_descriptor destination_vd, + VertexEdgeMap& relaxed_edges) + : destination_vd(destination_vd), relaxed_edges(relaxed_edges) + {} + + + void edge_relaxed(const edge_descriptor& e, const Graph& g) const + { + relaxed_edges[target(e, g)] = e; + } + + void finish_vertex(const vertex_descriptor& vd, const Graph& /* g*/) const + { + if (vd == destination_vd) + throw Dijkstra_end_exception(); + } + }; +} // namespace internal + +/*! +* \ingroup PkgBGLTraversal +* computes the shortest path between two vertices in a graph `g`, where the vertices must belong to the same connected component of `g`. +* +* @tparam Graph a model of the concept `HalfedgeListGraph` +* @tparam OutputIterator an output iterator with value type `boost::graph_traits::%halfedge_descriptor` +* @tparam NamedParameters a sequence of \ref bgl_namedparameters "Named Parameters" +* +* @param vs source vertex +* @param vt target vertex +* @param g the graph +* @param halfedge_sequence_oit the output iterator holding the output sequence +* of halfedges that form the shortest path from `vs` to `vt` on `g` +* @param np an optional sequence of \ref bgl_namedparameters "Named Parameters" among the ones listed below +* +* \cgalNamedParamsBegin +* \cgalParamNBegin{edge_weight_map} +* \cgalParamDescription{a property map associating to each edge in the graph its weight or "length". +* The weights must all be non-negative.} +* \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%edge_descriptor` +* as key type and a value type which as specified in the named parameter `distance_map`of the function + `boost::graph::dijkstra_shortest_paths()`, + with any model of `RingNumberType` fulfilling the requirements. } +* \cgalParamDefault{`get(boost::edge_weight, mesh)`} +* \cgalParamNEnd +* +* \cgalParamNBegin{vertex_index_map} +* \cgalParamDescription{a property map associating to each vertex of `g` a unique index between `0` and `num_vertices(g) - 1`} +* \cgalParamType{a class model of `ReadablePropertyMap` with `boost::graph_traits::%vertex_descriptor` +* as key type and `std::size_t` as value type} +* \cgalParamDefault{an automatically indexed internal map} +* \cgalParamNEnd +* \cgalNamedParamsEnd +*/ +template +OutputIterator dijkstra_shortest_path( + const typename boost::graph_traits::vertex_descriptor vs,//source + const typename boost::graph_traits::vertex_descriptor vt,//target + const Graph& g, + OutputIterator halfedge_sequence_oit, + const NamedParameters& np = parameters::default_values()) +{ + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + using edge_descriptor = typename boost::graph_traits::edge_descriptor; + + using Pred_umap = std::unordered_map; + using Pred_pmap = boost::associative_property_map; + + using parameters::get_parameter; + using parameters::choose_parameter; + + const auto w_map = choose_parameter(get_parameter(np, internal_np::edge_weight), + get(boost::edge_weight, g)); + const auto vim = get_initialized_vertex_index_map(g, np); + + Pred_umap predecessor; + Pred_pmap pred_pmap(predecessor); + + using VEMap = std::unordered_map; + VEMap relaxed_edges_map; + internal::Stop_at_target_Dijkstra_visitor vis(vt, relaxed_edges_map); + try + { + boost::dijkstra_shortest_paths(g, vs, + boost::predecessor_map(pred_pmap) + .visitor(vis) + .weight_map(w_map) + .vertex_index_map(vim)); + } + catch (const internal::Dijkstra_end_exception& ){} + + std::list path; + vertex_descriptor t = vt; + do { + path.push_front(halfedge(relaxed_edges_map[t],g)); + t = get(pred_pmap, t); + }while (t != vs); + for(auto he : path){ + *halfedge_sequence_oit++ = he; + } + return halfedge_sequence_oit; +} + +} // namespace CGAL + + +#endif //CGAL_BOOST_GRAPH_DIJKSTRA_SHORTEST_PATH_H diff --git a/BGL/include/CGAL/boost/graph/named_params_helper.h b/BGL/include/CGAL/boost/graph/named_params_helper.h index c93ac3cf5e5..c2be9fcab81 100644 --- a/BGL/include/CGAL/boost/graph/named_params_helper.h +++ b/BGL/include/CGAL/boost/graph/named_params_helper.h @@ -227,6 +227,32 @@ struct GetGeomTraits NamedParametersVPM>::type type; }; +namespace internal { +// Similar helper for polygon soups +template +struct Polygon_types +{ + typedef typename boost::range_value::type Point_3; + typedef typename boost::range_value::type Polygon_3; + + typedef typename boost::range_iterator::type V_ID_iterator; + typedef typename std::iterator_traits::value_type V_ID; + typedef typename std::vector::size_type P_ID; +}; +} + +template +struct GetPolygonGeomTraits +{ + typedef typename internal_np::Lookup_named_param_def < + internal_np::geom_traits_t, + NamedParameters, + typename CGAL::Kernel_traits< + typename internal::Polygon_types< + PointRange, PolygonRange>::Point_3 >::type + > ::type type; +}; + // Define the following structs: // // GetInitializedVertexIndexMap diff --git a/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_on_surface_base_2_impl.h b/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_on_surface_base_2_impl.h index 01f7d07f051..8c3c75f2377 100644 --- a/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_on_surface_base_2_impl.h +++ b/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_on_surface_base_2_impl.h @@ -540,7 +540,7 @@ template } //insert non-sipmle poloygons with holes (non incident edges may have -// common vertex, but they dont intersect at their interior +// common vertex, but they don't intersect at their interior template void Gps_on_surface_base_2:: _insert(const Polygon_with_holes_2 & pgn, Arrangement_on_surface_2 & arr) diff --git a/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_polygon_validation.h b/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_polygon_validation.h index d5bcafcbdfc..a709bddd4b1 100644 --- a/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_polygon_validation.h +++ b/Boolean_set_operations_2/include/CGAL/Boolean_set_operations_2/Gps_polygon_validation.h @@ -407,7 +407,7 @@ is_crossover_outer_boundary(const typename Traits_2::Polygon_with_holes_2& pgn, Vertex_const_handle cver; Point_2 second_point; if (cmp_endpoints(*next) == SMALLER) { - // next curve's minimum is the joint vertex. Look if it's max exists in + // next curve's minimum is the joint vertex. Look if its max exists in // the arrangement and insert lexicographically second_point = max_functor(*next); obj = pl.locate(second_point); @@ -639,8 +639,7 @@ bool are_holes_and_boundary_pairwise_disjoint typedef typename Polygon_set_2::Arrangement_on_surface_2 Arrangement_2; - /* Should be perfored more efficeintly than using sweep and than - * difference(). + /* Should be perfored more efficiently than using sweep and then difference(). * * Use sweep to find intersections on the interior of curves (not on vertices) * and overlapping edges which are not allowed (note that 0/1 dimension @@ -758,7 +757,7 @@ bool are_holes_and_boundary_pairwise_disjoint /* A valid polygon with holes is : * 1 - Has empty or closed boundary and all the holes are closed * 2 - The PWH is relatively simple polygon (holes are simple...) - * 3 - Has it's boundary oriented counterclockwise and the holes oriented + * 3 - Has its boundary oriented counterclockwise and the holes oriented * clockwise * 4 - All the segments (boundary and holes) do not cross or intersect in their * relative interior diff --git a/Boolean_set_operations_2/test/Boolean_set_operations_2/bop_test_suite_generator.cpp b/Boolean_set_operations_2/test/Boolean_set_operations_2/bop_test_suite_generator.cpp index 7b96e38151c..43c203ed190 100644 --- a/Boolean_set_operations_2/test/Boolean_set_operations_2/bop_test_suite_generator.cpp +++ b/Boolean_set_operations_2/test/Boolean_set_operations_2/bop_test_suite_generator.cpp @@ -227,7 +227,7 @@ int main(int argc, char *argv[]) write_complement_to_file(out, p1); write_complement_to_file(out, p2); std::cout<::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_Core/include/CGAL/CORE/poly/Sturm.h b/CGAL_Core/include/CGAL/CORE/poly/Sturm.h index 2fe00e3706d..2ab25c00835 100644 --- a/CGAL_Core/include/CGAL/CORE/poly/Sturm.h +++ b/CGAL_Core/include/CGAL/CORE/poly/Sturm.h @@ -69,7 +69,7 @@ public: // the array seq is not used! // Hence, one must test these special cases Polynomial * seq; // array of polynomials of length "len+1" - Polynomial g;//GCD of input polynomial P and it's derivative P' + Polynomial g;//GCD of input polynomial P and its derivative P' NT cont;//Content of the square-free part of input polynomial P //Thus P = g * cont * seq[0] static const int N_STOP_ITER = 10000; // Stop IterE after this many iterations. 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 0492e2acc6a..e02f4f91eb5 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/Cartesian_kernel/include/CGAL/Cartesian/Circle_2.h b/Cartesian_kernel/include/CGAL/Cartesian/Circle_2.h index a1a50a6fb9f..318aa6af4c4 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Circle_2.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Circle_2.h @@ -25,6 +25,7 @@ namespace CGAL { template class CircleC2 { + typedef typename R_::Boolean Boolean; typedef typename R_::FT FT; typedef typename R_::RT RT; typedef typename R_::Circle_2 Circle_2; @@ -49,8 +50,8 @@ public: base = Rep(center, squared_radius, orient); } - bool operator==(const CircleC2 &s) const; - bool operator!=(const CircleC2 &s) const; + Boolean operator==(const CircleC2& s) const; + Boolean operator!=(const CircleC2& s) const; const Point_2 & center() const { @@ -69,6 +70,25 @@ public: }; +template < class R > +typename R::Boolean +CircleC2::operator==(const CircleC2 &t) const +{ + if (CGAL::identical(base, t.base)) + return true; + + return center() == t.center() && + squared_radius() == t.squared_radius() && + orientation() == t.orientation(); +} + +template < class R > +typename R::Boolean +CircleC2::operator!=(const CircleC2 &t) const +{ + return !(*this == t); +} + } //namespace CGAL #endif // CGAL_CARTESIAN_CIRCLE_2_H diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Circle_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Circle_3.h index de200ba3e4b..8f2a2fadc8c 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Circle_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Circle_3.h @@ -23,6 +23,8 @@ namespace CGAL { template class CircleC3 { + typedef typename R_::Boolean Boolean; + typedef typename R_::Bounded_side Bounded_side; typedef typename R_::Sphere_3 Sphere_3; typedef typename R_::Plane_3 Plane_3; typedef typename R_::Point_3 Point_3; @@ -130,12 +132,12 @@ public: return diametral_sphere(); } - Point_3 center() const + decltype(auto) center() const { return diametral_sphere().center(); } - FT squared_radius() const + decltype(auto) squared_radius() const { return diametral_sphere().squared_radius(); } @@ -155,7 +157,7 @@ public: return CGAL_PI * CGAL_PI * 4.0 * to_double(squared_radius()); } - FT area_divided_by_pi() const + decltype(auto) area_divided_by_pi() const { return squared_radius(); } @@ -200,15 +202,15 @@ public: (x+mx).sup(),(y+my).sup(),(z+mz).sup()); } - bool operator==(const CircleC3 &) const; - bool operator!=(const CircleC3 &) const; + Boolean operator==(const CircleC3 &) const; + Boolean operator!=(const CircleC3 &) const; - bool has_on(const Point_3 &p) const; - bool has_on_bounded_side(const Point_3 &p) const; - bool has_on_unbounded_side(const Point_3 &p) const; + Boolean has_on(const Point_3 &p) const; + Boolean has_on_bounded_side(const Point_3 &p) const; + Boolean has_on_unbounded_side(const Point_3 &p) const; Bounded_side bounded_side(const Point_3 &p) const; - bool is_degenerate() const + Boolean is_degenerate() const { return diametral_sphere().is_degenerate(); } @@ -217,7 +219,7 @@ public: template < class R > inline -bool +typename R::Boolean CircleC3:: has_on(const typename CircleC3::Point_3 &p) const { @@ -227,7 +229,7 @@ has_on(const typename CircleC3::Point_3 &p) const template < class R > inline -bool +typename R::Boolean CircleC3:: has_on_bounded_side(const typename CircleC3::Point_3 &p) const { @@ -237,7 +239,7 @@ has_on_bounded_side(const typename CircleC3::Point_3 &p) const template < class R > inline -bool +typename R::Boolean CircleC3:: has_on_unbounded_side(const typename CircleC3::Point_3 &p) const { @@ -246,8 +248,7 @@ has_on_unbounded_side(const typename CircleC3::Point_3 &p) const } template < class R > -CGAL_KERNEL_INLINE -Bounded_side +typename R::Bounded_side CircleC3:: bounded_side(const typename CircleC3::Point_3 &p) const { @@ -256,8 +257,7 @@ bounded_side(const typename CircleC3::Point_3 &p) const } template < class R > -CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleC3::operator==(const CircleC3 &t) const { if (CGAL::identical(base, t.base)) @@ -283,8 +283,7 @@ CircleC3::operator==(const CircleC3 &t) const } template < class R > -CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleC3::operator!=(const CircleC3 &t) const { return !(*this == t); diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Direction_2.h b/Cartesian_kernel/include/CGAL/Cartesian/Direction_2.h index 302299d0898..b618d8fa459 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Direction_2.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Direction_2.h @@ -26,6 +26,8 @@ template < class R_ > class DirectionC2 { typedef DirectionC2 Self; + + typedef typename R_::Boolean Boolean; typedef typename R_::FT FT; typedef FT RT; typedef typename R_::Point_2 Point_2; @@ -49,8 +51,8 @@ public: DirectionC2(const FT &x, const FT &y) : base{x, y} {} - bool operator==(const DirectionC2 &d) const; - bool operator!=(const DirectionC2 &d) const; + Boolean operator==(const DirectionC2 &d) const; + Boolean operator!=(const DirectionC2 &d) const; Vector_2 to_vector() const; @@ -66,7 +68,7 @@ public: template < class R > inline -bool +typename R::Boolean DirectionC2::operator==(const DirectionC2 &d) const { if (CGAL::identical(base, d.base)) @@ -76,7 +78,7 @@ DirectionC2::operator==(const DirectionC2 &d) const template < class R > inline -bool +typename R::Boolean DirectionC2::operator!=(const DirectionC2 &d) const { return !( *this == d ); diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Iso_cuboid_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Iso_cuboid_3.h index 82f319261a0..28b65b121dd 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Iso_cuboid_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Iso_cuboid_3.h @@ -26,6 +26,8 @@ namespace CGAL { template < class R_ > class Iso_cuboidC3 { + typedef typename R_::Boolean Boolean; + typedef typename R_::Bounded_side Bounded_side; typedef typename R_::FT FT; typedef typename R_::Iso_cuboid_3 Iso_cuboid_3; typedef typename R_::Point_3 Point_3; @@ -98,8 +100,8 @@ public: Construct_point_3()(max_hx/hw, max_hy/hw, max_hz/hw))); } - typename R::Boolean operator==(const Iso_cuboidC3& s) const; - typename R::Boolean operator!=(const Iso_cuboidC3& s) const; + Boolean operator==(const Iso_cuboidC3& s) const; + Boolean operator!=(const Iso_cuboidC3& s) const; const Point_3 & min BOOST_PREVENT_MACRO_SUBSTITUTION () const { @@ -118,11 +120,11 @@ public: } Bounded_side bounded_side(const Point_3& p) const; - typename R::Boolean has_on(const Point_3& p) const; - typename R::Boolean has_on_boundary(const Point_3& p) const; - typename R::Boolean has_on_bounded_side(const Point_3& p) const; - typename R::Boolean has_on_unbounded_side(const Point_3& p) const; - typename R::Boolean is_degenerate() const; + Boolean has_on(const Point_3& p) const; + Boolean has_on_boundary(const Point_3& p) const; + Boolean has_on_bounded_side(const Point_3& p) const; + Boolean has_on_unbounded_side(const Point_3& p) const; + Boolean is_degenerate() const; const FT & xmin() const; const FT & ymin() const; const FT & zmin() const; @@ -267,7 +269,7 @@ Iso_cuboidC3::volume() const template < class R > CGAL_KERNEL_MEDIUM_INLINE -Bounded_side +typename R::Bounded_side Iso_cuboidC3:: bounded_side(const typename Iso_cuboidC3::Point_3& p) const { diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Line_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Line_3.h index 4a04a70bcbb..a3798844fe0 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Line_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Line_3.h @@ -24,6 +24,7 @@ namespace CGAL { template < class R_ > class LineC3 { + typedef typename R_::Boolean Boolean; typedef typename R_::FT FT; typedef typename R_::Point_3 Point_3; typedef typename R_::Vector_3 Vector_3; @@ -65,8 +66,8 @@ public: LineC3(const Point_3 &p, const Direction_3 &d) { *this = R().construct_line_3_object()(p, d); } - bool operator==(const LineC3 &l) const; - bool operator!=(const LineC3 &l) const; + typename R::Boolean operator==(const LineC3 &l) const; + typename R::Boolean operator!=(const LineC3 &l) const; Plane_3 perpendicular_plane(const Point_3 &p) const; Line_3 opposite() const; @@ -88,13 +89,13 @@ public: Point_3 point(const FT i) const; - bool has_on(const Point_3 &p) const; - bool is_degenerate() const; + Boolean has_on(const Point_3 &p) const; + Boolean is_degenerate() const; }; template < class R > inline -bool +typename R::Boolean LineC3::operator==(const LineC3 &l) const { if (CGAL::identical(base, l.base)) @@ -104,7 +105,7 @@ LineC3::operator==(const LineC3 &l) const template < class R > inline -bool +typename R::Boolean LineC3::operator!=(const LineC3 &l) const { return !(*this == l); @@ -135,7 +136,7 @@ LineC3::opposite() const template < class R > inline -bool +typename R::Boolean LineC3:: has_on(const typename LineC3::Point_3 &p) const { @@ -144,7 +145,7 @@ has_on(const typename LineC3::Point_3 &p) const template < class R > inline -bool +typename R::Boolean LineC3::is_degenerate() const { return to_vector() == NULL_VECTOR; diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Plane_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Plane_3.h index 38a119e1320..27509532ac7 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Plane_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Plane_3.h @@ -169,7 +169,9 @@ inline typename PlaneC3::Point_3 PlaneC3::point() const { - return point_on_plane(*this); + FT x, y, z; + point_on_planeC3(a(), b(), c(), d(), x, y, z); + return R().construct_point_3_object()(x, y, z); } template < class R > @@ -178,7 +180,13 @@ typename PlaneC3::Point_3 PlaneC3:: projection(const typename PlaneC3::Point_3 &p) const { - return projection_plane(p, *this); + FT x, y, z; + projection_planeC3(a(), b(), c(), d(), + R().compute_x_3_object()(p), + R().compute_y_3_object()(p), + R().compute_z_3_object()(p), + x, y, z); + return R().construct_point_3_object()(x, y, z); } template < class R > diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Point_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Point_3.h index e4dcd3357d8..5a107cf969d 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Point_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Point_3.h @@ -107,6 +107,15 @@ public: return base.cartesian_end(); } + typename R_::Boolean operator==(const PointC3 &p) const + { + return base == p.base; + } + typename R_::Boolean operator!=(const PointC3 &p) const + { + return !(*this == p); + } + int dimension() const { return base.dimension(); diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Segment_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Segment_3.h index 8ab1d082d6f..9ce93eba11c 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Segment_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Segment_3.h @@ -25,6 +25,7 @@ namespace CGAL { template < class R_ > class SegmentC3 { + typedef typename R_::Boolean Boolean; typedef typename R_::Point_3 Point_3; typedef typename R_::Direction_3 Direction_3; typedef typename R_::Vector_3 Vector_3; @@ -44,11 +45,11 @@ public: SegmentC3(const Point_3 &sp, const Point_3 &ep) : base{sp, ep} {} - bool has_on(const Point_3 &p) const; - bool collinear_has_on(const Point_3 &p) const; + Boolean has_on(const Point_3 &p) const; + Boolean collinear_has_on(const Point_3 &p) const; - bool operator==(const SegmentC3 &s) const; - bool operator!=(const SegmentC3 &s) const; + Boolean operator==(const SegmentC3 &s) const; + Boolean operator!=(const SegmentC3 &s) const; const Point_3 & source() const { @@ -73,12 +74,12 @@ public: Line_3 supporting_line() const; Segment_3 opposite() const; - bool is_degenerate() const; + Boolean is_degenerate() const; }; template < class R > inline -bool +typename R::Boolean SegmentC3::operator==(const SegmentC3 &s) const { if (CGAL::identical(base, s.base)) @@ -88,7 +89,7 @@ SegmentC3::operator==(const SegmentC3 &s) const template < class R > inline -bool +typename R::Boolean SegmentC3::operator!=(const SegmentC3 &s) const { return !(*this == s); @@ -184,7 +185,7 @@ SegmentC3::opposite() const template < class R > inline -bool +typename R::Boolean SegmentC3::is_degenerate() const { return source() == target(); @@ -192,7 +193,7 @@ SegmentC3::is_degenerate() const template < class R > inline -bool +typename R::Boolean SegmentC3:: has_on(const typename SegmentC3::Point_3 &p) const { @@ -201,7 +202,7 @@ has_on(const typename SegmentC3::Point_3 &p) const template < class R > inline -bool +typename R::Boolean SegmentC3:: collinear_has_on(const typename SegmentC3::Point_3 &p) const { diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Sphere_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Sphere_3.h index 3dee104e4c6..d9b733a666d 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Sphere_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Sphere_3.h @@ -27,6 +27,8 @@ namespace CGAL { template class SphereC3 { + typedef typename R_::Boolean Boolean; + typedef typename R_::Bounded_side Bounded_side; typedef typename R_::FT FT; // https://doc.cgal.org/latest/Manual/devman_code_format.html#secprogramming_conventions typedef typename R_::Point_3 Point_3_; @@ -124,17 +126,17 @@ public: //! precond: ! x.is_degenerate() (when available) // Returns R::ON_POSITIVE_SIDE, R::ON_ORIENTED_BOUNDARY or // R::ON_NEGATIVE_SIDE - typename R::Boolean has_on(const Circle_3 &p) const; - typename R::Boolean has_on(const Point_3_ &p) const; - typename R::Boolean has_on_boundary(const Point_3_ &p) const; - typename R::Boolean has_on_positive_side(const Point_3_ &p) const; - typename R::Boolean has_on_negative_side(const Point_3_ &p) const; + Boolean has_on(const Circle_3 &p) const; + Boolean has_on(const Point_3_ &p) const; + Boolean has_on_boundary(const Point_3_ &p) const; + Boolean has_on_positive_side(const Point_3_ &p) const; + Boolean has_on_negative_side(const Point_3_ &p) const; - typename R_::Bounded_side bounded_side(const Point_3_ &p) const; + Bounded_side bounded_side(const Point_3_ &p) const; //! precond: ! x.is_degenerate() (when available) // Returns R::ON_BOUNDED_SIDE, R::ON_BOUNDARY or R::ON_UNBOUNDED_SIDE - typename R::Boolean has_on_bounded_side(const Point_3_ &p) const; - typename R::Boolean has_on_unbounded_side(const Point_3_ &p) const; + Boolean has_on_bounded_side(const Point_3_ &p) const; + Boolean has_on_unbounded_side(const Point_3_ &p) const; }; template < class R > @@ -163,6 +165,7 @@ typename R::Oriented_side SphereC3:: oriented_side(const typename SphereC3::Point_3_ &p) const { + typedef typename R::Oriented_side Oriented_side; return enum_cast(bounded_side(p)) * orientation(); } @@ -172,6 +175,7 @@ typename R::Bounded_side SphereC3:: bounded_side(const typename SphereC3::Point_3_ &p) const { + typedef typename R::Bounded_side Bounded_side; return enum_cast(compare(squared_radius(), squared_distance(center(), p))); } diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Tetrahedron_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Tetrahedron_3.h index c20733313af..ef543fcc308 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Tetrahedron_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Tetrahedron_3.h @@ -143,7 +143,7 @@ oriented_side(const typename TetrahedronC3::Point_3 &p) const { typename R::Orientation o = orientation(); if (o != ZERO) - return enum_cast(bounded_side(p)) * o; + return enum_cast(bounded_side(p)) * o; CGAL_kernel_assertion (!is_degenerate()); return ON_ORIENTED_BOUNDARY; diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Triangle_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Triangle_3.h index 7e61db2dee2..40cae8796e0 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Triangle_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Triangle_3.h @@ -25,6 +25,7 @@ namespace CGAL { template class TriangleC3 { + typedef typename R_::Boolean Boolean; typedef typename R_::FT FT; typedef typename R_::Point_3 Point_3; typedef typename R_::Vector_3 Vector_3; @@ -44,13 +45,13 @@ public: TriangleC3(const Point_3 &p, const Point_3 &q, const Point_3 &r) : base{p, q, r} {} - bool operator==(const TriangleC3 &t) const; - bool operator!=(const TriangleC3 &t) const; + Boolean operator==(const TriangleC3 &t) const; + Boolean operator!=(const TriangleC3 &t) const; Plane_3 supporting_plane() const; - bool has_on(const Point_3 &p) const; - bool is_degenerate() const; + Boolean has_on(const Point_3 &p) const; + Boolean is_degenerate() const; const Point_3 & vertex(int i) const; const Point_3 & operator[](int i) const; @@ -59,7 +60,7 @@ public: }; template < class R > -bool +typename R::Boolean TriangleC3::operator==(const TriangleC3 &t) const { if (CGAL::identical(base, t.base)) @@ -75,7 +76,7 @@ TriangleC3::operator==(const TriangleC3 &t) const template < class R > inline -bool +typename R::Boolean TriangleC3::operator!=(const TriangleC3 &t) const { return !(*this == t); @@ -118,7 +119,7 @@ TriangleC3::supporting_plane() const template < class R > inline -bool +typename R::Boolean TriangleC3:: has_on(const typename TriangleC3::Point_3 &p) const { @@ -127,7 +128,7 @@ has_on(const typename TriangleC3::Point_3 &p) const } template < class R > -bool +typename R::Boolean TriangleC3::is_degenerate() const { return collinear(vertex(0),vertex(1),vertex(2)); diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Vector_2.h b/Cartesian_kernel/include/CGAL/Cartesian/Vector_2.h index 1ff9d12ee7b..b762bddb36b 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Vector_2.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Vector_2.h @@ -107,7 +107,7 @@ public: template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean operator==(const VectorC2 &v, const VectorC2 &w) { return w.x() == v.x() && w.y() == v.y(); @@ -115,7 +115,7 @@ operator==(const VectorC2 &v, const VectorC2 &w) template < class R > inline -bool +typename R::Boolean operator!=(const VectorC2 &v, const VectorC2 &w) { return !(v == w); @@ -123,7 +123,7 @@ operator!=(const VectorC2 &v, const VectorC2 &w) template < class R > inline -bool +typename R::Boolean operator==(const VectorC2 &v, const Null_vector &) { return CGAL_NTS is_zero(v.x()) && CGAL_NTS is_zero(v.y()); @@ -131,7 +131,7 @@ operator==(const VectorC2 &v, const Null_vector &) template < class R > inline -bool +typename R::Boolean operator==(const Null_vector &n, const VectorC2 &v) { return v == n; @@ -139,7 +139,7 @@ operator==(const Null_vector &n, const VectorC2 &v) template < class R > inline -bool +typename R::Boolean operator!=(const VectorC2 &v, const Null_vector &n) { return !(v == n); @@ -147,7 +147,7 @@ operator!=(const VectorC2 &v, const Null_vector &n) template < class R > inline -bool +typename R::Boolean operator!=(const Null_vector &n, const VectorC2 &v) { return !(v == n); diff --git a/Cartesian_kernel/include/CGAL/Cartesian/Vector_3.h b/Cartesian_kernel/include/CGAL/Cartesian/Vector_3.h index 89540fd9b0d..19543f9dcf4 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/Vector_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/Vector_3.h @@ -142,15 +142,15 @@ public: template < class R > inline -bool +typename R::Boolean operator==(const VectorC3 &v, const VectorC3 &w) { - return w.x() == v.x() && w.y() == v.y() && w.z() == v.z(); + return CGAL_AND_3(w.x() == v.x(), w.y() == v.y(), w.z() == v.z()); } template < class R > inline -bool +typename R::Boolean operator!=(const VectorC3 &v, const VectorC3 &w) { return !(v == w); @@ -158,16 +158,15 @@ operator!=(const VectorC3 &v, const VectorC3 &w) template < class R > inline -bool +typename R::Boolean operator==(const VectorC3 &v, const Null_vector &) { - return CGAL_NTS is_zero(v.x()) && CGAL_NTS is_zero(v.y()) && - CGAL_NTS is_zero(v.z()); + return CGAL_AND_3(CGAL_NTS is_zero(v.x()), CGAL_NTS is_zero(v.y()), CGAL_NTS is_zero(v.z())); } template < class R > inline -bool +typename R::Boolean operator==(const Null_vector &n, const VectorC3 &v) { return v == n; @@ -175,7 +174,7 @@ operator==(const Null_vector &n, const VectorC3 &v) template < class R > inline -bool +typename R::Boolean operator!=(const VectorC3 &v, const Null_vector &n) { return !(v == n); @@ -183,7 +182,7 @@ operator!=(const VectorC3 &v, const Null_vector &n) template < class R > inline -bool +typename R::Boolean operator!=(const Null_vector &n, const VectorC3 &v) { return !(v == n); diff --git a/Cartesian_kernel/include/CGAL/Cartesian/basic_constructions_3.h b/Cartesian_kernel/include/CGAL/Cartesian/basic_constructions_3.h index 1760074c988..f00c2ca8791 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/basic_constructions_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/basic_constructions_3.h @@ -17,7 +17,6 @@ #ifndef CGAL_CARTESIAN_BASIC_CONSTRUCTIONS_3_H #define CGAL_CARTESIAN_BASIC_CONSTRUCTIONS_3_H -#include #include #include diff --git a/Cartesian_kernel/include/CGAL/Cartesian/function_objects.h b/Cartesian_kernel/include/CGAL/Cartesian/function_objects.h index ffb1056f147..937bcbe6e23 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/function_objects.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/function_objects.h @@ -36,20 +36,20 @@ namespace CartesianKernelFunctors { template class Angle_2 { + typedef typename K::Angle Angle; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; - public: - typedef typename K::Angle result_type; - result_type + public: + Angle operator()(const Vector_2& u, const Vector_2& v) const { return angleC2(u.x(), u.y(), v.x(), v.y()); } - result_type + Angle operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { return angleC2(p.x(), p.y(), q.x(), q.y(), r.x(), r.y()); } - result_type + Angle operator()(const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& s) const { @@ -63,18 +63,19 @@ namespace CartesianKernelFunctors { template class Angle_3 { + typedef typename K::Angle Angle; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; - public: - typedef typename K::Angle result_type; - result_type + public: + Angle operator()(const Vector_3& u, const Vector_3& v) const { return angleC3(u.x(), u.y(), u.z(), v.x(), v.y(), v.z()); } - result_type + + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return angleC3(p.x(), p.y(), p.z(), @@ -82,7 +83,7 @@ namespace CartesianKernelFunctors { r.x(), r.y(), r.z()); } - result_type + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -92,7 +93,7 @@ namespace CartesianKernelFunctors { s.x(), s.y(), s.z()); } - result_type + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Vector_3& n) const { @@ -103,18 +104,17 @@ namespace CartesianKernelFunctors { template class Are_parallel_2 { + typedef typename K::Boolean Boolean; typedef typename K::Line_2 Line_2; typedef typename K::Segment_2 Segment_2; typedef typename K::Ray_2 Ray_2; public: - typedef typename K::Boolean result_type; - - result_type + Boolean operator()(const Line_2& l1, const Line_2& l2) const { return parallelC2(l1.a(), l1.b(), l2.a(), l2.b()); } - result_type + Boolean operator()(const Segment_2& s1, const Segment_2& s2) const { return parallelC2(s1.source().x(), s1.source().y(), s1.target().x(), s1.target().y(), @@ -122,7 +122,7 @@ namespace CartesianKernelFunctors { s2.target().x(), s2.target().y()); } - result_type + Boolean operator()(const Ray_2& r1, const Ray_2& r2) const { return parallelC2(r1.source().x(), r1.source().y(), r1.second_point().x(), r1.second_point().y(), @@ -134,28 +134,27 @@ namespace CartesianKernelFunctors { template class Are_parallel_3 { + typedef typename K::Boolean Boolean; typedef typename K::Line_3 Line_3; typedef typename K::Segment_3 Segment_3; typedef typename K::Ray_3 Ray_3; typedef typename K::Plane_3 Plane_3; public: - typedef typename K::Boolean result_type; - - result_type + Boolean operator()(const Line_3& l1, const Line_3& l2) const { return parallelC3( l1.to_vector().x(), l1.to_vector().y(), l1.to_vector().z(), l2.to_vector().x(), l2.to_vector().y(), l2.to_vector().z()); } - result_type + Boolean operator()(const Plane_3& h1, const Plane_3& h2) const { return parallelC3(h1.a(), h1.b(), h1.c(), h2.a(), h2.b(), h2.c()); } - result_type + Boolean operator()(const Segment_3& s1, const Segment_3& s2) const { return parallelC3(s1.source().x(), s1.source().y(), s1.source().z(), s1.target().x(), s1.target().y(), s1.target().z(), @@ -163,7 +162,7 @@ namespace CartesianKernelFunctors { s2.target().x(), s2.target().y(), s2.target().z()); } - result_type + Boolean operator()(const Ray_3& r1, const Ray_3& r2) const { return parallelC3(r1.source().x(), r1.source().y(), r1.source().z(), r1.second_point().x(), r1.second_point().y(), r1.second_point().z(), @@ -175,14 +174,14 @@ namespace CartesianKernelFunctors { template class Bounded_side_2 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_2 Point_2; typedef typename K::Circle_2 Circle_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Circle_2& c, const Point_2& p) const { typename K::Compute_squared_distance_2 squared_distance; @@ -190,7 +189,7 @@ namespace CartesianKernelFunctors { squared_distance(c.center(),p))); } - result_type + Bounded_side operator()( const Triangle_2& t, const Point_2& p) const { typename K::Collinear_are_ordered_along_line_2 @@ -213,7 +212,7 @@ namespace CartesianKernelFunctors { : ON_UNBOUNDED_SIDE; } - result_type + Bounded_side operator()( const Iso_rectangle_2& r, const Point_2& p) const { bool x_incr = (r.xmin() < p.x()) && (p.x() < r.xmax()), @@ -236,24 +235,24 @@ namespace CartesianKernelFunctors { template class Bounded_side_3 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Sphere_3 Sphere_3; typedef typename K::Circle_3 Circle_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Iso_cuboid_3 Iso_cuboid_3; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Sphere_3& s, const Point_3& p) const { return s.rep().bounded_side(p); } - result_type + Bounded_side operator()( const Circle_3& s, const Point_3& p) const { return s.rep().bounded_side(p); } - result_type + Bounded_side operator()( const Tetrahedron_3& t, const Point_3& p) const { FT alpha, beta, gamma, denom; @@ -273,7 +272,7 @@ namespace CartesianKernelFunctors { return ON_BOUNDED_SIDE; } - result_type + Bounded_side operator()( const Iso_cuboid_3& c, const Point_3& p) const { return c.rep().bounded_side(p); @@ -284,11 +283,11 @@ namespace CartesianKernelFunctors { template class Collinear_are_ordered_along_line_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { CGAL_kernel_exactness_precondition( collinear(p, q, r) ); @@ -300,11 +299,11 @@ namespace CartesianKernelFunctors { template class Collinear_are_ordered_along_line_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { CGAL_kernel_exactness_precondition( collinear(p, q, r) ); @@ -317,11 +316,11 @@ namespace CartesianKernelFunctors { template class Collinear_are_strictly_ordered_along_line_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { CGAL_kernel_exactness_precondition( collinear(p, q, r) ); @@ -333,11 +332,11 @@ namespace CartesianKernelFunctors { template class Collinear_are_strictly_ordered_along_line_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { CGAL_kernel_exactness_precondition( collinear(p, q, r) ); @@ -350,13 +349,13 @@ namespace CartesianKernelFunctors { template class Collinear_has_on_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Ray_2 Ray_2; typedef typename K::Segment_2 Segment_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Ray_2& r, const Point_2& p) const { const Point_2 & source = r.source(); @@ -378,7 +377,7 @@ namespace CartesianKernelFunctors { } // switch } - result_type + Boolean operator()( const Segment_2& s, const Point_2& p) const { return collinear_are_ordered_along_line(s.source(), p, s.target()); @@ -388,16 +387,17 @@ namespace CartesianKernelFunctors { template class Collinear_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Orientation_2 Orientation_2; - Orientation_2 o; - public: - typedef typename K::Boolean result_type; + Orientation_2 o; + + public: Collinear_2() {} Collinear_2(const Orientation_2 o_) : o(o_) {} - result_type + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { return o(p, q, r) == COLLINEAR; } }; @@ -405,11 +405,11 @@ namespace CartesianKernelFunctors { template class Collinear_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return collinearC3(p.x(), p.y(), p.z(), @@ -421,11 +421,11 @@ namespace CartesianKernelFunctors { template class Compare_angle_with_x_axis_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Direction_2 Direction_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Direction_2& d1, const Direction_2& d2) const { return compare_angle_with_x_axisC2(d1.dx(), d1.dy(), d2.dx(), d2.dy()); @@ -435,25 +435,25 @@ namespace CartesianKernelFunctors { template class Compare_distance_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { return cmp_dist_to_pointC2(p.x(), p.y(), q.x(), q.y(), r.x(), r.y()); } template - Needs_FT + Needs_FT operator()(const T1& p, const T2& q, const T3& r) const { return CGAL::compare(squared_distance(p, q), squared_distance(p, r)); } template - Needs_FT + Needs_FT operator()(const T1& p, const T2& q, const T3& r, const T4& s) const { return CGAL::compare(squared_distance(p, q), squared_distance(r, s)); @@ -560,12 +560,12 @@ namespace CartesianKernelFunctors { template class Compare_distance_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; typedef typename K::Segment_3 Segment_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return cmp_dist_to_pointC3(p.x(), p.y(), p.z(), @@ -573,33 +573,33 @@ namespace CartesianKernelFunctors { r.x(), r.y(), r.z()); } - result_type + Comparison_result operator()(const Point_3& p1, const Segment_3& s1, const Segment_3& s2) const { return internal::compare_distance_pssC3(p1,s1,s2, K()); } - result_type + Comparison_result operator()(const Point_3& p1, const Point_3& p2, const Segment_3& s2) const { return internal::compare_distance_ppsC3(p1,p2,s2, K()); } - result_type + Comparison_result operator()(const Point_3& p1, const Segment_3& s2, const Point_3& p2) const { return opposite(internal::compare_distance_ppsC3(p1,p2,s2, K())); } template - Needs_FT + Needs_FT operator()(const T1& p, const T2& q, const T3& r) const { return CGAL::compare(squared_distance(p, q), squared_distance(p, r)); } template - Needs_FT + Needs_FT operator()(const T1& p, const T2& q, const T3& r, const T4& s) const { return CGAL::compare(squared_distance(p, q), squared_distance(r, s)); @@ -614,8 +614,7 @@ namespace CartesianKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Comparison_result Comparison_result; - typedef Comparison_result result_type; - +public: Comparison_result operator()(const Point_2& r, const Weighted_point_2& p, const Weighted_point_2& q) const @@ -629,14 +628,13 @@ namespace CartesianKernelFunctors { template class Compare_signed_distance_to_line_2 { - typedef typename K::Point_2 Point_2; - typedef typename K::Line_2 Line_2; - typedef typename K::Equal_2 Equal_2; + typedef typename K::Comparison_result Comparison_result; + typedef typename K::Point_2 Point_2; + typedef typename K::Line_2 Line_2; + typedef typename K::Equal_2 Equal_2; public: - typedef typename K::Comparison_result result_type; - - result_type + Comparison_result operator()(const Point_2& a, const Point_2& b, const Point_2& c, const Point_2& d) const { @@ -649,7 +647,7 @@ namespace CartesianKernelFunctors { d.x(), d.y()); } - result_type + Comparison_result operator()(const Line_2& l, const Point_2& p, const Point_2& q) const { return cmp_signed_dist_to_directionC2(l.a(), l.b(), @@ -661,12 +659,12 @@ namespace CartesianKernelFunctors { template class Compare_squared_radius_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; typedef typename K::FT FT; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s, const FT& ft) const { FT num, den; @@ -678,7 +676,7 @@ namespace CartesianKernelFunctors { return CGAL::compare(num, den * ft); } - result_type + Comparison_result operator()(const Point_3& p, const Point_3& q, const Point_3& r, const FT& ft) const { FT num, den; @@ -689,7 +687,7 @@ namespace CartesianKernelFunctors { return CGAL::compare(num, den * ft); } - result_type + Comparison_result operator()(const Point_3& p, const Point_3& q, const FT& ft) const { FT num, den; @@ -699,7 +697,7 @@ namespace CartesianKernelFunctors { return CGAL::compare(num, den * ft); } - result_type + Comparison_result operator()(const Point_3&, const FT& ft) const { return - CGAL_NTS sign(ft); @@ -707,23 +705,22 @@ namespace CartesianKernelFunctors { }; - template class Compare_slope_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; typedef typename K::Segment_2 Segment_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Line_2& l1, const Line_2& l2) const { return compare_slopesC2(l1.a(), l1.b(), l2.a(), l2.b()); } - result_type + Comparison_result operator()(const Segment_2& s1, const Segment_2& s2) const { return compare_slopesC2(s1.source().x(), s1.source().y(), @@ -732,7 +729,7 @@ namespace CartesianKernelFunctors { s2.target().x(), s2.target().y()); } - result_type + Comparison_result operator()(const Point_2& s1s, const Point_2& s1t, const Point_2& s2s, const Point_2& s2t) const { return compare_slopesC2(s1s.x(), s1s.y(), @@ -745,30 +742,30 @@ namespace CartesianKernelFunctors { template class Compare_x_at_y_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Line_2& h) const { return compare_y_at_xC2(p.y(), p.x(), h.b(), h.a(), h.c()); } - result_type + Comparison_result operator()( const Point_2& p, const Line_2& h1, const Line_2& h2) const { return compare_y_at_xC2(p.y(), h1.b(), h1.a(), h1.c(), h2.b(), h2.a(), h2.c()); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h) const { return compare_y_at_xC2(l1.b(), l1.a(), l1.c(), l2.b(), l2.a(), l2.c(), h.b(), h.a(), h.c()); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { @@ -780,11 +777,11 @@ namespace CartesianKernelFunctors { template class Compare_xyz_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return compare_lexicographically_xyzC3(p.x(), p.y(), p.z(), @@ -795,11 +792,11 @@ namespace CartesianKernelFunctors { template class Compare_xy_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { return compare_lexicographically_xyC2(p.x(), p.y(), q.x(), q.y()); } }; @@ -807,11 +804,11 @@ namespace CartesianKernelFunctors { template class Compare_xy_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return compare_lexicographically_xyC2(p.x(), p.y(), q.x(), q.y()); } }; @@ -819,27 +816,27 @@ namespace CartesianKernelFunctors { template class Compare_x_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { return CGAL::compare(p.x(), q.x()); } - result_type + Comparison_result operator()( const Point_2& p, const Line_2& l, const Line_2& h) const { return compare_xC2(p.x(), l.a(), l.b(), l.c(), h.a(), h.b(), h.c()); } - result_type + Comparison_result operator()( const Line_2& l, const Line_2& h1, const Line_2& h2) const { return compare_xC2(l.a(), l.b(), l.c(), h1.a(), h1.b(), h1.c(), h2.a(), h2.b(), h2.c()); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { @@ -851,11 +848,11 @@ namespace CartesianKernelFunctors { template class Compare_x_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return CGAL::compare(p.x(), q.x()); } }; @@ -863,11 +860,11 @@ namespace CartesianKernelFunctors { template class Compare_yx_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { return compare_lexicographically_xyC2(p.y(), p.x(), q.y(), q.x()); } }; @@ -875,31 +872,31 @@ namespace CartesianKernelFunctors { template class Compare_y_at_x_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; typedef typename K::Segment_2 Segment_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Line_2& h) const { return compare_y_at_xC2(p.x(), p.y(), h.a(), h.b(), h.c()); } - result_type + Comparison_result operator()( const Point_2& p, const Line_2& h1, const Line_2& h2) const { return compare_y_at_xC2(p.x(), h1.a(), h1.b(), h1.c(), h2.a(), h2.b(), h2.c()); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h) const { return compare_y_at_xC2(l1.a(), l1.b(), l1.c(), l2.a(), l2.b(), l2.c(), h.a(), h.b(), h.c()); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { @@ -907,7 +904,7 @@ namespace CartesianKernelFunctors { h1.a(), h1.b(), h1.c(), h2.a(), h2.b(), h2.c()); } - result_type + Comparison_result operator()( const Point_2& p, const Segment_2& s) const { return compare_y_at_xC2(p.x(), p.y(), @@ -915,7 +912,7 @@ namespace CartesianKernelFunctors { s.target().x(), s.target().y()); } - result_type + Comparison_result operator()( const Point_2& p, const Segment_2& s1, const Segment_2& s2) const { @@ -930,16 +927,16 @@ namespace CartesianKernelFunctors { template class Compare_y_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { return CGAL::compare(p.y(), q.y()); } - result_type + Comparison_result operator()( const Point_2& p, const Line_2& l1, const Line_2& l2) const { return compare_xC2(p.y(), @@ -947,14 +944,14 @@ namespace CartesianKernelFunctors { l2.b(), l2.a(), l2.c()); } - result_type + Comparison_result operator()( const Line_2& l, const Line_2& h1, const Line_2& h2) const { return compare_xC2(l.b(), l.a(), l.c(), h1.b(), h1.a(), h1.c(), l.b(), l.a(), l.c(), h2.b(), h2.a(), h2.c()); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { @@ -966,11 +963,11 @@ namespace CartesianKernelFunctors { template class Compare_y_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return CGAL::compare(p.y(), q.y()); } }; @@ -978,11 +975,11 @@ namespace CartesianKernelFunctors { template class Compare_z_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return CGAL::compare(p.z(), q.z()); } }; @@ -994,10 +991,7 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; public: - - typedef double result_type; - - result_type + double operator() (const Circle_3 & c) const // { return c.rep().approximate_area(); } { return CGAL_PI * to_double(c.squared_radius()); } @@ -1010,10 +1004,7 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; public: - - typedef double result_type; - - result_type + double operator() (const Circle_3 & c) const // { return c.rep().approximate_squared_length(); } { return CGAL_PI * CGAL_PI * 4.0 * to_double(c.squared_radius()); } @@ -1027,10 +1018,9 @@ namespace CartesianKernelFunctors { typedef typename K::Iso_rectangle_2 Iso_rectangle_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Point_2 Point_2; - public: - typedef FT result_type; - result_type + public: + FT operator()( const Point_2& p, const Point_2& q, const Point_2& r ) const { FT v1x = q.x() - p.x(); @@ -1040,11 +1030,11 @@ namespace CartesianKernelFunctors { return determinant(v1x, v1y, v2x, v2y)/2; } - result_type + FT operator()( const Iso_rectangle_2& r ) const { return (r.xmax()-r.xmin()) * (r.ymax()-r.ymin()); } - result_type + FT operator()( const Triangle_2& t ) const { return t.area(); } }; @@ -1053,13 +1043,9 @@ namespace CartesianKernelFunctors { class Compute_area_divided_by_pi_3 { typedef typename K::Circle_3 Circle_3; - typedef typename K::FT FT; public: - - typedef FT result_type; - - result_type + decltype(auto) // FT or const FT& operator()(const Circle_3 & c) const { return c.rep().area_divided_by_pi(); } @@ -1070,10 +1056,9 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Vector_2& v, const Vector_2& w) const { return determinant(v.x(), v.y(), w.x(), w.y()); @@ -1085,10 +1070,9 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Vector_3& v, const Vector_3& w, const Vector_3& t) const { return determinant(v.x(), v.y(), v.z(), @@ -1102,10 +1086,9 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Vector_2& v, const Vector_2& w) const { return v.x() * w.x() + v.y() * w.y(); @@ -1117,10 +1100,9 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Vector_3& v, const Vector_3& w) const { return v.x() * w.x() + v.y() * w.y() + v.z() * w.z(); @@ -1133,16 +1115,15 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Triangle_3 Triangle_3; - public: - typedef FT result_type; - result_type + public: + FT operator()( const Triangle_3& t ) const { return this->operator()(t.vertex(0), t.vertex(1), t.vertex(2)); } - result_type + FT operator()( const Point_3& p, const Point_3& q, const Point_3& r ) const { return squared_areaC3(p.x(), p.y(), p.z(), @@ -1157,10 +1138,9 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; - public: - typedef FT result_type; - result_type + public: + FT operator()( const Point_2& p, const Point_2& q) const { return squared_distanceC2(p.x(), p.y(), q.x(), q.y()); @@ -1170,14 +1150,11 @@ namespace CartesianKernelFunctors { template class Compute_squared_length_divided_by_pi_square_3 { - typedef typename K::Circle_3 Circle_3; typedef typename K::FT FT; + typedef typename K::Circle_3 Circle_3; public: - - typedef FT result_type; - - result_type + FT operator() (const Circle_3 & c) const { return c.rep().squared_length_divided_by_pi_square(); } @@ -1189,31 +1166,27 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Circle_2 Circle_2; - public: - typedef FT result_type; - result_type + public: + decltype(auto) operator()( const Circle_2& c) const { return c.rep().squared_radius(); } - result_type + FT operator()( const Point_2& /*p*/) const { return FT(0); } - result_type + FT operator()( const Point_2& p, const Point_2& q) const { return squared_radiusC2(p.x(), p.y(), q.x(), q.y()); } - result_type + FT operator()( const Point_2& p, const Point_2& q, const Point_2& r) const { return squared_radiusC2(p.x(), p.y(), q.x(), q.y(), r.x(), r.y()); } }; } //namespace CartesianKernelFunctors -// For the non specialized template will do the right thing, -// namely return a copy of an FT - namespace CartesianKernelFunctors { template @@ -1223,22 +1196,21 @@ namespace CartesianKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Sphere_3 Sphere_3; typedef typename K::Circle_3 Circle_3; - public: - typedef FT result_type; - result_type +public: + decltype(auto) operator()( const Sphere_3& s) const { return s.rep().squared_radius(); } - result_type + decltype(auto) operator()( const Circle_3& c) const { return c.rep().squared_radius(); } - result_type + FT operator()( const Point_3& /*p*/) const { return FT(0); } - result_type + FT operator()( const Point_3& p, const Point_3& q) const { FT num, den; @@ -1248,7 +1220,7 @@ namespace CartesianKernelFunctors { return num / den; } - result_type + FT operator()( const Point_3& p, const Point_3& q, const Point_3& r) const { FT num, den; @@ -1259,7 +1231,7 @@ namespace CartesianKernelFunctors { return num / den; } - result_type + FT operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -1280,10 +1252,9 @@ namespace CartesianKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Iso_cuboid_3 Iso_cuboid_3; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Point_3& p0, const Point_3& p1, const Point_3& p2, const Point_3& p3) const { @@ -1292,14 +1263,14 @@ namespace CartesianKernelFunctors { p3.x()-p0.x(), p3.y()-p0.y(), p3.z()-p0.z())/6; } - result_type + FT operator()( const Tetrahedron_3& t ) const { return this->operator()(t.vertex(0), t.vertex(1), t.vertex(2), t.vertex(3)); } - result_type + FT operator()( const Iso_cuboid_3& c ) const { return c.rep().volume(); } }; @@ -1308,20 +1279,17 @@ namespace CartesianKernelFunctors { template class Compute_x_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().x(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().x(); @@ -1331,20 +1299,17 @@ namespace CartesianKernelFunctors { template class Compute_x_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().x(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().x(); @@ -1355,20 +1320,17 @@ namespace CartesianKernelFunctors { template class Compute_y_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().y(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().y(); @@ -1379,20 +1341,17 @@ namespace CartesianKernelFunctors { template class Compute_y_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().y(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().y(); @@ -1402,38 +1361,30 @@ namespace CartesianKernelFunctors { template class Compute_z_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().z(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().z(); } }; - - template class Compute_dx_2 { - typedef typename K::FT FT; typedef typename K::Direction_2 Direction_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Direction_2& d) const { return d.rep().dx(); @@ -1443,13 +1394,10 @@ namespace CartesianKernelFunctors { template class Compute_dx_3 { - typedef typename K::FT FT; typedef typename K::Direction_3 Direction_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Direction_3& d) const { return d.rep().dx(); @@ -1459,13 +1407,10 @@ namespace CartesianKernelFunctors { template class Compute_dy_2 { - typedef typename K::FT FT; typedef typename K::Direction_2 Direction_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Direction_2& d) const { return d.rep().dy(); @@ -1475,13 +1420,10 @@ namespace CartesianKernelFunctors { template class Compute_dy_3 { - typedef typename K::FT FT; typedef typename K::Direction_3 Direction_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Direction_3& d) const { return d.rep().dy(); @@ -1491,13 +1433,10 @@ namespace CartesianKernelFunctors { template class Compute_dz_3 { - typedef typename K::FT FT; typedef typename K::Direction_3 Direction_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Direction_3& d) const { return d.rep().dz(); @@ -1507,20 +1446,17 @@ namespace CartesianKernelFunctors { template class Compute_hx_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().hx(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().hx(); @@ -1530,20 +1466,17 @@ namespace CartesianKernelFunctors { template class Compute_hx_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hx(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hx(); @@ -1553,20 +1486,17 @@ namespace CartesianKernelFunctors { template class Compute_hy_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().hy(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().hy(); @@ -1576,20 +1506,17 @@ namespace CartesianKernelFunctors { template class Compute_hy_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hy(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hy(); @@ -1599,20 +1526,17 @@ namespace CartesianKernelFunctors { template class Compute_hz_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hz(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hz(); @@ -1622,20 +1546,17 @@ namespace CartesianKernelFunctors { template class Compute_hw_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().hw(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().hw(); @@ -1645,20 +1566,17 @@ namespace CartesianKernelFunctors { template class Compute_hw_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hw(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hw(); @@ -1669,13 +1587,10 @@ namespace CartesianKernelFunctors { template class Compute_xmin_2 { - typedef typename K::FT FT; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Iso_rectangle_2& r) const { return (r.min)().x(); @@ -1685,13 +1600,10 @@ namespace CartesianKernelFunctors { template class Compute_xmax_2 { - typedef typename K::FT FT; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Iso_rectangle_2& r) const { return (r.max)().x(); @@ -1701,13 +1613,10 @@ namespace CartesianKernelFunctors { template class Compute_ymin_2 { - typedef typename K::FT FT; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Iso_rectangle_2& r) const { return (r.min)().y(); @@ -1717,29 +1626,24 @@ namespace CartesianKernelFunctors { template class Compute_ymax_2 { - typedef typename K::FT FT; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; public: - typedef const FT& result_type; - - result_type + decltype(auto) operator()(const Iso_rectangle_2& r) const { return (r.max)().y(); } }; - template class Construct_barycenter_2 { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; - public: - typedef Point_2 result_type; - result_type + public: + Point_2 operator()(const Point_2& p1, const FT&w1, const Point_2& p2) const { typename K::Construct_point_2 construct_point_2; @@ -1748,7 +1652,7 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Point_2& p1, const FT& w1, const Point_2& p2, const FT& w2) const { typename K::Construct_point_2 construct_point_2; @@ -1757,7 +1661,7 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Point_2& p1, const FT& w1, const Point_2& p2, const FT& w2, const Point_2& p3) const { @@ -1767,7 +1671,7 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Point_2& p1, const FT& w1, const Point_2& p2, const FT& w2, const Point_2& p3, const FT& w3) const { @@ -1777,7 +1681,7 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Point_2& p1, const FT& w1, const Point_2& p2, const FT& w2, const Point_2& p3, const FT& w3, const Point_2& p4) const { @@ -1787,7 +1691,7 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Point_2& p1, const FT& w1, const Point_2& p2, const FT& w2, const Point_2& p3, const FT& w3, const Point_2& p4, const FT& w4) const { @@ -1804,10 +1708,9 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Point_3 Point_3; - public: - typedef Point_3 result_type; - result_type + public: + Point_3 operator()(const Point_3& p1, const FT&w1, const Point_3& p2) const { typename K::Construct_point_3 construct_point_3; @@ -1816,7 +1719,7 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Point_3& p1, const FT& w1, const Point_3& p2, const FT& w2) const { typename K::Construct_point_3 construct_point_3; @@ -1825,7 +1728,7 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Point_3& p1, const FT& w1, const Point_3& p2, const FT& w2, const Point_3& p3) const { @@ -1835,7 +1738,7 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Point_3& p1, const FT& w1, const Point_3& p2, const FT& w2, const Point_3& p3, const FT& w3) const { @@ -1846,7 +1749,7 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Point_3& p1, const FT& w1, const Point_3& p2, const FT& w2, const Point_3& p3, const FT& w3, const Point_3& p4) const { @@ -1857,7 +1760,7 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Point_3& p1, const FT& w1, const Point_3& p2, const FT& w2, const Point_3& p3, const FT& w3, const Point_3& p4, const FT& w4) const { @@ -1882,16 +1785,15 @@ namespace CartesianKernelFunctors { Construct_orthogonal_vector_3; Construct_cross_product_vector_3 cp; Construct_orthogonal_vector_3 co; - public: - typedef Vector_3 result_type; + public: Construct_base_vector_3() {} Construct_base_vector_3(const Construct_cross_product_vector_3& cp_, const Construct_orthogonal_vector_3& co_) : cp(cp_), co(co_) {} - result_type + Vector_3 operator()( const Plane_3& h, int index ) const { if (index == 1) { @@ -1933,10 +1835,9 @@ namespace CartesianKernelFunctors { typedef typename K::Iso_rectangle_2 Iso_rectangle_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Circle_2 Circle_2; - public: - typedef Bbox_2 result_type; - result_type + public: + Bbox_2 operator()(const Point_2& p) const { std::pair xp = CGAL_NTS to_interval(p.x()); @@ -1944,11 +1845,11 @@ namespace CartesianKernelFunctors { return Bbox_2(xp.first, yp.first, xp.second, yp.second); } - result_type + Bbox_2 operator()(const Segment_2& s) const { return s.source().bbox() + s.target().bbox(); } - result_type + Bbox_2 operator()(const Triangle_2& t) const { Bbox_2 bb = this->operator()(t.vertex(0)); @@ -1968,14 +1869,14 @@ namespace CartesianKernelFunctors { */ } - result_type + Bbox_2 operator()(const Iso_rectangle_2& r) const { typename K::Construct_bbox_2 construct_bbox_2; return construct_bbox_2((r.min)()) + construct_bbox_2((r.max)()); } - result_type + Bbox_2 operator()(const Circle_2& c) const { typename K::Construct_bbox_2 construct_bbox_2; @@ -2006,9 +1907,8 @@ namespace CartesianKernelFunctors { typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Sphere_3 Sphere_3; typedef typename K::Circle_3 Circle_3; - public: - typedef Bbox_3 result_type; + public: Bbox_3 operator()(const Point_3& p) const { @@ -2070,7 +1970,7 @@ namespace CartesianKernelFunctors { maxx.sup(), maxy.sup(), maxz.sup()); } - Bbox_3 + decltype(auto) operator()(const Circle_3& c) const { return c.rep().bbox(); } @@ -2083,10 +1983,9 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef Line_2 result_type; - result_type + public: + Line_2 operator()(const Point_2& p, const Point_2& q) const { FT a, b, c; @@ -2094,7 +1993,7 @@ namespace CartesianKernelFunctors { return Line_2(a, b, c); } - result_type + Line_2 operator()(const Line_2& p, const Line_2& q) const { FT a, b, c; @@ -2111,10 +2010,9 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Plane_3 Plane_3; - public: - typedef Plane_3 result_type; - result_type + public: + Plane_3 operator()(const Point_3& p, const Point_3& q) const { FT a, b, c, d; @@ -2124,7 +2022,7 @@ namespace CartesianKernelFunctors { return Plane_3(a, b, c, d); } - result_type + Plane_3 operator()(const Plane_3& p, const Plane_3& q) const { FT a, b, c, d; @@ -2141,10 +2039,9 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Triangle_2 Triangle_2; - public: - typedef Point_2 result_type; - result_type + public: + Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { typename K::Construct_point_2 construct_point_2; @@ -2153,13 +2050,13 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Triangle_2& t) const { return this->operator()(t.vertex(0), t.vertex(1), t.vertex(2)); } - result_type + Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& s) const { @@ -2177,10 +2074,9 @@ namespace CartesianKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Triangle_3 Triangle_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; - public: - typedef Point_3 result_type; - result_type + public: + Point_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { typename K::Construct_point_3 construct_point_3; @@ -2192,7 +2088,7 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -2206,13 +2102,13 @@ namespace CartesianKernelFunctors { return construct_point_3(x, y, z); } - result_type + Point_3 operator()(const Triangle_3& t) const { return this->operator()(t.vertex(0), t.vertex(1), t.vertex(2)); } - result_type + Point_3 operator()(const Tetrahedron_3& t) const { return this->operator()(t.vertex(0), t.vertex(1), @@ -2225,9 +2121,8 @@ namespace CartesianKernelFunctors { { typedef typename K::Point_2 Point_2; typedef typename K::Triangle_2 Triangle_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()(const Point_2& p, const Point_2& q) const { @@ -2235,7 +2130,7 @@ namespace CartesianKernelFunctors { return construct_midpoint_2(p, q); } - result_type + Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { typename K::Construct_point_2 construct_point_2; @@ -2245,7 +2140,7 @@ namespace CartesianKernelFunctors { return construct_point_2(x, y); } - result_type + Point_2 operator()(const Triangle_2& t) const { return this->operator()(t.vertex(0), t.vertex(1), t.vertex(2)); @@ -2259,9 +2154,8 @@ namespace CartesianKernelFunctors { typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Triangle_3 Triangle_3; typedef typename K::Point_3 Point_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()(const Point_3& p, const Point_3& q) const { @@ -2313,9 +2207,8 @@ namespace CartesianKernelFunctors { class Construct_cross_product_vector_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()(const Vector_3& v, const Vector_3& w) const { @@ -2334,15 +2227,14 @@ namespace CartesianKernelFunctors { typedef typename K::Construct_base_vector_3 Construct_base_vector_3; typedef typename K::Construct_point_on_3 Construct_point_on_3; typedef typename K::Construct_scaled_vector_3 Construct_scaled_vector_3; - typedef typename K::Construct_translated_point_3 - Construct_translated_point_3; + typedef typename K::Construct_translated_point_3 Construct_translated_point_3; + Construct_base_vector_3 cb; Construct_point_on_3 cp; Construct_scaled_vector_3 cs; Construct_translated_point_3 ct; - public: - typedef Point_3 result_type; + public: Construct_lifted_point_3() {} Construct_lifted_point_3(const Construct_base_vector_3& cb_, const Construct_point_on_3& cp_, @@ -2371,8 +2263,6 @@ namespace CartesianKernelFunctors { typedef typename K::RT RT; public: - typedef Direction_2 result_type; - Rep // Direction_2 operator()(Return_base_tag, const RT& x, const RT& y) const { return Rep(x, y); } @@ -2447,9 +2337,8 @@ namespace CartesianKernelFunctors { typedef typename K::Segment_3 Segment_3; typedef typename K::RT RT; typedef typename Direction_3::Rep Rep; - public: - typedef Direction_3 result_type; + public: Rep // Direction_3 operator()(Return_base_tag, const RT& x, const RT& y, const RT& z) const { return Rep(x, y, z); } @@ -2500,9 +2389,8 @@ namespace CartesianKernelFunctors { typedef typename K::Vector_3 Vector_3; typedef typename K::Line_3 Line_3; typedef typename Line_3::Rep Rep; - public: - typedef Line_3 result_type; + public: Line_3 operator()( const Point_3& p, const Point_3& q, const Point_3& s) const { @@ -2552,7 +2440,6 @@ namespace CartesianKernelFunctors { FT z = s.z() + num_z*inv; return Rep(Point_3(x, y, z), Vector_3(rsx, rsy, rsz)); } - }; template @@ -2565,8 +2452,6 @@ namespace CartesianKernelFunctors { typedef typename Iso_rectangle_2::Rep Rep; public: - typedef Iso_rectangle_2 result_type; - Rep // Iso_rectangle_2 operator()(Return_base_tag, const Point_2& p, const Point_2& q, int) const { @@ -2670,10 +2555,10 @@ namespace CartesianKernelFunctors { typedef typename K::Line_2 Line_2; typedef typename Line_2::Rep Rep; typedef typename K::Construct_point_on_2 Construct_point_on_2; - Construct_point_on_2 c; - public: - typedef Line_2 result_type; + Construct_point_on_2 c; + + public: Construct_line_2() {} Construct_line_2(const Construct_point_on_2& c_) : c(c_) {} @@ -2749,9 +2634,8 @@ namespace CartesianKernelFunctors { typedef typename K::Line_3 Line_3; typedef typename K::Vector_3 Vector_3; typedef typename Line_3::Rep Rep; - public: - typedef Line_3 result_type; + public: Rep // Line_3 operator()(Return_base_tag, const Point_3& p, const Point_3& q) const { return Rep(p, Vector_3(p, q)); } @@ -2800,9 +2684,8 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Segment_2 Segment_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()(const Point_2& p, const Point_2& q) const { @@ -2830,9 +2713,8 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Segment_3 Segment_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()(const Point_3& p, const Point_3& q) const { @@ -2858,9 +2740,8 @@ namespace CartesianKernelFunctors { class Construct_opposite_vector_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v) const { return Vector_2(-v.x(), -v.y()); } @@ -2870,9 +2751,8 @@ namespace CartesianKernelFunctors { class Construct_difference_of_vectors_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, const Vector_2& w) const { return Vector_2(v.x()-w.x(), v.y()-w.y()); } @@ -2882,9 +2762,8 @@ namespace CartesianKernelFunctors { class Construct_difference_of_vectors_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& v, const Vector_3& w) const { return Vector_3(v.x()-w.x(), v.y()-w.y(), v.z()-w.z()); } @@ -2898,8 +2777,6 @@ namespace CartesianKernelFunctors { typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename K::Line_2 Line_2; - typedef Line_2 result_type; - Line_2 operator() ( const Weighted_point_2 & p, const Weighted_point_2 & q) const { @@ -2917,9 +2794,8 @@ namespace CartesianKernelFunctors { class Construct_sum_of_vectors_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, const Vector_2& w) const { return Vector_2(v.x()+w.x(), v.y()+w.y()); } @@ -2929,9 +2805,8 @@ namespace CartesianKernelFunctors { class Construct_sum_of_vectors_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& v, const Vector_3& w) const { return Vector_3(v.x()+w.x(), v.y()+w.y(), v.z()+w.z()); } @@ -2941,9 +2816,8 @@ namespace CartesianKernelFunctors { class Construct_opposite_vector_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& v) const { return Vector_3(-v.x(), -v.y(), -v.z()); } @@ -2956,9 +2830,8 @@ namespace CartesianKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; typedef typename K::Plane_3 Plane_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Plane_3& p ) const { return Vector_3(p.a(), p.b(), p.c()); } @@ -2998,9 +2871,8 @@ namespace CartesianKernelFunctors { class Construct_perpendicular_vector_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, Orientation o) const { @@ -3016,9 +2888,8 @@ namespace CartesianKernelFunctors { class Construct_perpendicular_direction_2 { typedef typename K::Direction_2 Direction_2; - public: - typedef Direction_2 result_type; + public: Direction_2 operator()( const Direction_2& d, Orientation o) const { @@ -3036,9 +2907,8 @@ namespace CartesianKernelFunctors { { typedef typename K::Line_2 Line_2; typedef typename K::Point_2 Point_2; - public: - typedef Line_2 result_type; + public: Line_2 operator()( const Line_2& l, const Point_2& p) const { @@ -3058,24 +2928,9 @@ namespace CartesianKernelFunctors { typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename K::Line_2 Line_2; typedef typename Point_2::Rep Rep; + public: - - template - struct result { - typedef Point_2 type; - }; - - template - struct result { - typedef const Point_2& type; - }; - - template - struct result { - typedef const Point_2& type; - }; - - template + template Rep // Point_2 operator()(Return_base_tag, Args&& ...args) const { return Rep(std::forward(args)...); } @@ -3102,7 +2957,7 @@ namespace CartesianKernelFunctors { operator()(const Point_2 & p) const { return p; } - const Point_2& + decltype(auto) operator()(const Weighted_point_2 & p) const { return p.rep().point(); } @@ -3132,22 +2987,6 @@ namespace CartesianKernelFunctors { typedef typename Point_3::Rep Rep; public: - - template - struct result { - typedef Point_3 type; - }; - - template - struct result { - typedef const Point_3& type; - }; - - template - struct result { - typedef const Point_3& type; - }; - template Rep // Point_3 operator()(Return_base_tag, Args&& ...args) const @@ -3157,7 +2996,7 @@ namespace CartesianKernelFunctors { operator()(const Point_3 & p) const { return p; } - const Point_3& + decltype(auto) operator()(const Weighted_point_3 & p) const { return p.rep().point(); } @@ -3186,9 +3025,8 @@ namespace CartesianKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename Weighted_point_2::Rep Rep; - public: - typedef Weighted_point_2 result_type; + public: Rep operator()(Return_base_tag, Origin o) const { return Rep(o); } @@ -3229,9 +3067,8 @@ namespace CartesianKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Weighted_point_3 Weighted_point_3; typedef typename Weighted_point_3::Rep Rep; - public: - typedef Weighted_point_3 result_type; + public: Rep operator()(Return_base_tag, Origin o) const { return Rep(o); } @@ -3273,9 +3110,8 @@ namespace CartesianKernelFunctors { typedef typename K::Line_2 Line_2; typedef typename K::Segment_2 Segment_2; typedef typename K::Triangle_2 Triangle_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()( const Line_2& l, const Point_2& p ) const { @@ -3307,16 +3143,6 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; public: - template - struct result { - typedef const Point_3 type; - }; - - template - struct result { - typedef const Point_3& type; - }; - Point_3 operator()( const Line_3& l, const Point_3& p ) const { @@ -3365,10 +3191,7 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; public: - - typedef Line_2 result_type; - - result_type + Line_2 operator() (const Circle_2 & c1, const Circle_2 & c2) const { // Concentric Circles don't have radical line @@ -3391,10 +3214,7 @@ namespace CartesianKernelFunctors { typedef typename K::FT FT; public: - - typedef Plane_3 result_type; - - result_type + Plane_3 operator() (const Sphere_3 & s1, const Sphere_3 & s2) const { // Concentric Spheres don't have radical plane @@ -3418,9 +3238,8 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, const FT& c) const { @@ -3433,9 +3252,8 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, const FT& c) const { @@ -3448,9 +3266,8 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& v, const FT& c) const { @@ -3463,9 +3280,8 @@ namespace CartesianKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& w, const FT& c) const { @@ -3478,9 +3294,8 @@ namespace CartesianKernelFunctors { { typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()( const Point_2& p, const Vector_2& v) const { @@ -3501,9 +3316,8 @@ namespace CartesianKernelFunctors { { typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()( const Point_3& p, const Vector_3& v) const { @@ -3531,9 +3345,8 @@ namespace CartesianKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Direction_2 Direction_2; typedef typename Vector_2::Rep Rep; - public: - typedef Vector_2 result_type; + public: Rep // Vector_2 operator()(Return_base_tag, const Point_2& p, const Point_2& q) const { return Rep(q.x() - p.x(), q.y() - p.y()); } @@ -3635,9 +3448,8 @@ namespace CartesianKernelFunctors { typedef typename K::Vector_3 Vector_3; typedef typename K::Point_3 Point_3; typedef typename Vector_3::Rep Rep; - public: - typedef Vector_3 result_type; + public: Rep // Vector_3 operator()(Return_base_tag, const Point_3& p, const Point_3& q) const { @@ -3741,22 +3553,13 @@ namespace CartesianKernelFunctors { typedef typename K::Segment_2 Segment_2; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; typedef typename K::Triangle_2 Triangle_2; + public: - template - struct result { - typedef const Point_2& type; - }; - - template - struct result { - typedef Point_2 type; - }; - - const Point_2 & + decltype(auto) operator()( const Segment_2& s, int i) const { return s.vertex(i); } - const Point_2 & + decltype(auto) operator()( const Triangle_2& t, int i) const { return t.rep().vertex(i); } @@ -3779,6 +3582,7 @@ namespace CartesianKernelFunctors { template class Coplanar_orientation_3 { + typedef typename K::Orientation Orientation; typedef typename K::Point_3 Point_3; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Coplanar_3 Coplanar_3; @@ -3786,9 +3590,8 @@ namespace CartesianKernelFunctors { Coplanar_3 cp; Collinear_3 cl; #endif // CGAL_kernel_exactness_preconditions - public: - typedef typename K::Orientation result_type; + public: #ifdef CGAL_kernel_exactness_preconditions Coplanar_orientation_3() {} Coplanar_orientation_3(const Coplanar_3& cp_, const Collinear_3& cl_) @@ -3796,7 +3599,7 @@ namespace CartesianKernelFunctors { {} #endif // CGAL_kernel_exactness_preconditions - result_type + Orientation operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return coplanar_orientationC3(p.x(), p.y(), p.z(), @@ -3804,7 +3607,7 @@ namespace CartesianKernelFunctors { r.x(), r.y(), r.z()); } - result_type + Orientation operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -3827,16 +3630,16 @@ namespace CartesianKernelFunctors { template class Coplanar_side_of_bounded_circle_3 { - typedef typename K::Point_3 Point_3; + typedef typename K::Bounded_side Bounded_side; + typedef typename K::Point_3 Point_3; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Coplanar_3 Coplanar_3; typedef typename K::Collinear_3 Collinear_3; Coplanar_3 cp; Collinear_3 cl; #endif // CGAL_kernel_exactness_preconditions - public: - typedef typename K::Bounded_side result_type; + public: #ifdef CGAL_kernel_exactness_preconditions Coplanar_side_of_bounded_circle_3() {} Coplanar_side_of_bounded_circle_3(const Coplanar_3& cp_, @@ -3845,7 +3648,7 @@ namespace CartesianKernelFunctors { {} #endif // CGAL_kernel_exactness_preconditions - result_type + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& t) const { @@ -3865,11 +3668,11 @@ namespace CartesianKernelFunctors { template class Equal_xy_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return CGAL_AND( p.x() == q.x() , p.y() == q.y() ); @@ -3879,11 +3682,11 @@ namespace CartesianKernelFunctors { template class Equal_x_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return p.x() == q.x(); } }; @@ -3891,11 +3694,11 @@ namespace CartesianKernelFunctors { template class Equal_x_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.x() == q.x(); } }; @@ -3903,11 +3706,11 @@ namespace CartesianKernelFunctors { template class Equal_y_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return p.y() == q.y(); } }; @@ -3915,11 +3718,11 @@ namespace CartesianKernelFunctors { template class Equal_y_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.y() == q.y(); } }; @@ -3927,11 +3730,11 @@ namespace CartesianKernelFunctors { template class Equal_z_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.z() == q.z(); } }; @@ -3939,6 +3742,7 @@ namespace CartesianKernelFunctors { template class Has_on_3 { + typedef typename K::Boolean Boolean; typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; @@ -3949,30 +3753,29 @@ namespace CartesianKernelFunctors { typedef typename K::Triangle_3 Triangle_3; typedef typename K::Circle_3 Circle_3; typedef typename K::Sphere_3 Sphere_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Line_3& l, const Point_3& p) const { return l.rep().has_on(p); } - result_type + Boolean operator()( const Ray_3& r, const Point_3& p) const { return r.rep().has_on(p); } - result_type + Boolean operator()( const Segment_3& s, const Point_3& p) const { return s.rep().has_on(p); } - result_type + Boolean operator()( const Plane_3& pl, const Point_3& p) const { return pl.rep().has_on(p); } - result_type + Boolean operator()( const Plane_3& pl, const Line_3& l) const { return pl.rep().has_on(l); } - result_type + Boolean operator()( const Triangle_3& t, const Point_3& p) const { Point_3 o = t.vertex(0) + t.supporting_plane().orthogonal_vector(); @@ -3986,19 +3789,19 @@ namespace CartesianKernelFunctors { && ((alpha+beta+gamma == denum)); } - result_type + Boolean operator()(const Circle_3 &a, const Point_3 &p) const { return a.rep().has_on(p); } - Needs_FT + Needs_FT operator()(const Sphere_3 &a, const Circle_3 &p) const { return a.rep().has_on(p); } - result_type + Boolean operator()(const Sphere_3 &a, const Point_3 &p) const { return a.rep().has_on(p); } - result_type + Boolean operator()(const Plane_3 &a, const Circle_3 &p) const { return a.rep().has_on(p); } @@ -4008,11 +3811,11 @@ namespace CartesianKernelFunctors { template class Less_distance_to_point_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { return has_smaller_dist_to_pointC2(p.x(), p.y(), @@ -4024,11 +3827,11 @@ namespace CartesianKernelFunctors { template class Less_distance_to_point_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return has_smaller_dist_to_pointC3(p.x(), p.y(), p.z(), @@ -4041,13 +3844,13 @@ namespace CartesianKernelFunctors { template class Less_signed_distance_to_line_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; typedef typename K::Equal_2 Equal_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_2& a, const Point_2& b, const Point_2& c, const Point_2& d) const { @@ -4059,7 +3862,7 @@ namespace CartesianKernelFunctors { d.x(), d.y()) == SMALLER; } - result_type + Boolean operator()(const Line_2& l, const Point_2& p, const Point_2& q) const { return has_smaller_signed_dist_to_directionC2(l.a(), l.b(), @@ -4071,13 +3874,13 @@ namespace CartesianKernelFunctors { template class Less_signed_distance_to_plane_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Plane_3 Plane_3; typedef typename K::Collinear_3 Collinear_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Plane_3& h, const Point_3& p, const Point_3& q) const { return has_smaller_signed_dist_to_directionC3(h.a(), h.b(), h.c(), @@ -4085,7 +3888,7 @@ namespace CartesianKernelFunctors { q.x(), q.y(), q.z()); } - result_type + Boolean operator()( const Point_3& hp, const Point_3& hq, const Point_3& hr, const Point_3& p, const Point_3& q) const { @@ -4102,16 +3905,17 @@ namespace CartesianKernelFunctors { template class Less_xyz_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Compare_xyz_3 Compare_xyz_3; - Compare_xyz_3 c; - public: - typedef typename K::Boolean result_type; + Compare_xyz_3 c; + + public: Less_xyz_3() {} Less_xyz_3(const Compare_xyz_3& c_) : c(c_) {} - result_type + Boolean operator()( const Point_3& p, const Point_3& q) const { return c(p, q) == SMALLER; } }; @@ -4119,16 +3923,17 @@ namespace CartesianKernelFunctors { template class Less_xy_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Compare_xy_2 Compare_xy_2; - Compare_xy_2 c; - public: - typedef typename K::Boolean result_type; + Compare_xy_2 c; + + public: Less_xy_2() {} Less_xy_2(const Compare_xy_2& c_) : c(c_) {} - result_type + Boolean operator()( const Point_2& p, const Point_2& q) const { return c(p, q) == SMALLER; } }; @@ -4136,16 +3941,17 @@ namespace CartesianKernelFunctors { template class Less_xy_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Compare_xy_3 Compare_xy_3; - Compare_xy_3 c; - public: - typedef typename K::Boolean result_type; + Compare_xy_3 c; + + public: Less_xy_3() {} Less_xy_3(const Compare_xy_3& c_) : c(c_) {} - result_type + Boolean operator()( const Point_3& p, const Point_3& q) const { return c(p, q) == SMALLER; } }; @@ -4153,11 +3959,11 @@ namespace CartesianKernelFunctors { template class Less_x_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return p.x() < q.x(); } }; @@ -4165,11 +3971,11 @@ namespace CartesianKernelFunctors { template class Less_x_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.x() < q.x(); } }; @@ -4177,11 +3983,11 @@ namespace CartesianKernelFunctors { template class Less_yx_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return compare_lexicographically_xyC2(p.y(), p.x(), @@ -4192,11 +3998,11 @@ namespace CartesianKernelFunctors { template class Less_y_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return p.y() < q.y(); } }; @@ -4204,11 +4010,11 @@ namespace CartesianKernelFunctors { template class Less_y_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.y() < q.y(); } }; @@ -4216,11 +4022,11 @@ namespace CartesianKernelFunctors { template class Less_z_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.z() < q.z(); } }; @@ -4228,24 +4034,25 @@ namespace CartesianKernelFunctors { template class Orientation_2 { + typedef typename K::Orientation Orientation; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; typedef typename K::Circle_2 Circle_2; - public: - typedef typename K::Orientation result_type; - result_type operator()(const Point_2& p, const Point_2& q, const Point_2& r) const + public: + Orientation + operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { return orientationC2(p.x(), p.y(), q.x(), q.y(), r.x(), r.y()); } - result_type + Orientation operator()(const Vector_2& u, const Vector_2& v) const { return orientationC2(u.x(), u.y(), v.x(), v.y()); } - result_type + Orientation operator()(const Circle_2& c) const { return c.rep().orientation(); @@ -4255,14 +4062,14 @@ namespace CartesianKernelFunctors { template class Orientation_3 { + typedef typename K::Orientation Orientation; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Sphere_3 Sphere_3; - public: - typedef typename K::Orientation result_type; - result_type + public: + Orientation operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -4272,7 +4079,7 @@ namespace CartesianKernelFunctors { s.x(), s.y(), s.z()); } - result_type + Orientation operator()( const Vector_3& u, const Vector_3& v, const Vector_3& w) const { return orientationC3(u.x(), u.y(), u.z(), @@ -4280,7 +4087,7 @@ namespace CartesianKernelFunctors { w.x(), w.y(), w.z()); } - result_type + Orientation operator()( Origin, const Point_3& u, const Point_3& v, const Point_3& w) const { @@ -4289,13 +4096,13 @@ namespace CartesianKernelFunctors { w.x(), w.y(), w.z()); } - result_type + Orientation operator()( const Tetrahedron_3& t) const { return t.rep().orientation(); } - result_type + Orientation operator()(const Sphere_3& s) const { return s.rep().orientation(); @@ -4306,10 +4113,8 @@ namespace CartesianKernelFunctors { class Power_side_of_oriented_power_circle_2 { public: - typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename K::Oriented_side Oriented_side; - - typedef Oriented_side result_type; + typedef typename K::Weighted_point_2 Weighted_point_2; Oriented_side operator()(const Weighted_point_2& p, const Weighted_point_2& q, @@ -4358,24 +4163,24 @@ namespace CartesianKernelFunctors { template class Oriented_side_2 { + typedef typename K::Oriented_side Oriented_side; typedef typename K::Point_2 Point_2; typedef typename K::Circle_2 Circle_2; typedef typename K::Line_2 Line_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Segment_2 Segment_2; typedef typename K::FT FT; - public: - typedef typename K::Oriented_side result_type; - result_type + public: + Oriented_side operator()( const Circle_2& c, const Point_2& p) const { return enum_cast(c.bounded_side(p)) * c.orientation(); } - result_type + Oriented_side operator()( const Line_2& l, const Point_2& p) const { return side_of_oriented_lineC2(l.a(), l.b(), l.c(), p.x(), p.y()); } - result_type + Oriented_side operator()( const Triangle_2& t, const Point_2& p) const { typename K::Collinear_are_ordered_along_line_2 @@ -4389,7 +4194,8 @@ namespace CartesianKernelFunctors { ot = orientation(t.vertex(0), t.vertex(1), t.vertex(2)); if (o1 == ot && o2 == ot && o3 == ot) // ot cannot be COLLINEAR - return ot; + return enum_cast(ot); + return (o1 == COLLINEAR && collinear_are_ordered_along_line(t.vertex(0), p, t.vertex(1))) || @@ -4397,11 +4203,10 @@ namespace CartesianKernelFunctors { && collinear_are_ordered_along_line(t.vertex(1), p, t.vertex(2))) || (o3 == COLLINEAR && collinear_are_ordered_along_line(t.vertex(2), p, t.vertex(3))) - ? result_type(ON_ORIENTED_BOUNDARY) - : opposite(ot); + ? Oriented_side(ON_ORIENTED_BOUNDARY) : opposite(ot); } - result_type + Oriented_side operator()(const Segment_2& s, const Triangle_2& t) const { typename K::Construct_source_2 source; @@ -4429,11 +4234,11 @@ namespace CartesianKernelFunctors { template class Side_of_bounded_circle_2 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Point_2& p, const Point_2& q, const Point_2& t) const { return side_of_bounded_circleC2(p.x(), p.y(), @@ -4441,7 +4246,7 @@ namespace CartesianKernelFunctors { t.x(), t.y()); } - result_type + Bounded_side operator()( const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& t) const { @@ -4453,11 +4258,11 @@ namespace CartesianKernelFunctors { template class Side_of_bounded_sphere_3 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& test) const { return side_of_bounded_sphereC3(p.x(), p.y(), p.z(), @@ -4465,7 +4270,7 @@ namespace CartesianKernelFunctors { test.x(), test.y(), test.z()); } - result_type + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& test) const { @@ -4475,7 +4280,7 @@ namespace CartesianKernelFunctors { test.x(), test.y(), test.z()); } - result_type + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s, const Point_3& test) const { @@ -4490,11 +4295,11 @@ namespace CartesianKernelFunctors { template class Side_of_oriented_circle_2 { + typedef typename K::Oriented_side Oriented_side; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Oriented_side result_type; - result_type + public: + Oriented_side operator()( const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& t) const { @@ -4508,11 +4313,11 @@ namespace CartesianKernelFunctors { template class Side_of_oriented_sphere_3 { + typedef typename K::Oriented_side Oriented_side; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Oriented_side result_type; - result_type + public: + Oriented_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s, const Point_3& test) const { diff --git a/Cartesian_kernel/include/CGAL/Cartesian/point_constructions_3.h b/Cartesian_kernel/include/CGAL/Cartesian/point_constructions_3.h deleted file mode 100644 index 80afbff35e1..00000000000 --- a/Cartesian_kernel/include/CGAL/Cartesian/point_constructions_3.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2000 -// Utrecht University (The Netherlands), -// ETH Zurich (Switzerland), -// INRIA Sophia-Antipolis (France), -// Max-Planck-Institute Saarbruecken (Germany), -// and Tel-Aviv University (Israel). 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) : Herve Bronnimann - -#ifndef CGAL_CARTESIAN_POINT_CONSTRUCTIONS_3_H -#define CGAL_CARTESIAN_POINT_CONSTRUCTIONS_3_H - -#include -#include - -namespace CGAL { - -template -CGAL_KERNEL_LARGE_INLINE -PointC3 -point_on_plane(const PlaneC3 &p) -{ - typename K::FT x, y, z; - point_on_planeC3(p.a(), p.b(), p.c(), p.d(), x, y, z); - return PointC3(x, y, z); -} - -template -CGAL_KERNEL_LARGE_INLINE -PointC3 -projection_plane(const PointC3 &p, - const PlaneC3 &h) -{ - typename K::FT x, y, z; - projection_planeC3(h.a(), h.b(), h.c(), h.d(), - p.x(), p.y(), p.z(), - x, y, z); - return PointC3(x, y, z); -} - -} //namespace CGAL - -#endif // CGAL_CARTESIAN_POINT_CONSTRUCTIONS_3_H diff --git a/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_2.h b/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_2.h index 659b17f995f..3941d1b0fdf 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_2.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_2.h @@ -24,7 +24,7 @@ namespace CGAL { template < class K > inline -bool +typename K::Boolean equal_xy(const PointC2 &p, const PointC2 &q) { return CGAL_AND( p.x() == q.x() , p.y() == q.y() ); @@ -34,7 +34,7 @@ equal_xy(const PointC2 &p, const PointC2 &q) // Unused, undocumented, un-functorized. template < class K > inline -Comparison_result +typename K::Comparison_result compare_deltax_deltay(const PointC2& p, const PointC2& q, const PointC2& r, @@ -46,7 +46,7 @@ compare_deltax_deltay(const PointC2& p, template < class K > inline -Comparison_result +typename K::Comparison_result compare_lexicographically_yx(const PointC2 &p, const PointC2 &q) { diff --git a/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_3.h b/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_3.h index c3a7d37aabf..35c2e3a2c32 100644 --- a/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_3.h +++ b/Cartesian_kernel/include/CGAL/Cartesian/predicates_on_points_3.h @@ -24,7 +24,7 @@ namespace CGAL { template < class K > inline -bool +typename K::Boolean equal_xy(const PointC3 &p, const PointC3 &q) { return K().equal_xy_3_object()(p, q); @@ -32,7 +32,7 @@ equal_xy(const PointC3 &p, const PointC3 &q) template < class K > inline -bool +typename K::Boolean equal_xyz(const PointC3 &p, const PointC3 &q) { return p.x() == q.x() && p.y() == q.y() && p.z() == q.z(); @@ -40,7 +40,7 @@ equal_xyz(const PointC3 &p, const PointC3 &q) template < class K > inline -Comparison_result +typename K::Comparison_result compare_xy(const PointC3 &p, const PointC3 &q) { return K().compare_xy_3_object()(p, q); @@ -48,7 +48,7 @@ compare_xy(const PointC3 &p, const PointC3 &q) template < class K > inline -Comparison_result +typename K::Comparison_result compare_lexicographically_xy(const PointC3 &p, const PointC3 &q) { return K().compare_xy_3_object()(p, q); @@ -56,7 +56,7 @@ compare_lexicographically_xy(const PointC3 &p, const PointC3 &q) template < class K > inline -bool +typename K::Boolean lexicographically_xy_smaller_or_equal(const PointC3 &p, const PointC3 &q) { @@ -65,7 +65,7 @@ lexicographically_xy_smaller_or_equal(const PointC3 &p, template < class K > inline -bool +typename K::Boolean lexicographically_xy_smaller(const PointC3 &p, const PointC3 &q) { @@ -74,7 +74,7 @@ lexicographically_xy_smaller(const PointC3 &p, template < class K > inline -bool +typename K::Boolean strict_dominance(const PointC3 &p, const PointC3 &q) { @@ -84,7 +84,7 @@ strict_dominance(const PointC3 &p, template < class K > inline -bool +typename K::Boolean dominance(const PointC3 &p, const PointC3 &q) { diff --git a/Cartesian_kernel/include/CGAL/predicates/kernel_ftC2.h b/Cartesian_kernel/include/CGAL/predicates/kernel_ftC2.h index 56903c2c921..1f1d912e0d2 100644 --- a/Cartesian_kernel/include/CGAL/predicates/kernel_ftC2.h +++ b/Cartesian_kernel/include/CGAL/predicates/kernel_ftC2.h @@ -421,6 +421,7 @@ typename Same_uncertainty_nt::type angleC2(const FT &ux, const FT &uy, const FT &vx, const FT &vy) { + typedef typename Same_uncertainty_nt::type Angle; return enum_cast(CGAL_NTS sign(ux*vx + uy*vy)); } @@ -431,6 +432,7 @@ angleC2(const FT &px, const FT &py, const FT &qx, const FT &qy, const FT &rx, const FT &ry) { + typedef typename Same_uncertainty_nt::type Angle; return enum_cast(CGAL_NTS sign((px-qx)*(rx-qx)+(py-qy)*(ry-qy))); } @@ -442,6 +444,7 @@ angleC2(const FT &px, const FT &py, const FT &rx, const FT &ry, const FT &sx, const FT &sy) { + typedef typename Same_uncertainty_nt::type Angle; return enum_cast(CGAL_NTS sign((px-qx)*(rx-sx)+(py-qy)*(ry-sy))); } @@ -508,6 +511,7 @@ side_of_bounded_circleC2(const FT &px, const FT &py, const FT &rx, const FT &ry, const FT &tx, const FT &ty) { + typedef typename Same_uncertainty_nt::type Bounded_side; return enum_cast( side_of_oriented_circleC2(px,py,qx,qy,rx,ry,tx,ty) * orientationC2(px,py,qx,qy,rx,ry) ); } @@ -520,8 +524,8 @@ side_of_bounded_circleC2(const FT &px, const FT &py, const FT &tx, const FT &ty) { // Returns whether T lies inside or outside the circle which diameter is PQ. - return enum_cast( - CGAL_NTS compare((tx-px)*(qx-tx), (ty-py)*(ty-qy)) ); + typedef typename Same_uncertainty_nt::type Bounded_side; + return enum_cast(CGAL_NTS compare((tx-px)*(qx-tx), (ty-py)*(ty-qy)) ); } template < class FT > @@ -630,7 +634,7 @@ side_of_oriented_lineC2(const FT &a, const FT &b, const FT &c, } template -Comparison_result +typename Compare::result_type compare_power_distanceC2(const FT& px, const FT& py, const FT& pwt, const FT& qx, const FT& qy, const FT& qwt, const FT& rx, const FT& ry) @@ -643,24 +647,25 @@ compare_power_distanceC2(const FT& px, const FT& py, const FT& pwt, template CGAL_KERNEL_MEDIUM_INLINE -Bounded_side +typename Same_uncertainty_nt::type power_side_of_bounded_power_circleC2(const FT &px, const FT &py, const FT &pw, const FT &qx, const FT &qy, const FT &qw, const FT &tx, const FT &ty, const FT &tw) { + typedef typename Same_uncertainty_nt::type Bounded_side; + FT dpx = px - qx; FT dpy = py - qy; FT dtx = tx - qx; FT dty = ty - qy; FT dpz = CGAL_NTS square(dpx) + CGAL_NTS square(dpy); - return enum_cast - (CGAL_NTS sign(-(CGAL_NTS square(dtx) + CGAL_NTS square(dty)-tw+qw)*dpz + return enum_cast(CGAL_NTS sign(-(CGAL_NTS square(dtx) + CGAL_NTS square(dty)-tw+qw)*dpz +(dpz-pw+qw)*(dpx*dtx+dpy*dty))); } template -Oriented_side +typename Same_uncertainty_nt::type power_side_of_oriented_power_circleC2(const FT &px, const FT &py, const FT &pwt, const FT &qx, const FT &qy, const FT &qwt, const FT &rx, const FT &ry, const FT &rwt, @@ -685,7 +690,7 @@ power_side_of_oriented_power_circleC2(const FT &px, const FT &py, const FT &pwt, } template -Oriented_side +typename Same_uncertainty_nt::type power_side_of_oriented_power_circleC2(const FT &px, const FT &py, const FT &pwt, const FT &qx, const FT &qy, const FT &qwt, const FT &tx, const FT &ty, const FT &twt) @@ -709,7 +714,7 @@ power_side_of_oriented_power_circleC2(const FT &px, const FT &py, const FT &pwt, } template -Oriented_side +typename Same_uncertainty_nt::type circumcenter_oriented_side_of_oriented_segmentC2(const FT& ax, const FT& ay, const FT& bx, const FT& by, const FT& p0x, const FT& p0y, diff --git a/Cartesian_kernel/include/CGAL/predicates/kernel_ftC3.h b/Cartesian_kernel/include/CGAL/predicates/kernel_ftC3.h index 86bf5f74bd2..2d6fe0e53be 100644 --- a/Cartesian_kernel/include/CGAL/predicates/kernel_ftC3.h +++ b/Cartesian_kernel/include/CGAL/predicates/kernel_ftC3.h @@ -143,6 +143,7 @@ typename Same_uncertainty_nt::type angleC3(const FT &ux, const FT &uy, const FT &uz, const FT &vx, const FT &vy, const FT &vz) { + typedef typename Same_uncertainty_nt::type Angle; return enum_cast(CGAL_NTS sign(ux*vx + uy*vy + uz*vz)); } @@ -153,6 +154,7 @@ angleC3(const FT &px, const FT &py, const FT &pz, const FT &qx, const FT &qy, const FT &qz, const FT &rx, const FT &ry, const FT &rz) { + typedef typename Same_uncertainty_nt::type Angle; return enum_cast(CGAL_NTS sign((px-qx)*(rx-qx)+ (py-qy)*(ry-qy)+ (pz-qz)*(rz-qz))); @@ -166,6 +168,7 @@ angleC3(const FT &px, const FT &py, const FT &pz, const FT &rx, const FT &ry, const FT &rz, const FT &sx, const FT &sy, const FT &sz) { + typedef typename Same_uncertainty_nt::type Angle; return enum_cast(CGAL_NTS sign((px-qx)*(rx-sx)+ (py-qy)*(ry-sy)+ (pz-qz)*(rz-sz))); @@ -220,6 +223,8 @@ coplanar_side_of_bounded_circleC3(const FT &px, const FT &py, const FT &pz, const FT &rx, const FT &ry, const FT &rz, const FT &tx, const FT &ty, const FT &tz) { + typedef typename Same_uncertainty_nt::type Bounded_side; + // The approach is to compute side_of_bounded_sphere(p,q,r,t+v,t), // with v = pq ^ pr. // Note : since the circle defines the orientation of the plane, it can not @@ -373,6 +378,7 @@ side_of_bounded_sphereC3(const FT &px, const FT &py, const FT &pz, const FT &sx, const FT &sy, const FT &sz, const FT &tx, const FT &ty, const FT &tz) { + typedef typename Same_uncertainty_nt::type Bounded_side; return enum_cast( side_of_oriented_sphereC3(px, py, pz, qx, qy, qz, rx, ry, rz, @@ -392,6 +398,7 @@ side_of_bounded_sphereC3(const FT &px, const FT &py, const FT &pz, const FT &tx, const FT &ty, const FT &tz) { // Returns whether T lies inside or outside the sphere which diameter is PQ. + typedef typename Same_uncertainty_nt::type Bounded_side; return enum_cast( CGAL_NTS sign((tx-px)*(qx-tx) + (ty-py)*(qy-ty) + (tz-pz)*(qz-tz)) ); @@ -420,9 +427,9 @@ side_of_bounded_sphereC3(const FT &px, const FT &py, const FT &pz, { // Returns whether T lies inside or outside the sphere which equatorial // circle is PQR. + typedef typename Same_uncertainty_nt::type Bounded_side; // This code is inspired by the one of circumcenterC3(3 points). - FT psx = px-sx; FT psy = py-sy; FT psz = pz-sz; @@ -688,7 +695,7 @@ power_side_of_oriented_power_sphereC3(const FT &pwt, const FT &qwt) } template < class FT > -Comparison_result +typename Compare::result_type compare_power_distanceC3(const FT &px, const FT &py, const FT &pz, const FT &qx, const FT &qy, const FT &qz, const FT &qw, const FT &rx, const FT &ry, const FT &rz, const FT &rw) @@ -715,6 +722,8 @@ power_side_of_bounded_power_sphereC3( const FT &rx, const FT &ry, const FT &rz, const FT &rw, const FT &sx, const FT &sy, const FT &sz, const FT &sw) { + typedef typename Same_uncertainty_nt::type Bounded_side; + // Translate p to origin and compute determinants FT qpx = qx-px; FT qpy = qy-py; @@ -765,6 +774,8 @@ power_side_of_bounded_power_sphereC3( const FT &qx, const FT &qy, const FT &qz, const FT &qw, const FT &rx, const FT &ry, const FT &rz, const FT &rw) { + typedef typename Same_uncertainty_nt::type Bounded_side; + FT FT2(2); FT dpx = px - qx; FT dpy = py - qy; diff --git a/Circular_kernel_2/benchmark/README_benchmark_CK2.txt b/Circular_kernel_2/benchmark/README_benchmark_CK2.txt index e4fd6e11799..c5ae6610dcf 100644 --- a/Circular_kernel_2/benchmark/README_benchmark_CK2.txt +++ b/Circular_kernel_2/benchmark/README_benchmark_CK2.txt @@ -64,7 +64,7 @@ In std::cerr : Only the time needed to compute it. (it is useful to benchmark a lot of cases and redirect it on a .txt) ATTENTION: -1) dont use ./example a b +1) don't use ./example a b with 5 <= a <= 8 and 0 <= b <= 8, we cannot use the Circulartraits to handle the files 2) The files have to be put on a folder name DXF where the program is located diff --git a/Circular_kernel_2/examples/Circular_kernel_2/functor_has_on_2.cpp b/Circular_kernel_2/examples/Circular_kernel_2/functor_has_on_2.cpp index eba947c1d37..8efde8736ee 100644 --- a/Circular_kernel_2/examples/Circular_kernel_2/functor_has_on_2.cpp +++ b/Circular_kernel_2/examples/Circular_kernel_2/functor_has_on_2.cpp @@ -5,6 +5,7 @@ typedef CGAL::Exact_circular_kernel_2 Circular_k; typedef CGAL::Point_2 Point_2; typedef CGAL::Circular_arc_2 Circular_arc_2; +typedef CGAL::Circular_arc_point_2 Circular_arc_point_2; int main() { @@ -13,8 +14,8 @@ int main() for(int i = 0; i <= 10; i++) { for(int j = 0; j <= 10; j++) { - Point_2 p = Point_2(i, j); - if(Circular_k().has_on_2_object()(c,p)) { + Circular_arc_point_2 cap(Point_2(i, j)); + if(Circular_k().has_on_2_object()(c,cap)) { n++; std::cout << "(" << i << "," << j << ")" << std::endl; } diff --git a/Circular_kernel_2/include/CGAL/Circular_kernel_2/Circular_arc_2.h b/Circular_kernel_2/include/CGAL/Circular_kernel_2/Circular_arc_2.h index 41334a5934e..e9641e82d1e 100644 --- a/Circular_kernel_2/include/CGAL/Circular_kernel_2/Circular_arc_2.h +++ b/Circular_kernel_2/include/CGAL/Circular_kernel_2/Circular_arc_2.h @@ -656,7 +656,7 @@ public: return CGAL::CircularFunctors::circular_arc_bbox(*this); } - // Dont use this function, it is only for internal use + // Don't use this function, it is only for internal use void _setx_info(unsigned short int v_is_x_monotone, unsigned short int v_two_end_points_on_upper_part, unsigned short int v_is_complementary_x_monotone) const { diff --git a/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_circle_2.h b/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_circle_2.h index a1890928e7d..1454b4f9cff 100644 --- a/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_circle_2.h +++ b/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_circle_2.h @@ -31,25 +31,29 @@ namespace CGAL { namespace CircularFunctors { template < class CK > - class Construct_circle_2 : public CK::Linear_kernel::Construct_circle_2 + class Construct_circle_2 + // : public CK::Linear_kernel::Construct_circle_2 { - typedef typename CK::Linear_kernel::Construct_circle_2 Base_functor; - typedef typename CK::FT FT; - typedef typename CK::Linear_kernel::Point_2 Point_2; + typedef typename CK::Circle_2 Circle_2; + typedef typename CK::Circular_arc_2 Circular_arc_2; + + typedef typename CK::Linear_kernel::Construct_circle_2 Linear_Construct_circle_2; + public: - typedef typename Base_functor::result_type result_type; + // using Linear_Construct_circle_2::operator(); - using Base_functor::operator(); + template + decltype(auto) + operator()(const Args&... args) const + { return Linear_Construct_circle_2()(args...); } - typedef typename CK::Circular_arc_2 Circular_arc_2; - - result_type + Circle_2 operator() ( const typename CK::Polynomial_for_circles_2_2 &eq ) { return construct_circle_2(eq); } - result_type + decltype(auto) operator() (const Circular_arc_2 & a) const { return (a.rep().supporting_circle()); } diff --git a/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_line_2.h b/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_line_2.h index fba7f0f1d74..8209e845a92 100644 --- a/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_line_2.h +++ b/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_on_line_2.h @@ -30,26 +30,32 @@ namespace CGAL { namespace LinearFunctors { template < class CK > - class Construct_line_2 : public CK::Linear_kernel::Construct_line_2 + class Construct_line_2 + // : public CK::Linear_kernel::Construct_line_2 { - typedef typename CK::Line_arc_2 Line_arc_2; + typedef typename CK::Line_arc_2 Line_arc_2; typedef typename CK::Line_2 Line_2; - public: - typedef typename CK::Linear_kernel::Construct_line_2::result_type - result_type; - using CK::Linear_kernel::Construct_line_2::operator(); + typedef typename CK::Linear_kernel::Construct_line_2 Linear_Construct_circle_2; - result_type operator() (const Line_arc_2 & a) const + public: + // using CK::Linear_kernel::Construct_line_2::operator(); + + template + decltype(auto) + operator()(const Args&... args) const + { return Linear_Construct_circle_2()(args...); } + + decltype(auto) operator() (const Line_arc_2 & a) const { return (a.rep().supporting_line()); } - result_type + Line_2 operator() ( const typename CK::Polynomial_1_2 &eq ) - { - return construct_line_2(eq); - } + { + return construct_line_2(eq); + } }; } // namespace LinearFunctors diff --git a/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_polynomial_circular.h b/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_polynomial_circular.h index b68746e4a3d..e1e40b9bccc 100644 --- a/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_polynomial_circular.h +++ b/Circular_kernel_2/include/CGAL/Circular_kernel_2/function_objects_polynomial_circular.h @@ -30,148 +30,115 @@ namespace CGAL { namespace CircularFunctors { +#define CGAL_CIRCULAR_KERNEL_MACRO_FUNCTOR_COMPARE_(V)\ +template < class CK > \ + class Compare_ ##V## _2 {\ + /*: public CK::Linear_kernel::Compare_ ##V## _2{*/\ + typedef typename CK::Comparison_result Comparison_result;\ + typedef typename CK::Circular_arc_point_2 Circular_arc_point_2;\ + typedef typename CK::Linear_kernel::Compare_ ##V## _2 Linear_Compare_ ##V## _2;\ + public:\ + template \ + Comparison_result\ + operator()(const Args&... args) const\ + { return Linear_Compare_ ##V## _2()(args...); }\ + /*using CK::Linear_kernel::Compare_ ##V## _2::operator();*/\ + Comparison_result\ + operator() (const Circular_arc_point_2 &p0,\ + const Circular_arc_point_2 &p1) const\ + { return CircularFunctors::compare_ ##V (p0, p1); }\ + };\ - template < class CK > - class Compare_x_2 - : public CK::Linear_kernel::Compare_x_2 - { - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; - typedef typename CK::Point_2 Point_2; + CGAL_CIRCULAR_KERNEL_MACRO_FUNCTOR_COMPARE_(x) + CGAL_CIRCULAR_KERNEL_MACRO_FUNCTOR_COMPARE_(y) + CGAL_CIRCULAR_KERNEL_MACRO_FUNCTOR_COMPARE_(xy) - public: - - typedef typename CK::Linear_kernel::Compare_x_2::result_type result_type; - using CK::Linear_kernel::Compare_x_2::operator(); - - result_type - operator() (const Circular_arc_point_2 &p0, - const Circular_arc_point_2 &p1) const - { return CircularFunctors::compare_x(p0, p1);} - - }; - - - template < class CK > - class Compare_y_2 - : public CK::Linear_kernel::Compare_y_2 - { - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; - typedef typename CK::Point_2 Point_2; - - public: - - typedef typename CK::Linear_kernel::Compare_y_2::result_type result_type; - using CK::Linear_kernel::Compare_y_2::operator(); - - result_type - operator() (const Circular_arc_point_2 &p0, - const Circular_arc_point_2 &p1) const - {return CircularFunctors::compare_y(p0, p1);} - - }; - - template < class CK > - class Compare_xy_2 - : public CK::Linear_kernel::Compare_xy_2 - { - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; - typedef typename CK::Point_2 Point_2; - - public: - - typedef typename CK::Linear_kernel::Compare_xy_2::result_type result_type; - using CK::Linear_kernel::Compare_xy_2::operator(); - - result_type - operator() (const Circular_arc_point_2 &p0, - const Circular_arc_point_2 &p1) const - { return CircularFunctors::compare_xy(p0, p1);} - - }; +#undef CGAL_CIRCULAR_KERNEL_MACRO_FUNCTOR_COMPARE_ template < class CK > class In_x_range_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circular_arc_2 Circular_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Line_arc_2 Line_arc_2; public: - typedef bool result_type; - - result_type + Boolean operator()(const Circular_arc_2 &a, const Circular_arc_point_2 &p) const { return CircularFunctors::point_in_x_range(a, p); } - result_type + Boolean operator()(const Line_arc_2 &a, const Circular_arc_point_2 &p) const { return CircularFunctors::point_in_x_range(a, p); } - }; - template < class CK > class Has_on_2 - : public CK::Linear_kernel::Has_on_2 + // : public CK::Linear_kernel::Has_on_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Line_arc_2 Line_arc_2; typedef typename CK::Circle_2 Circle_2; typedef typename CK::Line_2 Line_2; + typedef typename CK::Linear_kernel::Has_on_2 Linear_Has_on_2; + public: - typedef typename CK::Linear_kernel::Has_on_2::result_type result_type; + // using CK::Linear_kernel::Has_on_2::operator(); - using CK::Linear_kernel::Has_on_2::operator(); + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Has_on_2()(a, b); } - result_type + Boolean operator()(const Circle_2 &a, const Circular_arc_point_2 &p) const { return CircularFunctors::has_on(a, p); } - result_type + Boolean operator()(const Line_2 &a, const Circular_arc_point_2 &p) const { return LinearFunctors::has_on(a, p); } - result_type + Boolean operator()(const Circular_arc_2 &a, const Circular_arc_point_2 &p) const { return CircularFunctors::has_on(a, p); } - result_type + Boolean operator()(const Line_arc_2 &a, const Circular_arc_point_2 &p) const { return CircularFunctors::has_on(a, p); } - }; template < class CK > class Compare_y_to_right_2 { + typedef typename CK::Comparison_result Comparison_result; typedef typename CK::Circular_arc_2 Circular_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Line_arc_2 Line_arc_2; public: - typedef CGAL::Comparison_result result_type; - - result_type + Comparison_result operator()(const Circular_arc_2 &a1, const Circular_arc_2 &a2, const Circular_arc_point_2 &p) const { return CircularFunctors::compare_y_to_right(a1, a2, p); } - result_type + Comparison_result operator()(const Line_arc_2 &a1, const Line_arc_2 &a2, const Circular_arc_point_2 &p) const { return CircularFunctors::compare_y_to_right(a1, a2, p); } - result_type + Comparison_result operator()(const Line_arc_2 &a1, const Circular_arc_2 &a2, const Circular_arc_point_2 &p) const { return CircularFunctors::compare_y_to_right(a1, a2, p); } - result_type + Comparison_result operator()(const Circular_arc_2 &a1, const Line_arc_2 &a2, const Circular_arc_point_2 &p) const @@ -179,21 +146,16 @@ namespace CircularFunctors { return CGAL::SMALLER; return CGAL::LARGER; } - }; template < class CK > class Equal_2 - : public CK::Linear_kernel::Equal_2 + // : public CK::Linear_kernel::Equal_2 { - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Boolean Boolean; + typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; - - public: - typedef typename CK::Linear_kernel LK; - typedef typename LK::Equal_2 LK_Equal_2; - typedef typename CK::Point_2 Point_2; typedef typename CK::Vector_2 Vector_2; typedef typename CK::Direction_2 Direction_2; @@ -204,69 +166,78 @@ namespace CircularFunctors { typedef typename CK::Iso_rectangle_2 Iso_rectangle_2; typedef typename CK::Circle_2 Circle_2; + typedef typename CK::Linear_kernel::Equal_2 Linear_Equal_2; - typedef typename CK::Linear_kernel::Equal_2::result_type result_type; - using CK::Linear_kernel::Equal_2::operator(); + public: + // using CK::Linear_kernel::Equal_2::operator(); - result_type + template + Boolean + operator()(const A& a, const B& b) const { + return Linear_Equal_2()(a, b); + } + + Boolean operator() (const Circular_arc_point_2 &p0, const Circular_arc_point_2 &p1) const { return CircularFunctors::equal(p0, p1); } - result_type + Boolean operator() (const Circular_arc_2 &a0, const Circular_arc_2 &a1) const { return CircularFunctors::equal(a0, a1); } - result_type + Boolean operator() (const Line_arc_2 &a0, const Line_arc_2 &a1) const { return CircularFunctors::equal(a0, a1); } - }; template < class CK > - class Compare_y_at_x_2 : public CK::Linear_kernel::Compare_y_at_x_2 + class Compare_y_at_x_2 + // : public CK::Linear_kernel::Compare_y_at_x_2 { + typedef typename CK::Comparison_result Comparison_result; typedef typename CK::Circular_arc_2 Circular_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Line_arc_2 Line_arc_2; + typedef typename CK::Linear_kernel::Compare_y_at_x_2 Linear_Compare_y_at_x_2; + public: - typedef typename CK::Linear_kernel::Compare_y_at_x_2::result_type result_type; + // using CK::Linear_kernel::Compare_y_at_x_2::operator(); - using CK::Linear_kernel::Compare_y_at_x_2::operator(); + template + Comparison_result + operator()(const Args&... args) const + { return Linear_Compare_y_at_x_2()(args...); } - result_type + Comparison_result operator() (const Circular_arc_point_2 &p, const Circular_arc_2 &A1) const { return CircularFunctors::compare_y_at_x(p, A1); } - result_type + Comparison_result operator() (const Circular_arc_point_2 &p, const Line_arc_2 &A1) const { return CircularFunctors::compare_y_at_x(p, A1); } - }; template < class CK > class Do_overlap_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; public: - typedef bool result_type; - - result_type + Boolean operator() (const Circular_arc_2 &A1, const Circular_arc_2 &A2) const { return CircularFunctors::do_overlap(A1, A2); } - result_type + Boolean operator() (const Line_arc_2 &A1, const Line_arc_2 &A2) const { return CircularFunctors::do_overlap(A1, A2); } - }; - template < class CK > class Make_x_monotone_2 { @@ -274,9 +245,6 @@ namespace CircularFunctors { typedef typename CK::Line_arc_2 Line_arc_2; public: - - typedef void result_type; //!!! - template < class OutputIterator > OutputIterator operator()(const Circular_arc_2 &A, OutputIterator res) const @@ -290,7 +258,6 @@ namespace CircularFunctors { { return CircularFunctors::make_x_monotone(A,res); } - }; template < class CK > @@ -300,9 +267,6 @@ namespace CircularFunctors { typedef typename CK::Line_arc_2 Line_arc_2; public: - - typedef void result_type; //!!! - template < class OutputIterator > OutputIterator operator()(const Circular_arc_2 &A, OutputIterator res) const @@ -326,19 +290,21 @@ namespace CircularFunctors { { *res++ = make_object(A); return res; } - }; template < class CK > class Do_intersect_2 - : public CK::Linear_kernel::Do_intersect_2 + // : public CK::Linear_kernel::Do_intersect_2 { + typedef typename CK::Boolean Boolean; + + typedef typename CK::Linear_kernel::Do_intersect_2 Linear_Do_intersect_2; + public: - typedef typename CK::Linear_kernel::Do_intersect_2::result_type result_type; - template - result_type - operator()(const T1& t1, const T2& t2) const - { return Intersections::internal::do_intersect(t1, t2, CK()); } + template + Boolean + operator()(const A& a, const B& b) const + { return Intersections::internal::do_intersect(a, b, CK()); } }; template < class CK > @@ -347,14 +313,12 @@ namespace CircularFunctors { //using the Lazy_kernel as linear kernel. //: public CK::Linear_kernel::Intersect_2 { - typedef typename CK::Circle_2 Circle; typedef typename CK::Circular_arc_2 Circular_arc; typedef typename CK::Line_arc_2 Line_arc; typedef typename CK::Line_2 Line; public: - //using CK::Linear_kernel::Intersect_2::operator(); template @@ -460,7 +424,6 @@ namespace CircularFunctors { *res++=typename CK::Linear_kernel::Intersect_2()(l1, l2); return res; } - }; template < class CK > @@ -470,61 +433,61 @@ namespace CircularFunctors { typename CK::Polynomial_1_2 operator() ( const typename CK::Line_2 & l ) - { - return LinearFunctors::get_equation(l); - } + { + return LinearFunctors::get_equation(l); + } typename CK::Polynomial_for_circles_2_2 operator() ( const typename CK::Circle_2 & c ) - { - return CircularFunctors::get_equation(c); - } + { + return CircularFunctors::get_equation(c); + } }; template < class CK > class Split_2 { typedef typename CK::Circular_arc_2 Circular_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Line_arc_2 Line_arc_2; public: - - typedef void result_type; - - result_type + void operator()(const Circular_arc_2 &A, const Circular_arc_point_2 &p, Circular_arc_2 &ca1, Circular_arc_2 &ca2) const { return CircularFunctors::split(A, p, ca1, ca2); } - - result_type + void operator()(const Line_arc_2 &A, const Circular_arc_point_2 &p, Line_arc_2 &ca1, Line_arc_2 &ca2) const { return CircularFunctors::split(A, p, ca1, ca2); } - }; template < class CK > class Is_vertical_2 - : public CK::Linear_kernel::Is_vertical_2 + // : public CK::Linear_kernel::Is_vertical_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; + typedef typename CK::Linear_kernel::Is_vertical_2 Linear_Is_vertical_2; + public: + // using CK::Linear_kernel::Is_vertical_2::operator(); - typedef typename CK::Linear_kernel::Is_vertical_2::result_type result_type; + template + Boolean + operator()(const A& a) const + { return Linear_Is_vertical_2()(a); } - using CK::Linear_kernel::Is_vertical_2::operator(); - - result_type + Boolean operator()(const Circular_arc_2 &A) const { return CircularFunctors::is_vertical(A); } - result_type + Boolean operator()(const Line_arc_2 &A) const { return CircularFunctors::is_vertical(A); } @@ -533,56 +496,51 @@ namespace CircularFunctors { template < class CK > class Construct_circular_arc_2 { - typedef typename CK::FT FT; - typedef typename CK::RT RT; typedef typename CK::Point_2 Point_2; typedef typename CK::Line_2 Line_2; typedef typename CK::Circle_2 Circle_2; typedef typename CK::Circular_arc_2 Circular_arc_2; - typedef typename CK::Kernel_base::Circular_arc_2 RCircular_arc_2; typedef typename Circular_arc_2::Rep Rep; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; public: - typedef Circular_arc_2 result_type; - - result_type + Circular_arc_2 operator()(void) { return Rep(); } - result_type + Circular_arc_2 operator()(const Circle_2 &c) const { return Rep(c); } - result_type + Circular_arc_2 operator()(const Circle_2 &support, const Circular_arc_point_2 &source, const Circular_arc_point_2 &target) const { return Rep(support,source,target); } // Not Documented - result_type + Circular_arc_2 operator()(const Circle_2 &support, const Line_2 &l1, bool b1, const Line_2 &l2, bool b2) const { return Rep(support,l1,b1,l2,b2); } // Not Documented - result_type + Circular_arc_2 operator()(const Circle_2 &c, const Circle_2 &c1, bool b_1, const Circle_2 &c2, bool b_2) const { return Rep(c,c1,b_1,c2,b_2); } - result_type + Circular_arc_2 operator()(const Point_2 &begin, const Point_2 &middle, const Point_2 &end) const { return Rep(begin,middle,end); } // Not Documented - result_type + Circular_arc_2 operator()(const Point_2 &begin, const Point_2 &end, const FT& bulge) const @@ -600,47 +558,44 @@ namespace CircularFunctors { typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Segment_2 Segment_2; typedef typename CK::Line_arc_2 Line_arc_2; - typedef typename CK::Kernel_base::Line_arc_2 RLine_arc_2; typedef typename Line_arc_2::Rep Rep; public: - typedef Line_arc_2 result_type; - - result_type + Line_arc_2 operator()(void) { return Rep(); } // Not Documented - result_type + Line_arc_2 operator()(const Line_2 &support, const Circle_2 &c1,const bool b1, const Circle_2 &c2,const bool b2) const { return Rep(support,c1,b1,c2,b2); } // Not Documented - result_type + Line_arc_2 operator()(const Line_2 &support, const Line_2 &l1, const Line_2 &l2) const { return Rep(support,l1,l2); } - result_type + Line_arc_2 operator()(const Line_2 &support, const Circular_arc_point_2 &p1, const Circular_arc_point_2 &p2) const { return Rep(support,p1,p2); } -// result_type +// Line_arc_2 // operator()(const Line_2 &support, // const Point_2 &p1, // const Point_2 &p2) const // { return Rep(support,p1,p2); } - result_type + Line_arc_2 operator()(const Segment_2 &s) const { return Rep(s); } - result_type + Line_arc_2 operator()(const Point_2 &p1, const Point_2 &p2) const { return Rep(p1,p2); } @@ -652,40 +607,32 @@ namespace CircularFunctors { { typedef typename CK::Point_2 Point_2; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; - typedef typename CK::Kernel_base::Circular_arc_point_2 - RCircular_arc_point_2; typedef typename Circular_arc_point_2::Rep Rep; typedef typename Circular_arc_point_2::Root_for_circles_2_2 Root_for_circles_2_2; public: - typedef Circular_arc_point_2 result_type; - - result_type + Circular_arc_point_2 operator()(void) { return Rep(); } - result_type + Circular_arc_point_2 operator()(const Root_for_circles_2_2 & np) const { return Rep(np); } - result_type + Circular_arc_point_2 operator()(const Point_2 & p) const { return Rep(p); } }; - template class Compute_circular_x_2 { typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; - typedef typename CK::Root_of_2 Root_of_2; public: - typedef const Root_of_2& result_type; - - result_type operator() (const Circular_arc_point_2 & a) const + decltype(auto) operator() (const Circular_arc_point_2 & a) const { return (a.rep().x()); } @@ -696,13 +643,9 @@ namespace CircularFunctors { class Compute_circular_y_2 { typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; - typedef typename CK::Root_of_2 Root_of_2; public: - - typedef const Root_of_2& result_type; - - result_type operator() (const Circular_arc_point_2 & a) const + decltype(auto) operator() (const Circular_arc_point_2 & a) const { return (a.rep().y()); } @@ -714,17 +657,14 @@ namespace CircularFunctors { { typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; public: - typedef const Circular_arc_point_2 & result_type; - - result_type operator() (const Circular_arc_2 & a) const + decltype(auto) operator() (const Circular_arc_2& a) const { return (a.rep().left()); } - result_type operator() (const Line_arc_2 & a) const + decltype(auto) operator() (const Line_arc_2& a) const { return (a.rep().left()); } @@ -736,18 +676,14 @@ namespace CircularFunctors { { typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; public: - - typedef const Circular_arc_point_2& result_type; - - result_type operator() (const Circular_arc_2 & a) const + decltype(auto) operator() (const Circular_arc_2& a) const { return (a.rep().right()); } - result_type operator() (const Line_arc_2 & a) const + decltype(auto) operator() (const Line_arc_2& a) const { return (a.rep().right()); } @@ -759,16 +695,12 @@ namespace CircularFunctors { { typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; public: - - typedef const Circular_arc_point_2& result_type; - - result_type operator() (const Circular_arc_2 & a) const + decltype(auto) operator() (const Circular_arc_2& a) const { return a.rep().source(); } - result_type operator() (const Line_arc_2 & a) const + decltype(auto) operator() (const Line_arc_2& a) const { return a.rep().source();} }; @@ -779,16 +711,12 @@ namespace CircularFunctors { { typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; - typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; public: - - typedef const Circular_arc_point_2& result_type; - - result_type operator() (const Circular_arc_2 & a) const + decltype(auto) operator() (const Circular_arc_2& a) const { return a.rep().target();} - result_type operator() (const Line_arc_2 & a) const + decltype(auto) operator() (const Line_arc_2& a) const { return a.rep().target();} }; @@ -796,19 +724,17 @@ namespace CircularFunctors { template class Is_x_monotone_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; public: - - typedef bool result_type; - - result_type operator() (const Circular_arc_2 & a) const + Boolean operator() (const Circular_arc_2 & a) const { return (a.rep().is_x_monotone()); } - result_type operator() (const Line_arc_2 & a) const + Boolean operator() (const Line_arc_2 & a) const { return (a.rep().is_x_monotone()); } @@ -818,19 +744,17 @@ namespace CircularFunctors { template class Is_y_monotone_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Line_arc_2 Line_arc_2; public: - - typedef bool result_type; - - result_type operator() (const Circular_arc_2 & a) const + Boolean operator() (const Circular_arc_2& a) const { return (a.rep().is_y_monotone()); } - result_type operator() (const Line_arc_2 & a) const + Boolean operator() (const Line_arc_2& a) const { return (a.rep().is_y_monotone()); } @@ -839,48 +763,58 @@ namespace CircularFunctors { template class Construct_bbox_2 - : public CK::Linear_kernel::Construct_bbox_2 + // : public CK::Linear_kernel::Construct_bbox_2 { typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; typedef typename CK::Line_arc_2 Line_arc_2; typedef typename CK::Circle_2 Circle_2; + typedef typename CK::Linear_kernel::Construct_bbox_2 Linear_Construct_bbox_2; + public: + // using CK::Linear_kernel::Construct_bbox_2::operator(); - typedef typename CK::Linear_kernel::Construct_bbox_2::result_type result_type; - using CK::Linear_kernel::Construct_bbox_2::operator(); + template + decltype(auto) + operator()(const A& a) const + { return Linear_Construct_bbox_2()(a); } - result_type operator() (const Circular_arc_point_2 & a) const + decltype(auto) operator() (const Circular_arc_point_2& a) const { return a.rep().bbox(); } - result_type operator() (const Circular_arc_2 & a) const + decltype(auto) operator() (const Circular_arc_2& a) const { return a.rep().bbox(); } - result_type operator() (const Line_arc_2 & a) const + decltype(auto) operator() (const Line_arc_2& a) const { return a.rep().bbox(); } - }; template class Bounded_side_2 - : public CK::Linear_kernel::Bounded_side_2 + // : public CK::Linear_kernel::Bounded_side_2 { + typedef typename CK::Bounded_side Bounded_side; typedef typename CK::Circle_2 Circle_2; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Linear_kernel::Bounded_side_2 Linear_Bounded_side_2; + public: - typedef typename CK::Linear_kernel::Bounded_side_2::result_type result_type; + // using CK::Linear_kernel::Bounded_side_2::operator(); - using CK::Linear_kernel::Bounded_side_2::operator(); + template + Bounded_side + operator()(const A& a, const B& b) const + { return Linear_Bounded_side_2()(a, b); } - result_type + Bounded_side operator()(const Circle_2& c, const Circular_arc_point_2& p) const { return CircularFunctors::bounded_side(c,p); } @@ -888,17 +822,23 @@ namespace CircularFunctors { template class Has_on_bounded_side_2 - : public CK::Linear_kernel::Has_on_bounded_side_2 + // : public CK::Linear_kernel::Has_on_bounded_side_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circle_2 Circle_2; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Linear_kernel::Has_on_bounded_side_2 Linear_Has_on_bounded_side_2; + public: - typedef typename CK::Linear_kernel::Has_on_bounded_side_2::result_type result_type; + // using CK::Linear_kernel::Has_on_bounded_side_2::operator(); - using CK::Linear_kernel::Has_on_bounded_side_2::operator(); + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Has_on_bounded_side_2()(a, b); } - result_type + Boolean operator()(const Circle_2& c, const Circular_arc_point_2& p) const { return CK().bounded_side_2_object()(c,p) == ON_BOUNDED_SIDE; } @@ -906,20 +846,25 @@ namespace CircularFunctors { template class Has_on_unbounded_side_2 - : public CK::Linear_kernel::Has_on_unbounded_side_2 + // : public CK::Linear_kernel::Has_on_unbounded_side_2 { + typedef typename CK::Boolean Boolean; typedef typename CK::Circle_2 Circle_2; typedef typename CK::Circular_arc_point_2 Circular_arc_point_2; + typedef typename CK::Linear_kernel::Has_on_unbounded_side_2 Linear_Has_on_unbounded_side_2; + public: - typedef typename CK::Linear_kernel::Has_on_unbounded_side_2::result_type result_type; + // using CK::Linear_kernel::Has_on_unbounded_side_2::operator(); - using CK::Linear_kernel::Has_on_unbounded_side_2::operator(); + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Has_on_unbounded_side_2()(a, b); } - result_type + Boolean operator()(const Circle_2& c, const Circular_arc_point_2& p) const { return CK().bounded_side_2_object()(c,p) == ON_UNBOUNDED_SIDE; } - }; #ifndef CGAL_NO_DEPRECATED_CODE @@ -927,31 +872,21 @@ namespace CircularFunctors { class Construct_supporting_circle_2 { typedef typename CK::Circular_arc_2 Circular_arc_2; - typedef typename CK::Circle_2 Circle_2; public: - - typedef Circle_2 result_type; - - CGAL_DEPRECATED result_type operator() (const Circular_arc_2 & a) const + CGAL_DEPRECATED decltype(auto) operator() (const Circular_arc_2 & a) const { return a.rep().supporting_circle(); } }; - template class Construct_supporting_line_2 { typedef typename CK::Line_arc_2 Line_arc_2; - typedef typename CK::Line_2 Line_2; - typedef typename CK::Circle_2 Circle_2; public: - - typedef Line_2 result_type; - - CGAL_DEPRECATED result_type operator() (const Line_arc_2 & a) const + CGAL_DEPRECATED decltype(auto) operator() (const Line_arc_2 & a) const { return a.rep().supporting_line(); } @@ -960,24 +895,28 @@ namespace CircularFunctors { template class Construct_center_2 - : public CK::Linear_kernel::Construct_center_2 + // : public CK::Linear_kernel::Construct_center_2 { typedef typename CK::Circular_arc_2 Circular_arc_2; - public: - typedef typename CK::Linear_kernel::Construct_center_2::result_type result_type; - using CK::Linear_kernel::Construct_center_2::operator(); - result_type + typedef typename CK::Linear_kernel::Construct_center_2 Linear_Construct_center_2; + + public: + // using CK::Linear_kernel::Construct_center_2::operator(); + + template + decltype(auto) + operator()(const A& a) const + { return Linear_Construct_center_2()(a); } + + decltype(auto) operator()(const Circular_arc_2& c) const { return c.rep().center(); } - }; template class Compute_squared_radius_2 { - - private: typedef typename CK::Circular_arc_2 Circular_arc_2; typedef typename CK::Linear_kernel LK; typedef typename LK::Compute_squared_radius_2 LK_Compute_squared_radius_2; diff --git a/Circular_kernel_2/include/CGAL/Circular_kernel_intersections.h b/Circular_kernel_2/include/CGAL/Circular_kernel_intersections.h index 9a3ca3b1a89..ae9453be1c0 100644 --- a/Circular_kernel_2/include/CGAL/Circular_kernel_intersections.h +++ b/Circular_kernel_2/include/CGAL/Circular_kernel_intersections.h @@ -54,7 +54,7 @@ namespace Intersections { \ } \ template \ inline \ -bool \ +typename K::Boolean \ do_intersect(const A &c1, const B &c2) \ { \ return typename K::Do_intersect_2()(c1, c2); \ diff --git a/Circular_kernel_2/include/CGAL/Filtered_bbox_circular_kernel_2/bbox_filtered_predicates.h b/Circular_kernel_2/include/CGAL/Filtered_bbox_circular_kernel_2/bbox_filtered_predicates.h index 90fbdc38534..53b554c95b8 100644 --- a/Circular_kernel_2/include/CGAL/Filtered_bbox_circular_kernel_2/bbox_filtered_predicates.h +++ b/Circular_kernel_2/include/CGAL/Filtered_bbox_circular_kernel_2/bbox_filtered_predicates.h @@ -33,6 +33,7 @@ namespace Bbox_functors { template class Compare_x_2 : public BK::Circular_kernel:: template Base< BK >::Type::Compare_x_2 { + typedef typename BK::Comparison_result Comparison_result; typedef typename BK::Circular_arc_point_2 Circular_arc_point_2; typedef typename BK::Point_2 Point_2; typedef typename BK::Circular_kernel:: @@ -40,12 +41,9 @@ class Compare_x_2 : public BK::Circular_kernel:: template Base< BK >::Type::Comp typedef CK_Compare_x_2 Base; public: - - typedef typename CK_Compare_x_2::result_type result_type; - using Base::operator(); - result_type + Comparison_result operator()( const Circular_arc_point_2 &a, const Circular_arc_point_2 &b) const { Bbox_2 bb1=a.bbox(),bb2=b.bbox(); @@ -65,16 +63,15 @@ public: template class Compare_y_2 : public BK::Circular_kernel:: template Base< BK >::Type::Compare_y_2 { + typedef typename BK::Comparison_result Comparison_result; typedef typename BK::Circular_arc_point_2 Circular_arc_point_2; typedef typename BK::Point_2 Point_2; typedef typename BK::Circular_kernel:: template Base< BK >::Type::Compare_y_2 CK_Compare_y_2; typedef CK_Compare_y_2 Base; + public: - - typedef typename CK_Compare_y_2::result_type result_type; - - result_type + Comparison_result operator() (const Point_2 &p0, const Point_2 &p1) const { @@ -83,7 +80,7 @@ public: using Base::operator(); - result_type + Comparison_result operator()( const Circular_arc_point_2 &a, const Circular_arc_point_2 &b) const { Bbox_2 bb1=a.bbox(),bb2=b.bbox(); @@ -105,17 +102,15 @@ class Compare_xy_2 : public BK::Circular_kernel:: template Base< BK >::Type::Com typedef typename BK::Circular_kernel:: template Base< BK >::Type::Compare_xy_2 CK_Compare_xy_2; typedef CK_Compare_xy_2 Base; + + typedef typename BK::Comparison_result Comparison_result; typedef typename BK::Circular_arc_point_2 Circular_arc_point_2; typedef typename BK::Point_2 Point_2; public: - - typedef typename Base::result_type result_type; - using Base::operator(); -public: - result_type + Comparison_result operator()( const Circular_arc_point_2 &a, const Circular_arc_point_2 &b) const { typename BK::Compare_x_2 compx; @@ -137,20 +132,18 @@ class In_x_range_2 : public BK::Circular_kernel:: template Base< BK >::Type::In_ typedef typename BK::Circular_kernel:: template Base< BK >::Type::In_x_range_2 CK_In_x_range_2; typedef CK_In_x_range_2 Base; + + typedef typename BK::Boolean Boolean; typedef typename BK::Circular_arc_point_2 Circular_arc_point_2; typedef typename BK::Circular_arc_2 Circular_arc_2; typedef typename BK::Line_arc_2 Line_arc_2; public: - - typedef typename CK_In_x_range_2::result_type result_type; - using Base::operator(); private: - template - result_type + Boolean _in_x_range_2(const Arc_2 &a, const Circular_arc_point_2 &p) const { @@ -179,42 +172,36 @@ private: } public: - - result_type + Boolean operator()( const Circular_arc_2 &a, const Circular_arc_point_2 &p) const { CGAL_precondition( a.is_x_monotone()); return _in_x_range_2(a,p); } - result_type + Boolean operator()( const Line_arc_2 &a, const Circular_arc_point_2 &p) const { return _in_x_range_2(a,p);} - - }; - template class Compare_y_at_x_2 : public BK::Circular_kernel:: template Base< BK >::Type::Compare_y_at_x_2 { typedef typename BK::Circular_kernel:: template Base< BK >::Type::Compare_y_at_x_2 CK_Compare_y_at_x_2; typedef CK_Compare_y_at_x_2 Base; + + typedef typename BK::Comparison_result Comparison_result; typedef typename BK::Circular_arc_2 Circular_arc_2; typedef typename BK::Circular_arc_point_2 Circular_arc_point_2; typedef typename BK::Line_arc_2 Line_arc_2; public: - - typedef typename CK_Compare_y_at_x_2::result_type result_type; - using Base::operator(); private: - template - result_type + Comparison_result _compare_y_at_x_2(const Circular_arc_point_2 &p,const Arc_2 &a) const { CGAL_precondition_code(bool tmp=In_x_range_2()(a,p)); @@ -232,41 +219,36 @@ private: } public: - - result_type + Comparison_result operator()( const Circular_arc_point_2 &p,const Circular_arc_2 &a ) const { CGAL_precondition( a.is_x_monotone()); return _compare_y_at_x_2(p,a); } - result_type + Comparison_result operator()( const Circular_arc_point_2 &p,const Line_arc_2 &a ) const {return _compare_y_at_x_2(p,a);} - }; - template class Has_on_2 : public BK::Circular_kernel:: template Base< BK >::Type::Has_on_2 { typedef typename BK::Circular_kernel:: template Base< BK >::Type::Has_on_2 CK_Has_on_2; typedef CK_Has_on_2 Base; + + typedef typename BK::Boolean Boolean; typedef typename BK::Circular_arc_2 Circular_arc_2; typedef typename BK::Circular_arc_point_2 Circular_arc_point_2; typedef typename BK::Line_arc_2 Line_arc_2; public: - - typedef typename CK_Has_on_2::result_type result_type; - using Base::operator(); private: - template - result_type + Boolean _has_on_2(const Arc_2 &a, const Circular_arc_point_2 &p) const { Bbox_2 bb1=a.bbox(),bb2=p.bbox(); @@ -278,27 +260,26 @@ private: } public: - - result_type + Boolean operator()( const Circular_arc_2 &a,const Circular_arc_point_2 &p ) const { CGAL_precondition( a.is_x_monotone()); return _has_on_2(a,p); } - result_type + Boolean operator()( const Line_arc_2 &a, const Circular_arc_point_2 &p ) const {return _has_on_2(a,p);} - }; - template class Equal_2 : public BK::Circular_kernel:: template Base< BK >::Type::Equal_2 { typedef typename BK::Circular_kernel:: template Base< BK >::Type::Equal_2 CK_Equal_2; + + typedef typename BK::Boolean Boolean; typedef typename BK::Circular_arc_2 Circular_arc_2; typedef typename BK::Point_2 Point_2; typedef typename BK::Direction_2 Direction_2; @@ -315,15 +296,11 @@ class Equal_2 typedef CK_Equal_2 Base; public: - - typedef typename CK_Equal_2::result_type result_type; - using Base::operator(); private: - template - result_type + Boolean _equal_2(const Arc_2 &a,const Arc_2 &b) const { Bbox_2 bb11=a.source().bbox(), @@ -346,8 +323,7 @@ private: } public: - - result_type + Boolean operator()( const Circular_arc_point_2 &a , const Circular_arc_point_2 &b) const { @@ -361,14 +337,15 @@ public: /* WAS THAT HERE FOR OTHER COMPILERS THAN VC* ??? // redefine to solve ambiguous call error - result_type + Boolean operator()( const Point_2 &a , const Point_2 &b) const { return CK_Equal_2()( a, b); } */ - result_type + + Boolean operator()( const Circular_arc_2 &a , const Circular_arc_2 &b ) const { CGAL_precondition( a.is_x_monotone()); @@ -377,17 +354,17 @@ public: return _equal_2(a,b); } - result_type + Boolean operator()( const Line_arc_2 &a , const Line_arc_2 &b ) const { return _equal_2(a,b);} - result_type + Boolean operator()( const Circular_arc_2 & , const Line_arc_2 & ) const { return false;} - result_type + Boolean operator()( const Line_arc_2 & , const Circular_arc_2 & ) const { return false;} @@ -401,19 +378,17 @@ class Do_overlap_2 : public BK::Circular_kernel:: template Base< BK >::Type::Do_ typedef typename BK::Circular_kernel:: template Base< BK >::Type::Do_overlap_2 CK_Do_overlap_2; typedef CK_Do_overlap_2 Base; + + typedef typename BK::Boolean Boolean; typedef typename BK::Circular_arc_2 Circular_arc_2; typedef typename BK::Line_arc_2 Line_arc_2; public: - - typedef typename CK_Do_overlap_2::result_type result_type; - using Base::operator(); private: - template - result_type + Boolean _do_overlap_2(const Arc_2 &a, const Arc_2 &b) const { Bbox_2 bb1=a.bbox(),bb2=b.bbox(); @@ -424,10 +399,8 @@ private: return false; } - public: - - result_type + Boolean operator()( const Circular_arc_2 &a , const Circular_arc_2 &b ) const { CGAL_precondition( a.is_x_monotone()); @@ -435,28 +408,26 @@ public: return _do_overlap_2(a,b); } - result_type + Boolean operator()( const Line_arc_2 &a , const Line_arc_2 &b ) const { return _do_overlap_2(a,b);} - result_type + Boolean operator()( const Circular_arc_2 & , const Line_arc_2 & ) const { return false;} - result_type + Boolean operator()( const Line_arc_2 & , const Circular_arc_2 & ) const { return false;} }; - template < class BK > class Intersect_2 : public BK::Circular_kernel:: template Base< BK >::Type::Intersect_2 { -public: typedef typename BK::Circular_kernel:: template Base< BK >::Type::Intersect_2 CK_Intersect_2; @@ -466,6 +437,7 @@ public: typedef typename BK::Circle_2 Circle; typedef typename BK::Line_2 Line_2; +public: using CK_Intersect_2::operator(); template < class OutputIterator > diff --git a/Circular_kernel_3/doc/Circular_kernel_3/Circular_kernel_3.txt b/Circular_kernel_3/doc/Circular_kernel_3/Circular_kernel_3.txt index 937d438332f..5f5ca1dc107 100644 --- a/Circular_kernel_3/doc/Circular_kernel_3/Circular_kernel_3.txt +++ b/Circular_kernel_3/doc/Circular_kernel_3/Circular_kernel_3.txt @@ -24,7 +24,7 @@ for which we refer the user to the \ref chapterkernel23 "2D and 3D Linear Kernel \section sectionSKobjects Spherical Kernel Objects New main geometric objects are introduced by `Spherical_kernel_3`: -circular arcs ((model of `SphericalKernel::CircularArc_3`), points +circular arcs (model of `SphericalKernel::CircularArc_3`), points of circular arcs (model of `SphericalKernel::CircularArcPoint_3`), and line segments (model of `SphericalKernel::LineArc_3`) whose endpoints are points of this new type. 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 b0dd39cd691..07be44cd4fe 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 9fd4f5445df..cd7569ddb0f 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 b589dc78525..99baff709ad 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/Circular_kernel_3/doc/Circular_kernel_3/dependencies b/Circular_kernel_3/doc/Circular_kernel_3/dependencies index bc945e6454c..47c71fadee2 100644 --- a/Circular_kernel_3/doc/Circular_kernel_3/dependencies +++ b/Circular_kernel_3/doc/Circular_kernel_3/dependencies @@ -1,3 +1,4 @@ +Algebraic_foundations Circular_kernel_2 Kernel_23 Number_types diff --git a/Circular_kernel_3/include/CGAL/Circular_kernel_3/Circular_arc_point_3.h b/Circular_kernel_3/include/CGAL/Circular_kernel_3/Circular_arc_point_3.h index 99eb521e1fa..aad283a9b18 100644 --- a/Circular_kernel_3/include/CGAL/Circular_kernel_3/Circular_arc_point_3.h +++ b/Circular_kernel_3/include/CGAL/Circular_kernel_3/Circular_arc_point_3.h @@ -211,7 +211,7 @@ public: const Root_for_spheres_2_3 & coordinates() const { return get_pointee_or_identity(base); } - const CGAL::Bbox_3 bbox() const { + CGAL::Bbox_3 bbox() const { return get_pointee_or_identity(base).bbox(); } diff --git a/Circular_kernel_3/include/CGAL/Circular_kernel_3/Line_arc_3.h b/Circular_kernel_3/include/CGAL/Circular_kernel_3/Line_arc_3.h index de30249490d..9bd639cce02 100644 --- a/Circular_kernel_3/include/CGAL/Circular_kernel_3/Line_arc_3.h +++ b/Circular_kernel_3/include/CGAL/Circular_kernel_3/Line_arc_3.h @@ -168,7 +168,7 @@ namespace CGAL { return begin_less_xyz_than_end() ? target() : source(); } - const CGAL::Bbox_3 bbox() const { + CGAL::Bbox_3 bbox() const { return source().bbox() + target().bbox(); } diff --git a/Circular_kernel_3/include/CGAL/Circular_kernel_3/function_objects_polynomial_sphere.h b/Circular_kernel_3/include/CGAL/Circular_kernel_3/function_objects_polynomial_sphere.h index ff7119586a5..c2940aa42d7 100644 --- a/Circular_kernel_3/include/CGAL/Circular_kernel_3/function_objects_polynomial_sphere.h +++ b/Circular_kernel_3/include/CGAL/Circular_kernel_3/function_objects_polynomial_sphere.h @@ -42,21 +42,27 @@ namespace SphericalFunctors { #define CGAL_SPHERICAL_KERNEL_MACRO_FUNCTOR_COMPARE_(V)\ template < class SK > \ - class Compare_ ##V## _3: public SK::Linear_kernel::Compare_ ##V## _3{\ + class Compare_ ##V## _3 {\ + /*: public SK::Linear_kernel::Compare_ ##V## _3{*/\ + typedef typename SK::Comparison_result Comparison_result;\ typedef typename SK::Circular_arc_point_3 Circular_arc_point_3;\ typedef typename SK::Point_3 Point_3;\ + typedef typename SK::Linear_kernel::Compare_ ##V## _3 Linear_Compare_ ##V## _3;\ public:\ - typedef typename SK::Linear_kernel::Compare_ ##V## _3::result_type result_type;\ - using SK::Linear_kernel::Compare_ ##V## _3::operator();\ - result_type\ + template \ + Comparison_result\ + operator()(const A& a, const B& b) const\ + { return Linear_Compare_ ##V## _3()(a, b); }\ + /*using SK::Linear_kernel::Compare_ ##V## _3::operator();*/\ + Comparison_result\ operator() (const Circular_arc_point_3 &p0,\ const Circular_arc_point_3 &p1) const\ { return SphericalFunctors::compare_ ##V (p0, p1); }\ - result_type\ + Comparison_result\ operator() (const Circular_arc_point_3 &p0,\ const Point_3 &p1) const\ { return SphericalFunctors::compare_ ##V (p0, p1); }\ - result_type\ + Comparison_result\ operator() (const Point_3 &p0,\ const Circular_arc_point_3 &p1) const\ { return SphericalFunctors::compare_ ##V (p0, p1); }\ @@ -68,17 +74,16 @@ template < class SK > \ CGAL_SPHERICAL_KERNEL_MACRO_FUNCTOR_COMPARE_(xy) CGAL_SPHERICAL_KERNEL_MACRO_FUNCTOR_COMPARE_(xyz) +#undef CGAL_SPHERICAL_KERNEL_MACRO_FUNCTOR_COMPARE_ + template class Compute_circular_x_3 { typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; - typedef typename SK::Root_of_2 Root_of_2; public: - - typedef const Root_of_2& result_type; - - result_type operator() (const Circular_arc_point_3 & a) const + // SK::Root_of_2 + decltype(auto) operator() (const Circular_arc_point_3 & a) const { return (a.rep().x()); } }; @@ -86,13 +91,10 @@ template < class SK > \ class Compute_circular_y_3 { typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; - typedef typename SK::Root_of_2 Root_of_2; public: - - typedef const Root_of_2& result_type; - - result_type operator() (const Circular_arc_point_3 & a) const + // SK::Root_of_2 + decltype(auto) operator() (const Circular_arc_point_3 & a) const { return (a.rep().y()); } }; @@ -100,23 +102,18 @@ template < class SK > \ class Compute_circular_z_3 { typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; - typedef typename SK::Root_of_2 Root_of_2; public: - - typedef const Root_of_2& result_type; - - result_type operator() (const Circular_arc_point_3 & a) const + // SK::Root_of_2 + decltype(auto) operator() (const Circular_arc_point_3 & a) const { return (a.rep().z()); } }; template < class SK > class Equal_3 - : public SK::Linear_kernel::Equal_3 + // : public SK::Linear_kernel::Equal_3 { - typedef typename SK::Linear_kernel LK; - typedef typename LK::Equal_3 LK_Equal_3; - + typedef typename SK::Boolean Boolean; typedef typename SK::Point_3 Point_3; typedef typename SK::Vector_3 Vector_3; typedef typename SK::Direction_3 Direction_3; @@ -134,35 +131,39 @@ template < class SK > \ typedef typename SK::Line_arc_3 Line_arc_3; typedef typename SK::Circular_arc_3 Circular_arc_3; + typedef typename SK::Linear_kernel::Equal_3 Linear_equal_3; + public: + // using SK::Linear_kernel::Equal_3::operator(); - typedef typename SK::Linear_kernel::Equal_3::result_type result_type; + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_equal_3()(a, b); } - using SK::Linear_kernel::Equal_3::operator(); - - result_type + Boolean operator() (const Circular_arc_point_3 &c0, const Circular_arc_point_3 &c1) const { return SphericalFunctors::equal(c0, c1); } - result_type + Boolean operator() (const Circular_arc_point_3 &c0, const Point_3 &c1) const { return SphericalFunctors::equal(c0, Circular_arc_point_3(c1)); } - result_type + Boolean operator() (const Point_3 &c0, const Circular_arc_point_3 &c1) const { return SphericalFunctors::equal(Circular_arc_point_3(c0), c1); } - // Our Line_arc_3 dont have orientation - result_type + // Our Line_arc_3 doesn't have orientation + Boolean operator() (const Line_arc_3 &l0, const Line_arc_3 &l1) const { return SphericalFunctors::equal(l0, l1); } - // Our Circular_arc_3 dont have orientation (as parameter) - result_type + // Our Circular_arc_3 doesn't have orientation (as parameter) + Boolean operator() (const Circular_arc_3 &c0, const Circular_arc_3 &c1) const { return SphericalFunctors::equal(c0, c1); } @@ -184,29 +185,26 @@ template < class SK > \ typedef typename Circular_arc_point_3::Root_for_spheres_2_3 Root_for_spheres_2_3; public: - typedef Circular_arc_point_3 result_type; - - result_type + Circular_arc_point_3 operator()(void) { return Rep(); } - result_type - operator()(const Root_of_2 & x, - const Root_of_2 & y, - const Root_of_2 & z - ) const + Circular_arc_point_3 + operator()(const Root_of_2 & x, + const Root_of_2 & y, + const Root_of_2 & z) const { return Rep(x,y,z); } - result_type + Circular_arc_point_3 operator()(const Root_for_spheres_2_3 & np) const { return Rep(np); } - result_type + Circular_arc_point_3 operator()(const Point_3 & p) const { return Rep(p); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Sphere_3 & s1, const Sphere_3 & s2, const Sphere_3 & s3, @@ -214,7 +212,7 @@ template < class SK > \ { return Rep(s1,s2,s3,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Plane_3 & p, const Sphere_3 & s1, const Sphere_3 & s2, @@ -222,7 +220,7 @@ template < class SK > \ { return Rep(p,s1,s2,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Sphere_3 & s1, const Plane_3 & p, const Sphere_3 & s2, @@ -230,7 +228,7 @@ template < class SK > \ { return Rep(p,s1,s2,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Sphere_3 & s1, const Sphere_3 & s2, const Plane_3 & p, @@ -238,7 +236,7 @@ template < class SK > \ { return Rep(p,s1,s2,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Plane_3 & p1, const Plane_3 & p2, const Sphere_3 & s, @@ -246,7 +244,7 @@ template < class SK > \ { return Rep(p1,p2,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Plane_3 & p1, const Sphere_3 & s, const Plane_3 & p2, @@ -254,7 +252,7 @@ template < class SK > \ { return Rep(p1,p2,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Sphere_3 & s, const Plane_3 & p1, const Plane_3 & p2, @@ -262,42 +260,42 @@ template < class SK > \ { return Rep(p1,p2,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Line_3 & l, const Sphere_3 & s, const bool less_xyz = true) const { return Rep(l,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Sphere_3 & s, const Line_3 & l, const bool less_xyz = true) const { return Rep(l,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Circle_3 & c, const Sphere_3 & s, const bool less_xyz = true) const { return Rep(c,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Sphere_3 & s, const Circle_3 & c, const bool less_xyz = true) const { return Rep(c,s,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Circle_3 & c, const Plane_3 & p, const bool less_xyz = true) const { return Rep(c,p,less_xyz); } // Not Documented - result_type + Circular_arc_point_3 operator()(const Plane_3 & p, const Circle_3 & c, const bool less_xyz = true) const @@ -309,83 +307,78 @@ template < class SK > \ class Construct_sphere_3 { typedef typename SK::Circular_arc_3 Circular_arc_3; - typedef typename SK::Linear_kernel LK; - typedef typename LK::Point_3 Point_3; - typedef typename LK::Circle_3 Circle_3; - typedef typename LK::Sphere_3 Sphere_3; + typedef typename SK::Sphere_3 Sphere_3; + typedef typename SK::Point_3 Point_3; + typedef typename SK::Circle_3 Circle_3; + typedef typename SK::FT FT; + + typedef typename SK::Linear_kernel LK; typedef typename LK::Construct_sphere_3 LK_Construct_sphere_3; - typedef typename LK::FT FT; public: - - typedef typename SK::Linear_kernel::Construct_sphere_3::result_type result_type; - - result_type + Sphere_3 operator()( Return_base_tag tag, const Point_3& center, const FT& squared_radius, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(tag, center, squared_radius, orientation); } - result_type + Sphere_3 operator()( Return_base_tag tag, const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { return LK_Construct_sphere_3()(tag, p, q, r, s); } - result_type + Sphere_3 operator()( Return_base_tag tag, const Point_3& p, const Point_3& q, const Point_3& r, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(tag, p, q, r, orientation); } - result_type + Sphere_3 operator()( Return_base_tag tag, const Point_3& p, const Point_3& q, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(tag, p, q, orientation); } - result_type + Sphere_3 operator()( Return_base_tag tag, const Point_3& center, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(tag, center, orientation); } - result_type + decltype(auto) operator() (Return_base_tag tag, const Circle_3 & c) const { return LK_Construct_sphere_3()(tag, c); } - - - - result_type + Sphere_3 operator()( const Point_3& center, const FT& squared_radius, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(center, squared_radius, orientation); } - result_type + Sphere_3 operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { return LK_Construct_sphere_3()(p, q, r, s); } - result_type + Sphere_3 operator()( const Point_3& p, const Point_3& q, const Point_3& r, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(p, q, r, orientation); } - result_type + Sphere_3 operator()( const Point_3& p, const Point_3& q, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(p, q, orientation); } - result_type + Sphere_3 operator()( const Point_3& center, Orientation orientation = COUNTERCLOCKWISE) const { return LK_Construct_sphere_3()(center, orientation); } - result_type + decltype(auto) operator() (const Circle_3 & c) const { return LK_Construct_sphere_3()(c); } - result_type + Sphere_3 operator() ( const typename SK::Polynomial_for_spheres_2_3 &eq ) { return SphericalFunctors::construct_sphere_3(eq); } - result_type operator() (const Circular_arc_3 & c) const + decltype(auto) operator() (const Circular_arc_3 & c) const { return c.rep().diametral_sphere(); } }; @@ -394,98 +387,92 @@ template < class SK > \ class Construct_plane_3 { typedef typename SK::Circular_arc_3 Circular_arc_3; + typedef typename SK::RT RT; + typedef typename SK::Point_3 Point_3; + typedef typename SK::Vector_3 Vector_3; + typedef typename SK::Direction_3 Direction_3; + typedef typename SK::Line_3 Line_3; + typedef typename SK::Ray_3 Ray_3; + typedef typename SK::Segment_3 Segment_3; + typedef typename SK::Plane_3 Plane_3; + typedef typename SK::Circle_3 Circle_3; + typedef typename SK::Linear_kernel LK; typedef typename LK::Construct_plane_3 LK_Construct_plane_3; - typedef typename LK::RT RT; - typedef typename LK::Point_3 Point_3; - typedef typename LK::Vector_3 Vector_3; - typedef typename LK::Direction_3 Direction_3; - typedef typename LK::Line_3 Line_3; - typedef typename LK::Ray_3 Ray_3; - typedef typename LK::Segment_3 Segment_3; - typedef typename LK::Plane_3 Plane_3; - typedef typename LK::Circle_3 Circle_3; public: - - typedef typename SK::Linear_kernel::Construct_plane_3::result_type result_type; - - public: - - result_type + Plane_3 operator()(Return_base_tag tag, const RT& a, const RT& b, const RT& c, const RT& d) const { return LK_Construct_plane_3()(tag, a, b, c, d); } - result_type + Plane_3 operator()(Return_base_tag tag, const Point_3& p, const Point_3& q, const Point_3& r) const { return LK_Construct_plane_3()(tag, p, q, r); } - result_type + Plane_3 operator()(Return_base_tag tag, const Point_3& p, const Direction_3& d) const { return LK_Construct_plane_3()(tag, p, d); } - result_type + Plane_3 operator()(Return_base_tag tag, const Point_3& p, const Vector_3& v) const { return LK_Construct_plane_3()(tag, p, v); } - result_type + Plane_3 operator()(Return_base_tag tag, const Line_3& l, const Point_3& p) const { return LK_Construct_plane_3()(tag, l, p); } - result_type + Plane_3 operator()(Return_base_tag tag, const Ray_3& r, const Point_3& p) const { return LK_Construct_plane_3()(tag, r, p); } - result_type + Plane_3 operator()(Return_base_tag tag, const Segment_3& s, const Point_3& p) const { return LK_Construct_plane_3()(tag, s, p); } - result_type + decltype(auto) operator()(Return_base_tag tag, const Circle_3 & c) const { return LK_Construct_plane_3()(tag, c); } - result_type + Plane_3 operator()(const RT& a, const RT& b, const RT& c, const RT& d) const { return this->operator()(Return_base_tag(), a, b, c, d); } - result_type + Plane_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return this->operator()(Return_base_tag(), p, q, r); } - result_type + Plane_3 operator()(const Point_3& p, const Direction_3& d) const { return this->operator()(Return_base_tag(), p, d); } - result_type + Plane_3 operator()(const Point_3& p, const Vector_3& v) const { return this->operator()(Return_base_tag(), p, v); } - result_type + Plane_3 operator()(const Line_3& l, const Point_3& p) const { return this->operator()(Return_base_tag(), l, p); } - result_type + Plane_3 operator()(const Ray_3& r, const Point_3& p) const { return this->operator()(Return_base_tag(), r, p); } - result_type + Plane_3 operator()(const Segment_3& s, const Point_3& p) const { return this->operator()(Return_base_tag(), s, p); } - result_type + decltype(auto) operator()(const Circle_3 & c) const { return this->operator()(Return_base_tag(), c); } - result_type + Plane_3 operator() ( const typename SK::Polynomial_1_3 &eq ) { return SphericalFunctors::construct_plane_3(eq); } - result_type operator() (const Circular_arc_3 & c) const + decltype(auto) operator() (const Circular_arc_3 & c) const { return c.rep().supporting_plane(); } }; - - template class Construct_line_3 { @@ -498,54 +485,53 @@ template < class SK > \ typedef typename SK::Ray_3 Ray_3; public: typedef typename SK::Linear_kernel::Construct_line_3 LK_Construct_line_3; - typedef typename LK_Construct_line_3::result_type result_type; - result_type + Line_3 operator()(Return_base_tag, const Point_3& p, const Point_3& q) const { return LK_Construct_line_3()(p, Vector_3(p, q)); } - result_type + Line_3 operator()(Return_base_tag, const Point_3& p, const Direction_3& d) const { return operator()(Return_base_tag(), p, Vector_3(d.dx(), d.dy(), d.dz())); } - result_type + Line_3 operator()(Return_base_tag, const Point_3& p, const Vector_3& v) const { return LK_Construct_line_3()(p, v); } - result_type + Line_3 operator()(Return_base_tag, const Segment_3& s) const { return LK_Construct_line_3()(s.source(), Vector_3(s.source(), s.target())); } - result_type + Line_3 operator()(Return_base_tag, const Ray_3& r) const { return LK_Construct_line_3()(r.source(), Vector_3(r.source(), r.second_point())); } - result_type + Line_3 operator()(const Point_3& p, const Point_3& q) const { return this->operator()(Return_base_tag(), p, q); } - result_type + Line_3 operator()(const Point_3& p, const Direction_3& d) const { return this->operator()(Return_base_tag(), p, d); } - result_type + Line_3 operator()(const Point_3& p, const Vector_3& v) const { return this->operator()(Return_base_tag(), p, v); } - result_type + Line_3 operator()(const Segment_3& s) const { return this->operator()(Return_base_tag(), s); } - result_type + Line_3 operator()(const Ray_3& r) const { return this->operator()(Return_base_tag(), r); } - const result_type& + decltype(auto) operator() (const Line_arc_3 & a) const { return (a.rep().supporting_line()); } - result_type + Line_3 operator() ( const typename SK::Polynomials_for_line_3 &eq ) { return SphericalFunctors::construct_line_3(eq); } @@ -554,8 +540,6 @@ template < class SK > \ template < class SK > class Construct_circle_3 { - typedef typename SK::Linear_kernel::Construct_circle_3 Extended; - typedef typename Extended::result_type forwarded_result_type; typedef typename SK::FT FT; typedef typename SK::Point_3 Point_3; typedef typename SK::Plane_3 Plane_3; @@ -564,57 +548,57 @@ template < class SK > \ typedef typename SK::Vector_3 Vector_3; typedef typename SK::Direction_3 Direction_3; typedef typename SK::Circular_arc_3 Circular_arc_3; - typedef typename SK::Kernel_base::Circle_3 RCircle_3; typedef typename Circle_3::Rep Rep; - public: - forwarded_result_type + typedef typename SK::Linear_kernel::Construct_circle_3 LK_Construct_circle_3; + + public: + Circle_3 operator()(const Point_3& p, const FT& sr, const Plane_3& plane) const - { return Extended()(p, sr, plane); } + { return LK_Construct_circle_3()(p, sr, plane); } - forwarded_result_type + Circle_3 operator() (const Point_3& p, const FT& sr, const Vector_3& v) const - { return Extended()(p, sr, v); } + { return LK_Construct_circle_3()(p, sr, v); } - forwarded_result_type + Circle_3 operator() (const Point_3& p, const FT& sr, const Direction_3& d) const - { return Extended()(p, sr, d); } + { return LK_Construct_circle_3()(p, sr, d); } - forwarded_result_type + Circle_3 operator() (const Sphere_3& s1, const Sphere_3& s2) const - { return Extended()(s1, s2); } + { return LK_Construct_circle_3()(s1, s2); } - forwarded_result_type + Circle_3 operator() (const Plane_3& p, const Sphere_3& s) const - { return Extended()(p, s); } + { return LK_Construct_circle_3()(p, s); } - forwarded_result_type + Circle_3 operator() (const Sphere_3& s, const Plane_3& p) const - { return Extended()(p, s); } + { return LK_Construct_circle_3()(p, s); } - forwarded_result_type + Circle_3 operator() (const Plane_3& p, const Sphere_3& s, int a) const - { return Extended()(p, s, a); } + { return LK_Construct_circle_3()(p, s, a); } - forwarded_result_type + Circle_3 operator() (const Sphere_3& s, const Plane_3& p, int a) const - { return Extended()(p, s, a); } + { return LK_Construct_circle_3()(p, s, a); } - forwarded_result_type + Circle_3 operator()( const Point_3& p1, const Point_3& p2, const Point_3& p3) const - { return Extended()(p1, p2, p3); } + { return LK_Construct_circle_3()(p1, p2, p3); } - forwarded_result_type + Circle_3 operator() ( const typename SK::Polynomials_for_circle_3 &eq ) - { return Rep(construct_circle_3(eq)); } + { return construct_circle_3(eq); } - const forwarded_result_type& + decltype(auto) operator() (const Circular_arc_3 & a) const { return (a.rep().supporting_circle()); } - }; template < class SK > @@ -632,43 +616,41 @@ template < class SK > \ typedef typename Line_arc_3::Rep Rep; public: - typedef Line_arc_3 result_type; - - result_type + Line_arc_3 operator()(void) const { return Rep(); } - result_type + Line_arc_3 operator()(const Line_3 &l, const Circular_arc_point_3 &s, const Circular_arc_point_3 &t) const { return Rep(l,s,t); } - result_type + Line_arc_3 operator()(const Point_3 &s, const Point_3 &t) const { return Rep(s,t); } - result_type + Line_arc_3 operator()(const Segment_3 &s) const { return Rep(s); } // Not Documented - result_type + Line_arc_3 operator()(const Line_3 &l, const Sphere_3 &s, bool less_xyz_first = true) const { return Rep(l,s,less_xyz_first); } // Not Documented - result_type + Line_arc_3 operator()(const Sphere_3 &s, const Line_3 &l, bool less_xyz_first = true) const { return Rep(l,s,less_xyz_first); } // Not Documented - result_type + Line_arc_3 operator()(const Line_3 &l, const Sphere_3 &s1, bool less_xyz_s1, const Sphere_3 &s2, bool less_xyz_s2) const @@ -676,7 +658,7 @@ template < class SK > \ s2,less_xyz_s2); } // Not Documented - result_type + Line_arc_3 operator()(const Sphere_3 &s1, bool less_xyz_s1, const Sphere_3 &s2, bool less_xyz_s2, const Line_3 &l) const @@ -684,14 +666,14 @@ template < class SK > \ s2,less_xyz_s2); } // Not Documented - result_type + Line_arc_3 operator()(const Line_3 &l, const Plane_3 &p1, const Plane_3 &p2) const { return Rep(l,p1,p2); } // Not Documented - result_type + Line_arc_3 operator()(const Plane_3 &p1, const Plane_3 &p2, const Line_3 &l) const @@ -715,56 +697,55 @@ template < class SK > \ typedef typename SK::Circular_arc_3 Circular_arc_3; typedef typename SK::Kernel_base::Circular_arc_3 RCircular_arc_3; typedef typename Circular_arc_3::Rep Rep; - public: - typedef Circular_arc_3 result_type; - result_type + public: + Circular_arc_3 operator()(void) const { return Rep(); } - result_type + Circular_arc_3 operator()(const Circle_3 &c) const { return Rep(c); } - result_type + Circular_arc_3 operator()(const Circle_3 &c,const Circular_arc_point_3& pt) const { return Rep(c,pt); } - result_type + Circular_arc_3 operator()(const Circle_3 &l, const Circular_arc_point_3 &s, const Circular_arc_point_3 &t) const { return Rep(l,s,t); } // Not Documented - result_type + Circular_arc_3 operator()(const Circle_3 &c, const Sphere_3 &s1, bool less_xyz_s1, const Sphere_3 &s2, bool less_xyz_s2) const { return Rep(c,s1,less_xyz_s1,s2,less_xyz_s2); } // Not Documented - result_type + Circular_arc_3 operator()(const Sphere_3 &s1, bool less_xyz_s1, const Sphere_3 &s2, bool less_xyz_s2, const Circle_3 &c) const { return Rep(c,s1,less_xyz_s1,s2,less_xyz_s2); } // Not Documented - result_type + Circular_arc_3 operator()(const Circle_3 &c, const Plane_3 &p1, bool less_xyz_p1, const Plane_3 &p2, bool less_xyz_p2) const { return Rep(c,p1,less_xyz_p1,p2,less_xyz_p2); } // Not Documented - result_type + Circular_arc_3 operator()(const Plane_3 &p1, bool less_xyz_p1, const Plane_3 &p2, bool less_xyz_p2, const Circle_3 &c) const { return Rep(c,p1,less_xyz_p1,p2,less_xyz_p2); } - result_type + Circular_arc_3 operator()(const Point_3 &begin, const Point_3 &middle, const Point_3 &end) const @@ -776,13 +757,9 @@ template < class SK > \ class Construct_circular_min_vertex_3 { typedef typename SK::Line_arc_3 Line_arc_3; - typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; public: - - typedef const Circular_arc_point_3& result_type; - - result_type operator() (const Line_arc_3 & a) const + decltype(auto) operator() (const Line_arc_3& a) const { return (a.rep().lower_xyz_extremity()); } }; @@ -791,13 +768,9 @@ template < class SK > \ class Construct_circular_max_vertex_3 { typedef typename SK::Line_arc_3 Line_arc_3; - typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; public: - - typedef const Circular_arc_point_3& result_type; - - result_type operator() (const Line_arc_3 & a) const + decltype(auto) operator() (const Line_arc_3& a) const { return (a.rep().higher_xyz_extremity()); } }; @@ -807,16 +780,12 @@ template < class SK > \ { typedef typename SK::Line_arc_3 Line_arc_3; typedef typename SK::Circular_arc_3 Circular_arc_3; - typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; public: - - typedef const Circular_arc_point_3& result_type; - - result_type operator() (const Line_arc_3 & a) const + decltype(auto) operator() (const Line_arc_3& a) const { return (a.rep().source()); } - result_type operator() (const Circular_arc_3 & a) const + decltype(auto) operator() (const Circular_arc_3& a) const { return (a.rep().source()); } }; @@ -825,30 +794,27 @@ template < class SK > \ class Construct_circular_target_vertex_3 { typedef typename SK::Line_arc_3 Line_arc_3; - typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; typedef typename SK::Circular_arc_3 Circular_arc_3; public: - - typedef const Circular_arc_point_3& result_type; - - result_type operator() (const Line_arc_3 & a) const + decltype(auto) operator() (const Line_arc_3 & a) const { return (a.rep().target()); } - result_type operator() (const Circular_arc_3 & a) const + decltype(auto) operator() (const Circular_arc_3 & a) const { return (a.rep().target()); } }; template < class SK > class Has_on_3 - : public SK::Linear_kernel::Has_on_3 + // : public SK::Linear_kernel::Has_on_3 { + typedef typename SK::Boolean Boolean; typedef typename SK::Point_3 Point_3; typedef typename SK::Sphere_3 Sphere_3; typedef typename SK::Plane_3 Plane_3; typedef typename SK::Line_3 Line_3; - typedef typename SK::Segment_3 Segment_3; + typedef typename SK::Segment_3 Segment_3; typedef typename SK::Ray_3 Ray_3; typedef typename SK::Triangle_3 Triangle_3; typedef typename SK::Line_arc_3 Line_arc_3; @@ -856,69 +822,73 @@ template < class SK > \ typedef typename SK::Circular_arc_3 Circular_arc_3; typedef typename SK::Circle_3 Circle_3; + typedef typename SK::Linear_kernel::Has_on_3 Linear_Has_on_3; public: - typedef typename SK::Linear_kernel::Has_on_3::result_type result_type; + // using SK::Linear_kernel::Has_on_3::operator(); - using SK::Linear_kernel::Has_on_3::operator(); + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Has_on_3()(a, b); } - result_type + Boolean operator()(const Sphere_3 &a, const Circular_arc_point_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Plane_3 &a, const Circular_arc_point_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Line_3 &a, const Circular_arc_point_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Circle_3 &a, const Circular_arc_point_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Line_arc_3 &a, const Circular_arc_point_3 &p, const bool already_know_point_on_line = false) const { return SphericalFunctors::has_on(a, p, already_know_point_on_line); } - result_type + Boolean operator()(const Line_arc_3 &a, const Point_3 &p, const bool already_know_point_on_line = false) const { return SphericalFunctors::has_on(a, p, already_know_point_on_line); } - result_type + Boolean operator()(const Plane_3 &p, const Line_arc_3 &a) const { return SphericalFunctors::has_on(p, a); } - result_type + Boolean operator()(const Line_3 &a, const Line_arc_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Circular_arc_3 &a, const Point_3 &p, const bool has_on_supporting_circle = false) const { return SphericalFunctors::has_on(a, p, has_on_supporting_circle); } - result_type + Boolean operator()(const Circular_arc_3 &a, const Circular_arc_point_3 &p, const bool has_on_supporting_circle = false) const { return SphericalFunctors::has_on(a, p, has_on_supporting_circle); } - result_type + Boolean operator()(const Sphere_3 &a, const Circular_arc_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Plane_3 &a, const Circular_arc_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Circle_3 &a, const Circular_arc_3 &p) const { return SphericalFunctors::has_on(a, p); } - result_type + Boolean operator()(const Circular_arc_3 &p, const Circle_3 &a) const { return SphericalFunctors::has_on(p, a); } @@ -926,9 +896,9 @@ template < class SK > \ template < class SK > class Do_intersect_3 - : public SK::Linear_kernel::Do_intersect_3 + // : public SK::Linear_kernel::Do_intersect_3 { - + typedef typename SK::Boolean Boolean; typedef typename SK::Sphere_3 Sphere_3; typedef typename SK::Line_3 Line_3; typedef typename SK::Line_arc_3 Line_arc_3; @@ -937,20 +907,25 @@ template < class SK > \ typedef typename SK::Circle_3 Circle_3; typedef typename SK::Circle_3 Circular_arc_point_3; - public: - typedef typename SK::Linear_kernel::Do_intersect_3::result_type result_type; + typedef typename SK::Linear_kernel::Do_intersect_3 Linear_Do_intersect_3; - using SK::Linear_kernel::Do_intersect_3::operator(); + public: + // using SK::Linear_kernel::Do_intersect_3::operator(); + + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Do_intersect_3()(a, b); } #define CGAL_SPHERICAL_KERNEL_MACRO_DO_INTERSECTION_3_2(A,B) \ - result_type \ + Boolean \ operator()(const A & c1, const B & c2) const \ { std::vector< typename SK3_Intersection_traits::type > res; \ typename SK::Intersect_3()(c1,c2,std::back_inserter(res)); \ return !res.empty(); } #define CGAL_SPHERICAL_KERNEL_MACRO_DO_INTERSECTION_3_3(A,B,C) \ - result_type \ + Boolean \ operator()(const A & c1, const B & c2, const C & c3) const \ { std::vector< typename SK3_Intersection_traits::type > res; \ typename SK::Intersect_3()(c1,c2,c3,std::back_inserter(res)); \ @@ -1008,22 +983,21 @@ template < class SK > \ typedef typename SK::Circle_3 Circle_3; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; - public: + typedef typename SK::Linear_kernel::Intersect_3 Linear_Intersect_3; + public: //using SK::Linear_kernel::Intersect_3::operator(); - typedef typename SK::Linear_kernel::Intersect_3 Intersect_linear_3; - - template + template decltype(auto) operator()(const A& a, const B& b) const{ - return Intersect_linear_3()(a,b); + return Linear_Intersect_3()(a,b); } decltype(auto) operator()(const Plane_3& p, const Plane_3& q, const Plane_3& r) const { - return Intersect_linear_3()(p, q, r); + return Linear_Intersect_3()(p, q, r); } template < class OutputIterator > @@ -1238,18 +1212,17 @@ template < class SK > \ template < class SK > class Do_overlap_3 { + typedef typename SK::Boolean Boolean; typedef typename SK::Line_arc_3 Line_arc_3; typedef typename SK::Circular_arc_3 Circular_arc_3; public: - typedef bool result_type; - - result_type + Boolean operator() (const Line_arc_3 &l1, const Line_arc_3 &l2, const bool known_equal_supporting_line = false) const { return SphericalFunctors::do_overlap(l1, l2, known_equal_supporting_line); } - result_type + Boolean operator() (const Circular_arc_3 &c1, const Circular_arc_3 &c2, const bool known_equal_supporting_circle = false) const { return SphericalFunctors::do_overlap(c1, c2, known_equal_supporting_circle); } @@ -1264,16 +1237,13 @@ template < class SK > \ typedef typename SK::Circular_arc_3 Circular_arc_3; public: - - typedef void result_type; - - result_type + void operator()(const Line_arc_3 &l, const Circular_arc_point_3 &p, Line_arc_3 &ca1, Line_arc_3 &ca2) const { return SphericalFunctors::split(l, p, ca1, ca2); } - result_type + void operator()(const Circular_arc_3 &c, const Circular_arc_point_3 &p, Circular_arc_3 &ca1, Circular_arc_3 &ca2) const @@ -1283,7 +1253,7 @@ template < class SK > \ template class Construct_bbox_3 - : public SK::Linear_kernel::Construct_bbox_3 + // : public SK::Linear_kernel::Construct_bbox_3 { typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; typedef typename SK::Circular_arc_3 Circular_arc_3; @@ -1296,80 +1266,96 @@ template < class SK > \ typedef typename SK::Iso_cuboid_3 Iso_cuboid_3; typedef typename SK::Line_arc_3 Line_arc_3; + typedef typename SK::Linear_kernel::Construct_bbox_3 Linear_Construct_bbox_3; + public: + // using SK::Linear_kernel::Construct_bbox_3::operator(); - typedef typename SK::Linear_kernel::Construct_bbox_3::result_type result_type; + template + decltype(auto) + operator()(const A& a) const + { return Linear_Construct_bbox_3()(a); } - using SK::Linear_kernel::Construct_bbox_3::operator(); - - result_type operator() (const Circular_arc_point_3 & c) const + decltype(auto) operator() (const Circular_arc_point_3 & c) const { return c.rep().bbox(); } - result_type operator() (const Line_arc_3 & l) const + decltype(auto) operator() (const Line_arc_3 & l) const { return l.rep().bbox(); } - result_type operator() (const Circular_arc_3 & c) const + decltype(auto) operator() (const Circular_arc_3 & c) const { return c.rep().bbox(); } }; template class Compute_approximate_squared_length_3 - : public SK::Linear_kernel::Compute_approximate_squared_length_3 + // : public SK::Linear_kernel::Compute_approximate_squared_length_3 { typedef typename SK::Circle_3 Circle_3; typedef typename SK::Circular_arc_3 Circular_arc_3; typedef typename SK::FT FT; + typedef typename SK::Linear_kernel::Compute_approximate_squared_length_3 Linear_Compute_approximate_squared_length_3; + public: + // using SK::Linear_kernel::Compute_approximate_squared_length_3::operator(); - typedef double result_type; - using SK::Linear_kernel::Compute_approximate_squared_length_3::operator(); + template + double + operator()(const A& a) const + { return Linear_Compute_approximate_squared_length_3()(a); } - result_type operator() (const Circular_arc_3 & c) const + double operator() (const Circular_arc_3 & c) const { return c.rep().approximate_squared_length(); } }; template class Compute_approximate_angle_3 - : public SK::Linear_kernel::Compute_approximate_angle_3 + // : public SK::Linear_kernel::Compute_approximate_angle_3 { - typedef typename SK::Point_3 Point_3; - typedef typename SK::Vector_3 Vector_3; typedef typename SK::Circular_arc_3 Circular_arc_3; - typedef typename SK::FT FT; + + typedef typename SK::Linear_kernel::Compute_approximate_angle_3 Linear_Compute_approximate_angle_3; public: + // using SK::Linear_kernel::Compute_approximate_angle_3::operator(); - typedef double result_type; + template + decltype(auto) // the linear kernel has "FT" as return type... + operator()(const Args&... args) const + { return Linear_Compute_approximate_angle_3()(args...); } - using SK::Linear_kernel::Compute_approximate_angle_3::operator(); - - result_type operator() (const Circular_arc_3 & c) const + double operator() (const Circular_arc_3 & c) const { return c.rep().approximate_angle(); } }; template class Bounded_side_3 - : public SK::Linear_kernel::Bounded_side_3 + // : public SK::Linear_kernel::Bounded_side_3 { + typedef typename SK::Bounded_side Bounded_side; typedef typename SK::Sphere_3 Sphere_3; typedef typename SK::Circle_3 Circle_3; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; typedef typename SK::Point_3 Point_3; + typedef typename SK::Linear_kernel::Bounded_side_3 Linear_Bounded_side_3; + public: - typedef typename SK::Linear_kernel::Bounded_side_3::result_type result_type; + // using SK::Linear_kernel::Bounded_side_3::operator(); - using SK::Linear_kernel::Bounded_side_3::operator(); + template + Bounded_side + operator()(const A& a, const B& b) const + { return Linear_Bounded_side_3()(a, b); } - result_type + Bounded_side operator()( const Sphere_3& s, const Circular_arc_point_3& p) const { return SphericalFunctors::bounded_side(s,p); } - result_type + Bounded_side operator()( const Circle_3& c, const Circular_arc_point_3& p) const { return SphericalFunctors::bounded_side(c,p); } @@ -1379,23 +1365,29 @@ template < class SK > \ template class Has_on_bounded_side_3 - : public SK::Linear_kernel::Has_on_bounded_side_3 + // : public SK::Linear_kernel::Has_on_bounded_side_3 { + typedef typename SK::Boolean Boolean; typedef typename SK::Sphere_3 Sphere_3; typedef typename SK::Circle_3 Circle_3; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; typedef typename SK::Point_3 Point_3; + typedef typename SK::Linear_kernel::Has_on_bounded_side_3 Linear_Has_on_bounded_side_3; + public: - typedef typename SK::Linear_kernel::Has_on_bounded_side_3::result_type result_type; + // using SK::Linear_kernel::Has_on_bounded_side_3::operator(); - using SK::Linear_kernel::Has_on_bounded_side_3::operator(); + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Has_on_bounded_side_3()(a, b); } - result_type + Boolean operator()( const Sphere_3& s, const Circular_arc_point_3& p) const { return SK().bounded_side_3_object()(s,p) == ON_BOUNDED_SIDE; } - result_type + Boolean operator()( const Circle_3& c, const Circular_arc_point_3& p) const { return SK().bounded_side_3_object()(c,p) == ON_BOUNDED_SIDE; } @@ -1405,87 +1397,93 @@ template < class SK > \ template class Has_on_unbounded_side_3 - : public SK::Linear_kernel::Has_on_unbounded_side_3 + // : public SK::Linear_kernel::Has_on_unbounded_side_3 { + typedef typename SK::Boolean Boolean; typedef typename SK::Sphere_3 Sphere_3; typedef typename SK::Circle_3 Circle_3; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; typedef typename SK::Point_3 Point_3; + typedef typename SK::Linear_kernel::Has_on_unbounded_side_3 Linear_Has_on_unbounded_side_3; + public: - typedef typename SK::Linear_kernel::Has_on_unbounded_side_3::result_type result_type; + // using SK::Linear_kernel::Has_on_unbounded_side_3::operator(); - using SK::Linear_kernel::Has_on_unbounded_side_3::operator(); + template + Boolean + operator()(const A& a, const B& b) const + { return Linear_Has_on_unbounded_side_3()(a, b); } - result_type + Boolean operator()( const Sphere_3& s, const Circular_arc_point_3& p) const { return SK().bounded_side_3_object()(s,p) == ON_UNBOUNDED_SIDE; } - result_type + Boolean operator()( const Circle_3& c, const Circular_arc_point_3& p) const { return SK().bounded_side_3_object()(c,p) == ON_UNBOUNDED_SIDE; } // We can maybe optimize it doing the operator() for point_3 too - }; template - class Is_theta_monotone_3{ + class Is_theta_monotone_3 + { typename SK::Sphere_3 sphere_; - public: - typedef bool result_type; + public: Is_theta_monotone_3(const typename SK::Sphere_3& sphere):sphere_(sphere){} - result_type + typename SK::Boolean operator()(const typename SK::Circular_arc_3& arc) const { return SphericalFunctors::is_theta_monotone_3(arc,sphere_); } }; template < class SK > - class Compare_theta_3{ + class Compare_theta_3 + { + typedef typename SK::Comparison_result Comparison_result; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; - typedef typename SK::Vector_3 Vector_3; + typedef typename SK::Vector_3 Vector_3; typename SK::Sphere_3 sphere_; public: - typedef CGAL::Comparison_result result_type; - Compare_theta_3(const typename SK::Sphere_3& sphere):sphere_(sphere){} - result_type + Comparison_result operator() (const Circular_arc_point_3 &p0, const Circular_arc_point_3 &p1) const { return SphericalFunctors::compare_theta_of_pts(p0, p1,sphere_); } - result_type + Comparison_result operator() (const Circular_arc_point_3 &p, const Vector_3 &v) const { return SphericalFunctors::compare_theta_pt_vector(p,v,sphere_); } - result_type + Comparison_result operator() (const Vector_3 &m1, const Vector_3 &m2) const { return SphericalFunctors::compare_theta_vectors(m1,m2); } - result_type + Comparison_result operator() (const Vector_3 &v,const Circular_arc_point_3 &p0) const { return CGAL::opposite( SphericalFunctors::compare_theta_pt_vector(p0, v,sphere_) ); } }; template < class SK > - class Compare_theta_z_3{ + class Compare_theta_z_3 + { + typedef typename SK::Comparison_result Comparison_result; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; + typename SK::Sphere_3 sphere_; public: - typedef CGAL::Comparison_result result_type; - Compare_theta_z_3(const typename SK::Sphere_3& sphere):sphere_(sphere){} - result_type + Comparison_result operator() (const Circular_arc_point_3 &p0, const Circular_arc_point_3 &p1,bool decreasing_z=false) const { return SphericalFunctors::compare_theta_z(p0, p1,sphere_,decreasing_z); } @@ -1493,7 +1491,8 @@ template < class SK > \ }; template < class SK > - class Make_theta_monotone_3{ + class Make_theta_monotone_3 + { typename SK::Sphere_3 sphere_; public: @@ -1512,16 +1511,18 @@ template < class SK > \ }; template - class Compare_z_to_right_3{ + class Compare_z_to_right_3 + { + typedef typename SK::Comparison_result Comparison_result; typedef typename SK::Circular_arc_point_3 Circular_arc_point_3; typedef typename SK::Circular_arc_3 Circular_arc_3; + typename SK::Sphere_3 sphere_; public: - typedef CGAL::Comparison_result result_type; Compare_z_to_right_3(const typename SK::Sphere_3& sphere):sphere_(sphere){} - result_type + Comparison_result operator()(const Circular_arc_3& arc1,const Circular_arc_3& arc2,const Circular_arc_point_3& pt,bool do_to_the_left=false){ CGAL_kernel_precondition(SK().has_on_3_object()(sphere_,arc1)); CGAL_kernel_precondition(SK().has_on_3_object()(sphere_,arc2)); @@ -1544,14 +1545,16 @@ template < class SK > \ }; template - class Compare_z_at_theta_3{ - typename SK::Sphere_3 sphere_; + class Compare_z_at_theta_3 + { + typedef typename SK::Comparison_result Comparison_result; + + typename SK::Sphere_3 sphere_; public: - typedef CGAL::Comparison_result result_type; Compare_z_at_theta_3(const typename SK::Sphere_3& sphere):sphere_(sphere){} - result_type + Comparison_result operator()( const typename SK::Circular_arc_3& arc1, const typename SK::Circular_arc_3& arc2, const typename SK::Vector_3& m) const @@ -1559,7 +1562,7 @@ template < class SK > \ return SphericalFunctors::compare_z_at_theta_arcs(arc1,arc2,m,sphere_); } - result_type + Comparison_result operator()(const typename SK::Circular_arc_point_3& point, const typename SK::Circular_arc_3& arc) const { diff --git a/Circular_kernel_3/include/CGAL/Circular_kernel_3/internal_functions_on_sphere_3.h b/Circular_kernel_3/include/CGAL/Circular_kernel_3/internal_functions_on_sphere_3.h index 73b38353b5c..f644d3e6ac1 100644 --- a/Circular_kernel_3/include/CGAL/Circular_kernel_3/internal_functions_on_sphere_3.h +++ b/Circular_kernel_3/include/CGAL/Circular_kernel_3/internal_functions_on_sphere_3.h @@ -85,7 +85,7 @@ namespace CGAL { template < class SK > inline - typename SK::Linear_kernel::Bounded_side_3::result_type + typename SK::Bounded_side bounded_side(const typename SK::Sphere_3 &s, const typename SK::Circular_arc_point_3 &p) { typedef typename SK::Algebraic_kernel Algebraic_kernel; @@ -99,7 +99,7 @@ namespace CGAL { template < class SK > inline - typename SK::Linear_kernel::Bounded_side + typename SK::Bounded_side bounded_side(const typename SK::Circle_3 &c, const typename SK::Circular_arc_point_3 &p) { typedef typename SK::Algebraic_kernel Algebraic_kernel; @@ -471,7 +471,7 @@ namespace CGAL { return std::transform(solutions.begin(), solutions.end(), res, internal::pair_transform()); } - // At the moment we dont need those functions + // At the moment we don't need those functions // But in the future maybe (some make_x_monotone? etc..) template typename SK::Circular_arc_point_3 diff --git a/Circular_kernel_3/include/CGAL/Spherical_kernel_intersections.h b/Circular_kernel_3/include/CGAL/Spherical_kernel_intersections.h index 9360c96d18f..a587d87cea4 100644 --- a/Circular_kernel_3/include/CGAL/Spherical_kernel_intersections.h +++ b/Circular_kernel_3/include/CGAL/Spherical_kernel_intersections.h @@ -46,7 +46,7 @@ intersection(const A &c1, const B &c2, OutputIterator res) \ } \ template \ inline \ -bool \ +typename K::Boolean \ do_intersect(const A &c1, const B &c2) \ { \ return typename K::Do_intersect_3()(c1, c2); \ @@ -61,7 +61,7 @@ intersection(const A &c1, const B &c2, const C &c3, OutputIterator r } \ template \ inline \ -bool \ +typename K::Boolean \ do_intersect(const A &c1, const B &c2, const C &c3) \ { \ return typename K::Do_intersect_3()(c1, c2, c3); \ diff --git a/Circular_kernel_3/test/Circular_kernel_3/include/CGAL/_test_sphere_predicates.h b/Circular_kernel_3/test/Circular_kernel_3/include/CGAL/_test_sphere_predicates.h index d49c0c7ab2d..559aeceeaf3 100644 --- a/Circular_kernel_3/test/Circular_kernel_3/include/CGAL/_test_sphere_predicates.h +++ b/Circular_kernel_3/test/Circular_kernel_3/include/CGAL/_test_sphere_predicates.h @@ -654,7 +654,7 @@ void _test_bounded_side(SK sk) { } } - // we dont need to test bounded_side(Circle, Circular_arc_point) because + // we don't need to test bounded_side(Circle, Circular_arc_point) because // bounded_side(Circle, Circular_arc_point) = bounded_side(Sphere, Circular_arc_point) + // has_on_3(supporting_plane, circular_arc_point) which has already been tested std::cout << "Testing bounded_side(Circle, Circular_arc_point)..." << std::endl; diff --git a/Classification/doc/Classification/Classification.txt b/Classification/doc/Classification/Classification.txt index 48c7531b362..2c5a4e3b2d1 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.h b/Combinatorial_map/include/CGAL/Combinatorial_map.h index 5135f5ec327..59f88e34970 100644 --- a/Combinatorial_map/include/CGAL/Combinatorial_map.h +++ b/Combinatorial_map/include/CGAL/Combinatorial_map.h @@ -969,7 +969,7 @@ namespace CGAL { { return mnb_used_marks; } /** Test if a given mark is reserved. - * @return true iff the mark is reserved (ie in used). + * @return true iff the mark is reserved (i.e. in used). */ bool is_reserved(size_type amark) const { diff --git a/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_internal_functors.h b/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_internal_functors.h index c76a210ae6f..6e82809a412 100644 --- a/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_internal_functors.h +++ b/Combinatorial_map/include/CGAL/Combinatorial_map/internal/Combinatorial_map_internal_functors.h @@ -64,7 +64,7 @@ * darts have the same info. * * internal::Test_is_same_attribute_functor to test if two - * i-attributes of two darts are isomorphic (ie they have the same info). + * i-attributes of two darts are isomorphic (i.e. they have the same info). * * internal::Test_is_same_attribute_point_functor to test if * the point of two i-attributes are equal. @@ -208,7 +208,7 @@ template struct Test_is_valid_attribute_functor { /** Test the validity of a i-cell-attribute. - * ie all the darts belonging to a i-cell are linked to the same attribute. + * In other words, all the darts belonging to a i-cell are linked to the same attribute. * @param adart a dart. * @param amark a mark used to mark darts of the i-cell. * @return true iff all the darts of the i-cell link to the same attribute. 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 b7ca3d12b42..7891d9280da 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/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationCellBase_3.h b/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationCellBase_3.h index 6dca2430ebf..f3e2858eec3 100644 --- a/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationCellBase_3.h +++ b/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationCellBase_3.h @@ -1,5 +1,5 @@ /*! -\ingroup PkgCT_3Concepts +\ingroup PkgConstrainedTriangulation3Concepts \cgalConcept The concept `ConformingConstrainedDelaunayTriangulationCellBase_3` refines the concept diff --git a/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationTraits_3.h b/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationTraits_3.h index ab63f742172..4eb75159b61 100644 --- a/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationTraits_3.h +++ b/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationTraits_3.h @@ -1,5 +1,5 @@ /*! -\ingroup PkgCT_3Concepts +\ingroup PkgConstrainedTriangulation3Concepts \cgalConcept The concept `ConformingConstrainedDelaunayTriangulationTraits_3` specifies the requirements diff --git a/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationVertexBase_3.h b/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationVertexBase_3.h index acbba3513c5..3f3cf13fef7 100644 --- a/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationVertexBase_3.h +++ b/Constrained_triangulation_3/doc/Constrained_triangulation_3/Concepts/ConstrainedDelaunayTriangulationVertexBase_3.h @@ -1,5 +1,5 @@ /*! -\ingroup PkgCT_3Concepts +\ingroup PkgConstrainedTriangulation3Concepts \cgalConcept The concept `ConformingConstrainedDelaunayTriangulationVertexBase_3` refines the concept diff --git a/Constrained_triangulation_3/doc/Constrained_triangulation_3/PackageDescription.txt b/Constrained_triangulation_3/doc/Constrained_triangulation_3/PackageDescription.txt index b635ad9e245..cfb6f3cfb12 100644 --- a/Constrained_triangulation_3/doc/Constrained_triangulation_3/PackageDescription.txt +++ b/Constrained_triangulation_3/doc/Constrained_triangulation_3/PackageDescription.txt @@ -1,17 +1,17 @@ /*! -\defgroup PkgCT_3Ref 3D Constrained Triangulations Reference +\defgroup PkgConstrainedTriangulation3Ref 3D Constrained Triangulations Reference -\defgroup PkgCT_3Concepts Concepts -\ingroup PkgCT_3Ref +\defgroup PkgConstrainedTriangulation3Concepts Concepts +\ingroup PkgConstrainedTriangulation3Ref -\defgroup PkgCT_3Functions Function Templates -\ingroup PkgCT_3Ref +\defgroup PkgConstrainedTriangulation3Functions Function Templates +\ingroup PkgConstrainedTriangulation3Ref -\defgroup PkgCT_3Classes Classes and Class Templates -\ingroup PkgCT_3Ref +\defgroup PkgConstrainedTriangulation3Classes Classes and Class Templates +\ingroup PkgConstrainedTriangulation3Ref -\addtogroup PkgCT_3Ref -\cgalPkgDescriptionBegin{3D Constrained Triangulations,PkgCT_3} +\addtogroup PkgConstrainedTriangulation3Ref +\cgalPkgDescriptionBegin{3D Constrained Triangulations,PkgConstrainedTriangulation3} \cgalPkgPicture{cdt3-small.png} \cgalPkgSummaryBegin @@ -22,7 +22,7 @@ The algorithm builds such a triangulation. The constrained triangulation does no and it may be necessary to add Steiner points to the PLC to make it tetrahedralizable. } -\cgalPkgManuals{Chapter_CT_3,PkgCT_3Ref} +\cgalPkgManuals{Chapter_CT_3,PkgConstrainedTriangulation3Ref} \cgalPkgSummaryEnd \cgalPkgShortInfoBegin diff --git a/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_3.h b/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_3.h index 0f8df4deb95..f72c43e7e83 100644 --- a/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_3.h +++ b/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_3.h @@ -59,10 +59,10 @@ #include #include #include -#if __has_include() +#if CGAL_CXX20 && __has_include() # include #endif -#if __has_include() +#if CGAL_CXX20 && __has_include() # include # include #elif CGAL_DEBUG_CDT_3 @@ -461,7 +461,7 @@ bool does_tetrahedron_intersect_triangle_interior(typename Kernel::Tetrahedron_3 namespace CGAL { -#if __cpp_lib_concepts >= 201806L && __cpp_lib_ranges >= 201911L +#if CGAL_CXX20 && __cpp_lib_concepts >= 201806L && __cpp_lib_ranges >= 201911L template concept Polygon_3 = std::ranges::common_range && (std::is_convertible_v, @@ -512,7 +512,7 @@ class Conforming_constrained_Delaunay_triangulation_3_impl; #endif // not DOXYGEN_RUNNING /*! - * \ingroup PkgCT_3Classes + * \ingroup PkgConstrainedTriangulation3Classes * \brief This class template represents a 3D conforming constrained Delaunay triangulation. * * It contains a data member of type `Tr` and provides additional functionality for handling @@ -3620,13 +3620,15 @@ public: { this->is_Delaunay = false; faces_region_numbers.resize(face_constraint_misses_subfaces.size()); - for(int i = 0, end = face_constraint_misses_subfaces.size(); i < end; ++i) { + for(CDT_3_signed_index i = 0, end = static_cast (face_constraint_misses_subfaces.size()); i < end; + ++i) + { CDT_2& cdt_2 = face_cdt_2[i]; fill_cdt_2(cdt_2, i); search_for_missing_subfaces(i); } if(this->debug_input_faces()) { - for(int i = 0, end = face_constraint_misses_subfaces.size(); i < end; ++i) { + for(CDT_3_signed_index i = 0, end = static_cast (face_constraint_misses_subfaces.size()); i < end; ++i) { dump_face(i); } } @@ -3636,7 +3638,7 @@ public: bool the_process_made_progress = false; while(i != npos) { try { - if(restore_face(i)) { + if(restore_face(static_cast (i))) { face_constraint_misses_subfaces_reset(i); } else { std::cerr << "restore_face(" << i << ") incomplete, back to conforming...\n"; @@ -3887,20 +3889,20 @@ protected: face_constraint_misses_subfaces[pos] = false; } static inline constexpr std::size_t face_constraint_misses_subfaces_npos = boost::dynamic_bitset<>::npos; - std::vector faces_region_numbers; + std::vector faces_region_numbers; }; #endif // DOXYGEN_RUNNING /*! -* \addtogroup PkgCT_3Functions -* \brief creates a triangulation that can be used for Tetrahedral remeshing +* \addtogroup PkgConstrainedTriangulation3Functions +* \brief creates a triangulation that can be used for tetrahedral remeshing * \tparam Traits is the geometric traits class of `ccdt` * \tparam Tr is the type of triangulation to which `ccdt` is converted * * \param ccdt the triangulation to be converted -* \return a triangulation of type `CGAL::Triangulation_3` that can be used for Tetrahedral remeshing +* \return a triangulation of type `CGAL::Triangulation_3` that can be used for tetrahedral remeshing */ template CGAL::Triangulation_3(ccdt_3_data().constrained_polyline_id(tr).index())); break; case CDT_3_vertex_type::STEINER_IN_FACE: set_dimension(2); diff --git a/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_vertex_data_3.h b/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_vertex_data_3.h index 39b37819855..8ab3cf88d33 100644 --- a/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_vertex_data_3.h +++ b/Constrained_triangulation_3/include/CGAL/Conforming_constrained_Delaunay_triangulation_vertex_data_3.h @@ -23,7 +23,7 @@ namespace CGAL { #ifdef DOXYGEN_RUNNING /*! - * @ingroup PkgCT_3Classes + * @ingroup PkgConstrainedTriangulation3Classes * @brief Internal per-vertex data for \cgal 3D conforming constrained Delaunay triangulations * * This class is an internal detail of the implementation of \cgal 3D conforming constrained Delaunay triangulations. @@ -145,7 +145,7 @@ public: } // namespace CGAL -#if CGAL_CXX20 +#if CGAL_CXX20 && __cpp_concepts >= 201911L # include static_assert(std::regular); diff --git a/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3/internal/read_polygon_mesh_for_cdt_3.h b/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3/internal/read_polygon_mesh_for_cdt_3.h index a70a3177d75..ad3b4b5f22c 100644 --- a/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3/internal/read_polygon_mesh_for_cdt_3.h +++ b/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3/internal/read_polygon_mesh_for_cdt_3.h @@ -78,7 +78,7 @@ namespace CGAL { std::cerr << "Warning: cannot read polygon soup" << std::endl; return return_error(); } - using Traits = typename PMP_internal::GetPolygonGeomTraits::type; + using Traits = typename GetPolygonGeomTraits::type; auto traits = choose_parameter(get_parameter(np, internal_np::geom_traits)); @@ -101,8 +101,7 @@ namespace CGAL { // So, if needed, create a copy of the range of faces, and triangulate it on the fly. // create a non-deleting pointer to `faces` (with a null deleter) - using Deleter_function = void(Faces*); - using Deleter = Deleter_function*; // function pointer type + using Deleter = std::function; // function pointer type using Ptr = std::unique_ptr; auto null_deleter = +[](Faces *) {}; Ptr triangle_faces_ptr{&faces, null_deleter}; diff --git a/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3_types.h b/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3_types.h index dd21de1cf24..6047772250f 100644 --- a/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3_types.h +++ b/Constrained_triangulation_3/include/CGAL/Constrained_triangulation_3_types.h @@ -19,7 +19,7 @@ namespace CGAL { /** - * @ingroup PkgCT_3Classes + * @ingroup PkgConstrainedTriangulation3Classes * \brief Signed integral type to store the index of constraints. * @see `Conforming_constrained_Delaunay_triangulation_cell_data_3` * @see `Conforming_constrained_Delaunay_triangulation_vertex_base_3` diff --git a/Constrained_triangulation_3/include/CGAL/make_conforming_constrained_Delaunay_triangulation_3.h b/Constrained_triangulation_3/include/CGAL/make_conforming_constrained_Delaunay_triangulation_3.h index 8231692a6ee..4838c7ed236 100644 --- a/Constrained_triangulation_3/include/CGAL/make_conforming_constrained_Delaunay_triangulation_3.h +++ b/Constrained_triangulation_3/include/CGAL/make_conforming_constrained_Delaunay_triangulation_3.h @@ -23,10 +23,10 @@ namespace CGAL { /*! - \addtogroup PkgCT_3Functions + \addtogroup PkgConstrainedTriangulation3Functions @{ - Free Functions for Creating Conforming Constrained Delaunay Triangulations {#PkgCT_3Functions} + Free Functions for Creating Conforming Constrained Delaunay Triangulations {#PkgConstrainedTriangulation3Functions} ========================================================================== Each of the following functions create a 3D conforming constrained Delaunay triangulation @@ -185,19 +185,9 @@ auto make_conforming_constrained_Delaunay_triangulation_3(const PointRange &poin using PointRange_value_type = CGAL::cpp20::remove_cvref_t; auto point_map = parameters::choose_parameter(parameters::get_parameter(np, internal_np::point_map), CGAL::Identity_property_map{}); - auto get_geom_traits_type = [&]() { - auto geom_traits_np = parameters::get_parameter(np, internal_np::geom_traits); - using Geom_traits_np_type = decltype(geom_traits_np); - if constexpr (!std::is_same_v) { - return static_cast(nullptr); - } else { - using Point = CGAL::cpp20::remove_cvref_t; - using Kernel = typename CGAL::Kernel_traits::Kernel; - return static_cast(nullptr); - } - }; - using Geom_traits = std::remove_pointer_t; + using Geom_traits = typename GetPolygonGeomTraits::type; + using Default_CDT = Conforming_constrained_Delaunay_triangulation_3; using CDT = typename CGAL::Default::Get::type; CDT cdt(points, polygons, np); @@ -206,7 +196,7 @@ auto make_conforming_constrained_Delaunay_triangulation_3(const PointRange &poin return remeshing_cdt; } -/// @} // end group PkgCT_3Functions +/// @} // end group PkgConstrainedTriangulation3Functions } // end namespace CGAL #endif // CGAL_MAKE_CONSTRAINED_DELAUNAY_TRIANGULATION_3_H diff --git a/Convex_hull_3/include/CGAL/convex_hull_3.h b/Convex_hull_3/include/CGAL/convex_hull_3.h index b2089fdc53c..239d83f9134 100644 --- a/Convex_hull_3/include/CGAL/convex_hull_3.h +++ b/Convex_hull_3/include/CGAL/convex_hull_3.h @@ -45,6 +45,7 @@ #include #include +#include #include @@ -69,6 +70,8 @@ template class Extreme_points_traits_ada namespace Convex_hull_3 { namespace internal { +BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(Has_nested_type_Collinear_3, Collinear_3, false) + // wrapper used as a MutableFaceGraph to extract extreme points template struct Output_iterator_wrapper @@ -993,7 +996,8 @@ void convex_hull_3(InputIterator first, InputIterator beyond, template void convex_hull_3(InputIterator first, InputIterator beyond, PolygonMesh& polyhedron, - const Traits& traits) + const Traits& traits, + std::enable_if_t::value>* = 0) { typedef typename Traits::Point_3 Point_3; typedef std::list Point_3_list; diff --git a/Convex_hull_3/test/Convex_hull_3/quickhull_test_3.cpp b/Convex_hull_3/test/Convex_hull_3/quickhull_test_3.cpp index 428866204fb..b6d2882b141 100644 --- a/Convex_hull_3/test/Convex_hull_3/quickhull_test_3.cpp +++ b/Convex_hull_3/test/Convex_hull_3/quickhull_test_3.cpp @@ -20,7 +20,7 @@ typedef K::Segment_3 Segment_3; typedef CGAL::Creator_uniform_3 Creator; typedef CGAL::Random_points_in_sphere_3 Generator; -const unsigned int num = 40; +const unsigned int num = 400000; template void compute_plane_equation(Facet_handle f) @@ -135,9 +135,9 @@ int main() std::cerr << "Testing coplanar hull" << std::endl; test_coplanar_hull(); - std::cerr << "Testing 500 random points" << std::endl; + std::cerr << "Testing " << num << " random points" << std::endl; std::vector points; - Generator g(500); + Generator g(1); std::copy_n( g, num, std::back_inserter(points)); assert(points.size() == num); diff --git a/Convex_hull_d/include/CGAL/Delaunay_d.h b/Convex_hull_d/include/CGAL/Delaunay_d.h index 6087d65e369..2531ec7f9e7 100644 --- a/Convex_hull_d/include/CGAL/Delaunay_d.h +++ b/Convex_hull_d/include/CGAL/Delaunay_d.h @@ -203,7 +203,7 @@ public: Simplex_iterator(Delaunay* x, Base_iterator y, Delaunay_voronoi_kind z = NEAREST) : Base_iterator(y), DT(x) /* if the facet is not nil we set the current marker to - the facet and insert all it's neighbors into the + the facet and insert all its neighbors into the candidates stack */ { CGAL_assertion(base() != Base_iterator()); tf = (z == NEAREST ? lower_hull : upper_hull); @@ -262,7 +262,7 @@ public: Simplex_const_iterator(const Delaunay* x, Base_iterator y, Delaunay_voronoi_kind z = NEAREST) : Base_iterator(y), DT(x) /* if the facet is not nil we set the current marker to - the facet and insert all it's neighbors into the + the facet and insert all its neighbors into the candidates stack */ { CGAL_assertion(base() != Base_iterator()); tf = (z == NEAREST ? lower_hull : upper_hull); diff --git a/Data/data/images/torus_gray_image.vti b/Data/data/images/torus_gray_image.vti new file mode 100644 index 00000000000..7a165899fca --- /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+NaJzFevD/toe0z7LXkO9reZUd6k95F85SBwWi6xHc9K44Pbq+blkHS85JpPM7a8N7Iyhm9tj33dTrbm+rbHq2Ja45m/N8d6lzTHvlLR17b7n+unPjj0//PHLPxx4cOHjgQ9e+OGJL97400EPXfTRSW9Zv3M1Z0vOV5wxWC/2mvZbYom4qvZUf6lB5GG5SDwWk6xLc9P44Di4mEnm8EEL0/E2vrf/oSW+uK05fnhzU1Q/3RifjGyMwUsa4mu3NiSta/c9109/duz54Y9f/uHAgwsfD3zwwg9PfPHGnw566KKPTnrL+p0tyo/OmNRNzhrst60f+y57D/W3GlQdphaRj+UkcVlssj7NUeOEq/VctbUtVkwr4XzcHGdd1JRo/OzDifHmR/WxS0N9vLa4Lk69ti5pXbvvuX76s2PPD3/88g8HHlz4eOCDF3544os3/nTQQxd9dNJb1u981Rmjczb50nmLMwf7bntP68keRB0u3qrH1CTystwkPotR1qm5arxwPmVSazK3jWvLHg3xzPT6WL5nXTy5tjbavhwfM84eH//eO21du++5fvqzY88Pf/zyDwceXPh44IMXfnjiizf+dNBDV6KvpJPesv6kPr47nfvO25w5yZ9qCftve1D7MOtLPS7mqMvUJvKzHCVOi1XWqzlr3HA/47LG+P5lE5Nx/ug3tbFh9/Hx2N01cdrNY+PuD6tjp4erk9a1+57rpz879vzwxy//cODBhY8HPnjhhye+eONPBz100UcnvWX94qEaUb3szNG5mznj/EU+VV/Zi9qP2ZNYb2pTcViNIk/LVeK1mGXdmrvGj4ZT8nXJ+F6/qibRevM+Y+KBzaNjaO3o+MmaUUnr2n3P9dOfHXt++OOXfzjw4MLHI6mx70r3THjiizf+dNBDF3100lvW71uD83ZnzuKk+tn5mzMo5zDmUlLft6T7uGRvsiRd9+o0tYp8LWeJ22KX9WsOG0daDpxWE89uGRPVj4yOSY+OjPXjR0SMHx5XvDgsaV2777l++rNjzw9//PIPBx5c+Hjggxd+eOKLN/500EMXfXTSW9Zvf6Q2cO7u7FndlJxV9kjPopzHOJOwLzfH7M/sUdTpalXrUWySt+Uu8VsMs47NZeNJk3H+/vQRsW7QsDi8YUhc9+Gg2Dh9UNK6dt9z/fRnx54f/vjlHw48uPDxwAcv/PDEF2/86aCHLvropLes3zcn3118e7B/VjM4h3UW6TxOfS2uOpuwP5dz5F9zT72uZlW3WZ/ytxwmjotl1rM5bVxpe+OYoYnmdYMGxPKL+sXqdXslrWv3PddPf3bs+eGPX/7hwIMLHw988MIPT3zxxj85Uz0grYXoo5Pesn7nJL49yY9ihHN4e0nnsWoJ53LqK/W2M4pkP1baq9qvycfqdnNS/aaGsV7lMvFcTLOuzW3jS+PVvfvHyB33jEUrd4/iHrsnrWv303czLOnPjj0//PHLPxx4cOHjgQ9e+OGJL97400EPXfTRSW9Zv72h729qRN9hfItQP8uf9lViifM5NYZzGrWn/bo9qzhs7yI/q2HVceaqfG79iutim/WdzvEBidbq9t3ijeY+sdem3knr2n3P9dOfHXt++OOXfzjw4MLHAx+88MMTX7zxp4Meuuijk96yfvWQ/ZHvcPaM9s++STiXdzatrrbHkF+dVTmvEXPUYfau9m/2MOKzXKWeU9PI6+aw9SzGWefmuvGm+b6VO8aUeT2T1rX7nuunPzv2/PDHL/9w4MGFjwc+eOGHJ754408HPXTRRye9Zf2+QTsncW7me5xvUuomucP5vDNq57TOKtXbzqyc28i/9u9qEvs4sUk9r6YVt9U28rscJ86b29a7OW/cad93hy17a12777l++rNjzw9//PIPBx5c+Hjggxd+eOKLN/500EMXfXTSu238t63/bfF/W/6v9Pqv0uv/St//Vfr+v9LPfyr9/K/Sz38r/fy/0r//VPr3v0r//lvp3/8r/fcflf77n0r//Vel//6v0n//Wem//630339X+u//K/3vPyr9738q/e+/Kv3v/yr97z8r/e9/K/nf/wFI7bQTeJzt24mbXFW193HhGlBIQoAwBQhk7HQn6XQ6PXdX1a+ZZFRAQO51IgwBmTSMYQzzFEW9iojKG6IgyKCoKBCDEFAiL1GigAoyXAaZL4gQZc5bn3Oo958o8jw8h3PO3mt9f7X3XmvtXdUf+tAH/xZNG5FHX/1I+heNzGmrN8g3Z2+c+dk0G03aImc+vGX+eOj4bLty21zykUk5cPyU/Hp0S156eFpmn9OWh9afkTnHzszFv2pP3zOzcsrrHbn82dn56x2d+fSZc7JDW1cWL+vKQf3dOWJJdxas7s70np6MOLgnBy7sySkXlFf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvQ39/r/t2pEZ6hyTf120cXb6xaaZ+rMt8r2ztsqtLdvku9+fkC+8Pin7j5+aqydMy+VrWrPo1ulZuefM3HZ3e+7aqiPb7zM7Pzi6MzsdNSeL9urKb7bqzuCq7rQc1ZPrX+/JXw7tzXdW9GbF2L6M2bMva5/cl//4Rl/2/m55de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNLb0O/z8OzmazbOV1/ZNLesOy6z/7lV1r1xm3xyu4mpXDs5bzw3NXetmZaXXmnLPUtn5Pa55Xhfs8vsLPpKZz738znFeP3h2u5iPFcO9uaYJ3pz1fy+/PB/+/Ld/fpz8Q39mfdaf56dNpAbPj6Q1kMGcsWR5dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0O/OeFz8W725HF5sHXrnL7Wttnu+onJ9Cm5ZmFLll7bmuU3TM9mF83M1OFZOeCBjnxsx84cfMmc7Hp3V776UHfuW9WTK28oNR84vj+f/0V/RvcN5PdXD+R/Rgzm8E8O5oivD+a2OwYz8snBPLh6MIe8U17de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Db0Wxfmhs9Hm8u22jajn5iYjy+YkjOfasnjk9ry04EZ2bO1PVNerY/5N2bngI3m5Mmjy/G+b0VPMWf/fl1fjjyhHN91fjuQ+buXmg6fNJRHjh/KvrcO5Z8vD+X7m1UytauSj+5YyZJdy6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3oZ+scH6MEd8Ttq+/PUpqWwyLa1HtmX1pTPyz8vac96xHWmZWs5161HM+utFvcX63f/0/mIOG7eVVw1ml46hTL56KONGVnLfAfX/fljJ6Y9XstnIaq6eWc1/7FjNir2r2edT5dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0O/+ChGWCfmis9LH3F17t0zMvbR9lx9T0ex3sRj8Vls+t2UvnytpxzvX703kHWX1ufsZ4aywfNDBfuIuyu5bFw1nzmgmrUvq2bzu6t577lqHl+7luvG1rLRNrWcNrG8uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oult6JcjxEmxwnoxZ3xu+h74WnuRcza+tzM/WNCV29/rzgaf7y3itnU58ScD+fZFg7l/l3Jub31qJWe/VinG9Rc/qubOl6qFxnt2r2XuUbXcd24tB11aS9sPannxR7VsfX15de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNLb0C9PyhXipZhh3Zg7Pj82zni8Mxcv7iry87+u7C3i8gYbDWTrsWXssl6Nz0UvV/LXg8vxM56v7F1L59m1/PTqWs67s5YrHqzl1idr2f3FWhb9o5Z9X6ul6/Xy6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3oV+tIF/KGeKm2GH9mEM+R7YW7VzGOHHY+vvcfw3mxO3L9X3GLyvF+h2ztJrWzevzeG4t915Warj8sVLbtW/VMnVNLa/V/1v2Xi2PvVPLfm/Xx/et8urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6FfvaRmkDflDvFTDLGOzCWfJ5vntvcX+Xr52YNFfBa3zcmrzq7mxX9Xi3Eyh8f8rpaVz9Yy/81aHqhrufndurbVtez/Qi23P1rLQ3+sj2u9zavLa7n79vLq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLehX82oblI7yJ9yiDgqllhP5pTPlW35+cqLh/KJeWVse+CGcn13nVLLHUtr+ejT9Xj2VjmOq96o5fd17jX31fLpm2q5ps5++Kn1NX5wLYvrGto+Vst225dX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70N/epmtaP6SQ0hj8ol4qmYYl2ZWz5fPjK/UuQq63HNUC2vf7M+bqvKdWy8FtbH6+anynGlZc/DahnurWXJurXs/Ug1f765msHvVbPeomq6zi2v7j33Xjvt9dOfHfYWvj+f+OGPX/5x4MGFDyfeIk7V+emghy766KS3od/eQf2shlRHqSXkUzmliKsblevdHPM587X2H6q5YZcybnc8Wq5X6/e2l8q5vLLO9p/71XLLmFpWrKhmz4X1NTJQzfPvVtJxbyUtV1Vy6Vcqufzc8urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6FffrSHUEerJdVTagp5VW4RX8UY68xc83nzKT6L15e8W8aw3R6vZcQN5bh1bFIrxvOr+1Rz8yulxr06K7nliaHMWjyU848ayt8+PpRTdyiv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnob+u0h7aPsJdTTakp1ldpCfpVjxFmxxnoz53zufH/8vXJ9funBcq5az+Zwz6HVnP9cJRseUmo+4KCh3PtyvRa+YDD9XYO5aPVAnvrDQO5fXl7de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Db020fbS4qT9hTqarWl+kqNIc/KNeKtmGPdmXs+fwzi9puLanmwrZYjflbNFi3VYvz++UI5vksXDyZrD+bL8wfywD/7s/Ki/hxV6c+yUfWY+m5fcXXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTS29DvLMF+Woywr5I71NdqTHWWWkO+lXPEXbHn2vfztnHAslfdrxhm/b6zW70WWjSUze4sx3ncjPoe98j+bLqsvs+f2JdFP+jNSTv25tT1e/P0v3uKq3vPvddOe/30L+zU7bHLPj/88cs/Djy48OHEixs/HfTQRR+d9Db0Ww/OFOyr7S3tr8RPdbZaU72l5pB35R7xVwyyDs1F44HJel3SWinW84MHl+N9wSn9ufPCUvMeddu7bdaTnRZ2Z+PR3Rn8XVcW39hVXN177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530NvSrEZ2rWB/21/aYxf77M2U9r+ZUd6k95F85SBwWi6xHc9K4YFtvYKhYxy9vPVDM7ZMerute1pMTrusutB75wJyM2WZOll3amS136MyqlvLq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLeh37masyXnK84YrBd7TfstsURcVXuqv9Qg8rBcJB6LSdaluWl8MO621kAxh089orcYT+P7p/PnpOOAztz46dnZ75yOTH5qVnZbOCsnfba8uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooas4m6nrpLeh39mi/FicMdXrJmcN9tvWj32XvYf6Ww2qDlOLyMdykrgsNlmf5qhxwmo977O8Ox/p68qcn3dmSWV2obHlxvaM/unMVF+dkfVPnpFv71Ve3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfQ29DtfdcbonE2+dN7izMG+297TerIHUYeLt+oxNYm8LDeJz2KUdWquGi/Ml6zTVcxt43rw/e15szozI/48Pa9e3ZZ5d7Xm5q7WtH+4vLr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpbegv6uOjyrnvvM2Zk/yplrD/tge1D7O+1ONijrpMbSI/y1HitFhlvZqzxg374h06ct0O7cU4b3NKWybdPy0vHtmSb396av72kykZOH5KcXXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTS29AvHqoR1cvOHJ27mTPOX+RT9ZW9qP2YPYn1pjYVh9Uo8rRcJV6LWdatuWv8aPjWmunF+P5mSUuh9f+OmJzHl03MHi9NyK+vnFBc3XvuvXba66c/O+yxyz4//PHLP46ixj6s5MOJFzd+Ouihiz466W3o912D83ZnzuKk+tn5mzMo5zDmUlHfry73ZcXeZGG57tVpahX5Ws4St8Uu69ccNo60nNLbkrfumJz9FkzMcadsmy1eGp9jXtw6t/z31sXVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Dv/2R2sC5u7NndZMzSPHTWZTzGGcS9uXmmP2ZPYo6Xa1qPYpN8rbcJX6LYdaxuWw8aTLO11W3yWaPbJWzXx2X5T/ZIq21LYqre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96Gft85+d7Fdw/2z2oG57DOIp3Hqa/FVWcT9udyjvxr7qnX1azqNutT/pbDxHGxzHo2p40rbaPGb1Vo3vyRTTOiukk2vHZscXXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTS29DvnMR3T/KjGOEc3l7Seaxawrmc+kq97Yyi2I/V96r2a/Kxut2cVL+pYaxXuUw8F9Osa3Pb+NK4dOUm2euejfL0FWPyxfs3KK7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oehv67Q19/6ZG9D2M7yLUz/KnfZVY4nxOjeGcRu1pv27PKg7bu8jPalh1nLkqnxfrtx7XxTbr2xw3zrTu+8bojFq9fnb81XrF1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvQ796yP7I93D2jPbPvpNwLu9sWl1tjyG/OqtyXiPmqMPsXe3f7GHEZ7lKPaemkdfNYetZjLPOzXXjTfNjV6ybr88dUVzde+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9P7/+v+C8pzHuZnv43wnpW6SO5zPO6N2TuusUr3tzMq5jfxr/64msY8Tm9TzalpxW20jv8tx4ry5bb2b88ad9mu+vKbm6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KT3g/H/YP1/EP8/yP/NXv81e/3f7Pu/Zt//N/v5T7Of/zX7+W+zn/83+/c/zf79X7N//9vs3/83++8/mv33P83++69m//1fs//+s9l//9vsv/9u9t//N/vffzT73/80+99/Nfvf/zX73382+9//fvCvuf91X7dOzjtuvTw5anRaFmyY6k1jM+6uzbLsqnGZ/tmtc/IL2+Sej09MbdHkrL9kag77+rR867Nt+ds6M3L2xTPz6Or29FY78sRh9fh7Ymc+dvicnLV9V0Z8uDv/e313dqn1ZOSynoyd3JsJC3rz4C29+fEzvVl/7b5MXr+vuLr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpbej3/w/MGJ1nbtowV4zaJP/o3TyrurbMTiPG56Brt80OUyZl4xOnZO0lLdnzytbsdNb0dA/NzPH3t2feLh05cvHsvPTnzuz+6pz845WudD3QnaMX9+Tve/dm1Su92e/Evpz5Ql+237U/X7q0P7f+qT/XvVOvFzYZyDtbDxRX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70N/T4Pz+ZO3yR9x2yeuRdtmYePG58bOyfk3d9MyrMzpmbJEdNy1FltueSYGTm20p7Dnp5VjMPeKzvTPaYr6/aU43zizHJcT/h1X7Y8uD+feL0/e35pIDs+NJDejnqdfsJgvnbdYD71x3rN/Oxgdn2lvLr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGr0De9/Awa+s0Jn4t3f/vhljn9+vGZds6EvNg+Oc/fMDV7r92ag2ZOz+Ed9dw6clbuu6sj632qM6+umJPRm3fntfpeq+8zvVnwyb7s0VFqHrlkIB/tG8wt9Vr1xLahXHjBUDb+81A2GVvJodtX8suDK1m4oF7TLiyv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnob+q0Lc8Pno832iyfkloPqOf+tqWmb15oLfjg9/3XbzLx1XV37sfUx37Tu75J6nf1qdzEeJ+1azu2vtg9k0zcHinG88WND2fK+oULT2Hote94blawZrOZ786vZ/TvVrPplNT9fUc1uvy+v7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnob+sUG68Mc8Tlp++2xLXnm0tbc//L0/J9x7fluPdfO+tfsrLpmTjH3rEcx68xR5Tpea61yDhu3E6ZV8trPKvlDazV3XVzNSU/V/2utpeXAWpZfXMteN9Zyw4pa5tf3ue8+VF7de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Db0i49ihHVirvi89BFX19ulPb/+fEf22r1c5+Kx+Cw2HXNNfwZuLcf7kDOH8tOhSk6s711uPaLU/JOda9nu8lrWeaqW67dM7tw5ueaI5MJzk30vTX61JGm5qry699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466W3olyPESbHCejFnfG76rn9CR5FzbtujK3u83Z0vnNmbW57oK+K2dXnv7KFsN6qS01ZWirm74r1qZp5QjuvnZyRHfrHUeOx9yfr/SE76yHBGjhvOA1OG880Zw1nRXl7de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Db0y5NyhXgpZlg35o7Pj43WA7vSO7GnyM+LW8p4fus3B7PiW0NF7LJejc+c+bWc9WytGD/j+e0Hk0dGDOfTbcOZteNwdtl/OAcdMpx/HTWc7mOHs+b44Tx2Qnl177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100tvQr1aQL+UMcVPssH7MIZ8jW9339hYxRxy2/tZ9dCgT7q4U67Otv1y/9W1r/vSd+jx+Ojl+q1LDx+aW2vY5bTirzhzO5WcNZ179ev7C4Xzo9OEccVp5dT/v/ffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTS29CvXlIzyJtyh/gphlhH5pLPk832nw4U+frwdcp4Lm6bk59YJ7nk5HK8zeGluw7nhMOHs+Wpwzm9rmfuGXVtC4azVn18DztgOGftUx/Xepvv7DCcL21XXt177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530NvSrGdVNaocivx9bxnmxxHoyp3yubMvPe2xYzZvPVYvYdHpHuU4fezc5vDKcn88bzq9OLcfx5FOGc2Kd+5q9h/Ph3uHsVWffeE09Bjyb7FzX8MA9yYu/La/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oehv61c1qR/WTGkIelUvEUzHFujK3fL58PP9atchVRTy/Pbl8s+F84ZPlOjZerXXOA+aV40rLWy8kL9ya7HZR8s7nkoUDyd/HJzeNSh5dt7y699x77bTXT3922Gt9fz7xwx+//OPAgwsfTry48dNBT1Ef1PXRSW9Dv72D+lkNqY5SS8incoq4KrZYX+aYz5mv6/ZM9luZIm4/9PlyvVq/h36xnMsn1NnWfiiZ+41k/q7Jm2vV5+xttfz3GbU8vEctq1pqGR5Ty04fKa/uPfdeO+31058d9thlf9778YFf/nHgwYUPJ17c+Omghy766KS3oV9+tIdQR6sl1VNqCnlVbhFfxRjrzFzzefMpPovXtTPKGLa6Hqt+PKsc74cvLcez/y+1HHBMqfGtm6qZe3A1D02opuMflZz7x0qmriiv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnob+u0h7aPsJdTTakp1ldpCfpVjxFmxxnoz53zufL9xRrk+t9i/nOvWszn8+PO1zD6ilqXPlprWe6aS4+bXa9r1K3nyl0PpWjCUL+9Vfv/t6t5z77XTXj/92WGPXfb54Y/fN97/DPDgwocTL278dNBDF3100tvQbx9tLylO2lOoq9WW6is1hjwr14i3Yo51Z+7V3tcubv9gdH1t3pBs0p3c9aNyvL97VLUY34MmVvL8OUPpeX0wpx8/mONH1ev4OwYy72sD2eWM8urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6HfWYL9tBhhXyV3qK/VmOostYZ8K+eIu2LPPu/nbeOA5e0ryxhm/V71h3otNLqaO3aoFOP6mx8P5k8vD+T26kCuv7I/XVP7M+l3fZlycV++cnJ5de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNLb0G89OFOwr7a3tL8SP9XZak31lppD3pV7xF8xyDo0F40HJut1t+urxXo+/dlyvGe/N5AjRw4U2v61d19WX9abV9bqzbKv9eTpXXuy85zy6t5z77XTXj/9i3lTt8cu+/zwxy//OPDgwocTL278dNBDF3100tvQr0Z0rmJ92F/bYxb77/peQ72t5lR3qT3kXzlIHBaLrEdz0rhgu2lZub4vvWKwmNuTPtuff1f7sm17b6F1009159YlXTlkXFd+e/ecnHztnOLq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLeh37masyXnK84YrBd7TfstsURcVXuqv9Qg8rBcJB6LSdaluWl8ML5+9mAxh6e8XI638T1lve48/OSc/Odj9Xm3bmd+f8jsrF5rdib+T0dxde+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNLb0O9sUX50xqRuctZgv2392HfZe6i/1aDqMLWIfCwnictik/VpjhonrNbzu9v35mdLu/NId1d2u6PU/MfOjtw8Z1aeO7Y9N70zM8MPzCyu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnob+p2vOmN0ziZfOm9x5mDfbe9pPdmDqMPFW/WYmkRelpvEZzHKOjVXjRfm2oXdxdw2rqP268j3l7fnx/vPzHfaZmTMTtMz9+a2/OW8tuLq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHrkJfXSe9Df1FffxKbzE3nLc5c5I/1RL23/ag9mHWl3pczFGXqU3kZzlKnBarrFdz1rhh32XF7OyzYlYxzve8Oz2/37ct33xlWrZ7rCXndLbk6X9PLa7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oehv6xUM1onrZmaNzN3PG+Yt8qr6yF7Ufsyex3tSm4rAaRZ6Wq8RrMcu6NXeNHw05a2YxvkdNbi20HnvBlFxYm5x/Hz0ph7ZMKq7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oehv6fdfgvN2Zszipfnb+5gzKOYy5VNT3C8p9mb2J+tz6U6epVeRrOUvcFrusX3PYONIyeem0XLn91Hzo7UkZ/96E3Hn0ttnq6G1y4CbbFFf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvQ399kdqA+fuzp7VTc4gxU9nUc5jnEnYl5tj9mf2KOp0tar1KDbJ23KX+C2GWcfmsvGkyTjvu3zbLP/c+Mw8bqsc3rll7l8+rri699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466W3o952T711892D/rGZwDuss0nmc+lpcdTZhfy7nyL/mnnpdzapusz7lbzlMHBfLrGdz2rjSdvMVWxeal39u8/z4jk2zdMamxdW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0O/cxLfPcmPYoRzeHtJ57FqCedy6iv1tjOKYj9W36var8nH6nZzUv2mhrFe5TLxXEyzrs1t40vjwZ/YLG/vNjYXT9ooW+y3YXF173n52Ywv2uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530NvTbG/r+TY3oexjfRaif5U/7KrHE+ZwawzmN2tN+3Z5VHLZ3kZ/VsOo4c1U+t37FdbHN+i7n+OaF1jUnj8kvTxyVlysji6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3oZ+9ZD9ke/h7Bntn30n4Vze2bS62h5DfnVW5bxGzFGH2bvav9nDiM9ylXpOTSOvm8PWsxhnnZvrxpvm8yd9NINPr1Nc3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfQ29PsO2jmJczPfx/lOSt0kdzifd0btnNZZpXrbmZVzG/nX/l1NYh8nNqnn1bTittpGfpfjxHlz23o354077e/tvFZxde+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNL7wfh/sP4/iP8f5P9mr/+avf5v9v1fs+//m/38p9nP/5r9/LfZz/+b/fufZv/+r9m//2327/+b/fcfzf77n2b//Vez//6v2X//2ey//2323383++//m/3vP5r973+a/e+/mv3v/5r97z+b/e9/m/nf/wN1R1KEeJzt2/ejXVWZPnCIlNCRmlCSkEJuclNuyu3tuedIUSGIAoKMg19KBGKAEFAUpYwJhARRujh0BCwZQCAkEaQklmFg5IvKGEAcEgcYBAQGGyDM7M/eHP+JQ35Z2Wu95Xn2Wut937X2uRtt9P6/Q1/ePLeu2CrDDtou+63eIf/4p53Tu8nI/Ob53fPRa0fln1v3yu+/Pi5HPTohUzZMzJJfTMo917bm7f2m5qbHpuWdjrYcfvaMDPvezOyzalY++/3ZuXFReybs05Ft/9CRE87pzJR3OtN+XFdqq7vypze78tjE7kypd+dDB1StZ/3GyZGnR58d9thlnx/++OUfDnjggg9OeOGGHw988MIPT3wb/P3/j/O3y2Z/3iEPztkl239xZF47fY/M3Xd0vvrSXjlu7vjMWrV3xm5oyYLnJueza6fk0K9MyxU7teX8pTNy4bMzs/WI2Zk/sz3bz+jIoTt35qJnO7PpZV15va07n1/VnRtae3Lssp5cvK4nT+3Ym0f7e/Pvh/Vmj6Oq1rN+4+TI06PPDnvsss8Pf/zyDwc8cMEHJ7xww48HPnjhhye+Df7eh75zP7dLjrhrZM59ZI+8vWJ0Hl84NntuNiGbnzQxa5ZPytfWtmbFXVNz6VnTs2RCNd+nbjM7hx7cnolfqOb5ypO6yvm84n+703tzT05u780pP+jNcbv15fAFfWlb2ZcfvNyXL+zQnzda+nNiW9V61m+cHHl69Nlhj132+eGPX/7hgAcu+OCEF2748cAHr5Lf56p30OBvTXgvxt5+YY9c98ro7P+Tsdn6lAnZ8tWJObU+OYtOmpJlC6Zl/YFteXWTmZn8zVn54BbtmXZER3Za2pkjrunKNy/rzkkLKs5TNvRm0hf7su7tvlw5rz/fe7g/s3cdSPuhAzl/0UD+4+aBXLd6INMfrFrP+o2TI0+PPjvsscs+P/zxyz8c8MAFH5zwwg0/HvjghR+e+Db42xfWhvdD5phnx2bdtydkRF9LPnLr5Hz3hSk5891p2e2VgvvdxZx/cnYm/0d7ls+s5vuqZd3lmr395N509FTz+/+X9Kd3+4rT7BcGckv3YEZ/ZTD33TmYzz09mNf+MphfDk/mbZuy9azfODny9Oizwx677PPDH7/8wwEPXPDBCS/c8OOBD1744Ylvg7/YYH9YI94T2VWHtmTzJyfnjelT88CR03Pfp2fkoM5Zee3F2eXasx/FrBvn9JT7d69atabN2xUnDGTHPw7kDycO5nePDeaq8clVJyb735SsfyxZ8Hry78OHcvEuQxm1e9V61m+cHHl69Nlhj132+eGPX/7hgAcu+OCEF2748cAHL/zwxLfBX3wUI+wTa8X7oiOuti6dnt9ePyMLvjar3G/isfgsNl3yYk8+9VZvOS+L1/Tn8a8M5Moxg3lyecX5sQuSY36b7D1+KI/+w1A2XDCUf10+lO/+dCinrxvKbzYMZb/nq9azfuPkyNOjzw577LLPD3/88g8HPHDBBye8cMOPBz544Ycnvg3+coQ4KVbYL9aM90Z3ysoZZc555qL2zO/rzAVruvLk2J4ybtuXL53an6PnDOSabaq1+8JgcuDKlPN69vyhXHhHxfHS7WtpnVHLVR+uZeqRtfxxbi13z6/lhZOr1rN+4+TI06PPDnvsss8Pf/zyDwc8cMEHJ7xww48HPnjhhye+Df7ypFwhXooZ9o214/2x8ZGb2nP4MZ1lfn7w+J4yLj/5RF9e+HV/GbvsV/NzyJ3JjS3V/JnPVbvU8rd9azlzXi0fO6+WE66q5au31LLL7bUcencto1fW8u7KqvWs3zg58vTos8Meu+zzwx+//MMBD1zwwQkv3PDjgQ9e+OGJb4O/WkG+lDPETbHD/rGGvEe2Dt26inHisP3XMmogQ5tX+/sjX6r281NfGcobTxfreEItl3+64vDZGytup91fy2travnR2lrOK9rvPFjLmAdqufD+qvV83nvj5MjTo88Oe+yyzw9//PIPBzxwwQcnvHDDjwc+eOGHJ74N/uolNYO8KXeIn2KIfWQteZ9sHvQ/vWW+XrZfFc/FbWvy5P2GsuLear6t4aeW1XLF92vp/VEt1xZ8zn2o4La6lrG31XLBDbXcdHkxr4XMDxfX8o2vVq1n/cbJkadHnx322GWfH/745R8OeOCCD0544YYfD3zwwg9PfBv81YzqJrVDmd+LHCKOiiX2kzXlvbItP5/08cGMnFzFtmsXVPv73YFalp5Vyy9vreU3P6rm8Vv31XJlgftfL61lwhm1nFpgn51ij7cUa7zg8Mcta9l686r1rN84OfL06LPDHrvs88Mfv/zDAQ9c8MEJL9zw44EPXvjhiW+Dv7pZ7ah+UkPIo3KJeCqm2FfWlvfLx5btVQ6zHx/eqJb7Dy/m7bJqH5uvDxc4z721mldcdptSy1ZvD2XeI0PZ4/qhXH/mUDb9zFB+Nad4b/tXrWf9xsmRp0efHfY+/N564oc/fvmHAx644IMT3jJOFfjxwKesDwp+eOLb4O/soH5WQ6qj1BLyqZxSxtUitthf1pj3zNejFw/lC9tUcfut66v9av+ef0e1lq8osI3brcD9qyK/Lyu41Is98m5y10PJWxclrx+f/L+Dk7kfrlrP+o2TI0+PPjvsscv+ee/FB375hwMeuOCDE1644ccDH7zwwxPfBn/50RlCHa2WVE+pKeRVuUV8FWPsM2vN++ZTfBavj3qoimE731TLY6dU8/3Wumo+jxhRcLir4rj7nwfzTzcP5s2jB3PwjMHcvMNg9h1etZ71GydHnh59dthjl31++OOXfzjggQs+OOGFG3488MELPzzxbfB3hnSOcpZQT6sp1VVqC/lVjhFnxRr7zZrz3vkesabanz1XVWvVfraGN2odysHLk6dbUnKaPHEwl99Z1MIHDOQDf+3PIav78y+X9OeaxVXrWb9xcuTp0WeHPXbZ54c/fvmHAx644IMTXrjhxwMfvPDDE98Gf+doZ0lx0plCXa22VF+pMeRZuUa8FXPsO2vP+4dB3F57UC3X/WEo7Z8fyu9+X833fbdV87vomIFs+dP+HNben2vv6csVc/rSuXFfznu8iKkP9ZatZ/3GyZGnR58d9thlnx/++OUfDnjggg9OeOGGHw988MIPT3wb/N0lOE+LEc5Vcof6Wo2pzlJryLdyjrgr9pz2Xt42D7Ds/nwVw+zfn25X1EIHDWb94oFyXv/r1b6i/urLs2cX5/znenLIZ3tS36In+z7Wndvu7S5bz/qNkyNPj35pp7DHLvv88Mcv/3DAAxd8cMILN/x44IMXfnji+/fzf7Ef3Ck4VztbOl+Jn+pstaZ6S80h78o94q8YZB9ai+YDJvt13ivVfr6uZaCcx4MH+3Lhgb0lt10L2zs/3ZXt61155vHObHphZ044rWo96zdOjjw9+uywxy77/PDHL/9wwAMXfHDCCzf8eOCDF3544tvgr0Z0r2J/OF87Y5bn7zFVPa/mVHepPeRfOUgcFovsR2vSvMD2q3eq/b1qfV+5tuvXFrzP6S42T1fJteObHXlyQ3vOO7I9z23enm+9NLtsPes3To48PfrssMcu+/zwxy//cMADF3xwwgs3/Hjggxd+eOLb4O9ezd2S+xV3DPaLs6bzllgirqo91V9qEHlYLhKPxST70to0PzDu/JNqre87vaecT/N79Uc78ta49nxp9OyM2X9WXrllZnauz0xtr6r1rN84OfL06LPDHrvs88Mfv/zDAQ9c8MEJL9zw44EPXuXdTMET3wZ/d4vyY3nHVNRN7hqct+0f5y5nD/W3GlQdphaRj+UkcVlssj+tUfMEq/08anFXfvF2R975fHvmbTy75Pj6whlZd1pbhq+Ynif6p+fonavWs37j5MjTo88Oe+yyzw9//PIPBzxwwQcnvHDDjwc+eOGHJ74N/u5X3TG6Z5Mv3be4c3Dudva0n5xB1OHirXpMTSIvy03isxhln1qr5gvmo/6to1zb5nXalTOydlhbHrtqWu6dNzVt50/JuX9tzV9+1lq2nvUbJ0eeHn122GOXfX7445d/OOCBCz444YUbfjzwwavkV/DEt8Fffeye1dpw3+bOSf5USzh/O4M6h9lf6nExR12mNpGf5ShxWqyyX61Z8wb7CcNn5fThM8p5fnFgal65ojUr2ibn6NGTcvPClmzS3VK2nvUbJ0eeHn122GOXfX7445f/ssb6VRUL4YMTXrjhxwMfvPDDE98Gf/FQjahedufo3s2acf8in6qvnEWdx5xJ7LeyNr2rqm3kablKvBaz7Ftr1/zh8Jm108r5vei4ySXXyx7eO989Z0J2vWN8lhw/vmw96zdOjjw9+uywxy77/PDHL/9wlDX2lAofnPDCDT8e+OCFH574/v38905ned/uzlmcVD+7f3MH5R7GWirr+9XVuaw8m9Srfa9OU6vI13KWuC122b/WsHnEZZ+3J+XHiyZmTN+EDAyOy+9u3yt9t4/JPx02pmw96zdOjjw9+uywxy77/PDHL/9wwAMXfHDCCzf8eOCDF3544tvg73ykNnDv7u5Z3eQOUvx0F+U+xp2Ec7k15nzmjKJOV6vaj2KTvC13id9imH1sLZtPnMzz6cPGZv11o3Pgij2zbOEeeWPYHmXrWb9xcuTp0WeHPXbZ54c/fvmHAx644IMTXrjhxwMfvPDDE98Gf9+cfHfx7cH5Wc3gHtZdpPs49bW46m7C+VzOkX+tPfW6mlXdZn/K33KYOC6W2c/WtHnF7dfrR5WcN1w3Mj/feESenr9r2XrWb5wceXr02WGPXfb54Y9f/uGABy744IQXbvjxwAcv/PDEt8HfPYlvT/KjGOEe3lnSfaxawr2c+kq97Y6iPI8VZ1XnNflY3W5Nqt/UMParXCaei2n2tbVtfnFc9I0R2f1rO+e2Y3dM95U7lK1n/cbJkadHnx322GWfH/745R8OeOCCD0544YYfD3zwwg9PfBv8nQ19f1Mj+g7jW4T6Wf50rhJL3M+pMdzTqD2d151ZxWFnF/lZDauOs1bl83L/FnFdbLO/rXHzjOvo+z6YX6/aNtudvU3ZetZvnBx5evTZYY9d9vnhj98y7xQ44IELPjjhhRt+PPDBCz888f17/KtX5z3f4ZwZnZ99k3Av725aXe2MIb+6q3JfI+aow5xdnd+cYcRnuUo9p6aR161h+1mMs8+tdfON83eO3TJHThhetp71GydHnh59dthjl31++OOXfzjggQs+OOGFG3488MELPzzxbfD3Ddo9iXsz3+N8k1I3yR3u591Ru6d1V6nedmfl3kb+dX5XkzjHiU3qeTWtuK22kd/lOHHe2rbfrXnzjvuopcPK1rN+4+TI06PPDnvsss8Pf/zyDwc8cMEHJ7xww48HPnjhhye+78//+/v//fj/fv5v9vqv2ev/Zj//Nfv5v9nvf5r9/q/Z73+b/f6/2b//NPv3v2b//tvs3/+b/fcfzf77n2b//Vez//6v2X//2ey//2323383++//m/3vP5r973+a/e+/mv3v/5r97z+b/e9/3//X3P9efml4BlZsnW/N2T6/XbVj/vTHXfKLD+yWhc/vkQ3XjM7M1rE59+vj8+dH9s7a9S0Z/YvJOfDaKblkv2npfWx6LuuYkVfPmpmrvlvUSStn593vtad7UUfu+1Bnbn6lMxud05Uf/60rjx7bnSeLNX3hm92ZO7Ena2s9+c1Hq9azfuPkyNOjzw577LLPD3/88g8HPHDBBye8cMOPBz544Ycnvg3+/r9s/va59k875pNzds2tZ+yWJafvmXf2GZPdXxqbvx03If+2cmJ+uH5Shj/XmnfXTM3LX56eqTvNyKilMzP+2Vn59q7t2WRmR25t68zLO3VlwrNdufrS7ixp68k2q3rS1dqbt5f2pmVdbxbs2Jdj+vty7GF9uesfq9azfuPkyNOjzw577LLPD3/88g8HPHDBBye8cMOPBz544Ycnvg3+3oe+EZ/bNa/duVtGPrJnLlkxJscvHJcVm+6da+e35IjlkzO+iC8H3jUtk89qy5gJ1XxvuU17Xv5YRx74fDXP00/qLudz6v/25Jff7s1m7X3Z/Ad9+dvI/rx6Sn9+dk9/9n+5P9vuMJClLQPZuK1qPes3To48PfrssMcu+/zwxy//cMADF3xwwgs3/Hjggxd+eOLb4G9NeC/GLnlhz3S8MibP/nhcbjp579zwh5ZsWW/N7idNzdgF03PGgTNy/iaz8tCVs/Od4R35yeGd+f4FXXnt6u60XdaTTRdUnNeu78uDZ/Tn5Lf7M31ecVZ8eCCP7DKYRw8ZzKhFg5l/82A6Vg/mpw9UrWf9xsmRp0efHfbYZZ8f/vjlHw544IIPTnjhhh8PfPDCD098G/ztC2vD+yHz1n+Oy8nf3ju3907Khltai0PH1Ozw7vQiP8/IeXfPyhafbM9DT3SkPrOa77YiX1mz+55c1KTd1fwev2Qgv9iu4vTI84Pp707u+XLyieLs9oGnk/P/UtR+Rc07bNuhsvWs3zg58vTos8Meu+zzwx+//MMBD1zwwQkv3PDjgQ9e+OGJb4O/2GB/WCPeE9mDDp2Ua9e1Zun0aTnsyLZ84tMz80LH7Cx5sb1ce/ajmNU9p7fcv6uHqjVt3qaeMJjvvTGYxUUNf2ZRz88ozjFtJw7lP28cyhmPDWX460M5dngtk9xj7Fa1nvUbJ0eeHn122GOXfX7445d/OOCBCz444YUbfjzwwQs/PPFt8BcfxQj7xFrxvuiIq2suaMvp18/MFl+bXe438Vh8Fpsmvdib19/sK+dljzUDOb6o26eNSU5ZXnGeW5zj3npmKD8aV8sx/1DLFy+o5ajltQz9tJat19WycEMtv32uaj3rN06OPD367LDHLvv88Mcv/3DAAxd8cMILN/x44IMXfnji2+AvR4iTYoX9Ys14b3TX3jOzzDmnXdSRTfu6MmZNd04Z21vGbfvyq6cO5M0Di7PKNtXaPWtwKM/dM1TO667FuX7cHRXHydvXs6atnrYP1/PjT9WzbG49B8yv56yTq9azfuPkyNOjzw577LLPD3/88g8HPHDBBye8cMOPBz544Ycnvg3+8qRcIV6KGfaNteP9sbH+xo68enRXmZ8/eXxvGZdPeaI/Z/16oIxd9qv5eekHQ+luqebPfH5sl3ou3beeHebV88Lieja+qp7dbqnnttvqefmueu65p57LV1atZ/3GyZGnR58d9thlnx/++OUfDnjggg9OeOGGHw988MIPT3wb/NUK8qWcIW6KHfaPNeQ9svXKVlWME4ftvwf2HMy6zar9veGL1X5e8JValj5dSybUM+XTFYd3b6i4bXV/PUvW1HPI2npGFe3gg/WsLPrG3V+1nke9N06OPD367LDHLvv88Mcv/3DAAxd8cMILN/x44IMXfnji2+CvXlIzyJtyh/gphthH1pL3yebzr/eV+XrsflU8F7etyc32q+WAe6v5toYXLKtn6vfr+eV99bQXfEY8VHBbXc/qf6lnTMGn5/J6LitkPl7wmvjVqvWs3zg58vTos8Meu+zzwx+//MMBD1zwwQkv3PDjgQ9e+OGJb4O/mlHdpHaQP+UQcVQssZ+sKe+Vbfl5048nd0yqYlv7gmp/Xz5Qz15n1XPirfUs/FE1jzML3NMK3J+5tJ77vlDPFkfW88hgscdb6tmo4LBsy3pu2qxqPes3To48PfrssMcu+/zwxy//cMADF3xwwgs3/Hjggxd+eOLb4K9uVjuqn9QQ8qhcIp6KKfaVteX98nHD7CqH2Y+f2aieQw8v5u2yah+br2cLvCNureYVlx+01nPjW7UMe6SWu66rpfPMWq4+qpZ5c4r3tn/VetZvnBx5evTZYe/Z99YTP/zxyz8c8MAFH5zwlnGqwI8HPnjhhye+Df7ODupnNaQ6Si0hn8opZVwtYov9ZY15z3wdc3Et225Txe2Lr6/2q/076o5qLU8tsP1wZD0jf1VLy7KCS63YI+8O5SMPDeXii4ay5Pih/PVj1fcvrWf9xsmRp0efHfbYZX/Ue/GBX/7hgAcu+OCEF2748cAHL/zwxLfBX350hlBHqyXVU2oKeVVuEV/FGPvMWvO++RSfxes/vxfLlt9Yz9xTqvm+eF01n6/vWsuIuyqOd/4pGXlz8o2jk/8uwnHfDskzm1etZ/3GyZGnR58d9thlnx/++OUfDnjggg9OeOGGHw988MIPT3wb/J0hnaOcJdTTakp1ldpCfpVjxFmxxn6z5rx3vm9/qNqfj3+zWqv2szV85eRa/vv7Qzm1ZajktGbvZMqdRS380cF86y8DeWnVQD50yUBmL65az/qNkyNPjz477LHLPj/88cs/HPDABR+c8MINPx744IUfnvg2+DtHO0uKk84U6mq1pfpKjSHPyjXirZhj31l73j8M4vanDqqn4w+1PHp6LV/6fTXfn7itmt/djxnMDT8ZyCuzB9JenO+mzunPzzfqz56PFzH1ob6y9azfODny9Oizwx677PPDH7/8wwEPXPDBCS/c8OOBD1744Ylvg7+7BOdpMcK5Su5QX6sx1VlqDflWzhF3xZ6t3svb5gGWO5+rYpj9++ntilpoTnLG4sFyXs98tb+ov/rzhbOLc/5zvXl5bm+eGt6bZ37ek33u7Slbz/qNkyNPj35pp7DHLvv88Mcv/3DAAxd8cMILN/x44IMXfnji2+BvP7hTcK52tnS+Ej/V2WpN9ZaaQ96Ve8RfMcg+tBbNB0z267BXqv3c0TJYzuOLA/0Zd2Bfye22S3uy/Knu3FrrzmmPd+XqZV3Z6LSq9azfODny9Oizwx677PPDH7/8wwEPXPDBCS/c8OOBD1744Vnyfe+fGtG9iv3hfO2M6ZzlrKHeVnOqu9Qe8q8cJA6LRfajNWleYJv3TrW/D1rfX67tp64peJ/dk1+7tyi4/vzKzizY0JE9j+zIlzfvyIyX2svWs37j5MjTo88Oe+yyzw9//PIPBzxwwQcnvHDDjwc+eOGHJ74N/u7V3C25X3HHYL84azpviSXiqtpT/aUGkYflIvFYTLIvrU3zA+PyH1dr/ZlpveV8mt9ZH+3MxeM68sHR7Vm53+wsvmVWltdm5akxVetZv3Fy5OnRZ4c9dtnnhz9++YcDHrjggxNeuOHHAx+8yruZgie+Df7uFuXH8o6pqJvcNThv2z/OXc4e6m81qDpMLSIfy0nisthkf1qj5glW+3nFou6c8HZnLvt8R4Zt3F5yXLJwZk4+bUauu7stn+tvy5s7Va1n/cbJkadHnx322GWfH/745R8OeOCCD0544YYfD3zwwg9PfBv83a+6Y3TPJl+6b3Hn4Nzt7Gk/OYOow8Vb9ZiaRF6Wm8RnMco+tVbNF8x/frizXNvm9SdXzMynhs3I3Kum5+PzpuVn503NiL9Oydd/NqVsPes3To48PfrssMcu+/zwxy//cMADF3xwwgs3/Hjggxd+eOLb4F/Wx23V2nff5s5J/lRLOH87gzqH2V/qcTFHXaY2kZ/lKHFarLJfrVnzBvtGw2dn6+Ezy3k+Z2BaFl0xJQe0tebNUZPTt3BS/rlrUtl61m+cHHl69Nlhj132+eGPX/7hgAcu+OCEF2748cAHL/zwxLfBXzxUI6qX3Tm6d7Nm3L/Ip+orZ1HnMWcS+01tKg6rUeRpuUq8FrPsW2vX/OHwlzXTy/mdcFxrybX14YnJOXvnttsnZPTxE8rWs37j5MjTo88Oe+yyzw9//PIPR1ljt1b44IQXbvjxwAcv/PDEt0Hftwb37e6cxUn1s/s3d1DuYaylsr5fXZ3LyrNJrdr36jS1inwtZ4nbYpf9aw2bR1x+89bkHLmoJSt7984TA+PzpdvH5pe37ZWRh+1Vtp71GydHnh59dthjl31++OOXfzjggQs+OOGFG3488MELPzzxbfB3PlIbuHd396xucgcpfrqLch/jTsK53BpzPnNGUaerVe1HsUnelrvEbzHMPraWzSdO5nnrYeNyxnVj8l93j8rYhXtm6bA9y9azfuPkyNOjzw577LLPD3/88g8HPHDBBye8cMOPBz544Ycnvg3+vjn57uLbg/OzmsE9rLtI93Hqa3HV3YTzuZwj/1p76nU1q7rN/pS/5TBxXCyzn61p84rbSetHl5y/eN1umbvxyJw6f0TZetZvnBx5evTZYY9d9vnhj1/+4YAHLvjghBdu+PHABy/88MS3wd89iW9P8qMY4R7eWdJ9rFrCvZz6Sr3tjqI8jxVnVec1+Vjdbk2q39Qw9qtcJp6Lafa1tW1+cdz9GyNz54W7ZJ9jd8rjV+xYtp71GydHnh59dthjl31++OOXfzjggQs+OOGFG3488MELPzzxbfB3NvT9TY3oO4xvEepn+dO5SixxP6fGcE+j9nRed2YVh51d5Gc1rDrOWpXPy/1bxHWxzf62xs0zrvfcu0NOWrVdbjlr27L1rN84OfL06LPDHrvs88Mfv/zDAQ9c8MEJL9zw44EPXvjhiW+Dv3rI+ch3OGdG52ffJNzLu5tWVztjyK/uqtzXiDnqMGdX5zdnGPFZrlLPqWnkdWvYfhbj7HNr3XzjPHjsVvmf8VuUrWf9xsmRp0efHfbYZZ8f/vjlHw544IIPTnjhhh8PfPDCD098G/x9g3ZP4t7M9zjfpNRNcof7eXfU7mndVaq33Vm5t5F/nd/VJM5xYpN6Xk0rbqtt5Hc5Tpy3tu13a968477igg+UrWf9xsmRp0efHfbYZZ8f/vjlHw544IIPTnjhhh8PfPDCD09835//9/f/+/H//fzf7PVfs9f/zX7+a/bzf7Pf/zT7/V+z3/82+/1/s3//afbvf83+/bfZv/83++8/mv33P83++69m//1fs//+s9l//9vsv/9u9t//N/vffzT73/80+99/Nfvf/zX73382+9//NvO//wPQLcyQeJzt24m7HVWV93GUIAKRQeYEAoGQebq5Se587++eUxpBREVAxZdBMN0KyBihg4QQaBEUX1A6ERwIijRDGEREMAoECZMIMujbCCrggIAQEGQKBN/6VHH6nzjkeXiKqtp7re/v7L3XWnufc9db7+1/ay/bKHse955cOnKLPHP8Vln/2m3z2C2jsuQHO+b5T+2cPLVLzv7QbhnxlQm5f/mkTDtnSj71f6blwg1m5ANfm5nv/7Mj6/o7c8m/z87TX5iTkZ+bm3mNrvzynd25dkV3Nh3syQMre/LwLr352/G9+db1vVnw197cv15fnt6or7q699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466W3p9//nT9kiK67dKvNHbpfr5o7Oss4x2WTE2Ey8bNdsPG58HvrCxNyxfHK2uWhqRi6ZnrW9M9P3QEemfqAzsy6YnR/9dk7e+/zcXLemK2sf6E7nBT25/KO9WbamN6OP78v7n+rLRrv3Z+6y/iy+vz/HvN6fY7cayC07DFRX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70t/T4Pz8ZP3i5vHj06E84ckwuPG5vjO8blF7eOzxVTJuXfD5uSWUum5VPHzEh3f0em/3lWNQ7b3j0nazftyj2z63EemFqPa9+NfXn8kP5s9WJ/tj5qIBs/NJB10wfz2wWD2e/ywexw32DOe2Iwm62pr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056W/rNCZ+LdxdePCbFirF59tRxuWbahFx9xaRs+46pmTR1embOmJnTN5mVpb/ozK/3nZOf3D43D27TnZ/O68mb+/dmcO++bDmj1nz/8oHcO3cwi346mP5JQ/nwl4fyu98M5eH3JlMbyRcPTYoTkt8sqq/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oelv6rQtzw+ejzbsvGJdFh0zIja9NyvOfmZq9Lp6enX8+MzdfPiv/dWw55luX/s7tykef767GY3D3em7vM20gj7wyUI3jF94/lMfuGao0/e4HyQdfSVb3DufTRw9ni/OHs/S64Sy8fTib/6q+uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oult6RcbrA9zxOek7QFbTs4Vy6bmvGen5zPbd+TTozvz0j9nZ9klc6u5Zz2KWfNG1uv4jn8NVHPYuPVNSG64Jjl34nDO/NpwBv9U/jexkWcPbuT0rzWyzdWNHHt7I3N/08itD9VX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70t/eKjGGGdmCs+L33E1fvmdeS0AzuzzQfrdS4ei89iU9cl/fnX9fV4Tz5lKOWj9P8xOfmwWvOCeY28+zuN3P14I8eMaubL85r5/GHN7PWfzYxa1sySC5t55qL66t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3pV+OECfFCuvFnPG56Xv/gs4q55y6Zxl313Zn+im9OfmxvipuW5dfnzmUDUcmjbtTzd2z1g3nhQX1uI6b0kzHkbXG7nubuW9NM4MbFnlguyLnjyuy/5QiZ02rr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056W/rlSblCvBQzrBtzx+fHxnMHd2Xdzj1Vfp4/vo7nJ587mLOWDlWxy3o1Pq8d1ci8JxrV+BnPA3/TzPdGFNl5UpGXmkU2+3iRiZ8p8rMjiqw9psjqBUUu+kJ9de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNLb0q9WkC/lDHFT7LB+zCGfI1tr7+qtYo44bP3d88hQnlidan0+11Wv38V9zZx3fjmP/9xM7+haw8iDa23bn1Rk6SlFDllSZEp5/dDJRW4rn3WcVF/dT3nrvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfS29KuX1Azyptwhfooh1pG55PNk859XD1T5euYGdTwXt83JrTZoZv+F9Xibw4t3L9L3uSKPnVikWeoZv7jUdkKROw4vMv2gIh/4WDmuZZuDS11zhuure8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96WfjWjukntIH/KIeKoWGI9mVM+V7bl5y03H85NfxuuYlNzRr1OL3qjmRn9Rf5jfpElX6zHcajk7i+5j/xokV/OKbLt9kV+t66MAU80s2mp4fw7m7lmdX1177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100tvSr25WO6qf1BDyqFwinoop1pW55fPl4+oXhqtcZT0eeVMzh25Tjtve9To2XmtK3vHz63Gl5eanmvnhDc1sfmYztxzQzPt6mrl8x2YWjiw/t3fVV/eee6+d9vrpzw57a96aT/zwxy//OPDgwocTbxWnHqvzAj100UcnvS399g7qZzWkOkotIZ/KKVVcPbde7+aYz5mvYz7czA53N6u4vfzAer1av1OPrOdyX8l25/80M+EbzczdvdSyXjlnf97IJxY3snzPRpaNb+RdmzUycsP66t5z77XTXj/92WGPXfanvBUf+OUfBx5c+HDixY2fDnrooo9Oelv65Ud7CHW0WlI9paaQV+UW8VWMsc7MNZ83n+KzeD1icR3DVpaxasH0eryXL6vH883fNjL+mFrjqmuHM+HQ4Vyw83BeXpPscV/y99vqq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peln57SPsoewn1tJpSXaW2kF/lGHFWrLHezDmfO983Lq7X56P71XPdejaHL36ykZc/18gpT9Safv2XpPfosqbdKLn0uqG8dvxQ9v7IUBrN+urec++1014//dlhj132+eGP3xvf+gzw4MKHEy9u/HTQQxd9dNLb0m8fbS8pTtpTqKvVluorNYY8K9eIt2KOdWfujXhLu7j92fcUKa5o5uHOZs64tB7vTx8xXI3vpLHJ1acN5fUXBtMs93d9Iwfz+5sGMuXsMqYurq/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oelv6nSXYT4sR9lVyh/pajanOUmvIt3KOuCv2bP9W3jYOWFZdVMcw6/fwe8paaORwTm+mGtczryz3uM8O5EsD5T7/ojI3j+vPk7f35e9n9eVjC+ure8+91057/fSv7JT22GWfH/745R8HHlz4cOLFjZ8Oeuiij056W/qtB2cK9tX2lvZX4qc6W62p3lJzyLtyj/grBlmH5qLxwGS9br5iuFrPxRP1eL/yxkA6NhmotP38o31ZeV5vrluvN6ee3ZPLd+/JprPqq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peln41onMV68P+2h6z2n+Xew31tppT3aX2kH/lIHFYLLIezUnjgm3hz+r1fcDywWpuP/mpUvdAX/7q3KLU+si+3Vl8YVembN+Vr9w2N0OXza2u7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnpb+p2rOVtyvuKMwXqx17TfEkvEVbWn+ksNIg/LReKxmGRdmpvGB+PKUwerOfz3Z+rxNr7DG3Vn+eNzs9Mf5uS2Debk3M/Mzsr1ZufJP3ZWV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXRVZzOlTnpb+p0tyo/VGVNZNzlrsN+2fuy77D3U32pQdZhaRD6Wk8Rlscn6NEeNE1br+dZGb074aXe+P7srm99ca17W0ZlFs2blymM7cuLrM7PhgzOrq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peln7nq84YnbPJl85bnDnYd9t7Wk/2IOpw8VY9piaRl+Um8VmMsk7NVeOFecQZ3dXcNq4P7tOZz67qyIKPz8zBk2bkt8X0jP/JtHz3S9Oqq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pelv6qPl7TW80N523OnORPtYT9tz2ofZj1pR4Xc9RlahP5WY4Sp8Uq69WcNW7YN719dkbdPqsa5//7xvR8Y59p2X/NlGz4h8nZo2NyLnt5UnV177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100tvSLx6qEdXLzhydu5kzzl/kU/WVvaj9mD2J9aY2FYfVKPK0XCVei1nWrblr/GjYYMnManw7d51aae358sTsNTghP//8+EwbP766uvfce+20109/dthjl31++OOXfxxVjV1y4cOJFzd+Ouihiz466W3p912D83ZnzuKk+tn5mzMo5zDmUlXfn1Dvy6q9SVmfW3/qNLWKfC1nidtil/VrDhtHWp6+YUo+15iU214bnz+/MS5nfH7XPH7ELpmw1S7V1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvS7/9kdrAubuzZ3WTM0jx01mU8xhnEvbl5pj9mT2KOl2taj2KTfK23CV+i2HWsblsPGkyzqNW7ZrTDxibF47dKTM7xuS8VTtWV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Lf2+c/K9i+8e7J/VDM5hnUU6j1Nfi6vOJuzP5Rz519xTr6tZ1W3Wp/wth4njYpn1bE4bV9pOWr5zpfnLB4zOgpu3zylTtq+u7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnpb+p2T+O5JfhQjnMPbSzqPVUs4l1NfqbedUVT7sXKvar8mH6vbzUn1mxrGepXLxHMxzbo2t40vjZP2GpVVe2ybj+2ydf64z1bV1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvS7+9oe/f1Ii+h/FdhPpZ/rSvEkucz6kxnNOoPe3X7VnFYXsX+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//OPDgwocTL278dNBDF3100tvS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8y9X9xPef66f/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/nt7l9fLUZ1aUO3p/UL5r6eryq9/+Z3nJ9r3iR/V94tW/bR7dh28RM/40MD76qiYW1g6J+r7DYu9nh8fPZ4yIr/UeFZceWxfvLqiPOa82xNJVo+Pz/03W4v2Ncd9/jY1fNzfFut83xaKtmuPxXzXHc+81x0+7W+KIQ1pi0akt8eLM7Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6Lf/29R/qBc+s6a8k291pdvuqNXzLq1T6yd3i9qxw6Iz64aFI+tGhx31yY5pG54rF1TGyvuGhmde9dF3aL6aBw+Oq7/1pj48qjGuGny2Fi+d1M0DW+OK59ojnMmt0S/91pi28NbY/Wi1mhJarPjvtEWh57YFof/rC1+d3l2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0e/z8Kx11/XlVct7xbCv+sTF7/aLI+cNiDt3rImrbtwyvvv60Bj72fD4xooR0b5gVNQfnI33ZuUxseLcxnjgtrHpeOVvak7HM7dtayxZ1hr/Oroter3dFp/t2x7v/qY9HlnZHns0dUT/vTrivIkd8c9J2dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0W/OeFz8e7nDX1jq+b+8dLaAXHdzTVxbcuQ2Oy0YTHiptoYPXdknHR2XZy9Q0M8tHh03LxzY/zx4rExd2GyR3qmOQpPtsRXv8k0L6ptjwfvaI9jezoif0NH7Ly+Ix6b0BlPXNAZdfd3xtF/T2L9+53xyCfZ1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvRb91YW74fLRZPWxgHLusJm6bOiReeWlY7NQwIgZtMyruaKqPs99JxvyiMfFQ/7Gxy5RsvIsPt6RztnxzkpdPyMb3yIc6YskemabH63Oxwwm5mH9XLvZbnosva/Ixq5iPo3bOx/pydnXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9EvNlgf5ojPSdsJFw6JXw0cHudNGhH7Xzoq9ptdH/84dnRSX2Vz3XoUs7ad1Zqu39+f0p7OYeOWu64z5nbm4ifX5+KU3vkofj/57/p8vPRiPk7qnewX2wtx+M6FaJ1QiDv3y67uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeiv6xUcxwjoxV3xe+oirCxeOih8/Xx+9HxmdrjfxWHwWm9rGtMV73dl4j1zTEZPmd0b+gFwc93ouZf/hwnx8OqQQ93+vEIddVoiTFxbiB68XYvwXhdh8YDFOGFGMv9dlV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9/57/SY4QJ8UK68Wc8bnpu3BlfZpzpj7aGF9NbYqGNc1x7EGtady2Ls/874749OzO6Clnc3v6Sfl4bWU+HdchNxai8a1CqrF9z2IsnFyM4pnFePiSYpx/dbInurEY02/Oru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056K/rlSblCvBQzrBtzx+fHxisvNsbKXzal+fk717amcfm4/h0xfUAWu6xX4/P22/nYbmI2fsZzwoRiXHJ6MQbdUIx/PFCMdU8Xo/alYtzi3H9FMeavLMZlq7Kre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96KfrWCfClniJtih/VjDvkc2VqxWxbjxGHr7w/f6YzndsrW9yu/zafr97j5hTh3cDKPDy5G5+xMw+cvZNr6flyMWWuK8e3PijEque74aTEWJM8aP86u7kdtfK+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Ff3qJTWDvCl3iJ9iiHVkLvk82Xy9vT3N16NndKbxWdw2J/91eiG+8UEhHSdz+PiHk73sa8VY8mExtkr0DFudaHu/GHf/oxj1iZ7t/pyMa9Jmn0RXy33Z1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvRb+aUd2kdkjz+zsNaRwVS6wnc8rnyrb8vOG8XNx+aBbbtpqbre/LphVj9IJiHPVyEs8+ysaxK+HOJ9w/eKIY995ejM2Svf7jJyVrfGIxvvhmsuZ3LcZ1O2VX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70V/epmtaP6SQ0hj8ol4qmYYl2ZWz5fPq45Op/mqjSeb1eM/S8uRsOT2To2Xi8nvMNezsaVljsOL8a1pWKs/7IQv3uuENvcWYgr5xRiyqxCXHpGdnXvuffaaa+f/uyw9/LG+cQPf/zyjwMPLnw48eLGTwc9aX2Q6KOT3op+ewf1sxpSHaWWkE/lFHFVbLG+zDGfM1+HPlaI/uUsbl/0fLZerd+6t7K5nEvY7tk34e5XjNaHk/E5NVkj2xRiz9X5uPjRpN65Nh+fnJuPtWdkV/eee6+d9vrpzw577LI/amN84Jd/HHhw4cOJFzd+Ouihiz466a3olx/tIdTRakn1lJpCXpVbxFcxxjoz13zefIrP4vVHG2PZvBeLccRvsnG7OInXxnPVtwoxdEWm8bf5fAz7ey4u+mUu3pici+33ysWy8dnVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Fvz2kfZS9hHpaTamuUlvIr3KMOCvWWG/mnM+d79tWZ+tz8dPZXLWezeHZhxXizdfycfzETPNDP8hFx/KkFp7ZGVcUO2P5ex2x6+MdUXogu7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrprei3j7aXFCftKdTVakv1lRpDnpVrxFsxx7oz93z+GMTtA88pxtYtxXji1kKc0lhIx2/fN7LxHXFlZ1z7eUe8c3RHbJXs73Kz2uPJ7dpjVJ8kpn7all7de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Fb0O0uwnxYj7KvkDvW1GlOdpdaQb+UccVfs6bsxbxsHLL9N/Iph1u/39khqoVm5mPZANs6ntiZ73EntceLv2+KwurZYfnVrvLBzayzr1Rq7f9CSXt177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvRbD84U7KvtLe2vxE91tlpTvaXmkHflHvFXDLIOzUXjgcl6Xd+UT9fz1hM703F8c1p7NJ6dab7liZaYV9MSN57aHFP7NMeVDzfFF/Oa0qt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3op+NaJzFevD/toeM91/H5DV82pOdZfaQ/6Vg8Rhsch6NCeNC7YpW+fSdTyhtiOd2y/8rTVu/X1LPJvYpvXJxWPjuBFjY+SljXHa+MboGptd3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfRW9DtXc7bkfMUZg/Vir2m/JZaIq2pP9ZcaRB6Wi8RjMcm6NDeND8Z5a7O5vvTI1nQ8jW9p5ti4+HuNMeC7Y2LBjNEx86WGmHdqQzx/QHZ177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDV3o2k+ikt6Lf2aL8mJ4xJXWTswb7bevHvsveQ/2tBlWHqUXkYzlJXBabrE9z1DhhtZ7vvL85Jvc0xSW3Ncb67cakGs+ZVx/H3FIXV78zKqacOCo+2Tu7uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oumt6He+6ozROZt86bzFmYN9t72n9WQPog4Xb9VjahJ5WW4Sn8Uo69RcNV6YP1o/Np3bxvXhp+rjgHF1ccSSkbHPDSPi0T/UxrCu2rhw3fD06t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omgh65UX6KT3op+9bFzVnPDeZszJ/lTLWH/bQ9qH2Z9qcfFHHWZ2kR+lqPEabHKejVnjRv2deNHR9+d69NxnjFtRMx8anjsNXlYfPofQ2PcvCHxy+OHpFf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvRX94qEaUb3szNG5mznj/EU+VV/Zi9qP2ZNYb2pTcViNIk/LVeK1mGXdmrvGj4aP14xMx7fpqmGp1vb1g2P83TVx65uDou7aQenVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRmerd+M93Dc7bnTmLk+pn52/OoJzDmEtpff9+ti+zN1GfW3/qNLWKfC1nidtil/VrDhtHWpaWhsVB928ZC6bWxDPTBsbJb24Rf3mjfwz/Wf/06t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ot/+SG3g3N3Zs7rJGaT46SzKeYwzCftyc8z+zB5Fna5WtR7FJnlb7hK/xTDr2Fw2njQZ583HDYiTnusXr73TN8bM6xPnjeuTXt177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvT7zsn3Lr57sH9WMziHdRbpPE59La46m7A/l3PkX3NPva5mVbdZn/K3HCaOi2XWszltXGk7prZfqvmk53rFw0dvKC/d/auyq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pein7nJL57kh/FCOfw9pLOY9USzuXUV+ptZxTpfizZq9qvycfqdnNS/aaGsV7lMvFcTLOuzW3jS2Ph/H+Vvz9zXfms0mflK+asTq/uPc8+m35pe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pein57Q9+/qRF9D+O7CPWz/GlfJZY4n1NjOKdRe9qv27OKw/Yu8rMaVh1nrsrn1q+4LrZZ39kc7xW0HvHqJ+W/vvh+ebdJq9Kre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk95/x79Ts/2e7+HsGe2ffSfhXN7ZtLraHkN+dVblvEbMUYfZu9q/2cOIz3KVek5NI6+bw9azGGedm+vGm+YTS2+Xl935enp177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvR7zto5yTOzXwf5zspdZPc4XzeGbVzWmeV6m1nVs5t5F/7dzWJfZzYpJ5X04rbahv5XY4T581t692cN+60Hzb9hfTq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropHfT+G9a/5vi/6b8X+31X7XX/9W+/6v2/X+1n/9U+/lftZ//Vvv5f7V//1Pt3/9V+/e/1f79f7X//qPaf/9T7b//qvbf/1X77z+r/fe/1f7772r//X+1//1Htf/9T7X//Ve1//1ftf/9Z7X//W81//t/oASXzHic7duHm1XV1cdxehsGhmF6vXdm7sQ3ouaNhsSCd4kSNWqMvjEaFJUAGgl2jRIJEiHmtWF9xViiohJrxBITiVFiA0uI2CJiwxILoAGVYuM9n3O4+ScuPI/P8Zyz91rf3917r7X2vnd69Njy78oZLxZHDX+t+OmYd4rTm1YVb79kbXHS/A3Fm0/9qrj72p5x6Zl9YsVb/eLg3MDoHFkRM7or4w9rh8THV1XFVZ3V8em5w2P/52pi45e1MWpwfRyxqT6ufKkhWn/bGP1GN8X455ui64DmGHF/c+xY2RKr92uJx89oia45LbHLddnVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Jv/9/aeY7xS8uXVU8YczHxaorNxafuXRT8fAre8UZxb4xbkH/2HrwoGgaOTiO2XFIHF5fFfstHRYX/HR4nPlaTczaoS76TKmPiWc3xIBfN8a+xzTF2Ts0x5fvNsf7s1riuMGt8dszW+PQ11vjf0e0xdKj2+LRi9visVvbouae7Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6Tf5+HZmhkfF68f8llxanuPWDuwdyxe1Ddqxw6ITX8bFAt6VMav64fGHf2HxXnPVseMqdl4Tx5fH/vd1BDtTzWm43Xhw83peM7+n9bYfmNrHH1OW/y0b3uMO6499n+sPb5WkYtbIhfHH5WLldNy8ZNZ2dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0m/OeFz8e7jnXvE5bv1jmjsF30eHRC9dq+IyXMqY9rDQ+Osx4bFsuuHx7uH1EbHqroYOK4huu9ojIrXmuKANc1x0XstMemxTHPXyPbI/709ntk/Fxc+lIu5rfnY+mf52OaWfJz5Uj6WbMzHnMqO+FpNR3p177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvSb12YGz4fbQ7doV88s2FAVF1UEaM/r4zrdq6Kkw+sjurdauLdAXUx+bb66Ph6Y9xwdjbeF7/eks7Z3z/SFtvObk/HcfErudh+YqZpxM4d8bsLOqJ+aUfc268zJm7XGe/t0xlPHdYZE36SXd177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530lvSLDdaHOeJz0nb+LRWxacSQWDmzKu67szruvasm9jyvLt4blc1161HM+u3c1nT9Nl7ens5h4zb7r/mo2Ksj/vVgRyzv7IyLT0/+e7AzYn1nLOvsimPGdMVjh3XFOZO7ovb47Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6RffBQjrBNzxeelj7ja+Vp1vPhJTRyzoi5db+Kx+Cw2nTuqLQ78fjbev6zLxxNLkzl7Skcs7ZGxL3q1M8Z+syvaTu+KR+d3xcuvdsXCHoW4vqkQx44oxHMjC1HcKbu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3plyPESbHCejFnfG76dlZkOe2FNxti0kVN8au6lnjm561p3LYu33o8F2Pn5uP/xmdz+/VLO2NMRTaup/2tK2b1yTSeN7EQnb8uxMXXFKJwZyFWLSjE7X8rxOuPZFf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvSX98qRcIV6KGdaNuePzY2P0+ob4wZ+a0vz8pwda07i89L9y8frWWQyzXo3PPv264spp2Tgbz/mTC/HJlYU4+aFC7PlyIcavLsQZnxViSO/u2G9Ad9RXdMe6iuzq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLekX60gX8oZ4qbYYf2YQz5HtvY7Motx4rD1lzspHzsemq3v0Us60/W7dGlXrNwumcdTC3HBXZmGI9YVUm1Tqrvjvbru+GN9d0xPrtfWdEfD8O6YVZ1d3U/f/F477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056S/rVS2oGeVPuED/FEOvIXPJ5svnd77an+XrmVfk0Povb5uTRV3XFHUOzcTKHn329ELM3FWL7Yd1xeaJnam2irbI7mnp1x4xPC3HV+8m4Jm3uXlaI//1ndnXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9KvZlQ3qR3S/D4gi/NiifVkTvlc2Zafj5rXEcOmZ7Ht8se60nW67pJCnPVsIZ76vBDPD8vG8ZKq7rgw4V74biFani7E5IR9xGXJGp9WiCMTDasOL0TvQ7Ore8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96SfnWz2lH9pIaQR+US8VRMsa7MLZ8vHz3P6UxzVRrPf1iI+24vxK/eK6Tr2HjtlnBO/TwbV1qqZxSi1/6FmNBeiJpPuuKKf3TFl/d0xdNzu+LTq7Ore8+91057/fRnh73dNs8nfvjjl38ceHDhw4kXN3466Enrg0QfnfSW9Ns7qJ/VkOootYR8KqeIq2KL9WWO+Zz5euSdrjh+fBa313xSSNer9Xtmn+50Ls9O2JqPS7i3KsQ5ryfjMydZIwd2xa21XbH2zaTeeaAzfnxTZxx+TXZ177n32mmvn/7ssMcu+9M3xwd++ceBBxc+nHhx46eDHrroo5Pekn750R5CHa2WVE+pKeRVuUV8FWOsM3PN582n+CxeH1ybxbDK9YVY9Gg2bmuTeG08D5jSFaf370o1Dv9eZ/xiY0esua8j9vp1R1xzVEfselh2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0m8PaR9lL6GeVlOqq9QW8qscI86KNdabOedz57uqLlvn/706m6vWszm8fnpX7N2jK56dlmnuOKMjzu+X1LTX5eOzffKxb2U+bvxXLi5blkuv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnr/s/9L9tH2kuKkPYW6Wm2pvlJjyLNyjXgr5lh35p7PH4O4/ZcbCjFndCG2eTLxvWs23vf26kzHd9qf8tGrKR/fPycXlw/Kxey57bHtQe0xvZDE1Nrs6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3pN9Zgv20GGFfJXeor9WY6iy1hnwr54i7Ys+UzXnbOGAZnvgVw6zfv05IaqEbOuKlZdk4v7J7ssed2R7/fC7Z5+/YFvv+pTV2Htcau3a2xryh2dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0m/9eBMwb7a3tL+SvxUZ6s11VtqDnlX7hF/xSDr0Fw0Hpis1wm7dabrec60bLz3vrQ9Zl2faR6S2K7criUGzGmOFwrN8eXrTXHk4qb06t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KT3P+c/SY3oXMX6sL+2x0z338leQ72t5lR3qT3kXzlIHBaLrEdz0rhge/qAjnQdz/9WLp3bO69tjaHPt8R3HmlOtW67qjGWjmyMX97ZEK8e2hCXFLOre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96SfudqzpacrzhjsF7sNe23xBJxVe2p/lKDyMNykXgsJlmX5qbxwVjZmEvn8KiZrel4Gt/Lrm2Mtac1xEkn10fD1XXxzme1UTmnNnY6Nbu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+OuihKz2bSXTSW9LvbFF+TM+YkrrJWYP9tvVj32Xvof5Wg6rD1CLysZwkLotN1qc5apywWs+1y5rjyf2b4pMnG2LCQfWpxvcX1cQ/Fie5Z+Dw+PvF1fHjY6rTq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pekn7nq84YnbPJl85bnDnYd9t7Wk/2IOpw8VY9piaRl+Um8VmMsk7NVeOF+eC2pnRuG9fCyppY8KPhsWj1sLj7oarYavnQmLrv0PioObu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+OuihK9WX6KS3pF997JzV3HDe5sxJ/lRL2H/bg9qHWV/qcTFHXaY2kZ/lKHFarLJezVnjhn38YXUx5bCadJxXXFIV73wwJG6fVRljTx4cVy+qiC/Or0iv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnpL+sVDNaJ62ZmjczdzxvmLfKq+she1H7Mnsd7UpuKwGkWelqvEazHLujV3jR8Nh9RXp+N79v2VqdbzWgfF9c8PiKF9BsSZD/RPr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056S/p91+C83ZmzOKl+dv7mDMo5jLmU1vfJntS+zN5EfW79qdPUKvK1nCVui13WrzlsHGkZtX9lPPDSoGi4aECMvLRfvNy7b+zQu0/84tbe6dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0m//ZHawLm7s2d1kzNI8dNZlPMYZxL25eaY/Zk9ijpdrWo9ik3yttwlfoth1rG5bDxpMs7H/qhvLPu4V4wZ2DP6Xrip+OIdXxVd3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfSW9PvOyfcuvnuwf1YzOId1Fuk8Tn0trjqbsD+Xc+Rfc0+9rmZVt1mf8rccJo6LZdazOW1cafvHt3oFzfe8u7F4wW3rijfN/DS9uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oukt6XdO4rsn+VGMcA5vL+k8Vi3hXE59pd52RpHux5K9qv2afKxuNyfVb2oY61UuE8/FNOva3Da+NG5YvL7Y/fCa4r4nri7+5MWV6dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0m/vaHv39SIvofxXYT6Wf60rxJLnM+pMZzTqD3t1+1ZxWF7F/lZDauOM1flc+tXXBfbrG9z3DjTum3ug+I1jW8XK+etSK/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oev8T/+Zk+z3fw9kz2j/7TsK5vLNpdbU9hvzqrMp5jZijDrN3tX+zhxGf5Sr1nJpGXjeHrWcxzjo31403zcUTlxfnrX0xvbr33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpLen3HbRzEudmvo/znZS6Se5wPu+M2jmts0r1tjMr5zbyr/27msQ+TmxSz6tpxW21jfwux4nz5rb1bs4bd9pHPPBUenXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSu2X8t6z/LfF/S/4v9/qv3Ov/ct//lfv+v9zPf8r9/K/cz3/L/fy/3L//Kffv/8r9+99y//6/3H//Ue6//yn333+V++//yv33n+X++99y//13uf/+v9z//qPc//6n3P/+q9z//q/c//6z3P/+d8u/8v538Oglxc+ff6F4d/9Xi7u88lbxmB9+UOw86d/FSd9eV9x93ufFE4/sEQsf7RUje/aNXvX944j+A+OC5wbFs9MGxy/6DInnfzY0tr23Kl56bVi0rK6OXd8YHlPvr4kvptbG2x11EX+si97frI+KK+uj4cP6eHrbhrjpsIbodXpDNP8qu7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpLen3/5ePebW44KC3i9v2X1lccuia4uyD1hd/se6L4thcz9j5qt4xaHXf2Fg3IPZsGhSjNlTEiLsrY8reQ2PcQ1UxobY63tp3eOx+dJIfJ9XG1t+ri0m19fHK4vpYNKkh9lvdEKcf2Rg7LWyMoyubYv5eTTH3pKa48bym+PjS7Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6Tf5+HZnaNXFo94Zk1xfo8NxbkvfFn8/c0945Od+sRrc/vFFSsGxMQNg+L89wbH5HuGxBFjs/Hee/TwGHF2TXx1W206XsfdUJ+O57E7NEb1y43x3clNsee/mmKX/Ztj23lJfbaqOX6Tb4nv79kST45rid0mZVf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvSX95oTPxbsbdtxQ/GCPr4rtn/WMN2/sE2909o+9Tx8YY2+oiPHzKuPPZw2Nx3ccFj3/Xp3UWjXRd3ZtfPBgXWz3bH0c/0RD7DEv09y7vjl6/KE57vxGEv+va4kZX7XEoH1bY/C5rXH4/a1xx8ut8fMPW6Pfp9nVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Jv3Vhbvh8tNmptlfcuaxPfHRC/8i/MjDObBkcB2w/JNbkq+Lx95MxP3949Ez2FWcdnY33CQsb0jl79o1NUXl8Nr6//2tLVI/JNFW0tMW049pi3V1tcem7bbF7VXssGtEet45qj9G7Z1f3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvSX9YoP1YY74nLS98Nz+8WrloHhy4uD4v4uGxCWXVEX3lOpY1JbNdetRzDp9ZmO6fjf8vDmdw8bt2N+1xgdbJXn92rZY0Kc9Tvhx8t+17dH2Unv8uU8u9urOxY2jkv3qPrn4ZP/s6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3pF98FCOsE3PF56WPuNrzoSHxxxeqYq+Hq9P1Jh6Lz2LTMW1N8d/bZeN92PqWuPnu1jj2oLaYv6ItZZ/3YHvsODwXXxySixsuzsX9D+bi2hW5mPF5dt59d30+2pqzq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pekn45QpwUK6wXc8bnpm+vVVVpzrnnkZrY48S6OHJ9fdx5cGMat63Lh3/fEjvObI1TRmdz+8FTklp9VXs6rj+am4sJ7+RSjZPH5KPXUfk4YXo+el+Uj6euysd5yf79wRuzq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pekn55Uq4QL8UM68bc8fmxkXupJra9oi7Nz5df05jG5fmDWuLBwVnssl6Nz9ffbY+p47LxM54X7pOP587Ix4HX5aP7L/mIJfkYuzwfq99K9oTv52Pdyny8sCq7uvfce+2010//Czd/X8Iu+/zwxy//OPDgwocTL278dNBDF3100lvSr1aQL+UMcVPssH7MIZ8jWyN2y2KcOGz9fXVgazTskq3v/J3t6fqdf3cunqhK5vHYfEy5JNOw6z8zbft8nI9F6/Nx2YZ8jEuu0z9NdCfPfvJxdnU/bvN77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3pVy+pGeRNuUP8FEOsI3PJ58lm4WvNab4eP601jc/itjn53WT/dv6/c+k4mcN3LczHsW/ko3ptPk5N9ByyLtH2YT42vJmPI17Mx9Qnk3FN2ly8IB9H/zm7uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oukt6VczqpvUDml+T3KIOCqWWE/mlM+Vbfl5j9+0xb8Pz2LbqfOy9f3CyfkYf08+bn0liWdrs3E8cU0+jku4r12cj89vz8deCfugU5M1Pi4fxUTDU8V8vLlzdnXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9KvblY7qp/UEPKoXCKeiinWlbnl8+XjjWPa01yVxvNvJWwX5OPIJ7J1bLxyCefBr2TjSsuaI/OxYrt8jO6Rj4+fz8XP5+filctycdvMXDz/y+zq3nPvtdNeP/3ZYS+3eT7xwx+//OPAgwsfTry48dNBT1ofJPropLek395B/ayGVEepJeRTOUVcFVusL3PM58zX3Mdzsd/oLG4/80K2Xq3fw9/J5vKxCdvG7ydzeGA+frowGZ/TkjWyfS7OWdceSx9J6p1r2uM7Z7fHqOnZ1b3n3munvX76s8Meu+yP2xwf+OUfBx5c+HDixY2fDnrooo9Oekv65Ud7CHW0WlI9paaQV+UW8VWMsc7MNZ83n+KzeD1yXRbDViWx6qabsnFbmsRr4/mN/XJx8HuZxjVfb49DXm6LZ+a0xdeOSmq8PduidVR2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0m8PaR9lL6GeVlOqq9QW8qscI86KNdabOedz5/ujddn6rFqSzVXr2Rx+8YhcbLWiPe4al2nueVhb/OzdpBb+VWssG9EaW3/YEjMXtcTJC7Kre8+91057/fRnhz122eeHP34/2vwZ4MGFDyde3PjpoIcu+uikt6TfPtpeUpy0p1BXqy3VV2oMeVauEW/FHOvO3Bu5Wbu4feWsxHdnPgbflosF7bl0/C55KxvfsVe0xhuftcQ2k1vi1JXNMWVmc1SObI5x/Zoj1jWlV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Jf3OEuynxQj7KrlDfa3GVGepNeRbOUfcFXv22Zy3jQOWtU35NIZZv1fvkdRCs9riTwuycf5LV0s8MbE57ru3KW5oaoqtr26Mpl0bo6VPY8z6d0N6de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0m89OFOwr7a3tL8SP9XZak31lppD3pV7xF8xyDo0F40HJut1dEd7up5PHdeajuNWpzTHhLMyzasXN8SqoQ3xr9Pq455+9bF8YV0Ub6lLr+4991477fXTP503iT122eeHP375x4EHFz6ceHHjp4Meuuijk96SfjWicxXrw/7aHjPdxx2U1fNqTnWX2kP+lYPEYbHIejQnjQu2277Zlq7j2XUt6dxueq4xPry3IepvrE+1Vv69NubX18ZhF9XEA7vUxIm57Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6TfuZqzJecrzhisF3tN+y2xRFxVe6q/1CDysFwkHotJ1qW5aXwwrtyYzfWWiY3peBrfk2bUxtJDauIHPxwe66dVx6PLh8Wq04ZF44+yq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx40/3jb/L4gB9dNJb0u9sUX50xqRuctZgv2392HfZe6i/1aDqMLWIfCwnictik/VpjhonrNbzJ/fXxy3fqIvnbquJ0SOHpxoX31wVf7hlaLz+/pC47aQh8Z3vZVf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvSX9zledMTpnky+dtzhzsO+297Se7EHU4eKtekxNIi/LTeKzGGWdmqvGC/PITbXp3DaufZ6uit9+e2jctKQyLr5ucPR/oCIO2aYilnwxKL2699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+OuihK9WX6KS3pD+tjydlc995mzMn+VMtYf9tD2ofZn2px8UcdZnaRH6Wo8Rpscp6NWeNG/biqOrYZ1RVOs4LTx4cjz41KM6bNDB2/OGAOOPm/rH82P7p1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvSb94qEZULztzdO5mzjh/kU/VV/ai9mP2JNab2lQcVqPI03KVeC1mWbfmrvGj4dsbKtPxnXjlwFTr5K/6xow/9okP3+4dh1/TO72699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3p912D83ZnzuKk+tn5mzMo5zDmUlrff5jty+xN1OfWnzpNrSJfy1nitthl/ZrDxpGW5m8MjKvu7xfrT+gTtaf0ivvf7hHdizcV76rbVHR177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvSb3+kNnDu7uxZ3eQMUvx0FuU8xpmEfbk5Zn9mj6JOV6taj2KTvC13id9imHVsLhtPmozzPt/uGSfc8mVxr+c3Fh/5wfripcevS6/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oekv6fefkexffPdg/qxmcwzqLdB6nvhZXnU3Yn8s58q+5p15Xs6rbrE/5Ww4Tx8Uy69mcNq60HbrNF6nmE25ZU/zesR8WJ4xZnV7de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Jb0Oyfx3ZP8KEY4h7eXdB6rlnAup75SbzujSPdjyV7Vfk0+Vrebk+o3NYz1KpeJ52KadW1uG18a7zv3o+JbM98vDtr+nWLuirfSq3vPs8/my6L2+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70l/faGvn9TI/oexncR6mf5075KLHE+p8ZwTqP2tF+3ZxWH7V3kZzWsOs5clc+tX3FdbLO+szm+JtW6asWK4qHLlxefOnpZenXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvzpb80TPXTRRye9Jf3qIfsj38PZM9o/+07CubyzaXW1PYb86qzKeY2Yow6zd7V/s4cRn+Uq9ZyaRl43h61nMc46N9eNN81ffvO54oR7l6RX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70l/b6Ddk7i3Mz3cb6TUjfJHc7nnVE7p3VWqd52ZuXcRv61f1eT2MeJTep5Na24rbaR3+U4cd7ctt7NeeNO+/u/fCS9uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+OtPvL7aM/5b1vyX+l33+L/f6r9zr/3Lf/5X7/r/cz3/K/fyv3M9/y/38v9y//yn37//K/fvfcv/+v9x//1Huv/8p999/lfvv/8r995/l/vvfcv/9d7n//r/c//6j3P/+p9z//qvc//6v3P/+s9z//rec//0/TQsvqnic7duHu1TVucfxQzkHDpzeZk6Z2FuuRI1ec637FbHEgtGYqGhUrBhb9IYY1Ago6jWCxIIk2MUYk4hdsRsLFlBRFBXFelFBFI2IgCDe/dnbuf/EwPP4bPfea73v9zdrrfd915o5VVVr//UZOyO5ufWF5Fe7v5p83PVW0nb5+8mLd3ycNP/us2TJgi+TS75bnmz+7Ork4uur4uD7ese9N/eND06viU16+sdLU2tjs4EDY9zP6+JH59TH6eMb4orRjTH74KbYv7M5tn+iOSYd2BKHvNwSw7drjVMmtMZ6L7bGqlWtcXChLU5bty27uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oukt6/f/u4x7NTn2ireSebt9kIyasjDZ7oolyUb/+VVyxOiVyfoj1yRHjO8V+93XJ655oDoun9wvzt2/Np5YOCDuPrwuHrinPrb7tCH+UtMUO1U3x9hFzfHgPS2x1YjWKFW3xY3j2+KF79riT0e0xyO3tkfNwvZY0dwRK3/YEXtunV/de+69dtrrpz877LHLPj/88cs/Djy48OHEm3Gn/HTQQxd9dNJb1u/z8GzY2A+SAY2LkkOTz5OhbcuSbyZ+k1x+z3fJ1oN7x5IxfeOByTXxwYX947EDBsS9Kwdm43Dd+w0xdqOmOHDvfJyfGpyP65OvtMWIUe1xVW1HXH1+R1y6pCPG7VaIwy4uxNv/KsTUjwqxzreFuLJvMbu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3rNyd8Lt7t9/vPkyvPW5Ys+vGq5L+GVMW2T/aO6wrVccfgfjF9t9qoX39g9Myri1+e1BA7vd0Yhw5qjji8Jc4/rTVmjGiLKbvlmg+5ryN+sW8h+r5UiKeSYrx2TTGOXFyMozbrjHsO7ozeZ3bG8xM649Ar8qt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3rJ+68Lc8Plo84MTViXVo6piSFOfOOOs6pj7UL/425za2P3xgdF9UX1c+x+pv380xRs1+XjPOCKf22/t2hFHN+Tj+O1hxRixoJhpOvKhzphT3xV7798VCy/oir/c3hU9z3dF1dtdMfmD/Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6xfbLA+zBGfk7YLNusTW0+rjnX69o/FWwyIhVvVxdkDG6L0aGM296xHMevF9duz9Tu0I5/Dxu2JnTojnu2MrqQrmm7qiqdXdMWMnbvjt2d0R8NN3XHNjO5YOb87Hv2kO/b6PL+699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3rFx/FCOvEXPF56SOuHnT4gBgwsi6uHZ6vc/FYfBabHn2kPS58sSMblzuvLMaa/Tvjya86o2ZsrnnVr7pj4l3dccCK7lixZU80HN4Ty8b0xGtTeuLGW3ui//Se+O8H86t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3rJ+OUKcFCusF3PG56bvwRfXZTmn9uimmNLUEvde2RrVy9uyuG1dFvcoxiXrd8bM9zuzudva1h1nXpyP66279MT943KNjy3oiYOqSzGjVIpDtijFujuU4v1dStE6JL+699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3rlyflCvFSzLBuzB2fHxu/O6Mpxv2kJcvPn+7YnsXl6n8UovXWYha7rFfjM+aC7nhxdXc2fsbzw096YtPuUvwtKcXZw0px5cmluP3MUux6XinG/k8p9rm4FD8cn1/de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Jb1qxXkSzlD3BQ7rB9zyOfI1rnvtWYxRxy2/g78shinvNWZrc8zhubrt+aAnljnjp6Yu7InHt8q13D573Jt119WitLkUixK/7v7ylK8ekWq+/JS3H9ZfnXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9avXlIzyJtyh/gphlhH5pLPk82znu7I8vX07jyei9vm5NXdPfH+xHy8zeGaI0vx5OhSjLi0FDNTLdMmpdomlGLouaW4d2QpZp9Qis3SNh8fUoqHD8qv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnrL+tWM6ia1g/wph4ijYon1ZE75XNmWn6ds3BW7renKYtOs3fJ1+sPWUtx3QCm+O6sU/S/Lx/GZP+Xcy44vxf77lOLalP3ItnSNr+6JSamG9d7tif96K7+699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3rVzerHdVPagh5VC4RT8UU68rc8vnysW3t97lKPH+1Jz7ZPGUaka9j4zUyHa9pZ+XjSsvuVaX4yeyemHxdT+z52554fr+e2GqbnqjaIP3cSvnVvefea6e9fvqzw97I7+cTP/zxyz8OPLjw4cSbxanleV6ghy766KS3rN/eQf2shlRHqSXkUzkli6v/yNe7OeZz5mvFsT0x9f2eLG5vPDJfr9bv3ePyufxkyrbfkp647ZaeeOSIVEshnbNzuuPdSd2x8dHdUdqpOyZs1B2Xl/Kre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96yfvnRHkIdrZZUT6kp5FW5RXwVY6wzc83nzaf4LF6Pn5THsF3OKMWqIfl4bzwtH88LPu2OaRfmGveY2RW3jeqKjX7SFX+o7oqXP+qM0+d3Zlf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvWX99pD2UfYS6mk1pbpKbSG/yjHirFhjvZlzPne+h1yZr8/jTs7nqvVsDm/+XXf8YUx39Ps213TQqs54/IK0Fl63M7Z4vhhjJhRj3nHFeO6Q/Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6zfPtpeUpy0p1BXqy3VV2oMeVauEW/FHOvO3PP5YxC3P9+gFLOe6Inhe/VE42P5eC88tysb3zt+0hnbTinGubXFmPnHQjyxflrHz+2Iu/6axtRJ+dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb1m/swT7aTHCvkruUF+rMdVZag35Vs7J4vyIfP2Zg8YByx4P5jHM+v3yf9NaaIOuqB/WmY1r81PpXrdvIeoOTPf5D7TH2B3a49S32+L0qW3x5sT86t5z77XTXj/9MzupPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3rJ+68GZgn21vaX9lfipzlZrqrfUHPKu3CP+ikHWobloPDBZr5Mfz9f1rNX5eJ/TWoj71+vItO2a2t7l9tbYsdAatTe3xFZHtsSkPfOre8+91057/fRnhz122eeHP375x4EnywsH5PUBXtz46aCHLvropLesX43oXMX6sL+2x7TPstdQb6s51V1qD/lXDhKHxSLr0Zw0Ltiq5uTre8G9hWxun3p6qvvAtjhp19ZM69EnNUfN9Ka4a4umaJ7fGE8/1phd3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfSW9TtXc7bkfMUZg/Vir2m/JZaIq2pP9ZcaRB6Wi8RjMcm6NDeND8Zd/pLP9dP7tGfjaXyfXac5NlnRGDcvbYh9ehqi66z62KVQH6cuq8uu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnrL+p0tyo/OmNRNzhrst60f+y57D/W3WkwdphaRj+UkcVlssj7NUeOE1Xre65DWWDO7OTbbuykmz801l/asi74/HRjbXDQgerUMiEsW1WZX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ71l/c5XnTE6Z5Mvnbc4c7Dvtve0nuxB1OHirXpMTSIvy03isxhlnZqrxgvz+Gubs7ltXIedWBefvzYgVp1cGx8n/eOwQ/vFtOdrYsOra7Kre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4MeujJ9qU56y/qz+rg6n/vO25w5yZ9qCftve1D7sJnff7ZijrpMbSI/y1HitFhlvZqzxg37pPn1ccP8gdk4t7f2j84Ta+L9vtUxcWmfeHmPPrFlfX5177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvWLx6qEdXLzhydu5kzzl/kU/WVvaj9mD2J9aY2FYfVKPK0XCVei1nWrblr/GgYP7k2G98Ht6vOtP7rml4x98CquLD/d8mIUWsSV/eee6+d9vrpzw577LLPD3/88o8jq7Grcj6ceHHjp4Meuuijk96yft81OG935ixOqp+dvzmDcg5jLmX1/YR8X5btTdL63PpTp6lV5Gs5S9wWu6xfc9g40nLa7L7xxcG9Y5+mqpi78eqk1H9l8nLN8uSwX3ydXd177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530lvXbH6kNnLs7e1Y3ZWeVh+dnVs5jnEnYl5tj9mf2KOp0tar1KDbJ23KX+C2GWcfmsvGkyTgPnrYy6V74VbK05d/JKROXJDvf9ll2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb1u87J9+7+O7B/lnN4BzWWaTzOPW1uOpswv5czpF/zT31uppV3WZ9yt9ymDgullnP5rRxpa3mpKWZ5p6FC5Nl//wwaRi3ILu699x77bTXT3922GOXfX7445d/HHhw4cOJFzf+7Ez1prwWoo9Oesv6nZP47kl+FCOcw9tLOo9VSziXU1+pt51RZPuxdK9qvyYfq9vNSfWbGsZ6lcvEczEtW+/p3Da+NA5/7qPkkifeS+49bX4y67U3s6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3rJ+e0Pfv6kRfQ/juwj1s/xpXyWWOJ9TYzinUXvar9uzisP2LvKzGlYdZ67K59avuC62Wd/muHGmdfK685KarleS3938UnZ177n32mXxIO2nPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530lvWrh+yPfA9nz2j/7DsJ5/LOptXV9hjyq7Mq5zVijjrM3tX+zR5GfJar1HNqGnndHLaexTjr3Fw33jTfctrMpGHpjOzq3nPvtdM+65f2Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pesn7fQTsncW7m+zjfSamb5A7n886ondM6q1RvO7NybiP/2r+rSezjxCb1vJpW3FbbyO9ynDhvblvv5rxxp/2Khx/Mru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056147/2vW/Nv6vzf+VXv9Vev1f6fu/St//V/r5T6Wf/1X6+W+ln/9X+vc/lf79X6V//1vp3/9X+u8/Kv33P5X++69K//1fpf/+s9J//1vpv/+u9N//V/rff1T63/9U+t9/Vfrf/1X6339W+t//rv1X2f+u2vHhJJn9ZPJ1r1nJmNdeTqYNfSM5/qR3kr9vtSC58PpFySYPLUlmnLc0uX318mTPTVcnY/tWxe0v9YqvRvWJq3tVx9cjauJnd/aLb97qHzt/UhtHvj0grrpvYPzgjLqoWbc+jrqrPjbaoiEGTW6I7Rc3xJI0Tj9zSGNsOLIxdjonv7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpLev3/28ms5I1+81JTu81L2n55bvJnP0+TB794pNkxfZfJA9tuSw5+NWVybabrklOKPSKI5b1jqG3941Ldq+J0Q/3i/NbaqN6rwFx7DED07qrLvbdoz4ubGmINTMaYtFRjXHqJ40x5VdNcdgjTXHRgOaYs1tzPHVqczz9P83Rfml+de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb1u/z8GzpjvOSqTPfTb765sPko9mLk8v3/ney+W++TqpiVTLyke+SC5b1its+7BPj76iOsQfl431iMiCGnjcw1v17XTZef7q+IRvPiVs1xTavN8WI45vjhP9tjsP3aYmfTW2JTRe1xN9/0Bq/GZLuJYa1xtFH5Vf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvWX95oTPxbuPt/4o+VHyaXLukn8ntUOWJ9WDVycnjqyKs6/vHedN7Rtvjq6Jhdv2jw1m1saAHQbGJml9Xfdwfew/uyEufboxjpuaa96orSXW/2dLvDwo3Tdek9bLaY22+U/b4kcXtsWY+9pi9utt8efFbbHpl/nVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Zv3Vhbvh8tJm+4ZfJjdOWJ+vNX53sOq8qbujsE/+9ZXW0rtMvFn6UjvlFA2KD/nVx0zH5eF/2SGM2Z2+5oTm2OCkf3+cebI1tBueaBnW2x7Untkfx9va4d0F7HFvfEYs264jnt++IYyK/uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oukt6xcbrA9zxOek7VGN3ybfDegVnw7vE9MnVMc9E/vFnifUxqLufK5bj2LWlDFN2frt+m1LNoeN28Sr2qJu4/b4+Or2mN+rIy77Zfrf1R0RczvizV6F+PUGhXh6+0L8cc9CdOybX9177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530lvWLj2KEdWKu+Lz0EVc3eLg6Xp/TL379WG223sRj8Vlsuri7OX6+eT7e53zVGjNvb4uJB7THnHfaM/ZnH+qIQ5sK8YNfFmLGJWnd/lAhHn+nEDeuKMQpA4rxalsxophf3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfSW9csR4qRYYb2YMz43fTdc1C/LOXP/NTCOO6U+zv2qIV4+sCmL29blgpta49AxbXFlks/t907riN0XdWTj+vvrCnH+B4VM4/jBxdjw6GJcdlYxNppQjM/+XIxp1xXjvRvyq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pesn55Uq4QL8UM68bc8fmxMXjuwPjZpPosP98/pSmLy3P6tcZ7tXnssl6Nzz4L0np9WD5+xvPOPYux7PfF+G26l9nz/nTvM6sYZ79RjMb3izH0o2IUFxVj+aL86t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766Mz0fv9PrSBfyhnipthh/ZhDPke2hu6cxzhx2Ppb92dtsf12+fre9daObP3Oub0Qi+vTeXxQMS6ZmGs48tVc28lfFGPRV8W4b1kxRqfX679Mdf+7GOO+yK/uR3//Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnrL+tVLagZ5U+4QP8UQ68hc8nmyuceGLVm+Pm9UWxafxW1zcsSoQtz2WSEbJ3P4lUeKMfHtYmzzeTEmp3rOXJpqS/e0Xe8VY+wrxbjqmXRc0zZ3Ty/GRffmV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Zf1qRnWT2iHL72kOEUfFEuvJnPK5si0/HzeuPVoOy2Pb5Kn5+l7+m2Kcd0cxnp+XxrPP83G8fEkx/pRyPz6jGKV/FOPXKfvmp6drfFgxhqcaPtsx3d9vl1/de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Jb1q5vVjuonNYQ8KpeIp2KKdWVu+Xz56H18R5arsnj+45Ttj8U49+l8HRuvwSnnqHn5uNLSengx+gwqxjHpPr99TiH+PK0Qay4txAtjCvH1mfnVvefea6e9fvqzw97g7+cTP/zxyz8OPLjw4cSLGz8d9GT1QaqPTnr/f/6newf1sxpSHaWWkE/lFHFVbLG+zDGfM19PPVmIU5M8bn85J1+v1u+YD/K5PDFl694nncM1xfjjI+n4jEzXyJaF+OfSjlj6r7TemdIRw87riCPOyq/uPfdeO+31058d9thlf/T38YFf/nHgwYUPJ17c+Omghy766KS3rF9+tIdQR6sl1VNqCnlVbhFfxRjrzFzzefMpPovXBy/NY1jD3GI8c2M+bkvTeG08D9irEKM+zDW2btoRZ77eHl9e0R4/PTqt8Ya0R7J9fnXvuffaaa+f/uywxy77/PDHL/848ODChxMvbvx00EMXfXTSW9ZvD2kfZS+hnlZTqqvUFvKrHCPOijXWmznnc+e7+at8ff54Vj5XrWdzeMVhhdjrnY54ZViueYND2mPCgrQWPqctVm3WFvsubo2/PtUak6bnV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9/7//S/fR9pLipD2Fulptqb5SY8izco14K+ZYd+aezx+DuP3w2NT3usX40S2FmN9TyMbvnvfy8T17Ulv0XtEa+x3fGpMXtsQlY1pii61bYnSfljhqaXN2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb1u8swX5ajLCvkjvU12pMdZZaQ76Vc8Rdsefk7/O2ccDSmvoVw6zfR3dJa6Gx7TFvej7Ob6/XGouHt8QbdzbHjEJz7PuXpthxh6bYuVdT3PxZY3Z177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvWbz04U7Cvtre0vxI/1dlqTfWWmkPelXvEXzHIOjQXjQcm6/WYdTqy9Tx5WFs2jnud1hLnj841N6a2G+obo//IhpjbpyG+faQ+ht9cn13de+69dtrrp382b1J77LLPD3/88o8DDy58OPHixk8HPXTRRye9Zf1qROcq1of9tT1mto87IK/n1ZzqLrWH/CsHicNikfVoThoXbC9s0Z6t4ztaW7O5veNLTdF0V2Nsd0NDpnWLmXUxp60uzpkwMN7ZbmBcXsqv7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnrL+p2rOVtyvuKMwXqx17TfEkvEVbWn+ksNIg/LReKxmGRdmpvGB2P98nyu7zy8KRtP43vFH+pi6S8Gxun7D4jOM2vjozf6R8PI/rHDz/Ore8+91057/fRnhz122eeHP375x4EHFz6ceHHjz/aNV+VxgD466S3rd7YoPzpjUjc5a7Dftn7su+w91N9qUHWYWkQ+lpPEZbHJ+jRHjRNW67ljekPMGlQfy24ZGMdsPSDT+Mlf+8VLN9dEr4+r44VTq2PYHvnVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Zv/NVZ4zO2eRL5y3OHOy77T2tJ3sQdbh4qx5Tk8jLcpP4LEZZp+aq8cJ88Oq6bG4b142f6xcPbVMTz8zqG3df0yc2e6B3nPnD3vHFyl7Z1b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD12ZvlQnvWX9WX18VD73nbc5c5I/1RL23/ag9mHWl3pczFGXqU3kZzlKnBarrFdz1rhhH759bZy8fb9snD/4TZ/46NleMe2oqrjvxjXJDnt/m3wzd3V2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb1i8eqhHVy84cnbuZM85f5FP1lb2o/Zg9ifWmNhWH1SjytFwlXotZ1q25a/xoOGRZ32x8L5hcFbR2Ll+ZDDl2ebLOU8uSXv+5LLu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466S3r912D83ZnzuKk+tn5mzMo5zDmUlbfL873ZfYm6nPrT52mVpGv5SxxW+yyfs1h40jLToOqYtTJq5Ifz/86OeWDL5P7nvw8+fUTnyXLmj7Lru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056y/rtj9QGzt2dPaubnEGKn86inMc4k7AvN8fsz+xR1OlqVetRbJK35S7xWwyzjs1l40mTcX5rxBfJPVMXJ+Nf/Djpt/eHyesnLMiu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnrL+n3n5HsX3z3YP6sZnMM6i3Qep74WV51N2J/LOfKvuadeV7Oq26xP+VsOE8fFMuvZnDautF23ySeZ5numvptccvz85G/JW9nVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Zv3MS3z3Jj2KEc3h7Seexagnncuor9bYzimw/lu5V7dfkY3W7Oal+U8NYr3KZeC6mWdfmtvGl8ZtxbyebnPN6MnTQK8kxl72cXd17nn82i7P2+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ71l/faGvn9TI/oexncR6mf5075KLHE+p8ZwTqP2tF+3ZxWH7V3kZzWsOs5clc+tX3FdbLO+8zn+bqZ1y/mzk+vmPpc0HvVMdnXvuffaaZ/1S/uzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Zv3rI/sj3cPaM9s++k3Au72xaXW2PIb86q3JeI+aow+xd7d/sYcRnuUo9p6aR181h61mMs87NdeNN8y6DHk/+dtvD2dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb1m/76Cdkzg3832c76TUTXKH83ln1M5pnVWqt51ZObeRf+3f1ST2cWKTel5NK26rbeR3OU6cN7etd3PeuNM+6Pd3Zlf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvWvHf+36Xxv/1+b/Sq//Kr3+r/T9X6Xv/yv9/KfSz/8q/fy30s//K/37n0r//q/Sv/+t9O//K/33H5X++59K//1Xpf/+r9J//1npv/+t9N9/V/rv/yv97z8q/e9/Kv3vvyr97/8q/e8/K/3vfyv53/8BmbmQoXic7duJ/13Tvf/x7zffeTjnO5yEomal0ih6SxvT+rjUPKUuQUjNQ0sNvcSc1FQtQcxFrlKEqlSMdQk1E5GYQgZUgohERCSIJJLffu7t3MfvfzjyeHhse++1Pp/X+6y1Pp/PWud86+q++7feqfelZ1v+N52z1ZOpueuF9NM/TErzb3sjbfbr6ak07d/pnkUfpN0f+zhtucWnafKhn6eLt/8yfTDt6/TbAd+kf69ZFwMvrI+rXu4T2y5piN83N8Vfv26Kd19rjsOuaondtmmN2ye1xrG7t8VJ97fFOS3tsfku7dF+WnscO6o9RtxQXN177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvX7/0NPfzJdcPELacnAyen6y6akAy6ekXbceGY693ez0/bHzUvvdn6WHjt0cTrwmCXpF+svT+tdtzK9enh9PD21T7zw48bY5eimuHNEc+wxvCWuOKI1Jvy4Lbaf1RabDG+P+5o74p0zOuLmaR0xccPO6Hd4Z7Re2hltt3fGAfcUV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Vf0+D8/OOHVy2rjhzXTWf7ydTmidlVa74KN0352fpItOX5iWffFFunL9pen9hhVp8sS6eOZ3xXiPHdIUo25ujiOebcnH6/XH2vLxfG3Pjhi2qCPuPr8z/r6yM245thRXPVGK3zSVY/5W5bj/0HJsOqwcd5xbXN177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvWbEz4X7357/NvpwWGzUutGc9LlZ89Pl5z5eTp4/a/S8DOWpZHDV6Y1b6qPH+3bEEfPbow9BzfHr8e0xN5TW+OaeW3xxqz2uOuJQvOxm5XiqOdL0Xe3crz+SDlmr9oVJx7VFSf/tSuefq0rehd3xYyW7vhNuTu/uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oumt6rcuzA2fjzbbHjIn/XD2/DSm76JUmvdVevE3y9PDe9TF/lv3iR81NMbY25vi6A1a4qMRxXhPmdaez9l54zvjlItL+Th2vlWOYUMLTSdt0R2z/tAdQ17qjiV1PXHnxj0xYKee6BrcE2MOLq7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeqv6xQbrwxzxOWk7Z+dF6cK9lqShy5enpXfVxZK7+8QlFzbGgJ8Xc916FLPeuakjX7+HXFHK57Bxe+3hrtj7P7uj/yPdsfZaPTHllOy/f/bEBQt7Ys21euOe1Bttg3vj5SN748Djiqt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3qp+8VGMsE7MFZ+XPuLqMVPrYvUFfeKeGY35ehOPxWexadLPOuPaXYrxfrK7K0oTszn72+7ot6w7Z++Y2hP/M6A3Dj+lN1rv7o21pvZG47Le+LBvJcZtWIlVN6/E+T8tru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056q/rlCHFSrLBezBmfm77HNBU5bbV3muOuP7XGs93t0ffkjjxuW5cbPVmO/7mpK6YNKeb2+pf1xB+binF99NHeeGFFb65x8tBKHDO8ElOuq8Rxd1Viswcr8dmjlVj/8eLq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLeqX56UK8RLMcO6MXd8fmxctLA5rh7XmufnZQ915HG53wblWH/DIoZZr8bn8rreePe0YvyM5+IjK/Hzayrx8COVuOSNStz+USWeWFyJX35TiVF9+saQpr6xVXNxde+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb1a9WkC/lDHFT7LB+zCGfI1ujDipinDhs/R15fFecs1+xvi96oSdfv/0m9sam/bN5/LtKvHp3oeGvnxVa7+3sGwN6+sbX2X9PdfeN98t94+BS33ihs7i699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466a3qVy+pGeRNuUP8FEOsI3PJ58nmn6KU5+vnr+nK47O4bU7efU1vLGwrxskcXmV6JV77uhLDOvrG9EzL+K5MW0vfbA5W4pkFlXj3/WxcszZfvl6Jia8WV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Vf1qRnWT2iHP7w1FnBdLrCdzyufKtvz8t790x36nF7Ft+hPF+t5qZCWem1iJri8r8b3OYhzfbO8br2fcjbMqcehzlRibsZ90WbbGT6vEbZmGzQ6sxM77FVf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvVX96ma1o/pJDSGPyiXiqZhiXZlbPl8+fnF+T56r8ni+dyWW3lGJZzNW69h4XZiN1/gvinGlZf8zK7HTrpUYs1olDvi0N95+sTe2H9sb3aN7Y+B1xdW9595rp71++rPD3oXfzid++OOXfxx4cOHDiRc3fjroyeuDTB+d9Fb12zuon9WQ6ii1hHwqp4irYov1ZY75nPlqea837h9SxO0tsjlrvVq/T68o5vJrGduvjs2416vEy9Oy8RmVrZE9euPTrt7Y8p2s3nm4J0bf3BO3Xldc3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfRW9cuP9hDqaLWkekpNIa/KLeKrGGOdmWs+bz7FZ/H6xq4ihg1aWImOJ4px23KjSj6e1xzdG4/V9+YaB+/YE48v6o4txnXHpcO7Y+ah3XHe/sXVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Vvz2kfZS9hHpaTamuUlvIr3KMOCvWWG/mnM+d7//qLtb5qR8Vc9V6Noe3PqM3Ri7tiVWGFZqPPrU7XqnLatobumK7nbriipaumPNeOaa+Xs6v7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnqr+u2j7SXFSXsKdbXaUn2lxpBn5RrxVsyx7sw9nz8GcXvl6ErM2KYSJz+T+R5YjPeS5cX4/mtcV+zUtyuuPL8c0xvL8dpNpThl71I8tXYWU7uKq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Peqn5nCfbTYoR9ldyhvlZjqrPUGvKtnCPuij33fpu3jQOWwZlfMcz6rR+a1UKju+P7bxTjvM522R733FKsMSnb5/+0M654sCOGD+6I89bsiLltxdW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb1W/9eBMwb7a3tL+SvxUZ6s11VtqDnlX7hF/xSDr0Fw0Hpis1zFb9+TrecZpxXiPHFmKF24sNP8ysz1o4/bYY1RbrLZOW2w/vTVue6o1v7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrprepXIzpXsT7sr+0x8/13ttdQb6s51V1qD/lXDhKHxSLr0Zw0Lti69+jO1/HiTcv53B7+SUfsO6k9zh7flms9ZXZL9Nu8JZ68qznW3b853hxYXN177r122uunPzvsscs+P/zxyz8OPLjw4cSLGz8d9NBFH530VvU7V3O25HzFGYP1Yq9pvyWWiKtqT/WXGkQelovEYzHJujQ3jQ/GQZVyPod/f05HPp7Gd+qfW2LLU5rjoROa4uBrG2PjLxpi0KiGOPfE4urec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIeu/Gwm00lvVb+zRfkxP2PK6iZnDfbb1o99l72H+lsNqg5Ti8jHcpK4LDZZn+aoccJqPR/4eluUd2uNnz/bHGP2bso1bvJUn6g8XR87NtRH96V1MfqIuvzq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLeq3/mqM0bnbPKl8xZnDvbd9p7Wkz2IOly8VY+pSeRluUl8FqOsU3PVeGG+8Xut+dw2rsd92CdW7FMf/V5Zmeb/7ps09W/L0pkjl6ZjBizNr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8OeujK9WU66a3qVx87ZzU3nLc5c5I/1RL23/ag9mHWl3pczFGXqU3kZzlKnBarrFdz1rhhv33/xrh3/z75OO+6+jdp0ISv08xvvkzbzFicnjx/URpRXpRf3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfRW9YuHakT1sjNH527mjPMX+VR9ZS9qP2ZPYr2pTcVhNYo8LVeJ12KWdWvuGj8abuqpC+N71TFf5Vpv3vSzNGH0/HTH8nnpDyfMy6/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeqv6fdfgvN2Zszipfnb+5gzKOYy5lNf32Z7UvszeRH1u/anT1CrytZwlbotd1q85bBxpqb/qy1R3x8L0z77z0+LVP07bLv8wfbb0/XTWru/nV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Vf32R2oD5+7OntVNziDFT2dRzmOcSdiXm2P2Z/Yo6nS1qvUoNsnbcpf4LYZZx+ay8aTJOB92y+y01dszU2/Lu2nk+TPS0Fun51f3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvVX9vnPyvYvvHuyf1QzOYZ1FOo9TX4urzibsz+Uc+dfcU6+rWdVt1qf8LYeJ42KZ9WxOG1fafnDoe7nmrd+ekvr95bW0yemv5lf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvVX9zkl89yQ/ihHO4e0lnceqJZzLqa/U284o8v1Ytle1X5OP1e3mpPpNDWO9ymXiuZhmXZvbxpfGEeNfT2Mfejm9cdSLae6E5/Ore8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96qfntD37+pEX0P47sI9bP8aV8lljifU2M4p1F72q/bs4rD9i7ysxpWHWeuyufWr7gutlnf5rhxpvWhvs+lH5T/la65YXx+de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNL7f/FvVLHf8z2cPaP9s+8knMs7m1ZX22PIr86qnNeIOeowe1f7N3sY8VmuUs+paeR1c9h6FuOsc3PdeNP8/FEPpx9/dF9+de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb1e87aOckzs18H+c7KXWT3OF83hm1c1pnleptZ1bObeRf+3c1iX2c2KSeV9OK22ob+V2OE+fNbevdnDfutD9w75j86t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KT3u/H/bv1/F/+/y/+1Xv/Vev1f6/u/Wt//1/r5T62f/9X6+W+tn//X+vc/tf79X61//1vr3//X+u8/av33P7X++69a//1frf/+s9Z//1vrv/+u9d//1/rff9T63//U+t9/1frf/9X633/W+t//fvevtv9d2//ONHD8P9JnCx5KZz4zPo3Z5pl02EET0m1rv5J+n82Zde+anp44+d/pb3PfTzusMict/3JeGnz5gjRr5ufpJwu/SLOfWJJu3GdZWnD7N+m0p1amQ96oi+vvqY81ftsnGldtiF/d1RDrbdgY/S9rjJ/Naox56zXFM/s0xXrHN8VWpxZX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ71V/f7/jU0eTku2fTwdv+CZ1LnjS2nitq+mf777Zvr8h1k+WGtm+uVTH6bNVpmbXv7+p+mRqQvTtXt8kVY9a0n6+uhlqbGyIvXZvi4OH1IfzQf1id22a4gL2hpj2WON8dGBTXH8zKa47r+a48AHmuOi+paYvE1LPHl0SzydreXei4ure8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96qfp+HZ/P7P5tG//Ol9OmcV9PM8W+lkQPfSRsOnZWWbvJROvHueal52oK0/yOLUu/eX6XlNy3Nx+HYLeti9zPrY82/9MnHa+R1jfl4XvrD5th8UnMcNbQljp7eEkN2aI09b2iNH7zXGmP6tcUJW7fFx4Pa4tADi6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3qp+c8Ln4t2sdV9LG28yNZ399jup4Sfvp5WbzkmvTP0kLfrxwlS/xRdp7PwlacKQ5SnOW5laflIfG5zXJ9rub4i9nmuMy8Y3xRE3FJrX62iNtW9tjUkbtMXIq9vi5s/bYuNojx+d2x7n3NMeEydlOTzL4z/4uLi699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466a3qty7MDZ+PNuN630033fh+WmPCnDTiL1ndtO7naeZeX6Z1BixNEx7NxnxEXayzsj5uGVKM9+UPNOVz9vbrs/rziGJ8nxvXFpsPLDT17+6Imw7viH53ZDltRkcc3tgZs9fpjBc374zDflZc3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfRW9YsN1oc54nPS9uBv5qSvGz5Nr437PB3X9lU6orQs/elfK9Ls3mKuW49i1nXDmvP1u+pvWvM5bNwuvbI92tbsiA+u6ohpSzri8j074/KrOmO7iZ3x1pLOOGb1Ujy9eSn+kEpR2bG4uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oumt6hcfxQjrxFzxeekjrm5/9FfpzquWpUnHr0zWm3gsPotNF/e2xD7rF+N9VlaPPn9He4zctSMmTyk0P3tfVt+0lOL7e5biqQtLMfW+Ujw+pRQ3L8jqwPpyvNpRjm27iqt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3qp+OUKcFCusF3PG56bvDv9alue01x+ujyOOaojh8xpj0u7Nedy2Lmfe2BYHDGuPq7Ys5vY7x3bGDu915uN66rWlOG9aKdf4x4HlWPegclx+SjnWv6Accy8vx9+uLcc71xdX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ71V/fKkXCFeihnWjbnj82MjTayPvS5tyPPzg6Oa87g8eUVrvFNXxC7r1fjsOqMzrh9UjJ/xHJvK8fmJ5Tj56nL84h/l+NVT5ThjcrY3nFqO3d8pR7/3yrH4veLq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLeqX60gX8oZ4qbYYf2YQz5Htnbfoohx4rD1t9bO7fGzzYr1HX/tzNfv5DtKMacxm8d7leOSiwoNh7xUaPv1R9kecF62z/mkHGdn19Efl2OVOdn4flRc3Z/97XvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrprepXL6kZ5E25Q/wUQ6wjc8nnyeaOa7Tm+XrESe15fBa3zcmjTsr2ph+U8nEyh195oByXvlGOzWeX4+pMz7C5mbZZ5fhetoc/d0I5rn88G9eszb1jy3HR34ure8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96qfjWjukntkOf3LIeIo2KJ9WRO+VzZlp+PPKsjuvYtYtvVNxTre/Ex5RgxphwvvpLFs9nFOF7xYTlGZtyPP1aO1W8px7EZe//jsjU+qBxDMw1z/6Mc9ZsVV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Vf3qZrWj+kkNIY/KJeKpmGJdmVs+Xz5WHlLksDyeb1yOB35fjuHji3VsvFLGOeyVYlxp6d6vHHUblOOwxaXofbEU195WimUXl2LCsFIsOrm4uvfce+20109/dthL384nfvjjl38ceHDhw4kXN3466Mnrg0wfnfRW9ds7qJ/VkOootYR8KqeIq2KL9WWO+Zz5evJ/S3HClkXcXvBisV6t33OmFXP50oxttR0y7m+y/P5ANj7HZ2tko1LcObczPns4q3dGdcbgMzvj4FOKq3vPvddOe/30Z4c9dtk/+9v4wC//OPDgwocTL278dNBDF3100lvVLz/aQ6ij1ZLqKTWFvCq3iK9ijHVmrvm8+RSfxev95hYxrGNiOZ75czFun2Xx2njuvX0pTnu70NizdmcMm9QRCy7piJ0O6ogbt+6IbTYvru4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056q/rtIe2j7CXU02pKdZXaQn6VY8RZscZ6M+d87nyX5xXrc9OnirlqPZvDX+xbip2ndMYrgwrN6+zTEX+akdXCp7bHknXaY7dZbXHro21x5dji6t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3qt8+2l5SnLSnUFerLdVXagx5Vq4Rb8Uc687c8/ljELcfOb0c16xajh/dXIpplVI+fuPeKsb3zEvbo25BW+wxtC2u/ndrXDqsNQb0b42zl7bEoXNb8qt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666Mvr2ExvVb+zBPtpMcK+Su5QX6sx1VlqDflWzhF3xZ5ff5u3jQOWnsyvGGb9PvrzrBY6vSPeHFuM8/TvZXvcA1pjyp0t8VS5JXa7ojkG/qQ5tlnSFLd90JRf3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300EUfnfRW9VsPzhTsq+0t7a/ET3W2WlO9peaQd+Ue8VcMsg7NReOByXo9bJXOfD1fM6g9H8edj22N804rNHdmtjsam6Lp+MZ4fWlDLHugIYaOLq7uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeqv61YjOVawP+2t7zHz/vWtRz6s51V1qD/lXDhKHxSLr0Zw0LtgmbNiRr+Ox7W353B74fHOU7mqKLa9vzLUOeLJPTO7oE2ddUB8zNquPK/oWV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Vf3O1ZwtOV9xxmC92Gvab4kl4qraU/2lBpGH5SLxWEyyLs1N44Ox49Nirm99QHM+nsb3yv/uE5/tUR8n7lIXA2atSM/d/E3qN3V5Ounq5fnVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfnZTKaT3qp+Z4vyY37GlNVNzhrst60f+y57D/W3GlQdphaRj+UkcVlssj7NUeOE1XqujG2MFzZoiM9vro/D+tcFjZMGLks3bv11Wv7oV+mal79M/zj7y/zq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLeq3/mqM0bnbPKl8xZnDvbd9p7Wkz2IOly8VY+pSeRluUl8FqOsU3PVeGHeb1GffG4b151HLEsnD16S/njhF+mwjRel3Q9dmD79z8/S9JkL8qt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx66cn2ZTnqr+tXHzlnNDedtzpzkT7WE/bc9qH2Y9aUeF3PUZWoT+VmOEqfFKuvVnDVu2McfviJNOXxpPs7jJ3+enh2+IO13/yfp3ivmpi0GfpwWPz0nv7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpreoXD9WI6mVnjs7dzBnnL/Kp+spe1H7MnsR6U5uKw2oUeVquEq/FLOvW3DV+NPx96hf5+LasOT/XWvnww7Tdvu+n1e+bmZatNzO/uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oumt6vddg/N2Z87ipPrZ+ZszKOcw5lJe388q9mX2Jupz60+dplaRr+UscVvssn7NYeNIy6m7fJL+e8hHacCEWenYV95N48bNSEfeOy0tWDE1v7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpreq3P1IbOHd39qxucgYpfjqLch7jTMK+3ByzP7NHUaerVa1HsUnelrvEbzHMOjaXjSdNxnnK/m+nsaPeShc99nqqH/hqenXwK/nVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Vv++cfO/iuwf7ZzWDc1hnkc7j1NfiqrMJ+3M5R/4199TralZ1m/Upf8th4rhYZj2b08aVtj/3ezPX/I9RL6WL93sh3bLJ8/nVvefea6e9fvqzw16eOzP7/PDHL/848ODChxMvbvx00EMXfXTSW9XvnMR3T/KjGOEc3l7Seaxawrmc+kq97Ywi349le1X7NflY3W5Oqt/yGiZbr3KZeC6mWdfmtvGlcfEpL6b1jn867bLGE2noiPH51b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvVb+9oe/f1Ii+h/FdhPpZ/rSvEkucz6kxnNOoPe3X7VnFYXsX+VkNq44zV+Vz61dcF9usb3PcONPaf8Kj6c9PP5ja9rk/v7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrp/b/4d3yx3/M9nD2j/bPvJJzLO5tWV9tjyK/OqpzXiDnqMHtX+zd7GPFZrlLPqWnkdXPYehbjrHNz3XjTvPUa96Rbb7ozv7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjqf/v/+/sV30M5JnJv5Ps53UuomucP5vDNq57TOKtXbzqyc28i/9u9qEvs4sUk9r6YVt9U28rscJ86b29a7OW/cad/o6NH51b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300Unvd+P/3fr/Lv5/l/9rvf6r9fq/1vd/tb7/r/Xzn1o//6v1899aP/+v9e9/av37v1r//rfWv/+v9d9/1Prvf2r991+1/vu/Wv/9Z63//rfWf/9d67//r/W//6j1v/+p9b//qvW//6v1v/+s9b//reV//w+R/mH3eJzt24mblMW1x/GZbmZpMeKWEDUJLjFqjHEhMXHhngoG9WrQuMYrxgX3qEhco0QiqHFJzFVU4lVRMai44AKuKIKiiAgKqICCIsgi+87AADO3P+9L/xUNz+Pz+r5v1TnfX1fVOaeqe2pqtvxrOmNAPLBsUJy4+7Mxfe3QKF3+eoz+58ioO/G9+GbUuLhp+sex66BPo+0PP49BR38ZZ+4zK15/e04cseO3ce8ei+KolUtiY9/l8ftpK2Pa6tXxg+lro9+d6+Lf2zTHxWdviA63bIwnem+KV45riYlrW+JXf2mNhZ+0xol1NemSbWqSq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pein7//8uzn43Tew6Lj3YbHj2uGRU/7zkmdt5ufJzcbVK0P2FKDFv9RfQ7embsf/w3sXPb+bGu98L4y5Al0e3/lscFXVbFBS+tiZ8saIoe89bH2uc2xMVdNsUfnm2J/ee1xkPX16T3l9SkO7rWplcfqE01U2rTytbatPr7hdR5t0J2de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0e/z8Oz4Pw6P1oWj4ve7vB+dl4+PpZdMjtvumhrdzp4RE7/8Oi5sOzdeW/RtXNlncZz58bJsHA56ZE00/WRdPHRFczZefz2nJRvXEW/XpO4X1qb7mmrTv68spH/OKKTeHYvp1OuKaepzxTTg02LaeVEx3b0yv7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpreg3J3wu3h1x8vtxx1kT4qttPonzzpsW53T/Mg5qOztOOWdedL9wYbTbdWn87F8rYuBLq+Py/k3x9MHNcdX9G6PlrZYYeWpNurdjrvmkQYX0+8OKqXVEMb21X5s08R9t0umft0lntK9Lzx1ZlzZdWJfG9KpLp/bNr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056K/qtC3PD56PNDl0+iZYJ0+KGjV/G7Mmz4+GT5scRfRZFnzOXxb5LV8aBHdfGwAPWx6AFG7LxHtU1n9tTDiykP24sZOPYdEyb1H1ym0xTt6fq0vgNdenIqE+zr6pP9wyoT+2H16fmcfXp7on51b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvRb/YYH2YIz4nbUfs/1WcfvA30XHO/Bh32OJ4L5bH4pWr4uenNWVzz3oUs8Zum6/jo9vkc9i4jdi3Lh36Sl367n71aat+9WnUvPo08mcNqcf5DamxX0O6f1hDWjWuIb02rSH99sv86t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ol98FCOsE3PF56WPuPrY/YujNGZ5HPDI6my9icfis9j02jO1qc+b+Xg/e3ObtC7KjLPrUs3luea1xzak2x9tSL+b15BW/qgxlX7XmJb1aEwTb2tMDz3QmAqPN6bLBudX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70V/XKEOClWWC/mjM9N30Erl2c5p+GxdbH3pg1xVqk1WufUZHHbutz+l23S7dvWpXc/rsvm7jaFhnTVdfm4PrF/Yxp2Ra7x9cmN6cRVjWlU21I66YeltPM+pTR9/1La5qD86t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ol+elCvESzHDujF3fH5sfD1uXWw6ZmOWn+f9NI/nNfcX0zYPtMlil/VqfK6/qiGNXdiQjZ/xnDmtMe1RKqXH9iula44upbu6ldJTF5VSpz+X0l+vKaUjryulPa/Pr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056K/rVCvKlnCFuih3Wjznkc2Rr3UOtIeaIw9bfcV+3SRd9UJetzz8fnq/fmtSYdnq4PI/nN6Y3O+Qa7jwv1/bgjaXU/pZS+qb835CbS+mjvmXdfUpp6I351b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvRb96Sc0gb8od4qcYYh2ZSz5PNq9+qZDl6xdLeTwXt83J/qXGNOOGfLzN4drjSmnEZaXU/W+l9F5Zy+Cbytp6ldLRPUvp+XNL6YM/lMe13GbWUaX0apf86t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ol/NqG5SO8ifcog4KpZYT+aUz5Vt+fneHetTLK7PYtN7HfN1umdtKb2YSmn9xaVUuDEfx7d7l9JbZe5lp5TSsYeW0v1l9tMLpTRyYWP637KGXSY0poM+yK/uPfdeO+31058d9thlnx/++OUfBx5c+HDixY2fDnrooo9Oeiv61c1qR/WTGkIelUvEUzHFujK3fL58HNBUn+WqLJ6/05jm7FxKL5yar2Pj1bM8Xk9enI8rLWlpYzpwRGPqd2djOqJ7YxrTqTH9bPfG1LxtY/px2/zq3nPvtdNeP/3ZYa/n5vnED3/88o8DDy58OPHixp/lq7Ieuuijk96KfnsH9bMaUh2llsjy6fV5nBdbrC9zzOfM18qTGtOAjxuzuL3rufl6tX6HXJHP5RFltv+e0ZgG929Mr3Yta6krz9lRDenzmxrSric0pPb7NqRbd2hId7bNr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056K/rlR3sIdbRaUj2lppBX5RbxVYyxzsw1nzef4rN4/feb8hh22PmltOagfLx3ezAfzxu/aEhPXp1r/M1r9WnwhfVp1z3r07WryjHk07p02bj86t5z77XTXj/92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ot8e0j7KXkI9raZUV6kt5Fc5RpwVa6w3c87nznfcnK/Ps7vlc916Nod/sqQhXdujIdUuyjWdsKAuvXFVubbfpi7tM7xN6tWrTfrk5DZp9FH51b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvRb99tL2kOGlPoa5WW6qv1BjyrFwj3oo51p255/PHIG4v3K68Nl9oTGf8ujwOQ/Lxnt2zPhvfp/esSwfc1ibd0FRM7/6lmN7ctlzHjy6kIfcU0l035Vf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvRX9zhLsp8UI+yq5Q32txlRnqTXkWzlH3BV7Htyct40Dlt8MzmOY9bt4UrkW2q4+NRxdl41r26HFtNPKQqrvXEirnqxNvfapTX/6sCZdendN+vSG/Orec++1014//dlhj132+eGPX/5x4MGFDyde3PjpoIcu+uikt6LfenCmYF9tb2l/JX6qs9Wa6i01h7wr94i/YpB1aC4aD0zW693P12fr+b2F+Xj/pbaYhrUrZNo6lW1fe2hrXLpVSzTsvSn+8NDG+NFlG7Ore8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96KfjWicxXrw/7aHjPbx5X3GuptNae6S+0h/8pB4rBYZD2ak8YFW/PIfH1/9Z9iNrf/dHZZd+eaNKF7S6Z1+EvNUXPM+jjtsHXR/t6m6NOtKbu699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466a3od67mbMn5ijMG68Ve035LLBFX1Z7qLzWIPCwXicdiknVpbhofjIfdWszm8KUr8vE2vrfs0hydxzdFGr0m7txxdew9eWVcu9XKmDxmRXZ177n32mmvXzZfynbYY5d9fvjjl38ceHDhw4kXN/5s37hvHgfoo5Pein5ni/KjMyZ1k7MG+23rx77L3kP9rQZVh6lF5GM5SVwWm6xPc9Q4YbWeb/tXS6zotSGOvGJd7HHrmkzj/pcuj42XLY0/Ll0ca1sXxbbPL8qu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnor+p2vOmN0ziZfOm9x5mDfbe9pPdmDqMPFW/WYmkRelpvEZzHKOjVXjRfmrXfakM1t4zp46PKYfPuSWPjqwnin27cxpN+8OP7quXH4jnOzq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHroyfWWd9Fb0q4+ds5obztucOcmfagn7b3tQ+zDrSz0u5qjL1CbysxwlTotV1qs5a9ywd7hvVRx837JsnDsUvo29hs6JV+bOiu1Hz4z+l3wVp6z9Mru699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466a3oz85Tr8/rfGeOzt3MGecv8qn6yl7UfsyexHpTm4rDahR5Wq4Sr8Us69bcNX40fKftomx8Lzp+dqb1qvbT45FbpkWvOVPijFOmZFf3nnuvnfb66c8Oe+yyzw9//PKPI6uxy1z4cOLFjZ8Oeuiij056K/p91+C83ZmzOKl+dv7mDMo5jLmU1fe98n1Ztjcp1+fWnzpNrSJfy1nitthl/ZrDxpGWKdfPis/unBF3bpwa4wqfxvZzJsaY2R/FCQd+lF3de+69dtrrpz877LHLPj/88cs/Djy48OHEixs/HfTQRR+d9Fb02x+pDZy7O3tWNzmDFD+dRTmPcSZhX26O2Z/Zo6jT1arWo9gkb8td4rcYZh2by8aTJuP8q9snRbt3x8f8pWPj3EvGRMc73suu7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnor+n3n5HsX3z3YP6sZnMM6i3Qep74WV51N2J/LOfKvuadeV7Oq26xP+VsOE8fFMuvZnDautG046sNM87bvjoqFt46I4tlvZlf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100Udn9j3U5n/OSXz3JD+KEc7h7SWdx6olnMupr9TbzijEW3tV+zX5WN1uTqrf1DDWq1wmnotp1rW5bXxpPPXxt+Kmh1+Lp7u+FKOGDs2u7j33Xjvt9dOfHfbYZZ8f/vjlHwceXPhw4sWNnw566KKPTnor+u0Nff+mRvQ9jO8i1M/yp32VWOJ8To3hnEbtab9uzyoO27vIz2pYdZy5Kp9bv+K62GZ9m+PGmdZ/bnwhNqx5Oi7pOzi7uvfce+20z/qV+7PDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NGZfd+4+Z96yP7I93D2jPbPvpNwLu9sWl1tjyG/OqtyXiPmqMPsXe3f7GHEZ7lKPaemkdfNYetZjLPOzXXjTfOAro9F8eMB2dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0W/76Cdkzg3832c76TUTXKH83ln1M5pnVWqt51ZObeRf+3f1ST2cWKTel5NK26rbeR3OU6cN7etd3PeuNN++/39sqt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx66Mn0r83OhLeO/Zf1vif9b8n+113/VXv9X+/6v2vf/1X7+U+3nf9V+/lvt5//V/v1PtX//V+3f/1b79//V/vuPav/9T7X//qvaf/9X7b//rPbf/1b777+r/ff/1f73H9X+9z/V/vdf1f73f9X+95/V/ve/W/5V978jt+4XCx7+vxg46dH46RNPxmk/ei62P/ylOLl2eBzUc2RMvuPduPykD+L08RNiVdOkGDLts2j8c3nPMHpGfDN5ZvQfODuOPWhuPHLr/Nhr0IK45I5FMevwJTFvRHmv0W559PjFiliz38qoa1kZHZ5YFX/fa3UcctPqWP366thzfH5177n32mmvn/7ssMcu+/zwxy//OPDgwocTL278dNBDF3100lvR7/9vbjcwnuowOH446bkYsefLcUOHN+LiUaPiP1uNiQtqPozioI9jVtMn0btlalw8fHoc9fOZ8f4Zs+Ppo+fGy6vnx8vnLoyry7XGiPuWRpczl8frq1fEE91WRe/7Vsdtg9bEV3esjXOPaYoRc5ri+G7rouPz6+LgWevi0xX51b3n3munvX76s8Meu+zzwx+//OPAgwsfTry48dNBD1300UlvRb/Pw7OHt34+jnvg5Xhk3Btx9yNvR6dyvJwa4+OZdpOiw52fxSvDP4+GB7+Mdw6YFUN6z8nGoc9xC+PIKYtj0W7LsvH6cNtV2Xh+cNDa2KF3U1z/8Lr4a//1ccFFzXHs9zbEhoEbomXrjXHHaRujbzmO97w3v7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpreg3J3wu3vUrvhmfb/NO7PfW+zFshwnxwnaTo8/wKTFo2+nx4vdmlvPM7OjVaV4sP3dBvNVlcayfuzRGH7UiuvZYFRPOWBPXfi/XvKZpfSzdc0N03XdjfLjVplg+ZlMUu7dEw1ct8czhrXH031pj+hOt0Twsv7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrprei3LswNn4823Ve/H11vmBDjn5kcB/adGksKM6LfAV/HxO/MiesHzI8bZy+M5TOXxKr7l2fj8dExa7I527z9+igNbs7GsVM5X+1wcq61UFuTrjqpJi3pX86B42vSoWtq0lvb1aaBu9emQ/bOr+4991477fXTnx322GWfH/745R8HHlz4cOLFjZ8Oeuiij056K/rFBuvDHPE5afudmZPj6blTo+89M2KnRV/HjsvmxGGPfRs3NORz3XoUs2ZOXJut35mvNmdz2LiNrW+NWd+pSW/eXJOGLqhJF/5Xbbrw5tr0vRG16fkFtSnaFtKDuxfSWQcU0uJf5Ff3nnuvnfb66c8Oe+yyzw9/md+yfxx4cOHDiRc3fjrooYs+Oumt6BcfxQjrxFzxeekjrq48alacdvXc+NtxC7L1Jh6Lz2LTqPp1ccLe+Xg/8dqmiP1b4/xDa9KTY3LNA/5Tmw5cV65zOhXSA9cU0gv/KaT+Ywrp2q8L6YiVhfR0uZb/bk0xu7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrpreiXI8RJscJ6MWd8bvquGjg3yzmnnrAkrn12eTz32qroet3aLG5bl1futCnOntgSl+yVz92X/6c27TKpNhvXrrcW0unjco3n/LSY1h9TTBeeW0wbriqmd24spr63FtPLt+dX9557r532+unPDnvsss8Pf/zyjwMPLnw48eLGTwc9dNFHJ70V/fKkXCFeihnWjbnj82PjgOuXxO+al2f5eZe6piwuHzdzQ/zpm01Z7LJejc/u42tTz875+BnPWw8opnFnF9N/31JMP3q0mH49rJhOGFVMc8YW0x4fFdOSicU0flJ+de+599ppr5/+7LDHLvv88Mcv/zjw4MKHEy9u/HTQQxd9dNJb0a9WkC/lDHFT7LB+zCGfI1tH/W51FnPEYetv8eUt0W63fH1//97abP0O/nchvb2mPI+jmM69NtfwyzdzbZ2nlfeBM4rpH18W08nl6zVfFNPS8rPTp+VX9ydvfq+d9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Ff3qJTWDvCl3iJ9iiHVkLvk82Tzk+81Zvn7xnfLesxyfxW1zstM5hXTTZ/l4m8ODHy+m894rptLUYrq0rOf46WVtnxTT8veL6Q9vFNOfny+Pa7nNbY8U05kD8qt7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3op+NaO6Se0gf8oh4qhYYj2ZUz5XtuXnwy+tSfOPyGPbpf/I1/f404rptPuLaeDb5Xg2NR/Hi6YU0/ll7v5DimnV3cWUri6mwunlNd65mH5V1vDOj4vpi13zq3vPvddOe/30Z4c9dtnnhz9++ceBBxc+nHhx46eDHrroo5Pein51s9pR/aSGkEflEvFUTLGuzC2fLx/TjstzmPX4753Le/UryuP2XL6OjVf7Mufxb+fjSsu3XYrp8x2L6ZC5hbTw9ULqcV8hfXJdIT12USGN755f3XvuvXba66c/O+y13zyf+OGPX/5x4MGFDyde3PjpoIcu+uikt6Lf3kH9rIZUR6kl5FM5RVwVW6wvc8znzNcDzxRSl73yuD12eL5erd9TxuVz+bwy24qOZe6l5fz+eCF9e0Z5jZT3+X2m16axg2vTyL61af9LatMvz82v7j33Xjvt9dOfHfbYZf/kzfGBX/5x4MGFDyde3PjpoIcu+uikt6JffrSHUEerJdVTagp5VW4RX8UY68xc83nzKT6L1/tNz2PYN+VYNeCOfNzGrszHc6+Dyhom5BoXtKtNvx9Zk97/a03qcGxNuvJnNWnH3fOre8+91057/fRnhz122eeHP375x4EHFz6ceHHjp4Meuuijk96KfntI+yh7CfW0mlJdpbaQX+UYcVassd7MOZ873/Om5+uzcVg+V61nc/ij3xZShzG16anOueZ1qSa9d39rtBnfEgP3aIkuT2yKNadtis/+K7+699x77bTXT3922GOXfX7443fe5s8ADy58OPHixk8HPXTRRye9Ff320faS4qQ9hbpabam+UmPIs3KNeCvmWHfm3n6btYvbd19cTJc1FlPxrkJ6sU0hG787xubjO6i5JV4YuSmOfnhjTHt0Q4yd2Bylg5vjqSnlmPrK+uzq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLei31mC/bQYYV8ld6iv1ZjqLLWGfCvniLtiT+fNeds4YFnQmscw6/fefcq10MU16Y/Rmo3r+TuU97j3NEe3juvjF61N0aXQFHscuTb2nrwm1j29Jru699x77bTXT3922GOXfX7445d/HHhw4cOJFzd+Ouihiz466a3otx6cKdhX21vaX4mf6my1pnpLzSHvyj3irxhkHZqLxgOT9XpIQ222nr+4uSUbxxjaHC9/tC7T9kG3NTFmwap44/WVcerUFfHEsSvish/kV/eee6+d9vrpzw577LLPD3/88o8DDy58OPHixp/pKOuhiz466a3oVyM6V7E+7K/tMe2z7DXU22pOdZfaQ/6Vg8Rhsch6NCeNC7bH2pfjZHkdb920IZvbu/dsinEd18QPtl+VaS2dtyyOX78knly4OC44YnFM3Cq/uvfce+20109/dthjl31++OOXfxx4cOHDiRc3fjrooYs+Oumt6Heu5mzJ+YozBuvFXtN+SywRV9We6i81iDwsF4nHYpJ1aW4aH4xjRmzI5vBe/dZm42l8Px23NO7utTjuumJhzBj9bVzbZ36MeX1e7HbNvOzq3nPvtdNeP/3ZYY9d9vnhj1/+ceDBhQ8nXtz46aCHLvropLei39mi/OiMSd3krMF+2/qx77L3UH+rQdVhahH5WE4Sl8Um69McNU5YreepnVZF2nd53Lfbkrji4IWZxt67zI1jf/hNPDtgVnR5/us468yvs6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3op+56vOGJ2zyZfOW5w52Hfbe1pP9iDqcPFWPaYmkZflJvFZjLJOzVXjhbnb2GXZ3DauTefMjd0OmR2HXDgztmv7ZWz8zfR4+MdfxD9Gf55d3XvuvXba66c/O+yxyz4//PHLPw48uPDhxIsbPx300JXpK+ukt6Jffeyc1dxw3ubMSf5US9h/24Pah1lf6nExR12mNpGf5ShxWqyyXs1Z44a9x2+/jVt+Oycb5x4vzohrzvk86u6bEudc8WnM3fmTGPT45Ozq3nPvtdNeP/3ZYY9d9vnhj1/+sxqrzIMLH068uPHTQQ9d9NFJb0W/eKhGVC87c3TuZs44f5FP1Vf2ovZj9iTWW1abTshrG3larhKvxSzr1tw1fjScMXxmNr6vtUzJtI4e83Es+8WEGHfPh/FMmw+zq3vPvddOe/30Z4c9dtnnhz9++ceR1dhdcj6ceHHjp4Meuuijk96Kft81OG935ixOqp+dvzmDcg5jLmX1/Sf5vizbm5yRr3t1mlpFvpazxG2xy/o1h40jLT/56ZT4cadJMeOZ8bHT0LFxzj3vxXf7jY5Hv34nu7r33HvttNdPf3bYY5d9fvjjl38ceHDhw4kXN3466KGLPjrprei3P1IbOHd39qxucgYpfjqLch7jTMK+3ByzP7NHUaerVa1HsUnelrvEbzHMOjaXjSdNxvnvvx4TZ135dhz88IgYuvMb0feQ4dnVvefea6e9fvqzwx677PPDH7/848CDCx9OvLjx00EPXfTRSW9Fv++cfO/iuwf7ZzWDc1hnkc7j1NfiqrMJ+3M5R/4199TralZ1m/Upf8th4rhYZj2b08aVtmOaRmaaz7ry5TjkV0PjxHYvZlf3nnuvnfb66c8Oe+yyzw9//PKPAw8ufDjx4sZPBz100UcnvRX9zkl89yQ/ihHO4e0lnceqJZzLqa/U284osv1Yea9qvyYfq9vNSfWbGsZ6lcvEczHNuja3jS+Nj588LCZ3HRLrNw6Odt2fzK7uPc8/m7ez9vrpzw577LLPD3/88o8DDy58OPHixk8HPXTRRye9Ff32hr5/UyP6HsZ3Eepn+dO+SixxPqfGcE6j9rRft2cVh+1d5Gc1rDrOXJXPrV9xXWyzvvM5/nKmdfozj8cxjz8Sww8ckF3de+69dtpn/cr92WGPXfb54Y9f/nHgwYUPJ17c+Omghy766KS3ol89ZH/kezh7Rvtn30k4l3c2ra62x5BfnVU5rxFz1GH2rvZv9jDis1ylnlPTyOvmsPUsxlnn5rrxpnnxxv5xYu9+2dW9595rp71++rPDHrvs88Mfv/zjwIMLH068uPHTQQ9d9NFJb0W/76Cdkzg3832c76TUTXKH83ln1M5pnVWqt51ZObeRf+3f1ST2cWKTel5NK26rbeR3OU6cN7etd3PeuNM+7ahbs6t7z73XTnv99GeHPXbZ54c/fvnHgQcXPpx4ceOngx666KOT3i3jv2X9b4n/W/J/tdd/1V7/V/v+r9r3/9V+/lPt53/Vfv5b7ef/1f79T7V//1ft3/9W+/f/1f77j2r//U+1//6r2n//V+2//6z23/9W+++/q/33/9X+9x/V/vc/1f73X9X+93/V/vef1f73v9X87/8BBOHtvnic7dvpl5XVlQZwF06JqNEkQlwa41J0OaFoQjDSZicYYwZaScfEjhCJRjtOTUtjo41mSSQqKhJjtO2EQcEWAhoILQYBBQoDKrMMylBAATVAURQ1UcO9jfT9nZvb/0J/uO/58vKes4fnueecvfc5b3HUUVkbc+Xjcf7KZ2LH4efjF+snxAvfmxrX3/nH+G3vWTFs4htx8ry34vVH3okXOyuiT6/lse+YFXHtpNWx9uC6+NKRDbFh9Ucx9pYtUfnGtvj5uu0x/S8744t37Ypz6ndHW//qeO1nNXHFzbUx4KK6GLK+Lpb+cG+MnL03+tTujds6i0/v+o2TI0+PPjvsscs+P/zxyz8c8MAFH5zwwg0/HvjghR+e+Jb4+3dF/xei5vsT4yeHp0bXD2bEgu/Pjlcb5sbOry6IqZcsjlj3bpzV6/1YcNGqmFa7NsbcvCGOefKjqL1/S7ScXRktv9kR//3Xquiq2B2PPlUdh86ujT1P1MWCir2xbN2+OGNefbw8cn/kTmmIcU80xH3bGmLESQfi1LMOpKd3/cbJkadHnx322GWfH/745R8OeOCCD0544YYfD3zwwg9PfEv8/R76tl75Sjy1fEZUts+ONSvfjFHXLYzP3bsk6voviyHzP4jW2tXxrfc+jCNDNkX9zM1pHt4ZtSPGdNsVF1y/J83Xp66uS/N57ND6GDRzf8xb0RDzlx6IV55vjLEDDsaVaw7GVV9riuW/borFc5riT0uKT+/6jZMjT48+O+yxyz4//PHLPxzwwAUfnPDCDT8e+OCFH574lvhbE34XY2sv+3P06P+XuLt+YTRFRTRcvTzeqV0RVX+3NhoHbIgJ+Y/irbu2Ru9nt0d+eFX0/eyeOOr+mnjqD3VxwpP7Yu6AIufLezXGxf9wMJ68qSmO79ccl7Q2xzfGt8Q1J7RG3Z2t8dhrrdFzQ2v0qyo+ves3To48PfrssMcu+/zwxy//cMADF3xwwgs3/Hjggxd+eOJb4m9fWBt+HzKTz347npxREZ/evDyGzVoZF122LtYM2RgnXbU55n1QmPOTd0bvE3dHn2XVaT66P7Avrdl+cSCu3diY5vHfhzfHDaNbEqdvXtoWvTa0Rc+bD8WgZYdiTo/2mH9De4we3h6zHy4+ves3To48PfrssMcu+/zwx29abwUc8MAFH5zwwg0/HvjghR+e+Jb4iw32hzXidyL73e7vRe2pq2Lx4nXx456bYtAZW+LBNZWx4MtVae3Zj2LWmYfr0/49s7oxrWHzduyXW+PIdW3xZt9D8fKRQ9F9Rnt079sRd07riAlHOmLhgM4YMbwz8uM64/P/UXx612+cHHl69Nlhj132+eEv+S34hwMeuOCDE1644ccDH7zwwxPfEn/xUYywT6wVvxcdcfXS+zfFC1O2xMJR29N+E4/FZ7Hp8BUN8cyNxfneU90cDw1ujeOmtMXT84ucHxjREZPP7Ixef+yM4ad3xaQRXXHX/K645EBXvHtKLp49Lxe/uKT49K7fODny9Oizwx677PPDH7/8wwEPXPDBCS/c8OOBD1744Ylvib8cIU6KFfaLNeN3o9tnzZaUc5775a54Y3N17K+ui6derU9x277887XNMel/WuJzD7WltTt9Z3uMWNOR5vXjq7qieUlX4vjJ6FxctjQX3VtzcUWPfFT0zseA/vmYHsWnd/3GyZGnR58d9thlnx/++OUfDnjggg9OeOGGHw988MIPT3xL/OVJuUK8FDPsG2vH78fGvdN2xdjza1J+/sfL96e4/HT3ppj+mZYUu+xX8zP6rx3xxTmdaf7M5/eeycWHB3Kx9qv5ePDefLz+XD52vp6P4xblY8z7+ei5Jh+b1haf3vUbJ0eeHn122GOXfX7445d/OOCBCz444YUbfjzwwQs/PPEt8VcryJdyhrgpdtg/1pDfka0xD+5NMUcctv8umNgSg4e1pf057MaOtH/HDe6KRT1zcfFruTj6jCKHaf9V5FZRmY8FNfn4YV0+aqrzccGufHxhRz6aKotP7/qNkyNPjz477LHLPj/88cs/HPDABR+c8MINPx744IUfnviW+KuX1Azyptwhfooh9pG15Pdkc+S3GlO+bmxsSfFZ3LYm5zV2xjUfd6V5soafeSAfx87Lx6Ct+TitwGfb7gK39fk48+181E/Nx1lP52N9Qeb6e/LR9U/Fp3f9xsmRp0efHfbYZZ8f/vjlHw544IIPTnjhhh8PfPDCD098S/zVjOomtYP8KYeIo2KJ/WRN+V3Zlp/nHlNY73PbU2w67Zri/t60PRcHhuTjkdn5eHZbcR5P2pKP4wu4734iH+cMysfbBezfqMrFCXNyMXNcLir+LRdN/1x8etdvnBx5evTZYY9d9vnhj1/+4YAHLvjghBdu+PHABy/88MS3xF/drHZUP6kh5FG5RDwVU+wra8vvy0fDB+0pV9mPd9+aixtPzcf+scV9bL7uKczX1lnFecXlxLdy0fijXMxu74pTX+6KL9zUFXu+1BWjD3fGxubO9PSu3zg58vTos8PePX9bT/zwxy//cMADF3xwwgs3/Hjggxd+eOJb4u/soH5WQ6qj1BLyqZwiroot9pc15nfm674xXbF8VC7F7VVTivvV/q1bUlzLxxawnfV8Lio/lYvcyK44sbawR37aGV/f0xGrftkRCy4v1ALdOmJaS3t6etdvnBx5evTZYY9d9vnhL/kt+IcDHrjggxNeuOHHAx+88MMT3xJ/+dEZQh2tllRPqSnkVblFfBVj7DNrze/Np/gsXr+4uxjDjp6Wj5HfLM736lNzaT7HPdsZW5cXuZ48sD0qZx6KlecdilFL2+Lcx9rijvuKT+/6jZMjT48+O+yxyz4//PHLPxzwwAUfnPDCDT8e6bxZ4IUfnviW+DtDOkc5S6in1ZTqKrWF/CrHiLNijf1mzfnd+f50dXF/DnyuuNbtZ2t485ud8dBbHTF+TpFz71lt0W1ZoRbubIkdN7TEr9Y3xxWPNcdn7yk+ves3To48PfrssMcu+/zwxy//cMADF3xwwgs3/Hjggxd+eOJb4u8c7SwpTjpTqKvVluorNYY8K9eIt2KOfWft+f1hELeHHslFj6sLcXlgV7zUtzPN36B3DqX5rTq/NRr2N8djK5ritNUH45jDjfHt2xqjpltjvL77QHp612+cHHl69Nlhj132+eGPX/7hgAcu+OCEF2748cAHL/zwxLfE312C87QY4Vwld6iv1ZjqLLWGfCvniLtiT8Xf8rZ5gOXkgl8xzP69/ZFCLfRJW/zhntY0r1MKVeeixY3xn0MPxPCLG+LRS/fH0H+tj9s/2Rdf+XhfenrXb5wceXr02WGPXfb54Y9f/uGABy744IQXbvjxwAcv/PDEt8TffnCn4FztbOl8JX6qs9Wa6i01h7wr94i/YpB9aC2aD5js11lXtaf93GNOS5rHh3c0RnOuyPm4sfvi6B57o7OmNp47ujZ2P1ATM79Tk57e9RsnR54efXbYY5d9fvjjl3844IELPjjhhRt+PPDBCz888S3xVyO6V7E/nK+dMZ2znDXU22pOdZfaQ/6Vg8Rhsch+tCbNC2y/GtyW9vF3zm1Ka3voxP1x/M/2xU++Xpe4Xvvsnhh33u6o7rErXvmXqjipX1V6etdvnBx5evTZYS/FjcHF3yDliYHFeAAHPHDBBye8cMOPBz544YcnviX+7tXcLblfccdgvzhrOm+JJeKq2lP9pQaRh+Ui8VhMsi+tTfMDY7f6g2kN376oPs2n+T2lY3esnl4VqybviNObKmPun7bF0bVb45apW9PTu37j5MjTo88Oe+yyzw9//PIPBzxwwQcnvHDDjwc+eOGHJ74l/u4W5Ud3TOomdw3O2/aPc5ezh/pbDaoOU4vIx3KSuCw22Z/WqHmC1X7+/N118fBN1bH+73fF7Nt2JI4Lr9sSY7/7cez7YFM8um1jTHxqY3p612+cHHl69Nlhj132+eGPX/7hgAcu+OCEF2748cAHL/zwxLfE3/2qO0b3bPKl+xZ3Ds7dzp72kzOIOly8VY+pSeRluUl8FqPsU2vVfMH8YtuetLbN61fGb4lb7vgoRv5uQ1zf78P42rC1sW3Qmnj/4Or09K7fODny9Oizwx677PPDH7/8wwEPXPDBCS/c8OOBD1744YlviX+qjyuKa999mzsn+VMt4fztDOocZn+px8UcdZnaRH6Wo8Rpscp+tWbNG+wz76uMpfdtTvM8c/u6eOOZ1TGgYkVMmvx+nHPde7Hrw+Xp6V2/cXLk6dFnhz122eeHP375hwMeuOCDE1644ccDH7zwwxPfEn/xUI2oXnbn6N7NmnH/Ip+qr5xFncecSew3tak4rEaRp+Uq8VrMsm+tXfOHw+9rN6T5bbtoZeJ6VOu7cfGtFXH8ksWxt8/i9PSu3zg58vTos8Meu+zzwx+//MORauwCLvjghBdu+PHABy/88MS3xN+3Bvft7pzFyVTn9+1Id1DuYaylVN+vL57L0tmktrjv1WlqFflazhK3xS771xo2j7jc9uMVcetdy+L0zUviRzvejsmL58cPFs2L7SfOS0/v+o2TI0+PPjvsscs+P/zxyz8c8MAFH5zwwg0/HvjghR+e+Jb4Ox+pDdy7u3tWN7mDFD/dRbmPcSfhXG6NOZ85o6jT1ar2o9gkb8td4rcYZh9by+YTJ/O89PYFMeGlN2PEijnR+O3ZsfiOWenpXb9xcuTp0WeHPXbZ54c/fvmHAx644IMTXrjhxwMfvPDDE98Sf9+cfHfx7cH5Wc3gHtZdpPs49bW46m7C+VzOkX+tPfW6mlXdZn/K33KYOC6W2c/WtHnF7fFz5ybOE1+aESN/Pi3G9381Pb3rN06OPD367LDHLvv88Mcv/3DAAxd8cMILN/x44IMXfnjiW+LvnsS3J/lRjHAP7yzpPlYt4V5OfaXedkch3jqrOq/Jx+p2a1L9poaxX+Uy8VxMs6+tbfOL467R0+Mzo6ZE3wsnxcDxE9LTu37j5MjTo88Oe+yyzw9//PIPBzxwwQcnvHDDjwc+eOGHJ74l/s6Gvr+pEX2H8S1C/Sx/OleJJe7n1BjuadSezuvOrOKws4v8rIZVx1mr8rn9K66Lbfa3NW6ece25+ffx+Ie/i/af/jY9ves3To48PfrssMcu+/zwxy//cMADF3xwwpvuHXsX6wB88MIPT3z/7/6rUA85H/kO58zo/OybhHt5d9PqamcM+dVdlfsaMUcd5uzq/OYMIz7LVeo5NY28bg3bz2KcfW6tm2+cL7jw6fjNzMfT07t+4+TI06PPDnvsss8Pf/zyD0eqwQq44IMTXrjhxwMfvPDDE98Sf9+g3ZO4N/M9zjcpdZPc4X7eHbV7WneV6m13Vu5t5F/ndzWJc5zYpJ5X04rbahv5XY4T561t+92aN++4X7h3ZHp612+cHHl69Nlhj132+eGPX/7hgAcu+BLOyiJu+PHABy/88MQ3m/9s/2fxP8v/5V7/lXv9X+7nv3I//5f7/U+53/+V+/1vud//l/v3n3L//lfu33/L/ft/uf/9R7n//U+5//1Xuf/9X7n//We5//1vuf/9d7n//X+5//+Pcv//P+X+/7/K/f//lfv//yz3//+btaxlLWtZy1rWspa1rGUta1nLWtaylrWsZS1rWcta1rKWtaxlLWtZy1rWspa1rGUta1nLWtaylrWsZS1rWcta1rKWtaxlLWtZy1rWspa1rGUta1nLWtay9v/Z/hclUV/1 + + diff --git a/Documentation/doc/Documentation/Third_party.txt b/Documentation/doc/Documentation/Third_party.txt index c039fbdd641..cbdd0d5105c 100644 --- a/Documentation/doc/Documentation/Third_party.txt +++ b/Documentation/doc/Documentation/Third_party.txt @@ -288,7 +288,7 @@ for more information. On some platforms, linking with `libunwind` was responsible for an increase of the runtime of the final application. If you experience such an issue, we recommend to compile \ceres without `glog` support. -\attention In the master branch of \ceres `glog` was dropped as a dependecy and `abseil` was added instead. It is not compatible with \visualstudio 2017 or earlier versions. +\attention In the master branch of \ceres `glog` was dropped as a dependency and `abseil` was added instead. It is not compatible with \visualstudio 2017 or earlier versions. \subsection thirdpartyGLPK GLPK diff --git a/Documentation/doc/Documentation/packages.txt b/Documentation/doc/Documentation/packages.txt index e3f1d34622b..b311159a37f 100644 --- a/Documentation/doc/Documentation/packages.txt +++ b/Documentation/doc/Documentation/packages.txt @@ -71,6 +71,7 @@ \package_listing{Triangulation_on_sphere_2} \package_listing{Periodic_2_triangulation_2} \package_listing{Hyperbolic_triangulation_2} +\package_listing{Triangulation_on_hyperbolic_surface_2} \package_listing{Periodic_4_hyperbolic_triangulation_2} \package_listing{Triangulation_3} \package_listing{TDS_3} @@ -94,6 +95,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 2692995162c..a2ad6677ee6 100644 --- a/Documentation/doc/biblio/cgal_manual.bib +++ b/Documentation/doc/biblio/cgal_manual.bib @@ -22,7 +22,6 @@ - @article{ cgal:afh-pdecm-02, author = "P. K. Agarwal and E. Flato and D. Halperin", title = "Polygon Decomposition for Efficient Construction of {Minkowski} Sums", @@ -130,6 +129,21 @@ ,update = "04.04 kettner" } +@book{cgal:b-gdg-83, + title={The Geometry of Discrete Groups}, + author={Beardon, A.F.}, + series={Graduate texts in mathematics}, + year={1983}, + publisher={Springer} +} + +@book{cgal:b-gscrs-92, + title={Geometry and Spectra of Compact Riemann Surfaces}, + author={Peter Buser}, + year={1992}, + publisher={Springer} +} + @article{ cgal:bbp-iayed-01 ,author = "H. Br{\"o}nnimann and C. Burnikel and S. Pion" ,title = "Interval arithmetic yields efficient dynamic filters @@ -387,6 +401,12 @@ Boissonnat} 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 +663,17 @@ note="Conference version: Symp. on Geometry Processing 2003" , 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.}, @@ -689,6 +720,33 @@ Mourrain and Monique Teillaud" keywords = "Convex hull problem, Frame, Linear programming, Data envelopment analysis, Redundancy" } +@inproceedings{despre2020flipping, + title={Flipping geometric triangulations on hyperbolic surfaces}, + author={Despr{\'e}, Vincent and Schlenker, Jean-Marc and Teillaud, Monique}, + booktitle={36th International Symposium on Computational Geometry}, + year={2020}, + url={https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.SoCG.2020.35} +} + +@article{despre2022experimental, + title={Experimental analysis of {Delaunay} flip algorithms on genus two hyperbolic surfaces}, + author={Despr{\'e}, Vincent and Dubois, Lo{\"\i}c and Kolbe, Benedikt and Teillaud, Monique}, + year={2022}, + url={https://inria.hal.science/hal-03665888v1/document} +} + +@article{aigon2005hyperbolic, + title={Hyperbolic octagons and {Teichm{\"u}ller} space in genus 2}, + author={Aigon-Dupuy, Aline and Buser, Peter and Cibils, Michel and K{\"u}nzle, Alfred F and Steiner, Frank}, + journal={Journal of mathematical physics}, + volume={46}, + number={3}, + year={2005}, + publisher={AIP Publishing}, + url = {https://doi.org/10.1063/1.1850177} +} + + @article{cgal:dl-cginc-19, author = {Despr\'{e}, Vincent and Lazarus, Francis}, title = {Computing the Geometric Intersection Number of Curves}, @@ -909,6 +967,17 @@ Teillaud" 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 +1317,14 @@ Teillaud" ,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 ++}" @@ -2386,6 +2463,46 @@ location = {Salt Lake City, Utah, USA} ,update = "98.01 kettner" } +@article{cgal:vc-acvdupmc-04, + author = {Valette, Sébastien and Chassery, Jean-Marc}, + doi = {10.1111/j.1467-8659.2004.00769.x}, + hal_id = {hal-00534535}, + hal_version = {v1}, + journal = {Computer Graphics Forum}, + note = {Eurographics 2004 proceedings}, + number = {3}, + pages = {381-389}, + pdf = {https://hal.archives-ouvertes.fr/hal-00534535/file/valette.pdf}, + publisher = {Wiley}, + title = {Approximated Centroidal Voronoi Diagrams for Uniform Polygonal Mesh Coarsening}, + url = {https://hal.archives-ouvertes.fr/hal-00534535}, + volume = {23}, + year = {2004} +} + +@inproceedings{cgal:audette2011approach, + title={Approach-guided controlled resolution brain meshing for fe-based interactive neurosurgery simulation}, + author={Audette, M and Rivi{\`e}re, D and Ewend, M and Enquobahrie, A and Valette, S}, + booktitle={Workshop on Mesh Processing in Medical Image Analysis, in conjunction with MICCAI 2011}, + pages={176--186}, + year={2011} +} + +@article{cgal:vcp-grtmmdvd-08, + author = {Valette, Sébastien and Chassery, Jean-Marc and Prost, Rémy}, + doi = {10.1109/TVCG.2007.70430}, + hal_id = {hal-00537025}, + hal_version = {v1}, + journal = {IEEE Transactions on Visualization and Computer Graphics}, + number = {2}, + pages = {369--381}, + pdf = {https://hal.archives-ouvertes.fr/hal-00537025/file/VCP08TVCG.pdf}, + publisher = {Institute of Electrical and Electronics Engineers}, + title = {Generic remeshing of 3D triangular meshes with metric-dependent discrete Voronoi Diagrams}, + url = {https://hal.archives-ouvertes.fr/hal-00537025}, + volume = {14}, + year = {2008} +} @techreport{cgal:vla-lod-15, title={LOD Generation for Urban Scenes}, @@ -3111,14 +3228,12 @@ pages = "207--221" bibsource = {dblp computer science bibliography, https://dblp.org/} } -@inproceedings {dunyach2013curvRemesh, - booktitle = {Eurographics 2013 - Short Papers}, - title = {{Adaptive Remeshing for Real-Time Mesh Deformation}}, - author = {Dunyach, Marion and Vanderhaeghe, David and Barthe, Loïc and Botsch, Mario}, - year = {2013}, - publisher = {The Eurographics Association}, - ISSN = {1017-4656}, - DOI = {10.2312/conf/EG2013/short/029-032} +@inproceedings{dunyach2013curvRemesh, + title={Adaptive remeshing for real-time mesh deformation}, + author={Dunyach, Marion and Vanderhaeghe, David and Barthe, Loïc and Botsch, Mario}, + booktitle={Eurographics 2013}, + year={2013}, + organization={The Eurographics Association} } @book{botsch2010PMP, diff --git a/Documentation/doc/resources/1.10.0/BaseDoxyfile.in b/Documentation/doc/resources/1.10.0/BaseDoxyfile.in index 66f51ad476c..e5b0e33e964 100644 --- a/Documentation/doc/resources/1.10.0/BaseDoxyfile.in +++ b/Documentation/doc/resources/1.10.0/BaseDoxyfile.in @@ -222,7 +222,7 @@ ALIASES = "cgal=%CGAL" \ # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# FortranFree, unknown formatted Fortran: Fortran. In the latter case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), diff --git a/Documentation/doc/resources/1.10.0/hacks.js b/Documentation/doc/resources/1.10.0/hacks.js index a290b02cee8..614428d1bdf 100644 --- a/Documentation/doc/resources/1.10.0/hacks.js +++ b/Documentation/doc/resources/1.10.0/hacks.js @@ -98,14 +98,14 @@ $(document).ready(function() { if(window.location.href.includes("doc.cgal.org")){ var url='https://doc.cgal.org/latest/Manual/menu_version.js'; var script = document.createElement("script"); // Make a script DOM node - script.src = url; // Set it's src to the provided URL + script.src = url; // Set its src to the provided URL document.head.appendChild(script); } else { var url='../Manual/menu_version.js'; var script = document.createElement("script"); // Make a script DOM node - script.src = url; // Set it's src to the provided URL + script.src = url; // Set its src to the provided URL document.head.appendChild(script); } })(); diff --git a/Documentation/doc/resources/1.8.13/BaseDoxyfile.in b/Documentation/doc/resources/1.8.13/BaseDoxyfile.in index 4bd2e290d30..0b0cdcd4cd0 100644 --- a/Documentation/doc/resources/1.8.13/BaseDoxyfile.in +++ b/Documentation/doc/resources/1.8.13/BaseDoxyfile.in @@ -213,7 +213,7 @@ ALIASES = "cgal=%CGAL" \ # language is one of the parsers supported by doxygen: IDL, Java, Javascript, # C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: # FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed +# Fortran. In the latter case the parser tries to guess whether the code is fixed # or free formatted code, this is the default for Fortran type files), VHDL. For # instance to make doxygen treat .inc files as Fortran files (default is PHP), # and .f files as C (default is Fortran), use: inc=Fortran f=C. diff --git a/Documentation/doc/resources/1.8.13/hacks.js b/Documentation/doc/resources/1.8.13/hacks.js index 26d9ae47435..9c96bcec48b 100644 --- a/Documentation/doc/resources/1.8.13/hacks.js +++ b/Documentation/doc/resources/1.8.13/hacks.js @@ -98,14 +98,14 @@ $(document).ready(function() { if(window.location.href.includes("doc.cgal.org")){ var url='https://doc.cgal.org/latest/Manual/menu_version.js'; var script = document.createElement("script"); // Make a script DOM node - script.src = url; // Set it's src to the provided URL + script.src = url; // Set its src to the provided URL document.head.appendChild(script); } else { var url='../Manual/menu_version.js'; var script = document.createElement("script"); // Make a script DOM node - script.src = url; // Set it's src to the provided URL + script.src = url; // Set its src to the provided URL document.head.appendChild(script); } })(); diff --git a/Documentation/doc/resources/1.9.6/BaseDoxyfile.in b/Documentation/doc/resources/1.9.6/BaseDoxyfile.in index 72b23dbc0fa..a0eb9568a19 100644 --- a/Documentation/doc/resources/1.9.6/BaseDoxyfile.in +++ b/Documentation/doc/resources/1.9.6/BaseDoxyfile.in @@ -222,7 +222,7 @@ ALIASES = "cgal=%CGAL" \ # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, # VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: -# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# FortranFree, unknown formatted Fortran: Fortran. In the latter case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files). For instance to make doxygen treat .inc files # as Fortran files (default is PHP), and .f files as C (default is Fortran), diff --git a/Documentation/doc/resources/1.9.6/hacks.js b/Documentation/doc/resources/1.9.6/hacks.js index 26d9ae47435..9c96bcec48b 100644 --- a/Documentation/doc/resources/1.9.6/hacks.js +++ b/Documentation/doc/resources/1.9.6/hacks.js @@ -98,14 +98,14 @@ $(document).ready(function() { if(window.location.href.includes("doc.cgal.org")){ var url='https://doc.cgal.org/latest/Manual/menu_version.js'; var script = document.createElement("script"); // Make a script DOM node - script.src = url; // Set it's src to the provided URL + script.src = url; // Set its src to the provided URL document.head.appendChild(script); } else { var url='../Manual/menu_version.js'; var script = document.createElement("script"); // Make a script DOM node - script.src = url; // Set it's src to the provided URL + script.src = url; // Set its src to the provided URL document.head.appendChild(script); } })(); diff --git a/Documentation/doc/scripts/generate_how_to_cite.py b/Documentation/doc/scripts/generate_how_to_cite.py index 409678ebb91..b88d25dbd74 100644 --- a/Documentation/doc/scripts/generate_how_to_cite.py +++ b/Documentation/doc/scripts/generate_how_to_cite.py @@ -230,6 +230,7 @@ def protect_accentuated_letters(authors): .replace("ş", r"{\c{s}}") .replace("%", "") .replace("đ", r"{\-d}") + .replace("ï", r"{\"i}") ) try: res.encode("ascii") diff --git a/Filtered_kernel/examples/Filtered_kernel/Filtered_predicate.cpp b/Filtered_kernel/examples/Filtered_kernel/Filtered_predicate.cpp index c69d93421e7..dd6e63907c0 100644 --- a/Filtered_kernel/examples/Filtered_kernel/Filtered_predicate.cpp +++ b/Filtered_kernel/examples/Filtered_kernel/Filtered_predicate.cpp @@ -16,9 +16,9 @@ struct My_orientation_2 typedef typename K::RT RT; typedef typename K::Point_2 Point_2; - typedef typename K::Orientation result_type; + typedef typename K::Orientation Orientation; - result_type + Orientation operator()(const Point_2 &p, const Point_2 &q, const Point_2 &r) const { RT prx = p.x() - r.x(); diff --git a/Filtered_kernel/include/CGAL/EPIC_predicate_if_convertible.h b/Filtered_kernel/include/CGAL/EPIC_predicate_if_convertible.h new file mode 100644 index 00000000000..615774f275e --- /dev/null +++ b/Filtered_kernel/include/CGAL/EPIC_predicate_if_convertible.h @@ -0,0 +1,69 @@ +// Copyright (c) 2017 GeometryFactory +// 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, Laurent Rineau + +#ifndef CGAL_EPIC_PREDICATE_IF_CONVERTIBLE_H +#define CGAL_EPIC_PREDICATE_IF_CONVERTIBLE_H + +#include + +namespace CGAL { + +template +class EPIC_predicate_if_convertible { +public: + FP fp; + EpicP epicp; + + template + auto + operator()(const Args&... args) const + -> decltype(fp(args...)) + { + const CGAL::Epic_converter converter; + + std::tuple converted_args; + + // When C++20 is available, check the blame and clean this all up with lambdas + bool success = convert_all_impl(std::index_sequence_for{}, converter, converted_args, args...); + if(!success) // failed to convert all arguments, call the base predicate + return fp(args...); + + return call_epicp_impl(std::index_sequence_for{}, converted_args); + } + +private: + template + bool convert_all_impl(std::index_sequence, + const Converter& converter, + Tuple& converted_args, + const Args&... args) const + { + auto convert = [&](auto index, const auto& arg) { + auto converted = converter(approx(arg)); + if(converted.second) + std::get(converted_args) = converted; + return converted.second; + }; + + return (... && convert(std::integral_constant{}, args)); + } + + template + auto call_epicp_impl(std::index_sequence, const Tuple& converted_args) const + -> decltype(epicp(std::get(converted_args).first...)) + { + return epicp(std::get(converted_args).first...); + } +}; + +} // CGAL + +#endif // CGAL_EPIC_PREDICATE_IF_CONVERTIBLE_H diff --git a/Filtered_kernel/include/CGAL/Filtered_construction.h b/Filtered_kernel/include/CGAL/Filtered_construction.h index 51910605980..7bdfc916a34 100644 --- a/Filtered_kernel/include/CGAL/Filtered_construction.h +++ b/Filtered_kernel/include/CGAL/Filtered_construction.h @@ -16,6 +16,8 @@ #include #include +#include + namespace CGAL { template - result_type - operator()(const A1 &a1) const + auto operator()(const A1 &a1) const + -> typename std::invoke_result::type { { - // Protection is outside the try block as VC8 has the CGAL_CFG_FPU_ROUNDING_MODE_UNWINDING_VC_BUG Protect_FPU_rounding P1; try { - return From_Filtered( Filter_construction(To_Filtered(a1)) ); + return From_Filtered(Filter_construction(To_Filtered(a1))); } catch (Uncertain_conversion_exception&) {} } Protect_FPU_rounding P(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - return From_Exact( Exact_construction(To_Exact(a1)) ); + return From_Exact(Exact_construction(To_Exact(a1))); } + template - result_type - operator()(const A1 &a1, const A2 &a2) const + auto operator()(const A1 &a1, const A2 &a2) const + -> typename std::invoke_result::type { { Protect_FPU_rounding P1; try { - return From_Filtered( Filter_construction(To_Filtered(a1), - To_Filtered(a2)) ); + return From_Filtered(Filter_construction(To_Filtered(a1), + To_Filtered(a2))); } catch (Uncertain_conversion_exception&) {} } Protect_FPU_rounding P(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - return From_Exact( Exact_construction(To_Exact(a1), - To_Exact(a2)) ); + return From_Exact(Exact_construction(To_Exact(a1), + To_Exact(a2))); } template - result_type - operator()(const A1 &a1, const A2 &a2, const A3 &a3) const + auto operator()(const A1 &a1, const A2 &a2, const A3 &a3) const + -> typename std::invoke_result::type { { Protect_FPU_rounding P1; try { - return From_Filtered( Filter_construction(To_Filtered(a1), - To_Filtered(a2), - To_Filtered(a3)) ); + return From_Filtered(Filter_construction(To_Filtered(a1), + To_Filtered(a2), + To_Filtered(a3))); } catch (Uncertain_conversion_exception&) {} } Protect_FPU_rounding P(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - return From_Exact( Exact_construction(To_Exact(a1), - To_Exact(a2), - To_Exact(a3)) ); + return From_Exact(Exact_construction(To_Exact(a1), + To_Exact(a2), + To_Exact(a3))); } - }; - - -} //namespace CGAL - +} // namespace CGAL #endif // CGAL_FILTERED_CONSTRUCTION_H diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Angle_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Angle_3.h index e9428fd2917..f54f028d47d 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Angle_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Angle_3.h @@ -34,13 +34,12 @@ template < typename K_base > class Angle_3 : public K_base::Angle_3 { - typedef typename K_base::Point_3 Point_3; + typedef typename K_base::Angle Angle; + typedef typename K_base::Point_3 Point_3; + typedef typename K_base::Angle_3 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); Sign sign_with_error(const double x, const double error) const { @@ -49,7 +48,7 @@ public: else return ZERO; } - result_type operator()(const Point_3 &p, const Point_3& q, const Point_3& r) const + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { CGAL_BRANCH_PROFILER_3("semi-static failures/attempts/calls to : Angle_3", tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Collinear_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Collinear_3.h index 9279f60c3d2..4dd64070b5c 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Collinear_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Collinear_3.h @@ -24,16 +24,16 @@ template < typename K_base > class Collinear_3 : public K_base::Collinear_3 { + typedef typename K_base::Boolean Boolean; typedef typename K_base::Point_3 Point_3; typedef typename K_base::Vector_3 Vector_3; typedef typename K_base::Sphere_3 Sphere_3; typedef typename K_base::Tetrahedron_3 Tetrahedron_3; - typedef typename K_base::Collinear_3 Base; + + typedef typename K_base::Collinear_3 Base; public: - - typedef typename Base::result_type result_type; - result_type + Boolean operator()(const Point_3 &p, const Point_3 &q, const Point_3 &r) const { CGAL_BRANCH_PROFILER_3("semi-static failures/attempts/calls to : Collinear_3", tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_distance_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_distance_3.h index f3880b3f0e2..4589aa9aa81 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_distance_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_distance_3.h @@ -30,17 +30,16 @@ template < typename K_base > class Compare_distance_3 : public K_base::Compare_distance_3 { - typedef typename K_base::Point_3 Point_3; - typedef typename K_base::Vector_3 Vector_3; + typedef typename K_base::Comparison_result Comparison_result; + typedef typename K_base::Point_3 Point_3; + typedef typename K_base::Vector_3 Vector_3; + typedef typename K_base::Compare_distance_3 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); - result_type operator()(const Point_3 &p, const Point_3& q, const Point_3& r) const + Comparison_result operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_squared_radius_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_squared_radius_3.h index 8e0683677aa..aecee9d3618 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_squared_radius_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_squared_radius_3.h @@ -24,15 +24,16 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { class Compare_squared_radius_3 : public K_base::Compare_squared_radius_3 { - typedef typename K_base::Point_3 Point_3; - typedef typename K_base::FT FT; - typedef typename K_base::Compare_squared_radius_3 Base; - public: - typedef typename Base::result_type result_type; + typedef typename K_base::Comparison_result Comparison_result; + typedef typename K_base::Point_3 Point_3; + typedef typename K_base::FT FT; + typedef typename K_base::Compare_squared_radius_3 Base; + + public: using Base::operator(); - result_type operator() ( + Comparison_result operator() ( const Point_3& p, const Point_3& q, const Point_3& r, @@ -185,7 +186,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { return Base::operator()(p,q,r,s,w); } - result_type operator() ( + Comparison_result operator() ( const Point_3& p, const Point_3& q, const Point_3& s, @@ -312,14 +313,14 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(int_tmp_result); + return static_cast(int_tmp_result); } else return Base::operator()(p,q,s,w); } - result_type operator() ( + Comparison_result operator() ( const Point_3& p, const Point_3& q, const FT& w diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_weighted_squared_radius_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_weighted_squared_radius_3.h index 41b9f1b9e77..3a8afc92ba3 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_weighted_squared_radius_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_weighted_squared_radius_3.h @@ -27,15 +27,16 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { class Compare_weighted_squared_radius_3: public K_base::Compare_weighted_squared_radius_3 { - typedef typename K_base::Weighted_point_3 Weighted_point_3; - typedef typename K_base::FT FT; - typedef typename K_base::Compare_weighted_squared_radius_3 Base; - public: - typedef typename Base::result_type result_type; + typedef typename K_base::Comparison_result Comparison_result; + typedef typename K_base::Weighted_point_3 Weighted_point_3; + typedef typename K_base::FT FT; + typedef typename K_base::Compare_weighted_squared_radius_3 Base; + + public: using Base::operator(); - result_type operator() ( + Comparison_result operator() ( const Weighted_point_3& p, const Weighted_point_3& q, const Weighted_point_3& r, @@ -175,7 +176,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { return Base::operator()(p,q,r,s,w); } - result_type + Comparison_result operator() ( const Weighted_point_3& p, const Weighted_point_3& q , @@ -292,7 +293,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { return Base::operator()(p,q,r,w); } - result_type + Comparison_result operator() ( const Weighted_point_3& p, const Weighted_point_3& q, diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_x_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_x_2.h index 29c6d291051..3478f524983 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_x_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_x_2.h @@ -31,17 +31,16 @@ template < typename K_base > class Compare_x_2 : public K_base::Compare_x_2 { - typedef typename K_base::Point_2 Point_2; - typedef typename K_base::Line_2 Line_2; - typedef typename K_base::Compare_x_2 Base; + typedef typename K_base::Comparison_result Comparison_result; + typedef typename K_base::Point_2 Point_2; + typedef typename K_base::Line_2 Line_2; + + typedef typename K_base::Compare_x_2 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); - result_type operator()(const Point_2 &p, const Point_2& q) const + Comparison_result operator()(const Point_2& p, const Point_2& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_2.h index da9b97215a8..043a174ce99 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_2.h @@ -31,17 +31,16 @@ template < typename K_base > class Compare_y_2 : public K_base::Compare_y_2 { - typedef typename K_base::Point_2 Point_2; - typedef typename K_base::Line_2 Line_2; - typedef typename K_base::Compare_y_2 Base; + typedef typename K_base::Comparison_result Comparison_result; + typedef typename K_base::Point_2 Point_2; + typedef typename K_base::Line_2 Line_2; + + typedef typename K_base::Compare_y_2 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); - result_type operator()(const Point_2 &p, const Point_2& q) const + Comparison_result operator()(const Point_2 &p, const Point_2& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_at_x_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_at_x_2.h index 5de1af9d7b2..8c9204f718f 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_at_x_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Compare_y_at_x_2.h @@ -22,13 +22,14 @@ template < typename K_base, typename Kernel > class Compare_y_at_x_2 : public K_base::Compare_y_at_x_2 { - typedef typename K_base::Point_2 Point_2; - typedef typename K_base::Segment_2 Segment_2; - typedef typename K_base::FT FT; - typedef typename K_base::Compare_y_at_x_2 Base; + typedef typename K_base::Comparison_result Comparison_result; + typedef typename K_base::Point_2 Point_2; + typedef typename K_base::Segment_2 Segment_2; + typedef typename K_base::FT FT; + + typedef typename K_base::Compare_y_at_x_2 Base; public: - using Base::operator(); Comparison_result diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_3.h index fedf31fcba9..38c4384769c 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_3.h @@ -25,17 +25,14 @@ template < typename K_base, typename SFK > class Coplanar_3 : public K_base::Coplanar_3 { - typedef typename K_base::Point_3 Point_3; + typedef typename K_base::Boolean Boolean; + typedef typename K_base::Point_3 Point_3; + typedef typename K_base::Coplanar_3 Base; typedef typename SFK::Orientation_3 Orientation_3; public: - - typedef typename Base::result_type result_type; - - - - result_type + Boolean operator()(const Point_3& p,const Point_3& q, const Point_3& r, const Point_3& s) const { return Orientation_3()(p,q,r,s) == COPLANAR; diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_orientation_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_orientation_3.h index 212dddec6cf..255f95c1ab7 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_orientation_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_orientation_3.h @@ -40,12 +40,12 @@ template < typename Kernel > class Coplanar_orientation_3 : public Kernel::Coplanar_orientation_3 { + typedef typename Kernel::Orientation Orientation; typedef typename Kernel::Point_3 Point_3; + typedef typename Kernel::Coplanar_orientation_3 Base; public: - typedef Orientation result_type; - Orientation operator()(const Point_3 &p, const Point_3 &q, const Point_3 &r) const { return opti_coplanar_orientationC3( diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_side_of_bounded_circle_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_side_of_bounded_circle_3.h index 48c96ec886c..ae9c2e36229 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_side_of_bounded_circle_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Coplanar_side_of_bounded_circle_3.h @@ -44,8 +44,6 @@ class Side_of_bounded_circle_3 } public: - typedef Bounded_side result_type; - Bounded_side operator()(const Point &p, const Point &q, const Point &r, const Point &t) const { diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_2.h index a5533c253fa..088648b9d72 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_2.h @@ -28,16 +28,13 @@ template < typename K_base, typename SFK > class Do_intersect_2 : public K_base::Do_intersect_2 { + typedef typename K_base::Boolean Boolean; typedef typename K_base::Point_2 Point_2; typedef typename K_base::Segment_2 Segment_2; + typedef typename K_base::Do_intersect_2 Base; - typedef K_base TA1; - typedef SFK TA2; public: - - typedef typename Base::result_type result_type; - using Base::operator(); // The internal::do_intersect(..) function @@ -47,19 +44,19 @@ public: // the statically filtered kernel we avoid // that doubles are put into Interval_nt // to get taken out again with fit_in_double - result_type + Boolean operator()(const Segment_2 &s, const Segment_2& t) const { return Intersections::internal::do_intersect(s,t, SFK()); } - result_type + Boolean operator()(const Point_2 &p, const Segment_2& t) const { return Intersections::internal::do_intersect(p,t, SFK()); } - result_type + Boolean operator()(const Segment_2& t, const Point_2 &p) const { return Intersections::internal::do_intersect(p,t, SFK()); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_3.h index 2b0ef97d7fc..1d19ce5a469 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Do_intersect_3.h @@ -39,18 +39,17 @@ template < typename K_base, typename SFK > class Do_intersect_3 : public K_base::Do_intersect_3 { + typedef typename K_base::Boolean Boolean; typedef typename K_base::Point_3 Point_3; typedef typename K_base::Ray_3 Ray_3; typedef typename K_base::Segment_3 Segment_3; typedef typename K_base::Triangle_3 Triangle_3; typedef typename K_base::Tetrahedron_3 Tetrahedron_3; typedef typename K_base::Sphere_3 Sphere_3; + typedef typename K_base::Do_intersect_3 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); Sign sign_with_error(const double x, const double error) const { @@ -67,32 +66,31 @@ public: // the statically filtered kernel we avoid // that doubles are put into Interval_nt // to get taken out again with fit_in_double - result_type + Boolean operator()(const Segment_3 &s, const Triangle_3& t) const { return Intersections::internal::do_intersect(t,s, SFK()); } - result_type + Boolean operator()(const Triangle_3& t, const Segment_3 &s) const { return Intersections::internal::do_intersect(t,s, SFK()); } - result_type + Boolean operator()(const Triangle_3 &t0, const Triangle_3& t1) const { return Intersections::internal::do_intersect(t0,t1, SFK()); } - - result_type + Boolean operator()(const Bbox_3& b, const Segment_3 &s) const { return this->operator()(s, b); } - result_type + Boolean operator()(const Segment_3 &s, const Bbox_3& b) const { CGAL_BRANCH_PROFILER_3(std::string("semi-static failures/attempts/calls to : ") + @@ -111,7 +109,7 @@ public: { CGAL_BRANCH_PROFILER_BRANCH_1(tmp); - const Uncertain ub = + const Uncertain ub = Intersections::internal::do_intersect_bbox_segment_aux operator()(t, b); } - result_type + Boolean operator()(const Tetrahedron_3 &t, const Bbox_3& b) const { CGAL_BRANCH_PROFILER_3(std::string("semi-static failures/attempts/calls to : ") + @@ -168,13 +166,13 @@ public: return Base::operator()(t,b); } - result_type + Boolean operator()(const Bbox_3& b, const Ray_3 &r) const { return this->operator()(r, b); } - result_type + Boolean operator()(const Ray_3 &r, const Bbox_3& b) const { CGAL_BRANCH_PROFILER_3(std::string("semi-static failures/attempts/calls to : ") + @@ -193,7 +191,7 @@ public: { CGAL_BRANCH_PROFILER_BRANCH_1(tmp); - const Uncertain ub = + const Uncertain ub = Intersections::internal::do_intersect_bbox_segment_aux operator()(t, b); @@ -487,7 +484,7 @@ public: return false; }; - result_type + Boolean operator()(const Triangle_3 &t, const Bbox_3& b) const { CGAL_BRANCH_PROFILER_3(std::string("semi-static failures/attempts/calls to : ") + @@ -616,14 +613,14 @@ public: return Base::operator()(t,b); } - result_type + Boolean operator()(const Bbox_3& b, const Sphere_3 &s) const { return this->operator()(s, b); } // The parameter overestimate is used to avoid a filter failure in AABB_tree::closest_point() - result_type + Boolean operator()(const Sphere_3 &s, const Bbox_3& b, bool overestimate = false) const { CGAL_BRANCH_PROFILER_3(std::string("semi-static failures/attempts/calls to : ") + diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_2.h index 60b2360a939..79832da2535 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_2.h @@ -31,18 +31,16 @@ template < typename K_base > class Equal_2 : public K_base::Equal_2 { + typedef typename K_base::Boolean Boolean; typedef typename K_base::FT FT; typedef typename K_base::Point_2 Point_2; typedef typename K_base::Vector_2 Vector_2; typedef typename K_base::Equal_2 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); - result_type operator()(const Point_2 &p, const Point_2& q) const + Boolean operator()(const Point_2& p, const Point_2& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); @@ -61,8 +59,7 @@ public: return Base::operator()(p, q); } - - result_type operator()(const Vector_2 &p, const Vector_2& q) const + Boolean operator()(const Vector_2& p, const Vector_2& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_3.h index 10c44051ae2..5d04c892d7f 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Equal_3.h @@ -31,17 +31,16 @@ template < typename K_base > class Equal_3 : public K_base::Equal_3 { + typedef typename K_base::Boolean Boolean; typedef typename K_base::Point_3 Point_3; typedef typename K_base::Vector_3 Vector_3; + typedef typename K_base::Equal_3 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); - result_type operator()(const Point_3 &p, const Point_3& q) const + Boolean operator()(const Point_3& p, const Point_3& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); @@ -62,7 +61,7 @@ public: } - result_type operator()(const Vector_3 &p, const Vector_3& q) const + Boolean operator()(const Vector_3& p, const Vector_3& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); @@ -83,7 +82,7 @@ public: } - result_type operator()(const Vector_3 &p, const Null_vector &q) const + Boolean operator()(const Vector_3& p, const Null_vector& q) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Is_degenerate_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Is_degenerate_3.h index a4af481f7e1..6b43f629126 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Is_degenerate_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Is_degenerate_3.h @@ -25,6 +25,7 @@ template < typename K_base, typename SFK > class Is_degenerate_3 : public K_base::Is_degenerate_3 { + typedef typename K_base::Boolean Boolean; typedef typename K_base::Ray_3 Ray_3; typedef typename K_base::Segment_3 Segment_3; typedef typename K_base::Plane_3 Plane_3; @@ -35,25 +36,21 @@ class Is_degenerate_3 typedef typename SFK::Equal_3 Equal_3; public: - - typedef typename Base::result_type result_type; - using Base::operator(); - result_type + Boolean operator()(const Segment_3& s) const { return Equal_3()(Construct_source_3()(s), Construct_target_3()(s)); } - - result_type + Boolean operator()(const Ray_3& r) const { return Equal_3()(Construct_source_3()(r), Construct_second_point_3()(r)); } - result_type + Boolean operator()(const Plane_3& p) const { CGAL_BRANCH_PROFILER(std::string("semi-static attempts/calls to : ") + diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_2.h index cbb4494e7cd..11cd803458a 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_2.h @@ -25,16 +25,14 @@ template < typename K_base > class Orientation_2 : public K_base::Orientation_2 { + typedef typename K_base::Orientation Orientation; typedef typename K_base::Point_2 Point_2; typedef typename K_base::Vector_2 Vector_2; - typedef typename K_base::Circle_2 Circle_2; + typedef typename K_base::Circle_2 Circle_2; typedef typename K_base::Orientation_2 Base; public: - - typedef typename Base::result_type result_type; - using Base::operator(); Orientation diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_3.h index 2fdffde6fda..f7d126dfaf0 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Orientation_3.h @@ -27,18 +27,18 @@ template < typename K_base > class Orientation_3 : public K_base::Orientation_3 { + typedef typename K_base::Orientation Orientation; typedef typename K_base::Point_3 Point_3; typedef typename K_base::Vector_3 Vector_3; typedef typename K_base::Sphere_3 Sphere_3; typedef typename K_base::Tetrahedron_3 Tetrahedron_3; + typedef typename K_base::Orientation_3 Base; public: - typedef typename Base::result_type result_type; - using Base::operator(); - result_type + Orientation operator()(const Point_3 &p, const Point_3 &q, const Point_3 &r, const Point_3 &s) const { diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_circle_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_circle_2.h index f9097923bab..e7f1c195da8 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_circle_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_circle_2.h @@ -26,15 +26,15 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { class Power_side_of_oriented_power_circle_2: public K_base::Power_side_of_oriented_power_circle_2 { + typedef typename K_base::Oriented_side Oriented_side; typedef typename K_base::Weighted_point_2 Weighted_point_2; typedef typename K_base::FT FT; typedef typename K_base::Power_side_of_oriented_power_circle_2 Base; - public: - typedef typename Base::result_type result_type; + public: using Base::operator(); - result_type operator() ( const Weighted_point_2 & p, + Oriented_side operator()(const Weighted_point_2 & p, const Weighted_point_2 & q, const Weighted_point_2 & r, const Weighted_point_2 & t) const @@ -64,7 +64,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { double drx = (rx - tx); double dry = (ry - ty); double drz = (((square( drx ) + square( dry )) - rwt) + twt); - result_type int_tmp_result; + Oriented_side int_tmp_result; double RT_tmp_result; double eps; RT_tmp_result = CGAL::determinant( dpx, dpy, dpz, dqx, dqy, dqz, drx, dry, drz ); @@ -162,7 +162,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } - result_type operator() ( const Weighted_point_2 & p, + Oriented_side operator()(const Weighted_point_2 & p, const Weighted_point_2 & q, const Weighted_point_2 & t) const { @@ -219,7 +219,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { double upper_bound_1; if( (cmpx != 0) ) { - result_type int_tmp_result; + Oriented_side int_tmp_result; double RT_tmp_result; RT_tmp_result = CGAL::determinant( dpx, dpz, dqx, dqz ); lower_bound_1 = max2; @@ -265,11 +265,11 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(cmpx * int_tmp_result); + return static_cast(cmpx * int_tmp_result); } int cmpy; cmpy = ((py > qy) ? 1 : ((py < qy) ? -1 : 0)); - result_type int_tmp_result_FFWKCAA; + Oriented_side int_tmp_result_FFWKCAA; double RT_tmp_result_k60Ocge = CGAL::determinant( dpy, dpz, dqy, dqz ); lower_bound_1 = max4; upper_bound_1 = max4; @@ -314,7 +314,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(cmpy * int_tmp_result_FFWKCAA); + return static_cast(cmpy * int_tmp_result_FFWKCAA); } else diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_sphere_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_sphere_3.h index f4adbbe18cf..c6fabbb4927 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_sphere_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Power_side_of_oriented_power_sphere_3.h @@ -26,12 +26,12 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { class Power_side_of_oriented_power_sphere_3: public K_base::Power_side_of_oriented_power_sphere_3 { + typedef typename K_base::Oriented_side Oriented_side; typedef typename K_base::Weighted_point_3 Weighted_point_3; typedef typename K_base::FT FT; typedef typename K_base::Power_side_of_oriented_power_sphere_3 Base; - public: - typedef typename Base::result_type result_type; + public: using Base::operator(); void @@ -107,11 +107,11 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } - result_type operator() ( const Weighted_point_3 & p, - const Weighted_point_3 & q, - const Weighted_point_3 & r, - const Weighted_point_3 & s, - const Weighted_point_3 & t) const + Oriented_side operator() (const Weighted_point_3& p, + const Weighted_point_3& q, + const Weighted_point_3& r, + const Weighted_point_3& s, + const Weighted_point_3& t) const { CGAL_BRANCH_PROFILER_3("semi-static failures/attempts/calls to : Power_side_of_power_sphere_3 with 4+1 wpoints", tmp); @@ -225,7 +225,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { return Base::operator()(p,q,r,s,t); } - result_type int_tmp_result; + Oriented_side int_tmp_result; double eps = (1.67106803095990471147e-13 * (((max2 * max3) * max4) * (CGAL::max) ( max5, (max1 * max1) ))); @@ -253,10 +253,10 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { return Base::operator()(p,q,r,s,t); } - result_type operator() ( const Weighted_point_3 & p, - const Weighted_point_3 & q, - const Weighted_point_3 & r, - const Weighted_point_3 & t) const + Oriented_side operator() (const Weighted_point_3& p, + const Weighted_point_3& q, + const Weighted_point_3& r, + const Weighted_point_3& t) const { CGAL_BRANCH_PROFILER_3("semi-static failures/attempts/calls to : Power_side_of_oriented_power_sphere_3 with 3+1 wpoints", tmp); @@ -408,15 +408,15 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(cmp * int_tmp_result_FFWKCAA); + return static_cast(cmp * int_tmp_result_FFWKCAA); } else return Base::operator()(p,q,r,t); } - result_type operator() ( const Weighted_point_3 & p, - const Weighted_point_3 & q, - const Weighted_point_3 & t) const + Oriented_side operator() (const Weighted_point_3& p, + const Weighted_point_3& q, + const Weighted_point_3& t) const { CGAL_BRANCH_PROFILER_3("semi-static failures/attempts/calls to : Power_side_of_oriented_power_sphere_3 with 2+1 wpoints", tmp); @@ -506,7 +506,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(cmp * int_tmp_result); + return static_cast(cmp * int_tmp_result); } cmp = ((py > qy) ? 1 : ((py < qy) ? -1 : 0)); if( (cmp != 0) ) @@ -549,7 +549,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(cmp * int_tmp_result_FFWKCAA); + return static_cast(cmp * int_tmp_result_FFWKCAA); } cmp = ((pz > qz) ? 1 : ((pz < qz) ? -1 : 0)); int int_tmp_result_3SPBwDj; @@ -592,7 +592,7 @@ namespace CGAL { namespace internal { namespace Static_filters_predicates { } } } - return static_cast(cmp * int_tmp_result_3SPBwDj); + return static_cast(cmp * int_tmp_result_3SPBwDj); } else return Base::operator()(p,q,t); diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_circle_2.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_circle_2.h index 3b59fed08db..0762a4948f4 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_circle_2.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_circle_2.h @@ -23,11 +23,11 @@ template < typename K_base > class Side_of_oriented_circle_2 : public K_base::Side_of_oriented_circle_2 { + typedef typename K_base::Oriented_side Oriented_side; typedef typename K_base::Point_2 Point_2; typedef typename K_base::Side_of_oriented_circle_2 Base; public: - Oriented_side operator()(const Point_2 &p, const Point_2 &q, const Point_2 &r, const Point_2 &t) const { diff --git a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_sphere_3.h b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_sphere_3.h index 41a54d33012..90b128c5eee 100644 --- a/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_sphere_3.h +++ b/Filtered_kernel/include/CGAL/Filtered_kernel/internal/Static_filters/Side_of_oriented_sphere_3.h @@ -22,11 +22,11 @@ template < typename K_base > class Side_of_oriented_sphere_3 : public K_base::Side_of_oriented_sphere_3 { + typedef typename K_base::Oriented_side Oriented_side; typedef typename K_base::Point_3 Point_3; typedef typename K_base::Side_of_oriented_sphere_3 Base; public: - Oriented_side operator()(const Point_3 &p, const Point_3 &q, const Point_3 &r, const Point_3 &s, const Point_3 &t) const diff --git a/Filtered_kernel/include/CGAL/Filtered_predicate.h b/Filtered_kernel/include/CGAL/Filtered_predicate.h index acfe6892a7c..99be87fecf1 100644 --- a/Filtered_kernel/include/CGAL/Filtered_predicate.h +++ b/Filtered_kernel/include/CGAL/Filtered_predicate.h @@ -28,7 +28,7 @@ namespace CGAL { // TODO : // - each predicate in the default kernel should define a tag that says if it -// wants to be filtered or not (=> all homogeneous predicate define this +// wants to be filtered or not (=> all homogeneous predicates define this // tag). We could even test-suite that automatically. It makes a strong // new requirement on the kernel though... // Could be done with a traits mechanism ? @@ -52,18 +52,13 @@ class Filtered_predicate EP ep; AP ap; - typedef typename AP::result_type Ares; - public: - + // AP's result type must be convertible to EP's result type. typedef AP Approximate_predicate; typedef EP Exact_predicate; typedef C2E To_exact_converter; typedef C2A To_approximate_converter; - typedef typename EP::result_type result_type; - // AP::result_type must be convertible to EP::result_type. - Filtered_predicate() {} @@ -85,11 +80,14 @@ public: {} template - result_type + auto operator()(const Args&... args) const { + typedef typename Remove_needs_FT >::Type result_type; #ifndef CGAL_EPICK_NO_INTERVALS + typedef typename Remove_needs_FT >::Type Ares; + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); // Protection is outside the try block as VC8 has the CGAL_CFG_FPU_ROUNDING_MODE_UNWINDING_VC_BUG { @@ -98,7 +96,7 @@ public: { Ares res = ap(c2a(args)...); if (is_certain(res)) - return get_certain(res); + return result_type(get_certain(res)); } catch (Uncertain_conversion_exception&) {} } @@ -106,7 +104,7 @@ public: Protect_FPU_rounding p(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); #endif // CGAL_EPICK_NO_INTERVALS - return ep(c2e(args)...); + return result_type(ep(c2e(args)...)); } }; @@ -120,27 +118,28 @@ class Filtered_predicate_RT_FT EP_FT ep_ft; AP ap; - using Ares = typename Remove_needs_FT::Type; - -public: - using result_type = typename Remove_needs_FT::Type; - private: + // Detect if the predicate's result type has been wrapped with the `Needs_FT` class template struct Call_operator_needs_FT { - using Actual_approx_res = decltype(ap(c2a(std::declval())...)); - using Approx_res = std::remove_cv_t >; - enum { value = std::is_same >::value }; + template + struct is_Needs_FT : std::false_type { }; + template + struct is_Needs_FT > : std::true_type { }; + + typedef CGAL::cpp20::remove_cvref_t())...))> Actual_approx_res; + enum { value = is_Needs_FT::value }; }; + // If there is no `Needs_FT` in the result, then we can use an RT-based exact predicate template ::value>* = nullptr> - result_type call(const Args&... args) const { return ep_ft(c2e_ft(args)...); } + decltype(auto) exact_call(const Args&... args) const { return ep_ft(c2e_ft(args)...); } template ::value>* = nullptr> - result_type call(const Args&... args) const { return ep_rt(c2e_rt(args)...); } + decltype(auto) exact_call(const Args&... args) const { return ep_rt(c2e_rt(args)...); } public: // ## Important note @@ -154,10 +153,14 @@ public: bool needs_FT(const Args&...) const { return Call_operator_needs_FT::value; } template - result_type + auto operator()(const Args&... args) const { + typedef typename Remove_needs_FT >::Type result_type; + #ifndef CGAL_EPICK_NO_INTERVALS + typedef typename Remove_needs_FT >::Type Ares; + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); // Protection is outside the try block as VC8 has the CGAL_CFG_FPU_ROUNDING_MODE_UNWINDING_VC_BUG { @@ -166,7 +169,7 @@ public: { Ares res = ap(c2a(args)...); if (is_certain(res)) - return get_certain(res); + return result_type(get_certain(res)); } catch (Uncertain_conversion_exception&) {} } @@ -174,7 +177,7 @@ public: Protect_FPU_rounding p(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); #endif // CGAL_EPICK_NO_INTERVALS - return call(args...); + return result_type(exact_call(args...)); } }; diff --git a/Filtered_kernel/include/CGAL/Filtered_predicate_with_state.h b/Filtered_kernel/include/CGAL/Filtered_predicate_with_state.h index 991fe8247b5..d985227f21c 100644 --- a/Filtered_kernel/include/CGAL/Filtered_predicate_with_state.h +++ b/Filtered_kernel/include/CGAL/Filtered_predicate_with_state.h @@ -33,33 +33,27 @@ class Filtered_predicate_with_state O1 o1; mutable std::optional oep; AP ap; - typedef typename AP::result_type Ares; public: - + // AP::result_type must be convertible to EP::result_type. typedef AP Approximate_predicate; typedef EP Exact_predicate; typedef C2E To_exact_converter; typedef C2A To_approximate_converter; - typedef typename EP::result_type result_type; - // AP::result_type must be convertible to EP::result_type. - Filtered_predicate_with_state(const O1 &o1) : c2e(), c2a(), o1(o1), oep(), ap(c2a(o1)) {} template - result_type - operator()(const Args&... args) const; -}; - -template - template -typename Filtered_predicate_with_state::result_type -Filtered_predicate_with_state:: + auto operator()(const Args&... args) const -{ + { + typedef typename Remove_needs_FT >::Type result_type; + +#ifndef CGAL_EPICK_NO_INTERVALS + typedef typename Remove_needs_FT >::Type Ares; + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); // Protection is outside the try block as VC8 has the CGAL_CFG_FPU_ROUNDING_MODE_UNWINDING_VC_BUG { @@ -68,18 +62,22 @@ Filtered_predicate_with_state:: { Ares res = ap(c2a(args)...); if (is_certain(res)) - return get_certain(res); + return result_type(get_certain(res)); } catch (Uncertain_conversion_exception&) {} } CGAL_BRANCH_PROFILER_BRANCH(tmp); Protect_FPU_rounding p(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); +#endif if(! oep){ oep.emplace(c2e(o1)); } - return (*oep)(c2e(args)...); -} + return result_type((*oep)(c2e(args)...)); + } +}; + + } //namespace CGAL diff --git a/Filtered_kernel/include/CGAL/Kernel_profiler.h b/Filtered_kernel/include/CGAL/Kernel_profiler.h index 20611510e94..aaaa310afce 100644 --- a/Filtered_kernel/include/CGAL/Kernel_profiler.h +++ b/Filtered_kernel/include/CGAL/Kernel_profiler.h @@ -25,8 +25,6 @@ template < typename P > struct Primitive_profiler : public P { - typedef typename P::result_type result_type; - // #define CGAL_KERNEL_PROFILER CGAL_PROFILER(CGAL_PRETTY_FUNCTION); #define CGAL_KERNEL_PROFILER \ CGAL_PROFILER(typeid(static_cast(*this)).name()) @@ -35,7 +33,7 @@ struct Primitive_profiler : P(p) {} template - result_type + decltype(auto) operator()(A&& ... a) const { CGAL_KERNEL_PROFILER; diff --git a/Filtered_kernel/include/CGAL/Lazy.h b/Filtered_kernel/include/CGAL/Lazy.h index 164653d522a..4fafd3f9219 100644 --- a/Filtered_kernel/include/CGAL/Lazy.h +++ b/Filtered_kernel/include/CGAL/Lazy.h @@ -867,217 +867,6 @@ struct Exact_converter { return b; } }; -//____________________________________________________________ - - - -template -class Lazy_rep_with_vector_1 final - : public Lazy_rep, std::vector, E2A> - , private EC -{ - typedef std::vector AT; - typedef std::vector ET; - typedef Lazy_rep Base; - - mutable L1 l1_; - - const EC& ec() const { return *this; } - -public: - - void - update_exact() const - { - auto* p = new typename Base::Indirect(); - // TODO : This looks really unfinished... - std::vector vec; - //this->et->reserve(this->at.size()); - ec()(CGAL::exact(l1_), std::back_inserter(p->et_)); - this->set_at(p); - this->set_ptr(p); - // Prune lazy tree - lazy_reset_member(l1_); - } - - Lazy_rep_with_vector_1(const AC& ac, const EC& /*ec*/, const L1& l1) - : l1_(l1) - { - ac(CGAL::approx(l1), std::back_inserter(this->at_orig.at_)); - } - -#ifdef CGAL_LAZY_KERNEL_DEBUG - void - print_dag(std::ostream& os, int level) const - { - this->print_at_et(os, level); - os << "A Lazy_rep_with_vector_1 of size " << this->approx().size() << std::endl; - if(this->is_lazy()){ - CGAL::msg(os, level, "DAG with one child node:"); - CGAL::print_dag(l1_, os, level+1); - - } - } -#endif -}; - - -template -class Lazy_rep_with_vector_2 final - : public Lazy_rep, std::vector, E2A> - , private EC -{ - typedef std::vector AT; - typedef std::vector ET; - typedef Lazy_rep Base; - - mutable L1 l1_; - mutable L2 l2_; - - const EC& ec() const { return *this; } - -public: - - void - update_exact() const - { - auto* p = new typename Base::Indirect(); - p->et_.reserve(this->at_orig.at().size()); - ec()(CGAL::exact(l1_), CGAL::exact(l2_), std::back_inserter(p->et_)); - this->set_at(p); - this->set_ptr(p); - // Prune lazy tree - lazy_reset_member(l1_); - lazy_reset_member(l2_); - } - - Lazy_rep_with_vector_2(const AC& ac, const EC& /*ec*/, const L1& l1, const L2& l2) - : l1_(l1), l2_(l2) - { - ac(CGAL::approx(l1), CGAL::approx(l2), std::back_inserter(this->at_orig.at_)); - } - -#ifdef CGAL_LAZY_KERNEL_DEBUG - void - print_dag(std::ostream& os, int level) const - { - this->print_at_et(os, level); - os << "A Lazy_rep_with_vector_2 of size " << this->approx().size() << std::endl; - if(this->is_lazy()){ - CGAL::msg(os, level, "DAG with two child nodes:"); - CGAL::print_dag(l1_, os, level+1); - CGAL::print_dag(l2_, os, level+1); - } - } -#endif -}; - - -template -class Lazy_rep_2_1 final - : public Lazy_rep - , private EC -{ - typedef typename R1::AT AT; - typedef typename R1::ET ET; - typedef Lazy_rep Base; - - mutable L1 l1_; - mutable L2 l2_; - - const EC& ec() const { return *this; } - -public: - - void - update_exact() const - { - auto* p = new typename Base::Indirect(); - ec()(CGAL::exact(l1_), CGAL::exact(l2_), p->et_); - this->set_at(p); - this->set_ptr(p); - // Prune lazy tree - lazy_reset_member(l1_); - lazy_reset_member(l2_); - } - - Lazy_rep_2_1(const AC& ac, const EC& /*ec*/, const L1& l1, const L2& l2) - : Lazy_rep(), l1_(l1), l2_(l2) - { - this->set_depth((std::max)(CGAL::depth(l1), CGAL::depth(l2))); - ac(CGAL::approx(l1), CGAL::approx(l2), this->at_orig.at_); - } - -#ifdef CGAL_LAZY_KERNEL_DEBUG - void - print_dag(std::ostream& os, int level) const - { - this->print_at_et(os, level); - os << "A Lazy_rep_2_1" << std::endl; - if(this->is_lazy()){ - CGAL::msg(os, level, "DAG with two child nodes:"); - CGAL::print_dag(l1_, os, level+1); - CGAL::print_dag(l2_, os, level+1); - } - } -#endif -}; - - -//____________________________________________________________________________________ -// The following rep class stores two non-const reference parameters of type R1 and R2 - -template -class Lazy_rep_2_2 final - : public Lazy_rep, std::pair, E2A> - , private EC -{ - typedef std::pair AT; - typedef std::pair ET; - typedef Lazy_rep Base; - - mutable L1 l1_; - mutable L2 l2_; - - const EC& ec() const { return *this; } - -public: - - void - update_exact() const - { - auto* p = new typename Base::Indirect(); - ec()(CGAL::exact(l1_), CGAL::exact(l2_), p->et_.first, p->et_.second ); - this->set_at(p); - this->set_ptr(p); - // Prune lazy tree - lazy_reset_member(l1_); - lazy_reset_member(l2_); - } - - Lazy_rep_2_2(const AC& ac, const EC& /*ec*/, const L1& l1, const L2& l2) - : Lazy_rep(), l1_(l1), l2_(l2) - { - this->set_depth((std::max)(CGAL::depth(l1), CGAL::depth(l2))); - ac(CGAL::approx(l1), CGAL::approx(l2), this->at_orig.at_.first, this->at_orig.at_.second); - } - -#ifdef CGAL_LAZY_KERNEL_DEBUG - void - print_dag(std::ostream& os, int level) const - { - this->print_at_et(os, level); - os << "A Lazy_rep_2_2" << std::endl; - if(this->is_lazy()){ - CGAL::msg(os, level, "DAG with two child nodes:"); - CGAL::print_dag(l1_, os, level+1); - CGAL::print_dag(l2_, os, level+1); - } - } -#endif -}; - - //____________________________________________________________ // The handle class template @@ -1181,41 +970,8 @@ public : Self_rep * ptr() const { return (Self_rep*) PTR; } }; -// The magic functor for Construct_bbox_[2,3], as there is no Lazy - template -struct Lazy_construction_bbox -{ - static const bool Protection = true; - typedef typename LK::Approximate_kernel AK; - typedef typename LK::Exact_kernel EK; - typedef typename AC::result_type result_type; - - CGAL_NO_UNIQUE_ADDRESS AC ac; - CGAL_NO_UNIQUE_ADDRESS EC ec; - - template - decltype(auto) - operator()(const L1& l1) const - { - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - // Protection is outside the try block as VC8 has the CGAL_CFG_FPU_ROUNDING_MODE_UNWINDING_VC_BUG - Protect_FPU_rounding P; - try { - return ac(CGAL::approx(l1)); - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - return ec(CGAL::exact(l1)); - } -}; - - -template -struct Lazy_construction_optional_for_polygonal_envelope +struct Lazy_construction_optional_for_polyhedral_envelope { static const bool Protection = true; typedef typename LK::Approximate_kernel AK; @@ -1301,7 +1057,7 @@ struct Lazy_construction_optional_for_polygonal_envelope } }; - +// used in Newkernel_d template struct Lazy_construction_nt { Lazy_construction_nt(){} @@ -1318,10 +1074,10 @@ struct Lazy_construction_nt { template auto operator()(L const&...l) const -> - Lazy_exact_nt>> + Lazy_exact_nt> { - typedef std::remove_cv_t> ET; - typedef std::remove_cv_t> AT; + typedef CGAL::cpp20::remove_cvref_t ET; + typedef CGAL::cpp20::remove_cvref_t AT; CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); { Protect_FPU_rounding P; @@ -1378,51 +1134,7 @@ CGAL_Kernel_obj(Point_3) return Object(); } - -// This functor selects the i'th element in a vector of Object's -// and casts it to what is in the Object - -template -struct Ith { - typedef T2 result_type; - - // We keep a Sign member object - // for future utilization, in case - // we have pairs of 2 T2 objects e.g. - // for a numeric_point vector returned - // from a construction of a possible - // lazy algebraic kernel - - int i; - Sign sgn; - - Ith(int i_) - : i(i_) - {sgn=NEGATIVE;} - - Ith(int i_, bool b_) - : i(i_) - { sgn= (b_) ? POSITIVE : ZERO;} - - const T2& - operator()(const std::vector& v) const - { - if(sgn==NEGATIVE) - return *object_cast(&v[i]); - - typedef std::pair Pair_type_1; - typedef std::pair > Pair_type_2; - - if(const Pair_type_1 *p1 = object_cast(&v[i])) - return p1->first; - else if(const Pair_type_2 *p2 = object_cast(&v[i])) - return p2->first; - - CGAL_error_msg( " Unexpected encapsulated type "); - } -}; - -// 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; @@ -1440,33 +1152,6 @@ struct Ith_for_intersection { } }; -// This functor selects the i'th element in a vector of T2's -template -struct Ith_for_intersection_with_variant { - typedef T2 result_type; - int i; - - Ith_for_intersection_with_variant(int i_) - : i(i_) - {} - - template< typename ... U > - const T2& - operator()(const std::optional< std::variant< U ... > >& o) const - { - const std::vector* ptr = (std::get_if >(&(*o))); - return (*ptr)[i]; - } - - template< typename ... U > - const T2& - operator()(const std::variant< U ... >& o) const - { - const std::vector* ptr = (std::get_if >(&o)); - return (*ptr)[i]; - } -}; - template struct Lazy_cartesian_const_iterator_2 { @@ -1524,181 +1209,6 @@ public: }; - -// This is the magic functor for functors that write their result in a reference argument -// In a first version we assume that the references are of type Lazy, -// and that the result type is void - -template -struct Lazy_functor_2_1 -{ - static const bool Protection = true; - typedef void result_type; - - CGAL_NO_UNIQUE_ADDRESS AC ac; - CGAL_NO_UNIQUE_ADDRESS EC ec; - -public: - - template - void - operator()(const L1& l1, const L2& l2, R1& r1) const - { - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - try { - // we suppose that R1 is a Lazy - r1 = R1(new Lazy_rep_2_1(ac, ec, l1, l2)); - return; - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - typename R1::ET et; - ec(CGAL::exact(l1), CGAL::exact(l2), et); - r1 = R1(new Lazy_rep_0(et)); - } -}; - - -template -struct First -{ - typedef typename T::first_type result_type; - - const typename T::first_type& - operator()(const T& p) const - { - return p.first; - } - }; - -template -struct Second -{ - typedef typename T::second_type result_type; - - const typename T::second_type& - operator()(const T& p) const - { - return p.second; - } -}; - -// This is the magic functor for functors that write their result in a reference argument -// In a first version we assume that the references are of type Lazy, -// and that the result type is void - -//template -template -struct Lazy_functor_2_2 -{ - static const bool Protection = true; - - typedef void result_type; - typedef typename LK::Approximate_kernel AK; - typedef typename LK::Exact_kernel EK; - typedef typename LK::E2A E2A; - - CGAL_NO_UNIQUE_ADDRESS AC ac; - CGAL_NO_UNIQUE_ADDRESS EC ec; - -public: - - template - void - operator()(const L1& l1, const L2& l2, R1& r1, R2& r2) const - { - typedef Lazy Handle_1; - typedef Lazy Handle_2; - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - try { - typedef Lazy, std::pair, E2A> Lazy_pair; - Lazy_pair lv(new Lazy_rep_2_2(ac, ec, l1, l2)); - // lv->approx() is a std::pair; - r1 = R1(Handle_1(new Lazy_rep_n >, First >, E2A, false, Lazy_pair>(First >(), First >(), lv))); - r2 = R2(Handle_2(new Lazy_rep_n >, Second >, E2A, false, Lazy_pair>(Second >(), Second >(), lv))); - return; - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - typename R1::ET et1, et2; - ec(CGAL::exact(l1), CGAL::exact(l2), et1, et2); - r1 = R1(Handle_1(new Lazy_rep_0(et1))); - r2 = R2(Handle_2(new Lazy_rep_0(et2))); - } -}; - - -// This is the magic functor for functors that write their result as Objects into an output iterator - -template -struct Lazy_intersect_with_iterators -{ - static const bool Protection = true; - typedef typename LK::Approximate_kernel AK; - typedef typename LK::Exact_kernel EK; - typedef typename LK::E2A E2A; - typedef void result_type; - typedef Lazy Lazy_object; - typedef Lazy, std::vector, E2A> Lazy_vector; - - CGAL_NO_UNIQUE_ADDRESS AC ac; - CGAL_NO_UNIQUE_ADDRESS EC ec; - -public: - - // In the example we intersect two Lazys - // and write into a back_inserter(list,Lazy]) >) - template - OutputIterator - operator()(const L1& l1, const L2& l2, OutputIterator it) const - { - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - try { - Lazy_vector lv(new Lazy_rep_with_vector_2(ac, ec, l1, l2)); - // lv.approx() is a std::vector - // that is, when we get here we have constructed all approximate results - for (unsigned int i = 0; i < lv.approx().size(); i++) { - // FIXME : I'm not sure how this work... - #define CGAL_Kernel_obj(X) if (object_cast(& (lv.approx()[i]))) { \ - *it++ = make_object(typename LK::X(new Lazy_rep_n, \ - Ith, E2A, false, Lazy_vector> \ - (Ith(i), Ith(i), lv))); \ - continue; \ - } - - #include - - std::cerr << "we need more casts" << std::endl; - } - return it; - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - // TODO: Instead of using a vector, write an iterator adapter - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - std::vector exact_objects; - ec(CGAL::exact(l1), CGAL::exact(l2), std::back_inserter(exact_objects)); - for (std::vector::const_iterator oit = exact_objects.begin(); - oit != exact_objects.end(); - ++oit){ - *it++ = make_lazy(*oit); - } - return it; - } -}; - - template struct Object_cast { @@ -1714,9 +1224,6 @@ struct Object_cast // The following functor returns an Object with a Lazy inside // As the nested kernels return Objects of AK::Something and EK::Something // we have to unwrap them from the Object, and wrap them in a Lazy -// -// TODO: write operators for other than two arguments. For the current kernel we only need two for Intersect_2 - template struct Lazy_construction_object { @@ -1735,16 +1242,15 @@ struct Lazy_construction_object CGAL_NO_UNIQUE_ADDRESS EC ec; public: - - template + template decltype(auto) - operator()(const L1& l1) const + operator()(const L&... l) const { CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); { Protect_FPU_rounding P; try { - Lazy_object lo(new Lazy_rep_n(ac, ec, l1)); + Lazy_object lo(new Lazy_rep_n(ac, ec, l...)); if(lo.approx().is_empty()) return Object(); @@ -1758,116 +1264,42 @@ public: #include - std::cerr << "object_cast inside Lazy_construction_rep::operator() failed. It needs more else if's (#1)" << std::endl; - std::cerr << "dynamic type of the Object : " << lo.approx().type().name() << std::endl; - - return Object(); - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - ET eto = ec(CGAL::exact(l1)); - return make_lazy(eto); - } - - template - decltype(auto) - operator()(const L1& l1, const L2& l2) const - { - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - try { - Lazy_object lo(new Lazy_rep_n(ac, ec, l1, l2)); - - if(lo.approx().is_empty()) - return Object(); - - #define CGAL_Kernel_obj(X) \ - if (object_cast(& (lo.approx()))) { \ - typedef Lazy_rep_n, Object_cast, E2A, false, Lazy_object> Lcr; \ - Lcr * lcr = new Lcr(Object_cast(), Object_cast(), lo); \ - return make_object(typename LK::X(lcr)); \ - } - - #include - // We now check vector - - #define CGAL_Kernel_obj(X) \ - { \ - const std::vector* v_ptr;\ +#define CGAL_Kernel_obj(X) \ + { \ + const std::vector* v_ptr; \ if ( (v_ptr = object_cast >(& (lo.approx()))) ) { \ - std::vector V;\ - V.resize(v_ptr->size()); \ - for (unsigned int i = 0; i < v_ptr->size(); i++) { \ + std::vector V; \ + V.resize(v_ptr->size()); \ + for (unsigned int i = 0; i < v_ptr->size(); i++) { \ V[i] = typename LK::X(new Lazy_rep_n, \ Ith_for_intersection, E2A, false, Lazy_object> \ (Ith_for_intersection(i), Ith_for_intersection(i), lo)); \ - } \ - return make_object(V); \ + } \ + return make_object(V); \ }\ } - CGAL_Kernel_obj(Point_2) - CGAL_Kernel_obj(Point_3) - #undef CGAL_Kernel_obj +CGAL_Kernel_obj(Point_2) +CGAL_Kernel_obj(Point_3) +#undef CGAL_Kernel_obj std::cerr << "object_cast inside Lazy_construction_rep::operator() failed. It needs more else if's (#1)" << std::endl; std::cerr << "dynamic type of the Object : " << lo.approx().type().name() << std::endl; - } catch (Uncertain_conversion_exception&) {} - return Object(); - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - ET eto = ec(CGAL::exact(l1), CGAL::exact(l2)); - return make_lazy(eto); - } - - template - decltype(auto) - operator()(const L1& l1, const L2& l2, const L3& l3) const - { - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - try { - Lazy_object lo(new Lazy_rep_n(ac, ec, l1, l2, l3)); - - if(lo.approx().is_empty()) - return Object(); - - #define CGAL_Kernel_obj(X) \ - if (object_cast(& (lo.approx()))) { \ - typedef Lazy_rep_n, Object_cast, E2A, false, Lazy_object> Lcr; \ - Lcr * lcr = new Lcr(Object_cast(), Object_cast(), lo); \ - return make_object(typename LK::X(lcr)); \ - } - - #include - - std::cerr << "object_cast inside Lazy_construction_rep::operator() failed. It needs more else if's (#1)" << std::endl; - std::cerr << "dynamic type of the Object : " << lo.approx().type().name() << std::endl; return Object(); } catch (Uncertain_conversion_exception&) {} } CGAL_BRANCH_PROFILER_BRANCH(tmp); Protect_FPU_rounding P2(CGAL_FE_TONEAREST); CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - ET eto = ec(CGAL::exact(l1), CGAL::exact(l2), CGAL::exact(l3)); + ET eto = ec(CGAL::exact(l)...); return make_lazy(eto); } }; - - //____________________________________________________________ // The magic functor that has Lazy as result type. -// Two versions are distinguished: one that needs to fiddle -// with decltype and another that can forward the result types. namespace internal { BOOST_MPL_HAS_XXX_TRAIT_DEF(result_type) @@ -1969,195 +1401,24 @@ struct Fill_lazy_variant_visitor_0 { } // internal -template -struct Lazy_construction_variant { - static const bool Protection = true; - - typedef typename LK::Approximate_kernel AK; - typedef typename LK::Exact_kernel EK; - typedef typename LK::E2A E2A; - - - template - struct result { - // this does not default, if you want to make a lazy lazy-kernel, - // you are on your own - }; - - template - struct result - { - typedef typename Type_mapper()(std::declval::type>()...)),AK,LK>::type type; - }; - - template - decltype(auto) - operator()(const L1& l1, const L2& l2) const { - - typedef typename result::type result_type; - - // typedef decltype(std::declval()(std::declval::type>(), - // std::declval::type>())) AT; - // typedef decltype(std::declval()(std::declval::type>(), - // std::declval::type>())) ET; - - typedef decltype(std::declval()(CGAL::approx(l1), CGAL::approx(l2))) AT; - typedef decltype(std::declval()( CGAL::exact(l1), CGAL::exact(l2))) ET; - - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - - try { - Lazy lazy(new Lazy_rep_n(AC(), EC(), l1, l2)); - - // the approximate result requires the trait with types from the AK - AT approx_v = lazy.approx(); - // the result we build - result_type res; - - if(!approx_v) { - // empty - return res; - } - - // the static visitor fills the result_type with the correct unwrapped type - internal::Fill_lazy_variant_visitor_2< result_type, AK, LK, EK, Lazy > visitor(res, lazy); - std::visit(visitor, *approx_v); - - return res; - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - ET exact_v = EC()(CGAL::exact(l1), CGAL::exact(l2)); - result_type res; - - if(!exact_v) { - return res; - } - - internal::Fill_lazy_variant_visitor_0 visitor(res); - std::visit(visitor, *exact_v); - return res; - } - - template - decltype(auto) - operator()(const L1& l1, const L2& l2, const L3& l3) const { - typedef typename result::type result_type; - - // typedef decltype(std::declval()(std::declval::type>(), - // std::declval::type>(), - // std::declval::type>())) AT; - // typedef decltype(std::declval()(std::declval::type>(), - // std::declval::type>(), - // std::declval::type>())) ET; - - typedef decltype(std::declval()(CGAL::approx(l1), CGAL::approx(l2), CGAL::approx(l3))) AT; - typedef decltype(std::declval()( CGAL::exact(l1), CGAL::exact(l2), CGAL::exact(l3))) ET; - - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - - try { - Lazy lazy(new Lazy_rep_n(AC(), EC(), l1, l2, l3)); - - // the approximate result requires the trait with types from the AK - AT approx_v = lazy.approx(); - // the result we build - result_type res; - - if(!approx_v) { - // empty - return res; - } - - // the static visitor fills the result_type with the correct unwrapped type - internal::Fill_lazy_variant_visitor_2< result_type, AK, LK, EK, Lazy > visitor(res, lazy); - std::visit(visitor, *approx_v); - - return res; - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - ET exact_v = EC()(CGAL::exact(l1), CGAL::exact(l2), CGAL::exact(l3)); - result_type res; - - if(!exact_v) { - return res; - } - - internal::Fill_lazy_variant_visitor_0< result_type, AK, LK, EK> visitor(res); - std::visit(visitor, *exact_v); - return res; - } +template +struct Disable_lazy_pruning +{ + static const bool value = false; +}; +template +struct Disable_lazy_pruning +{ + static const bool value = true; +}; +template +struct Disable_lazy_pruning +{ + static const bool value = true; }; -template::value && internal::has_result_type::value > -struct Lazy_construction; - -template struct Disable_lazy_pruning { static const bool value = false; }; -template struct Disable_lazy_pruning { static const bool value = true; }; -template struct Disable_lazy_pruning { static const bool value = true; }; - -// we have a result type, low effort -template -struct Lazy_construction { - static const bool Protection = true; - - typedef typename LK::Approximate_kernel AK; - typedef typename LK::Exact_kernel EK; - typedef std::remove_cv_t< - std::remove_reference_t < typename AC::result_type > > AT; - typedef std::remove_cv_t< - std::remove_reference_t < typename EC::result_type > > ET; - - typedef typename Default::Get::type E2A; - - typedef typename Type_mapper::type result_type; - - static const bool noprune = Disable_lazy_pruning::value; - - CGAL_NO_UNIQUE_ADDRESS AC ac; - CGAL_NO_UNIQUE_ADDRESS EC ec; - - template - decltype(auto) - operator()(const L&... l) const { - typedef Lazy < AT, ET, E2A > Handle; - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); - { - Protect_FPU_rounding P; - try { - return result_type(Handle(new Lazy_rep_n< AT, ET, AC, EC, E2A, noprune, L...>(ac, ec, l...))); - } catch (Uncertain_conversion_exception&) {} - } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - return result_type(Handle(new Lazy_rep_0< AT, ET, E2A >(ec(CGAL::exact(l)...)))); - } - - - // nullary - decltype(auto) - operator()() const - { - typedef Lazy Handle; - return result_type( Handle() ); - } - -}; - - -template -struct Lazy_construction +template +struct Lazy_construction { static const bool Protection = true; @@ -2165,49 +1426,188 @@ struct Lazy_construction typedef typename LK::Exact_kernel EK; typedef typename Default::Get::type E2A; - template - struct result { - // this does not default, if you want to make a lazy lazy-kernel, - // you are on your own - }; - - static const bool noprune = Disable_lazy_pruning::value; - CGAL_NO_UNIQUE_ADDRESS AC ac; CGAL_NO_UNIQUE_ADDRESS EC ec; - template - struct result - { - typedef typename Type_mapper()(std::declval::type>()...)),AK,LK>::type type; - }; + // to detect the return type of Intersection_[23]'s functors + template + struct is_optional_variant : std::false_type { }; + + template + struct is_optional_variant > > : std::true_type { }; template decltype(auto) - operator()(const L&... l) const { - typedef typename Type_mapper()(std::declval::type>()...)),EK,EK>::type ET; - typedef typename Type_mapper()(std::declval::type>()...)),AK,AK>::type AT; - typedef Lazy Handle; - typedef typename result::type result_type; - CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); + operator()(const L&... l) const + { + typedef typename Type_mapper::type...>, AK, AK>::type AT; + + // ----------------------- FT ----------------------- + if constexpr (std::is_same_v) { - Protect_FPU_rounding P; - try { - return result_type(Handle(new Lazy_rep_n (ac, ec, l...))); - } catch (Uncertain_conversion_exception&) {} + typedef typename Type_mapper::type...>,EK,EK>::type ET; + + typedef Lazy_exact_nt> result_type; + + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); + { + Protect_FPU_rounding P; + try { + return result_type(new Lazy_rep_n, false, L... >(ac, ec, l...)); + } catch (Uncertain_conversion_exception&) {} + } + CGAL_BRANCH_PROFILER_BRANCH(tmp); + Protect_FPU_rounding P2(CGAL_FE_TONEAREST); + CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); + return result_type(new Lazy_rep_0 >(ec( CGAL::exact(l)... ))); + } + // ----------------------- Bounding boxes ----------------------- + else if constexpr (std::disjunction_v, + std::is_same >) + { + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); + { + // Protection is outside the try block as VC8 has the CGAL_CFG_FPU_ROUNDING_MODE_UNWINDING_VC_BUG + Protect_FPU_rounding P; + try { + return ac(CGAL::approx(l...)); + } catch (Uncertain_conversion_exception&) {} + } + CGAL_BRANCH_PROFILER_BRANCH(tmp); + Protect_FPU_rounding P2(CGAL_FE_TONEAREST); + CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); + return ec(CGAL::exact(l...)); + } + // ----------------------- CGAL::Object ----------------------- + else if constexpr (std::is_same_v) + { + typedef Lazy Lazy_object; + + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); + { + Protect_FPU_rounding P; + try { + Lazy_object lo(new Lazy_rep_n(ac, ec, l...)); + + if(lo.approx().is_empty()) + return Object(); + +# define CGAL_Kernel_obj(X) \ + if (object_cast(& (lo.approx()))) { \ + typedef Lazy_rep_n< typename AK::X, typename EK::X, Object_cast, Object_cast, E2A, false, Lazy_object> Lcr; \ + Lcr * lcr = new Lcr(Object_cast(), Object_cast(), lo); \ + return make_object(typename LK::X(lcr)); \ + } + +# include + + // We now check vector +# define CGAL_Kernel_obj(X) \ + { \ + const std::vector* v_ptr; \ + if ( (v_ptr = object_cast >(& (lo.approx()))) ) { \ + std::vector V; \ + V.resize(v_ptr->size()); \ + for (unsigned int i = 0; i < v_ptr->size(); i++) { \ + V[i] = typename LK::X(new Lazy_rep_n, \ + Ith_for_intersection, E2A, false, Lazy_object> \ + (Ith_for_intersection(i), Ith_for_intersection(i), lo)); \ + } \ + return make_object(V); \ + }\ + } + + CGAL_Kernel_obj(Point_2) + CGAL_Kernel_obj(Point_3) +# undef CGAL_Kernel_obj + + std::cerr << "object_cast inside Lazy_construction_rep::operator() failed. It needs more else if's (#1)" << std::endl; + std::cerr << "dynamic type of the Object : " << lo.approx().type().name() << std::endl; + + return Object(); + } catch (Uncertain_conversion_exception&) {} + } + CGAL_BRANCH_PROFILER_BRANCH(tmp); + Protect_FPU_rounding P2(CGAL_FE_TONEAREST); + CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); + CGAL::Object eto = ec(CGAL::exact(l)...); + return make_lazy(eto); + } + // std::optional > (Intersection_23 result types) + else if constexpr (is_optional_variant::value) + { + typedef typename Type_mapper::type...>,EK,EK>::type ET; + + typedef typename Type_mapper::type...>, AK, LK>::type result_type; + + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); + { + Protect_FPU_rounding P; + + try { + Lazy lazy(new Lazy_rep_n(AC(), EC(), l...)); + + // the approximate result requires the trait with types from the AK + AT approx_v = lazy.approx(); + // the result we build + result_type res; + + if(!approx_v) { + // empty + return res; + } + + // the static visitor fills the result_type with the correct unwrapped type + internal::Fill_lazy_variant_visitor_2< result_type, AK, LK, EK, Lazy > visitor(res, lazy); + std::visit(visitor, *approx_v); + + return res; + } catch (Uncertain_conversion_exception&) {} + } + CGAL_BRANCH_PROFILER_BRANCH(tmp); + Protect_FPU_rounding P2(CGAL_FE_TONEAREST); + CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); + ET exact_v = EC()(CGAL::exact(l)...); + result_type res; + + if(!exact_v) { + return res; + } + + internal::Fill_lazy_variant_visitor_0 visitor(res); + std::visit(visitor, *exact_v); + return res; + } + // ----------------------- GENERIC ----------------------- + else + { + typedef typename Type_mapper::type...>,EK,EK>::type ET; + + typedef Lazy Handle; + typedef typename Type_mapper::type...>, AK, LK>::type result_type; + + static const bool noprune = Disable_lazy_pruning::value; + + CGAL_BRANCH_PROFILER(std::string(" failures/calls to : ") + std::string(CGAL_PRETTY_FUNCTION), tmp); + { + Protect_FPU_rounding P; + try { + return result_type(Handle(new Lazy_rep_n(ac, ec, l...))); + } catch (Uncertain_conversion_exception&) {} + } + CGAL_BRANCH_PROFILER_BRANCH(tmp); + Protect_FPU_rounding P2(CGAL_FE_TONEAREST); + CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); + return result_type(Handle(new Lazy_rep_0 (ec(CGAL::exact(l)...)))); } - CGAL_BRANCH_PROFILER_BRANCH(tmp); - Protect_FPU_rounding P2(CGAL_FE_TONEAREST); - CGAL_expensive_assertion(FPU_get_cw() == CGAL_FE_TONEAREST); - return result_type(Handle(new Lazy_rep_0 (ec(CGAL::exact(l)...)))); } // nullary decltype(auto) operator()() const { - typedef decltype(std::declval()()) AT; - typedef decltype(std::declval()()) ET; + typedef std::invoke_result_t AT; + typedef std::invoke_result_t ET; typedef Lazy Handle; typedef typename Type_mapper::type result_type; diff --git a/Filtered_kernel/include/CGAL/Lazy_kernel.h b/Filtered_kernel/include/CGAL/Lazy_kernel.h index a6b2ac165ba..819ed0fa8ee 100644 --- a/Filtered_kernel/include/CGAL/Lazy_kernel.h +++ b/Filtered_kernel/include/CGAL/Lazy_kernel.h @@ -15,7 +15,7 @@ #include //#include -#include +#include #include #include #include @@ -37,62 +37,14 @@ namespace CGAL { -namespace internal { - -// SFINAE way to detect result_type typedefs. -template -class Has_result_type_helper -{ - typedef char one; - typedef struct { char arr[2]; } two; - - template - struct Wrapper {}; - - template - static one test(Wrapper*); - - template - static two test(...); - -public: - static const bool value = sizeof(test(0)) == 1; -}; - -template -struct Has_result_type - : std::integral_constant< bool, - Has_result_type_helper< std::remove_cv_t>::value> -{}; - -template -struct Get_result_type { - typedef typename T::result_type type; -}; - -template -struct Lazy_result_type - : boost::mpl::eval_if< Has_result_type, - Get_result_type, - boost::mpl::identity > -{}; - -class Enum_holder { -protected: - enum { NONE, NT, VARIANT, OBJECT, BBOX, OPTIONAL_ }; -}; - -} // internal - // Exact_kernel = exact kernel that will be made lazy // Kernel = lazy kernel -// the Generic base simply applies the generic magic functor stupidly. -// then the real base fixes up a few special cases. +// `Lazy_kernel_generic_base` applies the generic magic functor stupidly. +// `Lazy_kernel_base` fixes up a few special cases. template < typename EK_, typename AK_, typename E2A_, typename Kernel_ > -class Lazy_kernel_generic_base : protected internal::Enum_holder +class Lazy_kernel_generic_base // : public Filtered_kernel_base - // TODO : Static_filters_base too ? Check performance { public: @@ -127,9 +79,18 @@ public: typedef typename Exact_kernel::Rep_tag Rep_tag; enum { Has_filtered_predicates = true }; - enum { Has_static_filters = false }; typedef Boolean_tag Has_filtered_predicates_tag; +#ifdef CGAL_NO_STATIC_FILTERS_FOR_LAZY_KERNEL + enum { Has_static_filters = false }; +#else + // @fixme, this should be 'true' but it's broken because EPIC_predicate_if_convertible + // assumes the static filtered predicate and the (non-static) filtered predicate + // have the same signature, which is not always the case, for example in + // Do_intersect_3(Sphere_3, Bbox_3, *bool*) + enum { Has_static_filters = false }; +#endif + // Types typedef CGAL::Lazy_exact_nt FT; typedef FT RT; @@ -168,115 +129,26 @@ public: typedef CGAL::Aff_transformationC2 Aff_transformation_2; typedef CGAL::Aff_transformationC3 Aff_transformation_3; -private: - // We use a combination of partial and logic to extract the right - // construction. Constructions without a result_type always have to - // be done through specializations. - // - // The case distinction goes as follows: - // result_type == FT => NT - // result_type == Object => Object - // result_type == std::optional => OPTIONAL_ Only for Intersect_point_3_for_polyhedral_envelope which returns a handle for a singleton - // result_type == Bbox_2 || result_type == Bbox_3 => BBOX - // default => NONE - // no result_type => NONE - // - // - // we require a Dummy because we cannot have complete - // specializations inside a non-namespace scope. - // The default implementation does some default handling, - // the special cases are filtered by partial specializations. - template - struct Lazy_wrapper_traits : - boost::mpl::eval_if< internal::Has_result_type, - boost::mpl::eval_if< std::is_same< std::remove_cv_t< - std::remove_reference_t< - typename internal::Lazy_result_type::type - > >, - typename Approximate_kernel::FT>, - boost::mpl::int_, - boost::mpl::eval_if< std::is_same< typename internal::Lazy_result_type::type, - CGAL::Object >, - boost::mpl::int_, - boost::mpl::eval_if< std::bool_constant< - std::is_same_v< typename internal::Lazy_result_type::type, CGAL::Bbox_2 > || - std::is_same_v< typename internal::Lazy_result_type::type, CGAL::Bbox_3 > >, - boost::mpl::int_, - boost::mpl::int_ > > >, - boost::mpl::int_ >::type {}; - -#define CGAL_WRAPPER_TRAIT(NAME, WRAPPER) \ - template \ - struct Lazy_wrapper_traits \ - : boost::mpl::int_ {}; - - CGAL_WRAPPER_TRAIT(Intersect_2, VARIANT) - CGAL_WRAPPER_TRAIT(Intersect_3, VARIANT) - CGAL_WRAPPER_TRAIT(Intersect_point_3_for_polyhedral_envelope, OPTIONAL_) - CGAL_WRAPPER_TRAIT(Compute_squared_radius_2, NT) - CGAL_WRAPPER_TRAIT(Compute_x_3, NT) - CGAL_WRAPPER_TRAIT(Compute_y_3, NT) - CGAL_WRAPPER_TRAIT(Compute_z_3, NT) - -#undef CGAL_WRAPPER_TRAIT - - template ::value> - struct Select_wrapper_impl; - - template - struct Select_wrapper_impl { - template - struct apply { typedef Lazy_construction type; }; - }; - - template - struct Select_wrapper_impl { - template - struct apply { typedef Lazy_construction_nt type; }; - }; - - template - struct Select_wrapper_impl { - template - struct apply { typedef Lazy_construction_variant type; }; - }; - - template - struct Select_wrapper_impl { - template - struct apply { typedef Lazy_construction_object type; }; - }; - - template - struct Select_wrapper_impl { - template - struct apply { typedef Lazy_construction_bbox type; }; - }; - - template - struct Select_wrapper_impl { - template - struct apply { typedef Lazy_construction_optional_for_polygonal_envelope type; }; - }; - - template - struct Select_wrapper : Select_wrapper_impl {}; - public: - - #ifdef CGAL_NO_STATIC_FILTERS_FOR_LAZY_KERNEL #define CGAL_Kernel_pred(P, Pf) \ typedef Filtered_predicate P; \ P Pf() const { return P(); } #else -#define CGAL_Kernel_pred(P, Pf) \ - typedef Static_filtered_predicate, Exact_predicates_inexact_constructions_kernel::P> P; \ +// - the first template parameter is because either it fits in a double, or not, so +// we might as well use the approximate kernel directly rather than the complete lazy kernel +// - the second is the predicate to be called if EPICK is not usable +// - the third is the equivalent predicate in EPICK +#define CGAL_Kernel_pred(P, Pf) \ + typedef EPIC_predicate_if_convertible, \ + Exact_predicates_inexact_constructions_kernel::P> P; \ P Pf() const { return P(); } #endif #define CGAL_Kernel_cons(C, Cf) \ - typedef typename Select_wrapper::template apply::type C; \ + typedef Lazy_construction C; \ C Cf() const { return C(); } #include @@ -287,11 +159,6 @@ public: struct Handle { typedef T type; }; }; - - - - - template < typename EK_, typename AK_, typename E2A_, typename Kernel_ > class Lazy_kernel_base : public Lazy_kernel_generic_base @@ -308,18 +175,16 @@ public: typedef CommonKernelFunctors::Assign_2 Assign_2; typedef CommonKernelFunctors::Assign_3 Assign_3; - typedef Lazy_construction_bbox Construct_bbox_2; - typedef Lazy_construction_bbox Construct_bbox_3; typedef Lazy_cartesian_const_iterator_2 Construct_cartesian_const_iterator_2; typedef Lazy_cartesian_const_iterator_3 Construct_cartesian_const_iterator_3; typedef CGAL::CartesianKernelFunctors::Compute_approximate_squared_length_3 Compute_approximate_squared_length_3; typedef CGAL::CartesianKernelFunctors::Compute_approximate_area_3 Compute_approximate_area_3; - // typedef void Compute_z_3; // to detect where .z() is called - // typedef void Construct_point_3; // to detect where the ctor is called - - + typedef CGAL::Lazy_construction_optional_for_polyhedral_envelope< + Kernel, + typename Approximate_kernel::Intersect_point_3_for_polyhedral_envelope, + typename Exact_kernel::Intersect_point_3_for_polyhedral_envelope> Intersect_point_3_for_polyhedral_envelope; struct Compute_weight_2 : public BaseClass::Compute_weight_2 { @@ -552,7 +417,6 @@ public: }; - struct Less_xyz_3 : public BaseClass::Less_xyz_3 { typedef typename Kernel_::Point_3 Point_3; @@ -593,14 +457,6 @@ public: assign_3_object() const { return Assign_3(); } - Construct_bbox_2 - construct_bbox_2_object() const - { return Construct_bbox_2(); } - - Construct_bbox_3 - construct_bbox_3_object() const - { return Construct_bbox_3(); } - Construct_cartesian_const_iterator_2 construct_cartesian_const_iterator_2_object() const { return Construct_cartesian_const_iterator_2(); } @@ -617,6 +473,10 @@ public: compute_approximate_area_3_object() const { return Compute_approximate_area_3(); } + Intersect_point_3_for_polyhedral_envelope + intersect_point_3_for_polyhedral_envelope_object() const + { return Intersect_point_3_for_polyhedral_envelope(); } + Less_xyz_3 less_xyz_3_object() const { return Less_xyz_3(); } diff --git a/Filtered_kernel/include/CGAL/Static_filtered_predicate.h b/Filtered_kernel/include/CGAL/Static_filtered_predicate.h deleted file mode 100644 index 9ff3aea5d1c..00000000000 --- a/Filtered_kernel/include/CGAL/Static_filtered_predicate.h +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) 2017 GeometryFactory -// 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, Laurent Rineau - -#ifndef CGAL_STATIC_FILTERED_PREDICATE_H -#define CGAL_STATIC_FILTERED_PREDICATE_H - -#include - -namespace CGAL { - -template -class Static_filtered_predicate { -public: - FP fp; - EpicP epicp; - typedef typename AK::FT IA; - typedef typename FP::result_type result_type; - - template - result_type operator()(const A1& a1) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1); - } - - return epicp(aa1.first); - } - - - template - result_type operator()(const A1& a1, const A2& a2) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2); - } - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2); - } - return epicp(aa1.first, aa2.first); - } - - - template - result_type operator()(const A1& a1, const A2& a2, const A3& a3) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2, a3); - } - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2, a3); - } - auto aa3 = convert(approx(a3)); - if(! aa3.second){ - return fp(a1, a2, a3); - } - return epicp(aa1.first, aa2.first, aa3.first); - } - - - template - result_type operator()(const A1& a1, const A2& a2, const A3& a3, const A4& a4) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2, a3, a4); - } - - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2, a3, a4); - } - - auto aa3 = convert(approx(a3)); - if(! aa3.second){ - return fp(a1, a2, a3, a4); - } - - auto aa4 = convert(approx(a4)); - if(! aa4.second){ - return fp(a1, a2, a3, a4); - } - return epicp(aa1.first, aa2.first, aa3.first, aa4.first); - } - - template - result_type operator()(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2, a3, a4, a5); - } - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2, a3, a4, a5); - } - auto aa3 = convert(approx(a3)); - if(! aa3.second){ - return fp(a1, a2, a3, a4, a5); - } - auto aa4 = convert(approx(a4)); - if(! aa4.second){ - return fp(a1, a2, a3, a4, a5); - } - auto aa5 = convert(approx(a5)); - if(! aa5.second){ - return fp(a1, a2, a3, a4, a5); - } - return epicp(aa1.first, aa2.first, aa3.first, aa4.first, aa5.first); - } - - template - result_type operator()(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2, a3, a4, a5, a6); - } - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2, a3, a4, a5, a6); - } - auto aa3 = convert(approx(a3)); - if(! aa3.second){ - return fp(a1, a2, a3, a4, a5, a6); - } - auto aa4 = convert(approx(a4)); - if(! aa4.second){ - return fp(a1, a2, a3, a4, a5, a6); - } - auto aa5 = convert(approx(a5)); - if(! aa5.second){ - return fp(a1, a2, a3, a4, a5, a6); - } - auto aa6 = convert(approx(a6)); - if(! aa6.second){ - return fp(a1, a2, a3, a4, a5, a6); - } - return epicp(aa1.first, aa2.first, aa3.first, aa4.first, aa5.first, aa6.first); - } - - template - result_type operator()(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A6& a7) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - auto aa3 = convert(approx(a3)); - if(! aa3.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - auto aa4 = convert(approx(a4)); - if(! aa4.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - auto aa5 = convert(approx(a5)); - if(! aa5.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - auto aa6 = convert(approx(a6)); - if(! aa6.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - auto aa7 = convert(approx(a7)); - if(! aa7.second){ - return fp(a1, a2, a3, a4, a5, a6, a7); - } - return epicp(aa1.first, aa2.first, aa3.first, aa4.first, aa5.first, aa6.first, aa7.first); - } - - - template - result_type operator()(const A1& a1, const A2& a2, const A3& a3, const A4& a4, const A5& a5, const A6& a6, const A7& a7, const A8& a8) const - { - CGAL::Epic_converter convert; - auto aa1 = convert(approx(a1)); - if(! aa1.second){ - return fp(a1, a2, a3, a4, a5, a6, a7, a8); - } - auto aa2 = convert(approx(a2)); - if(! aa2.second){ - return fp(a1, a2, a3, a4, a5, a6, a7, a8); - } - auto aa3 = convert(approx(a3)); - if(! aa3.second){ - return fp(a1, a2, a3, a4, a5, a6, a7, a8); - } - auto aa4 = convert(approx(a4)); - if(! aa4.second){ - return fp(a1, a2, a3, a4, a5, a6, a7, a8); - } - auto aa5 = convert(approx(a5)); - if(! aa5.second){ - return fp(a1, a2, a3, a4, a5, a6, a7, a8); - } - auto aa6 = convert(approx(a6)); - if(! aa6.second){ - return fp(a1, a2, a3, a5, a5, a6, a7, a8); - } - auto aa7 = convert(approx(a7)); - if(! aa7.second){ - return fp(a1, a2, a3, a5, a5, a6, a7, a8); - } - auto aa8 = convert(approx(a8)); - if(! aa8.second){ - return fp(a1, a2, a3, a4, a5, a6, a7, a8); - } - return epicp(aa1.first, aa2.first, aa3.first, aa4.first, aa5.first, aa6.first, aa7.first, aa8.first); - } -}; - -} // CGAL - -#endif // CGAL_STATIC_FILTERED_PREDICATE_H diff --git a/Generalized_map/include/CGAL/GMap_dart_iterators.h b/Generalized_map/include/CGAL/GMap_dart_iterators.h index 3e8b843eb6f..6a4c611bdaa 100644 --- a/Generalized_map/include/CGAL/GMap_dart_iterators.h +++ b/Generalized_map/include/CGAL/GMap_dart_iterators.h @@ -134,7 +134,7 @@ namespace CGAL { //**************************************************************************** /* Class CMap_dart_iterator_basic_of_two_alpha: to iterate * on the darts of the orbit : Ai1 (ie at most 4 darts). + * This general case if for delta>1 (i.e. at most 4 darts). * Basic classes do not guaranty correct marks (i.e. do not unmark darts in * the destructor, possible problem with the rewind). If you are not sure, * use CMap_dart_iterator_basic_of_two_alpha. @@ -445,7 +445,7 @@ namespace CGAL { /* Class CMap_dart_iterator_basic_of_three_alpha: to iterate * on the darts of the orbit : * Ai1 and delta2>1 (ie at most 8 darts). + * This general case if for delta1>1 and delta2>1 (i.e. at most 8 darts). * Basic classes do not guaranty correct marks (i.e. do not unmark darts in * the destructor, possible problem with the rewind). If you are not sure, * use CMap_dart_iterator_basic_of_two_alpha. diff --git a/Generalized_map/include/CGAL/Generalized_map.h b/Generalized_map/include/CGAL/Generalized_map.h index 4af0ee3a09a..acb4cc583da 100644 --- a/Generalized_map/include/CGAL/Generalized_map.h +++ b/Generalized_map/include/CGAL/Generalized_map.h @@ -876,7 +876,7 @@ namespace CGAL { { return mnb_used_marks; } /** Test if a given mark is reserved. - * @return true iff the mark is reserved (ie in used). + * @return true iff the mark is reserved (i.e. in used). */ bool is_reserved(size_type amark) const { diff --git a/GraphicsView/include/CGAL/Qt/ImageInterface.ui b/GraphicsView/include/CGAL/Qt/ImageInterface.ui index 0dfa667b8a8..dcc25b0cce0 100644 --- a/GraphicsView/include/CGAL/Qt/ImageInterface.ui +++ b/GraphicsView/include/CGAL/Qt/ImageInterface.ui @@ -141,7 +141,7 @@ - Antialiases image (when larger then 1.0) + Antialiases image (when larger than 1.0) x diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/CircleH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/CircleH2.h index f2414a459e4..e46139d0cd6 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/CircleH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/CircleH2.h @@ -84,20 +84,20 @@ public: CircleH2 opposite() const; - Oriented_side + typename R_::Oriented_side oriented_side(const Point_2& ) const; - Bounded_side + typename R_::Bounded_side bounded_side(const Point_2& ) const; bool operator==( const CircleH2& ) const; bool operator!=( const CircleH2& ) const; - bool has_on_positive_side(const Point_2& ) const; - bool has_on_negative_side(const Point_2& ) const; - bool has_on_boundary( const Point_2& ) const; - bool has_on_bounded_side( const Point_2& ) const; - bool has_on_unbounded_side(const Point_2&) const; - bool is_degenerate() const; + typename R_::Boolean has_on_positive_side(const Point_2& ) const; + typename R_::Boolean has_on_negative_side(const Point_2& ) const; + typename R_::Boolean has_on_boundary( const Point_2& ) const; + typename R_::Boolean has_on_bounded_side( const Point_2& ) const; + typename R_::Boolean has_on_unbounded_side(const Point_2&) const; + typename R_::Boolean is_degenerate() const; // bool oriented_equal( const CircleH2& ) const; // bool unoriented_equal( const CircleH2& ) const; @@ -133,17 +133,15 @@ CircleH2::orientation() const template CGAL_KERNEL_INLINE -Oriented_side +typename R::Oriented_side CircleH2::oriented_side( const typename CircleH2::Point_2& p) const { FT sq_dist = squared_distance( p, center() ); - FT sq_rad = squared_radius(); - Comparison_result vgl = CGAL_NTS compare( sq_dist, sq_rad ); - Oriented_side rel_pos = (vgl == LARGER ) ? - ON_NEGATIVE_SIDE : - ( (vgl == SMALLER ) ? - ON_POSITIVE_SIDE : - ON_ORIENTED_BOUNDARY); + const FT& sq_rad = squared_radius(); + typename R::Comparison_result vgl = CGAL_NTS compare( sq_dist, sq_rad ); + typename R::Oriented_side rel_pos = (vgl == LARGER ) ? ON_NEGATIVE_SIDE + : ((vgl == SMALLER) ? ON_POSITIVE_SIDE + : ON_ORIENTED_BOUNDARY); if (orientation() == POSITIVE) { return rel_pos; } else // NEGATIVE @@ -152,7 +150,7 @@ CircleH2::oriented_side( const typename CircleH2::Point_2& p) const template CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleH2::has_on_positive_side(const typename CircleH2::Point_2& p) const { if ( orientation() == POSITIVE ) @@ -163,7 +161,7 @@ CircleH2::has_on_positive_side(const typename CircleH2::Point_2& p) const template CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleH2::has_on_boundary(const typename CircleH2::Point_2& p) const { FT sq_dist = squared_distance( p, center() ); @@ -173,7 +171,7 @@ CircleH2::has_on_boundary(const typename CircleH2::Point_2& p) const template CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleH2::has_on_negative_side( const typename CircleH2::Point_2&p) const { if ( orientation() == NEGATIVE ) @@ -188,12 +186,12 @@ CircleH2::has_on_negative_side( const typename CircleH2::Point_2&p) const template CGAL_KERNEL_INLINE -Bounded_side +typename R::Bounded_side CircleH2::bounded_side(const typename CircleH2::Point_2& p) const { FT sq_dist = squared_distance( p, center() ); - FT sq_rad = squared_radius(); - Comparison_result vgl = CGAL_NTS compare( sq_dist, sq_rad ); + const FT& sq_rad = squared_radius(); + typename R::Comparison_result vgl = CGAL_NTS compare( sq_dist, sq_rad ); return (vgl == LARGER ) ? ON_UNBOUNDED_SIDE : ( (vgl == SMALLER ) ? ON_BOUNDED_SIDE : @@ -202,33 +200,33 @@ CircleH2::bounded_side(const typename CircleH2::Point_2& p) const template CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleH2::has_on_bounded_side(const typename CircleH2::Point_2& p) const { FT sq_dist = squared_distance( p, center() ); - FT sq_rad = squared_radius(); + const FT& sq_rad = squared_radius(); return ( sq_dist < sq_rad ); } template CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleH2::has_on_unbounded_side(const typename CircleH2::Point_2&p) const { FT sq_dist = squared_distance( p, center() ); - FT sq_rad = squared_radius(); + const FT& sq_rad = squared_radius(); return ( sq_rad < sq_dist ); } template inline -bool +typename R::Boolean CircleH2::is_degenerate() const { return ( squared_radius() == FT(0) ); } template CGAL_KERNEL_INLINE -bool +typename R::Boolean CircleH2::operator==(const CircleH2& c) const { return ( center() == c.center() ) @@ -238,7 +236,7 @@ CircleH2::operator==(const CircleH2& c) const template inline -bool +typename R::Boolean CircleH2::operator!=(const CircleH2& c) const { return !(*this == c); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH2.h index b30267cf7d2..54e59877d8d 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH2.h @@ -64,9 +64,8 @@ public: : base( w > RT(0) ? CGAL::make_array(x, y, w) : CGAL::make_array(-x, -y, -w) ) {} - bool operator==( const DirectionH2& d) const; - bool operator!=( const DirectionH2& d) const; - + typename R_::Boolean operator==( const DirectionH2& d) const; + typename R_::Boolean operator!=( const DirectionH2& d) const; Vector_2 to_vector() const; @@ -81,7 +80,7 @@ public: template CGAL_KERNEL_INLINE -bool +typename R::Boolean DirectionH2::operator==( const DirectionH2& d) const { return ( ( x() * d.y() == y() * d.x() ) @@ -91,7 +90,7 @@ DirectionH2::operator==( const DirectionH2& d) const template inline -bool +typename R::Boolean DirectionH2::operator!=( const DirectionH2& d) const { return !(*this == d); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH3.h index a4098d72848..4faa0dc180a 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/DirectionH3.h @@ -66,10 +66,10 @@ public: : base( w >= RT(0) ? CGAL::make_array(x, y, z, w) : CGAL::make_array(-x, -y, -z, -w) ) {} - bool is_degenerate() const; + typename R::Boolean is_degenerate() const; - bool operator==( const DirectionH3& d) const; - bool operator!=( const DirectionH3& d) const; + typename R::Boolean operator==( const DirectionH3& d) const; + typename R::Boolean operator!=( const DirectionH3& d) const; Vector_3 to_vector() const; Vector_3 vector() const { return to_vector(); } @@ -87,7 +87,7 @@ public: template CGAL_KERNEL_INLINE -bool +typename R::Boolean DirectionH3::operator==( const DirectionH3& d) const { return ( ( hx()*d.hy() == hy()*d.hx() ) @@ -100,13 +100,13 @@ DirectionH3::operator==( const DirectionH3& d) const template inline -bool +typename R::Boolean DirectionH3::operator!=( const DirectionH3& d) const { return !operator==(d); } template CGAL_KERNEL_INLINE -bool +typename R::Boolean DirectionH3::is_degenerate() const { return ((hx() == RT(0)) && (hy() == RT(0)) && (hz() == RT(0))); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_cuboidH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_cuboidH3.h index e4830033289..35f2a2a29f0 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_cuboidH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_cuboidH3.h @@ -63,23 +63,21 @@ public: Iso_cuboidH3(const RT& min_hx, const RT& min_hy, const RT& min_hz, const RT& max_hx, const RT& max_hy, const RT& max_hz); - bool operator==(const Iso_cuboidH3& s) const; - bool operator!=(const Iso_cuboidH3& s) const; + typename R::Boolean operator==(const Iso_cuboidH3& s) const; + typename R::Boolean operator!=(const Iso_cuboidH3& s) const; const Point_3 & min BOOST_PREVENT_MACRO_SUBSTITUTION () const; const Point_3 & max BOOST_PREVENT_MACRO_SUBSTITUTION () const; Point_3 vertex(int i) const; Point_3 operator[](int i) const; - Iso_cuboidH3 - transform(const Aff_transformation_3& t) const; - Bounded_side - bounded_side(const Point_3& p) const; - bool has_on(const Point_3& p) const; - bool has_on_boundary(const Point_3& p) const; - bool has_on_bounded_side(const Point_3& p) const; - bool has_on_unbounded_side(const Point_3& p) const; - bool is_degenerate() const; + Iso_cuboidH3 transform(const Aff_transformation_3& t) const; + typename R::Bounded_side bounded_side(const Point_3& p) const; + typename R::Boolean has_on(const Point_3& p) const; + typename R::Boolean has_on_boundary(const Point_3& p) const; + typename R::Boolean has_on_bounded_side(const Point_3& p) const; + typename R::Boolean has_on_unbounded_side(const Point_3& p) const; + typename R::Boolean is_degenerate() const; FT xmin() const; FT ymin() const; FT zmin() const; @@ -189,14 +187,14 @@ Iso_cuboidH3(const RT& min_hx, const RT& min_hy, const RT& min_hz, template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean Iso_cuboidH3:: operator==(const Iso_cuboidH3& r) const { return ((this->min)() == (r.min)()) && ((this->max)() == (r.max)()); } template < class R > inline -bool +typename R::Boolean Iso_cuboidH3:: operator!=(const Iso_cuboidH3& r) const { return !(*this == r); } @@ -313,7 +311,7 @@ Iso_cuboidH3::operator[](int i) const template < class R > CGAL_KERNEL_MEDIUM_INLINE -Bounded_side +typename R::Bounded_side Iso_cuboidH3:: bounded_side(const typename Iso_cuboidH3::Point_3& p) const { @@ -337,34 +335,34 @@ bounded_side(const typename Iso_cuboidH3::Point_3& p) const template < class R > inline -bool +typename R::Boolean Iso_cuboidH3:: has_on_boundary(const typename Iso_cuboidH3::Point_3& p) const { return ( bounded_side(p) == ON_BOUNDARY ); } template < class R > inline -bool +typename R::Boolean Iso_cuboidH3::has_on(const typename Iso_cuboidH3::Point_3& p) const { return ( bounded_side(p) == ON_BOUNDARY ); } template < class R > inline -bool +typename R::Boolean Iso_cuboidH3:: has_on_bounded_side(const typename Iso_cuboidH3::Point_3& p) const { return ( bounded_side(p) == ON_BOUNDED_SIDE ); } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean Iso_cuboidH3:: has_on_unbounded_side(const typename Iso_cuboidH3::Point_3& p) const { return ( bounded_side(p) == ON_UNBOUNDED_SIDE ); } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean Iso_cuboidH3::is_degenerate() const { return ( ( (this->min)().hx() == (this->max)().hx() ) diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_rectangleH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_rectangleH2.h index 6bdde3dec91..65f9e220174 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_rectangleH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/Iso_rectangleH2.h @@ -52,7 +52,7 @@ public: const Point_2 & min BOOST_PREVENT_MACRO_SUBSTITUTION () const; const Point_2 & max BOOST_PREVENT_MACRO_SUBSTITUTION () const; - Bounded_side bounded_side(const Point_2& p) const; + typename R_::Bounded_side bounded_side(const Point_2& p) const; }; @@ -71,7 +71,7 @@ Iso_rectangleH2::max BOOST_PREVENT_MACRO_SUBSTITUTION () const template < class R > CGAL_KERNEL_INLINE -Bounded_side +typename R::Bounded_side Iso_rectangleH2:: bounded_side(const typename Iso_rectangleH2::Point_2& p) const { diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/LineH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/LineH2.h index ab69b965f35..66d45a41a1a 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/LineH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/LineH2.h @@ -47,8 +47,8 @@ public: LineH2(const RT& a, const RT& b, const RT& c) : base{a, b, c} {} - bool operator==(const LineH2& l) const; - bool operator!=(const LineH2& l) const; + typename R_::Boolean operator==(const LineH2& l) const; + typename R_::Boolean operator!=(const LineH2& l) const; const RT & a() const { return get_pointee_or_identity(base)[0]; } const RT & b() const { return get_pointee_or_identity(base)[1]; } @@ -58,7 +58,7 @@ public: template < class R > CGAL_KERNEL_MEDIUM_INLINE -bool +typename R::Boolean LineH2::operator==(const LineH2& l) const { if ( (a() * l.c() != l.a() * c() ) @@ -83,7 +83,7 @@ LineH2::operator==(const LineH2& l) const template < class R > inline -bool +typename R::Boolean LineH2::operator!=(const LineH2& l) const { return !(*this == l); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/PlaneH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/PlaneH3.h index 6b05951a064..5e7ae6ac99c 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/PlaneH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/PlaneH3.h @@ -67,8 +67,8 @@ public: const RT & c() const; const RT & d() const; - bool operator==( const PlaneH3& ) const; - bool operator!=( const PlaneH3& ) const; + typename R::Boolean operator==( const PlaneH3& ) const; + typename R::Boolean operator!=( const PlaneH3& ) const; Line_3 perpendicular_line(const Point_3& ) const; Plane_3 opposite() const; // plane with opposite orientation @@ -78,13 +78,13 @@ public: Direction_3 orthogonal_direction() const; Vector_3 orthogonal_vector() const; - Oriented_side oriented_side(const Point_3 &p) const; - bool has_on(const Point_3 &p) const; - bool has_on(const Line_3 &p) const; - bool has_on_positive_side(const Point_3&l) const; - bool has_on_negative_side(const Point_3&l) const; + typename R::Oriented_side oriented_side(const Point_3& p) const; + typename R::Boolean has_on(const Point_3& p) const; + typename R::Boolean has_on(const Line_3& p) const; + typename R::Boolean has_on_positive_side(const Point_3& l) const; + typename R::Boolean has_on_negative_side(const Point_3& l) const; - bool is_degenerate() const; + typename R::Boolean is_degenerate() const; Aff_transformation_3 transform_to_2d() const; Point_2 to_2d(const Point_3& ) const; @@ -163,7 +163,7 @@ PlaneH3::new_rep(const RT &a, const RT &b, const RT &c, const RT &d) template < class R > inline -bool +typename R::Boolean PlaneH3::operator!=(const PlaneH3& l) const { return !(*this == l); @@ -397,7 +397,7 @@ PlaneH3::orthogonal_vector() const { return Vector_3(a(), b(), c() ); } template < class R > -bool +typename R::Boolean PlaneH3::is_degenerate() const { const RT RT0(0); @@ -405,14 +405,14 @@ PlaneH3::is_degenerate() const } template < class R > -bool +typename R::Boolean PlaneH3::has_on_positive_side( const typename PlaneH3::Point_3& p) const { return (a()*p.hx() + b()*p.hy() + c()*p.hz() + d()*p.hw() > RT(0) ); } template < class R > -bool +typename R::Boolean PlaneH3::has_on_negative_side( const typename PlaneH3::Point_3& p) const { return (a()*p.hx() + b()*p.hy() + c()*p.hz() + d()*p.hw() < RT(0) ); @@ -420,14 +420,14 @@ PlaneH3::has_on_negative_side( const typename PlaneH3::Point_3& p) const template < class R > -bool +typename R::Boolean PlaneH3::has_on( const typename PlaneH3::Point_3& p) const { return (a()*p.hx() + b()*p.hy() + c()*p.hz() + d()*p.hw() == RT(0) ); } template < class R > -bool +typename R::Boolean PlaneH3::has_on( const typename PlaneH3::Line_3& l) const { Point_3 p = l.point(); @@ -439,7 +439,7 @@ PlaneH3::has_on( const typename PlaneH3::Line_3& l) const } template < class R > -Oriented_side +typename R::Oriented_side PlaneH3::oriented_side( const typename PlaneH3::Point_3& p) const { return CGAL_NTS sign( a()*p.hx() + b()*p.hy() + c()*p.hz() + d()*p.hw() ); @@ -447,7 +447,7 @@ PlaneH3::oriented_side( const typename PlaneH3::Point_3& p) const template < class R > -bool +typename R::Boolean PlaneH3::operator==(const PlaneH3& l) const { if ( (a() * l.d() != l.a() * d() ) diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/PointH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/PointH2.h index 06ff55945fe..62dc1f28783 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/PointH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/PointH2.h @@ -63,8 +63,8 @@ public: PointH2(const RT& hx, const RT& hy, const RT& hw) : base(hx, hy, hw) {} - bool operator==( const PointH2& p) const; - bool operator!=( const PointH2& p) const; + typename R::Boolean operator==( const PointH2& p) const; + typename R::Boolean operator!=( const PointH2& p) const; const RT & hx() const { return base.hx(); } const RT & hy() const { return base.hy(); } @@ -94,7 +94,7 @@ public: template < class R > inline -bool +typename R::Boolean PointH2::operator==( const PointH2& p) const { return base == p.base; @@ -102,7 +102,7 @@ PointH2::operator==( const PointH2& p) const template < class R > inline -bool +typename R::Boolean PointH2::operator!=( const PointH2& p) const { return !(*this == p); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/PointH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/PointH3.h index f64d5b64c9a..ead132b42d5 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/PointH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/PointH3.h @@ -90,8 +90,8 @@ public: Direction_3 direction() const; Point_3 transform( const Aff_transformation_3 & t) const; - bool operator==( const PointH3& p) const; - bool operator!=( const PointH3& p) const; + typename R::Boolean operator==( const PointH3& p) const; + typename R::Boolean operator!=( const PointH3& p) const; }; @@ -175,7 +175,7 @@ PointH3::direction() const template < class R > inline -bool +typename R::Boolean PointH3::operator==( const PointH3 & p) const { return base == p.base; @@ -183,7 +183,7 @@ PointH3::operator==( const PointH3 & p) const template < class R > inline -bool +typename R::Boolean PointH3::operator!=( const PointH3 & p) const { return !(*this == p); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/RayH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/RayH3.h index 8a48ffd52bf..3a3999d459b 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/RayH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/RayH3.h @@ -63,12 +63,12 @@ public: const Vector_3 & to_vector() const; Line_3 supporting_line() const; RayH3 opposite() const; - bool has_on(const Point_3& p) const; - bool collinear_has_on(const Point_3 &p) const; - bool is_degenerate() const; + typename R::Boolean has_on(const Point_3& p) const; + typename R::Boolean collinear_has_on(const Point_3 &p) const; + typename R::Boolean is_degenerate() const; - bool operator==(const RayH3& r) const; - bool operator!=(const RayH3& r) const; + typename R::Boolean operator==(const RayH3& r) const; + typename R::Boolean operator!=(const RayH3& r) const; }; template < class R > @@ -133,7 +133,7 @@ RayH3::opposite() const template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean RayH3::has_on(const typename RayH3::Point_3 &p) const { return ( ( p == start() ) @@ -142,25 +142,25 @@ RayH3::has_on(const typename RayH3::Point_3 &p) const template < class R > inline /* XXX */ -bool +typename R::Boolean RayH3::collinear_has_on(const typename RayH3::Point_3 &p) const { return has_on(p); } template < class R > inline -bool +typename R::Boolean RayH3::is_degenerate() const { return to_vector() == NULL_VECTOR; } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean RayH3::operator==(const RayH3& r) const { return ( (start() == r.start() )&&( direction() == r.direction() ) ); } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean RayH3::operator!=( const RayH3& r) const { return !operator==(r); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/SphereH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/SphereH3.h index a00ec51b275..1615004c68b 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/SphereH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/SphereH3.h @@ -57,10 +57,10 @@ public: SphereH3(const Point_3& p, const Orientation& o = COUNTERCLOCKWISE); - bool + typename R::Boolean operator==(const SphereH3&) const; - bool + typename R::Boolean operator!=(const SphereH3& s) const { return !(*this == s); } @@ -70,32 +70,32 @@ public: Orientation orientation() const; - bool is_degenerate() const; + typename R::Boolean is_degenerate() const; SphereH3 opposite() const; - Oriented_side oriented_side(const Point_3& p) const; + typename R::Oriented_side oriented_side(const Point_3& p) const; - bool + typename R::Boolean has_on_boundary(const Point_3& p) const { return oriented_side(p)==ON_ORIENTED_BOUNDARY; } - bool + typename R::Boolean has_on_positive_side(const Point_3& p) const { return oriented_side(p)==ON_POSITIVE_SIDE; } - bool + typename R::Boolean has_on_negative_side(const Point_3& p) const { return oriented_side(p)==ON_NEGATIVE_SIDE; } - Bounded_side + typename R::Bounded_side bounded_side(const Point_3& p) const; - bool + typename R::Boolean has_on_bounded_side(const Point_3& p) const { return bounded_side(p)==ON_BOUNDED_SIDE; } - bool + typename R::Boolean has_on_unbounded_side(const Point_3& p) const { return bounded_side(p)==ON_UNBOUNDED_SIDE; } }; @@ -162,7 +162,7 @@ SphereH3::SphereH3(const typename SphereH3::Point_3& p, template CGAL_KERNEL_INLINE -bool +typename R::Boolean SphereH3::operator==(const SphereH3& s) const { return ( orientation() == s.orientation()) @@ -190,23 +190,23 @@ SphereH3::orientation() const template inline -bool +typename R::Boolean SphereH3::is_degenerate() const { return squared_radius() <= FT(0) ; } template CGAL_KERNEL_MEDIUM_INLINE -Oriented_side +typename R::Oriented_side SphereH3::oriented_side(const typename SphereH3::Point_3& p) const { return Oriented_side(static_cast(bounded_side(p)) * static_cast(orientation())); } template CGAL_KERNEL_INLINE -Bounded_side +typename R::Bounded_side SphereH3::bounded_side(const typename SphereH3::Point_3& p) const { - return Bounded_side(CGAL_NTS compare(squared_radius(), - squared_distance(center(),p))); + return enum_cast(CGAL_NTS compare(squared_radius(), + squared_distance(center(),p))); } template diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH2.h index 92afeec7941..1ae244802f1 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH2.h @@ -80,10 +80,10 @@ public: return static_cast(*this); } - bool operator==( const VectorH2& v) const; - bool operator!=( const VectorH2& v) const; - bool operator==( const Null_vector&) const; - bool operator!=( const Null_vector& v) const; + typename R::Boolean operator==( const VectorH2& v) const; + typename R::Boolean operator!=( const VectorH2& v) const; + typename R::Boolean operator==( const Null_vector&) const; + typename R::Boolean operator!=( const Null_vector& v) const; const RT & hx() const { return CGAL::get_pointee_or_identity(base)[0]; }; const RT & hy() const { return CGAL::get_pointee_or_identity(base)[1]; }; @@ -129,19 +129,19 @@ public: template < class R > inline -bool +typename R::Boolean VectorH2::operator==( const Null_vector&) const { return (hx() == RT(0)) && (hy() == RT(0)); } template < class R > inline -bool +typename R::Boolean VectorH2::operator!=( const Null_vector& v) const { return !(*this == v); } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean VectorH2::operator==( const VectorH2& v) const { return ( (hx() * v.hw() == v.hx() * hw() ) @@ -150,7 +150,7 @@ VectorH2::operator==( const VectorH2& v) const template < class R > inline -bool +typename R::Boolean VectorH2::operator!=( const VectorH2& v) const { return !(*this == v); } /* XXX */ diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH3.h index 56a0e2b9ad8..3bf8af6e68b 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/VectorH3.h @@ -129,8 +129,8 @@ public: Vector_3 operator-() const; - bool operator==( const VectorH3& v) const; - bool operator!=( const VectorH3& v) const; + typename R::Boolean operator==( const VectorH3& v) const; + typename R::Boolean operator!=( const VectorH3& v) const; Vector_3 operator+( const VectorH3 &v) const; Vector_3 operator-( const VectorH3 &v) const; @@ -171,7 +171,7 @@ VectorH3::direction() const template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean VectorH3::operator==( const VectorH3& v) const { return ( (hx() * v.hw() == v.hx() * hw() ) @@ -181,7 +181,7 @@ VectorH3::operator==( const VectorH3& v) const template < class R > inline -bool +typename R::Boolean VectorH3::operator!=( const VectorH3& v) const { return !(*this == v); } diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH2.h index 680c6dccc02..ccac644485b 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH2.h @@ -24,7 +24,7 @@ namespace CGAL { template < class R> CGAL_KERNEL_MEDIUM_INLINE -bool +typename R::Boolean has_larger_distance_to_point(const PointH2& p, const PointH2& q, const PointH2& r) @@ -74,7 +74,7 @@ has_larger_distance_to_point(const PointH2& p, template < class R> CGAL_KERNEL_INLINE -Comparison_result +typename R::Comparison_result compare_signed_distance_to_line(const LineH2& l, const PointH2& p, const PointH2& q) @@ -111,7 +111,7 @@ compare_signed_distance_to_line(const LineH2& l, template < class R> CGAL_KERNEL_INLINE -bool +typename R::Boolean has_larger_signed_distance_to_line(const LineH2& l, const PointH2& p, const PointH2& q) @@ -140,7 +140,7 @@ has_larger_signed_distance_to_line(const LineH2& l, template < class R> CGAL_KERNEL_INLINE -bool +typename R::Boolean has_smaller_signed_distance_to_line(const LineH2& l, const PointH2& p, const PointH2& q) @@ -167,7 +167,7 @@ has_smaller_signed_distance_to_line(const LineH2& l, template < class R> CGAL_KERNEL_MEDIUM_INLINE -Comparison_result +typename R::Comparison_result compare_signed_distance_to_line(const PointH2& p, const PointH2& q, const PointH2& r, @@ -208,7 +208,7 @@ compare_signed_distance_to_line(const PointH2& p, template < class R> CGAL_KERNEL_MEDIUM_INLINE -bool +typename R::Boolean has_smaller_signed_distance_to_line(const PointH2& p, const PointH2& q, const PointH2& r, @@ -241,7 +241,7 @@ has_smaller_signed_distance_to_line(const PointH2& p, template < class R> CGAL_KERNEL_MEDIUM_INLINE -bool +typename R::Boolean has_larger_signed_distance_to_line(const PointH2& p, const PointH2& q, const PointH2& r, diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH3.h index ede98c97698..aacad83beb0 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/distance_predicatesH3.h @@ -21,43 +21,43 @@ namespace CGAL { template -Comparison_result +typename R::Comparison_result compare_distance_to_point(const PointH3& , const PointH3& , const PointH3& ); template -bool +typename R::Boolean has_larger_distance_to_point(const PointH3& , const PointH3& , const PointH3& ); template -bool +typename R::Boolean has_smaller_distance_to_point(const PointH3& , const PointH3& , const PointH3& ); template -Comparison_result +typename R::Comparison_result compare_signed_distance_to_plane(const PlaneH3& , const PointH3& , const PointH3& ); template -bool +typename R::Boolean has_larger_signed_distance_to_plane(const PlaneH3& , const PointH3& , const PointH3& ); template -bool +typename R::Boolean has_smaller_signed_distance_to_plane(const PlaneH3&, const PointH3& , const PointH3& ); template -Comparison_result +typename R::Comparison_result compare_signed_distance_to_plane(const PointH3& , const PointH3& , const PointH3& , @@ -65,7 +65,7 @@ compare_signed_distance_to_plane(const PointH3& , const PointH3& ); template -bool +typename R::Boolean has_larger_signed_distance_to_plane(const PointH3& , const PointH3& , const PointH3& , @@ -73,7 +73,7 @@ has_larger_signed_distance_to_plane(const PointH3& , const PointH3& ); template -bool +typename R::Boolean has_smaller_signed_distance_to_plane(const PointH3& , const PointH3& , const PointH3& , @@ -82,7 +82,7 @@ has_smaller_signed_distance_to_plane(const PointH3& , template CGAL_KERNEL_MEDIUM_INLINE -Comparison_result +typename R::Comparison_result compare_distance_to_point(const PointH3& p, const PointH3& q, const PointH3& r) @@ -121,7 +121,7 @@ compare_distance_to_point(const PointH3& p, template < class R> CGAL_KERNEL_MEDIUM_INLINE -bool +typename R::Boolean has_larger_distance_to_point(const PointH3& p, const PointH3& q, const PointH3& r) @@ -157,7 +157,7 @@ has_larger_distance_to_point(const PointH3& p, template < class R> CGAL_KERNEL_MEDIUM_INLINE -bool +typename R::Boolean has_smaller_distance_to_point(const PointH3& p, const PointH3& q, const PointH3& r) @@ -193,7 +193,7 @@ has_smaller_distance_to_point(const PointH3& p, template < class R> CGAL_KERNEL_INLINE -Comparison_result +typename R::Comparison_result compare_signed_distance_to_plane(const PlaneH3& pl, const PointH3& p, const PointH3& q) @@ -227,7 +227,7 @@ compare_signed_distance_to_plane(const PlaneH3& pl, } template -bool +typename R::Boolean has_larger_signed_distance_to_plane(const PlaneH3& pl, const PointH3& p, const PointH3& q ) @@ -257,7 +257,7 @@ has_larger_signed_distance_to_plane(const PlaneH3& pl, } template -bool +typename R::Boolean has_smaller_signed_distance_to_plane(const PlaneH3& pl, const PointH3& p, const PointH3& q ) @@ -288,7 +288,7 @@ has_smaller_signed_distance_to_plane(const PlaneH3& pl, template inline -Comparison_result +typename R::Comparison_result compare_signed_distance_to_plane(const PointH3& p, const PointH3& q, const PointH3& r, @@ -302,7 +302,7 @@ compare_signed_distance_to_plane(const PointH3& p, template inline -bool +typename R::Boolean has_larger_signed_distance_to_plane(const PointH3& p, const PointH3& q, const PointH3& r, @@ -313,7 +313,7 @@ has_larger_signed_distance_to_plane(const PointH3& p, template inline -bool +typename R::Boolean has_smaller_signed_distance_to_plane(const PointH3& p, const PointH3& q, const PointH3& r, diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/function_objects.h b/Homogeneous_kernel/include/CGAL/Homogeneous/function_objects.h index b88aab964dc..00b6c05b1ad 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/function_objects.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/function_objects.h @@ -49,26 +49,27 @@ namespace HomogeneousKernelFunctors { template class Angle_2 { + typedef typename K::Angle Angle; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; typedef typename K::Construct_vector_2 Construct_vector_2; - Construct_vector_2 c; - public: - typedef typename K::Angle result_type; + Construct_vector_2 c; + + public: Angle_2() {} Angle_2(const Construct_vector_2& c_) : c(c_) {} - result_type + Angle operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { return operator()(c(q,p), c(q,r)); } - result_type + Angle operator()(const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& s) const { return operator()(c(q,p), c(s,r)); } - result_type + Angle operator()(const Vector_2& u, const Vector_2& v) const { return enum_cast(CGAL_NTS sign(u * v)); } @@ -78,33 +79,34 @@ namespace HomogeneousKernelFunctors { template class Angle_3 { + typedef typename K::Angle Angle; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; typedef typename K::Construct_vector_3 Construct_vector_3; - Construct_vector_3 c; - public: - typedef typename K::Angle result_type; + Construct_vector_3 c; + + public: Angle_3() {} Angle_3(const Construct_vector_3& c_) : c(c_) {} - result_type + Angle operator()(const Vector_3& u, const Vector_3& v) const { return enum_cast(CGAL_NTS sign(u * v)); } // FIXME: scalar product - result_type + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { return enum_cast(CGAL_NTS sign(c(q,p) * c(q,r))); } // FIXME: scalar product - result_type + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { return enum_cast(CGAL_NTS sign(c(q,p) * c(s,r))); } // FIXME: scalar product - result_type + Angle operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Vector_3& n) const { @@ -116,14 +118,14 @@ namespace HomogeneousKernelFunctors { template class Bounded_side_2 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_2 Point_2; typedef typename K::Circle_2 Circle_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Circle_2& c, const Point_2& p) const { typename K::Compute_squared_distance_2 squared_distance; @@ -131,7 +133,7 @@ namespace HomogeneousKernelFunctors { squared_distance(c.center(),p))); } - result_type + Bounded_side operator()( const Triangle_2& t, const Point_2& p) const { typename K::Collinear_are_ordered_along_line_2 @@ -154,7 +156,7 @@ namespace HomogeneousKernelFunctors { : ON_UNBOUNDED_SIDE; } - result_type + Bounded_side operator()( const Iso_rectangle_2& r, const Point_2& p) const { return r.rep().bounded_side(p); @@ -164,20 +166,20 @@ namespace HomogeneousKernelFunctors { template class Bounded_side_3 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; typedef typename K::Sphere_3 Sphere_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Iso_cuboid_3 Iso_cuboid_3; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Sphere_3& s, const Point_3& p) const { return s.rep().bounded_side(p); } - result_type + Bounded_side operator()( const Tetrahedron_3& t, const Point_3& p) const { Vector_3 v1 = t.vertex(1)-t.vertex(0); @@ -250,7 +252,7 @@ namespace HomogeneousKernelFunctors { return (t5 && t6) ? ON_BOUNDED_SIDE : ON_BOUNDARY; } - result_type + Bounded_side operator()( const Iso_cuboid_3& c, const Point_3& p) const { return c.rep().bounded_side(p); } }; @@ -258,21 +260,21 @@ namespace HomogeneousKernelFunctors { template class Collinear_are_ordered_along_line_2 { + typedef typename K::Boolean Boolean; typedef typename K::RT RT; typedef typename K::Point_2 Point_2; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Collinear_2 Collinear_2; Collinear_2 c; #endif // CGAL_kernel_exactness_preconditions - public: - typedef typename K::Boolean result_type; + public: #ifdef CGAL_kernel_exactness_preconditions Collinear_are_ordered_along_line_2() {} Collinear_are_ordered_along_line_2(const Collinear_2& c_) : c(c_) {} #endif // CGAL_kernel_exactness_preconditions - result_type + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { CGAL_kernel_exactness_precondition( c(p, q, r) ); @@ -309,20 +311,20 @@ namespace HomogeneousKernelFunctors { template class Collinear_are_ordered_along_line_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Collinear_3 Collinear_3; Collinear_3 c; #endif // CGAL_kernel_exactness_preconditions - public: - typedef typename K::Boolean result_type; + public: #ifdef CGAL_kernel_exactness_preconditions Collinear_are_ordered_along_line_3() {} Collinear_are_ordered_along_line_3(const Collinear_3& c_) : c(c_) {} #endif // CGAL_kernel_exactness_preconditions - result_type + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { CGAL_kernel_exactness_precondition( c(p, q, r) ); @@ -390,21 +392,21 @@ namespace HomogeneousKernelFunctors { template class Collinear_are_strictly_ordered_along_line_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Collinear_2 Collinear_2; Collinear_2 c; #endif // CGAL_kernel_exactness_preconditions - public: - typedef typename K::Boolean result_type; + public: #ifdef CGAL_kernel_exactness_preconditions Collinear_are_strictly_ordered_along_line_2() {} Collinear_are_strictly_ordered_along_line_2(const Collinear_2& c_) : c(c_) {} #endif // CGAL_kernel_exactness_preconditions - result_type + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { CGAL_kernel_exactness_precondition( c(p, q, r) ); @@ -440,22 +442,22 @@ namespace HomogeneousKernelFunctors { template class Collinear_are_strictly_ordered_along_line_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Direction_3 Direction_3; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Collinear_3 Collinear_3; Collinear_3 c; #endif // CGAL_kernel_exactness_preconditions - public: - typedef typename K::Boolean result_type; + public: #ifdef CGAL_kernel_exactness_preconditions Collinear_are_strictly_ordered_along_line_3() {} Collinear_are_strictly_ordered_along_line_3(const Collinear_3& c_) : c(c_) {} #endif // CGAL_kernel_exactness_preconditions - result_type + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { CGAL_kernel_exactness_precondition( c(p, q, r) ); @@ -469,6 +471,7 @@ namespace HomogeneousKernelFunctors { template class Collinear_has_on_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Direction_2 Direction_2; typedef typename K::Ray_2 Ray_2; @@ -480,23 +483,22 @@ namespace HomogeneousKernelFunctors { Collinear_are_ordered_along_line_2 co; Construct_point_on_2 cp; Compare_xy_2 cxy; - public: - typedef typename K::Boolean result_type; + public: Collinear_has_on_2() {} Collinear_has_on_2(const Construct_point_on_2& cp_, const Compare_xy_2& cxy_) : cp(cp_), cxy(cxy_) {} - result_type + Boolean operator()( const Ray_2& r, const Point_2& p) const { const Point_2 & source = cp(r,0); return p == source || Direction_2(p - source) == r.direction(); } // FIXME - result_type + Boolean operator()( const Segment_2& s, const Point_2& p) const { return co(cp(s,0), p, cp(s,1)); @@ -506,16 +508,17 @@ namespace HomogeneousKernelFunctors { template class Collinear_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Orientation_2 Orientation_2; - Orientation_2 o; - public: - typedef typename K::Boolean result_type; + Orientation_2 o; + + public: Collinear_2() {} Collinear_2(const Orientation_2 o_) : o(o_) {} - result_type + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { typedef typename K::RT RT; @@ -558,13 +561,13 @@ namespace HomogeneousKernelFunctors { template class Compare_angle_with_x_axis_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; typedef typename K::Direction_2 Direction_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Direction_2& d1, const Direction_2& d2) const { typedef typename K::RT RT; @@ -625,11 +628,11 @@ namespace HomogeneousKernelFunctors { template class Compare_distance_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { typedef typename K::RT RT; @@ -673,14 +676,14 @@ namespace HomogeneousKernelFunctors { } template - result_type + Comparison_result operator()(const T1& p, const T2& q, const T3& r) const { return CGAL::compare(squared_distance(p, q), squared_distance(p, r)); } template - result_type + Comparison_result operator()(const T1& p, const T2& q, const T3& r, const T4& s) const { return CGAL::compare(squared_distance(p, q), squared_distance(r, s)); @@ -690,11 +693,11 @@ namespace HomogeneousKernelFunctors { template class Compare_distance_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { typedef typename K::RT RT; @@ -724,16 +727,15 @@ namespace HomogeneousKernelFunctors { return CGAL_NTS sign(dosd); } - template - result_type + Comparison_result operator()(const T1& p, const T2& q, const T3& r) const { return CGAL::compare(squared_distance(p, q), squared_distance(p, r)); } template - result_type + Comparison_result operator()(const T1& p, const T2& q, const T3& r, const T4& s) const { return CGAL::compare(squared_distance(p, q), squared_distance(r, s)); @@ -743,13 +745,11 @@ namespace HomogeneousKernelFunctors { template < typename K > class Compare_power_distance_2 { - public: + typedef typename K::Comparison_result Comparison_result; typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename K::Point_2 Point_2; - typedef typename K::Comparison_result Comparison_result; - - typedef Comparison_result result_type; + public: Comparison_result operator()(const Point_2& r, const Weighted_point_2& p, const Weighted_point_2& q) const @@ -763,14 +763,13 @@ namespace HomogeneousKernelFunctors { template class Compare_signed_distance_to_line_2 { - typedef typename K::Point_2 Point_2; - typedef typename K::Line_2 Line_2; + typedef typename K::Comparison_result Comparison_result; + typedef typename K::Point_2 Point_2; + typedef typename K::Line_2 Line_2; typedef typename K::Less_signed_distance_to_line_2 Less_signed_distance_to_line_2; public: - typedef Comparison_result result_type; - - result_type + Comparison_result operator()(const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& s) const { @@ -796,7 +795,7 @@ namespace HomogeneousKernelFunctors { return compare(scaled_dist_r_minus_scaled_dist_s, 0); } - result_type + Comparison_result operator()(const Line_2& l, const Point_2& p, const Point_2& q) const { Less_signed_distance_to_line_2 less = K().less_signed_distance_to_line_2_object(); @@ -809,13 +808,13 @@ namespace HomogeneousKernelFunctors { template class Compare_slope_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; typedef typename K::Segment_2 Segment_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()(const Line_2& l1, const Line_2& l2) const { if (l1.is_horizontal()) @@ -840,22 +839,22 @@ namespace HomogeneousKernelFunctors { CGAL::abs(l1.a() * l2.b()) ); } // FIXME - result_type + Comparison_result operator()(const Segment_2& s1, const Segment_2& s2) const { return (*this)(s1.source(), s1.target(), s2.source(), s2.target()); } - result_type + Comparison_result operator()(const Point_2& s1s, const Point_2& s1t, const Point_2& s2s, const Point_2& s2t) const { typedef typename K::FT FT; - typename K::Comparison_result cmp_y1 = compare_y(s1s, s1t); + Comparison_result cmp_y1 = compare_y(s1s, s1t); if (cmp_y1 == EQUAL) // horizontal { - typename K::Comparison_result cmp_x2 = compare_x(s2s, s2t); + Comparison_result cmp_x2 = compare_x(s2s, s2t); if (cmp_x2 == EQUAL) return SMALLER; FT s_hw = s2s.hw(); @@ -864,10 +863,10 @@ namespace HomogeneousKernelFunctors { CGAL_NTS sign(s2s.hx()*t_hw - s2t.hx()*s_hw); } - typename K::Comparison_result cmp_y2 = compare_y(s2s, s2t); + Comparison_result cmp_y2 = compare_y(s2s, s2t); if (cmp_y2 == EQUAL) { - typename K::Comparison_result cmp_x1 = compare_x(s1s, s1t); + Comparison_result cmp_x1 = compare_x(s1s, s1t); if (cmp_x1 == EQUAL) return LARGER; FT s_hw = s1s.hw(); @@ -876,8 +875,8 @@ namespace HomogeneousKernelFunctors { CGAL_NTS sign(s1s.hx()*t_hw - s1t.hx()*s_hw); } - typename K::Comparison_result cmp_x1 = compare_x(s1s, s1t); - typename K::Comparison_result cmp_x2 = compare_x(s2s, s2t); + Comparison_result cmp_x1 = compare_x(s1s, s1t); + Comparison_result cmp_x2 = compare_x(s2s, s2t); if (cmp_x1 == EQUAL) return cmp_x2 == EQUAL ? EQUAL : LARGER; @@ -909,12 +908,12 @@ namespace HomogeneousKernelFunctors { template class Compare_x_at_y_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Line_2& h) const { typedef typename K::RT RT; @@ -927,17 +926,17 @@ namespace HomogeneousKernelFunctors { return ( ors == ON_NEGATIVE_SIDE ) ? SMALLER : EQUAL; } // FIXME - result_type + Comparison_result operator()( const Point_2& p, const Line_2& h1, const Line_2& h2) const { return CGAL::compare(h1.x_at_y( p.y() ), h2.x_at_y( p.y() )); } // FIXME - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h) const { return compare_x_at_y( gp_linear_intersection( l1, l2 ), h); } // FIXME - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { return compare_x_at_y( gp_linear_intersection( l1, l2 ), h1, h2 ); } @@ -947,11 +946,11 @@ namespace HomogeneousKernelFunctors { template class Compare_xyz_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { typedef typename K::RT RT; @@ -986,11 +985,11 @@ namespace HomogeneousKernelFunctors { template class Compare_xy_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { typedef typename K::RT RT; @@ -1016,11 +1015,11 @@ namespace HomogeneousKernelFunctors { template class Compare_yx_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { typedef typename K::RT RT; @@ -1046,11 +1045,11 @@ namespace HomogeneousKernelFunctors { template class Compare_xy_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { typedef typename K::RT RT; @@ -1074,31 +1073,31 @@ namespace HomogeneousKernelFunctors { template class Compare_x_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { return CGAL::compare(p.hx()*q.hw(), q.hx()*p.hw()); } - result_type + Comparison_result operator()( const Point_2& p, const Line_2& l1, const Line_2& l2) const { Point_2 ip = gp_linear_intersection( l1, l2 ); return this->operator()(p, ip); } // FIXME - result_type + Comparison_result operator()( const Line_2& l, const Line_2& h1, const Line_2& h2) const { return this->operator()(l, h1, l, h2); } // FIXME - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { @@ -1111,11 +1110,11 @@ namespace HomogeneousKernelFunctors { template class Compare_x_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return CGAL::compare(p.hx() * q.hw(), q.hx() * p.hw() ); } }; @@ -1123,13 +1122,13 @@ namespace HomogeneousKernelFunctors { template class Compare_y_at_x_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; typedef typename K::Segment_2 Segment_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Line_2& h) const { CGAL_kernel_precondition( ! h.is_vertical() ); @@ -1139,23 +1138,23 @@ namespace HomogeneousKernelFunctors { return ors; } // FIXME - result_type + Comparison_result operator()( const Point_2& p, const Line_2& h1, const Line_2& h2) const { return CGAL::compare(h1.y_at_x( p.x() ), h2.y_at_x( p.x() )); } // FIXME - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h) const { return compare_y_at_x( gp_linear_intersection( l1, l2 ), h); } // FIXME - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { return compare_y_at_x( gp_linear_intersection( l1, l2 ), h1, h2 ); } // FIXME - result_type + Comparison_result operator()( const Point_2& p, const Segment_2& s) const { // compares the y-coordinates of p and the vertical projection of p on s. @@ -1183,7 +1182,7 @@ namespace HomogeneousKernelFunctors { } } // FIXME - result_type + Comparison_result operator()( const Point_2& p, const Segment_2& s1, const Segment_2& s2) const { @@ -1243,12 +1242,12 @@ namespace HomogeneousKernelFunctors { template class Compare_y_2 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_2& p, const Point_2& q) const { typedef typename K::RT RT; @@ -1260,20 +1259,20 @@ namespace HomogeneousKernelFunctors { return CGAL::compare(phy * qhw, qhy * phw); } - result_type + Comparison_result operator()( const Point_2& p, const Line_2& l1, const Line_2& l2) const { Point_2 ip = gp_linear_intersection( l1, l2 ); return compare_y( p, ip ); } // FIXME - result_type + Comparison_result operator()( const Line_2& l, const Line_2& h1, const Line_2& h2) const { return this->operator()(l, h1, l, h2); } - result_type + Comparison_result operator()( const Line_2& l1, const Line_2& l2, const Line_2& h1, const Line_2& h2) const { @@ -1286,11 +1285,11 @@ namespace HomogeneousKernelFunctors { template class Compare_y_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return CGAL::compare(p.hy() * q.hw(), q.hy() * p.hw() ); } }; @@ -1298,11 +1297,11 @@ namespace HomogeneousKernelFunctors { template class Compare_z_3 { + typedef typename K::Comparison_result Comparison_result; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Comparison_result result_type; - result_type + public: + Comparison_result operator()( const Point_3& p, const Point_3& q) const { return CGAL::compare(p.hz() * q.hw(), q.hz() * p.hw() ); } }; @@ -1317,10 +1316,10 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; typedef typename K::Construct_vector_2 Construct_vector_2; - Construct_vector_2 co; - public: - typedef FT result_type; + Construct_vector_2 co; + + public: FT operator()( const Point_2& p, const Point_2& q, const Point_2& r ) const { @@ -1346,10 +1345,9 @@ namespace HomogeneousKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Vector_2& v, const Vector_2& w) const { return determinant(v.hx(), v.hy(), @@ -1362,10 +1360,9 @@ namespace HomogeneousKernelFunctors { { typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef FT result_type; - result_type + public: + FT operator()(const Vector_3& v, const Vector_3& w, const Vector_3& t) const { return determinant(v.hx(), v.hy(), v.hz(), @@ -1381,9 +1378,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::RT RT; typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef FT result_type; + public: FT operator()(const Vector_2& v, const Vector_2& w) const { @@ -1398,9 +1394,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::RT RT; typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef FT result_type; + public: FT operator()(const Vector_3& v, const Vector_3& w) const { @@ -1416,10 +1411,9 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Circle_2 Circle_2; - public: - typedef FT result_type; - FT + public: + decltype(auto) operator()( const Circle_2& c) const { return c.rep().squared_radius(); } @@ -1446,10 +1440,9 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Sphere_3 Sphere_3; - public: - typedef FT result_type; - FT + public: + decltype(auto) operator()( const Sphere_3& s) const { return s.rep().squared_radius(); } @@ -1486,9 +1479,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Vector_3 Vector_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Iso_cuboid_3 Iso_cuboid_3; - public: - typedef FT result_type; + public: FT operator()(const Point_3& p0, const Point_3& p1, const Point_3& p2, const Point_3& p3) const @@ -1531,20 +1523,17 @@ namespace HomogeneousKernelFunctors { template class Compute_x_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef FT result_type; - - FT + decltype(auto) operator()(const Point_2& p) const { return p.rep().x(); } - FT + decltype(auto) operator()(const Vector_2& v) const { return v.rep().x(); @@ -1554,20 +1543,17 @@ namespace HomogeneousKernelFunctors { template class Compute_x_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef FT result_type; - - FT + decltype(auto) operator()(const Point_3& p) const { return p.rep().x(); } - FT + decltype(auto) operator()(const Vector_3& v) const { return v.rep().x(); @@ -1577,20 +1563,17 @@ namespace HomogeneousKernelFunctors { template class Compute_y_2 { - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef FT result_type; - - FT + decltype(auto) operator()(const Point_2& p) const { return p.rep().y(); } - FT + decltype(auto) operator()(const Vector_2& v) const { return v.rep().y(); @@ -1600,20 +1583,17 @@ namespace HomogeneousKernelFunctors { template class Compute_y_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef FT result_type; - - FT + decltype(auto) operator()(const Point_3& p) const { return p.rep().y(); } - FT + decltype(auto) operator()(const Vector_3& v) const { return v.rep().y(); @@ -1623,20 +1603,17 @@ namespace HomogeneousKernelFunctors { template class Compute_z_3 { - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef FT result_type; - - FT + decltype(auto) operator()(const Point_3& p) const { return p.rep().z(); } - FT + decltype(auto) operator()(const Vector_3& v) const { return v.rep().z(); @@ -1646,13 +1623,10 @@ namespace HomogeneousKernelFunctors { template class Compute_dx_2 { - typedef typename K::RT RT; typedef typename K::Direction_2 Direction_2; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Direction_2& d) const { return d.rep().dx(); @@ -1662,13 +1636,10 @@ namespace HomogeneousKernelFunctors { template class Compute_dx_3 { - typedef typename K::RT RT; typedef typename K::Direction_3 Direction_3; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Direction_3& d) const { return d.rep().dx(); @@ -1678,13 +1649,10 @@ namespace HomogeneousKernelFunctors { template class Compute_dy_2 { - typedef typename K::RT RT; typedef typename K::Direction_2 Direction_2; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Direction_2& d) const { return d.rep().dy(); @@ -1694,13 +1662,10 @@ namespace HomogeneousKernelFunctors { template class Compute_dy_3 { - typedef typename K::RT RT; typedef typename K::Direction_3 Direction_3; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Direction_3& d) const { return d.rep().dy(); @@ -1710,13 +1675,10 @@ namespace HomogeneousKernelFunctors { template class Compute_dz_3 { - typedef typename K::RT RT; typedef typename K::Direction_3 Direction_3; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Direction_3& d) const { return d.rep().dz(); @@ -1726,21 +1688,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hx_2 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().hx(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().hx(); @@ -1750,21 +1708,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hx_3 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hx(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hx(); @@ -1774,21 +1728,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hy_2 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().hy(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().hy(); @@ -1798,21 +1748,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hy_3 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const RT & result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hy(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hy(); @@ -1822,21 +1768,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hz_3 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hz(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hz(); @@ -1846,21 +1788,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hw_2 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Point_2& p) const { return p.rep().hw(); } - result_type + decltype(auto) operator()(const Vector_2& v) const { return v.rep().hw(); @@ -1870,21 +1808,17 @@ namespace HomogeneousKernelFunctors { template class Compute_hw_3 { - typedef typename K::FT FT; - typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; public: - typedef const RT& result_type; - - result_type + decltype(auto) operator()(const Point_3& p) const { return p.rep().hw(); } - result_type + decltype(auto) operator()(const Vector_3& v) const { return v.rep().hw(); @@ -1898,11 +1832,11 @@ namespace HomogeneousKernelFunctors { typedef typename K::Plane_3 Plane_3; typedef typename K::RT RT; typedef typename K::Construct_orthogonal_vector_3 + Construct_orthogonal_vector_3; Construct_orthogonal_vector_3 co; - public: - typedef Vector_3 result_type; + public: Construct_base_vector_3() {} Construct_base_vector_3(const Construct_orthogonal_vector_3& co_) : co(co_) @@ -1951,9 +1885,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Iso_rectangle_2 Iso_rectangle_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Circle_2 Circle_2; - public: - typedef Bbox_2 result_type; + public: Bbox_2 operator()( const Point_2& p) const { @@ -2016,9 +1949,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Iso_cuboid_3 Iso_cuboid_3; typedef typename K::Sphere_3 Sphere_3; - public: - typedef Bbox_3 result_type; + public: Bbox_3 operator()(const Point_3& p) const { @@ -2091,12 +2023,10 @@ namespace HomogeneousKernelFunctors { class Construct_bisector_2 { typedef typename K::RT RT; - typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef Line_2 result_type; + public: Line_2 operator()(const Point_2& p, const Point_2& q) const { @@ -2134,12 +2064,10 @@ namespace HomogeneousKernelFunctors { class Construct_bisector_3 { typedef typename K::RT RT; - typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Plane_3 Plane_3; - public: - typedef Plane_3 result_type; + public: Plane_3 operator()(const Point_3& p, const Point_3& q) const { @@ -2180,16 +2108,14 @@ namespace HomogeneousKernelFunctors { template class Construct_centroid_2 { - typedef typename K::FT FT; + typedef typename K::RT RT; typedef typename K::Point_2 Point_2; typedef typename K::Triangle_2 Triangle_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { - typedef typename K::RT RT; const RT phw(p.hw()); const RT qhw(q.hw()); const RT rhw(r.hw()); @@ -2230,9 +2156,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Triangle_3 Triangle_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { @@ -2281,12 +2206,11 @@ namespace HomogeneousKernelFunctors { template class Construct_circumcenter_2 { - typedef typename K::FT FT; + typedef typename K::RT RT; typedef typename K::Point_2 Point_2; typedef typename K::Triangle_2 Triangle_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()(const Point_2& p, const Point_2& q) const { @@ -2297,7 +2221,6 @@ namespace HomogeneousKernelFunctors { Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { - typedef typename K::RT RT; const RT & phx = p.hx(); const RT & phy = p.hy(); const RT & phw = p.hw(); @@ -2361,14 +2284,13 @@ namespace HomogeneousKernelFunctors { template class Construct_circumcenter_3 { - typedef typename K::FT FT; + typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Triangle_3 Triangle_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Plane_3 Plane_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()(const Point_3& p, const Point_3& q) const { @@ -2394,8 +2316,6 @@ namespace HomogeneousKernelFunctors { operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { - typedef typename K::RT RT; - RT phw( p.hw() ); RT qhw( q.hw() ); RT rhw( r.hw() ); @@ -2468,9 +2388,8 @@ namespace HomogeneousKernelFunctors { class Construct_cross_product_vector_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()(const Vector_3& a, const Vector_3& b) const { @@ -2485,9 +2404,8 @@ namespace HomogeneousKernelFunctors { class Construct_difference_of_vectors_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()(const Vector_2& v, const Vector_2& w) const { @@ -2501,9 +2419,8 @@ namespace HomogeneousKernelFunctors { class Construct_difference_of_vectors_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()(const Vector_3& v, const Vector_3& w) const { @@ -2528,8 +2445,6 @@ namespace HomogeneousKernelFunctors { typedef typename K::RT RT; public: - typedef Direction_2 result_type; - Rep // Direction_2 operator()(Return_base_tag, const RT& x, const RT& y) const { return Rep(x, y); } @@ -2595,9 +2510,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Segment_3 Segment_3; typedef typename K::RT RT; typedef typename Direction_3::Rep Rep; - public: - typedef Direction_3 result_type; + public: Rep // Direction_3 operator()(Return_base_tag, const RT& x, const RT& y, const RT& z) const { return Rep(x, y, z); } @@ -2646,9 +2560,8 @@ namespace HomogeneousKernelFunctors { class Construct_sum_of_vectors_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()(const Vector_2& v, const Vector_2& w) const { @@ -2662,9 +2575,8 @@ namespace HomogeneousKernelFunctors { class Construct_sum_of_vectors_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()(const Vector_3& v, const Vector_3& w) const { @@ -2681,9 +2593,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::RT RT; typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()(const Vector_2& v, const FT& f ) const { @@ -2704,9 +2615,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::RT RT; typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()(const Vector_3& v, const FT& f ) const { @@ -2732,8 +2642,6 @@ namespace HomogeneousKernelFunctors { typedef typename Iso_rectangle_2::Rep Rep; public: - typedef Iso_rectangle_2 result_type; - Rep // Iso_rectangle_2 operator()(Return_base_tag, const Point_2& p, const Point_2& q, int) const { @@ -2851,9 +2759,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Point_3 Point_3; typedef typename K::Plane_3 Plane_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()(const Plane_3& h, const Point_2& p) const { @@ -2875,10 +2782,10 @@ namespace HomogeneousKernelFunctors { typedef typename K::Line_2 Line_2; typedef typename Line_2::Rep Rep; typedef typename K::Construct_point_on_2 Construct_point_on_2; - Construct_point_on_2 cp; - public: - typedef Line_2 result_type; + Construct_point_on_2 cp; + + public: Construct_line_2() {} Construct_line_2(const Construct_point_on_2& cp_) : cp(cp_) {} @@ -2958,9 +2865,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_2 Point_2; typedef typename K::Segment_2 Segment_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()(const Point_2& p, const Point_2& q) const { @@ -2992,9 +2898,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::Point_3 Point_3; typedef typename K::Segment_3 Segment_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()(const Point_3& p, const Point_3& q) const { @@ -3027,9 +2932,8 @@ namespace HomogeneousKernelFunctors { class Construct_opposite_vector_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v) const { return Vector_2(-v.hx(), -v.hy(), v.hw()); } @@ -3039,9 +2943,8 @@ namespace HomogeneousKernelFunctors { class Construct_opposite_vector_3 { typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& v) const { return Vector_3(-v.hx(), -v.hy(), -v.hz(), v.hw()); } @@ -3053,9 +2956,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; typedef typename K::Plane_3 Plane_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Plane_3& p ) const { return p.rep().orthogonal_vector(); } @@ -3071,9 +2973,8 @@ namespace HomogeneousKernelFunctors { class Construct_perpendicular_vector_2 { typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, Orientation o) const { @@ -3089,9 +2990,8 @@ namespace HomogeneousKernelFunctors { class Construct_perpendicular_direction_2 { typedef typename K::Direction_2 Direction_2; - public: - typedef Direction_2 result_type; + public: Direction_2 operator()( const Direction_2& d, Orientation o) const { @@ -3110,9 +3010,8 @@ namespace HomogeneousKernelFunctors { { typedef typename K::Line_2 Line_2; typedef typename K::Point_2 Point_2; - public: - typedef Line_2 result_type; + public: Line_2 operator()( const Line_2& l, const Point_2& p) const { return typename K::Line_2( -l.b()*p.hw(), l.a()*p.hw(), l.b()*p.hx() - l.a()*p.hy()); } @@ -3128,23 +3027,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Vector_2 Vector_2; typedef typename K::Line_2 Line_2; typedef typename Point_2::Rep Rep; + public: - - template - struct result { - typedef Point_2 type; - }; - - template - struct result { - typedef const Point_2& type; - }; - - template - struct result { - typedef const Point_2& type; - }; - Rep // Point_2 operator()(Return_base_tag, Origin o) const { return Rep(o); } @@ -3193,7 +3077,7 @@ namespace HomogeneousKernelFunctors { operator()(const Point_2 & p) const { return p; } - const Point_2& + decltype(auto) operator()(const Weighted_point_2 & p) const { return p.rep().point(); } @@ -3221,22 +3105,6 @@ namespace HomogeneousKernelFunctors { typedef typename Point_3::Rep Rep; public: - - template - struct result { - typedef Point_3 type; - }; - - template - struct result { - typedef const Point_3& type; - }; - - template - struct result { - typedef const Point_3& type; - }; - Rep // Point_3 operator()(Return_base_tag, Origin o) const { return Rep(o); } @@ -3258,7 +3126,7 @@ namespace HomogeneousKernelFunctors { operator()(const Point_3 & p) const { return p; } - const Point_3& + decltype(auto) operator()(const Weighted_point_3 & p) const { return p.rep().point(); } @@ -3288,9 +3156,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename Weighted_point_2::Rep Rep; - public: - typedef Weighted_point_2 result_type; + public: Rep operator()(Return_base_tag, Origin o) const { return Rep(o); } @@ -3331,9 +3198,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_3 Point_3; typedef typename K::Weighted_point_3 Weighted_point_3; typedef typename Weighted_point_3::Rep Rep; - public: - typedef Weighted_point_3 result_type; + public: Rep operator()(Return_base_tag, Origin o) const { return Rep(o); } @@ -3373,9 +3239,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Direction_2 Direction_2; typedef typename K::Line_2 Line_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()( const Line_2& l, const Point_2& p ) const { @@ -3400,16 +3265,6 @@ namespace HomogeneousKernelFunctors { typedef typename K::Ray_3 Ray_3; public: - template - struct result { - typedef const Point_3 type; - }; - - template - struct result { - typedef const Point_3& type; - }; - Point_3 operator()( const Line_3& l, const Point_3& p ) const { @@ -3432,7 +3287,7 @@ namespace HomogeneousKernelFunctors { return l.point() + ( (lambda_num * dir)/lambda_den ); } - Point_3 + decltype(auto) operator()( const Plane_3& h, const Point_3& p ) const { return h.rep().projection(p); } @@ -3462,10 +3317,7 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; public: - - typedef Line_2 result_type; - - result_type + Line_2 operator() (const Circle_2 & c1, const Circle_2 & c2) const { // Concentric Circles don't have radical line @@ -3494,10 +3346,7 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; public: - - typedef Plane_3 result_type; - - result_type + Plane_3 operator() (const Sphere_3 & s1, const Sphere_3 & s2) const { // Concentric Spheres don't have radical plane @@ -3527,9 +3376,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::RT RT; typedef typename K::FT FT; typedef typename K::Vector_2 Vector_2; - public: - typedef Vector_2 result_type; + public: Vector_2 operator()( const Vector_2& v, const RT& c) const { @@ -3549,9 +3397,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::RT RT; typedef typename K::FT FT; typedef typename K::Vector_3 Vector_3; - public: - typedef Vector_3 result_type; + public: Vector_3 operator()( const Vector_3& v, const RT& c) const { @@ -3570,9 +3417,8 @@ namespace HomogeneousKernelFunctors { { typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; - public: - typedef Point_2 result_type; + public: Point_2 operator()( const Point_2& p, const Vector_2& v) const { @@ -3593,9 +3439,8 @@ namespace HomogeneousKernelFunctors { { typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; - public: - typedef Point_3 result_type; + public: Point_3 operator()( const Point_3& p, const Vector_3& v) const { @@ -3624,9 +3469,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Point_2 Point_2; typedef typename K::Direction_2 Direction_2; typedef typename Vector_2::Rep Rep; - public: - typedef Vector_2 result_type; + public: Rep // Vector_2 operator()(Return_base_tag, const Point_2& p, const Point_2& q) const { @@ -3731,9 +3575,8 @@ namespace HomogeneousKernelFunctors { typedef typename K::Vector_3 Vector_3; typedef typename K::Point_3 Point_3; typedef typename Vector_3::Rep Rep; - public: - typedef Vector_3 result_type; + public: Rep // Vector_3 operator()(Return_base_tag, const Point_3& p, const Point_3& q) const { @@ -3835,22 +3678,13 @@ namespace HomogeneousKernelFunctors { typedef typename K::Segment_2 Segment_2; typedef typename K::Iso_rectangle_2 Iso_rectangle_2; typedef typename K::Triangle_2 Triangle_2; + public: - template - struct result { - typedef const Point_2& type; - }; - - template - struct result { - typedef Point_2 type; - }; - - const Point_2 & + decltype(auto) operator()( const Segment_2& s, int i) const { return s.vertex(i); } - const Point_2 & + decltype(auto) operator()( const Triangle_2& t, int i) const { return t.rep().vertex(i); } @@ -3871,14 +3705,10 @@ namespace HomogeneousKernelFunctors { } }; -} //namespace HomogeneousKernelFunctors - - -namespace HomogeneousKernelFunctors { - template class Coplanar_orientation_3 { + typedef typename K::Orientation Orientation; typedef typename K::Point_3 Point_3; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Coplanar_3 Coplanar_3; @@ -3887,7 +3717,6 @@ namespace HomogeneousKernelFunctors { Collinear_3 cl; #endif // CGAL_kernel_exactness_preconditions public: - typedef typename K::Orientation result_type; #ifdef CGAL_kernel_exactness_preconditions Coplanar_orientation_3() {} @@ -3895,7 +3724,7 @@ namespace HomogeneousKernelFunctors { : cp(cp_), cl(cl_) {} #endif // CGAL_kernel_exactness_preconditions - result_type + Orientation operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { Orientation oxy_pqr = sign_of_determinant(p.hx(), p.hy(), p.hw(), @@ -3915,7 +3744,7 @@ namespace HomogeneousKernelFunctors { r.hx(), r.hz(), r.hw()); } - result_type + Orientation operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -3960,6 +3789,7 @@ namespace HomogeneousKernelFunctors { template class Coplanar_side_of_bounded_circle_3 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_3 Point_3; #ifdef CGAL_kernel_exactness_preconditions typedef typename K::Coplanar_3 Coplanar_3; @@ -3968,7 +3798,6 @@ namespace HomogeneousKernelFunctors { Collinear_3 cl; #endif // CGAL_kernel_exactness_preconditions public: - typedef typename K::Bounded_side result_type; #ifdef CGAL_kernel_exactness_preconditions Coplanar_side_of_bounded_circle_3() {} @@ -3977,7 +3806,7 @@ namespace HomogeneousKernelFunctors { : cp(cp_), cl(cl_) {} #endif // CGAL_kernel_exactness_preconditions - result_type + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& t) const { @@ -3995,11 +3824,11 @@ namespace HomogeneousKernelFunctors { template class Equal_xy_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return (p.hx() * q.hw() == q.hx() * p.hw() ) @@ -4010,11 +3839,11 @@ namespace HomogeneousKernelFunctors { template class Equal_x_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return p.hx()*q.hw() == q.hx()*p.hw(); } }; @@ -4022,11 +3851,11 @@ namespace HomogeneousKernelFunctors { template class Equal_x_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.hx()*q.hw() == q.hx()*p.hw(); } }; @@ -4034,11 +3863,11 @@ namespace HomogeneousKernelFunctors { template class Equal_y_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return p.hy()*q.hw() == q.hy()*p.hw(); } }; @@ -4046,11 +3875,11 @@ namespace HomogeneousKernelFunctors { template class Equal_y_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.hy()*q.hw() == q.hy()*p.hw(); } }; @@ -4058,11 +3887,11 @@ namespace HomogeneousKernelFunctors { template class Equal_z_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return p.hz()*q.hw() == q.hz()*p.hw(); } }; @@ -4070,6 +3899,7 @@ namespace HomogeneousKernelFunctors { template class Has_on_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Line_3 Line_3; typedef typename K::Ray_3 Ray_3; @@ -4077,30 +3907,29 @@ namespace HomogeneousKernelFunctors { typedef typename K::Plane_3 Plane_3; typedef typename K::Triangle_3 Triangle_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Line_3& l, const Point_3& p) const { return l.rep().has_on(p); } - result_type + Boolean operator()( const Ray_3& r, const Point_3& p) const { return r.rep().has_on(p); } - result_type + Boolean operator()( const Segment_3& s, const Point_3& p) const { return s.rep().has_on(p); } - result_type + Boolean operator()( const Plane_3& pl, const Point_3& p) const { return pl.rep().has_on(p); } - result_type + Boolean operator()( const Plane_3& pl, const Line_3& l) const { return pl.rep().has_on(l); } - result_type + Boolean operator()( const Triangle_3& t, const Point_3& p) const { if (!t.is_degenerate() ) @@ -4143,11 +3972,11 @@ namespace HomogeneousKernelFunctors { template class Less_distance_to_point_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { typedef typename K::RT RT; @@ -4195,11 +4024,11 @@ namespace HomogeneousKernelFunctors { template class Less_distance_to_point_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_3& p, const Point_3& q, const Point_3& r) const { typedef typename K::RT RT; @@ -4232,19 +4061,19 @@ namespace HomogeneousKernelFunctors { template class Less_signed_distance_to_line_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()(const Point_2& p, const Point_2& q, const Point_2& r, const Point_2& s) const { return Compare_signed_distance_to_line_2().operator()(p, q, r, s) == SMALLER; } - result_type + Boolean operator()(const Line_2& l, const Point_2& p, const Point_2& q) const { @@ -4270,14 +4099,14 @@ namespace HomogeneousKernelFunctors { template class Less_signed_distance_to_plane_3 { + typedef typename K::Boolean Boolean; typedef typename K::RT RT; typedef typename K::Point_3 Point_3; typedef typename K::Plane_3 Plane_3; typedef typename K::Construct_plane_3 Construct_plane_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Plane_3& pl, const Point_3& p, const Point_3& q) const { const RT & pla = pl.a(); @@ -4300,7 +4129,7 @@ namespace HomogeneousKernelFunctors { return scaled_dist_p_minus_scaled_dist_q < 0; } - result_type + Boolean operator()(const Point_3& plp, const Point_3& plq, const Point_3& plr, const Point_3& p, const Point_3& q) const { @@ -4312,16 +4141,17 @@ namespace HomogeneousKernelFunctors { template class Less_xyz_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Compare_xyz_3 Compare_xyz_3; - Compare_xyz_3 c; - public: - typedef typename K::Boolean result_type; + Compare_xyz_3 c; + + public: Less_xyz_3() {} Less_xyz_3(const Compare_xyz_3& c_) : c(c_) {} - result_type + Boolean operator()( const Point_3& p, const Point_3& q) const { return c(p, q) == SMALLER; } }; @@ -4329,16 +4159,17 @@ namespace HomogeneousKernelFunctors { template class Less_xy_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; typedef typename K::Compare_xy_2 Compare_xy_2; - Compare_xy_2 c; - public: - typedef typename K::Boolean result_type; + Compare_xy_2 c; + + public: Less_xy_2() {} Less_xy_2(const Compare_xy_2& c_) : c(c_) {} - result_type + Boolean operator()( const Point_2& p, const Point_2& q) const { return c(p, q) == SMALLER; } }; @@ -4346,16 +4177,17 @@ namespace HomogeneousKernelFunctors { template class Less_xy_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; typedef typename K::Compare_xy_3 Compare_xy_3; - Compare_xy_3 c; - public: - typedef typename K::Boolean result_type; + Compare_xy_3 c; + + public: Less_xy_3() {} Less_xy_3(const Compare_xy_3& c_) : c(c_) {} - result_type + Boolean operator()( const Point_3& p, const Point_3& q) const { return c(p, q) == SMALLER; } }; @@ -4363,11 +4195,11 @@ namespace HomogeneousKernelFunctors { template class Less_x_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return ( p.hx()*q.hw() < q.hx()*p.hw() ); } }; @@ -4375,11 +4207,11 @@ namespace HomogeneousKernelFunctors { template class Less_x_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return ( p.hx()*q.hw() < q.hx()*p.hw() ); } }; @@ -4387,11 +4219,11 @@ namespace HomogeneousKernelFunctors { template class Less_yx_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { typedef typename K::RT RT; @@ -4422,11 +4254,11 @@ namespace HomogeneousKernelFunctors { template class Less_y_2 { + typedef typename K::Boolean Boolean; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_2& p, const Point_2& q) const { return ( p.hy()*q.hw() < q.hy()*p.hw() ); } }; @@ -4434,11 +4266,11 @@ namespace HomogeneousKernelFunctors { template class Less_y_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return ( p.hy()*q.hw() < q.hy()*p.hw() ); } }; @@ -4446,11 +4278,11 @@ namespace HomogeneousKernelFunctors { template class Less_z_3 { + typedef typename K::Boolean Boolean; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Boolean result_type; - result_type + public: + Boolean operator()( const Point_3& p, const Point_3& q) const { return (p.hz() * q.hw() < q.hz() * p.hw() ); } }; @@ -4458,13 +4290,13 @@ namespace HomogeneousKernelFunctors { template class Orientation_2 { + typedef typename K::Orientation Orientation; typedef typename K::Point_2 Point_2; typedef typename K::Vector_2 Vector_2; typedef typename K::Circle_2 Circle_2; - public: - typedef typename K::Orientation result_type; - result_type + public: + Orientation operator()(const Point_2& p, const Point_2& q, const Point_2& r) const { typedef typename K::RT RT; @@ -4490,14 +4322,14 @@ namespace HomogeneousKernelFunctors { return CGAL::compare(A*D, B*C); } - result_type + Orientation operator()(const Vector_2& u, const Vector_2& v) const { return sign_of_determinant(u.hx(), u.hy(), v.hx(), v.hy()); } - result_type + Orientation operator()(const Circle_2& c) const { return c.rep().orientation(); @@ -4507,14 +4339,14 @@ namespace HomogeneousKernelFunctors { template class Orientation_3 { + typedef typename K::Orientation Orientation; typedef typename K::Point_3 Point_3; typedef typename K::Vector_3 Vector_3; typedef typename K::Tetrahedron_3 Tetrahedron_3; typedef typename K::Sphere_3 Sphere_3; - public: - typedef typename K::Orientation result_type; - result_type + public: + Orientation operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s) const { @@ -4525,7 +4357,7 @@ namespace HomogeneousKernelFunctors { s.hx(), s.hy(), s.hz(), s.hw()); } - result_type + Orientation operator()( const Vector_3& u, const Vector_3& v, const Vector_3& w) const { return sign_of_determinant( u.hx(), u.hy(), u.hz(), @@ -4533,13 +4365,13 @@ namespace HomogeneousKernelFunctors { w.hx(), w.hy(), w.hz()); } - result_type + Orientation operator()( const Tetrahedron_3& t) const { return t.rep().orientation(); } - result_type + Orientation operator()(const Sphere_3& s) const { return s.rep().orientation(); @@ -4561,7 +4393,7 @@ namespace HomogeneousKernelFunctors { typedef typename K::FT FT; typedef typename K::Weighted_point_3 Weighted_point_3; typedef typename K::Oriented_side Oriented_side; - typedef Oriented_side result_type; + typedef typename K::Oriented_side Orientation; Oriented_side operator() ( const Weighted_point_3 & p, const Weighted_point_3 & q, @@ -4625,12 +4457,10 @@ namespace HomogeneousKernelFunctors { template < typename K > class Power_side_of_oriented_power_circle_2 { - public: - typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename K::Oriented_side Oriented_side; + typedef typename K::Weighted_point_2 Weighted_point_2; - typedef Oriented_side result_type; - + public: Oriented_side operator()(const Weighted_point_2& p, const Weighted_point_2& q, const Weighted_point_2& r, @@ -4678,20 +4508,20 @@ namespace HomogeneousKernelFunctors { template class Oriented_side_2 { + typedef typename K::Oriented_side Oriented_side; typedef typename K::RT RT; typedef typename K::Point_2 Point_2; typedef typename K::Circle_2 Circle_2; typedef typename K::Line_2 Line_2; typedef typename K::Triangle_2 Triangle_2; typedef typename K::Segment_2 Segment_2; - public: - typedef typename K::Oriented_side result_type; - result_type + public: + Oriented_side operator()( const Circle_2& c, const Point_2& p) const { return Oriented_side(static_cast(c.bounded_side(p)) * static_cast(c.orientation())); } - result_type + Oriented_side operator()( const Line_2& l, const Point_2& p) const { CGAL_kernel_precondition( ! l.is_degenerate() ); @@ -4699,7 +4529,7 @@ namespace HomogeneousKernelFunctors { return CGAL_NTS sign(v); } - result_type + Oriented_side operator()( const Triangle_2& t, const Point_2& p) const { typename K::Collinear_are_ordered_along_line_2 @@ -4724,7 +4554,7 @@ namespace HomogeneousKernelFunctors { : -ot; } - result_type + Oriented_side operator()(const Segment_2& s, const Triangle_2& t) const { typename K::Construct_source_2 source; @@ -4764,11 +4594,11 @@ namespace HomogeneousKernelFunctors { template class Side_of_bounded_circle_2 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Point_2& p, const Point_2& q, const Point_2& t) const { typedef typename K::RT RT; @@ -4783,12 +4613,11 @@ namespace HomogeneousKernelFunctors { const RT& thy = t.hy(); const RT& thw = t.hw(); - return enum_cast( - CGAL::compare((thx*phw-phx*thw)*(qhx*thw-thx*qhw), + return enum_cast(CGAL::compare((thx*phw-phx*thw)*(qhx*thw-thx*qhw), (thy*phw-phy*thw)*(thy*qhw-qhy*thw)) ); } - result_type + Bounded_side operator()( const Point_2& q, const Point_2& r, const Point_2& s, const Point_2& t) const { @@ -4854,11 +4683,11 @@ namespace HomogeneousKernelFunctors { template class Side_of_bounded_sphere_3 { + typedef typename K::Bounded_side Bounded_side; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Bounded_side result_type; - result_type + public: + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& t) const { typedef typename K::RT RT; @@ -4882,7 +4711,7 @@ namespace HomogeneousKernelFunctors { + (thz*phw-phz*thw)*(qhz*thw-thz*qhw))); } - result_type + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& t) const { @@ -4890,7 +4719,7 @@ namespace HomogeneousKernelFunctors { return enum_cast( compare_distance_to_point(center, p, t) ); } // FIXME - result_type + Bounded_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s, const Point_3& test) const { @@ -4920,11 +4749,11 @@ namespace HomogeneousKernelFunctors { template class Side_of_oriented_circle_2 { + typedef typename K::Oriented_side Oriented_side; typedef typename K::Point_2 Point_2; - public: - typedef typename K::Oriented_side result_type; - result_type + public: + Oriented_side operator()( const Point_2& q, const Point_2& r, const Point_2& s, const Point_2& t) const { @@ -4981,11 +4810,11 @@ namespace HomogeneousKernelFunctors { template class Side_of_oriented_sphere_3 { + typedef typename K::Oriented_side Oriented_side; typedef typename K::Point_3 Point_3; - public: - typedef typename K::Oriented_side result_type; - result_type + public: + Oriented_side operator()( const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s, const Point_3& t) const { @@ -5034,13 +4863,11 @@ namespace HomogeneousKernelFunctors { template < typename K > class Construct_radical_axis_2 { - public: typedef typename K::Weighted_point_2 Weighted_point_2; typedef typename K::Line_2 Line_2; typedef typename K::RT RT; - typedef Line_2 result_type; - + public: Line_2 operator()(const Weighted_point_2 & p, const Weighted_point_2 & q) const { diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH2.h b/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH2.h index 6c2177c916e..f51d9f61f3c 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH2.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH2.h @@ -24,7 +24,7 @@ namespace CGAL { template < class R> CGAL_KERNEL_INLINE -bool +typename R::Boolean equal_xy(const PointH2& p, const PointH2& q) { @@ -39,7 +39,7 @@ equal_xy(const PointH2& p, template CGAL_KERNEL_MEDIUM_INLINE -Oriented_side +typename R::Oriented_side _where_wrt_L_wedge( const PointH2& p, const PointH2& q ) { Sign xs = CGAL_NTS sign( q.hx()*p.hw() - p.hx()*q.hw() ); // sign( qx - px ) @@ -53,7 +53,7 @@ _where_wrt_L_wedge( const PointH2& p, const PointH2& q ) } template -Comparison_result +typename Compare::result_type compare_power_distanceH2(const RT& phx, const RT& phy, const RT& phw, const Quotient& pwt, const RT& qhx, const RT& qhy, const RT& qhw, @@ -82,7 +82,7 @@ compare_power_distanceH2(const RT& phx, const RT& phy, const RT& phw, template -Oriented_side +typename Same_uncertainty_nt::type power_testH2( const RT &phx, const RT &phy, const RT &phw, const Quotient &pwt, const RT &qhx, const RT &qhy, const RT &qhw, const Quotient &qwt, const RT &rhx, const RT &rhy, const RT &rhw, const Quotient &rwt, @@ -129,7 +129,7 @@ power_testH2( const RT &phx, const RT &phy, const RT &phw, const Quotient &p template -Oriented_side +typename Same_uncertainty_nt::type power_testH2( const RT &phx, const RT &phy, const RT &phw, const Quotient &pwt, const RT &qhx, const RT &qhy, const RT &qhw, const Quotient &qwt, const RT &thx, const RT &thy, const RT &thw, const Quotient &twt) @@ -180,7 +180,7 @@ power_testH2( const RT &phx, const RT &phy, const RT &phw, const Quotient &p // Unused, undocumented, un-functorized. template < class R > CGAL_KERNEL_MEDIUM_INLINE -Comparison_result +typename R::Comparison_result compare_deltax_deltay(const PointH2& p, const PointH2& q, const PointH2& r, diff --git a/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH3.h b/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH3.h index e734ac9493d..c1956fddb63 100644 --- a/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH3.h +++ b/Homogeneous_kernel/include/CGAL/Homogeneous/predicates_on_pointsH3.h @@ -25,8 +25,9 @@ namespace CGAL { template < class R > CGAL_KERNEL_MEDIUM_INLINE -bool lexicographically_xy_smaller(const PointH3 &p, - const PointH3 &q) +typename R::Boolean +lexicographically_xy_smaller(const PointH3 &p, + const PointH3 &q) { typedef typename R::RT RT; RT pV = p.hx()*q.hw(); @@ -51,7 +52,7 @@ bool lexicographically_xy_smaller(const PointH3 &p, template < class R> CGAL_KERNEL_MEDIUM_INLINE -Comparison_result +typename R::Comparison_result compare_xy(const PointH3& p, const PointH3& q) { typedef typename R::RT RT; @@ -82,7 +83,7 @@ compare_xy(const PointH3& p, const PointH3& q) template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean equal_xy(const PointH3 &p, const PointH3 &q) { return (p.hx() * q.hw() == q.hx() * p.hw() ) @@ -91,7 +92,7 @@ equal_xy(const PointH3 &p, const PointH3 &q) template < class R > // ??? -> == CGAL_KERNEL_INLINE -bool +typename R::Boolean equal_xyz(const PointH3 &p, const PointH3 &q) { return (p.hx() * q.hw() == q.hx() * p.hw() ) @@ -101,27 +102,27 @@ equal_xyz(const PointH3 &p, const PointH3 &q) template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean less_x(const PointH3 &p, const PointH3 &q) { return (p.hx() * q.hw() < q.hx() * p.hw() ); } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean less_y(const PointH3 &p, const PointH3 &q) { return (p.hy() * q.hw() < q.hy() * p.hw() ); } template < class R > CGAL_KERNEL_INLINE -bool +typename R::Boolean less_z(const PointH3 &p, const PointH3 &q) { return (p.hz() * q.hw() < q.hz() * p.hw() ); } template -Oriented_side +typename Same_uncertainty_nt::type power_side_of_oriented_power_sphereH3( const RT &phx, const RT &phy, const RT &phz, const RT &phw, const Quotient &pwt, const RT &qhx, const RT &qhy, const RT &qhz, const RT &qhw, const Quotient &qwt, diff --git a/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Concepts/HyperbolicDelaunayTriangulationTraits_2.h b/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Concepts/HyperbolicDelaunayTriangulationTraits_2.h index 1aaec1a1a00..2f6cef0e871 100644 --- a/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Concepts/HyperbolicDelaunayTriangulationTraits_2.h +++ b/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Concepts/HyperbolicDelaunayTriangulationTraits_2.h @@ -170,7 +170,7 @@ public: /// @} /// \name - /// The following functions must be provided only if the methods of `Hyperbolic_Delaunay_triangulation_2` + /// The following functions must be provided only if the methods of `CGAL::Hyperbolic_Delaunay_triangulation_2` /// that return elements of the Voronoi diagram are instantiated: /// @{ Construct_hyperbolic_segment_2 construct_hyperbolic_segment_2_object(); diff --git a/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Hyperbolic_triangulation_2.txt b/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Hyperbolic_triangulation_2.txt index d5b32a20979..418991dc8f9 100644 --- a/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Hyperbolic_triangulation_2.txt +++ b/Hyperbolic_triangulation_2/doc/Hyperbolic_triangulation_2/Hyperbolic_triangulation_2.txt @@ -15,7 +15,7 @@ namespace CGAL { -This package enables the computation of Delaunay triangulations of +This package enables the computation of hyperbolic Delaunay triangulations of point sets in the Poincaré disk model of the hyperbolic plane. \section HT2_Poincare_model The Poincaré Disk Model of the Hyperbolic Plane diff --git a/Hyperbolic_triangulation_2/include/CGAL/Hyperbolic_triangulation_2/internal/Hyperbolic_Delaunay_triangulation_traits_2_functions.h b/Hyperbolic_triangulation_2/include/CGAL/Hyperbolic_triangulation_2/internal/Hyperbolic_Delaunay_triangulation_traits_2_functions.h index 16e863753e2..63bb0c0c47f 100644 --- a/Hyperbolic_triangulation_2/include/CGAL/Hyperbolic_triangulation_2/internal/Hyperbolic_Delaunay_triangulation_traits_2_functions.h +++ b/Hyperbolic_triangulation_2/include/CGAL/Hyperbolic_triangulation_2/internal/Hyperbolic_Delaunay_triangulation_traits_2_functions.h @@ -225,13 +225,11 @@ class Side_of_oriented_hyperbolic_segment_2 typedef typename Traits::Construct_weighted_circumcenter_2 Construct_weighted_circumcenter_2; public: - typedef Oriented_side result_type; - Side_of_oriented_hyperbolic_segment_2(const Traits& gt = Traits()) : _gt(gt) {} - result_type operator()(const Hyperbolic_point_2& p, - const Hyperbolic_point_2& q, - const Hyperbolic_point_2& query) const + Oriented_side operator()(const Hyperbolic_point_2& p, + const Hyperbolic_point_2& q, + const Hyperbolic_point_2& query) const { // Check first if the points are collinear with the origin Circle_2 poincare(Hyperbolic_point_2(FT(0),FT(0)), FT(1)); diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 4db4c84f92c..e423b259b26 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -6,16 +6,39 @@ ### General Changes - The minimal supported version of Boost is now 1.74.0. + +### [3D Constrained Triangulations](https://doc.cgal.org/6.1/Manual/packages.html#PkgConstrainedTriangulation3) (new package) +- This package adds the function `CGAL::make_conforming_constrained_Delaunay_triangulation_3()` + to create a conforming constrained Delaunay triangulation in 3D, which can be represented by the new + class template `CGAL::Conforming_constrained_Delaunay_triangulation_3`. + +### 2D Triangulations on Hyperbolic Surfaces (new package) +- This package enables building and handling triangulations of closed orientable hyperbolic surfaces. + It offers functions for the generation of the triangulation from a convex fundamental domain, + the Delaunay flip algorithm and the construction of a portion of the lift of the triangulation + in the Poincaré disk. A method is offered that generates such domains in genus two. + +### 3D Isosurfacing (new package) + +- This package provides algorithms to extract isosurfaces from different inputs. The input is represented + as a 3D domain and can be an implicit function or a %Cartesian grid. The output is an indexed face + set that stores an isosurface in the form of a surface mesh. The provided algorithms include Marching Cubes, + topologically correct Marching Cubes, and Dual Contouring. + + ### [Polygon Mesh Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPolygonMeshProcessing) - Added the function `CGAL::Polygon_mesh_processing::discrete_mean_curvature` and `CGAL::Polygon_mesh_processing::discrete_Guassian_curvature` to evaluate the discrete curvature at a vertex of a mesh. - Added the function `CGAL::Polygon_mesh_processing::angle_sum` to compute the sum of the angles around a vertex. - Added a function in the [visitor of the corefinement based methods](https://doc.cgal.org/6.1/Polygon_mesh_processing/classPMPCorefinementVisitor.html) to know faces in the output meshes that are corresponding to input coplanar faces. +- Added the function `CGAL::Polygon_mesh_processing::approximated_centroidal_Voronoi_diagram_remeshing()` + to remesh triangle meshes. This remeshing algorithm uses clustering on polygonal meshes as to + approximate a Centroidal Voronoi Diagram construction, and can move vertices as to recover + sharp features and corners. ### [Point Set Processing](https://doc.cgal.org/6.1/Manual/packages.html#PkgPointSetProcessing3) - Added `poisson_eliminate()` to downsample a point cloud to a target size while providing Poisson disk property, i.e., a larger minimal distance between points. - ### [Algebraic Kernel](https://doc.cgal.org/6.1/Manual/packages.html#PkgAlgebraicKernelD) - **Breaking change**: Classes based on the RS Library are no longer provided. @@ -40,6 +63,12 @@ - `initial_points` : enables the user to specify a `Range` of initial points. - Added a new meshing parameter `surface_only`, to improve performances when the user is only interested in surface mesh generation. +### [3D Subdivision Methods](https://doc.cgal.org/6.1/Manual/packages.html#PkgSurfaceSubdivisionMethod3) + +- Added a new named parameter for `CGAL::Subdivision_method_3::Loop_subdivision()` and + `CGAL::Subdivision_method_3::CatmullClark_subdivision()`, which enables users to subdivide + a mesh without modifying its geometry. + ### [2D Triangulations](https://doc.cgal.org/6.1/Manual/packages.html#PkgTriangulation2) - **Breaking change**: In the class template `Constrained_triangulation_plus_2`, the value type of the range returned @@ -54,6 +83,11 @@ - All triangulations now offer the functions `point(Vertex_handle)` and `point(Simplex, int)`, which enables users to access the geometric position of a vertex and of the i-th vertex of a simplex of a triangulation. + +### [CGAL and the Boost Graph Library (BGL)](https://doc.cgal.org/6.1/Manual/packages.html#PkgBGL) + +- Added the function `dijkstra_shortest_path()` which computes the geometrically shortest sequence of halfedges between two vertices. + ## [Release 6.0.1](https://github.com/CGAL/cgal/releases/tag/v6.0.1) ### [Poisson Surface Reconstruction](https://doc.cgal.org/6.0.1/Manual/packages.html#PkgPoissonSurfaceReconstruction3) diff --git a/Installation/include/CGAL/config.h b/Installation/include/CGAL/config.h index a9e215f26e2..f3c8845350e 100644 --- a/Installation/include/CGAL/config.h +++ b/Installation/include/CGAL/config.h @@ -481,7 +481,7 @@ namespace cpp11{ }//namespace cpp11 } //namespace CGAL -#if __cpp_lib_concepts >= 201806L +#if __cpp_lib_concepts >= 201806L && __cpp_lib_ranges >= 201911L # define CGAL_CPP20_REQUIRE_CLAUSE(x) requires x # define CGAL_TYPE_CONSTRAINT(x) x #else diff --git a/Installation/include/CGAL/license/Isosurfacing_3.h b/Installation/include/CGAL/license/Isosurfacing_3.h new file mode 100644 index 00000000000..345fae13a82 --- /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/Polygon_mesh_processing/acvd.h b/Installation/include/CGAL/license/Polygon_mesh_processing/acvd.h new file mode 100644 index 00000000000..eb65e39c3b8 --- /dev/null +++ b/Installation/include/CGAL/license/Polygon_mesh_processing/acvd.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/license/README.md + +#ifndef CGAL_LICENSE_POLYGON_MESH_PROCESSING_ACVD_H +#define CGAL_LICENSE_POLYGON_MESH_PROCESSING_ACVD_H + +#include +#include + +#ifdef CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE + +# if CGAL_POLYGON_MESH_PROCESSING_ACVD_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 Polygon Mesh Processing - ACVD remeshing package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the Polygon Mesh Processing - ACVD remeshing package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL Polygon Mesh Processing - ACVD remeshing package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL Polygon Mesh Processing - ACVD remeshing package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_POLYGON_MESH_PROCESSING_ACVD_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_POLYGON_MESH_PROCESSING_ACVD_H diff --git a/Installation/include/CGAL/license/Triangulation_on_hyperbolic_surface_2.h b/Installation/include/CGAL/license/Triangulation_on_hyperbolic_surface_2.h new file mode 100644 index 00000000000..d3ce6f035fd --- /dev/null +++ b/Installation/include/CGAL/license/Triangulation_on_hyperbolic_surface_2.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/license/README.md + +#ifndef CGAL_LICENSE_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_H +#define CGAL_LICENSE_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_H + +#include +#include + +#ifdef CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_COMMERCIAL_LICENSE + +# if CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_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 2D Triangulations on Hyperbolic Surfaces package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the 2D Triangulations on Hyperbolic Surfaces package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL 2D Triangulations on Hyperbolic Surfaces package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL 2D Triangulations on Hyperbolic Surfaces package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_TRIANGULATION_ON_HYPERBOLIC_SURFACE_2_H diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index 4111229b47b..4d8e2fbc203 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -26,6 +26,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 Kinetic_surface_reconstruction Kinetic Surface Reconstruction Matrix_search Monotone and Sorted Matrix Search @@ -68,6 +69,7 @@ Polygon_mesh_processing/miscellaneous Polygon Mesh Processing - Miscellaneous Polygon_mesh_processing/detect_features Polygon Mesh Processing - Feature Detection Polygon_mesh_processing/collision_detection Polygon Mesh Processing - Collision Detection Polygon_mesh_processing/autorefinement Polygon Mesh Processing - Autorefinement +Polygon_mesh_processing/acvd Polygon Mesh Processing - ACVD remeshing Polyhedron 3D Polyhedral Surface Polyline_simplification_2 2D Polyline Simplification Polytope_distance_d Optimal Distances @@ -106,6 +108,7 @@ Three Three Triangulation_2 2D Triangulation Triangulation_3 3D Triangulations Triangulation dD Triangulations +Triangulation_on_hyperbolic_surface_2 2D Triangulations on Hyperbolic Surfaces Triangulation_on_sphere_2 2D Triangulation on Sphere Visibility_2 2D Visibility Computation Voronoi_diagram_2 2D Voronoi Diagram Adaptor diff --git a/Intersections_3/include/CGAL/Intersections_3/internal/Line_3_Tetrahedron_3_intersection.h b/Intersections_3/include/CGAL/Intersections_3/internal/Line_3_Tetrahedron_3_intersection.h index f1d8bdff1fd..706ceea7488 100644 --- a/Intersections_3/include/CGAL/Intersections_3/internal/Line_3_Tetrahedron_3_intersection.h +++ b/Intersections_3/include/CGAL/Intersections_3/internal/Line_3_Tetrahedron_3_intersection.h @@ -26,7 +26,6 @@ struct Tetrahedron_Line_intersection_3 : public Tetrahedron_lines_intersection_3_base > { typedef Tetrahedron_lines_intersection_3_base > Base; - typedef typename Base::Result_type Result_type; Tetrahedron_Line_intersection_3(const typename K::Tetrahedron_3& tet, const typename K::Line_3& l) diff --git a/Interval_skip_list/include/CGAL/Interval_skip_list.h b/Interval_skip_list/include/CGAL/Interval_skip_list.h index a445981a3f2..d2721b844ff 100644 --- a/Interval_skip_list/include/CGAL/Interval_skip_list.h +++ b/Interval_skip_list/include/CGAL/Interval_skip_list.h @@ -1111,7 +1111,7 @@ template Interval_skip_list::removeMarkers(IntervalSLnode* left, const Interval& I) { - // Remove markers for interval I, which has left as it's left + // Remove markers for interval I, which has left as its left // endpoint, following a staircase pattern. // Interval_handle res=0, tmp=0; // af: assignment not possible with std::list diff --git a/Isosurfacing_3/benchmark/Isosurfacing_3/CMakeLists.txt b/Isosurfacing_3/benchmark/Isosurfacing_3/CMakeLists.txt new file mode 100644 index 00000000000..b1ecd2b02d3 --- /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 00000000000..a2d1e2a73fa --- /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 00000000000..962b10edd43 --- /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 00000000000..e53c26236ed --- /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 00000000000..8bdc615aca0 --- /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 00000000000..8085431820f --- /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 00000000000..b5948907efe --- /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 00000000000..c2794f76cc9 --- /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 00000000000..ff72f3b738d --- /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 point `p`. + */ + Vector_3 gradient(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 00000000000..81c14bd1b49 --- /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 location of the vertex `v`. + */ + Point_3 point(vertex_descriptor v) const; + + /*! + returns the value of the value field at the point `p`. + */ + FT value(Point_3 p) const; + + /*! + returns the value of the value field at the vertex `v`. + */ + FT value(vertex_descriptor v) const; + + /*! + returns the two vertices incident to the edge `e`. + */ + Edge_vertices incident_vertices(edge_descriptor e) const; + + /*! + returns all the cells incident to the edge `e`, in a clockwise or counterclockwise order. + */ + Cells_incident_to_edge incident_cells(edge_descriptor e) const; + + /*! + returns all the vertices of the cell `c`. + */ + Cell_vertices cell_vertices(cell_descriptor c) const; + + /*! + returns all the edges of the cell `c`. + */ + Cell_edges cell_edges(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()(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()(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()(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 location of the first vertex of the edge + \param p_1 the location 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(Point_3 p_0, Point_3 p_1, + FT val_0, FT val_1, + 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 00000000000..c5177d2299f --- /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 location of the first vertex of the edge + * \param p_1 the location 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 + * + * \returns `true` if the intersection point exists, `false` otherwise. + */ + template + bool operator()(typename Domain::Geom_traits::Point_3 p_0, + typename Domain::Geom_traits::Point_3 p_1, + typename Domain::Geom_traits::FT val_0, + typename Domain::Geom_traits::FT val_1, + Domain domain, + 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 00000000000..26369fac7fa --- /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 point `p`. + */ + Vector_3 operator()(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 00000000000..0b983312496 --- /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`. + */ + 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 interpolated_value(Point_3 p, Grid g, 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 interpolated_gradient(Point_3 p, Grid g, 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 00000000000..2a56f46e72a --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Concepts/IsosurfacingPartition_3.h @@ -0,0 +1,34 @@ +/*! +\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`} +\cgalHasModels{`CGAL::Octree`} +\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 00000000000..d1aec7ee26c --- /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 00000000000..3b8bae76ddf --- /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 field at the point `p`. + */ + FT operator()(Point_3 p); + + /*! + returns the value of the field at the vertex `v`. + */ + FT operator()(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 00000000000..b92956d848d --- /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 location of the vertex `v`. + */ + static Point_3 point(vertex_descriptor v, IsosurfacingPartition_3 partition); + + /*! + * \returns the two vertices incident to the edge `e`. + */ + static Edge_vertices incident_vertices(edge_descriptor e, 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(edge_descriptor e, IsosurfacingPartition_3 partition); + + /*! + * \returns all the vertices of the cell `c`. + */ + static Cell_vertices cell_vertices(cell_descriptor c, IsosurfacingPartition_3 partition); + + /*! + * \returns all the edges of the cell `c`. + */ + static Cell_edges cell_edges(cell_descriptor c, 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()(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, 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()(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, 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()(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, 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 00000000000..4f7982c68b0 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Doxyfile.in @@ -0,0 +1,14 @@ +@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/MC.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/isosurfacing_inrimage.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/MC_TMC.png \ + ${CGAL_PACKAGE_DOC_DIR}/fig/MC_octree.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 00000000000..fa8f7b51115 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/Isosurfacing_3.txt @@ -0,0 +1,370 @@ +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 from qim.dk) +\cgalFigureCaptionEnd + +\section SecIsoSurfacingIntroduction Introduction + +Given a field of a scalar values, an isosurface is defined as the locus of points where the field +has a given constant value; in other words, it is a level set. +This constant value is referred to as the "isovalue", and, for well-behaved fields, +the level set forms a surface. +In the following, we shall refer to the the field of scalar values as the value field. +"Isosurfacing", also known as "isosurface extraction" or "contouring", is the process of constructing +the isosurface corresponding to a given value field and isovalue. +%Isosurfacing is often needed for volume visualization and for the simulation of physical phenomena. + +This \cgal package provides methods to extract isosurfaces from 3D value fields. +These contouring techniques rely on the partition of the 3D space and of the field to construct +an approximate representation of the isosurface. +The 3D value field can be described through various representations: an implicit function, +an interpolated set of discrete sampling values, a 3D image, 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 that only sample 3D value fields, +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 a 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 value field sampled at the grid corners. +Each cell corner is assigned a sign (+/-) to indicate whether its value field value is above +or below the user-defined isovalue. +A vertex is created along each grid edge where a sign change occurs, i.e., where the edge intersects the isosurface. +More specifically, the vertex location is computed using linear interpolation of +the value 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 are no less than 33 configurations (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, an edge, or a vertex), the Marching Cubes algorithm +generates as output a surface triangle mesh that is almost always combinatorially 2-manifold, +yet can still exhibit topological issues such as holes or non-conforming edges. + +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 value field along the grid edges, it can miss details or components +that are not captured by the sampling of the value field. + +Compared to other meshing approaches such as Delaunay refinement, Marching Cubes is substantially faster, +but often tends to generate more triangle facets for an equivalent desired sizing field. +In addition, the quality of the triangle facets is in general poor, with many needle or cap-shaped triangles. + +Furthermore, Marching Cubes does not preserve the sharp features present in the isovalue of the input value 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}. +More specifically, it generates as output a mesh that is homeomorphic to the trilinear interpolant of the input value 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. + +\cgalFigureAnchor{IsosurfacingMCTMC} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingMCTMC} +Marching Cubes vs Topologically Correct Marching Cubes. +Some vertex values can represent complex surfaces within a single cell: on the left, +the values at the vertices of the cell have been interpolated within the cell and correspond +to a tunnel-like surface. This surface is not correctly recovered when applying the cases of +the Marching Cubes algorithm to the cell: 2 independent disk-like sheets are created (middle). +However, by inserting new vertices, TMC correctly captures the topology within a single cell (right). +\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 instead. A facet is created for each edge that intersects the isosurface +by connecting the vertices of the incident cells. For a uniform hexahedral grid, this results +in 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 3D value field, %Dual Contouring requires knowledge about the gradient of the value field. + +The \cgal implementation uses a vertex positioning strategy based on *Quadric Error Metrics* \cgalCite{gh-ssqem-97} : +for a cell, the vertex location is computed by minimizing the error to the sum of the quadrics +defined at each edge-isosurface intersection. +Using this approach, the vertex may be located 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 vertex location inside the cell. + +By default, %Dual Contouring generates quads, but using edge-isosurface intersections, +one can "star" these quads to generate four triangles. Triangulating the quads is the default behavior in \cgal, +but this can be changed using a named parameter. + +\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 | Quads** | no | no | no | yes (not guaranteed) | + +(* assuming the isosurface does not exit the specified bounding box of the input 3D domain) + +(** which can be triangulated on-the-fly) +
+ +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 of this package: +
    +
  • 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()`, using the named parameter: `use_topologically_correct_marching_cubes` set to `true`; +
  • %Dual Contouring
  • : `CGAL::Isosurfacing::dual_contouring()`. +
+ +All 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, value 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 (i.e., no connectivity is shared between them) +or polygons sharing points (the same point in adjacent polygons will be the same point in the point range). + +All isosurfacing algorithms can run either sequentially in one thread or in parallel using multithreading. +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 file in the examples folder). + +\subsection SubSecIsosurfacingDomains Domains + +A domain is an object that provides functions to access the partition of the 3D volume, +the value field, and, optionally, the gradient field at a given point query. +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 a 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 3D value 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 value 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 the value field is defined: there is for + example no point incorporating 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 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 enables the user to switch from Marching Cubes to Topologically Correct Marching Cubes. +The resulting triangle soup is converted to a triangle mesh, and is remeshed using +the isotropic remeshing algorithm. + +\cgalExample{Isosurfacing_3/marching_cubes.cpp} + +\cgalFigureAnchor{IsosurfacingMC} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingMC} +Results of the Marching Cubes algorithm, and the final result after remeshing. +\cgalFigureCaptionEnd + +\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 (or disable) triangulation of the output, and to constrain the vertex location 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 vertex location (top row) or constrained vertex location (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 implicit and describes the unit sphere +by the distance to its center (set to the origin) as an implicit 3D value 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 generates 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} + +\subsection SubSecContouringOctreeExample Contouring using Octrees + +The following example shows the use of an octree as discretization instead of a %Cartesian grid +for both %Dual Contouring and Marching Cubes. Note that in this configuration, all methods +lose guarantees, and cracks can easily appear in the surface for Marching Cubes if the octree +is not adapted to the surface being extracted. + +\cgalExample{Isosurfacing_3/contouring_octree.cpp} + +\cgalFigureAnchor{IsosurfacingMCOctree} +
+ +
+\cgalFigureCaptionBegin{IsosurfacingMCOctree} +Running Marching Cubes with an octree: left, the octree (blue wireframe) has been applied arbitrary +refinement in one eighth of the domain and the resulting mesh exhibits cracks (boundaries) at +the regions where octree levels are discontinuous. Right, the octree was constructed to be dense +around the isosurface, and the surface is manifold. +\cgalFigureCaptionEnd + +\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 complete 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 00000000000..c7df5e6b418 --- /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 surface meshes +from 3D 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 3D 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 00000000000..b42f8f536e2 --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/dependencies @@ -0,0 +1,11 @@ +Manual +Kernel_23 +BGL +STL_Extension +Algebraic_foundations +Circulator +Stream_support +AABB_tree +Polygon_mesh_processing +Mesh_3 +Orthtree diff --git a/Isosurfacing_3/doc/Isosurfacing_3/examples.txt b/Isosurfacing_3/doc/Isosurfacing_3/examples.txt new file mode 100644 index 00000000000..aa0317f0d2e --- /dev/null +++ b/Isosurfacing_3/doc/Isosurfacing_3/examples.txt @@ -0,0 +1,10 @@ +/*! +\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/contouring_octree.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 00000000000..18b9d5f8f3c 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.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC.png new file mode 100644 index 00000000000..9f0a930c7c0 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC.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 00000000000..5801ca8d4d4 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 00000000000..2535321aee2 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 00000000000..f80821866b5 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_TMC.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_TMC.png new file mode 100644 index 00000000000..787c787600d Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_TMC.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 00000000000..e2569b16273 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/MC_octree.png b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_octree.png new file mode 100644 index 00000000000..14ba7f2fc15 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/MC_octree.png 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 00000000000..80540bd0eab 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 00000000000..be3b27a6047 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 00000000000..8b73e79fa89 Binary files /dev/null and b/Isosurfacing_3/doc/Isosurfacing_3/fig/isosurfacing_teaser.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 00000000000..bd42e9ca7b2 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/CMakeLists.txt @@ -0,0 +1,73 @@ +# 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 vtkIOMINC vtkIOLegacy NO_MODULE) +if(VTK_FOUND) + message(STATUS "VTK was found") +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") + create_single_source_cgal_program("contouring_octree.cpp") + + 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(contouring_octree 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) + target_link_libraries(contouring_octree 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) + 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 00000000000..af61c6a44c8 --- /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 00000000000..7175c1de27a --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_implicit_data.cpp @@ -0,0 +1,136 @@ +#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 00000000000..25588f00182 --- /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 00000000000..1697ba357c1 --- /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_3; + 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.0; + + 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_octree.cpp b/Isosurfacing_3/examples/Isosurfacing_3/contouring_octree.cpp new file mode 100644 index 00000000000..f370c622fbe --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/contouring_octree.cpp @@ -0,0 +1,288 @@ +#include + +#include +#include +#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 = CGAL::Octree >; +using Values = CGAL::Isosurfacing::Value_function_3; +using Gradients = CGAL::Isosurfacing::Gradient_function_3; +using MC_Domain = CGAL::Isosurfacing::Marching_cubes_domain_3; +using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + +namespace IS = CGAL::Isosurfacing; + +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()); +}; + +auto blobby_function = [](const Point& p) -> FT +{ + return std::exp(-1.5 * ((p.x() - 0.2) * (p.x() - 0.2) + (p.y() - 0.2) * (p.y() - 0.2) + (p.z() - 0.2) * (p.z() - 0.2))) + + std::exp(-1.5 * ((p.x() + 0.2) * (p.x() + 0.2) + (p.y() + 0.2) * (p.y() + 0.2) + (p.z() + 0.2) * (p.z() + 0.2))) + + std::exp(-1.5 * ((p.x() - 0.4) * (p.x() - 0.4) + (p.y() + 0.4) * (p.y() + 0.4) + (p.z() - 0.4) * (p.z() - 0.4))) + + std::exp(-6 * ((p.x() - 0.1) * (p.x() - 0.1) + (p.y() - 0.1) * (p.y() - 0.1))) + + std::exp(-6 * ((p.y() + 0.1) * (p.y() + 0.1) + (p.z() + 0.1) * (p.z() + 0.1))) + + std::exp(-6 * ((p.x() + 0.1) * (p.x() + 0.1) + (p.z() - 0.1) * (p.z() - 0.1))) - + 0.3; +}; + +auto blobby_gradient = [](const Point& p) -> Vector +{ + const FT g1 = -3 * std::exp(-1.5 * ((p.x() - 0.2) * (p.x() - 0.2) + (p.y() - 0.2) * (p.y() - 0.2) + (p.z() - 0.2) * (p.z() - 0.2))); + const FT g2 = -3 * std::exp(-1.5 * ((p.x() + 0.2) * (p.x() + 0.2) + (p.y() + 0.2) * (p.y() + 0.2) + (p.z() + 0.2) * (p.z() + 0.2))); + const FT g3 = -3 * std::exp(-1.5 * ((p.x() - 0.4) * (p.x() - 0.4) + (p.y() + 0.4) * (p.y() + 0.4) + (p.z() - 0.4) * (p.z() - 0.4))); + const FT g4 = -12 * std::exp(-6 * ((p.x() - 0.1) * (p.x() - 0.1) + (p.y() - 0.1) * (p.y() - 0.1))); + const FT g5 = -12 * std::exp(-6 * ((p.y() + 0.1) * (p.y() + 0.1) + (p.z() + 0.1) * (p.z() + 0.1))); + const FT g6 = -12 * std::exp(-6 * ((p.x() + 0.1) * (p.x() + 0.1) + (p.z() - 0.1) * (p.z() - 0.1))); + + return Vector(g1 * (p.x() - 0.2) + g2 * (p.x() + 0.2) + g3 * (p.x() - 0.4) + g4 * (p.x() - 0.1) + g6 * (p.x() + 0.1), + g1 * (p.y() - 0.2) + g2 * (p.y() + 0.2) + g3 * (p.y() + 0.4) + g4 * (p.y() - 0.1) + g5 * (p.y() + 0.1), + g1 * (p.z() - 0.2) + g2 * (p.z() + 0.2) + g3 * (p.z() - 0.4) + g5 * (p.z() + 0.1) + g6 * (p.z() - 0.1)); +}; + +// This is a naive refinement that is adapted to the isosurface: +// This refines: +// - at the minimum till minimum depth +// - at the maximum till maximum depth +// - we split if the the isovalue goes through the voxel, i.e. if not all vertices of the cell +// are on the same side of the isosurface defined by a function +// It's not a great refinement technique because the surface can enter and leave a cell +// without involving the cell's vertex. In practice, that means a hole if at nearby adjacent +// cells the voxels did get refined and registered the surface. +struct Refine_around_isovalue +{ + std::size_t min_depth_; + std::size_t max_depth_; + std::function function_; + FT isovalue_; + + Refine_around_isovalue(std::size_t min_depth, + std::size_t max_depth, + std::function function, + FT isovalue) + : min_depth_(min_depth), + max_depth_(max_depth), + function_(function), + isovalue_(isovalue) + {} + + bool operator()(const Octree::Node_index& ni, const Octree& octree) const + { + // Ensure minimum depth refinement + if (octree.depth(ni) < min_depth_) + return true; + + // Stop refinement at maximum depth + if (octree.depth(ni) >= max_depth_) + return false; + + // Get the bounding box of the node + auto bbox = octree.bbox(ni); + + // Evaluate the function at the corners of the bounding box + std::array corner_values; + int index = 0; + for (FT x : {bbox.xmin(), bbox.xmax()}) + for (FT y : {bbox.ymin(), bbox.ymax()}) + for (FT z : {bbox.zmin(), bbox.zmax()}) + corner_values[index++] = function_(Point(x, y, z)); + + // Check if the function values cross the isovalue + bool has_positive = false, has_negative = false; + for (const auto& value : corner_values) + { + if (value > isovalue_) + has_positive = true; + if (value < isovalue_) + has_negative = true; + if (has_positive && has_negative) + return true; // Refine if the isosurface intersects the voxel + } + + return false; // No refinement needed + } +}; + +// This is a refinement that is NOT adapted to the isosurface +struct Refine_one_eighth +{ + 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::Global_coordinates 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; + } +}; + +template +void run_DC_octree(const CGAL::Bbox_3 bbox, + const Splitter& split_predicate, + const std::function function, + const std::function gradient, + const FT isovalue, + const std::string& name) +{ + std::vector bbox_points { { bbox.xmin(), bbox.ymin(), bbox.zmin() }, + { bbox.xmax(), bbox.ymax(), bbox.zmax() } }; + + Octree octree(bbox_points); + octree.refine(split_predicate); + + + auto leaf_range = octree.traverse(CGAL::Orthtrees::Leaves_traversal(octree)); + std::size_t leaf_counter = std::distance(leaf_range.begin(), leaf_range.end()); + + std::cout << "octree has " << leaf_counter << " leaves" << std::endl; + + // fill up values and gradients + Values values { function, octree }; + Gradients gradients { gradient, octree }; + Domain domain { octree, values, gradients }; + + // output containers + Point_range points; + Polygon_range triangles; + + std::cout << "Running Dual Contouring with isovalue = " << isovalue << std::endl; + + // run Dual Contouring + IS::dual_contouring(domain, isovalue, points, triangles, + CGAL::parameters::do_not_triangulate_faces(true) + .constrain_to_cell(false)); + + std::cout << "Output #vertices (DC): " << points.size() << std::endl; + std::cout << "Output #triangles (DC): " << triangles.size() << std::endl; + + std::ofstream oo("octree_DC_" + name + ".polylines.txt"); + oo.precision(17); + octree.dump_to_polylines(oo); + + CGAL::IO::write_polygon_soup("DC_" + name + ".off", points, triangles); +} + +template +void run_MC_octree(const CGAL::Bbox_3 bbox, + const Splitter& split_predicate, + const std::function function, + const FT isovalue, + const std::string& name) +{ + std::vector bbox_points { { bbox.xmin(), bbox.ymin(), bbox.zmin() }, + { bbox.xmax(), bbox.ymax(), bbox.zmax() } }; + + Octree octree(bbox_points); + octree.refine(split_predicate); + + Values values { function, octree }; + + Point_range points; + Polygon_range triangles; + MC_Domain mcdomain { octree, values }; + + std::cout << "Running MC" << std::endl; + + CGAL::Isosurfacing::marching_cubes(mcdomain, isovalue, points, triangles); + + std::cout << "Output #vertices (MC): " << points.size() << std::endl; + std::cout << "Output #triangles (MC): " << triangles.size() << std::endl; + + std::ofstream oo("octree_MC_" + name + ".polylines.txt"); + oo.precision(17); + octree.dump_to_polylines(oo); + + CGAL::IO::write_polygon_soup("MC_" + name + ".off", points, triangles); +} + +// Whether you are using MC, TMC, or DC, there is no guarantee for an octree: +// it should behave well if your nodes are split with a uniform size around the surface, +// but it is sure to produce cracks if you have varying depths around the isosurface. +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.3; + + const CGAL::Bbox_3 bbox { -1., -1., -1., 1., 1., 1. }; + + Refine_one_eighth one_eight_splitter(3, 5); + run_DC_octree(bbox, one_eight_splitter, sphere_function, sphere_gradient, isovalue, "one_eight"); + + // This is + Refine_around_isovalue isovalue_splitter(3, 5, sphere_function, isovalue); + run_DC_octree(bbox, isovalue_splitter, sphere_function, sphere_gradient, isovalue, "sphere_adapted"); + + Refine_around_isovalue isvalue_splitter_2(3, 5, blobby_function, isovalue); + run_DC_octree(bbox, isvalue_splitter_2, blobby_function, blobby_gradient, isovalue, "blobby_adapted"); + + // to illustrate cracks + run_MC_octree(bbox, isovalue_splitter, sphere_function, isovalue, "adapted"); + + run_MC_octree(bbox, one_eight_splitter, sphere_function, isovalue, "one_eight"); + + std::cout << "Done" << std::endl; + + 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 00000000000..f36c48e8aff --- /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 00000000000..df9f624efe0 --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/dual_contouring.cpp @@ -0,0 +1,99 @@ +#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::Gradient_function_3; +using Domain = CGAL::Isosurfacing::Dual_contouring_domain_3; + +using Point_range = std::vector; +using Polygon_range = std::vector >; + +using Mesh = CGAL::Surface_mesh; + +// "Devil" - 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; + const FT box_c = (argc > 2) ? std::abs(std::stod(argv[2])) : 1.; + const std::size_t grid_n = (argc > 3) ? std::stoi(argv[3]) : 50; + + // create bounding box and grid + const CGAL::Bbox_3 bbox { -box_c, -box_c, -box_c, box_c, box_c, box_c }; + Grid grid { bbox, CGAL::make_array(grid_n, grid_n, grid_n) }; + + 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 & gradients + 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 << "Soup #vertices: " << points.size() << std::endl; + std::cout << "Soup #triangles: " << triangles.size() << std::endl; + + if(!CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles)) { + std::cerr << "Warning: the soup is not a 2-manifold surface, non-manifoldness?..." << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Create the mesh..." << std::endl; + Mesh mesh; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, triangles, mesh); + + CGAL::IO::write_polygon_mesh("dual_contouring.off", mesh, CGAL::parameters::stream_precision(17)); + + 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 00000000000..c8065ecaa1b --- /dev/null +++ b/Isosurfacing_3/examples/Isosurfacing_3/marching_cubes.cpp @@ -0,0 +1,95 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +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 >; + +using Mesh = CGAL::Surface_mesh; + +auto value_fn = [](const Point& p) -> FT +{ + const FT& x = p.x(), y = p.y(), z = p.z(); + + // "Klein Bottle" - https://www-sop.inria.fr/galaad/surface/ + return -1-4*y*z*z*x*x-2*y+6*z*z*x*x*y*y-16*x*z+16*x*z*y*y+3*x*x+7*y*y+11*z*z-11*z*z*z*z+z*z*z*z*z*z-3*x*x*x*x-7*y*y*y*y+x*x*x*x*x*x+y*y*y*y*y*y-14*z*z*x*x-18*z*z*y*y+3*z*z*z*z*x*x+3*z*z*z*z*y*y-10*x*x*y*y-4*y*y*y*z*z+3*z*z*x*x*x*x+3*z*z*y*y*y*y+16*x*x*x*z+3*x*x*x*x*y*y+3*x*x*y*y*y*y+4*x*x*y-12*z*z*y-2*x*x*x*x*y-4*x*x*y*y*y-2*z*z*z*z*y+16*x*z*z*z+12*y*y*y-2*y*y*y*y*y-32*x*z*y; +}; + +int main(int argc, char** argv) +{ + const FT isovalue = (argc > 1) ? std::stod(argv[1]) : 0.1; + const FT box_c = (argc > 2) ? std::abs(std::stod(argv[2])) : 5.; + const std::size_t grid_n = (argc > 3) ? std::stoi(argv[3]) : 50; + + // create bounding box and grid + const CGAL::Bbox_3 bbox { -box_c, -box_c, -box_c, box_c, box_c, box_c }; + Grid grid { bbox, CGAL::make_array(grid_n, grid_n, grid_n) }; + + 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 + Values values { 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 << "Soup #vertices: " << points.size() << std::endl; + std::cout << "Soup #triangles: " << triangles.size() << std::endl; + + if(!CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles)) { + std::cerr << "Warning: the soup is not a 2-manifold surface, non-manifoldness?..." << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Convert the soup to a triangle mesh..." << std::endl; + Mesh mesh; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points, triangles, mesh); + + CGAL::IO::write_polygon_mesh("marching_cubes.off", mesh, CGAL::parameters::stream_precision(17)); + + // Let's remesh it to something nicer looking + std::cout << "Remeshing..." << std::endl; + const FT target_edge_length = box_c / 50; + CGAL::Polygon_mesh_processing::isotropic_remeshing(faces(mesh), target_edge_length, mesh, + CGAL::parameters::number_of_iterations(5) + .number_of_relaxation_steps(5)); + + CGAL::IO::write_polygon_mesh("marching_cubes-remeshed.off", mesh, CGAL::parameters::stream_precision(17)); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} 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 00000000000..aad37317e9f --- /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 00000000000..b0a63c65de8 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Cartesian_grid_3.h @@ -0,0 +1,409 @@ +// 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 locations should be cached, or recomputed at each access. + * + * \tparam Tag a tag that is either `Tag_true` (locations are cached) or `Tag_false` (locations 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 locations. + */ +using Cache_vertex_locations = Grid_vertex_memory_policy; + +/** + * \ingroup IS_Partitions_helpers_grp + * + * A convenience alias for the policy that does not cache grid vertex locations. + */ +using Do_not_cache_vertex_locations = Grid_vertex_memory_policy; + +namespace internal { + +template +struct Cartesian_grid_location +{ + 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_location() { } // just for compilation + + Cartesian_grid_location(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) + typename GeomTraits::FT(i) * g.spacing()[0], + y_coord(min_p) + typename GeomTraits::FT(j) * g.spacing()[1], + z_coord(min_p) + typename GeomTraits::FT(k) * g.spacing()[2]); + } +}; + +template +struct Cartesian_grid_location +{ + 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_location() { } // just for compilation + + Cartesian_grid_location(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 locations of the grid vertices are stored or not. + * Possible values are `CGAL::Isosurfacing::Cache_vertex_locations` and `CGAL::Isosurfacing::Do_not_cache_vertex_locations`. + * + * \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_location; + +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 / FT(m_dims[0] - 1); + const FT d_y = y_span / FT(m_dims[1] - 1); + const FT d_z = z_span / FT(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(CGAL::to_double(x_span / m_spacing[0]))) + 1; + m_dims[1] = static_cast(std::ceil(CGAL::to_double(y_span / m_spacing[1]))) + 1; + m_dims[2] = static_cast(std::ceil(CGAL::to_double(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 `dimensions[0]*dimensions[1]*dimensions[2]` grid vertices. + * + * The grid covers the space described by the iso-cuboid `span`. + * + * \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 `dimensions[0]*dimensions[1]*dimensions[2]` grid vertices. + * + * The grid covers the space described by the iso-cuboid span, + * itself described through two diagonal corners `p` and `q`. + * + * \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 `spacing`. + * + * The grid covers the space described by the iso-cuboid `span`. + * + * \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: + /** + * returns the index of a grid cell given its indices (i.e., `(k * y_dim + j) * x_dim + i`). + */ + 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 coordinates of the grid cell that contains a given point. + * + * For points on the boundary between two cells, the smaller index is returned. + * + * \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 location of the grid vertex described by its three indices. + * + * Depending on the value of the template parameter `MemoryPolicy`, locations 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()` + * + * \returns a const reference or a newly constructed point, depending on the caching policy. + */ + 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 00000000000..134fe0af050 --- /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 00000000000..69e8a6e46a0 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Finite_difference_gradient_3.h @@ -0,0 +1,115 @@ +// 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 returns the value the gradient at a point in 3D space. + * + * \param p the point 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)) + { + CGAL_warning(false && "interpolated gradient is the null vector!"); + 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 00000000000..cd501cc9440 --- /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 returns the value of the function at the point `p`. + */ + Vector_3 operator()(const Point_3& p) const + { + return m_fn(p); + } + + /** + * \brief returns the value of 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 00000000000..a8d1a033a27 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/IO/Image_3.h @@ -0,0 +1,160 @@ +// 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 output grid + * \param values the output 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` + * \tparam Values must be `CGAL::Isosurfacing::Interpolated_discrete_values_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 constexpr(std::is_floating_point_v) + wordkind = WK_FLOAT; + else + wordkind = WK_FIXED; + + // select signed or unsigned + SIGN sign; + if constexpr(std::is_signed_v) + 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(); + + // 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 00000000000..5fd406006ba --- /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 all 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 its three 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 + * + * \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 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 its three 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 interpolated value at vertex `v`. + */ + FT operator()(const vertex_descriptor& v) const + { + return this->operator()(v[0], v[1], v[2]); + } + + /*! + * returns the interpolated value at point `p`. + */ + FT operator()(const Point_3& p) const + { + return m_interpolation.interpolated_value(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 00000000000..11008340f41 --- /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/Octree_partition.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Octree_partition.h new file mode 100644 index 00000000000..547704cbfe3 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/Octree_partition.h @@ -0,0 +1,21 @@ +// 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) : Sven Oesau + +#ifndef CGAL_ISOSURFACING_3_OCTREE_PARTITION_H +#define CGAL_ISOSURFACING_3_OCTREE_PARTITION_H + +#include + +#include +#include +#include + +#endif // CGAL_ISOSURFACING_3_OCTREE_PARTITION_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 00000000000..f495aa7f98d --- /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 returns the value of the function at the point `p`. + */ + FT operator()(const Point_3& p) const + { + return m_fn(p); + } + + /** + * \brief returns the value of 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 00000000000..ed907ab1e8d --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/dual_contouring_3.h @@ -0,0 +1,97 @@ +// 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 output polygon soup + * \param polygons the faces of the output polygon soup. Each element in the vector describes a polygon + * (either quads or triangles, see the named parameter `do_not_triangulate_faces`) + * 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 location 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 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 00000000000..5de1c3182eb --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/edge_intersection_oracles_3.h @@ -0,0 +1,223 @@ +// 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. + * + * \warning It is not optimal to use this class when 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; + double m_relative_eps; + +public: + /*! + * Constructor, enabling setting up the two criteria which can stop the dichotomy: either a + * threshold on the value (i.e., the difference between the isovalue and the value at the current + * point is smaller than `relative_eps * isovalue`), or a maximum number of iterations. + */ + Dichotomy_edge_intersection(unsigned int max_iterations = 10, + double relative_eps = 1e-7) + : m_max_iterations(max_iterations), + m_relative_eps(relative_eps) + { } + + /*! + * \brief computes the intersection point between an edge and the isosurface. + * + * The result (if it exists) is stored in `p`. + * + * \tparam Domain must be a model of `IsosurfacingDomain_3` + * + * \param p_0 the location of the first vertex of the edge + * \param p_1 the location 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 location of the first vertex of the edge + * \param p_1 the location 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 could do better than a dichotomy +// see https://github.com/MaelRL/cgal/blob/AW3-Sharp_and_sparse-GF/Alpha_wrap_3/include/CGAL/Alpha_wrap_3/internal/offset_intersection.h +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 00000000000..5ee49b20960 --- /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 00000000000..0f7d65f5491 --- /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 location 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::template for_each_vertex(f, m_partition); + } + + // iterates over all edges `e`, calling `f(e)` on each of them + template + void for_each_edge(Functor& f) const + { + PT::template for_each_edge(f, m_partition); + } + + // iterates over all cells `c`, calling `f(c)` on each of them + template + void for_each_cell(Functor& f) const + { + PT::template for_each_cell(f, m_partition); + } + + // 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_domain_3.h b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Octree_domain_3.h new file mode 100644 index 00000000000..f32394dea98 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/Octree_domain_3.h @@ -0,0 +1,182 @@ +// 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_OCTREE_DOMAIN_3_H +#define CGAL_ISOSURFACING_3_INTERNAL_OCTREE_DOMAIN_3_H + +#include +#include + +#include + +#include +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +// Specialization of the Isosurfacing_domain_3 for Orthtree +template +class Isosurfacing_domain_3::Kernel, std::vector >, ValueField, GradientField, IntersectionOracle> +{ +public: + using Edge_intersection_oracle = IntersectionOracle; + + using Partition = CGAL::Octree::Kernel, std::vector >; + + 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; + mutable std::vector m_leaf_vertices; // cache variable + mutable std::vector m_leaf_edges; // cache variable + mutable std::vector m_leaf_cells; // cache variable + +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 location 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 + { + if (m_leaf_vertices.empty()) + PT::get_leaves(m_partition, m_leaf_cells, m_leaf_edges, m_leaf_vertices); + + PT::template for_each_vertex(f, m_leaf_vertices, m_partition); + } + + // iterates over all edges `e`, calling `f(e)` on each of them + template + void for_each_edge(Functor& f) const + { + if (m_leaf_edges.empty()) + PT::get_leaves(m_partition, m_leaf_cells, m_leaf_edges, m_leaf_vertices); + PT::template for_each_edge(f, m_leaf_edges, m_partition); + } + + // iterates over all cells `c`, calling `f(c)` on each of them + template + void for_each_cell(Functor& f) const + { + if (m_leaf_cells.empty()) + PT::get_leaves(m_partition, m_leaf_cells, m_leaf_edges, m_leaf_vertices); + PT::template for_each_cell(f, m_leaf_cells, m_partition); + } + + // 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_OCTREE_DOMAIN_3_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 00000000000..a15703e75a5 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/dual_contouring_functors.h @@ -0,0 +1,671 @@ +// 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 +#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)(); + x_max = y_max = z_max = - (std::numeric_limits::max)(); + FT x(0), y(0), z(0); + + if(constrain_to_cell) + { + typename Domain::Cell_vertices vertices = domain.cell_vertices(c); + for(const auto& v : vertices) + { + const Point_3& cp = domain.point(v); + 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.cgal"); + 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.cgal"); + 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); + +#ifdef CGAL_ISOSURFACING_3_DC_FUNCTORS_DEBUG + static std::ofstream os("dual_to_sources.polylines.cgal"); + 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; + os << "2 " << edge_points.at(it->second) << " " << p << "\n"; + } +#endif + } + }; + 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 00000000000..83d2276b0f7 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/implicit_shapes_helper.h @@ -0,0 +1,164 @@ +// 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; +} + +// 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 00000000000..448fbee6a34 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/marching_cubes_functors.h @@ -0,0 +1,334 @@ +// 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 +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#else +#include +#endif + +#include +#include +#include + +namespace CGAL { +namespace Isosurfacing { +namespace internal { + +// Interpolate linearly between two vertex locations v0, v1 with values d0 and d1 according to the isovalue +template +std::pair +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(); + + // @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. + + const FT den = d1 - d0; + FT mu = is_zero(den) ? FT(1) / FT(2) : (isovalue - d0) / den; + mu = std::clamp(mu, FT(0), FT(1)); + + // linear interpolation + return { point((FT(1) - mu) * x_coord(p0) + mu * x_coord(p1), + (FT(1) - mu) * y_coord(p0) + mu * y_coord(p1), + (FT(1) - mu) * z_coord(p0) + mu * z_coord(p1)), 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, + bool isovalue_nudging) +{ + 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; + static_assert(Domain::VERTICES_PER_CELL == 8); + + for(const vertex_descriptor& v : vertices) + { + auto val = domain.value(v); + + // Avoiding singular cases. + if (isovalue_nudging && abs(val - isovalue) < 0.00000001) + val = isovalue - 0.00000001; + + values[v_id] = val; + if(values[v_id] >= isovalue) + index.set(v_id); + + ++v_id; + } + + if(index.all() || index.none()) // nothing is 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()).first; + } + + 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; + + 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) +{ + using Point = typename PointRange::value_type; + using PointIndexMap = std::unordered_map; + + PointIndexMap point_index_map; + std::size_t current_index = 0; + +#ifdef CGAL_LINKED_WITH_TBB + for(const auto& triangle_list : triangles) + { +#else + const auto& triangle_list = triangles; +#endif + + for(const auto& triangle : triangle_list) + { + auto& polygon = polygons.emplace_back(); + CGAL::internal::resize(polygon, 3); + + for (int i=2; i>=0; --i) + { + const Point& p = triangle[i]; + + auto [it, inserted] = point_index_map.emplace(p, current_index); + if(inserted) + { + points.push_back(p); + ++current_index; + } + polygon[i] = it->second; + } + } + +#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; + const bool m_isovalue_nudging; + + Triangles m_triangles; + +public: + // creates a Marching Cubes functor for a domain and isovalue + Marching_cubes_3(const Domain& domain, + const FT isovalue, + const bool isovalue_nudging = true) + : m_domain(domain), + m_isovalue(isovalue), + m_isovalue_nudging(isovalue_nudging) + { } + + // 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); + + // @speed 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, m_isovalue_nudging); + + // 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 00000000000..baff7a8e583 --- /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 00000000000..1b4bcf5be2d --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Cartesian_grid.h @@ -0,0 +1,302 @@ +// 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 + + template + static void for_each_vertex(Functor& f, const Grid& g) { return for_each_vertex(f, g, ConcurrencyTag{}); } + template + static void for_each_edge(Functor& f, const Grid& g) { return for_each_edge(f, g, ConcurrencyTag{}); } + template + static void for_each_cell(Functor& f, const Grid& g) { return for_each_cell(f, g, ConcurrencyTag{}); } +}; + +} // 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 00000000000..eccd3a247dd --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/partition_traits_Octree.h @@ -0,0 +1,672 @@ +// 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é +// Sven Oesau + +#ifndef CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_OCTREE_H +#define CGAL_ISOSURFACING_3_INTERNAL_PARTITION_TRAITS_OCTREE_H + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#ifdef CGAL_LINKED_WITH_TBB +#include +#endif // CGAL_LINKED_WITH_TBB + +#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 { + +template +struct partition_traits; + +template +struct partition_traits > > +{ + using Orthtree = CGAL::Octree >; + +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 Node_index = typename Orthtree::Node_index; + using Uniform_coords = typename Orthtree::Global_coordinates; // coordinates on max depth level + + using vertex_descriptor = std::size_t; + using edge_descriptor = internal::OW_Edge_handle; + using cell_descriptor = std::size_t; + + 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::vector; + using Cell_vertices = std::array; + using Cell_edges = std::array; + +public: + static std::set get_leaf_edges(const Orthtree& o) + { + std::set leaf_edge_set; + std::size_t dim = std::size_t(1) << o.depth(); + for (Node_index node_index : o.traverse(CGAL::Orthtrees::Leaves_traversal(o))) + { + const Uniform_coords& coords_uniform = uniform_coordinates(node_index, o); + + // write all leaf edges in a set + const Uniform_coords& coords_global = o.global_coordinates(node_index); + const std::size_t depth = o.depth(node_index); + const std::size_t df = std::size_t(1) << (o.depth() - depth); + for (const auto& edge_voxels : internal::Cube_table::edge_to_voxel_neighbor) + { + bool are_all_voxels_leafs = true; + for (const auto& node_ijk : edge_voxels) + { + const std::size_t x = coords_uniform[0] + df * node_ijk[0]; + const std::size_t y = coords_uniform[1] + df * node_ijk[1]; + const std::size_t z = coords_uniform[2] + df * node_ijk[2]; + // check for overflow / ignore edges on boundary + if (x >= dim || y >= dim || z >= dim) + { + are_all_voxels_leafs = false; + break; + } + + const Node_index n = get_node(x, y, z, o); + if (o.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_edge_set.insert({ e_gl, depth }); + } + } + } + + return leaf_edge_set; + } + + static std::set get_leaf_vertices(const Orthtree& o) + { + std::set leaf_vertex_set; + for (Node_index node_index : o.traverse(CGAL::Orthtrees::Leaves_traversal(o))) + { + const Uniform_coords& coords_uniform = uniform_coordinates(node_index, o); + + // write all vertices edges in a set + const Uniform_coords& coords_global = o.global_coordinates(node_index); + const std::size_t depth = o.depth(node_index); + const std::size_t df = std::size_t(1) << (o.depth() - depth); + + for (int i = 0; i < internal::Cube_table::N_VERTICES; ++i) + { + Uniform_coords v_coords = coords_global; + typename Orthtree::Local_coordinates local_coords(i); + + for (int j = 0; j < 3; ++j) + v_coords[j] += std::size_t(local_coords[j]); + + for (int j = 0; j < 3; ++j) + v_coords[j] *= static_cast(df); + + const std::size_t lex = lex_index(v_coords[0], v_coords[1], v_coords[2], o.depth()); + leaf_vertex_set.insert(lex); + } + } + + return leaf_vertex_set; + } + + static void get_leaves(const Orthtree& o, + std::vector& cells, + std::vector& edges, + std::vector& vertices) + { + std::set leaf_edge_set; + std::set leaf_vertex_set; + std::size_t dim = std::size_t(1) << o.depth(); + cells.clear(); + for (Node_index node_index : o.traverse(CGAL::Orthtrees::Leaves_traversal(o))) + { + const Uniform_coords& coords_uniform = uniform_coordinates(node_index, o); + cells.push_back(node_index); + + // write all leaf edges in a set + const Uniform_coords& coords_global = o.global_coordinates(node_index); + const std::size_t depth = o.depth(node_index); + const std::size_t df = std::size_t(1) << (o.depth() - depth); + + for (int i = 0; i < internal::Cube_table::N_VERTICES; ++i) + { + Uniform_coords v_coords = coords_global; + typename Orthtree::Local_coordinates local_coords(i); + + for (int j = 0; j < 3; ++j) + v_coords[j] += std::size_t(local_coords[j]); + + for (int j = 0; j < 3; ++j) + v_coords[j] *= static_cast(df); + + const std::size_t lex = lex_index(v_coords[0], v_coords[1], v_coords[2], o.depth()); + leaf_vertex_set.insert(lex); + } + + for (const auto& edge_voxels : internal::Cube_table::edge_to_voxel_neighbor) + { + bool are_all_voxels_leafs = true; + for (const auto& node_ijk : edge_voxels) + { + const std::size_t x = coords_uniform[0] + df * node_ijk[0]; + const std::size_t y = coords_uniform[1] + df * node_ijk[1]; + const std::size_t z = coords_uniform[2] + df * node_ijk[2]; + // check for overflow / ignore edges on boundary + if (x >= dim || y >= dim || z >= dim) + { + are_all_voxels_leafs = false; + break; + } + + const Node_index n = get_node(x, y, z, o); + if (o.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_edge_set.insert({ e_gl, depth }); + } + } + } + + edges.clear(); + vertices.clear(); + + std::copy(leaf_edge_set.begin(), leaf_edge_set.end(), std::back_inserter(edges)); + std::copy(leaf_vertex_set.begin(), leaf_vertex_set.end(), std::back_inserter(vertices)); + } + + static Point_3 point(const vertex_descriptor v, + const Orthtree& o) + { + std::size_t dim_ = std::size_t(1) << o.depth(); + const auto bbox = o.bbox(0); + FT hx_ = (bbox.xmax() - bbox.xmin()) / dim_; + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(v, o.depth()); + + const FT x0 = bbox.xmin() + i * hx_; + const FT y0 = bbox.ymin() + j * hx_; + const FT z0 = bbox.zmin() + k * hx_; + + return { x0, y0, z0 }; + } + + + static Edge_vertices incident_vertices(const edge_descriptor& e, + const Orthtree& o) + { + return edge_vertices(e, o); + } + + static Cells_incident_to_edge incident_cells(const edge_descriptor& e_id, + const Orthtree& o) + { + 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 max_depth = o.depth(); + const std::size_t df = std::size_t(1) << (max_depth - 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], o); + Node_index n1 = get_node(i + voxel_neighbors[1][0], j + voxel_neighbors[1][1], k + voxel_neighbors[1][2], o); + Node_index n2 = get_node(i + voxel_neighbors[2][0], j + voxel_neighbors[2][1], k + voxel_neighbors[2][2], o); + Node_index n3 = get_node(i + voxel_neighbors[3][0], j + voxel_neighbors[3][1], k + voxel_neighbors[3][2], o); + + const Uniform_coords n0_uniform_coords = uniform_coordinates(n0, o); + const Uniform_coords n1_uniform_coords = uniform_coordinates(n1, o); + const Uniform_coords n2_uniform_coords = uniform_coordinates(n2, o); + const Uniform_coords n3_uniform_coords = uniform_coordinates(n3, o); + + 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); + + Cells_incident_to_edge ics = { n0_lex, n1_lex, n2_lex, n3_lex }; + + // With an adaptative octree, there is no reason that all cells around an interesting edge + // have the same level. If there is such a jump, we have a duplicate to filter. + if (ics.front() == ics.back()) { + ics.pop_back(); // Remove the duplicate if the first and last are the same. + } else { + ics.erase(std::unique(ics.begin(), ics.end()), ics.end()); + } + + CGAL_postcondition(ics.size() >= 3); + CGAL_postcondition(std::set(ics.begin(), ics.end()).size() == ics.size()); + + return ics; + } + + static std::size_t depth(const cell_descriptor c, + const Orthtree& o) + { + return o.depth(c); + } + + static Cell_vertices cell_vertices(const cell_descriptor c, + const Orthtree& o) + { + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(c, o.depth()); + Node_index node_index = get_node(i, j, k, o); + const std::size_t df = std::size_t(1) << (o.depth() - o.depth(node_index)); + + std::array v; + for (int v_id = 0; v_id < internal::Cube_table::N_VERTICES; ++v_id) + { + const int* l = internal::Cube_table::local_vertex_location[v_id]; + const std::size_t lex = lex_index(i + df * l[0], j + df * l[1], k + df * l[2], o.depth()); + v[v_id] = lex; + } + + return v; + } + + static Cell_edges cell_edges(const cell_descriptor c, + const Orthtree& o) + { + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(c, o.depth()); + Node_index node_index = get_node(i, j, k, o); + + const Uniform_coords& coords_global = o.global_coordinates(node_index); + const std::size_t depth = o.depth(node_index); + + std::array edges; + for (std::size_t e_id = 0; e_id < edges.size(); ++e_id) + { + const std::size_t e_gl = e_glIndex(e_id, coords_global[0], coords_global[1], coords_global[2], depth); + edges[e_id] = { e_gl, depth }; + } + + return edges; + } + + template + static void for_each_vertex(Functor& f, + const Orthtree& o, + const CGAL::Sequential_tag) + { + for(const vertex_descriptor v : get_leaf_vertices(o)) + f(v); + } + + template + static void for_each_vertex(Functor& f, + std::vector& vertices, + const Orthtree&, + const CGAL::Sequential_tag) + { + for (const vertex_descriptor v : vertices) + f(v); + } + + template + static void for_each_edge(Functor& f, + const Orthtree& o, + Sequential_tag) + { + for(const edge_descriptor& e : get_leaf_edges(o)) + f(e); + } + + template + static void for_each_edge(Functor& f, + std::vector& edges, + const Orthtree& o, + Sequential_tag) + { + if (edges.empty()) { + std::set edge_set = get_leaf_edges(o); + edges.assign(edge_set.begin(), edge_set.end()); + } + + for (const edge_descriptor& e : edges) + f(e); + } + + template + static void for_each_cell(Functor& f, + const Orthtree& o, + CGAL::Sequential_tag) + { + for (const cell_descriptor c : o.traverse(CGAL::Orthtrees::Leaves_traversal(o))) { + const Uniform_coords uc = uniform_coordinates(c, o); + f(lex_index(uc[0], uc[1], uc[2], o.depth())); + } + } + + template + static void for_each_cell(Functor& f, + std::vector& cells, + const Orthtree& o, + CGAL::Sequential_tag) + { + if (cells.empty()) { + auto cell_range = o.traverse(CGAL::Orthtrees::Leaves_traversal(o)); + cells.assign(cell_range.begin(), cell_range.end()); + } + + for (const cell_descriptor c : cells) { + const Uniform_coords uc = uniform_coordinates(c, o); + f(lex_index(uc[0], uc[1], uc[2], o.depth())); + } + } + +#ifdef CGAL_LINKED_WITH_TBB + template + static void for_each_vertex(Functor& f, + const Orthtree& o, + Parallel_tag) + { + auto edges = get_leaf_edges(o); + + tbb::parallel_for_each(edges.begin(), edges.end(), f); + } + + template + static void for_each_vertex(Functor& f, + std::vector& vertices, + const Orthtree&, + Parallel_tag) + { + 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 Orthtree& o, + Parallel_tag) + { + std::set edges = get_leaf_edges(o); + + tbb::parallel_for_each(edges.begin(), edges.end(), f); + } + + template + static void for_each_edge(Functor& f, + std::vector& edges, + const Orthtree& o, + Parallel_tag) + { + if (edges.empty()) { + std::set edge_set = get_leaf_edges(o); + edges.assign(edge_set.begin(), edge_set.end()); + } + + 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 Orthtree& o, + Parallel_tag) + { + auto func = [&](const typename Orthtree::Node_index& n) + { + const Uniform_coords& uc = uniform_coordinates(n, o); + f(lex_index(uc[0], uc[1], uc[2], o.depth())); + }; + + const auto& cells = o.traverse(CGAL::Orthtrees::Leaves_traversal(o)); + tbb::parallel_for_each(cells.begin(), cells.end(), func); + } + + template + static void for_each_cell(Functor& f, + std::vector& cells, + const Orthtree& o, + Parallel_tag) + { + if (cells.empty()) { + auto cell_range = o.traverse(CGAL::Orthtrees::Leaves_traversal(o)); + cells.assign(cell_range.begin(), cell_range.end()); + } + + auto iterator = [&](const tbb::blocked_range& r) + { + for (std::size_t i = r.begin(); i != r.end(); ++i) { + const Uniform_coords& uc = uniform_coordinates(cells[i], o); + f(lex_index(uc[0], uc[1], uc[2], o.depth())); + } + }; + + tbb::parallel_for(tbb::blocked_range(0, cells.size()), iterator); + } +#endif // CGAL_LINKED_WITH_TBB + + template + static void for_each_vertex(Functor& f, const Orthtree& o) + { return for_each_vertex(f, o, ConcurrencyTag{}); } + + template + static void for_each_edge(Functor& f, std::vector& edges, const Orthtree& o) + { return for_each_edge(f, edges, o, ConcurrencyTag{}); } + template + static void for_each_edge(Functor& f, const Orthtree& o) + { return for_each_edge(f, o, ConcurrencyTag{}); } + + template + static void for_each_cell(Functor& f, std::vector& cells, const Orthtree& o) + { return for_each_cell(f, cells, o, ConcurrencyTag{}); } + template + static void for_each_cell(Functor& f, const Orthtree& o) + { return for_each_cell(f, o, ConcurrencyTag{}); } + +private: + static Uniform_coords uniform_coordinates(Node_index node_index, + const Orthtree& o) + { + Uniform_coords coords = o.global_coordinates(node_index); + const std::size_t df = std::size_t(1) << (o.depth() - o.depth(node_index)); + for (int i = 0; i < 3; ++i) + coords[i] *= static_cast(df); + + return coords; + } + + static std::tuple ijk_index(const std::size_t lex_index, + const std::size_t depth) + { + 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 + static 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 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); + } + + static 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) + { + std::size_t dim = (std::size_t(1) << depth) + 1; + return k * dim * dim + j * dim + i; + } + + static std::array edge_vertices(const edge_descriptor& e_id, + const Orthtree& o) + { + namespace Tables = internal::Cube_table; + + std::size_t e_global_id, depth, max_depth = o.depth(); + std::tie(e_global_id, depth) = static_cast&>(e_id); + const std::size_t df = std::size_t(1) << (max_depth - 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_location[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 }; + } + + static Node_index get_node(const std::size_t i, + const std::size_t j, + const std::size_t k, + const Orthtree& o) + { + Node_index node_index = o.root(); + const std::size_t x = i; + const std::size_t y = j; + const std::size_t z = k; + + while (!o.is_leaf(node_index)) + { + std::size_t dist_to_max = o.depth() - o.depth(node_index) - 1; + typename Orthtree::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 = o.child(node_index, loc.to_ulong()); + } + + return node_index; + } + + static Node_index get_node(const std::size_t lex_index, + const Orthtree& o) + { + std::size_t i, j, k; + std::tie(i, j, k) = ijk_index(lex_index, o.depth()); + return get_node(i, j, k, o); + } +}; + +} // 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 00000000000..9e2f4973b50 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/tables.h @@ -0,0 +1,703 @@ +// 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_location[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 +}; + +/* The asymptotic decider can be unstable if certain corner points have similar function values. +* +*/ +constexpr int asymptotic_remap[][11] = { + {0, 1, 2, 3, 4, 5, 6, 7, 0b0000, 0b0001, 0b0010}, // regular, 4th bit invert coordinates, 3th bit swap coordinates, first two bits axis of coordinates + {0, 2, 4, 6, 1, 3, 5, 7, 0b0110, 0b0100, 0b0001}, // rot 0-7 + {0, 4, 1, 5, 2, 6, 3, 7, 0b0101, 0b0010, 0b0100}, // 2# rot 0-7 + {3, 1, 7, 5, 2, 0, 6, 4, 0b1110, 0b1100, 0b0001}, // rot 1-6 (0, 3, 5) (2, 7, 4) + {5, 1, 4, 0, 7, 3, 6, 2, 0b1101, 0b0010, 0b1100}, // 2# rot 1-6 + {3, 7, 2, 6, 1, 5, 0, 4, 0b1101, 0b1010, 0b0100}, // rot 2-5 (0, 3, 6) (1, 4, 7) + {6, 4, 2, 0, 7, 5, 3, 1, 0b0110, 0b1100, 0b1001}, // 2# rot 2-5 (0, 3, 6) (1, 4, 7) + {5, 7, 1, 3, 4, 6, 0, 2, 0b1110, 0b0100, 0b1001}, // rot 3-4 (1, 2, 7) (0, 5, 6) + {6, 2, 7, 3, 4, 0, 5, 1, 0b0101, 0b1010, 0b1100}, // 2# rot 3-4 (1, 2, 7) (0, 5, 6) + {7, 6, 5, 4, 3, 2, 1, 0, 0b1100, 0b1101, 0b1110} // mirrored regular +}; + +} // 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 00000000000..1b6449edca3 --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/internal/topologically_correct_marching_cubes_functors.h @@ -0,0 +1,1249 @@ +// 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 +#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 + using Triangles = tbb::enumerable_thread_specific > >; +#else + using Triangles = std::vector >; +#endif + +private: + const Domain& m_domain; + FT m_isovalue; + bool m_isovalue_nudging; + bool m_constrain_to_cell; + + Triangles m_triangles; + +public: + TMC_functor(const Domain& domain, + const FT isovalue, + const bool isovalue_nudging = true, + const bool constrain_to_cell = true) + : m_domain(domain), + m_isovalue(isovalue), + m_isovalue_nudging(isovalue_nudging), + m_constrain_to_cell(constrain_to_cell) + { } + + // returns the created triangle list + Triangles& triangles() + { + return m_triangles; + } + 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, m_isovalue_nudging); + + // 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); + + // construct triangles +#ifdef CGAL_LINKED_WITH_TBB + auto& local_triangles = m_triangles.local(); +#else + auto& local_triangles = m_triangles; +#endif + for(int t=0; t<16; t+=3) + { + const std::size_t t_index = i_case * 16 + t; + + if(Cube_table::triangle_cases[t_index] == -1) + break; + + 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]; + + local_triangles.push_back({vertices[eg2], vertices[eg1], vertices[eg0]}); + } + } + + // returns the created triangle list + template + void to_triangle_soup(PR& points, TR& triangles) const + { +#ifdef CGAL_LINKED_WITH_TBB + for(const auto& triangle_list : m_triangles) + { +#else + const auto& triangle_list = m_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]); + + triangles.push_back({id + 2, id + 1, id + 0}); + + CGAL_assertion(triangles.back().size() == 3); + } +#ifdef CGAL_LINKED_WITH_TBB + } +#endif + } + +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 }; + } + + void add_triangle(const Point_3& p0, + const Point_3& p1, + const Point_3& p2) + { +#ifdef CGAL_LINKED_WITH_TBB + auto& local_triangles = m_triangles.local(); + local_triangles.push_back({p0, p1, p2}); +#else + m_triangles.push_back({p0, p1, p2}); +#endif + } + + void face_intersections(const std::array& values, const std::size_t idx, const FT i0, FT& a, FT& b, FT& c, FT& d) { + const int *remap = internal::Cube_table::asymptotic_remap[idx]; + + a = (values[remap[1]] - values[remap[0]]) * (values[remap[6]] - values[remap[7]]) + (values[remap[2]] - values[remap[3]]) * (values[remap[4]] - values[remap[5]]); + b = (i0 - values[remap[0]]) * (-values[remap[6]] + values[remap[7]] + values[remap[4]] - values[remap[5]]) + + (values[remap[0]] - values[remap[1]]) * (values[remap[6]] - values[remap[4]]) - + (i0 - values[remap[4]]) * (-values[remap[2]] + values[remap[3]] + values[remap[0]] - values[remap[1]]) - + (values[remap[4]] - values[remap[5]]) * (values[remap[2]] - values[remap[0]]); + c = (i0 - values[remap[0]]) * (values[remap[6]] - values[remap[4]]) - (i0 - values[remap[4]]) * (values[remap[2]] - values[remap[0]]); + + d = b * b - FT(4) * a * c; + } + + bool calc_coordinates(const std::array& values, const std::size_t idx, const FT i0, const FT a, const FT b, const FT d, const std::vector &f_flag, unsigned char &q_sol, FT ui[2], FT vi[2], FT wi[2]) { + const int* remap = internal::Cube_table::asymptotic_remap[idx]; + FT d2 = sqrt(CGAL::to_double(d)); + + // compute u-coord of solutions + ui[0] = (-b - d2) / (FT(2) * a); + ui[1] = (-b + d2) / (FT(2) * a); + + // compute v-coord of solutions + FT g1 = values[remap[0]] * (FT(1) - ui[0]) + values[remap[1]] * ui[0]; + FT g2 = values[remap[2]] * (FT(1) - ui[0]) + values[remap[3]] * ui[0]; + vi[0] = (i0 - g1) / (g2 - g1); + + g1 = values[remap[0]] * (FT(1) - ui[1]) + values[remap[1]] * ui[1]; + g2 = values[remap[2]] * (FT(1) - ui[1]) + values[remap[3]] * ui[1]; + vi[1] = (i0 - g1) / (g2 - g1); + + // compute w-coordinates of solutions + g1 = values[remap[0]] * (FT(1) - ui[0]) + values[remap[1]] * ui[0]; + g2 = values[remap[4]] * (FT(1) - ui[0]) + values[remap[5]] * ui[0]; + wi[0] = (i0 - g1) / (g2 - g1); + + g1 = values[remap[0]] * (FT(1) - ui[1]) + values[remap[1]] * ui[1]; + g2 = values[remap[4]] * (FT(1) - ui[1]) + values[remap[5]] * ui[1]; + wi[1] = (i0 - g1) / (g2 - g1); + + if (((remap[8] & 0b0011) != 0b0000) || ((remap[9] & 0b0011) != 0b0001) || ((remap[10] & 0b0011) != 0b0010)) { + FT tmp[3][2] = { { ui[0], ui[1] }, { vi[0], vi[1] }, { wi[0], wi[1] } }; + + ui[0] = tmp[remap[8] & 0b0011][0]; + ui[1] = tmp[remap[8] & 0b0011][1]; + vi[0] = tmp[remap[9] & 0b0011][0]; + vi[1] = tmp[remap[9] & 0b0011][1]; + wi[0] = tmp[remap[10] & 0b0011][0]; + wi[1] = tmp[remap[10] & 0b0011][1]; + } + + if (remap[8] & 0b1000) { + ui[0] = 1 - ui[0]; + ui[1] = 1 - ui[1]; + } + + if (remap[8] & 0b0100) + std::swap(ui[0], ui[1]); + + if (remap[9] & 0b1000) { + vi[0] = 1 - vi[0]; + vi[1] = 1 - vi[1]; + } + + if (remap[9] & 0b0100) + std::swap(vi[0], vi[1]); + + if (remap[10] & 0b1000) { + wi[0] = 1 - wi[0]; + wi[1] = 1 - wi[1]; + } + + if (remap[10] & 0b0100) + std::swap(wi[0], wi[1]); + + if (std::isnan(CGAL::to_double(vi[0])) || std::isinf(CGAL::to_double(vi[0]))) + vi[0] = FT(-1); + if (std::isnan(CGAL::to_double(vi[1])) || std::isinf(CGAL::to_double(vi[1]))) + vi[1] = FT(-1); + + if (std::isnan(CGAL::to_double(wi[0])) || std::isinf(CGAL::to_double(wi[0]))) + wi[0] = FT(-1); + if (std::isnan(CGAL::to_double(wi[1])) || std::isinf(CGAL::to_double(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; + + return true; + } + + bool p_slice(const cell_descriptor& /*cell*/, + const FT i0, + const std::array& corners, + 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 + 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 + unsigned int v0, v1; + get_edge_vertex(eg, v0, v1, l_edges_); + + std::tie(vertices[eg], ecoord[eg]) = vertex_interpolation(corners[v0], corners[v1], + values[v0], values[v1], + i0, m_domain.geom_traits()); + } + + // next edge + flag <<= 1; + } + + // compute oriented contours + // + // A contour consists of segments 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 corresponding 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; + } + } + + // 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; + }; + + // 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; + + FT a, b, c, d; + std::size_t idx = 0; + unsigned char nr_u, nr_v, nr_w, nr_t; + for (; idx < 10; idx++) { + face_intersections(values, idx, i0, a, b, c, d); + + if (!calc_coordinates(values, idx, i0, a, b, d, f_flag, q_sol, ui, vi, wi)) + continue; + + bool ui_invalid = !std::isfinite(CGAL::to_double(ui[0])) || !std::isfinite(CGAL::to_double(ui[1])); + + nr_u = nrQSolFace(0, q_sol); + nr_v = nrQSolFace(1, q_sol); + nr_w = nrQSolFace(2, q_sol); + nr_t = (nr_u + nr_v + nr_w); + + if (get_cnt_size(0, c_) == 7 && (ui_invalid || nr_t == nr_u || nr_t == nr_v || nr_t == nr_w)) + continue; + + if (ui_invalid && q_sol != 0) { + if (!(nr_t == nr_u || nr_t == nr_v || nr_t == nr_w)) + continue; + + if (numberOfSetBits(q_sol) == 6) + continue; + } + + break; + } + + // 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 contours 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. + std::array tg_idx; + for(int i=0; i<6; ++i) + { + FT u = hvt[i][0]; + FT v = hvt[i][1]; + FT w = hvt[i][2]; + + if (m_constrain_to_cell) { + u = u < 0.05 ? 0.05 : (u > 0.95 ? 0.95 : u); + v = v < 0.05 ? 0.05 : (v > 0.95 ? 0.95 : v); + w = w < 0.05 ? 0.05 : (w > 0.95 ? 0.95 : w); + } + + 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] = 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) + + if (m_constrain_to_cell) { + ucoord = ucoord < 0.05 ? 0.05 : (ucoord > 0.95 ? 0.95 : ucoord); + vcoord = vcoord < 0.05 ? 0.05 : (vcoord > 0.95 ? 0.95 : vcoord); + wcoord = wcoord < 0.05 ? 0.05 : (wcoord > 0.95 ? 0.95 : wcoord); + } + + // 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])))); + + // loop over the contours + for(int i=0; i + +#include +#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 interpolated_value(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(CGAL::to_double((x_coord(p) - x_coord(min_p)) / x_coord(spacing))); + std::size_t j = std::size_t(CGAL::to_double((y_coord(p) - y_coord(min_p)) / y_coord(spacing))); + std::size_t k = std::size_t(CGAL::to_double((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) + FT(i) * spacing[0]; + const FT min_y = y_coord(min_p) + FT(j) * spacing[1]; + const FT min_z = z_coord(min_p) + FT(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 interpolated_gradient(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 interpolated_value(const Point_3& p, const Grid&, const std::vector&) const + { + return m_value_fn(p); + } + + Vector_3 interpolated_gradient(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 00000000000..4ca6d4f49ff --- /dev/null +++ b/Isosurfacing_3/include/CGAL/Isosurfacing_3/marching_cubes_3.h @@ -0,0 +1,111 @@ +// 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 output triangle soup + * \param triangles the faces of the output triangle soup. 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, or the base version \cgalCite{cgal:c-mcctci-95}.} + * \cgalParamType{Boolean} + * \cgalParamDefault{`true`} + * \cgalParamNEnd + * + * \cond SKIP_IN_MANUAL + * \cgalParamNBegin{isovalue_nudging} + * \cgalParamDescription{snapping of function value at corner points to an epsilon below the isovalue if the function value is within an epsilon range around the isovalue} + * \cgalParamType{Boolean} + * \cgalParamDefault{`true`} + * \cgalParamExtra{Snapping the function value at corner points prevents singular cases. The geometry is changed only in case of the function value being within a small epsilon range. The potential change is small. } + * \cgalParamNEnd + * + * \cgalParamNBegin{constrain_to_cell} + * \cgalParamDescription{whether to exclude the cell boundary from the interior vertex placement. } + * \cgalParamType{Boolean} + * \cgalParamDefault{`true`} + * \cgalParamExtra{Prevents degenerate or duplicate triangles.} + * \cgalParamNEnd + * \endcond + * + * \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); + const bool isovalue_nudging = choose_parameter(get_parameter(np, internal_np::isovalue_nudging), true); + const bool constrain_to_cell = choose_parameter(get_parameter(np, internal_np::constrain_to_cell), true); + + if(use_tmc) + { + internal::TMC_functor functor(domain, isovalue, isovalue_nudging, constrain_to_cell); + domain.template for_each_cell(functor); + internal::triangles_to_polygon_soup(functor.triangles(), points, triangles); + } + else + { + // run marching cubes + internal::Marching_cubes_3 functor(domain, isovalue, isovalue_nudging); + domain.template for_each_cell(functor); + 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 00000000000..830b3a6f2be --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/copyright @@ -0,0 +1,2 @@ +Inria Sophia-Antipolis (France) +GeometryFactory diff --git a/Isosurfacing_3/package_info/Isosurfacing_3/dependencies b/Isosurfacing_3/package_info/Isosurfacing_3/dependencies new file mode 100644 index 00000000000..64e88168233 --- /dev/null +++ b/Isosurfacing_3/package_info/Isosurfacing_3/dependencies @@ -0,0 +1,21 @@ +Algebraic_foundations +BGL +CGAL_ImageIO +Cartesian_kernel +Circulator +Distance_2 +Distance_3 +Installation +Intersections_2 +Intersections_3 +Interval_support +Isosurfacing_3 +Kernel_23 +Modular_arithmetic +Number_types +Orthtree +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 00000000000..aa18e64751a --- /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 00000000000..23559ebbbaf --- /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 00000000000..feb2f41e98e --- /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 00000000000..c2585469932 --- /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 00000000000..c1d821264e6 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/CMakeLists.txt @@ -0,0 +1,50 @@ +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") +create_single_source_cgal_program("test_tmc_topology.cpp") +#create_single_source_cgal_program("verifier.cpp") +create_single_source_cgal_program("test_tmc_csg.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_strategies.cpp") + create_single_source_cgal_program("dual_contouring_intersection_oracles.cpp") + + 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_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) + target_link_libraries(test_tmc_topology PRIVATE CGAL::TBB_support) + #target_link_libraries(verifier 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 00000000000..2861d651a55 --- /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_strategies.cpp b/Isosurfacing_3/test/Isosurfacing_3/dual_contouring_strategies.cpp new file mode 100644 index 00000000000..58e91a8271c --- /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 00000000000..5c717d122be --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_dual_contouring.cpp @@ -0,0 +1,304 @@ +#include "test_util.h" + +#include +#include + +#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_implicit_shape(std::function implicit_function) +{ + 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 >; + + 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)); + + 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)); + + const FT computed_volume = CGAL::Polygon_mesh_processing::volume(mesh); + std::cout << "Computed Volume: " << computed_volume << std::endl; + + // Brute force estimation of the volume + std::cout << "Estimating volume using Monte Carlo..." << std::endl; + const std::size_t num_samples = 1000000; + CGAL::Random rng(0); + + std::size_t inside_count = 0; + for (std::size_t i = 0; i < num_samples; ++i) { + Point sample(rng.get_double(bbox.xmin(), bbox.xmax()), + rng.get_double(bbox.ymin(), bbox.ymax()), + rng.get_double(bbox.zmin(), bbox.zmax())); + if (implicit_function(sample) <= isovalue) { + ++inside_count; + } + } + + const FT bbox_volume = (bbox.xmax() - bbox.xmin()) * (bbox.ymax() - bbox.ymin()) * (bbox.zmax() - bbox.zmin()); + const FT monte_carlo_volume = bbox_volume * static_cast(inside_count) / static_cast(num_samples); + + std::cout << "Monte Carlo Estimated Volume: " << monte_carlo_volume << std::endl; + + // Compare computed volume with Monte Carlo estimation + const FT tolerance = 1e-2 * bbox_volume; + assert(CGAL::abs(computed_volume - monte_carlo_volume) < tolerance); + std::cout << "Volume check passed!" << std::endl; +} + +template +void test_box() +{ + auto box_function = [](const typename K::Point_3& q) { + return IS::Shapes::box(typename K::Point_3(-1, -1, -1), typename K::Point_3(1, 1, 1), q); + }; + test_implicit_shape(box_function); +} + +template +void test_union_of_boxes() +{ + auto union_of_boxes_function = [](const typename K::Point_3& q) { + auto box_1 = [](const typename K::Point_3& q) { + return IS::Shapes::box(typename K::Point_3(0, 0, 0), typename K::Point_3(1, 1, 1), q); + }; + auto box_2 = [](const typename K::Point_3& q) { + return IS::Shapes::box(typename K::Point_3(0.5, 0.5, 0.5), typename K::Point_3(1.5, 1.5, 1.5), q); + }; + return IS::Shapes::shape_union(box_1, box_2, q); + }; + test_implicit_shape(union_of_boxes_function); +} + +template +void test_union_of_boxes_minus_sphere() +{ + auto b1_b2_ms1_function = [](const typename K::Point_3& q) { + auto box_1 = [](const typename K::Point_3& q) { + return IS::Shapes::box(typename K::Point_3(0, 0, 0), typename K::Point_3(1, 1, 1), q); + }; + auto box_2 = [](const typename K::Point_3& q) { + return IS::Shapes::box(typename K::Point_3(0.5, 0.5, 0.5), typename K::Point_3(1.5, 1.5, 1.5), q); + }; + auto b1_and_b2 = [&](const typename K::Point_3& q) { + return IS::Shapes::shape_union(box_1, box_2, q); + }; + auto s1 = [](const typename K::Point_3& q) { + return IS::Shapes::sphere(typename K::Point_3(0, 0, 0.5), 1, q); + }; + return IS::Shapes::shape_difference(b1_and_b2, s1, q); + }; + test_implicit_shape(b1_b2_ms1_function); +} + +template +void test_union_of_spheres() +{ + auto union_of_spheres_function = [](const typename K::Point_3& q) { + auto sphere_1 = [](const typename K::Point_3& q) { + return IS::Shapes::sphere(typename K::Point_3(0, 0, 0), 1, q); + }; + auto sphere_2 = [](const typename K::Point_3& q) { + return IS::Shapes::sphere(typename K::Point_3(0, 0, 0.5), 1, q); + }; + return IS::Shapes::shape_union(sphere_1, sphere_2, q); + }; + test_implicit_shape(union_of_spheres_function); +} + +template +void test_torus() +{ + auto torus_function = [](const typename K::Point_3& q) { + return IS::Shapes::torus(typename K::Point_3(0, 0, 0), typename K::Vector_3(0, 0, 1), 0.25, 1, q); + }; + test_implicit_shape(torus_function); +} + +// template +// void test_infinite_cylinder() +// { +// auto cylinder_function = [](const typename K::Point_3& q) { +// return IS::Shapes::infinite_cylinder(typename K::Point_3(0, 0, 0), typename K::Vector_3(1, 1, 1), 1, q); +// }; +// test_implicit_shape(cylinder_function); +// } + +// template +// void test_cylinder_and_cube_minus_sphere() +// { +// auto cyl_cube_function = [](const typename K::Point_3& q) { +// auto cyl = [](const typename K::Point_3& q) { +// return IS::Shapes::infinite_cylinder(typename K::Point_3(0, 0, 0), typename K::Vector_3(0, 0, 1), 0.5, q); +// }; +// auto cube = [](const typename K::Point_3& q) { +// return IS::Shapes::box(typename K::Point_3(-0.5, -0.5, -0.5), typename K::Point_3(0.5, 0.5, 0.5), q); +// }; +// auto cyl_and_cube = [&](const typename K::Point_3& q) { +// return IS::Shapes::shape_union(cyl, cube, q); +// }; +// auto sphere = [](const typename K::Point_3& q) { +// return IS::Shapes::sphere(typename K::Point_3(0, 0, 0.5), 1, q); +// }; +// return IS::Shapes::shape_difference(cyl_and_cube, sphere, q); +// }; +// test_implicit_shape(cyl_cube_function); +// } + +template +void test_implicit_shapes() +{ + test_box(); + test_union_of_boxes(); + test_union_of_boxes_minus_sphere(); + test_union_of_spheres(); + test_torus(); +} + +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_implicit_shapes>(); + test_image>(); + + test_implicit_shapes(); + 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 00000000000..a9094351ad1 --- /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 00000000000..309c8cf7c8d --- /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_TESTSUITE_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_TESTSUITE_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_TESTSUITE_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_tmc_csg.cpp b/Isosurfacing_3/test/Isosurfacing_3/test_tmc_csg.cpp new file mode 100644 index 00000000000..07af88abbf1 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_tmc_csg.cpp @@ -0,0 +1,90 @@ +#include "test_util.h" + +#include +#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 Grid = IS::Cartesian_grid_3; + using Values = IS::Value_function_3; + using Domain = IS::Marching_cubes_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + auto implicit_function = [](const Point& q) -> FT + { + // --- + auto b1 = [](const Point& q) { return IS::Shapes::box(Point(-1,-1,-1), Point(1,1,1), q); }; + auto s1 = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0), 1, q); }; + auto cyl1 = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,0,1), 0.5, q); }; + auto cyl2 = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,1,0), 0.5, q); }; + auto cyl3 = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(1,0,0), 0.5, q); }; + + auto b1_is1 = [&](const Point& q) { return IS::Shapes::shape_intersection(b1, s1, q); }; + auto cyl1_cyl2 = [&](const Point& q) { return IS::Shapes::shape_union(cyl1, cyl2, q); }; + auto cyl1_cyl2_cyl3 = [&](const Point& q) { return IS::Shapes::shape_union(cyl1_cyl2, cyl3, q); }; + + auto b1_ms1_mcyl1_mcyl2 = [&](const Point& q) { return IS::Shapes::shape_difference(b1_is1, cyl1_cyl2_cyl3, q); }; + + return b1_ms1_mcyl1_mcyl2(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 }; + Domain domain { grid, values }; + + Point_range points; + Polygon_range polygons; + IS::marching_cubes(domain, isovalue, points, polygons, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #polygons: " << polygons.size() << std::endl; + + CGAL::IO::write_OFF("MC.off", points, polygons, CGAL::parameters::stream_precision(17)); +} + +int main(int, char**) +{ + test_cube >(); + test_cube(); + + std::cout << "Done" << std::endl; + + return EXIT_SUCCESS; +} diff --git a/Isosurfacing_3/test/Isosurfacing_3/test_tmc_topology.cpp b/Isosurfacing_3/test/Isosurfacing_3/test_tmc_topology.cpp new file mode 100644 index 00000000000..88a7f5126fb --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_tmc_topology.cpp @@ -0,0 +1,868 @@ +#include "test_util.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#define CGAL_TESTSUITE_ISOSURFACING_OUTPUT + +namespace IS = CGAL::Isosurfacing; + +template +bool check_closed_not_empty(const Grid& grid, + const IS::Interpolated_discrete_values_3& values, + const typename Grid::Geom_traits::FT iso = 0) +{ + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + + FT boundary_min = std::numeric_limits::max(); + FT total_min = std::numeric_limits::max(); + for(std::size_t x=0; x iso) + { + return false; + } + + for(std::size_t x=0; x +bool check_iso_vertices(const Grid& grid, + const IS::Interpolated_discrete_values_3& values, + const typename Grid::Geom_traits::FT iso = 0) +{ + for(std::size_t x=0; x +bool check_iso_edges(const Grid& grid, + const IS::Interpolated_discrete_values_3& values, + const typename Grid::Geom_traits::FT iso = 0) +{ + for(std::size_t x=0; x +void to_grid(const Grid& grid, + IS::Interpolated_discrete_values_3& values, + const std::array& center_values) +{ + assert(grid.xdim() == 2); + assert(grid.ydim() == 2); + assert(grid.zdim() == 2); + + values(0, 0, 0) = center_values[0]; + values(0, 0, 1) = center_values[1]; + values(0, 1, 0) = center_values[2]; + values(0, 1, 1) = center_values[3]; + values(1, 0, 0) = center_values[4]; + values(1, 0, 1) = center_values[5]; + values(1, 1, 0) = center_values[6]; + values(1, 1, 1) = center_values[7]; +} + +template +void embed_in_positive_grid(const Grid& grid, + IS::Interpolated_discrete_values_3& values, + const std::array& center_values) +{ + using Geom_traits = typename Grid::Geom_traits; + using FT = typename Geom_traits::FT; + + assert(grid.xdim() == 4); + assert(grid.ydim() == 4); + assert(grid.zdim() == 4); + + CGAL::Random rand; + + FT min = (std::numeric_limits::max)(); + FT max = std::numeric_limits::lowest(); + for (const FT v : center_values) + { + min = (std::min)(min, v); + max = (std::max)(max, v); + } + max += 1e-3; + + for(std::size_t x=0; x(max, 2 * max - min); + + values(1, 1, 1) = center_values[0]; + values(1, 1, 2) = center_values[1]; + values(1, 2, 1) = center_values[2]; + values(1, 2, 2) = center_values[3]; + values(2, 1, 1) = center_values[4]; + values(2, 1, 2) = center_values[5]; + values(2, 2, 1) = center_values[6]; + values(2, 2, 2) = center_values[7]; +} + +enum class AmbiguousCase +{ + CONTOUR_6_VTS, + CONTOUR_7_VTS_CONFIG_A, + CONTOUR_7_VTS_CONFIG_B, + CONTOUR_8_VTS_CONFIG_A, + CONTOUR_8_VTS_CONFIG_B, + CONTOUR_9_VTS_CONFIG_A, + MC_4_TUNNEL, + MC_7_TUNNEL, + MC_10_TUNNEL, + MC_12_TUNNEL, + MC_13_TUNNEL, + MC_13_12_VTS_CONFIG_A, + MC_13_12_VTS_CONFIG_B, + MC_13_SIMPLE +}; + +template +FT generate_predefined_inner_ambiguity(const AmbiguousCase topology_case, + std::array& values) +{ + FT iso; + + switch (topology_case) { + case AmbiguousCase::CONTOUR_6_VTS: + values = {-0.960492426392903, 0.793207329559554, 0.916735525189067, -0.422761282626275, -0.934993247757551, -0.850129305868777, -0.0367116785741896, -0.656740699156587}; + iso = 0.0387; + break; + case AmbiguousCase::CONTOUR_7_VTS_CONFIG_A: + values = {10.2967816247556, 9.45145192686147, 9.54753711271687, 10.6482067822841, 9.81494966341055, 9.31168538578250, 9.80950580411527, 10.7451536262220}; + iso = 9.8588; + break; + case AmbiguousCase::CONTOUR_7_VTS_CONFIG_B: + values = {9.9998593195995547, 9.9993381282115549, 9.9979160205452544, 9.9986053863704142, 9.9999374908631235, 9.999424800002032, 9.9983922749132219, 9.999579324965488}; + iso = 9.9994608191478135; + break; + case AmbiguousCase::CONTOUR_8_VTS_CONFIG_A: + values = {0.454797708726920, 0.801330575352402, 0.649991492712356, -0.973974554763863, -0.134171007607162, -0.0844698148589140, -0.826313795402046, 0.433391503783462}; + iso = 0.0097; + break; + case AmbiguousCase::CONTOUR_8_VTS_CONFIG_B: + values = {9.9985934885536665, 9.9998695572230147, 9.9999045831713928, 9.999316745478131, 9.9986117521866866, 9.9998754368055813, 9.9999031760062458, 9.9992041920402936}; + iso = 9.99946; + break; + case AmbiguousCase::CONTOUR_9_VTS_CONFIG_A: + values = {-15.6504952739285, 2.90290077342601, 24.5454566157887, -24.5274127623786, 21.6741877710053, -4.49696327433901, -19.7891575872492, -15.5588482753161}; + iso = -1.8061; + break; + case AmbiguousCase::MC_4_TUNNEL: + values = {-7.70146936482581, -3.21868369245987, -5.44023748418735, 15.6051950593180, 12.7611835388515, -4.46952393442309, -11.7240576326183, -9.23038948829007}; + iso = -1.7660; + break; + case AmbiguousCase::MC_7_TUNNEL: + values = {-3.42744283804455, 0.621278122151001, 4.48110777981235, -1.95551129669134, 2.30448107596369, -1.04182240925489, -3.51087814405650, -6.44976786808517}; + iso = -0.6; + break; + case AmbiguousCase::MC_10_TUNNEL: + values = {-0.100000000000000, -6.11000000000000, 2, 10.2000000000000, 10.8000000000000, 1.80000000000000, -8.20000000000000, -0.180000000000000}; + iso = 1.10918; + break; + case AmbiguousCase::MC_12_TUNNEL: + values = {-3.37811990337124, 0.473258332744286, 2.54344310345736, 7.87658724379480, 4.38700713005133, -1.49950251870885, -4.21025867362045, -1.00233824192217}; + iso = 0.0708; + break; + case AmbiguousCase::MC_13_TUNNEL: + values = {2.74742516087490, -3.39187542578189, -12.5297639669456, 0.431517989649243, -6.92460546400188, 2.52228314017858, 14.6950568276448, -10.0732624062474}; + iso = -1.3064; + break; + case AmbiguousCase::MC_13_12_VTS_CONFIG_A: + values = {0.546912886195662, -0.421103532406922, -0.643375084081520, 0.855507421818445, -0.260686312588506, 0.206413666735986, 0.237274227130530, -0.183297728364877}; + iso = 0.0293; + break; + case AmbiguousCase::MC_13_12_VTS_CONFIG_B: + values = {1069, 843, 950, 1133, 958, 1029, 1198, 946}; + iso = 1007.4; + break; + case AmbiguousCase::MC_13_SIMPLE: + values = {0.520482995461163, -0.839814387388296, -0.467491517013617, 0.937814095887345, -0.825777099007084, 0.506695544835103, 0.345318915961394, -0.861107217966913}; + iso = 0.0293; + break; + default: + assert(false); + } + return iso; +} + +enum class SingularCase +{ + SIMPLE, + DOUBLE, + TRIPLE, + TUNNEL_OPEN, + TUNNEL, + TUNNEL_DOUBLE +}; + +template +FT generate_predefined_singular(const SingularCase topology_case, + std::array& values) +{ + FT iso; + bool invert; + + switch (topology_case) { + case SingularCase::SIMPLE: + values = {-1, 1, -1, -1, -1, -1, -1, 1}; + iso = 0; + invert = false; + break; + case SingularCase::DOUBLE: + values = {-1, -1, -0.5, 0.5, -2, 1, 0.5, -0.5}; + iso = 0; + invert = false; + break; + case SingularCase::TRIPLE: + values = {-1, 1, -1, -1, 1, -1, -1, 1}; + iso = 0; + invert = false; + break; + case SingularCase::TUNNEL_OPEN: + values = {-0.5, 0.5, -0.4, 0.46, 0.5, -0.5, 0.71, -0.92}; + iso = 0; + invert = false; + break; + case SingularCase::TUNNEL: + values = {-0.5, 0.5, -0.33, 0.46, 0.5, -0.5, 0.71, -0.92}; + iso = 0; + invert = false; + break; + case SingularCase::TUNNEL_DOUBLE: + values = {-1.2, -0.6, 1.2, 0.9, 9.7, 0.6, -9.7, -0.9}; + iso = 0; + invert = false; + break; + default: + assert(false); + } + + for (FT& v : values) + { + if (invert) + v = 2 * iso - v; + } + + return iso; +} + +enum class IsoEdgeCase +{ + SIMPLE, + SEPARATED, + SINGULAR, + DOUBLE_SINGULAR_SIMPLE, + DOUBLE_SINGULAR, + DOUBLE_SINGULAR_SPLIT, + WEDGE, + WEDGE_SINGULAR, + EDGE_TUNNEL +}; + +template +FT generate_predefined_iso_edge(const IsoEdgeCase topology_case, + std::array& values) +{ + FT iso; + bool invert; + + switch (topology_case) { + case IsoEdgeCase::SIMPLE: + values = {0, 0, -0.4, -0.3, 0.7, 0.5, -0.71, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::SEPARATED: + values = {0, 0, 0.4, 0.3, 0.7, 0.5, -0.71, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::SINGULAR: + values = {0, 0, 0.4, 0.3, -0.7, 0.5, -0.71, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::DOUBLE_SINGULAR_SIMPLE: + values = {0, 0, -0.8, 0.3, -0.7, 0.5, -0.2, 0.1}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::DOUBLE_SINGULAR: + values = {0, 0, -0.2, 0.3, -0.7, 0.5, -0.2, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::DOUBLE_SINGULAR_SPLIT: + values = {0, 0, 0.2, -0.3, -0.7, 0.5, -0.2, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::WEDGE: + values = {0, 0, -0.4, -0.3, 0, 0.5, -0.2, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::WEDGE_SINGULAR: + values = {0, 0, 0.2, 0.3, 0, 0.5, -0.2, -0.92}; + iso = 0; + invert = false; + break; + case IsoEdgeCase::EDGE_TUNNEL: + values = {0.3, 0.4, 0.7, -0.4, -0.7, 0, 0.4, 0}; + iso = 0; + invert = false; + break; + default: + assert(false); + } + + for (FT& v : values) + { + if (invert) + v = 2 * iso - v; + } + + return iso; +} + +enum class PlaneCase +{ + SIMPLE, + INTERSECTING, + PLANE_TUBES, + CROSS, +}; + +template +FT generate_predefined_plane(const PlaneCase topology_case, + std::array& values) +{ + FT iso; + bool invert; + + switch (topology_case) { + case PlaneCase::SIMPLE: + values = {0, 0, 0, 0, 0.7, 0.5, 0.71, 0.92}; + iso = 0; + invert = false; + break; + case PlaneCase::INTERSECTING: + values = {-12, -91, 12, 91, 97, 9, -97, -9}; + iso = 0; + invert = false; + break; + case PlaneCase::PLANE_TUBES: + values = {-0.5, 0, 0.4, 0, 0.5, 0, -0.5, 0}; + iso = 0; + invert = false; + break; + case PlaneCase::CROSS: + values = {1, -1, -1, 1, -1, 1, 1, -1}; + iso = 0; + invert = false; + break; + default: + assert(false); + } + + for (FT& v : values) + { + if (invert) + v = 2 * iso - v; + } + + return iso; +} + +// export meshes to compare tmc and mc to the high resolution trilinear interpolation +template +void compare_tmc_mc_trilinear(const std::array& case_values, + typename KERNEL::FT iso) +{ + using K = KERNEL; + + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Domain = IS::Marching_cubes_domain_3; + using Point = typename K::Point_3; + + using Point_range = std::vector; + using Triangle_range = std::vector >; + + using Mesh = CGAL::Surface_mesh; + + Grid grid { Point{-1., -1., -1.}, Point{1., 1., 1.}, std::array{2, 2, 2} }; + Values values { grid }; + Domain domain { grid, values }; + + write_debug_grid(domain, "debug_grid_2x2.off"); + + to_grid(grid, values, case_values); + + { + Grid grid_high_res { Point{-1., -1., -1.}, Point{1., 1., 1.}, std::array{51, 51, 51} }; + IS::Value_function_3 values_high_res { values, grid_high_res }; + IS::Marching_cubes_domain_3> domain_high_res { grid_high_res, values_high_res }; + + Point_range points_high_res; + Triangle_range triangles_high_res; + IS::marching_cubes(domain_high_res, iso, points_high_res, triangles_high_res, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + +#ifdef CGAL_TESTSUITE_ISOSURFACING_OUTPUT + CGAL::IO::write_polygon_soup("trilinear.off", points_high_res, triangles_high_res); +#endif + + assert(CGAL::Polygon_mesh_processing::is_polygon_soup_a_polygon_mesh(triangles_high_res)); + Mesh m; + CGAL::Polygon_mesh_processing::polygon_soup_to_polygon_mesh(points_high_res, triangles_high_res, m); + + std::cout << "components: " << connected_components(m) << std::endl; + std::cout << "euler: " << euler_characteristic(m) << std::endl; + std::cout << "boundaries: " << boundary_components(m) << std::endl; + } + + { + Point_range points_mc; + Triangle_range triangles_mc; + IS::marching_cubes(domain, iso, points_mc, triangles_mc, CGAL::parameters::use_topologically_correct_marching_cubes(false)); + +#ifdef CGAL_TESTSUITE_ISOSURFACING_OUTPUT + CGAL::IO::write_polygon_soup("mc.off", points_mc, triangles_mc); +#endif + } + + Point_range points; + Triangle_range triangles; + IS::marching_cubes(domain, iso, points, triangles, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + +#ifdef CGAL_TESTSUITE_ISOSURFACING_OUTPUT + CGAL::IO::write_polygon_soup("tmc.off", points, triangles); +#endif +} + +// check if the mesh created by tmc has the expected topological properties +template +void assert_tmc(const std::array& case_values, + typename KERNEL::FT iso, + std::size_t components, + int euler, + std::size_t boundaries) +{ + using K = KERNEL; + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Domain = IS::Marching_cubes_domain_3; + using Point = typename K::Point_3; + using Mesh = CGAL::Surface_mesh; + using Point_range = std::vector; + using Triangle_range = std::vector >; + + { + Grid grid { Point{-1., -1., -1.}, Point{1., 1., 1.}, std::array{2, 2, 2} }; + Values values { grid }; + Domain domain { grid, values }; + + to_grid(grid, values, case_values); + + Point_range points; + Triangle_range triangles; + IS::marching_cubes(domain, iso, points, triangles, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + 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(connected_components(m) == components); + assert(euler_characteristic(m) == euler); + assert(boundary_components(m) == boundaries); + } + + { + Grid grid_closed { Point{-1., -1., -1.}, Point{1., 1., 1.}, std::array{4, 4, 4} }; + Values values_closed { grid_closed }; + Domain domain_closed { grid_closed, values_closed }; + + embed_in_positive_grid(grid_closed, values_closed, case_values); + + Point_range points; + Triangle_range triangles; + IS::marching_cubes(domain_closed, iso, points, triangles, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + 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(!has_degenerate_faces(m)); + assert(is_manifold(m)); + assert(is_watertight(m)); + } +} + +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 Domain = IS::Marching_cubes_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + using Mesh = CGAL::Surface_mesh; + + auto implicit_function = [](const Point& q) -> FT + { + // --- + auto b1 = [](const Point& q) { return IS::Shapes::box(Point(-1,-1,-1), Point(1,1,1), q); }; + auto s1 = [](const Point& q) { return IS::Shapes::sphere(Point(0,0,0), 1, q); }; + auto cyl1 = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,0,1), 0.5, q); }; + auto cyl2 = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(0,1,0), 0.5, q); }; + auto cyl3 = [](const Point& q) { return IS::Shapes::infinite_cylinder(Point(0,0,0), Vector(1,0,0), 0.5, q); }; + + auto b1_is1 = [&](const Point& q) { return IS::Shapes::shape_intersection(b1, s1, q); }; + auto cyl1_cyl2 = [&](const Point& q) { return IS::Shapes::shape_union(cyl1, cyl2, q); }; + auto cyl1_cyl2_cyl3 = [&](const Point& q) { return IS::Shapes::shape_union(cyl1_cyl2, cyl3, q); }; + + auto b1_ms1_mcyl1_mcyl2 = [&](const Point& q) { return IS::Shapes::shape_difference(b1_is1, cyl1_cyl2_cyl3, q); }; + + return b1_ms1_mcyl1_mcyl2(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 TMC (Implicit function) with isovalue = " << isovalue << std::endl; + std::cout << "Kernel: " << typeid(K).name() << std::endl; + + Grid grid { bbox, spacing }; + Values values {implicit_function , grid }; + Domain domain { grid, values }; + + Mesh debug_grid; + auto debug_grid_creator = [&](const typename Domain::cell_descriptor& c) + { + std::vector cell_vertices; + for (const auto& v : domain.cell_vertices(c)) { + cell_vertices.push_back(debug_grid.add_vertex(domain.point(v))); + } + debug_grid.add_face(cell_vertices[6], cell_vertices[2], cell_vertices[0], cell_vertices[4]); + debug_grid.add_face(cell_vertices[1], cell_vertices[3], cell_vertices[7], cell_vertices[5]); + debug_grid.add_face(cell_vertices[0], cell_vertices[1], cell_vertices[5], cell_vertices[4]); + debug_grid.add_face(cell_vertices[6], cell_vertices[7], cell_vertices[3], cell_vertices[2]); + debug_grid.add_face(cell_vertices[2], cell_vertices[3], cell_vertices[1], cell_vertices[0]); + debug_grid.add_face(cell_vertices[4], cell_vertices[5], cell_vertices[7], cell_vertices[6]); + }; + domain.template for_each_cell(debug_grid_creator); + CGAL::IO::write_OFF("debug_grid.off", debug_grid); + + Point_range points; + Polygon_range polygons; + IS::marching_cubes(domain, isovalue, points, polygons, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + std::cout << "Output #vertices: " << points.size() << std::endl; + std::cout << "Output #polygons: " << polygons.size() << std::endl; + + CGAL::IO::write_polygon_soup("test_cube.off", points, polygons, CGAL::parameters::stream_precision(17)); +} + +template +void test_all_ambiguous_cases() +{ + using FT = typename KERNEL::FT; + + std::cout << "Testing ambiguous cases..." << std::endl; + + std::array case_values; + FT iso; + + std::cout << "\tTesting case 'CONTOUR_6_VTS'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::CONTOUR_6_VTS, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'CONTOUR_7_VTS_CONFIG_A'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::CONTOUR_7_VTS_CONFIG_A, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'CONTOUR_7_VTS_CONFIG_B'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::CONTOUR_7_VTS_CONFIG_B, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'CONTOUR_8_VTS_CONFIG_A'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::CONTOUR_8_VTS_CONFIG_A, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'CONTOUR_8_VTS_CONFIG_B'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::CONTOUR_8_VTS_CONFIG_B, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'CONTOUR_9_VTS_CONFIG_A'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::CONTOUR_9_VTS_CONFIG_A, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'MC_4_TUNNEL'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_4_TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 0, 2); + + std::cout << "\tTesting case 'MC_7_TUNNEL'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_7_TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 0, 2); + + std::cout << "\tTesting case 'MC_10_TUNNEL'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_10_TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 0, 2); + + std::cout << "\tTesting case 'MC_12_TUNNEL'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_12_TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 0, 2); + + std::cout << "\tTesting case 'MC_13_TUNNEL'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_13_TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 2, 1, 3); + + std::cout << "\tTesting case 'MC_13_12_VTS_CONFIG_A'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_13_12_VTS_CONFIG_A, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'MC_13_12_VTS_CONFIG_B'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_13_12_VTS_CONFIG_B, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'MC_13_SIMPLE'..." << std::endl; + iso = generate_predefined_inner_ambiguity(AmbiguousCase::MC_13_SIMPLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 3, 3, 3); +} + +template +void test_all_singular_cases() +{ + using FT = typename KERNEL::FT; + + std::cout << "Testing singular cases..." << std::endl; + + std::array case_values; + FT iso; + + std::cout << "\tTesting case 'SIMPLE'..." << std::endl; + iso = generate_predefined_singular(SingularCase::SIMPLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + + std::cout << "\tTesting case 'DOUBLE'..." << std::endl; + iso = generate_predefined_singular(SingularCase::DOUBLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + + std::cout << "\tTesting case 'TRIPLE'..." << std::endl; + iso = generate_predefined_singular(SingularCase::TRIPLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + + std::cout << "\tTesting case 'TUNNEL_OPEN'..." << std::endl; + iso = generate_predefined_singular(SingularCase::TUNNEL_OPEN, case_values); + compare_tmc_mc_trilinear(case_values, iso); + + std::cout << "\tTesting case 'TUNNEL'..." << std::endl; + iso = generate_predefined_singular(SingularCase::TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + + std::cout << "\tTesting case 'TUNNEL_DOUBLE'..." << std::endl; + iso = generate_predefined_singular(SingularCase::TUNNEL_DOUBLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); +} + +template +void test_all_iso_edge_cases() +{ + using FT = typename KERNEL::FT; + + std::cout << "Testing iso edge cases..." << std::endl; + + std::array case_values; + FT iso; + + std::cout << "\tTesting case 'SIMPLE'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::SIMPLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'SEPARATED'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::SEPARATED, case_values); + compare_tmc_mc_trilinear(case_values, iso); + + std::cout << "\tTesting case 'SINGULAR'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::SINGULAR, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'DOUBLE_SINGULAR_SIMPLE'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::DOUBLE_SINGULAR_SIMPLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'DOUBLE_SINGULAR'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::DOUBLE_SINGULAR, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'DOUBLE_SINGULAR_SPLIT'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::DOUBLE_SINGULAR_SPLIT, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 2, 2, 2); + + std::cout << "\tTesting case 'WEDGE'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::WEDGE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + + std::cout << "\tTesting case 'WEDGE_SINGULAR'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::WEDGE_SINGULAR, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'EDGE_TUNNEL'..." << std::endl; + iso = generate_predefined_iso_edge(IsoEdgeCase::EDGE_TUNNEL, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); +} + +// this category includes cases that are almost always non-manifold +template +void test_all_plane_cases() +{ + using FT = typename KERNEL::FT; + + std::cout << "Testing plane cases..." << std::endl; + + std::array case_values; + FT iso; + + std::cout << "\tTesting case 'SIMPLE'..." << std::endl; + iso = generate_predefined_plane(PlaneCase::SIMPLE, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 1, 1, 1); + + std::cout << "\tTesting case 'INTERSECTING'..." << std::endl; + iso = generate_predefined_plane(PlaneCase::INTERSECTING, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 2, 2, 2); + + std::cout << "\tTesting case 'PLANE_TUBES'..." << std::endl; + iso = generate_predefined_plane(PlaneCase::PLANE_TUBES, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 2, 2, 2); + + std::cout << "\tTesting case 'CROSS'..." << std::endl; + iso = generate_predefined_plane(PlaneCase::CROSS, case_values); + compare_tmc_mc_trilinear(case_values, iso); + assert_tmc(case_values, iso, 4, 4, 4); +} + +int main(int, char**) +{ + using K = CGAL::Simple_cartesian; + + test_all_ambiguous_cases(); + test_all_singular_cases(); + test_all_iso_edge_cases(); + test_all_plane_cases(); + + // this is the example, where the issue was originally discovered + test_cube(); + + 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 00000000000..c35f9705ef3 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/test_util.h @@ -0,0 +1,137 @@ +#ifndef CGAL_ISOSURFACING_TEST_UTIL_H +#define CGAL_ISOSURFACING_TEST_UTIL_H + +#include +#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 is_self_intersecting(const PolygonMesh& pmesh) { + return CGAL::Polygon_mesh_processing::does_self_intersect(pmesh); +} + +template +bool is_watertight(PolygonMesh& pmesh) { + const bool manifold = is_manifold(pmesh); + const bool closed = is_closed(pmesh); + const bool self_intersecting = is_self_intersecting(pmesh); + return manifold && closed && !self_intersecting; +} + +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 +std::size_t connected_components(PolygonMesh& mesh) +{ + using face_descriptor = typename boost::graph_traits::face_descriptor; + + auto fccmap = mesh.template add_property_map("f:CC").first; + + return CGAL::Polygon_mesh_processing::connected_components(mesh, fccmap); +} + +template +int euler_characteristic(const PolygonMesh& mesh) +{ + return mesh.number_of_vertices() - mesh.number_of_edges() + mesh.number_of_faces(); +} + +template +std::size_t boundary_components(const PolygonMesh& mesh) +{ + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + std::vector border_cycles; + + CGAL::Polygon_mesh_processing::extract_boundary_cycles(mesh, std::back_inserter(border_cycles)); + + return border_cycles.size(); +} + +template +int betti_0(PolygonMesh& mesh) { + return connected_components(mesh); +} + +template +int betti_2(PolygonMesh& mesh) { + return connected_components(mesh); // only for closed surfaces +} + +template +int betti_1(PolygonMesh& mesh) { + return betti_0(mesh) + betti_2(mesh) - euler_characteristic(mesh); +} + +// 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; +// } + +template +void write_debug_grid(const Domain& domain, const std::string& filename) { + using Point = typename Domain::Geom_traits::Point_3; + using Mesh = CGAL::Surface_mesh; + + Mesh debug_grid; + auto debug_grid_creator = [&](const typename Domain::cell_descriptor& c) + { + std::vector cell_vertices; + for (const auto& v : domain.cell_vertices(c)) { + cell_vertices.push_back(debug_grid.add_vertex(domain.point(v))); + } + debug_grid.add_face(cell_vertices[6], cell_vertices[2], cell_vertices[0], cell_vertices[4]); + debug_grid.add_face(cell_vertices[1], cell_vertices[3], cell_vertices[7], cell_vertices[5]); + debug_grid.add_face(cell_vertices[0], cell_vertices[1], cell_vertices[5], cell_vertices[4]); + debug_grid.add_face(cell_vertices[6], cell_vertices[7], cell_vertices[3], cell_vertices[2]); + debug_grid.add_face(cell_vertices[2], cell_vertices[3], cell_vertices[1], cell_vertices[0]); + debug_grid.add_face(cell_vertices[4], cell_vertices[5], cell_vertices[7], cell_vertices[6]); + }; + domain.template for_each_cell(debug_grid_creator); + CGAL::IO::write_OFF(filename, debug_grid); +} + +#endif // CGAL_ISOSURFACING_TEST_UTIL_H diff --git a/Isosurfacing_3/test/Isosurfacing_3/verifier.cpp b/Isosurfacing_3/test/Isosurfacing_3/verifier.cpp new file mode 100644 index 00000000000..568423ab927 --- /dev/null +++ b/Isosurfacing_3/test/Isosurfacing_3/verifier.cpp @@ -0,0 +1,245 @@ +#include "test_util.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +/* +Verifier for topological correctness +Based on: Topology Verification for Isosurface Extraction, Tiago Etiene +Data sets: [Marching Cubes cases] [Randomly generated grids] from http://liscustodio.github.io/C_MC33/ +*/ + +namespace IS = CGAL::Isosurfacing; + +template +void read_iso_volume(const std::string& filename, Grid& grid, Values& values) +{ + using Geom_traits = typename Grid::Geom_traits; + 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(); + + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) { + throw std::runtime_error("Cannot open file: " + filename); + } + + // Read the three dimensions + int nx, ny, nz; + file.read(reinterpret_cast(&nx), sizeof(int)); + file.read(reinterpret_cast(&ny), sizeof(int)); + file.read(reinterpret_cast(&nz), sizeof(int)); + + // Read the bounding box + // (xmin, xmax, ymin, ymax, zmin, zmax) + std::array bbox; + for (int i = 0; i < 6; ++i) { + file.read(reinterpret_cast(&bbox[i]), sizeof(float)); + } + + Iso_cuboid_3 span = iso_cuboid(point(bbox[0], bbox[2], bbox[4]), + point(bbox[1], bbox[3], bbox[5])); + grid = Grid { span, CGAL::make_array(nx, ny, nz) }; + + // Calculate total number of data points + const std::size_t total_points = nx * ny * nz; + + // Read the volume data (32-bit floats) + std::vector volume_data(total_points); + for (std::size_t i = 0; i < total_points; ++i) { + file.read(reinterpret_cast(&volume_data[i]), sizeof(float)); + //std::cout << volume_data[i] << std::endl; + } + + for(int x=0; x> euler; + return euler; +} + +std::array read_betti(const std::string& filename) { + std::ifstream file(filename); + if (!file.is_open()) { + throw std::runtime_error("Cannot open file: " + filename); + } + int b0, b1; + file >> b0; + file >> b1; + return {b0, b1}; +} + +template +void verify_euler() { + using Point = typename K::Point_3; + + using Mesh = CGAL::Surface_mesh; + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Domain = IS::Marching_cubes_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + using Mesh = CGAL::Surface_mesh; + + std::cout << " == Verify Euler ==" << std::endl; + + const std::size_t num_tests = 10000; + + for (std::size_t i = 0; i < num_tests; i++) { + Grid grid; + Values values { grid }; + Domain domain { grid, values }; + + std::string filename = "/path/to/data/MarchingCubes_cases/Grids/" + std::to_string(i) + "-scalar_field.iso"; + + std::cout << "Verify " << filename << "..." << std::endl; + + read_iso_volume(filename, grid, values); + + Point_range points; + Polygon_range triangles; + IS::marching_cubes(domain, 0, points, triangles, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + CGAL::IO::write_polygon_soup("last_result.off", points, triangles); + + 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); + + const int euler = euler_characteristic(m); + const int solution = read_euler("/path/to/data/MarchingCubes_cases/Grid_invariants/" + std::to_string(i) + "-euler_number.txt"); + + if (euler != solution) + std::cout << "error in test " << i << ": euler " << euler << " != " << solution << std::endl; + } +} + +template +void verify_betti() +{ + using Point = typename K::Point_3; + + using Mesh = CGAL::Surface_mesh; + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Domain = IS::Marching_cubes_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + using Mesh = CGAL::Surface_mesh; + + std::cout << " == Verify Betti ==" << std::endl; + + const std::size_t num_tests = 10000; + + for (std::size_t i = 0; i < num_tests; i++) { + + Grid grid; + Values values { grid }; + Domain domain { grid, values }; + + std::string filename = "/path/to/data/Closed_Surfaces/Grids/" + std::to_string(i) + "-scalar_field.iso"; + + std::cout << "Verify " << filename << "..." << std::endl; + + read_iso_volume(filename, grid, values); + + Point_range points; + Polygon_range triangles; + IS::marching_cubes(domain, 0, points, triangles, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + CGAL::IO::write_polygon_soup("last_result.off", points, triangles); + + 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); + + const int b0 = betti_0(m); + const int b1 = betti_1(m); + const auto solution = read_betti("/path/to/data/Closed_Surfaces/InvariantsGrid/" + std::to_string(i) + "-invariant_grid.txt"); + + if (b0 != solution[0]) + std::cout << "error in test " << i << ": b0 " << b0 << " != " << solution[0] << std::endl; + if (b1 != (solution[1] * 2)) + std::cout << "error in test " << i << ": b1 " << b1 << " != " << solution[1] << std::endl; + + } +} + +template +void compare_to_reference(const std::string& filename) +{ + using Point = typename K::Point_3; + + using Grid = IS::Cartesian_grid_3; + using Values = IS::Interpolated_discrete_values_3; + using Domain = IS::Marching_cubes_domain_3; + + using Point_range = std::vector; + using Polygon_range = std::vector >; + + Grid grid; + Values values { grid }; + Domain domain { grid, values }; + + read_iso_volume(filename, grid, values); + + Point_range points; + Polygon_range triangles; + IS::marching_cubes(domain, 0, points, triangles, CGAL::parameters::use_topologically_correct_marching_cubes(true).isovalue_nudging(true).constrain_to_cell(true)); + + CGAL::IO::write_polygon_soup("verify_tmc_orig.off", points, triangles, CGAL::parameters::stream_precision(17)); + + Grid grid_high_res { grid.span().min(), grid.span().max(), std::array{151, 151, 151} }; + IS::Value_function_3 values_high_res { values, grid_high_res }; + IS::Marching_cubes_domain_3> domain_high_res { grid_high_res, values_high_res }; + + Point_range points_high_res; + Polygon_range triangles_high_res; + IS::marching_cubes(domain_high_res, 0, points_high_res, triangles_high_res, CGAL::parameters::use_topologically_correct_marching_cubes(true)); + + CGAL::IO::write_polygon_soup("verify_reference.off", points_high_res, triangles_high_res); + + //write_debug_grid(domain, "verify_cell.off"); +} + +int main(int, char**) +{ + using K = CGAL::Simple_cartesian; + + verify_euler(); + verify_betti(); + //compare_to_reference("/path/to/data/MarchingCubes_cases/Grids/" + std::to_string(100) + "-scalar_field.iso"); + // compare_to_reference("/path/to/data/Closed_Surfaces/Grids/" + std::to_string(0) + "-scalar_field.iso"); +} \ No newline at end of file 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 c2606d52cac..6ca223f77c6 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 602cdc2386e..4876de76514 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 a0a6315a867..4af9a71a2b2 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 d22c3589164..e0ec7c20c3d 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/Projection_traits_xy_3.h b/Kernel_23/doc/Kernel_23/CGAL/Projection_traits_xy_3.h index 4b28f7dd1bd..055ec11736f 100644 --- a/Kernel_23/doc/Kernel_23/CGAL/Projection_traits_xy_3.h +++ b/Kernel_23/doc/Kernel_23/CGAL/Projection_traits_xy_3.h @@ -72,7 +72,7 @@ typedef Line_3 Line_2; /// constructors required to build the dual Voronoi diagram. The /// functors operate on the 2D projection of their arguments. They /// come with preconditions that projections of the arguments are -/// non-degenerate, eg. a line segment does not project on a single +/// non-degenerate, e.g. a line segment does not project on a single /// point, two points do not project on the same point, etc. In the /// following, we specify the choice of the `z`-coordinate in case a /// new point is constructed. diff --git a/Kernel_23/doc/Kernel_23/CGAL/Tetrahedron_3.h b/Kernel_23/doc/Kernel_23/CGAL/Tetrahedron_3.h index eee115731e1..c8f8aef1806 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 11a9c516269..08ec687bb82 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 4efcfac0752..00755f7cc20 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 5f77fcd9c84..8a2236104b0 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 84c61818845..a4566f5b6bb 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 f06e42222d3..6cd66c28188 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 @@ public: /// @{ /*! - 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 8a442b9fb8f..b97f9d48fcf 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 @@ public: /// @{ /*! - 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 f367f26df4c..b651fa7fa3c 100644 --- a/Kernel_23/doc/Kernel_23/Concepts/FunctionObjectConcepts.h +++ b/Kernel_23/doc/Kernel_23/Concepts/FunctionObjectConcepts.h @@ -4009,7 +4009,7 @@ public: /*! - 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); @@ -4021,7 +4021,7 @@ public: &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); @@ -4054,7 +4054,7 @@ public: /// @{ /*! - 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); @@ -4066,7 +4066,7 @@ public: &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); @@ -4338,7 +4338,7 @@ public: Kernel::Vector_3 const& n); /*! - introduces a variable of type `Kernel::Point_3`. + introduces a variable of type `Kernel::Circle_3`. It is initialized to the circle passing through the three points. \pre The three points are not collinear. */ @@ -7074,7 +7074,7 @@ public: /*! introduces a null vector. */ - Kernel::Vector_2 operator()(const Null_vector &NULL_VECTOR); + Kernel::Vector_2 operator()(const CGAL::Null_vector &v); /// @} @@ -7133,7 +7133,7 @@ public: /*! introduces a null vector. */ - Kernel::Vector_3 operator()(const Null_vector &NULL_VECTOR); + Kernel::Vector_3 operator()(const CGAL::Null_vector &v); /// @} @@ -7168,7 +7168,7 @@ public: &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. */ @@ -7176,7 +7176,7 @@ public: 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 @@ -7190,9 +7190,6 @@ public: \ingroup PkgKernel23ConceptsFunctionObjects \cgalConcept -\image html IsoCuboid.png -\image latex IsoCuboid.png - \cgalRefines{AdaptableBinaryFunction} \sa `CGAL::Iso_cuboid_3` @@ -7217,23 +7214,25 @@ public: &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 @@ -7600,7 +7599,7 @@ public: and also for `Type1` and `Type2` of respective types - `Kernel::Triangle_3` and `Kernel::Tetrahedron_3` - - `Kernel::Plane_3` and `Kernel::Sphere_3` (or the contrary) + - `Kernel::Plane_3` and `Kernel::Sphere_3` - `Kernel::Sphere_3` and `Kernel::Sphere_3`. */ diff --git a/Kernel_23/examples/Kernel_23/MyConstruct_point_2.h b/Kernel_23/examples/Kernel_23/MyConstruct_point_2.h index 962bdd88742..8e9407486d3 100644 --- a/Kernel_23/examples/Kernel_23/MyConstruct_point_2.h +++ b/Kernel_23/examples/Kernel_23/MyConstruct_point_2.h @@ -8,9 +8,8 @@ class MyConstruct_point_2 typedef typename K::Point_2 Point_2; typedef typename K::Line_2 Line_2; typedef typename Point_2::Rep Rep; -public: - typedef Point_2 result_type; +public: // Note : the CGAL::Return_base_tag is really internal CGAL stuff. // Unfortunately it is needed for optimizing away copy-constructions, // due to current lack of delegating constructors in the C++ standard. diff --git a/Kernel_23/examples/Kernel_23/intersection_visitor.cpp b/Kernel_23/examples/Kernel_23/intersection_visitor.cpp index 61d0dbba7e2..417363ecbdb 100644 --- a/Kernel_23/examples/Kernel_23/intersection_visitor.cpp +++ b/Kernel_23/examples/Kernel_23/intersection_visitor.cpp @@ -8,8 +8,6 @@ typedef K::Line_2 Line_2; typedef K::Intersect_2 Intersect_2; struct Intersection_visitor { - typedef void result_type; - void operator()(const Point_2& p) const { std::cout << p << std::endl; diff --git a/Kernel_23/include/CGAL/Circle_2.h b/Kernel_23/include/CGAL/Circle_2.h index b6020ff7528..e21b2ea62a6 100644 --- a/Kernel_23/include/CGAL/Circle_2.h +++ b/Kernel_23/include/CGAL/Circle_2.h @@ -102,7 +102,8 @@ public: return R().compute_squared_radius_2_object()(*this); } - Orientation orientation() const + typename R::Orientation + orientation() const { // This make_certain(), the uncertain orientation of circles, the orientation // of circles, are all yucky. @@ -177,18 +178,6 @@ public: return R().construct_bbox_2_object()(*this); } - typename R::Boolean - operator==(const Circle_2 &c) const - { - return R().equal_2_object()(*this, c); - } - - typename R::Boolean - operator!=(const Circle_2 &c) const - { - return !(*this == c); - } - Circle_2 transform(const Aff_transformation_2 &t) const { return t.transform(*this); diff --git a/Kernel_23/include/CGAL/Circle_3.h b/Kernel_23/include/CGAL/Circle_3.h index b23e651e9fc..0b103f3a864 100644 --- a/Kernel_23/include/CGAL/Circle_3.h +++ b/Kernel_23/include/CGAL/Circle_3.h @@ -101,14 +101,14 @@ public: return typename R::Construct_sphere_3()(*this); } - Point_3 center() const + decltype(auto) center() const { - return typename R::Construct_sphere_3()(*this).center(); + return diametral_sphere().center(); } - FT squared_radius() const + decltype(auto) squared_radius() const { - return typename R::Construct_sphere_3()(*this).squared_radius(); + return diametral_sphere().squared_radius(); } decltype(auto) @@ -122,7 +122,7 @@ public: return typename R::Construct_bbox_3()(*this); } - FT area_divided_by_pi() const + decltype(auto) area_divided_by_pi() const { return typename R::Compute_area_divided_by_pi_3()(*this); } @@ -152,16 +152,7 @@ public: template < typename R > inline -bool -operator==(const Circle_3 &p, - const Circle_3 &q) -{ - return R().equal_3_object()(p, q); -} - -template < typename R > -inline -bool +typename R::Boolean operator!=(const Circle_3 &p, const Circle_3 &q) { diff --git a/Kernel_23/include/CGAL/Direction_2.h b/Kernel_23/include/CGAL/Direction_2.h index 407d4e31ed1..d7be7b4d409 100644 --- a/Kernel_23/include/CGAL/Direction_2.h +++ b/Kernel_23/include/CGAL/Direction_2.h @@ -156,18 +156,6 @@ public: return this->vector(); } - typename R::Boolean - operator==(const Direction_2& d) const - { - return R().equal_2_object()(*this, d); - } - - typename R::Boolean - operator!=(const Direction_2& d) const - { - return !(*this == d); - } - Direction_2 transform(const Aff_transformation_2 &t) const { return t.transform(*this); diff --git a/Kernel_23/include/CGAL/Is_a_predicate.h b/Kernel_23/include/CGAL/Is_a_predicate.h deleted file mode 100644 index 4f23fa9c4e3..00000000000 --- a/Kernel_23/include/CGAL/Is_a_predicate.h +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2002 -// Utrecht University (The Netherlands), -// ETH Zurich (Switzerland), -// INRIA Sophia-Antipolis (France), -// Max-Planck-Institute Saarbruecken (Germany), -// and Tel-Aviv University (Israel). 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) : Sylvain Pion - -#ifndef CGAL_IS_A_PREDICATE_H -#define CGAL_IS_A_PREDICATE_H - -// How to determine if a kernel functor is a predicate or a construction. - -#include -#include - -namespace CGAL { - -namespace internal { - -// By default it's a construction -template -struct Return_type_of_predicate { - typedef CGAL::Tag_false type; -}; - -// Specializations for predicates -template <> -struct Return_type_of_predicate { - typedef CGAL::Tag_true type; -}; - -template <> -struct Return_type_of_predicate { - typedef CGAL::Tag_true type; -}; - -template <> -struct Return_type_of_predicate { - typedef CGAL::Tag_true type; -}; - -template <> -struct Return_type_of_predicate { - typedef CGAL::Tag_true type; -}; - -} // namespace internal - -template -struct Is_a_predicate { - typedef typename internal::Return_type_of_predicate< - typename Functor::result_type>::type type; -}; - -} //namespace CGAL - -#endif // CGAL_IS_A_PREDICATE_H diff --git a/Kernel_23/include/CGAL/Iso_cuboid_3.h b/Kernel_23/include/CGAL/Iso_cuboid_3.h index d279426727f..6fab726dccb 100644 --- a/Kernel_23/include/CGAL/Iso_cuboid_3.h +++ b/Kernel_23/include/CGAL/Iso_cuboid_3.h @@ -29,6 +29,8 @@ namespace CGAL { template class Iso_cuboid_3 : public R_::Kernel_base::Iso_cuboid_3 { + typedef typename R_::Boolean Boolean; + typedef typename R_::Bounded_side Bounded_side; typedef typename R_::RT RT; typedef typename R_::FT FT; typedef typename R_::Point_3 Point_3; @@ -173,25 +175,25 @@ public: return zmax(); } - bool + typename R::Boolean has_on_bounded_side(const Point_3 &p) const { return R().has_on_bounded_side_3_object()(*this,p); } - bool + Boolean has_on_unbounded_side(const Point_3 &p) const { return R().has_on_unbounded_side_3_object()(*this,p); } - bool + Boolean has_on_boundary(const Point_3 &p) const { return R().has_on_boundary_3_object()(*this,p); } - bool + Boolean has_on(const Point_3 &p) const { return has_on_boundary(p); @@ -203,7 +205,7 @@ public: return R().bounded_side_3_object()(*this,p); } - bool + Boolean is_degenerate() const { return R().is_degenerate_3_object()(*this); diff --git a/Kernel_23/include/CGAL/Iso_rectangle_2.h b/Kernel_23/include/CGAL/Iso_rectangle_2.h index 7d3dc5a4709..226388bd5a8 100644 --- a/Kernel_23/include/CGAL/Iso_rectangle_2.h +++ b/Kernel_23/include/CGAL/Iso_rectangle_2.h @@ -27,6 +27,8 @@ namespace CGAL { template class Iso_rectangle_2 : public R_::Kernel_base::Iso_rectangle_2 { + typedef typename R_::Boolean Boolean; + typedef typename R_::Bounded_side Bounded_side; typedef typename R_::RT RT; typedef typename R_::FT FT; typedef typename R_::Point_2 Point_2; @@ -94,19 +96,6 @@ public: return R().construct_max_vertex_2_object()(*this); } - bool - operator==(const Iso_rectangle_2 &i) const - { - return R().equal_2_object()(*this, i); - } - - bool - operator!=(const Iso_rectangle_2 &i) const - { - return ! (*this == i); - } - - decltype(auto) vertex(int i) const { @@ -169,22 +158,19 @@ public: return R().compute_area_2_object()(*this); } - - bool + Boolean has_on_boundary(const Point_2 &p) const { return R().has_on_boundary_2_object()(*this,p); } - - bool + Boolean has_on_bounded_side(const Point_2 &p) const { return R().has_on_bounded_side_2_object()(*this,p); } - - bool + Boolean has_on_unbounded_side(const Point_2 &p) const { return R().has_on_unbounded_side_2_object()(*this,p); @@ -196,8 +182,7 @@ public: return R().bounded_side_2_object()(*this,p); } - - bool + Boolean is_degenerate() const { return R().is_degenerate_2_object()(*this); diff --git a/Kernel_23/include/CGAL/Kernel/Type_mapper.h b/Kernel_23/include/CGAL/Kernel/Type_mapper.h index bdb2785889e..6be3487b3f4 100644 --- a/Kernel_23/include/CGAL/Kernel/Type_mapper.h +++ b/Kernel_23/include/CGAL/Kernel/Type_mapper.h @@ -19,62 +19,25 @@ #include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - namespace CGAL { +template < typename T, typename K1, typename K2 > +struct Type_mapper; + namespace internal { -// the default implementation is required to catch the odd one-out -// object like Bbox -template +template < typename T, typename K1, typename K2, typename = void > // last tparam is for SFINAE struct Type_mapper_impl { typedef T type; }; -template < typename T, typename K1, typename K2 > -struct Type_mapper_impl, K1, K2 > { - typedef std::vector< typename Type_mapper_impl::type > type; +template < typename K1, typename K2> +struct Type_mapper_impl { + typedef K2 type; }; -template < typename T, typename K1, typename K2 > -struct Type_mapper_impl, K1, K2 > { - typedef std::optional< typename Type_mapper_impl::type > type; -}; - - -/// The following code is equivalent to the one commented in CODE_TAG -/// except that with this one, the variant is really variant and not -/// a internal obfuscated type -#define CGAL_TYPEMAP_TYPEDEFS(z, n, t) typedef typename Type_mapper_impl< t##n, K1, K2 >::type A##n; - -#define CGAL_VARIANT_TYPEMAP(z, n, d) \ -template< typename K1, typename K2, BOOST_PP_ENUM_PARAMS(n, class T) > \ -struct Type_mapper_impl, K1, K2> { \ - BOOST_PP_REPEAT(n, CGAL_TYPEMAP_TYPEDEFS, T) \ - typedef std::variant type; \ -}; - -BOOST_PP_REPEAT_FROM_TO(1, 10, CGAL_VARIANT_TYPEMAP, _) - -#undef CGAL_TYPEMAP_TYPEDEFS -#undef CGAL_VARIANT_TYPEMAP - -// Then we specialize for all kernel objects. -// More details on why it is like that are here: https://github.com/CGAL/cgal/pull/4878#discussion_r459986501 +// 'Rep' gets a weird partial specialization because of Return_base_tag shenanigans. +// See https://github.com/CGAL/cgal/issues/3035#issuecomment-428721414 #define CGAL_Kernel_obj(X) \ template < typename K1, typename K2 > \ struct Type_mapper_impl < typename K1::X, K1, K2 > \ @@ -89,20 +52,28 @@ template < typename K1, typename K2 > struct Type_mapper_impl < typename K1::FT, K1, K2 > { typedef typename K2::FT type; }; +// This matches about anything and recursively calls Type_mapper on the template parameters +// until reaching the other cases (kernel objects, K1, FT) +template