Skip to main content

gmac/morph/
ffd.rs

1use crate::{
2    core::transformation::rotate_nodes,
3    morph::{
4        linear_algebra::least_squares_solver,
5        transforms::{apply_affine_transform, apply_bernstein_transform},
6        design_block::DesignBlock,
7    },
8    error::{Error, Result},
9};
10
11const REFERENCE_COORDINATE_SYSTEM: [[f64; 3]; 4] = [
12    [0.0, 0.0, 0.0],
13    [1.0, 0.0, 0.0],
14    [0.0, 1.0, 0.0],
15    [0.0, 0.0, 1.0],
16];
17
18/// Free-form deformer
19pub struct FreeFormDeformer {
20    /// Input control node block.
21    pub original_design_block: DesignBlock,
22    /// Affine weights pre transformation
23    pub affine_weights_pre: [[f64; 4]; 4],
24    /// Affine weights post transformation
25    pub affine_weights_post: [[f64; 4]; 4],
26}
27
28impl FreeFormDeformer {
29    /// Instantiates a new `FreeFormDeformer` instance.
30    ///
31    /// # Arguments
32    /// * `original_design_block`: A `DesignBlock` containing the original node positions.
33    ///
34    /// # Returns
35    /// A new `FreeFormDeformer` instance or an error string if creation fails.
36    pub fn new(original_design_block: DesignBlock) -> Result<Self> {
37        // Evaluate affine weights
38        let affine_weights_pre = evaluate_affine_weights(
39            &original_design_block.local_coordinate_system,
40            &REFERENCE_COORDINATE_SYSTEM,
41        )?;
42
43        let affine_weights_post = evaluate_affine_weights(
44            &REFERENCE_COORDINATE_SYSTEM,
45            &original_design_block.local_coordinate_system,
46        )?;
47
48        Ok(FreeFormDeformer {
49            original_design_block,
50            affine_weights_pre,
51            affine_weights_post,
52        })
53    }
54
55    /// Deform points based on deltas of the control points.
56    ///
57    /// # Arguments
58    /// * `points`: New input mesh nodes to predict the deformed positions for.
59    /// * `deformed_design_nodes`: New nodal positions of design box.
60    ///
61    /// # Returns
62    /// New points or an error string if deformation fails.
63    pub fn deform(
64        &self,
65        points: &[[f64; 3]],
66        deformed_design_nodes: &[[f64; 3]],
67    ) -> Result<Vec<[f64; 3]>> {
68        if self.original_design_block.nodes.len() != deformed_design_nodes.len() {
69            return Err(Error::Deformation(
70                "The number of original and deformed design points must match!"
71                    .to_string(),
72            ));
73        }
74
75        // Apply affine transformation to reference
76        let corner_point = self.original_design_block.corner_nodes[0];
77        let scaled_points = apply_affine_transform(
78            &points
79                .iter()
80                .map(|pt| {
81                    [
82                        pt[0] - corner_point[0],
83                        pt[1] - corner_point[1],
84                        pt[2] - corner_point[2],
85                    ]
86                })
87                .collect::<Vec<[f64; 3]>>(),
88            &self.affine_weights_pre,
89        )?;
90
91        let point_ids_in_unit_cube = indices_inside_unit_cube(&scaled_points);
92
93        let scaled_points_in_cube = point_ids_in_unit_cube
94            .iter()
95            .map(|&index| scaled_points[index])
96            .collect::<Vec<[f64; 3]>>();
97
98        // Evaluate deltas
99        let deltas = evaluate_deltas(
100            &self.original_design_block.nodes,
101            deformed_design_nodes,
102            self.original_design_block.theta,
103            self.original_design_block.centre,
104            self.original_design_block.scaling_factors,
105        );
106
107        // Apply Bernstein transform
108        let shifted_points = apply_bernstein_transform(
109            &scaled_points_in_cube,
110            &deltas,
111            &self.original_design_block.resolution,
112        )?;
113
114        // Reverse affine transform
115        let reverse_scaled_points =
116            apply_affine_transform(&shifted_points, &self.affine_weights_post)?;
117
118        let mut new_points = points.to_vec();
119
120        point_ids_in_unit_cube
121            .iter()
122            .zip(reverse_scaled_points)
123            .for_each(|(&id, pt)| {
124                new_points[id] = [
125                    pt[0] + corner_point[0],
126                    pt[1] + corner_point[1],
127                    pt[2] + corner_point[2],
128                ]
129            });
130
131        Ok(new_points)
132    }
133}
134
135/// Evaluates the affine transformation weights between two coordinate systems.
136///
137/// # Arguments
138/// * `source_coordinate_system`: A 4x3 array representing the source coordinate system.
139///                               Each row is a point in the source space.
140/// * `target_coordinate_system`: A 4x3 array representing the target coordinate system.
141///                               Each row is a point in the target space.
142///
143/// # Returns
144/// Returns a 4x4 array representing the affine transformation weights.
145/// Otherwise, returns an error message as a `String`.
146///
147/// # Example
148/// ```
149/// let source = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 0.0]];
150/// let target = [[1.5, 0.0, 0.0], [0.0, 1.5, 0.0], [0.0, 0.0, 1.5], [0.0, 0.0, 0.0]];
151/// let result = evaluate_affine_weights(&source, &target);
152/// // ... (handle the Result)
153/// ```
154fn evaluate_affine_weights(
155    source_coordinate_system: &[[f64; 3]; 4],
156    target_coordinate_system: &[[f64; 3]; 4],
157) -> Result<[[f64; 4]; 4]> {
158    if source_coordinate_system.len() != target_coordinate_system.len() {
159        return Err(Error::Deformation(
160            "source_coordinate_system and target_coordinate_system must be of the same size!".to_string(),
161        ));
162    }
163
164    let padded_source_coordinate_system = source_coordinate_system
165        .iter()
166        .map(|&[x1, x2, x3]| vec![x1, x2, x3, 1.0])
167        .collect::<Vec<Vec<f64>>>();
168
169    let padded_target_coordinate_system = target_coordinate_system
170        .iter()
171        .map(|&[x1, x2, x3]| vec![x1, x2, x3, 1.0])
172        .collect::<Vec<Vec<f64>>>();
173
174    let a = least_squares_solver(
175        &padded_source_coordinate_system,
176        &padded_target_coordinate_system,
177    )?;
178
179    let mut arr = [[0.0; 4]; 4];
180    for (i, row) in a.iter().enumerate() {
181        for (j, &value) in row.iter().enumerate() {
182            arr[i][j] = value;
183        }
184    }
185
186    Ok(arr)
187}
188
189/// Find the indices of points that are inside the unit cube `[0, 1]^3`.
190///
191/// # Arguments
192/// * `points`: A vector of points in 3D space, represented as arrays `[x, y, z]`.
193///
194/// # Returns
195/// * `Vec<usize>`: A vector of indices of points that are inside the unit cube.
196fn indices_inside_unit_cube(points: &[[f64; 3]]) -> Vec<usize> {
197    let mut indx_points_inside = Vec::new();
198
199    for (index, &point) in points.iter().enumerate() {
200        if point.iter().all(|&coord| (0.0..=1.0).contains(&coord)) {
201            indx_points_inside.push(index);
202        }
203    }
204
205    indx_points_inside
206}
207
208/// Evaluate the deltas between original and deformed design nodes.
209///
210/// # Arguments
211/// * `original_nodes`: A vector of original node coordinates. Each node is represented as [f64; 3].
212/// * `deformed_nodes`: A vector of deformed node coordinates. Each node is represented as [f64; 3].
213/// * `theta`: The rotation vector as [f64; 3].
214/// * `centre`: The centre of rotation as [f64; 3].
215/// * `scaling_factors`: The scaling factors as [f64; 3].
216///
217/// # Returns
218/// A vector of deltas. Each delta is represented as [f64; 3].
219pub fn evaluate_deltas(
220    original_nodes: &[[f64; 3]],
221    deformed_nodes: &[[f64; 3]],
222    theta: [f64; 3],
223    centre: [f64; 3],
224    scaling_factors: [f64; 3],
225) -> Vec<[f64; 3]> {
226    let mut original = original_nodes.to_vec();
227    let mut deformed = deformed_nodes.to_vec();
228    let neg_theta = [-theta[0], -theta[1], -theta[2]];
229
230    rotate_nodes(&mut original, &neg_theta, &centre);
231    rotate_nodes(&mut deformed, &neg_theta, &centre);
232
233    original
234        .iter()
235        .zip(deformed.iter())
236        .map(|(&ori, &def)| {
237            [
238                scaling_factors[0] * (def[0] - ori[0]),
239                scaling_factors[1] * (def[1] - ori[1]),
240                scaling_factors[2] * (def[2] - ori[2]),
241            ]
242        })
243        .collect()
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249
250    #[test]
251    fn test_evaluate_deltas() {
252        let original_nodes = &[[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]];
253
254        let deformed_nodes = &[[2.0, 2.0, 2.0], [3.0, 3.0, 3.0]];
255
256        let theta = [0.0, 0.0, 0.0];
257        let centre = [0.0, 0.0, 0.0];
258        let scaling_factors = [1.0, 1.0, 1.0];
259
260        let deltas = evaluate_deltas(
261            original_nodes,
262            deformed_nodes,
263            theta,
264            centre,
265            scaling_factors,
266        );
267
268        assert_eq!(deltas, vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]);
269    }
270}