diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 0458975d8574..69690b745fe1 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -5,6 +5,15 @@ Release History Release date: December 2021 +### [Weights](https://doc.cgal.org/5.4/Manual/packages.html#PkgWeights) (new package) + +- This package provides a simple and unified interface to different types of weights. + In particular, it groups all weights into three category: analytic weights including + all basic weights which can be computed analytically for a query point with respect to its + local neighbors in 2D and 3D; barycentric weights including all weights which can be computed + for a query point with respect to the vertices of a planar polygon; and weighting regions + including all weights which are used to balance other weights. + ### [2D and 3D Linear Geometry Kernel](https://doc.cgal.org/5.4/Manual/packages.html#PkgKernel23) - Added `construct_centroid_2_object()` and `compute_determinant_2_object()` in `Projection_traits_xy_3`, `Projection_traits_xz_3`, diff --git a/Installation/include/CGAL/license/Weights.h b/Installation/include/CGAL/license/Weights.h new file mode 100644 index 000000000000..d4e9c72af795 --- /dev/null +++ b/Installation/include/CGAL/license/Weights.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_WEIGHTS_H +#define CGAL_LICENSE_WEIGHTS_H + +#include +#include + +#ifdef CGAL_WEIGHTS_COMMERCIAL_LICENSE + +# if CGAL_WEIGHTS_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 Weights package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the Weights package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_WEIGHTS_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_WEIGHTS_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_WEIGHTS_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL Weights package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_WEIGHTS_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL Weights package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_WEIGHTS_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_WEIGHTS_H diff --git a/Installation/include/CGAL/license/gpl_package_list.txt b/Installation/include/CGAL/license/gpl_package_list.txt index f47af4261cff..7d7524822451 100644 --- a/Installation/include/CGAL/license/gpl_package_list.txt +++ b/Installation/include/CGAL/license/gpl_package_list.txt @@ -96,3 +96,4 @@ Triangulation dD Triangulations Visibility_2 2D Visibility Computation Voronoi_diagram_2 2D Voronoi Diagram Adaptor Tetrahedral_remeshing Tetrahedral Remeshing +Weights Weights 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 63759e8c3ba6..ef9cc2451427 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 @@ -30,6 +30,7 @@ and predicates defined in `K`. \cgalModels `ConstrainedTriangulationTraits_2` \cgalModels `ConvexHullTraits_2` \cgalModels `DelaunayMeshTraits_2` +\cgalModels `AnalyticWeightTraits_2` */ template< typename K > diff --git a/Kernel_23/doc/Kernel_23/dependencies b/Kernel_23/doc/Kernel_23/dependencies index f87a813989f1..91e24d123e91 100644 --- a/Kernel_23/doc/Kernel_23/dependencies +++ b/Kernel_23/doc/Kernel_23/dependencies @@ -10,3 +10,4 @@ Circular_kernel_2 Circular_kernel_3 STL_Extension Polyhedron +Weights diff --git a/Weights/doc/Weights/Concepts/AnalyticWeightTraits_2.h b/Weights/doc/Weights/Concepts/AnalyticWeightTraits_2.h new file mode 100644 index 000000000000..4b882bd82d51 --- /dev/null +++ b/Weights/doc/Weights/Concepts/AnalyticWeightTraits_2.h @@ -0,0 +1,202 @@ +/*! +\ingroup PkgWeightsRefConcepts +\cgalConcept + +A concept that describes the set of requirements of classes used in the computation +of analytic weights in 2D. + +\cgalHasModel +- All models of `Kernel` +- `CGAL::Projection_traits_xy_3` +- `CGAL::Projection_traits_yz_3` +- `CGAL::Projection_traits_xz_3` +*/ +class AnalyticWeightTraits_2 { + +public: + +/// \name Types +/// @{ + +/*! + A model of `FieldNumberType`. +*/ +typedef unspecified_type FT; + +/*! + `CGAL::Comparison_result` or `Uncertain`. +*/ +typedef unspecified_type Comparison_result; + +/*! + `CGAL::Orientation` or `Uncertain`. +*/ +typedef unspecified_type Orientation; + +/// @} + +/// \name Geometric Objects +/// @{ + +/*! + 2D point type. +*/ +typedef unspecified_type Point_2; + +/*! + 2D vector type. +*/ +typedef unspecified_type Vector_2; + +/// @} + +/// \name Constructions +/// @{ + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Point_2& p, const Point_2& q, const Point_2& r)` + + that returns the signed area of the triangle defined by the points `p`, `q`, and `r`. +*/ +typedef unspecified_type Compute_area_2; + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Point_2& p, const Point_2& q)` + + that returns the squared Euclidean distance between the points `p` and `q`. +*/ +typedef unspecified_type Compute_squared_distance_2; + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Vector_2& v)` + + that returns the squared length of the vector `v`. +*/ +typedef unspecified_type Compute_squared_length_2; + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Vector_2& v, const Vector_2& w)` + + that returns the scalar product of the vectors `v` and `w`. +*/ +typedef unspecified_type Compute_scalar_product_2; + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Vector_2& v, const Vector_2& w)` + + that returns the determinant of the vectors `v` and `w`. +*/ +typedef unspecified_type Compute_determinant_2; + +/*! + A construction object that must provide the function operator: + + `%Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r)` + + that returns the center of the circle passing through the points `p`, `q`, and `r`. + + \pre The points `p`, `q`, and `r` are not collinear. +*/ +typedef unspecified_type Construct_circumcenter_2; + +/*! + A construction object that must provide the function operator: + + `%Vector_2 operator()(const Point_2& p, const Point_2& q)` + + that returns the vector through the points `p` and `q`. +*/ +typedef unspecified_type Construct_vector_2; + +/*! + A construction object that must provide the function operator: + + `%Point_2 operator()(const Point_2& p, const Point_2& q)` + + that returns the midpoint between the points `p` and `q`. +*/ +typedef unspecified_type Construct_midpoint_2; + +/*! + A construction object that must provide the function operator: + + `%Point_2 operator()(const Point_2& p, const Point_2& q, const Point_2& r)` + + that returns the centroid of the points `p`, `q`, and `r`. +*/ +typedef unspecified_type Construct_centroid_2; + +/// @} + +/// \name Predicates +/// @{ + +/*! + A predicate object that must provide the function operator: + + `bool operator()(const Point_2& p, const Point_2& q)` + + that returns `true` if `p = q` and `false` otherwise. +*/ +typedef unspecified_type Equal_2; + +/*! + A predicate object that must provide the function operator: + + `bool operator()(const Point_2& p, const Point_2& q, const Point_2& r)` + + that returns `true` if the points `p`, `q`, and `r` are collinear and `false` otherwise. +*/ +typedef unspecified_type Collinear_2; + +/*! + A predicate object that must provide the function operator: + + `bool operator()(const Point_2& p, const Point_2& q)` + + that returns `true` iff the x-coordinate of `p` is smaller than the x-coordinate of `q` or + if they are the same and the y-coordinate of `p` is smaller than the y-coordinate of `q`. +*/ +typedef unspecified_type Less_xy_2; + +/*! + A predicate object that must provide the function operator: + + `Comparison_result operator()(const Point_2& p, const Point_2& q)` + + that compares the %Cartesian x-coordinates of the points `p` and `q`. +*/ +typedef unspecified_type Compare_x_2; + +/*! + A predicate object that must provide the function operator: + + `Comparison_result operator()(const Point_2& p, const Point_2& q)` + + that compares the %Cartesian y-coordinates of the points `p` and `q`. +*/ +typedef unspecified_type Compare_y_2; + +/*! + A predicate object that must provide the function operator: + + `Orientation operator()(const Point_2& p, const Point_2& q, const Point_2& r)` + + that returns `CGAL::LEFT_TURN` if `r` lies to the left of the oriented line `l` defined by `p` and `q`, + returns `CGAL::RIGHT_TURN` if `r` lies to the right of `l`, and returns `CGAL::COLLINEAR` if `r` lies on `l`. +*/ +typedef unspecified_type Orientation_2; + +/// @} + +}; diff --git a/Weights/doc/Weights/Concepts/AnalyticWeightTraits_3.h b/Weights/doc/Weights/Concepts/AnalyticWeightTraits_3.h new file mode 100644 index 000000000000..3560a85f4789 --- /dev/null +++ b/Weights/doc/Weights/Concepts/AnalyticWeightTraits_3.h @@ -0,0 +1,123 @@ +/*! +\ingroup PkgWeightsRefConcepts +\cgalConcept + +A concept that describes the set of requirements of classes used in the computation +of analytic weights in 3D. + +\cgalHasModel +- All models of `Kernel` +*/ +class AnalyticWeightTraits_3 { + +public: + +/// \name Types +/// @{ + +/*! + A model of `FieldNumberType`. +*/ +typedef unspecified_type FT; + +/// @} + +/// \name Geometric Objects +/// @{ + +/*! + 3D point type. +*/ +typedef unspecified_type Point_3; + +/*! + 3D vector type. +*/ +typedef unspecified_type Vector_3; + +/// @} + +/// \name Constructions +/// @{ + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Point_3& p, const Point_3& q)` + + that returns the squared Euclidean distance between the points `p` and `q`. +*/ +typedef unspecified_type Compute_squared_distance_3; + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Vector_3& v)` + + that returns the squared length of the vector `v`. +*/ +typedef unspecified_type Compute_squared_length_3; + +/*! + A construction object that must provide the function operator: + + `FT operator()(const Vector_3& v, const Vector_3& w)` + + that returns the scalar product of the vectors `v` and `w`. +*/ +typedef unspecified_type Compute_scalar_product_3; + +/*! + A construction object that must provide the function operator: + + `%Vector_3 operator()(const Vector_3& v, const Vector_3& w)` + + that returns the cross product of the vectors `v` and `w`. +*/ +typedef unspecified_type Construct_cross_product_vector_3; + +/*! + A construction object that must provide the function operator: + + `%Point_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r)` + + that returns the center of the circle passing through the points `p`, `q`, and `r`. + + \pre The points `p`, `q`, and `r` are not collinear. +*/ +typedef unspecified_type Construct_circumcenter_3; + +/*! + A construction object that must provide the function operator: + + `%Vector_3 operator()(const Point_3& p, const Point_3& q)` + + that returns the vector through the points `p` and `q`. +*/ +typedef unspecified_type Construct_vector_3; + +/*! + A construction object that must provide the function operator: + + `%Point_3 operator()(const Point_3& p, const Point_3& q)` + + that returns the midpoint between the points `p` and `q`. +*/ +typedef unspecified_type Construct_midpoint_3; + +/*! + A construction object that must provide two function operators: + + `%Point_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r)` + + that returns the centroid of the points `p`, `q`, and `r` and + + `%Point_3 operator()(const Point_3& p, const Point_3& q, const Point_3& r, const Point_3& s)` + + that returns the centroid of the points `p`, `q`, `r` and `s`. +*/ +typedef unspecified_type Construct_centroid_3; + +/// @} + +}; diff --git a/Weights/doc/Weights/Concepts/BarycentricWeights_2.h b/Weights/doc/Weights/Concepts/BarycentricWeights_2.h new file mode 100644 index 000000000000..89638d209f6d --- /dev/null +++ b/Weights/doc/Weights/Concepts/BarycentricWeights_2.h @@ -0,0 +1,30 @@ +/*! +\ingroup PkgWeightsRefConcepts +\cgalConcept + +A concept that describes the set of methods required in all classes used in +the computation of 2D generalized barycentric weights. + +\cgalHasModel +- `CGAL::Weights::Wachspress_weights_2` +- `CGAL::Weights::Mean_value_weights_2` +- `CGAL::Weights::Discrete_harmonic_weights_2` +*/ +class BarycentricWeights_2 { + +public: + + /*! + fills a destination range with 2D generalized barycentric weights + computed at the `query` point with respect to the vertices of the input polygon. + + \tparam OutIterator + a model of `OutputIterator` whose value type is `FieldNumberType` + + The number of computed weights is equal to the number of polygon vertices. + */ + template + OutIterator operator()( + const Point_2& query, OutIterator w_begin) + { } +}; diff --git a/Weights/doc/Weights/Doxyfile.in b/Weights/doc/Weights/Doxyfile.in new file mode 100644 index 000000000000..41a40985fa9e --- /dev/null +++ b/Weights/doc/Weights/Doxyfile.in @@ -0,0 +1,3 @@ +@INCLUDE = ${CGAL_DOC_PACKAGE_DEFAULTS} +PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - Weights" +EXTRACT_ALL = false diff --git a/Weights/doc/Weights/PackageDescription.txt b/Weights/doc/Weights/PackageDescription.txt new file mode 100644 index 000000000000..1fc97c0ee1fa --- /dev/null +++ b/Weights/doc/Weights/PackageDescription.txt @@ -0,0 +1,566 @@ +namespace CGAL { +namespace Weights { + +/*! +\defgroup PkgWeightsRef Weight Interface Reference + +\defgroup PkgWeightsRefConcepts Concepts +\ingroup PkgWeightsRef + +Concepts which are used to parameterize and define the functions and classes of this package. + + +\defgroup PkgWeightsRefAnalytic Analytic Weights +\ingroup PkgWeightsRef + +Models and functions that can be used to compute weights which have a simple analytic expression. + + +\defgroup PkgWeightsRefUniformWeights Uniform Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is always equal to 1. + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefShepardWeights Shepard Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = \frac{1}{d^a}\f$ +with notations shown in the figure below and \f$a\f$ any real number +being the power parameter. + +Here, the distance is computed between the points `p` and `q`. + +\cgalFigureBegin{shepard_weight, shepard.svg} + Notation used for the Shepard weight. +\cgalFigureEnd + +\cgalHeading{Specializations} +- For \f$a = 1\f$, this weight is equal to the \ref PkgWeightsRefInverseDistanceWeights "Inverse Distance Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre d != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefInverseDistanceWeights Inverse Distance Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = \frac{1}{d}\f$ +with notations shown in the figure below. + +Here, the distance is computed between the points `p` and `q`. + +Alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{inverse_distance_weight, inverse_distance.svg} + Notation used for the inverse distance weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +This weight is a special case of the \ref PkgWeightsRefShepardWeights "Shepard Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre d != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefThreePointFamilyWeights Three Point Family Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = \frac{d_2^a A_1 - d^a B + d_1^a A_2}{A_1 A_2}\f$ +with notations shown in the figure below and \f$a\f$ any real number +being the power parameter. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +This weight supports only planar configurations (see more in section about \ref Weights_Implementation_Coplanarity) +while alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{three_point_family_weight, three_point_family.svg} + Notation used for the three point family weight. +\cgalFigureEnd + +\cgalHeading{Specializations} +- For \f$a = 0\f$, this weight is equal to the +\ref PkgWeightsRefWachspressWeights "Wachspress Weight" and +\ref PkgWeightsRefAuthalicWeights "Authalic Weight". +- For \f$a = 1\f$, this weight is equal to the +\ref PkgWeightsRefMeanValueWeights "Mean Value Weight" and +\ref PkgWeightsRefTangentWeights "Tangent Weight". +- For \f$a = 2\f$, this weight is equal to the +\ref PkgWeightsRefDiscreteHarmonicWeights "Discrete Harmonic Weight" and +\ref PkgWeightsRefCotangentWeights "Cotangent Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre A1 != 0 && A2 != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefWachspressWeights Wachspress Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = \frac{C}{A_1 A_2}\f$ +with notations shown in the figure below. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +This weight supports only planar configurations (see more in section about \ref Weights_Implementation_Coplanarity) +while alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{wachspress_weight, wachspress.svg} + Notation used for the Wachspress weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +- This weight is equal to the \ref PkgWeightsRefAuthalicWeights "Authalic Weight". +- This weight is a special case of the \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre A1 != 0 && A2 != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefAuthalicWeights Authalic Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = 2 \frac{\cot\beta + \cot\gamma}{d^2}\f$ +with notations shown in the figure below. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +Alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{authalic_weight, authalic.svg} + Notation used for the authalic weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +- This weight is equal to the \ref PkgWeightsRefWachspressWeights "Wachspress Weight". +- This weight is a special case of the \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre d != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefMeanValueWeights Mean Value Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = \pm 2 \sqrt{\frac{2 (d_1 d_2 - D)}{(d d_1 + D_1)(d d_2 + D_2)}}\f$ +with notations shown in the figure below and dot products + +\f$D_1 = (p_0 - q) \cdot (p_1 - q)\f$, +\f$D_2 = (p_1 - q) \cdot (p_2 - q)\f$, and +\f$D = (p_0 - q) \cdot (p_2 - q)\f$. + +The \f$\pm\f$ sign is a sign of the weight that depends on the configuration. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +This weight supports only planar configurations (see more in section about \ref Weights_Implementation_Coplanarity) +while alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{mean_value_weight, mean_value.svg} + Notation used for the mean value weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +- This weight is equal to the \ref PkgWeightsRefTangentWeights "Tangent Weight". +- This weight is a special case of the \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre (d * d1 + D1) != 0 && (d * d2 + D2) != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefTangentWeights Tangent Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = 2 \frac{t_1 + t_2}{d}\f$, where +\f$t_1 = \frac{2 A_1}{d d_1 + D_1}\f$ and +\f$t_2 = \frac{2 A_2}{d d_2 + D_2}\f$ +with notations shown in the figure below and dot products + +\f$D_1 = (p_0 - q) \cdot (p_1 - q)\f$ and +\f$D_2 = (p_1 - q) \cdot (p_2 - q)\f$. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +Alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{tangent_weight, tangent.svg} + Notation used for the tangent weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +- This weight is equal to the \ref PkgWeightsRefMeanValueWeights "Mean Value Weight". +- This weight is a special case of the \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre (d * d1 + D1) != 0 && (d * d2 + D2) != 0 && d != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefDiscreteHarmonicWeights Discrete Harmonic Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = \frac{d_2^2 A_1 - d^2 B + d_1^2 A_2}{A_1 A_2}\f$ +with notations shown in the figure below. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +This weight supports only planar configurations (see more in section about \ref Weights_Implementation_Coplanarity) +while alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{discrete_harmonic_weight, discrete_harmonic.svg} + Notation used for the discrete harmonic weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +- This weight is equal to the \ref PkgWeightsRefCotangentWeights "Cotangent Weight". +- This weight is a special case of the \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\pre A1 != 0 && A2 != 0 + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefCotangentWeights Cotangent Weight +\ingroup PkgWeightsRefAnalytic + +\verbatim +#include +\endverbatim + +This weight is computed as +\f$w = 2 (\cot\beta + \cot\gamma)\f$ +with notations shown in the figure below. + +Here, `q` is a query point and the points `p0`, `p1`, and `p2` are its neighbors. + +Alternative formulations are explained in \ref Weights_Implementation. + +\cgalFigureBegin{cotangent_weight, cotangent.svg} + Notation used for the cotangent weight. +\cgalFigureEnd + +\cgalHeading{Alternative Formulations} +- This weight is equal to the \ref PkgWeightsRefDiscreteHarmonicWeights "Discrete Harmonic Weight". +- This weight is a special case of the \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight". + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\addtogroup PkgWeightsRefAnalytic + + +\defgroup PkgWeightsRefBarycentric Barycentric Weights +\ingroup PkgWeightsRef + +Models and functions that can be used to compute barycentric weights with respect +to polygons. These weights are then normalized in order to obtain barycentric coordinates. + + +\defgroup PkgWeightsRefBarycentricWachspressWeights Wachspress Weights +\ingroup PkgWeightsRefBarycentric + +\verbatim +#include +\endverbatim + +Wachspress weights which can be computed for a query point with respect to the +vertices of a strictly convex polygon. + +\addtogroup PkgWeightsRefBarycentric + + +\defgroup PkgWeightsRefBarycentricMeanValueWeights Mean Value Weights +\ingroup PkgWeightsRefBarycentric + +\verbatim +#include +\endverbatim + +Mean value weights which can be computed for a query point with respect to the +vertices of a simple polygon. + +\addtogroup PkgWeightsRefBarycentric + + +\defgroup PkgWeightsRefBarycentricDiscreteHarmonicWeights Discrete Harmonic Weights +\ingroup PkgWeightsRefBarycentric + +\verbatim +#include +\endverbatim + +Discrete Harmonic weights which can be computed for a query point with respect to the +vertices of a strictly convex polygon. + +\addtogroup PkgWeightsRefBarycentric + + +\defgroup PkgWeightsRefRegions Weighting Regions +\ingroup PkgWeightsRef + +Models and functions that can be used to compute weighting regions. These weights are +used to balance other weights. + + +\defgroup PkgWeightsRefUniformRegionWeights Uniform Region Weight +\ingroup PkgWeightsRefRegions + +\verbatim +#include +\endverbatim + +This weight is always equal to 1. + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\addtogroup PkgWeightsRefRegions + + +\defgroup PkgWeightsRefTriangularRegionWeights Triangular Region Weight +\ingroup PkgWeightsRefRegions + +\verbatim +#include +\endverbatim + +This weight is the area of the shaded region in the figure below. The region is +the triangle `[p, q, r]`. + +\cgalFigureBegin{triangular_area, triangular_cell.svg} + Notation used for the triangular cell. +\cgalFigureEnd + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\addtogroup PkgWeightsRefRegions + + +\defgroup PkgWeightsRefBarycentricRegionWeights Barycentric Region Weight +\ingroup PkgWeightsRefRegions + +\verbatim +#include +\endverbatim + +This weight is the area of the shaded region in the figure below. The region +is formed by the two midpoints of the edges incident to `q` and the barycenter of +the triangle `[p, q, r]`. + +\cgalFigureBegin{barycentric_area, barycentric_cell.svg} + Notation used for the barycentric cell. +\cgalFigureEnd + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\addtogroup PkgWeightsRefRegions + + +\defgroup PkgWeightsRefVoronoiRegionWeights Voronoi Region Weight +\ingroup PkgWeightsRefRegions + +\verbatim +#include +\endverbatim + +This weight is the area of the shaded region in the figure below. The region +is formed by the two midpoints of the edges incident to `q` and the circumcenter of +the triangle `[p, q, r]`. + +\cgalFigureBegin{voronoi_area, voronoi_cell.svg} + Notation used for the Voronoi cell. +\cgalFigureEnd + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\sa \ref PkgWeightsRefMixedVoronoiRegionWeights "Mixed Voronoi Region Weight" + +\addtogroup PkgWeightsRefRegions + + +\defgroup PkgWeightsRefMixedVoronoiRegionWeights Mixed Voronoi Region Weight +\ingroup PkgWeightsRefRegions + +\verbatim +#include +\endverbatim + +This weight is the area of the shaded region in the figure below. The region +is formed by the two midpoints of the edges incident to `q` and the circumcenter of +the triangle `[p, q, r]`. + +\cgalFigureBegin{mixed_voronoi_area, mixed_voronoi_cell.svg} + Notation used for the mixed Voronoi cell. +\cgalFigureEnd + +However, unlike the original \ref PkgWeightsRefVoronoiRegionWeights "Voronoi Region Weight", +if one of the angles in the triangle `[p, q, r]` is obtuse and the circumcenter vertex of the region +is outside this triangle, this vertex is moved to the midpoint of the edge `[r, p]` as shown in +the figure below. + +\cgalFigureBegin{mixed_voronoi_area_obtuse, mixed_voronoi_cell_obtuse.svg} + The case with the obtuse angle. +\cgalFigureEnd + +\tparam GeomTraits +a model of `AnalyticWeightTraits_2` for 2D points; +a model of `AnalyticWeightTraits_3` for 3D points + +\sa \ref PkgWeightsRefVoronoiRegionWeights "Voronoi Region Weight" + +\addtogroup PkgWeightsRefRegions + +\addtogroup PkgWeightsRef + + +\cgalPkgDescriptionBegin{Weight Interface, PkgWeightsRef} +\cgalPkgPicture{logo_120x120.png} + +\cgalPkgSummaryBegin +\cgalPkgAuthors{Dmitry Anisimov} +\cgalPkgDesc{Many geometric algorithms rely on weighted constructions. This package +provides a simple and unified interface to different types of weights. In particular, +it includes numerous weights with a simple analytic expression, generalized barycentric +weights, and weighting regions. All weights are available both in 2D and 3D.} +\cgalPkgManuals{Chapter_Weights, PkgWeightsRef} +\cgalPkgSummaryEnd + +\cgalPkgShortInfoBegin +\cgalPkgSince{5.4} +\cgalPkgBib{cgal:a-wi} +\cgalPkgLicense{\ref licensesGPL "GPL"} +\cgalPkgDemo{Polyhedron demo, polyhedron_3.zip} +\cgalPkgShortInfoEnd + +\cgalPkgDescriptionEnd + +## Weight Interface ## + +### Concepts ### +- `BarycentricWeights_2` +- `AnalyticWeightTraits_2` +- `AnalyticWeightTraits_3` + +### Analytic %Weights ### +- \ref PkgWeightsRefUniformWeights "Uniform Weight" +- \ref PkgWeightsRefShepardWeights "Shepard Weight" +- \ref PkgWeightsRefInverseDistanceWeights "Inverse Distance Weight" +- \ref PkgWeightsRefThreePointFamilyWeights "Three Point Family Weight" +- \ref PkgWeightsRefWachspressWeights "Wachspress Weight" +- \ref PkgWeightsRefAuthalicWeights "Authalic Weight" +- \ref PkgWeightsRefMeanValueWeights "Mean Value Weight" +- \ref PkgWeightsRefTangentWeights "Tangent Weight" +- \ref PkgWeightsRefDiscreteHarmonicWeights "Discrete Harmonic Weight" +- \ref PkgWeightsRefCotangentWeights "Cotangent Weight" + +### Barycentric %Weights ### +- \ref PkgWeightsRefBarycentricWachspressWeights "Wachspress Weights" +- \ref PkgWeightsRefBarycentricMeanValueWeights "Mean Value Weights" +- \ref PkgWeightsRefBarycentricDiscreteHarmonicWeights "Discrete Harmonic Weights" + +### Weighting Regions ### +- \ref PkgWeightsRefUniformRegionWeights "Uniform Region Weight" +- \ref PkgWeightsRefTriangularRegionWeights "Triangular Region Weight" +- \ref PkgWeightsRefBarycentricRegionWeights "Barycentric Region Weight" +- \ref PkgWeightsRefVoronoiRegionWeights "Voronoi Region Weight" +- \ref PkgWeightsRefMixedVoronoiRegionWeights "Mixed Voronoi Region Weight" +*/ + +} /* namespace Weights */ +} /* namespace CGAL */ diff --git a/Weights/doc/Weights/Weights.txt b/Weights/doc/Weights/Weights.txt new file mode 100644 index 000000000000..911f6dd7bc5f --- /dev/null +++ b/Weights/doc/Weights/Weights.txt @@ -0,0 +1,218 @@ +namespace CGAL { +namespace Weights { + +/*! +\mainpage User Manual +\anchor Chapter_Weights + +\cgalAutoToc +\author Dmitry Anisimov + +\section Weights_Intro Introduction + +Many geometric algorithms rely on the intermediate computation of scalars, so-called *weights*, +which are then used for solving different linear systems or to favor one result over another, +also known as *weighting*. This package provides a simple and unified interface +to different types of weights. + +A typical example of a geometric algorithm that requires weights is the *Laplace smoothing* +of a triangle mesh: + +\f$v_i \leftarrow v_i + h \lambda\Delta v_i\f$, + +where \f$v_i\f$ is the position of the mesh vertex \f$i\f$, \f$h\f$ is a sufficiently +small time step, \f$\lambda\f$ is the scalar diffusion coefficient, and \f$\Delta v_i\f$ +is the discrete average of the *Laplace-Beltrami operator* at vertex \f$v_i\f$ computed +using the *cotangent weights*: + +\f$\Delta v_i = w_i\sum_{v_j \in N_1(v_i)} w_{ij} (v_j - v_i)\f$, + +where \f$w_i = \frac{1}{2A_i}\f$ and \f$w_{ij} = \cot\beta_{ij} + \cot\gamma_{ij}\f$ +and \f$A_i\f$ is a *local averaging domain*. + +Here, the weights \f$w_{ij}\f$ can be computed using the +\ref PkgWeightsRefCotangentWeights "cotangent weight" and the +local averaging domain can be computed using the +\ref PkgWeightsRefMixedVoronoiRegionWeights "mixed Voronoi region weight". The +algorithm above smooths the mesh geometry, resulting in a higher quality version of the +original mesh. The full example of the discretized *Laplacian* for all vertices +of a triangle mesh can be found \ref Weights_Examples_WeightedLaplacian "here". + +There are many other scenarios where the weights from this package are used. In particular, +the following \cgal packages make use of weights described in this package: +\ref PkgBarycentricCoordinates2 "2D Generalized Barycentric Coordinates", +\ref PkgPolygonMeshProcessing "Polygon Mesh Processing", +\ref PkgSurfaceMeshDeformation "Triangulated Surface Mesh Deformation", +\ref PkgSurfaceMeshParameterization "Triangulated Surface Mesh Parameterization", +\ref PkgSurfaceMeshSkeletonization "Triangulated Surface Mesh Skeletonization", and +\ref PkgHeatMethodSummary "The Heat Method". + + +\section Weights_Groups Weights + +We call *analytic weights* all weights which can be computed with a simple analytic +expression. All weights from this package can be computed analytically. However, +for better navigation through all available weights and their applications, we distinguish +three typical groups of weights: + +- \ref PkgWeightsRefAnalytic "Analytic Weights" +include all basic weights which can be computed for a query point with respect to its local +neighbors in 2D or 3D, however these neighbors are defined. Usually, the configuration is +a query point and three other points. These weights return one unique value per query point. +- \ref PkgWeightsRefBarycentric "Barycentric Weights" +include all weights which can be computed for a query point with respect to the vertices +of a planar polygon. These weights return \f$n\f$ values per query +point, where \f$n\f$ is the number of polygon vertices. Barycentric weights are also used +for computing \ref PkgBarycentricCoordinates2 "2D barycentric coordinates". +- \ref PkgWeightsRefRegions "Weighting Regions" +include all weights which are used to balance other weights but are rarely used on their own. +Sometimes, such weights are also referred to as *local averaging regions*. These weights +are usually lengths, areas, and volumes of 2D and 3D objects. + + +\section Weights_Implementation Implementation + +All weight functions have a simple and unified interface. In particular, all analytic weight functions +usually take a query point and three other points in 2D or 3D and return a unique scalar. They all +have the same signature and are parameterized by a traits class that must be a model of +`AnalyticWeightTraits_2` for 2D computations or `AnalyticWeightTraits_3` for 3D computations. + +The barycentric weight functions are parameterized by a traits class of the concept +`AnalyticWeightTraits_2` and they are all models of the concept `BarycentricWeights_2`. +They take an input polygon and a query point and compute the weights at this point +with respect to all vertices of the polygon. The computed weights are then returned in a +container providing the corresponding output iterator. These weight functions also provide a +\ref PkgPropertyMap "property map" mechanism for mapping a user type of the polygon +vertex to `CGAL::Point_2`. + +All weighting regions have the same signature and are parameterized by a traits class +of the concept `AnalyticWeightTraits_2` or `AnalyticWeightTraits_3`. The returned weight +is a unique scalar. + +The `traits` parameter can be omitted for all functions and classes if it can be deduced +from the input point type using `CGAL::Kernel_traits`. + +Several weights in this package have different implementations. One reason to have it is +explained in section about \ref Weights_Implementation_Coplanarity. Another reason is that +the same weights are named and computed differently in different communities. If one searches +for these weights, one needs to know all their alternative names which is problematic. We provide +the most common names and implementations of these weights. + + +\subsection Weights_Implementation_Coplanarity Coplanarity + +When computing weights for a query point \f$q\f$ with respect to its neighbors +\f$p_0\f$, \f$p_1\f$, and \f$p_2\f$, the local configuration is a quadrilateral +[\f$p_0\f$, \f$p_1\f$, \f$p_2\f$, \f$q\f$] or two connected triangles [\f$q\f$, \f$p_0\f$, \f$p_1\f$] +and [\f$q\f$, \f$p_1\f$, \f$p_2\f$]. When working in 3D, these triangles are not +necessarily coplanar, in other words, they do not belong to the same common plane. + +Certain weights in this package support only coplanar configurations, while other weights support both. +The weights which support non-coplanar configurations, provide the corresponding overloads with 3D points, +while other weights accept only 2D point types. For example, \ref PkgWeightsRefCotangentWeights "cotangent weights" +support both coplanar and non-coplanar configurations, while \ref PkgWeightsRefDiscreteHarmonicWeights "discrete harmonic weights" +support only coplanar configurations. + + +\subsection Weights_Implementation_Edge_Cases Edge Cases + +None of the weights in this package are defined for query points which belong to + +- *end segments* so-called *edges*, for example [\f$p_0\f$, \f$p_1\f$] or [\f$p_1\f$, \f$p_2\f$] or any polygon edge and to +- *end points* so-called *corners*, for example \f$p_0\f$, \f$p_1\f$, or \f$p_2\f$ or any polygon corner. + +For example, if \f$q\f$ = \f$p_0\f$ or \f$q \in [p_0, p_1]\f$, a weight may be undefined +due to invalid operations such as division by zero. + +Several weights also require additional conditions to be satisfied. Consult the reference +manual for more details. + + +\section Weights_Examples Examples + +In this section, you can find a few examples of how and when the provided +weights can be used. + + +\subsection Weights_Examples_First The First Example + +This trivial example shows how to compute several analytic weights and weighting regions. +Other weights have the same interface so they can be computed analogously. + +\cgalExample{Weights/weights.cpp} + + +\subsection Weights_Examples_CoordinatesOneQuery Computing 2D Coordinates for One Query Point + +This example shows how to compute barycentric weights and barycentric coordinates, +which are normalized barycentric weights, for a query point with respect to a polygon +in 2D. Since we have only one query point, we use a free function to show the simplified +interface. For multiple query points though, calling a free function is not efficient +(see the following example for more details). The used type of barycentric weights is +`Discrete_harmonic_weights_2`. + +\cgalExample{Weights/coordinates_one_query.cpp} + + +\subsection Weights_Examples_CoordinatesMultipleQueries Computing 2D Coordinates for Multiple Query Points + +This example shows how to compute barycentric weights and barycentric coordinates, +which are normalized barycentric weights, for a set of query points with respect to +a polygon in 2D. Since we have multiple query points, we first create a class and then +use it to compute the weights. Using a class for multiple query points is preferred, because +in that case, the memory required for computing weights is allocated only once, while +when using a free function as in the previous example, it is allocated for each query point. +The used type of barycentric weights is `Wachspress_weights_2`. + +\cgalExample{Weights/coordinates_multiple_queries.cpp} + + +\subsection Weights_Examples_CustomTraits Weights with Custom Traits + +As you could see from the reference manual, it is possible to provide your own traits class +with basic geometric objects, constructions, and predicates to the weight functions. +All weights in this \cgal component are models of the `AnalyticWeightTraits_2` and `AnalyticWeightTraits_3` +concepts. However, many weights do not require all objects from these concepts. This example shows that +the inverse distance weight, for instance, requires only the squared distance object which is specified +in the custom traits class. + +\cgalExample{Weights/custom_traits.cpp} + + +\subsection Weights_Examples_WeightedLaplacian Constructing Weighted Laplacian + +A typical example of using weights is discretizing *Poisson* and *Laplace +equations* which play an important role in various geometry processing applications +such as, for example, \ref PkgSurfaceMeshDeformation "Surface Mesh Deformation" and +\ref PkgSurfaceMeshParameterization "Surface Mesh Parameterization" (see +also \ref Weights_Intro "Introduction" for more details). This example shows +how to write the *discretized Laplacian* for all vertices of the given triangle mesh in +matrix notation. We use the standard cotangent discretization weighted by the areas of +the mixed Voronoi cells around each mesh vertex. + +\cgalExample{Weights/weighted_laplacian.cpp} + + +\subsection Weights_Examples_Convergence Convergence + +This little example shows how to use the family of weights which includes multiple types +of weights in one function. In particular, we show how, by changing an input parameter, +we converge from the `Wachspress_weights_2` to the `Mean_value_weights_2`. + +\cgalExample{Weights/convergence.cpp} + + +\section Weights_History History + +This package is a part of the weights unification effort inside \cgal that has been +carried out by Dmitry Anisimov in 2020. + + +\section Weights_Acknowledgements Acknowledgments + +We wish to thank Guillaume Damiand, Andreas Fabri, and Mael Rouxel-Labbé for useful +discussions and reviews. +*/ + +} /* namespace Weights */ +} /* namespace CGAL */ diff --git a/Weights/doc/Weights/dependencies b/Weights/doc/Weights/dependencies new file mode 100644 index 000000000000..987f0ea9bd49 --- /dev/null +++ b/Weights/doc/Weights/dependencies @@ -0,0 +1,12 @@ +Manual +Kernel_23 +Generator +STL_Extension +Algebraic_foundations +Barycentric_coordinates_2 +Solver_interface +Surface_mesh +Circulator +Stream_support +Property_map +BGL diff --git a/Weights/doc/Weights/examples.txt b/Weights/doc/Weights/examples.txt new file mode 100644 index 000000000000..1e374b4c1f13 --- /dev/null +++ b/Weights/doc/Weights/examples.txt @@ -0,0 +1,8 @@ +/*! +\example Weights/weights.cpp +\example Weights/coordinates_one_query.cpp +\example Weights/coordinates_multiple_queries.cpp +\example Weights/weighted_laplacian.cpp +\example Weights/custom_traits.cpp +\example Weights/convergence.cpp +*/ diff --git a/Weights/doc/Weights/fig/authalic.svg b/Weights/doc/Weights/fig/authalic.svg new file mode 100644 index 000000000000..365615eae86c --- /dev/null +++ b/Weights/doc/Weights/fig/authalic.svg @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/average.ai b/Weights/doc/Weights/fig/average.ai new file mode 100644 index 000000000000..d31a559395d8 --- /dev/null +++ b/Weights/doc/Weights/fig/average.ai @@ -0,0 +1,1059 @@ +%PDF-1.5 %���� +1 0 obj <>/OCGs[22 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream + + + + + application/pdf + + + average + + + Adobe Illustrator 24.0 (Macintosh) + 2020-08-26T14:25:09+02:00 + 2020-08-26T14:25:10+02:00 + 2020-08-26T14:25:10+02:00 + + + + 256 + 256 + JPEG + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYqh9Q1HT9Ns5b7UbmKzsoF5TXM7rHGi+LOxCjFXkep/85Bya3eyaN+VWhXH m7U1PCXUmVrfS7c+Mkz8C3yqoPZjiryLy95B/O/8q/PEv5jazp763p6PK+vpp10JZLiG5RvUcxn4 3ELsH+Jeq9QvxYq+pfJvnbyz5y0OLWvL16l5ZS7NTaSJwN45UPxI48D8xtviqeYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FUDrWu6LoenyajrN9Bp9jEKyXNz IsSD2qxFSew6nFXkl1+e/mHzZcvpv5R+XJtcYMUk8x6gjW2lxU6sC3B5P9UlW8FbFV+n/wDOP955 hvU1f82PME/mq+RucOkQM1tpcBr0SNODP86LX9oHFXqE2nfobyzc2nlfT7eCa1tpf0Vp8SpBB6wQ mJKDiqqXpU4qr+X9OuNM0LT9OubqS+ubS2ihuL2Z2kkmkRAryuzEsS7Atiryzzn+TmsaPrk3nj8q LhNH8yN8WpaI1Bp+pKDyKvHULHI3jsK7/C1WxVkH5ZfnHo3nKSfRr23fQvOWn1XU/L138Mqsv2nh LBfUj+io7ilCVXoWKuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KpT5l 82+WfLGntqHmDUrfTLQVpJcOFLEb8Y1+07f5KgnFXlMv5y+f/PLG0/Kfy25sXYo3m7WlMFkoBoWh i+1JT6SO6YqjdF/5x4sL7UV138ytXuPO2uDdIrkmLT4KmvGK1Q8aex+E/wAmKvWrS0tbO2jtbSGO 3toVCQwRKEjRR0VVUAAfLFVXFUk1ddel8xaFDZcotKja5utWuFK0YRw+lBbEE8v3kk/q1A/3V1Fd 1U7xV2KsD/M78oNA88xQ3olk0jzRYUbSvMNnVLmF1NVDFSpkSvYmo/ZIqcVYp5U/N/zB5X1qHyV+ bsUen6nIeGk+aY/h0+/VdgXeirFJ0rWg33C7VVezggio3B6HFXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYqkHm/wA/eTvJ1j9c8yarBp0RBMccjVlkp1EUK8pJP9ipxV5gfzO/Nr8w h6P5a+XzoWhykgebddAXkn89tbUfl/kmjjx44qm/lv8A5x38sw6l+nfOt7ced/MbULXmq/FbpTfj FaksgUdlYsB2AxV6rHHHFGscahI0AVEUAKqgUAAHQDFV2KuxV2KpL5auNdupdYuNTVobdtQmi0q2 dAjJa24WDmaDkwmljklUn9llptiqdYq7FXYqlHmvyl5d82aLPouv2Ud9p843jcfErUoHjcfEjrXZ lNcVeNR3vnz8jZlt9Sa581/lZy4wX4HqahpSHZVlG3OJenh4cdlKr2zQPMGieYdJt9X0S8iv9Nul 5QXMJqp8Qe6sOjKwBB2IriqYYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FWK+efzR8ieR7b 1fMmrRWkrKWhslrJcyD/ACIUq9O3Ijj4nFXnf+Mvzw/MZeHk7SB5J8tTGg8w6woa+kiP7UFrvxqO hIIPZxirIPJ//OPvkrRb861rbT+bfMslGl1fWm+sEMP99xPyRaduXJh2bFXpwAAoNgOgxV2KuxV2 KuxVJfOeqarpnlm+utIt/rOrFUg06HiXU3NxIsELOF39NZJAz+Cg7jriqcoGCKGPJgByYCgJ7mm+ Kt4q7FXYq7FVssUUsTxSoskUilZI2AZWVhQgg7EEYq8T1/8AK7zZ+XurXHm38o6NZzMZta8jyNS1 uAB8T2g/3XJToo+S7fAVWd/lr+a3ljz9YSSacz2mrWfw6pol0OF1ayA8WDoacl5bBx9NDUBVmeKu xV2KuxV2KuxV2KuxV2KuxV2KuxV2KsK8+/nH+X/kf91rWpBtSeno6TaD17xyfsgRL9nl2LlQfHFW C/pT8/fzIFNLtU/LnytN/wAf92DLq8sZ7xxfD6Veu/Ajs5xVlnkf8ifIXlS7OqehJrXmGQ85tc1Z /rVyZKbshYcUPuo5eJOKvQ8VdirsVdirsVdirsVSR9euX86R6BbxK0EOntf6jO3LkhlmENoi0+H9 56VwWr/IPHFU7xV2KuxV2KuxV2KuxV5p+ZX5MWvmK/j80eWLw+W/Ptn8VprVuKLMQKCO7QAiRGHw lqE06hl+HFUF5A/Oe6fWl8kfmNZr5e87R0SBmNLPUAdlktpPs8n/AJK7nYb/AAhV6xirsVdirsVd irsVdirsVdirsVdir5L8yw/nh5m/5yG1nynp3mDUrHSY54zdGxunjt7TSplVlk4KyoJfSfbbkX+/ FXuv5dflJ+WflCWabQbaO91mFzHe6tcyLdXolKgsHc/3TMGBKoFr4Yqz/FXYq7FXYq7FXYq7FXYq 7FUn8peYH8waHHq/oehb3Ms/1L4uXqWqTOlvP0FPWiVZAPBsVTjFXYq7FXYq7FXYq7FXYqxnz/8A lz5U896K2leYbQTIKtbXSfBcW8hH95DJ1U+3Q9wcVeX6f5388flBfQaH+Yry655JldYdJ86RIzyw A7LFfoOTbDvu3gX6Kq9p0vXNF1WMSaXf299GUSTlbypKOEgqjfATsw6YqjcVdirsVdirsVdirsVd irsVeN/kHTWfM35kedD8S6rrrafaSHqbbTU4REezLKv3e2KvTX8qaIfMUXmKOA2+rohimuIHaL6x GVKhLlUIWYJWqcweJ6UxVRtvM1wPMUmh6hpdzZs5ZtNv1Uz2lzGq8j++QUhlABrHLxr+yWxVPcVd irsVdirsVdirsVSrzH5gh0S2tJXha4mvb22sLa3jIDNJcyhCantGnKRv8lTiqZxRRRRJFEixxRqF jjUBVVVFAABsABiq7FXYq7FXYq7FXYq7FXYqofpCw+vfo/6zF9fMZnFpzX1vSBCmT068uIZgK0pv iqSQ3d15hmvtL1Ty3JF5fZJIZJtRaBhckMF4pbI0xMZFTykK9BRTWuKvm38mvym1X8v/APnJBNG1 DUV4RaXc39g8NeN7byMYEicELxZTWRl3FUxV9aYq7FXYq7FXYq7FXYq7FUk88a8PL3k3XNcJo2m2 Nxcx17vHEzIPpYAYqxL/AJx10A6L+TnlyF1pPeQNqEzHqxvHadCf+ebqMVekYq7FUjtNG16z8wTX UWrtc6Jdl5J9Mu09SSCUgcTazqVKxk/ajkDf5JXpiqN0zX9G1Se9t7C7jnuNOma2voFNJIZVNOLo aMK9VNKMNxUYqj8Vdiqnc3Nta20tzcypBbQI0k00jBURFFWZmOwAHU4qxXSfzb/LnVp4Lew1yGSe 6lSG0idZYnnaQ0UwLIiGVaj7aVUdzirLsVShdR0jUPMU2ktbeve6IkF99ZdEZIZLtZ4UEbkllm9J X5UH2H6/FTFU3xV2KuxV2KuxV2KuxVJdH8022r6jcWtnY3wt7YNy1Ke2e2tmkVuJjiM/pySHqeSI U2+10qq7SLDzUmp3F5rGrQz2rqyWul2lsIYowXBV5JZHllkkCimxRdz8PQ4qiNJ8s+XtHluJtL02 3s57t2lup4Y1WSV3PJmkcDk2/icVTLFXjX53D9AfmB+W/npDwjttTOi6jJ2+r6ihUFvZAJDir2XF XYq7FXYq7FXYq7FXYq8k/wCcoL+4T8rzolof9N8zajZaRbAdeUsvqnb3EJX6cVep6bYW+nada6fb Djb2cMdvCvgkShFH3DFURirsVdiqCfRNIfV4tYaziOqwxNbx33ACYROQWj5jcrUVocVQGl6p5l/T Vzpur6WqW37ybT9XtJA9u8QeixTI/GWKcKw6Bkbchh9kKp5irzv/AJyD0zVtS/KHX7XS4ZLm4KQS S20FfVlt4riOS4RKb1MStsOvTFWIeePPPkXzH5i/K6Lyzqdpf+nrsEohtCrGCE27hUkVP7k9KRvQ 7dNjir3J3SNGkkYIiAs7saAAbkknFUp8sy6Jf2A8w6VAY4/MCw38kzqUklDQJHE7htx+5RKD+NcV TfFXYq7FXYqk+keb/Lms39xY6Terfy2gJuJbdXlt0IbgUNyqmD1A3WPny67bYqt0WTzjNezzazDY Wen0K2tnbPLcXBPIcZJZ2EMa/DWsaxn/AF9t1XaP5P0HSdRuNUt4pJtVugyTahdTS3M5jdg5iV5m cxx1UUjSi7DbFU6xV2KuxV2KvOv+chPLZ1/8ofMVvGD9Zsrf9I2zL9oPZETnj7lEZfpxVkv5feZB 5m8j6Fr3INJqNjBPPTtMUAlXb+WQMMVZBirsVdirsVdirsVdirxv8z/9zv54/lr5YArDpzXXmC9H UD6uv+it9EsTD6cVeyYq7FXYq7FXYq7FUj0byrHo+rXV1YX1yunXgZ5NHlf1bdLh3DGaAycpIuXx co1bhvUKD1VX6H5u0XWLy70+CR4NVsDS80y6Qw3Ma1osnpt9qJ/2JEqp7HFWJfmL5I81axrflq68 u22lQ2mg6imrS/WZ5oJJ5lV4zHxht5VUcWB5lia/s7bqsx1+XR5bKPRtXk9Ndf8AU02OGNpA8rSw SPIiPGAy0ijduW1KV2xVMoYYoIY4YUEcUShI0XYKqigAHsMVX4qgodb0afU5tKgvreXU7dBJcWKS o08aE0DPGCWUEnaoxVBaJqvmK/vJze6IdJ01BS3e4uYpLqV69TDB6scaUqamYt4qMVdonlp9NvJ7 651bUNVvJ1KF7yYCJELcuMdtAsNutOnL0+dOrYqnKIiKFRQqjoAKDFW8VdirsVdirsVdiqncW8Nz by286CSCZGjljPRkYUYH5g4q8i/5xluJ7Hytrnku6ctdeT9ZvNOUN1Nu0hkjf/ZOZKYq9hxV2Kux V2KuxV2KuxV435HI1/8A5yL89a7Tnb+XbGz0K1c70aX9/MF91kjcH5++KvZMVdirsVdirsVdirsV WGCAzicxr64UoJaDmEJBK8utCQDTFUj0R/NlncXlv5gktbrToVMtnrMR9BylamO5tzVVZB/uxH4s P2UxVJPLPnf8sfP3mSO60DWU1PU/Lsc6rbp6kaqLnhG84SVE9SgTgrpVQHP8wxVkGgeZjrU05h0r ULSxjA9G+voRarOSSCI4ZWW5FBvWSJR4VxV2haT5jtrme71rWzqLzDjHZwW8draQjlWqL+9nZuxL zEeAGKo3SdE0bR7Y22k2NvYW5PJoraJIlLHqzBAKse5OKo3FXYq7FXYq7FXYq7FXYq7FXYq8a0X/ AJ1n/nJvXNO+xZeddIg1KEDobuxJiZR78Fkc/PFXsuKuxV2KuxV2KuxVZPPFBBJPMwSGJS8jnoqq Kkn5DFXkX/OMUUt55K1fzZcIVufNutX2qFm+16Zk9NV+Suj/AH4q9gxV2KuxV2KuxV2KuxV2Ksbk 1nTPMl7q3liK0uLrTkgmtNW1JQEtklkAja1R2IaSXg5LcAQnRiGNMVfOHk38tNT/AC289+ZJ/LGt kajpc66dD9bgSW3ntZre2vvTuFBR9zKgLRspBWvQ0xV7NoH536WHisvOlsPLV+5Ecd6z+rpc7b7p d0X0iaV4Tqh7Atir0xHR0V0YMjAFWBqCDuCCMVbxV2KuxV2KuxV2KuxV2KuxV2KuxV41+f3+4LzF +Xvn5DwGi6yLC/kHaz1FeErN7KqMB/rYq9lxV2KuxV2KuxV2KsE/PTzD+gPyj80agG4yNZPawnuJ Lwi2Uj3BlriqZ/lb5fHl78ufLej8OElpp9uJ1pT986B5tveRmxVlGKuxV2KuxV2KuxVSuru1tIDP dTR28ClQ0srBEBdgqgsxA3YgD3xVKbm181XPmOB0u4bHy9aKHaKJRLdXkrAgpK0i8IYUrX4Kux/a UCjKp3irwbWmp+ZvnWInc3VjMF9n0y2Tl9PpU+jFWpYYponimRZIpAVkjcBlZTsQQdiDilB6QvmT ys/qeT9S+qWoJZtAvA0+mN9o0jSoktSWatYWC+KNih6D5a/OrQbyePTvM1u3lfV5G4RJeOrWU7Gt BbXoCxMTT7D8H/ycVei4q7FXYq7FXYq7FXYq7FXYq7FWCfnr5Z/xJ+UvmXTVXlOtm13bACpMtmRc Iq+7elx+nFUy/KzzMfM/5deXtdZuc15YxG5bxnjHpz/8lUbFWU4q7FXYq7FXYq8b/wCcj+OrJ5J8 kjf/ABLr9sLpOtbO1+K4qO9PURvoxV7JirsVdirsVdirsVSfzN5mtNAtIpJIJ728u5PQ0/TrRPUn uJiCwRASqqAqlmd2VVAJYgYq1eeWtL1i80zVNXtTJc6ePVtrKVzJbw3DAH1TGP3byx9EkIPHfjSu KpzirsVeF+bLf0fza8ykGoubLS7g7UoSs8NPfaEH6cVWYpdiqldWlrd27213ClxbyjjLDKodGHgy sCDiqjod35u8pNGPK+oCXSY6BvLmps8trxBAK20/xz2uwNAOcY/kxQ9G8p/nF5b1m6i0vVI5PLvm CWippuoFVWZ6LUWlypMNxu1AFbn4oMVZ5irsVdirsVdirsVdirsVadEdGR1DIwIZSKgg7EEHFXjv /ONjvpOn+a/IcrEv5R1u4gtgTv8AU7ljJA2/87LI304q9jxV2KuxV2KuxV45qtPMH/OUGjWg+O38 n6HPfPXcLc3zejSnj6bow/sxV7HirsVdirsVdiqVanfahc6deJ5amsp9WgkFuTcSM0MEvwl/WWGr 8kjfn6dVLbCq15YqidHs7yz0y3tr2+k1K8jX9/fSpHG0rk1ZuESoijeiqBsPHriqMxV2KuxV4t+Y BRPzXvVAo0uhaa9R34XeoA1/4IYqgcUuxV2KuxVDahp1hqNo9nf28d1ayfbhmUOhpuNj4dsVX6Jr fnnykETQr0axo0Yp+gdWkdmRR+za31JJo+gCrKJF8OOKHpXlD82PKvmS7XSy0ukeYKEnRdRUQ3DB a1aBgWiuF+EnlE7bdaYqzPFXYq7FXYq7FXYq7FXjUH/Otf8AOUNxF9mz89aKsqgbA3unbEe9IImJ /wBbFXsuKuxV2KuxV2KvG/yRprf5g/md5yb94lxqyaNZS9vS0xOB4+zhozir2TFXYq7FXYqx3Sdc j82WmpRwWl5aaM6G3tNWLG2e55hllktlqJkRNuEpA5HddgGKqaaLoek6HpsWm6Tax2dlDXhDGKCp NWZiaszsd2ZiSx3JriqOxV2KuxV2KvFPzMDR/m7bkGi3Hl9QR4m3vXpT5eua/PFUHil2KuxV2Kux V2KoPVdH0vVrRrTUrZLq3Y14SCvFuzKeqsOzKajFU+8g3v5n2kt/HpMw8y6Bp3po1nqs5W9WaSsj Q2t6VbnwiZG43FftKPUG9FD0XQPPOg6zePpqvJYa3CvKfRb9Pq94ijqwjYkSp/xZEzp4NirIMVdi rsVdirsVeNf85GA6LJ5K8/R7HyxrUS3jeFle0S4qf+ear9OKvZQQRUbg9DirsVdirsVSvzTrcehe WdW1uWnDTLOe7YHofQjaSn08cVYH/wA41aJJpf5PaJJPU3eq+tqd056u11KzIx+cQTFXqGKuxVQu L+xtpbeK5uIoZbt/StY5HVGlkCl+EYJBZuKk0HYYqlenWPmdtfu9Q1O/jTTVDQabpFqtU9MsD9Yu ZZFEjTNSgVOKIKj4yeWKp3irsVdirsVdirsVeR/m1CV/MDy5NXaTSdUQDw9O5sCfv9TFUkxS7FXY q7FXYq7FVG6mkiiHox+tcyskNrBXiZJ5WEcUYJ6c3YCvbFXs3lPy9F5f0G10xZPWljBe6uacTLPI S8slN6cnY0HYUHbFCpr/AJZ0LzBarbavZpcpG3OCTdJoX/35BMhWSJ/8pGBxVj31Tz95YA+oSt5u 0detndyJDq0S+EVyeEFz7LNwbxkbFU58vec/L+vSS29lcGPUrYD65pVyjW97BX/ftvIFcDwYAq37 JIxVO8VdirsVYd+cXlk+Zvyw8yaMq85p7KSS2Xxnt6Twj6ZI1xVZ+TPmb/E35W+WtXZuc0llHDct WpM9tWCUn5vETirNMVdirsVeU/8AOTmrT2f5SX9ha/73a9cWuk2ij9p55QzL/so43GKvSdD0qDSN E0/Sbf8AuNOtobSLt8EEYjX8FxVG4qkmu+ZW0+/sdLsrCfU9UvmqsEQ4RQwKwEtxcTsPTjRAdhuz nZVO9FVdfLOijzA/mB4PV1dohBHcys0hhi7pArErEH6vwA5H7VaDFU0xV2KuxV2KuxV2KuxV5H+c zen508msSVElvq8IIruzfVJOJp7RE/RiqSYpdirsVdirsVdirIvy20U6l5gl1eZK2WjVitCej3sq fvHH/GGF+PgTIe6Yoeq4q7FXYqk/mHyjoHmBYjqNtW6t6mz1CFmhu7diKcoLiMrLGf8AVbfvXFUj 5ef/ACwPjD+cdGX9tRFBq8KDxX91bXdB4em/s5xVPvL3mvQfMEUj6XdCSa3IW7s5A0V1buRXhcW8 gWWJvZ1GKptirsVeNf8AOO/+4S888+QG2HlvWpJbFTtSyvgXgAHyjLf7LFXsuKuxV2KvG/zeP6c/ Nv8ALDykvxRxX0+vXqHoFsE5wE/6zLIuKvZCQBU7AdTirH9Wl1XXtKtj5U1a2t7S7kK3GsR0uHSB eSubQfFC0pdeIZ6qvXi1KYqnsMZihjjLtIUUKZHoWagpyagAqe+Kr8VdirsVdirsVdirsVdiryr8 7oF/THkm6r8a315BTtxlsJZCfnWAYqx3FLsVdirsVdiqlObkiOC0QSX11ItvZRN0aaU8U5U34j7T nsoJ7Yq9r8u6HbaFolppVuxkS2SjzNs0srEvLKwG3KSRmc07nFCY4q7FXYq7FXYqkXmHyV5f12eK 8uoWt9WtwRaaxaMbe9hHgk6UbjXqjVQ/tKcVSj9IefPLII1WBvNekLsNQ0+JY9TiXxnswRHce7W/ FvCI4qyLQfMmha/Zm80e8jvIVbhLwqHjcdY5Y2CyROO6OoYeGKvLLz/nWv8AnKCyuD8Nn550V7di dgb3T6PU/KCJVH+tir2XFXYq7FXiuj31tqP/ADkX5z8w3RZtO8laJb6d6iq8nFp/9KlZUQM7MvGV aKK+3TFXo8EKecvLksWv6PPp+n3kgKafcSlJ5bZSrIblIivp+pQ84GY/Ds/UqFWQW9vBbwR29vGs MEShIoo1CoqqKBVUUAAHbFV+KuxV2KuxV2KuxV2KuxV2KsC/NjSZtUfyhawzpbSHXKCd4zKF5aZf J9gNFy3YftD+GKqEf5T3pI9fXBQf74tBGT8/UlmxVEL+UdhT49c1IsdzxFkF+gG2Y0+ZOKqi/lNp YNTq+pH2Js/4W2Kq8X5U+U1oJje3AHQNeTx7+NYWiOKqw/K7yWOlrc/Tf35/XPiqN0vyL5V0y9iv rSx/0yGvoTzSzTvHyUoxQzPJxqrEGnY4qn2KuxV2KuxV2KuxV2KuxVj2veRdD1e+XVF9XTNejULF rWnv6F2FXortRkmQf77mV09sVfN//OQ35i6v5c8weU4bq80zW/MPlrUTf299ZOYpREoUS21/aD1B E01E+JJfiAb4F2xV9I+QPNqeb/JmkeZUtnsxqduJjaualGqVYBqDkvJTxam4ocVZBiqXeY7TVLzy 9qlppNyLPVbm0nisLs1pFcPEyxSbVPwOQcVfLH5W6V+dH5MalqvmDzP5ZvNU8vakAusGzuYLu4R0 bkt56cbys9Azg8uPWrEUxV9LeSfzC8n+dtMGo+W9SivogB68IPGeEn9maJqOh2NKih7VGKsixV2K uxV2KuxV2KuxV2KuxV2KsU8/0VvLU/Qw67Z0fsvqiSDf/W9Xjv44qyvFXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FWB+fPzq8jeTpvqFzctqWvyHjb6Dpy/Wbx3P2VKJtHX/LI9q4qwz9Dfnv8AmZ8WtXX/ ACrrynL/ANKyyb1NWmjPaWb4fSqNv2fdDirL9A/IP8pdF0n9Gx+XLS+ViHmutQjW7uJHX9ppJQSP ktF9sVZ5bW1va28VtbRJBbwqI4YY1CIiKKKqqtAAB0AxVUxVht7+cf5ZWdze2knmC2lutPYJeW9t zuZEY8q/BAsjHhwPOn2P2qYqyfSdX0zWNNt9T0u6jvdPu0ElvdQsHR1PcEfcfA4q8787fkL5f1jU z5j8sXUvlDzglWj1fTfgSRjuRcQKVSQN+0RQn9qvTFUitPzj86+Q7mLSvzf0nhZswitvOmlo0tjL 2BuIkHKJj7KCeyU3xV7DpOr6VrGnw6jpV3DfWFwOUN1buskbDpsykjbFUXirsVdirsVdirsVdirs VYp+ZNV0XTZgd4td0P4T3Euq20J+71a4qyvFXYq7FXYq7FXYq7FXYq7FXYq7FWCfmB+dPkXyS62d /dNfa5LRbbQtPX6xeOzfZBjU0j5V25kV7VxVhf1D8+vzM31Cb/lW/lOX/jzgJk1ieM9nf4fRr0/Y I7q2Ks98g/lD5C8ix10LTl/SDgifVrk+veSE/aLTMPh5d1QKvtirMsVeQf8AOQXl+1gtNI89mB54 /Lt5AdctkeQCfSpH4TAojDk0Rk5r4b4qh7S8XRNM85/mloVjDem8vXi06Ka4ljhayglWGedKLMKz 3SySCigFeND4qs+8teZ9ZufM2r+Wtbt7eLUNOt7W+huLNnaGW3vGlRQRIAyvG9uwP8woRTpirz7R /M/lDyv+en5iSa3qFno8U9po7RS3LpAjssEjSBWbipc1rx+03YGmKoL8q/NGp+XvLUVjp2kNd3Hm rVta1byzps8ps449IiIlDs3pzsgcuPTX0/i5g1A3xV7D5S8zaf5o8s6b5h08MLPU7dLiJHpzXkN0 am3JWqppiqY3dnaXtrLaXkEdzazqUmt5lWSN1PVWRgQQfA4q8e1X8jNZ8sajLr/5Q6udAvJG9S78 uXRaXSbo9SOB5GInoCK06Lw64qjPK35+2Saonlr8xtOfyX5poAouz/uPuT05291ulCf5jTsHY4q9 aBDAEGoO4I6EYq7FXYq7FXYq7FXYqxT80dvJs8hFRBd6dO3iBBqEEpI9wEqMVZXirsVdirsVdirs VdirsVdirBvP350eQvJDfVtTvjday9BBoliPrF67N9lfTU0Tl25la9sVYT6X59/mZ/et/wAq28py /wC60rLrM8Z8T8Bgr/sGH+UMVZ3+X/5QeRfIsZbRbH1NSkqbnWLsie9lLfaLTMPhDd1QKPbFWaYq 7FXYqhtU02x1TTbvTL+IT2N9DJb3ULVo8UqlHU08VOKsR87eS5W/K6fyd5XskMYtYrCzt3l9JUhj 4ipkIZiQq9epO+Kp35X8rabosc11DDMupaisTajNdXMt7OTEtEiM8zOzJFyYKBtuT1JxViugeS/N 1v8AmX5k8w6rbaVNonmEWiNAs80s8I06ORIJPTktljdn9T4hzHHsW7qpx5z8napqmt6N5h0O8htN a0eO8t4/rKO8MkN9GEcN6bKwaN40dfkR3qFUz8k+VrTyn5T0ry5aSNNBpdukAmcUaRhu7kCtOTEm nbFU7xV2KpR5p8oeWvNWlPpXmHTodRsXr+7mWpQkU5RuKPG3+UpBxV5K3kP81/yuY3H5e3zeavKa Hk/k/VJP9IhStSLK4PgK0X/hXOKsy/L386/JvnOZtNjeTSPMsJK3fl7Ul9C7R1HxBFanqUofs7gf aAxVn+KuxV2KuxV2KsU/Nb4fy61+WtDb2jXAbsDARKCfYFN/bFWV4q7FXYq7FXYq7FXYqwnz9+cf kLyMvpaxqAl1R6eho9mPXvZGb7IESn4eXYuVB8cVYMJfz6/Mw/u1/wCVbeUpertWTWp4z4D4DBX/ AGDL/lDFWc+Qfyd8heRwZtIsPV1V6m41m8Pr3sjN9omVh8PLuECg4qzbFXYq7FXYq7FXYq7FXYqw Bvzq8tSaxq2jaZpms6tqmisi3trZ6fLyAfl8Smb0V4jh3I5fscsVZT5V806L5p0K21zRp/XsLoHg zKUdWRijxyI1CroylWBxVNsVdirsVdirsVYZ+YP5R+SfPcKnWbP09Thp9U1i0Po3sLL9krKB8QU7 hXBX2xVgH6b/ADm/Kn4dfhk/MDyRF/0uLVaataRim88ZJ9YKK1JJ8S69MVeoeSfzC8n+dtMGo+W9 SivogB68IPGeEn9maJqOh2NKih7VGKsixV2KuxVjP5nwG4/LXzbADxMujaggJ6AtayDFWQ2s4uLW GcDiJkWQL1pyANMVVcVdirsVdirDfP35veQvIsdNd1Ff0g4Bg0m2Hr3khP2QsKn4eXZnKr74qwL6 3+en5njjaRN+W/lCT/j6lBk1m4jP8q/B6AP+xI7MwxVm3kH8l/IXkhvrOmWJutZepn1u+P1i9dm+ 03qMKJy78Ate+Ks5xV2KuxV2KuxV2KuxV2KuxV2KvFbTzdpnln87/wAw7rUoL2S3ltdERZLKzuLy kgt5Ssbi3SVkMm/EsAu3UYqhNDvPO35fflXqPmb9HQW0+reYJNTm0nUBIJba11a9it4o+EZWki+o HZWIp067Yq91xV2KuxV2KuxV2KuxV5h52/IXy/rGpnzH5YupfKHnBKtHq+m/AkjHci4gUqkgb9oi hP7VemKpFafnH518h3MWlfm/pPCzZhFbedNLRpbGXsDcRIOUTH2UE9kpvir2HSdX0rWNPh1HSruG +sLgcobq3dZI2HTZlJG2KovFUq82W/1nyrrNvw9T1rG5j9P+blCwp9NcVa8o3AufKmi3Af1BNYW0 nqHq3KFTXfxriqbYq7FWIeffzY8ieRbfn5g1NI7thWDTYf3t3LXpwhX4gD/M1F98VefnUPz2/M0/ 7i4/+VdeT5vs31wvqaxcRnukYI9GvUfZI7M2Ksy8g/kl5C8lyfXbK0bUNdcl59c1FvrF47t9pg7C kdf8gCveuKs9xV2KuxV2KuxV2KuxV2KuxV2KuxV2KsH8v+QNf0r8wda82y65b3MOvCGO905bFois dojpbCOb6y9GX1PjJQ8vBa7Kph+YnkiXznoX6FOqy6ZaSSRS3BgiikaQwSpPFvKG48ZIgduuKshs YbmCzhhurg3dwihZbkqsZkYdW4JRRXwGKq+KuxV2KuxV2KuxV2KqV3Z2l7ay2l5BHc2s6lJreZVk jdT1VkYEEHwOKvHtV/IzWfLGoy6/+UOrnQLyRvUu/Ll0Wl0m6PUjgeRiJ6AitOi8OuKozyt+ftkm qJ5a/MbTn8l+aaAKLs/7j7k9OdvdbpQn+Y07B2OKvVbiJLm1khLfBMjIWXwcUqPvxVj35XzvP+Wf lKdwA8ui6c7AdKtaxk0xVR8+fmn5H8i2nreYdSSG4ZeVvp0X727m7D04V+Lc7cjRfE4q88/TP57/ AJmfDotr/wAq68py/wDSzvV9TVpoz3ih+H0qjf8AZ9nOKsv8hfkd5E8n3H6Sht31bzC55z69qbfW btpD1ZWb4Yz7qOXiTir0DFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8stPPn5pax5280eVdM0vR rCbQBayxXV5Pc3CvFdLI0fJYkiJaRUB6jh/l4qyj8tPO7ecfLZ1C4tPqGp2V1Pp2rWIbmsV5aPwl VXoOSnZh88VZXirsVdirsVdirsVdirsVdirsVSjzT5Q8teatKfSvMOnQ6jYvX93MtShIpyjcUeNv 8pSDirxLzNoP5jfkho13r3k7V/075HswpuPLWr85ZbRZHVA1tLHRuCM+4+EAbkN1xVjX5Qecvzp/ MnyvF5a8tXNh5X0bQ0jtNQ19I3kumVgeCQRuWXnxHxEcfEMuKvZvIX5GeR/KN3+lvTl1rzM55z6/ qj/WLoyHqyFvhjPuByp1Y4q9DxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KvFbO6842v53f mJL5Z0+y1OY2uiJNbXt09mV5W8vCRHSKcMFIPNSATUUOKtXXkibyb5GWPUvMF2Nb1K51G+uLLR5p LI6hrWoqGiEMsbRSiO34E/EQlPjkHEUCr1XyjDq0HlbSIdYvE1DVorSFL+9joUlnWMCRwR1q1d++ KptirsVdirsVdirsVdirsVdirsVS/WNU8v2kX1fWbu0t4bpWX0rySNFlXo44yEBh8W+KoHSr3yHp EY0zSbjS9OjCm5FjaPbwLxaP1jL6UZXYx/Hyp9nfpiqdwTwzwxzwSLLBKoeKVCGVlYVVlYbEEdDi q/FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqxrTPy58paZ5kuPMllb3EetXZJu7pr29kEtQ ygSRvM0Tqoc8FZaL+zSgxVEeaPInk/zWLYeY9IttV+p8/qv1lOfp+rx58fDlwWvyxVMtI0jTNH02 30zS7aOz0+1Xhb20Q4oi1rRR8ziqLxV2KuxV2KuxV2KuxV2KuxV2KsC/O3yOfNvkG+htIUk1zTaa horsgdhc2xEgRQQf70L6ZHQ1xVhnlu+svOWieY/zL07T7K346JHpukpdWqzwj0bX178PEDGXUvL9 V+1SkXdeqr1ryjIJfKeiyiKOEPYWrCGFFjiQGFTxjRaKqjoFGwGKptirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVYz5g8rXjeVn8veVfqOj2tz60 NysluzxpBdCT1mgjieELL6knMcqr1qN8VT3TbCHTtOtdPgr6FnDHBFy3PCJQi1PjQYqicVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdir/9k= + + + + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + xmp.did:489ddd23-9caa-4376-83e3-49899306cb93 + uuid:844d2876-8f90-424c-b91c-1cf5e6aacc70 + proof:pdf + + uuid:a0b71ce9-9b79-cc48-999d-602a7105fde1 + xmp.did:b9d63d9b-a203-4809-a067-301c4e966386 + uuid:9E3E5C9A8C81DB118734DB58FDDE4BA7 + proof:pdf + + + + + saved + xmp.iid:b9d63d9b-a203-4809-a067-301c4e966386 + 2020-06-17T10:58:52+02:00 + Adobe Illustrator 24.0 (Macintosh) + / + + + saved + xmp.iid:489ddd23-9caa-4376-83e3-49899306cb93 + 2020-06-17T15:58:17+02:00 + Adobe Illustrator 24.0 (Macintosh) + / + + + + Basic RGB + Document + AIRobin + 1 + False + False + + 1500.000000 + 1500.000000 + Points + + + + + SourceSerifVariable-Roman + Source Serif Variable + Roman + Open Type + Version 1.010;PS 1.0;hotconv 16.6.54;makeotf.lib2.5.65590 + False + SourceSerifVariable-Roman.otf + + + + + + Cyan + Magenta + Yellow + Black + + + + + + Default Swatch Group + 0 + + + + White + RGB + PROCESS + 255 + 255 + 255 + + + Black + RGB + PROCESS + 0 + 0 + 0 + + + RGB Red + RGB + PROCESS + 255 + 0 + 0 + + + RGB Yellow + RGB + PROCESS + 255 + 255 + 0 + + + RGB Green + RGB + PROCESS + 0 + 255 + 0 + + + RGB Cyan + RGB + PROCESS + 0 + 255 + 255 + + + RGB Blue + RGB + PROCESS + 0 + 0 + 255 + + + RGB Magenta + RGB + PROCESS + 255 + 0 + 255 + + + R=193 G=39 B=45 + RGB + PROCESS + 193 + 39 + 45 + + + R=237 G=28 B=36 + RGB + PROCESS + 237 + 28 + 36 + + + R=241 G=90 B=36 + RGB + PROCESS + 241 + 90 + 36 + + + R=247 G=147 B=30 + RGB + PROCESS + 247 + 147 + 30 + + + R=251 G=176 B=59 + RGB + PROCESS + 251 + 176 + 59 + + + R=252 G=238 B=33 + RGB + PROCESS + 252 + 238 + 33 + + + R=217 G=224 B=33 + RGB + PROCESS + 217 + 224 + 33 + + + R=140 G=198 B=63 + RGB + PROCESS + 140 + 198 + 63 + + + R=57 G=181 B=74 + RGB + PROCESS + 57 + 181 + 74 + + + R=0 G=146 B=69 + RGB + PROCESS + 0 + 146 + 69 + + + R=0 G=104 B=55 + RGB + PROCESS + 0 + 104 + 55 + + + R=34 G=181 B=115 + RGB + PROCESS + 34 + 181 + 115 + + + R=0 G=169 B=157 + RGB + PROCESS + 0 + 169 + 157 + + + R=41 G=171 B=226 + RGB + PROCESS + 41 + 171 + 226 + + + R=0 G=113 B=188 + RGB + PROCESS + 0 + 113 + 188 + + + R=46 G=49 B=146 + RGB + PROCESS + 46 + 49 + 146 + + + R=27 G=20 B=100 + RGB + PROCESS + 27 + 20 + 100 + + + R=102 G=45 B=145 + RGB + PROCESS + 102 + 45 + 145 + + + R=147 G=39 B=143 + RGB + PROCESS + 147 + 39 + 143 + + + R=158 G=0 B=93 + RGB + PROCESS + 158 + 0 + 93 + + + R=212 G=20 B=90 + RGB + PROCESS + 212 + 20 + 90 + + + R=237 G=30 B=121 + RGB + PROCESS + 237 + 30 + 121 + + + R=199 G=178 B=153 + RGB + PROCESS + 199 + 178 + 153 + + + R=153 G=134 B=117 + RGB + PROCESS + 153 + 134 + 117 + + + R=115 G=99 B=87 + RGB + PROCESS + 115 + 99 + 87 + + + R=83 G=71 B=65 + RGB + PROCESS + 83 + 71 + 65 + + + R=198 G=156 B=109 + RGB + PROCESS + 198 + 156 + 109 + + + R=166 G=124 B=82 + RGB + PROCESS + 166 + 124 + 82 + + + R=140 G=98 B=57 + RGB + PROCESS + 140 + 98 + 57 + + + R=117 G=76 B=36 + RGB + PROCESS + 117 + 76 + 36 + + + R=96 G=56 B=19 + RGB + PROCESS + 96 + 56 + 19 + + + R=66 G=33 B=11 + RGB + PROCESS + 66 + 33 + 11 + + + + + + Cold + 1 + + + + C=56 M=0 Y=20 K=0 + RGB + PROCESS + 101 + 200 + 208 + + + C=51 M=43 Y=0 K=0 + RGB + PROCESS + 131 + 139 + 197 + + + C=26 M=41 Y=0 K=0 + RGB + PROCESS + 186 + 155 + 201 + + + + + + Grays + 1 + + + + R=0 G=0 B=0 + RGB + PROCESS + 0 + 0 + 0 + + + R=26 G=26 B=26 + RGB + PROCESS + 26 + 26 + 26 + + + R=51 G=51 B=51 + RGB + PROCESS + 51 + 51 + 51 + + + R=77 G=77 B=77 + RGB + PROCESS + 77 + 77 + 77 + + + R=102 G=102 B=102 + RGB + PROCESS + 102 + 102 + 102 + + + R=128 G=128 B=128 + RGB + PROCESS + 128 + 128 + 128 + + + R=153 G=153 B=153 + RGB + PROCESS + 153 + 153 + 153 + + + R=179 G=179 B=179 + RGB + PROCESS + 179 + 179 + 179 + + + R=204 G=204 B=204 + RGB + PROCESS + 204 + 204 + 204 + + + R=230 G=230 B=230 + RGB + PROCESS + 230 + 230 + 230 + + + R=242 G=242 B=242 + RGB + PROCESS + 242 + 242 + 242 + + + + + + + Adobe PDF library 15.00 + + + + + + + + + + + + + + + + + + + + + + + + + endstream endobj 3 0 obj <> endobj 5 0 obj <>/Resources<>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>>>/Thumb 27 0 R/TrimBox[0.0 0.0 1500.0 1500.0]/Type/Page>> endobj 24 0 obj <>stream +H��VM�7 ��W��D����k� ���@iЃӴ(��=���i���q@'�Y3�\�Z����c� )!}餠��y�M}6rl�G@<Ҵ��a�����f�4�����{6hh���� + ��H�`��I� �*&3��V��rM.�g��@]uUj� j�8�ϐ��UFe/���W��*P= �ߋ�%�'Lenٮ0�7�םm]���J�g�&�z��AUg�y���i�'�p��XM2\tu�|��K����GgW�1�:���/��s0�B��b��?�4Ht��Bzį<9hH������.qlq8�vQHs������ZG� b�h/i�YH��v����I�MZsO��3��d�'`,�E��,"��8��s~�9U�i���G`�vP/�,�%z����c�fU���!��t�uf�f]�ez}���씁��i���/�:x�l�.� ��8b�|;��g5d�Xk�OBy^� |��_���� �>�ÛD�(�:�U�B���������?�r���ؠ���aZ(c�09Pj W��%6ǥ��dw 专UP��*���݅?We\<�����,A�lz}R��V����|�6c�1�q�1�5k��V{�Z�ܫ]x�/� 0� � endstream endobj 27 0 obj <>stream +8;Z]_5mi9!&4OFLHL?omoruot%0-A.!:Wj`/X\dYn^@?$kLn8I +nFWm<\It1AVg1A)OUS@ceU,Akk3OS2%WsG[(c)mXXihTs=iuGi/QedkLVAOS._a]O +#$1R"_Yr?_oBu+tzrcJVL5hOo%~> endstream endobj 28 0 obj [/Indexed/DeviceRGB 255 29 0 R] endobj 29 0 obj <>stream +8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 +b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` +E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn +6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 22 0 obj <> endobj 30 0 obj [/View/Design] endobj 31 0 obj <>>> endobj 21 0 obj <> endobj 32 0 obj <> endobj 33 0 obj <> endobj 34 0 obj <>stream +H�|RmPTU>��{!��c�~��{��eY��D�"�%��\w�+p�\$ (Qs*�!Y\H싰eDK���LslԄ�J�AF'u�s�.�v�ӟ~=�~=�{��PhF�(jIv� k�r���^;o�n�&��m�Z�'拥6!ܴX^JɱjV٥�\y�q5�'f�Ԝ�E3F�(B����-A�Q2Z�S�6��dZ��:�+��2�&K���M�+��™M�.1 i�����;�%������#zm���vI�'#)����h ��bi�rW�\\>_�{w��,Q���l��e��i��圍��6_j�s�����Nr��˂;Y%�$�#I�r"�x9�X!H^7_nLʲ���s�ND�ۧB�a��C�P�F�F"~��s���٘^w�e]�������P{�PSw���{�H�>j�?���3Oa��yC�ޘLk�p +����@C���>��י�i�u0)�f���@ma/0� �GU�8.�yW��%V�-.q�ܝ8V7�lfV��L�Ƚ2x�<���֟_�הʛ����nX 0��`�5ށ @����Qܦj�k3��4Z����Ζ� ګ�kV��*~ �: �z@�w�~Y1�] ���a^~�����~�܎6v�CJo�g�L@ƙ�� J��B��n��FlL����8"�=�>v=�ǫշ����uk�R�OY�G���Zz���� F����{�����N���e'��^V1�]��M笒���J��y���`�p�q��g��L+T֔���>-9�ht�z�6\�1x7S��dW�wq@�]���T�aw�e���������Gw+{xrh5�"�W���{���R�XWP��qD�����_�@~x�ڵ��o�5V<װx���Z��ǜxpk �?�J>(� ���{�������'R����M=p�r�6#�/��1���USO�)����'�X�$lm���!@���DgO�V�N�S J�� Xh�K< Z��yTu:�e3���W2SCQ����p��=�} M�����E���>�����u�J��1$'�O��G&s��~_��-m�@��:�����S-j��O=SV=%c�_��b\ endstream endobj 26 0 obj <> endobj 25 0 obj [/ICCBased 35 0 R] endobj 35 0 obj <>stream +H���yTSw�oɞ����c [���5la�QIBH�ADED���2�mtFOE�.�c��}���0��8�׎�8G�Ng�����9�w���߽����'����0 �֠�J��b�  + 2y�.-;!���K�Z� ���^�i�"L��0���-�� @8(��r�;q��7�L��y��&�Q��q�4�j���|�9�� +�V��)g�B�0�i�W��8#�8wթ��8_�٥ʨQ����Q�j@�&�A)/��g�>'K���t�;\�� ӥ$պF�ZUn����(4T�%)뫔�0C&�����Z��i���8��bx��E���B�;�����P���ӓ̹�A� om?�W= +�x������-�����[���0����}��y)7ta�����>j���T�7���@���tܛ�`q�2��ʀ��&���6�Z�L�Ą?�_��yxg)˔z���çL�U���*�u�Sk�Se�O4?׸�c����.�� ��R� ߁��-��2�5������ ��S�>ӣV����d�`r��n~��Y�&�+`��;�A4�� ���A9�=�-�t��l�`;��~p���� �Gp| ��[`L��`<� "A � YA�+��Cb(��R�,�*�T�2B-� +�ꇆ��n���Q�t�}MA�0�al������S�x ��k�&�^���>�0|>_�'��,�G!"F$H:R��!z��F�Qd?r 9�\A&�G� ��rQ ��h������E��]�a�4z�Bg�����E#H �*B=��0H�I��p�p�0MxJ$�D1��D, V���ĭ����KĻ�Y�dE�"E��I2���E�B�G��t�4MzN�����r!YK� ���?%_&�#���(��0J:EAi��Q�(�()ӔWT6U@���P+���!�~��m���D �e�Դ�!��h�Ӧh/��']B/����ҏӿ�?a0n�hF!��X���8����܌k�c&5S�����6�l��Ia�2c�K�M�A�!�E�#��ƒ�d�V��(�k��e���l ����}�}�C�q�9 +N'��)�].�u�J�r� +�� w�G� xR^���[�oƜch�g�`>b���$���*~� �:����E���b��~���,m,�-��ݖ,�Y��¬�*�6X�[ݱF�=�3�뭷Y��~dó ���t���i �z�f�6�~`{�v���.�Ng����#{�}�}��������j������c1X6���fm���;'_9 �r�:�8�q�:��˜�O:ϸ8������u��Jq���nv=���M����m����R 4 � +n�3ܣ�k�Gݯz=��[=��=�<�=GTB(�/�S�,]6*�-���W:#��7�*���e��^YDY�}U�j��AyT�`�#�D=���"�b{ų���+�ʯ:�!kJ4G�m��t�}uC�%���K7YV��fF���Y �.�=b��?S��ƕƩ�Ⱥ����y��� چ ���k�5%4��m�7�lqlio�Z�lG+�Z�z�͹��mzy��]�����?u�u�w|�"űN���wW&���e֥ﺱ*|����j��5k��yݭ���ǯg��^y�kEk�����l�D_p߶������7Dm����o꿻1m��l�{��Mś� n�L�l�<9��O�[����$�����h�՛B��������d�Ҟ@��������i�ءG���&����v��V�ǥ8��������n��R�ĩ7�������u��\�ЭD���-�������u��`�ֲK�³8���%�������y��h��Y�ѹJ�º;���.���!������ +�����z���p���g���_���X���Q���K���F���Aǿ�=ȼ�:ɹ�8ʷ�6˶�5̵�5͵�6ζ�7ϸ�9к�<Ѿ�?���D���I���N���U���\���d���l���v��ۀ�܊�ݖ�ޢ�)߯�6��D���S���c���s���� ����2��F���[���p������(��@���X���r������4���P���m��������8���W���w����)���K���m�� ���� endstream endobj 7 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 24.0 %%AI8_CreatorVersion: 24.0.3 %%For: (Claude Monet) () %%Title: (average.ai) %%CreationDate: 26/08/2020 14:25 %%Canvassize: 16383 %%BoundingBox: 302 -926 530 -695 %%HiResBoundingBox: 302.5380859375 -925.6728515625 529.7587890625 -695.750794223665 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 14.0 %AI12_BuildNumber: 375 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -1500 1500 0 %AI3_TemplateBox: 750.5 -750.5 750.5 -750.5 %AI3_TileBox: 470.5 -1130 1029.5 -347 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 2 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -52 -588 1.79167027691535 1748 994 18 0 0 1769 53 0 0 0 1 1 0 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:350 -1050 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 18 0 obj <>stream +%AI24_ZStandard_Data(�/�X�f�ta���u���9H�h�a�0��N{㶰���Ϸ�sa����|��<*�8�&�8��5_�>�J���L�h�]9.u��7^ �Vsb��O̯����[ y1�K��q�0��L$�:߯��V E�ֻa�$6| Z�I62�l�1�Y�a�G���|�ߐ���L>�7hXS�����:OKh򍗛z\-�'#W �ؐߛ�b%�A����}��`�(����K�E�Ʒ�ɇ������๨1w,&��᠓�:�Z[�%���8�:�G��G}a$f5�H}ߎ"5�}14�~�"]�}�zUf�6�E���Yw����>G7z\�R���+�b4�<��K��;��M�\kH%��!*}�G/�n#�/'R%�#�顩�Z�vfQ��*�>3츸X��d�E�ADž���u7�"4�l5xU��FߠpQE/я�Q�Ņȱ��W$&�Ti����⚭�~�ҧ �#�����YIV�wG�)e�ٙO/���N�]d�\�B#u�(�{� +������;���a�펹���:���H��9��bE#�_5�s �[�R��4e �;˘[�LJu�ڰ�b�8n�����3�\���[�h=��0`xXXը[���J/.~z�-h4:IqA����E�ێ�L ,�&ԣ�٢�H��� r� [̑�d� �öpO4t��a���-'CU������>��7 �0`0� ��� +$T����� h�"„  t����� (LT@X���� X8`�`�� `�B8���� \�`! �v���  $�@�B0`�����:�.��".h���>5.\��y� �T0 +XC���/�Q]u��.���ʬ��p��`�DH@D@@@<@8@4@� +�  1����������?�C=�������ps �q��DD�C��� m���?� �pE�8! ~%������+���@ + �Y�x_'�II�$Us��Q��k]L�65da.����ls�Xu�r�����G��8����!q S9DH����l��JPJ�Z�^߆:����De$��p���-�0/����pG:���7j�th�&CC�!gn�*W�]�E"BB�A�A�A�'a%A��J��DDDD�C�CDC�#.�"*�"\��B�t��9ȃD���r84��cUՆfV�4� �.-���58H0�Ȕ �c��Q�,N��dB4GC-���� +[C���1��S2c�rƯ��a��4@��z��?�DE�� e��?DECC��B�.� � � !!���c�y��ID9PM�$��n�L�!ш�5� �x �8�8L�$ K�� 5��6�������� ��v�P $A6a���a>7fF08��A$$"" ���<�8���J\�5ܝQ ���,A�@ t � ���q�[F�>ܜyi0��8]��6�I�ㅎ=�펊l�" ������L50�A(y�Č����IF� \T��@a�� (!r���H2""�ӰfX��j�B*8�D#CD]�o�.&B�A$&| i�! ^���k�\�F�l�„>͏�C'23����x���� �gR��6dqZ��ʕ YpQ�]���b Y�I7�v��� L#񇻽N7/ ��Y�",� �����<Qaq�xDCDC�C�C�CDDDDDD�D�D�H�%(Q�J��HX�$A%Q&a'q�h�hpx����j(��(����`&a<�C9�tox��G\jkx�D��X$�e#$����(D�PF��0}�������<���Ї΃�@�Pe�p�C�CÃÃ����C�Căăă@��(�@ �!� +� ���h�h�p�p�x�x������������DL�#AARB$�!B B��A��rrbbE�R�@0@�� ��t���K\�=��5�Ϯ�n�Ǔ�0s�������hbQ�P ����j�$���tTDd" I8�vp�ﳹl�iZb"�a�anl�e8���C�t8��D%�Q�B��u�C�{=k�J��n;��e�R�x! X�pb!„@���P�a!d &����B� �E-d��b�� fpه0闦w$vCH������,���*�0Q�RG�X4�a ]ȂA&�"8`A� &($@���( h P\0� , H�0� &`a" !&L�@� (��@� (,X�x`�� +&�E�(LD�� +P\P�E(LD��� + "`AB�D#�h��„����`B�H�X`a� ���# T ��� +,��� �(NPAa���HX�&PH��� +.D���[@� �P�� + *(D@„8 i� „X`� 8h�@� &����@� &Dh`v1a!D�;%�@� &ع�0���t2,&*�� ~\D��P�  *.h`��(\0a�*4��* D���@a�,h�# + *hP�EX��P�  ʸ�0qA&Hh0��`���� !Bh�`T �„" �*P�(� &P�x� &T0��� ,�@a� >Ѐ���� P���BH�@��� TT�@���@ ,` ������� $LT�0�(P\@Qa�(@�PA��:!�Tx�� +"&(x &��P1� +:���0AE� " ��Ѐ���T��pAE�<@�� ("D��P�� + +"x�� &TT0!�����L� aB�� +D0�����T��� ($TH����(L�`A &� �‚ +"\��� +*@�ADCP@�0A�h +P@��`���TPA�Lx� + + *D� P����.��P��� X���BH�Pa�E�H��@��B�$P� �ay�?| ��3I}g���gfK�X�[�,h��Ξ���\U��娊f9�"39*��]���o��{^��Ve�g��A�ho��&�(YF����]�;�Y��}ւZ�ŔՍ�E�jG��]��r�IG�}��M�ađ�T��n&4������晄����dJj�f�yC���� ����F�Q���Ӑ�1{��L�ƾe���gq�"+�gQT���qq���n� _���k���ry��NF2|����C����\���>���5|������e� +O7�ᣯ����C6���G�"iȯUBtꂮDBtv���-�0��k �elHOX��7fE~���p�:Z��� ;��O���ʠʙ�օez�.H �a�Y�o's���Ռ��]����y��E���Ր��֖����.^4��֪�7��Y����A^��"����ķ��g�>�Ķ�g��ө��B&��TChQ�M�)-H��.�e���(Zp����Mħ��,�Rt#9��c$�;�Q�<Д,��e�dUwe�=f����`kc�.�D�%��ۘ.Ψh�����F��� gQwE4: ����!�u1՛r��F��7M,id�ͮnì��0��]X6�A]X�ߗ����E$���}����;:U��~�g�I�yI�.<����t�NG4fa2��I��Y�u܂�J�� �gr)��0 ⠅m6&9��yE��Eo(E�ɋ"�flxab&F���Nj6��� Zɇ�:��ȸ��Ph'T^�xe��a.hHF�����t��(kU�^�TS���|db��EeU��4�*$R���,�틆�<��U�Z�U2.fX�^��Ut[j��>�B���K��XZ�n\T� � ��7����Nn:�g&>ӫe㌋�:�1}�7丠r�=t���ɉK�E��FM����¤増=�&6�'r��5�F��o��{&���\k��f1.��5Dn؋�k�I�sCT&�U�-�LU �W�C5>U��kU��XF�j�2I �I�O'3Hd�Ö��:`�`��XYX�L���L�+͌�^��D�h�͘b����E�����ר�3'wawE��y+�Xi���%�7�!����Ϥ��~4�1�����M�Jh����26s��ͥ&|����+k�Cj�U�Ch5c_Z��ȴ���[���huq'�EǏ�.|D��a����������5v'*�鎉�jX���9��Wc6� �˳A<�O�XD����!���hn�4�� �.�7é)uA�M�ӽq��] �:� ��h>c^\|U�C�iq��knj�72����/:4�1_\�<�]�rc^��.*R���] -Hn�a[?���ST��dz!���^U%7���!���h��-Ic��MszV�ʼnIǷy��].��&c ����.�����G��w��^ԎɘZ��-.��?;�E[�i�]Go�&�<��f� s�W6W���f<+_\5���������Z�艭,��rc"�ۮJf��;�ێ��(������J(T��3-���ɨظ/lV��d��#�t1����_����.�q��F���Մ����Q�b�R��<��^%���%�ɸ�CJo�H�{��w\�8-�?��dC÷F\�#���Q�{�\�{��N+��f��J�64*VJ��?�tVe&F'�k+� �5�������b�>N�����\�b����_�nc�a.���n"3<3� )�^9T��)e��Nל}��I�tt)�w^���N���z����.ln|t�4 1��0`x��-���N�L�Z5l����c�E�$ק�!��njHN�} 뽻�x-:c�n��;��l��bț3ꘛ֙��-�qC���³c6��v�+�s�3FG���Q�s�k�Y���%6Y�aw�w��$i�|����/��|B����Njha�2�|�4�|1��u9�%ጝ��t�������g +��_����,��#��222� #F�֫\U6��N6L�&#�a"�0�,�h��T� "�3���҇�l7Ѩo�f�XuS2E�S�ؘ:�5^ūA��avVf��ّ��{��v6�6�dB'��Z�mBc�ˆ����܌�D{�&Ң��h����n�ғO�����*V�WU�O^Uy�+<ٵL����ظ�232mH4m�L#'�z�vs3��?�d���{4�M��� �gc +?'���n1�:��W�#�d�1���5���D5*o��ue�����*V��QUm 11��~�A3-Vu����N��+����{��9`���Dev�*���Hd�FF��^Tc֖�÷��-�1q��vwV��?��Cgz�D�Zh�Xd�-��L���z묛(�)�/���XU����T�^SXC���e"OJX'1���e��ک�|��Fh��\[2[�e�I����3���=R��p�%D�26�^Y�%�"�N4�*���U_rh�>����ذ�ع��jJ��Md$Vd�Zt�Uj��,�#C� ��+�dٕ"����"��]Ŋ'�!ٴ]/4��5u6b7RNX{II2����\��js����܌9����Һ���A���6SDŽ6�D�x��MDN~T֨g4y��K��#��&F���Y�:yՒ~��� +����f����|��ZQ��9͜�:T73��T5�! �Y��ʆ*� ���-s �s�g74�.�؈h�q����l �l��F=�ofQ5�v���!�a�)��j=TW�8�C�Oyxj�13$Xon��bCC6D�?�2'cn��)���(#CZR��!RR2~��T��w���q�J-C*���C:������M ���!2�c�dk� �Q�Q�A33j�fn�G�x�/��[� ��UGMΌ��A[������� ��4L5�0���IH�����R3�j�A4DBg!9���D7%�(�R�Z����<���e�G�>���JDDE��(�\�Futl�*ҠKFCl*�!����/N.#��u4LG|g�h����EF�1���n�}�ﯟ<���yd��Dh +}:QZ�����.c%�UK��ܬ�� ��fS:�7V�n��_�ɿ��<��a;���Tv�� ����A�z�h�P;�^���7���qt�QD��܎�9�㫏z�g�y͙���q���f�D�;^c�벎�5ڇ���(I��|����:��mT�^�z�X �Shp����pPa��&�`ƒ pPa �blVR$SʲIV#��ݐ��N��*���j%�ƪb�E+:����F?���������0Y��.4���Rd��2��lG1 m�����V����*��1ͱV��U�Y�u5v�󜈬�=�Y�]o���o˪�L/���%UV�3�َ%2�c�O�h'�Y�K>��Sgvgղ�s��|��VU��ܹ[ٵ�gV�y���{Y���YѬ��uB�����%W���-J,rS\�)�"9�vDrS\5r��Iz���f�~��3^�t֑Ԓn%���]y[�n%����B�߂�|jj.�Ddң1�I �>i�̬�[��ih��(��9�-�e�aW'=IΩ֔n24T;�n�  ��%c��3++�qla�M:���:Y!��it_>����;��']�����v��I���:�(C����\Źx��t�OT=�{�^���Tt-�2s�"��j�ͥH6�����(r����2�8�Iվ����Q�d�T;�qA�ڐw�wj�I5���p\4"QM�zM7��8f���f��.v���`�R�]8-�b'^T��Y�~����{"I���~r�HI�RbE�"�4jfq��w.1�Քr2�^vՏ��kN��ō���Fh m�Ej�����YLUi%#���NM�XRe$U��R�#1:�PyL�R�e�~ndUv��fj����hQeR��нU��[E �[e҂\,���0<�,�6Ӻow/Y��T�8uS��9!��,|O�����,�h<��E�L3 +i�ab�l�~��U��LgQ��u�{��P���Y��(���y�J�hvt�o��1zb:!��~Sr"���~77�Lrc�r�S�Q�Ѻ�;i«��0��#�bX��+ۍ� .J��Vug55J���љQY%.hH���&R4K5�Zי,���l��HӓOU���;6����)2i��j�Zgޭ�=��Tyش�K,\�qHu�Ӣ&?s4��5 �� ��6�S�>��̩����eU>3�}�8t'�rQ�Y���V��z���JQ��N��jfqF)Z�U.jq#��[��L�Q㫷c#��3Y.�v�#=,Fbպ�ɋ��4V)���L|��>T,�>=�I6Ej���ťWZYQ�#d�CT��4��<�)�I�e��Ԥ݊E�y��K��m���Hc�^gTK��i}X9�ߐL�,��������֭ݨ>�Ս��8�>g��NL4�~���q~��Q_���R�1 =33��ݱ�P�E��_�.� UH3�ߩi�X��h �fZ��6J���A�����.bd�G�8�3C5U�!���\Z5�����=�C���w6��� �r�s�t4~����4�����nN�������Lna-��[�:v�����n��܌?���u���4��Lu��i��%�ٸ}��-l��!�f�nqfJ��nA���b���-L�i���i��z�b͜ә�Z��g�n���m~R�?o�L}%�,+���I��T �<�넵hkt��#r�:��ů����z �D��T�mu�|Q��n����ܢ7�2X�m���b�%GFct2Z�2 ͥ��Ф2�.K�+�q�Q[��,�-|Vj mh��fl�?�}�O�yn��Ī�ݢqR��xV�/ꢞ�#}�dw:���g�T�}aSe2��v�غ��׫�D�,U�V�9��*����:eF�2��N� �0�xr��(+�bjU��3b���|r�C��3������|B��h�&b�M�Z�l6���� �-[�  .K��|��.U��j�Ш~;�o��Ώ���^�B:���}�)�LYȬ&�;��f�{�h�&�ȞzM��˥:���&�Ε�ͷ���C�cə�X ����O��\�Ӽ�9M1���FM�����-r�+���F�M�����$R,�FJ}�df�H����H]y!�){D�ι]8�"�F+r������l�؈g��T�ڛ߭�>V��nn�޳b2���t���Cn���Fv )/+v�o)m]J�n� !iRh.�)JM�W� ���1YC��GC��B��H�Esf��xm��6D6s|��ӍX��1���I#J�[vzIu�H$����;�x��g��i7���0��ř#�cF,2"�K�����b��قH����t�^7Ō4�.ql��e���lt #�G�L6� ��B��ێ[r�b�szC�^�XfGl�X��WkJ����焬����Y����*�lG�)dc�U�.�3Ϩ>.���C.k�� }J�h�u��|��&���S��4CS ��r׏2�jd�ٻ�Cd��Zf����3�����l|8�s�r����#�)۔O"��?ChHR'O;����g�1R�fxK��J�2G������ncO�"��� +�& +K+䔜x+V�E������3Z<��e���6��6�=�!C�D��ǡ���A�}�2��E��qHtu#�Yg�3��E-]�g��ѫ,Q�k�yUS�yc����1&���i��7ΤY��Ql<75�˪�� ]���!�fx���j4wݰHF4lzN|u5�ڳz��򓹫� O�z5���8C7c=u,��,5Yd�;o��rGg�rͨ�V'��$�^Ni�7���JR{�y�9eel�X�*9�x�,� +�lzs��0#�b��xb�MQQi�c:�Z�Nh\�3��qW%5��g ��6l�6 +�ʓ}���r#r��D���z��4wU?b�d(f�9�Y I�dع����S,㉼^%�do��UTԟ�]M՝�(�K +m����5,K�V��"������8cBIJʥT��^����򄴒�˭z�bVT�U����r�Ȯ��̨hHgeR��i�aϙ1� �|l�>�5��ξ�IM�ff��<�%�ܥL$�O��3iBĪ}X|������%��^��GT<��RT;�P)vc��Mi7i��9��d�(�I�6 +ˏٷ�~[�>���c$��j���C��h&�"�)����8("��r���B^��A�K��2�l�'�s� +�,x⊖���Q�M���W�)�q��+ x�T� �$p�Nr�p`I��Övr{�C:pp��N�ƾ�� +Φ�7$jv2��aY��$�vC�S's� ������� r�Nt�m��N���p��hi�l��NN�lH�bC�鄫�* �ND0vאU���`�e �I�j��]��餯I ��4�yN��ix�rrwKCb +�ᘤ!;� -P$�R9$�a�r£��pM�B� ���� &�S�@1-��X��Ͱ�9��bogςr����9 ��`M'Z� P�N��2|����t;�!ex)xr��D�A������Sy�#7=��nO�? ����O�U�m2|�����O�D P�H��(C� + � +b�BP^F5(�I>#�E��Pn h(o� +{(�� ����NoD��O�dTD��o�$�S�����+�:�?�"�PN���/C!=S� ���PH`��(Vt(�Xʡ�%ʐ �w��s(/�2,D�B���(j���Yȡ����%܊A2WjBޢ�% ��5Da.Q���:�,�X"����{� ��(�,CA�` ,P�(�2� QH�2�_�p-ҫ�x���(΃d��Ӏ��;���È���ʱk1���D��Ei��]F! û~H�F�eð�G���!x �' ���P��S�f����1sR�0�t�PXJa'8�+�(ڼ�P���r/T!�^H0eH��2�//&-^H6�B���(�r�U��L�@�<��ͅ?�Vr!JA�)q�P� +!� ��ʍ�B<*i$j�I�e �`*�b���M-�T!&&޹V�T��(U� g��V��/ ��*GP�U��X`��Z,��*4/z���7Vn,�e��W�� +z'�V�ZW����N\t[!_��0���� +�{+�\d����\�Xa��r�|�*��y��d4𕊥 +@�+���ac~ ��7�Y�� +�+�W�*�Rbi����X��mcA +7�X��dzF� ���dQM�@ �����/�rRH!/J]w'��,��\�:˝�ﳴ�P nh!�B� ���>݀�bZ0( �~B@�BV|ˆU�-zBԵ���V�r�~�l�ޜ�}��qi[����q�)لvK������Ԅu�[NM4�tr����t�����-:�*Z 3�0r �<��i � _ g�.��"º4=gv��](%��k��'�� �I@ A%!߅Đ�/��FBn��Cx� Y >��[�A��z�b/��#�D��]��/�bg�txF�V_H6F�x_N�"~�E@�_\E�ڿ�"��Ӧ��ڀ!��d�������W +�$;�� ���K�N" mB*�`d��* �)\/L&%��� �q�0LJ[y��m�E�a�"�/�%��Bp'�W��2�w����%�p�Ac��Е1�sL�1��r��K}8�M��ʸ=(��z���9�� *�L7� &2�&^82W���dJ�;�1�A�w@���`�&s�� 4A�^��łg (���T��A2��l��0/����\[�+��#�� ��̿���8�0 �����gf.$�f:pV3D��C����"ֿ�7\n� `�!ŧ����lЈ�!�f�ᙫ$��3@` ��3�O F�gx�4��3�C�����A�b�ދ��]hr� P�tg0J�ܜ�b�f�����/�_V�M�&���4G� ��4�:(V�1�xi���~�<�p�# +���i�(�5d V-j�� B�i�^�Ԑ1/X5��.-��~�"�j l׽aM.O"�����c�pA~� 8Qk����[3>��ܧ�l���4���������;5��m�x‚s�{,�+8ş��5u��EKcvMj� +8�㋖�C!*P_�@�S�_g +XXS)�U��)1����ï��PT��1(@�Æ����=A�Ǧ�:Y(Z��M\8A���6��B���K�3c�T�M�^�6�0f�(��}dx�=����-EqC���M��涢���эVL��vK���������F��� ��Q\�M$q�n� ��CF�`e�E�)�[�~� ��E%�8��`G � ;D�[�q�Y ��8WPjq�D �fqX���㠱�q�B 6��`������!�pV�2Jȉ;Um�$)$[r���~r��ꔓ4=�����"��rnEp��&�0�'"�3��A�՜�2��&����9 Rd��d��tY0 tH7L��9cQt� �vt�^�4� =f�=?>v{�� �O�`a#����`"�R����BV@�;x���Xn��bO]֦vjt�����(~={�,����O�| �^X�>����3\��� �Q3���!҉�� d�����ĻPR�Yk�� !o4<ぴGgs6*Ӗ�@ +���/�����v�ぴQL��Do��xyDpy}�x���3 ���F� ц�$R�g<��m|Y��Ĭ��� 4�J{�x��&t�χ� ���>p�5��x��]���X����o��Et�;x2cc��;BM�=��wl��x�4;��T�A.`�,ZT%��>dv��c�2`�~�R=��9;�Oa�y��$za,�a�w���x�����[�`v`%�d0���ف0�R!e�S�RXuI:0��:��G�`�r� �#z�������0�ǁ���Џ����_�� ���*��@7jIU�%�<���&A�6�49̥�:b0�X0��V�(j>�D:���R��}@�5p2�*C��5��J�Mo�� ���k�n�/zuէ~~6`i�63�9�x�~� E��k�F1���S�H��.ph�u;���>����5�)�B%μ)ul��pL�a^SB{uP2� :�������2m��,DD�pp�<0/�+-�� D��1yG�)�v 0����� �b���7.u�����0�\[��ܗ� ��~���ȡYO�� _GFж�aF���]O���t�8Y ����_�^ �e��ڷ���@N�?:I�� ,�E�l^�%(gf��u�Ũ +X{��o��C֙s �0:��d=&e�sR���򟀊(�BSX�O`~���~��@(bL\�F��#�N +e\X&�@%GMz� PL�K~�Jw-0 +�����駤\�#m��/���E�]F�Dm"0�v1�_��!���6_{ +�W��c� �� �Y2G2��]�ʈ��T����.*�Վ�� ����ly���Τ{�B�f��*pEu>/����;��@-���d��>�造�����&)r��_���Ѹ72Aij��6@��{\3T��0+�י��Ei@,dž�E��pd>!�R0�ܭ+��C�ޑ��w��u�ˬ+��$�� ��1����0�t_0b�7���f�hI��fr���$o�6�Ҕ(� �6 ��\���������NX�cY�<��J ���u�8� �;4}vm�*^���{��c +�5hR;!��o���b��i%�i�I�.�hZi푲@�N|���F�����4��%�?;�'�I�@~����@_ +dDk]�U��m +)��_�38ꍹ[�df 7��R|��$b� �?c@=BJ���G��¿��}���o]����{� �E�qj���y 1���3"(����f�#���m gܽ�?2��ޟ��>�/�VD���V`w�B�h�Ǧ\(�0{�scsb����]�E�a��ƅ�\���D(�BR����4���I�s�O��߆x���`=��B�K��� �����W����y����b���ÿk�e�3��?�ヌ9j��e��Ã4�^n��?��U�$?]��a,$��]���!8�4�P��~ ��dx]�� �q�/���s��]v�Aw\�����jf_�==H��1Qf��D��OҐ��J&V0�4���G�Ì�u&� +���hҷ�?���D���>8[x" ��*���uv ��_�I�C4���P���0ae/ɟXC����Ld��q���[�Y���ۄ�Re}.�l�z–����\:1��T������z�7scُ����jfdŚ��߃J�θ���\F�gB�-����$�1&^���� 4m�ƙtڗ����\�oΠ1�|�d$�ղ�r$:���c.�#�_�afjw��!�b? ��`>ک���[п��pק7�0���6��R����E~.�7��B��%vo�����7ݜ:�����%�2 ��zV�9� +8P,��0�ӗՍ`��-���K ����?�(} �)�2}��*Q�ي=�~X���o� }��~�M0����8��wS +�K��۠~�2s+63��%�H���x'�`�������4'=sF�h��.�����ך7��-\/"P�k$(@i���1��ү��Q7�|^?`��i���|D��3�̕�X�3�����-!�s֩�!xႇ�<�7�O�b�}��Yd����s���r?6�v*l�l#6����d��X4�<<}�u��O}���f2���~!~���uc�^.)����/gN�$~�U��,��gƪ�^�zp%H|�]zla�EH| n���7���ty��!��r��A, ��"�8L�����I�� ���2DE�L��q���Ep�r3Md�������y ��>�O���V��{m�%�Kˏ���Y�>5|ō,?��5�D >� ?�1`l� +E�%N���y"�U�V8SP�E�/d�p��oK>�׉D�z��p�=i���k�=2(m�:2��7o|�ț@w����]{�ǰ��Ax" ����#��X��ŷ�ހ_�ZIoyi�6�O~�M�����+1���� d�9~�o�K���E�[s����y-��7��l�n(� ��~�{e�Y���C���z�xI�?��� 6胶�E�X�}����xv�6�7t�u��}u�&%B�6"o���MvI���1k�&弈�6F Afx}��� +5���٩���[8�I.��y&��d9��@<[�~�b��˵G�� � ��ɦ�'m_���W3yD�x'7�[%u ��kc��\�},�`��b\�b�W�TLw�\k��� �b� �����T�uq>�f��֏�9�V�nMS _��mgQ����]�B5m��O��N��f{�yٷ��4���lvg��}ӟ�|k}� w-"��6���(���3�]}�p�H�7V�ޜ�s�<@ճ� U��E5ƿh�O=�ĉ�R�����]m�zb��#�U���D��)_���%<��P�,���@�!;�+M�V�b~�^�-dV=c���}9���*EפGR�6�"}2�≌��Gb����!��(�����x�N}Іs{�3^�3�J���^�����8;.���ZM7���'�gԼ�#�|��I� �H>;��+ Z��g��.= ���y���%��|^PX����uί�Z���x��i�$o���`g(���yam+��+�j������f��\*���⢙y��4����C�c�O?g�y�Du2��藏~f岺.G�O����ǖO����nq,�NvE\�6�o��#�&Y/ej�G=��Z�����f����خ7�|��6��;䢟�1w�*$` +��w�X��lZ�G�'gH� 7�r9��$Չ���Q�z�o�� +�#��v@>�L��F@���&x�����^��AQ��ſ��_o�-YwVA�00n�G���Ϥ|��4�xj�K;^�ٔ�ǛG�lǟ�‡� �v|,��U�df��/��-�??X?Nco��P���>p��㝄�ɇ_�c�Z1/����FR�*���yFrK�r|ƶ>*�x���z Ǘ�s@�q|e��FA�8��[�ǻ��1�hDj펷r2��Ϝ��z�vE��a��������W�U�K�������N�0��;<��=%��V|,6:�$ś�(2�M<�K��V^Nm5�DXl�TAzyx*2�c��|s.�V�g�<��E�h�x� _"��*K��� +xjf�#��E��6��kY��k�u�1��KJޗ����_ AT�l��q�t@}��e�O�0����]ϚW�] T.c]��W����/ ���u��x�GpȄ���[��.�>@����Љ���%��������ηgT�<SrsG��a�KY绊�@8��]JJ��/-��_a>]k�x���GNΓw�^��b�U��P�B9��V�K;�"{yǕy��^�s��J�+zެ���H�L���^d�4c�RM8w'��{�����sj��2��s� �p覂5z�(^,|��ǫr��Xɺ*3�3�t�P!�ސY�y�;�_4�������t0�Pn����ː1��q�s�b���ɨ��̈́pw{7*rC�������DfVN,Z{{t#O�����WI�`��ۣ�%���Y���@E�Ѵ=q���=V���KyD�`�!2���7�Y��S-�jWKǐ��Un��0���!7�����Z��Hi�x<�G�(��,=�ѴL��f�������g7 Ҁa�[�9;��V��7�����.�ك���$���_3�z�q;'{!iy�`�pm��l6:źy�' �T���?Jt� �b��M��}��AP�a��#����(�>�86�ӭwC��!J�}�j��T�ZL�7�ІLaX��c.I�����@���}=i�̃�D�z��Q�����[3ŷ�ꮛ��$��ub)#�T!Jt�\W�?&\�odp�XC1�uL��6;���'�@^D�k�喀e�n;�u�_6V� �g�%7�p��,����rr��D�(d5a���_„��؅�;��Ձ&t7��G� +��9WW@��n�9�j��ǯT�/�ꖋ!4��$N��¾m�GFh�N�3/B���-yQ]��@�w�y�=l� �����eh� ���+u��15�c�D�Q����,�3�IC�΍��g �ב�O��A=u� t ��ҟ�#JC�&I:D�YRш}����h��^��+N7� +X�����q@���"��5���0EJ�b54��?�w�G��ŗ^���8k�o_�<>�Y��� \䀔n��Bw�1JO���D_��@6��"+� U"�cTM���-��Q�t6xjo<=55�2�v�bW��;�\d��n�Ar��E�t������[,�It ����yÇ�H� �—�剅\���Nl, qI#%�a�}��� I���7A�����>�2�@Gl�qU�.�z�dp@O@0P �#�?o f]��K�� @�������Z0'����=�%�d� ]�����A�j��c""&�{�w�9>hG߹�X�F��qb�v~��?��-��j m��DۏK +I�Q���v��}#��!@��k��3�L-�ӏ8G� 5�tA�4��� �9��q b���B�1��ͅ��K��)~v#���f ��X�f��Me#¤f�8��c�y�����3?;G� WuB�:�\]s�m��̅�m9C'��99~��M�an�&�2�1'��,1u:��9�����sNR ������Y�ĉ4���w��8�������,gc��k�|��{Y�( +��A�bV(��}U�O`�ʭU����c���A��,9���iq�@D��I~<�ـAb��](���O�P������>;9��A(Nor��d��K����ŀL�򳵦K�@�];� vI���w)9�l/~���#���\R�Gc���m��I���E.�٨����03��ji��7�(�� 5.XW yr��U�z�<�gc�|� ����$��&0�0$C�� +ey\D�LT�-p�c>��^��t�P6$�W����n��P� +�����ʷ�!Z���8�Ɖ� �� ,l����iǑ�pɑ6�k�q-���_*�l.&J�5U�W Ju��R p�\sx��(� �n�0�yIj�yw�3&������K�.�As����� (��ڽ�pG���y +&y˄+�n�2��f�e�T��"?�r��.E������1��WI .�,�����H� n,�,x���F�8� �(x�U��3������W�qV��3 ��w �˪C.Fj��@�֗�c��՚�k �3+5Oo� x�#O�����I�<�S����?��&�xX|��+WY��@HJ��%aa +�%�nƷ(��U�e2p��Z�#~x�<.B4�j�� + x�`�h��O�u��XSp�g�x�2תJ������z�G��w��t���E�������i�:���x ����Y|�����,/~R�;E��8n��`�#�� �A����+���ݱ�H����%�R��O��'�8�����P�k��5��r�U]���� �������w ~d9�Ǎ~���E�/�w +���=B޿oU�� ?��o��sa߀b}���]WK��=.�S�)L� ����m��O�m.��"K��=:�'�m���5����B����a�<#�uI��Fߠ��s(tsĬx�x[o��%�5�+�9M��/�]0ۉ�<�݄>�).z���*�Q�nƀ��,����a�/Ue�|��x�׭��.͈��������LP�(w��ے��{������;^���CU��\����5�n�紲[OK�(1��J�N^&b�ŕn�Յ�p��n���q�I� P���_��&�3��$ݵ�gV������;A�.vtw��r8Vw�LcbB�ܺ����� �B�݂t��'~�\8��� �<�&�z�d���N�7W 7J��  L\%w�"^<22J�@o������Tt���(p�P���3��'n5U��ˋ���"��P�ĭ��}��~��qFC�I�ݾ.1D�6�V��7�8�fJp���z �>���;�l�/ ��A �f�Ip P����E����s��a��0�[�������聇4��Ȧ/�A��l�e���9��X��+���'��ۍ�|%��1g�,���)Z½�]�?��;#Π_��.�%܈�Ȣ�E�pG81KЭ�p3Y��l��B��E�؎�4n�f�c�3�ᆑ�>�pwc�-�p�^�D �`^k�AN�P +<�L\CȮd�4� j���8�n��g R:i��lK�p?{UnTN^L�3�f9 ��:��K�#���`��r�c�۶'-�g�7�TW���F�v�z�lT��q�n��V�����a⫪�����&.�.���C�"�A�g�����N���=��n$�f��p/�"�>��5���� w����AK!���J�Td&3�km��&G�[��kF�n��6�� 7���x���M��& ��n�P!����|'Z��p���@,qBn���$�?i��;N����`p�F�|#n�}AHq�v�Ь����n�T����E��i�J�-K�!%�AE�M���-�����71��+4�p���m0�)�[�I=\:@������ /�"4��X��HQ +�o�PN��$D� w �-����˰�L�[EM��=�"�Zp�5�m{&m=�����C�>*������1�l�f{�0�<+�T�%�[�`�D=%$���-��l{"�M D�]�v6 ��P����0�%����]6�\v��kW� c!��e��� ]�oq*�m��6񶂳�H���H�:ߴ�-�wg�4�������Ơti����,� ����%mw[�$�m��V f�eR�D�y��i���xڧ]@C%���lr�< �V����=lj� >"g_�͓�����=�y��b�i�w)�� )�?����,� f�F-% % �9���Z�IL�W��7�0e{�����:��C,kϒ��2��F�89�8/�"d׃-��lö*᫇2�c;��'�c����4��@����f_����b7�D3^f ��Z��i�A��`HpI?� sR��ч�v'F'�@�a7g��a��?�QV�uaw�8�K��e�����<���ۉ ��f�W� � �4� T`�,=��oqɏ�P������!�� �K����qN������d{�<���HЗ<5Ⱦ��"�]������i���FE ^Cd���0e�K�E,��4�<������z?�5!ϵ��mCP�V����;���3�IF�d*&�k�0PE�[�qQ�'^&.nͶz��G��עP2~�u���[�V����C�oZ�}\{c���h}N6G�+��%~Ƿ�L�,ʬ�쯃�]� HY�t�Ƃ�U�5^�U��yc�Q`���>��K������ZZ���9�(X7��W?��̫c��YL���Ni�b��G���U���,�{P�x��w�}�&���@��6�8�$H��?� �Ӻ�jn "��l�0��>���bEU��Dd��w���^��o9��� �i�886P�����U愡:I�xv%n}j(E��Կ�zl ���&�4��BA+����((F����Z���ѻ.(�= +�U��Fj��>����5�*(�i���4D�8��uS�m�1�/��F2Cv�0���������r(BdlCP���T ���O���}z�d+qH�"�iL���>#O�Sn����i׶�t+�I���t�U%���K&z��.���o��X��^�q��MKqP�kz�������7����Z�@�6�-�yI&� x@&��i#�F`�U˖�����^�R{KC�� ��QTΧ�t<�^�=z �L�' `@aĔ���捃� �'�)���&m���J����� H��p �࣑��-<��;�9��ě���3�� ���ntKyYɚl�Fgln���AU�O񼺥�i�1��E�� ��G!2uE#0��x �Di)z-0��f�8ѩ!��D�L_�$ڎn�ƘOb��� %�q2����>�_�b�&�ߡ�슳�zwCǦ���K=�V��8p�!G?A$r��w +=�Y��ER�.���`EB��q�Bϲڏ�S����`�7����'���6{�� +j� ��� +4�`)�y�4����|�2$ |@�w�/���sO?h!ʀ�Qk�a�@jq~�a��z�k� �+���2���� t���OO�g����tVaϑ��gƬ�w�����oƩe�QO�I'�3�:�'ʛ���U�m嶏~�W�k,�Y�1�Pk�s%|<�,�\�\��������6��H��avF����w�:^;LJÃ���5�,�,���x�U�j �7�3�Gxr�&����qÜ��Ĝ%ݸ���0��y�FE�"K��8*���2r.}\��b���;�E}�9.8/F a�نߜ&\x�e�+o^ai�t�b� f�! � � MW<����f�^BZ,e;��oGH�P0����!I��6X��8JE.k�Mq�#����누��9NC��=U�9Ls����Gs�4�K���f'c����<��J�'�tf$o�!E�f�6�xY���x1sK#7B�>d̆eFG� �Z&�����d������\"�s������6�*7�4���&v� V�Wf�ёI���rS3U�ܭ�މ�I��P�#��II$f7�P="1ǫggх���?&��|* s9 '$Y�܉�1/h�W8�lB�5̅��} ��0��S�3�a��]8��8�� ���W<�f"�B}�0�)P�e'�E�k�K�wlΘb�d'��T�~�k��`�`�Rl�n�jO��!�i�!�� D��� �x>�������zÜ�@;��a +7�g4d�Lb�P0\Pݳ��\H���T��b߽�� F� P��!��0���s� �H�c��;P �Q����Ng �s� ".{0�3~2�%s4���zL!�V��{SW��N����'���+�I�'��/{e'���{Sa�|��IS%I�_�Ҏu9�1#2��^ك�r"`P�14��H�V�?�[��0<Gɖs==R�[qX����� �5Vi���r��,[�}·%4�S�+���< �#~(�^����1��rr�rT���2��%����n BMpV�SUN�����R��^���X��hm+����C����f�h���79��X��IGx�X����q�� ��6A�s����Pq:�>`{��3����s��xӒY �yL�ӧ���P0��>t����N2�!T��N��A�M��م�ā�!;S]V߶����;\�vNR��@�vRٞ��S�y����'wLH#����/ϞuB�g<umz��Pv)X��;<�08>����o�8pWcҲ$B�< �x��L�[�=*�!�V?��CX󧦗S���u���\����w��@I��cx��ym;A��2�cЗ�@���* �J�y��y�U��R�B�`s�^�C��<��.oAM�jȆȂڀwe��D樂�g'�_Q��B}� �`�x�����â4��8^§�����F�Z��G�ғ��(t&���}jd��C�QC�el~4���)����C~K��G�1i%4&wnj�Iڝ���qDF��I7��@)�?�I)0��T��ͼ��n�4k�ځ;(ez儏�&�O��ԃ�v��W�1�2�f�⎓ �LR�G5���� LIe65@8O5��Bqj81���� �:����c��O����6* ���MA)� u�Ҁ-8��[]([� ��C��pD'��+ ���C+}#찣�Y)U/���S�e_ֱK�@'[f*��|S���E⩄վ����~#���E�fnD�z-��2Se;�[N�X̧C[-�����U�y��'^��*ݘw��j9ZuP�J��K�0�j�漋���չW'7���`�k>YpIXmy2Ir�_źj��/�1�^n!dW������R����Ž���=C�J��%�4N�7u�I�;�3�2� d%XU�{��*Z46O��86�X�>Y\��D&8�%�]���U�,��nW����߲_ O�ܓY�u�Ҭm�l�P)}C�6W굳�a�V�V�$���' �-u��h�2D�庖V7�>4K �N��S���ӧ=^ ��Z`#wُ���h���Z-z�?u@�W��~�k�\@� ��8QԾ��I�:lWr���r��O����ǝ��� -�����bہ���Ng%�| PU����H�nAh1~IsRij{�j��F�߶�h��E�����pQ�p���G�I����q���������^ʥ��N��N&G� t�TA��ܝ��R����@�S��C���|t�g�z�tE]V6�X[�U����;��׽e����v�0��.2� wO������HL�ވD�~7AOR��-���p�\�,uG�;7��x͡��ƕ�UҼ4w����3z�����P�ѓ��W�V4�݅җP{۝�ܻG������yP����w���(���A�-_]d�Ϸ �h����ҧ���� 8\�{��Wl�Jf ��};�B����ju&g�<$�V�g�$,������ +Ĺ+����� �y1�]�r)��}��̑�W�o�[�x��w �ѷ�n����+����Կ+���W�f�jε̄���7z�Z��-9��}髎�)��������w5X�6}�����EK��h@i�쏬S��^Ev�J��7��l*�엾{�o�Y�{�S�t� 蔾��a�ǻ���%� +>�Kt) �Y��� ���;_�0��K�K�I�+ݛI���ht��f��F�Vy6~N�8�ġ�������S���R�A9�b�O�������>2���/^\�ەP��������)���!ͩ��@g�o��f���W�&�jC��׀��(�7y��6Q��J�gs-kb��:��O|�������$��#���G�CJ|�#8@�A$_Ns���܂�0~������k `J�B};����_�]�=�����N�$��W��HVߡ!�U}{���zf������b�_R�ha�V������WI.���;�$�u��S� 2{�F�iS0Gp_��Sf��������;0��� �F��-Zm����=|7��N�>| q% +��÷������Wk��?�o����-6�Km�m+���mj����i�g�<�hU_��$��$b�#���i0�!㇝� ��y��� Q��{�F#8�]kg���s{W��"N[���9|O�Y�/?���H��wݤ�D���䰏����¢�*�E��]���K\Sf�U��k�E���:�%�Z����j���Э*��8����Iό@q�m5�_��������- ��_۔�����K|���R��lK|7�5��K�"����k�B�n���}��w�)�i����oY&�(�S⫃�%��|��-!�L��z�ĺ_oC�>�e�|F�=0Te��W�h����|a $_�^� +���&w����f���>b��]��-4�[��ijU�M� H�b�]�� 5�͗�kˍ�͗l���b��|�3e���P�nX�U��P����e�|��x� ��,����F��J�Ģ���6��{=��Sҷ�`G��|@��w�#.x �6���|<Խ $ƹ c�z]�y������K�������fs�}�ƢT�_j�˔��L��w��,� -�<=�����[���D0��|�y�)�߽�ՒS��=߬�jq]��w�y��]t\e��3������A�+��~�{96��� we� 9#��;�7�6���`=w�P�n�CU��M�K��7 +�2��竿����G�, ��/�K���&�6 w��b|�|ehg` +���B>���4*"�lj��+,�1]�^o۟c�/xw��A���|��pp��g΃�F%.k�b�.�M]% 6�f��w�pۜ sxȝ �J�E�a �a��x���>�0���`#�>|'MW�)q��rX�ѿ�=bd�;��$v�O��/�cN�9� ��#��R���8R�ε+�+~�[�X����,f����[

�^2TeG��m�����F������t���nd��l�^���L��F� ���6M@�*�<�.��gB���$:S4м�$�{j�+�$;z=c8,H�f6�$�S<�#:Pd�~ΔdݣЬu��$��������)� *b�9k�$'o0�L�$�%�3� +QZ%��EASIƘU�S2%w��>M]���t�w �c��#Q�|���e #RM� ���,������&�ɉV-�@�-�(��� ٹzC%��%�(bܳ9����/�w [�?H�GJ�n�k({��ra#�Q2�qտ1?)D� - ��rQ�D�yW�YՆ(y$^ȡ%�8�+O#�L(��� �o�׈�V�y1������]^���早�c��ǨB�U,�̽�8cfFwӭyf�b�h.h�s� 4�ث���>- +��A�F�[ߊ��d~��5�=^p^���g�39��9���\:��44i$����ŀ����"|tI�M}�T�����^_���>K���F���y"@��i��L����|�A� JE�/�B���i��#�4��f����`�h��.Xt�� �Esh�:����'U��i� �v�K�7�a��.$D:�g$IC�O��՚�ޯ� �N�ұ�8�O6O߹� I{;i\j4>_��/e��\ꉌ���iZڟ��j�u���E�/<爚�駋)�Y��w������@�E��/d�z_|x�p�3T��%��"��Rk�2d��U�f�s�R_�0�|�|WK�,ߩ�,��S[,���Om��q�j�&I���?�Oe����w���8��;KM�{�!�� �]�|u0Q=RW�e�~2R�����H��d���H���,d摺�^>0���)�㸳"Cj�]��1�L"Ɣ0�!����������T���B)5$8T"�Q/P��%H֣�f�a�� ��z&#Pv�g(j��lfGT�%DQ3I@�$����':�������.E�'윉5��.��u�}�e�J�c����g����*D��گdGx��a�&~(��A�@�h�<ǁz- ɻ��@-^8@v��"yA�@��@�u)����@���� u�G�ոP�Z +&�-pY����.s�@R4��]���x�5��u5ܿ���J�U����cw�)jE6#C��tlj������ij��/55�X��3���]M��Z�G� +�R���RK��]O�z:f�=����\���09N �֣�ZV��T��k"U�l1�n��-/C��g�$���S[�5 +X��UX�Z��}�/-\��_�r=�T���G��j�#!�XO]֝&���f�_((�uk�G]���ot�h���Sd �W��������)��֨�� �F��ޗҩV0R� �E��K)���lm9�}WD������ �K��e�X"���^ ��������5�>Pt&�Z[�}��) h��Z�eN�Y ?k#b��.��p�t�uw�F���oc�����[�:�Y��u�N�zL]��wuZ7FLl� �_wZK0���E��e2��m�i��v�`Y����u�$D�iM@��h�߃sZC]1ǿ�p�+��N�p�Y�k�݃g���5ҷ,�����d�f�- �o��zh��n;������cR�R-�1�V��x�և�* +�=$[?�!�����E�L#���nuڙϋ�����q��"�����D`%���5b?�؈J��uGG�v'�bn}�<� ��a�A�5�[�yf���ku˭!&��<ߥ�W�:�$���iwM�p����{�p�������1s���5T�is�\d��\K�D����Bھ#���vI�A˾��c۝��Sh��>˄���8츉���ir��R��<�F��n��>���#鏀�\>��c�= <f��jv���O�� �A;rw�?h�m�ic�9`��jV�Ы �"\[ +7��y��%�=b:B�Cۼ�m��T��?B<��g����o��q�m��ײ�;���3�Jn����]S�� �L��m6\��m��������n8e�>x�)��񺉑�g��/�}�a����'(��]��`����q��_l���0DY�KڛC��B��!��`YY���+���z15!����7����O l�7V�����܄��.txpL����V��Q�Q�ܿ ~�gw��:!��pj��c��Md�J<`6���t�\J����Fx럺;n�9�r��v�o+�V��g���J` ��b��8�)h|�-�-��υp�k{V�3i��㏿�r� �BwbLn��"?D��}�sy�!J΍ɿ�F3��a(��1�\1a�]��F��!M oW2�$��@��#bZœ4&Ps̏l��2��d��9��9���Y|�w}�l��9�p6��F +Zu�7عwnDl:�T8�F�sycU~�9c��������bá�v}"���}Z8�WH�t,�Zҥ�խ��"1anj3���`����,�� L� ����5uIHȤ^[[N{��ԻQ�T��*Ջ���^�X����z�Œ`]ģ�RY��+(�÷�LH��.��]7.׽V�C����,��@��8�Ydǿo�{�>������f��<\X}��NM��$v�i��ΰ��si������ʮ�ƛ��vy^Z +��J���V�g�7�;j>B��O�dA�ƛ�tg���W�ߺHݹ��|�@��>/�N���a1���V�˳ �1j��|_�Q����j��,�Yx)O�x���S +�LU�x� +��K���;� ��/����^�4�Su�Ixn~�������N����&ן���G��YϜt9�_׋��!XݞC�"��|�~��z��� �Iq����tj��}Ow��1����‰���]�4Z� �C�Z�9��i� +�����C��}p��K����^�?@�j�k�k?�+� �����5�cq�kP)+�(,+)��ݹ-��\�D��B�7�-X�.�,v]y��j|�ć��e�MpYiw�6Ģ��m�U�U��ݰnĹ����r#�hV@���ü�o7�o �d���*���z 2�0��-����_{�J�ġ�e��.�m%b��S����sf�en|�Y�c_�:?��n�M����i�K���փ��oL�$�+Dj ļOY�돦i�A��6��OR�а����U�|�K�ɜ@��% ���@8�߼�y}� V�'J�z�{�K�4ݗs��ơ�]o���v�}d���>Z�ҪU�ϺL�|�������.�j ϸ�J�� s�,"�"�[�r�`���� �0%�0 +M0�=�R���H-I7�n�j�Pa���� #�Y��9����c � [0h�4B͊5�[1�WQ�=�\��u�>�P�|�=9��ȰsC^DP���,)�|:��%���c���+"i��k<�9>�`�" 5A��"��I4���v�_[��V��0v��I�c�T�d� +�~agX^*)?F����V�^��8�k�l6���i�( 5�"�/�:�O��*��=����X�J������҆��?:���W{��#@ +��&} %��S�{Z��@w��2_%��;v�m4��7vD%�������ZTqP�� 5�,��h�$Kц/%�0���4��ὊF�@J��eoKw��I�f�bh6��8�����*��b��0 ��q8Rwm����c�v��e�۟.<�ǩ�Ҩ>7ӕ��+{~�>��r.k����d���P<��BZ/��I�od�s��Sd�> +�b���3�2�1Sb��;r�h�R��-���Y����&�����s#��G�/T!1I���#�)�۔�d�w;(b&&�^��,���A)��։s��W'"D��-z0+c�'�kԛ�<1��Nd� �Q�f� +�ĩ�[;+�a�`Z���Y���Z���qs�M��賺��W��f���c�V]�����2z�N���??l�Y@�D� D*����5oV�ǃih����B^�������&k�S�(!�hdC����$6&{' 3��z�N3^~�g46�R���Ւp�_ ?��4�� ��=#��9�� "Q!@�}/kg+N�wK��.9�x����|���q�{�;z�s�~LT��4b�%��Ŝ��Zh��x#q�,��ˈ�%�$��ɖ#�kǝQʻ���XV�C�89!f^����qֵK��*�i[#Ԓ��^�ߝ��a���Dx$C���eQ�z�����˕���8@��P� +�5�k�p ����� ��L=����$8Hc�7��\���Z��M&'Œs���u�����2SH��Nؾ�Ek���v�QN�!Y��g֔<��9�G�,��=Cv� ��r����;�,�m�� f�cD�Khg<�@�����M pw����K]����4q��ІG8��Gc9R��W6�`�7Yr��+b0�k� h6���U�:�>��1��I��O:���f=ln3�W%{�vz���6�pTJk�K�D���g4��ڑ���@��cR�"���&���m���� �@�bǫ<�AU�) �b�YX�[(�ſC�8 ��R�b��AVJ�$��B;^�3"H����_��͊jD��_�<�V/�� +\'�)q/�q�8��j���^�rY�<_���J[BN���V�Zo�N�hNFG�������th+�&y���)�d �u��D���f �X�2���� Z���j!%��\�T S1�����*��k��Z�5�F�A3p��%��Zq���,6��@!q�Mrɴ���Z��{ �����F&d�J������B�7{\K���a� �\��&.�����~��Q5]2�5 +q�p,�0�N��?��Ĝp�!�����j O[^�xXz�1�I��1��õ��-*����j���!�n!H��:+{���*pIJ�����_����i��;�;���$��^tA����=FoI � 6���[ ��B*&�h��#�4l D3g�y 2� ̊粀U��(Mx9�;��4YZ �fuVi�%^����z��h�HHPoM�( V8t��46U��xt�{��p�P"xR<�KPtl;�r9���ߵmc�1�m뀥��f���:��Z��jS–:ˌZ���:�iۣ�v@rAL��DBƹ�v��BH���IVEhx�4~vy�Ţ'4ƣ�!&M�,���F?[Ry�v� �N�$� ����` ���04��y���\�HMz�DWy d���,�m+r��P���o�Ͳ +� �+� �*wcA��J���Ć�$�gd�dN�ARg��O���m��]]�ձ�s��<��f4�|�pcF�/�Z�G�7@ ��B@�P&�!ޥ�R��5�|L.?��<��E��"�<-�~�e��j5�s�lu�8`. �e�Dv��"9�l�f�wh@��R}E�"�Z/J�# �6�T��`�ؙ� �$>� P[��:��\ä$,D.*��^��P���~�vc���5�o�#�����Y�el�$x��.�sg����.���� �w�Xا��Cc���X [ɕ.����/x��az��&������`I���=�08<�$-��{0{��n�#����#��*U����HAp�Ȱ-?SEW�����~��I�M�!��;Ʌ�Zkc�����@�͍��*�R*��� ����OB"����E�%Q�KOJ�3Ԗc�<b���̧Ƚ�50u�,��v�b��.��¹�i�5[�:�?���/`:^�td�%܀��x_���g�;�x�R����Ji+HH�l ��Եtf��͚_�:1�h�� ��e�[���H 1��C�<��Ћi�`�C�Oz�j6� ��J�̕�h� F_x\�:H��΂uD�Z���m�{� n�(j`Ԛ+��(=��ϊc?�ũ�ʐ��O�e;ٜ���5��<��F�� :GA�t��}�>��4GS��7g*,9S̷𺘾�r� �4��j��z>= ږ A�_y-�;V� Q�` �t/�aP}��.4�cw��zV�>��5���+۾��q5< �l���[0�Ӣ����8��N�M�a�fɳ���R��g��i�tM�M殘8������d'�W�x����f��5`i�%:ք\V˗X����+� a썌A���isi�����!SeP�ạj}4W0PV�4��-�T�Yf���_�`�)����sF�az�*���+2^�聓�� �K�3Lq�o�Y����*�t��I�O�Io ܪ��ʼn��*�!=�h`:�`T����WA1Ɔ����4�lBeEݡ3Ў�t�' Ɯ]o +li�7:��o��mP���ȊK�g��}?"|���DW>FN`��|�L�)� Ġ��@�:�&t� ����b`$t�t���J��=�4�@�$1�kM^�Ta��K��࠹xe>V�0��0�^�# ������8™[��F�'eG����<ɋ�‚r ��}�;�+ԤhS���آ!Ξ6_p�m�j:ŧm�����l�5Զt�CmgE 8ơ \w�A�F@����ThB���4o��� �9W~$�+�3���r�>�W�hmdž��a>�2-zqrF���V�ِ��t5[�N}!g��ȣQ�fˬ3�7FK�M}�W�b�:�;�( �NB��� �����s� �n :64��� qfw�N���h��z�no�#rf'ɹ!�ʀ�!)��HvV�d4��ȝM��v�q��lƋy��[�Tm�W6E����n%M����������t��9_�me�Ɋ�eC�d�I�|٫l�6���$�jT������p+q�n�<0��y^�(m�+���w�?���Bq����: ښ�(0Fg�B�p�\2_)j���^���?�teGu�-��*�o��l}:bwB/NmՋ#xG�F�攷=�ɬ:��4P��}t��a���n�(s"f�>��"~�t�=ʙr�Wt�;٫%4�e�4+�QUב`v�T���l� ���[ժ%�7Pнt4~̔���>��3U��$\g[����(��z��pݡ[kT=t D ����c�/�3�w�������� ���br���8n���f� 2���S��p#+��"p�m}��h�R�S���O����u�d'A� .��v�^�"`nL�2�֋����e��ȸV�4�V��*�_��D�#�q�[,7V�N�8�����I�B�7�m��3�������m�Ϣ�������g y�+�􅆀'+��Vb�T�*���B�1Z�t4�X��4��K�� �u���^Íf� !C��2�V!�ƕ�|̞�����~آ�:1��9��&}���ͫ��)�����%�n|�9�yN��3\٧1�rD)lcxb�ZP*����׌��u �[Հ�H�Y7���ջu��1�^��R��~ħ�{0�� ?��W�d�df�9���(�p: + b���T2+�q��b�߂�A�ਣ�S y�L�⥚�u֊� rړIc���/�;m���*�E =E:�y\��0�(�Z��`+ĪHW����jU��_���#{��:t�]���c�+�]� 6�&�G�����|ٶ[�g�`ܠ��ZVUfոj���5��J0u/�rM�X��Ohח�)6�qbVEU��{�ʑ\l�d����ڽ�n��mbx"\�X�W���a��)E���)MO�+�����^��WM*��*���E�lTj��sB;7E�NA�``&��� �v�� �!T���:����H5Z�Rm�#x�B ή[��t_�`Q@�x|���d�,�E.K��d$�T�X�Y��r|'��q�9� +3�I�����̾��l�tT�}�a/�R\�[��N�%�/!_�E�;ku(�]��6��<�u��5+�J��L��UM *�'�l�R�'�&�e),�B�J ��c��ZxY6��"KI"vw�+&��E�*mk��=��#����f�}W��M�P��dY����|��V��$�_�A����R�o�X ���쌿:�"��ޒ�"�i�\Z�5U�R1[Vg7���t�i:��j'7���������n�����M�ڎ��O����wZ�3����@�A�e�{�t�(IZ�2Uɠ��=�*�������F+�ǯ��o��$H�D�i�Я��Ԙ���ϐIc=�5�h�����/�h�����5"�}v���:׬���j�}���,*nܠ��7� @>- +�4W�o�0K��i����FJ}?�Ī�Y������WXސ�,���� �.$?�J~�Hᜓ�%m'��Ѝ�=�����%�����L3�W0L3*��J�i†SP�OeNmG�l5gFҋ� ??�L ����5� `��2��7zDU�@ +��]=l�~�&w� ��84��0z);G�c3� ��/L�%�N��$�Gw0l��~'��G�z��F!u���w�䋽���)�Nz;N��C��l„����B����#�a<7K��������Ͽ �*�Xĸ7:�����L���2���:�S0�2r�m<�8J��rt7��棧Ş�����$M�F�-$\��8����r�of��NɈ�F�v�P�7�o<t��bF�i�}��=a�����Gv����P�} ㍔z��g +��ةH��HzZwf�h}�\j +���E.mc���~|O���q���Bo2� +l�d�Ih�m�dx*��b��u��0�#�4�c�Kgx�����PIZr�� ��'J/(@�X������ +��C����.��ƽZ���,��h�R��^q��X��=U��_�U���� �^���@*/L�A������b-��H��mٻ�Y�k�σ�Ҝ� pV�%��,q�EtŚ+0���/�v���"r�)U�ʠv:o��gvn� �Fϧ���,�3kŕ��� ��e3"v|>�Oz���/Vf�� ��x)�/rKu�,� ��#v�����۟��@����uCU�.&�н�����Y�4h5�'g<91�#�i���מ�A2kw�i`���}����/���|—�4����'S\_��+�K�YI*�G�螤 �܊�,&N�����Z�w@�9fǣ�h�g�<���t��F;�������g�`�$��#�1H<��"�|�O�?��wPv�/�/FdT�C�[zM&�9,R�dPO��?[�6c@���-����.����_�:�x\9*�#��B Ds�z����<�����1���!&�]�<6�������Ƈ��»��0��n�˻d��>��tܛ~x *}%��$I�&�@�{ o�@�?.^��K$ڊ�׈gk��Θ��#�* +Mh�Ok�,��d��$��R�T�KU���ڏ@l}��ӈ�1m������/�+)s��<;6E��Y�uG�|�acIb*�{B�y�`�����C��o~��kn�$��ɍ8��8dw����Wt2����C�oɲ���&K� !WL��n�Pnc}��h F�T�1��D���^ �$ 1'�犭�(#��7���L�l�Z�J��Z 2�U��.�����/f�ZD��NOm7�!���#Y���4�mŵ Ĥ��-�"�b?�S�9P�,K�/��������a�̀[ �]��5�s� ?i5���Bm�6-�i���Y M�F��2ߢ?e��������eK���*51��&���N�?�����߽�㨧�y�ܬŤ~��H?�� �|��uN�F�o�t�J�@��P��JJ�<��ߐ�����S�At��<�<��+%u%i[����%W)�* S��Sy� �wp�/#_�壾{�`>×}"S�2I6������i��9����5��N�~�G��L�=��@�HH�����(�zx!8]t�,�I�"�N��U�pV6 +=6���R��� +���S��#r�F���X*�椌��6_�7.���X�y/ @�]���)+�,�F��E��!>�I�� (s�W�*�[�TJX�g�Z�f�� zn1g�QU�c���ѥ˔�Q\8ʈ�E������j�$��M{�Lm�zݘ�������se��8�Lq�2�d���[��z"ݮ&D�a����|��t!��f���%�%n-4o�L�<3��x��J���]���٣��yRq���@�N57LX�����#�Bʦ����ĥ�? ��x����b�&-��%�S���i'���ݔ�\��/��~��5�*�^�+O��ؤ�y��%/m�K�z��ܥ�{Q9b"� '�i)JLI�kb�R�^��ō<�}1!o�{�� +`M�AӒ�L�;!wr���X/�Pkz�=�j��G��NU�B�����NX俞�������օ��lo@�%���T���N\��\�����U^����8�/GK� +z@լ��BY_��>�׷�����^��Ƿ��e��Ef|��{Z|O��#�Q��2|'����L,z��_�_ +��|GY�t#�K� +QƌM��������@���QQ[:2V_uq�t_���wE��2WG ���Ē�,�pI��+;&D��f%��g:%,��+h�_&�/dF��0"]��ҋ�CoE#�������;�k<�L��ǰWҀ�}5����9�/2_�����_�{F*���giޢ�������j +�����u�{e��|�H��J}�}��V�+����x)V���/������qJ`2�'G���)2�i�ғH�S�ݲ:򺠀E��nN�}��ת�\�[�w|��?�y�P��ù�/�纫"�&����c:�w��DK���0 +��X]�F&�z�Զ�Q���85\8�Vt�"%z�9��©l@@�'H�SK]a�DU��Q�9�D��g�ъbm���͏���%��Η~�?���G�}ma�%1�a񈙃Yڝ��7�9X�vN��Th韺��6U���}�g/�^�:��b�� �o?�FUH��V�6Ljdz8��y���:,-�� �Y�[s� �]Tz�TD���][�Rm��Z�>�.gN �xe�H/�C| � ���oa��-��`�� �)�8��I��:M9��jת��̙��\V,2n�S�B�Rwmd�̉�q��˔���%C��{DD����P'��DD:ӫ�WOF����H�?ӌK�2.�Z���� ���ą�𜺵)���? ����i�/��q�:��/�uH(*�1���o�*J�$�Z��I��૗C p"?�5d�V�_k;�����0C��+� �_��C������?`��K!��J=�M����3�����Lij=e--�f$�Fk�;����]��qfF*�( �$�."�, G�0,z&��be��7zZ����k�����C���Yg�DCbA�y��\�i����&��b-�Zm��8[TD��K���-���֐���J��5��]�b% f����T���DCb�iGcj� ��56�i����k�3kHM��x���!�-liJ} ��S����B��l�l�Fq�U��4��pgoSigwv�z{���sJ�rJU�nJ�,/�h��F�ff�=������vq���ll4��;x>�����cx�����>m�+�r�d�_ʏ��-<O��5y}y���L��o%�$�f�d���I�7MZѤ�Sw��n�g�*�j;J�Cj�CxM�b�~��i&�&�(q���eJ�{y�r +��[�:K�E�8W6�;w�:�ti�߬�J�]?f�+eٍ����؞Z�b��.�y�~7�3�{��Eٝ��n +��yS�'Âi�" �C+K3����Nsx�SS����ԝ�e^��-��,Sm�ُ��6�F�+B�J�5i,}���!Wi(]������[$ �;��S�S����dc�S�E)��l[R�f�M��=�S��L����зHD��" +rD" +J,PPt���( y���.���s����>��V-�ǩ7���Z�8�֗v�0уW�k���*=Z7��iNne���o����u�I:aR��ҝ&�&�Us������7��9�mҞ����rl� Z�7MJ�:���M�/�&-��F��Y3��-׬�.�����u�~�Az� }�p+�{��4�_j��ܿ�vk˛��]��Lީ��k��M�9�ui��4Fe��:s| ����f����ꅺ��\�n�n` �+k�r,m?���zJ5����[�o�j�ch?��ވph��[���s��� "�_>����9V`.��&p��dE��S���n���E +�ZM�A;��;e�i%b�i�&�sV�Z��<��3��]�����ϑ���R2�i�1ܢ���� y�w{�"F�t�hH,�M�{7���#J缱��}�;�,ӹ���sgV��@Qi��FơK�#aN�1d��&1�&/�w�̻%���x��;fZ����O^�����~*�ˊ�{L��u��AL�'���w�N��չ�������t������o�hk?ںv ��7:�D�ԅ�Kq���ٺ��T��q1���U���?u��8v���cZ�����oEi��o�����ֿ������ �������r�<7zs����3��8�I �����;Z{t����-�c�ߢ�~v7�\]����!��R��5;�Lsv3�۫ü��9ew�]�����O+��˻�������{���G��3U��N]�~关*��ʱ���c�����u�!��#����I uo�t5���vtۜ�ͺcfIc��cek�S�YY��,˵�"2ga�-�������C�L܅� �4U�ggc�#-��Z�R5K����LC%TT�֎]Ѵ�ۺc4&a���!��VW��:!f��ƚ�Y�c������tN���9��TUD�����`i��<�Ђ�@�0(�BQ�,�������!y(, +�e�D�UDww��qH�8Ct ��d'���Vu��\y����d ���ٹHD�.3��a��* ��$ �t�{&Z�d^�j��FqӶ�U��t��u�<Ģ�H��xw�{=T5�Q��z�����c�i����{�V������U�UM�@�H$ �Db�@�~�cp>�N&D�`P���h��`0����3H�S���FE�^�����m���>�s"ŠS���:��ܭɑ��寸��ڭAWS�Cv�;�#�Xs�4&1��0����r��+A��no�~�1�;�5�?l���u8t���76U��auԊ3e�8(���(��EJͯ�8^zv��T� ^1:����"r_E�m� ڿ���/x2�ᧃ%3������R��^@aOB� >Y.�Q����v�݁��D@`��n!��F|GW����^!��˴���.�ݨ&�.�Q�%ь�H�q`�%H�b/aUʌO�9LAh�&4Jh}������ ���ǥi���3���7�m`pp�ùO���a3�3�����`OHN~����Ѳ�_�"�$���BN�0(�/�ܜ +�˃n����d]��.��<�Fv��~��9^<��w��<���:�+��c��������PO�!�D����f����o�گ�D�m�A@��Y%DU*�� ���Rr���F����c���n�tʆ�{ ��=��x|�7��|��F$��"��������+�<&`{������&�u�z�vm�L[�!2�.A��ƑvԺc�r[=3\~~�Y�2������p�G@�[l�H;�4��9A�S}�2i��n�Ee9p�b�cf�߉�S/T�&��h�M��"��O�D�53���'�/�i�W���Fg��� $�F�F��I����c�уH���:/Nw����R�~��~����h���^b�5&�rTʏ�YEIGЮSܢz�U�]k���C� ��Ac�VhZ,�n�?pH��s/�p��$�5h���K"Ϳ�cw�F�ɝl�����;BW/ +����@f�f�)��D��+�m��ܜ+PN]I��dS��WsV�P\��K~�y�~��-�76��",��6��]��;�?� �j8brP���)(KI+���9c��#�t��7X{�N�!���H��6@�8~9�P �V?t� �3RA��WѦ���g������"dA�`)M�]��~ܯ�ʷ�qw9�V^ �x��������n`�o� ��6�v�Z�� ���e�r�����3D\�����)��ͣ��W�ãm (u�w� 4^��&�������s[�X�V{6fQ�@:쒜���JZ�Y��X�����?f� �<��xa�< fJq�ȵ�@�n���j���2WEěm=�wE`��;�{� �a�/Z�TR�n��m�#0�{b��>��ɦY�F�\^�6����ٝ����y^!?h���*��J�e�@dU��ʈ"�́L�,4����[����;�J.�E.�q#zF�V]�/�2R��)z~ˍ�B&���J(���_��(uEŁ�����d��e@����r�M�d|����p�C���5 �'_O�h߸sw����K�yz�a�N-�w3ތ�9x�8�;(���#���w�( rn�N9<[�O�]�M��8�m�����i�0���-R����,ی��TT +��\ˣ2'�M'Dwy�"�Ƈ�%���X����cR���Z֙O9n��;�z z������xҘ�w�y��Wvx� �~�N��I�����g��R��$d_P�AGS6 1����-d� ^)��N �ﶺ7���1Y���r����6$�Q@ |Ů�\�I�7؎�N��������+co1Z���*4����qh�;a������'�=��E������^tp�Յ�#�M8}��9�f&E�-s��<��߰F��FNR_�F�Z}>ק�"��q<�G-�Ԩ���3n���(�}Q}�I�Y�1�?�k��o#���&�W-�k�-⼮���(=�ɉҳ��%S�����D��,��{�˞������B#�8���"K�\Sb�>{�������/Q��m�`o���U�^�_�DS�%��`�i_�0 �k��D_ ��N��Ε�d��_�F�_:�Q8��$Q���ƙ%�����~��)�6#�"`���E5#*��r.�4�X<��6��8�H��SMH� +����9�� g3�1��:w�C�s���K~���N�NA�l9�b(P!?���g�����c�b V~�P����X�˅Jb��h]�D�:Ԙ �j��kP�' +^���f����}O��-��I5ʌ�m"���R'��hyj�q�d#N�P��,<�S�{h�����>��{8`�Rnq��&��N�3�t�iXe���S �XU �M����# i��[?� -`g`��Y�7P��;Ҿb�� ?��i������|_�D��6m�n2��H�!�K��4�l���̘euPM_�4J���v$Y{�����㫤�f�o �u�VDߌ�����+����fdli)�7C� &[UNQ�"|�j �`B+�C�bT�u.7sU�Ev�!)�HB"�U�o�NK��Ma�Q�f~D����6����S0)�yp2����6���F�o4l���O����%�%#E��80y�p+4'�}r}��MiGNbEi蕇�������n��Y �����B@��wI9W�&֯��y�s�g��B�,#F�'g��j�2���3m�Wjӌ���Z,�X��g5�>���Y{�"(�����]o�֑&�!wK=f���I��%�V6}V�}�I�K�J�Д>fK�����fi��#Xh . ŷ�8vdrS]�**Ȳ >>+���y��;H�AI�����#���u�)n<�xw� ��' ��6��iIYа�0���_���ղ&>tcDKm^ҿf�� +pR�!�Z�����h����cW�{Kڏl��W��Z�U<`Ա#�ވ&]�4�7�7��#�)�a��?�KwS�����ǡ�k�5�ULȌ��NhV�Z@18d��Ge���� +7xP�@�S���W8teT�B�X|��ڴL��~) �\��vp�>Ԏ&�/z�Y}=k�=TPx$$��x�n�rX ��nkq<��w8����$IX��劈�� ^2(��`#�/k �����6)p�X38��X�E����/�D'&j8����:�C�k=P��i��GM�&��V��s[��2��J�t�&�y��g +��1�ѓ�"��{�R}A"C�����V�B��Vx�S)/܉)��*#��n9;5�t˺lNSS�db�cI�,�g��T�@?d�(uB�pO�����2EΤ��[�� � �ge� �Χi�L��ƃ��x.�M�0�hE付�[�1�2��A���/}�K)� �t���D5�Y�z�LYi�OՒS5//��Q�|J/ao8���^��(��E�׸#�'��]����r�},S��o�L���6���Ƶ1-��ux_:��b~9�g��hŐqO� ���32�FE�H�Z�� �s(�H,�|<�X�Y�o[��tG�̲AS����6���,���F/.{�Ü�乨)G,!SX� +�@2+�,MWk���%X&������'�F�� f�Әb�ɂZxd ���mˡ�>�gpI��;8\��g��)׆� ��<#������M`���2��H�p���A|��(PF(�M>U����&>�v�ø�JZ��{����*_��%%D��醨��ʃ�q��Ӟ!ī�m�25U�০��� �V�-����郻�.N�(����o@K@]� �[��m�lUCb�D� Z/FX5m�x����e)���uX�VS�$P�5�&1���� � � �śg��Z�u�B�A蟔=���N�:��I���Lˀ��0B�څa�A� �53e�5Au_]���l{˝f�Fv( ]��[+�la�Il�8�R��C�|>�R�D�� +�cv���׍ ͱ�P��㟑G����P��Mn���d�NB�uw�gj%��Xc�``~�I��Z�XU���^��� k���ǁڜ�A$L f�0-���ۋ�7W�OcZ$lV������Dȶ p�)x�vgs�vW�J*#�˜Ȅ��3NmJ� �r�rm� +&���1��q��!�����K�A��V�x�0�G��\���v��C��q��_�i-1f��7Fi͢PǢ��&�W�Ek�!> ?ܪ9�!YՒehQ[ ����nl��Ve����\f\��Z�cg��:�HH��W]�r`��q��@}(�D��764���0��3����`+j��h-�'��=�(gRa�,�}9����q)�u@����ã�����rK��`%�HBLeτ h�֖?���7/�rD[���̌�m�  �y�4 +'W�R`��сTs�\�tv�s��Ɛ+n�����м�)�}2�q��΁b����8B�M�t,�M�%}�#�G�+J pNr�ή��eaj�iR|�?+'u�zB���׵M=H�`��F~��"<}}Gbɂ1&kg7*B����l�3�q]v�)�to]�3���T�)o�[>��Y������YMO��cA�u\� z�>��޶�U%�Tl��J!�q�x��^r�@ �p_o���g�8�(+�D��1���� ������xMi{��p�.�r\ߊ�-c�����0]��B"��V�+��>8��Eޒ�����Qq�(���,/� Ê����2� D{:���.�fW�B6���!ɚ�zP �+c!oʴx ��V+9`�!AH����hd���[@��ؿ>��E��Q]�?�4l�� ��������� ̔���N/�L!�>�_o��m�+46��4��3����� �j�4���V2KN�Q��iI=mb�uBk9��]N^]� �N�1�I@����,�G�%�q�V�r<�����1ah�2p��18� %�`"3�_\�����)�J2��7�h +5����"�B:��%��q?��&���d�����vN8! �{��l�Է:k��X@M�ޅ*H��������7�D� ry��aH,�\��] +�< ˞xA����@<������G����;\m<`�~h�0�?�y+4`{ C� ҙ�G ���6A���4���*=���n�h�s���9���R��F�^L�� ��,�ǂ��������V��dD&)�ҽPϔ;s�iu���_ s��� �DE�qe%S�d� F;^�/�ڷt4k� ��m|�� +�[� +�����\dX$������W�������@2���LgP��h�B������Rln��K��t�*%.��'�9~8�������:b��?I��|�0�;�ߗ���I�~!b�U�dN�H�M���F�X� ;L��ɝ%�}�����PN��,lg� '�X��$�+��U�'VE ��m!��QI���X+�5�З���Q#~���ʤ���+�����ZB�t��8+��ǟ���_��.�srI�W�L�?�R]z��hTH���j�d���>�IN[4H�X•�بSܜ�\U3����Fd-R�H7� ��c���Ù6��d��D�Z��$��W�I��-��� &�d��'��VD��5PEN�n=>w�iz���Iq�qL5�X����d����vl�&���HD���W7�����f5�<,��p��a���G����)J��Lr�F>���W[�}��UP� "]!��E�f�����+N��dG�6s�Z�ȋ�>�����Ri�D�e4���i��שh.2#��9�9Z��w�e^�z�8����Ny�!�u��٣5Y6�R�+J���M׀/w`��[� ���Ƃ�ݻ�c_[�!eٱ��Jf����"���ẃ��%���{h �Uo�v ��q%tb�4�� �l�%0�&��@��F-�����g-� Shͯ��(xֳ�����?+�G,| +,v�u�[�oWG���Vo��_VC@!K�k� ��'e�-�� �P5������Z[�;�*a�*�蜩��7���0��?�b������~U�ΨL��� ���7��%0�+3gߟY�52К�EU|-�Eun��ic�3�o��Ѣ������Ҋ*5�jCd�u�.�d�)���e�1X��ʦլ��/gـ� �9'�-�l[?��ip���h�`��>1���K�H�gu�_� �����Ԭ���Z�a�!��֡Ļ��rfY�T9j�Q{oU�� ���Ǧ�zU�pDX�҇dŦXب]�r��|ܾ��N�9��%��.Pږ�n�\�.~1$���ɻX��&��}�D��9��~e������`�L��f+H�@���-Ճm��=[z���@� V�;��E�vSg:���Z�2����2���'��$�OJӋ���(/��i_T�t��LD;�F�~����%F�����P,3 �eP�e <���(�.�e4cQ���oȈe��zƞ�dM�7�mm}�C������+�.4s�>�sW +1w�~����������0?i^cK����|��ޡ2�|��;�Ӊ�g7S�g�ѩ�E'����Y�J#��+§9�H�Zgġ�;v�_��t�r8��� g���~.S��h_���O>-"���%�ՍW�x��@zFiɆ��X�{L(��D�[r���N�q�Zm:��?����wFM�M�x���%�A a+C9@� +K���3�J>��@`p�;ϛ^��aOX%ƞ&A�X�SA�8 8L��G��d�n�����P��E]:��D�F��y��U�i�2]���! �^�)�&'��a��f��:�z���-_�]I��-/��*��!0?�7�,4�M��}">�x�UAگ/t�%*c4�1I�3~�Q��Yb�}����@j4��J$�� ��]Ő�بI�_�y���� +!l�� �^�ݒ���s�h� s\�p2���ũ=v<�ϡJ] ��YO���3�!9�@{� 2 9��7�=�Iv �?.�M�� H�=|ϓ�{�X�;�p�2�Qx����CP\ۺ7�$�X�ܳPl ���t����+1�ݭ=,7���`��R�#��09��sa�j��8�������tQ��E)��4`���` �?c�PY�Sk�o@���I�g��BJ�������?��Wq;�6�]%^҅�Yp��T#8;0� +�y4�K���l�^���6�w���sw[��_�$:k,d'��.Տ�$�;-naO�tg���5�]�ؒM��)xT37Ec\�# VeW7��:X�@�a�^�b8�v4LFs��Z��V�e�o*$��Š�s*�?=|��f�Q���Y�X��3�K7 ��2��V��jv^��jCN��}I2s>�/��A ���w�S �A�i<'!X���6M�(̥�M��n[���4W*�E�w �!�w��8��B��s�ޫـ4�C�{ +�P#�3?*��'<���)����Vfò[p�Q���w��W�ق��4�l���Z�f���AĤg|��$kf8���ٲ���k�����%��7���f׊\���Z���|9f��r��juM�8��(]\���K��!��"oovp�m��6 K`:%����çO��r�+b��Cb�wl�)�]F���S���T��$�l�e��4�2��iʚ�Z�! y|��Q# +@3]>5[K��)������O�L�1�U�.�{�(��)$ѵb��e�q�m�L|Y� �#�}���T�x���O�^#R�%�گ�Dc��ܲ��&�\S�1�U-��|�i��|W+�8�"�8�k�\jr�Q�c3�k�Ϝ� Qc�'�|��6�j���]�1���y�6���9Lfy�������^�*�`�]�)�ҍ�p�$��"��F�v�7V�&�9��1��SD�Tb.A�Z(A ������ìE��[b9�HN����������d24RC(���)�Pf�O�RC�#P +�ߍ��� �S&��*��$��^�nGq`�O-��8�ݍ�w�و׃��0g���l��6�_}0����ke�/†�c��%R�*��`�y�&<�`K�e�!6 ��~X�F2���]&�:�OAE���˙��ۇ��Ӡf��L`t��������k��63aE��ƪ�؛�#lH\(G��U�[+A��r�n(�, ͸ �]�� �v}�RC��?�] D�ô�.�jee*��u����G"��T� ����W���gF�Q�Ě�A�r���*�a�K���}���h#����W�t���R��`��c�in4P\���ʴ��Ve-f�����C ��N��C�x�А�����+L-�&aV1=���=�#�P�Ct���)�yC 4�9�i���^n��p�яA��H�����k��z�̝�G�-!ņ�Jܔ���ޯL���ɑZ��q��`?���g����q���kS�zU�@�4J����n�l��&)�+�����u��H�j��7zc���k�r2�V�� ����x⏺;[$ ���eTA"�O�P��;KB��aQ�GQT�M���x{3 W�f��l7�w��'��I�Y�9`oK��� ���Z�Ӎː +(���P�����g9G hom=�W��1���6�L��X����6l�EܪLgG�Uq��c;[ Lx�.*� �E +��q�la +������[6$? [� �~��٬��*��s��H<�k�E� ��d�GA��PXW&4��E�›��FB����?{b��;{�� ۄ �3�B^�׷�F� ��'h��Vm�Ph9���lsA����E 6#�?���sS��nV���]� r������t���,$8N�?����k�V��æ3�� +F�;�Fpy��0� �3_�Ə�QK�05�1�R;�b��1����k�@C��F�+�F�@H}�L��� �!W�yW1�U�`�=|��Ѿ�+�^Mc�$��� +3gX� �L5��!ӃLH�ݯ����>��--"@�U^�*�1�v\g�Q������<� +ojU���|�d[G�p���L�z�����w4H�����inX���`�8P` +Jޭ�6u +�[(,t� /p0;X��J�O~�a�L������sK�7Ł:/ ���4�>��@Ӕ�Wξ� �[����?�����K@�Z{���mgY�!e��9�-�S�t{��ydk����Y"M�����lv)lT��R/0����7-�qǰ�:� D����U��?�4���F���,ykv0��iY�9) F�3m�5�9p��7�$[����T �9>�!ѫ��aCsl{wm�&=*�?�U ~�n|fBP +e���ˌHK�0d�v��{��#9ƌ�̆�Q�D�pWreG���f� �?�(�\�� +�mQ���/c!� ��5��OJ����F_���?/Β��78+}�|E����k��[���V 7�������S G��=���>G(��8��T���m����%Tl� ,� ��͗��[���TP�g-�q������3��jF%*�� +��6|p,X�<��A�)����Y��bs !�)$�_k��Fh���K���%0'^Y �KU��#�J(;�4Qm�"�Cm��}EGD���T[�v ����eYbփ�.��R AoE.�!Y���U 8h� 3�7^�م���dD���i'�' �s�5\]Z�� +�g^�Cx逰��QOd}�L +h���[}0`�����ƫqp*���_ݏ��|_��%��-��h�O�G��5d�"�2�0��\O�i�.��'�벂r �lH bm� +���0�D�_D�|K��٦g�])�Xr��T|uG�B�*�E� /�z���rFf8��[Dz_�Ρ��4l�y�2(�z� !&mN��P��Y����O�#˥4T���__~uWѲ���>�H08�M��j����c�$+TBLȭ��߈y +���tj�E\�3� '��s䕉Y�������Rj�FY� +i�&61�D�Z]l..��Չ�:lqF'�O��� +�I�&0�v���a����q9��e�TT0�==�"���bd6���S\��J�6�U��& ��ڣP�.�[]�A!پ3�Ӥ��@��F�#����Ѣ��M��hm,�Cj�������"�����x �#�n�(m�F��z A�/�ݥ�۵m� T5d:�8�Q�t�_�q�7L�XK׋ z��5��RS4�va @^��V�ڽ�i.���ky�x +���%�@Rh.1ѯ��?���F�1�SHŠ�l��d�o4��p&HA]K�71޹�L]#��0+�G�������s7�����'v�X)�f(��p�� `�X)����y<�n�0��y�w�ƍ+�{�,_~.�x�ocy�����d~�`��;�m� �TS�]�8�Pg���WP"���,��<�}�A��-��F 9@'^�?6l���d��P%�>7�L^e�%1���-��h�(�l4>ͨ|������zn�Gj�aTd�T�~K��_1Lh0Ƅn_���Z����wt�%�]�X⓽B�Y��Qm�a�q��d}rl/����=w�zl�鿽�g��C�g�ќ��{��RV�e� ��O�& +���J�OW�yW �Mn8�;*�� ��װ�����+[#-���VQ9.��$��,_jA��ҙ]+��0�/� az���#Z�dS�<?�M�4l�: Q2`i/�`)�����:�w������O�G�2�j��,P-7V!��q�X_�Y6��"[8`��a9�v� �EU� �#L�4�_+���(��҃=��W=t.mL�2?x�d�H��/����6_�8��h�"xn�6�B�z�4�����iL��UQ�k�+9�CY��`'��+�D�*j�Q�]"�4�����JS9��.�N���ƌ��:쁪������;�{�v��/L�d{ ���BR@1��cOC��&������of �� 4n���s�B�t���"/`�{[vsJr|\ON���Q?]py$��A=����z��|�=͔��C�*"�F +[jPJ�L�:�^�x����bW�ץ,�W* n�+����Z���Y�&�…�D�26�= �o6�n���칾#K''ʰ˘�NƐ_���:�۬�.^R�t�E0��/ �1��������Ӌ��z��+4�#�8�~oq�EaX����p+�s"�B+�.hs"z�B',���� �&��D���JD[&��,\������r ��tU�@-0����ٻ��D�� |@y�w/���Jd�����A��pN�j����� �Hd����#�@ g�H�b8��X'�}�@v��C-�3h���Vf� ��ޜA��g�܅.D����\��_D ���"�� ` t|��w�p�� ��Ȱ�@U��y� "˹�. ���!�� |J���!& �vb/�!x���㐺�А���,�r`�q�b��C��|N�`�S:7D�u�_C��X=^��i� v�� ��ߡ+C�sxȍ!VB�0ĺ�!��{!Mt8n�B�>-d�z �B&�{` J���q�+ +�'}`yB��r&d,�����ԗ�0��A�@�4BP��a'^��=���>A��T]�l.X�����!�?�� �F�n� �"\�� +�� +b*|��)Ȕ�8p�L�$7��.;��Ew��C\<�aA�;E�; ,B�"`7�1ˆ_Ҷ��\ �Ԉ#ү�Oi�7 ���݀��G. ̀���cm'|�B�)����HP+�?�( 1�J��Ê*����1������I4�?�J���SJ���E���0� t?d\ F��)���-�P? �����`"��`'&��!��6~�� ����f…M�Z��}��5�l-ńNP��֧8q�G6e��ɉgB9_:�c͔���Z���4��ĵ�1~�|�g��G�c>?��x +� �;�{�L�]��E���Q�r�����p���=�cP�5)���R\|2�z�@�MaM=�Z��>~�~�cܡ�*z�>*�E��M��Sq��QUp���}��9��A"�/�X�}y\�K��ϊ���V�C8p���j�Bc<@�GH� +~�W����WP��=�!ga��w( |�����wH$ �j�r$��"[w�nUs������4ix;��m�þ[�R��$[�g��.�nAB;��c��`9vl: ;̈\���ա���CO�7�.�x�h}��X��j��a�EAuX���ў.� ?�j�� ����,��k�c�}Akt��D�����1���p6`|{������L�Dl���V�0�6�#Is&1R2����_�a[sd�V+��1:S#�1�N{�1%��#2�l�I$,��&��q�)��ѓ��=��Z�㋣p_�i��hqk⨞��8���ڏ� �3��7�� C81�x ��3D�A�8p��P�h�@p�!���F��8h��M�|�绽q:{���7j� �۰���!��FX ���c7�a +uCO�A��P�������Fk�0�# 7�R�6v�l� Ah�xnC�� 6��A�j�.��QUG�i���m�m ĸ!C?_bD�<7F4:�Df���ٰ�7�� #�b��o�l8X���k����Ѧy˿(Vj��a$��װ���@���f�b�)¯���5���546ϐ�׶ɩ<r@�B���È�;���Q�q��8���c�O���������A�Xc��հ�⸭�+������0��V%�ԍ�EUc�#T������[��0�Ԡ^����!,�prq�|X�8��9 1UMCf��4�8��F�Ҥa�����02q|9�I?���D��И��� $4�l��gñ�sz�|�>���'֍q/&�sgĪ㈂~�tD���՘�Y��ͨ��� y��f��Y< 8Q7�F�8�^��< � �#�`�S��zK5׸����2^��2�� ��)��;�a�PL� (ULY��0J����A��02�F2���1� �Qc�+:g ����8�1lIЌ! \ �w/�G*0��S�,�C*�����]b�4^�Or��|B ��ƺ!��N⸖�1��`/q~�l8X��I�І��/�$䋡$q,,o"�H�8��^H�J/�]�Ś5y�����]D����sM��O�p� �*��- �q��.�|U��o9���J�Ѓ�8���7�/ܩQ@R7ɑ.�xv�-�Y"s𫻘��ǥA6?�6�;Gt삅h]��渢.�\������s��csa]s�h��6��D��7.,���i�ͅ b����%�o1e9x o���r����m��r|�a9j�t��\� ����O�Ec�q#��"S��B��A,-� +|�?�:ٮ_:= Ϡ��_΂���,첎����'v�+ �fu��c���,8���b��8d,6�;��u���������#�XX ����}�Ǚ�p�x|� +��� ��<~`�+�("=W<z�qK�[���д8]�Z�e;Z���5+p�:���ËX�n|�� +�-�]�w����� +����OU$s�R�D��U�_~8K��R�u?�&ۚ?�����E�ϡ���(��,r�K~����X� +g���X4�Q �3E#˘��z),�@�/����R��i�歂)��.HoRؘA�o� ?��`<�O������!/w}����2���OH�0B�$* +���x�.�\�� A7�B�r(���( E���,d}P� +�0y��~� +���O�a!����>Bז�!7�Dir_OT��Q��'<�މ-����iV'�� �ɐ7sbi��`L�{��PiH&��Qа�2�5a~j�r4A���>�sC�6� u�R�L�pC�� ,�1��6�䈉Icp���/�7d�K g�K��!�%�.�A�(��������U�V�lp� �J�'�8��U�!uR�eQb�~P��= z&'�Ϧ&oB�I(�!��V�$A�_( �� I��'#�~$ઇ�m$���0�"!�&�H�<�z�!a�� �#D��s#"�*ֈt���'9BL,��e�ۚ�ю0�Tt��D�ң���n#P��5b`V��`-�H#��͈��;e�d�&�z���0��El�c�մmbF,�T���*�N5�D�<Ex��W���1p���s���<‡D,��4"��:"b�~�F�X逄�X�P�C($�;��!!�%��HIjCX��d4�!I�y��I�b)%�k!*[�� +юL�m��ޤ�6I� a�N�%!��$9�(B�JhwJH ��ɔ:�D��UIpA��J�)���B �cI��Y�r ��- K�>�����%�Bd^B1@�� ��K���������b�m�r̬��&o�l�Cr���u���S�2��j&%�C.�T�C5O���I�����x�8ɚ���q{[�p-{�LحB߄�z`NȣV���x9�i��蕇�Ľɡ�CGm��xp��\'���v��dz�����xـ',�wX,��I!�a���|8�c�*_�}W]��|��0 S�d��p +&<�]$�_d�N3��n�z�"Ӂ厏ip5�u��c���a���U�;!�,�i'x��k'�� k@&X�N����ϨC����;RaUj�i'T�0�a5;!ķ���:f��0W�,� +�+�.k��[v�C�^� } ������E��&mR�&��E�Z�? +ע��Z>t_k����kk�n��~�{�{�[W[s�آ��Z>����"�rk�ݺZl���3߶{�����w��o念Տ���ۂ3Qs��=W��]��r�/�߀���ۻu���͝W�Y�����ϳ�ڃ��s���^�}�s��u�X`�j>���=ܪ�W�텭>QѾ�I�2T$�]�:�"�������l��s�3�lܜ�G�P���m@2 4e���S��@9��*�V'���B%$�yOA�X��Uh~��;M�*4CT��6@+T� + ��&�h�TJ0��hE$��4�J�u~��vT��U��6��2_��x�#�$��i%�uTRa�fc�z'I/�PI ��-A�'�D�e�J�[�-�\k]����)u�;�u�Z훡.�i1�5(�f� y{�|3���^�ϻ��w��;����e�����5W�����������X��>���=w��Wu�[��u�c{��M7�xw7��3��{�_�o��������ZO�gj+�����u�:{7�M���ί��9ƻo�y������W[�ݿx�M������zqݯ-�[o{�ڿk���v��V{��,����W����p�k�;���l���֫����ռ޿�Yi���)�=g]�����֞מm�]�=�^�_Os���o���;�\�{�v�V�7��u��.��1ݷ������[�߽�j��w�]��\�o��_[�n�ϯ����m�춝���狽�i+�َ~͗��u���;�u����v[��u�h��}n��:׷��V���b��[��R\��6F��_;����X����M���_~��͖[���w�U�wG�~n���o��^�/�Z[��[}����;Z�w;*�!�Uku�l��^�{S��ZgVSY�??�8�����u�k�q}~y��Іw-�m��wT��=�ڦ�[owTgm�����R��w�cms���=k;�����.w�������;˵X����֭;jsG9�(���۝�m������[mC��:k� ��}�m���s�u-�m����h͔h��l"e�}M�m���wOLq�*�i1��X !k��;-����ގr�{�؆���m�;k�wZ�z���^��O3��Y/f� endstream endobj 19 0 obj <>stream +5����Z���:�k;��r�r��\=���6{k�5��n�w����6���g��\�����������t�޽����N��k���v��-�^�z?ל���_���﮻vg|/���4�֎J�o:z7�{���:�������:������c�qzqG3��z�^m��p��{{�n�QNq�:�W{��V�{�˞��Z�_�=l�_[��_�j+���t�l=l;O��m�V�k��]��w��Ͽ���Z�h�e=Ч5��ʤb��:}@� d�� +,��B��4tw�����̳�;zo�xk]�g�i�����F��Vu?�y�u��VUk�ת�@�~ZEj���b��{����8�]s�5��������N�Н=�������g��=�}C�w������r]��B1O��3�fT���Y���+,?.Y����4L�Ӫ)Nq�p�E�'���x_���uGk��߉�~��P�5L4��V�U�f��{g�Q{�6��Q\k�\w�ڎ���ЧOk�h���4ZhCw�����������޻ש�Y{�֯�-�����ơ��m����چ�N�i�!ʭW�~��Rk��Q{�N q��\k���\�j#Z����i"�8�b�d�T}P�˴L�W�N(�����E�J6�<ʖ��[dl�����26nF���S��|�Ɣ��Q� 8��G����Dk����.�8:��t8۳���w�R� ��^�ݳ�2�(�GFF�Q�@B�cEı�͑�pڭ�3�iq$T�Ȉ��N4gb�2����L�z���Ө'U�E��a�hm<��O������m���Xt���x:�m�fv%����� 7�m۶��m�����۶��m�2��i'�d۶��g ۶5"۶���gV����S��� ���@ ۶I0(Py۶�����ol���m�&�(v�m�V +l�� ���n۶e�:��9��ٶd�m ɶedd�����A�xƄ���`DpPB���:�۶PpCQˤ����)�� +�^.[J�@�ζm�6@Aָ9��@S&�T(�E���ˁ�d&]�D�PYHG�Z9�_(fhx�2���D�"��7�((C�2_-(C~�_��Zp��s6�/��<==46 Y�H"b~d���W�Sx"��� �ߥ��K�IK��!}&��DXfȱܐ#���o�}!J�;�w~��c�>,U��{w��Z;`���p���2-h:A�w~P$c�I��l�a�QM.Y�2Cه*��'����j��if@Q�U�Uȉ��S�]�ec�K*�P\�#�� ���C��;q@&��o5�V#��D�T�%`D�3|9Prf��A1T��G�ƍ�Cb��X�K26n@1l�%� +_��1�)�DCY����S@�l�!�yƼ��J����q=�q��D�\٘�#�M���s�Dq�e`ի�H��*)N�Ώӱe8{�aHC��b��W��=Wn5����V�Z4�V���9���j��ݹ���7��m�W͇�j;�:�����k~\�kܿ��޵o�{�{�9lyƻo\s��f}w������x����6{�W���]7���/��b^��x�ן������~͕�_�7�����yo���m�н;Ϻ���Z����V���zg�-�٢���k����޻�6w�gΟk�u��j�5��lQm-�mm�����jk��[D�s�o�����u��lQ��Z h���Ζ�����?�[�^5�>���q!)Ŋ�Q��4w_�\[m+ֺn|��=ם9�{cˇ�n��u[k��Z�������^��^Ͼ�׾mŹ�ʯ�~�ߪ���ڬ�����+��b��^˵�Xg�7�_��׭�����Ӽ�]o���νr������}��^�W�^��׬;�W{u���}��[k����|���z����v�-��k�1���|��wW��EC{�|h���W+��V}oռ��y�vכ�J���ߢ����Co~���s����3�8g�y�۹�^�9�~/�]kn5���Ź��ջ9����W���b˹�oo��o���[������{�o�kݿ�ͷ��־ζc�R����ݢ���<��������o̹�\�|�����~����������g�{�����[44cˇ���z{�sk3��k}�-�>�9c�;�8g�k}��V���Z�c��狷�cˇ���q����j+�|�}3�ݫϻ���EC���⪵�^��㭭�h��篫��3��w�;�EC��V{�ڷ�޷vg�U��~������uߘW�����V��_��s�h(��b��mq޽b\��V��y�j��>�X�z9������z��߯��9��^^���b��h��|���n{�>��{���1��r����m�~��[[4���|hş���ݷ����+��֌�[��m����6_�mޜ�w��|���cmm�s��v�ֿ���=[4T�ϼZ�vok�_Ӎ����ע�?�6_��[/��w�R\k�^�ok�^�}|�����vWk���׭���~�[_H�xR��lP�t ���s�d��@*��I:���@=�$���Q�G]�0�gR����E��I�� �N�KcK]�����#�'�l���b�� <��������m ��� r���D��<��Q�O ���,K6nJ?�+?��U�O�*�m딁"�P4g�JJy8$ ��)[X9p���Xz|�'Tcc�Z��Q$`�x8�P�qU�P���#;��e"� �i�z����=I�( +�����䀑/��6�HZ�L$�1��4��^��EHOo��ZY�&ґAz"K�؊X�dp��1�g"g��ya�v�����G�Y@.�"F�0]�V$}�i�Ђ,� h` $8,K��B�*�W��ᾶф�R������A ֚=8�Z+ ��*lDPa!��&`P"���ʺb`]�4�)�+�7M+NC� |URMJ �B�H�R����}&�A��:�|*���:%*,`/�X�@XbAy=�R�#�T���K5LvXъ�F��-�F�����BE�P@�i�b���������Ll+�d⢪`IT�����l�DH҉E��L�E� +�-�[ +Ub XP V"�� B=\P �ΥU:48���re8 �4��Σ�]^"�[*2 +Vp*L  +�j5iP�N�i! � 0'~�L4�)U�T�����+����X S�Ҿ��R%U"%���8���Cc�p�Y�bc8+�i6���˒ ��h`֐�Z�xDވ3a[Xpd����,�\���h.D6l8�6��"Bm/]H�^�Ib +q�Ʀ,!��GBg(�R�XR@UDXL�QD��^ ��)��/ v&I(?��YI4�.$� �p��I ��y= �Ja��+B)�`|D+���i� �S����H��y�T����J�Ǽp�x b����&:� �t�ˣ�1�,HN��@��Q�\mk� �A}T�M�y�6���l�d��peU��GS�)+������fѼ>�E���rȸ X�L(�� ��bː��`�"��ݙ��gT9�Yb8�C +�# E�p��6]�y�!y�-�H]@�� ��c�� +���� �EA���^߂Eš\,Lg��haH&�gZ�H���� ;"���~g$��U��� +L�S��lp�(*p�r + �A��4�����"� p�S����a�$��$�v{0r���/\"8�l8��`�F��gT,sOƒ�a��� +��i��o�'&��1�U:e*�����h����L�� `���VY2�4dn����v����(��&6 @| +Lp������)���9����B�@�S��&�������đ\�#�a� dُ7y�+vƋ�@,�fB ΢H�7�z�dž��/�����?��" �a�HV�ePU� U�3�z���0Gí�H��*H� hU*"*�$U*f�Jv�xEU*u��Ш���ChP�<��s���("g`�Ҫ���P@I��R(\ E"5� �6��Dդ!&��`��a�� �BOoB��p��E �aN�P����Mƒ��H���xfFr ���D�T$+P�44gDW� � ��ꛘ���U5��`�Q2��d��s�� -<<8N�� +6�����̴� �D- �'@BU&>����( d�H��\�s6�p�HMt�XÖ. +���SApm -̨!����Uy>��pg��Sѡp&:ߩ�����A�NE ��h�Qf�\(�Q4`dž�c>HZ��pHKf��i)KE� i�ʁ ���Dk#d���51C�Ac+��T�C�4 �ç_8�T��1U��L}.^��%s�,��,�A�� ��L=t� � � � NØpxp��s�����J�[� �L9&&:iU H�r�EB|P����0Q#�a#e&�L~��LNQ#�s�4��3/� +^��k�+��H��cz8-u�X � �@������#}. +�k�l  +\�jW�$h�@0 +��Ҁ� +de�� ��Ak�s�$\�.p�,�bL p"7!6��]�� �ON�y���*���υz�� +sA��I�H0L +N�@H��O�HN�Ia7V-9��s� |�Ɨz�XN���*5�>�>0��i�Fs"�6�9c=hl烰*�k�گ(�bQ�����c��p2��*,{r��B3��c������Y��.�J� �O��j|?@�+3�򘣆b)��wz�ƒ�%�  .D�[��_� �t���s�C�s��ÇP�a!b ����*hf�SY 2K�:�B�<<(~���9j&�����i�)�+���`&X'(���#}.*KX��s )\�çЉ`(���7 ��7 ���L�(�:� D�{�ħ_8m���� +�_��8�L^S��!/��q9�jJ߁�|�&�B�����N�l� �c��s�P$93y�4�6�kb�ͤ� t.ׄ+T1�y�� CX*��2�Z7wVV,�Y9@�@���4t:(�^��O�8�`�h�O#a�Y��C{���i,J`"�W6+�-�x�NY�[ƒU'������VW��n�ӆB �[:%���c���2���#�á͊�$���2p0�c��I2P�T �i;b��phO���B!��H�����cC�HÀ�F�#\��y5D�WNCq���;#1{ *(F�`U�Fd�އ��&1�J]H�%�YP�Uf�,�,�I� +9�>��ǧZ@�L�D���p�TN[����fy�I��,���+s�^P$6����r��AYFTT�����H����la1�Re��L�?�υ2��L6�K�,��j$�pp�7M�R�8Q�̂��4ăU�O�P*��f��4.ˁPl�B�d&2��H6[. `3`M��.^[�x��0�n�*+�]0>�-!A�>Ӗ���Oéb��&>�M Q�rI� x$�l��6$ �K��ipV0���`4BF!"�Pyhl:�8��0�,4� ���}��܀)�ʪ�a��� "�#4�\s/# �4'_("Ӛ�A��r�hű�͓X�O���|�D ,���o`�T�6޲�l݊�u0>�-�[�B�.)[ R鶊J6L���qZEO�.����K��@�糔�Ooq4� s .��"J���t"J�VT( e[� E;��P�@�.K�ѱ%��QW�8��j����"C� �� �Y�5z���u�+� H�b��<�x��U ��������#�^�Lrl�dU�L~\P_���+�,W^)��fu:!��ʛy,&�0l��p*J�$���� � .&(uǏ��0%f-h�����D�^$>��#��i�`��K�@�|�����H�� ։� <#[|?��~�@��Y�V���g�S��g��,č�(j��,/A4���sc�3 i(�h������ރ�*�6?bq�s+D�B7���}U���������ݦg�U�Ji��A��a� '����:��݋.����� +���cL��B k/H�M�iW��qz� �˓�:��|��>S�w�Q�Q��*� �A�\^�#��}c=d�������J@b�54��m �W��u��yƕ�d�&i���� � �`D��|+���M 3>�Je���O����o{���Yl&��'�]lL@�N����E�rP�'~�_ɒ��b2��t�sJ=�F-��8���V�ڝ�ꗩ&�+Cឞ�nh�+�cN6SG�޿]�Vh���d.�����v5����T�� ���J���“Tt��e���n�o��j���l����[���Pxt�I[������3=����Eh�i�/�`j����Is �YT�,���WHk�/�'F��j����B>=�3���2e�lb��A7G�Ѕ9nH���;�W��ގ�;z#9�{7���X� +(��@�k��'sXI��~��sj�j�4�1�+0-�Fk{��z|����LC��UE3.�#���p�� �ez1���f���I&�Ζѱ�ޔ��ǁs�Mpr��;�.�_^�S�U�~�-=��O)pF\xY�}�3b>���{�pPt��{�7|�!G�Dl�׺���l������#���O���Wo#�&($������/���s�V�s�-Y9��m��rD|4q2��,�� ��d˻�[�f�z��b��/�����!WEz�"Q��]F%�f�$� �����Tp���™ �-Htg���P�ъX ��u����@X#��9���C/���zV�g�V�How,�1��p= }����JO��̒�{����Uͭ�S�!|5�f9<�ܡ� +*��,� ��9 �Z�Q�����OS>��ǹz(�Sx%�X�hX���8:�_��F������ C�~=a���镂��*r��Q ��ł����TдB����Ѹ����v/ ̩��G[���I�-Bi�K��eT89 +Od��d)(�5�9���$�Ru��Z�UP�]����ihQG��&A�� �xߜ+�� i�׈���t���>d�:Y^��Z� ����F��Y��#�o�~we��3���rZ�����j�6����E<��f�~�t���[&p� ɂ%x�� +�S��>�_�4�����EBm X�Dn��s�&�*ԺH�|1i�F� [��}�FM������\�6��h�HQ�y�Y( ��?��H��r�o�� +E��]�i��P���Y�H�s�p���f!KV�ū<���7�5Ʉ嶜�O �a^�K"k�P�y��F��=7 [��ʒS��0`@a�"��f�1��[��I�hj�=�f��S ?=�vs٭\?.O�%��LS����m��T�~�Fχ��B����-Dx�I@��&X4«�A#���� ������%Ԗ6��`rlV@�m��%��bddi.~�5]���P�e���/n T�Vr2�ҌHY�^�%�׀]�\Sz~+�K|ag� 3��J�EzQ����%lV���@�#�t�~�H�#�fv���ׂ��[y}�h6N����J6i,�������(�ve�ROƧf�!�1(3����ܢe`/�eЅ���NQ7G�!��>�b�wsPv�ؚ�/+P'��@�%���a�>a�s�l�����uO��W3��l�'ʏx�}uj�3+�G��S ���j4E�Q1�p��Av�>���c�=�E�T����K���>`^�hñv:��UB2R�(���x�K@��`Du���@��ٮ ���^��� +�x�wj�.�&�w��T;�'ߡ:L�â��W4����m�Is>e�KTJEy� G�<� ՛�s3�_��8���X��<��6��f�4�z�^���;G13 ���f�)~L ����Oc�~h拡۷i�Y���Z{�F~��޳AC�]P��� +ޓd ��Z���Z�ه3e �������<��@i��q���X� �d�A��<�-VY�� р�$'}K�t<��]�3-��~�mʒ}��T���&[����F�p��!w_G�$O�ܶ�p��3x�}��7m��ɽ� +*V�N� I;�68\Pܫkë��R�n�A�W�38+�1s�y\���{_������-�UY�V�+�=p|���G d�d��NgK/�8���>�x _\��6]�d�p��A��������������:=���e���=��I*p����)Ϫx�1��I�|i6�\|�Lq �i̔�8ِ�� $�� � ���Z���C���  +�:���8�$�������IDo�����-ȃ���[�4 =�,\1&�` ������_a�A�e�� qj�9ǧ�c�'B�x\&��%��}�;u)v��ns]rÑB���a� '"5���1�mp6;G7� &�eO<���S��Q��x�)�m'Y���G��\�Va~��� �,��C��*� 9��!`�N v��T����,��Ӏ%T�WW�*yJ|���WS:��:�����$���v��`�= +�������GK�ޖ��F��%�ly��x�|�"�?x��i_I3R��Cۃ��kB�i���P�N�P|�~����Lo₤&�U�:.�Zc7��$�r�P/X/�=��`͙K��7�g�0����;// [�+�x����u/�\������xx�!�#/�+���7�!0R���Q���'�: ���0�iN'����(�)�s��&�� ��"{�!;�&� ��rb�|P��l�����)����G��_"�9�'s"Z��$����'��8?�0�-S_u���D����� �5�PeDh��>`N���H˶�"�]��a*FG�I�wh������#�@�Մ���Flf5(|(vlM�`�W�5�&%ٞ���7�n�0B�� ��B�xp#��aN�� @��$_�m�:�q���' +蓔�7f�Yۘ�M2�N��������;�²���Ղ�Nl��c��� ���E`�;\������� z�MfX��9��^?��<����V�$AxZ����7�K�«���4������\�HQӢ�w�N2tB�2 ����,ɇ���4 v�u��:>X[|�z{i�?�Ol�\����Q�.�;*B�ᙆg۰���$��^\� wjA� V�.��ׁ��h���fR���B7�g�� .*嶨�˟�j8���Lj [qK� +A�1@��S ` �����%m��z +ﴭP#��;O�x�S��I{�a��_Hb�$1H�V�܊��,Z��ƹJ��q�b�D�/�.���C"d�� x� �)�>0"�Q�ѿQc�d� �Ѹ�����<��*��<���hf�.K���֬nVT�x���Zع{N`h��~1g�Wt��7M��&̎e�i�/O v�'a���-��y����V� H }�W �]���K[�õ*����C�{y ��8{�d�p�æg�s���64��s��as���f�#:�6�H4L��� �T�M���Գ��8�ʨ�#H!��N<[�x�U™�I�=K�&��l��!�~�6���V� +�R�)���N�f� �=�}Jzʱ8CBѮ �`;�� Ľ�Kq�5\0�&~����\&�Bu�g5y���u������I.Л���d���@:��������`�]���$MK����.(;�� qoh'!����4��MH�/n�vY�L�)WM��>�y!` @���ȳVZ͡XmG�,���1�Kx���!y.� _s|6�mj���F ��$1ݨ% <�;1�c �4���{���rs�ߟ�2 ��h���>��`��ʟ� 6%�;�<՝!I�)h/L$!�� $�g� 4����e �MW&�I����e*�0TX1"JX��T���@3^E�v�.q�)��¡��#*�����c9�����~��d��ܔ�X����F���8`/�ៗ�<���� +�l!���\�'�0��\���^#@E�+�:r[�L)���nφ���>|b/ ��v�ɹ��6�'V����{oP���� �����%w ��3�(UN�b�n�u|'�+؍�z���j��-s��d�v�+Կq+�v�<>y��6�o�C(}�����\�:/�����r� A��m��YXk'��]�e�'�����"ǖ�`�U6 ���u?�.�h#�?�m���78�$��5���^��%�,��ct[��,�ev�|ϼ�-Ty��B���!E�(�v=�,�p��)be[:��ϫ�yt������x;�J㓌��6��Y,�Q(�`����B;:��6�B�sL�Z(�=8TBxP�<��@�=4��o_��7ݫ��a}š��k3l�\��G�&,|,ukHZ����������Ԁ��:�5O S��}9��PBO����π���c�y�K�p�����skS`��M��z����� +I��}�,ޙD0 i �(�[�G��oBjFH���OA�k���mS@�uc� �����~�'��"IA���sq7x`�yL�',��5\�e�}"s�IоnVc �aB@�#�l���3X6<�.B�z]��Bw�e��L�:���p-[SD�� ��!(�g��/pGJ��������$D��������* +��i�&�odz��5n��i]TU�L\��XQbQ����bX�9���r��t�i��2qBȌ���:�uvP���M��یЩb��l��Zb�*|��W�H6��W ��T��ܵ]�A�y�����M�̐�ʥ�-��D�4�E'�Ww���1�_�e�^kED�2�1��8tu�-.�E%��[�6U��ur�.0��Lo|���2����9Ա��um�S�<��r*�7cI���=�t�n.kVK� ��o�Ŵ�� +�4�!A��.�ԑ��o`�KsN��C~o_��F�?%/Z'f�7��od�c b�;1���L��S����������� 5\yqm�W�4`��)�����g½�`�J�ـ�f��|��u*���E�R`��^��l�֫� +�+����g1>{�Lj��^�����'F���K0M�Z.5�}�tq�e(�^���˥���!���Ï�� ؍��6�Q^�"�п�4r3�K䗝 ��%t�nN?cF�O5��)�x� +�t=� ~��%�u�O����I�&\ L�4��bN�]���� �ΌC�1D���ٱK}4�?d��k+̤����5�00z�΍��ذT&�㥚�Q�n�Q]����{�g_Á��Ͱ�h��2�n���X{���o9�'uԪ����]e/�M+nNq�3t;����O ������#�a�̶��C̤���x����~>�<����I�f��9K���k��B�¤f�cє��4Z�f�gͬ�@D����LC$$Xݏ�ӕ�N��p�A�.�e.�S��Y� |-q�"��D��N��MA� ��5Ԟ��7�o�|`)�;�������%w" P��Y�%�'Y�.F<��a%[�)ND#̎d��*� +�4�&����-����庬�U_7;.��Q f:��?FQ�L�!,@�,���#�K\��ܪ�"l��h92$XjJ�+m��&�� �UUIa��8'7�}��)0x;���DW��Љ����m �� � &D�p�Es -��z&��1��T�>(d[����gI�5�N�4}���,� d����`.�0+�V^s��=z�rP�yQ�����K��� ��B5���'���j��� �W{�D;�}-��ӆް��*���x�9��ў��5����D���5)\� � �͇�,�}���K��'U����Hܦ3+���k�^ݣ��M6Ks'��G>�G:�*����jAA����ݽ#� ���B��UU<�u\=�Zt��Dy�Λ �Vj+��@uiT�0�4۫kY�쳠%?K~ɟj �� ���v��L k�ٿW,O)f`�( ����iAK��P�-Km_Ҷ�!�q�F`�vwK��IWFN�V��)� ��U�7��VE�lAW���x25 0�y/�r`�J�k-+���L�e_���$*9z%��#QR�a���KfԂ����B�&�YƔ5�!����i%��2M��6�]�p�nHP����c��IK���z8Q��k�Ŏ֘k����h��+��2�� +��C89T�M�HX�-���gtO��$\mъ"C$�� �Y�����;踽]�S1� UJ�p +��f��z���� +¾���+z �YUb�����tPEL �j =|M u�%P��/�^�*�#,���� `J�1��J�P�s�� 気��}e�)t�Z�]:����.8�����?�|}w $.��b�~ Rʌ%��QCw�Im�'��,��7����7"-��[����:8#=U�+��ǰ!��5�l�� �4��dE�9�k2�KM�봭���ڜ�:g����G��D�4�]��|�;�tҩm� A��*�Dz�͑����o 6����2r�3��t���{���"|��0�Ir8�����Q�Ɛ�R��}��"Ş9��_����i!�b���\����3��#)&��#$��� %�r��fF�5�f�Wި?V�3>�$J��U�螊w��[ZJ��^�y�T�!�^����� +�� ^pn���^MW�ێ0�J +@���QL>k�L�a�!��-Z?j�`��8X���u���k�C��[ ��V�C���$���;�ѨpLzЧ/C=\�0��A�O� +�n���/���4j�-�N��s�7|7��t��+t�����̫?��1�z19��T�Qs��Xv�#�(���V��>���с$r�<��o�V@s� �A�3�F����;�Gjp +�1�*9u�@��#��}�D���KjM�(���%� g���(s���Ș �xUs���%Y�3��,��WϘ;h�������4u�˱Rhx�R��@— *,��JGk����V�B����ߎ��%�y��}�6^�X.[��ρ7���H5����mF(m��]_iY +`���ͅ��dlj�, �21��ênB�5[��F�;��1���k };�.�L7}}� ʔ�M�}G�4��e ��L��P�E[z�AB�݊ g�?�[Q A��8E~����e̢S� }2r)�ń�x�{En�EM�BB��T�t�ˠ���2���Yi�9������؛$��q���-���v���p�Y]��f;Ys��w��`b?� I���6;�3^��|�� �^�W���e�e�0C�ZLq��A= +g��`@Z���#Y���Yh�� ��tX�(�O�o����5�s>��_O�~� bp��v��V�o�2�����jZ�i��Z +P`���: �D>������Ƌ��#�}-A�8��l���B�=LL0��l ��ѫ���Y4*�1]��-cO��6PA���A���'SV�^AX+'�(�����B���%�&�e��~���ihbx���b��T׻������l�T{���O����>���p ��Un�d�! +l�����>� +)���BZ��#�"�)N�)m0&�?(�,,�Ou��2��^{VF�FɅ��=�C�g�x�:��Ф�I�c[��T  �Á��L��T�(�.�w$ӫ�:��p +� .��!�U�.��!�t�f��9�A� + �4n�"׷a�Ԫ,)��w�|��I�z���|��0�jGP��!����H�}�!� "�P�2�iU0� WK�:� �K�NB03 ��$<�C� +n4�<4��Q�0 �������ߕ�灾3P/��8�Б��%`f�jX�F_g"/�-[���H�ᦀ?#�Q +�`���v[yX��R����� �:��4��#�f����xXew�hSH9_iH���ϫʹDV%�4w$�l���A�P�AU�ALa�:��-s� �|6��k�����^ܘo�� �u$rG2E�~�-ڮL���W���:�,�Rz{c���B�������Q ��-ì +� o.B�N�ݯ�� .�A]�/,�0 �J��b�d��?F I�=l���Uw �U�b7���3� ��0׎Q��ă��*� +^��݂�yd�*�|��6d��ւ=֫坧� �'R�u}ْ�:�� |-^NjV�ؕ��<4]��m�sh�~L��r������3�&�^?�%[l�x��.z�2�5)3)�h� �L�ח�q冰�1΂{'+� q`I٢ �4��N�݂�����[�Fȋ�Ā���2Z�)��).;�!�0"2RTQZ�I����W@�D���#��v�O>����ؖ���Aa6�`�/K���}1(�`N�d���q/eJ�9t����K�_�L��s��Z��֒�1�k\1���.���`��BE�E�d��Ϙ�x�Pbm3*��C���:������i�^���'����'�}x��^�'�\j�w�Ro� �T]��|і�^�=�|[O#��ʵʥ��bN�X8����4���ԇ�'{��?�R�zB�e���~�#������ߦG*ʗ~�MrI�SS�3�����T�E� BY����Q-��3-}���ͪqW��nr�$�Ϫ,z��~!u��]�H߀,MԀe~e��o�}I��+��@�C["\��M��K������:ث@�9 � ��al�6��{��h�%��e݁����ݽ*�!��\��"����`Cnx�����si� +�̀g��i�h{T�1�v�+�$ �ʌ��ގ�pC�p����^(�C|����q�}*���R ��� ��q]D�ɫ�R퓔��~M���� 7�<x����r �2H4m 2`�'#��Z]1��Z��W$�C �� P*s�*� ��_�Fi=���L�fq��~�O ��o�w���@Te�� +#��ل��Q]���Gl�� ��d�g�}�g�Z�d���zL�ٌx���*3����_�X%���c +�v�,+�W ��O�dh-诃��FasDIgIӈ>�n �<�4!���4$+T����|���H���2-�s+��m P����Eo����v����}a�+e�İ�Dk��<�Dž>�ð7|T k�5\�T�ۃ|e,&|\����dA�Y��$���~%�P�@�Zs��-�O_�q�\�k��TooV�)�:�[��4�/�8h� ��є/�� ��T��T���)�j�<�����J��bӌ�m��CQ:�u��=��6�/��.�r1+@��$�43+GY�و�=@� �>/� �XP�Y��!c:��f��&xϢ��g���I�f ��dz8�j-j����n�o��T(y +i���8�����g� +���Ux��E4op%�� ��� ���l1��fXA��H Y��}#:��� T�����x���\~���Y앯�-��j�C�(5Y~p!K��UQ{��B5;� +g� J�Ι�g"f����#z�T0@�ys�g��z Ѿ*M�Il�N�]&g�e�Έ�a�D�j5��XS.z�Wt��Q1C�Ȑ{�/��R���p�!�t�~vP����,��t�� ��SCw*����},`բP�O��t�Gv�;\6�D�Ŷ9G5�!-�����a =kWYM���$�+���F���İ�ɟ���O:����0z��F�����2m�-���((�pچ�5ح���� KƳweX�l�Zs����-�[ض�ꄻ���ډXݳ�v���,g0�]�p�0��C��M��W4 v3<�B_��|�i�??�/P[p~_�������G���0d<�1C� ��%M��^\c�a�����h6�0@]1�Mv���۞]L��γlL��}xdv��3�;���+|ZV�m�!Ԃ���;Fg9���c%f����������hj�U��#%��.���_:G5��9� �{C̝䗜"ձz��U� K�%����l�je�f|i��9/'TD��Y�����MѐT��ɾ�o�1ՠ휎^���\ +1U|�8 �!��Yq"0D�a�K��08&�rg���ӪR���@m�&&�V/A��/0�hޙ���!Q��inWD��siQ�i���v7ňp�WH{�����/�8���B��tc<�e�R�r�\���m{��� ���|������kP�&������>k +^׊X���4C@1�_�H+�9:�Ĺ�t� +�Bp��!�J��n$� ����?�ȗ�9Ĉ�Kn����:���L'M��uӔ��z��uަ��!>g�PW/�W�𩬶0!�����A�!�4�y�)�1�:Ƞ)U�2�tƑC`&-��b����[*�C�1(ˆ j�a�����;��y�/;F�rE �w3�ܑc������ct+�`��n���p�)�0Y��n��#�AH���|[�L[g��a2S٧�E��" &a�jzc�d�r_VE�1%���uԿ�pb�)��KM5�(()�I\���-�&" �3�d�����s.��ƾw ���<�N�c� � �K��hF�1��K0?��<���,;���ɜ#CL��0����fͰHa�oE2�*�ɝ2҇ ��! ȵ)���4��cT,��!�^��nG�Q�b�VQ*@RiM�N+-&�0,�"�� �K����M�GB�1�ԪU��ZoƈK+Ec�͞`���wHF�W�:]�~���r$M�3o��?^��5�H��c���������z4g��i���O����F����J�ڮ?(ޯ�*�_��K@@R/��ܸS�� Fv��w+#��w���r���(�Y���o�蝣n�����\r��N���c���������&�ň���Y±� �3�񹠮8@窅�1���1�K�Ɣ��p�8�N`��J ��f}����{���#�v�"[i~�"o(�rO�4c4�/J!%R�{�&#{(�����l?��$�۱�,MC��Z'��+{)-d@���hV��B%�����9@�ǵ|�� ��Xkކ+Y]4����!Q+�j��>���HA��=�(Z�8IIII��ywjF$K/1�}���1��n���!�c��'a5<����9ֆ� +�`RZ�b<�$�:P��j��5`+Y0�J_DQl8.X_f E���Q+a !��0�~�I�CqK�m"W"G��S�u�$u�#�ɧ���q |�_��B�B3�\�Ph3IE�,˲2��GDu�i>��ek=�s��4i6��m�$��Ǔ�b����G��nH� �S�j&�20 �hM��fb��b.���sn�˕�y€��N�ؙ&�y���S�4���D��Q$���5`Ja@�� g�sn�!�j���#z� +q[�3K��q�P���c^�Wq����'D5��C��V�q0L� R��P�� aÈ�*���� B+�t�1p~L��q�JS�� ��9a���5��,80HxZ_���Gt�>=���BK�H�FC-���j�4��>�S����O�z�9������6/��[T��+44�g��W�}�R zJ�D��s|i ~u@�<��9�i�OK%�K.�ֹ������) �C h�n�`v�����-�:ƨ:)R�UZ4�|�]���<�a +�p����K*Z ����Fː⭅�2�g�ˢ,�����Q�����( �Ia�H6�@���p ���B +������i��N 5�� �VB��X d�Q;-2��q �@�:�I���a�������0�0�$�`�#���A3$�5�N�+Sh +�I���G�b�f�����3��k9�Kd~>m<����IA��NcG`2���2vձ ;k��T�YV\oS���Q��:E̓u�x�v�� +c��Z��t��-��8I?6X>׃` +Cǖ� �l�0Z07���N�r!���'D�b�]ܓB�UHi~R &g�A�S +�g���&�] +��ݗ����R0�G�̠�7V�D�T�)Vr���b+!Yn'D��Vǧ�zķ �}TT��bA�)6������ �$�4�J��u���ͦ��m���S��|� i>���C; �'��֙U���j}6E�R��R0͒� �O�uG�k$ł���3�ZQdV5��!�aK@�H��_� �Ǖ23 �e+YI6�kJ�j��NbĂW���q��ͧ�`�J�9�pNL +�D@�hh&����4 H#�Ȫ�W�� 䪒[��c��b�*�L���7�qi5Ϛ�D�+A5�8:Ői5%��l�pZ�X�D(DpP�0�m�lj�����ʞ���Tb��2PP�[#� MIQ��fd� +m��+�t;��W뭏�l�<�,+#��zNDԘ&d�\_��������$�,��R-H6�u ��B������w%G��Ձ�VQ(F�*iD��.��"�%`~���!��ط���Z* IB��K��1�sE���� Z���(�(k�BUR�}���!`���V�Ǩ���2��9K�>ͯmT� �vn:���8Wl� �M�`f %;�ח:�g���E����Ŗ�/���)���� �i}m�O�dH�B��R#�B�qp��F��i>���AK�H��)f�����{(L�4؎ 1�������|��,��n�3$�o��J��� +�c��Qa4���XIH��C(�LV�S*�Lb�{K )��f�cq1�.�Ǚ�L�w)� +A��X8��g&�0J� �T�@�u Q���s`'[�S,%'�C�s��X�bM��� �E`e�XlʧXBR�C/Vh=eI�h�Ҟt0��5H|�f�� � +�wHL`/�,`54hE�Ȭ���@>E+��j�ˆ�?�+zU�SA�S$+MU<��v�)Z�N�w�N>�o���q�҄v���Q��|F@[��%��MLY�����̨_�%�s���,�4����X� /Xq\��+�y�\P��*��zJl�e¥bD|���"H�z��H^bմ�$x��U�A͠g2u�� t����z&aMC�RH���J�GD&U�( FG�R0A�� ����FDM�CY�"\ܓh?m{;����M?�������P� |~ ��/*(#C�|�AC+��*c_���㓰~Ĥ���g�<��R� �+@;iUO��7E��<��@����|n���Avq�q����; �����L��IY���q�����DPd�>SafN.���UA!1��3 ¥��g+�q���G�t� {�� -L����~�a�>�R��y֙��IZ)8k^wƭd�Me��S��U���z���E �F�yH�P�M����p��C�jIyL��\.C�v� C�����d/�тyy\W�'�S*��� FDN䲸E��ʽXL�AW�h���q��J�U&���T7���:�j��aQ��X ��yY��*p� �s�RE�*�3Z0���_2/�zd\��+���_"�G�%AF���vC�)�f��D�"�dH�O�V�e0� �-;&�Z�EY���ғ5술��� ����lc7��;�B<�9 +�B2aȴ6v^0;�������N.Ă�3�*�F��Š�6�p�D��mDܐ$������80 +���։�������/T�-�^8"g� �=��ɧ�x�@&�QH�Y2ր9?���bbzU/�`}��s��F��y�ǵR�f +�C�6��Z��C+!Ek�R��L�:���%p�l!U+h%�<�Ǒ$'�������`��ݚ[��<�ߟq�3�`��q���?.s�q�ouy1�`�Z1'Z�[;�y�^��l��<�zu�9��������?<׻ڭ1��3�z�1�8� ��&�?��th,'@d^&��d�X N�9�����?�ZK)Kv�<�%��j-Y4�(ُ����_�V�������/Q&P�+Ǝ��4�塨 +�Rhff$hC G���u�O:$L>H.8&��80�@ +�(� +�bBY[(W��ih���օ�=n���/�����j��Ź�G�f؍�A��� ��R�*�»�p0�W}V"��Dȃ .��§TgS�0����=EZ��FN��J��*��I�u �[^�giS.��5�e�_�����F}Ys���,:*۷��Lj� +�]h +�7��YCr>Z�>e�]xM�z���U����Ӌ�в������ i~����#͈�ŭ����-A��L�5ʌ��y��]��ߔ�LY*��/�l +��x>�a�x6ܺ���� +����g���U��|��� ���������=� h� *�F��짓��+�#=[ �Nn=��W���:yJ�:�BC6��q�� �av+����ر��m�fbH|������b��c8N�Ê0��wo^�q�W�DN��=�Q#"f��Ka�ӕ�}ix�XE�u�맪4C6ғ؊:�W��>�1��ʸW������3��O�+� +� � ���� �"q-�K:jP��^��n����\э���!a�Q�����~,5 -gdr����k ��[��쌻�����PHvkj��#�1[T�z�9yҔ��� sz��+6M����Er��rb-��S��Iʶ���Tc��c�b�3s�Ÿ��[�\��8�ϼ0���Zh>�xEX�el�]G�+Cl��J�8:��}�`��O��:�P���9:J������8��;�Ѣ�Gة��:��7�������:$��Xݷaix=��dȷϕac7k +x�|¨�ȏ�RT��� ����9�е��a�vp� Ils�!�C��[| }T=:-�=�(S�A���)�D�� �x�ǝ�̙��*�@�.(DB��4U��1�AI"{�;�(^��EG#G�w�ę��� ��D�*�%��Ɖ�t�08Fz|CFx�ςs�!D$��pƔcۡ�.��q�&9tC^�J�_���VӋ����^�������S!~T4�5�B��8��U��;[��_�ܥ��Ż�G�f��{Ya�A��r�y8B�[��n�����u��f�+��1C��ldC���S��*Y���b^j*Bf����p�NAa0�������W�t t�T]i�댶EJ nY�IN +�f���'R������y�,yXN��E��n!du ,$�l(��IQ�V6��n��]ռȯ8�l���R��h�dF�Ee���@������0�MH�����@�S�~g�A�i�#eƊ����L�� �'�ވ^�%�e����� +�$����h�s�E/��(@,�Iѐ"u�'��A��C��QA +Rx\��&�'WY�a���T�}g�l����!��#G�N�a^n�#������H�s;W�V��hY7/5�#�u���v�L��A_J�҆����/h��rg����`���2�w8H̚�2(�1��ٹ�)3,W����e���o % �,S���م�B&#M��-��G�SvOL��ѯ��T/2�-���"�q=?lΛ��*^���u�'����N�^h��Ѕ�f0$ՅՏ��f���U �v��4��G�+\�Z_>k� +���<�'�G���� ����qV< �f7�Ի�U�1|o��KԘ�qq5]ɵ�S �3�S`����� ����P(:�d���U��u�$�r�$�/2[.���g��v a�̻Y�`�%�}d��f��"���ά�(l��fQ^�TC�N��N�"�� ��{�حr%�>�lK�� +� �?�Ϯ dZ��R��b�m�f�&����(ߡ\�`�y\QW�\C6��G��}ڔ�#8��|���<�u�F�`f�?y"�+wE����`���ͥ����M�n�6\@�{�� �z�v��%@An�Ge�b�* ���X��]G�#Q�i�]�M��` �l���F��6Ie�*�b��Y��W!h�T���*:��^�tč�g�^?]���?O%���!ߢīy7|)RN%�@�_q�I���n�{[\�`�f��m�,nҵr�wi�?���OBP�lj�!$��7��D�Lq�-���l��� ~z�05�l�'7DB��mC��"8��u:?��`�w1%p�n���$�m7ѱ��v��n�ፉ5;q�����U��ω?n��]M�0s���L�;q�� +7��Q�8PF{��0�$��7e��'�K�R�^Aq|f:���W8��B��<#�s�Az}�8�}�U�R{'�`0Fu�N| +M6,q�S���%�N<~���� �ω;(�/�x8'�(��"W�?��w�s#�W�VsG�t�w�|sAqPd?��8�b����D�C�P�#��AW,9Rn" �26�=��1p���'.Ӯ�|�8_3�Pd���9\Ώr e��CL8��� ������'z��͌8mp�_^nh�/>6�M8�b�Om�*�}���)����7�����%b�9p��N �����Z 8�̧q��{��l�����D�� �fIj�S��A�D�\��"�i�۸���Tu ���\�_kN�����B/*��9���/b-���(XLV�5-ä�ўu��m�*�r����"@����j�3�Ĭ��H��(Ob� +˘��oo=1��7AQ�0�3ͥ�î�@��_m����Y���(�by�Y,mSU1z�%-�6W�!��mE&��}�X/L�a��,,o~�"@~(��wg�� ��R`j��DvD�/�#Г�m����$���K�����;ș�ѓ��P�_�_�A�T !X��d��(P���Mq<���6Z��aCwg�� �#�6'�s6���ӿ2�;8/���.'��*�_�$��c�P��6U�삢���W�Qχ +�3Q-��]Ov�ȅ��G$��j�#)��{@ +��w5��s@�|mB��5��>��,D�0ϼ'�� P�lO�X����a'X ɍķ5Ԏ��>L�;WlL˫�n�({��϶�Ui���L"���@�&&��Љ��3sS?�:�#���F��� +�����Cw&���,�� �H�0�U2F��%�hK��G��A����%�NT��N4��&Z�����jK�R�~�Dw9���}8���`�L��(�uW���~v��A�� �q�P���K�;�� !��i��`}>�ݘg��5��K��n ��X���g�T�K|�L�� w6 j�� ����^��ˈߍ�-I +c���NA �bB(��Qj��ҀC;�f�mM��#[�L�D�u�a� ˅��� ,%0��]ݯN8q��*��Hi'J�eǁ���μ�=��/^�݊:�y\���m,�>n���f m�(���M��0ȴ׮�w�ɲ����-yb�=����-D�UcBM�J�2�T�t�� �I�Tm-�ħ:�O���T%���S�� P���C�:�˝���(�I����pq���S��C�@�jh���MKA�Q���B��-T2�} ��������R����=ۙ%��g�E I��qK^�^���sݔ������b�ѥ.u�8l)��\��BOdHa�X�׫I7��'�- +�=���3 ���rȑ�����<�X�5� ~ck��� 6�o�pS=b�R|"�:� +� +��;G�aJyI�i�����͔��2��}{��ٶ}P՚�����sv3:_Vg��{��؂�ɔiz\-C����7d�x$ϑj_#ѵp� lⓨU~���tG����d;u�"��6��J�׻N�G�^�� �mPZ1�����/@7�9/��� +���cZGL�}�[)Ʋ���פ��F���������"�a�~���-��οUT̕�(�M����lnD��(Ͱ⮳�mk���Q"�|���������R? ()nv��T������LT�!�0�RG�] ��;�N:,���F! jG&�O�heH&�~�\�8Dܡ��E�bj���aG���֌�0��E�35��5!�SC�pP�c�����p�G'��Jɪ!�~��h�Yg����NPu�rId�d��jhl%�/�qP�ߚy�{�ɞ��^��c�!a e�/~ػ����e)��qT��3z��8�hm$f��xuq�A2^���đ�(�$�x�L�ʰ8Z�7�@ĚZ�F�2"�;A�k!T��� B�&���\d�� ��2sA?x�6��EJ�D��cB��鄀P�$y��=��_��T� ڨ�4�L���'�����Q}*<�I��"Tb�v�2�>z�ݖx*: +�PZ'����O{u� +u�7�v9�󿒸(��=��b@&t�(�� +���H�pT�a����g�>�U�z‹�Z�,VCX��a�Wnb�Ҥ�qxsW�@�{��1�L܉�o�� + g����ZB\|�!u�(�_Ԓ}�5���jD�jE22����bl\G��=A�2y����Q�\�����+�굂���ža����}o�]=3:e" �J���D�{��tHK��!2� �|oB��í�ey��W�dB�U�wV�t;���s�_z��/ɯ2�g€D�yhoy�����]�|�.#�]n �V.�Z��������Q��W��3 {��>�'��1?v�9˹$�U����s�g�ٻ1���c��V{��v����G2�;-�K_�˕$8��J�J������W�� �hU�O�!�W�*S�6��:����2�^Vt8�qP���e�~���z%�*�ޥ4HV:` M F�? Uu�F��>*�C��Ffȧ�sU�W7��b�Jҿ����m!9c��&�Z��J�u5��ǫ.s���%�s��t�0�1Vj�:����:���s�RfY��4r �����A���� �'�+�߮��w 4�1��r� ��ús}��v�&H���T��)\��i�o�K(s{�?r#�[$Lc1������,��m��f�ʅ,�� o��{|���ħp&ǵ3 +G�f����ڃ|?u�_j����ޙ+1�����Z�~��R� �wZ�-�<��S�Q�����D�*P�� l��~Qt��d J;¥T��~���L+-�ie��~{JDF�f ��X� +F�Y��2_��9�ۯ����I��}ߞ��X-��)L�brS2��4; �2~v��n����$f��x.�ևem�a�y��m�zw���)Ҩ���P���7����KP"��~�5/�F4rw�{Ԇط�v��K�ӥ�Ҵ���$� +�A$ag�.>e��0̗.�)Q"�ʊ�j!����q'�pś�������R���Ǣ�D�ԉ7K�c�G���~P@���P ��;������l���)�e$Q��`�V3�ܐ��V��9����4���꫗.�m�W6_��A|>�DZ.���/Y=����[�2�)�q&>W�C��9��")�%��2���w5h��p� ���U� �h�(?���5k?�K���Q�O;Qte�J]���m��W� +�Iy��ܢL�)u+�<6�>Qm�8�.��U���W���j�3��ٚq�0]�:�m�J��P-�������t�c;Q���٨�-"�S�6������� ����y�^f�/��^�Bf���i���`��mф0T�Md���� }wt�JoIҌ�H�_���'�_�:7��_� �J�c�g�?��H���o%��0w9��D/$M��>e+;�f��{���E3#JM����!!�o� b ��x�Ogw�a�X>{'� e���&�����y�a�ʴ���9��!�����8��&���n�o ���^��� ��N�f3��k��h���=� ��n���R1f�RV�! �v��`6R��!�}nȳD�/�`uw�* +��c�Vwm�H���.vW��'MuW8�2\;�'�Fݕ��`�#`����z3�I����� �Q}��侌}gs�IvW4�嵺k�L*d�!�V�g%S��xh�&(L�2�c-I�M�S�tf�+��A��� l*W7�~�[�W�@�^`���������έ�^}� {���[_�g]�!5EjeS֙a�u�]�[B�HB�S�#+�-WH��3o�P4bU R �*a.M��J`�|Bl�Ї3�T�p���^���{��m�sU��yC���O�*C�T^�����%꾉讐fnBv�ӥ"ꅋe ���.peB\ �zhu�_S��Xsڡ�Q��4�Lg"3t�Ev��?QЌ�7��>�w������)�P!�S)��7ݰ��q�_���V���YB H)�](GX����F?�%JPEAT� +�rA�=����X�_e:�*���D]�H|�-&�����b�t����k��{�Нue�z��y�Di�m���u�+���x�O�'u�D�((qQ���ۀ��7��g� ����Yj�}^&Io���-�,��M���Qסv&N��`��k����(�#nQȨ[������^���z�F�g��Y�(L�i\�u����d�V8�A"�̅6-�v3��ȠW�����;"�vb8_�1x-�F�+�)��jR)�+�܏�6�j�#G���EG��BX�`L v �ϯt+��ł�ܤU NY��&��W��N,:�|����vW�.�g& b�����aZRz���Q�򛣗w`*V�L\2� 2�=s��B�De��Bp����[m�]YC&8�k�o�}���R�v^����˅�n��5�AlG�����Iu�xX��L�����5;�3͂�s;��$���#uX���;c�t�:�>Sи Jg�yS��u�?�A�g�ˠ#��}$g� +!<����HX� 7N��(�d6� +�a����?D�A͠Ըc�D0C��r�}*d�<��Y�� �o�i,ɢō��[��� ��@��o���������h\k��Vf��C ���g�?%�#�b�x�5�s�nd�$D1����b�s� 5�M]-�g�,�pi��*�� -�↖QA�M +ZeJ�T�i�sYJ��!��Գ�R}�H(���V�(���I)مfY���[�2��:\�2��{С�F�X}��S�[�y ��V�i�$��8F �Ȗ���(���#)�0�RLr���D�k^�v!^��[�����#��IV1o�r�c�^�x��b�Գ8L����b��;\���5h�I1q���B��#;/�bN ��D-Sq���"+� x�\�a�>�k�3ـ8��f�sLvBr��iY��P�3�pFVM(����R�UY��gd&��H x�e��,�Svh�nBR�K&�Ş�^Ʋv�q�95��m�>I��Ə��&���qpۘ�*I���kQi"��F����������)�m���S����-B������ 1m��D�ܛ���,Ȥ���1�ٞ))�"Xq�����G�yif|����y����]CPq�z��딚���'x#�6���5�{�M`�� ��"�vX�=��<�@�vӒ�QdRK��%~5��wKY����7Bm�!��Z�CtU>B�Va��1D�ҵ,&h���3a�[ �/�a\���/`N �n~h\��R�x[�c]���.Q��g��%!M�U��5��w���+#_ɤ�Q�����Vƍ�b��PA�$�ә�̼�"��pĜ�b�����`�a�� :�VHDL�� D*�n��T���"���d� ���Z�AVdFZ��J endstream endobj 23 0 obj [22 0 R] endobj 36 0 obj <> endobj xref +0 37 +0000000004 65535 f +0000000016 00000 n +0000000147 00000 n +0000049208 00000 n +0000000000 00000 f +0000049259 00000 n +0000000000 00000 f +0000056386 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000056459 00000 n +0000056633 00000 n +0000057775 00000 n +0000123363 00000 n +0000000000 00000 f +0000051653 00000 n +0000051466 00000 n +0000159566 00000 n +0000049698 00000 n +0000053703 00000 n +0000053590 00000 n +0000050602 00000 n +0000050905 00000 n +0000050953 00000 n +0000051537 00000 n +0000051568 00000 n +0000051839 00000 n +0000051982 00000 n +0000052336 00000 n +0000053738 00000 n +0000159591 00000 n +trailer <<1DA5599560F24228A66A90C469885E5B>]>> startxref 159778 %%EOF \ No newline at end of file diff --git a/Weights/doc/Weights/fig/barycentric_cell.svg b/Weights/doc/Weights/fig/barycentric_cell.svg new file mode 100644 index 000000000000..b284ef9c99c6 --- /dev/null +++ b/Weights/doc/Weights/fig/barycentric_cell.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/cotangent.svg b/Weights/doc/Weights/fig/cotangent.svg new file mode 100644 index 000000000000..4c9620bccd39 --- /dev/null +++ b/Weights/doc/Weights/fig/cotangent.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/discrete_harmonic.svg b/Weights/doc/Weights/fig/discrete_harmonic.svg new file mode 100644 index 000000000000..21fdafc5cd71 --- /dev/null +++ b/Weights/doc/Weights/fig/discrete_harmonic.svg @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/flattening.svg b/Weights/doc/Weights/fig/flattening.svg new file mode 100644 index 000000000000..8ebbe82260c6 --- /dev/null +++ b/Weights/doc/Weights/fig/flattening.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Weights/doc/Weights/fig/inverse_distance.svg b/Weights/doc/Weights/fig/inverse_distance.svg new file mode 100644 index 000000000000..d31c8639b419 --- /dev/null +++ b/Weights/doc/Weights/fig/inverse_distance.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/logo.svg b/Weights/doc/Weights/fig/logo.svg new file mode 100644 index 000000000000..630edb9e4154 --- /dev/null +++ b/Weights/doc/Weights/fig/logo.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/logo_120x120.png b/Weights/doc/Weights/fig/logo_120x120.png new file mode 100644 index 000000000000..9af1eb1398b4 Binary files /dev/null and b/Weights/doc/Weights/fig/logo_120x120.png differ diff --git a/Weights/doc/Weights/fig/mean_value.svg b/Weights/doc/Weights/fig/mean_value.svg new file mode 100644 index 000000000000..0e40e87eaefb --- /dev/null +++ b/Weights/doc/Weights/fig/mean_value.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/mixed_voronoi_cell.svg b/Weights/doc/Weights/fig/mixed_voronoi_cell.svg new file mode 100644 index 000000000000..0fd2fa651912 --- /dev/null +++ b/Weights/doc/Weights/fig/mixed_voronoi_cell.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/mixed_voronoi_cell_obtuse.svg b/Weights/doc/Weights/fig/mixed_voronoi_cell_obtuse.svg new file mode 100644 index 000000000000..19a14d48f166 --- /dev/null +++ b/Weights/doc/Weights/fig/mixed_voronoi_cell_obtuse.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/neighborhood.svg b/Weights/doc/Weights/fig/neighborhood.svg new file mode 100644 index 000000000000..7a4106a73b45 --- /dev/null +++ b/Weights/doc/Weights/fig/neighborhood.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/pkg-small.png b/Weights/doc/Weights/fig/pkg-small.png new file mode 100644 index 000000000000..5609b037587a Binary files /dev/null and b/Weights/doc/Weights/fig/pkg-small.png differ diff --git a/Weights/doc/Weights/fig/shepard.svg b/Weights/doc/Weights/fig/shepard.svg new file mode 100644 index 000000000000..d31c8639b419 --- /dev/null +++ b/Weights/doc/Weights/fig/shepard.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/tangent.svg b/Weights/doc/Weights/fig/tangent.svg new file mode 100644 index 000000000000..a52fcf03170a --- /dev/null +++ b/Weights/doc/Weights/fig/tangent.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/three_point_family.svg b/Weights/doc/Weights/fig/three_point_family.svg new file mode 100644 index 000000000000..21fdafc5cd71 --- /dev/null +++ b/Weights/doc/Weights/fig/three_point_family.svg @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/triangular_cell.svg b/Weights/doc/Weights/fig/triangular_cell.svg new file mode 100644 index 000000000000..4372a47f760c --- /dev/null +++ b/Weights/doc/Weights/fig/triangular_cell.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/voronoi_cell.svg b/Weights/doc/Weights/fig/voronoi_cell.svg new file mode 100644 index 000000000000..e4697bb19135 --- /dev/null +++ b/Weights/doc/Weights/fig/voronoi_cell.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/doc/Weights/fig/wachspress.svg b/Weights/doc/Weights/fig/wachspress.svg new file mode 100644 index 000000000000..cc751743d65e --- /dev/null +++ b/Weights/doc/Weights/fig/wachspress.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Weights/examples/Weights/CMakeLists.txt b/Weights/examples/Weights/CMakeLists.txt new file mode 100644 index 000000000000..be8efdb3bc67 --- /dev/null +++ b/Weights/examples/Weights/CMakeLists.txt @@ -0,0 +1,27 @@ +# Created by the script cgal_create_cmake_script. +# This is the CMake script for compiling a CGAL application. + +project(Weights_Examples) + +cmake_minimum_required(VERSION 3.1...3.15) +set(CMAKE_CXX_STANDARD 14) + +find_package(CGAL REQUIRED COMPONENTS Core) +include(${CGAL_USE_FILE}) +include(CGAL_CreateSingleSourceCGALProgram) + +create_single_source_cgal_program("weights.cpp") +create_single_source_cgal_program("coordinates_one_query.cpp") +create_single_source_cgal_program("coordinates_multiple_queries.cpp") +create_single_source_cgal_program("projection_traits.cpp") +create_single_source_cgal_program("custom_traits.cpp") +create_single_source_cgal_program("convergence.cpp") + +find_package(Eigen3 3.1.0 QUIET) # (3.1.0 or greater) +include(CGAL_Eigen3_support) +if(TARGET CGAL::Eigen3_support) + create_single_source_cgal_program("weighted_laplacian.cpp") + target_link_libraries(weighted_laplacian PUBLIC CGAL::Eigen3_support) +else() + message(NOTICE "Several examples require the Eigen library, and will not be compiled.") +endif() diff --git a/Weights/examples/Weights/convergence.cpp b/Weights/examples/Weights/convergence.cpp new file mode 100644 index 000000000000..8cc859155319 --- /dev/null +++ b/Weights/examples/Weights/convergence.cpp @@ -0,0 +1,38 @@ +#include +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point_2 = typename Kernel::Point_2; + +int main() { + std::cout << std::fixed; + + // 3D configuration. + const Point_2 p0(0, 1); + const Point_2 p1(2, 0); + const Point_2 p2(7, 1); + const Point_2 q0(3, 1); + + // Choose a type of the weight: + // e.g. 0 - Wachspress (WP) weight; 1 - mean value (MV); + const FT wp = 0.0; + const FT mv = 1.0; + + // Compute WP and MV weights. + std::cout << "3D Wachspress (WP, q0): "; + std::cout << CGAL::Weights::three_point_family_weight(p0, p1, p2, q0, wp) << std::endl; + std::cout << "3D mean value (MV, q0): "; + std::cout << CGAL::Weights::three_point_family_weight(p0, p1, p2, q0, mv) << std::endl; + + // Converge WP towards MV. + std::cout << "Converge WP to MV on q0: " << std::endl; + const FT step = 0.1; + for (FT x = 0.0; x <= 1.0; x += step) { + std::cout << "3D x: "; + std::cout << CGAL::Weights::three_point_family_weight(p0, p1, p2, q0, x) << std::endl; + } + return EXIT_SUCCESS; +} diff --git a/Weights/examples/Weights/coordinates_multiple_queries.cpp b/Weights/examples/Weights/coordinates_multiple_queries.cpp new file mode 100644 index 000000000000..ec11863fd0b0 --- /dev/null +++ b/Weights/examples/Weights/coordinates_multiple_queries.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point_2 = typename Kernel::Point_2; + +using PointRange = std::vector; +using Creator = CGAL::Creator_uniform_2; +using Generator = CGAL::Random_points_in_square_2; +using Wachspress = CGAL::Weights::Wachspress_weights_2; + +int main() { + + // Choose how many query points we want to generate. + const std::size_t num_queries = 10; + + // Create a polygon. + const std::vector polygon = + { Point_2(0, 0), Point_2(1, 0), Point_2(1, 1), Point_2(0, 1) }; + + // Generate a set of query points. + std::vector queries; + queries.reserve(num_queries); + Generator generator(1.0); + std::copy_n(generator, num_queries, std::back_inserter(queries)); + assert(queries.size() == num_queries); + + // Allocate memory for weights and coordinates. + std::vector weights; + weights.reserve(num_queries * polygon.size()); + std::vector coordinates; + coordinates.reserve(num_queries * polygon.size()); + + // Compute barycentric weights. + Wachspress wachspress(polygon); + for (const Point_2& query : queries) { + wachspress(query, std::back_inserter(weights)); + } + assert(weights.size() == num_queries * polygon.size()); + + std::cout << "2D weights: " << std::endl; + for (std::size_t i = 0; i < weights.size(); i += polygon.size()) { + for (std::size_t j = 0; j < polygon.size(); ++j) { + std::cout << weights[i + j] << " "; + } + std::cout << std::endl; + } + + // Normalize weights in order to get barycentric coordinates. + for (std::size_t i = 0; i < weights.size(); i += polygon.size()) { + FT sum = 0.0; + for (std::size_t j = 0; j < polygon.size(); ++j) { + sum += weights[i + j]; + } + assert(sum != 0.0); + for (std::size_t j = 0; j < polygon.size(); ++j) { + coordinates.push_back(weights[i + j] / sum); + } + } + assert(coordinates.size() == weights.size()); + + std::cout << "2D coordinates: " << std::endl; + for (std::size_t i = 0; i < coordinates.size(); i += polygon.size()) { + for (std::size_t j = 0; j < polygon.size(); ++j) { + std::cout << coordinates[i + j] << " "; + } + std::cout << std::endl; + } + return EXIT_SUCCESS; +} diff --git a/Weights/examples/Weights/coordinates_one_query.cpp b/Weights/examples/Weights/coordinates_one_query.cpp new file mode 100644 index 000000000000..00f4b9328ec3 --- /dev/null +++ b/Weights/examples/Weights/coordinates_one_query.cpp @@ -0,0 +1,49 @@ +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point_2 = typename Kernel::Point_2; + +int main() { + + // Create a polygon and a query point. + const std::vector polygon = + { Point_2(0, 0), Point_2(1, 0), Point_2(1, 1), Point_2(0, 1) }; + const Point_2 query(0.5, 0.5); + + // Allocate memory for weights and coordinates. + std::vector weights; + weights.reserve(polygon.size()); + std::vector coordinates; + coordinates.reserve(polygon.size()); + + // Compute barycentric weights. + CGAL::Weights::discrete_harmonic_weights_2(polygon, query, std::back_inserter(weights)); + assert(weights.size() == polygon.size()); + + std::cout << "2D weights: "; + for (const FT weight : weights) { + std::cout << weight << " "; + } + std::cout << std::endl; + + // Normalize weights in order to get barycentric coordinates. + FT sum = 0.0; + for (const FT weight : weights) { + sum += weight; + } + assert(sum != 0.0); + for (const FT weight : weights) { + coordinates.push_back(weight / sum); + } + assert(coordinates.size() == weights.size()); + + std::cout << "2D coordinates: "; + for (const FT coordinate : coordinates) { + std::cout << coordinate << " "; + } + std::cout << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/examples/Weights/custom_traits.cpp b/Weights/examples/Weights/custom_traits.cpp new file mode 100644 index 000000000000..ac5276437b89 --- /dev/null +++ b/Weights/examples/Weights/custom_traits.cpp @@ -0,0 +1,41 @@ +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using Point_2 = typename Kernel::Point_2; +using Point_3 = typename Kernel::Point_3; + +// Custom traits class that has only two objects. +struct Custom_traits { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + + decltype(auto) compute_squared_distance_2_object() const { + return Kernel::Compute_squared_distance_2(); + } + decltype(auto) compute_squared_distance_3_object() const { + return Kernel::Compute_squared_distance_3(); + } +}; + +int main() { + + // 2D configuration. + const Point_2 p2(0, 0); + const Point_2 q2(0, 1); + + // 3D configuration. + const Point_3 p3(0, 0, 1); + const Point_3 q3(0, 1, 1); + + // Create custom traits. + const Custom_traits ctraits; + + // Compute weights. + std::cout << "2D/3D weight: "; + std::cout << CGAL::Weights::inverse_distance_weight(p2, q2, ctraits) << "/"; + std::cout << CGAL::Weights::inverse_distance_weight(p3, q3, ctraits) << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/examples/Weights/projection_traits.cpp b/Weights/examples/Weights/projection_traits.cpp new file mode 100644 index 000000000000..81c35b4e4adf --- /dev/null +++ b/Weights/examples/Weights/projection_traits.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point_3 = typename Kernel::Point_3; +using Vector_3 = typename Kernel::Vector_3; +using PTraits = CGAL::Projection_traits_xy_3; + +int main() { + + // Create a polygon and a query point. + const std::vector polygon = + { Point_3(0, 0, 1), Point_3(1, 0, 1), Point_3(1, 1, 1), Point_3(0, 1, 1) }; + const Point_3 query(0.5, 0.5, 1); + + // Allocate memory for weights. + std::vector weights; + weights.reserve(polygon.size()); + + // Create projection traits. + const PTraits ptraits; + + // Compute weights. + CGAL::Weights::mean_value_weights_2(polygon, query, std::back_inserter(weights), ptraits); + assert(weights.size() == polygon.size()); + + std::cout << "2D mean value weights: "; + for (const FT weight : weights) { + std::cout << weight << " "; + } + std::cout << std::endl; + + // Almost coplanar case. + const FT eps = 0.001; + const Point_3 t3(-1, 0, 1.0 + eps); + const Point_3 r3( 0, -1, 1); + const Point_3 p3( 1, 0, 1.0 + eps); + const Point_3 q3( 0, 0, 1); + std::cout << "2D Wachspress weight: " << + CGAL::Weights::wachspress_weight(t3, r3, p3, q3, ptraits) << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/examples/Weights/weighted_laplacian.cpp b/Weights/examples/Weights/weighted_laplacian.cpp new file mode 100644 index 000000000000..bd44f1fb0ea9 --- /dev/null +++ b/Weights/examples/Weights/weighted_laplacian.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point_3 = typename Kernel::Point_3; + +using Solver_traits = CGAL::Eigen_solver_traits< + Eigen::SparseLU::EigenType> >; +using Matrix = Solver_traits::Matrix; + +using Mesh = CGAL::Surface_mesh; +using VD = boost::graph_traits::vertex_descriptor; +using HD = boost::graph_traits::halfedge_descriptor; + +template +FT get_w_ij(const Mesh& mesh, const HD he, const PointMap pmap) { + + const auto v0 = target(he, mesh); + const auto v1 = source(he, mesh); + + const auto& q = get(pmap, v0); // query + const auto& p1 = get(pmap, v1); // neighbor j + + if (is_border_edge(he, mesh)) { + const auto he_cw = opposite(next(he, mesh), mesh); + auto v2 = source(he_cw, mesh); + + if (is_border_edge(he_cw, mesh)) { + const auto he_ccw = prev(opposite(he, mesh), mesh); + v2 = source(he_ccw, mesh); + + const auto& p2 = get(pmap, v2); // neighbor jp + return CGAL::Weights::cotangent(p1, p2, q); + } else { + const auto& p0 = get(pmap, v2); // neighbor jm + return CGAL::Weights::cotangent(q, p0, p1); + } + } + + const auto he_cw = opposite(next(he, mesh), mesh); + const auto v2 = source(he_cw, mesh); + const auto he_ccw = prev(opposite(he, mesh), mesh); + const auto v3 = source(he_ccw, mesh); + + const auto& p0 = get(pmap, v2); // neighbor jm + const auto& p2 = get(pmap, v3); // neighbor jp + return CGAL::Weights::cotangent_weight(p0, p1, p2, q) / 2.0; +} + +template +FT get_w_i(const Mesh& mesh, const VD v_i, const PointMap pmap) { + + FT A_i = 0.0; + const auto v0 = v_i; + const auto init = halfedge(v_i, mesh); + for (const auto& he : halfedges_around_target(init, mesh)) { + assert(v0 == target(he, mesh)); + if (is_border(he, mesh)) { continue; } + + const auto v1 = source(he, mesh); + const auto v2 = target(next(he, mesh), mesh); + + const auto& p = get(pmap, v0); + const auto& q = get(pmap, v1); + const auto& r = get(pmap, v2); + A_i += CGAL::Weights::mixed_voronoi_area(p, q, r); + } + assert(A_i != 0.0); + return 1.0 / (2.0 * A_i); +} + +void set_laplacian_matrix(const Mesh& mesh, Matrix& L) { + + const auto pmap = get(CGAL::vertex_point, mesh); // vertex to point map + const auto imap = get(CGAL::vertex_index, mesh); // vertex to index map + + // Precompute Voronoi areas. + std::map w_i; + for (const auto& v_i : vertices(mesh)) { + w_i[get(imap, v_i)] = get_w_i(mesh, v_i, pmap); + } + + // Fill the matrix. + for (const auto& he : halfedges(mesh)) { + const auto vi = source(he, mesh); + const auto vj = target(he, mesh); + + const std::size_t i = get(imap, vi); + const std::size_t j = get(imap, vj); + if (i > j) { continue; } + + const FT w_ij = w_i[j] * get_w_ij(mesh, he, pmap); + L.set_coef(i, j, w_ij, true); + L.set_coef(j, i, w_ij, true); + L.add_coef(i, i, -w_ij); + L.add_coef(j, j, -w_ij); + } +} + +int main() { + + // Create mesh. + Mesh mesh; + const auto v0 = mesh.add_vertex(Point_3(0, 2, 0)); + const auto v1 = mesh.add_vertex(Point_3(2, 2, 0)); + const auto v2 = mesh.add_vertex(Point_3(0, 0, 0)); + const auto v3 = mesh.add_vertex(Point_3(2, 0, 0)); + const auto v4 = mesh.add_vertex(Point_3(1, 1, 1)); + mesh.add_face(v0, v2, v4); + mesh.add_face(v2, v3, v4); + mesh.add_face(v3, v1, v4); + mesh.add_face(v1, v0, v4); + mesh.add_face(v2, v3, v1); + mesh.add_face(v1, v0, v2); + assert(CGAL::is_triangle_mesh(mesh)); + + // Set discretized Laplacian. + const std::size_t n = 5; // we have 5 vertices + Matrix L(n, n); + set_laplacian_matrix(mesh, L); + std::cout << std::fixed; std::cout << std::showpos; + for (std::size_t i = 0; i < n; ++i) { + for (std::size_t j = 0; j < n; ++j) { + std::cout << L.get_coef(i, j) << " "; + } + std::cout << std::endl; + } + return EXIT_SUCCESS; +} diff --git a/Weights/examples/Weights/weights.cpp b/Weights/examples/Weights/weights.cpp new file mode 100644 index 000000000000..dc53da2355c4 --- /dev/null +++ b/Weights/examples/Weights/weights.cpp @@ -0,0 +1,36 @@ +#include +#include + +// Typedefs. +using Kernel = CGAL::Simple_cartesian; +using FT = typename Kernel::FT; +using Point_2 = typename Kernel::Point_2; +using Point_3 = typename Kernel::Point_3; + +int main() { + + // 2D configuration. + const Point_2 t2(-1, 0); // p0 + const Point_2 r2( 0, -1); // p1 + const Point_2 p2( 1, 0); // p2 + const Point_2 q2( 0, 0); // query + + // 3D configuration. + const Point_3 t3(-1, 0, 1); // p0 + const Point_3 r3( 0, -1, 1); // p1 + const Point_3 p3( 1, 0, 1); // p2 + const Point_3 q3( 0, 0, 1); // query + + std::cout << "2D/3D tangent weight: "; + std::cout << CGAL::Weights::tangent_weight(t2, r2, p2, q2) << "/"; + std::cout << CGAL::Weights::tangent_weight(t3, r3, p3, q3) << std::endl; + + std::cout << "2D/3D Shepard weight: "; + std::cout << CGAL::Weights::shepard_weight(r2, q2, 2.0) << "/"; + std::cout << CGAL::Weights::shepard_weight(r3, q3, 2.0) << std::endl; + + std::cout << "2D/3D barycentric area: "; + std::cout << CGAL::Weights::barycentric_area(p2, q2, r2) << "/"; + std::cout << CGAL::Weights::barycentric_area(p3, q3, r3) << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/include/CGAL/Weights.h b/Weights/include/CGAL/Weights.h new file mode 100644 index 000000000000..75d4db6fa0d9 --- /dev/null +++ b/Weights/include/CGAL/Weights.h @@ -0,0 +1,50 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_WEIGHTS_H +#define CGAL_WEIGHTS_H + +/// \cond SKIP_IN_MANUAL +#include +/// \endcond + +/** +* \ingroup PkgWeightsRef +* \file CGAL/Weights.h +* A convenience header that includes all weights. +*/ + +/// \cond SKIP_IN_MANUAL +#include +/// \endcond + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#endif // CGAL_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/authalic_weights.h b/Weights/include/CGAL/Weights/authalic_weights.h new file mode 100644 index 000000000000..45b5c5650ef9 --- /dev/null +++ b/Weights/include/CGAL/Weights/authalic_weights.h @@ -0,0 +1,203 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_AUTHALIC_WEIGHTS_H +#define CGAL_AUTHALIC_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace authalic_ns { + + template + FT half_weight(const FT cot, const FT r2) { + + FT w = FT(0); + CGAL_precondition(r2 != FT(0)); + if (r2 != FT(0)) { + const FT inv = FT(2) / r2; + w = cot * inv; + } + return w; + } + + template + FT weight(const FT cot_gamma, const FT cot_beta, const FT r2) { + + FT w = FT(0); + CGAL_precondition(r2 != FT(0)); + if (r2 != FT(0)) { + const FT inv = FT(2) / r2; + w = (cot_gamma + cot_beta) * inv; + } + return w; + } + } + /// \endcond + + /*! + \ingroup PkgWeightsRefAuthalicWeights + + \brief computes the half value of the authalic weight. + + This function constructs the half of the authalic weight using the precomputed + cotangent and squared distance values. The returned value is + \f$\frac{2\textbf{cot}}{\textbf{d2}}\f$. + + \tparam FT + a model of `FieldNumberType` + + \param cot + the cotangent value + + \param d2 + the squared distance value + + \pre d2 != 0 + + \sa `authalic_weight()` + */ + template + FT half_authalic_weight(const FT cot, const FT d2) { + return authalic_ns::half_weight(cot, d2); + } + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefAuthalicWeights + + \brief computes the authalic weight in 2D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT authalic_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefAuthalicWeights + + \brief computes the authalic weight in 3D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT authalic_weight( + const typename GeomTraits::Point_3& p0, + const typename GeomTraits::Point_3& p1, + const typename GeomTraits::Point_3& p2, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefAuthalicWeights + + \brief computes the authalic weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT authalic_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q) { } + + /*! + \ingroup PkgWeightsRefAuthalicWeights + + \brief computes the authalic weight in 3D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT authalic_weight( + const CGAL::Point_3& p0, + const CGAL::Point_3& p1, + const CGAL::Point_3& p2, + const CGAL::Point_3& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + // Overloads! + template + typename GeomTraits::FT authalic_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT cot_gamma = internal::cotangent_2(traits, t, r, q); + const FT cot_beta = internal::cotangent_2(traits, q, r, p); + + const auto squared_distance_2 = + traits.compute_squared_distance_2_object(); + const FT d2 = squared_distance_2(q, r); + return authalic_ns::weight(cot_gamma, cot_beta, d2); + } + + template + typename GeomTraits::FT authalic_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return authalic_weight(t, r, p, q, traits); + } + + template + typename GeomTraits::FT authalic_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT cot_gamma = internal::cotangent_3(traits, t, r, q); + const FT cot_beta = internal::cotangent_3(traits, q, r, p); + + const auto squared_distance_3 = + traits.compute_squared_distance_3_object(); + const FT d2 = squared_distance_3(q, r); + return authalic_ns::weight(cot_gamma, cot_beta, d2); + } + + template + typename GeomTraits::FT authalic_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return authalic_weight(t, r, p, q, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_AUTHALIC_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/barycentric_region_weights.h b/Weights/include/CGAL/Weights/barycentric_region_weights.h new file mode 100644 index 000000000000..fecf5fc5d014 --- /dev/null +++ b/Weights/include/CGAL/Weights/barycentric_region_weights.h @@ -0,0 +1,148 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_BARYCENTRIC_REGION_WEIGHTS_H +#define CGAL_BARYCENTRIC_REGION_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefBarycentricRegionWeights + + \brief computes the area of the barycentric cell in 2D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT barycentric_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefBarycentricRegionWeights + + \brief computes the area of the barycentric cell in 3D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT barycentric_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefBarycentricRegionWeights + + \brief computes the area of the barycentric cell in 2D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT barycentric_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { } + + /*! + \ingroup PkgWeightsRefBarycentricRegionWeights + + \brief computes the area of the barycentric cell in 3D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT barycentric_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT barycentric_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto midpoint_2 = + traits.construct_midpoint_2_object(); + const auto centroid_2 = + traits.construct_centroid_2_object(); + + const auto center = centroid_2(p, q, r); + const auto m1 = midpoint_2(q, r); + const auto m2 = midpoint_2(q, p); + + const FT A1 = internal::positive_area_2(traits, q, m1, center); + const FT A2 = internal::positive_area_2(traits, q, center, m2); + return A1 + A2; + } + + template + typename GeomTraits::FT barycentric_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return barycentric_area(p, q, r, traits); + } + + template + typename GeomTraits::FT barycentric_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto midpoint_3 = + traits.construct_midpoint_3_object(); + const auto centroid_3 = + traits.construct_centroid_3_object(); + + const auto center = centroid_3(p, q, r); + const auto m1 = midpoint_3(q, r); + const auto m2 = midpoint_3(q, p); + + const FT A1 = internal::positive_area_3(traits, q, m1, center); + const FT A2 = internal::positive_area_3(traits, q, center, m2); + return A1 + A2; + } + + template + typename GeomTraits::FT barycentric_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return barycentric_area(p, q, r, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_BARYCENTRIC_REGION_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/cotangent_weights.h b/Weights/include/CGAL/Weights/cotangent_weights.h new file mode 100644 index 000000000000..2c61b726f3d5 --- /dev/null +++ b/Weights/include/CGAL/Weights/cotangent_weights.h @@ -0,0 +1,500 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_COTANGENT_WEIGHTS_H +#define CGAL_COTANGENT_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace cotangent_ns { + + template + FT half_weight(const FT cot) { + return FT(2) * cot; + } + + template + FT weight(const FT cot_beta, const FT cot_gamma) { + return FT(2) * (cot_beta + cot_gamma); + } + } + /// \endcond + + /*! + \ingroup PkgWeightsRefCotangentWeights + + \brief computes the half value of the cotangent weight. + + This function constructs the half of the cotangent weight using the precomputed + cotangent value. The returned value is + \f$2\textbf{cot}\f$. + + \tparam FT + a model of `FieldNumberType` + + \param cot + the cotangent value + + \sa `cotangent_weight()` + */ + template + FT half_cotangent_weight(const FT cot) { + return cotangent_ns::half_weight(cot); + } + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefCotangentWeights + + \brief computes the cotangent weight in 2D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT cotangent_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefCotangentWeights + + \brief computes the cotangent weight in 3D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT cotangent_weight( + const typename GeomTraits::Point_3& p0, + const typename GeomTraits::Point_3& p1, + const typename GeomTraits::Point_3& p2, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefCotangentWeights + + \brief computes the cotangent weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT cotangent_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q) { } + + /*! + \ingroup PkgWeightsRefCotangentWeights + + \brief computes the cotangent weight in 3D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT cotangent_weight( + const CGAL::Point_3& p0, + const CGAL::Point_3& p1, + const CGAL::Point_3& p2, + const CGAL::Point_3& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT cotangent_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT cot_beta = internal::cotangent_2(traits, q, t, r); + const FT cot_gamma = internal::cotangent_2(traits, r, p, q); + return cotangent_ns::weight(cot_beta, cot_gamma); + } + + template + typename GeomTraits::FT cotangent_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return cotangent_weight(t, r, p, q, traits); + } + + template + typename GeomTraits::FT cotangent_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT cot_beta = internal::cotangent_3(traits, q, t, r); + const FT cot_gamma = internal::cotangent_3(traits, r, p, q); + return cotangent_ns::weight(cot_beta, cot_gamma); + } + + template + typename GeomTraits::FT cotangent_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return cotangent_weight(t, r, p, q, traits); + } + + // Undocumented cotangent weight class. + // Its constructor takes a polygon mesh and a vertex to point map + // and its operator() is defined based on the halfedge_descriptor only. + // This version is currently used in: + // Polygon_mesh_processing -> curvature_flow_impl.h + template< + typename PolygonMesh, + typename VertexPointMap = typename boost::property_map::type> + class Edge_cotangent_weight { + + using GeomTraits = typename CGAL::Kernel_traits< + typename boost::property_traits::value_type>::type; + using FT = typename GeomTraits::FT; + + const PolygonMesh& m_pmesh; + const VertexPointMap m_pmap; + const GeomTraits m_traits; + + public: + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + Edge_cotangent_weight(const PolygonMesh& pmesh, const VertexPointMap pmap) : + m_pmesh(pmesh), m_pmap(pmap), m_traits() { } + + FT operator()(const halfedge_descriptor he) const { + + FT weight = FT(0); + if (is_border_edge(he, m_pmesh)) { + const auto h1 = next(he, m_pmesh); + + const auto v0 = target(he, m_pmesh); + const auto v1 = source(he, m_pmesh); + const auto v2 = target(h1, m_pmesh); + + const auto& p0 = get(m_pmap, v0); + const auto& p1 = get(m_pmap, v1); + const auto& p2 = get(m_pmap, v2); + + weight = internal::cotangent_3(m_traits, p0, p2, p1); + + } else { + const auto h1 = next(he, m_pmesh); + const auto h2 = prev(opposite(he, m_pmesh), m_pmesh); + + const auto v0 = target(he, m_pmesh); + const auto v1 = source(he, m_pmesh); + const auto v2 = target(h1, m_pmesh); + const auto v3 = source(h2, m_pmesh); + + const auto& p0 = get(m_pmap, v0); + const auto& p1 = get(m_pmap, v1); + const auto& p2 = get(m_pmap, v2); + const auto& p3 = get(m_pmap, v3); + + weight = cotangent_weight(p2, p1, p3, p0) / FT(2); + } + return weight; + } + }; + + // Undocumented cotangent weight class. + // Returns a single cotangent weight, its operator() is defined based on the + // halfedge_descriptor, polygon mesh, and vertex to point map. + // For border edges it returns zero. + // This version is currently used in: + // Surface_mesh_deformation -> Surface_mesh_deformation.h + template + class Single_cotangent_weight { + + public: + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + template + decltype(auto) operator()(const halfedge_descriptor he, + const PolygonMesh& pmesh, const VertexPointMap pmap) const { + + using GeomTraits = typename CGAL::Kernel_traits< + typename boost::property_traits::value_type>::type; + using FT = typename GeomTraits::FT; + const GeomTraits traits; + + if (is_border(he, pmesh)) { + return FT(0); + } + + const vertex_descriptor v0 = target(he, pmesh); + const vertex_descriptor v1 = source(he, pmesh); + const vertex_descriptor v2 = target(next(he, pmesh), pmesh); + + const auto& p0 = get(pmap, v0); + const auto& p1 = get(pmap, v1); + const auto& p2 = get(pmap, v2); + + return internal::cotangent_3(traits, p0, p2, p1); + } + }; + + // Undocumented cotangent weight class. + // Its constructor takes a boolean flag to choose between default and clamped + // versions of the cotangent weights and its operator() is defined based on the + // halfedge_descriptor, polygon mesh, and vertex to point map. + // This version is currently used in: + // Surface_mesh_deformation -> Surface_mesh_deformation.h (default version) + // Surface_mesh_parameterizer -> Orbifold_Tutte_parameterizer_3.h (default version) + // Surface_mesh_skeletonization -> Mean_curvature_flow_skeletonization.h (clamped version) + template + class Cotangent_weight { + bool m_use_clamped_version; + + public: + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + Cotangent_weight(const bool use_clamped_version = false) : + m_use_clamped_version(use_clamped_version) { } + + template + decltype(auto) operator()(const halfedge_descriptor he, + const PolygonMesh& pmesh, const VertexPointMap pmap) const { + + using GeomTraits = typename CGAL::Kernel_traits< + typename boost::property_traits::value_type>::type; + using FT = typename GeomTraits::FT; + const GeomTraits traits; + + const auto v0 = target(he, pmesh); + const auto v1 = source(he, pmesh); + + const auto& p0 = get(pmap, v0); + const auto& p1 = get(pmap, v1); + + FT weight = FT(0); + if (is_border_edge(he, pmesh)) { + const auto he_cw = opposite(next(he, pmesh), pmesh); + auto v2 = source(he_cw, pmesh); + + if (is_border_edge(he_cw, pmesh)) { + const auto he_ccw = prev(opposite(he, pmesh), pmesh); + v2 = source(he_ccw, pmesh); + + const auto& p2 = get(pmap, v2); + if (m_use_clamped_version) { + weight = internal::cotangent_3_clamped(traits, p1, p2, p0); + } else { + weight = internal::cotangent_3(traits, p1, p2, p0); + } + weight = (CGAL::max)(FT(0), weight); + weight /= FT(2); + } else { + const auto& p2 = get(pmap, v2); + if (m_use_clamped_version) { + weight = internal::cotangent_3_clamped(traits, p0, p2, p1); + } else { + weight = internal::cotangent_3(traits, p0, p2, p1); + } + weight = (CGAL::max)(FT(0), weight); + weight /= FT(2); + } + + } else { + const auto he_cw = opposite(next(he, pmesh), pmesh); + const auto v2 = source(he_cw, pmesh); + const auto he_ccw = prev(opposite(he, pmesh), pmesh); + const auto v3 = source(he_ccw, pmesh); + + const auto& p2 = get(pmap, v2); + const auto& p3 = get(pmap, v3); + FT cot_beta = FT(0), cot_gamma = FT(0); + + if (m_use_clamped_version) { + cot_beta = internal::cotangent_3_clamped(traits, p0, p2, p1); + } else { + cot_beta = internal::cotangent_3(traits, p0, p2, p1); + } + + if (m_use_clamped_version) { + cot_gamma = internal::cotangent_3_clamped(traits, p1, p3, p0); + } else { + cot_gamma = internal::cotangent_3(traits, p1, p3, p0); + } + + cot_beta = (CGAL::max)(FT(0), cot_beta); cot_beta /= FT(2); + cot_gamma = (CGAL::max)(FT(0), cot_gamma); cot_gamma /= FT(2); + weight = cot_beta + cot_gamma; + } + return weight; + } + }; + + // Undocumented cotangent weight class. + // Its constructor takes a polygon mesh and a vertex to point map + // and its operator() is defined based on the halfedge_descriptor only. + // This class is using a special clamped version of the cotangent weights. + // This version is currently used in: + // Polygon_mesh_processing -> fair.h + // Polyhedron demo -> Hole_filling_plugin.cpp + template< + typename PolygonMesh, + typename VertexPointMap = typename boost::property_map::type> + class Secure_cotangent_weight_with_voronoi_area { + + using GeomTraits = typename CGAL::Kernel_traits< + typename boost::property_traits::value_type>::type; + using FT = typename GeomTraits::FT; + + const PolygonMesh& m_pmesh; + const VertexPointMap m_pmap; + const GeomTraits m_traits; + + public: + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + Secure_cotangent_weight_with_voronoi_area(const PolygonMesh& pmesh, const VertexPointMap pmap) : + m_pmesh(pmesh), m_pmap(pmap), m_traits() { } + + FT w_i(const vertex_descriptor v_i) const { + return FT(1) / (FT(2) * voronoi(v_i)); + } + + FT w_ij(const halfedge_descriptor he) const { + return cotangent_clamped(he); + } + + private: + FT cotangent_clamped(const halfedge_descriptor he) const { + + const auto v0 = target(he, m_pmesh); + const auto v1 = source(he, m_pmesh); + + const auto& p0 = get(m_pmap, v0); + const auto& p1 = get(m_pmap, v1); + + FT weight = FT(0); + if (is_border_edge(he, m_pmesh)) { + const auto he_cw = opposite(next(he, m_pmesh), m_pmesh); + auto v2 = source(he_cw, m_pmesh); + + if (is_border_edge(he_cw, m_pmesh)) { + const auto he_ccw = prev(opposite(he, m_pmesh), m_pmesh); + v2 = source(he_ccw, m_pmesh); + + const auto& p2 = get(m_pmap, v2); + weight = internal::cotangent_3_clamped(m_traits, p1, p2, p0); + } else { + const auto& p2 = get(m_pmap, v2); + weight = internal::cotangent_3_clamped(m_traits, p0, p2, p1); + } + + } else { + const auto he_cw = opposite(next(he, m_pmesh), m_pmesh); + const auto v2 = source(he_cw, m_pmesh); + const auto he_ccw = prev(opposite(he, m_pmesh), m_pmesh); + const auto v3 = source(he_ccw, m_pmesh); + + const auto& p2 = get(m_pmap, v2); + const auto& p3 = get(m_pmap, v3); + + const FT cot_beta = internal::cotangent_3_clamped(m_traits, p0, p2, p1); + const FT cot_gamma = internal::cotangent_3_clamped(m_traits, p1, p3, p0); + weight = cot_beta + cot_gamma; + } + return weight; + } + + FT voronoi(const vertex_descriptor v0) const { + + const auto squared_length_3 = + m_traits.compute_squared_length_3_object(); + const auto construct_vector_3 = + m_traits.construct_vector_3_object(); + + FT voronoi_area = FT(0); + CGAL_assertion(CGAL::is_triangle_mesh(m_pmesh)); + for (const auto& he : halfedges_around_target(halfedge(v0, m_pmesh), m_pmesh)) { + CGAL_assertion(v0 == target(he, m_pmesh)); + if (is_border(he, m_pmesh)) { + continue; + } + + const auto v1 = source(he, m_pmesh); + const auto v2 = target(next(he, m_pmesh), m_pmesh); + + const auto& p0 = get(m_pmap, v0); + const auto& p1 = get(m_pmap, v1); + const auto& p2 = get(m_pmap, v2); + + const auto angle0 = CGAL::angle(p1, p0, p2); + const auto angle1 = CGAL::angle(p2, p1, p0); + const auto angle2 = CGAL::angle(p0, p2, p1); + + const bool obtuse = + (angle0 == CGAL::OBTUSE) || + (angle1 == CGAL::OBTUSE) || + (angle2 == CGAL::OBTUSE); + + if (!obtuse) { + const FT cot_p1 = internal::cotangent_3(m_traits, p2, p1, p0); + const FT cot_p2 = internal::cotangent_3(m_traits, p0, p2, p1); + + const auto v1 = construct_vector_3(p0, p1); + const auto v2 = construct_vector_3(p0, p2); + + const FT t1 = cot_p1 * squared_length_3(v2); + const FT t2 = cot_p2 * squared_length_3(v1); + voronoi_area += (t1 + t2) / FT(8); + + } else { + + const FT A = internal::positive_area_3(m_traits, p0, p1, p2); + if (angle0 == CGAL::OBTUSE) { + voronoi_area += A / FT(2); + } else { + voronoi_area += A / FT(4); + } + } + } + CGAL_assertion(voronoi_area != FT(0)); + return voronoi_area; + } + }; + + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_COTANGENT_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/discrete_harmonic_weights.h b/Weights/include/CGAL/Weights/discrete_harmonic_weights.h new file mode 100644 index 000000000000..5dce9e5f5f0a --- /dev/null +++ b/Weights/include/CGAL/Weights/discrete_harmonic_weights.h @@ -0,0 +1,445 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov, David Bommes, Kai Hormann, Pierre Alliez +// + +#ifndef CGAL_DISCRETE_HARMONIC_WEIGHTS_H +#define CGAL_DISCRETE_HARMONIC_WEIGHTS_H + +#include + +// Internal includes. +#include +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace discrete_harmonic_ns { + + template + FT weight( + const FT r1, const FT r2, const FT r3, + const FT A1, const FT A2, const FT B) { + + FT w = FT(0); + CGAL_precondition(A1 != FT(0) && A2 != FT(0)); + const FT prod = A1 * A2; + if (prod != FT(0)) { + const FT inv = FT(1) / prod; + w = (r3 * A1 - r2 * B + r1 * A2) * inv; + } + return w; + } + } + /// \endcond + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefDiscreteHarmonicWeights + + \brief computes the discrete harmonic weight in 2D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT discrete_harmonic_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefDiscreteHarmonicWeights + + \brief computes the discrete harmonic weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT discrete_harmonic_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT discrete_harmonic_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto squared_distance_2 = + traits.compute_squared_distance_2_object(); + + const FT d1 = squared_distance_2(q, t); + const FT d2 = squared_distance_2(q, r); + const FT d3 = squared_distance_2(q, p); + + const FT A1 = internal::area_2(traits, r, q, t); + const FT A2 = internal::area_2(traits, p, q, r); + const FT B = internal::area_2(traits, p, q, t); + + return discrete_harmonic_ns::weight( + d1, d2, d3, A1, A2, B); + } + + template + typename GeomTraits::FT discrete_harmonic_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return discrete_harmonic_weight(t, r, p, q, traits); + } + + namespace internal { + + template + typename GeomTraits::FT discrete_harmonic_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using Point_2 = typename GeomTraits::Point_2; + Point_2 tf, rf, pf, qf; + internal::flatten( + traits, + t, r, p, q, + tf, rf, pf, qf); + return CGAL::Weights:: + discrete_harmonic_weight(tf, rf, pf, qf, traits); + } + + template + typename GeomTraits::FT discrete_harmonic_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return discrete_harmonic_weight(t, r, p, q, traits); + } + + } // namespace internal + + /// \endcond + + /*! + \ingroup PkgWeightsRefBarycentricDiscreteHarmonicWeights + + \brief 2D discrete harmonic weights for polygons. + + This class implements 2D discrete harmonic weights ( \cite cgal:bc:fhk-gcbcocp-06, + \cite cgal:pp-cdmsc-93, \cite cgal:bc:eddhls-maam-95 ) which can be computed + at any point inside a strictly convex polygon. + + Discrete harmonic weights are well-defined inside a strictly convex polygon + but they are not necessarily positive. These weights are computed analytically + using the formulation from the `discrete_harmonic_weight()`. + + \tparam VertexRange + a model of `ConstRange` whose iterator type is `RandomAccessIterator` + + \tparam GeomTraits + a model of `AnalyticWeightTraits_2` + + \tparam PointMap + a model of `ReadablePropertyMap` whose key type is `VertexRange::value_type` and + value type is `Point_2`. The default is `CGAL::Identity_property_map`. + + \cgalModels `BarycentricWeights_2` + */ + template< + typename VertexRange, + typename GeomTraits, + typename PointMap = CGAL::Identity_property_map > + class Discrete_harmonic_weights_2 { + + public: + + /// \name Types + /// @{ + + /// \cond SKIP_IN_MANUAL + using Vertex_range = VertexRange; + using Geom_traits = GeomTraits; + using Point_map = PointMap; + + using Area_2 = typename GeomTraits::Compute_area_2; + using Squared_distance_2 = typename GeomTraits::Compute_squared_distance_2; + /// \endcond + + /// Number type. + typedef typename GeomTraits::FT FT; + + /// Point type. + typedef typename GeomTraits::Point_2 Point_2; + + /// @} + + /// \name Initialization + /// @{ + + /*! + \brief initializes all internal data structures. + + This class implements the behavior of discrete harmonic weights + for 2D query points inside strictly convex polygons. + + \param polygon + an instance of `VertexRange` with the vertices of a strictly convex polygon + + \param traits + a traits class with geometric objects, predicates, and constructions; + the default initialization is provided + + \param point_map + an instance of `PointMap` that maps a vertex from `polygon` to `Point_2`; + the default initialization is provided + + \pre polygon.size() >= 3 + \pre polygon is simple + \pre polygon is strictly convex + */ + Discrete_harmonic_weights_2( + const VertexRange& polygon, + const GeomTraits traits = GeomTraits(), + const PointMap point_map = PointMap()) : + m_polygon(polygon), + m_traits(traits), + m_point_map(point_map), + m_area_2(m_traits.compute_area_2_object()), + m_squared_distance_2(m_traits.compute_squared_distance_2_object()) { + + CGAL_precondition( + polygon.size() >= 3); + CGAL_precondition( + internal::is_simple_2(polygon, traits, point_map)); + CGAL_precondition( + internal::polygon_type_2(polygon, traits, point_map) == + internal::Polygon_type::STRICTLY_CONVEX); + resize(); + } + + /// @} + + /// \name Access + /// @{ + + /*! + \brief computes 2D discrete harmonic weights. + + This function fills a destination range with 2D discrete harmonic weights + computed at the `query` point with respect to the vertices of the input polygon. + + The number of computed weights is equal to the number of polygon vertices. + + \tparam OutIterator + a model of `OutputIterator` whose value type is `FT` + + \param query + a query point + + \param w_begin + the beginning of the destination range with the computed weights + + \return an output iterator to the element in the destination range, + one past the last weight stored + */ + template + OutIterator operator()(const Point_2& query, OutIterator w_begin) { + const bool normalize = false; + return operator()(query, w_begin, normalize); + } + + /// @} + + /// \cond SKIP_IN_MANUAL + template + OutIterator operator()(const Point_2& query, OutIterator w_begin, const bool normalize) { + return optimal_weights(query, w_begin, normalize); + } + /// \endcond + + private: + + // Fields. + const VertexRange& m_polygon; + const GeomTraits m_traits; + const PointMap m_point_map; + + const Area_2 m_area_2; + const Squared_distance_2 m_squared_distance_2; + + std::vector r; + std::vector A; + std::vector B; + std::vector w; + + // Functions. + void resize() { + r.resize(m_polygon.size()); + A.resize(m_polygon.size()); + B.resize(m_polygon.size()); + w.resize(m_polygon.size()); + } + + template + OutputIterator optimal_weights( + const Point_2& query, OutputIterator weights, const bool normalize) { + + // Get the number of vertices in the polygon. + const std::size_t n = m_polygon.size(); + + // Compute areas A, B, and distances r following the notation from [1]. + // Split the loop to make this computation faster. + const auto& p1 = get(m_point_map, *(m_polygon.begin() + 0)); + const auto& p2 = get(m_point_map, *(m_polygon.begin() + 1)); + const auto& pn = get(m_point_map, *(m_polygon.begin() + (n - 1))); + + r[0] = m_squared_distance_2(p1, query); + A[0] = m_area_2(p1, p2, query); + B[0] = m_area_2(pn, p2, query); + + for (std::size_t i = 1; i < n - 1; ++i) { + const auto& pi0 = get(m_point_map, *(m_polygon.begin() + (i - 1))); + const auto& pi1 = get(m_point_map, *(m_polygon.begin() + (i + 0))); + const auto& pi2 = get(m_point_map, *(m_polygon.begin() + (i + 1))); + + r[i] = m_squared_distance_2(pi1, query); + A[i] = m_area_2(pi1, pi2, query); + B[i] = m_area_2(pi0, pi2, query); + } + + const auto& pm = get(m_point_map, *(m_polygon.begin() + (n - 2))); + r[n - 1] = m_squared_distance_2(pn, query); + A[n - 1] = m_area_2(pn, p1, query); + B[n - 1] = m_area_2(pm, p1, query); + + // Compute unnormalized weights following the formula (25) with p = 2 from [1]. + CGAL_assertion(A[n - 1] != FT(0) && A[0] != FT(0)); + w[0] = (r[1] * A[n - 1] - r[0] * B[0] + r[n - 1] * A[0]) / (A[n - 1] * A[0]); + + for (std::size_t i = 1; i < n - 1; ++i) { + CGAL_assertion(A[i - 1] != FT(0) && A[i] != FT(0)); + w[i] = (r[i + 1] * A[i - 1] - r[i] * B[i] + r[i - 1] * A[i]) / (A[i - 1] * A[i]); + } + + CGAL_assertion(A[n - 2] != FT(0) && A[n - 1] != FT(0)); + w[n - 1] = (r[0] * A[n - 2] - r[n - 1] * B[n - 1] + r[n - 2] * A[n - 1]) / (A[n - 2] * A[n - 1]); + + // Normalize if necessary. + if (normalize) { + internal::normalize(w); + } + + // Return weights. + for (std::size_t i = 0; i < n; ++i) { + *(weights++) = w[i]; + } + return weights; + } + }; + + /*! + \ingroup PkgWeightsRefBarycentricDiscreteHarmonicWeights + + \brief computes 2D discrete harmonic weights for polygons. + + This function computes 2D discrete harmonic weights at a given `query` point + with respect to the vertices of a strictly convex `polygon`, that is one + weight per vertex. The weights are stored in a destination range + beginning at `w_begin`. + + Internally, the class `Discrete_harmonic_weights_2` is used. If one wants to process + multiple query points, it is better to use that class. When using the free function, + internal memory is allocated for each query point, while when using the class, + it is allocated only once which is much more efficient. However, for a few query + points, it is easier to use this function. It can also be used when the processing + time is not a concern. + + \tparam PointRange + a model of `ConstRange` whose iterator type is `RandomAccessIterator` + and value type is `GeomTraits::Point_2` + + \tparam OutIterator + a model of `OutputIterator` whose value type is `GeomTraits::FT` + + \tparam GeomTraits + a model of `AnalyticWeightTraits_2` + + \param polygon + an instance of `PointRange` with 2D points which form a strictly convex polygon + + \param query + a query point + + \param w_begin + the beginning of the destination range with the computed weights + + \param traits + a traits class with geometric objects, predicates, and constructions; + this parameter can be omitted if the traits class can be deduced from the point type + + \return an output iterator to the element in the destination range, + one past the last weight stored + + \pre polygon.size() >= 3 + \pre polygon is simple + \pre polygon is strictly convex + */ + template< + typename PointRange, + typename OutIterator, + typename GeomTraits> + OutIterator discrete_harmonic_weights_2( + const PointRange& polygon, const typename GeomTraits::Point_2& query, + OutIterator w_begin, const GeomTraits& traits) { + + Discrete_harmonic_weights_2 + discrete_harmonic(polygon, traits); + return discrete_harmonic(query, w_begin); + } + + /// \cond SKIP_IN_MANUAL + template< + typename PointRange, + typename OutIterator> + OutIterator discrete_harmonic_weights_2( + const PointRange& polygon, + const typename PointRange::value_type& query, + OutIterator w_begin) { + + using Point_2 = typename PointRange::value_type; + using GeomTraits = typename Kernel_traits::Kernel; + const GeomTraits traits; + return discrete_harmonic_weights_2( + polygon, query, w_begin, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_DISCRETE_HARMONIC_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/internal/pmp_weights_deprecated.h b/Weights/include/CGAL/Weights/internal/pmp_weights_deprecated.h new file mode 100644 index 000000000000..3624e0758c3b --- /dev/null +++ b/Weights/include/CGAL/Weights/internal/pmp_weights_deprecated.h @@ -0,0 +1,970 @@ +// Copyright (c) 2015-2020 GeometryFactory SARL (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) : Yin Xu, Andreas Fabri, Ilker O. Yaz +// + +#ifndef CGAL_WEIGHTS_PMP_DEPRECATED_H +#define CGAL_WEIGHTS_PMP_DEPRECATED_H + +#include + +#define CGAL_DEPRECATED_HEADER "" +#define CGAL_DEPRECATED_MESSAGE_DETAILS \ + "This part of the package is deprecated since the version 5.4 of CGAL!" +#include + +// README: +// This header collects all weights, which have been in CGAL before unifying them +// into the new package Weights. This header is for information purpose only. It +// will be removed in the next release. + +// Includes. +#include +#include +#include + +namespace CGAL { +namespace Weights { +namespace deprecated { + +/// \cond SKIP_IN_MANUAL + +// Returns the cotangent value of the half angle [v0, v1, v2] +// using the formula in -[Meyer02] Discrete Differential-Geometry Operators for- page 19. +// The potential problem with the previous one (Cotangent_value) is that it does not produce symmetric results +// (i.e. for v0, v1, v2 and v2, v1, v0 the returned cot weights can be slightly different). +// This one provides stable results. +template +struct Cotangent_value_Meyer_impl { + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + template + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2, + const VertexPointMap& ppmap) { + + typedef typename Kernel_traits< + typename boost::property_traits::value_type >::Kernel::Vector_3 Vector; + + const Vector a = get(ppmap, v0) - get(ppmap, v1); + const Vector b = get(ppmap, v2) - get(ppmap, v1); + + const double dot_ab = to_double(a * b); + + // Rewritten for safer fp arithmetic. + // double dot_aa = a.squared_length(); + // double dot_bb = b.squared_length(); + // double divider = CGAL::sqrt(dot_aa * dot_bb - dot_ab * dot_ab); + + const Vector cross_ab = CGAL::cross_product(a, b); + const double divider = CGAL::to_double( + CGAL::approximate_sqrt(cross_ab * cross_ab)); + + if (divider == 0.0 /* || divider != divider */) { + CGAL::collinear(get(ppmap, v0), get(ppmap, v1), get(ppmap, v2)) ? + CGAL_warning_msg(false, "Infinite Cotangent value with the degenerate triangle!") : + CGAL_warning_msg(false, "Infinite Cotangent value due to the floating point arithmetic!"); + + return dot_ab > 0.0 ? + (std::numeric_limits::max)() : + -(std::numeric_limits::max)(); + } + return dot_ab / divider; + } +}; + +// Same as above but with a different API. +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type> +class Cotangent_value_Meyer { + +protected: + typedef VertexPointMap Point_property_map; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + PolygonMesh& pmesh_; + Point_property_map ppmap_; + +public: + Cotangent_value_Meyer( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + pmesh_(pmesh_), + ppmap_(vpmap_) + { } + + PolygonMesh& pmesh() { + return pmesh_; + } + + Point_property_map& ppmap() { + return ppmap_; + } + + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + return Cotangent_value_Meyer_impl()(v0, v1, v2, ppmap()); + } +}; + +// Imported from skeletonization. +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type> +class Cotangent_value_Meyer_secure { + + typedef VertexPointMap Point_property_map; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + PolygonMesh& pmesh_; + Point_property_map ppmap_; + +public: + Cotangent_value_Meyer_secure( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + pmesh_(pmesh_), + ppmap_(vpmap_) + { } + + PolygonMesh& pmesh() { + return pmesh_; + } + + Point_property_map& ppmap() { + return ppmap_; + } + + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + const Vector a = get(ppmap(), v0) - get(ppmap(), v1); + const Vector b = get(ppmap(), v2) - get(ppmap(), v1); + + const double dot_ab = CGAL::to_double(a * b); + const double dot_aa = CGAL::to_double(a.squared_length()); + const double dot_bb = CGAL::to_double(b.squared_length()); + const double lb = -0.999, ub = 0.999; + double cosine = dot_ab / CGAL::sqrt(dot_aa) / CGAL::sqrt(dot_bb); + cosine = (cosine < lb) ? lb : cosine; + cosine = (cosine > ub) ? ub : cosine; + const double sine = CGAL::sqrt(1.0 - cosine * cosine); + return cosine / sine; + } +}; + +// Returns the cotangent value of the half angle [v0, v1, v2] by clamping between +// [1, 89] degrees as suggested by -[Friedel] Unconstrained Spherical Parameterization-. +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Cotangent_value_clamped : CotangentValue { + + Cotangent_value_clamped() + { } + +public: + Cotangent_value_clamped( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + const double cot_1 = 57.289962; + const double cot_89 = 0.017455; + const double value = CotangentValue::operator()(v0, v1, v2); + return (std::max)(cot_89, (std::min)(value, cot_1)); + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Cotangent_value_clamped_2 : CotangentValue { + + Cotangent_value_clamped_2() + { } + +public: + Cotangent_value_clamped_2( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + const double cot_5 = 5.671282; + const double cot_175 = -cot_5; + const double value = CotangentValue::operator()(v0, v1, v2); + return (std::max)(cot_175, (std::min)(value, cot_5)); + } +}; + +template< +typename PolygonMesh, +typename CotangentValue = Cotangent_value_Meyer_impl > +struct Cotangent_value_minimum_zero_impl : CotangentValue { + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + template + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2, + const VertexPointMap ppmap) { + + const double value = CotangentValue::operator()(v0, v1, v2, ppmap); + return (std::max)(0.0, value); + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Cotangent_value_minimum_zero : CotangentValue { + + Cotangent_value_minimum_zero() + { } + +public: + Cotangent_value_minimum_zero( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + const double value = CotangentValue::operator()(v0, v1, v2); + return (std::max)(0.0, value); + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Voronoi_area : CotangentValue { + +public: + Voronoi_area( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + typedef typename boost::graph_traits::in_edge_iterator in_edge_iterator; + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + typedef VertexPointMap Point_property_map; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + double operator()(vertex_descriptor v0) { + + // return 1.0; + double voronoi_area = 0.0; + for (const halfedge_descriptor he : + halfedges_around_target(halfedge(v0, pmesh()), pmesh())) { + + if (is_border(he, pmesh()) ) { continue; } + + CGAL_assertion(CGAL::is_triangle_mesh(pmesh())); + CGAL_assertion(v0 == target(he, pmesh())); + const vertex_descriptor v1 = source(he, pmesh()); + const vertex_descriptor v_op = target(next(he, pmesh()), pmesh()); + + const Point& v0_p = get(ppmap(), v0); + const Point& v1_p = get(ppmap(), v1); + const Point& v_op_p = get(ppmap(), v_op); + + // (?) Check if there is a better way to predicate triangle is obtuse or not. + const CGAL::Angle angle0 = CGAL::angle(v1_p, v0_p, v_op_p); + const CGAL::Angle angle1 = CGAL::angle(v_op_p, v1_p, v0_p); + const CGAL::Angle angle_op = CGAL::angle(v0_p, v_op_p, v1_p); + + bool obtuse = + (angle0 == CGAL::OBTUSE) || + (angle1 == CGAL::OBTUSE) || + (angle_op == CGAL::OBTUSE); + + if (!obtuse) { + const double cot_v1 = CotangentValue::operator()(v_op, v1, v0); + const double cot_v_op = CotangentValue::operator()(v0, v_op, v1); + + const double term1 = cot_v1 * to_double((v_op_p - v0_p).squared_length()); + const double term2 = cot_v_op * to_double((v1_p - v0_p).squared_length()); + voronoi_area += (1.0 / 8.0) * (term1 + term2); + + } else { + const double area_t = to_double( + CGAL::approximate_sqrt( + CGAL::squared_area(v0_p, v1_p, v_op_p))); + + if (angle0 == CGAL::OBTUSE) { + voronoi_area += area_t / 2.0; + } else { + voronoi_area += area_t / 4.0; + } + } + } + CGAL_warning_msg(voronoi_area != 0.0, "Zero Voronoi area!"); + return voronoi_area; + } +}; + +// Returns the cotangent value of the half angle [v0, v1, v2] by dividing the triangle area +// as suggested by -[Mullen08] Spectral Conformal Parameterization-. +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Cotangent_value_area_weighted : CotangentValue { + + Cotangent_value_area_weighted() + { } + +public: + Cotangent_value_area_weighted( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + double operator()( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + return CotangentValue::operator()(v0, v1, v2) / + CGAL::sqrt(CGAL::squared_area( + get(this->ppmap(), v0), + get(this->ppmap(), v1), + get(this->ppmap(), v2))); + } +}; + +// Cotangent weight calculator: +// Cotangent_value: as suggested by -[Sorkine07] ARAP Surface Modeling-. +// Cotangent_value_area_weighted: as suggested by -[Mullen08] Spectral Conformal Parameterization-. +template< +typename PolygonMesh, +typename CotangentValue = Cotangent_value_minimum_zero_impl > +struct Cotangent_weight_impl : CotangentValue { + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + // Returns the cotangent weight of the specified halfedge_descriptor. + // Edge orientation is trivial. + template + double operator()( + halfedge_descriptor he, + PolygonMesh& pmesh, + const VertexPointMap& ppmap) { + + const vertex_descriptor v0 = target(he, pmesh); + const vertex_descriptor v1 = source(he, pmesh); + + // Only one triangle for border edges. + if (is_border_edge(he, pmesh)) { + + const halfedge_descriptor he_cw = opposite(next(he, pmesh), pmesh); + vertex_descriptor v2 = source(he_cw, pmesh); + if (is_border_edge(he_cw, pmesh)) { + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh), pmesh); + v2 = source(he_ccw, pmesh); + } + return (CotangentValue::operator()(v0, v2, v1, ppmap) / 2.0); + + } else { + const halfedge_descriptor he_cw = opposite(next(he, pmesh), pmesh); + const vertex_descriptor v2 = source(he_cw, pmesh); + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh), pmesh); + const vertex_descriptor v3 = source(he_ccw, pmesh); + + return ( + CotangentValue::operator()(v0, v2, v1, ppmap) / 2.0 + + CotangentValue::operator()(v0, v3, v1, ppmap) / 2.0 ); + } + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_minimum_zero > +class Cotangent_weight : CotangentValue { + + Cotangent_weight() + { } + +public: + Cotangent_weight( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + Cotangent_weight(PolygonMesh& pmesh_) : + CotangentValue(pmesh_, get(CGAL::vertex_point, pmesh_)) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + typedef typename boost::property_map::type Point_property_map; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + // Returns the cotangent weight of the specified halfedge_descriptor. + // Edge orientation is trivial. + double operator()(halfedge_descriptor he) { + const vertex_descriptor v0 = target(he, pmesh()); + const vertex_descriptor v1 = source(he, pmesh()); + + // Only one triangle for border edges. + if (is_border_edge(he, pmesh())) { + + const halfedge_descriptor he_cw = opposite(next(he, pmesh()), pmesh()); + vertex_descriptor v2 = source(he_cw, pmesh()); + if (is_border_edge(he_cw, pmesh())) { + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh()), pmesh()); + v2 = source(he_ccw, pmesh()); + } + return (CotangentValue::operator()(v0, v2, v1) / 2.0); + + } else { + const halfedge_descriptor he_cw = opposite(next(he, pmesh()), pmesh()); + const vertex_descriptor v2 = source(he_cw, pmesh()); + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh()), pmesh()); + const vertex_descriptor v3 = source(he_ccw, pmesh()); + + return ( + CotangentValue::operator()(v0, v2, v1) / 2.0 + + CotangentValue::operator()(v0, v3, v1) / 2.0 ); + } + } +}; + +// Single cotangent from -[Chao10] Simple Geometric Model for Elastic Deformation. +template< +typename PolygonMesh, +typename CotangentValue = Cotangent_value_Meyer_impl > +struct Single_cotangent_weight_impl : CotangentValue { + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + // Returns the cotangent of the opposite angle of the edge + // 0 for border edges (which does not have an opposite angle). + template + double operator()( + halfedge_descriptor he, + PolygonMesh& pmesh, + const VertexPointMap& ppmap) { + + if (is_border(he, pmesh)) { return 0.0; } + + const vertex_descriptor v0 = target(he, pmesh); + const vertex_descriptor v1 = source(he, pmesh); + const vertex_descriptor v_op = target(next(he, pmesh), pmesh); + return CotangentValue::operator()(v0, v_op, v1, ppmap); + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Single_cotangent_weight : CotangentValue { + + Single_cotangent_weight() + { } + +public: + Single_cotangent_weight( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + typedef typename boost::property_map::type Point_property_map; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + // Returns the cotangent of the opposite angle of the edge + // 0 for border edges (which does not have an opposite angle). + double operator()(halfedge_descriptor he) { + + if (is_border(he, pmesh())) { return 0.0; } + + const vertex_descriptor v0 = target(he, pmesh()); + const vertex_descriptor v1 = source(he, pmesh()); + const vertex_descriptor v_op = target(next(he, pmesh()), pmesh()); + return CotangentValue::operator()(v0, v_op, v1); + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type, +typename CotangentValue = Cotangent_value_Meyer > +class Cotangent_weight_with_triangle_area : CotangentValue { + + typedef PolygonMesh PM; + typedef VertexPointMap VPMap; + typedef typename boost::property_traits::value_type Point; + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + Cotangent_weight_with_triangle_area() + { } + +public: + Cotangent_weight_with_triangle_area( + PolygonMesh& pmesh_, + VertexPointMap vpmap_) : + CotangentValue(pmesh_, vpmap_) + { } + + PolygonMesh& pmesh() { + return CotangentValue::pmesh(); + } + + VertexPointMap& ppmap() { + return CotangentValue::ppmap(); + } + + double operator()(halfedge_descriptor he) { + const vertex_descriptor v0 = target(he, pmesh()); + const vertex_descriptor v1 = source(he, pmesh()); + + // Only one triangle for border edges. + if (is_border_edge(he, pmesh())) { + const halfedge_descriptor he_cw = opposite(next(he, pmesh()), pmesh()); + vertex_descriptor v2 = source(he_cw, pmesh()); + if (is_border_edge(he_cw, pmesh())) { + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh()), pmesh()); + v2 = source(he_ccw, pmesh()); + } + + const Point& v0_p = get(ppmap(), v0); + const Point& v1_p = get(ppmap(), v1); + const Point& v2_p = get(ppmap(), v2); + const double area_t = to_double( + CGAL::sqrt(CGAL::squared_area(v0_p, v1_p, v2_p))); + return (CotangentValue::operator()(v0, v2, v1) / area_t); + + } else { + const halfedge_descriptor he_cw = opposite(next(he, pmesh()), pmesh()); + const vertex_descriptor v2 = source(he_cw, pmesh()); + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh()), pmesh()); + const vertex_descriptor v3 = source(he_ccw, pmesh()); + + const Point& v0_p = get(ppmap(), v0); + const Point& v1_p = get(ppmap(), v1); + const Point& v2_p = get(ppmap(), v2); + const Point& v3_p = get(ppmap(), v3); + const double area_t1 = to_double(CGAL::sqrt(CGAL::squared_area(v0_p, v1_p, v2_p))); + const double area_t2 = to_double(CGAL::sqrt(CGAL::squared_area(v0_p, v1_p, v3_p))); + + return ( + CotangentValue::operator()(v0, v2, v1) / area_t1 + + CotangentValue::operator()(v0, v3, v1) / area_t2 ); + } + return 0.0; + } +}; + +// Mean value calculator described in -[Floater04] Mean Value Coordinates- +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type> +class Mean_value_weight { + + // Mean_value_weight() + // {} + + PolygonMesh& pmesh_; + VertexPointMap vpmap_; + +public: + Mean_value_weight( + PolygonMesh& pmesh_, + VertexPointMap vpmap) : + pmesh_(pmesh_), + vpmap_(vpmap) + { } + + PolygonMesh& pmesh() { + return pmesh_; + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + typedef VertexPointMap Point_property_map; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + // Returns the mean-value coordinate of the specified halfedge_descriptor. + // Returns different value for different edge orientation (which is a normal + // behavior according to the formula). + double operator()(halfedge_descriptor he) { + + const vertex_descriptor v0 = target(he, pmesh()); + const vertex_descriptor v1 = source(he, pmesh()); + const Vector vec = get(vpmap_, v0) - get(vpmap_, v1); + const double norm = CGAL::sqrt(vec.squared_length()); + + // Only one triangle for border edges. + if (is_border_edge(he, pmesh())) { + + const halfedge_descriptor he_cw = opposite(next(he, pmesh()), pmesh()); + vertex_descriptor v2 = source(he_cw, pmesh()); + if (is_border_edge(he_cw, pmesh())) { + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh()), pmesh()); + v2 = source(he_ccw, pmesh()); + } + return (half_tan_value_2(v1, v0, v2) / norm); + + } else { + const halfedge_descriptor he_cw = opposite(next(he, pmesh()), pmesh()); + const vertex_descriptor v2 = source(he_cw, pmesh()); + const halfedge_descriptor he_ccw = prev(opposite(he, pmesh()), pmesh()); + const vertex_descriptor v3 = source(he_ccw, pmesh()); + return ( + half_tan_value_2(v1, v0, v2) / norm + + half_tan_value_2(v1, v0, v3) / norm); + } + } + +private: + // Returns the tangent value of the half angle v0_v1_v2 / 2. + double half_tan_value( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + const Vector vec0 = get(vpmap_, v1) - get(vpmap_, v2); + const Vector vec1 = get(vpmap_, v2) - get(vpmap_, v0); + const Vector vec2 = get(vpmap_, v0) - get(vpmap_, v1); + const double e0_square = vec0.squared_length(); + const double e1_square = vec1.squared_length(); + const double e2_square = vec2.squared_length(); + const double e0 = CGAL::sqrt(e0_square); + const double e2 = CGAL::sqrt(e2_square); + double cos_angle = (e0_square + e2_square - e1_square) / 2.0 / e0 / e2; + cos_angle = (std::max)(-1.0, (std::min)(1.0, cos_angle)); // clamp into [-1, 1] + const double angle = acos(cos_angle); + return (tan(angle / 2.0)); + } + + // My deviation built on Meyer_02. + double half_tan_value_2( + vertex_descriptor v0, + vertex_descriptor v1, + vertex_descriptor v2) { + + const Vector a = get(vpmap_, v0) - get(vpmap_, v1); + const Vector b = get(vpmap_, v2) - get(vpmap_, v1); + const double dot_ab = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + const double dot_aa = a.squared_length(); + const double dot_bb = b.squared_length(); + const double dot_aa_bb = dot_aa * dot_bb; + + const double cos_rep = dot_ab; + const double sin_rep = CGAL::sqrt(dot_aa_bb - dot_ab * dot_ab); + const double normalizer = CGAL::sqrt(dot_aa_bb); // |a| * |b| + + // The formula from [Floater04] page 4: + // tan(Q/2) = (1 - cos(Q)) / sin(Q). + return (normalizer - cos_rep) / sin_rep; + } +}; + +template< +typename PolygonMesh, +typename PrimaryWeight = Cotangent_weight, +typename SecondaryWeight = Mean_value_weight > +class Hybrid_weight : public PrimaryWeight, SecondaryWeight { + + PrimaryWeight primary; + SecondaryWeight secondary; + + Hybrid_weight() + { } + +public: + Hybrid_weight(PolygonMesh& pmesh_) : + primary(pmesh_), + secondary(pmesh_) + { } + + PolygonMesh& pmesh() { + return primary.pmesh(); + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + double operator()(halfedge_descriptor he) { + + const double weight = primary(he); + // if (weight < 0.0) { std::cout << "Negative weight!" << std::endl; } + return (weight >= 0.0) ? weight : secondary(he); + } +}; + +// Trivial uniform weights (created for test purposes). +template +class Uniform_weight { +public: + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + + double operator()(halfedge_descriptor /* e */) + { return 1.0; } +}; + +template +class Scale_dependent_weight_fairing { + + PolygonMesh& pmesh_; + +public: + Scale_dependent_weight_fairing(PolygonMesh& pmesh_) : + pmesh_(pmesh_) + { } + + PolygonMesh& pmesh() { + return pmesh_; + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + typedef typename boost::property_map::type Point_property_map; + typedef typename boost::property_traits::value_type Point; + typedef typename Kernel_traits::Kernel::Vector_3 Vector; + + double w_i(vertex_descriptor /* v_i */) { return 1.0; } + + double w_ij(halfedge_descriptor he) { + + const Vector v = target(he, pmesh())->point() - source(he, pmesh())->point(); + const double divider = CGAL::sqrt(v.squared_length()); + if (divider == 0.0) { + CGAL_warning_msg(false, "Scale dependent weight - zero length edge."); + return (std::numeric_limits::max)(); + } + return 1.0 / divider; + } +}; + +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type> +class Cotangent_weight_with_voronoi_area_fairing { + + typedef PolygonMesh PM; + typedef VertexPointMap VPMap; + Voronoi_area voronoi_functor; + Cotangent_weight > cotangent_functor; + +public: + Cotangent_weight_with_voronoi_area_fairing(PM& pmesh_) : + voronoi_functor(pmesh_, get(CGAL::vertex_point, pmesh_)), + cotangent_functor(pmesh_, get(CGAL::vertex_point, pmesh_)) + { } + + Cotangent_weight_with_voronoi_area_fairing( + PM& pmesh_, + VPMap vpmap_) : + voronoi_functor(pmesh_, vpmap_), + cotangent_functor(pmesh_, vpmap_) + { } + + PM& pmesh() { + return voronoi_functor.pmesh(); + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + double w_i(vertex_descriptor v_i) { + return 0.5 / voronoi_functor(v_i); + } + + double w_ij(halfedge_descriptor he) { + return cotangent_functor(he) * 2.0; + } +}; + +// Cotangent_value_Meyer has been changed to the version: +// Cotangent_value_Meyer_secure to avoid imprecisions from +// the issue #4706 - https://github.com/CGAL/cgal/issues/4706. +template< +typename PolygonMesh, +typename VertexPointMap = typename boost::property_map::type> +class Cotangent_weight_with_voronoi_area_fairing_secure { + + typedef PolygonMesh PM; + typedef VertexPointMap VPMap; + Voronoi_area voronoi_functor; + Cotangent_weight > cotangent_functor; + +public: + Cotangent_weight_with_voronoi_area_fairing_secure(PM& pmesh_) : + voronoi_functor(pmesh_, get(CGAL::vertex_point, pmesh_)), + cotangent_functor(pmesh_, get(CGAL::vertex_point, pmesh_)) + { } + + Cotangent_weight_with_voronoi_area_fairing_secure( + PM& pmesh_, + VPMap vpmap_) : + voronoi_functor(pmesh_, vpmap_), + cotangent_functor(pmesh_, vpmap_) + { } + + PM& pmesh() { + return voronoi_functor.pmesh(); + } + + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + double w_i(vertex_descriptor v_i) { + return 0.5 / voronoi_functor(v_i); + } + + double w_ij(halfedge_descriptor he) { + return cotangent_functor(he) * 2.0; + } +}; + +template +class Uniform_weight_fairing { + +public: + typedef typename boost::graph_traits::halfedge_descriptor halfedge_descriptor; + typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; + + Uniform_weight_fairing(PolygonMesh&) + { } + + double w_ij(halfedge_descriptor /* e */) { return 1.0; } + double w_i(vertex_descriptor /* v_i */) { return 1.0; } +}; + +/// \endcond + +} // namespace deprecated +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_WEIGHTS_PMP_DEPRECATED_H diff --git a/Weights/include/CGAL/Weights/internal/polygon_utils_2.h b/Weights/include/CGAL/Weights/internal/polygon_utils_2.h new file mode 100644 index 000000000000..46c4efc48f43 --- /dev/null +++ b/Weights/include/CGAL/Weights/internal/polygon_utils_2.h @@ -0,0 +1,371 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_WEIGHTS_INTERNAL_POLYGON_UTILS_2_H +#define CGAL_WEIGHTS_INTERNAL_POLYGON_UTILS_2_H + +#include + +// STL includes. +#include +#include +#include +#include +#include +#include +#include + +// CGAL includes. +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Weights { +namespace internal { + + enum class Edge_case { + + EXTERIOR = 0, // exterior part of the polygon + BOUNDARY = 1, // boundary part of the polygon + INTERIOR = 2 // interior part of the polygon + }; + + // VertexRange type enum. + enum class Polygon_type { + + // Concave polygon = non-convex polygon. + CONCAVE = 0, + + // This is a convex polygon with collinear vertices. + WEAKLY_CONVEX = 1, + + // This is a convex polygon without collinear vertices. + STRICTLY_CONVEX = 2 + }; + + // This function is taken from the Polygon_2_algorithms.h header. + // But it is updated to support property maps. + template< + class Point_2, + class Orientation_2, + class CompareX_2> + int which_side_in_slab_2( + const Point_2& query, const Point_2& low, const Point_2& high, + const Orientation_2& orientation_2, const CompareX_2& compare_x_2) { + + const auto low_x_comp_res = compare_x_2(query, low); + const auto high_x_comp_res = compare_x_2(query, high); + if (low_x_comp_res == CGAL::SMALLER) { + if (high_x_comp_res == CGAL::SMALLER) { + return -1; + } + } else { + switch (high_x_comp_res) { + case CGAL::LARGER: return 1; + case CGAL::SMALLER: break; + case CGAL::EQUAL: return (low_x_comp_res == CGAL::EQUAL) ? 0 : 1; + } + } + switch (orientation_2(low, query, high)) { + case CGAL::LEFT_TURN: return 1; + case CGAL::RIGHT_TURN: return -1; + default: return 0; + } + } + + // This function is taken from the Polygon_2_algorithms.h header. + // But it is updated to support property maps. + template< + typename VertexRange, + typename GeomTraits, + typename PointMap> + Edge_case bounded_side_2( + const VertexRange& polygon, const typename GeomTraits::Point_2& query, + const GeomTraits& traits, const PointMap point_map) { + + const auto first = polygon.begin(); + const auto last = polygon.end(); + + auto curr = first; + if (curr == last) { + return Edge_case::EXTERIOR; + } + + auto next = curr; ++next; + if (next == last) { + return Edge_case::EXTERIOR; + } + + const auto compare_x_2 = traits.compare_x_2_object(); + const auto compare_y_2 = traits.compare_y_2_object(); + const auto orientation_2 = traits.orientation_2_object(); + + bool is_inside = false; + auto curr_y_comp_res = compare_y_2(get(point_map, *curr), query); + + // Check if the segment (curr, next) intersects + // the ray { (t, query.y()) | t >= query.x() }. + do { + const auto& currp = get(point_map, *curr); + const auto& nextp = get(point_map, *next); + + auto next_y_comp_res = compare_y_2(nextp, query); + switch (curr_y_comp_res) { + case CGAL::SMALLER: + switch (next_y_comp_res) { + case CGAL::SMALLER: + break; + case CGAL::EQUAL: + switch (compare_x_2(query, nextp)) { + case CGAL::SMALLER: is_inside = !is_inside; break; + case CGAL::EQUAL: return Edge_case::BOUNDARY; + case CGAL::LARGER: break; + } + break; + case CGAL::LARGER: + switch (which_side_in_slab_2( + query, currp, nextp, orientation_2, compare_x_2)) { + case -1: is_inside = !is_inside; break; + case 0: return Edge_case::BOUNDARY; + } + break; + } + break; + case CGAL::EQUAL: + switch (next_y_comp_res) { + case CGAL::SMALLER: + switch (compare_x_2(query, currp)) { + case CGAL::SMALLER: is_inside = !is_inside; break; + case CGAL::EQUAL: return Edge_case::BOUNDARY; + case CGAL::LARGER: break; + } + break; + case CGAL::EQUAL: + switch (compare_x_2(query, currp)) { + case CGAL::SMALLER: + if (compare_x_2(query, nextp) != CGAL::SMALLER) { + return Edge_case::BOUNDARY; + } + break; + case CGAL::EQUAL: return Edge_case::BOUNDARY; + case CGAL::LARGER: + if (compare_x_2(query, nextp) != CGAL::LARGER) { + return Edge_case::BOUNDARY; + } + break; + } + break; + case CGAL::LARGER: + if (compare_x_2(query, currp) == CGAL::EQUAL) { + return Edge_case::BOUNDARY; + } + break; + } + break; + case CGAL::LARGER: + switch (next_y_comp_res) { + case CGAL::SMALLER: + switch (which_side_in_slab_2( + query, nextp, currp, orientation_2, compare_x_2)) { + case -1: is_inside = !is_inside; break; + case 0: return Edge_case::BOUNDARY; + } + break; + case CGAL::EQUAL: + if (compare_x_2(query, nextp) == CGAL::EQUAL) { + return Edge_case::BOUNDARY; + } + break; + case CGAL::LARGER: + break; + } + break; + } + + curr = next; + curr_y_comp_res = next_y_comp_res; + ++next; + if (next == last) { + next = first; + } + } while (curr != first); + return is_inside ? Edge_case::INTERIOR : Edge_case::EXTERIOR; + } + + // This function is taken from the Polygon_2_algorithms.h header. + // But it is updated to support property maps. + template< + typename VertexRange, + typename GeomTraits, + typename PointMap> + bool is_convex_2( + const VertexRange& polygon, const GeomTraits traits, const PointMap point_map) { + + auto first = polygon.begin(); + const auto last = polygon.end(); + + auto prev = first; + if (prev == last) { + return true; + } + + auto curr = prev; ++curr; + if (curr == last) { + return true; + } + + auto next = curr; ++next; + if (next == last) { + return true; + } + + const auto equal_2 = traits.equal_2_object(); + while (equal_2(get(point_map, *prev), get(point_map, *curr))) { + curr = next; ++next; + if (next == last) { + return true; + } + } + + const auto less_xy_2 = traits.less_xy_2_object(); + const auto orientation_2 = traits.orientation_2_object(); + + bool has_clockwise_triplets = false; + bool has_counterclockwise_triplets = false; + bool order = less_xy_2( + get(point_map, *prev), get(point_map, *curr)); + int num_order_changes = 0; + + do { + switch_orient: + switch (orientation_2( + get(point_map, *prev), get(point_map, *curr), get(point_map, *next))) { + + case CGAL::CLOCKWISE: + has_clockwise_triplets = true; + break; + case CGAL::COUNTERCLOCKWISE: + has_counterclockwise_triplets = true; + break; + case CGAL::ZERO: { + if (equal_2( + get(point_map, *curr), + get(point_map, *next))) { + + if (next == first) { + first = curr; + } + ++next; + if (next == last) { + next = first; + } + goto switch_orient; + } + break; + } + } + + const bool new_order = less_xy_2( + get(point_map, *curr), get(point_map, *next)); + + if (order != new_order) { + num_order_changes++; + } + + if (num_order_changes > 2) { + return false; + } + + if (has_clockwise_triplets && has_counterclockwise_triplets) { + return false; + } + + prev = curr; + curr = next; + ++next; + if (next == last) { + next = first; + } + order = new_order; + } while (prev != first); + return true; + } + + // This function is taken from the Polygon_2_algorithms.h header. + // But it is updated to support property maps. + template< + typename VertexRange, + typename GeomTraits, + typename PointMap> + bool is_simple_2( + const VertexRange& polygon, const GeomTraits traits, const PointMap point_map) { + + const auto first = polygon.begin(); + const auto last = polygon.end(); + if (first == last) { + return true; + } + + std::vector poly; + poly.reserve(polygon.size()); + for (const auto& vertex : polygon) { + poly.push_back(get(point_map, vertex)); + } + return CGAL::is_simple_2(poly.begin(), poly.end(), traits); + } + + template< + typename VertexRange, + typename GeomTraits, + typename PointMap> + Polygon_type polygon_type_2( + const VertexRange& polygon, const GeomTraits traits, const PointMap point_map) { + + const auto collinear_2 = + traits.collinear_2_object(); + CGAL_precondition(polygon.size() >= 3); + + // First, test the polygon on convexity. + if (is_convex_2(polygon, traits, point_map)) { + + // Test all the consequent triplets of polygon vertices on collinearity. + // In case we find at least one, return WEAKLY_CONVEX polygon. + const std::size_t n = polygon.size(); + for (std::size_t i = 0; i < n; ++i) { + const auto& p1 = get(point_map, *(polygon.begin() + i)); + + const std::size_t im = (i + n - 1) % n; + const std::size_t ip = (i + 1) % n; + + const auto& p0 = get(point_map, *(polygon.begin() + im)); + const auto& p2 = get(point_map, *(polygon.begin() + ip)); + + if (collinear_2(p0, p1, p2)) { + return Polygon_type::WEAKLY_CONVEX; + } + } + // Otherwise, return STRICTLY_CONVEX polygon. + return Polygon_type::STRICTLY_CONVEX; + } + // Otherwise, return CONCAVE polygon. + return Polygon_type::CONCAVE; + } + +} // namespace internal +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_WEIGHTS_INTERNAL_POLYGON_UTILS_2_H diff --git a/Weights/include/CGAL/Weights/internal/utils.h b/Weights/include/CGAL/Weights/internal/utils.h new file mode 100644 index 000000000000..0786d605ff76 --- /dev/null +++ b/Weights/include/CGAL/Weights/internal/utils.h @@ -0,0 +1,736 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_WEIGHTS_INTERNAL_UTILS_H +#define CGAL_WEIGHTS_INTERNAL_UTILS_H + +#include + +// STL includes. +#include +#include +#include +#include +#include +#include +#include +#include + +// Boost headers. +#include +#include + +// CGAL includes. +#include +#include +#include +#include +#include +#include +#include + +namespace CGAL { +namespace Weights { +namespace internal { + + // Sqrt helpers. + template + class Default_sqrt { + + private: + using Traits = GeomTraits; + using FT = typename Traits::FT; + + public: + FT operator()(const FT value) const { + return static_cast( + CGAL::sqrt(CGAL::to_double(CGAL::abs(value)))); + } + }; + + BOOST_MPL_HAS_XXX_TRAIT_NAMED_DEF(Has_nested_type_Sqrt, Sqrt, false) + + // Case: do_not_use_default = false. + template::value> + class Get_sqrt { + + public: + using Traits = GeomTraits; + using Sqrt = Default_sqrt; + + static Sqrt sqrt_object(const Traits& ) { + return Sqrt(); + } + }; + + // Case: do_not_use_default = true. + template + class Get_sqrt { + + public: + using Traits = GeomTraits; + using Sqrt = typename Traits::Sqrt; + + static Sqrt sqrt_object(const Traits& traits) { + return traits.sqrt_object(); + } + }; + + // Normalize values. + template + void normalize(std::vector& values) { + + FT sum = FT(0); + for (const FT& value : values) { + sum += value; + } + + CGAL_assertion(sum != FT(0)); + if (sum == FT(0)) { + return; + } + + const FT inv_sum = FT(1) / sum; + for (FT& value : values) { + value *= inv_sum; + } + } + + // Raises value to the power. + template + typename GeomTraits::FT power( + const GeomTraits&, + const typename GeomTraits::FT value, + const typename GeomTraits::FT p) { + + using FT = typename GeomTraits::FT; + const double base = CGAL::to_double(value); + const double exp = CGAL::to_double(p); + return static_cast(std::pow(base, exp)); + } + + // Computes distance between two 2D points. + template + typename GeomTraits::FT distance_2( + const GeomTraits& traits, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q) { + + using Get_sqrt = Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(traits); + + const auto squared_distance_2 = + traits.compute_squared_distance_2_object(); + return sqrt(squared_distance_2(p, q)); + } + + // Computes length of a 2D vector. + template + typename GeomTraits::FT length_2( + const GeomTraits& traits, + const typename GeomTraits::Vector_2& v) { + + using Get_sqrt = Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(traits); + + const auto squared_length_2 = + traits.compute_squared_length_2_object(); + return sqrt(squared_length_2(v)); + } + + // Normalizes a 2D vector. + template + void normalize_2( + const GeomTraits& traits, + typename GeomTraits::Vector_2& v) { + + using FT = typename GeomTraits::FT; + const FT length = length_2(traits, v); + CGAL_assertion(length != FT(0)); + if (length == FT(0)) { + return; + } + v /= length; + } + + // Computes cotanget between two 2D vectors. + template + typename GeomTraits::FT cotangent_2( + const GeomTraits& traits, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r) { + + using FT = typename GeomTraits::FT; + const auto dot_product_2 = + traits.compute_scalar_product_2_object(); + const auto cross_product_2 = + traits.compute_determinant_2_object(); + const auto construct_vector_2 = + traits.construct_vector_2_object(); + + const auto v1 = construct_vector_2(q, r); + const auto v2 = construct_vector_2(q, p); + + const FT dot = dot_product_2(v1, v2); + const FT cross = cross_product_2(v1, v2); + + const FT length = CGAL::abs(cross); + CGAL_assertion(length != FT(0)); + if (length != FT(0)) { + return dot / length; + } else { + return FT(0); // undefined + } + } + + // Computes tanget between two 2D vectors. + template + typename GeomTraits::FT tangent_2( + const GeomTraits& traits, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r) { + + using FT = typename GeomTraits::FT; + const auto dot_product_2 = + traits.compute_scalar_product_2_object(); + const auto cross_product_2 = + traits.compute_determinant_2_object(); + const auto construct_vector_2 = + traits.construct_vector_2_object(); + + const auto v1 = construct_vector_2(q, r); + const auto v2 = construct_vector_2(q, p); + + const FT dot = dot_product_2(v1, v2); + const FT cross = cross_product_2(v1, v2); + + const FT length = CGAL::abs(cross); + CGAL_assertion(dot != FT(0)); + if (dot != FT(0)) { + return length / dot; + } else { + return FT(0); // undefined + } + } + + // Computes distance between two 3D points. + template + typename GeomTraits::FT distance_3( + const GeomTraits& traits, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q) { + + using Get_sqrt = Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(traits); + + const auto squared_distance_3 = + traits.compute_squared_distance_3_object(); + return sqrt(squared_distance_3(p, q)); + } + + // Computes length of a 3D vector. + template + typename GeomTraits::FT length_3( + const GeomTraits& traits, + const typename GeomTraits::Vector_3& v) { + + using Get_sqrt = Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(traits); + + const auto squared_length_3 = + traits.compute_squared_length_3_object(); + return sqrt(squared_length_3(v)); + } + + // Normalizes a 3D vector. + template + void normalize_3( + const GeomTraits& traits, + typename GeomTraits::Vector_3& v) { + + using FT = typename GeomTraits::FT; + const FT length = length_3(traits, v); + CGAL_assertion(length != FT(0)); + if (length == FT(0)) { + return; + } + v /= length; + } + + // Computes cotanget between two 3D vectors. + template + typename GeomTraits::FT cotangent_3( + const GeomTraits& traits, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r) { + + using FT = typename GeomTraits::FT; + const auto dot_product_3 = + traits.compute_scalar_product_3_object(); + const auto cross_product_3 = + traits.construct_cross_product_vector_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v1 = construct_vector_3(q, r); + const auto v2 = construct_vector_3(q, p); + + const FT dot = dot_product_3(v1, v2); + const auto cross = cross_product_3(v1, v2); + + const FT length = length_3(traits, cross); + CGAL_assertion(length != FT(0)); + if (length != FT(0)) { + return dot / length; + } else { + return FT(0); // undefined + } + } + + // Computes tanget between two 3D vectors. + template + typename GeomTraits::FT tangent_3( + const GeomTraits& traits, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r) { + + using FT = typename GeomTraits::FT; + const auto dot_product_3 = + traits.compute_scalar_product_3_object(); + const auto cross_product_3 = + traits.construct_cross_product_vector_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v1 = construct_vector_3(q, r); + const auto v2 = construct_vector_3(q, p); + + const FT dot = dot_product_3(v1, v2); + const auto cross = cross_product_3(v1, v2); + + const FT length = length_3(traits, cross); + CGAL_assertion(dot != FT(0)); + if (dot != FT(0)) { + return length / dot; + } else { + return FT(0); // undefined + } + } + + // Computes 3D angle between two vectors. + template + double angle_3( + const GeomTraits& traits, + const typename GeomTraits::Vector_3& v1, + const typename GeomTraits::Vector_3& v2) { + + const auto dot_product_3 = + traits.compute_scalar_product_3_object(); + const double dot = + CGAL::to_double(dot_product_3(v1, v2)); + + double angle_rad = 0.0; + if (dot < -1.0) { + angle_rad = std::acos(-1.0); + } else if (dot > 1.0) { + angle_rad = std::acos(+1.0); + } else { + angle_rad = std::acos(dot); + } + return angle_rad; + } + + // Rotates a 3D point around axis. + template + typename GeomTraits::Point_3 rotate_point_3( + const GeomTraits&, + const double angle_rad, + const typename GeomTraits::Vector_3& axis, + const typename GeomTraits::Point_3& query) { + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + + const FT c = static_cast(std::cos(angle_rad)); + const FT s = static_cast(std::sin(angle_rad)); + const FT C = FT(1) - c; + + const auto x = axis.x(); + const auto y = axis.y(); + const auto z = axis.z(); + + return Point_3( + (x * x * C + c) * query.x() + + (x * y * C - z * s) * query.y() + + (x * z * C + y * s) * query.z(), + (y * x * C + z * s) * query.x() + + (y * y * C + c) * query.y() + + (y * z * C - x * s) * query.z(), + (z * x * C - y * s) * query.x() + + (z * y * C + x * s) * query.y() + + (z * z * C + c) * query.z()); + } + + // Computes two 3D orthogonal base vectors wrt a given normal. + template + void orthogonal_bases_3( + const GeomTraits& traits, + const typename GeomTraits::Vector_3& normal, + typename GeomTraits::Vector_3& b1, + typename GeomTraits::Vector_3& b2) { + + using Vector_3 = typename GeomTraits::Vector_3; + const auto cross_product_3 = + traits.construct_cross_product_vector_3_object(); + + const auto nx = normal.x(); + const auto ny = normal.y(); + const auto nz = normal.z(); + + if (CGAL::abs(nz) >= CGAL::abs(ny)) { + b1 = Vector_3(nz, 0, -nx); + } else { + b1 = Vector_3(ny, -nx, 0); + } + b2 = cross_product_3(normal, b1); + + normalize_3(traits, b1); + normalize_3(traits, b2); + } + + // Converts a 3D point into a 2D point wrt to a given plane. + template + typename GeomTraits::Point_2 to_2d( + const GeomTraits& traits, + const typename GeomTraits::Vector_3& b1, + const typename GeomTraits::Vector_3& b2, + const typename GeomTraits::Point_3& origin, + const typename GeomTraits::Point_3& query) { + + using Point_2 = typename GeomTraits::Point_2; + const auto dot_product_3 = + traits.compute_scalar_product_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v = construct_vector_3(origin, query); + const auto x = dot_product_3(b1, v); + const auto y = dot_product_3(b2, v); + return Point_2(x, y); + } + + // Flattening. + + // \cgalFigureBegin{flattening, flattening.svg} + // The non-planar configuration (top) is flattened to the planar configuration (bottom). + // \cgalFigureEnd + + // When computing weights for a query point \f$q\f$ with respect to its neighbors + // \f$p_0\f$, \f$p_1\f$, and \f$p_2\f$, the local configuration is a quadrilateral + // [\f$p_0\f$, \f$p_1\f$, \f$p_2\f$, \f$q\f$] or two connected triangles [\f$q\f$, \f$p_0\f$, \f$p_1\f$] + // and [\f$q\f$, \f$p_1\f$, \f$p_2\f$]. When working in 3D, these triangles are not + // necessarily coplanar, in other words, they do not belong to the same common plane. + // When they are not coplanar, they can be made coplanar through the process called *flattening* (see the Figure above), + // however the latter introduces a distortion because the weights are computed with respect to the + // flattened configuration rather than to the original non-flat configuration. + + // \subsection Weights_Examples_ProjectionTraits Computing 2D Weights in 3D + + // If you have a 2D polygon in 3D plane that is not an XY plane, you can still compute + // the 2D weights, however you need to provide a special projection traits class. + // The common plane that is used in this example is projectable to the XY plane. We first + // compute `Mean_value_weights_2` for a 3D polygon in this plane. We then also show how to use + // the projection traits to compute the \ref PkgWeightsRefWachspressWeights "2D Wachspress weight" + // for 3D points which are not strictly coplanar. + + // \cgalExample{Weights/projection_traits.cpp} + + // Example of flattening: + + // 3D configuration. + // const Point_3 p0(0, 1, 1); + // const Point_3 p1(2, 0, 1); + // const Point_3 p2(7, 1, 1); + // const Point_3 q0(3, 1, 1); + + // Choose a type of the weight: + // e.g. 0 - Wachspress (WP) weight. + // const FT wp = FT(0); + + // Compute WP weights for q1 which is not on the plane [p0, p1, p2]. + + // Point_3 q1(3, 1, 2); + // std::cout << "3D wachspress (WP, q1): "; + // std::cout << CGAL::Weights::three_point_family_weight(p0, p1, p2, q1, wp) << std::endl; + + // Converge q1 towards q0 that is we flatten the configuration. + // We also compare the result with the authalic weight. + + // std::cout << "Converge q1 to q0: " << std::endl; + // for (FT x = FT(0); x <= FT(1); x += step) { + // std::cout << "3D wachspress/authalic: "; + // q1 = Point_3(3, 1, FT(2) - x); + // std::cout << CGAL::Weights::three_point_family_weight(p0, p1, p2, q1, wp) << "/"; + // std::cout << CGAL::Weights::authalic_weight(p0, p1, p2, q1) << std::endl; + // } + + // Flattens an arbitrary quad into a planar quad. + template + void flatten( + const GeomTraits& traits, + const typename GeomTraits::Point_3& t, // prev neighbor/vertex/point + const typename GeomTraits::Point_3& r, // curr neighbor/vertex/point + const typename GeomTraits::Point_3& p, // next neighbor/vertex/point + const typename GeomTraits::Point_3& q, // query point + typename GeomTraits::Point_2& tf, + typename GeomTraits::Point_2& rf, + typename GeomTraits::Point_2& pf, + typename GeomTraits::Point_2& qf) { + + // std::cout << std::endl; + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + + const auto cross_product_3 = + traits.construct_cross_product_vector_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + const auto centroid_3 = + traits.construct_centroid_3_object(); + + // Compute centroid. + const auto center = centroid_3(t, r, p, q); + // std::cout << "centroid: " << center << std::endl; + + // Translate. + const Point_3 t1 = Point_3( + t.x() - center.x(), t.y() - center.y(), t.z() - center.z()); + const Point_3 r1 = Point_3( + r.x() - center.x(), r.y() - center.y(), r.z() - center.z()); + const Point_3 p1 = Point_3( + p.x() - center.x(), p.y() - center.y(), p.z() - center.z()); + const Point_3 q1 = Point_3( + q.x() - center.x(), q.y() - center.y(), q.z() - center.z()); + + // std::cout << "translated t1: " << t1 << std::endl; + // std::cout << "translated r1: " << r1 << std::endl; + // std::cout << "translated p1: " << p1 << std::endl; + // std::cout << "translated q1: " << q1 << std::endl; + + // Middle axis. + auto ax = construct_vector_3(q1, r1); + normalize_3(traits, ax); + + // Prev and next vectors. + auto v1 = construct_vector_3(q1, t1); + auto v2 = construct_vector_3(q1, p1); + + normalize_3(traits, v1); + normalize_3(traits, v2); + + // Two triangle normals. + auto n1 = cross_product_3(v1, ax); + auto n2 = cross_product_3(ax, v2); + + normalize_3(traits, n1); + normalize_3(traits, n2); + + // std::cout << "normal n1: " << n1 << std::endl; + // std::cout << "normal n2: " << n2 << std::endl; + + // Angle between two normals. + const double angle_rad = angle_3(traits, n1, n2); + // std::cout << "angle deg n1 <-> n2: " << angle_rad * 180.0 / CGAL_PI << std::endl; + + // Rotate p1 around ax so that it lands onto the plane [q1, t1, r1]. + const auto& t2 = t1; + const auto& r2 = r1; + const auto p2 = rotate_point_3(traits, angle_rad, ax, p1); + const auto& q2 = q1; + // std::cout << "rotated p2: " << p2 << std::endl; + + // Compute orthogonal base vectors. + Vector_3 b1, b2; + const auto& normal = n1; + orthogonal_bases_3(traits, normal, b1, b2); + + // const auto angle12 = angle_3(traits, b1, b2); + // std::cout << "angle deg b1 <-> b2: " << angle12 * 180.0 / CGAL_PI << std::endl; + + // Flatten a quad. + const auto& origin = q2; + tf = to_2d(traits, b1, b2, origin, t2); + rf = to_2d(traits, b1, b2, origin, r2); + pf = to_2d(traits, b1, b2, origin, p2); + qf = to_2d(traits, b1, b2, origin, q2); + + // std::cout << "flattened qf: " << qf << std::endl; + // std::cout << "flattened tf: " << tf << std::endl; + // std::cout << "flattened rf: " << rf << std::endl; + // std::cout << "flattened pf: " << pf << std::endl; + + // std::cout << "A1: " << area_2(traits, rf, qf, pf) << std::endl; + // std::cout << "A2: " << area_2(traits, pf, qf, rf) << std::endl; + // std::cout << "C: " << area_2(traits, tf, rf, pf) << std::endl; + // std::cout << "B: " << area_2(traits, pf, qf, tf) << std::endl; + } + + // Computes area of a 2D triangle. + template + typename GeomTraits::FT area_2( + const GeomTraits& traits, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r) { + + const auto area_2 = traits.compute_area_2_object(); + return area_2(p, q, r); + } + + // Computes positive area of a 2D triangle. + template + typename GeomTraits::FT positive_area_2( + const GeomTraits& traits, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r) { + + return CGAL::abs(area_2(traits, p, q, r)); + } + + // Computes area of a 3D triangle. + template + typename GeomTraits::FT area_3( + const GeomTraits& traits, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r) { + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + using Vector_3 = typename GeomTraits::Vector_3; + + const auto cross_product_3 = + traits.construct_cross_product_vector_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + const auto centroid_3 = + traits.construct_centroid_3_object(); + + // Compute centroid. + const auto center = centroid_3(p, q, r); + + // Translate. + const Point_3 a = Point_3( + p.x() - center.x(), p.y() - center.y(), p.z() - center.z()); + const Point_3 b = Point_3( + q.x() - center.x(), q.y() - center.y(), q.z() - center.z()); + const Point_3 c = Point_3( + r.x() - center.x(), r.y() - center.y(), r.z() - center.z()); + + // Prev and next vectors. + auto v1 = construct_vector_3(b, a); + auto v2 = construct_vector_3(b, c); + normalize_3(traits, v1); + normalize_3(traits, v2); + + // Compute normal. + auto normal = cross_product_3(v1, v2); + normalize_3(traits, normal); + + // Compute orthogonal base vectors. + Vector_3 b1, b2; + orthogonal_bases_3(traits, normal, b1, b2); + + // Compute area. + const auto& origin = b; + const auto pf = to_2d(traits, b1, b2, origin, a); + const auto qf = to_2d(traits, b1, b2, origin, b); + const auto rf = to_2d(traits, b1, b2, origin, c); + + const FT A = area_2(traits, pf, qf, rf); + return A; + } + + // Computes positive area of a 3D triangle. + template + typename GeomTraits::FT positive_area_3( + const GeomTraits& traits, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r) { + + using FT = typename GeomTraits::FT; + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v1 = construct_vector_3(q, r); + const auto v2 = construct_vector_3(q, p); + + const auto cross_product_3 = + traits.construct_cross_product_vector_3_object(); + const auto cross = cross_product_3(v1, v2); + const FT half = FT(1) / FT(2); + const FT A = half * length_3(traits, cross); + return A; + } + + // Computes a clamped cotangent between two 3D vectors. + // In the old version of weights in PMP, it has been called secure. + // See Weights/internal/pmp_weights_deprecated.h for more information. + template + typename GeomTraits::FT cotangent_3_clamped( + const GeomTraits& traits, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r) { + + using FT = typename GeomTraits::FT; + using Get_sqrt = Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(traits); + + const auto dot_product_3 = + traits.compute_scalar_product_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v1 = construct_vector_3(q, r); + const auto v2 = construct_vector_3(q, p); + + const FT dot = dot_product_3(v1, v2); + const FT length_v1 = length_3(traits, v1); + const FT length_v2 = length_3(traits, v2); + + const FT lb = -FT(999) / FT(1000), ub = FT(999) / FT(1000); + FT cosine = dot / length_v1 / length_v2; + cosine = (cosine < lb) ? lb : cosine; + cosine = (cosine > ub) ? ub : cosine; + const FT sine = sqrt(FT(1) - cosine * cosine); + + CGAL_assertion(sine != FT(0)); + if (sine != FT(0)) { + return cosine / sine; + } + return FT(0); // undefined + } + +} // namespace internal +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_WEIGHTS_INTERNAL_UTILS_H diff --git a/Weights/include/CGAL/Weights/inverse_distance_weights.h b/Weights/include/CGAL/Weights/inverse_distance_weights.h new file mode 100644 index 000000000000..f2179d3ba127 --- /dev/null +++ b/Weights/include/CGAL/Weights/inverse_distance_weights.h @@ -0,0 +1,236 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_INVERSE_DISTANCE_WEIGHTS_H +#define CGAL_INVERSE_DISTANCE_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace inverse_distance_ns { + + template + FT weight(const FT d) { + + FT w = FT(0); + CGAL_precondition(d != FT(0)); + if (d != FT(0)) { + w = FT(1) / d; + } + return w; + } + } + /// \endcond + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 2D using the points `p` and `q`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 3D using the points `p` and `q`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 2D using the points `p` and `q`, + which are parameterized by a `Kernel` K. + */ + template + typename K::FT inverse_distance_weight( + const CGAL::Point_2&, + const CGAL::Point_2& p, + const CGAL::Point_2&, + const CGAL::Point_2& q) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 3D using the points `p` and `q`, + which are parameterized by a `Kernel` K. + */ + template + typename K::FT inverse_distance_weight( + const CGAL::Point_3&, + const CGAL::Point_3& p, + const CGAL::Point_3&, + const CGAL::Point_3& q) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 2D using the points `p` and `q`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 3D using the points `p` and `q`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 2D using the points `p` and `q`, + which are parameterized by a `Kernel` K. + */ + template + typename K::FT inverse_distance_weight( + const CGAL::Point_2& p, + const CGAL::Point_2& q) { } + + /*! + \ingroup PkgWeightsRefInverseDistanceWeights + + \brief computes the inverse distance weight in 3D using the points `p` and `q`, + which are parameterized by a `Kernel` K. + */ + template + typename K::FT inverse_distance_weight( + const CGAL::Point_3& p, + const CGAL::Point_3& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT d = internal::distance_2(traits, q, r); + return inverse_distance_ns::weight(d); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return inverse_distance_weight(t, r, p, q, traits); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + typename GeomTraits::Point_2 stub; + return inverse_distance_weight(stub, p, stub, q, traits); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + CGAL::Point_2 stub; + return inverse_distance_weight(stub, p, stub, q); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT d = internal::distance_3(traits, q, r); + return inverse_distance_ns::weight(d); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return inverse_distance_weight(t, r, p, q, traits); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + typename GeomTraits::Point_3 stub; + return inverse_distance_weight(stub, p, stub, q, traits); + } + + template + typename GeomTraits::FT inverse_distance_weight( + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + CGAL::Point_3 stub; + return inverse_distance_weight(stub, p, stub, q); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_INVERSE_DISTANCE_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/mean_value_weights.h b/Weights/include/CGAL/Weights/mean_value_weights.h new file mode 100644 index 000000000000..d66d9bf34873 --- /dev/null +++ b/Weights/include/CGAL/Weights/mean_value_weights.h @@ -0,0 +1,607 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov, David Bommes, Kai Hormann, Pierre Alliez +// + +#ifndef CGAL_MEAN_VALUE_WEIGHTS_H +#define CGAL_MEAN_VALUE_WEIGHTS_H + +#include + +// Internal includes. +#include +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace mean_value_ns { + + template + FT sign_of_weight(const FT A1, const FT A2, const FT B) { + + if (A1 > FT(0) && A2 > FT(0) && B <= FT(0)) { + return +FT(1); + } + if (A1 < FT(0) && A2 < FT(0) && B >= FT(0)) { + return -FT(1); + } + if (B > FT(0)) { + return +FT(1); + } + if (B < FT(0)) { + return -FT(1); + } + return FT(0); + } + + template + typename GeomTraits::FT weight( + const GeomTraits& traits, + const typename GeomTraits::FT r1, + const typename GeomTraits::FT r2, + const typename GeomTraits::FT r3, + const typename GeomTraits::FT D1, + const typename GeomTraits::FT D2, + const typename GeomTraits::FT D, + const typename GeomTraits::FT sign) { + + using FT = typename GeomTraits::FT; + using Get_sqrt = internal::Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(traits); + + const FT P1 = r1 * r2 + D1; + const FT P2 = r2 * r3 + D2; + + FT w = FT(0); + CGAL_precondition(P1 != FT(0) && P2 != FT(0)); + const FT prod = P1 * P2; + if (prod != FT(0)) { + const FT inv = FT(1) / prod; + w = FT(2) * (r1 * r3 - D) * inv; + CGAL_assertion(w >= FT(0)); + w = sqrt(w); + } + w *= FT(2); w *= sign; + return w; + } + } + /// \endcond + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefMeanValueWeights + + \brief computes the mean value weight in 2D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT mean_value_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefMeanValueWeights + + \brief computes the mean value weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT mean_value_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT mean_value_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto dot_product_2 = + traits.compute_scalar_product_2_object(); + const auto construct_vector_2 = + traits.construct_vector_2_object(); + + const auto v1 = construct_vector_2(q, t); + const auto v2 = construct_vector_2(q, r); + const auto v3 = construct_vector_2(q, p); + + const FT l1 = internal::length_2(traits, v1); + const FT l2 = internal::length_2(traits, v2); + const FT l3 = internal::length_2(traits, v3); + + const FT D1 = dot_product_2(v1, v2); + const FT D2 = dot_product_2(v2, v3); + const FT D = dot_product_2(v1, v3); + + const FT A1 = internal::area_2(traits, r, q, t); + const FT A2 = internal::area_2(traits, p, q, r); + const FT B = internal::area_2(traits, p, q, t); + + const FT sign = mean_value_ns::sign_of_weight(A1, A2, B); + return mean_value_ns::weight( + traits, l1, l2, l3, D1, D2, D, sign); + } + + template + typename GeomTraits::FT mean_value_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return mean_value_weight(t, r, p, q, traits); + } + + namespace internal { + + template + typename GeomTraits::FT mean_value_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using Point_2 = typename GeomTraits::Point_2; + Point_2 tf, rf, pf, qf; + internal::flatten( + traits, + t, r, p, q, + tf, rf, pf, qf); + return CGAL::Weights:: + mean_value_weight(tf, rf, pf, qf, traits); + } + + template + typename GeomTraits::FT mean_value_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return mean_value_weight(t, r, p, q, traits); + } + + } // namespace internal + + // Undocumented mean value weight class. + // Its constructor takes a polygon mesh and a vertex to point map + // and its operator() is defined based on the halfedge_descriptor only. + // This version is currently used in: + // Surface_mesh_parameterizer -> Iterative_authalic_parameterizer_3.h + template< + typename PolygonMesh, + typename VertexPointMap = typename boost::property_map::type> + class Mean_value_weight { + + using GeomTraits = typename CGAL::Kernel_traits< + typename boost::property_traits::value_type>::type; + using FT = typename GeomTraits::FT; + + const PolygonMesh& m_pmesh; + const VertexPointMap m_pmap; + const GeomTraits m_traits; + + public: + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + + Mean_value_weight(const PolygonMesh& pmesh, const VertexPointMap pmap) : + m_pmesh(pmesh), m_pmap(pmap), m_traits() { } + + // Returns the mean-value coordinate of the specified halfedge_descriptor. + // Returns different values for different edge orientations (which is normal + // behavior according to the formula). + FT operator()(const halfedge_descriptor he) const { + + const vertex_descriptor v0 = target(he, m_pmesh); + const vertex_descriptor v1 = source(he, m_pmesh); + const auto& p0 = get(m_pmap, v0); + const auto& p1 = get(m_pmap, v1); + const FT norm = internal::distance_3(m_traits, p0, p1); + + // Only one triangle for border edges. + if (is_border_edge(he, m_pmesh)) { + const halfedge_descriptor he_cw = opposite(next(he, m_pmesh), m_pmesh); + vertex_descriptor v2 = source(he_cw, m_pmesh); + if (is_border_edge(he_cw, m_pmesh)) { + const halfedge_descriptor he_ccw = prev(opposite(he, m_pmesh), m_pmesh); + v2 = source(he_ccw, m_pmesh); + } + return half_tan_value_2(v1, v0, v2) / norm; + } else { + const halfedge_descriptor he_cw = opposite(next(he, m_pmesh), m_pmesh); + const vertex_descriptor v2 = source(he_cw, m_pmesh); + const halfedge_descriptor he_ccw = prev(opposite(he, m_pmesh), m_pmesh); + const vertex_descriptor v3 = source(he_ccw, m_pmesh); + return ( + half_tan_value_2(v1, v0, v2) / norm + + half_tan_value_2(v1, v0, v3) / norm ); + } + } + + private: + // The authors deviation built on Meyer_02. + FT half_tan_value_2( + const vertex_descriptor v0, + const vertex_descriptor v1, + const vertex_descriptor v2) const { + + using Get_sqrt = internal::Get_sqrt; + const auto sqrt = Get_sqrt::sqrt_object(m_traits); + + const auto squared_length_3 = + m_traits.compute_squared_length_3_object(); + const auto construct_vector_3 = + m_traits.construct_vector_3_object(); + + const auto& p0 = get(m_pmap, v0); + const auto& p1 = get(m_pmap, v1); + const auto& p2 = get(m_pmap, v2); + + const auto a = construct_vector_3(p1, p0); + const auto b = construct_vector_3(p1, p2); + + const FT dot_ab = a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + const FT dot_aa = squared_length_3(a); + const FT dot_bb = squared_length_3(b); + const FT dot_aa_bb = dot_aa * dot_bb; + + const FT cos_rep = dot_ab; + const FT sin_rep = sqrt(dot_aa_bb - dot_ab * dot_ab); + const FT normalizer = sqrt(dot_aa_bb); // |a| * |b| + + // The formula from [Floater04] page 4: + // tan(Q / 2) = (1 - cos(Q)) / sin(Q). + return (normalizer - cos_rep) / sin_rep; + } + }; + + /// \endcond + + /*! + \ingroup PkgWeightsRefBarycentricMeanValueWeights + + \brief 2D mean value weights for polygons. + + This class implements 2D mean value weights ( \cite cgal:bc:hf-mvcapp-06, + \cite cgal:bc:fhk-gcbcocp-06, \cite cgal:f-mvc-03 ) which can be computed + at any point inside and outside a simple polygon. + + Mean value weights are well-defined inside and outside a simple polygon and are + non-negative in the kernel of a star-shaped polygon. These weights are computed + analytically using the formulation from the `tangent_weight()`. + + \tparam VertexRange + a model of `ConstRange` whose iterator type is `RandomAccessIterator` + + \tparam GeomTraits + a model of `AnalyticWeightTraits_2` + + \tparam PointMap + a model of `ReadablePropertyMap` whose key type is `VertexRange::value_type` and + value type is `Point_2`. The default is `CGAL::Identity_property_map`. + + \cgalModels `BarycentricWeights_2` + */ + template< + typename VertexRange, + typename GeomTraits, + typename PointMap = CGAL::Identity_property_map > + class Mean_value_weights_2 { + + public: + + /// \name Types + /// @{ + + /// \cond SKIP_IN_MANUAL + using Vertex_range = VertexRange; + using Geom_traits = GeomTraits; + using Point_map = PointMap; + + using Vector_2 = typename GeomTraits::Vector_2; + using Area_2 = typename GeomTraits::Compute_area_2; + using Construct_vector_2 = typename GeomTraits::Construct_vector_2; + using Squared_length_2 = typename GeomTraits::Compute_squared_length_2; + using Scalar_product_2 = typename GeomTraits::Compute_scalar_product_2; + using Get_sqrt = internal::Get_sqrt; + using Sqrt = typename Get_sqrt::Sqrt; + /// \endcond + + /// Number type. + typedef typename GeomTraits::FT FT; + + /// Point type. + typedef typename GeomTraits::Point_2 Point_2; + + /// @} + + /// \name Initialization + /// @{ + + /*! + \brief initializes all internal data structures. + + This class implements the behavior of mean value weights + for 2D query points inside simple polygons. + + \param polygon + an instance of `VertexRange` with the vertices of a simple polygon + + \param traits + a traits class with geometric objects, predicates, and constructions; + the default initialization is provided + + \param point_map + an instance of `PointMap` that maps a vertex from `polygon` to `Point_2`; + the default initialization is provided + + \pre polygon.size() >= 3 + \pre polygon is simple + */ + Mean_value_weights_2( + const VertexRange& polygon, + const GeomTraits traits = GeomTraits(), + const PointMap point_map = PointMap()) : + m_polygon(polygon), + m_traits(traits), + m_point_map(point_map), + m_area_2(m_traits.compute_area_2_object()), + m_construct_vector_2(m_traits.construct_vector_2_object()), + m_squared_length_2(m_traits.compute_squared_length_2_object()), + m_scalar_product_2(m_traits.compute_scalar_product_2_object()), + m_sqrt(Get_sqrt::sqrt_object(m_traits)) { + + CGAL_precondition( + polygon.size() >= 3); + CGAL_precondition( + internal::is_simple_2(polygon, traits, point_map)); + resize(); + } + + /// @} + + /// \name Access + /// @{ + + /*! + \brief computes 2D mean value weights. + + This function fills a destination range with 2D mean value weights computed at + the `query` point with respect to the vertices of the input polygon. + + The number of computed weights is equal to the number of polygon vertices. + + \tparam OutIterator + a model of `OutputIterator` whose value type is `FT` + + \param query + a query point + + \param w_begin + the beginning of the destination range with the computed weights + + \return an output iterator to the element in the destination range, + one past the last weight stored + */ + template + OutIterator operator()(const Point_2& query, OutIterator w_begin) { + const bool normalize = false; + return operator()(query, w_begin, normalize); + } + + /// @} + + /// \cond SKIP_IN_MANUAL + template + OutIterator operator()(const Point_2& query, OutIterator weights, const bool normalize) { + return optimal_weights(query, weights, normalize); + } + /// \endcond + + private: + + // Fields. + const VertexRange& m_polygon; + const GeomTraits m_traits; + const PointMap m_point_map; + + const Area_2 m_area_2; + const Construct_vector_2 m_construct_vector_2; + const Squared_length_2 m_squared_length_2; + const Scalar_product_2 m_scalar_product_2; + const Sqrt m_sqrt; + + std::vector s; + std::vector r; + std::vector A; + std::vector D; + std::vector t; + std::vector w; + + // Functions. + void resize() { + s.resize(m_polygon.size()); + r.resize(m_polygon.size()); + A.resize(m_polygon.size()); + D.resize(m_polygon.size()); + t.resize(m_polygon.size()); + w.resize(m_polygon.size()); + } + + template + OutputIterator optimal_weights( + const Point_2& query, OutputIterator weights, const bool normalize) { + + // Get the number of vertices in the polygon. + const std::size_t n = m_polygon.size(); + + // Compute vectors s following the pseudo-code in the Figure 10 from [1]. + for (std::size_t i = 0; i < n; ++i) { + const auto& pi = get(m_point_map, *(m_polygon.begin() + i)); + s[i] = m_construct_vector_2(query, pi); + } + + // Compute lengths r, areas A, and dot products D following the pseudo-code + // in the Figure 10 from [1]. Split the loop to make this computation faster. + const auto& p1 = get(m_point_map, *(m_polygon.begin() + 0)); + const auto& p2 = get(m_point_map, *(m_polygon.begin() + 1)); + + r[0] = m_sqrt(m_squared_length_2(s[0])); + A[0] = m_area_2(p1, p2, query); + D[0] = m_scalar_product_2(s[0], s[1]); + + for (std::size_t i = 1; i < n - 1; ++i) { + const auto& pi1 = get(m_point_map, *(m_polygon.begin() + (i + 0))); + const auto& pi2 = get(m_point_map, *(m_polygon.begin() + (i + 1))); + + r[i] = m_sqrt(m_squared_length_2(s[i])); + A[i] = m_area_2(pi1, pi2, query); + D[i] = m_scalar_product_2(s[i], s[i + 1]); + } + + const auto& pn = get(m_point_map, *(m_polygon.begin() + (n - 1))); + r[n - 1] = m_sqrt(m_squared_length_2(s[n - 1])); + A[n - 1] = m_area_2(pn, p1, query); + D[n - 1] = m_scalar_product_2(s[n - 1], s[0]); + + // Compute intermediate values t using the formulas from slide 19 here + // - http://www.inf.usi.ch/hormann/nsfworkshop/presentations/Hormann.pdf + for (std::size_t i = 0; i < n - 1; ++i) { + CGAL_assertion((r[i] * r[i + 1] + D[i]) != FT(0)); + t[i] = FT(2) * A[i] / (r[i] * r[i + 1] + D[i]); + } + + CGAL_assertion((r[n - 1] * r[0] + D[n - 1]) != FT(0)); + t[n - 1] = FT(2) * A[n - 1] / (r[n - 1] * r[0] + D[n - 1]); + + // Compute mean value weights using the same pseudo-code as before. + CGAL_assertion(r[0] != FT(0)); + w[0] = FT(2) * (t[n - 1] + t[0]) / r[0]; + + for (std::size_t i = 1; i < n - 1; ++i) { + CGAL_assertion(r[i] != FT(0)); + w[i] = FT(2) * (t[i - 1] + t[i]) / r[i]; + } + + CGAL_assertion(r[n - 1] != FT(0)); + w[n - 1] = FT(2) * (t[n - 2] + t[n - 1]) / r[n - 1]; + + // Normalize if necessary. + if (normalize) { + internal::normalize(w); + } + + // Return weights. + for (std::size_t i = 0; i < n; ++i) { + *(weights++) = w[i]; + } + return weights; + } + }; + + /*! + \ingroup PkgWeightsRefBarycentricMeanValueWeights + + \brief computes 2D mean value weights for polygons. + + This function computes 2D mean value weights at a given `query` point + with respect to the vertices of a simple `polygon`, that is one + weight per vertex. The weights are stored in a destination range + beginning at `w_begin`. + + Internally, the class `Mean_value_weights_2` is used. If one wants to process + multiple query points, it is better to use that class. When using the free function, + internal memory is allocated for each query point, while when using the class, + it is allocated only once which is much more efficient. However, for a few query + points, it is easier to use this function. It can also be used when the processing + time is not a concern. + + \tparam PointRange + a model of `ConstRange` whose iterator type is `RandomAccessIterator` + and value type is `GeomTraits::Point_2` + + \tparam OutIterator + a model of `OutputIterator` whose value type is `GeomTraits::FT` + + \tparam GeomTraits + a model of `AnalyticWeightTraits_2` + + \param polygon + an instance of `PointRange` with 2D points which form a simple polygon + + \param query + a query point + + \param w_begin + the beginning of the destination range with the computed weights + + \param traits + a traits class with geometric objects, predicates, and constructions; + this parameter can be omitted if the traits class can be deduced from the point type + + \return an output iterator to the element in the destination range, + one past the last weight stored + + \pre polygon.size() >= 3 + \pre polygon is simple + */ + template< + typename PointRange, + typename OutIterator, + typename GeomTraits> + OutIterator mean_value_weights_2( + const PointRange& polygon, const typename GeomTraits::Point_2& query, + OutIterator w_begin, const GeomTraits& traits) { + + Mean_value_weights_2 + mean_value(polygon, traits); + return mean_value(query, w_begin); + } + + /// \cond SKIP_IN_MANUAL + template< + typename PointRange, + typename OutIterator> + OutIterator mean_value_weights_2( + const PointRange& polygon, + const typename PointRange::value_type& query, + OutIterator w_begin) { + + using Point_2 = typename PointRange::value_type; + using GeomTraits = typename Kernel_traits::Kernel; + const GeomTraits traits; + return mean_value_weights_2( + polygon, query, w_begin, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_MEAN_VALUE_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/mixed_voronoi_region_weights.h b/Weights/include/CGAL/Weights/mixed_voronoi_region_weights.h new file mode 100644 index 000000000000..6d09c8a7282a --- /dev/null +++ b/Weights/include/CGAL/Weights/mixed_voronoi_region_weights.h @@ -0,0 +1,174 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_MIXED_VORONOI_REGION_WEIGHTS_H +#define CGAL_MIXED_VORONOI_REGION_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefMixedVoronoiRegionWeights + + \brief computes the area of the mixed Voronoi cell in 2D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT mixed_voronoi_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefMixedVoronoiRegionWeights + + \brief computes the area of the mixed Voronoi cell in 3D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT mixed_voronoi_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefMixedVoronoiRegionWeights + + \brief computes the area of the mixed Voronoi cell in 2D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT mixed_voronoi_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { } + + /*! + \ingroup PkgWeightsRefMixedVoronoiRegionWeights + + \brief computes the area of the mixed Voronoi cell in 3D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT mixed_voronoi_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT mixed_voronoi_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + using Point_2 = typename GeomTraits::Point_2; + + const auto angle_2 = + traits.angle_2_object(); + const auto a1 = angle_2(p, q, r); + const auto a2 = angle_2(q, r, p); + const auto a3 = angle_2(r, p, q); + + Point_2 center; + const auto midpoint_2 = + traits.construct_midpoint_2_object(); + if (a1 != CGAL::OBTUSE && a2 != CGAL::OBTUSE && a3 != CGAL::OBTUSE) { + const auto circumcenter_2 = + traits.construct_circumcenter_2_object(); + center = circumcenter_2(p, q, r); + } else { + center = midpoint_2(r, p); + } + + const auto m1 = midpoint_2(q, r); + const auto m2 = midpoint_2(q, p); + + const FT A1 = internal::positive_area_2(traits, q, m1, center); + const FT A2 = internal::positive_area_2(traits, q, center, m2); + return A1 + A2; + } + + template + typename GeomTraits::FT mixed_voronoi_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return mixed_voronoi_area(p, q, r, traits); + } + + template + typename GeomTraits::FT mixed_voronoi_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + using Point_3 = typename GeomTraits::Point_3; + + const auto angle_3 = + traits.angle_3_object(); + const auto a1 = angle_3(p, q, r); + const auto a2 = angle_3(q, r, p); + const auto a3 = angle_3(r, p, q); + + Point_3 center; + const auto midpoint_3 = + traits.construct_midpoint_3_object(); + if (a1 != CGAL::OBTUSE && a2 != CGAL::OBTUSE && a3 != CGAL::OBTUSE) { + const auto circumcenter_3 = + traits.construct_circumcenter_3_object(); + center = circumcenter_3(p, q, r); + } else { + center = midpoint_3(r, p); + } + + const auto m1 = midpoint_3(q, r); + const auto m2 = midpoint_3(q, p); + + const FT A1 = internal::positive_area_3(traits, q, m1, center); + const FT A2 = internal::positive_area_3(traits, q, center, m2); + return A1 + A2; + } + + template + typename GeomTraits::FT mixed_voronoi_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return mixed_voronoi_area(p, q, r, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_MIXED_VORONOI_REGION_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/shepard_weights.h b/Weights/include/CGAL/Weights/shepard_weights.h new file mode 100644 index 000000000000..65df108c19bf --- /dev/null +++ b/Weights/include/CGAL/Weights/shepard_weights.h @@ -0,0 +1,268 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_SHEPARD_WEIGHTS_H +#define CGAL_SHEPARD_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace shepard_ns { + + template + typename GeomTraits::FT weight( + const GeomTraits& traits, + const typename GeomTraits::FT d, + const typename GeomTraits::FT p) { + + using FT = typename GeomTraits::FT; + FT w = FT(0); + CGAL_precondition(d != FT(0)); + if (d != FT(0)) { + FT denom = d; + if (p != FT(1)) { + denom = internal::power(traits, d, p); + } + w = FT(1) / denom; + } + return w; + } + } + /// \endcond + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 2D using the points `p` and `q` and the power parameter `a`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 3D using the points `p` and `q` and the power parameter `a`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 2D using the points `p` and `q`, + which are parameterized by a `Kernel` K, and the power parameter `a` which + can be omitted. + */ + template + typename K::FT shepard_weight( + const CGAL::Point_2&, + const CGAL::Point_2& p, + const CGAL::Point_2&, + const CGAL::Point_2& q, + const typename K::FT a = typename K::FT(1)) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 3D using the points `p` and `q`, + which are parameterized by a `Kernel` K, and the power parameter `a` which + can be omitted. + */ + template + typename K::FT shepard_weight( + const CGAL::Point_3&, + const CGAL::Point_3& p, + const CGAL::Point_3&, + const CGAL::Point_3& q, + const typename K::FT a = typename K::FT(1)) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 2D using the points `p` and `q` and the power parameter `a`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 3D using the points `p` and `q` and the power parameter `a`, + given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 2D using the points `p` and `q`, + which are parameterized by a `Kernel` K, and the power parameter `a` which + can be omitted. + */ + template + typename K::FT shepard_weight( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const typename K::FT a = typename K::FT(1)) { } + + /*! + \ingroup PkgWeightsRefShepardWeights + + \brief computes the Shepard weight in 3D using the points `p` and `q`, + which are parameterized by a `Kernel` K, and the power parameter `a` which + can be omitted. + */ + template + typename K::FT shepard_weight( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const typename K::FT a = typename K::FT(1)) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT d = internal::distance_2(traits, q, r); + return shepard_ns::weight(traits, d, a); + } + + template + typename GeomTraits::FT shepard_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const typename GeomTraits::FT a = + typename GeomTraits::FT(1)) { + + const GeomTraits traits; + return shepard_weight(t, r, p, q, a, traits); + } + + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { + + typename GeomTraits::Point_2 stub; + return shepard_weight(stub, p, stub, q, a, traits); + } + + template + typename GeomTraits::FT shepard_weight( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const typename GeomTraits::FT a = + typename GeomTraits::FT(1)) { + + CGAL::Point_2 stub; + return shepard_weight(stub, p, stub, q, a); + } + + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT d = internal::distance_3(traits, q, r); + return shepard_ns::weight(traits, d, a); + } + + template + typename GeomTraits::FT shepard_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const typename GeomTraits::FT a = + typename GeomTraits::FT(1)) { + + const GeomTraits traits; + return shepard_weight(t, r, p, q, a, traits); + } + + template + typename GeomTraits::FT shepard_weight( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { + + typename GeomTraits::Point_3 stub; + return shepard_weight(stub, p, stub, q, a, traits); + } + + template + typename GeomTraits::FT shepard_weight( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const typename GeomTraits::FT a = + typename GeomTraits::FT(1)) { + + CGAL::Point_3 stub; + return shepard_weight(stub, p, stub, q, a); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_SHEPARD_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/tangent_weights.h b/Weights/include/CGAL/Weights/tangent_weights.h new file mode 100644 index 000000000000..a7e93109a026 --- /dev/null +++ b/Weights/include/CGAL/Weights/tangent_weights.h @@ -0,0 +1,451 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_TANGENT_WEIGHTS_H +#define CGAL_TANGENT_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace tangent_ns { + + template + FT half_angle_tangent(const FT r, const FT d, const FT A, const FT D) { + + FT t = FT(0); + const FT P = r * d + D; + CGAL_precondition(P != FT(0)); + if (P != FT(0)) { + const FT inv = FT(2) / P; + t = A * inv; + } + return t; + } + + template + FT half_weight(const FT t, const FT r) { + + FT w = FT(0); + CGAL_precondition(r != FT(0)); + if (r != FT(0)) { + const FT inv = FT(2) / r; + w = t * inv; + } + return w; + } + + template + FT weight(const FT t1, const FT t2, const FT r) { + + FT w = FT(0); + CGAL_precondition(r != FT(0)); + if (r != FT(0)) { + const FT inv = FT(2) / r; + w = (t1 + t2) * inv; + } + return w; + } + + template + FT weight( + const FT d1, const FT r, const FT d2, + const FT A1, const FT A2, + const FT D1, const FT D2) { + + const FT P1 = d1 * r + D1; + const FT P2 = d2 * r + D2; + + FT w = FT(0); + CGAL_precondition(P1 != FT(0) && P2 != FT(0)); + if (P1 != FT(0) && P2 != FT(0)) { + const FT inv1 = FT(2) / P1; + const FT inv2 = FT(2) / P2; + const FT t1 = A1 * inv1; + const FT t2 = A2 * inv2; + w = weight(t1, t2, r); + } + return w; + } + + // This is positive case only. + // This version is based on the positive area. + // This version is more precise for all positive cases. + template + typename GeomTraits::FT tangent_weight_v1( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto dot_product_3 = + traits.compute_scalar_product_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v1 = construct_vector_3(q, t); + const auto v2 = construct_vector_3(q, r); + const auto v3 = construct_vector_3(q, p); + + const FT l1 = internal::length_3(traits, v1); + const FT l2 = internal::length_3(traits, v2); + const FT l3 = internal::length_3(traits, v3); + + const FT A1 = internal::positive_area_3(traits, r, q, t); + const FT A2 = internal::positive_area_3(traits, p, q, r); + + const FT D1 = dot_product_3(v1, v2); + const FT D2 = dot_product_3(v2, v3); + + return weight(l1, l2, l3, A1, A2, D1, D2); + } + + // This version handles both positive and negative cases. + // However, it is less precise. + template + typename GeomTraits::FT tangent_weight_v2( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + auto v1 = construct_vector_3(q, t); + auto v2 = construct_vector_3(q, r); + auto v3 = construct_vector_3(q, p); + + const FT l2 = internal::length_3(traits, v2); + + internal::normalize_3(traits, v1); + internal::normalize_3(traits, v2); + internal::normalize_3(traits, v3); + + const double ha_rad_1 = internal::angle_3(traits, v1, v2) / 2.0; + const double ha_rad_2 = internal::angle_3(traits, v2, v3) / 2.0; + const FT t1 = static_cast(std::tan(ha_rad_1)); + const FT t2 = static_cast(std::tan(ha_rad_2)); + + return weight(t1, t2, l2); + } + } + /// \endcond + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the tangent of the half angle. + + This function computes the tangent of the half angle using the precomputed + distance, area, and dot product values. The returned value is + \f$\frac{2\textbf{A}}{\textbf{d}\textbf{l} + \textbf{D}}\f$. + + \tparam FT + a model of `FieldNumberType` + + \param d + the distance value + + \param l + the distance value + + \param A + the area value + + \param D + the dot product value + + \pre (d * l + D) != 0 + + \sa `half_tangent_weight()` + */ + template + FT tangent_half_angle(const FT d, const FT l, const FT A, const FT D) { + return tangent_ns::half_angle_tangent(d, l, A, D); + } + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the half value of the tangent weight. + + This function constructs the half of the tangent weight using the precomputed + half angle tangent and distance values. The returned value is + \f$\frac{2\textbf{tan05}}{\textbf{d}}\f$. + + \tparam FT + a model of `FieldNumberType` + + \param tan05 + the half angle tangent value + + \param d + the distance value + + \pre d != 0 + + \sa `tangent_half_angle()` + \sa `tangent_weight()` + */ + template + FT half_tangent_weight(const FT tan05, const FT d) { + return tangent_ns::half_weight(tan05, d); + } + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the half value of the tangent weight. + + This function constructs the half of the tangent weight using the precomputed + distance, area, and dot product values. The returned value is + \f$\frac{2\textbf{t}}{\textbf{d}}\f$ where + \f$\textbf{t} = \frac{2\textbf{A}}{\textbf{d}\textbf{l} + \textbf{D}}\f$. + + \tparam FT + a model of `FieldNumberType` + + \param d + the distance value + + \param l + the distance value + + \param A + the area value + + \param D + the dot product value + + \pre (d * l + D) != 0 && d != 0 + + \sa `tangent_weight()` + */ + template + FT half_tangent_weight(const FT d, const FT l, const FT A, const FT D) { + const FT tan05 = tangent_half_angle(d, l, A, D); + return half_tangent_weight(tan05, d); + } + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the tangent weight in 2D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT tangent_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the tangent weight in 3D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT tangent_weight( + const typename GeomTraits::Point_3& p0, + const typename GeomTraits::Point_3& p1, + const typename GeomTraits::Point_3& p2, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the tangent weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT tangent_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q) { } + + /*! + \ingroup PkgWeightsRefTangentWeights + + \brief computes the tangent weight in 3D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT tangent_weight( + const CGAL::Point_3& p0, + const CGAL::Point_3& p1, + const CGAL::Point_3& p2, + const CGAL::Point_3& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT tangent_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto dot_product_2 = + traits.compute_scalar_product_2_object(); + const auto construct_vector_2 = + traits.construct_vector_2_object(); + + const auto v1 = construct_vector_2(q, t); + const auto v2 = construct_vector_2(q, r); + const auto v3 = construct_vector_2(q, p); + + const FT l1 = internal::length_2(traits, v1); + const FT l2 = internal::length_2(traits, v2); + const FT l3 = internal::length_2(traits, v3); + + const FT A1 = internal::area_2(traits, r, q, t); + const FT A2 = internal::area_2(traits, p, q, r); + + const FT D1 = dot_product_2(v1, v2); + const FT D2 = dot_product_2(v2, v3); + + return tangent_ns::weight( + l1, l2, l3, A1, A2, D1, D2); + } + + template + typename GeomTraits::FT tangent_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return tangent_weight(t, r, p, q, traits); + } + + template + typename GeomTraits::FT tangent_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + // return tangent_ns::tangent_weight_v1(t, r, p, q, traits); + return tangent_ns::tangent_weight_v2(t, r, p, q, traits); + } + + template + typename GeomTraits::FT tangent_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return tangent_weight(t, r, p, q, traits); + } + + // Undocumented tangent weight class. + // Its constructor takes three points either in 2D or 3D. + // This version is currently used in: + // Surface_mesh_parameterizer -> MVC_post_processor_3.h + // Surface_mesh_parameterizer -> Orbifold_Tutte_parameterizer_3.h + template + class Tangent_weight { + FT m_d_r, m_d_p, m_w_base; + + public: + template + Tangent_weight( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + const auto scalar_product_2 = + traits.compute_scalar_product_2_object(); + const auto construct_vector_2 = + traits.construct_vector_2_object(); + + m_d_r = internal::distance_2(traits, q, r); + CGAL_assertion(m_d_r != FT(0)); // two points are identical! + m_d_p = internal::distance_2(traits, q, p); + CGAL_assertion(m_d_p != FT(0)); // two points are identical! + + const auto v1 = construct_vector_2(q, r); + const auto v2 = construct_vector_2(q, p); + + const auto A = internal::area_2(traits, p, q, r); + CGAL_assertion(A != FT(0)); // three points are identical! + const auto S = scalar_product_2(v1, v2); + m_w_base = -tangent_half_angle(m_d_r, m_d_p, A, S); + } + + template + Tangent_weight( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + const auto scalar_product_3 = + traits.compute_scalar_product_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + m_d_r = internal::distance_3(traits, q, r); + CGAL_assertion(m_d_r != FT(0)); // two points are identical! + m_d_p = internal::distance_3(traits, q, p); + CGAL_assertion(m_d_p != FT(0)); // two points are identical! + + const auto v1 = construct_vector_3(q, r); + const auto v2 = construct_vector_3(q, p); + + const auto A = internal::positive_area_3(traits, p, q, r); + CGAL_assertion(A != FT(0)); // three points are identical! + const auto S = scalar_product_3(v1, v2); + m_w_base = -tangent_half_angle(m_d_r, m_d_p, A, S); + } + + FT get_w_r() const { + return half_tangent_weight(m_w_base, m_d_r) / FT(2); + } + + FT get_w_p() const { + return half_tangent_weight(m_w_base, m_d_p) / FT(2); + } + }; + + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_TANGENT_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/three_point_family_weights.h b/Weights/include/CGAL/Weights/three_point_family_weights.h new file mode 100644 index 000000000000..904721d99459 --- /dev/null +++ b/Weights/include/CGAL/Weights/three_point_family_weights.h @@ -0,0 +1,172 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_THREE_POINT_FAMILY_WEIGHTS_H +#define CGAL_THREE_POINT_FAMILY_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace three_point_family_ns { + + template + typename GeomTraits::FT weight( + const GeomTraits& traits, + const typename GeomTraits::FT d1, + const typename GeomTraits::FT d2, + const typename GeomTraits::FT d3, + const typename GeomTraits::FT A1, + const typename GeomTraits::FT A2, + const typename GeomTraits::FT B, + const typename GeomTraits::FT p) { + + using FT = typename GeomTraits::FT; + FT w = FT(0); + CGAL_precondition(A1 != FT(0) && A2 != FT(0)); + const FT prod = A1 * A2; + if (prod != FT(0)) { + const FT inv = FT(1) / prod; + FT r1 = d1; + FT r2 = d2; + FT r3 = d3; + if (p != FT(1)) { + r1 = internal::power(traits, d1, p); + r2 = internal::power(traits, d2, p); + r3 = internal::power(traits, d3, p); + } + w = (r3 * A1 - r2 * B + r1 * A2) * inv; + } + return w; + } + } + /// \endcond + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefThreePointFamilyWeights + + \brief computes the three-point family weight in 2D at `q` using the points `p0`, `p1`, + and `p2` and the power parameter `a`, given a traits class `traits` with geometric objects, + predicates, and constructions. + */ + template + typename GeomTraits::FT three_point_family_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefThreePointFamilyWeights + + \brief computes the three-point family weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K, and the power parameter `a` which + can be omitted. + */ + template + typename K::FT three_point_family_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q, + const typename K::FT a = typename K::FT(1)) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT three_point_family_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT d1 = internal::distance_2(traits, q, t); + const FT d2 = internal::distance_2(traits, q, r); + const FT d3 = internal::distance_2(traits, q, p); + + const FT A1 = internal::area_2(traits, r, q, t); + const FT A2 = internal::area_2(traits, p, q, r); + const FT B = internal::area_2(traits, p, q, t); + + return three_point_family_ns::weight( + traits, d1, d2, d3, A1, A2, B, a); + } + + template + typename GeomTraits::FT three_point_family_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const typename GeomTraits::FT a = + typename GeomTraits::FT(1)) { + + const GeomTraits traits; + return three_point_family_weight(t, r, p, q, a, traits); + } + + namespace internal { + + template + typename GeomTraits::FT three_point_family_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::FT a, + const GeomTraits& traits) { + + using Point_2 = typename GeomTraits::Point_2; + Point_2 tf, rf, pf, qf; + internal::flatten( + traits, + t, r, p, q, + tf, rf, pf, qf); + return CGAL::Weights:: + three_point_family_weight(tf, rf, pf, qf, a, traits); + } + + template + typename GeomTraits::FT three_point_family_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const typename GeomTraits::FT a = + typename GeomTraits::FT(1)) { + + const GeomTraits traits; + return three_point_family_weight(t, r, p, q, a, traits); + } + + } // namespace internal + + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_THREE_POINT_FAMILY_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/triangular_region_weights.h b/Weights/include/CGAL/Weights/triangular_region_weights.h new file mode 100644 index 000000000000..063e85cf558b --- /dev/null +++ b/Weights/include/CGAL/Weights/triangular_region_weights.h @@ -0,0 +1,124 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_TRIANGULAR_REGION_WEIGHTS_H +#define CGAL_TRIANGULAR_REGION_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefTriangularRegionWeights + + \brief computes the area of the triangular cell in 2D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT triangular_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefTriangularRegionWeights + + \brief computes the area of the triangular cell in 3D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT triangular_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefTriangularRegionWeights + + \brief computes the area of the triangular cell in 2D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT triangular_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { } + + /*! + \ingroup PkgWeightsRefTriangularRegionWeights + + \brief computes the area of the triangular cell in 3D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT triangular_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT triangular_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { + + return internal::positive_area_2(traits, p, q, r); + } + + template + typename GeomTraits::FT triangular_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return triangular_area(p, q, r, traits); + } + + template + typename GeomTraits::FT triangular_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { + + return internal::positive_area_3(traits, p, q, r); + } + + template + typename GeomTraits::FT triangular_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return triangular_area(p, q, r, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_TRIANGULAR_REGION_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/uniform_region_weights.h b/Weights/include/CGAL/Weights/uniform_region_weights.h new file mode 100644 index 000000000000..07dfd667e7c2 --- /dev/null +++ b/Weights/include/CGAL/Weights/uniform_region_weights.h @@ -0,0 +1,126 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_UNIFORM_REGION_WEIGHTS_H +#define CGAL_UNIFORM_REGION_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefUniformRegionWeights + + \brief this function always returns 1, given three points in 2D and a traits class + with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT uniform_area( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const GeomTraits&) { } + + /*! + \ingroup PkgWeightsRefUniformRegionWeights + + \brief this function always returns 1, given three points in 3D and a traits class + with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT uniform_area( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const GeomTraits&) { } + + /*! + \ingroup PkgWeightsRefUniformRegionWeights + + \brief this function always returns 1, given three points in 2D which are + parameterized by a `Kernel` K. + */ + template + typename K::FT uniform_area( + const CGAL::Point_2&, + const CGAL::Point_2&, + const CGAL::Point_2&) { } + + /*! + \ingroup PkgWeightsRefUniformRegionWeights + + \brief this function always returns 1, given three points in 3D which are + parameterized by a `Kernel` K. + */ + template + typename K::FT uniform_area( + const CGAL::Point_3&, + const CGAL::Point_3&, + const CGAL::Point_3&) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT uniform_area( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const GeomTraits&) { + + using FT = typename GeomTraits::FT; + return FT(1); + } + + template + typename GeomTraits::FT uniform_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return uniform_area(p, q, r, traits); + } + + template + typename GeomTraits::FT uniform_area( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const GeomTraits&) { + + using FT = typename GeomTraits::FT; + return FT(1); + } + + template + typename GeomTraits::FT uniform_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return uniform_area(p, q, r, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_UNIFORM_REGION_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/uniform_weights.h b/Weights/include/CGAL/Weights/uniform_weights.h new file mode 100644 index 000000000000..f54313e1f067 --- /dev/null +++ b/Weights/include/CGAL/Weights/uniform_weights.h @@ -0,0 +1,151 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_UNIFORM_WEIGHTS_H +#define CGAL_UNIFORM_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefUniformWeights + + \brief this function always returns 1, given four points in 2D and a traits class + with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT uniform_weight( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const GeomTraits&) { } + + /*! + \ingroup PkgWeightsRefUniformWeights + + \brief this function always returns 1, given four points in 3D and a traits class + with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT uniform_weight( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const GeomTraits&) { } + + /*! + \ingroup PkgWeightsRefUniformWeights + + \brief this function always returns 1, given four points in 2D which are + parameterized by a `Kernel` K. + */ + template + typename K::FT uniform_weight( + const CGAL::Point_2&, + const CGAL::Point_2&, + const CGAL::Point_2&, + const CGAL::Point_2&) { } + + /*! + \ingroup PkgWeightsRefUniformWeights + + \brief this function always returns 1, given four points in 3D which are + parameterized by a `Kernel` K. + */ + template + typename K::FT uniform_weight( + const CGAL::Point_3&, + const CGAL::Point_3&, + const CGAL::Point_3&, + const CGAL::Point_3&) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT uniform_weight( + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const typename GeomTraits::Point_2&, + const GeomTraits&) { + + using FT = typename GeomTraits::FT; + return FT(1); + } + + template + typename GeomTraits::FT uniform_weight( + const CGAL::Point_2& q, + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p) { + + const GeomTraits traits; + return uniform_weight(q, t, r, p, traits); + } + + template + typename GeomTraits::FT uniform_weight( + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const typename GeomTraits::Point_3&, + const GeomTraits&) { + + using FT = typename GeomTraits::FT; + return FT(1); + } + + template + typename GeomTraits::FT uniform_weight( + const CGAL::Point_3& q, + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p) { + + const GeomTraits traits; + return uniform_weight(q, t, r, p, traits); + } + + // Undocumented uniform weight class taking as input a polygon mesh. + // It is currently used in: + // Polygon_mesh_processing -> triangulate_hole_Polyhedron_3_test.cpp + // Polygon_mesh_processing -> triangulate_hole_Polyhedron_3_no_delaunay_test.cpp + // Polyhedron demo -> Fairing_plugin.cpp + // Polyhedron demo -> Hole_filling_plugin.cpp + template + class Uniform_weight { + + public: + using vertex_descriptor = typename boost::graph_traits::vertex_descriptor; + using halfedge_descriptor = typename boost::graph_traits::halfedge_descriptor; + double w_i(vertex_descriptor) { return 1; } + double w_ij(halfedge_descriptor) { return 1; } + }; + + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_UNIFORM_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/utils.h b/Weights/include/CGAL/Weights/utils.h new file mode 100644 index 000000000000..919b2fc2bf23 --- /dev/null +++ b/Weights/include/CGAL/Weights/utils.h @@ -0,0 +1,211 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_WEIGHTS_UTILS_H +#define CGAL_WEIGHTS_UTILS_H + +#include + +// Internal includes. +#include +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT tangent( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { + + return internal::tangent_2(traits, p, q, r); + } + + template + typename GeomTraits::FT tangent( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return tangent(p, q, r, traits); + } + + template + typename GeomTraits::FT tangent( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { + + return internal::tangent_3(traits, p, q, r); + } + + template + typename GeomTraits::FT tangent( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return tangent(p, q, r, traits); + } + + template + typename GeomTraits::FT cotangent( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { + + return internal::cotangent_2(traits, p, q, r); + } + + template + typename GeomTraits::FT cotangent( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return cotangent(p, q, r, traits); + } + + template + typename GeomTraits::FT cotangent( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { + + return internal::cotangent_3(traits, p, q, r); + } + + template + typename GeomTraits::FT cotangent( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return cotangent(p, q, r, traits); + } + /// \endcond + + /// \cond SKIP_IN_MANUAL + // These are free functions to be used when building weights from parts rather + // than using the predefined weight functions. In principle, they can be removed. + // They are here to have unified interface within the Weights package and its + // construction weight system. + template + typename GeomTraits::FT squared_distance( + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + const auto squared_distance_2 = + traits.compute_squared_distance_2_object(); + return squared_distance_2(p, q); + } + + template + typename GeomTraits::FT squared_distance( + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + const auto squared_distance_3 = + traits.compute_squared_distance_3_object(); + return squared_distance_3(p, q); + } + + template + typename GeomTraits::FT distance( + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return internal::distance_2(traits, p, q); + } + + template + typename GeomTraits::FT distance( + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return internal::distance_3(traits, p, q); + } + + template + typename GeomTraits::FT area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return internal::area_2(traits, p, q, r); + } + + template + typename GeomTraits::FT area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return internal::positive_area_3(traits, p, q, r); + } + + template + typename GeomTraits::FT scalar_product( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + const auto scalar_product_2 = + traits.compute_scalar_product_2_object(); + const auto construct_vector_2 = + traits.construct_vector_2_object(); + + const auto v1 = construct_vector_2(q, r); + const auto v2 = construct_vector_2(q, p); + return scalar_product_2(v1, v2); + } + + template + typename GeomTraits::FT scalar_product( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + const auto scalar_product_3 = + traits.compute_scalar_product_3_object(); + const auto construct_vector_3 = + traits.construct_vector_3_object(); + + const auto v1 = construct_vector_3(q, r); + const auto v2 = construct_vector_3(q, p); + return scalar_product_3(v1, v2); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_WEIGHTS_UTILS_H diff --git a/Weights/include/CGAL/Weights/voronoi_region_weights.h b/Weights/include/CGAL/Weights/voronoi_region_weights.h new file mode 100644 index 000000000000..057c4a9b6ec3 --- /dev/null +++ b/Weights/include/CGAL/Weights/voronoi_region_weights.h @@ -0,0 +1,148 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov +// + +#ifndef CGAL_VORONOI_REGION_WEIGHTS_H +#define CGAL_VORONOI_REGION_WEIGHTS_H + +#include + +// Internal includes. +#include + +namespace CGAL { +namespace Weights { + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefVoronoiRegionWeights + + \brief computes the area of the Voronoi cell in 2D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT voronoi_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefVoronoiRegionWeights + + \brief computes the area of the Voronoi cell in 3D using the points `p`, `q` + and `r`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT voronoi_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefVoronoiRegionWeights + + \brief computes the area of the Voronoi cell in 2D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT voronoi_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { } + + /*! + \ingroup PkgWeightsRefVoronoiRegionWeights + + \brief computes the area of the Voronoi cell in 3D using the points `p`, `q` + and `r` which are parameterized by a `Kernel` K. + */ + template + typename K::FT voronoi_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT voronoi_area( + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const typename GeomTraits::Point_2& r, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto circumcenter_2 = + traits.construct_circumcenter_2_object(); + const auto midpoint_2 = + traits.construct_midpoint_2_object(); + + const auto center = circumcenter_2(p, q, r); + const auto m1 = midpoint_2(q, r); + const auto m2 = midpoint_2(q, p); + + const FT A1 = internal::positive_area_2(traits, q, m1, center); + const FT A2 = internal::positive_area_2(traits, q, center, m2); + return A1 + A2; + } + + template + typename GeomTraits::FT voronoi_area( + const CGAL::Point_2& p, + const CGAL::Point_2& q, + const CGAL::Point_2& r) { + + const GeomTraits traits; + return voronoi_area(p, q, r, traits); + } + + template + typename GeomTraits::FT voronoi_area( + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const typename GeomTraits::Point_3& r, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const auto circumcenter_3 = + traits.construct_circumcenter_3_object(); + const auto midpoint_3 = + traits.construct_midpoint_3_object(); + + const auto center = circumcenter_3(p, q, r); + const auto m1 = midpoint_3(q, r); + const auto m2 = midpoint_3(q, p); + + const FT A1 = internal::positive_area_3(traits, q, m1, center); + const FT A2 = internal::positive_area_3(traits, q, center, m2); + return A1 + A2; + } + + template + typename GeomTraits::FT voronoi_area( + const CGAL::Point_3& p, + const CGAL::Point_3& q, + const CGAL::Point_3& r) { + + const GeomTraits traits; + return voronoi_area(p, q, r, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_VORONOI_REGION_WEIGHTS_H diff --git a/Weights/include/CGAL/Weights/wachspress_weights.h b/Weights/include/CGAL/Weights/wachspress_weights.h new file mode 100644 index 000000000000..cf99617c797c --- /dev/null +++ b/Weights/include/CGAL/Weights/wachspress_weights.h @@ -0,0 +1,425 @@ +// Copyright (c) 2020 GeometryFactory SARL (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) : Dmitry Anisimov, David Bommes, Kai Hormann, Pierre Alliez +// + +#ifndef CGAL_WACHSPRESS_WEIGHTS_H +#define CGAL_WACHSPRESS_WEIGHTS_H + +#include + +// Internal includes. +#include +#include + +namespace CGAL { +namespace Weights { + + /// \cond SKIP_IN_MANUAL + namespace wachspress_ns { + + template + FT weight(const FT A1, const FT A2, const FT C) { + + FT w = FT(0); + CGAL_precondition(A1 != FT(0) && A2 != FT(0)); + const FT prod = A1 * A2; + if (prod != FT(0)) { + const FT inv = FT(1) / prod; + w = C * inv; + } + return w; + } + } + /// \endcond + + #if defined(DOXYGEN_RUNNING) + + /*! + \ingroup PkgWeightsRefWachspressWeights + + \brief computes the Wachspress weight in 2D at `q` using the points `p0`, `p1`, + and `p2`, given a traits class `traits` with geometric objects, predicates, and constructions. + */ + template + typename GeomTraits::FT wachspress_weight( + const typename GeomTraits::Point_2& p0, + const typename GeomTraits::Point_2& p1, + const typename GeomTraits::Point_2& p2, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { } + + /*! + \ingroup PkgWeightsRefWachspressWeights + + \brief computes the Wachspress weight in 2D at `q` using the points `p0`, `p1`, + and `p2` which are parameterized by a `Kernel` K. + */ + template + typename K::FT wachspress_weight( + const CGAL::Point_2& p0, + const CGAL::Point_2& p1, + const CGAL::Point_2& p2, + const CGAL::Point_2& q) { } + + #endif // DOXYGEN_RUNNING + + /// \cond SKIP_IN_MANUAL + template + typename GeomTraits::FT wachspress_weight( + const typename GeomTraits::Point_2& t, + const typename GeomTraits::Point_2& r, + const typename GeomTraits::Point_2& p, + const typename GeomTraits::Point_2& q, + const GeomTraits& traits) { + + using FT = typename GeomTraits::FT; + const FT A1 = internal::area_2(traits, r, q, t); + const FT A2 = internal::area_2(traits, p, q, r); + const FT C = internal::area_2(traits, t, r, p); + return wachspress_ns::weight(A1, A2, C); + } + + template + typename GeomTraits::FT wachspress_weight( + const CGAL::Point_2& t, + const CGAL::Point_2& r, + const CGAL::Point_2& p, + const CGAL::Point_2& q) { + + const GeomTraits traits; + return wachspress_weight(t, r, p, q, traits); + } + + namespace internal { + + template + typename GeomTraits::FT wachspress_weight( + const typename GeomTraits::Point_3& t, + const typename GeomTraits::Point_3& r, + const typename GeomTraits::Point_3& p, + const typename GeomTraits::Point_3& q, + const GeomTraits& traits) { + + using Point_2 = typename GeomTraits::Point_2; + Point_2 tf, rf, pf, qf; + internal::flatten( + traits, + t, r, p, q, + tf, rf, pf, qf); + return CGAL::Weights:: + wachspress_weight(tf, rf, pf, qf, traits); + } + + template + typename GeomTraits::FT wachspress_weight( + const CGAL::Point_3& t, + const CGAL::Point_3& r, + const CGAL::Point_3& p, + const CGAL::Point_3& q) { + + const GeomTraits traits; + return wachspress_weight(t, r, p, q, traits); + } + + } // namespace internal + + /// \endcond + + /*! + \ingroup PkgWeightsRefBarycentricWachspressWeights + + \brief 2D Wachspress weights for polygons. + + This class implements 2D Wachspress weights ( \cite cgal:bc:fhk-gcbcocp-06, + \cite cgal:bc:mlbd-gbcip-02, \cite cgal:bc:w-rfeb-75 ) which can be computed + at any point inside a strictly convex polygon. + + Wachspress weights are well-defined and non-negative inside a strictly convex polygon. + The weights are computed analytically using the formulation from the `wachspress_weight()`. + + \tparam VertexRange + a model of `ConstRange` whose iterator type is `RandomAccessIterator` + + \tparam GeomTraits + a model of `AnalyticWeightTraits_2` + + \tparam PointMap + a model of `ReadablePropertyMap` whose key type is `VertexRange::value_type` and + value type is `Point_2`. The default is `CGAL::Identity_property_map`. + + \cgalModels `BarycentricWeights_2` + */ + template< + typename VertexRange, + typename GeomTraits, + typename PointMap = CGAL::Identity_property_map > + class Wachspress_weights_2 { + + public: + + /// \name Types + /// @{ + + /// \cond SKIP_IN_MANUAL + using Vertex_range = VertexRange; + using Geom_traits = GeomTraits; + using Point_map = PointMap; + + using Area_2 = typename GeomTraits::Compute_area_2; + /// \endcond + + /// Number type. + typedef typename GeomTraits::FT FT; + + /// Point type. + typedef typename GeomTraits::Point_2 Point_2; + + /// @} + + /// \name Initialization + /// @{ + + /*! + \brief initializes all internal data structures. + + This class implements the behavior of Wachspress weights + for 2D query points inside strictly convex polygons. + + \param polygon + an instance of `VertexRange` with the vertices of a strictly convex polygon + + \param traits + a traits class with geometric objects, predicates, and constructions; + the default initialization is provided + + \param point_map + an instance of `PointMap` that maps a vertex from `polygon` to `Point_2`; + the default initialization is provided + + \pre polygon.size() >= 3 + \pre polygon is simple + \pre polygon is strictly convex + */ + Wachspress_weights_2( + const VertexRange& polygon, + const GeomTraits traits = GeomTraits(), + const PointMap point_map = PointMap()) : + m_polygon(polygon), + m_traits(traits), + m_point_map(point_map), + m_area_2(m_traits.compute_area_2_object()) { + + CGAL_precondition( + polygon.size() >= 3); + CGAL_precondition( + internal::is_simple_2(polygon, traits, point_map)); + CGAL_precondition( + internal::polygon_type_2(polygon, traits, point_map) == + internal::Polygon_type::STRICTLY_CONVEX); + resize(); + } + + /// @} + + /// \name Access + /// @{ + + /*! + \brief computes 2D Wachspress weights. + + This function fills a destination range with 2D Wachspress weights computed + at the `query` point with respect to the vertices of the input polygon. + + The number of computed weights is equal to the number of polygon vertices. + + \tparam OutIterator + a model of `OutputIterator` whose value type is `FT` + + \param query + a query point + + \param w_begin + the beginning of the destination range with the computed weights + + \return an output iterator to the element in the destination range, + one past the last weight stored + */ + template + OutIterator operator()(const Point_2& query, OutIterator w_begin) { + const bool normalize = false; + return operator()(query, w_begin, normalize); + } + + /// @} + + /// \cond SKIP_IN_MANUAL + template + OutIterator operator()(const Point_2& query, OutIterator w_begin, const bool normalize) { + return optimal_weights(query, w_begin, normalize); + } + /// \endcond + + private: + + // Fields. + const VertexRange& m_polygon; + const GeomTraits m_traits; + const PointMap m_point_map; + + const Area_2 m_area_2; + + std::vector A; + std::vector C; + std::vector w; + + // Functions. + void resize() { + A.resize(m_polygon.size()); + C.resize(m_polygon.size()); + w.resize(m_polygon.size()); + } + + template + OutputIterator optimal_weights( + const Point_2& query, OutputIterator weights, const bool normalize) { + + // Get the number of vertices in the polygon. + const std::size_t n = m_polygon.size(); + + // Compute areas A and C following the area notation from [1]. + // Split the loop to make this computation faster. + const auto& p1 = get(m_point_map, *(m_polygon.begin() + 0)); + const auto& p2 = get(m_point_map, *(m_polygon.begin() + 1)); + const auto& pn = get(m_point_map, *(m_polygon.begin() + (n - 1))); + + A[0] = m_area_2(p1, p2, query); + C[0] = m_area_2(pn, p1, p2); + + for (std::size_t i = 1; i < n - 1; ++i) { + const auto& pi0 = get(m_point_map, *(m_polygon.begin() + (i - 1))); + const auto& pi1 = get(m_point_map, *(m_polygon.begin() + (i + 0))); + const auto& pi2 = get(m_point_map, *(m_polygon.begin() + (i + 1))); + + A[i] = m_area_2(pi1, pi2, query); + C[i] = m_area_2(pi0, pi1, pi2); + } + + const auto& pm = get(m_point_map, *(m_polygon.begin() + (n - 2))); + A[n - 1] = m_area_2(pn, p1, query); + C[n - 1] = m_area_2(pm, pn, p1); + + // Compute unnormalized weights following the formula (28) from [1]. + CGAL_assertion(A[n - 1] != FT(0) && A[0] != FT(0)); + w[0] = C[0] / (A[n - 1] * A[0]); + + for (std::size_t i = 1; i < n - 1; ++i) { + CGAL_assertion(A[i - 1] != FT(0) && A[i] != FT(0)); + w[i] = C[i] / (A[i - 1] * A[i]); + } + + CGAL_assertion(A[n - 2] != FT(0) && A[n - 1] != FT(0)); + w[n - 1] = C[n - 1] / (A[n - 2] * A[n - 1]); + + // Normalize if necessary. + if (normalize) { + internal::normalize(w); + } + + // Return weights. + for (std::size_t i = 0; i < n; ++i) { + *(weights++) = w[i]; + } + return weights; + } + }; + + /*! + \ingroup PkgWeightsRefBarycentricWachspressWeights + + \brief computes 2D Wachspress weights for polygons. + + This function computes 2D Wachspress weights at a given `query` point + with respect to the vertices of a strictly convex `polygon`, that is one + weight per vertex. The weights are stored in a destination range + beginning at `w_begin`. + + Internally, the class `Wachspress_weights_2` is used. If one wants to process + multiple query points, it is better to use that class. When using the free function, + internal memory is allocated for each query point, while when using the class, + it is allocated only once which is much more efficient. However, for a few query + points, it is easier to use this function. It can also be used when the processing + time is not a concern. + + \tparam PointRange + a model of `ConstRange` whose iterator type is `RandomAccessIterator` + and value type is `GeomTraits::Point_2` + + \tparam OutIterator + a model of `OutputIterator` whose value type is `GeomTraits::FT` + + \tparam GeomTraits + a model of `AnalyticWeightTraits_2` + + \param polygon + an instance of `PointRange` with 2D points which form a strictly convex polygon + + \param query + a query point + + \param w_begin + the beginning of the destination range with the computed weights + + \param traits + a traits class with geometric objects, predicates, and constructions; + this parameter can be omitted if the traits class can be deduced from the point type + + \return an output iterator to the element in the destination range, + one past the last weight stored + + \pre polygon.size() >= 3 + \pre polygon is simple + \pre polygon is strictly convex + */ + template< + typename PointRange, + typename OutIterator, + typename GeomTraits> + OutIterator wachspress_weights_2( + const PointRange& polygon, const typename GeomTraits::Point_2& query, + OutIterator w_begin, const GeomTraits& traits) { + + Wachspress_weights_2 + wachspress(polygon, traits); + return wachspress(query, w_begin); + } + + /// \cond SKIP_IN_MANUAL + template< + typename PointRange, + typename OutIterator> + OutIterator wachspress_weights_2( + const PointRange& polygon, + const typename PointRange::value_type& query, + OutIterator w_begin) { + + using Point_2 = typename PointRange::value_type; + using GeomTraits = typename Kernel_traits::Kernel; + const GeomTraits traits; + return wachspress_weights_2( + polygon, query, w_begin, traits); + } + /// \endcond + +} // namespace Weights +} // namespace CGAL + +#endif // CGAL_WACHSPRESS_WEIGHTS_H diff --git a/Weights/package_info/Weights/copyright b/Weights/package_info/Weights/copyright new file mode 100644 index 000000000000..d40f5fad9cd0 --- /dev/null +++ b/Weights/package_info/Weights/copyright @@ -0,0 +1 @@ +GeometryFactory SARL (France) diff --git a/Weights/package_info/Weights/description.txt b/Weights/package_info/Weights/description.txt new file mode 100644 index 000000000000..ba798bb5f974 --- /dev/null +++ b/Weights/package_info/Weights/description.txt @@ -0,0 +1 @@ +This CGAL package enables to compute various weights. diff --git a/Weights/package_info/Weights/license.txt b/Weights/package_info/Weights/license.txt new file mode 100644 index 000000000000..8bb8efcb72b0 --- /dev/null +++ b/Weights/package_info/Weights/license.txt @@ -0,0 +1 @@ +GPL (v3 or later) diff --git a/Weights/package_info/Weights/long_description.txt b/Weights/package_info/Weights/long_description.txt new file mode 100644 index 000000000000..ba798bb5f974 --- /dev/null +++ b/Weights/package_info/Weights/long_description.txt @@ -0,0 +1 @@ +This CGAL package enables to compute various weights. diff --git a/Weights/package_info/Weights/maintainer b/Weights/package_info/Weights/maintainer new file mode 100644 index 000000000000..ced4c0722c5b --- /dev/null +++ b/Weights/package_info/Weights/maintainer @@ -0,0 +1 @@ +Dmitry Anisimov diff --git a/Weights/test/Weights/CMakeLists.txt b/Weights/test/Weights/CMakeLists.txt new file mode 100644 index 000000000000..e0a968fea0a9 --- /dev/null +++ b/Weights/test/Weights/CMakeLists.txt @@ -0,0 +1,32 @@ +# Created by the script cgal_create_cmake_script. +# This is the CMake script for compiling a CGAL application. + +project(Weights_Tests) + +cmake_minimum_required(VERSION 3.1...3.15) +set(CMAKE_CXX_STANDARD 14) + +find_package(CGAL REQUIRED COMPONENTS Core) +include(${CGAL_USE_FILE}) +include(CGAL_CreateSingleSourceCGALProgram) + +create_single_source_cgal_program("test_uniform_weights.cpp") +create_single_source_cgal_program("test_shepard_weights.cpp") +create_single_source_cgal_program("test_inverse_distance_weights.cpp") +create_single_source_cgal_program("test_three_point_family_weights.cpp") +create_single_source_cgal_program("test_projected_weights.cpp") + +create_single_source_cgal_program("test_wachspress_weights.cpp") +create_single_source_cgal_program("test_authalic_weights.cpp") + +create_single_source_cgal_program("test_mean_value_weights.cpp") +create_single_source_cgal_program("test_tangent_weights.cpp") + +create_single_source_cgal_program("test_discrete_harmonic_weights.cpp") +create_single_source_cgal_program("test_cotangent_weights.cpp") + +create_single_source_cgal_program("test_uniform_region_weights.cpp") +create_single_source_cgal_program("test_triangular_region_weights.cpp") +create_single_source_cgal_program("test_barycentric_region_weights.cpp") +create_single_source_cgal_program("test_voronoi_region_weights.cpp") +create_single_source_cgal_program("test_mixed_voronoi_region_weights.cpp") diff --git a/Weights/test/Weights/include/utils.h b/Weights/test/Weights/include/utils.h new file mode 100644 index 000000000000..bfbc8d78086b --- /dev/null +++ b/Weights/test/Weights/include/utils.h @@ -0,0 +1,557 @@ +#ifndef CGAL_WEIGHTS_TESTS_UTILS_H +#define CGAL_WEIGHTS_TESTS_UTILS_H + +// STL includes. +#include +#include +#include +#include +#include + +// CGAL includes. +#include +#include +#include + +namespace tests { + +template +FT get_tolerance() { + return FT(1) / FT(10000000000); +} + +template +std::vector< std::array > +get_all_triangles() { + + using Point_2 = typename Kernel::Point_2; + const std::array triangle0 = { + Point_2(-1, 0), Point_2(0, -1), Point_2(1, 0) + }; + const std::array triangle1 = { + Point_2(-2, 0), Point_2(0, -1), Point_2(2, 0) + }; + const std::array triangle2 = { + Point_2(-2, 0), Point_2(-2, -2), Point_2(2, 0) + }; + const std::array triangle3 = { + Point_2(-2, 0), Point_2(2, -2), Point_2(2, 0) + }; + return { triangle0, triangle1, triangle2, triangle3 }; +} + +template +std::vector< std::array > +get_symmetric_triangles() { + + using Point_2 = typename Kernel::Point_2; + const std::array triangle0 = { + Point_2(-1, 0), Point_2(0, -1), Point_2(1, 0) + }; + const std::array triangle1 = { + Point_2(-2, 0), Point_2(0, -1), Point_2(2, 0) + }; + const std::array triangle2 = { + Point_2(-3, 0), Point_2(0, -1), Point_2(3, 0) + }; + return { triangle0, triangle1, triangle2 }; +} + +template +std::vector< std::array > +get_uniform_triangles() { + + using Point_2 = typename Kernel::Point_2; + const std::array triangle0 = { + Point_2(-1, 0), Point_2(0, -1), Point_2(1, 0) + }; + const std::array triangle1 = { + Point_2(-2, 0), Point_2(0, -2), Point_2(2, 0) + }; + const std::array triangle2 = { + Point_2(1, 0), Point_2(-1, 0), Point_2(-1, -2) + }; + const std::array triangle3 = { + Point_2(1, -2), Point_2(1, 0), Point_2(-1, 0) + }; + return { triangle0, triangle1, triangle2, triangle3 }; +} + +template +std::vector< std::vector > +get_all_polygons() { + + using Point_2 = typename Kernel::Point_2; + const std::vector polygon0 = { + Point_2(-2, -2), Point_2(2, -2), Point_2(0, 2) + }; + const std::vector polygon1 = { + Point_2(-1, -1), Point_2(1, -1), Point_2(1, 1), Point_2(-1, 1) + }; + const std::vector polygon2 = { + Point_2(-2, 0), Point_2(0, -2), Point_2(2, 0), Point_2(0, 2) + }; + const std::vector polygon3 = { + Point_2(-2, -2), Point_2(2, -2), Point_2(2, 0), Point_2(0, 2), Point_2(-2, 0) + }; + return { polygon0, polygon1, polygon2, polygon3 }; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_query( + const Weight_wrapper& wrapper, + const typename Kernel::Point_2& query, + const std::array& neighbors) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT tol = get_tolerance(); + + // 2D configuration. + const Point_2& t2 = neighbors[0]; + const Point_2& r2 = neighbors[1]; + const Point_2& p2 = neighbors[2]; + const Point_2& q2 = query; + + // 3D configuration. + const Point_3 t3(t2.x(), t2.y(), 1); + const Point_3 r3(r2.x(), r2.y(), 1); + const Point_3 p3(p2.x(), p2.y(), 1); + const Point_3 q3(q2.x(), q2.y(), 1); + + const auto a2 = wrapper.weight_a(t2, r2, p2, q2); + const auto b2 = wrapper.weight_b(t2, r2, p2, q2); + CGAL_assertion(a2 >= FT(0) && b2 >= FT(0)); + if (a2 < FT(0) || b2 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a2 - b2) < tol); + if (CGAL::abs(a2 - b2) >= tol) return false; + + if (wrapper.supports_3d()) { + const auto a3 = wrapper.weight_a(t3, r3, p3, q3); + const auto b3 = wrapper.weight_b(t3, r3, p3, q3); + CGAL_assertion(a3 >= FT(0) && b3 >= FT(0)); + if (a3 < FT(0) || b3 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a3 - b3) < tol); + if (CGAL::abs(a3 - b3) >= tol) return false; + CGAL_assertion(CGAL::abs(a2 - a3) < tol); + CGAL_assertion(CGAL::abs(b2 - b3) < tol); + if (CGAL::abs(a2 - a3) >= tol) return false; + if (CGAL::abs(b2 - b3) >= tol) return false; + } + return true; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_symmetry_x( + const Weight_wrapper& wrapper, + const std::array& neighbors, + const typename Kernel::FT& x) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT tol = get_tolerance(); + + // 2D configuration. + const Point_2& t2 = neighbors[0]; + const Point_2& r2 = neighbors[1]; + const Point_2& p2 = neighbors[2]; + + // 3D configuration. + const Point_3 t3(t2.x(), t2.y(), 1); + const Point_3 r3(r2.x(), r2.y(), 1); + const Point_3 p3(p2.x(), p2.y(), 1); + + const auto a2 = wrapper.weight_a(t2, r2, p2, Point_2(-x, 0)); + const auto b2 = wrapper.weight_a(t2, r2, p2, Point_2(+x, 0)); + CGAL_assertion(a2 >= FT(0) && b2 >= FT(0)); + if (a2 < FT(0) || b2 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a2 - b2) < tol); + if (CGAL::abs(a2 - b2) >= tol) return false; + + if (wrapper.supports_3d()) { + const auto a3 = wrapper.weight_a(t3, r3, p3, Point_3(-x, 0, 1)); + const auto b3 = wrapper.weight_a(t3, r3, p3, Point_3(+x, 0, 1)); + CGAL_assertion(a3 >= FT(0) && b3 >= FT(0)); + if (a3 < FT(0) || b3 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a3 - b3) < tol); + if (CGAL::abs(a3 - b3) >= tol) return false; + CGAL_assertion(CGAL::abs(a2 - a3) < tol); + CGAL_assertion(CGAL::abs(b2 - b3) < tol); + if (CGAL::abs(a2 - a3) >= tol) return false; + if (CGAL::abs(b2 - b3) >= tol) return false; + } + return true; +} + +template< +typename Kernel, +typename Weight_wrapper_1, +typename Weight_wrapper_2> +bool test_compare( + const Weight_wrapper_1& wrapper1, + const Weight_wrapper_2& wrapper2, + const typename Kernel::Point_2& query, + const std::array& neighbors) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT tol = get_tolerance(); + + // 2D configuration. + const Point_2& t2 = neighbors[0]; + const Point_2& r2 = neighbors[1]; + const Point_2& p2 = neighbors[2]; + const Point_2& q2 = query; + + // 3D configuration. + const Point_3 t3(t2.x(), t2.y(), 1); + const Point_3 r3(r2.x(), r2.y(), 1); + const Point_3 p3(p2.x(), p2.y(), 1); + const Point_3 q3(q2.x(), q2.y(), 1); + + const auto a2 = wrapper1.weight_a(t2, r2, p2, q2); + const auto b2 = wrapper2.weight_a(t2, r2, p2, q2); + CGAL_assertion(a2 >= FT(0) && b2 >= FT(0)); + if (a2 < FT(0) || b2 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a2 - b2) < tol); + if (CGAL::abs(a2 - b2) >= tol) return false; + + if (wrapper1.supports_3d() && wrapper2.supports_3d()) { + const auto a3 = wrapper1.weight_a(t3, r3, p3, q3); + const auto b3 = wrapper2.weight_a(t3, r3, p3, q3); + CGAL_assertion(a3 >= FT(0) && b3 >= FT(0)); + if (a3 < FT(0) || b3 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a3 - b3) < tol); + if (CGAL::abs(a3 - b3) >= tol) return false; + } + return true; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_neighbors( + const Weight_wrapper& wrapper, + const std::array& neighbors) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT tol = get_tolerance(); + + // 2D configuration. + const Point_2& p2 = neighbors[0]; + const Point_2& q2 = neighbors[1]; + const Point_2& r2 = neighbors[2]; + + // 3D configuration. + const Point_3 p3(p2.x(), p2.y(), 1); + const Point_3 q3(q2.x(), q2.y(), 1); + const Point_3 r3(r2.x(), r2.y(), 1); + + const auto a2 = wrapper.weight(p2, q2, r2); + const auto a3 = wrapper.weight(p3, q3, r3); + CGAL_assertion(a2 >= FT(0) && a3 >= FT(0)); + if (a2 < FT(0) || a3 < FT(0)) return false; + CGAL_assertion(CGAL::abs(a2 - a3) < tol); + if (CGAL::abs(a2 - a3) >= tol) return false; + return true; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_area( + const Weight_wrapper& wrapper, + const std::array& neighbors) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + + // 2D configuration. + const Point_2& p2 = neighbors[0]; + const Point_2& q2 = neighbors[1]; + const Point_2& r2 = neighbors[2]; + + // 3D configuration. + const Point_3 p3(p2.x(), p2.y(), 1); + const Point_3 q3(q2.x(), q2.y(), 1); + const Point_3 r3(r2.x(), r2.y(), 1); + + const auto a2 = wrapper.weight(p2, q2, r2); + const auto a3 = wrapper.weight(p3, q3, r3); + CGAL_assertion(a2 <= CGAL::Weights::area(p2, q2, r2)); + CGAL_assertion(a3 <= CGAL::Weights::area(p3, q3, r3)); + if (a2 > CGAL::Weights::area(p2, q2, r2)) return false; + if (a3 > CGAL::Weights::area(p3, q3, r3)) return false; + CGAL_assertion(a2 >= FT(0)); + CGAL_assertion(a3 >= FT(0)); + if (a2 < FT(0)) return false; + if (a3 < FT(0)) return false; + return true; +} + +template +bool test_coordinates( + const Point& query, + const std::vector& polygon, + const std::vector& weights) { + + CGAL_assertion(weights.size() > 0); + if (weights.size() == 0) return false; + + // Compute the sum of weights. + const FT tol = get_tolerance(); + FT sum = FT(0); + for (const FT& weight : weights) { + sum += weight; + } + CGAL_assertion(sum >= tol); + if (sum < tol) return false; + + // Compute coordinates. + std::vector coordinates; + coordinates.reserve(weights.size()); + for (const FT& weight : weights) { + coordinates.push_back(weight / sum); + } + CGAL_assertion(coordinates.size() == weights.size()); + if (coordinates.size() != weights.size()) return false; + + // Test partition of unity. + sum = FT(0); + for (const FT& coordinate : coordinates) { + sum += coordinate; + } + CGAL_assertion(CGAL::abs(FT(1) - sum) < tol); + if (CGAL::abs(FT(1) - sum) >= tol) return false; + + // Test linear precision. + FT x = FT(0), y = FT(0); + for (std::size_t i = 0; i < polygon.size(); ++i) { + x += coordinates[i] * polygon[i].x(); + y += coordinates[i] * polygon[i].y(); + } + CGAL_assertion(CGAL::abs(query.x() - x) < tol); + CGAL_assertion(CGAL::abs(query.y() - y) < tol); + if (CGAL::abs(query.x() - x) >= tol) return false; + if (CGAL::abs(query.y() - y) >= tol) return false; + return true; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_on_polygon( + const Weight_wrapper& wrapper, + const typename Kernel::Point_2& query_2, + const std::vector& polygon_2) { + + // Get weights. + using FT = typename Kernel::FT; + CGAL_assertion(polygon_2.size() >= 3); + if (polygon_2.size() < 3) return false; + + // 2D version. + std::vector weights_2; + weights_2.reserve(polygon_2.size()); + wrapper.compute_on_polygon( + polygon_2, query_2, Kernel(), std::back_inserter(weights_2)); + CGAL_assertion(weights_2.size() == polygon_2.size()); + if (weights_2.size() != polygon_2.size()) return false; + if (!test_coordinates(query_2, polygon_2, weights_2)) return false; + + // 3D version. + using Point_3 = typename Kernel::Point_3; + const Point_3 query_3(query_2.x(), query_2.y(), 1); + std::vector polygon_3; + polygon_3.reserve(polygon_2.size()); + for (const auto& vertex_2 : polygon_2) { + polygon_3.push_back(Point_3(vertex_2.x(), vertex_2.y(), 1)); + } + CGAL_assertion(polygon_3.size() == polygon_2.size()); + if (polygon_3.size() != polygon_2.size()) return false; + const CGAL::Projection_traits_xy_3 ptraits; + + std::vector weights_3; + weights_3.reserve(polygon_3.size()); + wrapper.compute_on_polygon( + polygon_3, query_3, ptraits, std::back_inserter(weights_3)); + CGAL_assertion(weights_3.size() == polygon_3.size()); + if (weights_3.size() != polygon_3.size()) return false; + if (!test_coordinates(query_3, polygon_3, weights_3)) return false; + return true; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_barycentric_properties( + const Weight_wrapper& wrapper, + const typename Kernel::Point_2& query, + const std::vector& polygon) { + + // Get weights. + using FT = typename Kernel::FT; + const std::size_t n = polygon.size(); + CGAL_assertion(n >= 3); + if (n < 3) return false; + + // Check properties. + std::vector weights; + weights.reserve(n); + for (std::size_t i = 0; i < n; ++i) { + const std::size_t im = (i + n - 1) % n; + const std::size_t ip = (i + 1) % n; + const auto& t = polygon[im]; + const auto& r = polygon[i]; + const auto& p = polygon[ip]; + const auto& q = query; + const FT weight = wrapper.weight_a(t, r, p, q); + weights.push_back(weight); + } + CGAL_assertion(weights.size() == n); + if (weights.size() != n) return false; + if (!test_coordinates(query, polygon, weights)) return false; + return true; +} + +template< +typename Kernel, +typename Weight_wrapper_1, +typename Weight_wrapper_2> +bool test_analytic_weight( + const Weight_wrapper_1& weight, + const Weight_wrapper_2& alternative) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + + // Data. + const FT q = FT(1) / FT(4); + const FT h = FT(1) / FT(2); + const FT t = FT(3) / FT(4); + + const Point_2 zero(0, 0); + const std::vector queries = { + Point_2(-q, 0), Point_2(+q, 0), Point_2(0, -q), Point_2(0, +q), + Point_2(-h, 0), Point_2(+h, 0), Point_2(0, -h), Point_2(0, +h), + Point_2(-t, 0), Point_2(+t, 0), Point_2(0, -t), Point_2(0, +t) + }; + + // Test query points. + auto configs = get_all_triangles(); + for (const auto& config : configs) { + if (!test_query(weight, zero, config)) return false; + for (const auto& query : queries) { + if (!test_query(weight, query, config)) return false; + } + } + + // Test alternative formulations. + for (const auto& config : configs) { + if (!test_compare(weight, alternative, zero, config)) { + return false; + } + for (const auto& query : queries) { + if (!test_compare(weight, alternative, query, config)) { + return false; + } + } + } + + // Test symmetry along x axis. + configs = get_symmetric_triangles(); + for (const auto& config : configs) { + if (!test_symmetry_x(weight, config, q)) return false; + if (!test_symmetry_x(weight, config, h)) return false; + if (!test_symmetry_x(weight, config, t)) return false; + } + + // Test barycentric properties. + if (weight.is_barycentric()) { + const auto polygons = get_all_polygons(); + for (const auto& polygon : polygons) { + if (!test_barycentric_properties(weight, zero, polygon)) { + return false; + } + for (const auto& query : queries) { + if (!test_barycentric_properties(weight, query, polygon)) { + return false; + } + } + } + } + return true; +} + +template< +typename Kernel, +typename Weight_wrapper_1, +typename Weight_wrapper_2> +bool test_barycentric_weight( + const Weight_wrapper_1& weight, + const Weight_wrapper_2& alternative) { + + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + + // Data. + const FT q = FT(1) / FT(4); + const FT h = FT(1) / FT(2); + const Point_2 zero(0, 0); + const std::vector queries = { + Point_2(-h, 0), Point_2(+h, 0), Point_2(-q, 0), Point_2(+q, 0), + Point_2( 0, -h), Point_2( 0, +h), Point_2( 0, -q), Point_2( 0, +q), + Point_2(-h, -h), Point_2(+h, +h), Point_2(-q, -q), Point_2(+q, +q), + Point_2(-h, +q), Point_2(+h, -q), Point_2(-q, +h), Point_2(+q, -h) + }; + + // Test analytic formulations. + if (!test_analytic_weight(weight, alternative)) { + return false; + } + + // Test on polygons. + const auto polygons = get_all_polygons(); + for (const auto& polygon : polygons) { + if (!test_on_polygon(weight, zero, polygon)) return false; + for (const auto& query : queries) { + if (!test_on_polygon(weight, query, polygon)) { + return false; + } + } + } + return true; +} + +template< +typename Kernel, +typename Weight_wrapper> +bool test_region_weight(const Weight_wrapper& weight) { + + // Test neighborhoods. + auto configs = get_all_triangles(); + for (const auto& config : configs) { + if (!test_neighbors(weight, config)) return false; + } + + // Test areas. + configs = get_uniform_triangles(); + for (const auto& config : configs) { + if (!test_area(weight, config)) return false; + } + return true; +} + +} // namespace tests + +#endif // CGAL_WEIGHTS_TESTS_UTILS_H diff --git a/Weights/test/Weights/include/wrappers.h b/Weights/test/Weights/include/wrappers.h new file mode 100644 index 000000000000..9946236476a2 --- /dev/null +++ b/Weights/test/Weights/include/wrappers.h @@ -0,0 +1,265 @@ +#ifndef CGAL_WEIGHTS_TESTS_WRAPPERS_H +#define CGAL_WEIGHTS_TESTS_WRAPPERS_H + +// STL includes. +#include +#include + +// CGAL includes. +#include + +namespace wrappers { + +template +struct Authalic_wrapper { + using FT = typename Kernel::FT; + template + FT weight_a(const Point& t, const Point& r, const Point& p, const Point& q) const { + return CGAL::Weights::authalic_weight(t, r, p, q); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return + CGAL::Weights::half_authalic_weight( + CGAL::Weights::cotangent(t, r, q), + CGAL::Weights::squared_distance(q, r)) + + CGAL::Weights::half_authalic_weight( + CGAL::Weights::cotangent(q, r, p), + CGAL::Weights::squared_distance(q, r)); + } + bool supports_3d() const { return true; } + bool is_barycentric() const { return true; } +}; + +template +struct Cotangent_wrapper { + using FT = typename Kernel::FT; + template + FT weight_a(const Point& t, const Point& r, const Point& p, const Point& q) const { + return CGAL::Weights::cotangent_weight(t, r, p, q); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return + CGAL::Weights::half_cotangent_weight( + CGAL::Weights::cotangent(q, t, r)) + + CGAL::Weights::half_cotangent_weight( + CGAL::Weights::cotangent(r, p, q)); + } + bool supports_3d() const { return true; } + bool is_barycentric() const { return true; } +}; + +template +struct Tangent_wrapper { + using FT = typename Kernel::FT; + template + FT weight_a(const Point& t, const Point& r, const Point& p, const Point& q) const { + return CGAL::Weights::tangent_weight(t, r, p, q); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return + CGAL::Weights::half_tangent_weight( + CGAL::Weights::distance(r, q), + CGAL::Weights::distance(t, q), + CGAL::Weights::area(r, q, t), + CGAL::Weights::scalar_product(r, q, t)) + + CGAL::Weights::half_tangent_weight( + CGAL::Weights::distance(r, q), + CGAL::Weights::distance(p, q), + CGAL::Weights::area(p, q, r), + CGAL::Weights::scalar_product(p, q, r)); + } + bool supports_3d() const { return true; } + bool is_barycentric() const { return true; } +}; + +template +struct Wachspress_wrapper { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + FT weight_a(const Point_2& t, const Point_2& r, const Point_2& p, const Point_2& q) const { + return CGAL::Weights::wachspress_weight(t, r, p, q); + } + FT weight_a(const Point_3&, const Point_3&, const Point_3&, const Point_3&) const { + return FT(-1); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + template + void compute_on_polygon( + const Polygon& polygon, const Point& query, const Traits& traits, OutputIterator out) const { + CGAL::Weights::wachspress_weights_2(polygon, query, out, traits); + } + bool supports_3d() const { return false; } + bool is_barycentric() const { return true; } +}; + +template +struct Discrete_harmonic_wrapper { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + FT weight_a(const Point_2& t, const Point_2& r, const Point_2& p, const Point_2& q) const { + return CGAL::Weights::discrete_harmonic_weight(t, r, p, q); + } + FT weight_a(const Point_3&, const Point_3&, const Point_3&, const Point_3&) const { + return FT(-1); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + template + void compute_on_polygon( + const Polygon& polygon, const Point& query, const Traits& traits, OutputIterator out) const { + CGAL::Weights::discrete_harmonic_weights_2(polygon, query, out, traits); + } + bool supports_3d() const { return false; } + bool is_barycentric() const { return true; } +}; + +template +struct Mean_value_wrapper { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + FT weight_a(const Point_2& t, const Point_2& r, const Point_2& p, const Point_2& q) const { + return CGAL::Weights::mean_value_weight(t, r, p, q); + } + FT weight_a(const Point_3&, const Point_3&, const Point_3&, const Point_3&) const { + return FT(-1); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + template + void compute_on_polygon( + const Polygon& polygon, const Point& query, const Traits& traits, OutputIterator out) const { + CGAL::Weights::mean_value_weights_2(polygon, query, out, traits); + } + bool supports_3d() const { return false; } + bool is_barycentric() const { return true; } +}; + +template +struct Three_point_family_wrapper { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT a; + Three_point_family_wrapper(const FT a) : a(a) { } + FT weight_a(const Point_2& t, const Point_2& r, const Point_2& p, const Point_2& q) const { + return CGAL::Weights::three_point_family_weight(t, r, p, q, a); + } + FT weight_a(const Point_3&, const Point_3&, const Point_3&, const Point_3&) const { + return FT(-1); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + bool supports_3d() const { return false; } + bool is_barycentric() const { return true; } +}; + +template +struct Uniform_region_wrapper { + using FT = typename Kernel::FT; + template + FT weight(const Point& p, const Point& q, const Point& r) const { + return CGAL::Weights::uniform_area(p, q, r); + } +}; + +template +struct Triangular_region_wrapper { + using FT = typename Kernel::FT; + template + FT weight(const Point& p, const Point& q, const Point& r) const { + return CGAL::Weights::triangular_area(p, q, r); + } +}; + +template +struct Barycentric_region_wrapper { + using FT = typename Kernel::FT; + template + FT weight(const Point& p, const Point& q, const Point& r) const { + return CGAL::Weights::barycentric_area(p, q, r); + } +}; + +template +struct Voronoi_region_wrapper { + using FT = typename Kernel::FT; + template + FT weight(const Point& p, const Point& q, const Point& r) const { + return CGAL::Weights::voronoi_area(p, q, r); + } +}; + +template +struct Mixed_voronoi_region_wrapper { + using FT = typename Kernel::FT; + template + FT weight(const Point& p, const Point& q, const Point& r) const { + return CGAL::Weights::mixed_voronoi_area(p, q, r); + } +}; + +template +struct Uniform_wrapper { + using FT = typename Kernel::FT; + template + FT weight_a(const Point& t, const Point& r, const Point& p, const Point& q) const { + return CGAL::Weights::uniform_weight(t, r, p, q); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + bool supports_3d() const { return true; } + bool is_barycentric() const { return false; } +}; + +template +struct Inverse_distance_wrapper { + using FT = typename Kernel::FT; + template + FT weight_a(const Point& t, const Point& r, const Point& p, const Point& q) const { + return CGAL::Weights::inverse_distance_weight(t, r, p, q); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + bool supports_3d() const { return true; } + bool is_barycentric() const { return false; } +}; + +template +struct Shepard_wrapper { + using FT = typename Kernel::FT; + const FT a; + Shepard_wrapper(const FT a) : a(a) { } + template + FT weight_a(const Point& t, const Point& r, const Point& p, const Point& q) const { + return CGAL::Weights::shepard_weight(t, r, p, q, a); + } + template + FT weight_b(const Point& t, const Point& r, const Point& p, const Point& q) const { + return weight_a(t, r, p, q); + } + bool supports_3d() const { return true; } + bool is_barycentric() const { return false; } +}; + +} // namespace wrappers + +#endif // CGAL_WEIGHTS_TESTS_WRAPPERS_H diff --git a/Weights/test/Weights/test_authalic_weights.cpp b/Weights/test/Weights/test_authalic_weights.cpp new file mode 100644 index 000000000000..1aa439a3523f --- /dev/null +++ b/Weights/test/Weights/test_authalic_weights.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Authalic_wrapper aut; + const wrappers::Wachspress_wrapper whp; + return tests::test_analytic_weight(aut, whp); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_authalic_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_barycentric_region_weights.cpp b/Weights/test/Weights/test_barycentric_region_weights.cpp new file mode 100644 index 000000000000..49be26810bf9 --- /dev/null +++ b/Weights/test/Weights/test_barycentric_region_weights.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Barycentric_region_wrapper bar; + return tests::test_region_weight(bar); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_barycentric_region_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_cotangent_weights.cpp b/Weights/test/Weights/test_cotangent_weights.cpp new file mode 100644 index 000000000000..962ed140c44c --- /dev/null +++ b/Weights/test/Weights/test_cotangent_weights.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Cotangent_wrapper cot; + const wrappers::Discrete_harmonic_wrapper dhw; + return tests::test_analytic_weight(cot, dhw); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_cotangent_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_discrete_harmonic_weights.cpp b/Weights/test/Weights/test_discrete_harmonic_weights.cpp new file mode 100644 index 000000000000..b49d7beac513 --- /dev/null +++ b/Weights/test/Weights/test_discrete_harmonic_weights.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const Point_2 t1(-1, 0); + const Point_2 r1( 0, -1); + const Point_2 p1( 1, 0); + const Point_2 q1( 0, 0); + const Point_3 t2(-1, 0, 1); + const Point_3 r2( 0, -1, 1); + const Point_3 p2( 1, 0, 1); + const Point_3 q2( 0, 0, 1); + const FT a2 = CGAL::Weights::discrete_harmonic_weight(t1, r1, p1, q1); + const FT a3 = CGAL::Weights::internal::discrete_harmonic_weight(t2, r2, p2, q2); + assert(a2 >= FT(0)); + assert(a3 >= FT(0)); + assert(a2 == a3); + struct Traits : public Kernel { }; + assert(CGAL::Weights::discrete_harmonic_weight(t1, r1, p1, q1, Traits()) == a2); + assert(CGAL::Weights::internal::discrete_harmonic_weight(t2, r2, p2, q2, Traits()) == a3); + CGAL::Projection_traits_xy_3 ptraits; + const FT a23 = CGAL::Weights::discrete_harmonic_weight(t2, r2, p2, q2, ptraits); + assert(a23 >= FT(0)); + assert(a23 == a2 && a23 == a3); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Discrete_harmonic_wrapper dhw; + const wrappers::Cotangent_wrapper cot; + return tests::test_barycentric_weight(dhw, cot); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_discrete_harmonic_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_inverse_distance_weights.cpp b/Weights/test/Weights/test_inverse_distance_weights.cpp new file mode 100644 index 000000000000..a19dec047492 --- /dev/null +++ b/Weights/test/Weights/test_inverse_distance_weights.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const Point_2 p1(0, 0); + const Point_2 q1(1, 0); + const Point_3 p2(0, 0, 1); + const Point_3 q2(1, 0, 1); + const FT a2 = CGAL::Weights::inverse_distance_weight(p1, q1); + const FT a3 = CGAL::Weights::inverse_distance_weight(p2, q2); + assert(a2 == FT(1)); + assert(a3 == FT(1)); + assert(CGAL::Weights::inverse_distance_weight(p1, p1, q1, q1) == a2); + assert(CGAL::Weights::inverse_distance_weight(p2, p2, q2, q2) == a3); + struct Traits : public Kernel { }; + assert(CGAL::Weights::inverse_distance_weight(p1, p1, q1, q1, Traits()) == a2); + assert(CGAL::Weights::inverse_distance_weight(p2, p2, q2, q2, Traits()) == a3); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Inverse_distance_wrapper idw; + const wrappers::Shepard_wrapper spw(1); + return tests::test_analytic_weight(idw, spw); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_inverse_distance_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_mean_value_weights.cpp b/Weights/test/Weights/test_mean_value_weights.cpp new file mode 100644 index 000000000000..883ff79d5913 --- /dev/null +++ b/Weights/test/Weights/test_mean_value_weights.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const Point_2 t1(-1, 0); + const Point_2 r1( 0, -1); + const Point_2 p1( 1, 0); + const Point_2 q1( 0, 0); + const Point_3 t2(-1, 0, 1); + const Point_3 r2( 0, -1, 1); + const Point_3 p2( 1, 0, 1); + const Point_3 q2( 0, 0, 1); + const FT a2 = CGAL::Weights::mean_value_weight(t1, r1, p1, q1); + const FT a3 = CGAL::Weights::internal::mean_value_weight(t2, r2, p2, q2); + assert(a2 >= FT(0)); + assert(a3 >= FT(0)); + assert(a2 == a3); + struct Traits : public Kernel { }; + assert(CGAL::Weights::mean_value_weight(t1, r1, p1, q1, Traits()) == a2); + assert(CGAL::Weights::internal::mean_value_weight(t2, r2, p2, q2, Traits()) == a3); + CGAL::Projection_traits_xy_3 ptraits; + const FT a23 = CGAL::Weights::mean_value_weight(t2, r2, p2, q2, ptraits); + assert(a23 >= FT(0)); + assert(a23 == a2 && a23 == a3); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Mean_value_wrapper mvw; + const wrappers::Tangent_wrapper tan; + return tests::test_barycentric_weight(mvw, tan); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_mean_value_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_mixed_voronoi_region_weights.cpp b/Weights/test/Weights/test_mixed_voronoi_region_weights.cpp new file mode 100644 index 000000000000..3c78f1f18a2b --- /dev/null +++ b/Weights/test/Weights/test_mixed_voronoi_region_weights.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Mixed_voronoi_region_wrapper mix; + return tests::test_region_weight(mix); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_mixed_voronoi_region_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_projected_weights.cpp b/Weights/test/Weights/test_projected_weights.cpp new file mode 100644 index 000000000000..96d016444994 --- /dev/null +++ b/Weights/test/Weights/test_projected_weights.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_kernel() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + + using XY_Traits = CGAL::Projection_traits_xy_3; + using XZ_Traits = CGAL::Projection_traits_xz_3; + using YZ_Traits = CGAL::Projection_traits_yz_3; + + const XY_Traits xy_traits; + const XZ_Traits xz_traits; + const YZ_Traits yz_traits; + + const Point_2 t(-1, 0); + const Point_2 r( 0, -1); + const Point_2 p( 1, 0); + const Point_2 q( 0, 0); + + // XY. + const Point_3 t1(t.x(), t.y(), 1); + const Point_3 r1(r.x(), r.y(), 1); + const Point_3 p1(p.x(), p.y(), 1); + const Point_3 q1(q.x(), q.y(), 1); + + // XZ. + const Point_3 t2(t.x(), 1, t.y()); + const Point_3 r2(r.x(), 1, r.y()); + const Point_3 p2(p.x(), 1, p.y()); + const Point_3 q2(q.x(), 1, q.y()); + + // YZ. + const Point_3 t3(1, t.x(), t.y()); + const Point_3 r3(1, r.x(), r.y()); + const Point_3 p3(1, p.x(), p.y()); + const Point_3 q3(1, q.x(), q.y()); + + const FT ref_value = FT(4); + assert(CGAL::Weights::authalic_weight(t1, r1, p1, q1, xy_traits) == ref_value); + assert(CGAL::Weights::authalic_weight(t2, r2, p2, q2, xz_traits) == ref_value); + assert(CGAL::Weights::authalic_weight(t3, r3, p3, q3, yz_traits) == ref_value); + + assert(CGAL::Weights::wachspress_weight(t1, r1, p1, q1, xy_traits) == ref_value); + assert(CGAL::Weights::wachspress_weight(t2, r2, p2, q2, xz_traits) == ref_value); + assert(CGAL::Weights::wachspress_weight(t3, r3, p3, q3, yz_traits) == ref_value); + + assert(CGAL::Weights::cotangent_weight(t1, r1, p1, q1, xy_traits) == ref_value); + assert(CGAL::Weights::cotangent_weight(t2, r2, p2, q2, xz_traits) == ref_value); + assert(CGAL::Weights::cotangent_weight(t3, r3, p3, q3, yz_traits) == ref_value); + + assert(CGAL::Weights::discrete_harmonic_weight(t1, r1, p1, q1, xy_traits) == ref_value); + assert(CGAL::Weights::discrete_harmonic_weight(t2, r2, p2, q2, xz_traits) == ref_value); + assert(CGAL::Weights::discrete_harmonic_weight(t3, r3, p3, q3, yz_traits) == ref_value); + + assert(CGAL::Weights::tangent_weight(t1, r1, p1, q1, xy_traits) == ref_value); + assert(CGAL::Weights::tangent_weight(t2, r2, p2, q2, xz_traits) == ref_value); + assert(CGAL::Weights::tangent_weight(t3, r3, p3, q3, yz_traits) == ref_value); + + assert(CGAL::Weights::mean_value_weight(t1, r1, p1, q1, xy_traits) == ref_value); + assert(CGAL::Weights::mean_value_weight(t2, r2, p2, q2, xz_traits) == ref_value); + assert(CGAL::Weights::mean_value_weight(t3, r3, p3, q3, yz_traits) == ref_value); + + assert(CGAL::Weights::uniform_area(t1, r1, p1, xy_traits) == FT(1)); + assert(CGAL::Weights::uniform_area(t2, r2, p2, xz_traits) == FT(1)); + assert(CGAL::Weights::uniform_area(t3, r3, p3, yz_traits) == FT(1)); + + assert(CGAL::Weights::triangular_area(t1, r1, p1, xy_traits) >= FT(0)); + assert(CGAL::Weights::triangular_area(t2, r2, p2, xz_traits) >= FT(0)); + assert(CGAL::Weights::triangular_area(t3, r3, p3, yz_traits) >= FT(0)); + + assert(CGAL::Weights::barycentric_area(t1, r1, p1, xy_traits) >= FT(0)); + assert(CGAL::Weights::barycentric_area(t2, r2, p2, xz_traits) >= FT(0)); + assert(CGAL::Weights::barycentric_area(t3, r3, p3, yz_traits) >= FT(0)); + + assert(CGAL::Weights::voronoi_area(t1, r1, p1, xy_traits) >= FT(0)); + assert(CGAL::Weights::voronoi_area(t2, r2, p2, xz_traits) >= FT(0)); + assert(CGAL::Weights::voronoi_area(t3, r3, p3, yz_traits) >= FT(0)); + + assert(CGAL::Weights::mixed_voronoi_area(t1, r1, p1, xy_traits) >= FT(0)); + assert(CGAL::Weights::mixed_voronoi_area(t2, r2, p2, xz_traits) >= FT(0)); + assert(CGAL::Weights::mixed_voronoi_area(t3, r3, p3, yz_traits) >= FT(0)); + + assert(CGAL::Weights::uniform_weight(t1, r1, p1, q1, xy_traits) == FT(1)); + assert(CGAL::Weights::uniform_weight(t2, r2, p2, q2, xz_traits) == FT(1)); + assert(CGAL::Weights::uniform_weight(t3, r3, p3, q3, yz_traits) == FT(1)); + + assert(CGAL::Weights::inverse_distance_weight(t1, r1, p1, q1, xy_traits) == FT(1)); + assert(CGAL::Weights::inverse_distance_weight(t2, r2, p2, q2, xz_traits) == FT(1)); + assert(CGAL::Weights::inverse_distance_weight(t3, r3, p3, q3, yz_traits) == FT(1)); + + assert(CGAL::Weights::shepard_weight(t1, r1, p1, q1, 1, xy_traits) == FT(1)); + assert(CGAL::Weights::shepard_weight(t2, r2, p2, q2, 1, xz_traits) == FT(1)); + assert(CGAL::Weights::shepard_weight(t3, r3, p3, q3, 1, yz_traits) == FT(1)); + + assert(CGAL::Weights::three_point_family_weight(t1, r1, p1, q1, 1, xy_traits) == ref_value); + assert(CGAL::Weights::three_point_family_weight(t2, r2, p2, q2, 1, xz_traits) == ref_value); + assert(CGAL::Weights::three_point_family_weight(t3, r3, p3, q3, 1, yz_traits) == ref_value); +} + +int main() { + test_kernel(); + test_kernel(); + test_kernel(); + std::cout << "* test_projected_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_shepard_weights.cpp b/Weights/test/Weights/test_shepard_weights.cpp new file mode 100644 index 000000000000..e178b5741444 --- /dev/null +++ b/Weights/test/Weights/test_shepard_weights.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const Point_2 p1(0, 0); + const Point_2 q1(1, 0); + const Point_3 p2(0, 0, 1); + const Point_3 q2(1, 0, 1); + const FT a2 = CGAL::Weights::shepard_weight(p1, q1); + const FT a3 = CGAL::Weights::shepard_weight(p2, q2); + assert(a2 == FT(1)); + assert(a3 == FT(1)); + assert(CGAL::Weights::shepard_weight(p1, p1, q1, q1) == a2); + assert(CGAL::Weights::shepard_weight(p2, p2, q2, q2) == a3); + struct Traits : public Kernel { }; + assert(CGAL::Weights::shepard_weight(p1, p1, q1, q1, 1, Traits()) == a2); + assert(CGAL::Weights::shepard_weight(p2, p2, q2, q2, 1, Traits()) == a3); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Shepard_wrapper spwa(1); + const wrappers::Shepard_wrapper spwb(2); + const wrappers::Inverse_distance_wrapper idw; + assert(tests::test_analytic_weight(spwa, idw)); + return tests::test_analytic_weight(spwb, spwb); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_shepard_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_tangent_weights.cpp b/Weights/test/Weights/test_tangent_weights.cpp new file mode 100644 index 000000000000..8fe08620ad7b --- /dev/null +++ b/Weights/test/Weights/test_tangent_weights.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Tangent_wrapper tan; + const wrappers::Mean_value_wrapper mvw; + return tests::test_analytic_weight(tan, mvw); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_tangent_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_three_point_family_weights.cpp b/Weights/test/Weights/test_three_point_family_weights.cpp new file mode 100644 index 000000000000..e6ebfb0f64bc --- /dev/null +++ b/Weights/test/Weights/test_three_point_family_weights.cpp @@ -0,0 +1,64 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const Point_2 t1(-1, 0); + const Point_2 r1( 0, -1); + const Point_2 p1( 1, 0); + const Point_2 q1( 0, 0); + const Point_3 t2(-1, 0, 1); + const Point_3 r2( 0, -1, 1); + const Point_3 p2( 1, 0, 1); + const Point_3 q2( 0, 0, 1); + const FT a2 = CGAL::Weights::three_point_family_weight(t1, r1, p1, q1); + const FT a3 = CGAL::Weights::internal::three_point_family_weight(t2, r2, p2, q2); + assert(a2 >= FT(0)); + assert(a3 >= FT(0)); + assert(a2 == a3); + struct Traits : public Kernel { }; + assert(CGAL::Weights::three_point_family_weight(t1, r1, p1, q1, 1, Traits()) == a2); + assert(CGAL::Weights::internal::three_point_family_weight(t2, r2, p2, q2, 1, Traits()) == a3); + CGAL::Projection_traits_xy_3 ptraits; + const FT a23 = CGAL::Weights::three_point_family_weight(t2, r2, p2, q2, 0, ptraits); + assert(a23 >= FT(0)); + assert(a23 == a2 && a23 == a3); +} + +template +bool test_kernel() { + test_overloads(); + using FT = typename Kernel::FT; + const FT h = FT(1) / FT(2); + const wrappers::Three_point_family_wrapper tpfa(0); + const wrappers::Three_point_family_wrapper tpfb(1); + const wrappers::Three_point_family_wrapper tpfc(2); + const wrappers::Three_point_family_wrapper tpfd(h); + const wrappers::Wachspress_wrapper whp; + const wrappers::Mean_value_wrapper mvw; + const wrappers::Discrete_harmonic_wrapper dhw; + assert(tests::test_analytic_weight(tpfa, whp)); + assert(tests::test_analytic_weight(tpfb, mvw)); + assert(tests::test_analytic_weight(tpfc, dhw)); + return tests::test_analytic_weight(tpfd, tpfd); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_three_point_family_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_triangular_region_weights.cpp b/Weights/test/Weights/test_triangular_region_weights.cpp new file mode 100644 index 000000000000..c6bde65c4921 --- /dev/null +++ b/Weights/test/Weights/test_triangular_region_weights.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Triangular_region_wrapper tri; + return tests::test_region_weight(tri); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_triangular_region_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_uniform_region_weights.cpp b/Weights/test/Weights/test_uniform_region_weights.cpp new file mode 100644 index 000000000000..52124ede9964 --- /dev/null +++ b/Weights/test/Weights/test_uniform_region_weights.cpp @@ -0,0 +1,41 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT a = FT(1); + const Point_2 p(0, 0); + const Point_3 q(0, 0, 0); + assert(CGAL::Weights::uniform_area(p, p, p) == a); + assert(CGAL::Weights::uniform_area(q, q, q) == a); + struct Traits : public Kernel { }; + assert(CGAL::Weights::uniform_area(p, p, p, Traits()) == a); + assert(CGAL::Weights::uniform_area(q, q, q, Traits()) == a); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Uniform_region_wrapper uni; + return tests::test_region_weight(uni); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_uniform_region_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_uniform_weights.cpp b/Weights/test/Weights/test_uniform_weights.cpp new file mode 100644 index 000000000000..68ae5af005ce --- /dev/null +++ b/Weights/test/Weights/test_uniform_weights.cpp @@ -0,0 +1,41 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const FT a = FT(1); + const Point_2 p(0, 0); + const Point_3 q(0, 0, 0); + assert(CGAL::Weights::uniform_weight(p, p, p, p) == a); + assert(CGAL::Weights::uniform_weight(q, q, q, q) == a); + struct Traits : public Kernel { }; + assert(CGAL::Weights::uniform_weight(p, p, p, p, Traits()) == a); + assert(CGAL::Weights::uniform_weight(q, q, q, q, Traits()) == a); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Uniform_wrapper uni; + return tests::test_analytic_weight(uni, uni); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_uniform_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_voronoi_region_weights.cpp b/Weights/test/Weights/test_voronoi_region_weights.cpp new file mode 100644 index 000000000000..a0d6bcc43d40 --- /dev/null +++ b/Weights/test/Weights/test_voronoi_region_weights.cpp @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +bool test_kernel() { + const wrappers::Voronoi_region_wrapper vor; + return tests::test_region_weight(vor); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_voronoi_region_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +} diff --git a/Weights/test/Weights/test_wachspress_weights.cpp b/Weights/test/Weights/test_wachspress_weights.cpp new file mode 100644 index 000000000000..75b43f464378 --- /dev/null +++ b/Weights/test/Weights/test_wachspress_weights.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#include "include/utils.h" +#include "include/wrappers.h" + +// Typedefs. +using SCKER = CGAL::Simple_cartesian; +using EPICK = CGAL::Exact_predicates_inexact_constructions_kernel; +using EPECK = CGAL::Exact_predicates_exact_constructions_kernel; + +template +void test_overloads() { + using FT = typename Kernel::FT; + using Point_2 = typename Kernel::Point_2; + using Point_3 = typename Kernel::Point_3; + const Point_2 t1(-1, 0); + const Point_2 r1( 0, -1); + const Point_2 p1( 1, 0); + const Point_2 q1( 0, 0); + const Point_3 t2(-1, 0, 1); + const Point_3 r2( 0, -1, 1); + const Point_3 p2( 1, 0, 1); + const Point_3 q2( 0, 0, 1); + const FT a2 = CGAL::Weights::wachspress_weight(t1, r1, p1, q1); + const FT a3 = CGAL::Weights::internal::wachspress_weight(t2, r2, p2, q2); + assert(a2 >= FT(0)); + assert(a3 >= FT(0)); + assert(a2 == a3); + struct Traits : public Kernel { }; + assert(CGAL::Weights::wachspress_weight(t1, r1, p1, q1, Traits()) == a2); + assert(CGAL::Weights::internal::wachspress_weight(t2, r2, p2, q2, Traits()) == a3); + CGAL::Projection_traits_xy_3 ptraits; + const FT a23 = CGAL::Weights::wachspress_weight(t2, r2, p2, q2, ptraits); + assert(a23 >= FT(0)); + assert(a23 == a2 && a23 == a3); +} + +template +bool test_kernel() { + test_overloads(); + const wrappers::Wachspress_wrapper whp; + const wrappers::Authalic_wrapper aut; + return tests::test_barycentric_weight(whp, aut); +} + +int main() { + assert(test_kernel()); + assert(test_kernel()); + assert(test_kernel()); + std::cout << "* test_wachspress_weights: SUCCESS" << std::endl; + return EXIT_SUCCESS; +}