geop_core_geometry/nurb_surface/
flat.rs1use geop_core_math::{
5 geop_error::{GeopError, GeopResult},
6 scalars::Scalar,
7 vector::{Vector2, Vector3},
8};
9
10use super::NurbSurface3D;
11use crate::{
12 aabb::union_aabb,
13 nurb_curve::{NurbCurve, NurbCurve2D},
14};
15
16#[derive(Clone, Debug)]
27pub struct FlatPatch {
28 origin: Vector3,
29 e1: Vector3,
30 e2: Vector3,
31 lo: Vector2,
32 size: Vector2,
33 swapped: bool,
34}
35
36impl FlatPatch {
37 pub fn around<'a>(
40 origin: Vector3,
41 e1: Vector3,
42 e2: Vector3,
43 curves: impl IntoIterator<Item = &'a NurbCurve2D>,
44 swapped: bool,
45 ) -> GeopResult<Self> {
46 let bounds = union_aabb(curves.into_iter().map(NurbCurve::aabb))
47 .ok_or_else(|| GeopError::new("FlatPatch::around: no curves to span"))?;
48 let lo: [f64; 2] = std::array::from_fn(|k| bounds[k].lower().to_f64());
49 let hi: [f64; 2] = std::array::from_fn(|k| bounds[k].upper().to_f64());
50 let size = std::array::from_fn(|k| reaching(lo[k], hi[k]));
51 Ok(Self {
52 origin,
53 e1,
54 e2,
55 lo: Vector2::from_array(lo.map(Scalar::from_f64)),
56 size: Vector2::from_array(size),
57 swapped,
58 })
59 }
60
61 pub fn past(&self, [low, high]: [Vector2; 2]) -> Self {
72 let margin = self.size[0].to_f64().max(self.size[1].to_f64());
73 let lo: [f64; 2] =
74 std::array::from_fn(|k| self.lo[k].to_f64().min(low[k].lower().to_f64()) - margin);
75 let hi: [f64; 2] = std::array::from_fn(|k| {
76 let own = self.lo[k].add(self.size[k]).upper().to_f64();
77 own.max(high[k].upper().to_f64()) + margin
78 });
79 let size = std::array::from_fn(|k| reaching(lo[k], hi[k]));
80 Self {
81 lo: Vector2::from_array(lo.map(Scalar::from_f64)),
82 size: Vector2::from_array(size),
83 ..self.clone()
84 }
85 }
86
87 pub fn reach(&self) -> [Vector2; 2] {
90 [self.lo, self.lo.add(&self.size)]
91 }
92
93 pub fn pcurve(&self, curve: &NurbCurve2D) -> GeopResult<NurbCurve2D> {
95 let control_points = curve
96 .control_points
97 .iter()
98 .map(|cp| {
99 Ok(Vector3::from_array([
100 cp[0].sub(cp[2].mul(self.lo[0])).div(self.size[0])?,
101 cp[1].sub(cp[2].mul(self.lo[1])).div(self.size[1])?,
102 cp[2],
103 ]))
104 })
105 .collect::<GeopResult<Vec<_>>>()?;
106 let pcurve = NurbCurve::try_new(curve.degree, control_points, curve.knot_vector.clone())?;
107 Ok(if self.swapped {
108 pcurve.swap_xy()
109 } else {
110 pcurve
111 })
112 }
113
114 pub fn surface(&self) -> GeopResult<NurbSurface3D> {
116 let corner = |i: usize, j: usize| {
117 let x = if i == 1 {
118 self.lo[0].add(self.size[0])
119 } else {
120 self.lo[0]
121 };
122 let y = if j == 1 {
123 self.lo[1].add(self.size[1])
124 } else {
125 self.lo[1]
126 };
127 self.origin
128 .add(&self.e1.prod_scalar(x))
129 .add(&self.e2.prod_scalar(y))
130 };
131 if self.swapped {
132 NurbSurface3D::bilinear(corner(0, 0), corner(0, 1), corner(1, 1), corner(1, 0))
133 } else {
134 NurbSurface3D::bilinear(corner(0, 0), corner(1, 0), corner(1, 1), corner(0, 1))
135 }
136 }
137}
138
139fn reaching(lo: f64, hi: f64) -> Scalar {
144 let (from, to) = (Scalar::from_f64(lo), Scalar::from_f64(hi));
145 let plain = Scalar::from_f64(hi - lo);
146 if from.add(plain).definitely_less(to) {
147 to.sub(from).upper()
148 } else {
149 plain
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
161 fn patch_encloses_its_curves() {
162 let corners = [(-3.0, -3.0), (0.001, -3.0), (0.001, 0.001), (-3.0, 0.001)]
163 .map(|(x, y)| Vector2::from_array([Scalar::from_f64(x), Scalar::from_f64(y)]));
164 let sides: Vec<NurbCurve2D> = (0..4)
165 .map(|k| NurbCurve2D::line(corners[k], corners[(k + 1) % 4]).unwrap())
166 .collect();
167 let patch = FlatPatch::around(
168 Vector3::zero(),
169 Vector3::axis(0),
170 Vector3::axis(1),
171 &sides,
172 false,
173 )
174 .unwrap();
175 let reach = Scalar::from_f64(0.001);
176 let [_, far] = patch.reach();
177 for k in 0..2 {
178 assert!(
179 !far[k].definitely_less(reach),
180 "along axis {k} the patch reaches {:?}, short of the curves' {reach:?}",
181 far[k]
182 );
183 }
184 }
185}