Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
pure-transform.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Class for pure transformations, such as translating, scaling, stretching, skewing, and rotating
4 *
5 * Authors:
6 * Diederik van Lierop <mail@diedenrezi.nl>
7 *
8 * Copyright (C) 2015 Diederik van Lierop
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include "pure-transform.h"
14#include "snap.h"
15
16namespace Inkscape
17
18{
19
20void PureTransform::snap(::SnapManager *sm, std::vector<Inkscape::SnapCandidatePoint> const &points, Geom::Point const &pointer) {
21 std::vector<Inkscape::SnapCandidatePoint> transformed_points;
22 Geom::Rect bbox;
23
24 long source_num = 0;
25 for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); ++i) {
26
27 /* Work out the transformed version of this point */
28 Geom::Point transformed = getTransformedPoint(*i); // _transformPoint(*i, transformation_type, transformation, origin, dim, uniform);
29
30 // add the current transformed point to the box hulling all transformed points
31 if (i == points.begin()) {
32 bbox = Geom::Rect(transformed, transformed);
33 } else {
34 bbox.expandTo(transformed);
35 }
36
37 transformed_points.emplace_back(transformed, (*i).getSourceType(), source_num, Inkscape::SNAPTARGET_UNDEFINED, Geom::OptRect());
38 source_num++;
39 }
40
41 /* The current best metric for the best transformation; lower is better, whereas Geom::infinity()
42 ** means that we haven't snapped anything.
43 */
44 Inkscape::SnapCandidatePoint best_original_point;
45 g_assert(best_snapped_point.getAlwaysSnap() == false); // Check initialization of snapped point
46 g_assert(best_snapped_point.getAtIntersection() == false);
47 g_assert(best_snapped_point.getSnapped() == false); // Check initialization to catch any regression
48
49 std::vector<Inkscape::SnapCandidatePoint>::iterator j = transformed_points.begin();
50
51 // std::cout << std::endl;
52 bool first_free_snap = true;
53
54 for (std::vector<Inkscape::SnapCandidatePoint>::const_iterator i = points.begin(); i != points.end(); ++i) {
55
56 // If we have a collection of SnapCandidatePoints, with mixed constrained snapping and free snapping
57 // requirements (this can happen when scaling, see PureScale::snap()), then freeSnap might never see the
58 // SnapCandidatePoint with source_num == 0. The freeSnap() method in the object snapper depends on this,
59 // because only for source-num == 0 the target nodes will be collected. Therefore we enforce that the first
60 // SnapCandidatePoint that is to be freeSnapped always has source_num == 0;
61 // TODO: This is a bit ugly so fix this; do we need sourcenum for anything else? if we don't then get rid
62 // of it and explicitly communicate to the object snapper that this is a first point
63 if (first_free_snap) {
64 (*j).setSourceNum(0);
65 first_free_snap = false;
66 }
67
68 Inkscape::SnappedPoint snapped_point = snap(sm, *j, (*i).getPoint(), bbox); // Calls the snap() method of the derived classes
69
70 snapped_point.setPointerDistance(Geom::L2(pointer - (*i).getPoint()));
71
72 /*Find the transformation that describes where the snapped point has
73 ** ended up, and also the metric for this transformation.
74 */
75
76 bool store_best_snap = false;
77 if (snapped_point.getSnapped()) {
78 // We snapped; keep track of the best snap
79 if (best_snapped_point.isOtherSnapBetter(snapped_point, true)) {
80 store_best_snap = true;
81 }
82 } else {
83 // So we didn't snap for this point
85 // ... and none of the points before snapped either
86 // We might still need to apply a constraint though, if we tried a constrained snap. And
87 // in case of a free snap we might have use for the transformed point, so let's return that
88 // point, whether it's constrained or not
89
90 if (best_snapped_point.isOtherSnapBetter(snapped_point, true) ) {
91 // .. so we must keep track of the best non-snapped constrained point.. but what
92 // is the best? There is no best, or is there? We cannot compare on snapped distance
93 // because neither has snapped, and both have their snapped distance set to infinity.
94 // There might be a difference in "constrainedness" though, 1D vs 2D snapping
95 store_best_snap = true;
96 }
97 }
98 }
99
100 if (store_best_snap || i == points.begin()) {
101 best_original_point = (*i);
102 best_snapped_point = snapped_point; // Can be a point that didn't snap, but then at least we
103 // return something meaningful; we might have use for the transformation. The default
104 // snapped_point, as initialized before this loop, is not very meaningful at all.
105 }
106
107 ++j;
108 }
109
110 /* The current best transformation */
111 //Geom::Point best_transformation = getResult(best_original_point, best_snapped_point);
112 storeTransform(best_original_point, best_snapped_point);
113
115
116 // Using " < 1e6" instead of " < Geom::infinity()" for catching some rounding errors
117 // These rounding errors might be caused by NRRects, see bug #1584301
118 best_snapped_point.setSnapDistance(best_metric < 1e6 ? best_metric : Geom::infinity());
119}
120
121
122
123
124
128
129void PureTranslate::storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) {
130 /* Consider the case in which a box is almost aligned with a grid in both
131 * horizontal and vertical directions. The distance to the intersection of
132 * the grid lines will always be larger then the distance to a single grid
133 * line. If we prefer snapping to an intersection over to a single
134 * grid line, then we cannot use "metric = Geom::L2(result)". Therefore the
135 * snapped distance will be used as a metric. Please note that the snapped
136 * distance to an intersection is defined as the distance to the nearest line
137 * of the intersection, and not to the intersection itself!
138 */
139 // Only for translations, the relevant metric will be the real snapped distance,
140 // so we don't have to do anything special here
141 _vector_snapped = snapped_point.getPoint() - original_point.getPoint();
142}
143
144SnappedPoint PureTranslate::snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point /*pt_orig*/, Geom::OptRect const &bbox_to_snap) const {
145 return sm->freeSnap(p, bbox_to_snap);
146}
147
149 // Calculate a constraint dedicated for this specific point
150 // When doing a constrained translation, all points will move in the same direction, i.e.
151 // either horizontally or vertically. The lines along which they move are therefore all
152 // parallel, but might not be co-linear. Therefore we will have to specify the point through
153 // which the constraint-line runs here, for each point individually.
154 Snapper::SnapConstraint dedicated_constraint = Snapper::SnapConstraint(pt_orig, _direction);
155 return sm->constrainedSnap(p, dedicated_constraint, bbox_to_snap);
156}
157
158
159
160
161
165
166void PureScale::storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) {
168 // If this point *i is horizontally or vertically aligned with
169 // the origin of the scaling, then it will scale purely in X or Y
170 // We can therefore only calculate the scaling in this direction
171 // and the scaling factor for the other direction should remain
172 // untouched (unless scaling is uniform of course)
173 Geom::Point const a = snapped_point.getPoint() - _origin; // vector to snapped point
174 Geom::Point const b = original_point.getPoint() - _origin; // vector to original point (not the transformed point!)
175 for (int index = 0; index < 2; index++) {
176 if (fabs(b[index]) > 1e-4) { // if SCALING CAN occur in this direction
177 if (fabs(fabs(a[index]/b[index]) - fabs(_scale[index])) > 1e-7) { // if SNAPPING DID occur in this direction
178 _scale_snapped[index] = a[index] / b[index]; // then calculate it!
179 // _scale_snapped will be (1,1) if we haven't snapped, because the snapped point equals the original point
180 }
181 // we might have left result[1-index] = Geom::infinity() if scaling didn't occur in the other direction
182 }
183 }
184
186 // This point must have been at the origin, so we cannot possibly snap; it won't scale (i.e. won't move while dragging)
187 snapped_point.setSnapDistance(Geom::infinity());
188 snapped_point.setSecondSnapDistance(Geom::infinity());
189 return;
190 }
191
192 if (_uniform) {
193 // Lock the scaling the be uniform, but keep the sign such that we don't change which quadrant we have dragged into
194 if (fabs(_scale_snapped[0]) < fabs(_scale_snapped[1])) {
195 _scale_snapped[1] = fabs(_scale_snapped[0]) * Geom::sgn(_scale[1]);
196 } else {
197 _scale_snapped[0] = fabs(_scale_snapped[1]) * Geom::sgn(_scale[0]);
198 }
199 }
200
201 // Don't ever exit with one of scaling components uninitialized
202 for (int index = 0; index < 2; index++) {
205 }
206 }
207
208 // Compare the resulting scaling with the desired scaling
209 Geom::Point scale_metric = _scale_snapped.vector() - _scale.vector();
210 snapped_point.setSnapDistance(Geom::L2(scale_metric));
211 snapped_point.setSecondSnapDistance(Geom::infinity());
212}
213
214// When scaling, a point aligned either horizontally or vertically with the origin can only
215// move in that specific direction; therefore it should only snap in that direction, so this
216// then becomes a constrained snap; otherwise we can use a free snap;
217SnappedPoint PureScale::snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const {
218 Geom::Point const b = (pt_orig - _origin); // vector to original point (not the transformed point!)
219 bool const c1 = fabs(b[Geom::X]) < 1e-6;
220 bool const c2 = fabs(b[Geom::Y]) < 1e-6;
221 if ((c1 || c2) && !(c1 && c2)) {
222 Geom::Point cvec; cvec[c1] = 1.;
224 return sm->constrainedSnap(p, dedicated_constraint, bbox_to_snap);
225 } else {
226 return sm->freeSnap(p, bbox_to_snap);
227 }
228}
229
231 // When constrained scaling, only uniform scaling is supported.
232 // When uniformly scaling, each point will have its own unique constraint line,
233 // running from the scaling origin to the original untransformed point. We will
234 // calculate that line here as a dedicated constraint
235 Geom::Point b = pt_orig - _origin;
237 return sm->constrainedSnap(p, dedicated_constraint, bbox_to_snap);
238}
239
240
241
242
243
245 Geom::Scale s(1, 1);
246 if (_uniform)
247 s[Geom::X] = s[Geom::Y] = _magnitude;
248 else {
250 s[1 - _direction] = 1;
251 }
252 return ((p.getPoint() - _origin) * s) + _origin;
253}
254
256 Snapper::SnapConstraint dedicated_constraint;
257 if (_uniform) {
258 // When uniformly stretching, each point will have its own unique constraint line,
259 // running from the scaling origin to the original untransformed point. We will
260 // calculate that line here
261 Geom::Point b = pt_orig - _origin;
262 dedicated_constraint = Inkscape::Snapper::SnapConstraint(_origin, b); // dedicated constraint
263 } else {
264 Geom::Point cvec; cvec[_direction] = 1.;
265 dedicated_constraint = Inkscape::Snapper::SnapConstraint(pt_orig, cvec);
266 }
267
268 return sm->constrainedSnap(p, dedicated_constraint, bbox_to_snap);
269}
270
271void PureStretchConstrained::storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) {
272 Geom::Point const a = snapped_point.getPoint() - _origin; // vector to snapped point
273 Geom::Point const b = original_point.getPoint() - _origin; // vector to original point (not the transformed point!)
274
276 if (fabs(b[_direction]) > 1e-4) { // if STRETCHING will occur for this point
279 } else { // STRETCHING might occur for this point, but only when the stretching is uniform
280 if (_uniform && fabs(b[1-_direction]) > 1e-4) {
283 }
284 }
285
286 // _stretch_snapped might have one or both components at infinity!
287
288 // Store the metric for this transformation as a virtual distance
289 snapped_point.setSnapDistance(std::abs(_stretch_snapped[_direction] - _magnitude));
290 snapped_point.setSecondSnapDistance(Geom::infinity());
291}
292
293
294
295
296
298 Geom::Point transformed;
299 // Apply the skew factor
300 transformed[_direction] = (p.getPoint())[_direction] + _skew * ((p.getPoint())[1 - _direction] - _origin[1 - _direction]);
301 // While skewing, mirroring and scaling (by integer multiples) in the opposite direction is also allowed.
302 // Apply that scale factor here
303 transformed[1-_direction] = (p.getPoint() - _origin)[1 - _direction] * _scale + _origin[1 - _direction];
304 return transformed;
305}
306
308 // Snapping the nodes of the bounding box of a selection that is being transformed, will only work if
309 // the transformation of the bounding box is equal to the transformation of the individual nodes. This is
310 // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew,
311 // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping
312 // of bounding boxes is not allowed here.
314
315 Geom::Point constraint_vector;
316 constraint_vector[1-_direction] = 0.0;
317 constraint_vector[_direction] = 1.0;
318
319 return sm->constrainedSnap(p, Inkscape::Snapper::SnapConstraint(constraint_vector), bbox_to_snap);
320}
321
322void PureSkewConstrained::storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) {
323 Geom::Point const b = original_point.getPoint() - _origin; // vector to original point (not the transformed point!)
324 _skew_snapped = (snapped_point.getPoint()[_direction] - (original_point.getPoint())[_direction]) / b[1 - _direction]; // skew factor
325
326 // Store the metric for this transformation as a virtual distance
327 snapped_point.setSnapDistance(std::abs(_skew_snapped - _skew));
328 snapped_point.setSecondSnapDistance(Geom::infinity());
329}
330
331
332
333
337
339 // Snapping the nodes of the bounding box of a selection that is being transformed, will only work if
340 // the transformation of the bounding box is equal to the transformation of the individual nodes. This is
341 // NOT the case for example when rotating or skewing. The bounding box itself cannot possibly rotate or skew,
342 // so it's corners have a different transformation. The snappers cannot handle this, therefore snapping
343 // of bounding boxes is not allowed here.
345
346 // Calculate a constraint dedicated for this specific point
347 Geom::Point b = pt_orig - _origin;
348 Geom::Coord r = Geom::L2(b); // the radius of the circular constraint
350 return sm->constrainedSnap(p, dedicated_constraint, bbox_to_snap);
351}
352
353void PureRotateConstrained::storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) {
354 Geom::Point const a = snapped_point.getPoint() - _origin; // vector to snapped point
355 Geom::Point const b = (original_point.getPoint() - _origin); // vector to original point (not the transformed point!)
356 // a is vector to snapped point; b is vector to original point; now lets calculate angle between a and b
357 _angle_snapped = atan2(Geom::dot(Geom::rot90(b), a), Geom::dot(b, a));
358 if (Geom::L2(b) < 1e-9) { // points too close to the rotation center will not move. Don't try to snap these
359 // as they will always yield a perfect snap result if they're already snapped beforehand (e.g.
360 // when the transformation center has been snapped to a grid intersection in the selector tool)
361 snapped_point.setSnapDistance(Geom::infinity());
362 // PS1: Apparently we don't have to do this for skewing, but why?
363 // PS2: We cannot easily filter these points upstream, e.g. in the grab() method (seltrans.cpp)
364 // because the rotation center will change when pressing shift, and grab() won't be recalled.
365 // Filtering could be done in handleRequest() (again in seltrans.cpp), by iterating through
366 // the snap candidates. But hey, we're iterating here anyway.
367 } else {
368 snapped_point.setSnapDistance(fabs(_angle_snapped - _angle));
369 }
370 snapped_point.setSecondSnapDistance(Geom::infinity());
371
372}
373
374}
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
Point vector() const
Definition transforms.h:171
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
Geom::Point getTransformedPoint(SnapCandidatePoint const &p) const override
void storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) override
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
Geom::Scale _scale_snapped
void storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) override
Geom::Point getTransformedPoint(SnapCandidatePoint const &p) const override
void storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) override
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
Geom::Point getTransformedPoint(SnapCandidatePoint const &p) const override
Geom::Point getTransformedPoint(SnapCandidatePoint const &p) const override
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
void storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) override
SnappedPoint best_snapped_point
virtual Geom::Point getTransformedPoint(SnapCandidatePoint const &p) const =0
virtual SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const =0
virtual void storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point)=0
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
void storeTransform(SnapCandidatePoint const &original_point, SnappedPoint &snapped_point) override
SnappedPoint snap(::SnapManager *sm, SnapCandidatePoint const &p, Geom::Point pt_orig, Geom::OptRect const &bbox_to_snap) const override
Geom::Point getTransformedPoint(SnapCandidatePoint const &p) const override
Class to store data for points which are snap candidates, either as a source or as a target.
Inkscape::SnapSourceType getSourceType() const
Geom::Point const & getPoint() const
Class describing the result of an attempt to snap.
void setSecondSnapDistance(Geom::Coord const d)
Geom::Point getPoint() const
bool getAlwaysSnap() const
Geom::Coord getSnapDistance() const
void setSnapDistance(Geom::Coord const d)
bool isOtherSnapBetter(SnappedPoint const &other_one, bool weighted) const
bool getAtIntersection() const
void setPointerDistance(Geom::Coord const d)
Class to coordinate snapping operations.
Definition snap.h:80
Inkscape::SnappedPoint constrainedSnap(Inkscape::SnapCandidatePoint const &p, Inkscape::Snapper::SnapConstraint const &constraint, Geom::OptRect const &bbox_to_snap=Geom::OptRect()) const
Try to snap a point along a constraint line to grids, guides or objects.
Definition snap.cpp:242
Inkscape::SnappedPoint freeSnap(Inkscape::SnapCandidatePoint const &p, Geom::OptRect const &bbox_to_snap=Geom::OptRect(), bool to_path_only=false) const
Try to snap a point to grids, guides or objects.
Definition snap.cpp:143
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
int sgn(const T &x)
Sign function - indicates the sign of a numeric type.
Definition math-utils.h:51
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
Helper class to stream background task notifications as a series of messages.
@ SNAPSOURCE_BBOX_CATEGORY
Definition snap-enums.h:23
@ SNAPTARGET_UNDEFINED
Definition snap-enums.h:71
int index