1use axiolid_contracts::{GeomError, GeomResult};
35use axiolid_core::{Frame2, Frame3, Interval, Point2, Point3, Scalar, Tolerance, Vec2, Vec3};
36use axiolid_curve::{
37 BSplineCurve, BSplineCurve2, BSplineCurve3, Circle2, Circle3, Curve2, Curve3, CurveEvaluator,
38 Ellipse2, Ellipse3, Line2, Line3, Polyline2, Polyline3,
39};
40
41use crate::nurbs::SplineAxis;
42
43#[derive(Debug, Clone, Copy, Default)]
48pub struct ScalarCurve;
49
50impl ScalarCurve {
51 #[must_use]
53 pub const fn new() -> Self {
54 Self
55 }
56}
57
58#[derive(Debug, Clone, Copy, PartialEq)]
64pub struct CurveJet<P, D> {
65 pub point: P,
67 pub first: D,
69 pub second: D,
71}
72
73#[must_use]
77pub fn domain2(curve: &Curve2) -> Interval {
78 match curve {
79 Curve2::Line(_) => Interval::UNIT,
82 Curve2::Circle(_) | Curve2::Ellipse(_) => full_turn(),
83 Curve2::Polyline(p) => polyline_domain(p.points.len(), p.closed),
84 Curve2::BSpline(b) => spline_domain(b),
85 Curve2::Intrinsic(i) if i.length.is_finite() && i.length > 0.0 => Interval {
89 start: 0.0,
90 end: i.length,
91 },
92 Curve2::Sinusoid(_) => full_turn(),
94 Curve2::Chain(c) => c.length().map_or(
96 Interval {
97 start: 0.0,
98 end: 0.0,
99 },
100 |end| Interval { start: 0.0, end },
101 ),
102 _ => Interval {
104 start: 0.0,
105 end: 0.0,
106 },
107 }
108}
109
110#[must_use]
112pub fn domain3(curve: &Curve3) -> Interval {
113 match curve {
114 Curve3::Line(_) => Interval::UNIT,
115 Curve3::Circle(_) | Curve3::Ellipse(_) => full_turn(),
116 Curve3::Polyline(p) => polyline_domain(p.points.len(), p.closed),
117 Curve3::BSpline(b) => spline_domain(b),
118 Curve3::Intrinsic(i) if i.length.is_finite() && i.length > 0.0 => Interval {
122 start: 0.0,
123 end: i.length,
124 },
125 Curve3::Banked(b) if b.cant.is_well_formed() => Interval {
128 start: 0.0,
129 end: b.span(),
130 },
131 Curve3::Elevated(e) => crate::elevated::elevated_domain(e),
134 _ => Interval {
135 start: 0.0,
136 end: 0.0,
137 },
138 }
139}
140
141fn full_turn() -> Interval {
142 Interval {
143 start: 0.0,
144 end: core::f64::consts::TAU,
145 }
146}
147
148fn polyline_domain(count: usize, closed: bool) -> Interval {
150 let segments = if closed {
151 count
152 } else {
153 count.saturating_sub(1)
154 };
155 Interval {
156 start: 0.0,
157 end: segments as Scalar,
158 }
159}
160
161fn spline_domain<P>(b: &BSplineCurve<P>) -> Interval {
164 SplineAxis::new(
165 &b.knots,
166 &b.multiplicities,
167 b.degree,
168 b.control_points.len(),
169 "B-spline curve",
170 )
171 .map_or(
172 Interval {
173 start: 0.0,
174 end: 0.0,
175 },
176 |axis| {
177 let (start, end) = axis.domain();
178 Interval { start, end }
179 },
180 )
181}
182
183pub fn evaluate2(curve: &Curve2, t: Scalar) -> GeomResult<Point2> {
187 finite(t)?;
188 let value = match curve {
189 Curve2::Line(l) => Ok(line_point(l.origin, l.direction, t)),
190 Curve2::Circle(c) => Ok(conic_point2(&c.frame, c.radius, c.radius, t)),
191 Curve2::Ellipse(e) => Ok(conic_point2(&e.frame, e.semi_axis_x, e.semi_axis_y, t)),
192 Curve2::Polyline(p) => polyline_point(&p.points, p.closed, t),
193 Curve2::BSpline(b) => de_boor(b, t, |p| [p.x, p.y], |c| Point2::new(c[0], c[1])),
194 Curve2::Intrinsic(i) => crate::arc_length::intrinsic_point(i, t),
198 Curve2::Chain(c) => crate::chain::chain_point(c, t),
200 Curve2::Sinusoid(w) => Ok(Point2::new(t, w.height(t))),
202 Curve2::QuadraticGraph(g) => g
204 .height(t)
205 .map(|v| Point2::new(t, v))
206 .ok_or_else(|| outside_graph(t)),
207 Curve2::AngleGraph(g) => g
209 .angle(t)
210 .map(|u| Point2::new(u, t))
211 .ok_or_else(|| outside_graph(t)),
212 Curve2::Implicit(c) => c.point(t).ok_or_else(|| outside_graph(t)),
214 Curve2::Lifted(l) => {
216 if let Some((section, first)) = pair_side(l) {
218 let (a, b, _) = section.solve(t).ok_or_else(|| outside_graph(t))?;
219 return finite2(if first { a } else { b }, "curve point");
220 }
221 let p = evaluate3(&l.curve, t)?;
222 l.unwrap_at(t, p).ok_or_else(|| outside_graph(t))
223 }
224 _ => Err(GeomError::Unsupported {
227 backend: axiolid_contracts::BackendId::new("axiolid-reference"),
228 operation: axiolid_contracts::Operation::CurveEvaluation,
229 }),
230 }?;
231 finite2(value, "curve point")
232}
233
234pub fn derivative2(curve: &Curve2, t: Scalar) -> GeomResult<Vec2> {
236 finite(t)?;
237 let value = match curve {
238 Curve2::Line(l) => Ok(l.direction),
239 Curve2::Circle(c) => Ok(conic_tangent2(&c.frame, c.radius, c.radius, t)),
240 Curve2::Ellipse(e) => Ok(conic_tangent2(&e.frame, e.semi_axis_x, e.semi_axis_y, t)),
241 Curve2::Polyline(p) => polyline_tangent(&p.points, p.closed, t),
242 Curve2::BSpline(b) => de_boor_derivative(b, t, |p| [p.x, p.y], |c| Vec2::new(c[0], c[1])),
243 Curve2::Intrinsic(i) => crate::arc_length::intrinsic_tangent(i, t),
247 Curve2::Chain(c) => crate::chain::chain_tangent(c, t),
248 Curve2::Sinusoid(w) => {
249 let (sin, cos) = t.sin_cos();
250 Ok(Vec2::new(1.0, -w.cosine * sin + w.sine * cos))
251 }
252 Curve2::QuadraticGraph(g) => g
253 .slope(t)
254 .map(|slope| Vec2::new(1.0, slope))
255 .ok_or_else(|| outside_graph(t)),
256 Curve2::AngleGraph(g) => g
257 .slope(t)
258 .map(|slope| Vec2::new(slope, 1.0))
259 .ok_or_else(|| outside_graph(t)),
260 Curve2::Implicit(c) => c.derivative(t).ok_or_else(|| outside_graph(t)),
261 Curve2::Lifted(l) => lifted_rates(l, t).map(|(d, _)| d),
262 _ => Err(GeomError::Unsupported {
265 backend: axiolid_contracts::BackendId::new("axiolid-reference"),
266 operation: axiolid_contracts::Operation::CurveEvaluation,
267 }),
268 }?;
269 finite2(value, "curve derivative")
270}
271
272pub fn second_derivative2(curve: &Curve2, t: Scalar) -> GeomResult<Vec2> {
274 finite(t)?;
275 let value = match curve {
276 Curve2::Line(_) | Curve2::Polyline(_) => Ok(Vec2::ZERO),
277 Curve2::Circle(c) => Ok(conic_second2(&c.frame, c.radius, c.radius, t)),
278 Curve2::Ellipse(e) => Ok(conic_second2(&e.frame, e.semi_axis_x, e.semi_axis_y, t)),
279 Curve2::BSpline(b) => {
280 de_boor_second_derivative(b, t, |p| [p.x, p.y], |c| Vec2::new(c[0], c[1]))
281 }
282 Curve2::Sinusoid(w) => {
283 let (sin, cos) = t.sin_cos();
284 Ok(Vec2::new(0.0, -w.cosine * cos - w.sine * sin))
285 }
286 Curve2::QuadraticGraph(g) => g
287 .bend(t)
288 .map(|bend| Vec2::new(0.0, bend))
289 .ok_or_else(|| outside_graph(t)),
290 Curve2::AngleGraph(g) => g
291 .bend(t)
292 .map(|bend| Vec2::new(bend, 0.0))
293 .ok_or_else(|| outside_graph(t)),
294 Curve2::Implicit(c) => c.second_derivative(t).ok_or_else(|| outside_graph(t)),
295 Curve2::Lifted(l) => lifted_rates(l, t).map(|(_, dd)| dd),
296 _ => Err(GeomError::Unsupported {
297 backend: axiolid_contracts::BackendId::new("axiolid-reference"),
298 operation: axiolid_contracts::Operation::CurveEvaluation,
299 }),
300 }?;
301 finite2(value, "curve second derivative")
302}
303
304pub fn bspline_jet2(curve: &BSplineCurve2, t: Scalar) -> GeomResult<CurveJet<Point2, Vec2>> {
306 Ok(CurveJet {
307 point: de_boor(curve, t, |p| [p.x, p.y], |c| Point2::new(c[0], c[1]))?,
308 first: de_boor_derivative(curve, t, |p| [p.x, p.y], |c| Vec2::new(c[0], c[1]))?,
309 second: de_boor_second_derivative(curve, t, |p| [p.x, p.y], |c| Vec2::new(c[0], c[1]))?,
310 })
311}
312
313pub fn jet2(curve: &Curve2, t: Scalar) -> GeomResult<CurveJet<Point2, Vec2>> {
315 Ok(CurveJet {
316 point: evaluate2(curve, t)?,
317 first: derivative2(curve, t)?,
318 second: second_derivative2(curve, t)?,
319 })
320}
321
322fn lifted_rates(l: &axiolid_curve::LiftedCurve2, t: Scalar) -> GeomResult<(Vec2, Vec2)> {
326 if let Some((section, first)) = pair_side(l) {
327 let (a, b, _) = section.rates(t).ok_or_else(|| outside_graph(t))?;
328 let (aa, bb, _) = section.second_rates(t).ok_or_else(|| outside_graph(t))?;
329 let pick = |x: Point2, y: Point2| if first { x } else { y };
330 let (d, dd) = (pick(a, b), pick(aa, bb));
331 return Ok((Vec2::new(d.x, d.y), Vec2::new(dd.x, dd.y)));
332 }
333 let p = evaluate3(&l.curve, t)?;
334 let uv = l.unwrap_at(t, p).ok_or_else(|| outside_graph(t))?;
335 let jet = l.carrier.jet(uv.x, uv.y);
336 let (a, b, c) = (jet.u.dot(jet.u), jet.u.dot(jet.v), jet.v.dot(jet.v));
337 let det = a * c - b * b;
338 if det == 0.0 || !det.is_finite() {
339 return Err(outside_graph(t));
340 }
341 let solve = |w: Vec3| {
342 let (g0, g1) = (jet.u.dot(w), jet.v.dot(w));
343 Vec2::new((c * g0 - b * g1) / det, (a * g1 - b * g0) / det)
344 };
345 let d = solve(derivative3(&l.curve, t)?);
346 let rest = second_derivative3(&l.curve, t)?
347 - (jet.uu * (d.x * d.x) + jet.uv * (2.0 * d.x * d.y) + jet.vv * (d.y * d.y));
348 Ok((d, solve(rest)))
349}
350
351fn pair_side(l: &axiolid_curve::LiftedCurve2) -> Option<(&axiolid_curve::PairSection3, bool)> {
354 match l.curve.as_ref() {
355 Curve3::PairSection(section) => section.side(&l.carrier).map(|first| (section, first)),
356 _ => None,
357 }
358}
359
360fn outside_graph(t: Scalar) -> GeomError {
363 GeomError::Degenerate(format!(
364 "quadratic section graph has no regular point at t = {t}"
365 ))
366}
367
368pub fn evaluate3(curve: &Curve3, t: Scalar) -> GeomResult<Point3> {
372 finite(t)?;
373 let value = match curve {
374 Curve3::Line(l) => Ok(line_point(l.origin, l.direction, t)),
375 Curve3::Circle(c) => Ok(conic_point3(&c.frame, c.radius, c.radius, t)),
376 Curve3::Ellipse(e) => Ok(conic_point3(&e.frame, e.semi_axis_x, e.semi_axis_y, t)),
377 Curve3::Polyline(p) => polyline_point(&p.points, p.closed, t),
378 Curve3::BSpline(b) => de_boor(b, t, |p| [p.x, p.y, p.z], |c| Point3::new(c[0], c[1], c[2])),
379 Curve3::Intrinsic(i) => crate::frenet::frenet_point(i, t),
384 Curve3::RuledSection(r) => r.point(t).ok_or_else(|| outside_graph(t)),
386 Curve3::TorusSection(r) => r.point(t).ok_or_else(|| outside_graph(t)),
387 Curve3::ImplicitSection(r) => r.point(t).ok_or_else(|| outside_graph(t)),
388 Curve3::PairSection(r) => r.point(t).ok_or_else(|| outside_graph(t)),
389 Curve3::Banked(b) => crate::banked::banked_point(b, t),
391 Curve3::Elevated(e) => crate::arc_length::elevated_point(e, t),
394 _ => Err(GeomError::Unsupported {
397 backend: axiolid_contracts::BackendId::new("axiolid-reference"),
398 operation: axiolid_contracts::Operation::CurveEvaluation,
399 }),
400 }?;
401 finite3(value, "curve point")
402}
403
404pub fn derivative3(curve: &Curve3, t: Scalar) -> GeomResult<Vec3> {
406 finite(t)?;
407 let value = match curve {
408 Curve3::Line(l) => Ok(l.direction),
409 Curve3::Circle(c) => Ok(conic_tangent3(&c.frame, c.radius, c.radius, t)),
410 Curve3::Ellipse(e) => Ok(conic_tangent3(&e.frame, e.semi_axis_x, e.semi_axis_y, t)),
411 Curve3::Polyline(p) => polyline_tangent(&p.points, p.closed, t),
412 Curve3::BSpline(b) => {
413 de_boor_derivative(b, t, |p| [p.x, p.y, p.z], |c| Vec3::new(c[0], c[1], c[2]))
414 }
415 Curve3::Intrinsic(i) => crate::frenet::frenet_tangent(i, t),
417 Curve3::RuledSection(r) => r.tangent(t).ok_or_else(|| outside_graph(t)),
418 Curve3::TorusSection(r) => r.tangent(t).ok_or_else(|| outside_graph(t)),
419 Curve3::ImplicitSection(r) => r.tangent(t).ok_or_else(|| outside_graph(t)),
420 Curve3::PairSection(r) => r.tangent(t).ok_or_else(|| outside_graph(t)),
421 Curve3::Banked(b) => crate::banked::banked_derivative(b, t),
423 Curve3::Elevated(e) => crate::elevated::elevated_derivative(e, t),
425 _ => Err(GeomError::Unsupported {
428 backend: axiolid_contracts::BackendId::new("axiolid-reference"),
429 operation: axiolid_contracts::Operation::CurveEvaluation,
430 }),
431 }?;
432 finite3(value, "curve derivative")
433}
434
435pub fn second_derivative3(curve: &Curve3, t: Scalar) -> GeomResult<Vec3> {
437 finite(t)?;
438 let value = match curve {
439 Curve3::Line(_) | Curve3::Polyline(_) => Ok(Vec3::ZERO),
440 Curve3::Circle(c) => Ok(conic_second3(&c.frame, c.radius, c.radius, t)),
441 Curve3::Ellipse(e) => Ok(conic_second3(&e.frame, e.semi_axis_x, e.semi_axis_y, t)),
442 Curve3::BSpline(b) => {
443 de_boor_second_derivative(b, t, |p| [p.x, p.y, p.z], |c| Vec3::new(c[0], c[1], c[2]))
444 }
445 Curve3::RuledSection(r) => r.bend(t).ok_or_else(|| outside_graph(t)),
446 Curve3::TorusSection(r) => r.bend(t).ok_or_else(|| outside_graph(t)),
447 Curve3::ImplicitSection(r) => r.bend(t).ok_or_else(|| outside_graph(t)),
448 Curve3::PairSection(r) => r.bend(t).ok_or_else(|| outside_graph(t)),
449 Curve3::Elevated(e) => crate::elevated::elevated_second_derivative(e, t),
451 Curve3::Banked(b) => crate::banked::banked_second_derivative(b, t),
452 _ => Err(GeomError::Unsupported {
453 backend: axiolid_contracts::BackendId::new("axiolid-reference"),
454 operation: axiolid_contracts::Operation::CurveEvaluation,
455 }),
456 }?;
457 finite3(value, "curve second derivative")
458}
459
460pub fn bspline_jet3(curve: &BSplineCurve3, t: Scalar) -> GeomResult<CurveJet<Point3, Vec3>> {
462 Ok(CurveJet {
463 point: de_boor(
464 curve,
465 t,
466 |p| [p.x, p.y, p.z],
467 |c| Point3::new(c[0], c[1], c[2]),
468 )?,
469 first: de_boor_derivative(
470 curve,
471 t,
472 |p| [p.x, p.y, p.z],
473 |c| Vec3::new(c[0], c[1], c[2]),
474 )?,
475 second: de_boor_second_derivative(
476 curve,
477 t,
478 |p| [p.x, p.y, p.z],
479 |c| Vec3::new(c[0], c[1], c[2]),
480 )?,
481 })
482}
483
484pub fn jet3(curve: &Curve3, t: Scalar) -> GeomResult<CurveJet<Point3, Vec3>> {
486 Ok(CurveJet {
487 point: evaluate3(curve, t)?,
488 first: derivative3(curve, t)?,
489 second: second_derivative3(curve, t)?,
490 })
491}
492
493fn finite(t: Scalar) -> GeomResult<()> {
496 if t.is_finite() {
497 Ok(())
498 } else {
499 Err(GeomError::InvalidInput(format!(
500 "curve parameter must be finite, got {t}"
501 )))
502 }
503}
504
505fn finite2(value: Vec2, what: &str) -> GeomResult<Vec2> {
506 if value.is_finite() {
507 Ok(value)
508 } else {
509 Err(GeomError::Degenerate(format!("{what} is non-finite")))
510 }
511}
512
513fn finite3(value: Vec3, what: &str) -> GeomResult<Vec3> {
514 if value.is_finite() {
515 Ok(value)
516 } else {
517 Err(GeomError::Degenerate(format!("{what} is non-finite")))
518 }
519}
520
521fn line_point<P>(origin: P, direction: P, t: Scalar) -> P
522where
523 P: core::ops::Add<Output = P> + core::ops::Mul<Scalar, Output = P>,
524{
525 origin + direction * t
526}
527
528fn conic_point2(frame: &Frame2, rx: Scalar, ry: Scalar, t: Scalar) -> Point2 {
529 frame.origin + frame.x * (rx * t.cos()) + frame.y * (ry * t.sin())
530}
531
532fn conic_tangent2(frame: &Frame2, rx: Scalar, ry: Scalar, t: Scalar) -> Vec2 {
533 frame.x * (-rx * t.sin()) + frame.y * (ry * t.cos())
534}
535
536fn conic_second2(frame: &Frame2, rx: Scalar, ry: Scalar, t: Scalar) -> Vec2 {
537 frame.x * (-rx * t.cos()) + frame.y * (-ry * t.sin())
538}
539
540fn conic_point3(frame: &Frame3, rx: Scalar, ry: Scalar, t: Scalar) -> Point3 {
541 frame.origin + frame.x * (rx * t.cos()) + frame.y * (ry * t.sin())
542}
543
544fn conic_tangent3(frame: &Frame3, rx: Scalar, ry: Scalar, t: Scalar) -> Vec3 {
545 frame.x * (-rx * t.sin()) + frame.y * (ry * t.cos())
546}
547
548fn conic_second3(frame: &Frame3, rx: Scalar, ry: Scalar, t: Scalar) -> Vec3 {
549 frame.x * (-rx * t.cos()) + frame.y * (-ry * t.sin())
550}
551
552fn polyline_span(count: usize, closed: bool, t: Scalar) -> Option<(usize, usize, Scalar)> {
556 let segments = if closed {
557 count
558 } else {
559 count.saturating_sub(1)
560 };
561 if count < 2 || segments == 0 {
562 return None;
563 }
564 let clamped = t.clamp(0.0, segments as Scalar);
567 let mut index = clamped.floor() as usize;
568 if index >= segments {
569 index = segments - 1;
570 }
571 let local = clamped - index as Scalar;
572 let next = (index + 1) % count;
573 Some((index, next, local))
574}
575
576fn polyline_point<P>(points: &[P], closed: bool, t: Scalar) -> GeomResult<P>
577where
578 P: Copy
579 + core::ops::Add<Output = P>
580 + core::ops::Sub<Output = P>
581 + core::ops::Mul<Scalar, Output = P>,
582{
583 let (i, j, local) = polyline_span(points.len(), closed, t).ok_or_else(|| {
584 GeomError::Degenerate(format!(
585 "polyline with {} points has no evaluable segment",
586 points.len()
587 ))
588 })?;
589 Ok(points[i] + (points[j] - points[i]) * local)
590}
591
592fn polyline_tangent<P>(points: &[P], closed: bool, t: Scalar) -> GeomResult<P>
593where
594 P: Copy + core::ops::Sub<Output = P>,
595{
596 let (i, j, _) = polyline_span(points.len(), closed, t).ok_or_else(|| {
597 GeomError::Degenerate(format!(
598 "polyline with {} points has no evaluable segment",
599 points.len()
600 ))
601 })?;
602 Ok(points[j] - points[i])
604}
605
606pub(crate) fn span_in(knots: &[Scalar], n: usize, d: usize, u: Scalar) -> usize {
614 let mut span = d;
615 for (k, knot) in knots.iter().enumerate().take(n).skip(d) {
616 if *knot <= u {
617 span = k;
618 } else {
619 break;
620 }
621 }
622 span
623}
624
625pub(crate) fn de_boor_recurrence<const N: usize>(
632 knots: &[Scalar],
633 span: usize,
634 d: usize,
635 u: Scalar,
636 points: &mut [[Scalar; N]],
637 weights: &mut [Scalar],
638) {
639 for r in 1..=d {
640 for j in (r..=d).rev() {
641 let i = span - d + j;
642 let denom = knots[i + d + 1 - r] - knots[i];
643 let alpha = if denom.abs() > 0.0 {
644 (u - knots[i]) / denom
645 } else {
646 0.0
647 };
648 let (prev, cur) = (points[j - 1], points[j]);
649 points[j] = core::array::from_fn(|k| prev[k] * (1.0 - alpha) + cur[k] * alpha);
650 weights[j] = weights[j - 1] * (1.0 - alpha) + weights[j] * alpha;
651 }
652 }
653}
654
655fn spline_span<P>(b: &BSplineCurve<P>, t: Scalar) -> GeomResult<(Vec<Scalar>, usize, usize)> {
659 let axis = SplineAxis::new(
660 &b.knots,
661 &b.multiplicities,
662 b.degree,
663 b.control_points.len(),
664 "B-spline curve",
665 )?;
666 if let Some(weights) = &b.weights {
667 if weights.len() != b.control_points.len() {
668 return Err(GeomError::InvalidInput(format!(
669 "B-spline has {} weights for {} control points",
670 weights.len(),
671 b.control_points.len()
672 )));
673 }
674 if weights
675 .iter()
676 .any(|weight| !weight.is_finite() || *weight <= 0.0)
677 {
678 return Err(GeomError::InvalidInput(
679 "B-spline weights must be finite and strictly positive".to_owned(),
680 ));
681 }
682 }
683 let t = axis.clamp(t);
684 let span = span_in(&axis.knots, axis.count, axis.degree, t);
685 Ok((axis.knots, span, axis.degree))
686}
687
688fn finite_control_points<P, const N: usize, F>(
692 control_points: &[P],
693 to: &F,
694) -> GeomResult<Vec<[Scalar; N]>>
695where
696 F: Fn(&P) -> [Scalar; N],
697{
698 let points: Vec<[Scalar; N]> = control_points.iter().map(to).collect();
699 if points
700 .iter()
701 .flatten()
702 .any(|coordinate| !coordinate.is_finite())
703 {
704 return Err(GeomError::InvalidInput(
705 "B-spline control points must be finite".to_owned(),
706 ));
707 }
708 Ok(points)
709}
710
711fn de_boor<P, const N: usize, F, G, Q>(
717 b: &BSplineCurve<P>,
718 t: Scalar,
719 to: F,
720 from: G,
721) -> GeomResult<Q>
722where
723 F: Fn(&P) -> [Scalar; N],
724 G: Fn([Scalar; N]) -> Q,
725{
726 let (knots, span, d) = spline_span(b, t)?;
727 let control_points = finite_control_points(&b.control_points, &to)?;
728 let u = t.clamp(knots[d], knots[b.control_points.len()]);
729
730 let mut work: Vec<[Scalar; N]> = Vec::with_capacity(d + 1);
733 let mut weights: Vec<Scalar> = Vec::with_capacity(d + 1);
734 for j in 0..=d {
735 let idx = span - d + j;
736 let w = b.weights.as_ref().map_or(1.0, |ws| ws[idx]);
737 let c = control_points[idx];
738 let homogeneous = core::array::from_fn(|k| c[k] * w);
741 if homogeneous.iter().any(|value| !value.is_finite()) {
742 return Err(GeomError::Degenerate(
743 "B-spline homogeneous control point overflowed".to_owned(),
744 ));
745 }
746 work.push(homogeneous);
747 weights.push(w);
748 }
749
750 de_boor_recurrence(&knots, span, d, u, &mut work, &mut weights);
753
754 let w = weights[d];
755 if !w.is_finite() || w == 0.0 {
756 return Err(GeomError::Degenerate(
757 "B-spline weight collapsed to zero".to_owned(),
758 ));
759 }
760 Ok(from(core::array::from_fn(|k| work[d][k] / w)))
761}
762
763fn de_boor_derivative<P, const N: usize, F, G, Q>(
772 b: &BSplineCurve<P>,
773 t: Scalar,
774 to: F,
775 from: G,
776) -> GeomResult<Q>
777where
778 F: Fn(&P) -> [Scalar; N],
779 G: Fn([Scalar; N]) -> Q,
780{
781 let (knots, _, d) = spline_span(b, t)?;
782 let control_points = finite_control_points(&b.control_points, &to)?;
783 let n = b.control_points.len();
784 let u = t.clamp(knots[d], knots[n]);
785
786 let hom: Vec<[Scalar; N]> = (0..n)
788 .map(|i| {
789 let w = b.weights.as_ref().map_or(1.0, |ws| ws[i]);
790 let c = control_points[i];
791 core::array::from_fn(|k| c[k] * w)
792 })
793 .collect();
794 if hom.iter().flatten().any(|value| !value.is_finite()) {
795 return Err(GeomError::Degenerate(
796 "B-spline homogeneous control point overflowed".to_owned(),
797 ));
798 }
799 let hw: Vec<Scalar> = (0..n)
800 .map(|i| b.weights.as_ref().map_or(1.0, |ws| ws[i]))
801 .collect();
802
803 let mut dhom: Vec<[Scalar; N]> = Vec::with_capacity(n - 1);
805 let mut dhw: Vec<Scalar> = Vec::with_capacity(n - 1);
806 for i in 0..n - 1 {
807 let denom = knots[i + d + 1] - knots[i + 1];
808 let f = if denom.abs() > 0.0 {
809 d as Scalar / denom
810 } else {
811 0.0
812 };
813 dhom.push(core::array::from_fn(|k| (hom[i + 1][k] - hom[i][k]) * f));
814 dhw.push((hw[i + 1] - hw[i]) * f);
815 }
816
817 let dknots = &knots[1..knots.len() - 1];
819 let (da, dw) = eval_homogeneous(dknots, d - 1, &dhom, &dhw, u);
820 let (a, w) = eval_homogeneous(&knots, d, &hom, &hw, u);
822
823 if !w.is_finite() || w == 0.0 {
824 return Err(GeomError::Degenerate(
825 "B-spline weight collapsed to zero".to_owned(),
826 ));
827 }
828 Ok(from(core::array::from_fn(|k| {
830 (da[k] - (a[k] / w) * dw) / w
831 })))
832}
833
834fn de_boor_second_derivative<P, const N: usize, F, G, Q>(
839 b: &BSplineCurve<P>,
840 t: Scalar,
841 to: F,
842 from: G,
843) -> GeomResult<Q>
844where
845 F: Fn(&P) -> [Scalar; N],
846 G: Fn([Scalar; N]) -> Q,
847{
848 let (knots, _, degree) = spline_span(b, t)?;
849 let control_points = finite_control_points(&b.control_points, &to)?;
850 let count = b.control_points.len();
851 let u = t.clamp(knots[degree], knots[count]);
852
853 let points: Vec<[Scalar; N]> = (0..count)
854 .map(|i| {
855 let weight = b.weights.as_ref().map_or(1.0, |weights| weights[i]);
856 core::array::from_fn(|axis| control_points[i][axis] * weight)
857 })
858 .collect();
859 if points.iter().flatten().any(|value| !value.is_finite()) {
860 return Err(GeomError::Degenerate(
861 "B-spline homogeneous control point overflowed".to_owned(),
862 ));
863 }
864 let weights: Vec<Scalar> = (0..count)
865 .map(|i| b.weights.as_ref().map_or(1.0, |values| values[i]))
866 .collect();
867
868 let (point, weight) = eval_homogeneous(&knots, degree, &points, &weights, u);
869 if !weight.is_finite() || weight == 0.0 {
870 return Err(GeomError::Degenerate(
871 "B-spline weight collapsed to zero".to_owned(),
872 ));
873 }
874
875 let (first_points, first_weights) = derivative_controls(&points, &weights, &knots, degree);
876 let first_knots = &knots[1..knots.len() - 1];
877 let (first, first_weight) =
878 eval_homogeneous(first_knots, degree - 1, &first_points, &first_weights, u);
879 let position: [Scalar; N] = core::array::from_fn(|axis| point[axis] / weight);
880 let first_projected: [Scalar; N] =
881 core::array::from_fn(|axis| (first[axis] - position[axis] * first_weight) / weight);
882
883 let (second, second_weight) = if degree == 1 {
884 ([0.0; N], 0.0)
885 } else {
886 let (second_points, second_weights) =
887 derivative_controls(&first_points, &first_weights, first_knots, degree - 1);
888 let second_knots = &first_knots[1..first_knots.len() - 1];
889 eval_homogeneous(second_knots, degree - 2, &second_points, &second_weights, u)
890 };
891 Ok(from(core::array::from_fn(|axis| {
892 (second[axis] - 2.0 * first_weight * first_projected[axis] - second_weight * position[axis])
893 / weight
894 })))
895}
896
897fn derivative_controls<const N: usize>(
899 points: &[[Scalar; N]],
900 weights: &[Scalar],
901 knots: &[Scalar],
902 degree: usize,
903) -> (Vec<[Scalar; N]>, Vec<Scalar>) {
904 let mut derivative_points = Vec::with_capacity(points.len() - 1);
905 let mut derivative_weights = Vec::with_capacity(weights.len() - 1);
906 for i in 0..points.len() - 1 {
907 let denominator = knots[i + degree + 1] - knots[i + 1];
908 let factor = if denominator.abs() > 0.0 {
909 degree as Scalar / denominator
910 } else {
911 0.0
912 };
913 derivative_points.push(core::array::from_fn(|axis| {
914 (points[i + 1][axis] - points[i][axis]) * factor
915 }));
916 derivative_weights.push((weights[i + 1] - weights[i]) * factor);
917 }
918 (derivative_points, derivative_weights)
919}
920
921pub(crate) fn eval_homogeneous<const N: usize>(
923 knots: &[Scalar],
924 d: usize,
925 hom: &[[Scalar; N]],
926 hw: &[Scalar],
927 u: Scalar,
928) -> ([Scalar; N], Scalar) {
929 let n = hom.len();
930 if d == 0 {
931 let mut idx = 0;
933 for (k, knot) in knots.iter().enumerate().take(n) {
934 if *knot <= u {
935 idx = k;
936 }
937 }
938 return (hom[idx.min(n - 1)], hw[idx.min(n - 1)]);
939 }
940 let mut span = d;
941 for (k, knot) in knots.iter().enumerate().take(n).skip(d) {
942 if *knot <= u {
943 span = k;
944 } else {
945 break;
946 }
947 }
948 let mut work: Vec<[Scalar; N]> = (0..=d).map(|j| hom[span - d + j]).collect();
949 let mut weights: Vec<Scalar> = (0..=d).map(|j| hw[span - d + j]).collect();
950 for r in 1..=d {
951 for j in (r..=d).rev() {
952 let i = span - d + j;
953 let denom = knots[i + d + 1 - r] - knots[i];
954 let alpha = if denom.abs() > 0.0 {
955 (u - knots[i]) / denom
956 } else {
957 0.0
958 };
959 let (prev, cur) = (work[j - 1], work[j]);
960 work[j] = core::array::from_fn(|k| prev[k] * (1.0 - alpha) + cur[k] * alpha);
961 weights[j] = weights[j - 1] * (1.0 - alpha) + weights[j] * alpha;
962 }
963 }
964 (work[d], weights[d])
965}
966
967pub fn flatten2(
989 curve: &Curve2,
990 domain: Interval,
991 chord_tolerance: Scalar,
992 max_depth: u32,
993) -> GeomResult<Vec<Point2>> {
994 const MAX_POINTS: usize = 1 << 16;
998 if !(chord_tolerance.is_finite()
999 && chord_tolerance.is_sign_positive()
1000 && chord_tolerance != 0.0)
1001 {
1002 return Err(GeomError::InvalidInput(format!(
1003 "chord tolerance must be positive and finite, got {chord_tolerance}"
1004 )));
1005 }
1006 if let Curve2::Line(_) = curve {
1009 return Ok(vec![
1010 evaluate2(curve, domain.start)?,
1011 evaluate2(curve, domain.end)?,
1012 ]);
1013 }
1014 if let Curve2::Polyline(p) = curve {
1015 let natural = polyline_domain(p.points.len(), p.closed);
1021 let requested = (domain.end - domain.start).abs();
1022 if natural.end > 1.0 && requested <= 1.0 {
1023 return Err(GeomError::InvalidInput(format!(
1024 "polyline domain {:?} spans {requested} of {} segments; a \
1025 polyline parameter is one unit per segment, so this would \
1026 discard {} vertices",
1027 domain,
1028 natural.end,
1029 p.points.len().saturating_sub(2)
1030 )));
1031 }
1032 return polyline_flatten(&p.points, p.closed, domain, |t| evaluate2(curve, t));
1033 }
1034
1035 let mut out = vec![evaluate2(curve, domain.start)?];
1036 let eval = |t| evaluate2(curve, t);
1037 let bound = |a, b| crate::bound::chord_bound2(curve, a, b);
1038 for (a, b) in smooth_pieces(domain, &crate::bound::continuity_breaks2(curve, 1)) {
1039 subdivide(
1040 &eval,
1041 &bound,
1042 a,
1043 b,
1044 chord_tolerance,
1045 max_depth.min(MAX_DEPTH_CEILING),
1046 MAX_POINTS,
1047 &mut out,
1048 )?;
1049 out.push(evaluate2(curve, b)?);
1050 }
1051 Ok(out)
1052}
1053
1054fn smooth_pieces(domain: Interval, breaks: &[Scalar]) -> Vec<(Scalar, Scalar)> {
1060 let (lo, hi) = if domain.start <= domain.end {
1061 (domain.start, domain.end)
1062 } else {
1063 (domain.end, domain.start)
1064 };
1065 let mut cuts: Vec<Scalar> = breaks
1066 .iter()
1067 .copied()
1068 .filter(|&t| t > lo && t < hi)
1069 .collect();
1070 cuts.sort_by(Scalar::total_cmp);
1071 cuts.dedup();
1072 if domain.start > domain.end {
1073 cuts.reverse();
1074 }
1075 let mut out = Vec::with_capacity(cuts.len() + 1);
1076 let mut from = domain.start;
1077 for cut in cuts {
1078 out.push((from, cut));
1079 from = cut;
1080 }
1081 out.push((from, domain.end));
1082 out
1083}
1084
1085const MAX_DEPTH_CEILING: u32 = 20;
1090
1091trait ChordPoint: Copy {
1104 fn sub(self, other: Self) -> Self;
1105 fn add_scaled(self, direction: Self, scale: Scalar) -> Self;
1106 fn dot(self, other: Self) -> Scalar;
1107 fn length(self) -> Scalar;
1108 fn length_squared(self) -> Scalar;
1109}
1110
1111impl ChordPoint for Point2 {
1112 fn sub(self, other: Self) -> Self {
1113 self - other
1114 }
1115 fn add_scaled(self, direction: Self, scale: Scalar) -> Self {
1116 self + direction * scale
1117 }
1118 fn dot(self, other: Self) -> Scalar {
1119 Point2::dot(self, other)
1120 }
1121 fn length(self) -> Scalar {
1122 Point2::length(self)
1123 }
1124 fn length_squared(self) -> Scalar {
1125 Point2::length_squared(self)
1126 }
1127}
1128
1129impl ChordPoint for Point3 {
1130 fn sub(self, other: Self) -> Self {
1131 self - other
1132 }
1133 fn add_scaled(self, direction: Self, scale: Scalar) -> Self {
1134 self + direction * scale
1135 }
1136 fn dot(self, other: Self) -> Scalar {
1137 Point3::dot(self, other)
1138 }
1139 fn length(self) -> Scalar {
1140 Point3::length(self)
1141 }
1142 fn length_squared(self) -> Scalar {
1143 Point3::length_squared(self)
1144 }
1145}
1146
1147fn sagitta<P: ChordPoint>(a: P, b: P, m: P) -> Scalar {
1149 let ab = b.sub(a);
1150 let len2 = ab.length_squared();
1151 if len2 <= 0.0 {
1152 return m.sub(a).length();
1155 }
1156 let t = (m.sub(a).dot(ab) / len2).clamp(0.0, 1.0);
1157 m.sub(a.add_scaled(ab, t)).length()
1158}
1159
1160#[allow(clippy::too_many_arguments)]
1175fn subdivide<P, F, B>(
1176 eval: &F,
1177 bound: &B,
1178 a: Scalar,
1179 b: Scalar,
1180 tol: Scalar,
1181 depth: u32,
1182 budget: usize,
1183 out: &mut Vec<P>,
1184) -> GeomResult<()>
1185where
1186 P: ChordPoint,
1187 F: Fn(Scalar) -> GeomResult<P>,
1188 B: Fn(Scalar, Scalar) -> Option<Scalar>,
1189{
1190 if out.len() >= budget {
1191 return Err(GeomError::Degenerate(format!(
1192 "curve flattening exceeded {budget} points before meeting the \
1193 chord tolerance {tol}; the curve may be degenerate"
1194 )));
1195 }
1196 let mid = 0.5 * (a + b);
1197 let pa = eval(a)?;
1198 let pb = eval(b)?;
1199 if !(mid > a && mid < b) {
1205 if sagitta(pa, pb, pa) <= tol && (pb.sub(pa)).length() <= tol {
1206 return Ok(());
1207 }
1208 return Err(GeomError::Degenerate(format!(
1209 "curve parameter interval ({a}, {b}) is too small to bisect but \
1210 its chord still exceeds the tolerance {tol}"
1211 )));
1212 }
1213 let pm = eval(mid)?;
1214 if sagitta(pa, pb, pm) <= tol && bound(a, b).is_none_or(|certified| certified <= tol) {
1215 return Ok(());
1217 }
1218 if depth == 0 {
1219 return Err(GeomError::BudgetExceeded {
1220 resource: "curve flattening depth",
1221 });
1222 }
1223 subdivide(eval, bound, a, mid, tol, depth - 1, budget, out)?;
1224 out.push(pm);
1225 subdivide(eval, bound, mid, b, tol, depth - 1, budget, out)?;
1226 Ok(())
1227}
1228
1229fn polyline_flatten<P, F>(
1231 points: &[P],
1232 closed: bool,
1233 domain: Interval,
1234 eval: F,
1235) -> GeomResult<Vec<P>>
1236where
1237 P: Copy,
1238 F: Fn(Scalar) -> GeomResult<P>,
1239{
1240 let segments = if closed {
1241 points.len()
1242 } else {
1243 points.len().saturating_sub(1)
1244 };
1245 if segments == 0 {
1246 return Err(GeomError::Degenerate(
1247 "polyline has no evaluable segment".to_owned(),
1248 ));
1249 }
1250 let lo = domain.start.min(domain.end);
1251 let hi = domain.start.max(domain.end);
1252 let mut out = vec![eval(lo)?];
1253 let first = lo.floor() as i64 + 1;
1255 let last = hi.ceil() as i64 - 1;
1256 for k in first..=last {
1257 let t = k as Scalar;
1258 if t > lo && t < hi {
1259 out.push(eval(t)?);
1260 }
1261 }
1262 out.push(eval(hi)?);
1263 Ok(out)
1264}
1265
1266impl CurveEvaluator<Curve2> for ScalarCurve {
1269 type Point = Point2;
1270 type Derivative = Vec2;
1271 type Error = GeomError;
1272
1273 fn domain(&self, curve: &Curve2) -> Interval {
1274 domain2(curve)
1275 }
1276
1277 fn evaluate(
1278 &self,
1279 curve: &Curve2,
1280 t: Scalar,
1281 _tolerance: Tolerance,
1282 ) -> Result<Self::Point, Self::Error> {
1283 evaluate2(curve, t)
1284 }
1285
1286 fn derivative(
1287 &self,
1288 curve: &Curve2,
1289 t: Scalar,
1290 _tolerance: Tolerance,
1291 ) -> Result<Self::Derivative, Self::Error> {
1292 derivative2(curve, t)
1293 }
1294}
1295
1296impl CurveEvaluator<Curve3> for ScalarCurve {
1297 type Point = Point3;
1298 type Derivative = Vec3;
1299 type Error = GeomError;
1300
1301 fn domain(&self, curve: &Curve3) -> Interval {
1302 domain3(curve)
1303 }
1304
1305 fn evaluate(
1306 &self,
1307 curve: &Curve3,
1308 t: Scalar,
1309 _tolerance: Tolerance,
1310 ) -> Result<Self::Point, Self::Error> {
1311 evaluate3(curve, t)
1312 }
1313
1314 fn derivative(
1315 &self,
1316 curve: &Curve3,
1317 t: Scalar,
1318 _tolerance: Tolerance,
1319 ) -> Result<Self::Derivative, Self::Error> {
1320 derivative3(curve, t)
1321 }
1322}
1323
1324#[allow(unused)]
1326fn _type_anchors(_: Circle2, _: Circle3, _: Ellipse2, _: Ellipse3, _: Line2, _: Line3) {}
1327#[allow(unused)]
1328fn _poly_anchors(_: Polyline2, _: Polyline3) {}
1329
1330pub fn flatten3(
1338 curve: &Curve3,
1339 domain: Interval,
1340 chord_tolerance: Scalar,
1341 max_depth: u32,
1342) -> GeomResult<Vec<Point3>> {
1343 const MAX_POINTS: usize = 1 << 16;
1347 if !(chord_tolerance.is_finite()
1348 && chord_tolerance.is_sign_positive()
1349 && chord_tolerance != 0.0)
1350 {
1351 return Err(GeomError::InvalidInput(format!(
1352 "chord tolerance must be positive and finite, got {chord_tolerance}"
1353 )));
1354 }
1355 if let Curve3::Line(_) = curve {
1358 return Ok(vec![
1359 evaluate3(curve, domain.start)?,
1360 evaluate3(curve, domain.end)?,
1361 ]);
1362 }
1363 if let Curve3::Polyline(p) = curve {
1364 let natural = polyline_domain(p.points.len(), p.closed);
1368 let requested = (domain.end - domain.start).abs();
1369 if natural.end > 1.0 && requested <= 1.0 {
1370 return Err(GeomError::InvalidInput(format!(
1371 "polyline domain {:?} spans {requested} of {} segments; a \
1372 polyline parameter is one unit per segment, so this would \
1373 discard {} vertices",
1374 domain,
1375 natural.end,
1376 p.points.len().saturating_sub(2)
1377 )));
1378 }
1379 return polyline_flatten(&p.points, p.closed, domain, |t| evaluate3(curve, t));
1380 }
1381
1382 let eval = |t| evaluate3(curve, t);
1383 let bound = |a, b| crate::bound::chord_bound3(curve, a, b);
1384 let mut out = vec![eval(domain.start)?];
1385 for (a, b) in smooth_pieces(domain, &crate::bound::continuity_breaks3(curve, 1)) {
1386 subdivide(
1387 &eval,
1388 &bound,
1389 a,
1390 b,
1391 chord_tolerance,
1392 max_depth.min(MAX_DEPTH_CEILING),
1393 MAX_POINTS,
1394 &mut out,
1395 )?;
1396 out.push(eval(b)?);
1397 }
1398 Ok(out)
1399}
1400
1401fn no_closed_form_inversion() -> GeomError {
1406 GeomError::InvalidInput(
1407 "curve family has no closed form inversion; a point trim on this basis \
1408 would require iteration, which trim resolution does not perform"
1409 .to_owned(),
1410 )
1411}
1412
1413fn point_not_on_curve(distance: Scalar, tolerance: Scalar) -> GeomError {
1415 GeomError::InvalidInput(format!(
1416 "point is {distance} from the curve, outside the {tolerance} tolerance; \
1417 refusing rather than projecting it onto the nearest parameter"
1418 ))
1419}
1420
1421fn invert_line(
1427 origin: impl Into<[Scalar; 3]>,
1428 direction: [Scalar; 3],
1429 point: [Scalar; 3],
1430 tolerance: Scalar,
1431) -> GeomResult<Scalar> {
1432 let origin = origin.into();
1433 let dd = direction.iter().map(|c| c * c).sum::<Scalar>();
1434 if !dd.is_finite() || dd <= Scalar::EPSILON {
1435 return Err(GeomError::InvalidInput(
1436 "line direction is degenerate, so no parameter names a point".to_owned(),
1437 ));
1438 }
1439 let offset = [
1440 point[0] - origin[0],
1441 point[1] - origin[1],
1442 point[2] - origin[2],
1443 ];
1444 let t = offset
1445 .iter()
1446 .zip(direction.iter())
1447 .map(|(o, d)| o * d)
1448 .sum::<Scalar>()
1449 / dd;
1450 let residual = [
1453 offset[0] - direction[0] * t,
1454 offset[1] - direction[1] * t,
1455 offset[2] - direction[2] * t,
1456 ];
1457 let distance = residual.iter().map(|c| c * c).sum::<Scalar>().sqrt();
1458 if distance > tolerance {
1459 return Err(point_not_on_curve(distance, tolerance));
1460 }
1461 Ok(t)
1462}
1463
1464fn invert_conic(
1470 local_x: Scalar,
1471 local_y: Scalar,
1472 semi_x: Scalar,
1473 semi_y: Scalar,
1474) -> GeomResult<Scalar> {
1475 if !(semi_x.is_finite() && semi_y.is_finite()) || semi_x <= 0.0 || semi_y <= 0.0 {
1476 return Err(GeomError::InvalidInput(
1477 "conic semi-axes must be finite and positive to invert a point".to_owned(),
1478 ));
1479 }
1480 let angle = (local_y / semi_y).atan2(local_x / semi_x);
1481 if !angle.is_finite() {
1482 return Err(GeomError::InvalidInput(
1483 "conic inversion produced a non-finite angle".to_owned(),
1484 ));
1485 }
1486 Ok(angle.rem_euclid(std::f64::consts::TAU))
1489}
1490
1491pub fn invert2(curve: &Curve2, point: Point2, tolerance: Tolerance) -> GeomResult<Scalar> {
1503 finite2(point, "inversion point")?;
1504 let linear = tolerance.linear();
1505 match curve {
1506 Curve2::Line(l) => invert_line(
1507 [l.origin.x, l.origin.y, 0.0],
1508 [l.direction.x, l.direction.y, 0.0],
1509 [point.x, point.y, 0.0],
1510 linear,
1511 ),
1512 Curve2::Circle(c) => {
1513 let t = invert_conic_in_frame2(&c.frame, point, c.radius, c.radius)?;
1514 verify2(curve, t, point, linear)
1515 }
1516 Curve2::Ellipse(e) => {
1517 let t = invert_conic_in_frame2(&e.frame, point, e.semi_axis_x, e.semi_axis_y)?;
1518 verify2(curve, t, point, linear)
1519 }
1520 Curve2::Sinusoid(_) | Curve2::QuadraticGraph(_) => verify2(curve, point.x, point, linear),
1523 Curve2::AngleGraph(g) => {
1526 let u = g.angle(point.y).ok_or_else(|| outside_graph(point.y))?;
1527 let turns = ((point.x - u) / std::f64::consts::TAU).round();
1528 let shifted = Point2::new(point.x - turns * std::f64::consts::TAU, point.y);
1529 verify2(curve, point.y, shifted, linear)
1530 }
1531 Curve2::Lifted(l) => {
1533 let lifted = l.carrier.jet(point.x, point.y).point;
1534 let t = invert3(&l.curve, lifted, tolerance)?;
1535 verify2(curve, t, point, linear)
1536 }
1537 Curve2::Implicit(c) => {
1539 let t = c
1540 .parameter_of(point)
1541 .ok_or_else(|| point_not_on_curve(Scalar::INFINITY, linear))?;
1542 verify2(curve, t, point, linear)
1543 }
1544 _ => Err(no_closed_form_inversion()),
1545 }
1546}
1547
1548fn invert_conic_in_frame2(
1550 frame: &axiolid_core::Frame2,
1551 point: Point2,
1552 semi_x: Scalar,
1553 semi_y: Scalar,
1554) -> GeomResult<Scalar> {
1555 let offset = point - frame.origin;
1556 invert_conic(offset.dot(frame.x), offset.dot(frame.y), semi_x, semi_y)
1557}
1558
1559fn verify2(curve: &Curve2, t: Scalar, point: Point2, tolerance: Scalar) -> GeomResult<Scalar> {
1566 let found = evaluate2(curve, t)?;
1567 let distance = (found - point).length();
1568 if distance > tolerance {
1569 return Err(point_not_on_curve(distance, tolerance));
1570 }
1571 Ok(t)
1572}
1573
1574pub fn invert3(curve: &Curve3, point: Point3, tolerance: Tolerance) -> GeomResult<Scalar> {
1582 finite3(point, "inversion point")?;
1583 let linear = tolerance.linear();
1584 match curve {
1585 Curve3::Line(l) => invert_line(
1586 [l.origin.x, l.origin.y, l.origin.z],
1587 [l.direction.x, l.direction.y, l.direction.z],
1588 [point.x, point.y, point.z],
1589 linear,
1590 ),
1591 Curve3::Circle(c) => {
1592 let t = invert_conic_in_frame3(&c.frame, point, c.radius, c.radius)?;
1593 verify3(curve, t, point, linear)
1594 }
1595 Curve3::Ellipse(e) => {
1596 let t = invert_conic_in_frame3(&e.frame, point, e.semi_axis_x, e.semi_axis_y)?;
1597 verify3(curve, t, point, linear)
1598 }
1599 Curve3::RuledSection(r) => {
1602 let carrier = axiolid_curve::Carrier::Ruled(r.carrier);
1603 let (u, _) = carrier.parameters(point);
1604 first_on_curve(curve, &turns_of(u), point, linear)
1605 }
1606 Curve3::TorusSection(r) => {
1607 let carrier = axiolid_curve::Carrier::Torus(r.torus);
1608 let (_, v) = carrier.parameters(point);
1609 first_on_curve(curve, &turns_of(v), point, linear)
1610 }
1611 Curve3::PairSection(r) => {
1613 let t = r
1614 .parameter_of(point)
1615 .ok_or_else(|| point_not_on_curve(Scalar::INFINITY, linear))?;
1616 verify3(curve, t, point, linear)
1617 }
1618 Curve3::ImplicitSection(r) => {
1621 if let axiolid_curve::Carrier::Spline(b) = &r.carrier {
1624 let surface = axiolid_surface::Surface::BSpline((**b).clone());
1625 let (u, v) = crate::surface::locate(&surface, point, tolerance)?;
1626 let t = r
1627 .curve
1628 .parameter_of(Point2::new(u, v))
1629 .ok_or_else(|| point_not_on_curve(Scalar::INFINITY, linear))?;
1630 return verify3(curve, t, point, linear);
1631 }
1632 let (u, v) = r.carrier.parameters(point);
1633 let (pu, pv) = r.carrier.periodic();
1634 let turns = |periodic: bool| -> &'static [Scalar] {
1635 if periodic {
1636 &[0.0, 1.0, -1.0, 2.0, -2.0]
1637 } else {
1638 &[0.0]
1639 }
1640 };
1641 let mut candidates = Vec::new();
1642 for &ku in turns(pu) {
1643 for &kv in turns(pv) {
1644 let tau = std::f64::consts::TAU;
1645 if let Some(t) = r
1646 .curve
1647 .parameter_of(Point2::new(u + ku * tau, v + kv * tau))
1648 {
1649 candidates.push(t);
1650 }
1651 }
1652 }
1653 first_on_curve(curve, &candidates, point, linear)
1654 }
1655 _ => Err(no_closed_form_inversion()),
1656 }
1657}
1658
1659fn turns_of(angle: Scalar) -> Vec<Scalar> {
1661 let tau = std::f64::consts::TAU;
1662 vec![
1663 angle,
1664 angle + tau,
1665 angle - tau,
1666 angle + 2.0 * tau,
1667 angle - 2.0 * tau,
1668 ]
1669}
1670
1671fn first_on_curve(
1673 curve: &Curve3,
1674 candidates: &[Scalar],
1675 point: Point3,
1676 tolerance: Scalar,
1677) -> GeomResult<Scalar> {
1678 let mut nearest = Scalar::INFINITY;
1679 for &t in candidates {
1680 if let Ok(found) = evaluate3(curve, t) {
1681 let distance = (found - point).length();
1682 if distance <= tolerance {
1683 return Ok(t);
1684 }
1685 nearest = nearest.min(distance);
1686 }
1687 }
1688 Err(point_not_on_curve(nearest, tolerance))
1689}
1690
1691pub fn locate3(curve: &Curve3, point: Point3, tolerance: Tolerance) -> GeomResult<Scalar> {
1700 match invert3(curve, point, tolerance) {
1701 Ok(t) => Ok(t),
1702 Err(error) => match curve {
1703 Curve3::BSpline(b) => {
1704 let domain = spline_domain(b);
1705 let t = nearest_parameter(
1706 domain,
1707 b.control_points.len().max(2) * 64,
1708 |t| evaluate3(curve, t).map(|p| (p - point).length()),
1709 |t| {
1710 let (p, d, dd) = (
1711 evaluate3(curve, t)?,
1712 derivative3(curve, t)?,
1713 second_derivative3(curve, t)?,
1714 );
1715 let r = p - point;
1716 Ok((r.dot(d), d.dot(d) + r.dot(dd)))
1717 },
1718 )?;
1719 verify3(curve, t, point, tolerance.linear())
1720 }
1721 _ => Err(error),
1722 },
1723 }
1724}
1725
1726pub fn locate2(curve: &Curve2, point: Point2, tolerance: Tolerance) -> GeomResult<Scalar> {
1733 match invert2(curve, point, tolerance) {
1734 Ok(t) => Ok(t),
1735 Err(error) => match curve {
1736 Curve2::BSpline(b) => {
1737 let domain = spline_domain(b);
1738 let t = nearest_parameter(
1739 domain,
1740 b.control_points.len().max(2) * 64,
1741 |t| evaluate2(curve, t).map(|p| (p - point).length()),
1742 |t| {
1743 let (p, d, dd) = (
1744 evaluate2(curve, t)?,
1745 derivative2(curve, t)?,
1746 second_derivative2(curve, t)?,
1747 );
1748 let r = p - point;
1749 Ok((r.dot(d), d.dot(d) + r.dot(dd)))
1750 },
1751 )?;
1752 verify2(curve, t, point, tolerance.linear())
1753 }
1754 _ => Err(error),
1755 },
1756 }
1757}
1758
1759fn nearest_parameter(
1763 domain: Interval,
1764 samples: usize,
1765 distance: impl Fn(Scalar) -> GeomResult<Scalar>,
1766 gradient: impl Fn(Scalar) -> GeomResult<(Scalar, Scalar)>,
1767) -> GeomResult<Scalar> {
1768 let (lo, hi) = (domain.start.min(domain.end), domain.start.max(domain.end));
1769 let mut best = (Scalar::INFINITY, lo);
1770 for i in 0..=samples {
1771 let t = lo + (hi - lo) * i as Scalar / samples as Scalar;
1772 let d = distance(t)?;
1773 if d < best.0 {
1774 best = (d, t);
1775 }
1776 }
1777 let mut t = best.1;
1778 for _ in 0..60 {
1779 let (g, h) = gradient(t)?;
1780 if h <= 0.0 || !h.is_finite() {
1781 break;
1782 }
1783 let next = (t - g / h).clamp(lo, hi);
1784 let moved = (next - t).abs();
1785 t = next;
1786 if moved <= 4.0 * Scalar::EPSILON * (1.0 + t.abs()) {
1787 break;
1788 }
1789 }
1790 Ok(t)
1791}
1792
1793fn invert_conic_in_frame3(
1795 frame: &axiolid_core::Frame3,
1796 point: Point3,
1797 semi_x: Scalar,
1798 semi_y: Scalar,
1799) -> GeomResult<Scalar> {
1800 let offset = point - frame.origin;
1801 invert_conic(offset.dot(frame.x), offset.dot(frame.y), semi_x, semi_y)
1802}
1803
1804fn verify3(curve: &Curve3, t: Scalar, point: Point3, tolerance: Scalar) -> GeomResult<Scalar> {
1806 let found = evaluate3(curve, t)?;
1807 let distance = (found - point).length();
1808 if distance > tolerance {
1809 return Err(point_not_on_curve(distance, tolerance));
1810 }
1811 Ok(t)
1812}