Flutter Engine
 
Loading...
Searching...
No Matches
arc_unittests.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
6#include "gtest/gtest.h"
7
10
11namespace impeller {
12namespace testing {
13
14namespace {
15
16// Tests the basic relationships of the angles iterated according to the
17// rules of the ArcIteration struct. Each step iterated should be just
18// about the same angular distance from the previous step, computed using
19// the Cross product of the adjacent vectors, which should be the same as
20// the sine of the angle between them when using unit vectors.
21//
22// Special support for shorter starting and ending steps to bridge the gap
23// from the true arc start and the true arc end and the first and last
24// angles iterated from the trigs.
25void TestArcIterator(const impeller::Arc::Iteration arc_iteration,
27 Degrees start,
28 Degrees sweep,
29 const std::string& label) {
31 << label;
32 EXPECT_POINT_NEAR(arc_iteration.end, impeller::Matrix::CosSin(start + sweep))
33 << label;
34 if (arc_iteration.quadrant_count == 0u) {
35 // There is just the begin and end angle and there are no constraints
36 // on how far apart they should be, but the end vector should be
37 // non-counterclockwise from the start vector.
38 EXPECT_GE(arc_iteration.start.Cross(arc_iteration.end), 0.0f);
39 return;
40 }
41
42 const size_t steps = trigs.size() - 1;
43 const Scalar step_angle = kPiOver2 / steps;
44
45 // The first and last steps are allowed to be from 0.1 to 1.1 in size
46 // as we don't want to iterate an extra step that is less than 0.1 steps
47 // from the begin/end angles. We use min/max values that are ever so
48 // slightly larger than that to avoid round-off errors.
49 const Scalar edge_min_cross = std::sin(step_angle * 0.099f);
50 const Scalar edge_max_cross = std::sin(step_angle * 1.101f);
51 const Scalar typical_min_cross = std::sin(step_angle * 0.999f);
52 const Scalar typical_max_cross = std::sin(step_angle * 1.001f);
53
54 Vector2 cur_vector;
55 auto trace = [&cur_vector](Vector2 vector, Scalar min_cross, Scalar max_cross,
56 const std::string& label) -> void {
57 EXPECT_GT(cur_vector.Cross(vector), min_cross) << label;
58 EXPECT_LT(cur_vector.Cross(vector), max_cross) << label;
59 cur_vector = vector;
60 };
61
62 // The first edge encountered in the loop should be judged by the edge
63 // conditions. After that the steps derived from the Trigs should be
64 // judged by the typical min/max values.
65 Scalar min_cross = edge_min_cross;
66 Scalar max_cross = edge_max_cross;
67
68 cur_vector = arc_iteration.start;
69 for (size_t i = 0; i < arc_iteration.quadrant_count; i++) {
70 auto& quadrant = arc_iteration.quadrants[i];
71 EXPECT_LT(quadrant.start_index, quadrant.end_index)
72 << label << ", quadrant: " << i;
73 for (size_t j = quadrant.start_index; j < quadrant.end_index; j++) {
74 trace(trigs[j] * quadrant.axis, min_cross, max_cross,
75 label + ", quadrant: " + std::to_string(i) +
76 ", step: " + std::to_string(j));
77 // At this point we can guarantee that we've already used the initial
78 // min/max values, now replace them with the typical values.
79 min_cross = typical_min_cross;
80 max_cross = typical_max_cross;
81 }
82 }
83
84 // The jump to the end angle should be judged by the edge conditions.
85 trace(arc_iteration.end, edge_min_cross, edge_max_cross,
86 label + " step to end");
87}
88
89void TestFullCircleArc(Degrees start, Degrees sweep) {
90 auto label =
91 std::to_string(start.degrees) + " += " + std::to_string(sweep.degrees);
92
93 Arc arc(Rect::MakeLTRB(10, 10, 20, 20), Degrees(start), Degrees(sweep),
94 false);
95
96 Tessellator tessellator;
97 const auto trigs = tessellator.GetTrigsForDeviceRadius(100);
98 size_t steps = trigs.GetSteps();
99 const auto& arc_iteration = arc.ComputeIterations(steps);
100
101 EXPECT_EQ(arc_iteration.start, Vector2(1.0f, 0.0f)) << label;
102 EXPECT_EQ(arc_iteration.quadrant_count, 4u) << label;
103 EXPECT_EQ(arc_iteration.quadrants[0].axis, Vector2(1.0f, 0.0f)) << label;
104 EXPECT_EQ(arc_iteration.quadrants[0].start_index, 1u) << label;
105 EXPECT_EQ(arc_iteration.quadrants[0].end_index, steps) << label;
106 EXPECT_EQ(arc_iteration.quadrants[1].axis, Vector2(0.0f, 1.0f)) << label;
107 EXPECT_EQ(arc_iteration.quadrants[1].start_index, 0u) << label;
108 EXPECT_EQ(arc_iteration.quadrants[1].end_index, steps) << label;
109 EXPECT_EQ(arc_iteration.quadrants[2].axis, Vector2(-1.0f, 0.0f)) << label;
110 EXPECT_EQ(arc_iteration.quadrants[2].start_index, 0u) << label;
111 EXPECT_EQ(arc_iteration.quadrants[2].end_index, steps) << label;
112 EXPECT_EQ(arc_iteration.quadrants[3].axis, Vector2(0.0f, -1.0f)) << label;
113 EXPECT_EQ(arc_iteration.quadrants[3].start_index, 0u) << label;
114 EXPECT_EQ(arc_iteration.quadrants[3].end_index, steps) << label;
115 EXPECT_EQ(arc_iteration.end, Vector2(1.0f, 0.0f)) << label;
116
117 // For full circle arcs the original start and sweep are ignored and it
118 // returns an iterator that always goes from 0->360.
119 TestArcIterator(arc_iteration, trigs, Degrees(0), Degrees(360),
120 "Full Circle(" + label + ")");
121}
122
123} // namespace
124
125TEST(ArcTest, ArcIterationsFullCircle) {
126 // Anything with a sweep <=-360 or >=360 is a full circle regardless of
127 // starting angle
128 for (int start = -720; start < 720; start += 30) {
129 for (int sweep = 360; sweep < 1080; sweep += 45) {
130 TestFullCircleArc(Degrees(start), Degrees(sweep));
131 TestFullCircleArc(Degrees(start), Degrees(-sweep));
132 }
133 }
134}
135
136namespace {
137static void CheckOneQuadrant(Degrees start, Degrees sweep) {
138 Arc arc(Rect::MakeLTRB(10, 10, 20, 20), start, sweep, false);
139 Tessellator tessellator;
140 const auto trigs = tessellator.GetTrigsForDeviceRadius(100);
141 const auto& arc_iteration = arc.ComputeIterations(trigs.GetSteps());
142
143 EXPECT_POINT_NEAR(arc_iteration.start, Matrix::CosSin(start));
144 EXPECT_EQ(arc_iteration.quadrant_count, 1u);
145 EXPECT_POINT_NEAR(arc_iteration.end, Matrix::CosSin(start + sweep));
146
147 std::string label = "Quadrant(" + std::to_string(start.degrees) +
148 " += " + std::to_string(sweep.degrees) + ")";
149 TestArcIterator(arc_iteration, trigs, start, sweep, label);
150}
151} // namespace
152
153TEST(ArcTest, ArcIterationsVariousStartAnglesNearQuadrantAxis) {
154 Tessellator tessellator;
155 const auto trigs = tessellator.GetTrigsForDeviceRadius(100);
156 const Degrees sweep(45);
157
158 for (int start_i = -1000; start_i < 1000; start_i += 5) {
159 Scalar start_degrees = start_i * 0.01f;
160 for (int quadrant = -360; quadrant <= 360; quadrant += 90) {
161 const Degrees start(quadrant + start_degrees);
162 Arc arc(Rect::MakeLTRB(10, 10, 20, 20), start, sweep, false);
163 const auto& arc_iteration = arc.ComputeIterations(trigs.GetSteps());
164
165 TestArcIterator(arc_iteration, trigs, start, sweep,
166 "Various angles(" + std::to_string(start.degrees) +
167 " += " + std::to_string(sweep.degrees));
168 }
169 }
170}
171
172TEST(ArcTest, ArcIterationsVariousEndAnglesNearQuadrantAxis) {
173 Tessellator tessellator;
174 const auto trigs = tessellator.GetTrigsForDeviceRadius(100);
175
176 for (int sweep_i = 5; sweep_i < 20000; sweep_i += 5) {
177 const Degrees sweep(sweep_i * 0.01f);
178 for (int quadrant = -360; quadrant <= 360; quadrant += 90) {
179 const Degrees start(quadrant + 80);
180 Arc arc(Rect::MakeLTRB(10, 10, 20, 20), start, sweep, false);
181 const auto& arc_iteration = arc.ComputeIterations(trigs.GetSteps());
182
183 TestArcIterator(arc_iteration, trigs, start, sweep,
184 "Various angles(" + std::to_string(start.degrees) +
185 " += " + std::to_string(sweep.degrees));
186 }
187 }
188}
189
190TEST(ArcTest, ArcIterationsVariousTinyArcsNearQuadrantAxis) {
191 Tessellator tessellator;
192 const auto trigs = tessellator.GetTrigsForDeviceRadius(100);
193 const Degrees sweep(0.1f);
194
195 for (int start_i = -1000; start_i < 1000; start_i += 5) {
196 Scalar start_degrees = start_i * 0.01f;
197 for (int quadrant = -360; quadrant <= 360; quadrant += 90) {
198 const Degrees start(quadrant + start_degrees);
199 Arc arc(Rect::MakeLTRB(10, 10, 20, 20), start, sweep, false);
200 const auto& arc_iteration = arc.ComputeIterations(trigs.GetSteps());
201 ASSERT_EQ(arc_iteration.quadrant_count, 0u);
202
203 TestArcIterator(arc_iteration, trigs, start, sweep,
204 "Various angles(" + std::to_string(start.degrees) +
205 " += " + std::to_string(sweep.degrees));
206 }
207 }
208}
209
210TEST(ArcTest, ArcIterationsOnlyFirstQuadrant) {
211 CheckOneQuadrant(Degrees(90 * 0 + 30), Degrees(30));
212}
213
214TEST(ArcTest, ArcIterationsOnlySecondQuadrant) {
215 CheckOneQuadrant(Degrees(90 * 1 + 30), Degrees(30));
216}
217
218TEST(ArcTest, ArcIterationsOnlyThirdQuadrant) {
219 CheckOneQuadrant(Degrees(90 * 2 + 30), Degrees(30));
220}
221
222TEST(ArcTest, ArcIterationsOnlyFourthQuadrant) {
223 CheckOneQuadrant(Degrees(90 * 3 + 30), Degrees(30));
224}
225
226namespace {
227static void CheckFiveQuadrants(Degrees start, Degrees sweep) {
228 std::string label =
229 std::to_string(start.degrees) + " += " + std::to_string(sweep.degrees);
230
231 Tessellator tessellator;
232 const auto trigs = tessellator.GetTrigsForDeviceRadius(100);
233 Arc arc(Rect::MakeLTRB(10, 10, 20, 20), start, sweep, false);
234 const auto& arc_iteration = arc.ComputeIterations(trigs.GetSteps());
235 size_t steps = trigs.size() - 1;
236
237 EXPECT_POINT_NEAR(arc_iteration.start, Matrix::CosSin(start)) << label;
238 EXPECT_EQ(arc_iteration.quadrant_count, 5u) << label;
239
240 // quadrant 0 start index depends on angle
241 EXPECT_EQ(arc_iteration.quadrants[0].end_index, steps) << label;
242
243 EXPECT_EQ(arc_iteration.quadrants[1].start_index, 0u) << label;
244 EXPECT_EQ(arc_iteration.quadrants[1].end_index, steps) << label;
245
246 EXPECT_EQ(arc_iteration.quadrants[2].start_index, 0u) << label;
247 EXPECT_EQ(arc_iteration.quadrants[2].end_index, steps) << label;
248
249 EXPECT_EQ(arc_iteration.quadrants[3].start_index, 0u) << label;
250 EXPECT_EQ(arc_iteration.quadrants[3].end_index, steps) << label;
251
252 EXPECT_EQ(arc_iteration.quadrants[4].start_index, 0u) << label;
253 // quadrant 4 end index depends on angle
254
255 EXPECT_POINT_NEAR(arc_iteration.end, Matrix::CosSin(start + sweep)) << label;
256
257 TestArcIterator(arc_iteration, trigs, start, sweep,
258 "Five quadrants(" + label + ")");
259}
260} // namespace
261
262TEST(ArcTest, ArcIterationsAllQuadrantsFromFirst) {
263 CheckFiveQuadrants(Degrees(90 * 0 + 60), Degrees(330));
264}
265
266TEST(ArcTest, ArcIterationsAllQuadrantsFromSecond) {
267 CheckFiveQuadrants(Degrees(90 * 1 + 60), Degrees(330));
268}
269
270TEST(ArcTest, ArcIterationsAllQuadrantsFromThird) {
271 CheckFiveQuadrants(Degrees(90 * 2 + 60), Degrees(330));
272}
273
274TEST(ArcTest, ArcIterationsAllQuadrantsFromFourth) {
275 CheckFiveQuadrants(Degrees(90 * 3 + 60), Degrees(330));
276}
277
278} // namespace testing
279} // namespace impeller
A utility that generates triangles of the specified fill type given a polyline. This happens on the C...
Definition tessellator.h:37
Trigs GetTrigsForDeviceRadius(Scalar pixel_radius)
#define EXPECT_POINT_NEAR(a, b)
TEST(FrameTimingsRecorderTest, RecordVsync)
Point Vector2
Definition point.h:331
float Scalar
Definition scalar.h:19
constexpr float kPiOver2
Definition constants.h:32
impeller::Vector2 axis
Definition arc.h:46
impeller::Vector2 end
Definition arc.h:59
impeller::Vector2 start
Definition arc.h:58
Quadrant quadrants[9]
Definition arc.h:86
size_t quadrant_count
Definition arc.h:63
Iteration ComputeIterations(size_t step_count, bool simplify_360=true) const
Definition arc.cc:102
Scalar degrees
Definition scalar.h:67
static Vector2 CosSin(Radians radians)
Definition matrix.h:685
constexpr Type Cross(const TPoint &p) const
Definition point.h:218
static constexpr TRect MakeLTRB(Type left, Type top, Type right, Type bottom)
Definition rect.h:129
const size_t start