| 1 | use crate::scalar::Scalar; |
| 2 | use crate::segment::{BoundingBox, Segment}; |
| 3 | use crate::traits::Transformation; |
| 4 | use crate::{point, Box2D, Point, Vector}; |
| 5 | use crate::{CubicBezierSegment, Line, LineEquation, LineSegment, Triangle}; |
| 6 | use arrayvec::ArrayVec; |
| 7 | use num_traits::NumCast; |
| 8 | |
| 9 | use core::mem; |
| 10 | use core::ops::Range; |
| 11 | |
| 12 | /// A 2d curve segment defined by three points: the beginning of the segment, a control |
| 13 | /// point and the end of the segment. |
| 14 | /// |
| 15 | /// The curve is defined by equation: |
| 16 | /// ```∀ t ∈ [0..1], P(t) = (1 - t)² * from + 2 * (1 - t) * t * ctrl + t² * to``` |
| 17 | #[derive (Copy, Clone, Debug, PartialEq)] |
| 18 | #[cfg_attr (feature = "serialization" , derive(Serialize, Deserialize))] |
| 19 | pub struct QuadraticBezierSegment<S> { |
| 20 | pub from: Point<S>, |
| 21 | pub ctrl: Point<S>, |
| 22 | pub to: Point<S>, |
| 23 | } |
| 24 | |
| 25 | impl<S: Scalar> QuadraticBezierSegment<S> { |
| 26 | pub fn cast<NewS: NumCast>(self) -> QuadraticBezierSegment<NewS> { |
| 27 | QuadraticBezierSegment { |
| 28 | from: self.from.cast(), |
| 29 | ctrl: self.ctrl.cast(), |
| 30 | to: self.to.cast(), |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | /// Sample the curve at t (expecting t between 0 and 1). |
| 35 | pub fn sample(&self, t: S) -> Point<S> { |
| 36 | let t2 = t * t; |
| 37 | let one_t = S::ONE - t; |
| 38 | let one_t2 = one_t * one_t; |
| 39 | |
| 40 | self.from * one_t2 + self.ctrl.to_vector() * S::TWO * one_t * t + self.to.to_vector() * t2 |
| 41 | } |
| 42 | |
| 43 | /// Sample the x coordinate of the curve at t (expecting t between 0 and 1). |
| 44 | pub fn x(&self, t: S) -> S { |
| 45 | let t2 = t * t; |
| 46 | let one_t = S::ONE - t; |
| 47 | let one_t2 = one_t * one_t; |
| 48 | |
| 49 | self.from.x * one_t2 + self.ctrl.x * S::TWO * one_t * t + self.to.x * t2 |
| 50 | } |
| 51 | |
| 52 | /// Sample the y coordinate of the curve at t (expecting t between 0 and 1). |
| 53 | pub fn y(&self, t: S) -> S { |
| 54 | let t2 = t * t; |
| 55 | let one_t = S::ONE - t; |
| 56 | let one_t2 = one_t * one_t; |
| 57 | |
| 58 | self.from.y * one_t2 + self.ctrl.y * S::TWO * one_t * t + self.to.y * t2 |
| 59 | } |
| 60 | |
| 61 | #[inline ] |
| 62 | fn derivative_coefficients(&self, t: S) -> (S, S, S) { |
| 63 | (S::TWO * t - S::TWO, -S::FOUR * t + S::TWO, S::TWO * t) |
| 64 | } |
| 65 | |
| 66 | /// Sample the curve's derivative at t (expecting t between 0 and 1). |
| 67 | pub fn derivative(&self, t: S) -> Vector<S> { |
| 68 | let (c0, c1, c2) = self.derivative_coefficients(t); |
| 69 | self.from.to_vector() * c0 + self.ctrl.to_vector() * c1 + self.to.to_vector() * c2 |
| 70 | } |
| 71 | |
| 72 | /// Sample the x coordinate of the curve's derivative at t (expecting t between 0 and 1). |
| 73 | pub fn dx(&self, t: S) -> S { |
| 74 | let (c0, c1, c2) = self.derivative_coefficients(t); |
| 75 | self.from.x * c0 + self.ctrl.x * c1 + self.to.x * c2 |
| 76 | } |
| 77 | |
| 78 | /// Sample the y coordinate of the curve's derivative at t (expecting t between 0 and 1). |
| 79 | pub fn dy(&self, t: S) -> S { |
| 80 | let (c0, c1, c2) = self.derivative_coefficients(t); |
| 81 | self.from.y * c0 + self.ctrl.y * c1 + self.to.y * c2 |
| 82 | } |
| 83 | |
| 84 | /// Swap the beginning and the end of the segment. |
| 85 | pub fn flip(&self) -> Self { |
| 86 | QuadraticBezierSegment { |
| 87 | from: self.to, |
| 88 | ctrl: self.ctrl, |
| 89 | to: self.from, |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | /// Find the advancement of the y-most position in the curve. |
| 94 | /// |
| 95 | /// This returns the advancement along the curve, not the actual y position. |
| 96 | pub fn y_maximum_t(&self) -> S { |
| 97 | if let Some(t) = self.local_y_extremum_t() { |
| 98 | let y = self.y(t); |
| 99 | if y > self.from.y && y > self.to.y { |
| 100 | return t; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | if self.from.y > self.to.y { |
| 105 | S::ZERO |
| 106 | } else { |
| 107 | S::ONE |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | /// Find the advancement of the y-least position in the curve. |
| 112 | /// |
| 113 | /// This returns the advancement along the curve, not the actual y position. |
| 114 | pub fn y_minimum_t(&self) -> S { |
| 115 | if let Some(t) = self.local_y_extremum_t() { |
| 116 | let y = self.y(t); |
| 117 | if y < self.from.y && y < self.to.y { |
| 118 | return t; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | if self.from.y < self.to.y { |
| 123 | S::ZERO |
| 124 | } else { |
| 125 | S::ONE |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /// Return the y inflection point or None if this curve is y-monotonic. |
| 130 | pub fn local_y_extremum_t(&self) -> Option<S> { |
| 131 | let div = self.from.y - S::TWO * self.ctrl.y + self.to.y; |
| 132 | if div == S::ZERO { |
| 133 | return None; |
| 134 | } |
| 135 | let t = (self.from.y - self.ctrl.y) / div; |
| 136 | if t > S::ZERO && t < S::ONE { |
| 137 | return Some(t); |
| 138 | } |
| 139 | |
| 140 | None |
| 141 | } |
| 142 | |
| 143 | /// Find the advancement of the x-most position in the curve. |
| 144 | /// |
| 145 | /// This returns the advancement along the curve, not the actual x position. |
| 146 | pub fn x_maximum_t(&self) -> S { |
| 147 | if let Some(t) = self.local_x_extremum_t() { |
| 148 | let x = self.x(t); |
| 149 | if x > self.from.x && x > self.to.x { |
| 150 | return t; |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | if self.from.x > self.to.x { |
| 155 | S::ZERO |
| 156 | } else { |
| 157 | S::ONE |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | /// Find the advancement of the x-least position in the curve. |
| 162 | /// |
| 163 | /// This returns the advancement along the curve, not the actual x position. |
| 164 | pub fn x_minimum_t(&self) -> S { |
| 165 | if let Some(t) = self.local_x_extremum_t() { |
| 166 | let x = self.x(t); |
| 167 | if x < self.from.x && x < self.to.x { |
| 168 | return t; |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | if self.from.x < self.to.x { |
| 173 | S::ZERO |
| 174 | } else { |
| 175 | S::ONE |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /// Return the x inflection point or None if this curve is x-monotonic. |
| 180 | pub fn local_x_extremum_t(&self) -> Option<S> { |
| 181 | let div = self.from.x - S::TWO * self.ctrl.x + self.to.x; |
| 182 | if div == S::ZERO { |
| 183 | return None; |
| 184 | } |
| 185 | let t = (self.from.x - self.ctrl.x) / div; |
| 186 | if t > S::ZERO && t < S::ONE { |
| 187 | return Some(t); |
| 188 | } |
| 189 | |
| 190 | None |
| 191 | } |
| 192 | |
| 193 | /// Return the sub-curve inside a given range of t. |
| 194 | /// |
| 195 | /// This is equivalent splitting at the range's end points. |
| 196 | pub fn split_range(&self, t_range: Range<S>) -> Self { |
| 197 | let t0 = t_range.start; |
| 198 | let t1 = t_range.end; |
| 199 | |
| 200 | let from = self.sample(t0); |
| 201 | let to = self.sample(t1); |
| 202 | let ctrl = from + (self.ctrl - self.from).lerp(self.to - self.ctrl, t0) * (t1 - t0); |
| 203 | |
| 204 | QuadraticBezierSegment { from, ctrl, to } |
| 205 | } |
| 206 | |
| 207 | /// Split this curve into two sub-curves. |
| 208 | pub fn split(&self, t: S) -> (QuadraticBezierSegment<S>, QuadraticBezierSegment<S>) { |
| 209 | let split_point = self.sample(t); |
| 210 | |
| 211 | ( |
| 212 | QuadraticBezierSegment { |
| 213 | from: self.from, |
| 214 | ctrl: self.from.lerp(self.ctrl, t), |
| 215 | to: split_point, |
| 216 | }, |
| 217 | QuadraticBezierSegment { |
| 218 | from: split_point, |
| 219 | ctrl: self.ctrl.lerp(self.to, t), |
| 220 | to: self.to, |
| 221 | }, |
| 222 | ) |
| 223 | } |
| 224 | |
| 225 | /// Return the curve before the split point. |
| 226 | pub fn before_split(&self, t: S) -> QuadraticBezierSegment<S> { |
| 227 | QuadraticBezierSegment { |
| 228 | from: self.from, |
| 229 | ctrl: self.from.lerp(self.ctrl, t), |
| 230 | to: self.sample(t), |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | /// Return the curve after the split point. |
| 235 | pub fn after_split(&self, t: S) -> QuadraticBezierSegment<S> { |
| 236 | QuadraticBezierSegment { |
| 237 | from: self.sample(t), |
| 238 | ctrl: self.ctrl.lerp(self.to, t), |
| 239 | to: self.to, |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | /// Elevate this curve to a third order bézier. |
| 244 | pub fn to_cubic(&self) -> CubicBezierSegment<S> { |
| 245 | CubicBezierSegment { |
| 246 | from: self.from, |
| 247 | ctrl1: (self.from + self.ctrl.to_vector() * S::TWO) / S::THREE, |
| 248 | ctrl2: (self.to + self.ctrl.to_vector() * S::TWO) / S::THREE, |
| 249 | to: self.to, |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | #[inline ] |
| 254 | pub fn baseline(&self) -> LineSegment<S> { |
| 255 | LineSegment { |
| 256 | from: self.from, |
| 257 | to: self.to, |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | /// Returns whether the curve can be approximated with a single point, given |
| 262 | /// a tolerance threshold. |
| 263 | pub fn is_a_point(&self, tolerance: S) -> bool { |
| 264 | let tol2 = tolerance * tolerance; |
| 265 | (self.from - self.to).square_length() <= tol2 |
| 266 | && (self.from - self.ctrl).square_length() <= tol2 |
| 267 | } |
| 268 | |
| 269 | /// Returns true if the curve can be approximated with a single line segment |
| 270 | /// given a tolerance threshold. |
| 271 | pub fn is_linear(&self, tolerance: S) -> bool { |
| 272 | if self.from == self.to { |
| 273 | return true; |
| 274 | } |
| 275 | |
| 276 | let d = self |
| 277 | .baseline() |
| 278 | .to_line() |
| 279 | .square_distance_to_point(self.ctrl); |
| 280 | |
| 281 | d <= (tolerance * tolerance * S::FOUR) |
| 282 | } |
| 283 | |
| 284 | /// Computes a "fat line" of this segment. |
| 285 | /// |
| 286 | /// A fat line is two conservative lines between which the segment |
| 287 | /// is fully contained. |
| 288 | pub fn fat_line(&self) -> (LineEquation<S>, LineEquation<S>) { |
| 289 | let l1 = self.baseline().to_line().equation(); |
| 290 | let d = S::HALF * l1.signed_distance_to_point(&self.ctrl); |
| 291 | let l2 = l1.offset(d); |
| 292 | if d >= S::ZERO { |
| 293 | (l1, l2) |
| 294 | } else { |
| 295 | (l2, l1) |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | /// Applies the transform to this curve and returns the results. |
| 300 | #[inline ] |
| 301 | pub fn transformed<T: Transformation<S>>(&self, transform: &T) -> Self { |
| 302 | QuadraticBezierSegment { |
| 303 | from: transform.transform_point(self.from), |
| 304 | ctrl: transform.transform_point(self.ctrl), |
| 305 | to: transform.transform_point(self.to), |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | /// Find the interval of the beginning of the curve that can be approximated with a |
| 310 | /// line segment. |
| 311 | pub fn flattening_step(&self, tolerance: S) -> S { |
| 312 | let v1 = self.ctrl - self.from; |
| 313 | let v2 = self.to - self.from; |
| 314 | |
| 315 | let v1_cross_v2 = v2.x * v1.y - v2.y * v1.x; |
| 316 | let h = S::sqrt(v1.x * v1.x + v1.y * v1.y); |
| 317 | |
| 318 | if S::abs(v1_cross_v2 * h) <= S::EPSILON { |
| 319 | return S::ONE; |
| 320 | } |
| 321 | |
| 322 | let s2inv = h / v1_cross_v2; |
| 323 | |
| 324 | let t = S::TWO * S::sqrt(tolerance * S::abs(s2inv) / S::THREE); |
| 325 | |
| 326 | if t > S::ONE { |
| 327 | return S::ONE; |
| 328 | } |
| 329 | |
| 330 | t |
| 331 | } |
| 332 | |
| 333 | /// Approximates the curve with sequence of line segments. |
| 334 | /// |
| 335 | /// The `tolerance` parameter defines the maximum distance between the curve and |
| 336 | /// its approximation. |
| 337 | /// |
| 338 | /// This implements the algorithm described by Raph Levien at |
| 339 | /// <https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html> |
| 340 | pub fn for_each_flattened<F>(&self, tolerance: S, callback: &mut F) |
| 341 | where |
| 342 | F: FnMut(&LineSegment<S>), |
| 343 | { |
| 344 | self.for_each_flattened_with_t(tolerance, &mut |segment, _| callback(segment)); |
| 345 | } |
| 346 | |
| 347 | /// Compute a flattened approximation of the curve, invoking a callback at |
| 348 | /// each step. |
| 349 | /// |
| 350 | /// The `tolerance` parameter defines the maximum distance between the curve and |
| 351 | /// its approximation. |
| 352 | /// |
| 353 | /// The end of the t parameter range at the final segment is guaranteed to be equal to `1.0`. |
| 354 | /// |
| 355 | /// This implements the algorithm described by Raph Levien at |
| 356 | /// <https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html> |
| 357 | pub fn for_each_flattened_with_t<F>(&self, tolerance: S, callback: &mut F) |
| 358 | where |
| 359 | F: FnMut(&LineSegment<S>, Range<S>), |
| 360 | { |
| 361 | let params = FlatteningParameters::new(self, tolerance); |
| 362 | |
| 363 | let mut i = S::ONE; |
| 364 | let mut from = self.from; |
| 365 | let mut t_from = S::ZERO; |
| 366 | for _ in 1..params.count.to_u32().unwrap() { |
| 367 | let t = params.t_at_iteration(i); |
| 368 | i += S::ONE; |
| 369 | let s = LineSegment { |
| 370 | from, |
| 371 | to: self.sample(t), |
| 372 | }; |
| 373 | |
| 374 | callback(&s, t_from..t); |
| 375 | from = s.to; |
| 376 | t_from = t; |
| 377 | } |
| 378 | |
| 379 | let s = LineSegment { from, to: self.to }; |
| 380 | |
| 381 | callback(&s, t_from..S::ONE); |
| 382 | } |
| 383 | |
| 384 | /// Returns the flattened representation of the curve as an iterator, starting *after* the |
| 385 | /// current point. |
| 386 | pub fn flattened(&self, tolerance: S) -> Flattened<S> { |
| 387 | Flattened::new(self, tolerance) |
| 388 | } |
| 389 | /// Returns the flattened representation of the curve as an iterator, starting *after* the |
| 390 | /// current point. |
| 391 | pub fn flattened_t(&self, tolerance: S) -> FlattenedT<S> { |
| 392 | FlattenedT::new(self, tolerance) |
| 393 | } |
| 394 | |
| 395 | /// Invokes a callback for each monotonic part of the segment. |
| 396 | pub fn for_each_monotonic_range<F>(&self, cb: &mut F) |
| 397 | where |
| 398 | F: FnMut(Range<S>), |
| 399 | { |
| 400 | let mut t0 = self.local_x_extremum_t(); |
| 401 | let mut t1 = self.local_y_extremum_t(); |
| 402 | |
| 403 | let swap = match (t0, t1) { |
| 404 | (Some(tx), Some(ty)) => tx > ty, |
| 405 | _ => false, |
| 406 | }; |
| 407 | |
| 408 | if swap { |
| 409 | mem::swap(&mut t0, &mut t1); |
| 410 | } |
| 411 | |
| 412 | let mut start = S::ZERO; |
| 413 | |
| 414 | if let Some(t) = t0 { |
| 415 | cb(start..t); |
| 416 | start = t; |
| 417 | } |
| 418 | |
| 419 | if let Some(t) = t1 { |
| 420 | // In extreme cases the same point can be an x and y inflection point. |
| 421 | if t != start { |
| 422 | cb(start..t); |
| 423 | start = t |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | cb(start..S::ONE); |
| 428 | } |
| 429 | |
| 430 | /// Invokes a callback for each monotonic part of the segment. |
| 431 | pub fn for_each_monotonic<F>(&self, cb: &mut F) |
| 432 | where |
| 433 | F: FnMut(&QuadraticBezierSegment<S>), |
| 434 | { |
| 435 | self.for_each_monotonic_range(&mut |range| { |
| 436 | let mut sub = self.split_range(range); |
| 437 | // Due to finite precision the split may actually result in sub-curves |
| 438 | // that are almost but not-quite monotonic. Make sure they actually are. |
| 439 | let min_x = sub.from.x.min(sub.to.x); |
| 440 | let max_x = sub.from.x.max(sub.to.x); |
| 441 | let min_y = sub.from.y.min(sub.to.y); |
| 442 | let max_y = sub.from.y.max(sub.to.y); |
| 443 | sub.ctrl.x = sub.ctrl.x.max(min_x).min(max_x); |
| 444 | sub.ctrl.y = sub.ctrl.y.max(min_y).min(max_y); |
| 445 | cb(&sub); |
| 446 | }); |
| 447 | } |
| 448 | |
| 449 | /// Invokes a callback for each y-monotonic part of the segment. |
| 450 | pub fn for_each_y_monotonic_range<F>(&self, cb: &mut F) |
| 451 | where |
| 452 | F: FnMut(Range<S>), |
| 453 | { |
| 454 | match self.local_y_extremum_t() { |
| 455 | Some(t) => { |
| 456 | cb(S::ZERO..t); |
| 457 | cb(t..S::ONE); |
| 458 | } |
| 459 | None => { |
| 460 | cb(S::ZERO..S::ONE); |
| 461 | } |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | /// Invokes a callback for each y-monotonic part of the segment. |
| 466 | pub fn for_each_y_monotonic<F>(&self, cb: &mut F) |
| 467 | where |
| 468 | F: FnMut(&QuadraticBezierSegment<S>), |
| 469 | { |
| 470 | match self.local_y_extremum_t() { |
| 471 | Some(t) => { |
| 472 | let (a, b) = self.split(t); |
| 473 | cb(&a); |
| 474 | cb(&b); |
| 475 | } |
| 476 | None => { |
| 477 | cb(self); |
| 478 | } |
| 479 | } |
| 480 | } |
| 481 | |
| 482 | /// Invokes a callback for each x-monotonic part of the segment. |
| 483 | pub fn for_each_x_monotonic_range<F>(&self, cb: &mut F) |
| 484 | where |
| 485 | F: FnMut(Range<S>), |
| 486 | { |
| 487 | match self.local_x_extremum_t() { |
| 488 | Some(t) => { |
| 489 | cb(S::ZERO..t); |
| 490 | cb(t..S::ONE); |
| 491 | } |
| 492 | None => { |
| 493 | cb(S::ZERO..S::ONE); |
| 494 | } |
| 495 | } |
| 496 | } |
| 497 | |
| 498 | /// Invokes a callback for each x-monotonic part of the segment. |
| 499 | pub fn for_each_x_monotonic<F>(&self, cb: &mut F) |
| 500 | where |
| 501 | F: FnMut(&QuadraticBezierSegment<S>), |
| 502 | { |
| 503 | match self.local_x_extremum_t() { |
| 504 | Some(t) => { |
| 505 | let (mut a, mut b) = self.split(t); |
| 506 | // Due to finite precision the split may actually result in sub-curves |
| 507 | // that are almost but not-quite monotonic. Make sure they actually are. |
| 508 | let a_min = a.from.x.min(a.to.x); |
| 509 | let a_max = a.from.x.max(a.to.x); |
| 510 | let b_min = b.from.x.min(b.to.x); |
| 511 | let b_max = b.from.x.max(b.to.x); |
| 512 | a.ctrl.x = a.ctrl.x.max(a_min).min(a_max); |
| 513 | b.ctrl.x = b.ctrl.x.max(b_min).min(b_max); |
| 514 | cb(&a); |
| 515 | cb(&b); |
| 516 | } |
| 517 | None => { |
| 518 | cb(self); |
| 519 | } |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | /// Returns a triangle containing this curve segment. |
| 524 | pub fn bounding_triangle(&self) -> Triangle<S> { |
| 525 | Triangle { |
| 526 | a: self.from, |
| 527 | b: self.ctrl, |
| 528 | c: self.to, |
| 529 | } |
| 530 | } |
| 531 | |
| 532 | /// Returns a conservative rectangle that contains the curve. |
| 533 | pub fn fast_bounding_box(&self) -> Box2D<S> { |
| 534 | let (min_x, max_x) = self.fast_bounding_range_x(); |
| 535 | let (min_y, max_y) = self.fast_bounding_range_y(); |
| 536 | |
| 537 | Box2D { |
| 538 | min: point(min_x, min_y), |
| 539 | max: point(max_x, max_y), |
| 540 | } |
| 541 | } |
| 542 | |
| 543 | /// Returns a conservative range of x that contains this curve. |
| 544 | pub fn fast_bounding_range_x(&self) -> (S, S) { |
| 545 | let min_x = self.from.x.min(self.ctrl.x).min(self.to.x); |
| 546 | let max_x = self.from.x.max(self.ctrl.x).max(self.to.x); |
| 547 | |
| 548 | (min_x, max_x) |
| 549 | } |
| 550 | |
| 551 | /// Returns a conservative range of y that contains this curve. |
| 552 | pub fn fast_bounding_range_y(&self) -> (S, S) { |
| 553 | let min_y = self.from.y.min(self.ctrl.y).min(self.to.y); |
| 554 | let max_y = self.from.y.max(self.ctrl.y).max(self.to.y); |
| 555 | |
| 556 | (min_y, max_y) |
| 557 | } |
| 558 | |
| 559 | /// Returns the smallest rectangle the curve is contained in |
| 560 | pub fn bounding_box(&self) -> Box2D<S> { |
| 561 | let (min_x, max_x) = self.bounding_range_x(); |
| 562 | let (min_y, max_y) = self.bounding_range_y(); |
| 563 | |
| 564 | Box2D { |
| 565 | min: point(min_x, min_y), |
| 566 | max: point(max_x, max_y), |
| 567 | } |
| 568 | } |
| 569 | |
| 570 | /// Returns the smallest range of x that contains this curve. |
| 571 | pub fn bounding_range_x(&self) -> (S, S) { |
| 572 | let min_x = self.x(self.x_minimum_t()); |
| 573 | let max_x = self.x(self.x_maximum_t()); |
| 574 | |
| 575 | (min_x, max_x) |
| 576 | } |
| 577 | |
| 578 | /// Returns the smallest range of y that contains this curve. |
| 579 | pub fn bounding_range_y(&self) -> (S, S) { |
| 580 | let min_y = self.y(self.y_minimum_t()); |
| 581 | let max_y = self.y(self.y_maximum_t()); |
| 582 | |
| 583 | (min_y, max_y) |
| 584 | } |
| 585 | |
| 586 | /// Returns whether this segment is monotonic on the x axis. |
| 587 | pub fn is_x_monotonic(&self) -> bool { |
| 588 | self.local_x_extremum_t().is_none() |
| 589 | } |
| 590 | |
| 591 | /// Returns whether this segment is monotonic on the y axis. |
| 592 | pub fn is_y_monotonic(&self) -> bool { |
| 593 | self.local_y_extremum_t().is_none() |
| 594 | } |
| 595 | |
| 596 | /// Returns whether this segment is fully monotonic. |
| 597 | pub fn is_monotonic(&self) -> bool { |
| 598 | self.is_x_monotonic() && self.is_y_monotonic() |
| 599 | } |
| 600 | |
| 601 | /// Computes the intersections (if any) between this segment a line. |
| 602 | /// |
| 603 | /// The result is provided in the form of the `t` parameters of each |
| 604 | /// point along curve. To get the intersection points, sample the curve |
| 605 | /// at the corresponding values. |
| 606 | pub fn line_intersections_t(&self, line: &Line<S>) -> ArrayVec<S, 2> { |
| 607 | // take the quadratic bezier formulation and inject it in |
| 608 | // the line equation ax + by + c = 0. |
| 609 | let eqn = line.equation(); |
| 610 | let i = eqn.a() * self.from.x + eqn.b() * self.from.y; |
| 611 | let j = eqn.a() * self.ctrl.x + eqn.b() * self.ctrl.y; |
| 612 | let k = eqn.a() * self.to.x + eqn.b() * self.to.y; |
| 613 | // Solve "(i - 2j + k)t² + (2j - 2i)t + (i + c) = 0" |
| 614 | let a = i - j - j + k; |
| 615 | let b = j + j - i - i; |
| 616 | let c = i + eqn.c(); |
| 617 | |
| 618 | let mut result = ArrayVec::new(); |
| 619 | |
| 620 | if a == S::ZERO { |
| 621 | // Linear equation bt + c = 0. |
| 622 | let t = c / b; |
| 623 | if t >= S::ZERO && t <= S::ONE { |
| 624 | result.push(t); |
| 625 | return result; |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | let delta = b * b - S::FOUR * a * c; |
| 630 | if delta >= S::ZERO { |
| 631 | // To avoid potential float precision issues when b is close to |
| 632 | // sqrt_delta, we exploit the fact that given the roots t1 and t2, |
| 633 | // t2 = c / (a * t1) and t1 = c / (a * t2). |
| 634 | let sqrt_delta = S::sqrt(delta); |
| 635 | let s_sqrt_delta = -b.signum() * sqrt_delta; |
| 636 | let mut t1 = (-b + s_sqrt_delta) / (S::TWO * a); |
| 637 | let mut t2 = c / (a * t1); |
| 638 | |
| 639 | if t1 > t2 { |
| 640 | mem::swap(&mut t1, &mut t2); |
| 641 | } |
| 642 | |
| 643 | if t1 >= S::ZERO && t1 <= S::ONE { |
| 644 | result.push(t1); |
| 645 | } |
| 646 | |
| 647 | if t2 >= S::ZERO && t2 <= S::ONE && t1 != t2 { |
| 648 | result.push(t2); |
| 649 | } |
| 650 | } |
| 651 | |
| 652 | result |
| 653 | } |
| 654 | |
| 655 | /// Computes the intersection points (if any) between this segment a line. |
| 656 | pub fn line_intersections(&self, line: &Line<S>) -> ArrayVec<Point<S>, 2> { |
| 657 | let intersections = self.line_intersections_t(line); |
| 658 | |
| 659 | let mut result = ArrayVec::new(); |
| 660 | for t in intersections { |
| 661 | result.push(self.sample(t)); |
| 662 | } |
| 663 | |
| 664 | result |
| 665 | } |
| 666 | |
| 667 | /// Computes the intersections (if any) between this segment and a line segment. |
| 668 | /// |
| 669 | /// The result is provided in the form of the `t` parameters of each |
| 670 | /// point along curve and segment. To get the intersection points, sample |
| 671 | /// the segments at the corresponding values. |
| 672 | pub fn line_segment_intersections_t(&self, segment: &LineSegment<S>) -> ArrayVec<(S, S), 2> { |
| 673 | if !self |
| 674 | .fast_bounding_box() |
| 675 | .inflate(S::EPSILON, S::EPSILON) |
| 676 | .intersects(&segment.bounding_box().inflate(S::EPSILON, S::EPSILON)) |
| 677 | { |
| 678 | return ArrayVec::new(); |
| 679 | } |
| 680 | |
| 681 | let intersections = self.line_intersections_t(&segment.to_line()); |
| 682 | |
| 683 | let mut result = ArrayVec::new(); |
| 684 | if intersections.is_empty() { |
| 685 | return result; |
| 686 | } |
| 687 | |
| 688 | let seg_is_mostly_vertical = |
| 689 | S::abs(segment.from.y - segment.to.y) >= S::abs(segment.from.x - segment.to.x); |
| 690 | let (seg_long_axis_min, seg_long_axis_max) = if seg_is_mostly_vertical { |
| 691 | segment.bounding_range_y() |
| 692 | } else { |
| 693 | segment.bounding_range_x() |
| 694 | }; |
| 695 | |
| 696 | for t in intersections { |
| 697 | let intersection_xy = if seg_is_mostly_vertical { |
| 698 | self.y(t) |
| 699 | } else { |
| 700 | self.x(t) |
| 701 | }; |
| 702 | if intersection_xy >= seg_long_axis_min && intersection_xy <= seg_long_axis_max { |
| 703 | let t2 = (self.sample(t) - segment.from).length() / segment.length(); |
| 704 | // Don't take intersections that are on endpoints of both curves at the same time. |
| 705 | if (t != S::ZERO && t != S::ONE) || (t2 != S::ZERO && t2 != S::ONE) { |
| 706 | result.push((t, t2)); |
| 707 | } |
| 708 | } |
| 709 | } |
| 710 | |
| 711 | result |
| 712 | } |
| 713 | |
| 714 | #[inline ] |
| 715 | pub fn from(&self) -> Point<S> { |
| 716 | self.from |
| 717 | } |
| 718 | |
| 719 | #[inline ] |
| 720 | pub fn to(&self) -> Point<S> { |
| 721 | self.to |
| 722 | } |
| 723 | |
| 724 | /// Computes the intersection points (if any) between this segment a line segment. |
| 725 | pub fn line_segment_intersections(&self, segment: &LineSegment<S>) -> ArrayVec<Point<S>, 2> { |
| 726 | let intersections = self.line_segment_intersections_t(segment); |
| 727 | |
| 728 | let mut result = ArrayVec::new(); |
| 729 | for (t, _) in intersections { |
| 730 | result.push(self.sample(t)); |
| 731 | } |
| 732 | |
| 733 | result |
| 734 | } |
| 735 | |
| 736 | /// Analytic solution to finding the closest point on the curve to `pos`. |
| 737 | pub fn closest_point(&self, pos: Point<S>) -> S { |
| 738 | // We are looking for the points in the curve where the line passing through pos |
| 739 | // and these points are perpendicular to the curve. |
| 740 | let a = self.from - pos; |
| 741 | let b = self.ctrl - self.from; |
| 742 | let c = self.from + self.to.to_vector() - self.ctrl * S::TWO; |
| 743 | |
| 744 | // Polynomial coefficients |
| 745 | let c0 = c.dot(c); |
| 746 | let c1 = b.dot(c) * S::THREE; |
| 747 | let c2 = b.dot(b) * S::TWO + a.dot(c); |
| 748 | let c3 = a.dot(b); |
| 749 | |
| 750 | let roots = crate::utils::cubic_polynomial_roots(c0, c1, c2, c3); |
| 751 | |
| 752 | let mut sq_dist = a.square_length(); |
| 753 | let mut t = S::ZERO; |
| 754 | let to_dist = (self.to - pos).square_length(); |
| 755 | if to_dist < sq_dist { |
| 756 | sq_dist = to_dist; |
| 757 | t = S::ONE |
| 758 | } |
| 759 | for root in roots { |
| 760 | if root >= S::ZERO && root <= S::ONE { |
| 761 | let p = self.sample(root); |
| 762 | let d = (pos - p).square_length(); |
| 763 | if d < sq_dist { |
| 764 | sq_dist = d; |
| 765 | t = root; |
| 766 | } |
| 767 | } |
| 768 | } |
| 769 | |
| 770 | t |
| 771 | } |
| 772 | |
| 773 | /// Returns the shortest distance between this segment and a point. |
| 774 | pub fn distance_to_point(&self, pos: Point<S>) -> S { |
| 775 | (self.sample(self.closest_point(pos)) - pos).length() |
| 776 | } |
| 777 | |
| 778 | /// Returns the shortest squared distance between this segment and a point. |
| 779 | /// |
| 780 | /// May be useful to avoid the cost of a square root when comparing against a distance |
| 781 | /// that can be squared instead. |
| 782 | pub fn square_distance_to_point(&self, pos: Point<S>) -> S { |
| 783 | (self.sample(self.closest_point(pos)) - pos).square_length() |
| 784 | } |
| 785 | |
| 786 | // Returns a quadratic bézier curve built by dragging this curve's point at `t` |
| 787 | // to a new position without moving the endpoints. |
| 788 | pub fn drag(&self, t: S, new_position: Point<S>) -> Self { |
| 789 | let t2 = t * t; |
| 790 | let one_t = S::ONE - t; |
| 791 | let one_t2 = one_t * one_t; |
| 792 | |
| 793 | let u = t2 / (t2 + one_t2); |
| 794 | let c = self.from.lerp(self.to, u); |
| 795 | |
| 796 | let inv_r = S::abs((t2 + one_t2) / (t2 + one_t2 - S::ONE)); |
| 797 | |
| 798 | QuadraticBezierSegment { |
| 799 | from: self.from, |
| 800 | ctrl: new_position + (new_position - c) * inv_r, |
| 801 | to: self.to, |
| 802 | } |
| 803 | } |
| 804 | |
| 805 | /// Computes the length of this segment. |
| 806 | /// |
| 807 | /// Implements Raph Levien's analytical approach described in |
| 808 | /// https://raphlinus.github.io/curves/2018/12/28/bezier-arclength.html |
| 809 | pub fn length(&self) -> S { |
| 810 | // This is ported from kurbo's implementation. |
| 811 | // https://github.com/linebender/kurbo/blob/d0b956b47f219ba2303b4e2f2d904ea7b946e783/src/quadbez.rs#L239 |
| 812 | let d2 = self.from - self.ctrl * S::TWO + self.to.to_vector(); |
| 813 | let d1 = self.ctrl - self.from; |
| 814 | let a = d2.square_length(); |
| 815 | let c = d1.square_length(); |
| 816 | if a < S::value(1e-4) * c { |
| 817 | // The segment is almost straight. |
| 818 | // |
| 819 | // Legendre-Gauss quadrature using formula from Behdad |
| 820 | // in https://github.com/Pomax/BezierInfo-2/issues/77 |
| 821 | let v0 = (self.from.to_vector() * S::value(-0.492943519233745) |
| 822 | + self.ctrl.to_vector() * S::value(0.430331482911935) |
| 823 | + self.to.to_vector() * S::value(0.0626120363218102)) |
| 824 | .length(); |
| 825 | let v1 = ((self.to - self.from) * S::value(0.4444444444444444)).length(); |
| 826 | let v2 = (self.from.to_vector() * S::value(-0.0626120363218102) |
| 827 | + self.ctrl.to_vector() * S::value(-0.430331482911935) |
| 828 | + self.to.to_vector() * S::value(0.492943519233745)) |
| 829 | .length(); |
| 830 | return v0 + v1 + v2; |
| 831 | } |
| 832 | |
| 833 | let b = S::TWO * d2.dot(d1); |
| 834 | |
| 835 | let sqr_abc = (a + b + c).sqrt(); |
| 836 | let a2 = a.powf(-S::HALF); |
| 837 | let a32 = a2.powi(3); |
| 838 | let c2 = S::TWO * c.sqrt(); |
| 839 | let ba_c2 = b * a2 + c2; |
| 840 | |
| 841 | let v0 = S::HALF * S::HALF * a2 * a2 * b * (S::TWO * sqr_abc - c2) + sqr_abc; |
| 842 | |
| 843 | if ba_c2 < S::EPSILON { |
| 844 | // The curve has a sharp turns. |
| 845 | v0 |
| 846 | } else { |
| 847 | v0 + S::HALF |
| 848 | * S::HALF |
| 849 | * a32 |
| 850 | * (S::FOUR * c * a - b * b) |
| 851 | * (((S::TWO * a + b) * a2 + S::TWO * sqr_abc) / ba_c2).ln() |
| 852 | } |
| 853 | } |
| 854 | |
| 855 | // This is to conform to the `impl_segment!` macro |
| 856 | fn approximate_length(&self, _tolerance: S) -> S { |
| 857 | self.length() |
| 858 | } |
| 859 | |
| 860 | pub fn to_f32(&self) -> QuadraticBezierSegment<f32> { |
| 861 | QuadraticBezierSegment { |
| 862 | from: self.from.to_f32(), |
| 863 | ctrl: self.ctrl.to_f32(), |
| 864 | to: self.to.to_f32(), |
| 865 | } |
| 866 | } |
| 867 | |
| 868 | pub fn to_f64(&self) -> QuadraticBezierSegment<f64> { |
| 869 | QuadraticBezierSegment { |
| 870 | from: self.from.to_f64(), |
| 871 | ctrl: self.ctrl.to_f64(), |
| 872 | to: self.to.to_f64(), |
| 873 | } |
| 874 | } |
| 875 | } |
| 876 | |
| 877 | pub struct FlatteningParameters<S> { |
| 878 | count: S, |
| 879 | integral_from: S, |
| 880 | integral_step: S, |
| 881 | inv_integral_from: S, |
| 882 | div_inv_integral_diff: S, |
| 883 | } |
| 884 | |
| 885 | impl<S: Scalar> FlatteningParameters<S> { |
| 886 | // See https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html |
| 887 | pub fn new(curve: &QuadraticBezierSegment<S>, tolerance: S) -> Self { |
| 888 | // Checking for the single segment approximation is much cheaper than evaluating |
| 889 | // the general flattening approximation. |
| 890 | if curve.is_linear(tolerance) { |
| 891 | return FlatteningParameters { |
| 892 | count: S::ZERO, |
| 893 | // This are irrelevant as if count is 0. |
| 894 | integral_from: S::ZERO, |
| 895 | integral_step: S::ZERO, |
| 896 | inv_integral_from: S::ZERO, |
| 897 | div_inv_integral_diff: S::ZERO, |
| 898 | }; |
| 899 | } |
| 900 | |
| 901 | // Map the quadratic bézier segment to y = x^2 parabola. |
| 902 | let ddx = S::TWO * curve.ctrl.x - curve.from.x - curve.to.x; |
| 903 | let ddy = S::TWO * curve.ctrl.y - curve.from.y - curve.to.y; |
| 904 | let cross = (curve.to.x - curve.from.x) * ddy - (curve.to.y - curve.from.y) * ddx; |
| 905 | let inv_cross = S::ONE / cross; |
| 906 | let parabola_from = |
| 907 | ((curve.ctrl.x - curve.from.x) * ddx + (curve.ctrl.y - curve.from.y) * ddy) * inv_cross; |
| 908 | let parabola_to = |
| 909 | ((curve.to.x - curve.ctrl.x) * ddx + (curve.to.y - curve.ctrl.y) * ddy) * inv_cross; |
| 910 | // Note, scale can be NaN, for example with straight lines. When it happens the NaN will |
| 911 | // propagate to other parameters. We catch it all by setting the iteration count to zero |
| 912 | // and leave the rest as garbage. |
| 913 | let scale = |
| 914 | cross.abs() / (S::sqrt(ddx * ddx + ddy * ddy) * (parabola_to - parabola_from).abs()); |
| 915 | |
| 916 | let integral_from = approx_parabola_integral(parabola_from); |
| 917 | let integral_to = approx_parabola_integral(parabola_to); |
| 918 | let integral_diff = integral_to - integral_from; |
| 919 | |
| 920 | let inv_integral_from = approx_parabola_inv_integral(integral_from); |
| 921 | let inv_integral_to = approx_parabola_inv_integral(integral_to); |
| 922 | let div_inv_integral_diff = S::ONE / (inv_integral_to - inv_integral_from); |
| 923 | |
| 924 | // We could store this as an integer but the generic code makes that awkward and we'll |
| 925 | // use it as a scalar again while iterating, so it's kept as a scalar. |
| 926 | let mut count = (S::HALF * integral_diff.abs() * (scale / tolerance).sqrt()).ceil(); |
| 927 | // If count is NaN the curve can be approximated by a single straight line or a point. |
| 928 | if !count.is_finite() { |
| 929 | count = S::ZERO; |
| 930 | } |
| 931 | |
| 932 | let integral_step = integral_diff / count; |
| 933 | |
| 934 | FlatteningParameters { |
| 935 | count, |
| 936 | integral_from, |
| 937 | integral_step, |
| 938 | inv_integral_from, |
| 939 | div_inv_integral_diff, |
| 940 | } |
| 941 | } |
| 942 | |
| 943 | fn t_at_iteration(&self, iteration: S) -> S { |
| 944 | let u = approx_parabola_inv_integral(self.integral_from + self.integral_step * iteration); |
| 945 | let t = (u - self.inv_integral_from) * self.div_inv_integral_diff; |
| 946 | |
| 947 | t |
| 948 | } |
| 949 | } |
| 950 | |
| 951 | /// Compute an approximation to integral (1 + 4x^2) ^ -0.25 dx used in the flattening code. |
| 952 | fn approx_parabola_integral<S: Scalar>(x: S) -> S { |
| 953 | let d: S = S::value(0.67); |
| 954 | let quarter: S = S::HALF * S::HALF; |
| 955 | x / (S::ONE - d + (d.powi(4) + quarter * x * x).sqrt().sqrt()) |
| 956 | } |
| 957 | |
| 958 | /// Approximate the inverse of the function above. |
| 959 | fn approx_parabola_inv_integral<S: Scalar>(x: S) -> S { |
| 960 | let b: S = S::value(0.39); |
| 961 | let quarter: S = S::HALF * S::HALF; |
| 962 | x * (S::ONE - b + (b * b + quarter * x * x).sqrt()) |
| 963 | } |
| 964 | |
| 965 | /// A flattening iterator for quadratic bézier segments. |
| 966 | /// |
| 967 | /// Yields points at each iteration. |
| 968 | pub struct Flattened<S> { |
| 969 | curve: QuadraticBezierSegment<S>, |
| 970 | params: FlatteningParameters<S>, |
| 971 | i: S, |
| 972 | done: bool, |
| 973 | } |
| 974 | |
| 975 | impl<S: Scalar> Flattened<S> { |
| 976 | #[inline ] |
| 977 | pub(crate) fn new(curve: &QuadraticBezierSegment<S>, tolerance: S) -> Self { |
| 978 | let params: FlatteningParameters = FlatteningParameters::new(curve, tolerance); |
| 979 | |
| 980 | Flattened { |
| 981 | curve: *curve, |
| 982 | params, |
| 983 | i: S::ONE, |
| 984 | done: false, |
| 985 | } |
| 986 | } |
| 987 | } |
| 988 | |
| 989 | impl<S: Scalar> Iterator for Flattened<S> { |
| 990 | type Item = Point<S>; |
| 991 | |
| 992 | #[inline ] |
| 993 | fn next(&mut self) -> Option<Point<S>> { |
| 994 | if self.done { |
| 995 | return None; |
| 996 | } |
| 997 | |
| 998 | if self.i >= self.params.count - S::EPSILON { |
| 999 | self.done = true; |
| 1000 | return Some(self.curve.to); |
| 1001 | } |
| 1002 | |
| 1003 | let t = self.params.t_at_iteration(self.i); |
| 1004 | self.i += S::ONE; |
| 1005 | |
| 1006 | Some(self.curve.sample(t)) |
| 1007 | } |
| 1008 | |
| 1009 | #[inline ] |
| 1010 | fn size_hint(&self) -> (usize, Option<usize>) { |
| 1011 | let count = (self.params.count + S::ONE - self.i).to_usize().unwrap(); |
| 1012 | (count, Some(count)) |
| 1013 | } |
| 1014 | } |
| 1015 | |
| 1016 | /// A flattening iterator for quadratic bézier segments. |
| 1017 | /// |
| 1018 | /// Yields the curve parameter at each iteration. |
| 1019 | pub struct FlattenedT<S> { |
| 1020 | params: FlatteningParameters<S>, |
| 1021 | i: S, |
| 1022 | done: bool, |
| 1023 | } |
| 1024 | |
| 1025 | impl<S: Scalar> FlattenedT<S> { |
| 1026 | #[inline ] |
| 1027 | pub(crate) fn new(curve: &QuadraticBezierSegment<S>, tolerance: S) -> Self { |
| 1028 | let params: FlatteningParameters = FlatteningParameters::new(curve, tolerance); |
| 1029 | FlattenedT { |
| 1030 | i: S::ONE, |
| 1031 | params, |
| 1032 | done: false, |
| 1033 | } |
| 1034 | } |
| 1035 | } |
| 1036 | |
| 1037 | impl<S: Scalar> Iterator for FlattenedT<S> { |
| 1038 | type Item = S; |
| 1039 | |
| 1040 | #[inline ] |
| 1041 | fn next(&mut self) -> Option<S> { |
| 1042 | if self.done { |
| 1043 | return None; |
| 1044 | } |
| 1045 | |
| 1046 | if self.i >= self.params.count - S::EPSILON { |
| 1047 | self.done = true; |
| 1048 | return Some(S::ONE); |
| 1049 | } |
| 1050 | |
| 1051 | let t = self.params.t_at_iteration(self.i); |
| 1052 | self.i += S::ONE; |
| 1053 | |
| 1054 | Some(t) |
| 1055 | } |
| 1056 | |
| 1057 | #[inline ] |
| 1058 | fn size_hint(&self) -> (usize, Option<usize>) { |
| 1059 | let count = (self.params.count + S::ONE - self.i).to_usize().unwrap(); |
| 1060 | (count, Some(count)) |
| 1061 | } |
| 1062 | } |
| 1063 | |
| 1064 | impl<S: Scalar> Segment for QuadraticBezierSegment<S> { |
| 1065 | impl_segment!(S); |
| 1066 | |
| 1067 | fn for_each_flattened_with_t( |
| 1068 | &self, |
| 1069 | tolerance: Self::Scalar, |
| 1070 | callback: &mut dyn FnMut(&LineSegment<S>, Range<S>), |
| 1071 | ) { |
| 1072 | self.for_each_flattened_with_t(tolerance, &mut |s: &LineSegment, t: Range| callback(s, t)); |
| 1073 | } |
| 1074 | } |
| 1075 | |
| 1076 | impl<S: Scalar> BoundingBox for QuadraticBezierSegment<S> { |
| 1077 | type Scalar = S; |
| 1078 | fn bounding_box(&self) -> Box2D<S> { |
| 1079 | self.bounding_box() |
| 1080 | } |
| 1081 | fn fast_bounding_box(&self) -> Box2D<S> { |
| 1082 | self.fast_bounding_box() |
| 1083 | } |
| 1084 | fn bounding_range_x(&self) -> (S, S) { |
| 1085 | self.bounding_range_x() |
| 1086 | } |
| 1087 | fn bounding_range_y(&self) -> (S, S) { |
| 1088 | self.bounding_range_y() |
| 1089 | } |
| 1090 | fn fast_bounding_range_x(&self) -> (S, S) { |
| 1091 | self.fast_bounding_range_x() |
| 1092 | } |
| 1093 | fn fast_bounding_range_y(&self) -> (S, S) { |
| 1094 | self.fast_bounding_range_y() |
| 1095 | } |
| 1096 | } |
| 1097 | |
| 1098 | #[test ] |
| 1099 | fn bounding_box_for_monotonic_quadratic_bezier_segment() { |
| 1100 | let a = QuadraticBezierSegment { |
| 1101 | from: Point::new(0.0, 0.0), |
| 1102 | ctrl: Point::new(0.0, 0.0), |
| 1103 | to: Point::new(2.0, 0.0), |
| 1104 | }; |
| 1105 | |
| 1106 | let expected_aabb = Box2D { |
| 1107 | min: point(0.0, 0.0), |
| 1108 | max: point(2.0, 0.0), |
| 1109 | }; |
| 1110 | |
| 1111 | let actual_aabb = a.bounding_box(); |
| 1112 | |
| 1113 | assert_eq!(expected_aabb, actual_aabb) |
| 1114 | } |
| 1115 | |
| 1116 | #[test ] |
| 1117 | fn fast_bounding_box_for_quadratic_bezier_segment() { |
| 1118 | let a = QuadraticBezierSegment { |
| 1119 | from: Point::new(0.0, 0.0), |
| 1120 | ctrl: Point::new(1.0, 1.0), |
| 1121 | to: Point::new(2.0, 0.0), |
| 1122 | }; |
| 1123 | |
| 1124 | let expected_aabb = Box2D { |
| 1125 | min: point(0.0, 0.0), |
| 1126 | max: point(2.0, 1.0), |
| 1127 | }; |
| 1128 | |
| 1129 | let actual_aabb = a.fast_bounding_box(); |
| 1130 | |
| 1131 | assert_eq!(expected_aabb, actual_aabb) |
| 1132 | } |
| 1133 | |
| 1134 | #[test ] |
| 1135 | fn minimum_bounding_box_for_quadratic_bezier_segment() { |
| 1136 | let a = QuadraticBezierSegment { |
| 1137 | from: Point::new(0.0, 0.0), |
| 1138 | ctrl: Point::new(1.0, 1.0), |
| 1139 | to: Point::new(2.0, 0.0), |
| 1140 | }; |
| 1141 | |
| 1142 | let expected_aabb = Box2D { |
| 1143 | min: point(0.0, 0.0), |
| 1144 | max: point(2.0, 0.5), |
| 1145 | }; |
| 1146 | |
| 1147 | let actual_aabb = a.bounding_box(); |
| 1148 | |
| 1149 | assert_eq!(expected_aabb, actual_aabb) |
| 1150 | } |
| 1151 | |
| 1152 | #[test ] |
| 1153 | fn y_maximum_t_for_simple_segment() { |
| 1154 | let a = QuadraticBezierSegment { |
| 1155 | from: Point::new(0.0, 0.0), |
| 1156 | ctrl: Point::new(1.0, 1.0), |
| 1157 | to: Point::new(2.0, 0.0), |
| 1158 | }; |
| 1159 | |
| 1160 | let expected_y_maximum = 0.5; |
| 1161 | |
| 1162 | let actual_y_maximum = a.y_maximum_t(); |
| 1163 | |
| 1164 | assert_eq!(expected_y_maximum, actual_y_maximum) |
| 1165 | } |
| 1166 | |
| 1167 | #[test ] |
| 1168 | fn local_y_extremum_for_simple_segment() { |
| 1169 | let a = QuadraticBezierSegment { |
| 1170 | from: Point::new(0.0, 0.0), |
| 1171 | ctrl: Point::new(1.0, 1.0), |
| 1172 | to: Point::new(2.0, 0.0), |
| 1173 | }; |
| 1174 | |
| 1175 | let expected_y_inflection = 0.5; |
| 1176 | |
| 1177 | match a.local_y_extremum_t() { |
| 1178 | Some(actual_y_inflection) => assert_eq!(expected_y_inflection, actual_y_inflection), |
| 1179 | None => panic!(), |
| 1180 | } |
| 1181 | } |
| 1182 | |
| 1183 | #[test ] |
| 1184 | fn y_minimum_t_for_simple_segment() { |
| 1185 | let a = QuadraticBezierSegment { |
| 1186 | from: Point::new(0.0, 0.0), |
| 1187 | ctrl: Point::new(1.0, -1.0), |
| 1188 | to: Point::new(2.0, 0.0), |
| 1189 | }; |
| 1190 | |
| 1191 | let expected_y_minimum = 0.5; |
| 1192 | |
| 1193 | let actual_y_minimum = a.y_minimum_t(); |
| 1194 | |
| 1195 | assert_eq!(expected_y_minimum, actual_y_minimum) |
| 1196 | } |
| 1197 | |
| 1198 | #[test ] |
| 1199 | fn x_maximum_t_for_simple_segment() { |
| 1200 | let a = QuadraticBezierSegment { |
| 1201 | from: Point::new(0.0, 0.0), |
| 1202 | ctrl: Point::new(1.0, 1.0), |
| 1203 | to: Point::new(0.0, 2.0), |
| 1204 | }; |
| 1205 | |
| 1206 | let expected_x_maximum = 0.5; |
| 1207 | |
| 1208 | let actual_x_maximum = a.x_maximum_t(); |
| 1209 | |
| 1210 | assert_eq!(expected_x_maximum, actual_x_maximum) |
| 1211 | } |
| 1212 | |
| 1213 | #[test ] |
| 1214 | fn local_x_extremum_for_simple_segment() { |
| 1215 | let a = QuadraticBezierSegment { |
| 1216 | from: Point::new(0.0, 0.0), |
| 1217 | ctrl: Point::new(1.0, 1.0), |
| 1218 | to: Point::new(0.0, 2.0), |
| 1219 | }; |
| 1220 | |
| 1221 | let expected_x_inflection = 0.5; |
| 1222 | |
| 1223 | match a.local_x_extremum_t() { |
| 1224 | Some(actual_x_inflection) => assert_eq!(expected_x_inflection, actual_x_inflection), |
| 1225 | None => panic!(), |
| 1226 | } |
| 1227 | } |
| 1228 | |
| 1229 | #[test ] |
| 1230 | fn x_minimum_t_for_simple_segment() { |
| 1231 | let a = QuadraticBezierSegment { |
| 1232 | from: Point::new(2.0, 0.0), |
| 1233 | ctrl: Point::new(1.0, 1.0), |
| 1234 | to: Point::new(2.0, 2.0), |
| 1235 | }; |
| 1236 | |
| 1237 | let expected_x_minimum = 0.5; |
| 1238 | |
| 1239 | let actual_x_minimum = a.x_minimum_t(); |
| 1240 | |
| 1241 | assert_eq!(expected_x_minimum, actual_x_minimum) |
| 1242 | } |
| 1243 | |
| 1244 | #[test ] |
| 1245 | fn length_straight_line() { |
| 1246 | // Sanity check: aligned points so both these curves are straight lines |
| 1247 | // that go form (0.0, 0.0) to (2.0, 0.0). |
| 1248 | |
| 1249 | let len = QuadraticBezierSegment { |
| 1250 | from: Point::new(0.0f64, 0.0), |
| 1251 | ctrl: Point::new(1.0, 0.0), |
| 1252 | to: Point::new(2.0, 0.0), |
| 1253 | } |
| 1254 | .length(); |
| 1255 | assert!((len - 2.0).abs() < 0.000001); |
| 1256 | |
| 1257 | let len = CubicBezierSegment { |
| 1258 | from: Point::new(0.0f64, 0.0), |
| 1259 | ctrl1: Point::new(1.0, 0.0), |
| 1260 | ctrl2: Point::new(1.0, 0.0), |
| 1261 | to: Point::new(2.0, 0.0), |
| 1262 | } |
| 1263 | .approximate_length(0.0001); |
| 1264 | assert!((len - 2.0).abs() < 0.000001); |
| 1265 | } |
| 1266 | |
| 1267 | #[test ] |
| 1268 | fn derivatives() { |
| 1269 | let c1 = QuadraticBezierSegment { |
| 1270 | from: Point::new(1.0, 1.0), |
| 1271 | ctrl: Point::new(2.0, 1.0), |
| 1272 | to: Point::new(2.0, 2.0), |
| 1273 | }; |
| 1274 | |
| 1275 | assert_eq!(c1.dy(0.0), 0.0); |
| 1276 | assert_eq!(c1.dx(1.0), 0.0); |
| 1277 | assert_eq!(c1.dy(0.5), c1.dx(0.5)); |
| 1278 | } |
| 1279 | |
| 1280 | #[test ] |
| 1281 | fn fat_line() { |
| 1282 | use crate::point; |
| 1283 | |
| 1284 | let c1 = QuadraticBezierSegment { |
| 1285 | from: point(1.0f32, 2.0), |
| 1286 | ctrl: point(1.0, 3.0), |
| 1287 | to: point(11.0, 12.0), |
| 1288 | }; |
| 1289 | |
| 1290 | let (l1, l2) = c1.fat_line(); |
| 1291 | |
| 1292 | for i in 0..100 { |
| 1293 | let t = i as f32 / 99.0; |
| 1294 | assert!(l1.signed_distance_to_point(&c1.sample(t)) >= -0.000001); |
| 1295 | assert!(l2.signed_distance_to_point(&c1.sample(t)) <= 0.000001); |
| 1296 | } |
| 1297 | } |
| 1298 | |
| 1299 | #[test ] |
| 1300 | fn is_linear() { |
| 1301 | let mut angle = 0.0; |
| 1302 | let center = Point::new(1000.0, -700.0); |
| 1303 | for _ in 0..100 { |
| 1304 | for i in 0..10 { |
| 1305 | let (sin, cos) = f64::sin_cos(angle); |
| 1306 | let endpoint = Vector::new(cos * 100.0, sin * 100.0); |
| 1307 | let curve = QuadraticBezierSegment { |
| 1308 | from: center - endpoint, |
| 1309 | ctrl: center + endpoint.lerp(-endpoint, i as f64 / 9.0), |
| 1310 | to: center + endpoint, |
| 1311 | }; |
| 1312 | |
| 1313 | assert!(curve.is_linear(1e-10)); |
| 1314 | } |
| 1315 | angle += 0.001; |
| 1316 | } |
| 1317 | } |
| 1318 | |
| 1319 | #[test ] |
| 1320 | fn test_flattening() { |
| 1321 | use crate::point; |
| 1322 | |
| 1323 | let c1 = QuadraticBezierSegment { |
| 1324 | from: point(0.0, 0.0), |
| 1325 | ctrl: point(5.0, 0.0), |
| 1326 | to: point(5.0, 5.0), |
| 1327 | }; |
| 1328 | |
| 1329 | let c2 = QuadraticBezierSegment { |
| 1330 | from: point(0.0, 0.0), |
| 1331 | ctrl: point(50.0, 0.0), |
| 1332 | to: point(50.0, 50.0), |
| 1333 | }; |
| 1334 | |
| 1335 | let c3 = QuadraticBezierSegment { |
| 1336 | from: point(0.0, 0.0), |
| 1337 | ctrl: point(100.0, 100.0), |
| 1338 | to: point(5.0, 0.0), |
| 1339 | }; |
| 1340 | |
| 1341 | fn check_tolerance(curve: &QuadraticBezierSegment<f64>, tolerance: f64) { |
| 1342 | let mut c = curve.clone(); |
| 1343 | loop { |
| 1344 | let t = c.flattening_step(tolerance); |
| 1345 | if t >= 1.0 { |
| 1346 | break; |
| 1347 | } |
| 1348 | let (before, after) = c.split(t); |
| 1349 | let mid_point = before.sample(0.5); |
| 1350 | let distance = before |
| 1351 | .baseline() |
| 1352 | .to_line() |
| 1353 | .equation() |
| 1354 | .distance_to_point(&mid_point); |
| 1355 | assert!(distance <= tolerance); |
| 1356 | c = after; |
| 1357 | } |
| 1358 | } |
| 1359 | |
| 1360 | check_tolerance(&c1, 1.0); |
| 1361 | check_tolerance(&c1, 0.1); |
| 1362 | check_tolerance(&c1, 0.01); |
| 1363 | check_tolerance(&c1, 0.001); |
| 1364 | check_tolerance(&c1, 0.0001); |
| 1365 | |
| 1366 | check_tolerance(&c2, 1.0); |
| 1367 | check_tolerance(&c2, 0.1); |
| 1368 | check_tolerance(&c2, 0.01); |
| 1369 | check_tolerance(&c2, 0.001); |
| 1370 | check_tolerance(&c2, 0.0001); |
| 1371 | |
| 1372 | check_tolerance(&c3, 1.0); |
| 1373 | check_tolerance(&c3, 0.1); |
| 1374 | check_tolerance(&c3, 0.01); |
| 1375 | check_tolerance(&c3, 0.001); |
| 1376 | check_tolerance(&c3, 0.0001); |
| 1377 | } |
| 1378 | |
| 1379 | #[test ] |
| 1380 | fn test_flattening_empty_curve() { |
| 1381 | use crate::point; |
| 1382 | |
| 1383 | let curve = QuadraticBezierSegment { |
| 1384 | from: point(0.0, 0.0), |
| 1385 | ctrl: point(0.0, 0.0), |
| 1386 | to: point(0.0, 0.0), |
| 1387 | }; |
| 1388 | |
| 1389 | let mut iter = FlattenedT::new(&curve, 0.1); |
| 1390 | |
| 1391 | assert_eq!(iter.next(), Some(1.0)); |
| 1392 | assert_eq!(iter.next(), None); |
| 1393 | |
| 1394 | let mut count: u32 = 0; |
| 1395 | curve.for_each_flattened(0.1, &mut |_| count += 1); |
| 1396 | assert_eq!(count, 1); |
| 1397 | } |
| 1398 | |
| 1399 | #[test ] |
| 1400 | fn test_flattening_straight_line() { |
| 1401 | use crate::point; |
| 1402 | |
| 1403 | let curve = QuadraticBezierSegment { |
| 1404 | from: point(0.0, 0.0), |
| 1405 | ctrl: point(10.0, 0.0), |
| 1406 | to: point(20.0, 0.0), |
| 1407 | }; |
| 1408 | |
| 1409 | let mut iter = FlattenedT::new(&curve, 0.1); |
| 1410 | |
| 1411 | assert_eq!(iter.next(), Some(1.0)); |
| 1412 | assert!(iter.next().is_none()); |
| 1413 | |
| 1414 | let mut count: u32 = 0; |
| 1415 | curve.for_each_flattened(0.1, &mut |_| count += 1); |
| 1416 | assert_eq!(count, 1); |
| 1417 | } |
| 1418 | |
| 1419 | #[test ] |
| 1420 | fn issue_678() { |
| 1421 | let points = [ |
| 1422 | [-7768.80859375f32, -35563.80859375], |
| 1423 | [-38463.125, -10941.41796875], |
| 1424 | [-21846.12890625, -13518.1953125], |
| 1425 | [-11727.439453125, -22080.33203125], |
| 1426 | ]; |
| 1427 | |
| 1428 | let quadratic = QuadraticBezierSegment { |
| 1429 | from: Point::new(points[0][0], points[0][1]), |
| 1430 | ctrl: Point::new(points[1][0], points[1][1]), |
| 1431 | to: Point::new(points[2][0], points[2][1]), |
| 1432 | }; |
| 1433 | |
| 1434 | let line = Line { |
| 1435 | point: Point::new(points[3][0], points[3][1]), |
| 1436 | vector: Vector::new(-0.5, -0.5).normalize(), |
| 1437 | }; |
| 1438 | |
| 1439 | let intersections = quadratic.line_intersections(&line); |
| 1440 | std::println!("{intersections:?}" ); |
| 1441 | |
| 1442 | assert_eq!(intersections.len(), 1); |
| 1443 | } |
| 1444 | |
| 1445 | #[test ] |
| 1446 | fn line_intersections_t() { |
| 1447 | let curve = QuadraticBezierSegment { |
| 1448 | from: point(0.0f64, 0.0), |
| 1449 | ctrl: point(100.0, 0.0), |
| 1450 | to: point(100.0, 500.0), |
| 1451 | }; |
| 1452 | let cubic = curve.to_cubic(); |
| 1453 | |
| 1454 | let line = Line { |
| 1455 | point: point(0.0, -50.0), |
| 1456 | vector: crate::vector(100.0, 500.0), |
| 1457 | }; |
| 1458 | |
| 1459 | let mut i1 = curve.line_intersections_t(&line); |
| 1460 | let mut i2 = curve.to_cubic().line_intersections_t(&line); |
| 1461 | |
| 1462 | use std::cmp::Ordering::{Equal, Greater, Less}; |
| 1463 | i1.sort_by(|a, b| { |
| 1464 | if a == b { |
| 1465 | Equal |
| 1466 | } else if a > b { |
| 1467 | Greater |
| 1468 | } else { |
| 1469 | Less |
| 1470 | } |
| 1471 | }); |
| 1472 | i2.sort_by(|a, b| { |
| 1473 | if a == b { |
| 1474 | Equal |
| 1475 | } else if a > b { |
| 1476 | Greater |
| 1477 | } else { |
| 1478 | Less |
| 1479 | } |
| 1480 | }); |
| 1481 | |
| 1482 | for (t1, t2) in i1.iter().zip(i2.iter()) { |
| 1483 | use euclid::approxeq::ApproxEq; |
| 1484 | let p1 = curve.sample(*t1); |
| 1485 | let p2 = cubic.sample(*t2); |
| 1486 | assert!(p1.approx_eq(&p2), "{:?} == {:?}" , p1, p2); |
| 1487 | } |
| 1488 | assert_eq!(i2.len(), 2); |
| 1489 | assert_eq!(i1.len(), 2); |
| 1490 | } |
| 1491 | |
| 1492 | #[test ] |
| 1493 | fn drag() { |
| 1494 | let curve = QuadraticBezierSegment { |
| 1495 | from: point(0.0f32, 0.0), |
| 1496 | ctrl: point(100.0, 0.0), |
| 1497 | to: point(100.0, 100.0), |
| 1498 | }; |
| 1499 | |
| 1500 | for t in [0.5, 0.25, 0.1, 0.4, 0.7] { |
| 1501 | let target = point(0.0, 10.0); |
| 1502 | |
| 1503 | let dragged = curve.drag(t, target); |
| 1504 | |
| 1505 | use euclid::approxeq::ApproxEq; |
| 1506 | let p1 = dragged.sample(t); |
| 1507 | assert!( |
| 1508 | p1.approx_eq_eps(&target, &point(0.001, 0.001)), |
| 1509 | "{:?} == {:?}" , |
| 1510 | p1, |
| 1511 | target |
| 1512 | ); |
| 1513 | } |
| 1514 | } |
| 1515 | |
| 1516 | #[test ] |
| 1517 | fn arc_length() { |
| 1518 | let curves = [ |
| 1519 | QuadraticBezierSegment { |
| 1520 | from: point(0.0f64, 0.0), |
| 1521 | ctrl: point(100.0, 0.0), |
| 1522 | to: point(0.0, 100.0), |
| 1523 | }, |
| 1524 | QuadraticBezierSegment { |
| 1525 | from: point(0.0, 0.0), |
| 1526 | ctrl: point(100.0, 0.0), |
| 1527 | to: point(200.0, 0.0), |
| 1528 | }, |
| 1529 | QuadraticBezierSegment { |
| 1530 | from: point(100.0, 0.0), |
| 1531 | ctrl: point(0.0, 0.0), |
| 1532 | to: point(50.0, 1.0), |
| 1533 | }, |
| 1534 | ]; |
| 1535 | |
| 1536 | for (idx, curve) in curves.iter().enumerate() { |
| 1537 | let length = curve.length(); |
| 1538 | let mut accum = 0.0; |
| 1539 | curve.for_each_flattened(0.00000001, &mut |line| { |
| 1540 | accum += line.length(); |
| 1541 | }); |
| 1542 | |
| 1543 | assert!( |
| 1544 | (length - accum).abs() < 0.00001, |
| 1545 | "curve {:?}, {:?} == {:?}" , |
| 1546 | idx, |
| 1547 | length, |
| 1548 | accum |
| 1549 | ); |
| 1550 | } |
| 1551 | } |
| 1552 | |