// Copyright (c) 2017 GeometryFactory (France). // All rights reserved. // // This file is part of CGAL (www.cgal.org). // You can redistribute it and/or modify it under the terms of the GNU // General Public License as published by the Free Software Foundation, // either version 3 of the License, or (at your option) any later version. // // Licensees holding a valid commercial license may use this file in // accordance with the commercial license agreement provided with the software. // // This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE // WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. // // $URL$ // $Id$ // SPDX-License-Identifier: GPL-3.0+ // // Author(s) : Mael Rouxel-Labbé // The purpose of this file is to implement a function 'canonicalize_point()' // which transforms a point into its canonical representative (the representative // that belongs to the domain). To improve robustness, the function is allowed // to modify slightly the position of the point, snapping it to the domain if // it is epsilon-close (and outside). // // Although 'canoncalize_point()' is used by Periodic_3_mesh_3, this file is here // (in Periodic_3_triangulation_3) because of 'construct_periodic_point()', // which is a function used in P3T3.h and also needed by 'canonicalize_point()'. // However, P3M3 needs 'canoncalize_point()' without having access to a triangulation // and to avoid duplicating it, the function is here. // Geom_traits must be a model of the concept 'P3T3Traits' for 'construct_periodic_point()'. // Geom_traits must be a model of the concept 'P3T3RegularTraits' for everything else. #ifndef CGAL_PERIODIC_3_TRIANGULATION_3_CANONICALIZE_HELPER_H #define CGAL_PERIODIC_3_TRIANGULATION_3_CANONICALIZE_HELPER_H #include #include #include #include namespace CGAL { namespace P3T3 { // can't name it Periodic_3_triangulation_3 because it's already a class... namespace internal { template std::pair construct_periodic_point_exact(const typename Gt_::Point_3& p, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::Periodic_3_offset_3 Offset; typedef typename Geom_traits::Iso_cuboid_3 Iso_cuboid; const Iso_cuboid& domain = gt.get_domain(); typedef typename Geom_traits::Kernel K; typedef typename Exact_kernel_selector::Exact_kernel EK; typedef typename Exact_kernel_selector::C2E C2E; C2E to_exact; typedef Periodic_3_triangulation_traits_3 Exact_traits; Exact_traits etraits(to_exact(domain)); Offset transl(0, 0, 0); typename EK::Point_3 ep = to_exact(p); typename EK::Point_3 dp; const typename EK::Iso_cuboid_3& exact_domain = etraits.get_domain(); while(true) /* while not in */ { dp = etraits.construct_point_3_object()(ep, transl); if(dp.x() < exact_domain.xmin()) transl.x() += 1; else if(dp.y() < exact_domain.ymin()) transl.y() += 1; else if(dp.z() < exact_domain.zmin()) transl.z() += 1; else if(!(dp.x() < exact_domain.xmax())) transl.x() -= 1; else if(!(dp.y() < exact_domain.ymax())) transl.y() -= 1; else if(!(dp.z() < exact_domain.zmax())) transl.z() -= 1; else break; } return std::make_pair(p, transl); } // Given a point `p` in space, compute its offset `o` with respect // to the canonical instance and returns (p, o) template std::pair construct_periodic_point(const typename Gt_::Point_3& p, bool& encountered_issue, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::Point_3 Point; typedef typename Geom_traits::Periodic_3_offset_3 Offset; typedef typename Geom_traits::Iso_cuboid_3 Iso_cuboid; const Iso_cuboid& domain = gt.get_domain(); // Check if p lies within the domain. If not, translate. if(!(p.x() < domain.xmin()) && p.x() < domain.xmax() && !(p.y() < domain.ymin()) && p.y() < domain.ymax() && !(p.z() < domain.zmin()) && p.z() < domain.zmax()) return std::make_pair(p, Offset()); typename Geom_traits::Construct_point_3 cp = gt.construct_point_3_object(); // Numerical approximations might create inconsistencies between the constructions // and the comparisons. For example in a cubic domain of size 2: // 1. initial point: P(2+1e-17, 0, 0) // 2. the function computes an offset(1, 0, 0), // 3. P + (-1, 0, 0) * domain_size constructs Q(-1e-17, 0, 0) // numerical approximation // 4. the function computes an offset of (-1, 0, 0) // 5. Q + (1, 0, 0) * domain_size constructs (2+1e-17, 0, 0) (that is P) // And the function is looping... // // If this is happening the 'Last_change' enum will break this infinite // loop and return the wrong point and the 'encountered_issue' bool will be // set to 'true'. An exact version of this function should then be called. enum Last_change { NO_LAST_CHANGE, INCREASED_X, DECREASED_X, INCREASED_Y, DECREASED_Y, INCREASED_Z, DECREASED_Z }; Last_change lc = NO_LAST_CHANGE; bool in = false; Offset transl(0, 0, 0); Point dp; while(!in) { dp = cp(p, transl); if(dp.x() < domain.xmin()) { if(lc == DECREASED_X) // stuck in a loop break; lc = INCREASED_X; transl.x() += 1; } else if(dp.y() < domain.ymin()) { if(lc == DECREASED_Y) // stuck in a loop break; lc = INCREASED_Y; transl.y() += 1; } else if(dp.z() < domain.zmin()) { if(lc == DECREASED_Z) // stuck in a loop break; lc = INCREASED_Z; transl.z() += 1; } else if(!(dp.x() < domain.xmax())) { if(lc == INCREASED_X) // stuck in a loop break; lc = DECREASED_X; transl.x() -= 1; } else if(!(dp.y() < domain.ymax())) { if(lc == INCREASED_Y) // stuck in a loop break; lc = DECREASED_Y; transl.y() -= 1; } else if(!(dp.z() < domain.zmax())) { if(lc == INCREASED_Z) // stuck in a loop break; lc = DECREASED_Z; transl.z() -= 1; } else { in = true; } } std::pair pp(p, transl); if(dp.x() < domain.xmin() || !(dp.x() < domain.xmax()) || dp.y() < domain.ymin() || !(dp.y() < domain.ymax()) || dp.z() < domain.zmin() || !(dp.z() < domain.zmax())) { encountered_issue = true; pp = construct_periodic_point_exact(p, gt); } return pp; } template bool is_point_too_close_to_border(const std::pair& pbp, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::FT FT; typedef typename Geom_traits::Point_3 Bare_point; typedef typename Geom_traits::Iso_cuboid_3 Iso_cuboid; typename Geom_traits::Construct_point_3 cp = gt.construct_point_3_object(); const Bare_point p = cp(pbp.first /*point*/, pbp.second /*offset*/); const FT px = p.x(); const FT py = p.y(); const FT pz = p.z(); const Iso_cuboid& domain = gt.get_domain(); const FT dxm = domain.xmin(); const FT dym = domain.ymin(); const FT dzm = domain.zmin(); const FT dxM = domain.xmax(); const FT dyM = domain.ymax(); const FT dzM = domain.zmax(); // simply comparing to FT::epsilon() is probably not completely satisfactory const FT eps = std::numeric_limits::epsilon(); FT diff = CGAL::abs(px - dxm); if(diff < eps && diff > 0) return true; diff = CGAL::abs(px - dxM); if(diff < eps && diff > 0) return true; diff = CGAL::abs(py - dym); if(diff < eps && diff > 0) return true; diff = CGAL::abs(py - dyM); if(diff < eps && diff > 0) return true; diff = CGAL::abs(pz - dzm); if(diff < eps && diff > 0) return true; diff = CGAL::abs(pz - dzM); if(diff < eps && diff > 0) return true; return false; } template typename Gt_::Point_3 snap_to_domain_border(const typename Gt_::Point_3& p, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::FT FT; typedef typename Geom_traits::Iso_cuboid_3 Iso_cuboid; const FT px = p.x(); const FT py = p.y(); const FT pz = p.z(); FT sx = px, sy = py, sz = pz; const Iso_cuboid& domain = gt.get_domain(); const FT dxm = domain.xmin(); const FT dym = domain.ymin(); const FT dzm = domain.zmin(); const FT dxM = domain.xmax(); const FT dyM = domain.ymax(); const FT dzM = domain.zmax(); // simply comparing to FT::epsilon() is probably not completely satisfactory const FT eps = std::numeric_limits::epsilon(); if(CGAL::abs(px - dxm) < eps) sx = dxm; if(CGAL::abs(px - dxM) < eps) sx = dxM; if(CGAL::abs(py - dym) < eps) sy = dym; if(CGAL::abs(py - dyM) < eps) sy = dyM; if(CGAL::abs(pz - dzm) < eps) sz = dzm; if(CGAL::abs(pz - dzM) < eps) sz = dzM; return gt.construct_point_3_object()(sx, sy, sz); } template typename Gt_::Weighted_point_3 snap_to_domain_border(const typename Gt_::Weighted_point_3& p, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::Point_3 Bare_point; typename Geom_traits::Compute_weight_3 cw = gt.compute_weight_3_object(); const Bare_point snapped_p = snap_to_domain_border(gt.construct_point_3_object()(p), gt); return gt.construct_weighted_point_3_object()(snapped_p, cw(p)); } /// transform a bare point (living anywhere in space) into the canonical /// instance of the same bare point that lives inside the base domain template typename Gt_::Point_3 robust_canonicalize_point(const typename Gt_::Point_3& p, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::Point_3 Bare_point; typedef typename Geom_traits::Periodic_3_offset_3 Offset; typedef typename Geom_traits::Iso_cuboid_3 Iso_cuboid; const Iso_cuboid& domain = gt.get_domain(); if(p.x() >= domain.xmin() && p.x() < domain.xmax() && p.y() >= domain.ymin() && p.y() < domain.ymax() && p.z() >= domain.zmin() && p.z() < domain.zmax()) return p; bool should_snap = false; std::pair pbp = construct_periodic_point(p, should_snap, gt); if(!should_snap) { // Even if there is no issue while constructing the canonical point, // snap the point if it's too close to a border of the domain should_snap = is_point_too_close_to_border(pbp, gt); } if(should_snap) { Bare_point sp = snap_to_domain_border(p, gt); // might have snapped to a 'max' of the domain, which is not in the domain // note: we could snap to 'min' all the time in 'snap_to_domain_border' // but this is clearer like that (and costs very little since we should // not have to use exact computations too often) return robust_canonicalize_point(sp, gt); } typename Geom_traits::Construct_point_3 cp = gt.construct_point_3_object(); Bare_point canonical_p = cp(pbp.first /*point*/, pbp.second /*offset*/); CGAL_postcondition( !(canonical_p.x() < domain.xmin()) && (canonical_p.x() < domain.xmax()) ); CGAL_postcondition( !(canonical_p.y() < domain.ymin()) && (canonical_p.y() < domain.ymax()) ); CGAL_postcondition( !(canonical_p.z() < domain.zmin()) && (canonical_p.z() < domain.zmax()) ); return canonical_p; } /// transform a weighted point (living anywhere in space) into the canonical /// instance of the same weighted point that lives inside the base domain template typename Gt_::Weighted_point_3 robust_canonicalize_point(const typename Gt_::Weighted_point_3& wp, const Gt_& gt) { typedef Gt_ Geom_traits; typedef typename Geom_traits::Point_3 Bare_point; typedef typename Geom_traits::Iso_cuboid_3 Iso_cuboid; const Iso_cuboid& domain = gt.get_domain(); if(wp.x() >= domain.xmin() && wp.x() < domain.xmax() && wp.y() >= domain.ymin() && wp.y() < domain.ymax() && wp.z() >= domain.zmin() && wp.z() < domain.zmax()) return wp; typename Geom_traits::Construct_point_3 cp = gt.construct_point_3_object(); typename Geom_traits::Compute_weight_3 cw = gt.compute_weight_3_object(); typename Geom_traits::Construct_weighted_point_3 cwp = gt.construct_weighted_point_3_object(); const Bare_point& bp = cp(wp); Bare_point canonical_point = robust_canonicalize_point(bp, gt); return cwp(canonical_point, cw(wp)); } } // end namespace internal } // end namespace Periodic_3_triangulation_3 } // end namespace CGAL #endif // CGAL_PERIODIC_3_TRIANGULATION_3_CANONICALIZE_HELPER_H