Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
elliptical-arc-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 "testing.h"
36#include <glib.h>
37
38using namespace Geom;
39
40TEST(EllipticalArcTest, PointAt) {
41 EllipticalArc a(Point(0,0), Point(10,20), M_PI/2, false, true, Point(-40,0));
42 EXPECT_near(a.pointAt(0), a.initialPoint(), 1e-14);
43 EXPECT_near(a.pointAt(1), a.finalPoint(), 1e-14);
44 EXPECT_near(a.pointAt(0.5), Point(-20,10), 1e-14);
45
46 EllipticalArc b(Point(0,0), Point(10,20), 0, false, true, Point(-40,0));
47 EXPECT_near(b.pointAt(0), b.initialPoint(), 1e-14);
48 EXPECT_near(b.pointAt(1), b.finalPoint(), 1e-14);
49 EXPECT_near(b.pointAt(0.5), Point(-20,40), 1e-14);
50
51 EllipticalArc c(Point(200,0), Point(40,20), Angle::from_degrees(90), false, false, Point(200,100));
52 EXPECT_near(c.pointAt(0), c.initialPoint(), 1e-13);
53 EXPECT_near(c.pointAt(1), c.finalPoint(), 1e-13);
54 EXPECT_near(c.pointAt(0.5), Point(175, 50), 1e-13);
55}
56
57TEST(EllipticalArc, Transform) {
58 EllipticalArc a(Point(0,0), Point(10,20), M_PI/2, false, true, Point(-40,0));
59 EllipticalArc b(Point(-40,0), Point(10,20), M_PI/2, false, true, Point(0,0));
60 EllipticalArc c = a;
61 Affine m = Rotate::around(Point(-20,0), M_PI);
62 c.transform(m);
63
64 for (unsigned i = 0; i <= 100; ++i) {
65 Coord t = i/100.;
66 EXPECT_near(c.pointAt(t), b.pointAt(t), 1e-12);
67 EXPECT_near(a.pointAt(t)*m, c.pointAt(t), 1e-12);
68 }
69}
70
71TEST(EllipticalArcTest, Duplicate) {
72 EllipticalArc a(Point(0,0), Point(10,20), M_PI/2, true, false, Point(-40,0));
73 EllipticalArc *b = static_cast<EllipticalArc*>(a.duplicate());
74 EXPECT_EQ(a, *b);
75 delete b;
76}
77
78TEST(EllipticalArcTest, LineSegmentIntersection) {
79 std::vector<CurveIntersection> r1;
80 EllipticalArc a3(Point(0,0), Point(5,1.5), 0, true, true, Point(0,2));
81 LineSegment ls(Point(0,5), Point(7,-3));
82 r1 = a3.intersect(ls);
83 EXPECT_EQ(r1.size(), 2u);
84 EXPECT_intersections_valid(a3, ls, r1, 1e-10);
85
86 g_random_set_seed(0xB747A380);
87 // Test with randomized arcs and segments.
88 for (size_t _ = 0; _ < 10'000; _++) {
89 auto arc = EllipticalArc({g_random_double_range(1.0, 5.0), 0.0},
90 {g_random_double_range(6.0, 8.0), g_random_double_range(2.0, 7.0)},
91 g_random_double_range(-0.5, 0.5), true, g_random_boolean(),
92 {g_random_double_range(-5.0, -1.0), 0.0});
93 Coord x = g_random_double_range(15, 30);
94 Coord y = g_random_double_range(10, 20);
95 auto seg = LineSegment(Point(-x, y), Point(x, -y));
96 auto xings = arc.intersect(seg);
97 EXPECT_EQ(xings.size(), 1u);
98 EXPECT_intersections_valid(arc, seg, xings, 1e-12);
99 }
100
101 // Test with degenerate arcs
102 EllipticalArc x_squash_pos{{3.0, 0.0}, {3.0, 2.0}, 0, true, true, {-3.0, 0.0}};
103 EllipticalArc x_squash_neg{{3.0, 0.0}, {3.0, 2.0}, 0, true, false, {-3.0, 0.0}};
104 auto const squash_to_x = Scale(1.0, 0.0);
105 x_squash_pos *= squash_to_x; // squash to X axis interval [-3, 3].
106 x_squash_neg *= squash_to_x;
107
108 for (size_t _ = 0; _ < 10'000; _++) {
109 auto seg = LineSegment(Point(g_random_double_range(-3.0, 3.0), g_random_double_range(-3.0, -1.0)),
110 Point(g_random_double_range(-3.0, 3.0), g_random_double_range(1.0, 3.0)));
111 auto xings = x_squash_pos.intersect(seg);
112 EXPECT_EQ(xings.size(), 1u);
113 EXPECT_intersections_valid(x_squash_pos, seg, xings, 1e-12);
114
115 std::unique_ptr<Curve> rev{x_squash_pos.reverse()};
116 xings = rev->intersect(seg);
117 EXPECT_EQ(xings.size(), 1u);
118 EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
119
120 xings = x_squash_neg.intersect(seg);
121 EXPECT_EQ(xings.size(), 1u);
122 EXPECT_intersections_valid(x_squash_neg, seg, xings, 1e-12);
123
124 rev.reset(x_squash_neg.reverse());
125 xings = rev->intersect(seg);
126 EXPECT_EQ(xings.size(), 1u);
127 EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
128 }
129
130 // Now test with an arc squashed to the Y-axis.
131 EllipticalArc y_squash_pos{{0.0, -2.0}, {3.0, 2.0}, 0, true, true, {0.0, 2.0}};
132 EllipticalArc y_squash_neg{{0.0, -2.0}, {3.0, 2.0}, 0, true, false, {0.0, 2.0}};
133 auto const squash_to_y = Scale(0.0, 1.0);
134 y_squash_pos *= squash_to_y; // Y-axis interval [-2, 2].
135 y_squash_neg *= squash_to_y;
136
137 for (size_t _ = 0; _ < 10'000; _++) {
138 auto seg = LineSegment(Point(g_random_double_range(-3.0, -1.0), g_random_double_range(-2.0, 2.0)),
139 Point(g_random_double_range(1.0, 3.0), g_random_double_range(-2.0, 2.0)));
140 auto xings = y_squash_pos.intersect(seg, 1e-10);
141 EXPECT_EQ(xings.size(), 1u);
142 EXPECT_intersections_valid(y_squash_pos, seg, xings, 1e-12);
143
144 std::unique_ptr<Curve> rev{y_squash_pos.reverse()};
145 xings = rev->intersect(seg, 1e-12);
146 EXPECT_EQ(xings.size(), 1u);
147 EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
148
149 xings = y_squash_neg.intersect(seg, 1e-12);
150 EXPECT_EQ(xings.size(), 1u);
151 EXPECT_intersections_valid(y_squash_neg, seg, xings, 1e-12);
152
153 rev.reset(y_squash_neg.reverse());
154 xings = rev->intersect(seg, 1e-12);
155 EXPECT_EQ(xings.size(), 1u);
156 EXPECT_intersections_valid(*rev, seg, xings, 1e-12);
157 }
158
159 // Test whether the coincidence between the common endpoints of an
160 // arc and a segment is correctly detected as an intersection.
161 {
162 Point const from{1, 0};
163 Point const to{0.30901699437494745, 0.9510565162951535};
164 auto arc = EllipticalArc(from, {1, 1}, 0, false, true, to);
165 auto seg = LineSegment({0, 0}, to);
166 auto xings = arc.intersect(seg);
167 ASSERT_EQ(xings.size(), 1);
168 EXPECT_TRUE(are_near(xings[0].point(), to, 1e-12));
169 EXPECT_TRUE(are_near(xings[0].first, 1.0, 1e-24));
170 EXPECT_TRUE(are_near(xings[0].second, 1.0, 1e-24));
171
172 auto seg2 = LineSegment(Point{1, 1}, from);
173 xings = arc.intersect(seg2);
174 ASSERT_EQ(xings.size(), 1);
175 EXPECT_TRUE(are_near(xings[0].point(), from, 1e-12));
176 EXPECT_TRUE(are_near(xings[0].first, 0.0, 1e-24));
177 EXPECT_TRUE(are_near(xings[0].second, 1.0, 1e-24));
178 }
179}
180
181TEST(EllipticalArcTest, ArcIntersection) {
182 std::vector<CurveIntersection> r1, r2;
183
184 EllipticalArc a1(Point(0,0), Point(6,3), 0.1, false, false, Point(10,0));
185 EllipticalArc a2(Point(0,2), Point(6,3), -0.1, false, true, Point(10,2));
186 r1 = a1.intersect(a2);
187 EXPECT_EQ(r1.size(), 2u);
188 EXPECT_intersections_valid(a1, a2, r1, 1e-10);
189
190 EllipticalArc a3(Point(0,0), Point(5,1.5), 0, true, true, Point(0,2));
191 EllipticalArc a4(Point(3,5), Point(5,1.5), M_PI/2, true, true, Point(5,0));
192 r2 = a3.intersect(a4);
193 EXPECT_EQ(r2.size(), 3u);
194 EXPECT_intersections_valid(a3, a4, r2, 1e-10);
195
196 // Make sure intersections are found between two identical arcs on the unit circle.
197 EllipticalArc const upper(Point(1, 0), Point(1, 1), 0, true, true, Point(-1, 0));
198 auto self_intersect = upper.intersect(upper);
199 EXPECT_EQ(self_intersect.size(), 2u);
200
201 // Make sure intersections are found between overlapping arcs.
202 EllipticalArc const right(Point(0, -1), Point(1, 1), 0, true, true, Point(0, 1));
203 auto quartering_overlap_xings = right.intersect(upper);
204 EXPECT_EQ(quartering_overlap_xings.size(), 2u);
205
206 // Make sure intersecections are found between an arc and its sub-arc.
207 EllipticalArc const middle(upper.pointAtAngle(0.25 * M_PI), Point(1, 1), 0, true, true, upper.pointAtAngle(-0.25 * M_PI));
208 EXPECT_EQ(middle.intersect(upper).size(), 2u);
209
210 // Make sure intersections are NOT found between non-overlapping sub-arcs of the same circle.
211 EllipticalArc const arc1{Point(1, 0), Point(1, 1), 0, true, true, Point(0, 1)};
212 EllipticalArc const arc2{Point(-1, 0), Point(1, 1), 0, true, true, Point(0, -1)};
213 EXPECT_EQ(arc1.intersect(arc2).size(), 0u);
214
215 // Overlapping sub-arcs but on an Ellipse with different rays.
216 EllipticalArc const eccentric{Point(2, 0), Point(2, 1), 0, true, true, Point(-2, 0)};
217 EllipticalArc const subarc{eccentric.pointAtAngle(0.8), Point(2, 1), 0, true, true, eccentric.pointAtAngle(2)};
218 EXPECT_EQ(eccentric.intersect(subarc).size(), 2u);
219
220 // Check intersection times for two touching arcs.
221 EllipticalArc const lower{Point(-1, 0), Point(1, 1), 0, false, true, Point(0, -1)};
222 auto expected_neg_x = upper.intersect(lower);
223 ASSERT_EQ(expected_neg_x.size(), 1);
224 auto const &left_pt = expected_neg_x[0];
225 EXPECT_EQ(left_pt.point(), Point(-1, 0));
226 EXPECT_DOUBLE_EQ(left_pt.first, 1.0); // Expect (-1, 0) reached at the end of upper
227 EXPECT_DOUBLE_EQ(left_pt.second, 0.0); // Expect (-1, 0) passed at the start of lower
228}
229
230TEST(EllipticalArcTest, BezierIntersection) {
231 std::vector<CurveIntersection> r1, r2;
232
233 EllipticalArc a3(Point(0,0), Point(1.5,5), M_PI/2, true, true, Point(0,2));
234 CubicBezier bez1(Point(0,3), Point(7,3), Point(0,-1), Point(7,-1));
235 r1 = a3.intersect(bez1);
236 EXPECT_EQ(r1.size(), 2u);
237 EXPECT_intersections_valid(a3, bez1, r1, 1e-10);
238
239 EllipticalArc a4(Point(3,5), Point(5,1.5), 3*M_PI/2, true, true, Point(5,5));
240 CubicBezier bez2(Point(0,5), Point(10,-4), Point(10,5), Point(0,-4));
241 r2 = a4.intersect(bez2);
242 EXPECT_EQ(r2.size(), 4u);
243 EXPECT_intersections_valid(a4, bez2, r2, 1e-10);
244}
245
246TEST(EllipticalArcTest, ExpandToTransformedTest)
247{
248 auto test_curve = [] (EllipticalArc const &c, bool with_initial_bbox) {
249 constexpr int N = 200;
250 for (int i = 0; i < N; i++) {
251 auto angle = 2 * M_PI * i / N;
252 auto transform = Affine(Rotate(angle)) * Scale(0.9, 1.2);
253
254 auto const box0 = with_initial_bbox ? Rect::from_xywh(10 * std::sin(angle * 13), 10 * std::sin(angle * 17), 5.0, 5.0) : OptRect();
255
256 auto copy = std::unique_ptr<Curve>(c.duplicate());
257 *copy *= transform;
258 auto box1 = copy->boundsExact() | box0;
259
260 auto pt = c.initialPoint() * transform;
261 auto box2 = Rect(pt, pt) | box0;
262 c.expandToTransformed(box2, transform);
263
264 for (auto i : { X, Y }) {
265 EXPECT_NEAR(box1[i].min(), box2[i].min(), 2e-15);
266 EXPECT_NEAR(box1[i].max(), box2[i].max(), 2e-15);
267 }
268 }
269 };
270
271 for (auto b : { false, true }) {
272 test_curve(EllipticalArc(Point(0, 0), 1.0, 2.0, 0.0, false, false, Point(1, 1)), b);
273 test_curve(EllipticalArc(Point(0, 0), 3.0, 2.0, M_PI / 6, false, false, Point(1, 1)), b);
274 test_curve(EllipticalArc(Point(0, 0), 1.0, 2.0, M_PI / 5, true, true, Point(1, 1)), b);
275 test_curve(EllipticalArc(Point(1, 0), 1.0, 0.0, M_PI / 5, false, false, Point(1, 1)), b);
276 test_curve(EllipticalArc(Point(1, 0), 0.0, 0.0, 0.0, false, false, Point(2, 0)), b);
277 test_curve(EllipticalArc(Point(1, 0), 0.0, 0.0, 0.0, false, false, Point(1, 0)), b);
278 }
279}
3x3 matrix representing an affine transformation.
Definition affine.h:70
static Angle from_degrees(Coord d)
Create an angle from its measure in degrees.
Definition angle.h:136
std::vector< CurveIntersection > intersect(Curve const &other, Coord eps=EPSILON) const override
Compute intersections with another curve.
unsigned size() const
Get the number of control points.
Elliptical arc curve.
Point pointAt(Coord t) const override
Evaluate the arc in the curve domain, i.e. .
Point finalPoint() const override
Retrieve the end of the curve.
Curve * duplicate() const override
Create an exact copy of this curve.
Point initialPoint() const override
Retrieve the start of the curve.
std::vector< CurveIntersection > intersect(Curve const &other, Coord eps=EPSILON) const override
Compute intersections with another curve.
Point pointAtAngle(Coord t) const
Evaluate the arc at the specified angular coordinate.
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
double c[8][4]
Elliptical arc curve.
BezierCurveN< 1 > LineSegment
Line segment.
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
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
TEST(AffineTest, Equality)
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
size_t N
int test_curve(void)
Definition spiro.cpp:1075