1use crate::{DiagnosticSeverity, KernelDiagnostics, KernelStage, KnotVector, NurbsCurve, Vec3, Vec4};
2
3pub fn solve_dense(mut matrix: Vec<Vec<f64>>, mut rhs: Vec<f64>) -> Result<Vec<f64>, String> {
4 let count = rhs.len();
5 if count == 0 || matrix.len() != count || matrix.iter().any(|row| row.len() != count) {
6 return Err("solve_dense: matrix must be square and match RHS".into());
7 }
8 let scale = matrix
9 .iter()
10 .flatten()
11 .fold(0.0_f64, |maximum, value| maximum.max(value.abs()));
12 if scale == 0.0 {
13 return Err("solve_dense: singular matrix".into());
14 }
15 for column in 0..count {
16 let pivot = (column..count)
17 .max_by(|&a, &b| matrix[a][column].abs().total_cmp(&matrix[b][column].abs()))
18 .unwrap();
19 if matrix[pivot][column].abs() <= 1e-13 * scale {
20 return Err("solve_dense: singular matrix".into());
21 }
22 matrix.swap(column, pivot);
23 rhs.swap(column, pivot);
24 let diagonal = matrix[column][column];
25 for row in column + 1..count {
26 let factor = matrix[row][column] / diagonal;
27 matrix[row][column] = 0.0;
28 for entry in column + 1..count {
29 matrix[row][entry] -= factor * matrix[column][entry];
30 }
31 rhs[row] -= factor * rhs[column];
32 }
33 }
34 let mut result = vec![0.0; count];
35 for row in (0..count).rev() {
36 let remainder: f64 = (row + 1..count)
37 .map(|column| matrix[row][column] * result[column])
38 .sum();
39 result[row] = (rhs[row] - remainder) / matrix[row][row];
40 }
41 Ok(result)
42}
43
44pub fn solve_small<const N: usize>(
48 mut matrix: [[f64; N]; N],
49 mut rhs: [f64; N],
50 count: usize,
51) -> Result<[f64; N], String> {
52 if count == 0 || count > N {
53 return Err("solve_small: invalid system size".into());
54 }
55 let scale = matrix
56 .iter()
57 .take(count)
58 .flat_map(|row| row.iter().take(count))
59 .fold(0.0_f64, |maximum, value| maximum.max(value.abs()));
60 if scale == 0.0 {
61 return Err("solve_small: singular matrix".into());
62 }
63 for column in 0..count {
64 let pivot = (column..count)
65 .max_by(|&a, &b| matrix[a][column].abs().total_cmp(&matrix[b][column].abs()))
66 .unwrap();
67 if matrix[pivot][column].abs() <= 1e-13 * scale {
68 return Err("solve_small: singular matrix".into());
69 }
70 matrix.swap(column, pivot);
71 rhs.swap(column, pivot);
72 let diagonal = matrix[column][column];
73 for row in column + 1..count {
74 let factor = matrix[row][column] / diagonal;
75 matrix[row][column] = 0.0;
76 for entry in column + 1..count {
77 matrix[row][entry] -= factor * matrix[column][entry];
78 }
79 rhs[row] -= factor * rhs[column];
80 }
81 }
82 let mut result = [0.0; N];
83 for row in (0..count).rev() {
84 let remainder: f64 = (row + 1..count)
85 .map(|column| matrix[row][column] * result[column])
86 .sum();
87 result[row] = (rhs[row] - remainder) / matrix[row][row];
88 }
89 Ok(result)
90}
91
92pub fn solve_collocation(
103 matrix: &[Vec<f64>],
104 rhs: &[f64],
105 degree: usize,
106) -> Result<Vec<f64>, String> {
107 let count = rhs.len();
108 let bandwidth = degree.max(1).min(count.saturating_sub(1).max(1));
109 if let Ok(solution) = solve_banded(matrix, rhs, bandwidth) {
110 let scale = rhs
111 .iter()
112 .fold(0.0_f64, |maximum, value| maximum.max(value.abs()));
113 let tolerance = 1e-9 * scale.max(1.0);
114 let mut residual = 0.0_f64;
115 for row in 0..count {
116 let lo = row.saturating_sub(bandwidth);
117 let hi = (row + bandwidth).min(count - 1);
118 let mut accumulated = 0.0;
119 for col in lo..=hi {
120 accumulated += matrix[row][col] * solution[col];
121 }
122 residual = residual.max((accumulated - rhs[row]).abs());
123 }
124 if residual <= tolerance {
125 return Ok(solution);
126 }
127 }
128 solve_dense(matrix.to_vec(), rhs.to_vec())
129}
130
131pub fn solve_banded(
132 matrix: &[Vec<f64>],
133 rhs: &[f64],
134 bandwidth: usize,
135) -> Result<Vec<f64>, String> {
136 let count = rhs.len();
137 if count == 0 || matrix.len() != count || matrix.iter().any(|row| row.len() != count) {
138 return Err("solve_banded: matrix must be square and match RHS".into());
139 }
140 let mut matrix = matrix.to_vec();
141 let mut rhs = rhs.to_vec();
142 for column in 0..count {
143 let pivot = matrix[column][column];
144 if pivot.abs() <= 1e-300 {
145 return Err("solve_banded: singular matrix".into());
146 }
147 let maximum_row = (column + bandwidth).min(count - 1);
148 for row in column + 1..=maximum_row {
149 let factor = matrix[row][column] / pivot;
150 if factor == 0.0 {
151 continue;
152 }
153 let maximum_column = (column + bandwidth).min(count - 1);
154 for entry in column..=maximum_column {
155 matrix[row][entry] -= factor * matrix[column][entry];
156 }
157 rhs[row] -= factor * rhs[column];
158 }
159 }
160 let mut result = vec![0.0; count];
161 for row in (0..count).rev() {
162 let mut value = rhs[row];
163 let maximum_column = (row + bandwidth).min(count - 1);
164 for column in row + 1..=maximum_column {
165 value -= matrix[row][column] * result[column];
166 }
167 if matrix[row][row].abs() <= 1e-300 {
168 return Err("solve_banded: singular matrix".into());
169 }
170 result[row] = value / matrix[row][row];
171 }
172 Ok(result)
173}
174
175pub fn interpolate_homogeneous(
182 points: &[Vec4],
183 degree: usize,
184 parameters: &[f64],
185) -> Result<NurbsCurve, String> {
186 if points.len() < 2 || parameters.len() != points.len() {
187 return Err(
188 "interpolate_homogeneous: points and parameters must have matching length >= 2".into(),
189 );
190 }
191 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
192 return Err("interpolate_homogeneous: parameters must be strictly increasing".into());
193 }
194 let n = points.len() - 1;
195 let degree = degree.min(n);
196 let m = n + degree + 1;
197 let mut knots = vec![0.0; m + 1];
198 for knot in &mut knots[m - degree..=m] {
199 *knot = 1.0;
200 }
201 for j in 1..=n.saturating_sub(degree) {
202 knots[j + degree] = parameters[j..j + degree].iter().sum::<f64>() / degree as f64;
203 }
204 let knot_vector = KnotVector::new(knots.clone(), degree)?;
205 let mut matrix = vec![vec![0.0; n + 1]; n + 1];
206 for (row, ¶meter) in parameters.iter().enumerate() {
207 let span = knot_vector.find_span(parameter);
208 let basis = knot_vector.basis_functions(span, parameter);
209 for (offset, value) in basis.into_iter().enumerate() {
210 matrix[row][span - degree + offset] = value;
211 }
212 }
213 let solve_axis = |axis: fn(&Vec4) -> f64| {
214 solve_collocation(
215 &matrix,
216 &points.iter().map(axis).collect::<Vec<_>>(),
217 degree,
218 )
219 };
220 let xs = solve_axis(|point| point.x)?;
221 let ys = solve_axis(|point| point.y)?;
222 let zs = solve_axis(|point| point.z)?;
223 let ws = solve_axis(|point| point.w)?;
224 NurbsCurve::new(
225 degree,
226 knots,
227 (0..=n)
228 .map(|index| Vec4 {
229 x: xs[index],
230 y: ys[index],
231 z: zs[index],
232 w: ws[index],
233 })
234 .collect(),
235 )
236}
237
238pub fn interpolate_curve(
254 points: &[Vec3],
255 degree: usize,
256 parameters: &[f64],
257) -> Result<NurbsCurve, String> {
258 if points.len() < 2 || parameters.len() != points.len() {
259 return Err(
260 "interpolate_curve: points and parameters must have matching length >= 2".into(),
261 );
262 }
263 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
264 return Err("interpolate_curve: parameters must be strictly increasing".into());
265 }
266 let n = points.len() - 1;
267 let degree = degree.min(n);
268 let m = n + degree + 1;
269 let mut knots = vec![parameters[0]; m + 1];
270 for knot in &mut knots[m - degree..=m] {
271 *knot = parameters[n];
272 }
273 for j in 1..=n.saturating_sub(degree) {
274 knots[j + degree] = parameters[j..j + degree].iter().sum::<f64>() / degree as f64;
275 }
276 let knot_vector = KnotVector::new(knots.clone(), degree)?;
277 let mut matrix = vec![vec![0.0; n + 1]; n + 1];
278 for (row, ¶meter) in parameters.iter().enumerate() {
279 let span = knot_vector.find_span(parameter);
280 let basis = knot_vector.basis_functions(span, parameter);
281 for (offset, value) in basis.into_iter().enumerate() {
282 matrix[row][span - degree + offset] = value;
283 }
284 }
285 let solve_axis = |axis: fn(Vec3) -> f64| {
286 solve_collocation(
287 &matrix,
288 &points.iter().copied().map(axis).collect::<Vec<_>>(),
289 degree,
290 )
291 };
292 let xs = solve_axis(|point| point.x)?;
293 let ys = solve_axis(|point| point.y)?;
294 let zs = solve_axis(|point| point.z)?;
295 NurbsCurve::new(
296 degree,
297 knots,
298 (0..=n)
299 .map(|index| Vec4::from_point(Vec3::new(xs[index], ys[index], zs[index]), 1.0))
300 .collect(),
301 )
302}
303
304pub fn interpolate_curve_with_end_tangents(
311 points: &[Vec3],
312 parameters: &[f64],
313 start_tangent: Vec3,
314 end_tangent: Vec3,
315) -> Result<NurbsCurve, String> {
316 if points.len() < 2 || parameters.len() != points.len() {
317 return Err(
318 "interpolate_curve_with_end_tangents: points and parameters must match, >= 2".into(),
319 );
320 }
321 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
322 return Err("interpolate_curve_with_end_tangents: parameters must increase".into());
323 }
324 let degree = 3usize;
325 let n = points.len() - 1;
326 let control_count = n + 3;
327 let t0 = parameters[0];
328 let t1 = parameters[n];
329 let mut knots = vec![t0; degree + 1];
332 for j in 0..n.saturating_sub(1) {
333 let window = ¶meters[j + 1..(j + degree).min(n) + 1];
334 knots.push(window.iter().sum::<f64>() / window.len() as f64);
335 }
336 knots.extend(std::iter::repeat(t1).take(degree + 1));
337 if knots.len() != control_count + degree + 1 {
338 return Err(format!(
339 "interpolate_curve_with_end_tangents: internal knot count {} for {} controls",
340 knots.len(),
341 control_count
342 ));
343 }
344 let knot_vector = KnotVector::new(knots.clone(), degree)?;
345 let mut matrix = vec![vec![0.0; control_count]; control_count];
346 let mut rhs_points = vec![Vec3::default(); control_count];
347 matrix[0][0] = 1.0;
351 rhs_points[0] = points[0];
352 let start_span = knots[degree + 1] - t0;
353 matrix[1][0] = -(degree as f64) / start_span;
354 matrix[1][1] = (degree as f64) / start_span;
355 rhs_points[1] = start_tangent;
356 for (index, ¶meter) in parameters.iter().enumerate().take(n).skip(1) {
357 let row = index + 1;
358 let span = knot_vector.find_span(parameter);
359 let basis = knot_vector.basis_functions(span, parameter);
360 for (offset, value) in basis.into_iter().enumerate() {
361 matrix[row][span - degree + offset] = value;
362 }
363 rhs_points[row] = points[index];
364 }
365 let end_span = t1 - knots[control_count - 1];
366 matrix[control_count - 2][control_count - 2] = -(degree as f64) / end_span;
367 matrix[control_count - 2][control_count - 1] = (degree as f64) / end_span;
368 rhs_points[control_count - 2] = end_tangent;
369 matrix[control_count - 1][control_count - 1] = 1.0;
370 rhs_points[control_count - 1] = points[n];
371 let solve_axis = |axis: fn(Vec3) -> f64| {
372 solve_dense(
373 matrix.clone(),
374 rhs_points.iter().copied().map(axis).collect::<Vec<_>>(),
375 )
376 };
377 let xs = solve_axis(|point| point.x)?;
378 let ys = solve_axis(|point| point.y)?;
379 let zs = solve_axis(|point| point.z)?;
380 NurbsCurve::new(
381 degree,
382 knots,
383 (0..control_count)
384 .map(|index| Vec4::from_point(Vec3::new(xs[index], ys[index], zs[index]), 1.0))
385 .collect(),
386 )
387}
388
389pub fn interpolate_curve_thinned(
390 points: &[Vec3],
391 degree: usize,
392 maximum_points: usize,
393) -> Result<NurbsCurve, String> {
394 if maximum_points < 2 {
395 return Err("interpolate_curve_thinned: maximum point count must be at least 2".into());
396 }
397 if points.len() <= maximum_points {
398 let parameters = chord_parameters(points);
399 return interpolate_curve(points, degree, ¶meters);
400 }
401 let step = (points.len() - 1) as f64 / (maximum_points - 1) as f64;
402 let thinned = (0..maximum_points)
403 .map(|index| points[(index as f64 * step).round() as usize])
404 .collect::<Vec<_>>();
405 let parameters = chord_parameters(&thinned);
406 interpolate_curve(&thinned, degree, ¶meters)
407}
408
409fn chord_parameters(points: &[Vec3]) -> Vec<f64> {
410 if points.len() < 2 {
411 return vec![0.0; points.len()];
412 }
413 let mut parameters = vec![0.0; points.len()];
414 for index in 1..points.len() {
415 parameters[index] = parameters[index - 1] + points[index].sub(points[index - 1]).length();
416 }
417 let length = parameters[points.len() - 1];
418 if length <= 1e-15 {
419 for (index, parameter) in parameters.iter_mut().enumerate() {
420 *parameter = index as f64 / (points.len() - 1) as f64;
421 }
422 } else {
423 for parameter in &mut parameters {
424 *parameter /= length;
425 }
426 }
427 parameters
428}
429
430pub fn simplify_polyline_indices(points: &[Vec3], tolerance: f64) -> Vec<usize> {
439 if points.len() <= 2 {
440 return (0..points.len()).collect();
441 }
442 let mut keep = vec![false; points.len()];
443 keep[0] = true;
444 keep[points.len() - 1] = true;
445 let mut stack = vec![(0usize, points.len() - 1)];
446 while let Some((start, end)) = stack.pop() {
447 if end - start < 2 {
448 continue;
449 }
450 let a = points[start];
451 let direction = points[end].sub(a);
452 let length_squared = direction.length_squared().max(1e-300);
453 let mut worst = None;
454 let mut worst_distance = tolerance;
455 for (index, point) in points.iter().enumerate().take(end).skip(start + 1) {
456 let fraction = point.sub(a).dot(direction) / length_squared;
457 let fraction = fraction.clamp(0.0, 1.0);
458 let distance = point.sub(a.add(direction.scale(fraction))).length();
459 if distance > worst_distance {
460 worst_distance = distance;
461 worst = Some(index);
462 }
463 }
464 if let Some(index) = worst {
465 keep[index] = true;
466 stack.push((start, index));
467 stack.push((index, end));
468 }
469 }
470 keep.into_iter()
471 .enumerate()
472 .filter_map(|(index, keep)| keep.then_some(index))
473 .collect()
474}
475
476pub fn simplify_polyline(points: &[Vec3], tolerance: f64) -> Vec<Vec3> {
477 simplify_polyline_indices(points, tolerance)
478 .into_iter()
479 .map(|index| points[index])
480 .collect()
481}
482
483pub fn interpolate_curve_local(
484 points: &[Vec3],
485 parameters: &[f64],
486 tension: f64,
487) -> Result<NurbsCurve, String> {
488 if points.len() != parameters.len() || points.len() < 2 {
489 return Err(
490 "interpolate_curve_local: points and parameters must have matching length >= 2".into(),
491 );
492 }
493 if points.len() == 2 {
494 return interpolate_curve(points, 1, parameters);
495 }
496 let count = points.len();
497 let mut tangents = vec![Vec3::default(); count];
498 tangents[0] = points[1]
499 .sub(points[0])
500 .scale(1.0 / (parameters[1] - parameters[0]));
501 tangents[count - 1] = points[count - 1]
502 .sub(points[count - 2])
503 .scale(1.0 / (parameters[count - 1] - parameters[count - 2]));
504 for index in 1..count - 1 {
505 let previous_interval = parameters[index] - parameters[index - 1];
506 let next_interval = parameters[index + 1] - parameters[index];
507 let total = previous_interval + next_interval;
508 let previous_secant = points[index]
509 .sub(points[index - 1])
510 .scale(1.0 / previous_interval);
511 let next_secant = points[index + 1]
512 .sub(points[index])
513 .scale(1.0 / next_interval);
514 let mut tangent =
515 points[index - 1]
516 .scale(-next_interval / (previous_interval * total))
517 .add(points[index].scale(
518 (next_interval - previous_interval) / (previous_interval * next_interval),
519 ))
520 .add(points[index + 1].scale(previous_interval / (next_interval * total)));
521 if previous_secant.dot(next_secant) <= 0.0
522 || tangent.dot(previous_secant) <= 0.0
523 || tangent.dot(next_secant) <= 0.0
524 {
525 tangent = Vec3::default();
526 } else {
527 let maximum = 3.0 * previous_secant.length().min(next_secant.length());
528 if tangent.length() > maximum {
529 tangent = tangent.normalized()?.scale(maximum);
530 }
531 }
532 tangents[index] = tangent;
533 }
534 if tension != 1.0 {
535 for tangent in &mut tangents {
536 *tangent = tangent.scale(tension);
537 }
538 }
539 let mut control_points = vec![Vec4::from_point(points[0], 1.0)];
540 for index in 0..count - 1 {
541 let interval = parameters[index + 1] - parameters[index];
542 control_points.extend([
543 Vec4::from_point(
544 points[index].add(tangents[index].scale(interval / 3.0)),
545 1.0,
546 ),
547 Vec4::from_point(
548 points[index + 1].sub(tangents[index + 1].scale(interval / 3.0)),
549 1.0,
550 ),
551 Vec4::from_point(points[index + 1], 1.0),
552 ]);
553 }
554 let mut knots = vec![parameters[0]; 4];
555 for parameter in ¶meters[1..count - 1] {
556 knots.extend([*parameter; 3]);
557 }
558 knots.extend([parameters[count - 1]; 4]);
559 NurbsCurve::new(3, knots, control_points)
560}
561
562const LADDER_BUDGET_FACTOR: usize = 2;
578
579pub const MAX_FIT_STATIONS: usize = 1024;
601
602const MEASURE_NEWTON_STEPS: usize = 3;
608
609const MEASURE_SPAN_FRACTIONS: [f64; 3] = [0.25, 0.5, 0.75];
615
616const STALL_FACTOR: f64 = 0.9;
621
622#[derive(Clone, Copy, Debug, PartialEq)]
640pub struct PolylineFitReport {
641 pub deviation: f64,
645 pub rms: f64,
647 pub between_stations: f64,
651 pub tolerance: f64,
653 pub floor: f64,
665 pub stations: usize,
667 pub controls: usize,
669 pub input_points: usize,
671 pub pool_points: usize,
674 pub dropped_band: f64,
681 pub rungs: usize,
684 pub local_lane: bool,
689 pub lane_switched: bool,
692 pub exit: PolylineFitExit,
694}
695
696impl PolylineFitReport {
697 pub fn met_tolerance(&self) -> bool {
701 self.deviation <= self.floor
702 }
703
704 pub fn met_requested_tolerance(&self) -> bool {
706 self.deviation <= self.tolerance
707 }
708
709 pub fn saturated(&self) -> bool {
720 self.stations >= self.pool_points
721 }
722}
723
724#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
729pub enum PolylineFitExit {
730 Converged,
732 StationCeiling,
735 InputExhausted,
739 Stalled,
743 Unrefined,
746}
747
748#[derive(Clone, Debug)]
749pub struct PolylineFit {
750 pub curve: NurbsCurve,
751 pub parameters: Vec<f64>,
752 pub kept: Vec<Vec3>,
753 pub report: PolylineFitReport,
755}
756
757fn station_pool(points: &[Vec3], tolerance: f64) -> Vec<usize> {
766 let simplified = simplify_polyline_indices(points, tolerance);
767 if simplified.len() > 2 {
768 let total: f64 = simplified
769 .windows(2)
770 .map(|pair| points[pair[1]].sub(points[pair[0]]).length())
771 .sum();
772 let floor = (tolerance * 0.01).max(total * 1e-4);
773 let first = points[simplified[0]];
774 let last = points[simplified[simplified.len() - 1]];
775 let mut conditioned = vec![simplified[0]];
776 for index in &simplified[1..simplified.len() - 1] {
777 let point = points[*index];
778 if point.sub(first).length() > floor
779 && point.sub(last).length() > floor
780 && point.sub(points[*conditioned.last().unwrap()]).length() > floor
781 {
782 conditioned.push(*index);
783 }
784 }
785 conditioned.push(simplified[simplified.len() - 1]);
786 conditioned
787 } else {
788 let mut distinct: Vec<usize> = Vec::new();
789 for index in simplified {
790 if distinct.last().is_none_or(|previous: &usize| {
791 points[index].sub(points[*previous]).length() > tolerance * 0.01
792 }) {
793 distinct.push(index);
794 }
795 }
796 distinct
797 }
798}
799
800fn decimate_stations(pool: &[usize], maximum_points: usize) -> Vec<usize> {
802 if pool.len() <= maximum_points {
803 return pool.to_vec();
804 }
805 let step = (pool.len() - 1) as f64 / (maximum_points - 1) as f64;
806 (0..maximum_points)
807 .map(|index| pool[(index as f64 * step).round() as usize])
808 .collect()
809}
810
811fn station_parameters(points: &[Vec3], stations: &[usize]) -> Option<Vec<f64>> {
814 let total: f64 = stations
815 .windows(2)
816 .map(|pair| points[pair[1]].sub(points[pair[0]]).length())
817 .sum();
818 if total <= 0.0 {
819 return None;
820 }
821 let mut parameters = vec![0.0; stations.len()];
822 let mut accumulated = 0.0;
823 for index in 1..stations.len() {
824 accumulated += points[stations[index]]
825 .sub(points[stations[index - 1]])
826 .length();
827 parameters[index] = accumulated / total;
828 }
829 *parameters.last_mut().unwrap() = 1.0;
830 Some(parameters)
831}
832
833fn distance_to_segment(point: Vec3, from: Vec3, to: Vec3) -> f64 {
834 let direction = to.sub(from);
835 let length_squared = direction.length_squared();
836 if length_squared <= 0.0 {
837 return point.sub(from).length();
838 }
839 let fraction = (point.sub(from).dot(direction) / length_squared).clamp(0.0, 1.0);
840 point.sub(from.add(direction.scale(fraction))).length()
841}
842
843fn distance_to_curve_near(
852 curve: &NurbsCurve,
853 point: Vec3,
854 seed: f64,
855 lower: f64,
856 upper: f64,
857) -> Result<f64, String> {
858 let mut parameter = seed.clamp(lower, upper);
859 let mut best = f64::INFINITY;
860 for _ in 0..MEASURE_NEWTON_STEPS {
861 let derivatives = curve.derivatives_small(parameter, 2)?;
862 let residual = derivatives[0].sub(point);
863 best = best.min(residual.length_squared());
864 let slope = derivatives[1].dot(residual);
865 let curvature = derivatives[2].dot(residual) + derivatives[1].length_squared();
866 if curvature.abs() <= f64::MIN_POSITIVE {
867 break;
868 }
869 let next = (parameter - slope / curvature).clamp(lower, upper);
870 if next == parameter {
871 break;
872 }
873 parameter = next;
874 }
875 let residual = curve.evaluate(parameter)?.sub(point).length_squared();
876 Ok(best.min(residual).sqrt())
877}
878
879fn measure_polyline_fit(
901 curve: &NurbsCurve,
902 points: &[Vec3],
903 stations: &[usize],
904 parameters: &[f64],
905 arc: &[f64],
906) -> Result<(f64, f64, f64), String> {
907 let mut worst = 0.0_f64;
908 let mut sum_squares = 0.0_f64;
909 let mut samples = 0usize;
910 let mut between = 0.0_f64;
911 let last = stations.len() - 1;
912 for span in 0..last {
913 let (from, to) = (stations[span], stations[span + 1]);
914 let (t0, t1) = (parameters[span], parameters[span + 1]);
915 let lower = parameters[span.saturating_sub(1)];
918 let upper = parameters[(span + 2).min(last)];
919 let length = arc[to] - arc[from];
923 for index in from..to {
924 let seed = if length > 0.0 {
925 t0 + (t1 - t0) * (arc[index] - arc[from]) / length
926 } else {
927 t0
928 };
929 let distance = distance_to_curve_near(curve, points[index], seed, lower, upper)?;
930 worst = worst.max(distance);
931 sum_squares += distance * distance;
932 samples += 1;
933 }
934 let window_start = from.saturating_sub(1);
937 let window_end = (to + 1).min(points.len() - 1);
938 for fraction in MEASURE_SPAN_FRACTIONS {
939 let sample = curve.evaluate(t0 + (t1 - t0) * fraction)?;
940 let mut nearest = f64::INFINITY;
941 for index in window_start..window_end {
942 nearest = nearest.min(distance_to_segment(
943 sample,
944 points[index],
945 points[index + 1],
946 ));
947 }
948 if nearest.is_finite() {
949 between = between.max(nearest);
950 }
951 }
952 }
953 let distance = distance_to_curve_near(
954 curve,
955 points[stations[last]],
956 parameters[last],
957 parameters[last.saturating_sub(1)],
958 parameters[last],
959 )?;
960 worst = worst.max(distance);
961 sum_squares += distance * distance;
962 samples += 1;
963 let rms = (sum_squares / samples as f64).sqrt();
964 Ok((worst, rms, between))
965}
966
967fn dropped_band(points: &[Vec3], pool: &[usize]) -> f64 {
971 let mut worst = 0.0_f64;
972 for span in 0..pool.len() - 1 {
973 let (from, to) = (pool[span], pool[span + 1]);
974 for index in from + 1..to {
975 worst = worst.max(distance_to_segment(
976 points[index],
977 points[from],
978 points[to],
979 ));
980 }
981 }
982 worst
983}
984
985fn cumulative_chord(points: &[Vec3]) -> Vec<f64> {
987 let mut arc = vec![0.0; points.len()];
988 for index in 1..points.len() {
989 arc[index] = arc[index - 1] + points[index].sub(points[index - 1]).length();
990 }
991 arc
992}
993
994fn fit_rung(
996 points: &[Vec3],
997 pool: &[usize],
998 arc: &[f64],
999 maximum_points: usize,
1000 local_interpolation: bool,
1001) -> Result<(NurbsCurve, Vec<usize>, Vec<f64>, f64, f64, f64), String> {
1002 let stations = decimate_stations(pool, maximum_points);
1003 let parameters = station_parameters(points, &stations)
1004 .ok_or_else(|| "fit_polyline: degenerate polyline".to_string())?;
1005 let kept = stations.iter().map(|index| points[*index]).collect::<Vec<_>>();
1006 let curve = if local_interpolation {
1007 interpolate_curve_local(&kept, ¶meters, 1.0)?
1008 } else {
1009 interpolate_curve(&kept, 3usize.min(kept.len() - 1), ¶meters)?
1010 };
1011 let (deviation, rms, between) =
1012 measure_polyline_fit(&curve, points, &stations, ¶meters, arc)?;
1013 Ok((curve, stations, parameters, deviation, rms, between))
1014}
1015
1016pub fn fit_polyline_at(
1024 points: &[Vec3],
1025 tolerance: f64,
1026 maximum_points: usize,
1027 local_interpolation: bool,
1028) -> Result<PolylineFit, String> {
1029 let fit = fit_polyline_inner(points, tolerance, maximum_points, local_interpolation, false)?;
1030 record_polyline_fit(&fit.report);
1031 Ok(fit)
1032}
1033
1034pub fn fit_polyline(
1083 points: &[Vec3],
1084 tolerance: f64,
1085 maximum_points: usize,
1086 local_interpolation: bool,
1087) -> Result<PolylineFit, String> {
1088 let refine = std::env::var("BREP_POLYLINE_FIT_REFINE").as_deref() != Ok("0");
1089 let asked = fit_polyline_inner(points, tolerance, maximum_points, local_interpolation, refine)?;
1090 if asked.report.met_tolerance() || !refine {
1091 record_polyline_fit(&asked.report);
1092 return Ok(asked);
1093 }
1094 let mut other =
1095 fit_polyline_inner(points, tolerance, maximum_points, !local_interpolation, refine)?;
1096 let spent = asked.report.rungs + other.report.rungs;
1097 let mut best = if other.report.deviation < asked.report.deviation {
1098 other.report.lane_switched = true;
1099 other
1100 } else {
1101 asked
1102 };
1103 best.report.rungs = spent;
1105 record_polyline_fit(&best.report);
1106 Ok(best)
1107}
1108
1109fn fit_polyline_inner(
1110 points: &[Vec3],
1111 tolerance: f64,
1112 maximum_points: usize,
1113 local_interpolation: bool,
1114 refine: bool,
1115) -> Result<PolylineFit, String> {
1116 let pool = station_pool(points, tolerance);
1117 if pool.len() < 2 {
1118 return Err("fit_polyline: degenerate polyline".into());
1119 }
1120 let arc = cumulative_chord(points);
1121 let band = dropped_band(points, &pool);
1122 let floor = tolerance.max(band);
1123 let mut budget = maximum_points.max(2);
1124 let ceiling = pool
1125 .len()
1126 .min(MAX_FIT_STATIONS)
1127 .min(budget.saturating_mul(LADDER_BUDGET_FACTOR))
1128 .max(budget);
1129 let (mut curve, mut stations, mut parameters, mut deviation, mut rms, mut between) =
1130 fit_rung(points, &pool, &arc, budget, local_interpolation)?;
1131 let mut rungs = 1usize;
1132 let mut exit = if deviation <= floor {
1133 PolylineFitExit::Converged
1134 } else {
1135 PolylineFitExit::Unrefined
1136 };
1137 while refine && deviation > floor {
1138 if budget >= ceiling {
1139 exit = if ceiling >= pool.len() {
1140 PolylineFitExit::InputExhausted
1141 } else {
1142 PolylineFitExit::StationCeiling
1143 };
1144 break;
1145 }
1146 budget = (budget * 2).min(ceiling);
1147 let rung = fit_rung(points, &pool, &arc, budget, local_interpolation)?;
1148 rungs += 1;
1149 if rung.3 >= deviation * STALL_FACTOR {
1150 if rung.3 < deviation {
1151 (curve, stations, parameters, deviation, rms, between) = rung;
1152 }
1153 exit = if deviation <= floor {
1157 PolylineFitExit::Converged
1158 } else {
1159 PolylineFitExit::Stalled
1160 };
1161 break;
1162 }
1163 (curve, stations, parameters, deviation, rms, between) = rung;
1164 if deviation <= floor {
1165 exit = PolylineFitExit::Converged;
1166 }
1167 }
1168 let report = PolylineFitReport {
1169 deviation,
1170 rms,
1171 between_stations: between,
1172 tolerance,
1173 floor,
1174 stations: stations.len(),
1175 controls: curve.control_points.len(),
1176 input_points: points.len(),
1177 pool_points: pool.len(),
1178 dropped_band: band,
1179 rungs,
1180 local_lane: local_interpolation,
1181 lane_switched: false,
1182 exit,
1183 };
1184 Ok(PolylineFit {
1185 curve,
1186 parameters,
1187 kept: stations.iter().map(|index| points[*index]).collect(),
1188 report,
1189 })
1190}
1191
1192#[derive(Clone, Copy, Debug, Default, PartialEq)]
1205pub struct PolylineFitLedger {
1206 pub fits: u64,
1208 pub unmet: u64,
1210 pub station_ceiling: u64,
1212 pub input_exhausted: u64,
1214 pub stalled: u64,
1216 pub unrefined: u64,
1218 pub refined: u64,
1220 pub met_at_pool: u64,
1223 pub lane_switched: u64,
1226 pub extra_rungs: u64,
1228 pub worst_unmet_deviation: f64,
1230 pub worst_dropped_band: f64,
1232}
1233
1234impl PolylineFitLedger {
1235 fn record(&mut self, report: &PolylineFitReport) {
1236 self.fits += 1;
1237 self.extra_rungs += (report.rungs - 1) as u64;
1238 if report.rungs > 1 {
1239 self.refined += 1;
1240 }
1241 self.worst_dropped_band = self.worst_dropped_band.max(report.dropped_band);
1242 if report.lane_switched {
1243 self.lane_switched += 1;
1244 }
1245 if report.met_tolerance() {
1246 if report.saturated() {
1247 self.met_at_pool += 1;
1248 }
1249 return;
1250 }
1251 self.unmet += 1;
1252 match report.exit {
1253 PolylineFitExit::Converged => {}
1254 PolylineFitExit::StationCeiling => self.station_ceiling += 1,
1255 PolylineFitExit::InputExhausted => self.input_exhausted += 1,
1256 PolylineFitExit::Stalled => self.stalled += 1,
1257 PolylineFitExit::Unrefined => self.unrefined += 1,
1258 }
1259 self.worst_unmet_deviation = self.worst_unmet_deviation.max(report.deviation);
1260 }
1261
1262 fn fold(&mut self, child: &PolylineFitLedger) {
1263 self.fits += child.fits;
1264 self.unmet += child.unmet;
1265 self.station_ceiling += child.station_ceiling;
1266 self.input_exhausted += child.input_exhausted;
1267 self.stalled += child.stalled;
1268 self.unrefined += child.unrefined;
1269 self.refined += child.refined;
1270 self.met_at_pool += child.met_at_pool;
1271 self.lane_switched += child.lane_switched;
1272 self.extra_rungs += child.extra_rungs;
1273 self.worst_unmet_deviation = self.worst_unmet_deviation.max(child.worst_unmet_deviation);
1274 self.worst_dropped_band = self.worst_dropped_band.max(child.worst_dropped_band);
1275 }
1276
1277 pub fn report_into(&self, diagnostics: &mut KernelDiagnostics) {
1284 diagnostics.count_n("fit.polyline.fits", self.fits);
1285 diagnostics.count_n("fit.polyline.unmet_tolerance", self.unmet);
1286 diagnostics.count_n("fit.polyline.exit.station_ceiling", self.station_ceiling);
1287 diagnostics.count_n("fit.polyline.exit.input_exhausted", self.input_exhausted);
1288 diagnostics.count_n("fit.polyline.exit.stalled", self.stalled);
1289 diagnostics.count_n("fit.polyline.exit.unrefined", self.unrefined);
1290 diagnostics.count_n("fit.polyline.refined", self.refined);
1291 diagnostics.count_n("fit.polyline.met_at_pool", self.met_at_pool);
1292 diagnostics.count_n("fit.polyline.lane_switched", self.lane_switched);
1293 diagnostics.count_n("fit.polyline.extra_rungs", self.extra_rungs);
1294 diagnostics.measure_max("fit.polyline.worst_unmet_deviation", self.worst_unmet_deviation);
1295 diagnostics.measure_max("fit.polyline.worst_dropped_band", self.worst_dropped_band);
1296 if self.unmet > 0 {
1297 diagnostics.event(
1298 DiagnosticSeverity::Degraded,
1299 KernelStage::Intersect,
1300 "fit.polyline.unmet_tolerance",
1301 format!(
1302 "{} of {} polyline fits did not reach the tolerance they were given \
1303 (station ceiling {}, input exhausted {}, stalled {}, unrefined {}); \
1304 worst deviation {:.3e}; the simplification's own band reaches {:.3e}",
1305 self.unmet,
1306 self.fits,
1307 self.station_ceiling,
1308 self.input_exhausted,
1309 self.stalled,
1310 self.unrefined,
1311 self.worst_unmet_deviation,
1312 self.worst_dropped_band,
1313 ),
1314 );
1315 }
1316 }
1317}
1318
1319thread_local! {
1320 static POLYLINE_FIT_SCOPES: std::cell::RefCell<Vec<PolylineFitLedger>> =
1322 const { std::cell::RefCell::new(Vec::new()) };
1323}
1324
1325pub struct PolylineFitScope {
1332 _thread_bound: std::marker::PhantomData<*const ()>,
1334}
1335
1336impl PolylineFitScope {
1337 pub fn open() -> Self {
1338 POLYLINE_FIT_SCOPES.with(|scopes| scopes.borrow_mut().push(PolylineFitLedger::default()));
1339 Self {
1340 _thread_bound: std::marker::PhantomData,
1341 }
1342 }
1343
1344 pub fn close(self) -> PolylineFitLedger {
1346 let ledger = POLYLINE_FIT_SCOPES.with(|scopes| {
1347 let mut scopes = scopes.borrow_mut();
1348 let ledger = scopes.pop().unwrap_or_default();
1349 if let Some(parent) = scopes.last_mut() {
1350 parent.fold(&ledger);
1351 }
1352 ledger
1353 });
1354 std::mem::forget(self);
1355 ledger
1356 }
1357}
1358
1359impl Drop for PolylineFitScope {
1360 fn drop(&mut self) {
1361 POLYLINE_FIT_SCOPES.with(|scopes| {
1362 scopes.borrow_mut().pop();
1363 });
1364 }
1365}
1366
1367fn record_polyline_fit(report: &PolylineFitReport) {
1368 if std::env::var_os("BREP_DEBUG_POLYLINE_FIT").is_some() {
1371 eprintln!("POLYLINE-FIT {report:?}");
1372 }
1373 POLYLINE_FIT_SCOPES.with(|scopes| {
1374 if let Some(open) = scopes.borrow_mut().last_mut() {
1375 open.record(report);
1376 }
1377 });
1378}
1379
1380
1381
1382fn raw_basis(knots: &[f64], degree: usize, span: usize, parameter: f64) -> Vec<f64> {
1386 let mut basis = vec![0.0; degree + 1];
1387 let mut left = vec![0.0; degree + 1];
1388 let mut right = vec![0.0; degree + 1];
1389 basis[0] = 1.0;
1390 for j in 1..=degree {
1391 left[j] = parameter - knots[span + 1 - j];
1392 right[j] = knots[span + j] - parameter;
1393 let mut saved = 0.0;
1394 for r in 0..j {
1395 let denominator = right[r + 1] + left[j - r];
1396 let temp = if denominator.abs() > 0.0 {
1397 basis[r] / denominator
1398 } else {
1399 0.0
1400 };
1401 basis[r] = saved + right[r + 1] * temp;
1402 saved = left[j - r] * temp;
1403 }
1404 basis[j] = saved;
1405 }
1406 basis
1407}
1408
1409fn raw_insert_knot(knots: &mut Vec<f64>, controls: &mut Vec<Vec3>, degree: usize, parameter: f64) {
1411 let span = knots
1415 .iter()
1416 .rposition(|&knot| knot <= parameter + 1e-14)
1417 .unwrap()
1418 .min(controls.len() - 1);
1419 let mut fresh = Vec::with_capacity(controls.len() + 1);
1420 fresh.extend_from_slice(&controls[..=span - degree]);
1421 for i in span - degree + 1..=span {
1422 let denominator = knots[i + degree] - knots[i];
1423 let alpha = if denominator.abs() > 0.0 {
1424 (parameter - knots[i]) / denominator
1425 } else {
1426 0.0
1427 };
1428 fresh.push(
1429 controls[i - 1]
1430 .scale(1.0 - alpha)
1431 .add(controls[i].scale(alpha)),
1432 );
1433 }
1434 fresh.extend_from_slice(&controls[span..]);
1435 *controls = fresh;
1436 knots.insert(span + 1, parameter);
1437}
1438
1439pub fn interpolate_curve_closed(points: &[Vec3], parameters: &[f64]) -> Result<NurbsCurve, String> {
1447 let degree = 3usize;
1448 let station_count = points.len();
1449 if station_count < 4 {
1450 return Err("interpolate_curve_closed: need at least 4 stations".into());
1451 }
1452 if parameters.len() != station_count + 1 {
1453 return Err(
1454 "interpolate_curve_closed: parameters must have one more entry than points".into(),
1455 );
1456 }
1457 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
1458 return Err("interpolate_curve_closed: parameters must increase".into());
1459 }
1460 let period = parameters[station_count] - parameters[0];
1461 let cyclic = |j: i64| -> f64 {
1464 let s = station_count as i64;
1465 let wrap = j.div_euclid(s);
1466 parameters[j.rem_euclid(s) as usize] + wrap as f64 * period
1467 };
1468 let raw_knots: Vec<f64> = (-3..=(station_count as i64 + 3)).map(cyclic).collect();
1469 let mut matrix = vec![vec![0.0; station_count]; station_count];
1472 for i in 0..station_count {
1473 let span = i + 3;
1474 let basis = raw_basis(&raw_knots, degree, span, parameters[i]);
1475 for (offset, value) in basis.iter().enumerate() {
1476 let index = (i as i64 + offset as i64 - 3).rem_euclid(station_count as i64) as usize;
1479 matrix[i][index] += value;
1480 }
1481 }
1482 let solve_axis = |axis: fn(Vec3) -> f64| {
1483 solve_dense(
1484 matrix.clone(),
1485 points.iter().copied().map(axis).collect::<Vec<_>>(),
1486 )
1487 };
1488 let xs = solve_axis(|point| point.x)?;
1489 let ys = solve_axis(|point| point.y)?;
1490 let zs = solve_axis(|point| point.z)?;
1491 let cyclic_controls: Vec<Vec3> = (0..station_count)
1492 .map(|index| Vec3::new(xs[index], ys[index], zs[index]))
1493 .collect();
1494 let mut window_controls: Vec<Vec3> = (-3..(station_count as i64))
1496 .map(|j| cyclic_controls[j.rem_euclid(station_count as i64) as usize])
1497 .collect();
1498 let mut window_knots = raw_knots.clone();
1499 for _ in 0..degree {
1502 raw_insert_knot(
1503 &mut window_knots,
1504 &mut window_controls,
1505 degree,
1506 parameters[0],
1507 );
1508 }
1509 for _ in 0..degree {
1510 raw_insert_knot(
1511 &mut window_knots,
1512 &mut window_controls,
1513 degree,
1514 parameters[station_count],
1515 );
1516 }
1517 let first = window_knots
1521 .iter()
1522 .position(|&knot| (knot - parameters[0]).abs() < 1e-12)
1523 .ok_or("interpolate_curve_closed: clamp lost the start knot")?;
1524 let last = window_knots
1525 .iter()
1526 .rposition(|&knot| (knot - parameters[station_count]).abs() < 1e-12)
1527 .ok_or("interpolate_curve_closed: clamp lost the end knot")?;
1528 let clamped_knots: Vec<f64> = window_knots[first..=last].to_vec();
1529 let control_count = clamped_knots.len() - degree - 1;
1530 let clamped_controls: Vec<Vec4> = window_controls[first..first + control_count]
1531 .iter()
1532 .map(|point| Vec4::from_point(*point, 1.0))
1533 .collect();
1534 NurbsCurve::new(degree, clamped_knots, clamped_controls)
1535}
1536