1use crate::curve::{
2 basis_derivatives_into, basis_functions_into, knot_clamp, knot_domain, knot_find_span,
3 validate_knots, MAX_STACK_DEGREE, MAX_STACK_ORDER,
4};
5use crate::{KnotVector, NurbsCurve, Vec3, Vec4};
6use serde::{Deserialize, Serialize};
7
8#[path = "surface/extension.rs"]
9mod extension;
10pub use extension::{
11 ExtendRefusal, SurfaceSide, WeightSite, MAXIMUM_FOLD_ANGLE, MAXIMUM_GROWTH_RATIO,
12};
13
14const EPS: f64 = 1e-12;
15const NEAR_AXIS_RELATIVE_TOLERANCE: f64 = 1e-4;
19
20pub(crate) const TENSOR_MAX_BLOCK: usize = 24;
29
30#[derive(Clone, Debug, Deserialize, Serialize)]
40pub struct NurbsSurface {
41 pub degree_u: usize,
42 pub degree_v: usize,
43 pub knots_u: Vec<f64>,
44 pub knots_v: Vec<f64>,
45 pub control_points: Vec<Vec<Vec4>>,
46 #[serde(skip, default)]
50 analytic: std::cell::OnceCell<Option<crate::AnalyticSurface>>,
51 #[serde(skip, default)]
53 validated: std::cell::Cell<bool>,
54 #[serde(skip, default)]
57 closed_directions: std::cell::OnceCell<(bool, bool)>,
58 #[serde(skip, default)]
61 pub(crate) projection_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
62 #[serde(skip, default)]
66 pub(crate) projection_dense_grid: std::cell::OnceCell<Vec<(f64, f64, Vec3)>>,
67 #[serde(skip, default)]
71 pub(crate) projection_ring_grid: std::cell::OnceCell<Vec<Vec3>>,
72 #[serde(skip, default)]
76 pub(crate) projection_cells: std::cell::OnceCell<Option<crate::projection::ProjectionCells>>,
77}
78
79impl NurbsSurface {
80 pub fn new(
81 degree_u: usize,
82 degree_v: usize,
83 knots_u: Vec<f64>,
84 knots_v: Vec<f64>,
85 control_points: Vec<Vec<Vec4>>,
86 ) -> Result<Self, String> {
87 let surface = Self {
88 degree_u,
89 degree_v,
90 knots_u,
91 knots_v,
92 control_points,
93 analytic: std::cell::OnceCell::new(),
94 validated: std::cell::Cell::new(false),
95 closed_directions: std::cell::OnceCell::new(),
96 projection_grid: std::cell::OnceCell::new(),
97 projection_dense_grid: std::cell::OnceCell::new(),
98 projection_ring_grid: std::cell::OnceCell::new(),
99 projection_cells: std::cell::OnceCell::new(),
100 };
101 surface.ensure_valid()?;
102 Ok(surface)
103 }
104
105 pub fn closed_directions(&self) -> Result<(bool, bool), String> {
110 if let Some(&cached) = self.closed_directions.get() {
111 return Ok(cached);
112 }
113 const CLOSED_SEAM_TOLERANCE: f64 = 1e-6;
114 let [u0, u1] = self.domain_u()?;
115 let [v0, v1] = self.domain_v()?;
116 let closed_along = |direction_u: bool| -> Result<bool, String> {
117 let mut travels = false;
125 for fraction in [0.17, 0.5, 0.83] {
126 let (a, b, middle) = if direction_u {
127 let v = v0 + (v1 - v0) * fraction;
128 (
129 self.evaluate(u0, v)?,
130 self.evaluate(u1, v)?,
131 self.evaluate(0.5 * (u0 + u1), v)?,
132 )
133 } else {
134 let u = u0 + (u1 - u0) * fraction;
135 (
136 self.evaluate(u, v0)?,
137 self.evaluate(u, v1)?,
138 self.evaluate(u, 0.5 * (v0 + v1))?,
139 )
140 };
141 if a.sub(b).length() > CLOSED_SEAM_TOLERANCE {
142 return Ok(false);
143 }
144 if middle.sub(a).length() > CLOSED_SEAM_TOLERANCE {
145 travels = true;
146 }
147 }
148 Ok(travels)
149 };
150 let value = (closed_along(true)?, closed_along(false)?);
151 Ok(*self.closed_directions.get_or_init(|| value))
152 }
153
154 fn ensure_valid(&self) -> Result<(), String> {
156 if self.validated.get() {
157 return Ok(());
158 }
159 validate_knots(&self.knots_u, self.degree_u)?;
160 validate_knots(&self.knots_v, self.degree_v)?;
161 let rows = self.knots_u.len() - self.degree_u - 1;
162 let columns = self.knots_v.len() - self.degree_v - 1;
163 if self.control_points.len() != rows {
164 return Err(format!(
165 "NurbsSurface: u knot vector implies {} rows, got {}",
166 rows,
167 self.control_points.len()
168 ));
169 }
170 for row in &self.control_points {
171 if row.len() != columns {
172 return Err(format!(
173 "NurbsSurface: v knot vector implies {} columns, got {}",
174 columns,
175 row.len()
176 ));
177 }
178 if row.iter().any(|point| {
179 point.w <= EPS
180 || ![point.x, point.y, point.z, point.w]
181 .iter()
182 .all(|value| value.is_finite())
183 }) {
184 return Err(
185 "NurbsSurface: control points must be finite with positive weights".into(),
186 );
187 }
188 }
189 self.validated.set(true);
190 Ok(())
191 }
192
193 pub fn domain_u(&self) -> Result<[f64; 2], String> {
194 self.ensure_valid()?;
195 Ok(knot_domain(&self.knots_u, self.degree_u))
196 }
197
198 pub fn domain_v(&self) -> Result<[f64; 2], String> {
199 self.ensure_valid()?;
200 Ok(knot_domain(&self.knots_v, self.degree_v))
201 }
202
203 pub fn analytic(&self) -> Option<&crate::AnalyticSurface> {
206 self.analytic
207 .get_or_init(|| crate::analytic_surface::recognize(self))
208 .as_ref()
209 }
210
211 pub(crate) fn warm_caches(&self) -> Vec<&'static str> {
215 let mut warm = Vec::new();
216 let cells = [
217 ("analytic", self.analytic.get().is_some()),
218 ("validated", self.validated.get()),
219 ("closed", self.closed_directions.get().is_some()),
220 ("grid", self.projection_grid.get().is_some()),
221 ("dense", self.projection_dense_grid.get().is_some()),
222 ("ring", self.projection_ring_grid.get().is_some()),
223 ("cells", self.projection_cells.get().is_some()),
224 ];
225 for (name, held) in cells {
226 if held {
227 warm.push(name);
228 }
229 }
230 warm
231 }
232
233 pub(crate) fn exchange_caches(&mut self, names: &[&str], from: Option<&Self>) {
238 let wants = |name: &str| names.iter().any(|&n| n == name || n == "all");
239 if wants("analytic") {
240 self.analytic = from.map(|f| f.analytic.clone()).unwrap_or_default();
241 }
242 if wants("validated") {
243 self.validated = std::cell::Cell::new(from.is_some_and(|f| f.validated.get()));
244 }
245 if wants("closed") {
246 self.closed_directions = from.map(|f| f.closed_directions.clone()).unwrap_or_default();
247 }
248 if wants("grid") {
249 self.projection_grid = from.map(|f| f.projection_grid.clone()).unwrap_or_default();
250 }
251 if wants("dense") {
252 self.projection_dense_grid = from.map(|f| f.projection_dense_grid.clone()).unwrap_or_default();
253 }
254 if wants("ring") {
255 self.projection_ring_grid = from.map(|f| f.projection_ring_grid.clone()).unwrap_or_default();
256 }
257 if wants("cells") {
258 self.projection_cells = from.map(|f| f.projection_cells.clone()).unwrap_or_default();
259 }
260 }
261
262 fn knot_vectors(&self) -> Result<(KnotVector, KnotVector), String> {
263 Ok((
264 KnotVector::new(self.knots_u.clone(), self.degree_u)?,
265 KnotVector::new(self.knots_v.clone(), self.degree_v)?,
266 ))
267 }
268
269 pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String> {
270 self.ensure_valid()?;
271 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
272 return self.evaluate_homogeneous_heap(u, v);
273 }
274 let u = knot_clamp(&self.knots_u, self.degree_u, u);
275 let v = knot_clamp(&self.knots_v, self.degree_v, v);
276 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
277 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
278 let mut basis_u = [0.0f64; MAX_STACK_ORDER];
279 let mut basis_v = [0.0f64; MAX_STACK_ORDER];
280 basis_functions_into(&self.knots_u, self.degree_u, span_u, u, &mut basis_u);
281 basis_functions_into(&self.knots_v, self.degree_v, span_v, v, &mut basis_v);
282 let mut point = Vec4 {
283 x: 0.0,
284 y: 0.0,
285 z: 0.0,
286 w: 0.0,
287 };
288 for (i, value_u) in basis_u[..=self.degree_u].iter().enumerate() {
289 let row = &self.control_points[span_u - self.degree_u + i];
290 for (j, value_v) in basis_v[..=self.degree_v].iter().enumerate() {
291 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
292 }
293 }
294 Ok(point)
295 }
296
297 fn evaluate_homogeneous_heap(&self, u: f64, v: f64) -> Result<Vec4, String> {
298 let (knot_u, knot_v) = self.knot_vectors()?;
299 let u = knot_u.clamp_param(u);
300 let v = knot_v.clamp_param(v);
301 let span_u = knot_u.find_span(u);
302 let span_v = knot_v.find_span(v);
303 let basis_u = knot_u.basis_functions(span_u, u);
304 let basis_v = knot_v.basis_functions(span_v, v);
305 let mut point = Vec4 {
306 x: 0.0,
307 y: 0.0,
308 z: 0.0,
309 w: 0.0,
310 };
311 for (i, value_u) in basis_u.iter().enumerate() {
312 let row = &self.control_points[span_u - self.degree_u + i];
313 for (j, value_v) in basis_v.iter().enumerate() {
314 point = point.add(row[span_v - self.degree_v + j].scale(value_u * value_v));
315 }
316 }
317 Ok(point)
318 }
319
320 pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String> {
321 self.evaluate_homogeneous(u, v)?.point()
322 }
323
324 pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String> {
330 Ok(self.derivatives_extended(u, v, 0)?[0][0])
331 }
332
333 pub fn derivatives_extended(
338 &self,
339 u: f64,
340 v: f64,
341 derivative_count: usize,
342 ) -> Result<Vec<Vec<Vec3>>, String> {
343 let [u0, u1] = self.domain_u()?;
344 let [v0, v1] = self.domain_v()?;
345 let (closed_u, closed_v) = self.closed_directions()?;
346 let mut uu = u;
347 let mut vv = v;
348 if closed_u && (u < u0 || u > u1) {
349 uu = u0 + (u - u0).rem_euclid(u1 - u0);
350 }
351 if closed_v && (v < v0 || v > v1) {
352 vv = v0 + (v - v0).rem_euclid(v1 - v0);
353 }
354 let du_out = if uu < u0 {
355 uu - u0
356 } else if uu > u1 {
357 uu - u1
358 } else {
359 0.0
360 };
361 let dv_out = if vv < v0 {
362 vv - v0
363 } else if vv > v1 {
364 vv - v1
365 } else {
366 0.0
367 };
368 if du_out == 0.0 && dv_out == 0.0 {
369 return self.derivatives(uu, vv, derivative_count);
370 }
371 let anchor_u = uu - du_out;
372 let anchor_v = vv - dv_out;
373 let base = self.derivatives(anchor_u, anchor_v, derivative_count.max(2))?;
374 let order = derivative_count + 1;
375 let mut result = vec![vec![Vec3::default(); order]; order];
376 result[0][0] = base[0][0]
380 .add(base[1][0].scale(du_out))
381 .add(base[0][1].scale(dv_out))
382 .add(base[1][1].scale(du_out * dv_out));
383 if derivative_count >= 1 {
384 result[1][0] = base[1][0].add(base[1][1].scale(dv_out));
385 result[0][1] = base[0][1].add(base[1][1].scale(du_out));
386 result[1][1] = base[1][1];
387 }
388 if derivative_count >= 2 {
389 let second = self.derivatives(anchor_u, anchor_v, 2)?;
390 if du_out == 0.0 {
391 result[2][0] = second[2][0];
392 }
393 if dv_out == 0.0 {
394 result[0][2] = second[0][2];
395 }
396 }
397 Ok(result)
398 }
399
400 pub fn derivatives(
401 &self,
402 u: f64,
403 v: f64,
404 derivative_count: usize,
405 ) -> Result<Vec<Vec<Vec3>>, String> {
406 self.ensure_valid()?;
407 let du = derivative_count.min(self.degree_u);
408 let dv = derivative_count.min(self.degree_v);
409 let stride = derivative_count + 1;
410 let zero = Vec4 {
412 x: 0.0,
413 y: 0.0,
414 z: 0.0,
415 w: 0.0,
416 };
417 let mut homogeneous = vec![zero; stride * stride];
418 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
419 let u = knot_clamp(&self.knots_u, self.degree_u, u);
420 let v = knot_clamp(&self.knots_v, self.degree_v, v);
421 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
422 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
423 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
424 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
425 basis_derivatives_into(
426 &self.knots_u,
427 self.degree_u,
428 span_u,
429 u,
430 du,
431 &mut basis_u[..=du],
432 );
433 basis_derivatives_into(
434 &self.knots_v,
435 self.degree_v,
436 span_v,
437 v,
438 dv,
439 &mut basis_v[..=dv],
440 );
441 for k in 0..=du {
442 for l in 0..=dv {
443 if k + l > derivative_count {
444 continue;
445 }
446 let mut point = zero;
447 for i in 0..=self.degree_u {
448 let row = &self.control_points[span_u - self.degree_u + i];
449 for j in 0..=self.degree_v {
450 point = point.add(
451 row[span_v - self.degree_v + j]
452 .scale(basis_u[k][i] * basis_v[l][j]),
453 );
454 }
455 }
456 homogeneous[k * stride + l] = point;
457 }
458 }
459 } else {
460 let (knot_u, knot_v) = self.knot_vectors()?;
461 let u = knot_u.clamp_param(u);
462 let v = knot_v.clamp_param(v);
463 let span_u = knot_u.find_span(u);
464 let span_v = knot_v.find_span(v);
465 let basis_u = knot_u.basis_derivatives(span_u, u, du);
466 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
467 for k in 0..=du {
468 for l in 0..=dv {
469 if k + l > derivative_count {
470 continue;
471 }
472 let mut point = zero;
473 for i in 0..=self.degree_u {
474 let row = &self.control_points[span_u - self.degree_u + i];
475 for j in 0..=self.degree_v {
476 point = point.add(
477 row[span_v - self.degree_v + j]
478 .scale(basis_u[k][i] * basis_v[l][j]),
479 );
480 }
481 }
482 homogeneous[k * stride + l] = point;
483 }
484 }
485 }
486
487 let mut result = vec![vec![Vec3::default(); derivative_count + 1]; derivative_count + 1];
488 let weight = homogeneous[0].w;
489 if weight.abs() <= EPS {
490 return Err("NurbsSurface: zero evaluated weight".into());
491 }
492 for k in 0..=derivative_count {
493 for l in 0..=derivative_count - k {
494 if k > du || l > dv {
495 continue;
496 }
497 let at = |row: usize, column: usize| homogeneous[row * stride + column];
498 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
499 for j in 1..=l {
500 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
501 }
502 for i in 1..=k {
503 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
504 let mut mixed = Vec3::default();
505 for j in 1..=l {
506 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
507 }
508 value = value.sub(mixed.scale(binomial(k, i)));
509 }
510 result[k][l] = value.scale(1.0 / weight);
511 }
512 }
513 Ok(result)
514 }
515
516 pub(crate) fn derivatives_small(
531 &self,
532 u: f64,
533 v: f64,
534 derivative_count: usize,
535 ) -> Result<[[Vec3; 3]; 3], String> {
536 debug_assert!(derivative_count <= 2);
537 self.ensure_valid()?;
538 let du = derivative_count.min(self.degree_u);
539 let dv = derivative_count.min(self.degree_v);
540 let stride = derivative_count + 1;
541 let zero = Vec4 {
542 x: 0.0,
543 y: 0.0,
544 z: 0.0,
545 w: 0.0,
546 };
547 let mut homogeneous = [zero; 9];
548 if self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE {
549 let u = knot_clamp(&self.knots_u, self.degree_u, u);
550 let v = knot_clamp(&self.knots_v, self.degree_v, v);
551 let span_u = knot_find_span(&self.knots_u, self.degree_u, u);
552 let span_v = knot_find_span(&self.knots_v, self.degree_v, v);
553 let mut basis_u = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
554 let mut basis_v = [[0.0f64; MAX_STACK_ORDER]; MAX_STACK_ORDER];
555 basis_derivatives_into(
556 &self.knots_u,
557 self.degree_u,
558 span_u,
559 u,
560 du,
561 &mut basis_u[..=du],
562 );
563 basis_derivatives_into(
564 &self.knots_v,
565 self.degree_v,
566 span_v,
567 v,
568 dv,
569 &mut basis_v[..=dv],
570 );
571 for k in 0..=du {
572 for l in 0..=dv {
573 if k + l > derivative_count {
574 continue;
575 }
576 let mut point = zero;
577 for i in 0..=self.degree_u {
578 let row = &self.control_points[span_u - self.degree_u + i];
579 for j in 0..=self.degree_v {
580 point = point.add(
581 row[span_v - self.degree_v + j]
582 .scale(basis_u[k][i] * basis_v[l][j]),
583 );
584 }
585 }
586 homogeneous[k * stride + l] = point;
587 }
588 }
589 } else {
590 let (knot_u, knot_v) = self.knot_vectors()?;
591 let u = knot_u.clamp_param(u);
592 let v = knot_v.clamp_param(v);
593 let span_u = knot_u.find_span(u);
594 let span_v = knot_v.find_span(v);
595 let basis_u = knot_u.basis_derivatives(span_u, u, du);
596 let basis_v = knot_v.basis_derivatives(span_v, v, dv);
597 for k in 0..=du {
598 for l in 0..=dv {
599 if k + l > derivative_count {
600 continue;
601 }
602 let mut point = zero;
603 for i in 0..=self.degree_u {
604 let row = &self.control_points[span_u - self.degree_u + i];
605 for j in 0..=self.degree_v {
606 point = point.add(
607 row[span_v - self.degree_v + j]
608 .scale(basis_u[k][i] * basis_v[l][j]),
609 );
610 }
611 }
612 homogeneous[k * stride + l] = point;
613 }
614 }
615 }
616
617 let mut result = [[Vec3::default(); 3]; 3];
618 let weight = homogeneous[0].w;
619 if weight.abs() <= EPS {
620 return Err("NurbsSurface: zero evaluated weight".into());
621 }
622 for k in 0..=derivative_count {
623 for l in 0..=derivative_count - k {
624 if k > du || l > dv {
625 continue;
626 }
627 let at = |row: usize, column: usize| homogeneous[row * stride + column];
628 let mut value = Vec3::new(at(k, l).x, at(k, l).y, at(k, l).z);
629 for j in 1..=l {
630 value = value.sub(result[k][l - j].scale(binomial(l, j) * at(0, j).w));
631 }
632 for i in 1..=k {
633 value = value.sub(result[k - i][l].scale(binomial(k, i) * at(i, 0).w));
634 let mut mixed = Vec3::default();
635 for j in 1..=l {
636 mixed = mixed.add(result[k - i][l - j].scale(binomial(l, j) * at(i, j).w));
637 }
638 value = value.sub(mixed.scale(binomial(k, i)));
639 }
640 result[k][l] = value.scale(1.0 / weight);
641 }
642 }
643 Ok(result)
644 }
645
646 #[inline]
650 pub(crate) fn deriv1_tensor_supported(&self) -> bool {
651 self.degree_u <= MAX_STACK_DEGREE && self.degree_v <= MAX_STACK_DEGREE
652 }
653
654 pub(crate) fn deriv1_tensor_each<F>(
676 &self,
677 us: &[f64],
678 vs: &[f64],
679 mut f: F,
680 ) -> Result<(), String>
681 where
682 F: FnMut(usize, usize, Vec3, Vec3, Vec3) -> Result<(), String>,
683 {
684 self.ensure_valid()?;
685 const MAX_BLOCK: usize = TENSOR_MAX_BLOCK;
687 if !self.deriv1_tensor_supported() || us.len() > MAX_BLOCK || vs.len() > MAX_BLOCK {
688 return Err("NurbsSurface: tensor block outside the stack evaluator".into());
689 }
690 let du = 1usize.min(self.degree_u);
691 let dv = 1usize.min(self.degree_v);
692 let mut span_u = [0usize; MAX_BLOCK];
693 let mut span_v = [0usize; MAX_BLOCK];
694 let mut basis_u = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
695 let mut basis_v = [[[0.0f64; MAX_STACK_ORDER]; 2]; MAX_BLOCK];
696 for (index, &value) in us.iter().enumerate() {
697 let value = knot_clamp(&self.knots_u, self.degree_u, value);
698 span_u[index] = knot_find_span(&self.knots_u, self.degree_u, value);
699 basis_derivatives_into(
700 &self.knots_u,
701 self.degree_u,
702 span_u[index],
703 value,
704 du,
705 &mut basis_u[index][..=du],
706 );
707 }
708 for (index, &value) in vs.iter().enumerate() {
709 let value = knot_clamp(&self.knots_v, self.degree_v, value);
710 span_v[index] = knot_find_span(&self.knots_v, self.degree_v, value);
711 basis_derivatives_into(
712 &self.knots_v,
713 self.degree_v,
714 span_v[index],
715 value,
716 dv,
717 &mut basis_v[index][..=dv],
718 );
719 }
720 let zero = Vec4 {
721 x: 0.0,
722 y: 0.0,
723 z: 0.0,
724 w: 0.0,
725 };
726 for i in 0..us.len() {
727 let rows = &self.control_points[span_u[i] - self.degree_u..];
728 for j in 0..vs.len() {
729 let column = span_v[j] - self.degree_v;
730 let sum = |k: usize, l: usize| {
734 let mut point = zero;
735 for row_index in 0..=self.degree_u {
736 let row = &rows[row_index];
737 for column_index in 0..=self.degree_v {
738 point = point.add(
739 row[column + column_index]
740 .scale(basis_u[i][k][row_index] * basis_v[j][l][column_index]),
741 );
742 }
743 }
744 point
745 };
746 let h00 = sum(0, 0);
747 let h01 = if dv >= 1 { sum(0, 1) } else { zero };
748 let h10 = if du >= 1 { sum(1, 0) } else { zero };
749 let weight = h00.w;
750 if weight.abs() <= EPS {
751 return Err("NurbsSurface: zero evaluated weight".into());
752 }
753 let inverse = 1.0 / weight;
754 let point = Vec3::new(h00.x, h00.y, h00.z).scale(inverse);
755 let partial_v = if dv >= 1 {
756 Vec3::new(h01.x, h01.y, h01.z)
757 .sub(point.scale(h01.w))
758 .scale(inverse)
759 } else {
760 Vec3::default()
761 };
762 let partial_u = if du >= 1 {
763 Vec3::new(h10.x, h10.y, h10.z)
764 .sub(point.scale(h10.w))
765 .scale(inverse)
766 } else {
767 Vec3::default()
768 };
769 f(i, j, point, partial_u, partial_v)?;
770 }
771 }
772 Ok(())
773 }
774
775 #[inline]
778 pub(crate) fn deriv1(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
779 let g = self.derivatives_small(u, v, 1)?;
780 Ok((g[0][0], g[1][0], g[0][1]))
781 }
782
783 pub(crate) fn deriv1_extended(&self, u: f64, v: f64) -> Result<(Vec3, Vec3, Vec3), String> {
791 let [u0, u1] = self.domain_u()?;
792 let [v0, v1] = self.domain_v()?;
793 let (closed_u, closed_v) = self.closed_directions()?;
794 let mut uu = u;
795 let mut vv = v;
796 if closed_u && (u < u0 || u > u1) {
797 uu = u0 + (u - u0).rem_euclid(u1 - u0);
798 }
799 if closed_v && (v < v0 || v > v1) {
800 vv = v0 + (v - v0).rem_euclid(v1 - v0);
801 }
802 let du_out = if uu < u0 {
803 uu - u0
804 } else if uu > u1 {
805 uu - u1
806 } else {
807 0.0
808 };
809 let dv_out = if vv < v0 {
810 vv - v0
811 } else if vv > v1 {
812 vv - v1
813 } else {
814 0.0
815 };
816 if du_out == 0.0 && dv_out == 0.0 {
817 let g = self.derivatives_small(uu, vv, 1)?;
818 return Ok((g[0][0], g[1][0], g[0][1]));
819 }
820 let anchor_u = uu - du_out;
821 let anchor_v = vv - dv_out;
822 let base = self.derivatives_small(anchor_u, anchor_v, 2)?;
823 let s = base[0][0]
827 .add(base[1][0].scale(du_out))
828 .add(base[0][1].scale(dv_out))
829 .add(base[1][1].scale(du_out * dv_out));
830 let su = base[1][0].add(base[1][1].scale(dv_out));
831 let sv = base[0][1].add(base[1][1].scale(du_out));
832 Ok((s, su, sv))
833 }
834
835 pub(crate) fn partial_by_differences(
860 &self,
861 along_u: bool,
862 u: f64,
863 v: f64,
864 ) -> Result<Vec3, String> {
865 self.ensure_valid()?;
866 if self.degree_u > MAX_STACK_DEGREE || self.degree_v > MAX_STACK_DEGREE {
867 return Err("NurbsSurface: partial by differences exceeds the stack degree".into());
868 }
869 let (pu, pv) = (self.degree_u, self.degree_v);
870 let u = knot_clamp(&self.knots_u, pu, u);
871 let v = knot_clamp(&self.knots_v, pv, v);
872 let span_u = knot_find_span(&self.knots_u, pu, u);
873 let span_v = knot_find_span(&self.knots_v, pv, v);
874 let mut full_u = [0.0f64; MAX_STACK_ORDER];
875 let mut full_v = [0.0f64; MAX_STACK_ORDER];
876 basis_functions_into(&self.knots_u, pu, span_u, u, &mut full_u);
877 basis_functions_into(&self.knots_v, pv, span_v, v, &mut full_v);
878 let zero = Vec4 {
879 x: 0.0,
880 y: 0.0,
881 z: 0.0,
882 w: 0.0,
883 };
884 let control = |i: usize, j: usize| self.control_points[span_u - pu + i][span_v - pv + j];
885 let mut point = zero;
886 for i in 0..=pu {
887 for j in 0..=pv {
888 point = point.add(control(i, j).scale(full_u[i] * full_v[j]));
889 }
890 }
891 if point.w.abs() <= EPS {
892 return Err("NurbsSurface: zero evaluated weight".into());
893 }
894 let source = Vec3::new(point.x, point.y, point.z).scale(1.0 / point.w);
895 let (knots, degree, span, parameter) = if along_u {
896 (&self.knots_u, pu, span_u, u)
897 } else {
898 (&self.knots_v, pv, span_v, v)
899 };
900 if degree == 0 {
901 return Ok(Vec3::default());
902 }
903 let mut lowered = [0.0f64; MAX_STACK_ORDER];
904 basis_functions_into(knots, degree - 1, span, parameter, &mut lowered);
905 let mut hodograph = zero;
908 for r in 0..degree {
909 let index = span - degree + r;
912 let width = knots[index + degree + 1] - knots[index + 1];
913 if width.abs() <= EPS {
914 continue;
915 }
916 let factor = lowered[r] * degree as f64 / width;
917 if along_u {
918 for j in 0..=pv {
919 let difference = control(r + 1, j).add(control(r, j).scale(-1.0));
920 hodograph = hodograph.add(difference.scale(factor * full_v[j]));
921 }
922 } else {
923 for i in 0..=pu {
924 let difference = control(i, r + 1).add(control(i, r).scale(-1.0));
925 hodograph = hodograph.add(difference.scale(factor * full_u[i]));
926 }
927 }
928 }
929 Ok(Vec3::new(hodograph.x, hodograph.y, hodograph.z)
930 .sub(source.scale(hodograph.w))
931 .scale(1.0 / point.w))
932 }
933
934 pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
935 let derivatives = self.derivatives(u, v, 1)?;
936 derivatives[1][0].cross(derivatives[0][1]).normalized()
937 }
938
939 pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String> {
946 let derivatives = self.derivatives(u, v, 2)?;
947 let su = derivatives[1][0];
948 let sv = derivatives[0][1];
949 let cross = su.cross(sv);
950 let cross_length = cross.length();
951 if cross_length <= 1e-12 {
952 return Err(format!(
953 "degenerate parametrization at (u={u:.4}, v={v:.4})"
954 ));
955 }
956 let normal = cross.scale(1.0 / cross_length);
957 let e1 = su.dot(su);
958 let f1 = su.dot(sv);
959 let g1 = sv.dot(sv);
960 let l2 = derivatives[2][0].dot(normal);
961 let m2 = derivatives[1][1].dot(normal);
962 let n2 = derivatives[0][2].dot(normal);
963 let denominator = e1 * g1 - f1 * f1;
964 let mean_double = (l2 * g1 - 2.0 * m2 * f1 + n2 * e1) / denominator; let gauss = (l2 * n2 - m2 * m2) / denominator; let discriminant = (mean_double * mean_double * 0.25 - gauss).max(0.0).sqrt();
967 Ok((
968 mean_double * 0.5 - discriminant,
969 mean_double * 0.5 + discriminant,
970 ))
971 }
972
973 pub fn is_affine(&self) -> Result<bool, String> {
978 if self.degree_u != 1
979 || self.degree_v != 1
980 || self.control_points.len() != 2
981 || self.control_points[0].len() != 2
982 || self.control_points[1].len() != 2
983 {
984 return Ok(false);
985 }
986 let weight = self.control_points[0][0].w;
987 if [
988 self.control_points[0][1].w,
989 self.control_points[1][0].w,
990 self.control_points[1][1].w,
991 ]
992 .iter()
993 .any(|other| (*other - weight).abs() > 1e-12 * other.abs().max(weight.abs()))
994 {
995 return Ok(false);
996 }
997 let p00 = self.control_points[0][0].point()?;
998 let p01 = self.control_points[0][1].point()?;
999 let p10 = self.control_points[1][0].point()?;
1000 let p11 = self.control_points[1][1].point()?;
1001 let gap = p11.sub(p10).sub(p01.sub(p00)).length();
1002 let extent = [
1007 p01.sub(p00),
1008 p10.sub(p00),
1009 p11.sub(p00),
1010 p10.sub(p01),
1011 p11.sub(p01),
1012 p11.sub(p10),
1013 ]
1014 .iter()
1015 .map(|delta| delta.length())
1016 .fold(0.0, f64::max);
1017 Ok(gap <= 1e-9 * (1.0 + extent))
1018 }
1019
1020 pub fn iso_curve_u(&self, u: f64) -> Result<NurbsCurve, String> {
1021 self.ensure_valid()?;
1022 let u = knot_clamp(&self.knots_u, self.degree_u, u);
1023 let span = knot_find_span(&self.knots_u, self.degree_u, u);
1024 let basis = if self.degree_u <= MAX_STACK_DEGREE {
1025 let mut stack = [0.0f64; MAX_STACK_ORDER];
1026 basis_functions_into(&self.knots_u, self.degree_u, span, u, &mut stack);
1027 stack[..=self.degree_u].to_vec()
1028 } else {
1029 KnotVector::new(self.knots_u.clone(), self.degree_u)?.basis_functions(span, u)
1030 };
1031 let mut points = Vec::with_capacity(self.control_points[0].len());
1032 for column in 0..self.control_points[0].len() {
1033 let mut point = Vec4 {
1034 x: 0.0,
1035 y: 0.0,
1036 z: 0.0,
1037 w: 0.0,
1038 };
1039 for (index, value) in basis.iter().enumerate() {
1040 point = point
1041 .add(self.control_points[span - self.degree_u + index][column].scale(*value));
1042 }
1043 points.push(point);
1044 }
1045 NurbsCurve::new(self.degree_v, self.knots_v.clone(), points)
1046 }
1047
1048 pub fn iso_curve_v(&self, v: f64) -> Result<NurbsCurve, String> {
1049 self.ensure_valid()?;
1050 let v = knot_clamp(&self.knots_v, self.degree_v, v);
1051 let span = knot_find_span(&self.knots_v, self.degree_v, v);
1052 let basis = if self.degree_v <= MAX_STACK_DEGREE {
1053 let mut stack = [0.0f64; MAX_STACK_ORDER];
1054 basis_functions_into(&self.knots_v, self.degree_v, span, v, &mut stack);
1055 stack[..=self.degree_v].to_vec()
1056 } else {
1057 KnotVector::new(self.knots_v.clone(), self.degree_v)?.basis_functions(span, v)
1058 };
1059 let mut points = Vec::with_capacity(self.control_points.len());
1060 for row in &self.control_points {
1061 let mut point = Vec4 {
1062 x: 0.0,
1063 y: 0.0,
1064 z: 0.0,
1065 w: 0.0,
1066 };
1067 for (index, value) in basis.iter().enumerate() {
1068 point = point.add(row[span - self.degree_v + index].scale(*value));
1069 }
1070 points.push(point);
1071 }
1072 NurbsCurve::new(self.degree_u, self.knots_u.clone(), points)
1073 }
1074}
1075
1076pub fn make_plane(
1077 origin: Vec3,
1078 u_direction: Vec3,
1079 v_direction: Vec3,
1080 u_max: f64,
1081 v_max: f64,
1082) -> Result<NurbsSurface, String> {
1083 if u_max <= EPS || v_max <= EPS {
1084 return Err("makePlane: parameter extents must be positive".into());
1085 }
1086 let p10 = origin.add(u_direction.scale(u_max));
1087 let p01 = origin.add(v_direction.scale(v_max));
1088 let p11 = p10.add(v_direction.scale(v_max));
1089 NurbsSurface::new(
1090 1,
1091 1,
1092 vec![0.0, 0.0, u_max, u_max],
1093 vec![0.0, 0.0, v_max, v_max],
1094 vec![
1095 vec![Vec4::from_point(origin, 1.0), Vec4::from_point(p01, 1.0)],
1096 vec![Vec4::from_point(p10, 1.0), Vec4::from_point(p11, 1.0)],
1097 ],
1098 )
1099}
1100
1101pub fn make_extrusion(profile: &NurbsCurve, direction: Vec3) -> Result<NurbsSurface, String> {
1102 let rows = profile
1103 .control_points
1104 .iter()
1105 .map(|point| {
1106 vec![
1107 *point,
1108 Vec4 {
1109 x: point.x + point.w * direction.x,
1110 y: point.y + point.w * direction.y,
1111 z: point.z + point.w * direction.z,
1112 w: point.w,
1113 },
1114 ]
1115 })
1116 .collect();
1117 NurbsSurface::new(
1118 profile.degree,
1119 1,
1120 profile.knots.clone(),
1121 vec![0.0, 0.0, 1.0, 1.0],
1122 rows,
1123 )
1124}
1125
1126pub fn make_revolution(
1127 axis_point: Vec3,
1128 axis_direction: Vec3,
1129 generatrix: &NurbsCurve,
1130 theta: f64,
1131) -> Result<NurbsSurface, String> {
1132 if theta <= EPS || theta > std::f64::consts::TAU + EPS {
1133 return Err("makeRevolution: theta must be in (0, 2*PI]".into());
1134 }
1135 let theta = theta.min(std::f64::consts::TAU);
1136 let axis = axis_direction.normalized()?;
1137 let arc_count = ((theta / std::f64::consts::FRAC_PI_2 - EPS).ceil() as usize).clamp(1, 4);
1138 let arc_angle = theta / arc_count as f64;
1139 let middle_weight = (arc_angle / 2.0).cos();
1140 let mut knots_u = vec![0.0, 0.0, 0.0];
1141 for index in 1..arc_count {
1142 let knot = index as f64 / arc_count as f64;
1143 knots_u.extend([knot, knot]);
1144 }
1145 knots_u.extend([1.0, 1.0, 1.0]);
1146 let mut rows = vec![
1147 vec![
1148 Vec4 {
1149 x: 0.0,
1150 y: 0.0,
1151 z: 0.0,
1152 w: 1.0,
1153 };
1154 generatrix.control_points.len()
1155 ];
1156 2 * arc_count + 1
1157 ];
1158
1159 let mut radial_extent: f64 = 0.0;
1162 for control_point in &generatrix.control_points {
1163 let point = control_point.point()?;
1164 let offset = point.sub(axis_point);
1165 radial_extent = radial_extent.max(offset.sub(axis.scale(offset.dot(axis))).length());
1166 }
1167
1168 for (column, control_point) in generatrix.control_points.iter().enumerate() {
1169 let point = control_point.point()?;
1170 let weight = control_point.w;
1171 let axis_origin = axis_point.add(axis.scale(point.sub(axis_point).dot(axis)));
1172 let radial_vector = point.sub(axis_origin);
1173 let measured_radius = radial_vector.length();
1183 let pole_tolerance = EPS.max(1e-8 * radial_extent);
1184 let radius = if measured_radius <= pole_tolerance {
1185 0.0
1186 } else {
1187 measured_radius
1188 };
1189 let (x_axis, y_axis) = if radius <= EPS {
1190 (Vec3::default(), Vec3::default())
1191 } else {
1192 if radius <= NEAR_AXIS_RELATIVE_TOLERANCE * radial_extent {
1204 return Err(format!(
1205 "makeRevolution: generatrix control point {column} sits {radius:e} from the \
1206 axis, neither on it nor clear of it against the generatrix's {radial_extent:e} \
1207 radial extent (a fuzzy pole)"
1208 ));
1209 }
1210 let x_axis = radial_vector.scale(1.0 / radius);
1211 (x_axis, axis.cross(x_axis))
1212 };
1213 rows[0][column] = Vec4::from_point(point, weight);
1214 let mut start_point = point;
1215 let mut start_tangent = y_axis.scale(radius);
1216 let mut angle = 0.0;
1217 for arc in 1..=arc_count {
1218 angle += arc_angle;
1219 let end_point = if radius <= EPS {
1220 point
1221 } else {
1222 axis_origin
1223 .add(x_axis.scale(radius * angle.cos()))
1224 .add(y_axis.scale(radius * angle.sin()))
1225 };
1226 let end_tangent = if radius <= EPS {
1227 Vec3::default()
1228 } else {
1229 x_axis
1230 .scale(-radius * angle.sin())
1231 .add(y_axis.scale(radius * angle.cos()))
1232 };
1233 let row = 2 * (arc - 1);
1234 if radius <= EPS {
1235 rows[row + 1][column] = Vec4::from_point(point, weight * middle_weight);
1236 } else {
1237 let cross = start_tangent.cross(end_tangent);
1238 let denominator = cross.length_squared();
1239 let tangent_scale = start_tangent.length_squared() * end_tangent.length_squared();
1248 if denominator <= EPS * tangent_scale {
1249 return Err("makeRevolution: arc tangents are parallel".into());
1250 }
1251 let distance =
1252 end_point.sub(start_point).cross(end_tangent).dot(cross) / denominator;
1253 let middle = start_point.add(start_tangent.scale(distance));
1254 rows[row + 1][column] = Vec4::from_point(middle, weight * middle_weight);
1255 }
1256 rows[row + 2][column] = Vec4::from_point(end_point, weight);
1257 start_point = end_point;
1258 start_tangent = end_tangent;
1259 }
1260 }
1261 NurbsSurface::new(
1262 2,
1263 generatrix.degree,
1264 knots_u,
1265 generatrix.knots.clone(),
1266 rows,
1267 )
1268}
1269
1270pub fn make_cylinder_surface(
1271 base: Vec3,
1272 axis_direction: Vec3,
1273 radius: f64,
1274 height: f64,
1275) -> Result<NurbsSurface, String> {
1276 if radius <= EPS || height <= EPS {
1277 return Err("makeCylinder: radius and height must be positive".into());
1278 }
1279 let axis = axis_direction.normalized()?;
1280 let x_axis = axis.perpendicular()?;
1281 let start = base.add(x_axis.scale(radius));
1282 let end = start.add(axis.scale(height));
1283 let generatrix = crate::make_line(start, end)?;
1284 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1285}
1286
1287pub fn make_cone_surface(
1288 base: Vec3,
1289 axis_direction: Vec3,
1290 radius_bottom: f64,
1291 radius_top: f64,
1292 height: f64,
1293) -> Result<NurbsSurface, String> {
1294 if radius_bottom <= EPS || radius_top < 0.0 || height <= EPS {
1295 return Err("makeCone: invalid radius or height".into());
1296 }
1297 let axis = axis_direction.normalized()?;
1298 let x_axis = axis.perpendicular()?;
1299 let start = base.add(x_axis.scale(radius_bottom));
1300 let end = base.add(axis.scale(height)).add(x_axis.scale(radius_top));
1301 let generatrix = crate::make_line(start, end)?;
1302 make_revolution(base, axis, &generatrix, std::f64::consts::TAU)
1303}
1304
1305pub fn make_sphere_surface(
1306 center: Vec3,
1307 radius: f64,
1308 polar_axis: Vec3,
1309) -> Result<NurbsSurface, String> {
1310 make_sphere_surface_framed(center, radius, polar_axis, None)
1311}
1312
1313pub fn make_sphere_surface_framed(
1325 center: Vec3,
1326 radius: f64,
1327 polar_axis: Vec3,
1328 seam_direction: Option<Vec3>,
1329) -> Result<NurbsSurface, String> {
1330 if radius <= EPS {
1331 return Err("makeSphere: radius must be positive".into());
1332 }
1333 let axis = polar_axis.normalized()?;
1334 let x_axis = match seam_direction {
1335 Some(seam) => {
1336 let planar = seam.sub(axis.scale(seam.dot(axis)));
1337 match planar.normalized() {
1338 Ok(unit) if planar.length() > EPS => unit,
1339 _ => axis.perpendicular()?,
1340 }
1341 }
1342 None => axis.perpendicular()?,
1343 };
1344 let meridian = crate::make_arc(
1345 center,
1346 x_axis,
1347 axis,
1348 radius,
1349 -std::f64::consts::FRAC_PI_2,
1350 std::f64::consts::FRAC_PI_2,
1351 )?;
1352 make_revolution(center, axis, &meridian, std::f64::consts::TAU)
1353}
1354
1355pub fn make_torus_surface(
1356 center: Vec3,
1357 axis_direction: Vec3,
1358 major_radius: f64,
1359 minor_radius: f64,
1360) -> Result<NurbsSurface, String> {
1361 if minor_radius <= EPS || major_radius <= minor_radius {
1362 return Err("makeTorus: requires positive minorRadius < majorRadius".into());
1363 }
1364 let axis = axis_direction.normalized()?;
1365 let x_axis = axis.perpendicular()?;
1366 let tube_center = center.add(x_axis.scale(major_radius));
1367 let tube = crate::make_arc(
1368 tube_center,
1369 x_axis,
1370 axis,
1371 minor_radius,
1372 0.0,
1373 std::f64::consts::TAU,
1374 )?;
1375 make_revolution(center, axis, &tube, std::f64::consts::TAU)
1376}
1377
1378fn binomial(n: usize, k: usize) -> f64 {
1379 if k > n {
1380 return 0.0;
1381 }
1382 let k = k.min(n - k);
1383 (1..=k).fold(1.0, |value, index| {
1384 value * (n - k + index) as f64 / index as f64
1385 })
1386}
1387
1388
1389
1390pub fn carrier_preview_patch(surface: &NurbsSurface, inflate: f64) -> Result<NurbsSurface, String> {
1402 let inflate = if inflate.is_finite() {
1403 inflate.clamp(1.0, 16.0)
1404 } else {
1405 2.0
1406 };
1407 let (closed_u, closed_v) = surface.closed_directions()?;
1408 let [u0, u1] = surface.domain_u()?;
1409 let [v0, v1] = surface.domain_v()?;
1410 let stretch = |d0: f64, d1: f64, closed: bool| -> (f64, f64) {
1411 if closed || inflate <= 1.0 {
1412 (d0, d1)
1413 } else {
1414 let centre = 0.5 * (d0 + d1);
1415 let half = 0.5 * (d1 - d0) * inflate;
1416 (centre - half, centre + half)
1417 }
1418 };
1419 let (nu0, nu1) = stretch(u0, u1, closed_u);
1420 let (nv0, nv1) = stretch(v0, v1, closed_v);
1421 if (nu0, nu1) == (u0, u1) && (nv0, nv1) == (v0, v1) {
1422 return Ok(surface.clone());
1423 }
1424 if surface.is_affine()? {
1425 let corners = [
1428 surface.evaluate_extended(nu0, nv0)?,
1429 surface.evaluate_extended(nu1, nv0)?,
1430 surface.evaluate_extended(nu0, nv1)?,
1431 surface.evaluate_extended(nu1, nv1)?,
1432 ];
1433 return NurbsSurface::new(
1434 1,
1435 1,
1436 vec![nu0, nu0, nu1, nu1],
1437 vec![nv0, nv0, nv1, nv1],
1438 vec![
1439 vec![
1440 Vec4::from_point(corners[0], 1.0),
1441 Vec4::from_point(corners[2], 1.0),
1442 ],
1443 vec![
1444 Vec4::from_point(corners[1], 1.0),
1445 Vec4::from_point(corners[3], 1.0),
1446 ],
1447 ],
1448 );
1449 }
1450 const SAMPLES: usize = 32;
1451 let u_params: Vec<f64> = (0..=SAMPLES)
1452 .map(|index| nu0 + (nu1 - nu0) * index as f64 / SAMPLES as f64)
1453 .collect();
1454 let v_params: Vec<f64> = (0..=SAMPLES)
1455 .map(|index| nv0 + (nv1 - nv0) * index as f64 / SAMPLES as f64)
1456 .collect();
1457 let mut column_curves = Vec::with_capacity(u_params.len());
1460 for &u in &u_params {
1461 let column: Vec<Vec3> = v_params
1462 .iter()
1463 .map(|&v| surface.evaluate_extended(u, v))
1464 .collect::<Result<_, _>>()?;
1465 column_curves.push(crate::interpolate_curve(
1466 &column,
1467 3,
1468 &normalized(&v_params),
1469 )?);
1470 }
1471 let v_knots = column_curves[0].knots.clone();
1472 let control_rows = column_curves[0].control_points.len();
1473 let mut grid: Vec<Vec<Vec4>> = Vec::with_capacity(u_params.len());
1474 let mut u_knots = Vec::new();
1475 for row_index in 0..control_rows {
1476 let row: Vec<Vec3> = column_curves
1477 .iter()
1478 .map(|curve| curve.control_points[row_index].point())
1479 .collect::<Result<_, _>>()?;
1480 let fitted = crate::interpolate_curve(&row, 3, &normalized(&u_params))?;
1481 u_knots = fitted.knots.clone();
1482 for (u_index, control) in fitted.control_points.iter().enumerate() {
1483 if grid.len() <= u_index {
1484 grid.push(Vec::new());
1485 }
1486 grid[u_index].push(*control);
1487 }
1488 }
1489 let rescale = |knots: &[f64], d0: f64, d1: f64| -> Vec<f64> {
1492 knots.iter().map(|k| d0 + (d1 - d0) * k).collect()
1493 };
1494 NurbsSurface::new(
1495 3,
1496 3,
1497 rescale(&u_knots, nu0, nu1),
1498 rescale(&v_knots, nv0, nv1),
1499 grid,
1500 )
1501}
1502
1503fn normalized(parameters: &[f64]) -> Vec<f64> {
1504 let first = parameters[0];
1505 let last = parameters[parameters.len() - 1];
1506 parameters
1507 .iter()
1508 .map(|p| (p - first) / (last - first))
1509 .collect()
1510}
1511
1512