Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
Segment.cpp
Go to the documentation of this file.
1// Copyright 2023 Google LLC
2// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
3
5
9
10#include <algorithm>
11#include <cmath>
12
13namespace bentleyottmann {
14
15// -- Segment --------------------------------------------------------------------------------------
17 return std::min(p0, p1);
18}
19
21 return std::max(p0, p1);
22}
23
24// Use auto [l, t, r, b] = s.bounds();
25std::tuple<int32_t, int32_t, int32_t, int32_t> Segment::bounds() const {
26 auto [l, r] = std::minmax(p0.x, p1.x);
27 auto [t, b] = std::minmax(p0.y, p1.y);
28 return std::make_tuple(l, t, r, b);
29}
30
31bool operator==(const Segment& s0, const Segment& s1) {
32 return s0.upper() == s1.upper() && s0.lower() == s1.lower();
33}
34
35bool operator<(const Segment& s0, const Segment& s1) {
36 return std::make_tuple(s0.upper(), s0.lower()) < std::make_tuple(s1.upper(), s1.lower());
37}
38
40 auto [left0, top0, right0, bottom0] = s0.bounds();
41 auto [left1, top1, right1, bottom1] = s1.bounds();
42 // If the sides of the box touch, then there is no new intersection.
43 return right0 <= left1 || right1 <= left0 || bottom0 <= top1 || bottom1 <= top0;
44}
45
46// Derivation of Intersection
47// The intersection point I = (X, Y) of the two segments (x0, y0) -> (x1, y1)
48// and (x2, y2) -> (x3, y3).
49// X = x0 + s(x1 - x0) = x2 + t(x3 - x2)
50// Y = y0 + s(y1 - y0) = y2 + t(y3 - y2)
51//
52// Solve for s in terms of x.
53// x0 + s(x1 - x0) = x2 + t(x3 - x2)
54// s(x1 - x0) = x2 - x0 + t(x3 - x2)
55// s = (x2 - x0 + t(x3 - x2)) / (x1 - x0)
56//
57// Back substitute s into the equation for Y.
58// y0 + ((x2 - x0 + t(x3 - x2)) / (x1 - x0))(y1 - y0) = y2 + t(y3 - y2)
59// (x2 - x0 + t(x3 - x2)) / (x1 - x0) = (y2 - y0 + t(y3 - y2)) / (y1 - y0)
60// (y1 - y0)(x2 - x0 + t(x3 - x2)) = (x1 - x0)(y2 - y0 + t(y3 - y2))
61// (y1 - y0)(x2 - x0) + t(y1 - y0)(x3 - x2) = (x1 - x0)(y2 - y0) + t(x1 - x0)(y3 - y2)
62// Collecting t's on one side, and constants on the other.
63// t((y1 - y0)(x3 - x2) - (x1 - x0)(y3 - y2)) = (x1 - x0)(y2 - y0) - (y1 - y0)(x2 - x0)
64//
65// Solve for t in terms of x.
66// x0 + s(x1 - x0) = x2 + t(x3 - x2)
67// x0 - x2 + s(x1 - x0) = t(x3 - x2)
68// (x0 - x2 + s(x1 - x0)) / (x3 - x2) = t
69// Back substitute t into the equation for Y.
70// y0 + s(y1 - y0) = y2 + ((x0 - x2 + s(x1 - x0)) / (x3 - x2))(y3 - y2)
71// (y0 - y2 + s(y1 - y0)) / (y3 - y2) = (x0 - x2 + s(x1 - x0)) / (x3 - x2)
72// (x3 - x2)(y0 - y2 + s(y1 - y0)) = (y3 - y2)(x0 - x2 + s(x1 - x0))
73// (x3 - x2)(y0 - y2) + s(x3 - x2)(y1 - y0) = (y3 - y2)(x0 - x2) + s(y3 - y2)(x1 - x0)
74// Collecting s's on on side and constants on the other.
75// s((x3 - x2)(y1 - y0) - (y3 - y2)(x1 - x0)) = (y3 - y2)(x0 - x2) - (x3 - x2)(y0 - y2)
76
77// Assign names and vectors to extract the cross products. The vector (x0, y0) -> (x1, y1) is
78// P0 -> P1, and is named Q = (x1 - x0, y1 - y0) = P1 - P0. The following vectors are defined in
79// a similar way.
80// * Q: P1 - P0
81// * R: P2 - P0
82// * T: P3 - P2
83// Extracting cross products from above for t.
84// t((P3 - P2) x (P1 - P0)) = (P1 - P0) x (P2 - P0)
85// t(T x Q) = Q x R
86// t = (Q x R) / (T x Q)
87// Extracting cross products from above for t.
88// s((P3 - P2) x (P1 - P0)) = (P0 - P2) x (P3 - P2)
89// s(T x Q) = -R x T
90// s = (T x R) / (T x Q)
91//
92// There is an intersection only if t and s are on [0, 1].
93//
94// This method of calculating the intersection only uses 8 multiplies, and 1 division. It also
95// determines if the two segments cross with no round-off error and is always correct using 6
96// multiplies. However, the actual crossing point is rounded to fit back into the int32_t.
97std::optional<Point> intersect(const Segment& s0, const Segment& s1) {
98
99 // Check if the bounds intersect.
101 return std::nullopt;
102 }
103
104 // Create the end Points for s0 and s1
105 const Point P0 = s0.upper(),
106 P1 = s0.lower(),
107 P2 = s1.upper(),
108 P3 = s1.lower();
109
110 if (P0 == P2 || P1 == P3 || P1 == P2 || P3 == P0) {
111 // Lines don't intersect if they share an end point.
112 return std::nullopt;
113 }
114
115 // Create the Q, R, and T.
116 const Point Q = P1 - P0,
117 R = P2 - P0,
118 T = P3 - P2;
119
120 // 64-bit cross product.
121 auto cross = [](const Point& v0, const Point& v1) {
122 int64_t x0 = SkToS64(v0.x),
123 y0 = SkToS64(v0.y),
124 x1 = SkToS64(v1.x),
125 y1 = SkToS64(v1.y);
126 return x0 * y1 - y0 * x1;
127 };
128
129 // Calculate the cross products needed for calculating s and t.
130 const int64_t QxR = cross(Q, R),
131 TxR = cross(T, R),
132 TxQ = cross(T, Q);
133
134 if (TxQ == 0) {
135 // Both t and s are either < 0 or > 1 because the denominator is 0.
136 return std::nullopt;
137 }
138
139 // t = (Q x R) / (T x Q). s = (T x R) / (T x Q). Check that t & s are on [0, 1]
140 if ((QxR ^ TxQ) < 0 || (TxR ^ TxQ) < 0) {
141 // The division is negative and t or s < 0.
142 return std::nullopt;
143 }
144
145 if (TxQ > 0) {
146 if (QxR > TxQ || TxR > TxQ) {
147 // t or s is greater than 1.
148 return std::nullopt;
149 }
150 } else {
151 if (QxR < TxQ || TxR < TxQ) {
152 // t or s is greater than 1.
153 return std::nullopt;
154 }
155 }
156
157 // Calculate the intersection using doubles.
158 // TODO: This is just a placeholder approximation for calculating x and y should use big math
159 // above.
160 const double t = static_cast<double>(QxR) / static_cast<double>(TxQ);
161 SkASSERT(0 <= t && t <= 1);
162 const int32_t x = std::round(t * (P3.x - P2.x) + P2.x),
163 y = std::round(t * (P3.y - P2.y) + P2.y);
164
165 return Point{x, y};
166}
167
168// The comparison is:
169// x0 + (y - y0)(x1 - x0) / (y1 - y0) <? x2 + (y - y2)(x3 - x2) / (y3 - y2)
170// Factor out numerators:
171// [x0(y1 - y0) + (y - y0)(x1 - x0)] / (y1 - y0) <? [x2(y3 - y2) + (y - y2)(x3 -x 2)] / (y3 - y2)
172// Removing the divides by cross multiplying.
173// [x0(y1 - y0) + (y - y0)(x1 - x0)] (y3 - y2) <? [x2(y3 - y2) + (y - y2)(x3 - x2)] (y1 - y0)
174// This is a 64-bit int x0 + (y - y0) (x1 - x0) times a 32-int (y3 - y2) resulting in a 96-bit int,
175// and the same applies to the other side of the <?. Because y0 <= y1 and y2 <= y3, then the
176// differences of (y1 - y0) and (y3 - y2) are positive allowing us to multiply through without
177// worrying about sign changes.
178bool less_than_at(const Segment& s0, const Segment& s1, int32_t y) {
179 auto [l0, t0, r0, b0] = s0.bounds();
180 auto [l1, t1, r1, b1] = s1.bounds();
181 SkASSERT(t0 <= y && y <= b0);
182 SkASSERT(t1 <= y && y <= b1);
183
184 // Return true if the bounding box of s0 is fully to the left of s1.
185 if (r0 < l1) {
186 return true;
187 }
188
189 // Return false if the bounding box of s0 is fully to the right of s1.
190 if (r1 < l0) {
191 return false;
192 }
193
194 // Check the x intercepts along the horizontal line at y.
195 // Make s0 be (x0, y0) -> (x1, y1) and s1 be (x2, y2) -> (x3, y3).
196 auto [x0, y0] = s0.upper();
197 auto [x1, y1] = s0.lower();
198 auto [x2, y2] = s1.upper();
199 auto [x3, y3] = s1.lower();
200
201 int64_t s0YDiff = y - y0,
202 s1YDiff = y - y2,
203 s0YDelta = y1 - y0,
204 s1YDelta = y3 - y2,
205 x0Offset = x0 * s0YDelta + s0YDiff * (x1 - x0),
206 x2Offset = x2 * s1YDelta + s1YDiff * (x3 - x2);
207
208 Int96 s0Factor = multiply(x0Offset, y3 - y2),
209 s1Factor = multiply(x2Offset, y1 - y0);
210
211 return s0Factor < s1Factor;
212}
213
215 auto [l, t, r, b] = segment.bounds();
216
217 // Ensure that the segment intersects the horizontal sweep line
218 SkASSERT(t <= p.y && p.y <= b);
219
220 // Fast answers using bounding boxes.
221 if (p.x < l) {
222 return true;
223 } else if (p.x >= r) {
224 return false;
225 }
226
227 auto [x0, y0] = segment.upper();
228 auto [x1, y1] = segment.lower();
229 auto [x2, y2] = p;
230
231 // For a point and a segment the comparison is:
232 // x2 < x0 + (y2 - y0)(x1 - x0) / (y1 - y0)
233 // becomes
234 // (x2 - x0)(y1 - y0) < (x1 - x0)(y2 - y0)
235 // We don't need to worry about the signs changing in the cross multiply because (y1 - y0) is
236 // always positive. Manipulating a little further derives predicate 2 from "Robust Plane
237 // Sweep for Intersecting Segments" page 9.
238 // 0 < (x1 - x0)(y2 - y0) - (x2 - x0)(y1 - y0)
239 // becomes
240 // | x1-x0 x2-x0 |
241 // 0 < | y1-y0 y2-y0 |
242 return SkToS64(x2 - x0) * SkToS64(y1 - y0) < SkToS64(y2 - y0) * SkToS64(x1 - x0);
243}
244
245// The design of this function allows its use with std::lower_bound. lower_bound returns the
246// iterator to the first segment where rounded_point_less_than_segment_in_x_lower returns false.
247// Therefore, we want s(y) < (x - ½) to return true, then start returning false when s(y) ≥ (x - ½).
249 const auto [l, t, r, b] = s.bounds();
250 const auto [x, y] = p;
251
252 // Ensure that the segment intersects the horizontal sweep line
253 SkASSERT(t <= y && y <= b);
254
255 // In the comparisons below, x is really x - ½
256 if (r < x) {
257 // s is entirely < p.
258 return true;
259 } else if (x <= l) {
260 // s is entirely > p. This also handles vertical lines, so we don't have to handle them
261 // below.
262 return false;
263 }
264
265 const auto [x0, y0] = s.upper();
266 const auto [x1, y1] = s.lower();
267
268 // Horizontal - from the guards above we know that p is on s.
269 if (y0 == y1) {
270 return false;
271 }
272
273 // s is not horizontal or vertical.
274 SkASSERT(x0 != x1 && y0 != y1);
275
276 // Given the segment upper = (x0, y0) and lower = (x1, y1)
277 // x0 + (x1 - x0)(y - y0) / (y1 - y0) < x - ½
278 // (x1 - x0)(y - y0) / (y1 - y0) < x - x0 - ½
279 // Because (y1 - y0) is always positive we can multiply through the inequality without
280 // worrying about sign changes.
281 // (x1 - x0)(y - y0) < (x - x0 - ½)(y1 - y0)
282 // (x1 - x0)(y - y0) < ½(2x - 2x0 - 1)(y1 - y0)
283 // 2(x1 - x0)(y - y0) < (2(x - x0) - 1)(y1 - y0)
284 return 2 * SkToS64(x1 - x0) * SkToS64(y - y0) < (2 * SkToS64(x - x0) - 1) * SkToS64(y1 - y0);
285}
286
287// The design of this function allows use with std::lower_bound. lower_bound returns the iterator
288// to the first segment where rounded_point_less_than_segment_in_x_upper is false. This function
289// implements s(y) < (x + ½).
291 const auto [l, t, r, b] = s.bounds();
292 const auto [x, y] = p;
293
294 // Ensure that the segment intersects the horizontal sweep line
295 SkASSERT(t <= y && y <= b);
296
297 // In the comparisons below, x is really x + ½
298 if (r <= x) {
299 // s is entirely < p.
300 return true;
301 } else if (x < l) {
302 // s is entirely > p. This also handles vertical lines, so we don't have to handle them
303 // below.
304 return false;
305 }
306
307 const auto [x0, y0] = s.upper();
308 const auto [x1, y1] = s.lower();
309
310 // Horizontal - from the guards above we know that p is on s.
311 if (y0 == y1) {
312 return false;
313 }
314
315 // s is not horizontal or vertical.
316 SkASSERT(x0 != x1 && y0 != y1);
317
318 // Given the segment upper = (x0, y0) and lower = (x1, y1)
319 // x0 + (x1 - x0)(y - y0) / (y1 - y0) < x + ½
320 // (x1 - x0)(y - y0) / (y1 - y0) < x - x0 + ½
321 // Because (y1 - y0) is always positive we can multiply through the inequality without
322 // worrying about sign changes.
323 // (x1 - x0)(y - y0) < (x - x0 + ½)(y1 - y0)
324 // (x1 - x0)(y - y0) < ½(2x - 2x0 + 1)(y1 - y0)
325 // 2(x1 - x0)(y - y0) < (2(x - x0) + 1)(y1 - y0)
326 return 2 * SkToS64(x1 - x0) * SkToS64(y - y0) < (2 * SkToS64(x - x0) + 1) * SkToS64(y1 - y0);
327}
328
329int compare_slopes(const Segment& s0, const Segment& s1) {
330 Point s0Delta = s0.lower() - s0.upper(),
331 s1Delta = s1.lower() - s1.upper();
332
333 // Handle the horizontal cases to avoid dealing with infinities.
334 if (s0Delta.y == 0 || s1Delta.y == 0) {
335 if (s0Delta.y != 0) {
336 return -1;
337 } else if (s1Delta.y != 0) {
338 return 1;
339 } else {
340 return 0;
341 }
342 }
343
344 // Compare s0Delta.x / s0Delta.y ? s1Delta.x / s1Delta.y. I used the alternate slope form for
345 // two reasons.
346 // * no change of sign - since the delta ys are always positive, then I don't need to worry
347 // about the change in sign with the cross-multiply.
348 // * proper slope ordering - the slope monotonically increases from the smallest along the
349 // negative x-axis increasing counterclockwise to the largest along
350 // the positive x-axis.
351 int64_t lhs = SkToS64(s0Delta.x) * SkToS64(s1Delta.y),
352 rhs = SkToS64(s1Delta.x) * SkToS64(s0Delta.y);
353
354 if (lhs < rhs) {
355 return -1;
356 } else if (lhs > rhs) {
357 return 1;
358 } else {
359 return 0;
360 }
361}
362} // namespace bentleyottmann
#define SkASSERT(cond)
Definition SkAssert.h:116
constexpr int64_t SkToS64(S x)
Definition SkTo.h:27
static bool b
struct MyStruct s
#define R(r)
double y
double x
bool point_less_than_segment_in_x(Point p, const Segment &segment)
Definition Segment.cpp:214
bool operator<(const Int96 &a, const Int96 &b)
Definition Int96.cpp:23
bool rounded_point_less_than_segment_in_x_lower(const Segment &s, Point p)
Definition Segment.cpp:248
bool rounded_point_less_than_segment_in_x_upper(const Segment &s, Point p)
Definition Segment.cpp:290
bool no_intersection_by_bounding_box(const Segment &s0, const Segment &s1)
Definition Segment.cpp:39
std::optional< Point > intersect(const Segment &s0, const Segment &s1)
Definition Segment.cpp:97
int compare_slopes(const Segment &s0, const Segment &s1)
Definition Segment.cpp:329
Int96 multiply(int64_t a, int32_t b)
Definition Int96.cpp:41
bool operator==(const Int96 &a, const Int96 &b)
Definition Int96.cpp:19
bool less_than_at(const Segment &s0, const Segment &s1, int32_t y)
Definition Segment.cpp:178
#define T
Point lower() const
Definition Segment.cpp:20
std::tuple< int32_t, int32_t, int32_t, int32_t > bounds() const
Definition Segment.cpp:25
Point upper() const
Definition Segment.cpp:16