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