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