1use super::compat;
52use super::{
53 corner_band, report_skeleton, CoonsRefusal, CoonsReport, CompatWork, RibbonReport,
54 BOUNDARY_SAMPLES,
55};
56use crate::{model_scale, NurbsCurve, NurbsSurface, Vec3, Vec4};
57
58const POLYNOMIAL_WEIGHT_BAND: f64 = 1e-12;
60
61pub fn coons_with_ribbons(
68 boundaries: [NurbsCurve; 4],
69 ribbons: [Option<NurbsCurve>; 4],
70) -> Result<(NurbsSurface, CoonsReport), CoonsRefusal> {
71 let v_prescribed = usize::from(ribbons[0].is_some()) + usize::from(ribbons[2].is_some());
72 let u_prescribed = usize::from(ribbons[1].is_some()) + usize::from(ribbons[3].is_some());
73 if v_prescribed == 1 {
74 return Err(CoonsRefusal::RibbonPairing {
75 direction: 'v',
76 prescribed: v_prescribed,
77 });
78 }
79 if u_prescribed == 1 {
80 return Err(CoonsRefusal::RibbonPairing {
81 direction: 'u',
82 prescribed: u_prescribed,
83 });
84 }
85 if v_prescribed == 0 && u_prescribed == 0 {
86 return super::coons_bilinear_reported(boundaries);
87 }
88 build(boundaries, ribbons, v_prescribed == 2, u_prescribed == 2)
89}
90
91fn point_of(control: Vec4) -> Vec3 {
93 Vec3::new(control.x / control.w, control.y / control.w, control.z / control.w)
94}
95
96fn bezier_knots(degree: usize) -> Vec<f64> {
98 let mut knots = vec![0.0; degree + 1];
99 knots.extend(std::iter::repeat_n(1.0, degree + 1));
100 knots
101}
102
103fn express(
111 points: &[Vec3],
112 target_degree: usize,
113 target_knots: &[f64],
114) -> Result<Vec<Vec3>, String> {
115 let native_degree = points.len() - 1;
116 let curve = NurbsCurve::new(
117 native_degree,
118 bezier_knots(native_degree),
119 points
120 .iter()
121 .map(|point| Vec4::from_point(*point, 1.0))
122 .collect(),
123 )?;
124 let elevated = compat::elevate_degree(&curve, target_degree)?;
125 let carrier = NurbsCurve::new(
126 target_degree,
127 target_knots.to_vec(),
128 vec![
129 Vec4 {
130 x: 0.0,
131 y: 0.0,
132 z: 0.0,
133 w: 1.0
134 };
135 target_knots.len() - target_degree - 1
136 ],
137 )?;
138 let (_, refined, _, _) = compat::merge_knots(&carrier, &elevated)?;
139 Ok(refined.control_points.iter().copied().map(point_of).collect())
140}
141
142fn hermite(start: Vec3, start_derivative: Vec3, end: Vec3, end_derivative: Vec3) -> [Vec3; 4] {
145 [
146 start,
147 start.add(start_derivative.scale(1.0 / 3.0)),
148 end.sub(end_derivative.scale(1.0 / 3.0)),
149 end,
150 ]
151}
152
153fn weight_spread(curve: &NurbsCurve) -> f64 {
155 curve
156 .control_points
157 .iter()
158 .map(|control| (control.w - 1.0).abs())
159 .fold(0.0f64, f64::max)
160}
161
162fn derivative_at(curve: &NurbsCurve, fraction: f64) -> Result<Vec3, String> {
164 let [start, end] = curve.domain()?;
165 let derivatives = curve.derivatives(start + (end - start) * fraction, 1)?;
166 Ok(derivatives[1].scale(end - start))
167}
168
169fn build(
170 boundaries: [NurbsCurve; 4],
171 ribbons: [Option<NurbsCurve>; 4],
172 v_hermite: bool,
173 u_hermite: bool,
174) -> Result<(NurbsSurface, CoonsReport), CoonsRefusal> {
175 for (index, curve) in boundaries.iter().enumerate() {
178 let spread = weight_spread(curve);
179 if spread > POLYNOMIAL_WEIGHT_BAND {
180 return Err(CoonsRefusal::RationalRibbon {
181 boundary: index,
182 weight_spread: spread,
183 });
184 }
185 }
186 for (index, ribbon) in ribbons.iter().enumerate() {
187 if let Some(curve) = ribbon {
188 let spread = weight_spread(curve);
189 if spread > POLYNOMIAL_WEIGHT_BAND {
190 return Err(CoonsRefusal::RationalRibbon {
191 boundary: index,
192 weight_spread: spread,
193 });
194 }
195 }
196 }
197
198 let oriented = super::orient_and_reconcile(&boundaries)?;
199 let flip = |curve: &NurbsCurve, reversed: bool| -> Result<NurbsCurve, CoonsRefusal> {
200 if reversed {
201 curve.reversed().map_err(CoonsRefusal::Geometry)
202 } else {
203 Ok(curve.clone())
204 }
205 };
206 let mut ribbon_curves: [Option<NurbsCurve>; 4] = [None, None, None, None];
209 for index in 0..4 {
210 if let Some(ribbon) = &ribbons[index] {
211 ribbon_curves[index] = Some(flip(ribbon, oriented.reversed[index])?);
212 }
213 }
214 let c0 = oriented.curves[0].clone();
215 let d1 = oriented.curves[1].clone();
216 let c1 = oriented.curves[2].reversed().map_err(CoonsRefusal::Geometry)?;
217 let d0 = oriented.curves[3].reversed().map_err(CoonsRefusal::Geometry)?;
218 let reverse_ribbon = |ribbon: &Option<NurbsCurve>| -> Result<Option<NurbsCurve>, CoonsRefusal> {
219 match ribbon {
220 Some(curve) => Ok(Some(curve.reversed().map_err(CoonsRefusal::Geometry)?)),
221 None => Ok(None),
222 }
223 };
224 let tv0 = ribbon_curves[0].clone();
226 let tv1 = reverse_ribbon(&ribbon_curves[2])?;
227 let tu0 = reverse_ribbon(&ribbon_curves[3])?;
228 let tu1 = ribbon_curves[1].clone();
229
230 let mut u_family: Vec<NurbsCurve> = vec![c0, c1];
233 if v_hermite {
234 u_family.push(tv0.clone().expect("paired"));
235 u_family.push(tv1.clone().expect("paired"));
236 }
237 let mut v_family: Vec<NurbsCurve> = vec![d0, d1];
238 if u_hermite {
239 v_family.push(tu0.clone().expect("paired"));
240 v_family.push(tu1.clone().expect("paired"));
241 }
242 let u_native = if u_hermite { 3 } else { 1 };
243 let v_native = if v_hermite { 3 } else { 1 };
244 let (u_family, u_work) =
245 compat::make_family_compatible(&u_family, u_native).map_err(|detail| {
246 CoonsRefusal::Incompatible {
247 direction: 'u',
248 detail,
249 }
250 })?;
251 let (v_family, v_work) =
252 compat::make_family_compatible(&v_family, v_native).map_err(|detail| {
253 CoonsRefusal::Incompatible {
254 direction: 'v',
255 detail,
256 }
257 })?;
258 let mut work = [CompatWork::default(); 4];
259 work[0] = u_work[0];
260 work[2] = u_work[1];
261 work[3] = v_work[0];
262 work[1] = v_work[1];
263
264 let degree_u = u_family[0].degree;
265 let degree_v = v_family[0].degree;
266 let knots_u = u_family[0].knots.clone();
267 let knots_v = v_family[0].knots.clone();
268 let rows = u_family[0].control_points.len();
269 let columns = v_family[0].control_points.len();
270 let net_of = |curve: &NurbsCurve| -> Vec<Vec3> {
271 curve.control_points.iter().copied().map(point_of).collect()
272 };
273 let c0_net = net_of(&u_family[0]);
274 let c1_net = net_of(&u_family[1]);
275 let d0_net = net_of(&v_family[0]);
276 let d1_net = net_of(&v_family[1]);
277 let tv0_net = v_hermite.then(|| net_of(&u_family[2]));
278 let tv1_net = v_hermite.then(|| net_of(&u_family[3]));
279 let tu0_net = u_hermite.then(|| net_of(&v_family[2]));
280 let tu1_net = u_hermite.then(|| net_of(&v_family[3]));
281
282 let corner = |net: &[Vec3], last: bool| net[if last { net.len() - 1 } else { 0 }];
286 let corners = [
287 [corner(&c0_net, false), corner(&c1_net, false)],
288 [corner(&c0_net, true), corner(&c1_net, true)],
289 ];
290 let tangent_u = [
291 [
292 derivative_at(&u_family[0], 0.0).map_err(CoonsRefusal::Geometry)?,
293 derivative_at(&u_family[1], 0.0).map_err(CoonsRefusal::Geometry)?,
294 ],
295 [
296 derivative_at(&u_family[0], 1.0).map_err(CoonsRefusal::Geometry)?,
297 derivative_at(&u_family[1], 1.0).map_err(CoonsRefusal::Geometry)?,
298 ],
299 ];
300 let tangent_v = [
301 [
302 derivative_at(&v_family[0], 0.0).map_err(CoonsRefusal::Geometry)?,
303 derivative_at(&v_family[0], 1.0).map_err(CoonsRefusal::Geometry)?,
304 ],
305 [
306 derivative_at(&v_family[1], 0.0).map_err(CoonsRefusal::Geometry)?,
307 derivative_at(&v_family[1], 1.0).map_err(CoonsRefusal::Geometry)?,
308 ],
309 ];
310
311 let mut points: Vec<Vec3> = Vec::new();
315 for boundary in &boundaries {
316 let [start, end] = boundary.domain().map_err(CoonsRefusal::Geometry)?;
317 for index in 0..=BOUNDARY_SAMPLES {
318 points.push(
319 boundary
320 .evaluate(start + (end - start) * index as f64 / BOUNDARY_SAMPLES as f64)
321 .map_err(CoonsRefusal::Geometry)?,
322 );
323 }
324 }
325 let band = corner_band(model_scale(points.iter().copied()));
326 let mut corner_tangent_gap = 0.0f64;
327 if v_hermite {
328 for (v_end, ribbon) in [(0usize, &u_family[2]), (1usize, &u_family[3])] {
329 for (u_side, fraction) in [(0usize, 0.0f64), (1usize, 1.0f64)] {
330 let [start, end] = ribbon.domain().map_err(CoonsRefusal::Geometry)?;
331 let value = ribbon
332 .evaluate(start + (end - start) * fraction)
333 .map_err(CoonsRefusal::Geometry)?;
334 let expected = tangent_v[u_side][v_end];
335 let gap = value.sub(expected).length();
336 corner_tangent_gap = corner_tangent_gap.max(gap);
337 if gap > band {
338 return Err(CoonsRefusal::RibbonCornerTangent {
339 corner: u_side * 2 + v_end,
340 gap,
341 band,
342 });
343 }
344 }
345 }
346 }
347 if u_hermite {
348 for (u_side, ribbon) in [(0usize, &v_family[2]), (1usize, &v_family[3])] {
349 for (v_end, fraction) in [(0usize, 0.0f64), (1usize, 1.0f64)] {
350 let [start, end] = ribbon.domain().map_err(CoonsRefusal::Geometry)?;
351 let value = ribbon
352 .evaluate(start + (end - start) * fraction)
353 .map_err(CoonsRefusal::Geometry)?;
354 let expected = tangent_u[u_side][v_end];
355 let gap = value.sub(expected).length();
356 corner_tangent_gap = corner_tangent_gap.max(gap);
357 if gap > band {
358 return Err(CoonsRefusal::RibbonCornerTangent {
359 corner: u_side * 2 + v_end,
360 gap,
361 band,
362 });
363 }
364 }
365 }
366 }
367
368 let mut twist = [[Vec3::default(); 2]; 2];
371 let mut twist_discrepancy = 0.0f64;
372 if v_hermite && u_hermite {
373 for side in 0..2 {
374 for corner_index in 0..2 {
375 let from_v = derivative_at(
377 &u_family[2 + corner_index],
378 if side == 0 { 0.0 } else { 1.0 },
379 )
380 .map_err(CoonsRefusal::Geometry)?;
381 let from_u = derivative_at(
383 &v_family[2 + side],
384 if corner_index == 0 { 0.0 } else { 1.0 },
385 )
386 .map_err(CoonsRefusal::Geometry)?;
387 twist_discrepancy = twist_discrepancy.max(from_v.sub(from_u).length());
388 twist[side][corner_index] = from_v.add(from_u).scale(0.5);
389 }
390 }
391 }
392
393 let mut first: Vec<Vec<Vec3>> = Vec::with_capacity(rows);
395 for row in 0..rows {
396 let data: Vec<Vec3> = if v_hermite {
397 hermite(
398 c0_net[row],
399 tv0_net.as_ref().expect("paired")[row],
400 c1_net[row],
401 tv1_net.as_ref().expect("paired")[row],
402 )
403 .to_vec()
404 } else {
405 vec![c0_net[row], c1_net[row]]
406 };
407 first.push(express(&data, degree_v, &knots_v).map_err(CoonsRefusal::Geometry)?);
408 }
409 let mut second: Vec<Vec<Vec3>> = vec![vec![Vec3::default(); columns]; rows];
411 for column in 0..columns {
412 let data: Vec<Vec3> = if u_hermite {
413 hermite(
414 d0_net[column],
415 tu0_net.as_ref().expect("paired")[column],
416 d1_net[column],
417 tu1_net.as_ref().expect("paired")[column],
418 )
419 .to_vec()
420 } else {
421 vec![d0_net[column], d1_net[column]]
422 };
423 let expressed = express(&data, degree_u, &knots_u).map_err(CoonsRefusal::Geometry)?;
424 for (row, value) in expressed.into_iter().enumerate() {
425 second[row][column] = value;
426 }
427 }
428 let native_u = if u_hermite { 4 } else { 2 };
430 let native_v = if v_hermite { 4 } else { 2 };
431 let mut correction: Vec<Vec<Vec3>> = vec![vec![Vec3::default(); native_v]; native_u];
432 for side in 0..2 {
433 for corner_index in 0..2 {
434 let row = if u_hermite { side * 3 } else { side };
435 let column = if v_hermite { corner_index * 3 } else { corner_index };
436 correction[row][column] = corners[side][corner_index];
437 }
438 }
439 if u_hermite {
440 for corner_index in 0..2 {
441 let column = if v_hermite { corner_index * 3 } else { corner_index };
442 correction[1][column] = corners[0][corner_index]
443 .add(tangent_u[0][corner_index].scale(1.0 / 3.0));
444 correction[2][column] = corners[1][corner_index]
445 .sub(tangent_u[1][corner_index].scale(1.0 / 3.0));
446 }
447 }
448 if v_hermite {
449 for side in 0..2 {
450 let row = if u_hermite { side * 3 } else { side };
451 correction[row][1] =
452 corners[side][0].add(tangent_v[side][0].scale(1.0 / 3.0));
453 correction[row][2] =
454 corners[side][1].sub(tangent_v[side][1].scale(1.0 / 3.0));
455 }
456 }
457 if u_hermite && v_hermite {
458 for side in 0..2 {
460 for corner_index in 0..2 {
461 let row = if side == 0 { 1 } else { 2 };
462 let column = if corner_index == 0 { 1 } else { 2 };
463 let u_sign = if side == 0 { 1.0 } else { -1.0 };
464 let v_sign = if corner_index == 0 { 1.0 } else { -1.0 };
465 correction[row][column] = corners[side][corner_index]
466 .add(tangent_u[side][corner_index].scale(u_sign / 3.0))
467 .add(tangent_v[side][corner_index].scale(v_sign / 3.0))
468 .add(twist[side][corner_index].scale(u_sign * v_sign / 9.0));
469 }
470 }
471 }
472 let mut correction_v: Vec<Vec<Vec3>> = Vec::with_capacity(native_u);
474 for row in correction.iter() {
475 correction_v.push(express(row, degree_v, &knots_v).map_err(CoonsRefusal::Geometry)?);
476 }
477 let mut third: Vec<Vec<Vec3>> = vec![vec![Vec3::default(); columns]; rows];
478 for column in 0..columns {
479 let data: Vec<Vec3> = correction_v.iter().map(|row| row[column]).collect();
480 let expressed = express(&data, degree_u, &knots_u).map_err(CoonsRefusal::Geometry)?;
481 for (row, value) in expressed.into_iter().enumerate() {
482 third[row][column] = value;
483 }
484 }
485
486 let mut net: Vec<Vec<Vec4>> = vec![
488 vec![
489 Vec4 {
490 x: 0.0,
491 y: 0.0,
492 z: 0.0,
493 w: 1.0
494 };
495 columns
496 ];
497 rows
498 ];
499 for row in 0..rows {
500 for column in 0..columns {
501 let blended = first[row][column]
502 .add(second[row][column])
503 .sub(third[row][column]);
504 net[row][column] = Vec4::from_point(blended, 1.0);
505 }
506 }
507 for (row, control) in u_family[0].control_points.iter().enumerate() {
508 net[row][0] = Vec4::from_point(point_of(*control), 1.0);
509 }
510 for (row, control) in u_family[1].control_points.iter().enumerate() {
511 net[row][columns - 1] = Vec4::from_point(point_of(*control), 1.0);
512 }
513 for (column, control) in v_family[0].control_points.iter().enumerate() {
514 net[0][column] = Vec4::from_point(point_of(*control), 1.0);
515 }
516 for (column, control) in v_family[1].control_points.iter().enumerate() {
517 net[rows - 1][column] = Vec4::from_point(point_of(*control), 1.0);
518 }
519 net[0][0] = Vec4::from_point(corners[0][0], 1.0);
520 net[rows - 1][0] = Vec4::from_point(corners[1][0], 1.0);
521 net[0][columns - 1] = Vec4::from_point(corners[0][1], 1.0);
522 net[rows - 1][columns - 1] = Vec4::from_point(corners[1][1], 1.0);
523
524 let surface = NurbsSurface::new(degree_u, degree_v, knots_u, knots_v, net)
525 .map_err(CoonsRefusal::Geometry)?;
526
527 let mut residual = 0.0f64;
529 let mut ribbon_scale = 0.0f64;
530 let mut prescribed = [false; 4];
531 let mut measure = |ribbon: &NurbsCurve,
532 along_u: bool,
533 at: f64,
534 surface: &NurbsSurface|
535 -> Result<(), CoonsRefusal> {
536 let [start, end] = ribbon.domain().map_err(CoonsRefusal::Geometry)?;
537 for index in 0..=BOUNDARY_SAMPLES {
538 let fraction = index as f64 / BOUNDARY_SAMPLES as f64;
539 let expected = ribbon
540 .evaluate(start + (end - start) * fraction)
541 .map_err(CoonsRefusal::Geometry)?;
542 let (u, v) = if along_u { (fraction, at) } else { (at, fraction) };
543 let derivatives = surface.derivatives(u, v, 1).map_err(CoonsRefusal::Geometry)?;
544 let achieved = if along_u {
545 derivatives[0][1]
546 } else {
547 derivatives[1][0]
548 };
549 residual = residual.max(achieved.sub(expected).length());
550 ribbon_scale = ribbon_scale.max(expected.length());
551 }
552 Ok(())
553 };
554 if v_hermite {
555 prescribed[0] = true;
556 prescribed[2] = true;
557 measure(&u_family[2], true, 0.0, &surface)?;
558 measure(&u_family[3], true, 1.0, &surface)?;
559 }
560 if u_hermite {
561 prescribed[1] = true;
562 prescribed[3] = true;
563 measure(&v_family[2], false, 0.0, &surface)?;
564 measure(&v_family[3], false, 1.0, &surface)?;
565 }
566
567 let mut report = report_skeleton(&oriented, work);
568 report.degree_u = degree_u;
569 report.degree_v = degree_v;
570 report.control_rows = rows;
571 report.control_columns = columns;
572 report.min_weight = 1.0;
573 report.boundary_residual = boundary_residual(&surface, &u_family, &v_family);
574 report.ribbons = Some(RibbonReport {
575 prescribed,
576 corner_tangent_gap,
577 twist_discrepancy,
578 cross_derivative_residual: residual,
579 cross_derivative_scale: ribbon_scale,
580 });
581 Ok((surface, report))
582}
583
584fn boundary_residual(
587 surface: &NurbsSurface,
588 u_family: &[NurbsCurve],
589 v_family: &[NurbsCurve],
590) -> f64 {
591 let net = &surface.control_points;
592 let rows = net.len();
593 let columns = net[0].len();
594 let mut worst = 0.0f64;
595 for row in 0..rows {
596 worst = worst.max(
597 point_of(net[row][0])
598 .sub(point_of(u_family[0].control_points[row]))
599 .length(),
600 );
601 worst = worst.max(
602 point_of(net[row][columns - 1])
603 .sub(point_of(u_family[1].control_points[row]))
604 .length(),
605 );
606 }
607 for column in 0..columns {
608 worst = worst.max(
609 point_of(net[0][column])
610 .sub(point_of(v_family[0].control_points[column]))
611 .length(),
612 );
613 worst = worst.max(
614 point_of(net[rows - 1][column])
615 .sub(point_of(v_family[1].control_points[column]))
616 .length(),
617 );
618 }
619 worst
620}