geop_core_topology/edit/
transform_body.rs1use geop_core_geometry::{aabb::union_aabb, deform::Deformation};
2use geop_core_math::{
3 geop_error::{GeopError, GeopResult, WithContext},
4 vector::Vector3,
5};
6
7use crate::{
8 Body, Model, boundary::BoundaryType, contains::face::face_interior_point,
9 validation::ValidationParameters,
10};
11
12const SEED: u64 = 0x7A5F_0D1E_2B3C_4D5E;
15
16impl Model {
17 pub fn transform_body(
42 &mut self,
43 body: impl Into<Body>,
44 map: &dyn Deformation,
45 ) -> GeopResult<()> {
46 let body = body.into();
47 let ctx = |e: GeopError| e.with_context(format!("Model::transform_body(body={body})"));
48 let vertices: Vec<_> = self.iter_body_vertices(body).with_context(&ctx)?.collect();
49 let edges: Vec<_> = self.iter_body_edges(body).with_context(&ctx)?.collect();
50 let faces = self.body_faces(body).with_context(&ctx)?;
51 let points = (vertices.iter())
53 .map(|v| map.point(&self.vertices[v].point))
54 .collect::<GeopResult<Vec<_>>>()
55 .with_context(&ctx)?;
56 let curves = (edges.iter())
57 .map(|&edge| {
58 let curve = &self.edges[&edge].curve;
59 let at = || curve.evaluate(curve.mid_parameter()?);
60 map.curve(curve, &at)
61 .map_err(|e| e.with_context(format!("moving edge {edge}")))
62 })
63 .collect::<GeopResult<Vec<_>>>()
64 .with_context(&ctx)?;
65 let surfaces = (faces.iter())
66 .map(|&face| {
67 let whole = &self.faces[&face].surface;
68 let outer = match self.faces[&face].outer {
71 BoundaryType::Loop(anchor) => (self.iterate_loop_coedges(anchor))
72 .map(|c| Ok(*self.get_coedge(c)?.pcurve.aabb()))
73 .collect::<GeopResult<Vec<_>>>()?,
74 BoundaryType::Vertex(_) => Vec::new(),
75 };
76 let restricted = match union_aabb(&outer) {
77 Some(uv) if !map.affine() => Some(whole.sub_surface(
78 (uv[0].lower(), uv[0].upper()),
79 (uv[1].lower(), uv[1].upper()),
80 )?),
81 _ => None,
82 };
83 let surface = restricted.as_ref().unwrap_or(whole);
84 let at = || -> GeopResult<Vector3> {
85 let params = ValidationParameters::default();
86 let (u, v) = face_interior_point(
87 self,
88 face,
89 params.max_nodes,
90 params.min_subdivision_size,
91 SEED,
92 )?;
93 surface.evaluate(u.sharpen(), v.sharpen())
94 };
95 map.surface(surface, &at)
96 .map_err(|e| e.with_context(format!("moving face {face}")))
97 })
98 .collect::<GeopResult<Vec<_>>>()
99 .with_context(&ctx)?;
100 for (vertex, point) in vertices.into_iter().zip(points) {
101 self.get_vertex_mut(vertex).with_context(&ctx)?.point = point;
102 }
103 for (edge, curve) in edges.into_iter().zip(curves) {
104 self.get_edge_mut(edge).with_context(&ctx)?.curve = curve;
105 }
106 for (&face, surface) in faces.iter().zip(surfaces) {
107 self.get_face_mut(face).with_context(&ctx)?.surface = surface;
108 }
109 if map.mirrors() {
110 for face in faces {
111 self.reverse_face(face).with_context(&ctx)?;
112 }
113 }
114 Ok(())
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use geop_core_math::{
121 primitives::{Motion, Pose},
122 scalars::Scalar,
123 vector::Vector3,
124 };
125
126 use crate::{
127 Model,
128 test_fixtures::test_cube_solid,
129 validation::{ValidationParameters, validate},
130 };
131
132 fn v(x: f64, y: f64, z: f64) -> Vector3 {
133 Vector3::from_array([x, y, z].map(Scalar::from_f64))
134 }
135
136 fn assert_valid(model: &Model) {
137 if let Err(errors) = validate(&ValidationParameters::default(), model) {
138 let messages: Vec<String> = errors.iter().map(|e| format!("{e:?}")).collect();
139 panic!("{}", messages.join("\n"));
140 }
141 }
142
143 #[test]
146 fn translation_is_exact() {
147 let mut model = Model::new();
148 let cube = test_cube_solid(&mut model);
149 let before = model.clone();
150 let offset = v(2.0, -3.0, 0.5);
151 model
152 .transform_body(cube, &Motion::translation(offset))
153 .unwrap();
154 assert_valid(&model);
155 for (id, vertex) in &model.vertices {
156 let expected = before.vertices[id].point.add(&offset);
157 assert_eq!(
158 format!("{:?}", vertex.point),
159 format!("{expected:?}"),
160 "moved by one addition"
161 );
162 }
163 }
164
165 #[test]
168 fn rotated_cube_stays_valid() {
169 let mut model = Model::new();
170 let cube = test_cube_solid(&mut model);
171 let before = model.clone();
172 let axis = v(1.0, 2.0, 3.0).normalize().unwrap();
173 let pose =
174 Pose::rotation_about(&v(0.5, -1.0, 2.0), &axis, Scalar::from_f64(1.234)).unwrap();
175 model.transform_body(cube, &pose.motion()).unwrap();
176 assert_valid(&model);
177 for (id, vertex) in &model.vertices {
178 let expected = pose.apply(&before.vertices[id].point);
179 assert!(vertex.point.could_be_equal(&expected));
180 }
181 }
182
183 #[test]
186 fn mirrored_cube_stays_valid() {
187 let mut model = Model::new();
188 let cube = test_cube_solid(&mut model);
189 let before = model.clone();
190 let mirror = Motion::mirror(&v(2.0, 0.0, 0.0), &v(1.0, 0.0, 0.0)).unwrap();
191 model.transform_body(cube, &mirror).unwrap();
192 assert_valid(&model);
193 for (id, vertex) in &model.vertices {
194 let p = before.vertices[id].point;
195 let expected = v(4.0, 0.0, 0.0).add(&Vector3::from_array([p[0].neg(), p[1], p[2]]));
196 assert!(vertex.point.could_be_equal(&expected), "{vertex:?}");
197 }
198 }
199}