1use crate::{KnotVector, NurbsCurve, Vec3, Vec4};
2
3#[derive(Clone, Debug)]
4pub struct PolylineFit {
5 pub curve: NurbsCurve,
6 pub parameters: Vec<f64>,
7 pub kept: Vec<Vec3>,
8}
9
10pub fn solve_dense(mut matrix: Vec<Vec<f64>>, mut rhs: Vec<f64>) -> Result<Vec<f64>, String> {
11 let count = rhs.len();
12 if count == 0 || matrix.len() != count || matrix.iter().any(|row| row.len() != count) {
13 return Err("solve_dense: matrix must be square and match RHS".into());
14 }
15 let scale = matrix
16 .iter()
17 .flatten()
18 .fold(0.0_f64, |maximum, value| maximum.max(value.abs()));
19 if scale == 0.0 {
20 return Err("solve_dense: singular matrix".into());
21 }
22 for column in 0..count {
23 let pivot = (column..count)
24 .max_by(|&a, &b| matrix[a][column].abs().total_cmp(&matrix[b][column].abs()))
25 .unwrap();
26 if matrix[pivot][column].abs() <= 1e-13 * scale {
27 return Err("solve_dense: singular matrix".into());
28 }
29 matrix.swap(column, pivot);
30 rhs.swap(column, pivot);
31 let diagonal = matrix[column][column];
32 for row in column + 1..count {
33 let factor = matrix[row][column] / diagonal;
34 matrix[row][column] = 0.0;
35 for entry in column + 1..count {
36 matrix[row][entry] -= factor * matrix[column][entry];
37 }
38 rhs[row] -= factor * rhs[column];
39 }
40 }
41 let mut result = vec![0.0; count];
42 for row in (0..count).rev() {
43 let remainder: f64 = (row + 1..count)
44 .map(|column| matrix[row][column] * result[column])
45 .sum();
46 result[row] = (rhs[row] - remainder) / matrix[row][row];
47 }
48 Ok(result)
49}
50
51pub fn solve_small<const N: usize>(
55 mut matrix: [[f64; N]; N],
56 mut rhs: [f64; N],
57 count: usize,
58) -> Result<[f64; N], String> {
59 if count == 0 || count > N {
60 return Err("solve_small: invalid system size".into());
61 }
62 let scale = matrix
63 .iter()
64 .take(count)
65 .flat_map(|row| row.iter().take(count))
66 .fold(0.0_f64, |maximum, value| maximum.max(value.abs()));
67 if scale == 0.0 {
68 return Err("solve_small: singular matrix".into());
69 }
70 for column in 0..count {
71 let pivot = (column..count)
72 .max_by(|&a, &b| matrix[a][column].abs().total_cmp(&matrix[b][column].abs()))
73 .unwrap();
74 if matrix[pivot][column].abs() <= 1e-13 * scale {
75 return Err("solve_small: singular matrix".into());
76 }
77 matrix.swap(column, pivot);
78 rhs.swap(column, pivot);
79 let diagonal = matrix[column][column];
80 for row in column + 1..count {
81 let factor = matrix[row][column] / diagonal;
82 matrix[row][column] = 0.0;
83 for entry in column + 1..count {
84 matrix[row][entry] -= factor * matrix[column][entry];
85 }
86 rhs[row] -= factor * rhs[column];
87 }
88 }
89 let mut result = [0.0; N];
90 for row in (0..count).rev() {
91 let remainder: f64 = (row + 1..count)
92 .map(|column| matrix[row][column] * result[column])
93 .sum();
94 result[row] = (rhs[row] - remainder) / matrix[row][row];
95 }
96 Ok(result)
97}
98
99pub fn solve_collocation(
110 matrix: &[Vec<f64>],
111 rhs: &[f64],
112 degree: usize,
113) -> Result<Vec<f64>, String> {
114 let count = rhs.len();
115 let bandwidth = degree.max(1).min(count.saturating_sub(1).max(1));
116 if let Ok(solution) = solve_banded(matrix, rhs, bandwidth) {
117 let scale = rhs
118 .iter()
119 .fold(0.0_f64, |maximum, value| maximum.max(value.abs()));
120 let tolerance = 1e-9 * scale.max(1.0);
121 let mut residual = 0.0_f64;
122 for row in 0..count {
123 let lo = row.saturating_sub(bandwidth);
124 let hi = (row + bandwidth).min(count - 1);
125 let mut accumulated = 0.0;
126 for col in lo..=hi {
127 accumulated += matrix[row][col] * solution[col];
128 }
129 residual = residual.max((accumulated - rhs[row]).abs());
130 }
131 if residual <= tolerance {
132 return Ok(solution);
133 }
134 }
135 solve_dense(matrix.to_vec(), rhs.to_vec())
136}
137
138pub fn solve_banded(
139 matrix: &[Vec<f64>],
140 rhs: &[f64],
141 bandwidth: usize,
142) -> Result<Vec<f64>, String> {
143 let count = rhs.len();
144 if count == 0 || matrix.len() != count || matrix.iter().any(|row| row.len() != count) {
145 return Err("solve_banded: matrix must be square and match RHS".into());
146 }
147 let mut matrix = matrix.to_vec();
148 let mut rhs = rhs.to_vec();
149 for column in 0..count {
150 let pivot = matrix[column][column];
151 if pivot.abs() <= 1e-300 {
152 return Err("solve_banded: singular matrix".into());
153 }
154 let maximum_row = (column + bandwidth).min(count - 1);
155 for row in column + 1..=maximum_row {
156 let factor = matrix[row][column] / pivot;
157 if factor == 0.0 {
158 continue;
159 }
160 let maximum_column = (column + bandwidth).min(count - 1);
161 for entry in column..=maximum_column {
162 matrix[row][entry] -= factor * matrix[column][entry];
163 }
164 rhs[row] -= factor * rhs[column];
165 }
166 }
167 let mut result = vec![0.0; count];
168 for row in (0..count).rev() {
169 let mut value = rhs[row];
170 let maximum_column = (row + bandwidth).min(count - 1);
171 for column in row + 1..=maximum_column {
172 value -= matrix[row][column] * result[column];
173 }
174 if matrix[row][row].abs() <= 1e-300 {
175 return Err("solve_banded: singular matrix".into());
176 }
177 result[row] = value / matrix[row][row];
178 }
179 Ok(result)
180}
181
182pub fn interpolate_homogeneous(
189 points: &[Vec4],
190 degree: usize,
191 parameters: &[f64],
192) -> Result<NurbsCurve, String> {
193 if points.len() < 2 || parameters.len() != points.len() {
194 return Err(
195 "interpolate_homogeneous: points and parameters must have matching length >= 2".into(),
196 );
197 }
198 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
199 return Err("interpolate_homogeneous: parameters must be strictly increasing".into());
200 }
201 let n = points.len() - 1;
202 let degree = degree.min(n);
203 let m = n + degree + 1;
204 let mut knots = vec![0.0; m + 1];
205 for knot in &mut knots[m - degree..=m] {
206 *knot = 1.0;
207 }
208 for j in 1..=n.saturating_sub(degree) {
209 knots[j + degree] = parameters[j..j + degree].iter().sum::<f64>() / degree as f64;
210 }
211 let knot_vector = KnotVector::new(knots.clone(), degree)?;
212 let mut matrix = vec![vec![0.0; n + 1]; n + 1];
213 for (row, ¶meter) in parameters.iter().enumerate() {
214 let span = knot_vector.find_span(parameter);
215 let basis = knot_vector.basis_functions(span, parameter);
216 for (offset, value) in basis.into_iter().enumerate() {
217 matrix[row][span - degree + offset] = value;
218 }
219 }
220 let solve_axis = |axis: fn(&Vec4) -> f64| {
221 solve_collocation(
222 &matrix,
223 &points.iter().map(axis).collect::<Vec<_>>(),
224 degree,
225 )
226 };
227 let xs = solve_axis(|point| point.x)?;
228 let ys = solve_axis(|point| point.y)?;
229 let zs = solve_axis(|point| point.z)?;
230 let ws = solve_axis(|point| point.w)?;
231 NurbsCurve::new(
232 degree,
233 knots,
234 (0..=n)
235 .map(|index| Vec4 {
236 x: xs[index],
237 y: ys[index],
238 z: zs[index],
239 w: ws[index],
240 })
241 .collect(),
242 )
243}
244
245pub fn interpolate_curve(
261 points: &[Vec3],
262 degree: usize,
263 parameters: &[f64],
264) -> Result<NurbsCurve, String> {
265 if points.len() < 2 || parameters.len() != points.len() {
266 return Err(
267 "interpolate_curve: points and parameters must have matching length >= 2".into(),
268 );
269 }
270 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
271 return Err("interpolate_curve: parameters must be strictly increasing".into());
272 }
273 let n = points.len() - 1;
274 let degree = degree.min(n);
275 let m = n + degree + 1;
276 let mut knots = vec![parameters[0]; m + 1];
277 for knot in &mut knots[m - degree..=m] {
278 *knot = parameters[n];
279 }
280 for j in 1..=n.saturating_sub(degree) {
281 knots[j + degree] = parameters[j..j + degree].iter().sum::<f64>() / degree as f64;
282 }
283 let knot_vector = KnotVector::new(knots.clone(), degree)?;
284 let mut matrix = vec![vec![0.0; n + 1]; n + 1];
285 for (row, ¶meter) in parameters.iter().enumerate() {
286 let span = knot_vector.find_span(parameter);
287 let basis = knot_vector.basis_functions(span, parameter);
288 for (offset, value) in basis.into_iter().enumerate() {
289 matrix[row][span - degree + offset] = value;
290 }
291 }
292 let solve_axis = |axis: fn(Vec3) -> f64| {
293 solve_collocation(
294 &matrix,
295 &points.iter().copied().map(axis).collect::<Vec<_>>(),
296 degree,
297 )
298 };
299 let xs = solve_axis(|point| point.x)?;
300 let ys = solve_axis(|point| point.y)?;
301 let zs = solve_axis(|point| point.z)?;
302 NurbsCurve::new(
303 degree,
304 knots,
305 (0..=n)
306 .map(|index| Vec4::from_point(Vec3::new(xs[index], ys[index], zs[index]), 1.0))
307 .collect(),
308 )
309}
310
311pub fn interpolate_curve_with_end_tangents(
318 points: &[Vec3],
319 parameters: &[f64],
320 start_tangent: Vec3,
321 end_tangent: Vec3,
322) -> Result<NurbsCurve, String> {
323 if points.len() < 2 || parameters.len() != points.len() {
324 return Err(
325 "interpolate_curve_with_end_tangents: points and parameters must match, >= 2".into(),
326 );
327 }
328 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
329 return Err("interpolate_curve_with_end_tangents: parameters must increase".into());
330 }
331 let degree = 3usize;
332 let n = points.len() - 1;
333 let control_count = n + 3;
334 let t0 = parameters[0];
335 let t1 = parameters[n];
336 let mut knots = vec![t0; degree + 1];
339 for j in 0..n.saturating_sub(1) {
340 let window = ¶meters[j + 1..(j + degree).min(n) + 1];
341 knots.push(window.iter().sum::<f64>() / window.len() as f64);
342 }
343 knots.extend(std::iter::repeat(t1).take(degree + 1));
344 if knots.len() != control_count + degree + 1 {
345 return Err(format!(
346 "interpolate_curve_with_end_tangents: internal knot count {} for {} controls",
347 knots.len(),
348 control_count
349 ));
350 }
351 let knot_vector = KnotVector::new(knots.clone(), degree)?;
352 let mut matrix = vec![vec![0.0; control_count]; control_count];
353 let mut rhs_points = vec![Vec3::default(); control_count];
354 matrix[0][0] = 1.0;
358 rhs_points[0] = points[0];
359 let start_span = knots[degree + 1] - t0;
360 matrix[1][0] = -(degree as f64) / start_span;
361 matrix[1][1] = (degree as f64) / start_span;
362 rhs_points[1] = start_tangent;
363 for (index, ¶meter) in parameters.iter().enumerate().take(n).skip(1) {
364 let row = index + 1;
365 let span = knot_vector.find_span(parameter);
366 let basis = knot_vector.basis_functions(span, parameter);
367 for (offset, value) in basis.into_iter().enumerate() {
368 matrix[row][span - degree + offset] = value;
369 }
370 rhs_points[row] = points[index];
371 }
372 let end_span = t1 - knots[control_count - 1];
373 matrix[control_count - 2][control_count - 2] = -(degree as f64) / end_span;
374 matrix[control_count - 2][control_count - 1] = (degree as f64) / end_span;
375 rhs_points[control_count - 2] = end_tangent;
376 matrix[control_count - 1][control_count - 1] = 1.0;
377 rhs_points[control_count - 1] = points[n];
378 let solve_axis = |axis: fn(Vec3) -> f64| {
379 solve_dense(
380 matrix.clone(),
381 rhs_points.iter().copied().map(axis).collect::<Vec<_>>(),
382 )
383 };
384 let xs = solve_axis(|point| point.x)?;
385 let ys = solve_axis(|point| point.y)?;
386 let zs = solve_axis(|point| point.z)?;
387 NurbsCurve::new(
388 degree,
389 knots,
390 (0..control_count)
391 .map(|index| Vec4::from_point(Vec3::new(xs[index], ys[index], zs[index]), 1.0))
392 .collect(),
393 )
394}
395
396pub fn interpolate_curve_thinned(
397 points: &[Vec3],
398 degree: usize,
399 maximum_points: usize,
400) -> Result<NurbsCurve, String> {
401 if maximum_points < 2 {
402 return Err("interpolate_curve_thinned: maximum point count must be at least 2".into());
403 }
404 if points.len() <= maximum_points {
405 let parameters = chord_parameters(points);
406 return interpolate_curve(points, degree, ¶meters);
407 }
408 let step = (points.len() - 1) as f64 / (maximum_points - 1) as f64;
409 let thinned = (0..maximum_points)
410 .map(|index| points[(index as f64 * step).round() as usize])
411 .collect::<Vec<_>>();
412 let parameters = chord_parameters(&thinned);
413 interpolate_curve(&thinned, degree, ¶meters)
414}
415
416fn chord_parameters(points: &[Vec3]) -> Vec<f64> {
417 if points.len() < 2 {
418 return vec![0.0; points.len()];
419 }
420 let mut parameters = vec![0.0; points.len()];
421 for index in 1..points.len() {
422 parameters[index] = parameters[index - 1] + points[index].sub(points[index - 1]).length();
423 }
424 let length = parameters[points.len() - 1];
425 if length <= 1e-15 {
426 for (index, parameter) in parameters.iter_mut().enumerate() {
427 *parameter = index as f64 / (points.len() - 1) as f64;
428 }
429 } else {
430 for parameter in &mut parameters {
431 *parameter /= length;
432 }
433 }
434 parameters
435}
436
437pub fn simplify_polyline(points: &[Vec3], tolerance: f64) -> Vec<Vec3> {
438 if points.len() <= 2 {
439 return points.to_vec();
440 }
441 let mut keep = vec![false; points.len()];
442 keep[0] = true;
443 keep[points.len() - 1] = true;
444 let mut stack = vec![(0usize, points.len() - 1)];
445 while let Some((start, end)) = stack.pop() {
446 if end - start < 2 {
447 continue;
448 }
449 let a = points[start];
450 let direction = points[end].sub(a);
451 let length_squared = direction.length_squared().max(1e-300);
452 let mut worst = None;
453 let mut worst_distance = tolerance;
454 for (index, point) in points.iter().enumerate().take(end).skip(start + 1) {
455 let fraction = point.sub(a).dot(direction) / length_squared;
456 let fraction = fraction.clamp(0.0, 1.0);
457 let distance = point.sub(a.add(direction.scale(fraction))).length();
458 if distance > worst_distance {
459 worst_distance = distance;
460 worst = Some(index);
461 }
462 }
463 if let Some(index) = worst {
464 keep[index] = true;
465 stack.push((start, index));
466 stack.push((index, end));
467 }
468 }
469 points
470 .iter()
471 .copied()
472 .zip(keep)
473 .filter_map(|(point, keep)| keep.then_some(point))
474 .collect()
475}
476
477pub fn interpolate_curve_local(
478 points: &[Vec3],
479 parameters: &[f64],
480 tension: f64,
481) -> Result<NurbsCurve, String> {
482 if points.len() != parameters.len() || points.len() < 2 {
483 return Err(
484 "interpolate_curve_local: points and parameters must have matching length >= 2".into(),
485 );
486 }
487 if points.len() == 2 {
488 return interpolate_curve(points, 1, parameters);
489 }
490 let count = points.len();
491 let mut tangents = vec![Vec3::default(); count];
492 tangents[0] = points[1]
493 .sub(points[0])
494 .scale(1.0 / (parameters[1] - parameters[0]));
495 tangents[count - 1] = points[count - 1]
496 .sub(points[count - 2])
497 .scale(1.0 / (parameters[count - 1] - parameters[count - 2]));
498 for index in 1..count - 1 {
499 let previous_interval = parameters[index] - parameters[index - 1];
500 let next_interval = parameters[index + 1] - parameters[index];
501 let total = previous_interval + next_interval;
502 let previous_secant = points[index]
503 .sub(points[index - 1])
504 .scale(1.0 / previous_interval);
505 let next_secant = points[index + 1]
506 .sub(points[index])
507 .scale(1.0 / next_interval);
508 let mut tangent =
509 points[index - 1]
510 .scale(-next_interval / (previous_interval * total))
511 .add(points[index].scale(
512 (next_interval - previous_interval) / (previous_interval * next_interval),
513 ))
514 .add(points[index + 1].scale(previous_interval / (next_interval * total)));
515 if previous_secant.dot(next_secant) <= 0.0
516 || tangent.dot(previous_secant) <= 0.0
517 || tangent.dot(next_secant) <= 0.0
518 {
519 tangent = Vec3::default();
520 } else {
521 let maximum = 3.0 * previous_secant.length().min(next_secant.length());
522 if tangent.length() > maximum {
523 tangent = tangent.normalized()?.scale(maximum);
524 }
525 }
526 tangents[index] = tangent;
527 }
528 if tension != 1.0 {
529 for tangent in &mut tangents {
530 *tangent = tangent.scale(tension);
531 }
532 }
533 let mut control_points = vec![Vec4::from_point(points[0], 1.0)];
534 for index in 0..count - 1 {
535 let interval = parameters[index + 1] - parameters[index];
536 control_points.extend([
537 Vec4::from_point(
538 points[index].add(tangents[index].scale(interval / 3.0)),
539 1.0,
540 ),
541 Vec4::from_point(
542 points[index + 1].sub(tangents[index + 1].scale(interval / 3.0)),
543 1.0,
544 ),
545 Vec4::from_point(points[index + 1], 1.0),
546 ]);
547 }
548 let mut knots = vec![parameters[0]; 4];
549 for parameter in ¶meters[1..count - 1] {
550 knots.extend([*parameter; 3]);
551 }
552 knots.extend([parameters[count - 1]; 4]);
553 NurbsCurve::new(3, knots, control_points)
554}
555
556pub fn fit_polyline(
557 points: &[Vec3],
558 tolerance: f64,
559 maximum_points: usize,
560 local_interpolation: bool,
561) -> Result<PolylineFit, String> {
562 let mut kept = simplify_polyline(points, tolerance);
563 if kept.len() > 2 {
564 let total: f64 = kept
565 .windows(2)
566 .map(|pair| pair[1].sub(pair[0]).length())
567 .sum();
568 let floor = (tolerance * 0.01).max(total * 1e-4);
569 let first = kept[0];
570 let last = kept[kept.len() - 1];
571 let mut conditioned = vec![first];
572 for point in &kept[1..kept.len() - 1] {
573 if point.sub(first).length() > floor
574 && point.sub(last).length() > floor
575 && point.sub(*conditioned.last().unwrap()).length() > floor
576 {
577 conditioned.push(*point);
578 }
579 }
580 conditioned.push(last);
581 kept = conditioned;
582 } else {
583 let mut distinct = Vec::new();
584 for point in kept {
585 if distinct
586 .last()
587 .is_none_or(|previous: &Vec3| point.sub(*previous).length() > tolerance * 0.01)
588 {
589 distinct.push(point);
590 }
591 }
592 kept = distinct;
593 }
594 if kept.len() < 2 {
595 return Err("fit_polyline: degenerate polyline".into());
596 }
597 let maximum_points = maximum_points.max(2);
598 if kept.len() > maximum_points {
599 let step = (kept.len() - 1) as f64 / (maximum_points - 1) as f64;
600 kept = (0..maximum_points)
601 .map(|index| kept[(index as f64 * step).round() as usize])
602 .collect();
603 }
604 let total: f64 = kept
605 .windows(2)
606 .map(|pair| pair[1].sub(pair[0]).length())
607 .sum();
608 if total <= 0.0 {
609 return Err("fit_polyline: degenerate polyline".into());
610 }
611 let mut parameters = vec![0.0; kept.len()];
612 let mut accumulated = 0.0;
613 for index in 1..kept.len() {
614 accumulated += kept[index].sub(kept[index - 1]).length();
615 parameters[index] = accumulated / total;
616 }
617 *parameters.last_mut().unwrap() = 1.0;
618 let curve = if local_interpolation {
619 interpolate_curve_local(&kept, ¶meters, 1.0)?
620 } else {
621 interpolate_curve(&kept, 3usize.min(kept.len() - 1), ¶meters)?
622 };
623 Ok(PolylineFit {
624 curve,
625 parameters,
626 kept,
627 })
628}
629
630fn raw_basis(knots: &[f64], degree: usize, span: usize, parameter: f64) -> Vec<f64> {
638 let mut basis = vec![0.0; degree + 1];
639 let mut left = vec![0.0; degree + 1];
640 let mut right = vec![0.0; degree + 1];
641 basis[0] = 1.0;
642 for j in 1..=degree {
643 left[j] = parameter - knots[span + 1 - j];
644 right[j] = knots[span + j] - parameter;
645 let mut saved = 0.0;
646 for r in 0..j {
647 let denominator = right[r + 1] + left[j - r];
648 let temp = if denominator.abs() > 0.0 {
649 basis[r] / denominator
650 } else {
651 0.0
652 };
653 basis[r] = saved + right[r + 1] * temp;
654 saved = left[j - r] * temp;
655 }
656 basis[j] = saved;
657 }
658 basis
659}
660
661fn raw_insert_knot(knots: &mut Vec<f64>, controls: &mut Vec<Vec3>, degree: usize, parameter: f64) {
663 let span = knots
667 .iter()
668 .rposition(|&knot| knot <= parameter + 1e-14)
669 .unwrap()
670 .min(controls.len() - 1);
671 let mut fresh = Vec::with_capacity(controls.len() + 1);
672 fresh.extend_from_slice(&controls[..=span - degree]);
673 for i in span - degree + 1..=span {
674 let denominator = knots[i + degree] - knots[i];
675 let alpha = if denominator.abs() > 0.0 {
676 (parameter - knots[i]) / denominator
677 } else {
678 0.0
679 };
680 fresh.push(
681 controls[i - 1]
682 .scale(1.0 - alpha)
683 .add(controls[i].scale(alpha)),
684 );
685 }
686 fresh.extend_from_slice(&controls[span..]);
687 *controls = fresh;
688 knots.insert(span + 1, parameter);
689}
690
691pub fn interpolate_curve_closed(points: &[Vec3], parameters: &[f64]) -> Result<NurbsCurve, String> {
699 let degree = 3usize;
700 let station_count = points.len();
701 if station_count < 4 {
702 return Err("interpolate_curve_closed: need at least 4 stations".into());
703 }
704 if parameters.len() != station_count + 1 {
705 return Err(
706 "interpolate_curve_closed: parameters must have one more entry than points".into(),
707 );
708 }
709 if parameters.windows(2).any(|pair| pair[1] <= pair[0]) {
710 return Err("interpolate_curve_closed: parameters must increase".into());
711 }
712 let period = parameters[station_count] - parameters[0];
713 let cyclic = |j: i64| -> f64 {
716 let s = station_count as i64;
717 let wrap = j.div_euclid(s);
718 parameters[j.rem_euclid(s) as usize] + wrap as f64 * period
719 };
720 let raw_knots: Vec<f64> = (-3..=(station_count as i64 + 3)).map(cyclic).collect();
721 let mut matrix = vec![vec![0.0; station_count]; station_count];
724 for i in 0..station_count {
725 let span = i + 3;
726 let basis = raw_basis(&raw_knots, degree, span, parameters[i]);
727 for (offset, value) in basis.iter().enumerate() {
728 let index = (i as i64 + offset as i64 - 3).rem_euclid(station_count as i64) as usize;
731 matrix[i][index] += value;
732 }
733 }
734 let solve_axis = |axis: fn(Vec3) -> f64| {
735 solve_dense(
736 matrix.clone(),
737 points.iter().copied().map(axis).collect::<Vec<_>>(),
738 )
739 };
740 let xs = solve_axis(|point| point.x)?;
741 let ys = solve_axis(|point| point.y)?;
742 let zs = solve_axis(|point| point.z)?;
743 let cyclic_controls: Vec<Vec3> = (0..station_count)
744 .map(|index| Vec3::new(xs[index], ys[index], zs[index]))
745 .collect();
746 let mut window_controls: Vec<Vec3> = (-3..(station_count as i64))
748 .map(|j| cyclic_controls[j.rem_euclid(station_count as i64) as usize])
749 .collect();
750 let mut window_knots = raw_knots.clone();
751 for _ in 0..degree {
754 raw_insert_knot(
755 &mut window_knots,
756 &mut window_controls,
757 degree,
758 parameters[0],
759 );
760 }
761 for _ in 0..degree {
762 raw_insert_knot(
763 &mut window_knots,
764 &mut window_controls,
765 degree,
766 parameters[station_count],
767 );
768 }
769 let first = window_knots
773 .iter()
774 .position(|&knot| (knot - parameters[0]).abs() < 1e-12)
775 .ok_or("interpolate_curve_closed: clamp lost the start knot")?;
776 let last = window_knots
777 .iter()
778 .rposition(|&knot| (knot - parameters[station_count]).abs() < 1e-12)
779 .ok_or("interpolate_curve_closed: clamp lost the end knot")?;
780 let clamped_knots: Vec<f64> = window_knots[first..=last].to_vec();
781 let control_count = clamped_knots.len() - degree - 1;
782 let clamped_controls: Vec<Vec4> = window_controls[first..first + control_count]
783 .iter()
784 .map(|point| Vec4::from_point(*point, 1.0))
785 .collect();
786 NurbsCurve::new(degree, clamped_knots, clamped_controls)
787}
788
789