Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
ellipse-test.cpp
Go to the documentation of this file.
1/*
5 * Authors:
6 * Krzysztof KosiƄski <tweenk.pl@gmail.com>
7 *
8 * Copyright 2015 Authors
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it either under the terms of the GNU Lesser General Public
12 * License version 2.1 as published by the Free Software Foundation
13 * (the "LGPL") or, at your option, under the terms of the Mozilla
14 * Public License Version 1.1 (the "MPL"). If you do not alter this
15 * notice, a recipient may use your version of this file under either
16 * the MPL or the LGPL.
17 *
18 * You should have received a copy of the LGPL along with this library
19 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * You should have received a copy of the MPL along with this library
22 * in the file COPYING-MPL-1.1
23 *
24 * The contents of this file are subject to the Mozilla Public License
25 * Version 1.1 (the "License"); you may not use this file except in
26 * compliance with the License. You may obtain a copy of the License at
27 * http://www.mozilla.org/MPL/
28 *
29 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
30 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
31 * the specific language governing rights and limitations.
32 */
33
34#include <iostream>
35#include <glib.h>
36
37#include <2geom/angle.h>
38#include <2geom/ellipse.h>
40#include <memory>
41
42#include "testing.h"
43
44#ifndef M_SQRT2
45# define M_SQRT2 1.41421356237309504880
46#endif
47
48using namespace Geom;
49
50TEST(EllipseTest, Arcs) {
51 Ellipse e(Point(5,10), Point(5, 10), 0);
52
53 std::unique_ptr<EllipticalArc> arc1(e.arc(Point(5,0), Point(0,0), Point(0,10)));
54
55 EXPECT_EQ(arc1->initialPoint(), Point(5,0));
56 EXPECT_EQ(arc1->finalPoint(), Point(0,10));
57 EXPECT_EQ(arc1->boundsExact(), Rect::from_xywh(0,0,5,10));
58 EXPECT_EQ(arc1->center(), e.center());
59 EXPECT_EQ(arc1->largeArc(), false);
60 EXPECT_EQ(arc1->sweep(), false);
61
62 std::unique_ptr<EllipticalArc> arc1r(e.arc(Point(0,10), Point(0,0), Point(5,0)));
63
64 EXPECT_EQ(arc1r->boundsExact(), arc1->boundsExact());
65 EXPECT_EQ(arc1r->sweep(), true);
66 EXPECT_EQ(arc1r->largeArc(), false);
67
68 std::unique_ptr<EllipticalArc> arc2(e.arc(Point(5,0), Point(10,20), Point(0,10)));
69
70 EXPECT_EQ(arc2->boundsExact(), Rect::from_xywh(0,0,10,20));
71 EXPECT_EQ(arc2->largeArc(), true);
72 EXPECT_EQ(arc2->sweep(), true);
73
74 std::unique_ptr<EllipticalArc> arc2r(e.arc(Point(0,10), Point(10,20), Point(5,0)));
75
76 EXPECT_EQ(arc2r->boundsExact(), arc2->boundsExact());
77 EXPECT_EQ(arc2r->largeArc(), true);
78 EXPECT_EQ(arc2r->sweep(), false);
79
80 // exactly half arc
81 std::unique_ptr<EllipticalArc> arc3(e.arc(Point(5,0), Point(0,10), Point(5,20)));
82
83 EXPECT_EQ(arc3->boundsExact(), Rect::from_xywh(0,0,5,20));
84 EXPECT_EQ(arc3->largeArc(), false);
85 EXPECT_EQ(arc3->sweep(), false);
86
87 // inner point exactly at midpoint between endpoints
88 std::unique_ptr<EllipticalArc> arc4(e.arc(Point(5,0), Point(2.5,5), Point(0,10)));
89
90 EXPECT_EQ(arc4->initialPoint(), Point(5,0));
91 EXPECT_EQ(arc4->finalPoint(), Point(0,10));
92 EXPECT_EQ(arc4->boundsExact(), Rect::from_xywh(0,0,5,10));
93 EXPECT_EQ(arc4->largeArc(), false);
94 EXPECT_EQ(arc4->sweep(), false);
95
96 std::unique_ptr<EllipticalArc> arc4r(e.arc(Point(0,10), Point(2.5,5), Point(5,0)));
97
98 EXPECT_EQ(arc4r->initialPoint(), Point(0,10));
99 EXPECT_EQ(arc4r->finalPoint(), Point(5,0));
100 EXPECT_EQ(arc4r->boundsExact(), Rect::from_xywh(0,0,5,10));
101 EXPECT_EQ(arc4r->largeArc(), false);
102 EXPECT_EQ(arc4r->sweep(), true);
103}
104
105TEST(EllipseTest, AreNear) {
106 Ellipse e1(Point(5.000001,10), Point(5,10), Angle::from_degrees(45));
107 Ellipse e2(Point(5.000000,10), Point(5,10), Angle::from_degrees(225));
108 Ellipse e3(Point(4.999999,10), Point(10,5), Angle::from_degrees(135));
109 Ellipse e4(Point(5.000001,10), Point(10,5), Angle::from_degrees(315));
110
111 EXPECT_TRUE(are_near(e1, e2, 1e-5));
112 EXPECT_TRUE(are_near(e1, e3, 1e-5));
113 EXPECT_TRUE(are_near(e1, e4, 1e-5));
114
115 Ellipse c1(Point(20.000001,35.000001), Point(5.000001,4.999999), Angle::from_degrees(180.00001));
116 Ellipse c2(Point(19.999999,34.999999), Point(4.999999,5.000001), Angle::from_degrees(179.99999));
117 //std::cout << c1 << "\n" << c2 << std::endl;
118 EXPECT_TRUE(are_near(c1, c2, 2e-5));
119
120 EXPECT_FALSE(are_near(c1, e1, 1e-5));
121 EXPECT_FALSE(are_near(c2, e1, 1e-5));
122 EXPECT_FALSE(are_near(c1, e2, 1e-5));
123 EXPECT_FALSE(are_near(c2, e2, 1e-5));
124 EXPECT_FALSE(are_near(c1, e3, 1e-5));
125 EXPECT_FALSE(are_near(c2, e3, 1e-5));
126 EXPECT_FALSE(are_near(c1, e4, 1e-5));
127 EXPECT_FALSE(are_near(c2, e4, 1e-5));
128}
129
130TEST(EllipseTest, Transformations) {
131 Ellipse e(Point(5,10), Point(5,10), Angle::from_degrees(45));
132
133 Ellipse er = e * Rotate::around(Point(5,10), Angle::from_degrees(45));
134 Ellipse ercmp(Point(5,10), Point(5,10), Angle::from_degrees(90));
135 //std::cout << e << "\n" << er << "\n" << ercmp << std::endl;
136 EXPECT_TRUE(are_near(er, ercmp, 1e-12));
137
138 Ellipse eflip = e * Affine(Scale(-1,1));
139 Ellipse eflipcmp(Point(-5, 10), Point(5,10), Angle::from_degrees(135));
140 EXPECT_TRUE(are_near(eflip, eflipcmp, 1e-12));
141}
142
143TEST(EllipseTest, TimeAt) {
144 Ellipse e(Point(4, 17), Point(22, 34), 2);
145
146 for (unsigned i = 0; i < 100; ++i) {
147 Coord t = g_random_double_range(0, 2*M_PI);
148 Point p = e.pointAt(t);
149 Coord t2 = e.timeAt(p);
150 EXPECT_FLOAT_EQ(t, t2);
151 }
152}
153
154TEST(EllipseTest, LineIntersection) {
155 Ellipse e(Point(0, 0), Point(3, 2), 0);
156 Line l(Point(0, -2), Point(1, 0));
157
158 std::vector<ShapeIntersection> xs = e.intersect(l);
159
160 ASSERT_EQ(xs.size(), 2ul);
161
162 // due to numeric imprecision when evaluating Ellipse,
163 // the points may deviate by around 2e-16
164 EXPECT_NEAR(xs[0].point()[X], 0, 1e-15);
165 EXPECT_NEAR(xs[0].point()[Y], -2, 1e-15);
166 EXPECT_NEAR(xs[1].point()[X], 9./5, 1e-15);
167 EXPECT_NEAR(xs[1].point()[Y], 8./5, 1e-15);
168
169 EXPECT_intersections_valid(e, l, xs, 1e-15);
170
171 // Test with a degenerate ellipse
172 auto degen = Ellipse({0, 0}, {3, 2}, 0);
173 degen *= Scale(1.0, 0.0); // Squash to the X-axis interval [-3, 3].
174
175 g_random_set_seed(0xCAFECAFE);
176 // Intersect with a line
177 for (size_t _ = 0; _ < 10'000; _++) {
178 auto line = Line(Point(g_random_double_range(-3.0, 3.0), g_random_double_range(-3.0, -1.0)),
179 Point(g_random_double_range(-3.0, 3.0), g_random_double_range(1.0, 3.0)));
180 auto xings = degen.intersect(line);
181 EXPECT_EQ(xings.size(), 2u);
182 EXPECT_intersections_valid(degen, line, xings, 1e-14);
183 }
184 // Intersect with another, non-degenerate ellipse
185 for (size_t _ = 0; _ < 10'000; _++) {
186 auto other = Ellipse(Point(g_random_double_range(-1.0, 1.0), g_random_double_range(-1.0, 1.0)),
187 Point(g_random_double_range(1.0, 2.0), g_random_double_range(1.0, 3.0)), 0);
188 auto xings = degen.intersect(other);
189 EXPECT_intersections_valid(degen, other, xings, 1e-14);
190 }
191 // Intersect with another ellipse which is also degenerate
192 for (size_t _ = 0; _ < 10'000; _++) {
193 auto other = Ellipse({0, 0}, {1, 1}, 0); // Unit circle
194 other *= Scale(0.0, g_random_double_range(0.5, 4.0)); // Squash to Y axis
195 other *= Rotate(g_random_double_range(-1.5, 1.5)); // Rotate a little (still passes through the origin)
196 other *= Translate(g_random_double_range(-2.9, 2.9), 0.0);
197 auto xings = degen.intersect(other);
198 EXPECT_EQ(xings.size(), 4u);
199 EXPECT_intersections_valid(degen, other, xings, 1e-14);
200 }
201}
202
203TEST(EllipseTest, EllipseIntersection) {
204 Ellipse e1;
205 Ellipse e2;
206 std::vector<ShapeIntersection> xs;
207
208 e1.set(Point(300, 300), Point(212, 70), -0.785);
209 e2.set(Point(250, 300), Point(230, 90), 1.321);
210 xs = e1.intersect(e2);
211 EXPECT_EQ(xs.size(), 4ul);
212 EXPECT_intersections_valid(e1, e2, xs, 4e-10);
213
214 e1.set(Point(0, 0), Point(1, 1), 0);
215 e2.set(Point(0, 1), Point(1, 1), 0);
216 xs = e1.intersect(e2);
217 EXPECT_EQ(xs.size(), 2ul);
218 EXPECT_intersections_valid(e1, e2, xs, 1e-10);
219
220 e1.set(Point(0, 0), Point(1, 1), 0);
221 e2.set(Point(1, 0), Point(1, 1), 0);
222 xs = e1.intersect(e2);
223 EXPECT_EQ(xs.size(), 2ul);
224 EXPECT_intersections_valid(e1, e2, xs, 1e-10);
225
226 // === Test detection of external tangency between ellipses ===
227 // Perpendicular major axes
228 e1.set({0, 0}, {5, 3}, 0); // rightmost point (5, 0)
229 e2.set({6, 0}, {1, 2}, 0); // leftmost point (5, 0)
230 xs = e1.intersect(e2);
231 ASSERT_GT(xs.size(), 0);
232 EXPECT_intersections_valid(e1, e2, xs, 1e-10);
233 EXPECT_TRUE(are_near(xs[0].point(), Point(5, 0)));
234
235 // Collinear major axes
236 e1.set({30, 0}, {9, 1}, 0); // leftmost point (21, 0)
237 e2.set({18, 0}, {3, 2}, 0); // rightmost point (21, 0)
238 xs = e1.intersect(e2);
239 ASSERT_GT(xs.size(), 0);
240 EXPECT_intersections_valid(e1, e2, xs, 1e-10);
241 EXPECT_TRUE(are_near(xs[0].point(), Point(21, 0)));
242
243 // Circles not aligned to an axis (Pythagorean triple: 3^2 + 4^2 == 5^2)
244 e1.set({0, 0}, {3, 3}, 0); // radius 3
245 e2.set({3, 4}, {2, 2}, 0); // radius 2
246 // We know 2 + 3 == 5 == distance((0, 0), (3, 4)) so there's an external tangency
247 // between these circles, at a point at distance 3 from the origin, on the line x = 0.75 y.
248 xs = e1.intersect(e2);
249 ASSERT_GT(xs.size(), 0);
250 EXPECT_intersections_valid(e1, e2, xs, 1e-6);
251
252 // === Test the detection of internal tangency between ellipses ===
253 // Perpendicular major axes
254 e1.set({0, 0}, {8, 17}, 0); // rightmost point (8, 0)
255 e2.set({6, 0}, {2, 1}, 0); // rightmost point (8, 0)
256 xs = e1.intersect(e2);
257 ASSERT_GT(xs.size(), 0);
258 EXPECT_intersections_valid(e1, e2, xs, 1e-10);
259 EXPECT_TRUE(are_near(xs[0].point(), Point(8, 0)));
260
261 // Collinear major axes
262 e1.set({30, 0}, {9, 5}, 0); // rightmost point (39, 0)
263 e2.set({36, 0}, {3, 1}, 0); // rightmost point (39, 0)
264 xs = e1.intersect(e2);
265 ASSERT_GT(xs.size(), 0);
266 EXPECT_intersections_valid(e1, e2, xs, 1e-6);
267 EXPECT_TRUE(are_near(xs[0].point(), Point(39, 0)));
268
269 // Circles not aligned to an axis (Pythagorean triple: 3^2 + 4^2 == 5^2)
270 e1.set({4, 3}, {5, 5}, 0); // Passes through (0, 0), center on the line y = 0.75 x
271 e2.set({8, 6}, {10, 10}, 0); // Also passes through (0, 0), center on the same line.
272 xs = e1.intersect(e2);
273 ASSERT_GT(xs.size(), 0);
274 EXPECT_intersections_valid(e1, e2, xs, 1e-6);
275 EXPECT_TRUE(are_near(xs[0].point(), Point(0, 0)));
276}
277
278TEST(EllipseTest, BezierIntersection) {
279 Ellipse e(Point(300, 300), Point(212, 70), -3.926);
280 D2<Bezier> b(Bezier(100, 300, 100, 500), Bezier(100, 100, 500, 500));
281
282 std::vector<ShapeIntersection> xs = e.intersect(b);
283
284 EXPECT_EQ(xs.size(), 2ul);
285 EXPECT_intersections_valid(e, b, xs, 6e-12);
286}
287
288TEST(EllipseTest, Coefficients) {
289 std::vector<Ellipse> es;
290 es.emplace_back(Point(-15,25), Point(10,15), Angle::from_degrees(45).radians0());
291 es.emplace_back(Point(-10,33), Point(40,20), M_PI);
292 es.emplace_back(Point(10,-33), Point(40,20), Angle::from_degrees(135).radians0());
293 es.emplace_back(Point(-10,-33), Point(50,10), Angle::from_degrees(330).radians0());
294
295 for (auto & i : es) {
296 Coord a, b, c, d, e, f;
297 i.coefficients(a, b, c, d, e, f);
298 Ellipse te(a, b, c, d, e, f);
299 EXPECT_near(i, te, 1e-10);
300 for (Coord t = -5; t < 5; t += 0.125) {
301 Point p = i.pointAt(t);
302 Coord eq = a*p[X]*p[X] + b*p[X]*p[Y] + c*p[Y]*p[Y]
303 + d*p[X] + e*p[Y] + f;
304 EXPECT_NEAR(eq, 0, 1e-10);
305 }
306 }
307}
308
309TEST(EllipseTest, UnitCircleTransform) {
310 std::vector<Ellipse> es;
311 es.emplace_back(Point(-15,25), Point(10,15), Angle::from_degrees(45));
312 es.emplace_back(Point(-10,33), Point(40,20), M_PI);
313 es.emplace_back(Point(10,-33), Point(40,20), Angle::from_degrees(135));
314 es.emplace_back(Point(-10,-33), Point(50,10), Angle::from_degrees(330));
315
316 for (auto & e : es) {
317 EXPECT_near(e.unitCircleTransform() * e.inverseUnitCircleTransform(), Affine::identity(), 1e-8);
318
319 for (Coord t = -1; t < 10; t += 0.25) {
320 Point p = e.pointAt(t);
321 p *= e.inverseUnitCircleTransform();
322 EXPECT_near(p.length(), 1., 1e-10);
323 p *= e.unitCircleTransform();
324 EXPECT_near(e.pointAt(t), p, 1e-10);
325 }
326 }
327}
328
329TEST(EllipseTest, PointAt) {
330 Ellipse a(Point(0,0), Point(10,20), 0);
331 EXPECT_near(a.pointAt(0), Point(10,0), 1e-10);
332 EXPECT_near(a.pointAt(M_PI/2), Point(0,20), 1e-10);
333 EXPECT_near(a.pointAt(M_PI), Point(-10,0), 1e-10);
334 EXPECT_near(a.pointAt(3*M_PI/2), Point(0,-20), 1e-10);
335
336 Ellipse b(Point(0,0), Point(10,20), M_PI/2);
337 EXPECT_near(b.pointAt(0), Point(0,10), 1e-10);
338 EXPECT_near(b.pointAt(M_PI/2), Point(-20,0), 1e-10);
339 EXPECT_near(b.pointAt(M_PI), Point(0,-10), 1e-10);
340 EXPECT_near(b.pointAt(3*M_PI/2), Point(20,0), 1e-10);
341}
342
343TEST(EllipseTest, UnitTangentAt) {
344 Ellipse a(Point(14,-7), Point(20,10), 0);
345 Ellipse b(Point(-77,23), Point(40,10), Angle::from_degrees(45));
346
347 EXPECT_near(a.unitTangentAt(0), Point(0,1), 1e-12);
348 EXPECT_near(a.unitTangentAt(M_PI/2), Point(-1,0), 1e-12);
349 EXPECT_near(a.unitTangentAt(M_PI), Point(0,-1), 1e-12);
350 EXPECT_near(a.unitTangentAt(3*M_PI/2), Point(1,0), 1e-12);
351
352 EXPECT_near(b.unitTangentAt(0), Point(-M_SQRT2/2, M_SQRT2/2), 1e-12);
353 EXPECT_near(b.unitTangentAt(M_PI/2), Point(-M_SQRT2/2, -M_SQRT2/2), 1e-12);
354 EXPECT_near(b.unitTangentAt(M_PI), Point(M_SQRT2/2, -M_SQRT2/2), 1e-12);
355 EXPECT_near(b.unitTangentAt(3*M_PI/2), Point(M_SQRT2/2, M_SQRT2/2), 1e-12);
356}
357
358TEST(EllipseTest, Bounds)
359{
360 // Create example ellipses
361 std::vector<Ellipse> es;
362 es.emplace_back(Point(-15,25), Point(10,15), Angle::from_degrees(45));
363 es.emplace_back(Point(-10,33), Point(40,20), M_PI);
364 es.emplace_back(Point(10,-33), Point(40,20), Angle::from_degrees(111));
365 es.emplace_back(Point(-10,-33), Point(50,10), Angle::from_degrees(222));
366
367 // for reproducibility
368 g_random_set_seed(1234);
369
370 for (auto & e : es) {
371 Rect r = e.boundsExact();
372 Rect f = e.boundsFast();
373 for (unsigned j = 0; j < 10000; ++j) {
374 Coord t = g_random_double_range(-M_PI, M_PI);
375 auto const p = e.pointAt(t);
376 EXPECT_TRUE(r.contains(p));
377 EXPECT_TRUE(f.contains(p));
378 }
379 }
380
381 Ellipse e(Point(0,0), Point(10, 10), M_PI);
382 Rect bounds = e.boundsExact();
383 Rect coarse = e.boundsFast();
384 EXPECT_EQ(bounds, Rect(Point(-10,-10), Point(10,10)));
385 EXPECT_TRUE(bounds.contains(e.pointAt(0)));
386 EXPECT_TRUE(bounds.contains(e.pointAt(M_PI/2)));
387 EXPECT_TRUE(bounds.contains(e.pointAt(M_PI)));
388 EXPECT_TRUE(bounds.contains(e.pointAt(3*M_PI/2)));
389 EXPECT_TRUE(bounds.contains(e.pointAt(2*M_PI)));
390 EXPECT_TRUE(coarse.contains(e.pointAt(0)));
391 EXPECT_TRUE(coarse.contains(e.pointAt(M_PI/2)));
392 EXPECT_TRUE(coarse.contains(e.pointAt(M_PI)));
393 EXPECT_TRUE(coarse.contains(e.pointAt(3*M_PI/2)));
394 EXPECT_TRUE(coarse.contains(e.pointAt(2*M_PI)));
395
396 e = Ellipse(Point(0,0), Point(10, 10), M_PI/2);
397 bounds = e.boundsExact();
398 coarse = e.boundsFast();
399 EXPECT_EQ(bounds, Rect(Point(-10,-10), Point(10,10)));
400 EXPECT_TRUE(bounds.contains(e.pointAt(0)));
401 EXPECT_TRUE(bounds.contains(e.pointAt(M_PI/2)));
402 EXPECT_TRUE(bounds.contains(e.pointAt(M_PI)));
403 EXPECT_TRUE(bounds.contains(e.pointAt(3*M_PI/2)));
404 EXPECT_TRUE(bounds.contains(e.pointAt(2*M_PI)));
405 EXPECT_TRUE(coarse.contains(e.pointAt(0)));
406 EXPECT_TRUE(coarse.contains(e.pointAt(M_PI/2)));
407 EXPECT_TRUE(coarse.contains(e.pointAt(M_PI)));
408 EXPECT_TRUE(coarse.contains(e.pointAt(3*M_PI/2)));
409 EXPECT_TRUE(coarse.contains(e.pointAt(2*M_PI)));
410}
Various trigoniometric helper functions.
Geom::IntRect bounds
Definition canvas.cpp:182
3x3 matrix representing an affine transformation.
Definition affine.h:70
static Affine identity()
static Angle from_degrees(Coord d)
Create an angle from its measure in degrees.
Definition angle.h:136
Polynomial in Bernstein-Bezier basis.
Definition bezier.h:126
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Set of points with a constant sum of distances from two foci.
Definition ellipse.h:68
Point unitTangentAt(Coord t) const
Get the value of the derivative at time t normalized to unit length.
Definition ellipse.cpp:398
Rect boundsFast() const
Get a fast to compute bounding box which contains the ellipse.
Definition ellipse.cpp:162
Coord timeAt(Point const &p) const
Find the time value of a point on an ellipse.
Definition ellipse.cpp:382
std::vector< ShapeIntersection > intersect(Line const &line) const
Compute intersections with an infinite line.
Definition ellipse.cpp:460
void set(Point const &c, Point const &r, Coord angle)
Set center, rays and angle.
Definition ellipse.h:91
EllipticalArc * arc(Point const &ip, Point const &inner, Point const &fp)
Create an elliptical arc from a section of the ellipse.
Definition ellipse.cpp:226
Point pointAt(Coord t) const
Evaluate a point on the ellipse.
Definition ellipse.cpp:358
Rect boundsExact() const
Get the tight-fitting bounding box of the ellipse.
Definition ellipse.cpp:146
Point center() const
Definition ellipse.h:119
bool contains(GenericRect< C > const &r) const
Check whether the rectangle includes all points in the given rectangle.
Infinite line on a plane.
Definition line.h:53
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
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
Translation by a vector.
Definition transforms.h:115
vector< Edge > es
double c[8][4]
Ellipse shape.
Elliptical arc curve.
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Various utility functions.
Definition affine.h:22
xAx degen
Definition conic-6.cpp:70
TEST(AffineTest, Equality)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)