1use std::collections::BTreeMap;
13
14use geop_core_geometry::{
15 nurb_curve::{NurbCurve, NurbCurve2D, NurbCurve3D, true_point_fractions},
16 nurb_surface::NurbSurface3D,
17};
18use geop_core_math::{
19 geop_error::{GeopError, GeopResult, WithContext},
20 scalars::Scalar,
21 vector::{Vector2, Vector3, Vector4},
22 with_context,
23};
24use geop_core_topology::Sense;
25
26use crate::{
27 layout::{Home, Layout, LayoutEdge, Seg},
28 sheet::{BendFrame, Placement, Sheet},
29 thicken::{SheetCoedge, SheetEdge, SheetFace, SheetSurface, SheetVertex},
30};
31
32const SAMPLES: usize = 32;
36
37type Pair = (Vector3, Vector3);
39
40impl Sheet {
41 pub fn folded(&self) -> GeopResult<SheetSurface> {
43 self.surface(&self.layout()?, true)
44 }
45
46 pub fn unfolded(&self) -> GeopResult<SheetSurface> {
51 self.surface(&self.layout()?, false)
52 }
53
54 fn surface(&self, layout: &Layout, folded: bool) -> GeopResult<SheetSurface> {
56 let t = Scalar::from_f64(self.rules.thickness);
57 let shifts = self.flat_shifts()?;
58 let used = layout.used_edges();
59 let mut vertex_ids: Vec<usize> = used
60 .iter()
61 .flat_map(|&e| [layout.edges[e].start, layout.edges[e].end])
62 .collect();
63 vertex_ids.sort_unstable();
64 vertex_ids.dedup();
65 let vindex: BTreeMap<usize, usize> = vertex_ids
66 .iter()
67 .enumerate()
68 .map(|(i, &v)| (v, i))
69 .collect();
70 let eindex: BTreeMap<usize, usize> =
71 used.iter().enumerate().map(|(i, &e)| (e, i)).collect();
72 let root = &self.flats[0].place;
73 let wraps: Vec<Wrap> = if folded {
74 (0..self.bends.len())
75 .map(|b| Wrap::new(self, b, layout))
76 .collect::<GeopResult<_>>()?
77 } else {
78 Vec::new()
79 };
80
81 let mut home: BTreeMap<usize, Home> = BTreeMap::new();
84 for &e in &used {
85 let edge = &layout.edges[e];
86 for v in [edge.start, edge.end] {
87 if !matches!(home.get(&v), Some(Home::Flat(_))) {
88 home.insert(v, edge.home);
89 }
90 }
91 }
92 let mut points: BTreeMap<usize, Pair> = BTreeMap::new();
93 let mut sheet = SheetSurface::default();
94 for &v in &vertex_ids {
95 let vertex = &layout.vertices[v];
96 let pair = if !folded {
97 (root.point(&vertex.at), root.offset(t).point(&vertex.at))
98 } else if let Some((f, own)) = &vertex.own {
99 let place = &self.flats[*f].place;
100 (place.point(own), place.offset(t).point(own))
101 } else {
102 match home[&v] {
103 Home::Flat(f) => {
104 let own = vertex.at.sub(&shifts[f]);
105 let place = &self.flats[f].place;
106 (place.point(&own), place.offset(t).point(&own))
107 }
108 Home::Bend(b) => wraps[b].pair(&vertex.at)?,
109 }
110 };
111 points.insert(v, pair);
112 sheet.vertices.push(SheetVertex {
113 a: pair.0,
114 b: pair.1,
115 name: vertex.name.clone().ok_or_else(|| {
116 GeopError::new(format!(
117 "the layout's vertex at {:?} has no name",
118 vertex.at
119 ))
120 })?,
121 });
122 }
123
124 let own = |edge: &LayoutEdge, f: usize| -> GeopResult<NurbCurve2D> {
126 match &edge.own {
127 Some(curve) => Ok(curve.clone()),
128 None => Ok(edge.curve.translate(shifts[f].neg())),
129 }
130 };
131 for &e in &used {
132 let edge = &layout.edges[e];
133 let ctx = with_context!("edge {}", edge.name.root());
134 let (a, b) = if !folded {
135 (root.curve(&edge.curve)?, root.offset(t).curve(&edge.curve)?)
136 } else {
137 match edge.home {
138 Home::Flat(f) => {
139 let curve = own(edge, f).with_context(ctx)?;
140 let place = &self.flats[f].place;
141 (place.curve(&curve)?, place.offset(t).curve(&curve)?)
142 }
143 Home::Bend(b) => wraps[b]
144 .edge(edge, &points[&edge.start], &points[&edge.end])
145 .with_context(ctx)?,
146 }
147 };
148 sheet.edges.push(SheetEdge {
149 a,
150 b,
151 start: vindex[&edge.start],
152 end: vindex[&edge.end],
153 name: edge.name.clone(),
154 });
155 }
156
157 for face in &layout.faces {
158 let ctx = with_context!("face {}", face.name.root());
159 let surfaces = match (folded, face.home) {
160 (true, Home::Bend(b)) => {
161 let wrap = &wraps[b];
162 let uv = wrap.vertex_uv(face, layout).with_context(ctx)?;
163 let coedges = |lp: &Vec<(usize, Sense)>| -> GeopResult<Vec<SheetCoedge>> {
164 lp.iter()
165 .map(|&(e, sense)| {
166 Ok(SheetCoedge {
167 edge: eindex[&e],
168 sense,
169 pcurve: wrap.pcurve(&layout.edges[e], sense, &uv)?,
170 })
171 })
172 .collect()
173 };
174 let outer = coedges(&face.outer).with_context(ctx)?;
175 let holes = face
176 .holes
177 .iter()
178 .map(coedges)
179 .collect::<GeopResult<_>>()
180 .with_context(ctx)?;
181 sheet.faces.push(SheetFace {
182 a: wrap.strips.0.clone(),
183 b: wrap.strips.1.clone(),
184 outer,
185 holes,
186 name: face.name.clone(),
187 });
188 continue;
189 }
190 (true, Home::Flat(f)) => Some(f),
191 (false, _) => None,
192 };
193 let place: &Placement = match surfaces {
195 Some(f) => &self.flats[f].place,
196 None => root,
197 };
198 let curve = |e: usize| -> GeopResult<NurbCurve2D> {
199 match surfaces {
200 Some(f) => own(&layout.edges[e], f),
201 None => Ok(layout.edges[e].curve.clone()),
202 }
203 };
204 let coedges = |lp: &Vec<(usize, Sense)>| -> GeopResult<Vec<SheetCoedge>> {
205 lp.iter()
206 .map(|&(e, sense)| {
207 let c = curve(e)?;
208 Ok(SheetCoedge {
209 edge: eindex[&e],
210 sense,
211 pcurve: match sense {
212 Sense::Forward => c,
213 Sense::Reversed => c.reverse(),
214 },
215 })
216 })
217 .collect()
218 };
219 let outer = coedges(&face.outer).with_context(ctx)?;
220 let holes: Vec<Vec<SheetCoedge>> = face
221 .holes
222 .iter()
223 .map(coedges)
224 .collect::<GeopResult<_>>()
225 .with_context(ctx)?;
226 let refs: Vec<&NurbCurve2D> = std::iter::once(&outer)
227 .chain(&holes)
228 .flatten()
229 .map(|c| &c.pcurve)
230 .collect();
231 sheet.faces.push(SheetFace {
232 a: place.plane(&refs).with_context(ctx)?,
233 b: place.offset(t).plane(&refs).with_context(ctx)?,
234 outer,
235 holes,
236 name: face.name.clone(),
237 });
238 }
239 Ok(sheet)
240 }
241}
242
243struct Wrap {
254 frame: BendFrame,
255 origin: Vector2,
258 along: Vector2,
259 out: Vector2,
260 length: Scalar,
261 neutral: Scalar,
262 spans: usize,
265 step: Scalar,
266 quarter_tan: Scalar,
268 strips: (NurbSurface3D, NurbSurface3D),
270 sides: [(NurbCurve3D, NurbCurve3D); 2],
273 keys: [String; 4],
275}
276
277impl Wrap {
278 fn new(sheet: &Sheet, b: usize, layout: &Layout) -> GeopResult<Self> {
279 let bend = &sheet.bends[b];
280 let ctx = with_context!("bend {}", bend.name.root());
281 let t = Scalar::from_f64(sheet.rules.thickness);
282 let frame = sheet.bend_frame(bend).with_context(ctx)?;
283 let [pa, pb, ca, cb] = layout.corners[b];
284 let corner = |v: usize| -> GeopResult<Pair> {
285 let (f, own) = layout.vertices[v].own.as_ref().ok_or_else(|| {
286 GeopError::new(format!(
287 "bend {}'s corner is no vertex of the sheet as built",
288 bend.name.root()
289 ))
290 })?;
291 let place = &sheet.flats[*f].place;
292 Ok((place.point(own), place.offset(t).point(own)))
293 };
294 let (s0, s1) = {
295 let turn = |p: Pair, c: Pair| -> GeopResult<(NurbCurve3D, NurbCurve3D)> {
296 Ok((
297 turn(&frame, p.0, c.0, Scalar::ZERO, bend.angle, frame.r_a)?,
298 turn(&frame, p.1, c.1, Scalar::ZERO, bend.angle, frame.r_b)?,
299 ))
300 };
301 (
302 turn(corner(pa)?, corner(ca)?).with_context(ctx)?,
303 turn(corner(pb)?, corner(cb)?).with_context(ctx)?,
304 )
305 };
306 let strips = (
309 NurbSurface3D::ruled_compatible(&s0.0, &s1.0)?,
310 NurbSurface3D::ruled_compatible(&s0.1, &s1.1)?,
311 );
312 let origin = layout.vertices[pa].at;
313 let run = layout.vertices[pb].at.sub(&origin);
314 let along = run.normalize().with_context(ctx)?;
315 let neutral = bend
316 .radius
317 .add(Scalar::from_f64(sheet.rules.k_factor).mul(t));
318 let spans = spans(bend.angle);
319 let step = match spans {
320 1 => bend.angle,
321 n => bend.angle.div(Scalar::from_i64(n as i64))?,
322 };
323 let quarter = step.div(Scalar::from_f64(4.0))?;
324 Ok(Self {
325 origin,
326 out: Vector2::from_array([along[1], along[0].neg()]),
327 along,
328 length: run.norm(),
329 neutral,
330 spans,
331 step,
332 quarter_tan: quarter.sin().div(quarter.cos())?,
333 strips,
334 sides: [s0, s1],
335 keys: [
336 bend.parent_edge.clone(),
337 bend.child_edge.clone(),
338 bend.name.scoped("s0").root(),
339 bend.name.scoped("s1").root(),
340 ],
341 frame,
342 })
343 }
344
345 fn coords(&self, x: &Vector2) -> GeopResult<(Scalar, Scalar)> {
348 let d = x.sub(&self.origin);
349 Ok((
350 d.prod_dot(&self.along),
351 d.prod_dot(&self.out).div(self.neutral)?,
352 ))
353 }
354
355 fn point(&self, along: Scalar, theta: Scalar, radius: Scalar) -> Vector3 {
358 let f = &self.frame;
359 let radial =
360 f.m.prod_scalar(theta.sin())
361 .sub(&f.n.prod_scalar(f.sigma.mul(theta.cos())));
362 f.center
363 .add(&f.tau.prod_scalar(along))
364 .add(&radial.prod_scalar(radius))
365 }
366
367 fn pair(&self, x: &Vector2) -> GeopResult<Pair> {
369 let (along, theta) = self.coords(x)?;
370 Ok((
371 self.point(along, theta, self.frame.r_a),
372 self.point(along, theta, self.frame.r_b),
373 ))
374 }
375
376 fn uv(&self, along: Scalar, theta: Scalar) -> GeopResult<Vector2> {
380 let span =
381 ((theta.to_f64() / self.step.to_f64()).floor().max(0.0) as usize).min(self.spans - 1);
382 let local = match span {
383 0 => theta,
384 j => theta.sub(self.step.mul(Scalar::from_i64(j as i64))),
385 };
386 let half = local.sub(self.step.div(Scalar::TWO)?).div(Scalar::TWO)?;
387 let tan = half.sin().div(half.cos())?;
388 let t = Scalar::ONE
389 .add(tan.div(self.quarter_tan)?)
390 .div(Scalar::TWO)?;
391 let u = match self.spans {
392 1 => t,
393 n => t
394 .add(Scalar::from_i64(span as i64))
395 .div(Scalar::from_i64(n as i64))?,
396 };
397 Ok(Vector2::from_array([u, along.div(self.length)?]))
398 }
399
400 fn vertex_uv(
404 &self,
405 face: &crate::layout::LayoutFace,
406 layout: &Layout,
407 ) -> GeopResult<BTreeMap<usize, Vector2>> {
408 let mut fixed: BTreeMap<usize, [Option<Scalar>; 2]> = BTreeMap::new();
409 for &(e, _) in face.loops().flatten() {
410 let edge = &layout.edges[e];
411 let set = match edge.origin.as_deref() {
412 Some(k) if k == self.keys[0] => Some((0, Scalar::ZERO)),
413 Some(k) if k == self.keys[1] => Some((0, Scalar::ONE)),
414 Some(k) if k == self.keys[2] => Some((1, Scalar::ZERO)),
415 Some(k) if k == self.keys[3] => Some((1, Scalar::ONE)),
416 _ => None,
417 };
418 for v in [edge.start, edge.end] {
419 let slot = fixed.entry(v).or_default();
420 if let Some((k, value)) = set {
421 slot[k] = Some(value);
422 }
423 }
424 }
425 fixed
426 .into_iter()
427 .map(|(v, [u, w])| {
428 let (along, theta) = self.coords(&layout.vertices[v].at)?;
429 let free = self.uv(along, theta)?;
430 Ok((
431 v,
432 Vector2::from_array([u.unwrap_or(free[0]), w.unwrap_or(free[1])]),
433 ))
434 })
435 .collect()
436 }
437
438 fn kind(&self, edge: &LayoutEdge) -> GeopResult<Run> {
441 if let Some(origin) = &edge.origin {
442 return Ok(if *origin == self.keys[2] || *origin == self.keys[3] {
443 Run::Across
444 } else {
445 Run::Along
446 });
447 }
448 Ok(match Seg::of(&edge.curve)? {
449 Seg::Line { a, b } => {
450 let d = b.sub(&a);
451 if d.prod_cross(&self.along).could_be_equal(Scalar::ZERO) {
452 Run::Along
453 } else if d.prod_dot(&self.along).could_be_equal(Scalar::ZERO) {
454 Run::Across
455 } else {
456 Run::Wrapped
457 }
458 }
459 _ => Run::Wrapped,
460 })
461 }
462
463 fn edge(
465 &self,
466 edge: &LayoutEdge,
467 start: &Pair,
468 end: &Pair,
469 ) -> GeopResult<(NurbCurve3D, NurbCurve3D)> {
470 for (k, side) in self.sides.iter().enumerate() {
472 let key = &self.keys[2 + k];
473 if edge.origin.as_ref() == Some(key) && edge.name.root() == *key {
474 return Ok(side.clone());
475 }
476 }
477 match self.kind(edge)? {
478 Run::Along => Ok((
479 NurbCurve3D::line(start.0, end.0)?,
480 NurbCurve3D::line(start.1, end.1)?,
481 )),
482 Run::Across => {
483 let (_, from) = self.coords(&edge.curve.start()?)?;
484 let (_, to) = self.coords(&edge.curve.end()?)?;
485 Ok((
486 turn(&self.frame, start.0, end.0, from, to, self.frame.r_a)?,
487 turn(&self.frame, start.1, end.1, from, to, self.frame.r_b)?,
488 ))
489 }
490 Run::Wrapped => self.wrapped(&edge.curve, start, end),
491 }
492 }
493
494 fn wrapped(
498 &self,
499 curve: &NurbCurve2D,
500 start: &Pair,
501 end: &Pair,
502 ) -> GeopResult<(NurbCurve3D, NurbCurve3D)> {
503 let (params, samples, between) = sample(curve)?;
504 let mut sides = Vec::new();
505 for (k, radius) in [self.frame.r_a, self.frame.r_b].into_iter().enumerate() {
506 let at = |x: &Vector2| -> GeopResult<Vector3> {
507 let (along, theta) = self.coords(x)?;
508 Ok(self.point(along, theta, radius))
509 };
510 let mut points = samples.iter().map(at).collect::<GeopResult<Vec<_>>>()?;
511 let last = points.len() - 1;
512 points[0] = if k == 0 { start.0 } else { start.1 };
513 points[last] = if k == 0 { end.0 } else { end.1 };
514 let values: Vec<Vector4> = points
517 .iter()
518 .map(|p| {
519 let c = p.sharpen();
520 Vector4::from_array([c[0], c[1], c[2], Scalar::ONE])
521 })
522 .collect();
523 let mut fit = NurbCurve::interpolate_homogeneous(&values, ¶ms, 3)?;
524 let mut checks: Vec<(Scalar, Vector3)> =
525 params.iter().copied().zip(points.iter().copied()).collect();
526 for (param, x) in between.iter().flatten() {
527 checks.push((*param, at(x)?));
528 }
529 let pad = fit.enclosing_pad(&checks)?;
530 fit.widen(&pad);
531 sides.push(fit);
532 }
533 let b = sides.pop().expect("two sides");
534 let a = sides.pop().expect("two sides");
535 Ok((a, b))
536 }
537
538 fn pcurve(
541 &self,
542 edge: &LayoutEdge,
543 sense: Sense,
544 uv: &BTreeMap<usize, Vector2>,
545 ) -> GeopResult<NurbCurve2D> {
546 let (from, to) = match sense {
547 Sense::Forward => (edge.start, edge.end),
548 Sense::Reversed => (edge.end, edge.start),
549 };
550 if !matches!(self.kind(edge)?, Run::Wrapped) {
551 return NurbCurve2D::line(uv[&from], uv[&to]);
552 }
553 let curve = match sense {
554 Sense::Forward => edge.curve.clone(),
555 Sense::Reversed => edge.curve.reverse(),
556 };
557 let (_, samples, between) = sample(&curve)?;
558 let at = |x: &Vector2| -> GeopResult<Vector2> {
559 let (along, theta) = self.coords(x)?;
560 self.uv(along, theta)
561 };
562 let mut points = samples.iter().map(at).collect::<GeopResult<Vec<_>>>()?;
563 let last = points.len() - 1;
564 points[0] = uv[&from];
565 points[last] = uv[&to];
566 let inside = between
567 .iter()
568 .map(|chunk| chunk.iter().map(|(_, x)| at(x)).collect())
569 .collect::<GeopResult<Vec<Vec<_>>>>()?;
570 NurbCurve2D::interpolate_enclosing(&points, &inside, 3)
571 }
572}
573
574fn spans(angle: Scalar) -> usize {
578 if angle.abs().to_f64() <= 2.0 * std::f64::consts::PI / 3.0 {
579 1
580 } else {
581 2
582 }
583}
584
585fn turn(
589 frame: &BendFrame,
590 p: Vector3,
591 q: Vector3,
592 from: Scalar,
593 to: Scalar,
594 radius: Scalar,
595) -> GeopResult<NurbCurve3D> {
596 let starts_flat = from.is_sharp() && from.could_be_equal(Scalar::ZERO);
597 let delta = if starts_flat { to } else { to.sub(from) };
598 let n = spans(delta);
599 let step = match n {
600 1 => delta,
601 n => delta.div(Scalar::from_i64(n as i64))?,
602 };
603 let half = step.div(Scalar::TWO)?;
604 let (tan, weight) = (half.sin().div(half.cos())?, half.cos());
605 let direction = |theta: Scalar, flat: bool| {
606 if flat {
608 (frame.m, frame.n.prod_scalar(frame.sigma.neg()))
609 } else {
610 let (c, s) = (theta.cos(), theta.sin());
611 (
612 frame
613 .m
614 .prod_scalar(c)
615 .add(&frame.n.prod_scalar(frame.sigma.mul(s))),
616 frame
617 .m
618 .prod_scalar(s)
619 .sub(&frame.n.prod_scalar(frame.sigma.mul(c))),
620 )
621 }
622 };
623 if n == 1 {
624 let (tangent, _) = direction(from, starts_flat);
625 return NurbCurve3D::arc(p, p.add(&tangent.prod_scalar(radius.mul(tan))), q, weight);
626 }
627 let mut control_points = vec![p.homogeneous(Scalar::ONE)];
628 let mut knots = vec![Scalar::ZERO; 3];
629 let mut at = p;
630 for j in 0..n {
631 let theta = if j == 0 {
632 from
633 } else {
634 from.add(step.mul(Scalar::from_i64(j as i64)))
635 };
636 let (tangent, radial) = direction(theta, j == 0 && starts_flat);
637 let apex = at.add(&tangent.prod_scalar(radius.mul(tan)));
638 let next = if j + 1 == n {
639 q
640 } else {
641 let (_, out) = direction(from.add(step.mul(Scalar::from_i64(j as i64 + 1))), false);
642 at.add(&out.sub(&radial).prod_scalar(radius))
643 };
644 control_points.push(apex.homogeneous(weight));
645 control_points.push(next.homogeneous(Scalar::ONE));
646 if j + 1 < n {
647 let knot = Scalar::from_f64((j + 1) as f64 / n as f64);
648 knots.extend([knot, knot]);
649 }
650 at = next;
651 }
652 knots.extend([Scalar::ONE; 3]);
653 NurbCurve::try_new(2, control_points, knots)
654}
655
656enum Run {
658 Along,
660 Across,
662 Wrapped,
664}
665
666#[allow(clippy::type_complexity)]
671fn sample(
672 curve: &NurbCurve2D,
673) -> GeopResult<(Vec<Scalar>, Vec<Vector2>, Vec<Vec<(Scalar, Vector2)>>)> {
674 let (t0, t1) = curve.domain();
675 let at = |f: Scalar| -> GeopResult<Vector2> { curve.evaluate(Scalar::fraction(t0, t1, f)) };
677 let mut params = Vec::new();
678 let mut samples = Vec::new();
679 let mut between = Vec::new();
680 for i in 0..=SAMPLES {
681 let f = Scalar::from_f64(i as f64 / SAMPLES as f64);
683 params.push(f);
684 samples.push(at(f)?);
685 if i < SAMPLES {
686 let mut inside = Vec::new();
687 for &(num, den) in true_point_fractions(i, SAMPLES) {
688 let g =
689 Scalar::from_f64((i as i64 * den + num) as f64 / (SAMPLES as i64 * den) as f64);
690 inside.push((g, at(g)?));
691 }
692 between.push(inside);
693 }
694 }
695 Ok((params, samples, between))
696}