1use crate::topology::BrepSolid;
2use crate::{NurbsCurve, NurbsSurface, Vec3, Vec4};
3
4#[derive(Clone, Copy, Debug)]
5pub struct AffineTransform {
6 pub elements: [f64; 16],
7}
8
9impl AffineTransform {
10 pub fn new(elements: [f64; 16]) -> Result<Self, String> {
11 if elements.iter().any(|value| !value.is_finite()) {
12 return Err("AffineTransform: matrix must be finite".into());
13 }
14 if elements[12].abs() > 1e-12
15 || elements[13].abs() > 1e-12
16 || elements[14].abs() > 1e-12
17 || (elements[15] - 1.0).abs() > 1e-12
18 {
19 return Err("AffineTransform: projective matrices are unsupported".into());
20 }
21 if (Self { elements }).determinant3().abs() <= 1e-14 {
22 return Err("AffineTransform: singular matrix".into());
23 }
24 Ok(Self { elements })
25 }
26
27 pub fn determinant3(self) -> f64 {
28 let m = self.elements;
29 m[0] * (m[5] * m[10] - m[6] * m[9]) - m[1] * (m[4] * m[10] - m[6] * m[8])
30 + m[2] * (m[4] * m[9] - m[5] * m[8])
31 }
32
33 pub fn point(self, point: Vec3) -> Vec3 {
34 let m = self.elements;
35 Vec3::new(
36 m[0] * point.x + m[1] * point.y + m[2] * point.z + m[3],
37 m[4] * point.x + m[5] * point.y + m[6] * point.z + m[7],
38 m[8] * point.x + m[9] * point.y + m[10] * point.z + m[11],
39 )
40 }
41
42 fn homogeneous(self, point: Vec4) -> Result<Vec4, String> {
43 Ok(Vec4::from_point(self.point(point.point()?), point.w))
44 }
45}
46
47pub(crate) fn transform_curve(
48 curve: &NurbsCurve,
49 transform: AffineTransform,
50) -> Result<NurbsCurve, String> {
51 NurbsCurve::new(
52 curve.degree,
53 curve.knots.clone(),
54 curve
55 .control_points
56 .iter()
57 .map(|point| transform.homogeneous(*point))
58 .collect::<Result<_, _>>()?,
59 )
60}
61
62pub(crate) fn transform_surface(
63 surface: &NurbsSurface,
64 transform: AffineTransform,
65) -> Result<NurbsSurface, String> {
66 NurbsSurface::new(
67 surface.degree_u,
68 surface.degree_v,
69 surface.knots_u.clone(),
70 surface.knots_v.clone(),
71 surface
72 .control_points
73 .iter()
74 .map(|row| {
75 row.iter()
76 .map(|point| transform.homogeneous(*point))
77 .collect::<Result<Vec<_>, _>>()
78 })
79 .collect::<Result<_, _>>()?,
80 )
81}
82
83pub fn transform_brep(
86 solid: &BrepSolid,
87 transform: AffineTransform,
88 reverse_orientation: bool,
89) -> Result<BrepSolid, String> {
90 let determinant = transform.determinant3();
91 if determinant < 0.0 && !reverse_orientation {
92 return Err("transformSolid: reflection requires orientation reversal".into());
93 }
94 if determinant > 0.0 && reverse_orientation {
95 return Err("transformSolid: orientation reversal requires a reflection".into());
96 }
97 let mut result = solid.clone();
98 for vertex in &mut result.vertices {
99 vertex.point = transform.point(vertex.point);
100 }
101 for edge in &mut result.edges {
102 edge.curve = transform_curve(&edge.curve, transform)?;
103 }
104 for shell in &mut result.shells {
105 for face in &mut shell.faces {
106 face.surface = transform_surface(&face.surface, transform)?;
107 if reverse_orientation {
108 face.same_sense = !face.same_sense;
109 for loop_record in &mut face.loops {
110 loop_record.coedges.reverse();
111 for coedge in &mut loop_record.coedges {
112 coedge.forward = !coedge.forward;
113 coedge.pcurve = coedge.pcurve.reversed()?;
114 }
115 }
116 }
117 }
118 }
119 let issues = result.validate();
120 if issues.is_empty() {
121 Ok(result)
122 } else {
123 Err(format!("transformed BREP is invalid: {issues:?}"))
124 }
125}
126
127pub fn mirror_brep(
135 solid: &BrepSolid,
136 plane_point: Vec3,
137 plane_normal: Vec3,
138) -> Result<BrepSolid, String> {
139 let n = plane_normal.normalized()?;
140 let (nx, ny, nz) = (n.x, n.y, n.z);
141 let d = plane_point.dot(n);
142 let (tx, ty, tz) = (2.0 * d * nx, 2.0 * d * ny, 2.0 * d * nz);
143 let matrix = [
144 1.0 - 2.0 * nx * nx,
145 -2.0 * nx * ny,
146 -2.0 * nx * nz,
147 tx,
148 -2.0 * ny * nx,
149 1.0 - 2.0 * ny * ny,
150 -2.0 * ny * nz,
151 ty,
152 -2.0 * nz * nx,
153 -2.0 * nz * ny,
154 1.0 - 2.0 * nz * nz,
155 tz,
156 0.0,
157 0.0,
158 0.0,
159 1.0,
160 ];
161 let transform = AffineTransform::new(matrix)?;
162 transform_brep(solid, transform, true)
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::make_box_brep;
169 use crate::mass_properties::solid_mass_properties;
170
171 #[test]
172 fn affine_transform_preserves_exact_box_topology() {
173 let box_solid = make_box_brep(Vec3::default(), 2.0, 3.0, 4.0).unwrap();
174 let transform = AffineTransform::new([
175 2.0, 0.2, 0.0, 5.0, 0.0, 3.0, 0.0, -2.0, 0.0, 0.0, 0.5, 7.0, 0.0, 0.0, 0.0, 1.0,
176 ])
177 .unwrap();
178 let result = transform_brep(&box_solid, transform, false).unwrap();
179 assert!(result.validate().is_empty());
180 assert_eq!(result.vertices.len(), 8);
181 assert_eq!(result.edges.len(), 12);
182 assert_eq!(result.shells[0].faces.len(), 6);
183 assert!(
184 result.vertices[0]
185 .point
186 .sub(Vec3::new(5.0, -2.0, 7.0))
187 .length()
188 < 1e-12
189 );
190 }
191
192 #[test]
193 fn reflected_box_reverses_face_and_loop_orientation() {
194 let box_solid = make_box_brep(Vec3::default(), 2.0, 3.0, 4.0).unwrap();
195 let transform = AffineTransform::new([
196 -1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
197 ])
198 .unwrap();
199 let result = transform_brep(&box_solid, transform, true).unwrap();
200 assert!(result.validate().is_empty());
201 }
202
203 fn aabb(solid: &BrepSolid) -> (Vec3, Vec3) {
204 let mut min = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
205 let mut max = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
206 for vertex in &solid.vertices {
207 min = Vec3::new(
208 min.x.min(vertex.point.x),
209 min.y.min(vertex.point.y),
210 min.z.min(vertex.point.z),
211 );
212 max = Vec3::new(
213 max.x.max(vertex.point.x),
214 max.y.max(vertex.point.y),
215 max.z.max(vertex.point.z),
216 );
217 }
218 (min, max)
219 }
220
221 #[test]
222 fn mirror_brep_reflects_a_box() {
223 let box_solid = make_box_brep(Vec3::default(), 10.0, 10.0, 10.0).unwrap();
224 let (orig_min, orig_max) = aabb(&box_solid);
225 let original = solid_mass_properties(&box_solid).unwrap();
226
227 let mirrored = mirror_brep(&box_solid, Vec3::default(), Vec3::new(1.0, 0.0, 0.0)).unwrap();
228
229 assert!(mirrored.validate().is_empty());
231 assert_eq!(mirrored.vertices.len(), 8);
232 assert_eq!(mirrored.edges.len(), 12);
233 assert_eq!(mirrored.shells[0].faces.len(), 6);
234
235 let reflected = solid_mass_properties(&mirrored).unwrap();
237 assert!(reflected.volume > 0.0);
238 assert!((reflected.volume - original.volume).abs() <= 1e-6 * original.volume);
239
240 let (min, max) = aabb(&mirrored);
243 assert!((min.x - (-orig_max.x)).abs() < 1e-9);
244 assert!((max.x - (-orig_min.x)).abs() < 1e-9);
245 assert!((min.y - orig_min.y).abs() < 1e-9);
246 assert!((max.y - orig_max.y).abs() < 1e-9);
247 assert!((min.z - orig_min.z).abs() < 1e-9);
248 assert!((max.z - orig_max.z).abs() < 1e-9);
249 }
250
251 #[test]
252 fn mirror_brep_stays_valid_and_outward_oriented() {
253 let box_solid = make_box_brep(Vec3::new(1.0, 2.0, 3.0), 4.0, 5.0, 6.0).unwrap();
254 let mirrored = mirror_brep(
255 &box_solid,
256 Vec3::new(0.0, 0.0, 0.0),
257 Vec3::new(0.0, 1.0, 0.0),
258 )
259 .unwrap();
260 assert!(mirrored.validate().is_empty());
261 assert!(solid_mass_properties(&mirrored).unwrap().volume > 0.0);
262 }
263}