1 // Copyright (c) 2020 GeometryFactory Sarl (France).
2 // All rights reserved.
3 //
4 // This file is part of CGAL (www.cgal.org).
5 //
6 // $URL$
7 // $Id$
8 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial
9 //
10 // Author(s): Ahmed Essam <theartful.ae@gmail.com>
11 
12 #include <CGAL/ipower.h>
13 #include <QGraphicsView>
14 
15 #include "ArrangementTypes.h"
16 #include "ArrangementTypesUtils.h"
17 #include "PointSnapper.h"
18 
19 #include <boost/optional.hpp>
20 
21 template <typename Arr_>
22 class PointSnapper : public PointSnapperBase
23 {
24   using Arrangement = Arr_;
25   using Compute_squared_distance_2 = Rat_kernel::Compute_squared_distance_2;
26 
27 public:
28   PointSnapper(QGraphicsScene*, GridGraphicsItem*, Arrangement*);
29   boost::optional<Point_2> snapToArrangement(const QPointF& qpt) override;
30 
31 private:
32   Arrangement* arr;
33 };
34 
PointSnapperBase(QGraphicsScene * scene,GridGraphicsItem * grid)35 PointSnapperBase::PointSnapperBase(
36   QGraphicsScene* scene, GridGraphicsItem* grid) :
37     GraphicsSceneMixin(scene),
38     gridGraphicsItem{grid}, snapToGridEnabled{false},
39     snapToArrangementEnabled{false}
40 {
41 }
42 
43 namespace
44 {
45 struct ExplicitLambda
46 {
47   template <typename Arrangement>
operator ()__anon60074e120111::ExplicitLambda48   void operator()(demo_types::TypeHolder<Arrangement>)
49   {
50     Arrangement* arr = nullptr;
51     CGAL::assign(arr, arr_obj);
52     res = new PointSnapper<Arrangement>(scene, grid, arr);
53   }
54 
55   PointSnapperBase*& res;
56   QGraphicsScene* scene;
57   GridGraphicsItem* grid;
58   CGAL::Object& arr_obj;
59 };
60 } // anonymous namespace
61 
create(demo_types::TraitsType tt,QGraphicsScene * scene,GridGraphicsItem * grid,CGAL::Object arr_obj)62 PointSnapperBase* PointSnapperBase::create(
63   demo_types::TraitsType tt, QGraphicsScene* scene, GridGraphicsItem* grid,
64   CGAL::Object arr_obj)
65 {
66   PointSnapperBase* res;
67   ExplicitLambda explicit_lambda{res, scene, grid, arr_obj};
68   demo_types::visitArrangementType(tt, explicit_lambda);
69   return res;
70 }
71 
snapPoint(const QPointF & qpt)72 auto PointSnapperBase::snapPoint(const QPointF& qpt) -> Point_2
73 {
74   if (this->snapToGridEnabled && this->snapToArrangementEnabled)
75   {
76     Point_2 pt = {qpt.x(), qpt.y()};
77     auto gridPt = snapToGrid(qpt);
78     auto arrPt = snapToArrangement(qpt);
79     if (!arrPt)
80       return gridPt;
81     else if (
82       compute_squared_distance_2(pt, gridPt) <
83       compute_squared_distance_2(pt, *arrPt))
84       return gridPt;
85     else
86       return *arrPt;
87   }
88   else if (this->snapToGridEnabled)
89   {
90     return snapToGrid(qpt);
91   }
92   else if (this->snapToArrangementEnabled)
93   {
94     auto arrPt = snapToArrangement(qpt);
95     if (arrPt)
96       return *snapToArrangement(qpt);
97     else
98       return {qpt.x(), qpt.y()};
99   }
100   else
101     return Point_2{qpt.x(), qpt.y()};
102 }
103 
104 // snap to grid without loss of precision
snapToGrid(const QPointF & qpt)105 auto PointSnapperBase::snapToGrid(const QPointF& qpt) -> Point_2
106 {
107   Rational two{2};
108   Rational five{5};
109   Rational half{0.5};
110   // can't use 0.2 since it's not perfectly representable as a float/double
111   Rational fifth{Rational{1} / five};
112 
113   Rational x;
114   {
115     int a = gridGraphicsItem->getXPower2();
116     int b = gridGraphicsItem->getXPower5();
117     // we have to calculate l in BigRat to be exact
118     Rational lx;
119     if (a < 0)
120       lx = CGAL::ipower(half, -a);
121     else
122       lx = CGAL::ipower(two, a);
123     if (b < 0)
124       lx *= CGAL::ipower(fifth, -b);
125     else
126       lx *= CGAL::ipower(five, b);
127     x = lx * std::lround(CGAL::to_double(Rational(qpt.x()) / lx));
128   }
129 
130   Rational y;
131   {
132     int a = gridGraphicsItem->getYPower2();
133     int b = gridGraphicsItem->getYPower5();
134     // we have to calculate l in BigRat to be exact
135     Rational ly;
136     if (a < 0)
137       ly = CGAL::ipower(half, -a);
138     else
139       ly = CGAL::ipower(two, a);
140     if (b < 0)
141       ly *= CGAL::ipower(fifth, -b);
142     else
143       ly *= CGAL::ipower(five, b);
144     y = ly * std::lround(CGAL::to_double(Rational(qpt.y()) / ly));
145   }
146 
147   return Point_2{x, y};
148 }
149 
setSnapToGrid(bool val)150 void PointSnapperBase::setSnapToGrid(bool val)
151 {
152   this->snapToGridEnabled = val;
153 }
setSnapToArrangement(bool val)154 void PointSnapperBase::setSnapToArrangement(bool val)
155 {
156   this->snapToArrangementEnabled = val;
157 }
isSnapToGridEnabled()158 bool PointSnapperBase::isSnapToGridEnabled() { return this->snapToGridEnabled; }
isSnapToArrangementEnabled()159 bool PointSnapperBase::isSnapToArrangementEnabled()
160 {
161   return this->snapToArrangementEnabled;
162 }
163 
164 template <typename Arr_>
PointSnapper(QGraphicsScene * scene,GridGraphicsItem * grid,Arrangement * arr_)165 PointSnapper<Arr_>::PointSnapper(
166   QGraphicsScene* scene, GridGraphicsItem* grid, Arrangement* arr_) :
167     PointSnapperBase(scene, grid),
168     arr{arr_}
169 {
170 }
171 
172 template <typename Arrangement>
snapToArrangement(const QPointF & qpt,const QTransform & worldTransform,Arrangement * arr)173 inline boost::optional<PointSnapperBase::Point_2> snapToArrangement(
174   const QPointF& qpt, const QTransform& worldTransform, Arrangement* arr)
175 {
176   using Point_2 = PointSnapperBase::Point_2;
177   using Rational = PointSnapperBase::Rational;
178   using Compute_squared_distance_2 =
179     PointSnapperBase::Compute_squared_distance_2;
180 
181   Compute_squared_distance_2 compute_squared_distance_2;
182 
183   Point_2 initialPoint{qpt.x(), qpt.y()};
184   Point_2 closestPoint = initialPoint;
185 
186   bool first = true;
187   Rational minDist(0);
188 
189   static constexpr int PIXEL_RADIUS = 15;
190   Rational maxDist =
191     PIXEL_RADIUS * PIXEL_RADIUS *
192     std::abs(1 / (worldTransform.m11() * worldTransform.m22()));
193 
194   for (auto vit = arr->vertices_begin(); vit != arr->vertices_end(); ++vit)
195   {
196     auto arr_point = vit->point();
197     Point_2 point{arr_point.x(), arr_point.y()};
198     auto dist = compute_squared_distance_2(initialPoint, point);
199     if (first || (dist < minDist))
200     {
201       first = false;
202       minDist = dist;
203       closestPoint = point;
204     }
205   }
206 
207   if (!first && minDist < maxDist)
208     return closestPoint;
209   else
210     return {};
211 }
212 
213 template <typename Traits>
214 struct SnapToArrangement
215 {
216   using Point_2 = PointSnapperBase::Point_2;
217   template <typename Arrangement>
218   boost::optional<Point_2>
operator ()SnapToArrangement219   operator()(const QPointF& qpt, const QTransform&, Arrangement*)
220   {
221     return Point_2{qpt.x(), qpt.y()};
222   }
223 };
224 template <typename Kernel>
225 struct SnapToArrangement<CGAL::Arr_linear_traits_2<Kernel>>
226 {
227   using Point_2 = PointSnapperBase::Point_2;
228   template <typename Arrangement>
operator ()SnapToArrangement229   boost::optional<Point_2> operator()(
230     const QPointF& qpt, const QTransform& worldTransform, Arrangement* arr)
231   {
232     return snapToArrangement(qpt, worldTransform, arr);
233   }
234 };
235 template <typename Kernel>
236 struct SnapToArrangement<CGAL::Arr_segment_traits_2<Kernel>>
237 {
238   using Point_2 = PointSnapperBase::Point_2;
239   template <typename Arrangement>
operator ()SnapToArrangement240   boost::optional<Point_2> operator()(
241     const QPointF& qpt, const QTransform& worldTransform, Arrangement* arr)
242   {
243     return snapToArrangement(qpt, worldTransform, arr);
244   }
245 };
246 template <typename Kernel>
247 struct SnapToArrangement<CGAL::Arr_polyline_traits_2<Kernel>>
248 {
249   using Point_2 = PointSnapperBase::Point_2;
250   template <typename Arrangement>
operator ()SnapToArrangement251   boost::optional<Point_2> operator()(
252     const QPointF& qpt, const QTransform& worldTransform, Arrangement* arr)
253   {
254     return snapToArrangement(qpt, worldTransform, arr);
255   }
256 };
257 
258 template <typename Arr_>
snapToArrangement(const QPointF & qpt)259 auto PointSnapper<Arr_>::snapToArrangement(const QPointF& qpt)
260   -> boost::optional<Point_2>
261 {
262   using Traits = typename Arrangement::Geometry_traits_2;
263   auto view = getView();
264   if (view)
265     return SnapToArrangement<Traits>{}(qpt, view->transform(), arr);
266   else
267     return {};
268 }
269