Skip to main content

gmac/morph/
design_block.rs

1use crate::core::{
2    clusters::generate_block_cluster,
3    mesh::Mesh,
4    selection::select_nodes_in_box,
5    transformation::{
6        build_rotation_matrix, rotate_node, rotate_nodes, transform_selected_nodes,
7    },
8};
9
10use crate::error::{Error, Result};
11
12/// `DesignBlock` represents a geometric block in 3D space.
13///
14/// # Arguments
15/// * `nodes`: A vector of nodes (or points) inside the block, each represented by a `[f64; 3]` array.
16/// * `length`: The lengths of the block in the x, y, and z directions as a `[f64; 3]` array.
17/// * `centre`: The coordinates of the center of the block as a `[f64; 3]` array.
18/// * `theta`: The angles (in radians) to rotate the block around the x, y, and z axes as a `[f64; 3]` array.
19/// * `resolution`: The resolution of the block in each dimension, represented as a `[usize; 3]` array.
20/// * `corner_nodes`: The 8 corner in x, y, and z directions as a `[[f64; 3]; 8]` array.
21/// * `scaling_factors`: The scaling factors in x, y, and z directions as a `[f64; 3]` array.
22/// * `local_coordinate_system`: A 4x3 array defining the local coordinate system of the block.
23#[derive(Debug, Clone)]
24pub struct DesignBlock {
25    pub nodes: Vec<[f64; 3]>,
26    pub length: [f64; 3],
27    pub centre: [f64; 3],
28    pub theta: [f64; 3],
29    pub resolution: [usize; 3],
30    pub corner_nodes: [[f64; 3]; 8],
31    pub scaling_factors: [f64; 3],
32    pub local_coordinate_system: [[f64; 3]; 4],
33}
34
35impl DesignBlock {
36    /// Creates a new `DesignBlock` instance.
37    ///
38    /// # Arguments
39    /// * `length`: The lengths of the block in the x, y, and z directions as a `[f64; 3]` array.
40    /// * `centre`: The coordinates of the center of the block as a `[f64; 3]` array.
41    /// * `theta`: The angles (in radians) to rotate the block around the x, y, and z axes as a `[f64; 3]` array.
42    /// * `resolution`: The resolution of the block in each dimension, represented as a `[usize; 3]` array.
43    ///
44    /// # Returns
45    /// A new instance of `DesignBlock`.
46    pub fn new(
47        length: [f64; 3],
48        centre: [f64; 3],
49        theta: [f64; 3],
50        resolution: [usize; 3],
51    ) -> Result<Self> {
52        let nodes = generate_block_cluster(length, centre, theta, resolution)?;
53
54        // Evaluate scaling factors
55        let scaling_factors = [1. / length[0], 1. / length[1], 1. / length[2]];
56
57        // Evaluate corner points
58        let [cx, cy, cz] = centre;
59        let [lx, ly, lz] = length;
60
61        let half_lx = lx / 2.0;
62        let half_ly = ly / 2.0;
63        let half_lz = lz / 2.0;
64
65        let mut corner_nodes = [
66            [cx - half_lx, cy - half_ly, cz - half_lz], // Corner 0
67            [cx + half_lx, cy - half_ly, cz - half_lz], // Corner 1
68            [cx - half_lx, cy + half_ly, cz - half_lz], // Corner 2
69            [cx + half_lx, cy + half_ly, cz - half_lz], // Corner 3
70            [cx - half_lx, cy - half_ly, cz + half_lz], // Corner 4
71            [cx + half_lx, cy - half_ly, cz + half_lz], // Corner 5
72            [cx - half_lx, cy + half_ly, cz + half_lz], // Corner 6
73            [cx + half_lx, cy + half_ly, cz + half_lz], // Corner 7
74        ];
75
76        rotate_nodes(&mut corner_nodes, &theta, &centre);
77
78        let local_coordinate_system = [
79            corner_nodes[0],
80            corner_nodes[1],
81            corner_nodes[2],
82            corner_nodes[4],
83        ];
84
85        let updated_coordinate_system: [[f64; 3]; 4] = {
86            let mut result = [[0.0; 3]; 4];
87            for (i, &node) in local_coordinate_system.iter().enumerate() {
88                result[i] = [
89                    node[0] - corner_nodes[0][0],
90                    node[1] - corner_nodes[0][1],
91                    node[2] - corner_nodes[0][2],
92                ];
93            }
94            result
95        };
96
97        Ok(DesignBlock {
98            nodes,
99            length,
100            centre,
101            theta,
102            resolution,
103            corner_nodes,
104            scaling_factors,
105            local_coordinate_system: updated_coordinate_system,
106        })
107    }
108
109    /// Selects free deformable control nodes by checking intersection with a mesh body.
110    ///
111    /// # Arguments
112    /// * `target_mesh`: Mesh to check intersections with.
113    /// * `fixed_layers`: How many layers to exclude from the intersection, for instance quadratic=2, linear=1.
114    ///
115    /// # Returns
116    /// Node ids corresponding to free deformable `DesignBlock` nodes.
117    pub fn select_free_design_nodes(
118        &self,
119        target_mesh: &Mesh,
120        fixed_layers: Option<usize>,
121    ) -> Result<Vec<usize>> {
122        let fixed_layers = fixed_layers.unwrap_or(2);
123
124        if self.resolution.iter().any(|&res| res < fixed_layers) {
125            return Err(Error::Deformation(
126                "Block resultion must be at least the size of the fixed layers!"
127                    .to_string(),
128            ));
129        }
130
131        // Get sides
132        let corner = self.corner_nodes;
133
134        let sides = [
135            [corner[0], corner[1], corner[3], corner[2]], // Side 0 (Front)
136            [corner[4], corner[5], corner[7], corner[6]], // Side 1 (Back)
137            [corner[0], corner[1], corner[5], corner[4]], // Side 2 (Bottom)
138            [corner[2], corner[3], corner[7], corner[6]], // Side 3 (Top)
139            [corner[0], corner[2], corner[6], corner[4]], // Side 4 (Left)
140            [corner[1], corner[3], corner[7], corner[5]], // Side 5 (Right)
141        ];
142
143        let mut fixed_sides: Vec<usize> = Vec::new();
144        for (i, side) in sides.iter().enumerate() {
145            let a = [
146                side[1][0] - side[0][0],
147                side[1][1] - side[0][1],
148                side[1][2] - side[0][2],
149            ];
150            let b = [
151                side[2][0] - side[0][0],
152                side[2][1] - side[0][1],
153                side[2][2] - side[0][2],
154            ];
155
156            let normal = [
157                a[1] * b[2] - a[2] * b[1],
158                a[2] * b[0] - a[0] * b[2],
159                a[0] * b[1] - a[1] * b[0],
160            ];
161
162            let mut centre = [0.0; 3];
163            for point in side.iter() {
164                centre[0] += point[0];
165                centre[1] += point[1];
166                centre[2] += point[2];
167            }
168            centre[0] /= 4.0;
169            centre[1] /= 4.0;
170            centre[2] /= 4.0;
171
172            // Intersection found
173            if target_mesh.slice(centre, normal).is_some() {
174                fixed_sides.push(i)
175            }
176        }
177
178        // Extract valid block nodes
179        let mut new_length = self.length;
180        let mut new_centre = self.centre;
181
182        let step = [
183            self.length[0] / (self.resolution[0] as f64),
184            self.length[1] / (self.resolution[1] as f64),
185            self.length[2] / (self.resolution[2] as f64),
186        ];
187
188        let correction_factor = 0.01;
189        let correction = [
190            step[0] * (fixed_layers - 1) as f64 + correction_factor * step[0],
191            step[1] * (fixed_layers - 1) as f64 + correction_factor * step[1],
192            step[2] * (fixed_layers - 1) as f64 + correction_factor * step[2],
193        ];
194
195        // Left check
196        if fixed_sides.contains(&4) {
197            new_length[0] -= correction[0];
198            new_centre[0] += 0.5 * correction[0];
199        }
200        // Right check
201        if fixed_sides.contains(&5) {
202            new_length[0] -= correction[0];
203            new_centre[0] -= 0.5 * correction[0];
204        }
205
206        // Bottom check
207        if fixed_sides.contains(&2) {
208            new_length[1] -= correction[1];
209            new_centre[1] += 0.5 * correction[1];
210        }
211        // Top check
212        if fixed_sides.contains(&3) {
213            new_length[1] -= correction[1];
214            new_centre[1] -= 0.5 * correction[1];
215        }
216        // Front check
217        if fixed_sides.contains(&0) {
218            new_length[2] -= correction[2];
219            new_centre[2] += 0.5 * correction[2];
220        }
221        // Back check
222        if fixed_sides.contains(&1) {
223            new_length[2] -= correction[2];
224            new_centre[2] -= 0.5 * correction[2];
225        }
226
227        new_length[0] += 0.1 * correction_factor * step[0];
228        new_length[1] += 0.1 * correction_factor * step[1];
229        new_length[2] += 0.1 * correction_factor * step[2];
230
231        let rotation_matrix = build_rotation_matrix(&self.theta);
232        rotate_node(&mut new_centre, &rotation_matrix, &self.centre);
233
234        let free_node_ids =
235            select_nodes_in_box(&self.nodes, new_length, new_centre, self.theta);
236
237        Ok(free_node_ids)
238    }
239
240    /// Selects the node ids corresponding to the target mesh that fit inside the design block.
241    ///
242    /// # Arguments
243    /// * `target_mesh_nodes`: Mesh nodes to check.
244    ///
245    /// # Returns
246    /// Node ids corresponding to free deformable `Mesh` nodes.
247    pub fn select_mesh_nodes_inside(
248        &self,
249        target_mesh_nodes: &[[f64; 3]],
250    ) -> Result<Vec<usize>> {
251        Ok(select_nodes_in_box(
252            target_mesh_nodes,
253            self.length,
254            self.centre,
255            self.theta,
256        ))
257    }
258
259    /// Creates a new set of nodes by applying a transformation to a subset of the original nodes.
260    ///
261    /// # Arguments
262    /// * `node_ids`: A slice of indices corresponding to the control points to be transformed.
263    /// * `transform_matrix`: The 4x4 affine transformation matrix to apply.
264    /// * `pivot`: The point around which the transformation (like rotation) should occur.
265    ///
266    /// # Returns
267    /// A new `Vec<[f64; 3]>` containing the full set of nodes with the specified subset transformed.
268    pub fn transform_subset(
269        &self,
270        node_ids: &[usize],
271        transform_matrix: &[[f64; 4]; 4],
272        pivot: &[f64; 3],
273    ) -> Vec<[f64; 3]> {
274        transform_selected_nodes(&self.nodes, node_ids, transform_matrix, pivot)
275    }
276}
277
278/// Creates a local coordinate system in 3D space given lengths, centre, and rotations.
279/// The local coordinate system is composed of four points:
280/// - Origin node (centre)
281/// - X-axis node
282/// - Y-axis node
283/// - Z-axis node
284///
285/// These nodes represent the coordinate system after applying the specified
286/// lengths and rotations around the centre.
287///
288/// # Arguments
289/// * `length`: An array `[x_length, y_length, z_length]` that specifies the length of each axis.
290/// * `centre`: An array `[x, y, z]` specifying the centre point of the orientation frame.
291/// * `theta`: An array `[theta_x, theta_y, theta_z]` specifying rotations in radians for each axis.
292///
293/// # Returns
294/// Returns a `Vec<[f64; 3]>` containing four nodes, each represented as `[x, y, z]`.
295pub fn evaluate_lcs_from_components(
296    length: [f64; 3],
297    centre: [f64; 3],
298    theta: [f64; 3],
299) -> [[f64; 3]; 4] {
300    let origin = [
301        0.5 * length[0] - centre[0],
302        0.5 * length[1] - centre[1],
303        0.5 * length[2] - centre[2],
304    ];
305    let mut local_coordinate_system = [
306        [centre[0], centre[1], centre[2]],
307        [centre[0] + length[0], centre[1], centre[2]],
308        [centre[0], centre[1] + length[1], centre[2]],
309        [centre[0], centre[1], centre[2] + length[2]],
310    ];
311
312    rotate_nodes(&mut local_coordinate_system, &theta, &origin);
313
314    local_coordinate_system
315}