1use geop_core_math::{
2 geop_error::{GeopError, GeopResult, WithContext},
3 scalars::Scalar,
4};
5
6use crate::{Curve2, FaceId, Model, Sense};
7
8impl Model {
9 pub fn reverse_face(&mut self, face_id: FaceId) -> GeopResult<()> {
25 let ctx = |e: GeopError| e.with_context(format!("Model::reverse_face(face={face_id})"));
26
27 let face = self.get_face(face_id).with_context(&ctx)?;
28 let (u_lo, u_hi) = face.surface.domain_u();
29 let span = u_lo.add(u_hi);
30 let reversed = face.surface.reverse_u();
31 self.get_face_mut(face_id).with_context(&ctx)?.surface = reversed;
32
33 for coedge_id in self.iterate_face_coedges(face_id).collect::<Vec<_>>() {
34 let coedge = self.get_coedge_mut(coedge_id).with_context(&ctx)?;
35 mirror_u(&mut coedge.pcurve, span);
36
37 std::mem::swap(&mut coedge.next, &mut coedge.prev);
56 coedge.pcurve = coedge.pcurve.reverse();
57 coedge.sense = match coedge.sense {
58 Sense::Forward => Sense::Reversed,
59 Sense::Reversed => Sense::Forward,
60 };
61 }
62 Ok(())
63 }
64}
65
66pub(crate) fn mirror_u(pcurve: &mut Curve2, span: Scalar) {
76 for point in &mut pcurve.control_points {
77 point[0] = span.mul(point[2]).sub(point[0]);
78 }
79 pcurve.recompute_aabb();
85}
86
87#[cfg(test)]
88mod tests {
89 use geop_core_geometry::{
90 nurb_curve::{NurbCurve, NurbCurve2D},
91 nurb_surface::NurbSurface3D,
92 };
93 use geop_core_math::{
94 scalars::Scalar,
95 vector::{Vector3, Vector4},
96 };
97
98 use crate::{
99 Coedge, CoedgeGeometry, CoedgeId, Edge, Face, Model, Sense, ShellId, Vertex, VertexId,
100 boundary::BoundaryType,
101 };
102
103 fn saddle_face(model: &mut Model) -> crate::FaceId {
106 let p = |x: f64, y: f64, z: f64| {
107 Vector4::from_array([
108 Scalar::from_f64(x),
109 Scalar::from_f64(y),
110 Scalar::from_f64(z),
111 Scalar::ONE,
112 ])
113 };
114 let surface = NurbSurface3D::try_new(
115 1,
116 1,
117 vec![p(0., 0., 0.), p(0., 2., 1.), p(2., 0., 1.), p(2., 2., 0.)],
118 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
119 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
120 )
121 .unwrap();
122 let face_id = model.insert_face(Face {
123 surface,
124 outer: BoundaryType::Vertex(VertexId(0)),
125 holes: Vec::new(),
126 shell: ShellId(999),
127 });
128
129 let corners = [(0.2, 0.2), (0.8, 0.2), (0.5, 0.8)];
130 let line2 = |a: (f64, f64), b: (f64, f64)| -> NurbCurve2D {
131 NurbCurve::try_new(
132 1,
133 vec![
134 Vector3::from_array([
135 Scalar::from_f64(a.0),
136 Scalar::from_f64(a.1),
137 Scalar::ONE,
138 ]),
139 Vector3::from_array([
140 Scalar::from_f64(b.0),
141 Scalar::from_f64(b.1),
142 Scalar::ONE,
143 ]),
144 ],
145 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
146 )
147 .unwrap()
148 };
149 let coedges: Vec<CoedgeId> = (0..3)
150 .map(|i| {
151 let a = corners[i];
152 let b = corners[(i + 1) % 3];
153 let v0 = model.insert_vertex(Vertex {
154 point: Vector3::from_array([
155 Scalar::from_f64(a.0),
156 Scalar::from_f64(a.1),
157 Scalar::ZERO,
158 ]),
159 });
160 let v1 = model.insert_vertex(Vertex {
161 point: Vector3::from_array([
162 Scalar::from_f64(b.0),
163 Scalar::from_f64(b.1),
164 Scalar::ZERO,
165 ]),
166 });
167 let edge = model.insert_edge(Edge {
168 curve: NurbCurve::try_new(
169 1,
170 vec![
171 Vector4::from_array([
172 Scalar::from_f64(a.0),
173 Scalar::from_f64(a.1),
174 Scalar::ZERO,
175 Scalar::ONE,
176 ]),
177 Vector4::from_array([
178 Scalar::from_f64(b.0),
179 Scalar::from_f64(b.1),
180 Scalar::ZERO,
181 Scalar::ONE,
182 ]),
183 ],
184 vec![Scalar::ZERO, Scalar::ZERO, Scalar::ONE, Scalar::ONE],
185 )
186 .unwrap(),
187 start_vertex: v0,
188 end_vertex: v1,
189 });
190 model.insert_coedge(Coedge {
191 geometry: CoedgeGeometry::Edge(edge),
192 sense: Sense::Forward,
193 pcurve: line2(a, b),
194 next: CoedgeId(0),
195 prev: CoedgeId(0),
196 face: face_id,
197 })
198 })
199 .collect();
200 for i in 0..3 {
201 model.coedges.get_mut(&coedges[i]).unwrap().next = coedges[(i + 1) % 3];
202 model.coedges.get_mut(&coedges[i]).unwrap().prev = coedges[(i + 2) % 3];
203 }
204 model.faces.get_mut(&face_id).unwrap().outer = BoundaryType::Loop(coedges[0]);
205 face_id
206 }
207
208 #[test]
211 fn reverse_face_flips_normal_and_keeps_the_patch() {
212 let mut model = Model::new();
213 let face_id = saddle_face(&mut model);
214
215 let anchor = match model.get_face(face_id).unwrap().outer {
216 BoundaryType::Loop(a) => a,
217 BoundaryType::Vertex(_) => unreachable!(),
218 };
219 let sample_t = Scalar::from_f64(0.3);
220 let before_uv = model
221 .get_coedge(anchor)
222 .unwrap()
223 .pcurve
224 .evaluate(sample_t)
225 .unwrap();
226 let surface_before = model.get_face(face_id).unwrap().surface.clone();
227 let before_point = surface_before.evaluate(before_uv[0], before_uv[1]).unwrap();
228 let before_normal = surface_before.normal(before_uv[0], before_uv[1]).unwrap();
229
230 model.reverse_face(face_id).unwrap();
231
232 let (t0, t1) = model.get_coedge(anchor).unwrap().pcurve.domain();
235 let after_uv = model
236 .get_coedge(anchor)
237 .unwrap()
238 .pcurve
239 .evaluate(t0.add(t1).sub(sample_t))
240 .unwrap();
241 let surface_after = &model.get_face(face_id).unwrap().surface;
242 let after_point = surface_after.evaluate(after_uv[0], after_uv[1]).unwrap();
243 let after_normal = surface_after.normal(after_uv[0], after_uv[1]).unwrap();
244
245 for c in 0..3 {
246 assert!(
247 before_point[c].could_be_equal(after_point[c]),
248 "coord {c}: the trim curve moved: {before_point:?} vs {after_point:?}"
249 );
250 assert!(
251 before_normal[c].could_be_equal(after_normal[c].neg()),
252 "coord {c}: the normal did not flip: {before_normal:?} vs {after_normal:?}"
253 );
254 }
255 }
256
257 #[test]
268 fn reverse_face_preserves_loop_winding() {
269 let mut model = Model::new();
270 let face_id = saddle_face(&mut model);
271
272 let area = |model: &Model| {
273 let BoundaryType::Loop(anchor) = model.get_face(face_id).unwrap().outer else {
274 unreachable!()
275 };
276 crate::uv_loop::loop_signed_area(model, anchor).unwrap()
277 };
278
279 let before = area(&model);
280 model.reverse_face(face_id).unwrap();
281 let after = area(&model);
282
283 assert!(
284 before.definitely_greater(Scalar::ZERO) == after.definitely_greater(Scalar::ZERO)
285 && before.definitely_less(Scalar::ZERO) == after.definitely_less(Scalar::ZERO),
286 "winding flipped: signed area went from {before:?} to {after:?}"
287 );
288 }
289
290 #[test]
292 fn reverse_face_twice_is_identity() {
293 let mut model = Model::new();
294 let face_id = saddle_face(&mut model);
295 let anchor = match model.get_face(face_id).unwrap().outer {
296 BoundaryType::Loop(a) => a,
297 BoundaryType::Vertex(_) => unreachable!(),
298 };
299 let t = Scalar::from_f64(0.4);
300 let before = model
301 .get_coedge(anchor)
302 .unwrap()
303 .pcurve
304 .evaluate(t)
305 .unwrap();
306 let before_sense = model.get_coedge(anchor).unwrap().sense;
307
308 model.reverse_face(face_id).unwrap();
309 model.reverse_face(face_id).unwrap();
310
311 let after = model
312 .get_coedge(anchor)
313 .unwrap()
314 .pcurve
315 .evaluate(t)
316 .unwrap();
317 for c in 0..2 {
318 assert!(before[c].could_be_equal(after[c]), "coord {c} drifted");
319 }
320 assert_eq!(
321 before_sense,
322 model.get_coedge(anchor).unwrap().sense,
323 "sense must come back to where it started"
324 );
325 }
326}