1use crate::geometry::equation_solver::{solve_cubic, solve_quadratic};
15use crate::math::scalar::{non_zero_sign, sign};
16use crate::math::{SignedDistance, Vector2, cross, dot};
17
18const CUBIC_SEARCH_STARTS: i32 = 4;
19const CUBIC_SEARCH_STEPS: i32 = 4;
20
21#[inline]
25fn vmix(a: Vector2, b: Vector2, t: f64) -> Vector2 {
26 a * (1.0 - t) + b * t
27}
28
29#[derive(Debug, Clone, Copy, PartialEq)]
31pub enum EdgeSegment {
32 Line([Vector2; 2]),
33 Quadratic([Vector2; 3]),
34 Cubic([Vector2; 4]),
35}
36
37impl EdgeSegment {
38 #[inline]
40 pub fn line(p0: Vector2, p1: Vector2) -> EdgeSegment {
41 EdgeSegment::Line([p0, p1])
42 }
43
44 pub fn quadratic(p0: Vector2, p1: Vector2, p2: Vector2) -> EdgeSegment {
46 if cross(p1 - p0, p2 - p1) == 0.0 {
47 EdgeSegment::Line([p0, p2])
48 } else {
49 EdgeSegment::Quadratic([p0, p1, p2])
50 }
51 }
52
53 pub fn cubic(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2) -> EdgeSegment {
55 let p12 = p2 - p1;
56 if cross(p1 - p0, p12) == 0.0 && cross(p12, p3 - p2) == 0.0 {
57 return EdgeSegment::Line([p0, p3]);
58 }
59 let q = 1.5 * p1 - 0.5 * p0;
60 if q == 1.5 * p2 - 0.5 * p3 {
61 return EdgeSegment::Quadratic([p0, q, p3]);
62 }
63 EdgeSegment::Cubic([p0, p1, p2, p3])
64 }
65
66 #[inline]
68 pub fn order(&self) -> usize {
69 match self {
70 EdgeSegment::Line(_) => 1,
71 EdgeSegment::Quadratic(_) => 2,
72 EdgeSegment::Cubic(_) => 3,
73 }
74 }
75
76 #[inline]
78 pub fn control_points(&self) -> ([Vector2; 4], usize) {
79 match self {
80 EdgeSegment::Line(p) => ([p[0], p[1], Vector2::ZERO, Vector2::ZERO], 1),
81 EdgeSegment::Quadratic(p) => ([p[0], p[1], p[2], Vector2::ZERO], 2),
82 EdgeSegment::Cubic(p) => (*p, 3),
83 }
84 }
85
86 pub fn point(&self, t: f64) -> Vector2 {
88 match self {
89 EdgeSegment::Line(p) => vmix(p[0], p[1], t),
90 EdgeSegment::Quadratic(p) => vmix(vmix(p[0], p[1], t), vmix(p[1], p[2], t), t),
91 EdgeSegment::Cubic(p) => {
92 let p12 = vmix(p[1], p[2], t);
93 vmix(
94 vmix(vmix(p[0], p[1], t), p12, t),
95 vmix(p12, vmix(p[2], p[3], t), t),
96 t,
97 )
98 }
99 }
100 }
101
102 pub fn direction(&self, t: f64) -> Vector2 {
104 match self {
105 EdgeSegment::Line(p) => p[1] - p[0],
106 EdgeSegment::Quadratic(p) => {
107 let tangent = vmix(p[1] - p[0], p[2] - p[1], t);
108 if !tangent.is_nonzero() {
109 p[2] - p[0]
110 } else {
111 tangent
112 }
113 }
114 EdgeSegment::Cubic(p) => {
115 let tangent = vmix(
116 vmix(p[1] - p[0], p[2] - p[1], t),
117 vmix(p[2] - p[1], p[3] - p[2], t),
118 t,
119 );
120 if !tangent.is_nonzero() {
121 if t == 0.0 {
122 return p[2] - p[0];
123 }
124 if t == 1.0 {
125 return p[3] - p[1];
126 }
127 }
128 tangent
129 }
130 }
131 }
132
133 pub fn direction_change(&self, t: f64) -> Vector2 {
135 match self {
136 EdgeSegment::Line(_) => Vector2::ZERO,
137 EdgeSegment::Quadratic(p) => (p[2] - p[1]) - (p[1] - p[0]),
138 EdgeSegment::Cubic(p) => vmix(
139 (p[2] - p[1]) - (p[1] - p[0]),
140 (p[3] - p[2]) - (p[2] - p[1]),
141 t,
142 ),
143 }
144 }
145
146 pub fn length(&self) -> f64 {
148 match self {
149 EdgeSegment::Line(p) => (p[1] - p[0]).length(),
150 EdgeSegment::Quadratic(p) => {
151 let ab = p[1] - p[0];
152 let br = p[2] - p[1] - ab;
153 let abab = dot(ab, ab);
154 let abbr = dot(ab, br);
155 let brbr = dot(br, br);
156 let ab_len = abab.sqrt();
157 let br_len = brbr.sqrt();
158 let crs = cross(ab, br);
159 let h = (abab + abbr + abbr + brbr).sqrt();
160 (br_len * ((abbr + brbr) * h - abbr * ab_len)
161 + crs * crs * ((br_len * h + abbr + brbr) / (br_len * ab_len + abbr)).ln())
162 / (brbr * br_len)
163 }
164 EdgeSegment::Cubic(_) => f64::NAN,
166 }
167 }
168
169 pub fn signed_distance(&self, origin: Vector2) -> (SignedDistance, f64) {
172 match self {
173 EdgeSegment::Line(p) => self.linear_signed_distance(p, origin),
174 EdgeSegment::Quadratic(p) => self.quadratic_signed_distance(p, origin),
175 EdgeSegment::Cubic(p) => self.cubic_signed_distance(p, origin),
176 }
177 }
178
179 fn linear_signed_distance(&self, p: &[Vector2; 2], origin: Vector2) -> (SignedDistance, f64) {
180 let aq = origin - p[0];
181 let ab = p[1] - p[0];
182 let param = dot(aq, ab) / dot(ab, ab);
183 let eq = (if param > 0.5 { p[1] } else { p[0] }) - origin;
184 let endpoint_distance = eq.length();
185 if param > 0.0 && param < 1.0 {
186 let ortho_distance = dot(ab.orthonormal(false, false), aq);
187 if ortho_distance.abs() < endpoint_distance {
188 return (SignedDistance::new(ortho_distance, 0.0), param);
189 }
190 }
191 let sd = SignedDistance::new(
192 non_zero_sign(cross(aq, ab)) as f64 * endpoint_distance,
193 dot(ab.normalize(false), eq.normalize(false)).abs(),
194 );
195 (sd, param)
196 }
197
198 fn quadratic_signed_distance(
199 &self,
200 p: &[Vector2; 3],
201 origin: Vector2,
202 ) -> (SignedDistance, f64) {
203 let qa = p[0] - origin;
204 let ab = p[1] - p[0];
205 let br = p[2] - p[1] - ab;
206 let a = dot(br, br);
207 let b = 3.0 * dot(ab, br);
208 let c = 2.0 * dot(ab, ab) + dot(qa, br);
209 let d = dot(qa, ab);
210 let solutions = solve_cubic(a, b, c, d);
211
212 let mut ep_dir = self.direction(0.0);
213 let mut min_distance = non_zero_sign(cross(ep_dir, qa)) as f64 * qa.length(); let mut param = -dot(qa, ep_dir) / dot(ep_dir, ep_dir);
215 {
216 let distance = (p[2] - origin).length(); if distance < min_distance.abs() {
218 ep_dir = self.direction(1.0);
219 min_distance = non_zero_sign(cross(ep_dir, p[2] - origin)) as f64 * distance;
220 param = dot(origin - p[1], ep_dir) / dot(ep_dir, ep_dir);
221 }
222 }
223 for &t in solutions.iter() {
224 if t > 0.0 && t < 1.0 {
225 let qe = qa + 2.0 * t * ab + (t * t) * br;
226 let distance = qe.length();
227 if distance <= min_distance.abs() {
228 min_distance = non_zero_sign(cross(ab + t * br, qe)) as f64 * distance;
229 param = t;
230 }
231 }
232 }
233
234 if (0.0..=1.0).contains(¶m) {
235 (SignedDistance::new(min_distance, 0.0), param)
236 } else if param < 0.5 {
237 (
238 SignedDistance::new(
239 min_distance,
240 dot(self.direction(0.0).normalize(false), qa.normalize(false)).abs(),
241 ),
242 param,
243 )
244 } else {
245 (
246 SignedDistance::new(
247 min_distance,
248 dot(
249 self.direction(1.0).normalize(false),
250 (p[2] - origin).normalize(false),
251 )
252 .abs(),
253 ),
254 param,
255 )
256 }
257 }
258
259 fn cubic_signed_distance(&self, p: &[Vector2; 4], origin: Vector2) -> (SignedDistance, f64) {
260 let qa = p[0] - origin;
261 let ab = p[1] - p[0];
262 let br = p[2] - p[1] - ab;
263 let as_ = (p[3] - p[2]) - (p[2] - p[1]) - br;
264
265 let mut ep_dir = self.direction(0.0);
266 let mut min_distance = non_zero_sign(cross(ep_dir, qa)) as f64 * qa.length(); let mut param = -dot(qa, ep_dir) / dot(ep_dir, ep_dir);
268 {
269 let distance = (p[3] - origin).length(); if distance < min_distance.abs() {
271 ep_dir = self.direction(1.0);
272 min_distance = non_zero_sign(cross(ep_dir, p[3] - origin)) as f64 * distance;
273 param = dot(ep_dir - (p[3] - origin), ep_dir) / dot(ep_dir, ep_dir);
274 }
275 }
276 for i in 0..=CUBIC_SEARCH_STARTS {
278 let mut t = (1.0 / CUBIC_SEARCH_STARTS as f64) * i as f64;
279 let mut qe = qa + 3.0 * t * ab + 3.0 * (t * t) * br + (t * t * t) * as_;
280 let mut d1 = 3.0 * ab + 6.0 * t * br + 3.0 * (t * t) * as_;
281 let mut d2 = 6.0 * br + 6.0 * t * as_;
282 let mut improved_t = t - dot(qe, d1) / (dot(d1, d1) + dot(qe, d2));
283 if improved_t > 0.0 && improved_t < 1.0 {
284 let mut remaining_steps = CUBIC_SEARCH_STEPS;
285 loop {
286 t = improved_t;
287 qe = qa + 3.0 * t * ab + 3.0 * (t * t) * br + (t * t * t) * as_;
288 d1 = 3.0 * ab + 6.0 * t * br + 3.0 * (t * t) * as_;
289 remaining_steps -= 1;
290 if remaining_steps == 0 {
291 break;
292 }
293 d2 = 6.0 * br + 6.0 * t * as_;
294 improved_t = t - dot(qe, d1) / (dot(d1, d1) + dot(qe, d2));
295 if !(improved_t > 0.0 && improved_t < 1.0) {
296 break;
297 }
298 }
299 let distance = qe.length();
300 if distance < min_distance.abs() {
301 min_distance = non_zero_sign(cross(d1, qe)) as f64 * distance;
302 param = t;
303 }
304 }
305 }
306
307 if (0.0..=1.0).contains(¶m) {
308 (SignedDistance::new(min_distance, 0.0), param)
309 } else if param < 0.5 {
310 (
311 SignedDistance::new(
312 min_distance,
313 dot(self.direction(0.0).normalize(false), qa.normalize(false)).abs(),
314 ),
315 param,
316 )
317 } else {
318 (
319 SignedDistance::new(
320 min_distance,
321 dot(
322 self.direction(1.0).normalize(false),
323 (p[3] - origin).normalize(false),
324 )
325 .abs(),
326 ),
327 param,
328 )
329 }
330 }
331
332 pub fn distance_to_perpendicular_distance(
336 &self,
337 distance: &mut SignedDistance,
338 origin: Vector2,
339 param: f64,
340 ) {
341 if param < 0.0 {
342 let dir = self.direction(0.0).normalize(false);
343 let aq = origin - self.point(0.0);
344 let ts = dot(aq, dir);
345 if ts < 0.0 {
346 let perpendicular_distance = cross(aq, dir);
347 if perpendicular_distance.abs() <= distance.distance.abs() {
348 distance.distance = perpendicular_distance;
349 distance.dot = 0.0;
350 }
351 }
352 } else if param > 1.0 {
353 let dir = self.direction(1.0).normalize(false);
354 let bq = origin - self.point(1.0);
355 let ts = dot(bq, dir);
356 if ts > 0.0 {
357 let perpendicular_distance = cross(bq, dir);
358 if perpendicular_distance.abs() <= distance.distance.abs() {
359 distance.distance = perpendicular_distance;
360 distance.dot = 0.0;
361 }
362 }
363 }
364 }
365
366 pub fn scanline_intersections(&self, y: f64, out: &mut [(f64, i32); 3]) -> usize {
369 match self {
370 EdgeSegment::Line(p) => Self::linear_scanline(p, y, out),
371 EdgeSegment::Quadratic(p) => Self::quadratic_scanline(p, y, out),
372 EdgeSegment::Cubic(p) => Self::cubic_scanline(p, y, out),
373 }
374 }
375
376 fn linear_scanline(p: &[Vector2; 2], y: f64, out: &mut [(f64, i32); 3]) -> usize {
377 if (y >= p[0].y && y < p[1].y) || (y >= p[1].y && y < p[0].y) {
378 let param = (y - p[0].y) / (p[1].y - p[0].y);
379 out[0] = (
380 crate::math::scalar::mix(p[0].x, p[1].x, param),
381 sign(p[1].y - p[0].y),
382 );
383 1
384 } else {
385 0
386 }
387 }
388
389 fn quadratic_scanline(p: &[Vector2; 3], y: f64, out: &mut [(f64, i32); 3]) -> usize {
390 let mut total = 0usize;
391 let mut next_dy = if y > p[0].y { 1 } else { -1 };
392 out[total].0 = p[0].x;
393 if p[0].y == y {
394 if p[0].y < p[1].y || (p[0].y == p[1].y && p[0].y < p[2].y) {
395 out[total].1 = 1;
396 total += 1;
397 } else {
398 next_dy = 1;
399 }
400 }
401 {
402 let ab = p[1] - p[0];
403 let br = p[2] - p[1] - ab;
404 let mut t = solve_quadratic(br.y, 2.0 * ab.y, p[0].y - y).roots();
405 if t.len() >= 2 && t[0] > t[1] {
406 t.swap(0, 1);
407 }
408 let mut i = 0;
409 while i < t.len() && total < 2 {
410 let ti = t[i];
411 if (0.0..=1.0).contains(&ti) {
412 out[total].0 = p[0].x + 2.0 * ti * ab.x + ti * ti * br.x;
413 if next_dy as f64 * (ab.y + ti * br.y) >= 0.0 {
414 out[total].1 = next_dy;
415 total += 1;
416 next_dy = -next_dy;
417 }
418 }
419 i += 1;
420 }
421 }
422 if p[2].y == y {
423 if next_dy > 0 && total > 0 {
424 total -= 1;
425 next_dy = -1;
426 }
427 if (p[2].y < p[1].y || (p[2].y == p[1].y && p[2].y < p[0].y)) && total < 2 {
428 out[total].0 = p[2].x;
429 if next_dy < 0 {
430 out[total].1 = -1;
431 total += 1;
432 next_dy = 1;
433 }
434 }
435 }
436 if next_dy != (if y >= p[2].y { 1 } else { -1 }) {
437 if total > 0 {
438 total -= 1;
439 } else {
440 if (p[2].y - y).abs() < (p[0].y - y).abs() {
441 out[total].0 = p[2].x;
442 }
443 out[total].1 = next_dy;
444 total += 1;
445 }
446 }
447 total
448 }
449
450 fn cubic_scanline(p: &[Vector2; 4], y: f64, out: &mut [(f64, i32); 3]) -> usize {
451 let mut total = 0usize;
452 let mut next_dy = if y > p[0].y { 1 } else { -1 };
453 out[total].0 = p[0].x;
454 if p[0].y == y {
455 if p[0].y < p[1].y
456 || (p[0].y == p[1].y && (p[0].y < p[2].y || (p[0].y == p[2].y && p[0].y < p[3].y)))
457 {
458 out[total].1 = 1;
459 total += 1;
460 } else {
461 next_dy = 1;
462 }
463 }
464 {
465 let ab = p[1] - p[0];
466 let br = p[2] - p[1] - ab;
467 let as_ = (p[3] - p[2]) - (p[2] - p[1]) - br;
468 let mut t = solve_cubic(as_.y, 3.0 * br.y, 3.0 * ab.y, p[0].y - y);
469 if t.len() >= 2 {
471 if t[0] > t[1] {
472 t.swap(0, 1);
473 }
474 if t.len() >= 3 && t[1] > t[2] {
475 t.swap(1, 2);
476 if t[0] > t[1] {
477 t.swap(0, 1);
478 }
479 }
480 }
481 let mut i = 0;
482 while i < t.len() && total < 3 {
483 let ti = t[i];
484 if (0.0..=1.0).contains(&ti) {
485 out[total].0 =
486 p[0].x + 3.0 * ti * ab.x + 3.0 * ti * ti * br.x + ti * ti * ti * as_.x;
487 if next_dy as f64 * (ab.y + 2.0 * ti * br.y + ti * ti * as_.y) >= 0.0 {
488 out[total].1 = next_dy;
489 total += 1;
490 next_dy = -next_dy;
491 }
492 }
493 i += 1;
494 }
495 }
496 if p[3].y == y {
497 if next_dy > 0 && total > 0 {
498 total -= 1;
499 next_dy = -1;
500 }
501 if (p[3].y < p[2].y
502 || (p[3].y == p[2].y && (p[3].y < p[1].y || (p[3].y == p[1].y && p[3].y < p[0].y))))
503 && total < 3
504 {
505 out[total].0 = p[3].x;
506 if next_dy < 0 {
507 out[total].1 = -1;
508 total += 1;
509 next_dy = 1;
510 }
511 }
512 }
513 if next_dy != (if y >= p[3].y { 1 } else { -1 }) {
514 if total > 0 {
515 total -= 1;
516 } else {
517 if (p[3].y - y).abs() < (p[0].y - y).abs() {
518 out[total].0 = p[3].x;
519 }
520 out[total].1 = next_dy;
521 total += 1;
522 }
523 }
524 total
525 }
526
527 pub fn bound(&self, min: &mut Vector2, max: &mut Vector2) {
529 let mut pb = |p: Vector2| {
530 if p.x < min.x {
531 min.x = p.x;
532 }
533 if p.y < min.y {
534 min.y = p.y;
535 }
536 if p.x > max.x {
537 max.x = p.x;
538 }
539 if p.y > max.y {
540 max.y = p.y;
541 }
542 };
543 match self {
544 EdgeSegment::Line(p) => {
545 pb(p[0]);
546 pb(p[1]);
547 }
548 EdgeSegment::Quadratic(p) => {
549 pb(p[0]);
550 pb(p[2]);
551 let bot = (p[1] - p[0]) - (p[2] - p[1]);
552 if bot.x != 0.0 {
553 let param = (p[1].x - p[0].x) / bot.x;
554 if param > 0.0 && param < 1.0 {
555 pb(self.point(param));
556 }
557 }
558 if bot.y != 0.0 {
559 let param = (p[1].y - p[0].y) / bot.y;
560 if param > 0.0 && param < 1.0 {
561 pb(self.point(param));
562 }
563 }
564 }
565 EdgeSegment::Cubic(p) => {
566 pb(p[0]);
567 pb(p[3]);
568 let a0 = p[1] - p[0];
569 let a1 = 2.0 * (p[2] - p[1] - a0);
570 let a2 = p[3] - 3.0 * p[2] + 3.0 * p[1] - p[0];
571 for ¶m in solve_quadratic(a2.x, a1.x, a0.x).roots().iter() {
572 if param > 0.0 && param < 1.0 {
573 pb(self.point(param));
574 }
575 }
576 for ¶m in solve_quadratic(a2.y, a1.y, a0.y).roots().iter() {
577 if param > 0.0 && param < 1.0 {
578 pb(self.point(param));
579 }
580 }
581 }
582 }
583 }
584
585 pub fn reverse(&mut self) {
587 match self {
588 EdgeSegment::Line(p) => p.swap(0, 1),
589 EdgeSegment::Quadratic(p) => p.swap(0, 2),
590 EdgeSegment::Cubic(p) => {
591 p.swap(0, 3);
592 p.swap(1, 2);
593 }
594 }
595 }
596
597 pub fn move_start_point(&mut self, to: Vector2) {
599 match self {
600 EdgeSegment::Line(p) => p[0] = to,
601 EdgeSegment::Quadratic(p) => {
602 let orig_s_dir = p[0] - p[1];
603 let orig_p1 = p[1];
604 p[1] = p[1]
605 + cross(p[0] - p[1], to - p[0]) / cross(p[0] - p[1], p[2] - p[1])
606 * (p[2] - p[1]);
607 p[0] = to;
608 if dot(orig_s_dir, p[0] - p[1]) < 0.0 {
609 p[1] = orig_p1;
610 }
611 }
612 EdgeSegment::Cubic(p) => {
613 p[1] = p[1] + (to - p[0]);
614 p[0] = to;
615 }
616 }
617 }
618
619 pub fn move_end_point(&mut self, to: Vector2) {
621 match self {
622 EdgeSegment::Line(p) => p[1] = to,
623 EdgeSegment::Quadratic(p) => {
624 let orig_e_dir = p[2] - p[1];
625 let orig_p1 = p[1];
626 p[1] = p[1]
627 + cross(p[2] - p[1], to - p[2]) / cross(p[2] - p[1], p[0] - p[1])
628 * (p[0] - p[1]);
629 p[2] = to;
630 if dot(orig_e_dir, p[2] - p[1]) < 0.0 {
631 p[1] = orig_p1;
632 }
633 }
634 EdgeSegment::Cubic(p) => {
635 p[2] = p[2] + (to - p[3]);
636 p[3] = to;
637 }
638 }
639 }
640
641 pub fn split_in_thirds(&self) -> [EdgeSegment; 3] {
643 match self {
644 EdgeSegment::Line(_) => [
645 EdgeSegment::line(self.point(0.0), self.point(1.0 / 3.0)),
646 EdgeSegment::line(self.point(1.0 / 3.0), self.point(2.0 / 3.0)),
647 EdgeSegment::line(self.point(2.0 / 3.0), self.point(1.0)),
648 ],
649 EdgeSegment::Quadratic(p) => [
650 EdgeSegment::Quadratic([p[0], vmix(p[0], p[1], 1.0 / 3.0), self.point(1.0 / 3.0)]),
651 EdgeSegment::Quadratic([
652 self.point(1.0 / 3.0),
653 vmix(
654 vmix(p[0], p[1], 5.0 / 9.0),
655 vmix(p[1], p[2], 4.0 / 9.0),
656 0.5,
657 ),
658 self.point(2.0 / 3.0),
659 ]),
660 EdgeSegment::Quadratic([self.point(2.0 / 3.0), vmix(p[1], p[2], 2.0 / 3.0), p[2]]),
661 ],
662 EdgeSegment::Cubic(p) => [
663 EdgeSegment::Cubic([
664 p[0],
665 if p[0] == p[1] {
666 p[0]
667 } else {
668 vmix(p[0], p[1], 1.0 / 3.0)
669 },
670 vmix(
671 vmix(p[0], p[1], 1.0 / 3.0),
672 vmix(p[1], p[2], 1.0 / 3.0),
673 1.0 / 3.0,
674 ),
675 self.point(1.0 / 3.0),
676 ]),
677 EdgeSegment::Cubic([
678 self.point(1.0 / 3.0),
679 vmix(
680 vmix(
681 vmix(p[0], p[1], 1.0 / 3.0),
682 vmix(p[1], p[2], 1.0 / 3.0),
683 1.0 / 3.0,
684 ),
685 vmix(
686 vmix(p[1], p[2], 1.0 / 3.0),
687 vmix(p[2], p[3], 1.0 / 3.0),
688 1.0 / 3.0,
689 ),
690 2.0 / 3.0,
691 ),
692 vmix(
693 vmix(
694 vmix(p[0], p[1], 2.0 / 3.0),
695 vmix(p[1], p[2], 2.0 / 3.0),
696 2.0 / 3.0,
697 ),
698 vmix(
699 vmix(p[1], p[2], 2.0 / 3.0),
700 vmix(p[2], p[3], 2.0 / 3.0),
701 2.0 / 3.0,
702 ),
703 1.0 / 3.0,
704 ),
705 self.point(2.0 / 3.0),
706 ]),
707 EdgeSegment::Cubic([
708 self.point(2.0 / 3.0),
709 vmix(
710 vmix(p[1], p[2], 2.0 / 3.0),
711 vmix(p[2], p[3], 2.0 / 3.0),
712 2.0 / 3.0,
713 ),
714 if p[2] == p[3] {
715 p[3]
716 } else {
717 vmix(p[2], p[3], 2.0 / 3.0)
718 },
719 p[3],
720 ]),
721 ],
722 }
723 }
724
725 pub fn convert_to_cubic(&self) -> EdgeSegment {
727 match self {
728 EdgeSegment::Quadratic(p) => EdgeSegment::Cubic([
729 p[0],
730 vmix(p[0], p[1], 2.0 / 3.0),
731 vmix(p[1], p[2], 1.0 / 3.0),
732 p[2],
733 ]),
734 other => *other,
735 }
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 #[test]
744 fn line_distance_perpendicular() {
745 let e = EdgeSegment::line(Vector2::new(0.0, 0.0), Vector2::new(10.0, 0.0));
746 let (sd, t) = e.signed_distance(Vector2::new(5.0, 3.0));
747 assert!((sd.distance.abs() - 3.0).abs() < 1e-9);
748 assert!((t - 0.5).abs() < 1e-9);
749 }
750
751 #[test]
752 fn line_endpoints() {
753 let e = EdgeSegment::line(Vector2::new(0.0, 0.0), Vector2::new(10.0, 0.0));
754 assert_eq!(e.point(0.0), Vector2::new(0.0, 0.0));
755 assert_eq!(e.point(1.0), Vector2::new(10.0, 0.0));
756 assert_eq!(e.point(0.5), Vector2::new(5.0, 0.0));
757 }
758
759 #[test]
760 fn quadratic_collapses_to_line() {
761 let e = EdgeSegment::quadratic(
763 Vector2::new(0.0, 0.0),
764 Vector2::new(1.0, 0.0),
765 Vector2::new(2.0, 0.0),
766 );
767 assert!(matches!(e, EdgeSegment::Line(_)));
768 }
769
770 #[test]
771 fn reverse_line() {
772 let mut e = EdgeSegment::line(Vector2::new(0.0, 0.0), Vector2::new(10.0, 0.0));
773 e.reverse();
774 assert_eq!(e.point(0.0), Vector2::new(10.0, 0.0));
775 }
776
777 #[test]
778 fn scanline_line_crossing() {
779 let e = EdgeSegment::line(Vector2::new(0.0, 0.0), Vector2::new(0.0, 10.0));
780 let mut out = [(0.0, 0); 3];
781 let n = e.scanline_intersections(5.0, &mut out);
782 assert_eq!(n, 1);
783 assert!((out[0].0 - 0.0).abs() < 1e-9);
784 assert_eq!(out[0].1, 1);
785 }
786}