1use axiolid_contracts::{BackendId, GeomError, GeomResult, Operation};
42use axiolid_core::{Point2, Point3, Scalar, Tolerance, Vec3};
43use axiolid_curve::{Curve2, Curve3, PathBound};
44pub use axiolid_curve_evaluate_contract::{PATH_POINT_AMBIGUOUS, PATH_POINT_OFF_CURVE};
45
46use super::composite::{StationCurve, JOINT_TOLERANCE};
47use crate::curve::{
48 derivative2, derivative3, domain2, domain3, evaluate2, evaluate3, invert2, invert3,
49 second_derivative2, second_derivative3,
50};
51
52pub const POINT_BOUND_UNSUPPORTED: &str =
55 "a curve path piece bounded by a point on a curve this evaluator does not invert (an \
56 intrinsic curve, a chain, an elevated or banked curve, an offset, or a family other than a \
57 line, a circle, an ellipse, a polyline or a B-spline): state its parameter instead";
58
59const SAMPLES_PER_CONTROL_POINT: usize = 64;
61
62const NEWTON_STEPS: usize = 60;
64
65fn refused(input: &'static str) -> GeomError {
66 GeomError::UnsupportedInput {
67 backend: BackendId::new("axiolid-evaluate"),
68 operation: Operation::CurveEvaluation,
69 input,
70 }
71}
72
73#[must_use]
76pub fn point_bound_tolerance(point: Point3) -> Scalar {
77 JOINT_TOLERANCE * point.abs().max_element().max(1.0)
78}
79
80pub(super) fn bound_parameter(curve: StationCurve<'_>, bound: PathBound) -> GeomResult<Scalar> {
92 match bound {
93 PathBound::Parameter(t) if t.is_finite() => Ok(t),
94 PathBound::Parameter(t) => Err(GeomError::Degenerate(format!(
95 "station: a curve path piece is bounded at parameter {t}, which is not finite"
96 ))),
97 PathBound::Point(point) => point_parameter(curve, point),
98 _ => Err(refused(
99 "a curve path piece bound of a kind this evaluator does not know",
100 )),
101 }
102}
103
104fn point_parameter(curve: StationCurve<'_>, point: Point3) -> GeomResult<Scalar> {
107 if !(point.x.is_finite() && point.y.is_finite() && point.z.is_finite()) {
108 return Err(GeomError::InvalidInput(format!(
109 "station: a curve path piece is bounded at point {point:?}, which is not finite"
110 )));
111 }
112 let traced = Traced::of(curve)?;
113 let found = traced.parameters(point, point_bound_tolerance(point))?;
114 match found.as_slice() {
115 [] => Err(refused(PATH_POINT_OFF_CURVE)),
116 [t] => Ok(*t),
117 _ => Err(refused(PATH_POINT_AMBIGUOUS)),
118 }
119}
120
121#[derive(Clone, Copy)]
123enum Traced<'c> {
124 Two(&'c Curve2),
125 Three(&'c Curve3),
126}
127
128impl<'c> Traced<'c> {
129 fn of(curve: StationCurve<'c>) -> GeomResult<Self> {
130 match curve {
131 StationCurve::Two(
132 curve @ (Curve2::Line(_)
133 | Curve2::Circle(_)
134 | Curve2::Ellipse(_)
135 | Curve2::Polyline(_)
136 | Curve2::BSpline(_)),
137 ) => Ok(Self::Two(curve)),
138 StationCurve::Three(
139 curve @ (Curve3::Line(_)
140 | Curve3::Circle(_)
141 | Curve3::Ellipse(_)
142 | Curve3::Polyline(_)
143 | Curve3::BSpline(_)),
144 ) => Ok(Self::Three(curve)),
145 _ => Err(refused(POINT_BOUND_UNSUPPORTED)),
146 }
147 }
148
149 fn point(self, t: Scalar) -> GeomResult<Point3> {
150 match self {
151 Self::Two(curve) => {
152 let p = evaluate2(curve, t)?;
153 Ok(Point3::new(p.x, p.y, 0.0))
154 }
155 Self::Three(curve) => evaluate3(curve, t),
156 }
157 }
158
159 fn derivatives(self, t: Scalar) -> GeomResult<(Vec3, Vec3)> {
160 match self {
161 Self::Two(curve) => {
162 let (d, dd) = (derivative2(curve, t)?, second_derivative2(curve, t)?);
163 Ok((Vec3::new(d.x, d.y, 0.0), Vec3::new(dd.x, dd.y, 0.0)))
164 }
165 Self::Three(curve) => Ok((derivative3(curve, t)?, second_derivative3(curve, t)?)),
166 }
167 }
168
169 fn distance(self, t: Scalar, point: Point3) -> GeomResult<Scalar> {
170 Ok((self.point(t)? - point).length())
171 }
172
173 fn parameters(self, point: Point3, tolerance: Scalar) -> GeomResult<Vec<Scalar>> {
176 match self {
177 Self::Two(Curve2::Polyline(polyline)) => {
178 let points: Vec<Point3> = polyline
179 .points
180 .iter()
181 .map(|p| Point3::new(p.x, p.y, 0.0))
182 .collect();
183 Ok(polyline_parameters(
184 &points,
185 polyline.closed,
186 point,
187 tolerance,
188 ))
189 }
190 Self::Three(Curve3::Polyline(polyline)) => Ok(polyline_parameters(
191 &polyline.points,
192 polyline.closed,
193 point,
194 tolerance,
195 )),
196 Self::Two(Curve2::BSpline(_)) | Self::Three(Curve3::BSpline(_)) => {
197 self.spline_parameters(point, tolerance)
198 }
199 _ => {
200 let anywhere = Tolerance::new(Scalar::MAX, 0.0).map_err(|_| {
204 GeomError::InvalidInput("station: no inversion tolerance".into())
205 })?;
206 let t = match self {
207 Self::Two(curve) => invert2(curve, Point2::new(point.x, point.y), anywhere)?,
208 Self::Three(curve) => invert3(curve, point, anywhere)?,
209 };
210 Ok(if self.distance(t, point)? <= tolerance {
211 vec![t]
212 } else {
213 Vec::new()
214 })
215 }
216 }
217 }
218
219 fn spline_parameters(self, point: Point3, tolerance: Scalar) -> GeomResult<Vec<Scalar>> {
222 let (domain, count, knots) = match self {
223 Self::Two(curve @ Curve2::BSpline(spline)) => (
224 domain2(curve),
225 spline.control_points.len(),
226 spline.knots.as_slice(),
227 ),
228 Self::Three(curve @ Curve3::BSpline(spline)) => (
229 domain3(curve),
230 spline.control_points.len(),
231 spline.knots.as_slice(),
232 ),
233 _ => return Err(refused(POINT_BOUND_UNSUPPORTED)),
234 };
235 let (lo, hi) = (domain.start.min(domain.end), domain.start.max(domain.end));
236 if !(lo.is_finite() && hi.is_finite() && hi > lo) {
237 return Err(GeomError::Degenerate(
238 "station: a B-spline with no parameter domain bounds a curve path piece".into(),
239 ));
240 }
241 let samples = count.max(2) * SAMPLES_PER_CONTROL_POINT;
242 let mut at: Vec<Scalar> = (0..=samples)
243 .map(|i| lo + (hi - lo) * i as Scalar / samples as Scalar)
244 .chain(knots.iter().copied().filter(|&k| k > lo && k < hi))
245 .collect();
246 at.sort_by(Scalar::total_cmp);
247 at.dedup();
248 let off = at
249 .iter()
250 .map(|&t| self.distance(t, point))
251 .collect::<GeomResult<Vec<_>>>()?;
252 let last = at.len() - 1;
253 let mut found = Vec::new();
254 for i in 0..=last {
255 let falls = i == 0 || off[i] <= off[i - 1];
256 let rises = i == last || off[i] <= off[i + 1];
257 if !(falls && rises) {
258 continue;
259 }
260 let (a, b) = (at[i.saturating_sub(1)], at[(i + 1).min(last)]);
261 let refined = self.refine(at[i], a, b, point)?;
262 let near = self.distance(refined, point)?;
263 let (t, distance) = if near <= off[i] {
264 (refined, near)
265 } else {
266 (at[i], off[i])
267 };
268 if distance <= tolerance {
269 found.push((t, distance));
270 }
271 }
272 found.sort_by(|x, y| x.0.total_cmp(&y.0));
273 places(found, |ta, tb| {
274 let between = at
275 .iter()
276 .zip(&off)
277 .filter(|(t, _)| **t > ta && **t < tb)
278 .all(|(_, d)| *d <= tolerance);
279 Ok(between && self.distance(0.5 * (ta + tb), point)? <= tolerance)
280 })
281 }
282
283 fn refine(self, mut t: Scalar, a: Scalar, b: Scalar, point: Point3) -> GeomResult<Scalar> {
285 for _ in 0..NEWTON_STEPS {
286 let (d, dd) = self.derivatives(t)?;
287 let r = self.point(t)? - point;
288 let (g, h) = (r.dot(d), d.dot(d) + r.dot(dd));
289 if !(h.is_finite() && h > 0.0) {
290 break;
291 }
292 let next = (t - g / h).clamp(a, b);
293 let moved = (next - t).abs();
294 t = next;
295 if moved <= 4.0 * Scalar::EPSILON * (1.0 + t.abs()) {
296 break;
297 }
298 }
299 Ok(t)
300 }
301}
302
303fn polyline_parameters(
307 points: &[Point3],
308 closed: bool,
309 point: Point3,
310 tolerance: Scalar,
311) -> Vec<Scalar> {
312 let n = points.len();
313 let segments = if closed { n } else { n.saturating_sub(1) };
314 let mut found = Vec::new();
315 for i in 0..segments {
316 let (a, b) = (points[i], points[(i + 1) % n]);
317 let d = b - a;
318 let dd = d.dot(d);
319 let u = if dd > 0.0 {
320 ((point - a).dot(d) / dd).clamp(0.0, 1.0)
321 } else {
322 0.0
323 };
324 let distance = (a + d * u - point).length();
325 if distance <= tolerance {
326 found.push((i as Scalar + u, distance));
327 }
328 }
329 let vertex = |k: usize| points[k % n.max(1)];
330 places(found, |ta, tb| {
331 let first = ta.floor() as usize + 1;
334 let last = tb.ceil() as usize;
335 Ok((first..last)
336 .filter(|&k| (k as Scalar) > ta && (k as Scalar) < tb)
337 .all(|k| (vertex(k) - point).length() <= tolerance))
338 })
339 .unwrap_or_default()
340}
341
342fn places(
347 found: Vec<(Scalar, Scalar)>,
348 same: impl Fn(Scalar, Scalar) -> GeomResult<bool>,
349) -> GeomResult<Vec<Scalar>> {
350 let mut out: Vec<(Scalar, Scalar)> = Vec::new();
351 let mut previous: Option<Scalar> = None;
352 for (t, distance) in found {
353 let joins = match previous {
354 Some(before) => same(before, t)?,
355 None => false,
356 };
357 previous = Some(t);
358 match out.last_mut() {
359 Some(place) if joins => {
360 if distance < place.1 {
361 *place = (t, distance);
362 }
363 }
364 _ => out.push((t, distance)),
365 }
366 }
367 Ok(out.into_iter().map(|(t, _)| t).collect())
368}