Skip to main content

brep_kernel/brep/
transform_topology.rs

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    /// Invert a rigid map as `[Rᵀ | −Rᵀ·t]`. The caller must verify rigidity.
28    pub(crate) fn rigid_inverse(&self) -> Result<Self, String> {
29        let m = &self.elements;
30        let r = [[m[0], m[1], m[2]], [m[4], m[5], m[6]], [m[8], m[9], m[10]]];
31        let t = [m[3], m[7], m[11]];
32        let mut out = [0.0f64; 16];
33        for row in 0..3 {
34            for col in 0..3 {
35                out[row * 4 + col] = r[col][row]; // Rᵀ
36            }
37            out[row * 4 + 3] = -(0..3).map(|k| r[k][row] * t[k]).sum::<f64>();
38        }
39        out[15] = 1.0;
40        AffineTransform::new(out)
41    }
42
43    pub fn determinant3(self) -> f64 {
44        let m = self.elements;
45        m[0] * (m[5] * m[10] - m[6] * m[9]) - m[1] * (m[4] * m[10] - m[6] * m[8])
46            + m[2] * (m[4] * m[9] - m[5] * m[8])
47    }
48
49    pub fn point(self, point: Vec3) -> Vec3 {
50        let m = self.elements;
51        Vec3::new(
52            m[0] * point.x + m[1] * point.y + m[2] * point.z + m[3],
53            m[4] * point.x + m[5] * point.y + m[6] * point.z + m[7],
54            m[8] * point.x + m[9] * point.y + m[10] * point.z + m[11],
55        )
56    }
57
58    fn homogeneous(self, point: Vec4) -> Result<Vec4, String> {
59        Ok(Vec4::from_point(self.point(point.point()?), point.w))
60    }
61}
62
63pub(crate) fn transform_curve(
64    curve: &NurbsCurve,
65    transform: AffineTransform,
66) -> Result<NurbsCurve, String> {
67    NurbsCurve::new(
68        curve.degree,
69        curve.knots.clone(),
70        curve
71            .control_points
72            .iter()
73            .map(|point| transform.homogeneous(*point))
74            .collect::<Result<_, _>>()?,
75    )
76}
77
78pub(crate) fn transform_surface(
79    surface: &NurbsSurface,
80    transform: AffineTransform,
81) -> Result<NurbsSurface, String> {
82    NurbsSurface::new(
83        surface.degree_u,
84        surface.degree_v,
85        surface.knots_u.clone(),
86        surface.knots_v.clone(),
87        surface
88            .control_points
89            .iter()
90            .map(|row| {
91                row.iter()
92                    .map(|point| transform.homogeneous(*point))
93                    .collect::<Result<Vec<_>, _>>()
94            })
95            .collect::<Result<_, _>>()?,
96    )
97}
98
99/// Transform exact BREP geometry without changing entity IDs or parameter
100/// ranges. `reverse_orientation` is required for negative-determinant maps.
101pub fn transform_brep(
102    solid: &BrepSolid,
103    transform: AffineTransform,
104    reverse_orientation: bool,
105) -> Result<BrepSolid, String> {
106    let determinant = transform.determinant3();
107    if determinant < 0.0 && !reverse_orientation {
108        return Err("transformSolid: reflection requires orientation reversal".into());
109    }
110    if determinant > 0.0 && reverse_orientation {
111        return Err("transformSolid: orientation reversal requires a reflection".into());
112    }
113    let mut result = solid.clone();
114    for vertex in &mut result.vertices {
115        vertex.point = transform.point(vertex.point);
116    }
117    for edge in &mut result.edges {
118        edge.curve = transform_curve(&edge.curve, transform)?;
119    }
120    for shell in &mut result.shells {
121        for face in &mut shell.faces {
122            face.surface = transform_surface(&face.surface, transform)?;
123            if reverse_orientation {
124                face.same_sense = !face.same_sense;
125                for loop_record in &mut face.loops {
126                    loop_record.coedges.reverse();
127                    for coedge in &mut loop_record.coedges {
128                        coedge.forward = !coedge.forward;
129                        coedge.pcurve = coedge.pcurve.reversed()?;
130                    }
131                }
132            }
133        }
134    }
135    let issues = result.validate();
136    if issues.is_empty() {
137        Ok(result)
138    } else {
139        Err(format!("transformed BREP is invalid: {issues:?}"))
140    }
141}
142
143/// Reflect an exact BREP across the plane through `plane_point` with unit
144/// normal `n = plane_normal.normalized()`. The reflection is the affine map
145/// `p' = R p + t` with linear part `R = I - 2 n nᵀ` and translation
146/// `t = 2 (plane_point·n) n`, so points on the plane map to themselves. A
147/// reflection has negative determinant (it flips handedness), so faces are
148/// re-oriented via the existing transform with `reverse_orientation = true`,
149/// keeping outward normals.
150pub fn mirror_brep(
151    solid: &BrepSolid,
152    plane_point: Vec3,
153    plane_normal: Vec3,
154) -> Result<BrepSolid, String> {
155    let n = plane_normal.normalized()?;
156    let (nx, ny, nz) = (n.x, n.y, n.z);
157    let d = plane_point.dot(n);
158    let (tx, ty, tz) = (2.0 * d * nx, 2.0 * d * ny, 2.0 * d * nz);
159    let matrix = [
160        1.0 - 2.0 * nx * nx,
161        -2.0 * nx * ny,
162        -2.0 * nx * nz,
163        tx,
164        -2.0 * ny * nx,
165        1.0 - 2.0 * ny * ny,
166        -2.0 * ny * nz,
167        ty,
168        -2.0 * nz * nx,
169        -2.0 * nz * ny,
170        1.0 - 2.0 * nz * nz,
171        tz,
172        0.0,
173        0.0,
174        0.0,
175        1.0,
176    ];
177    let transform = AffineTransform::new(matrix)?;
178    transform_brep(solid, transform, true)
179}
180
181// BREP private tests: 3908a150c7552e06