| 1 | use crate::cubic_bezier_intersections::cubic_bezier_intersections_t; |
| 2 | use crate::scalar::Scalar; |
| 3 | use crate::segment::{BoundingBox, Segment}; |
| 4 | use crate::traits::Transformation; |
| 5 | use crate::utils::{cubic_polynomial_roots, min_max}; |
| 6 | use crate::{point, Box2D, Point, Vector}; |
| 7 | use crate::{Line, LineEquation, LineSegment, QuadraticBezierSegment}; |
| 8 | use arrayvec::ArrayVec; |
| 9 | |
| 10 | use core::cmp::Ordering::{Equal, Greater, Less}; |
| 11 | use core::ops::Range; |
| 12 | |
| 13 | #[cfg (test)] |
| 14 | use std::vec::Vec; |
| 15 | |
| 16 | /// A 2d curve segment defined by four points: the beginning of the segment, two control |
| 17 | /// points and the end of the segment. |
| 18 | /// |
| 19 | /// The curve is defined by equation:² |
| 20 | /// ```∀ t ∈ [0..1], P(t) = (1 - t)³ * from + 3 * (1 - t)² * t * ctrl1 + 3 * t² * (1 - t) * ctrl2 + t³ * to``` |
| 21 | #[derive (Copy, Clone, Debug, PartialEq)] |
| 22 | #[cfg_attr (feature = "serialization" , derive(Serialize, Deserialize))] |
| 23 | pub struct CubicBezierSegment<S> { |
| 24 | pub from: Point<S>, |
| 25 | pub ctrl1: Point<S>, |
| 26 | pub ctrl2: Point<S>, |
| 27 | pub to: Point<S>, |
| 28 | } |
| 29 | |
| 30 | impl<S: Scalar> CubicBezierSegment<S> { |
| 31 | /// Sample the curve at t (expecting t between 0 and 1). |
| 32 | pub fn sample(&self, t: S) -> Point<S> { |
| 33 | let t2 = t * t; |
| 34 | let t3 = t2 * t; |
| 35 | let one_t = S::ONE - t; |
| 36 | let one_t2 = one_t * one_t; |
| 37 | let one_t3 = one_t2 * one_t; |
| 38 | |
| 39 | self.from * one_t3 |
| 40 | + self.ctrl1.to_vector() * S::THREE * one_t2 * t |
| 41 | + self.ctrl2.to_vector() * S::THREE * one_t * t2 |
| 42 | + self.to.to_vector() * t3 |
| 43 | } |
| 44 | |
| 45 | /// Sample the x coordinate of the curve at t (expecting t between 0 and 1). |
| 46 | pub fn x(&self, t: S) -> S { |
| 47 | let t2 = t * t; |
| 48 | let t3 = t2 * t; |
| 49 | let one_t = S::ONE - t; |
| 50 | let one_t2 = one_t * one_t; |
| 51 | let one_t3 = one_t2 * one_t; |
| 52 | |
| 53 | self.from.x * one_t3 |
| 54 | + self.ctrl1.x * S::THREE * one_t2 * t |
| 55 | + self.ctrl2.x * S::THREE * one_t * t2 |
| 56 | + self.to.x * t3 |
| 57 | } |
| 58 | |
| 59 | /// Sample the y coordinate of the curve at t (expecting t between 0 and 1). |
| 60 | pub fn y(&self, t: S) -> S { |
| 61 | let t2 = t * t; |
| 62 | let t3 = t2 * t; |
| 63 | let one_t = S::ONE - t; |
| 64 | let one_t2 = one_t * one_t; |
| 65 | let one_t3 = one_t2 * one_t; |
| 66 | |
| 67 | self.from.y * one_t3 |
| 68 | + self.ctrl1.y * S::THREE * one_t2 * t |
| 69 | + self.ctrl2.y * S::THREE * one_t * t2 |
| 70 | + self.to.y * t3 |
| 71 | } |
| 72 | |
| 73 | /// Return the parameter values corresponding to a given x coordinate. |
| 74 | pub fn solve_t_for_x(&self, x: S) -> ArrayVec<S, 3> { |
| 75 | let (min, max) = self.fast_bounding_range_x(); |
| 76 | if min > x || max < x { |
| 77 | return ArrayVec::new(); |
| 78 | } |
| 79 | |
| 80 | self.parameters_for_xy_value(x, self.from.x, self.ctrl1.x, self.ctrl2.x, self.to.x) |
| 81 | } |
| 82 | |
| 83 | /// Return the parameter values corresponding to a given y coordinate. |
| 84 | pub fn solve_t_for_y(&self, y: S) -> ArrayVec<S, 3> { |
| 85 | let (min, max) = self.fast_bounding_range_y(); |
| 86 | if min > y || max < y { |
| 87 | return ArrayVec::new(); |
| 88 | } |
| 89 | |
| 90 | self.parameters_for_xy_value(y, self.from.y, self.ctrl1.y, self.ctrl2.y, self.to.y) |
| 91 | } |
| 92 | |
| 93 | fn parameters_for_xy_value( |
| 94 | &self, |
| 95 | value: S, |
| 96 | from: S, |
| 97 | ctrl1: S, |
| 98 | ctrl2: S, |
| 99 | to: S, |
| 100 | ) -> ArrayVec<S, 3> { |
| 101 | let mut result = ArrayVec::new(); |
| 102 | |
| 103 | let a = -from + S::THREE * ctrl1 - S::THREE * ctrl2 + to; |
| 104 | let b = S::THREE * from - S::SIX * ctrl1 + S::THREE * ctrl2; |
| 105 | let c = -S::THREE * from + S::THREE * ctrl1; |
| 106 | let d = from - value; |
| 107 | |
| 108 | let roots = cubic_polynomial_roots(a, b, c, d); |
| 109 | for root in roots { |
| 110 | if root > S::ZERO && root < S::ONE { |
| 111 | result.push(root); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | result |
| 116 | } |
| 117 | |
| 118 | #[inline ] |
| 119 | fn derivative_coefficients(&self, t: S) -> (S, S, S, S) { |
| 120 | let t2 = t * t; |
| 121 | ( |
| 122 | -S::THREE * t2 + S::SIX * t - S::THREE, |
| 123 | S::NINE * t2 - S::value(12.0) * t + S::THREE, |
| 124 | -S::NINE * t2 + S::SIX * t, |
| 125 | S::THREE * t2, |
| 126 | ) |
| 127 | } |
| 128 | |
| 129 | /// Sample the curve's derivative at t (expecting t between 0 and 1). |
| 130 | pub fn derivative(&self, t: S) -> Vector<S> { |
| 131 | let (c0, c1, c2, c3) = self.derivative_coefficients(t); |
| 132 | self.from.to_vector() * c0 |
| 133 | + self.ctrl1.to_vector() * c1 |
| 134 | + self.ctrl2.to_vector() * c2 |
| 135 | + self.to.to_vector() * c3 |
| 136 | } |
| 137 | |
| 138 | /// Sample the x coordinate of the curve's derivative at t (expecting t between 0 and 1). |
| 139 | pub fn dx(&self, t: S) -> S { |
| 140 | let (c0, c1, c2, c3) = self.derivative_coefficients(t); |
| 141 | self.from.x * c0 + self.ctrl1.x * c1 + self.ctrl2.x * c2 + self.to.x * c3 |
| 142 | } |
| 143 | |
| 144 | /// Sample the y coordinate of the curve's derivative at t (expecting t between 0 and 1). |
| 145 | pub fn dy(&self, t: S) -> S { |
| 146 | let (c0, c1, c2, c3) = self.derivative_coefficients(t); |
| 147 | self.from.y * c0 + self.ctrl1.y * c1 + self.ctrl2.y * c2 + self.to.y * c3 |
| 148 | } |
| 149 | |
| 150 | /// Return the sub-curve inside a given range of t. |
| 151 | /// |
| 152 | /// This is equivalent to splitting at the range's end points. |
| 153 | pub fn split_range(&self, t_range: Range<S>) -> Self { |
| 154 | let (t0, t1) = (t_range.start, t_range.end); |
| 155 | let from = self.sample(t0); |
| 156 | let to = self.sample(t1); |
| 157 | |
| 158 | let d = QuadraticBezierSegment { |
| 159 | from: (self.ctrl1 - self.from).to_point(), |
| 160 | ctrl: (self.ctrl2 - self.ctrl1).to_point(), |
| 161 | to: (self.to - self.ctrl2).to_point(), |
| 162 | }; |
| 163 | |
| 164 | let dt = t1 - t0; |
| 165 | let ctrl1 = from + d.sample(t0).to_vector() * dt; |
| 166 | let ctrl2 = to - d.sample(t1).to_vector() * dt; |
| 167 | |
| 168 | CubicBezierSegment { |
| 169 | from, |
| 170 | ctrl1, |
| 171 | ctrl2, |
| 172 | to, |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | /// Split this curve into two sub-curves. |
| 177 | pub fn split(&self, t: S) -> (CubicBezierSegment<S>, CubicBezierSegment<S>) { |
| 178 | let ctrl1a = self.from + (self.ctrl1 - self.from) * t; |
| 179 | let ctrl2a = self.ctrl1 + (self.ctrl2 - self.ctrl1) * t; |
| 180 | let ctrl1aa = ctrl1a + (ctrl2a - ctrl1a) * t; |
| 181 | let ctrl3a = self.ctrl2 + (self.to - self.ctrl2) * t; |
| 182 | let ctrl2aa = ctrl2a + (ctrl3a - ctrl2a) * t; |
| 183 | let ctrl1aaa = ctrl1aa + (ctrl2aa - ctrl1aa) * t; |
| 184 | |
| 185 | ( |
| 186 | CubicBezierSegment { |
| 187 | from: self.from, |
| 188 | ctrl1: ctrl1a, |
| 189 | ctrl2: ctrl1aa, |
| 190 | to: ctrl1aaa, |
| 191 | }, |
| 192 | CubicBezierSegment { |
| 193 | from: ctrl1aaa, |
| 194 | ctrl1: ctrl2aa, |
| 195 | ctrl2: ctrl3a, |
| 196 | to: self.to, |
| 197 | }, |
| 198 | ) |
| 199 | } |
| 200 | |
| 201 | /// Return the curve before the split point. |
| 202 | pub fn before_split(&self, t: S) -> CubicBezierSegment<S> { |
| 203 | let ctrl1a = self.from + (self.ctrl1 - self.from) * t; |
| 204 | let ctrl2a = self.ctrl1 + (self.ctrl2 - self.ctrl1) * t; |
| 205 | let ctrl1aa = ctrl1a + (ctrl2a - ctrl1a) * t; |
| 206 | let ctrl3a = self.ctrl2 + (self.to - self.ctrl2) * t; |
| 207 | let ctrl2aa = ctrl2a + (ctrl3a - ctrl2a) * t; |
| 208 | let ctrl1aaa = ctrl1aa + (ctrl2aa - ctrl1aa) * t; |
| 209 | |
| 210 | CubicBezierSegment { |
| 211 | from: self.from, |
| 212 | ctrl1: ctrl1a, |
| 213 | ctrl2: ctrl1aa, |
| 214 | to: ctrl1aaa, |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | /// Return the curve after the split point. |
| 219 | pub fn after_split(&self, t: S) -> CubicBezierSegment<S> { |
| 220 | let ctrl1a = self.from + (self.ctrl1 - self.from) * t; |
| 221 | let ctrl2a = self.ctrl1 + (self.ctrl2 - self.ctrl1) * t; |
| 222 | let ctrl1aa = ctrl1a + (ctrl2a - ctrl1a) * t; |
| 223 | let ctrl3a = self.ctrl2 + (self.to - self.ctrl2) * t; |
| 224 | let ctrl2aa = ctrl2a + (ctrl3a - ctrl2a) * t; |
| 225 | |
| 226 | CubicBezierSegment { |
| 227 | from: ctrl1aa + (ctrl2aa - ctrl1aa) * t, |
| 228 | ctrl1: ctrl2a + (ctrl3a - ctrl2a) * t, |
| 229 | ctrl2: ctrl3a, |
| 230 | to: self.to, |
| 231 | } |
| 232 | } |
| 233 | |
| 234 | #[inline ] |
| 235 | pub fn baseline(&self) -> LineSegment<S> { |
| 236 | LineSegment { |
| 237 | from: self.from, |
| 238 | to: self.to, |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | /// Returns true if the curve can be approximated with a single line segment, given |
| 243 | /// a tolerance threshold. |
| 244 | pub fn is_linear(&self, tolerance: S) -> bool { |
| 245 | // Similar to Line::square_distance_to_point, except we keep |
| 246 | // the sign of c1 and c2 to compute tighter upper bounds as we |
| 247 | // do in fat_line_min_max. |
| 248 | let baseline = self.to - self.from; |
| 249 | let v1 = self.ctrl1 - self.from; |
| 250 | let v2 = self.ctrl2 - self.from; |
| 251 | let c1 = baseline.cross(v1); |
| 252 | let c2 = baseline.cross(v2); |
| 253 | // TODO: it would be faster to multiply the threshold with baseline_len2 |
| 254 | // instead of dividing d1 and d2, but it changes the behavior when the |
| 255 | // baseline length is zero in ways that breaks some of the cubic intersection |
| 256 | // tests. |
| 257 | let inv_baseline_len2 = S::ONE / baseline.square_length(); |
| 258 | let d1 = (c1 * c1) * inv_baseline_len2; |
| 259 | let d2 = (c2 * c2) * inv_baseline_len2; |
| 260 | |
| 261 | let factor = if (c1 * c2) > S::ZERO { |
| 262 | S::THREE / S::FOUR |
| 263 | } else { |
| 264 | S::FOUR / S::NINE |
| 265 | }; |
| 266 | |
| 267 | let f2 = factor * factor; |
| 268 | let threshold = tolerance * tolerance; |
| 269 | |
| 270 | d1 * f2 <= threshold && d2 * f2 <= threshold |
| 271 | } |
| 272 | |
| 273 | /// Returns whether the curve can be approximated with a single point, given |
| 274 | /// a tolerance threshold. |
| 275 | pub(crate) fn is_a_point(&self, tolerance: S) -> bool { |
| 276 | let tolerance_squared = tolerance * tolerance; |
| 277 | // Use <= so that tolerance can be zero. |
| 278 | (self.from - self.to).square_length() <= tolerance_squared |
| 279 | && (self.from - self.ctrl1).square_length() <= tolerance_squared |
| 280 | && (self.to - self.ctrl2).square_length() <= tolerance_squared |
| 281 | } |
| 282 | |
| 283 | /// Computes the signed distances (min <= 0 and max >= 0) from the baseline of this |
| 284 | /// curve to its two "fat line" boundary lines. |
| 285 | /// |
| 286 | /// A fat line is two conservative lines between which the segment |
| 287 | /// is fully contained. |
| 288 | pub(crate) fn fat_line_min_max(&self) -> (S, S) { |
| 289 | let baseline = self.baseline().to_line().equation(); |
| 290 | let (d1, d2) = min_max( |
| 291 | baseline.signed_distance_to_point(&self.ctrl1), |
| 292 | baseline.signed_distance_to_point(&self.ctrl2), |
| 293 | ); |
| 294 | |
| 295 | let factor = if (d1 * d2) > S::ZERO { |
| 296 | S::THREE / S::FOUR |
| 297 | } else { |
| 298 | S::FOUR / S::NINE |
| 299 | }; |
| 300 | |
| 301 | let d_min = factor * S::min(d1, S::ZERO); |
| 302 | let d_max = factor * S::max(d2, S::ZERO); |
| 303 | |
| 304 | (d_min, d_max) |
| 305 | } |
| 306 | |
| 307 | /// Computes a "fat line" of this segment. |
| 308 | /// |
| 309 | /// A fat line is two conservative lines between which the segment |
| 310 | /// is fully contained. |
| 311 | pub fn fat_line(&self) -> (LineEquation<S>, LineEquation<S>) { |
| 312 | let baseline = self.baseline().to_line().equation(); |
| 313 | let (d1, d2) = self.fat_line_min_max(); |
| 314 | |
| 315 | (baseline.offset(d1), baseline.offset(d2)) |
| 316 | } |
| 317 | |
| 318 | /// Applies the transform to this curve and returns the results. |
| 319 | #[inline ] |
| 320 | pub fn transformed<T: Transformation<S>>(&self, transform: &T) -> Self { |
| 321 | CubicBezierSegment { |
| 322 | from: transform.transform_point(self.from), |
| 323 | ctrl1: transform.transform_point(self.ctrl1), |
| 324 | ctrl2: transform.transform_point(self.ctrl2), |
| 325 | to: transform.transform_point(self.to), |
| 326 | } |
| 327 | } |
| 328 | |
| 329 | /// Swap the beginning and the end of the segment. |
| 330 | pub fn flip(&self) -> Self { |
| 331 | CubicBezierSegment { |
| 332 | from: self.to, |
| 333 | ctrl1: self.ctrl2, |
| 334 | ctrl2: self.ctrl1, |
| 335 | to: self.from, |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | /// Approximate the curve with a single quadratic bézier segment. |
| 340 | /// |
| 341 | /// This is terrible as a general approximation but works if the cubic |
| 342 | /// curve does not have inflection points and is "flat" enough. Typically |
| 343 | /// usable after subdividing the curve a few times. |
| 344 | pub fn to_quadratic(&self) -> QuadraticBezierSegment<S> { |
| 345 | let c1 = (self.ctrl1 * S::THREE - self.from) * S::HALF; |
| 346 | let c2 = (self.ctrl2 * S::THREE - self.to) * S::HALF; |
| 347 | QuadraticBezierSegment { |
| 348 | from: self.from, |
| 349 | ctrl: ((c1 + c2) * S::HALF).to_point(), |
| 350 | to: self.to, |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | /// Evaluates an upper bound on the maximum distance between the curve |
| 355 | /// and its quadratic approximation obtained using `to_quadratic`. |
| 356 | pub fn to_quadratic_error(&self) -> S { |
| 357 | // See http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html |
| 358 | S::sqrt(S::THREE) / S::value(36.0) |
| 359 | * ((self.to - self.ctrl2 * S::THREE) + (self.ctrl1 * S::THREE - self.from)).length() |
| 360 | } |
| 361 | |
| 362 | /// Returns true if the curve can be safely approximated with a single quadratic bézier |
| 363 | /// segment given the provided tolerance threshold. |
| 364 | /// |
| 365 | /// Equivalent to comparing `to_quadratic_error` with the tolerance threshold, avoiding |
| 366 | /// the cost of two square roots. |
| 367 | pub fn is_quadratic(&self, tolerance: S) -> bool { |
| 368 | S::THREE / S::value(1296.0) |
| 369 | * ((self.to - self.ctrl2 * S::THREE) + (self.ctrl1 * S::THREE - self.from)) |
| 370 | .square_length() |
| 371 | <= tolerance * tolerance |
| 372 | } |
| 373 | |
| 374 | /// Computes the number of quadratic bézier segments required to approximate this cubic curve |
| 375 | /// given a tolerance threshold. |
| 376 | /// |
| 377 | /// Derived by Raph Levien from section 10.6 of Sedeberg's CAGD notes |
| 378 | /// <https://scholarsarchive.byu.edu/cgi/viewcontent.cgi?article=1000&context=facpub#section.10.6> |
| 379 | /// and the error metric from the caffein owl blog post <http://caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html> |
| 380 | pub fn num_quadratics(&self, tolerance: S) -> u32 { |
| 381 | self.num_quadratics_impl(tolerance).to_u32().unwrap_or(1) |
| 382 | } |
| 383 | |
| 384 | fn num_quadratics_impl(&self, tolerance: S) -> S { |
| 385 | debug_assert!(tolerance > S::ZERO); |
| 386 | |
| 387 | let x = self.from.x - S::THREE * self.ctrl1.x + S::THREE * self.ctrl2.x - self.to.x; |
| 388 | let y = self.from.y - S::THREE * self.ctrl1.y + S::THREE * self.ctrl2.y - self.to.y; |
| 389 | |
| 390 | let err = x * x + y * y; |
| 391 | |
| 392 | (err / (S::value(432.0) * tolerance * tolerance)) |
| 393 | .powf(S::ONE / S::SIX) |
| 394 | .ceil() |
| 395 | .max(S::ONE) |
| 396 | } |
| 397 | |
| 398 | /// Returns the flattened representation of the curve as an iterator, starting *after* the |
| 399 | /// current point. |
| 400 | pub fn flattened(&self, tolerance: S) -> Flattened<S> { |
| 401 | Flattened::new(self, tolerance) |
| 402 | } |
| 403 | |
| 404 | /// Invokes a callback for each monotonic part of the segment. |
| 405 | pub fn for_each_monotonic_range<F>(&self, cb: &mut F) |
| 406 | where |
| 407 | F: FnMut(Range<S>), |
| 408 | { |
| 409 | let mut extrema: ArrayVec<S, 4> = ArrayVec::new(); |
| 410 | self.for_each_local_x_extremum_t(&mut |t| extrema.push(t)); |
| 411 | self.for_each_local_y_extremum_t(&mut |t| extrema.push(t)); |
| 412 | extrema.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap()); |
| 413 | |
| 414 | let mut t0 = S::ZERO; |
| 415 | for &t in &extrema { |
| 416 | if t != t0 { |
| 417 | cb(t0..t); |
| 418 | t0 = t; |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | cb(t0..S::ONE); |
| 423 | } |
| 424 | |
| 425 | /// Invokes a callback for each monotonic part of the segment. |
| 426 | pub fn for_each_monotonic<F>(&self, cb: &mut F) |
| 427 | where |
| 428 | F: FnMut(&CubicBezierSegment<S>), |
| 429 | { |
| 430 | self.for_each_monotonic_range(&mut |range| { |
| 431 | let mut sub = self.split_range(range); |
| 432 | // Due to finite precision the split may actually result in sub-curves |
| 433 | // that are almost but not-quite monotonic. Make sure they actually are. |
| 434 | let min_x = sub.from.x.min(sub.to.x); |
| 435 | let max_x = sub.from.x.max(sub.to.x); |
| 436 | let min_y = sub.from.y.min(sub.to.y); |
| 437 | let max_y = sub.from.y.max(sub.to.y); |
| 438 | sub.ctrl1.x = sub.ctrl1.x.max(min_x).min(max_x); |
| 439 | sub.ctrl1.y = sub.ctrl1.y.max(min_y).min(max_y); |
| 440 | sub.ctrl2.x = sub.ctrl2.x.max(min_x).min(max_x); |
| 441 | sub.ctrl2.y = sub.ctrl2.y.max(min_y).min(max_y); |
| 442 | cb(&sub); |
| 443 | }); |
| 444 | } |
| 445 | |
| 446 | /// Invokes a callback for each y-monotonic part of the segment. |
| 447 | pub fn for_each_y_monotonic_range<F>(&self, cb: &mut F) |
| 448 | where |
| 449 | F: FnMut(Range<S>), |
| 450 | { |
| 451 | let mut t0 = S::ZERO; |
| 452 | self.for_each_local_y_extremum_t(&mut |t| { |
| 453 | cb(t0..t); |
| 454 | t0 = t; |
| 455 | }); |
| 456 | |
| 457 | cb(t0..S::ONE); |
| 458 | } |
| 459 | |
| 460 | /// Invokes a callback for each y-monotonic part of the segment. |
| 461 | pub fn for_each_y_monotonic<F>(&self, cb: &mut F) |
| 462 | where |
| 463 | F: FnMut(&CubicBezierSegment<S>), |
| 464 | { |
| 465 | self.for_each_y_monotonic_range(&mut |range| { |
| 466 | let mut sub = self.split_range(range); |
| 467 | // Due to finite precision the split may actually result in sub-curves |
| 468 | // that are almost but not-quite monotonic. Make sure they actually are. |
| 469 | let min_y = sub.from.y.min(sub.to.y); |
| 470 | let max_y = sub.from.y.max(sub.to.y); |
| 471 | sub.ctrl1.y = sub.ctrl1.y.max(min_y).min(max_y); |
| 472 | sub.ctrl2.y = sub.ctrl2.y.max(min_y).min(max_y); |
| 473 | cb(&sub); |
| 474 | }); |
| 475 | } |
| 476 | |
| 477 | /// Invokes a callback for each x-monotonic part of the segment. |
| 478 | pub fn for_each_x_monotonic_range<F>(&self, cb: &mut F) |
| 479 | where |
| 480 | F: FnMut(Range<S>), |
| 481 | { |
| 482 | let mut t0 = S::ZERO; |
| 483 | self.for_each_local_x_extremum_t(&mut |t| { |
| 484 | cb(t0..t); |
| 485 | t0 = t; |
| 486 | }); |
| 487 | |
| 488 | cb(t0..S::ONE); |
| 489 | } |
| 490 | |
| 491 | /// Invokes a callback for each x-monotonic part of the segment. |
| 492 | pub fn for_each_x_monotonic<F>(&self, cb: &mut F) |
| 493 | where |
| 494 | F: FnMut(&CubicBezierSegment<S>), |
| 495 | { |
| 496 | self.for_each_x_monotonic_range(&mut |range| { |
| 497 | let mut sub = self.split_range(range); |
| 498 | // Due to finite precision the split may actually result in sub-curves |
| 499 | // that are almost but not-quite monotonic. Make sure they actually are. |
| 500 | let min_x = sub.from.x.min(sub.to.x); |
| 501 | let max_x = sub.from.x.max(sub.to.x); |
| 502 | sub.ctrl1.x = sub.ctrl1.x.max(min_x).min(max_x); |
| 503 | sub.ctrl2.x = sub.ctrl2.x.max(min_x).min(max_x); |
| 504 | cb(&sub); |
| 505 | }); |
| 506 | } |
| 507 | |
| 508 | /// Approximates the cubic bézier curve with sequence of quadratic ones, |
| 509 | /// invoking a callback at each step. |
| 510 | pub fn for_each_quadratic_bezier<F>(&self, tolerance: S, cb: &mut F) |
| 511 | where |
| 512 | F: FnMut(&QuadraticBezierSegment<S>), |
| 513 | { |
| 514 | self.for_each_quadratic_bezier_with_t(tolerance, &mut |quad, _range| cb(quad)); |
| 515 | } |
| 516 | |
| 517 | /// Approximates the cubic bézier curve with sequence of quadratic ones, |
| 518 | /// invoking a callback at each step. |
| 519 | pub fn for_each_quadratic_bezier_with_t<F>(&self, tolerance: S, cb: &mut F) |
| 520 | where |
| 521 | F: FnMut(&QuadraticBezierSegment<S>, Range<S>), |
| 522 | { |
| 523 | debug_assert!(tolerance >= S::EPSILON * S::EPSILON); |
| 524 | |
| 525 | let num_quadratics = self.num_quadratics_impl(tolerance); |
| 526 | let step = S::ONE / num_quadratics; |
| 527 | let n = num_quadratics.to_u32().unwrap_or(1); |
| 528 | let mut t0 = S::ZERO; |
| 529 | for _ in 0..(n - 1) { |
| 530 | let t1 = t0 + step; |
| 531 | |
| 532 | let quad = self.split_range(t0..t1).to_quadratic(); |
| 533 | cb(&quad, t0..t1); |
| 534 | |
| 535 | t0 = t1; |
| 536 | } |
| 537 | |
| 538 | // Do the last step manually to make sure we finish at t = 1.0 exactly. |
| 539 | let quad = self.split_range(t0..S::ONE).to_quadratic(); |
| 540 | cb(&quad, t0..S::ONE) |
| 541 | } |
| 542 | |
| 543 | /// Approximates the curve with sequence of line segments. |
| 544 | /// |
| 545 | /// The `tolerance` parameter defines the maximum distance between the curve and |
| 546 | /// its approximation. |
| 547 | pub fn for_each_flattened<F: FnMut(&LineSegment<S>)>(&self, tolerance: S, callback: &mut F) { |
| 548 | debug_assert!(tolerance >= S::EPSILON * S::EPSILON); |
| 549 | let quadratics_tolerance = tolerance * S::value(0.4); |
| 550 | let flattening_tolerance = tolerance * S::value(0.8); |
| 551 | |
| 552 | self.for_each_quadratic_bezier(quadratics_tolerance, &mut |quad| { |
| 553 | quad.for_each_flattened(flattening_tolerance, &mut |segment| { |
| 554 | callback(segment); |
| 555 | }); |
| 556 | }); |
| 557 | } |
| 558 | |
| 559 | /// Approximates the curve with sequence of line segments. |
| 560 | /// |
| 561 | /// The `tolerance` parameter defines the maximum distance between the curve and |
| 562 | /// its approximation. |
| 563 | /// |
| 564 | /// The end of the t parameter range at the final segment is guaranteed to be equal to `1.0`. |
| 565 | pub fn for_each_flattened_with_t<F: FnMut(&LineSegment<S>, Range<S>)>( |
| 566 | &self, |
| 567 | tolerance: S, |
| 568 | callback: &mut F, |
| 569 | ) { |
| 570 | debug_assert!(tolerance >= S::EPSILON * S::EPSILON); |
| 571 | let quadratics_tolerance = tolerance * S::value(0.4); |
| 572 | let flattening_tolerance = tolerance * S::value(0.8); |
| 573 | |
| 574 | let mut t_from = S::ZERO; |
| 575 | self.for_each_quadratic_bezier_with_t(quadratics_tolerance, &mut |quad, range| { |
| 576 | let last_quad = range.end == S::ONE; |
| 577 | let range_len = range.end - range.start; |
| 578 | quad.for_each_flattened_with_t(flattening_tolerance, &mut |segment, range_sub| { |
| 579 | let last_seg = range_sub.end == S::ONE; |
| 580 | let t = if last_quad && last_seg { |
| 581 | S::ONE |
| 582 | } else { |
| 583 | range_sub.end * range_len + range.start |
| 584 | }; |
| 585 | callback(segment, t_from..t); |
| 586 | t_from = t; |
| 587 | }); |
| 588 | }); |
| 589 | } |
| 590 | |
| 591 | /// Compute the length of the segment using a flattened approximation. |
| 592 | pub fn approximate_length(&self, tolerance: S) -> S { |
| 593 | let mut length = S::ZERO; |
| 594 | |
| 595 | self.for_each_quadratic_bezier(tolerance, &mut |quad| { |
| 596 | length += quad.length(); |
| 597 | }); |
| 598 | |
| 599 | length |
| 600 | } |
| 601 | |
| 602 | /// Invokes a callback at each inflection point if any. |
| 603 | pub fn for_each_inflection_t<F>(&self, cb: &mut F) |
| 604 | where |
| 605 | F: FnMut(S), |
| 606 | { |
| 607 | // Find inflection points. |
| 608 | // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation |
| 609 | // of this approach. |
| 610 | let pa = self.ctrl1 - self.from; |
| 611 | let pb = self.ctrl2.to_vector() - (self.ctrl1.to_vector() * S::TWO) + self.from.to_vector(); |
| 612 | let pc = self.to.to_vector() - (self.ctrl2.to_vector() * S::THREE) |
| 613 | + (self.ctrl1.to_vector() * S::THREE) |
| 614 | - self.from.to_vector(); |
| 615 | |
| 616 | let a = pb.cross(pc); |
| 617 | let b = pa.cross(pc); |
| 618 | let c = pa.cross(pb); |
| 619 | |
| 620 | if S::abs(a) < S::EPSILON { |
| 621 | // Not a quadratic equation. |
| 622 | if S::abs(b) < S::EPSILON { |
| 623 | // Instead of a linear acceleration change we have a constant |
| 624 | // acceleration change. This means the equation has no solution |
| 625 | // and there are no inflection points, unless the constant is 0. |
| 626 | // In that case the curve is a straight line, essentially that means |
| 627 | // the easiest way to deal with is is by saying there's an inflection |
| 628 | // point at t == 0. The inflection point approximation range found will |
| 629 | // automatically extend into infinity. |
| 630 | if S::abs(c) < S::EPSILON { |
| 631 | cb(S::ZERO); |
| 632 | } |
| 633 | } else { |
| 634 | let t = -c / b; |
| 635 | if in_range(t) { |
| 636 | cb(t); |
| 637 | } |
| 638 | } |
| 639 | |
| 640 | return; |
| 641 | } |
| 642 | |
| 643 | fn in_range<S: Scalar>(t: S) -> bool { |
| 644 | t >= S::ZERO && t < S::ONE |
| 645 | } |
| 646 | |
| 647 | let discriminant = b * b - S::FOUR * a * c; |
| 648 | |
| 649 | if discriminant < S::ZERO { |
| 650 | return; |
| 651 | } |
| 652 | |
| 653 | if discriminant < S::EPSILON { |
| 654 | let t = -b / (S::TWO * a); |
| 655 | |
| 656 | if in_range(t) { |
| 657 | cb(t); |
| 658 | } |
| 659 | |
| 660 | return; |
| 661 | } |
| 662 | |
| 663 | // This code is derived from https://www2.units.it/ipl/students_area/imm2/files/Numerical_Recipes.pdf page 184. |
| 664 | // Computing the roots this way avoids precision issues when a, c or both are small. |
| 665 | let discriminant_sqrt = S::sqrt(discriminant); |
| 666 | let sign_b = if b >= S::ZERO { S::ONE } else { -S::ONE }; |
| 667 | let q = -S::HALF * (b + sign_b * discriminant_sqrt); |
| 668 | let mut first_inflection = q / a; |
| 669 | let mut second_inflection = c / q; |
| 670 | |
| 671 | if first_inflection > second_inflection { |
| 672 | core::mem::swap(&mut first_inflection, &mut second_inflection); |
| 673 | } |
| 674 | |
| 675 | if in_range(first_inflection) { |
| 676 | cb(first_inflection); |
| 677 | } |
| 678 | |
| 679 | if in_range(second_inflection) { |
| 680 | cb(second_inflection); |
| 681 | } |
| 682 | } |
| 683 | |
| 684 | /// Return local x extrema or None if this curve is monotonic. |
| 685 | /// |
| 686 | /// This returns the advancements along the curve, not the actual x position. |
| 687 | pub fn for_each_local_x_extremum_t<F>(&self, cb: &mut F) |
| 688 | where |
| 689 | F: FnMut(S), |
| 690 | { |
| 691 | Self::for_each_local_extremum(self.from.x, self.ctrl1.x, self.ctrl2.x, self.to.x, cb) |
| 692 | } |
| 693 | |
| 694 | /// Return local y extrema or None if this curve is monotonic. |
| 695 | /// |
| 696 | /// This returns the advancements along the curve, not the actual y position. |
| 697 | pub fn for_each_local_y_extremum_t<F>(&self, cb: &mut F) |
| 698 | where |
| 699 | F: FnMut(S), |
| 700 | { |
| 701 | Self::for_each_local_extremum(self.from.y, self.ctrl1.y, self.ctrl2.y, self.to.y, cb) |
| 702 | } |
| 703 | |
| 704 | fn for_each_local_extremum<F>(p0: S, p1: S, p2: S, p3: S, cb: &mut F) |
| 705 | where |
| 706 | F: FnMut(S), |
| 707 | { |
| 708 | // See www.faculty.idc.ac.il/arik/quality/appendixa.html for an explanation |
| 709 | // The derivative of a cubic bezier curve is a curve representing a second degree polynomial function |
| 710 | // f(x) = a * x² + b * x + c such as : |
| 711 | |
| 712 | let a = S::THREE * (p3 + S::THREE * (p1 - p2) - p0); |
| 713 | let b = S::SIX * (p2 - S::TWO * p1 + p0); |
| 714 | let c = S::THREE * (p1 - p0); |
| 715 | |
| 716 | fn in_range<S: Scalar>(t: S) -> bool { |
| 717 | t > S::ZERO && t < S::ONE |
| 718 | } |
| 719 | |
| 720 | // If the derivative is a linear function |
| 721 | if a == S::ZERO { |
| 722 | if b != S::ZERO { |
| 723 | let t = -c / b; |
| 724 | if in_range(t) { |
| 725 | cb(t); |
| 726 | } |
| 727 | } |
| 728 | return; |
| 729 | } |
| 730 | |
| 731 | let discriminant = b * b - S::FOUR * a * c; |
| 732 | |
| 733 | // There is no Real solution for the equation |
| 734 | if discriminant < S::ZERO { |
| 735 | return; |
| 736 | } |
| 737 | |
| 738 | // There is one Real solution for the equation |
| 739 | if discriminant == S::ZERO { |
| 740 | let t = -b / (S::TWO * a); |
| 741 | if in_range(t) { |
| 742 | cb(t); |
| 743 | } |
| 744 | return; |
| 745 | } |
| 746 | |
| 747 | // There are two Real solutions for the equation |
| 748 | let discriminant_sqrt = discriminant.sqrt(); |
| 749 | |
| 750 | let mut first_extremum = (-b - discriminant_sqrt) / (S::TWO * a); |
| 751 | let mut second_extremum = (-b + discriminant_sqrt) / (S::TWO * a); |
| 752 | if first_extremum > second_extremum { |
| 753 | core::mem::swap(&mut first_extremum, &mut second_extremum); |
| 754 | } |
| 755 | |
| 756 | if in_range(first_extremum) { |
| 757 | cb(first_extremum); |
| 758 | } |
| 759 | |
| 760 | if in_range(second_extremum) { |
| 761 | cb(second_extremum); |
| 762 | } |
| 763 | } |
| 764 | |
| 765 | /// Find the advancement of the y-most position in the curve. |
| 766 | /// |
| 767 | /// This returns the advancement along the curve, not the actual y position. |
| 768 | pub fn y_maximum_t(&self) -> S { |
| 769 | let mut max_t = S::ZERO; |
| 770 | let mut max_y = self.from.y; |
| 771 | if self.to.y > max_y { |
| 772 | max_t = S::ONE; |
| 773 | max_y = self.to.y; |
| 774 | } |
| 775 | self.for_each_local_y_extremum_t(&mut |t| { |
| 776 | let y = self.y(t); |
| 777 | if y > max_y { |
| 778 | max_t = t; |
| 779 | max_y = y; |
| 780 | } |
| 781 | }); |
| 782 | |
| 783 | max_t |
| 784 | } |
| 785 | |
| 786 | /// Find the advancement of the y-least position in the curve. |
| 787 | /// |
| 788 | /// This returns the advancement along the curve, not the actual y position. |
| 789 | pub fn y_minimum_t(&self) -> S { |
| 790 | let mut min_t = S::ZERO; |
| 791 | let mut min_y = self.from.y; |
| 792 | if self.to.y < min_y { |
| 793 | min_t = S::ONE; |
| 794 | min_y = self.to.y; |
| 795 | } |
| 796 | self.for_each_local_y_extremum_t(&mut |t| { |
| 797 | let y = self.y(t); |
| 798 | if y < min_y { |
| 799 | min_t = t; |
| 800 | min_y = y; |
| 801 | } |
| 802 | }); |
| 803 | |
| 804 | min_t |
| 805 | } |
| 806 | |
| 807 | /// Find the advancement of the x-most position in the curve. |
| 808 | /// |
| 809 | /// This returns the advancement along the curve, not the actual x position. |
| 810 | pub fn x_maximum_t(&self) -> S { |
| 811 | let mut max_t = S::ZERO; |
| 812 | let mut max_x = self.from.x; |
| 813 | if self.to.x > max_x { |
| 814 | max_t = S::ONE; |
| 815 | max_x = self.to.x; |
| 816 | } |
| 817 | self.for_each_local_x_extremum_t(&mut |t| { |
| 818 | let x = self.x(t); |
| 819 | if x > max_x { |
| 820 | max_t = t; |
| 821 | max_x = x; |
| 822 | } |
| 823 | }); |
| 824 | |
| 825 | max_t |
| 826 | } |
| 827 | |
| 828 | /// Find the x-least position in the curve. |
| 829 | pub fn x_minimum_t(&self) -> S { |
| 830 | let mut min_t = S::ZERO; |
| 831 | let mut min_x = self.from.x; |
| 832 | if self.to.x < min_x { |
| 833 | min_t = S::ONE; |
| 834 | min_x = self.to.x; |
| 835 | } |
| 836 | self.for_each_local_x_extremum_t(&mut |t| { |
| 837 | let x = self.x(t); |
| 838 | if x < min_x { |
| 839 | min_t = t; |
| 840 | min_x = x; |
| 841 | } |
| 842 | }); |
| 843 | |
| 844 | min_t |
| 845 | } |
| 846 | |
| 847 | /// Returns a conservative rectangle the curve is contained in. |
| 848 | /// |
| 849 | /// This method is faster than `bounding_box` but more conservative. |
| 850 | pub fn fast_bounding_box(&self) -> Box2D<S> { |
| 851 | let (min_x, max_x) = self.fast_bounding_range_x(); |
| 852 | let (min_y, max_y) = self.fast_bounding_range_y(); |
| 853 | |
| 854 | Box2D { |
| 855 | min: point(min_x, min_y), |
| 856 | max: point(max_x, max_y), |
| 857 | } |
| 858 | } |
| 859 | |
| 860 | /// Returns a conservative range of x that contains this curve. |
| 861 | #[inline ] |
| 862 | pub fn fast_bounding_range_x(&self) -> (S, S) { |
| 863 | let min_x = self |
| 864 | .from |
| 865 | .x |
| 866 | .min(self.ctrl1.x) |
| 867 | .min(self.ctrl2.x) |
| 868 | .min(self.to.x); |
| 869 | let max_x = self |
| 870 | .from |
| 871 | .x |
| 872 | .max(self.ctrl1.x) |
| 873 | .max(self.ctrl2.x) |
| 874 | .max(self.to.x); |
| 875 | |
| 876 | (min_x, max_x) |
| 877 | } |
| 878 | |
| 879 | /// Returns a conservative range of y that contains this curve. |
| 880 | #[inline ] |
| 881 | pub fn fast_bounding_range_y(&self) -> (S, S) { |
| 882 | let min_y = self |
| 883 | .from |
| 884 | .y |
| 885 | .min(self.ctrl1.y) |
| 886 | .min(self.ctrl2.y) |
| 887 | .min(self.to.y); |
| 888 | let max_y = self |
| 889 | .from |
| 890 | .y |
| 891 | .max(self.ctrl1.y) |
| 892 | .max(self.ctrl2.y) |
| 893 | .max(self.to.y); |
| 894 | |
| 895 | (min_y, max_y) |
| 896 | } |
| 897 | |
| 898 | /// Returns a conservative rectangle that contains the curve. |
| 899 | #[inline ] |
| 900 | pub fn bounding_box(&self) -> Box2D<S> { |
| 901 | let (min_x, max_x) = self.bounding_range_x(); |
| 902 | let (min_y, max_y) = self.bounding_range_y(); |
| 903 | |
| 904 | Box2D { |
| 905 | min: point(min_x, min_y), |
| 906 | max: point(max_x, max_y), |
| 907 | } |
| 908 | } |
| 909 | |
| 910 | /// Returns the smallest range of x that contains this curve. |
| 911 | #[inline ] |
| 912 | pub fn bounding_range_x(&self) -> (S, S) { |
| 913 | let min_x = self.x(self.x_minimum_t()); |
| 914 | let max_x = self.x(self.x_maximum_t()); |
| 915 | |
| 916 | (min_x, max_x) |
| 917 | } |
| 918 | |
| 919 | /// Returns the smallest range of y that contains this curve. |
| 920 | #[inline ] |
| 921 | pub fn bounding_range_y(&self) -> (S, S) { |
| 922 | let min_y = self.y(self.y_minimum_t()); |
| 923 | let max_y = self.y(self.y_maximum_t()); |
| 924 | |
| 925 | (min_y, max_y) |
| 926 | } |
| 927 | |
| 928 | /// Returns whether this segment is monotonic on the x axis. |
| 929 | pub fn is_x_monotonic(&self) -> bool { |
| 930 | let mut found = false; |
| 931 | self.for_each_local_x_extremum_t(&mut |_| { |
| 932 | found = true; |
| 933 | }); |
| 934 | !found |
| 935 | } |
| 936 | |
| 937 | /// Returns whether this segment is monotonic on the y axis. |
| 938 | pub fn is_y_monotonic(&self) -> bool { |
| 939 | let mut found = false; |
| 940 | self.for_each_local_y_extremum_t(&mut |_| { |
| 941 | found = true; |
| 942 | }); |
| 943 | !found |
| 944 | } |
| 945 | |
| 946 | /// Returns whether this segment is fully monotonic. |
| 947 | pub fn is_monotonic(&self) -> bool { |
| 948 | self.is_x_monotonic() && self.is_y_monotonic() |
| 949 | } |
| 950 | |
| 951 | /// Computes the intersections (if any) between this segment and another one. |
| 952 | /// |
| 953 | /// The result is provided in the form of the `t` parameters of each point along the curves. To |
| 954 | /// get the intersection points, sample the curves at the corresponding values. |
| 955 | /// |
| 956 | /// Returns endpoint intersections where an endpoint intersects the interior of the other curve, |
| 957 | /// but not endpoint/endpoint intersections. |
| 958 | /// |
| 959 | /// Returns no intersections if either curve is a point. |
| 960 | pub fn cubic_intersections_t(&self, curve: &CubicBezierSegment<S>) -> ArrayVec<(S, S), 9> { |
| 961 | cubic_bezier_intersections_t(self, curve) |
| 962 | } |
| 963 | |
| 964 | /// Computes the intersection points (if any) between this segment and another one. |
| 965 | pub fn cubic_intersections(&self, curve: &CubicBezierSegment<S>) -> ArrayVec<Point<S>, 9> { |
| 966 | let intersections = self.cubic_intersections_t(curve); |
| 967 | |
| 968 | let mut result_with_repeats = ArrayVec::<_, 9>::new(); |
| 969 | for (t, _) in intersections { |
| 970 | result_with_repeats.push(self.sample(t)); |
| 971 | } |
| 972 | |
| 973 | // We can have up to nine "repeated" values here (for example: two lines, each of which |
| 974 | // overlaps itself 3 times, intersecting in their 3-fold overlaps). We make an effort to |
| 975 | // dedupe the results, but that's hindered by not having predictable control over how far |
| 976 | // the repeated intersections can be from each other (and then by the fact that true |
| 977 | // intersections can be arbitrarily close), so the results will never be perfect. |
| 978 | |
| 979 | let pair_cmp = |s: &Point<S>, t: &Point<S>| { |
| 980 | if s.x < t.x || (s.x == t.x && s.y < t.y) { |
| 981 | Less |
| 982 | } else if s.x == t.x && s.y == t.y { |
| 983 | Equal |
| 984 | } else { |
| 985 | Greater |
| 986 | } |
| 987 | }; |
| 988 | result_with_repeats.sort_unstable_by(pair_cmp); |
| 989 | if result_with_repeats.len() <= 1 { |
| 990 | return result_with_repeats; |
| 991 | } |
| 992 | |
| 993 | #[inline ] |
| 994 | fn dist_sq<S: Scalar>(p1: &Point<S>, p2: &Point<S>) -> S { |
| 995 | (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y) |
| 996 | } |
| 997 | |
| 998 | let epsilon_squared = S::EPSILON * S::EPSILON; |
| 999 | let mut result = ArrayVec::new(); |
| 1000 | let mut reference_intersection = &result_with_repeats[0]; |
| 1001 | result.push(*reference_intersection); |
| 1002 | for i in 1..result_with_repeats.len() { |
| 1003 | let intersection = &result_with_repeats[i]; |
| 1004 | if dist_sq(reference_intersection, intersection) < epsilon_squared { |
| 1005 | continue; |
| 1006 | } else { |
| 1007 | result.push(*intersection); |
| 1008 | reference_intersection = intersection; |
| 1009 | } |
| 1010 | } |
| 1011 | |
| 1012 | result |
| 1013 | } |
| 1014 | |
| 1015 | /// Computes the intersections (if any) between this segment a quadratic bézier segment. |
| 1016 | /// |
| 1017 | /// The result is provided in the form of the `t` parameters of each point along the curves. To |
| 1018 | /// get the intersection points, sample the curves at the corresponding values. |
| 1019 | /// |
| 1020 | /// Returns endpoint intersections where an endpoint intersects the interior of the other curve, |
| 1021 | /// but not endpoint/endpoint intersections. |
| 1022 | /// |
| 1023 | /// Returns no intersections if either curve is a point. |
| 1024 | pub fn quadratic_intersections_t( |
| 1025 | &self, |
| 1026 | curve: &QuadraticBezierSegment<S>, |
| 1027 | ) -> ArrayVec<(S, S), 9> { |
| 1028 | self.cubic_intersections_t(&curve.to_cubic()) |
| 1029 | } |
| 1030 | |
| 1031 | /// Computes the intersection points (if any) between this segment and a quadratic bézier segment. |
| 1032 | pub fn quadratic_intersections( |
| 1033 | &self, |
| 1034 | curve: &QuadraticBezierSegment<S>, |
| 1035 | ) -> ArrayVec<Point<S>, 9> { |
| 1036 | self.cubic_intersections(&curve.to_cubic()) |
| 1037 | } |
| 1038 | |
| 1039 | /// Computes the intersections (if any) between this segment and a line. |
| 1040 | /// |
| 1041 | /// The result is provided in the form of the `t` parameters of each |
| 1042 | /// point along curve. To get the intersection points, sample the curve |
| 1043 | /// at the corresponding values. |
| 1044 | pub fn line_intersections_t(&self, line: &Line<S>) -> ArrayVec<S, 3> { |
| 1045 | if line.vector.square_length() < S::EPSILON { |
| 1046 | return ArrayVec::new(); |
| 1047 | } |
| 1048 | |
| 1049 | let from = self.from.to_vector(); |
| 1050 | let ctrl1 = self.ctrl1.to_vector(); |
| 1051 | let ctrl2 = self.ctrl2.to_vector(); |
| 1052 | let to = self.to.to_vector(); |
| 1053 | |
| 1054 | let p1 = to - from + (ctrl1 - ctrl2) * S::THREE; |
| 1055 | let p2 = from * S::THREE + (ctrl2 - ctrl1 * S::TWO) * S::THREE; |
| 1056 | let p3 = (ctrl1 - from) * S::THREE; |
| 1057 | let p4 = from; |
| 1058 | |
| 1059 | let c = line.point.y * line.vector.x - line.point.x * line.vector.y; |
| 1060 | |
| 1061 | let roots = cubic_polynomial_roots( |
| 1062 | line.vector.y * p1.x - line.vector.x * p1.y, |
| 1063 | line.vector.y * p2.x - line.vector.x * p2.y, |
| 1064 | line.vector.y * p3.x - line.vector.x * p3.y, |
| 1065 | line.vector.y * p4.x - line.vector.x * p4.y + c, |
| 1066 | ); |
| 1067 | |
| 1068 | let mut result = ArrayVec::new(); |
| 1069 | |
| 1070 | for root in roots { |
| 1071 | if root >= S::ZERO && root <= S::ONE { |
| 1072 | result.push(root); |
| 1073 | } |
| 1074 | } |
| 1075 | |
| 1076 | // TODO: sort the intersections? |
| 1077 | |
| 1078 | result |
| 1079 | } |
| 1080 | |
| 1081 | /// Computes the intersection points (if any) between this segment and a line. |
| 1082 | pub fn line_intersections(&self, line: &Line<S>) -> ArrayVec<Point<S>, 3> { |
| 1083 | let intersections = self.line_intersections_t(line); |
| 1084 | |
| 1085 | let mut result = ArrayVec::new(); |
| 1086 | for t in intersections { |
| 1087 | result.push(self.sample(t)); |
| 1088 | } |
| 1089 | |
| 1090 | result |
| 1091 | } |
| 1092 | |
| 1093 | /// Computes the intersections (if any) between this segment and a line segment. |
| 1094 | /// |
| 1095 | /// The result is provided in the form of the `t` parameters of each |
| 1096 | /// point along curve and segment. To get the intersection points, sample |
| 1097 | /// the segments at the corresponding values. |
| 1098 | pub fn line_segment_intersections_t(&self, segment: &LineSegment<S>) -> ArrayVec<(S, S), 3> { |
| 1099 | if !self |
| 1100 | .fast_bounding_box() |
| 1101 | .inflate(S::EPSILON, S::EPSILON) |
| 1102 | .intersects(&segment.bounding_box().inflate(S::EPSILON, S::EPSILON)) |
| 1103 | { |
| 1104 | return ArrayVec::new(); |
| 1105 | } |
| 1106 | |
| 1107 | let intersections = self.line_intersections_t(&segment.to_line()); |
| 1108 | |
| 1109 | let mut result = ArrayVec::new(); |
| 1110 | if intersections.is_empty() { |
| 1111 | return result; |
| 1112 | } |
| 1113 | |
| 1114 | let seg_is_mostly_vertical = |
| 1115 | S::abs(segment.from.y - segment.to.y) >= S::abs(segment.from.x - segment.to.x); |
| 1116 | let (seg_long_axis_min, seg_long_axis_max) = if seg_is_mostly_vertical { |
| 1117 | segment.bounding_range_y() |
| 1118 | } else { |
| 1119 | segment.bounding_range_x() |
| 1120 | }; |
| 1121 | |
| 1122 | for t in intersections { |
| 1123 | let intersection_xy = if seg_is_mostly_vertical { |
| 1124 | self.y(t) |
| 1125 | } else { |
| 1126 | self.x(t) |
| 1127 | }; |
| 1128 | if intersection_xy >= seg_long_axis_min && intersection_xy <= seg_long_axis_max { |
| 1129 | let t2 = (self.sample(t) - segment.from).length() / segment.length(); |
| 1130 | // Don't take intersections that are on endpoints of both curves at the same time. |
| 1131 | if (t != S::ZERO && t != S::ONE) || (t2 != S::ZERO && t2 != S::ONE) { |
| 1132 | result.push((t, t2)); |
| 1133 | } |
| 1134 | } |
| 1135 | } |
| 1136 | |
| 1137 | result |
| 1138 | } |
| 1139 | |
| 1140 | #[inline ] |
| 1141 | pub fn from(&self) -> Point<S> { |
| 1142 | self.from |
| 1143 | } |
| 1144 | |
| 1145 | #[inline ] |
| 1146 | pub fn to(&self) -> Point<S> { |
| 1147 | self.to |
| 1148 | } |
| 1149 | |
| 1150 | pub fn line_segment_intersections(&self, segment: &LineSegment<S>) -> ArrayVec<Point<S>, 3> { |
| 1151 | let intersections = self.line_segment_intersections_t(segment); |
| 1152 | |
| 1153 | let mut result = ArrayVec::new(); |
| 1154 | for (t, _) in intersections { |
| 1155 | result.push(self.sample(t)); |
| 1156 | } |
| 1157 | |
| 1158 | result |
| 1159 | } |
| 1160 | |
| 1161 | fn baseline_projection(&self, t: S) -> S { |
| 1162 | // See https://pomax.github.io/bezierinfo/#abc |
| 1163 | // We are computing the interpolation factor between |
| 1164 | // `from` and `to` to get the position of C. |
| 1165 | let one_t = S::ONE - t; |
| 1166 | let one_t3 = one_t * one_t * one_t; |
| 1167 | let t3 = t * t * t; |
| 1168 | |
| 1169 | t3 / (t3 + one_t3) |
| 1170 | } |
| 1171 | |
| 1172 | fn abc_ratio(&self, t: S) -> S { |
| 1173 | // See https://pomax.github.io/bezierinfo/#abc |
| 1174 | let one_t = S::ONE - t; |
| 1175 | let one_t3 = one_t * one_t * one_t; |
| 1176 | let t3 = t * t * t; |
| 1177 | |
| 1178 | ((t3 + one_t3 - S::ONE) / (t3 + one_t3)).abs() |
| 1179 | } |
| 1180 | |
| 1181 | // Returns a quadratic bézier curve built by dragging this curve's point at `t` |
| 1182 | // to a new position, without moving the endpoints. |
| 1183 | // |
| 1184 | // The relative effect on control points is chosen to give a similar "feel" to |
| 1185 | // most vector graphics editors: dragging from near the first endpoint will affect |
| 1186 | // the first control point more than the second control point, etc. |
| 1187 | pub fn drag(&self, t: S, new_position: Point<S>) -> Self { |
| 1188 | // A lot of tweaking could go into making the weight feel as natural as possible. |
| 1189 | let min = S::value(0.1); |
| 1190 | let max = S::value(0.9); |
| 1191 | let weight = if t < min { |
| 1192 | S::ZERO |
| 1193 | } else if t > max { |
| 1194 | S::ONE |
| 1195 | } else { |
| 1196 | (t - min) / (max - min) |
| 1197 | }; |
| 1198 | |
| 1199 | self.drag_with_weight(t, new_position, weight) |
| 1200 | } |
| 1201 | |
| 1202 | // Returns a quadratic bézier curve built by dragging this curve's point at `t` |
| 1203 | // to a new position, without moving the endpoints. |
| 1204 | // |
| 1205 | // The provided weight specifies the relative effect on control points. |
| 1206 | // - with `weight = 0.5`, `ctrl1` and `ctrl2` are equally affected, |
| 1207 | // - with `weight = 0.0`, only `ctrl1` is affected, |
| 1208 | // - with `weight = 1.0`, only `ctrl2` is affected, |
| 1209 | // - etc. |
| 1210 | pub fn drag_with_weight(&self, t: S, new_position: Point<S>, weight: S) -> Self { |
| 1211 | // See https://pomax.github.io/bezierinfo/#abc |
| 1212 | // |
| 1213 | // From-----------Ctrl1 |
| 1214 | // | \ d1 \ |
| 1215 | // C-------P--------A \ d12 |
| 1216 | // | \d2 \ |
| 1217 | // | \ \ |
| 1218 | // To-----------------Ctrl2 |
| 1219 | // |
| 1220 | // The ABC relation means we can place the new control points however we like |
| 1221 | // as long as the ratios CA/CP, d1/d12 and d2/d12 remain constant. |
| 1222 | // |
| 1223 | // we use the weight to guide our decisions. A weight of 0.5 would be a uniform |
| 1224 | // displacement (d1 and d2 do not change and both control points are moved by the |
| 1225 | // same amount). |
| 1226 | // The approach is to use the weight interpolate the most constrained control point |
| 1227 | // between it's old position and the position it would have with uniform displacement. |
| 1228 | // then we determine the position of the least constrained control point such that |
| 1229 | // the ratios mentioned earlier remain constant. |
| 1230 | |
| 1231 | let c = self.from.lerp(self.to, self.baseline_projection(t)); |
| 1232 | let cacp_ratio = self.abc_ratio(t); |
| 1233 | |
| 1234 | let old_pos = self.sample(t); |
| 1235 | // Construct A before and after drag using the constance ca/cp ratio |
| 1236 | let old_a = old_pos + (old_pos - c) / cacp_ratio; |
| 1237 | let new_a = new_position + (new_position - c) / cacp_ratio; |
| 1238 | |
| 1239 | // Sort ctrl1 and ctrl2 such ctrl1 is the least affected (or most constrained). |
| 1240 | let mut ctrl1 = self.ctrl1; |
| 1241 | let mut ctrl2 = self.ctrl2; |
| 1242 | if t < S::HALF { |
| 1243 | core::mem::swap(&mut ctrl1, &mut ctrl2); |
| 1244 | } |
| 1245 | |
| 1246 | // Move the most constrained control point by a subset of the uniform displacement |
| 1247 | // depending on the weight. |
| 1248 | let uniform_displacement = new_a - old_a; |
| 1249 | let f = if t < S::HALF { |
| 1250 | S::TWO * weight |
| 1251 | } else { |
| 1252 | S::TWO * (S::ONE - weight) |
| 1253 | }; |
| 1254 | let mut new_ctrl1 = ctrl1 + uniform_displacement * f; |
| 1255 | |
| 1256 | // Now that the most constrained control point is placed there is only one position |
| 1257 | // for the least constrained control point that satisfies the constant ratios. |
| 1258 | let d1_pre = (old_a - ctrl1).length(); |
| 1259 | let d12_pre = (self.ctrl2 - self.ctrl1).length(); |
| 1260 | |
| 1261 | let mut new_ctrl2 = new_ctrl1 + (new_a - new_ctrl1) * (d12_pre / d1_pre); |
| 1262 | |
| 1263 | if t < S::HALF { |
| 1264 | core::mem::swap(&mut new_ctrl1, &mut new_ctrl2); |
| 1265 | } |
| 1266 | |
| 1267 | CubicBezierSegment { |
| 1268 | from: self.from, |
| 1269 | ctrl1: new_ctrl1, |
| 1270 | ctrl2: new_ctrl2, |
| 1271 | to: self.to, |
| 1272 | } |
| 1273 | } |
| 1274 | |
| 1275 | pub fn to_f32(&self) -> CubicBezierSegment<f32> { |
| 1276 | CubicBezierSegment { |
| 1277 | from: self.from.to_f32(), |
| 1278 | ctrl1: self.ctrl1.to_f32(), |
| 1279 | ctrl2: self.ctrl2.to_f32(), |
| 1280 | to: self.to.to_f32(), |
| 1281 | } |
| 1282 | } |
| 1283 | |
| 1284 | pub fn to_f64(&self) -> CubicBezierSegment<f64> { |
| 1285 | CubicBezierSegment { |
| 1286 | from: self.from.to_f64(), |
| 1287 | ctrl1: self.ctrl1.to_f64(), |
| 1288 | ctrl2: self.ctrl2.to_f64(), |
| 1289 | to: self.to.to_f64(), |
| 1290 | } |
| 1291 | } |
| 1292 | } |
| 1293 | |
| 1294 | impl<S: Scalar> Segment for CubicBezierSegment<S> { |
| 1295 | impl_segment!(S); |
| 1296 | |
| 1297 | fn for_each_flattened_with_t( |
| 1298 | &self, |
| 1299 | tolerance: Self::Scalar, |
| 1300 | callback: &mut dyn FnMut(&LineSegment<S>, Range<S>), |
| 1301 | ) { |
| 1302 | self.for_each_flattened_with_t(tolerance, &mut |s: &LineSegment, t: Range| callback(s, t)); |
| 1303 | } |
| 1304 | } |
| 1305 | |
| 1306 | impl<S: Scalar> BoundingBox for CubicBezierSegment<S> { |
| 1307 | type Scalar = S; |
| 1308 | fn bounding_range_x(&self) -> (S, S) { |
| 1309 | self.bounding_range_x() |
| 1310 | } |
| 1311 | fn bounding_range_y(&self) -> (S, S) { |
| 1312 | self.bounding_range_y() |
| 1313 | } |
| 1314 | fn fast_bounding_range_x(&self) -> (S, S) { |
| 1315 | self.fast_bounding_range_x() |
| 1316 | } |
| 1317 | fn fast_bounding_range_y(&self) -> (S, S) { |
| 1318 | self.fast_bounding_range_y() |
| 1319 | } |
| 1320 | } |
| 1321 | |
| 1322 | use crate::quadratic_bezier::FlattenedT as FlattenedQuadraticSegment; |
| 1323 | |
| 1324 | pub struct Flattened<S: Scalar> { |
| 1325 | curve: CubicBezierSegment<S>, |
| 1326 | current_curve: FlattenedQuadraticSegment<S>, |
| 1327 | remaining_sub_curves: i32, |
| 1328 | tolerance: S, |
| 1329 | range_step: S, |
| 1330 | range_start: S, |
| 1331 | } |
| 1332 | |
| 1333 | impl<S: Scalar> Flattened<S> { |
| 1334 | pub(crate) fn new(curve: &CubicBezierSegment<S>, tolerance: S) -> Self { |
| 1335 | debug_assert!(tolerance >= S::EPSILON * S::EPSILON); |
| 1336 | |
| 1337 | let quadratics_tolerance: S = tolerance * S::value(0.4); |
| 1338 | let flattening_tolerance: S = tolerance * S::value(0.8); |
| 1339 | |
| 1340 | let num_quadratics: S = curve.num_quadratics_impl(quadratics_tolerance); |
| 1341 | |
| 1342 | let range_step: S = S::ONE / num_quadratics; |
| 1343 | |
| 1344 | let quadratic: QuadraticBezierSegment = curve.split_range(S::ZERO..range_step).to_quadratic(); |
| 1345 | let current_curve: FlattenedT = FlattenedQuadraticSegment::new(&quadratic, flattening_tolerance); |
| 1346 | |
| 1347 | Flattened { |
| 1348 | curve: *curve, |
| 1349 | current_curve, |
| 1350 | remaining_sub_curves: num_quadratics.to_i32().unwrap() - 1, |
| 1351 | tolerance: flattening_tolerance, |
| 1352 | range_start: S::ZERO, |
| 1353 | range_step, |
| 1354 | } |
| 1355 | } |
| 1356 | } |
| 1357 | |
| 1358 | impl<S: Scalar> Iterator for Flattened<S> { |
| 1359 | type Item = Point<S>; |
| 1360 | |
| 1361 | fn next(&mut self) -> Option<Point<S>> { |
| 1362 | if let Some(t_inner) = self.current_curve.next() { |
| 1363 | let t = self.range_start + t_inner * self.range_step; |
| 1364 | return Some(self.curve.sample(t)); |
| 1365 | } |
| 1366 | |
| 1367 | if self.remaining_sub_curves <= 0 { |
| 1368 | return None; |
| 1369 | } |
| 1370 | |
| 1371 | self.range_start += self.range_step; |
| 1372 | let t0 = self.range_start; |
| 1373 | let t1 = self.range_start + self.range_step; |
| 1374 | self.remaining_sub_curves -= 1; |
| 1375 | |
| 1376 | let quadratic = self.curve.split_range(t0..t1).to_quadratic(); |
| 1377 | self.current_curve = FlattenedQuadraticSegment::new(&quadratic, self.tolerance); |
| 1378 | |
| 1379 | let t_inner = self.current_curve.next().unwrap_or(S::ONE); |
| 1380 | let t = t0 + t_inner * self.range_step; |
| 1381 | |
| 1382 | Some(self.curve.sample(t)) |
| 1383 | } |
| 1384 | |
| 1385 | fn size_hint(&self) -> (usize, Option<usize>) { |
| 1386 | ( |
| 1387 | self.remaining_sub_curves as usize * self.current_curve.size_hint().0, |
| 1388 | None, |
| 1389 | ) |
| 1390 | } |
| 1391 | } |
| 1392 | |
| 1393 | #[cfg (test)] |
| 1394 | fn print_arrays(a: &[Point<f32>], b: &[Point<f32>]) { |
| 1395 | std::println!("left: {a:?}" ); |
| 1396 | std::println!("right: {b:?}" ); |
| 1397 | } |
| 1398 | |
| 1399 | #[cfg (test)] |
| 1400 | fn assert_approx_eq(a: &[Point<f32>], b: &[Point<f32>]) { |
| 1401 | if a.len() != b.len() { |
| 1402 | print_arrays(a, b); |
| 1403 | panic!("Lengths differ ({} != {})" , a.len(), b.len()); |
| 1404 | } |
| 1405 | for i in 0..a.len() { |
| 1406 | let threshold = 0.029; |
| 1407 | let dx = f32::abs(a[i].x - b[i].x); |
| 1408 | let dy = f32::abs(a[i].y - b[i].y); |
| 1409 | if dx > threshold || dy > threshold { |
| 1410 | print_arrays(a, b); |
| 1411 | std::println!("diff = {dx:?} {dy:?}" ); |
| 1412 | panic!("The arrays are not equal" ); |
| 1413 | } |
| 1414 | } |
| 1415 | } |
| 1416 | |
| 1417 | #[test ] |
| 1418 | fn test_iterator_builder_1() { |
| 1419 | let tolerance = 0.01; |
| 1420 | let c1 = CubicBezierSegment { |
| 1421 | from: Point::new(0.0, 0.0), |
| 1422 | ctrl1: Point::new(1.0, 0.0), |
| 1423 | ctrl2: Point::new(1.0, 1.0), |
| 1424 | to: Point::new(0.0, 1.0), |
| 1425 | }; |
| 1426 | let iter_points: Vec<Point<f32>> = c1.flattened(tolerance).collect(); |
| 1427 | let mut builder_points = Vec::new(); |
| 1428 | c1.for_each_flattened(tolerance, &mut |s| { |
| 1429 | builder_points.push(s.to); |
| 1430 | }); |
| 1431 | |
| 1432 | assert!(iter_points.len() > 2); |
| 1433 | assert_approx_eq(&iter_points[..], &builder_points[..]); |
| 1434 | } |
| 1435 | |
| 1436 | #[test ] |
| 1437 | fn test_iterator_builder_2() { |
| 1438 | let tolerance = 0.01; |
| 1439 | let c1 = CubicBezierSegment { |
| 1440 | from: Point::new(0.0, 0.0), |
| 1441 | ctrl1: Point::new(1.0, 0.0), |
| 1442 | ctrl2: Point::new(0.0, 1.0), |
| 1443 | to: Point::new(1.0, 1.0), |
| 1444 | }; |
| 1445 | let iter_points: Vec<Point<f32>> = c1.flattened(tolerance).collect(); |
| 1446 | let mut builder_points = Vec::new(); |
| 1447 | c1.for_each_flattened(tolerance, &mut |s| { |
| 1448 | builder_points.push(s.to); |
| 1449 | }); |
| 1450 | |
| 1451 | assert!(iter_points.len() > 2); |
| 1452 | assert_approx_eq(&iter_points[..], &builder_points[..]); |
| 1453 | } |
| 1454 | |
| 1455 | #[test ] |
| 1456 | fn test_iterator_builder_3() { |
| 1457 | let tolerance = 0.01; |
| 1458 | let c1 = CubicBezierSegment { |
| 1459 | from: Point::new(141.0, 135.0), |
| 1460 | ctrl1: Point::new(141.0, 130.0), |
| 1461 | ctrl2: Point::new(140.0, 130.0), |
| 1462 | to: Point::new(131.0, 130.0), |
| 1463 | }; |
| 1464 | let iter_points: Vec<Point<f32>> = c1.flattened(tolerance).collect(); |
| 1465 | let mut builder_points = Vec::new(); |
| 1466 | c1.for_each_flattened(tolerance, &mut |s| { |
| 1467 | builder_points.push(s.to); |
| 1468 | }); |
| 1469 | |
| 1470 | assert!(iter_points.len() > 2); |
| 1471 | assert_approx_eq(&iter_points[..], &builder_points[..]); |
| 1472 | } |
| 1473 | |
| 1474 | #[test ] |
| 1475 | fn test_issue_19() { |
| 1476 | let tolerance = 0.15; |
| 1477 | let c1 = CubicBezierSegment { |
| 1478 | from: Point::new(11.71726, 9.07143), |
| 1479 | ctrl1: Point::new(1.889879, 13.22917), |
| 1480 | ctrl2: Point::new(18.142855, 19.27679), |
| 1481 | to: Point::new(18.142855, 19.27679), |
| 1482 | }; |
| 1483 | let iter_points: Vec<Point<f32>> = c1.flattened(tolerance).collect(); |
| 1484 | let mut builder_points = Vec::new(); |
| 1485 | c1.for_each_flattened(tolerance, &mut |s| { |
| 1486 | builder_points.push(s.to); |
| 1487 | }); |
| 1488 | |
| 1489 | assert_approx_eq(&iter_points[..], &builder_points[..]); |
| 1490 | |
| 1491 | assert!(iter_points.len() > 1); |
| 1492 | } |
| 1493 | |
| 1494 | #[test ] |
| 1495 | fn test_issue_194() { |
| 1496 | let segment = CubicBezierSegment { |
| 1497 | from: Point::new(0.0, 0.0), |
| 1498 | ctrl1: Point::new(0.0, 0.0), |
| 1499 | ctrl2: Point::new(50.0, 70.0), |
| 1500 | to: Point::new(100.0, 100.0), |
| 1501 | }; |
| 1502 | |
| 1503 | let mut points = Vec::new(); |
| 1504 | segment.for_each_flattened(0.1, &mut |s| { |
| 1505 | points.push(s.to); |
| 1506 | }); |
| 1507 | |
| 1508 | assert!(points.len() > 2); |
| 1509 | } |
| 1510 | |
| 1511 | #[test ] |
| 1512 | fn flatten_with_t() { |
| 1513 | let segment = CubicBezierSegment { |
| 1514 | from: Point::new(0.0f32, 0.0), |
| 1515 | ctrl1: Point::new(0.0, 0.0), |
| 1516 | ctrl2: Point::new(50.0, 70.0), |
| 1517 | to: Point::new(100.0, 100.0), |
| 1518 | }; |
| 1519 | |
| 1520 | for tolerance in &[0.1, 0.01, 0.001, 0.0001] { |
| 1521 | let tolerance = *tolerance; |
| 1522 | |
| 1523 | let mut a = Vec::new(); |
| 1524 | segment.for_each_flattened(tolerance, &mut |s| { |
| 1525 | a.push(*s); |
| 1526 | }); |
| 1527 | |
| 1528 | let mut b = Vec::new(); |
| 1529 | let mut ts = Vec::new(); |
| 1530 | segment.for_each_flattened_with_t(tolerance, &mut |s, t| { |
| 1531 | b.push(*s); |
| 1532 | ts.push(t); |
| 1533 | }); |
| 1534 | |
| 1535 | assert_eq!(a, b); |
| 1536 | |
| 1537 | for i in 0..b.len() { |
| 1538 | let sampled = segment.sample(ts[i].start); |
| 1539 | let point = b[i].from; |
| 1540 | let dist = (sampled - point).length(); |
| 1541 | assert!(dist <= tolerance); |
| 1542 | |
| 1543 | let sampled = segment.sample(ts[i].end); |
| 1544 | let point = b[i].to; |
| 1545 | let dist = (sampled - point).length(); |
| 1546 | assert!(dist <= tolerance); |
| 1547 | } |
| 1548 | } |
| 1549 | } |
| 1550 | |
| 1551 | #[test ] |
| 1552 | fn test_flatten_end() { |
| 1553 | let segment = CubicBezierSegment { |
| 1554 | from: Point::new(0.0, 0.0), |
| 1555 | ctrl1: Point::new(100.0, 0.0), |
| 1556 | ctrl2: Point::new(100.0, 100.0), |
| 1557 | to: Point::new(100.0, 200.0), |
| 1558 | }; |
| 1559 | |
| 1560 | let mut last = segment.from; |
| 1561 | segment.for_each_flattened(0.0001, &mut |s| { |
| 1562 | last = s.to; |
| 1563 | }); |
| 1564 | |
| 1565 | assert_eq!(last, segment.to); |
| 1566 | } |
| 1567 | |
| 1568 | #[test ] |
| 1569 | fn test_flatten_point() { |
| 1570 | let segment = CubicBezierSegment { |
| 1571 | from: Point::new(0.0, 0.0), |
| 1572 | ctrl1: Point::new(0.0, 0.0), |
| 1573 | ctrl2: Point::new(0.0, 0.0), |
| 1574 | to: Point::new(0.0, 0.0), |
| 1575 | }; |
| 1576 | |
| 1577 | let mut last = segment.from; |
| 1578 | segment.for_each_flattened(0.0001, &mut |s| { |
| 1579 | last = s.to; |
| 1580 | }); |
| 1581 | |
| 1582 | assert_eq!(last, segment.to); |
| 1583 | } |
| 1584 | |
| 1585 | #[test ] |
| 1586 | fn issue_652() { |
| 1587 | use crate::point; |
| 1588 | |
| 1589 | let curve = CubicBezierSegment { |
| 1590 | from: point(-1061.0, -3327.0), |
| 1591 | ctrl1: point(-1061.0, -3177.0), |
| 1592 | ctrl2: point(-1061.0, -3477.0), |
| 1593 | to: point(-1061.0, -3327.0), |
| 1594 | }; |
| 1595 | |
| 1596 | for _ in curve.flattened(1.0) {} |
| 1597 | for _ in curve.flattened(0.1) {} |
| 1598 | for _ in curve.flattened(0.01) {} |
| 1599 | |
| 1600 | curve.for_each_flattened(1.0, &mut |_| {}); |
| 1601 | curve.for_each_flattened(0.1, &mut |_| {}); |
| 1602 | curve.for_each_flattened(0.01, &mut |_| {}); |
| 1603 | } |
| 1604 | |
| 1605 | #[test ] |
| 1606 | fn fast_bounding_box_for_cubic_bezier_segment() { |
| 1607 | let a = CubicBezierSegment { |
| 1608 | from: Point::new(0.0, 0.0), |
| 1609 | ctrl1: Point::new(0.5, 1.0), |
| 1610 | ctrl2: Point::new(1.5, -1.0), |
| 1611 | to: Point::new(2.0, 0.0), |
| 1612 | }; |
| 1613 | |
| 1614 | let expected_aabb = Box2D { |
| 1615 | min: point(0.0, -1.0), |
| 1616 | max: point(2.0, 1.0), |
| 1617 | }; |
| 1618 | |
| 1619 | let actual_aabb = a.fast_bounding_box(); |
| 1620 | |
| 1621 | assert_eq!(expected_aabb, actual_aabb) |
| 1622 | } |
| 1623 | |
| 1624 | #[test ] |
| 1625 | fn minimum_bounding_box_for_cubic_bezier_segment() { |
| 1626 | let a = CubicBezierSegment { |
| 1627 | from: Point::new(0.0, 0.0), |
| 1628 | ctrl1: Point::new(0.5, 2.0), |
| 1629 | ctrl2: Point::new(1.5, -2.0), |
| 1630 | to: Point::new(2.0, 0.0), |
| 1631 | }; |
| 1632 | |
| 1633 | let expected_bigger_aabb: Box2D<f32> = Box2D { |
| 1634 | min: point(0.0, -0.6), |
| 1635 | max: point(2.0, 0.6), |
| 1636 | }; |
| 1637 | let expected_smaller_aabb: Box2D<f32> = Box2D { |
| 1638 | min: point(0.1, -0.5), |
| 1639 | max: point(2.0, 0.5), |
| 1640 | }; |
| 1641 | |
| 1642 | let actual_minimum_aabb = a.bounding_box(); |
| 1643 | |
| 1644 | assert!(expected_bigger_aabb.contains_box(&actual_minimum_aabb)); |
| 1645 | assert!(actual_minimum_aabb.contains_box(&expected_smaller_aabb)); |
| 1646 | } |
| 1647 | |
| 1648 | #[test ] |
| 1649 | fn y_maximum_t_for_simple_cubic_segment() { |
| 1650 | let a = CubicBezierSegment { |
| 1651 | from: Point::new(0.0, 0.0), |
| 1652 | ctrl1: Point::new(0.5, 1.0), |
| 1653 | ctrl2: Point::new(1.5, 1.0), |
| 1654 | to: Point::new(2.0, 2.0), |
| 1655 | }; |
| 1656 | |
| 1657 | let expected_y_maximum = 1.0; |
| 1658 | |
| 1659 | let actual_y_maximum = a.y_maximum_t(); |
| 1660 | |
| 1661 | assert_eq!(expected_y_maximum, actual_y_maximum) |
| 1662 | } |
| 1663 | |
| 1664 | #[test ] |
| 1665 | fn y_minimum_t_for_simple_cubic_segment() { |
| 1666 | let a = CubicBezierSegment { |
| 1667 | from: Point::new(0.0, 0.0), |
| 1668 | ctrl1: Point::new(0.5, 1.0), |
| 1669 | ctrl2: Point::new(1.5, 1.0), |
| 1670 | to: Point::new(2.0, 0.0), |
| 1671 | }; |
| 1672 | |
| 1673 | let expected_y_minimum = 0.0; |
| 1674 | |
| 1675 | let actual_y_minimum = a.y_minimum_t(); |
| 1676 | |
| 1677 | assert_eq!(expected_y_minimum, actual_y_minimum) |
| 1678 | } |
| 1679 | |
| 1680 | #[test ] |
| 1681 | fn y_extrema_for_simple_cubic_segment() { |
| 1682 | let a = CubicBezierSegment { |
| 1683 | from: Point::new(0.0, 0.0), |
| 1684 | ctrl1: Point::new(1.0, 2.0), |
| 1685 | ctrl2: Point::new(2.0, 2.0), |
| 1686 | to: Point::new(3.0, 0.0), |
| 1687 | }; |
| 1688 | |
| 1689 | let mut n: u32 = 0; |
| 1690 | a.for_each_local_y_extremum_t(&mut |t| { |
| 1691 | assert_eq!(t, 0.5); |
| 1692 | n += 1; |
| 1693 | }); |
| 1694 | assert_eq!(n, 1); |
| 1695 | } |
| 1696 | |
| 1697 | #[test ] |
| 1698 | fn x_extrema_for_simple_cubic_segment() { |
| 1699 | let a = CubicBezierSegment { |
| 1700 | from: Point::new(0.0, 0.0), |
| 1701 | ctrl1: Point::new(1.0, 2.0), |
| 1702 | ctrl2: Point::new(1.0, 2.0), |
| 1703 | to: Point::new(0.0, 0.0), |
| 1704 | }; |
| 1705 | |
| 1706 | let mut n: u32 = 0; |
| 1707 | a.for_each_local_x_extremum_t(&mut |t| { |
| 1708 | assert_eq!(t, 0.5); |
| 1709 | n += 1; |
| 1710 | }); |
| 1711 | assert_eq!(n, 1); |
| 1712 | } |
| 1713 | |
| 1714 | #[test ] |
| 1715 | fn x_maximum_t_for_simple_cubic_segment() { |
| 1716 | let a = CubicBezierSegment { |
| 1717 | from: Point::new(0.0, 0.0), |
| 1718 | ctrl1: Point::new(0.5, 1.0), |
| 1719 | ctrl2: Point::new(1.5, 1.0), |
| 1720 | to: Point::new(2.0, 0.0), |
| 1721 | }; |
| 1722 | let expected_x_maximum = 1.0; |
| 1723 | |
| 1724 | let actual_x_maximum = a.x_maximum_t(); |
| 1725 | |
| 1726 | assert_eq!(expected_x_maximum, actual_x_maximum) |
| 1727 | } |
| 1728 | |
| 1729 | #[test ] |
| 1730 | fn x_minimum_t_for_simple_cubic_segment() { |
| 1731 | let a = CubicBezierSegment { |
| 1732 | from: Point::new(0.0, 0.0), |
| 1733 | ctrl1: Point::new(0.5, 1.0), |
| 1734 | ctrl2: Point::new(1.5, 1.0), |
| 1735 | to: Point::new(2.0, 0.0), |
| 1736 | }; |
| 1737 | |
| 1738 | let expected_x_minimum = 0.0; |
| 1739 | |
| 1740 | let actual_x_minimum = a.x_minimum_t(); |
| 1741 | |
| 1742 | assert_eq!(expected_x_minimum, actual_x_minimum) |
| 1743 | } |
| 1744 | |
| 1745 | #[test ] |
| 1746 | fn derivatives() { |
| 1747 | let c1 = CubicBezierSegment { |
| 1748 | from: Point::new(1.0, 1.0), |
| 1749 | ctrl1: Point::new(1.0, 2.0), |
| 1750 | ctrl2: Point::new(2.0, 1.0), |
| 1751 | to: Point::new(2.0, 2.0), |
| 1752 | }; |
| 1753 | |
| 1754 | assert_eq!(c1.dx(0.0), 0.0); |
| 1755 | assert_eq!(c1.dx(1.0), 0.0); |
| 1756 | assert_eq!(c1.dy(0.5), 0.0); |
| 1757 | } |
| 1758 | |
| 1759 | #[test ] |
| 1760 | fn fat_line() { |
| 1761 | use crate::point; |
| 1762 | |
| 1763 | let c1 = CubicBezierSegment { |
| 1764 | from: point(1.0f32, 2.0), |
| 1765 | ctrl1: point(1.0, 3.0), |
| 1766 | ctrl2: point(11.0, 11.0), |
| 1767 | to: point(11.0, 12.0), |
| 1768 | }; |
| 1769 | |
| 1770 | let (l1, l2) = c1.fat_line(); |
| 1771 | |
| 1772 | for i in 0..100 { |
| 1773 | let t = i as f32 / 99.0; |
| 1774 | assert!(l1.signed_distance_to_point(&c1.sample(t)) >= -0.000001); |
| 1775 | assert!(l2.signed_distance_to_point(&c1.sample(t)) <= 0.000001); |
| 1776 | } |
| 1777 | |
| 1778 | let c2 = CubicBezierSegment { |
| 1779 | from: point(1.0f32, 2.0), |
| 1780 | ctrl1: point(1.0, 3.0), |
| 1781 | ctrl2: point(11.0, 14.0), |
| 1782 | to: point(11.0, 12.0), |
| 1783 | }; |
| 1784 | |
| 1785 | let (l1, l2) = c2.fat_line(); |
| 1786 | |
| 1787 | for i in 0..100 { |
| 1788 | let t = i as f32 / 99.0; |
| 1789 | assert!(l1.signed_distance_to_point(&c2.sample(t)) >= -0.000001); |
| 1790 | assert!(l2.signed_distance_to_point(&c2.sample(t)) <= 0.000001); |
| 1791 | } |
| 1792 | |
| 1793 | let c3 = CubicBezierSegment { |
| 1794 | from: point(0.0f32, 1.0), |
| 1795 | ctrl1: point(0.5, 0.0), |
| 1796 | ctrl2: point(0.5, 0.0), |
| 1797 | to: point(1.0, 1.0), |
| 1798 | }; |
| 1799 | |
| 1800 | let (l1, l2) = c3.fat_line(); |
| 1801 | |
| 1802 | for i in 0..100 { |
| 1803 | let t = i as f32 / 99.0; |
| 1804 | assert!(l1.signed_distance_to_point(&c3.sample(t)) >= -0.000001); |
| 1805 | assert!(l2.signed_distance_to_point(&c3.sample(t)) <= 0.000001); |
| 1806 | } |
| 1807 | } |
| 1808 | |
| 1809 | #[test ] |
| 1810 | fn is_linear() { |
| 1811 | let mut angle = 0.0; |
| 1812 | let center = Point::new(1000.0, -700.0); |
| 1813 | for _ in 0..100 { |
| 1814 | for i in 0..10 { |
| 1815 | for j in 0..10 { |
| 1816 | let (sin, cos) = f64::sin_cos(angle); |
| 1817 | let endpoint = Vector::new(cos * 100.0, sin * 100.0); |
| 1818 | let curve = CubicBezierSegment { |
| 1819 | from: center - endpoint, |
| 1820 | ctrl1: center + endpoint.lerp(-endpoint, i as f64 / 9.0), |
| 1821 | ctrl2: center + endpoint.lerp(-endpoint, j as f64 / 9.0), |
| 1822 | to: center + endpoint, |
| 1823 | }; |
| 1824 | assert!(curve.is_linear(1e-10)); |
| 1825 | } |
| 1826 | } |
| 1827 | angle += 0.001; |
| 1828 | } |
| 1829 | } |
| 1830 | |
| 1831 | #[test ] |
| 1832 | fn test_monotonic() { |
| 1833 | use crate::point; |
| 1834 | let curve = CubicBezierSegment { |
| 1835 | from: point(1.0, 1.0), |
| 1836 | ctrl1: point(10.0, 2.0), |
| 1837 | ctrl2: point(1.0, 3.0), |
| 1838 | to: point(10.0, 4.0), |
| 1839 | }; |
| 1840 | |
| 1841 | curve.for_each_monotonic_range(&mut |range| { |
| 1842 | let sub_curve = curve.split_range(range); |
| 1843 | assert!(sub_curve.is_monotonic()); |
| 1844 | }); |
| 1845 | } |
| 1846 | |
| 1847 | #[test ] |
| 1848 | fn test_line_segment_intersections() { |
| 1849 | use crate::point; |
| 1850 | fn assert_approx_eq(a: ArrayVec<(f32, f32), 3>, b: &[(f32, f32)], epsilon: f32) { |
| 1851 | for i in 0..a.len() { |
| 1852 | if f32::abs(a[i].0 - b[i].0) > epsilon || f32::abs(a[i].1 - b[i].1) > epsilon { |
| 1853 | std::println!("{a:?} != {b:?}" ); |
| 1854 | } |
| 1855 | assert!((a[i].0 - b[i].0).abs() <= epsilon && (a[i].1 - b[i].1).abs() <= epsilon); |
| 1856 | } |
| 1857 | assert_eq!(a.len(), b.len()); |
| 1858 | } |
| 1859 | |
| 1860 | let epsilon = 0.0001; |
| 1861 | |
| 1862 | // Make sure we find intersections with horizontal and vertical lines. |
| 1863 | |
| 1864 | let curve1 = CubicBezierSegment { |
| 1865 | from: point(-1.0, -1.0), |
| 1866 | ctrl1: point(0.0, 4.0), |
| 1867 | ctrl2: point(10.0, -4.0), |
| 1868 | to: point(11.0, 1.0), |
| 1869 | }; |
| 1870 | let seg1 = LineSegment { |
| 1871 | from: point(0.0, 0.0), |
| 1872 | to: point(10.0, 0.0), |
| 1873 | }; |
| 1874 | assert_approx_eq( |
| 1875 | curve1.line_segment_intersections_t(&seg1), |
| 1876 | &[(0.5, 0.5)], |
| 1877 | epsilon, |
| 1878 | ); |
| 1879 | |
| 1880 | let curve2 = CubicBezierSegment { |
| 1881 | from: point(-1.0, 0.0), |
| 1882 | ctrl1: point(0.0, 5.0), |
| 1883 | ctrl2: point(0.0, 5.0), |
| 1884 | to: point(1.0, 0.0), |
| 1885 | }; |
| 1886 | let seg2 = LineSegment { |
| 1887 | from: point(0.0, 0.0), |
| 1888 | to: point(0.0, 5.0), |
| 1889 | }; |
| 1890 | assert_approx_eq( |
| 1891 | curve2.line_segment_intersections_t(&seg2), |
| 1892 | &[(0.5, 0.75)], |
| 1893 | epsilon, |
| 1894 | ); |
| 1895 | } |
| 1896 | |
| 1897 | #[test ] |
| 1898 | fn test_parameters_for_value() { |
| 1899 | use crate::point; |
| 1900 | fn assert_approx_eq(a: ArrayVec<f32, 3>, b: &[f32], epsilon: f32) { |
| 1901 | for i in 0..a.len() { |
| 1902 | if f32::abs(a[i] - b[i]) > epsilon { |
| 1903 | std::println!("{a:?} != {b:?}" ); |
| 1904 | } |
| 1905 | assert!((a[i] - b[i]).abs() <= epsilon); |
| 1906 | } |
| 1907 | assert_eq!(a.len(), b.len()); |
| 1908 | } |
| 1909 | |
| 1910 | { |
| 1911 | let curve = CubicBezierSegment { |
| 1912 | from: point(0.0, 0.0), |
| 1913 | ctrl1: point(0.0, 8.0), |
| 1914 | ctrl2: point(10.0, 8.0), |
| 1915 | to: point(10.0, 0.0), |
| 1916 | }; |
| 1917 | |
| 1918 | let epsilon = 1e-4; |
| 1919 | assert_approx_eq(curve.solve_t_for_x(5.0), &[0.5], epsilon); |
| 1920 | assert_approx_eq(curve.solve_t_for_y(6.0), &[0.5], epsilon); |
| 1921 | } |
| 1922 | { |
| 1923 | let curve = CubicBezierSegment { |
| 1924 | from: point(0.0, 10.0), |
| 1925 | ctrl1: point(0.0, 10.0), |
| 1926 | ctrl2: point(10.0, 10.0), |
| 1927 | to: point(10.0, 10.0), |
| 1928 | }; |
| 1929 | |
| 1930 | assert_approx_eq(curve.solve_t_for_y(10.0), &[], 0.0); |
| 1931 | } |
| 1932 | } |
| 1933 | |
| 1934 | #[test ] |
| 1935 | fn test_cubic_intersection_deduping() { |
| 1936 | use crate::point; |
| 1937 | |
| 1938 | let epsilon = 0.0001; |
| 1939 | |
| 1940 | // Two "line segments" with 3-fold overlaps, intersecting in their overlaps for a total of nine |
| 1941 | // parameter intersections. |
| 1942 | let line1 = CubicBezierSegment { |
| 1943 | from: point(-1_000_000.0, 0.0), |
| 1944 | ctrl1: point(2_000_000.0, 3_000_000.0), |
| 1945 | ctrl2: point(-2_000_000.0, -1_000_000.0), |
| 1946 | to: point(1_000_000.0, 2_000_000.0), |
| 1947 | }; |
| 1948 | let line2 = CubicBezierSegment { |
| 1949 | from: point(-1_000_000.0, 2_000_000.0), |
| 1950 | ctrl1: point(2_000_000.0, -1_000_000.0), |
| 1951 | ctrl2: point(-2_000_000.0, 3_000_000.0), |
| 1952 | to: point(1_000_000.0, 0.0), |
| 1953 | }; |
| 1954 | let intersections = line1.cubic_intersections(&line2); |
| 1955 | // (If you increase the coordinates above to 10s of millions, you get two returned intersection |
| 1956 | // points; i.e. the test fails.) |
| 1957 | assert_eq!(intersections.len(), 1); |
| 1958 | assert!(f64::abs(intersections[0].x) < epsilon); |
| 1959 | assert!(f64::abs(intersections[0].y - 1_000_000.0) < epsilon); |
| 1960 | |
| 1961 | // Two self-intersecting curves that intersect in their self-intersections, for a total of four |
| 1962 | // parameter intersections. |
| 1963 | let curve1 = CubicBezierSegment { |
| 1964 | from: point(-10.0, -13.636363636363636), |
| 1965 | ctrl1: point(15.0, 11.363636363636363), |
| 1966 | ctrl2: point(-15.0, 11.363636363636363), |
| 1967 | to: point(10.0, -13.636363636363636), |
| 1968 | }; |
| 1969 | let curve2 = CubicBezierSegment { |
| 1970 | from: point(13.636363636363636, -10.0), |
| 1971 | ctrl1: point(-11.363636363636363, 15.0), |
| 1972 | ctrl2: point(-11.363636363636363, -15.0), |
| 1973 | to: point(13.636363636363636, 10.0), |
| 1974 | }; |
| 1975 | let intersections = curve1.cubic_intersections(&curve2); |
| 1976 | assert_eq!(intersections.len(), 1); |
| 1977 | assert!(f64::abs(intersections[0].x) < epsilon); |
| 1978 | assert!(f64::abs(intersections[0].y) < epsilon); |
| 1979 | } |
| 1980 | |
| 1981 | #[test ] |
| 1982 | fn cubic_line_intersection_on_endpoint() { |
| 1983 | let l1 = LineSegment { |
| 1984 | from: Point::new(0.0, -100.0), |
| 1985 | to: Point::new(0.0, 100.0), |
| 1986 | }; |
| 1987 | |
| 1988 | let cubic = CubicBezierSegment { |
| 1989 | from: Point::new(0.0, 0.0), |
| 1990 | ctrl1: Point::new(20.0, 20.0), |
| 1991 | ctrl2: Point::new(20.0, 40.0), |
| 1992 | to: Point::new(0.0, 60.0), |
| 1993 | }; |
| 1994 | |
| 1995 | let intersections = cubic.line_segment_intersections_t(&l1); |
| 1996 | |
| 1997 | assert_eq!(intersections.len(), 2); |
| 1998 | assert_eq!(intersections[0], (1.0, 0.8)); |
| 1999 | assert_eq!(intersections[1], (0.0, 0.5)); |
| 2000 | |
| 2001 | let l2 = LineSegment { |
| 2002 | from: Point::new(0.0, 0.0), |
| 2003 | to: Point::new(0.0, 60.0), |
| 2004 | }; |
| 2005 | |
| 2006 | let intersections = cubic.line_segment_intersections_t(&l2); |
| 2007 | |
| 2008 | assert!(intersections.is_empty()); |
| 2009 | |
| 2010 | let c1 = CubicBezierSegment { |
| 2011 | from: Point::new(0.0, 0.0), |
| 2012 | ctrl1: Point::new(20.0, 0.0), |
| 2013 | ctrl2: Point::new(20.0, 20.0), |
| 2014 | to: Point::new(0.0, 60.0), |
| 2015 | }; |
| 2016 | |
| 2017 | let c2 = CubicBezierSegment { |
| 2018 | from: Point::new(0.0, 60.0), |
| 2019 | ctrl1: Point::new(-40.0, 4.0), |
| 2020 | ctrl2: Point::new(-20.0, 20.0), |
| 2021 | to: Point::new(0.0, 00.0), |
| 2022 | }; |
| 2023 | |
| 2024 | let intersections = c1.cubic_intersections_t(&c2); |
| 2025 | |
| 2026 | assert!(intersections.is_empty()); |
| 2027 | } |
| 2028 | |
| 2029 | #[test ] |
| 2030 | fn test_cubic_to_quadratics() { |
| 2031 | use euclid::approxeq::ApproxEq; |
| 2032 | |
| 2033 | let quadratic = QuadraticBezierSegment { |
| 2034 | from: point(1.0, 2.0), |
| 2035 | ctrl: point(10.0, 5.0), |
| 2036 | to: point(0.0, 1.0), |
| 2037 | }; |
| 2038 | |
| 2039 | let mut count = 0; |
| 2040 | assert_eq!(quadratic.to_cubic().num_quadratics(0.0001), 1); |
| 2041 | quadratic |
| 2042 | .to_cubic() |
| 2043 | .for_each_quadratic_bezier(0.0001, &mut |c| { |
| 2044 | assert_eq!(count, 0); |
| 2045 | assert!(c.from.approx_eq(&quadratic.from)); |
| 2046 | assert!(c.ctrl.approx_eq(&quadratic.ctrl)); |
| 2047 | assert!(c.to.approx_eq(&quadratic.to)); |
| 2048 | count += 1; |
| 2049 | }); |
| 2050 | |
| 2051 | let cubic = CubicBezierSegment { |
| 2052 | from: point(1.0f32, 1.0), |
| 2053 | ctrl1: point(10.0, 2.0), |
| 2054 | ctrl2: point(1.0, 3.0), |
| 2055 | to: point(10.0, 4.0), |
| 2056 | }; |
| 2057 | |
| 2058 | let mut prev = cubic.from; |
| 2059 | let mut count = 0; |
| 2060 | cubic.for_each_quadratic_bezier(0.01, &mut |c| { |
| 2061 | assert!(c.from.approx_eq(&prev)); |
| 2062 | prev = c.to; |
| 2063 | count += 1; |
| 2064 | }); |
| 2065 | assert!(prev.approx_eq(&cubic.to)); |
| 2066 | assert!(count < 10); |
| 2067 | assert!(count > 4); |
| 2068 | } |
| 2069 | |