Skip to main content

gmac/core/
transformation.rs

1use std::f64::consts::PI;
2
3use crate::core::algebra::{mat3_mat3_mul, mat3_vec3_mul, mat4_vec4_mul};
4
5/// Transforms a list of 3D points using a 4x4 transformation matrix.
6///
7/// # Arguments
8/// * `nodes`: A mutable vector of 3D nodes represented as arrays of f64.
9/// * `transformation_matrix`: A 4x4 array representing the transformation matrix.
10/// * `origin`: A reference to an `[f64; 3]` specifying the origin of rotation.
11pub fn transform_nodes(
12    nodes: &mut [[f64; 3]],
13    transformation_matrix: &[[f64; 4]; 4],
14    origin: &[f64; 3],
15) {
16    for node in nodes.iter_mut() {
17        transform_node(node, transformation_matrix, origin)
18    }
19}
20
21/// Creates a new set of nodes by applying a transformation to a specified subset.
22///
23/// This function is non-mutating; it returns a new vector with the transformed points.
24///
25/// # Arguments
26/// * `original_nodes`: A slice of the initial node positions.
27/// * `node_ids`: A slice of indices specifying which nodes to transform.
28/// * `transform_matrix`: The 4x4 affine transformation matrix to apply.
29/// * `origin`: The point around which the transformation (e.g., rotation) should occur.
30///
31/// # Returns
32/// A new `Vec<[f64; 3]>` containing the full set of nodes with the specified subset transformed.
33pub fn transform_selected_nodes(
34    original_nodes: &[[f64; 3]],
35    node_ids: &[usize],
36    transform_matrix: &[[f64; 4]; 4],
37    origin: &[f64; 3],
38) -> Vec<[f64; 3]> {
39    let mut transformed_nodes = original_nodes.to_vec();
40
41    for &id in node_ids {
42        if id < transformed_nodes.len() {
43            transform_node(&mut transformed_nodes[id], transform_matrix, origin);
44        }
45    }
46
47    transformed_nodes
48}
49
50/// Transforms a node using a 4x4 transformation matrix.
51///
52/// # Arguments
53/// * `node`: A mutable 3D node.
54/// * `transformation_matrix`: A 4x4 array representing the transformation matrix.
55/// * `origin`: A reference to an `[f64; 3]` specifying the origin of rotation.
56pub fn transform_node(
57    node: &mut [f64; 3],
58    transformation_matrix: &[[f64; 4]; 4],
59    origin: &[f64; 3],
60) {
61    let new_node = [
62        node[0] - origin[0],
63        node[1] - origin[1],
64        node[2] - origin[2],
65        1.0,
66    ];
67
68    let product = mat4_vec4_mul(transformation_matrix, &new_node);
69
70    node[0] = product[0] + origin[0];
71    node[1] = product[1] + origin[1];
72    node[2] = product[2] + origin[2];
73}
74
75/// Creates a 4x4 transformation matrix from translation, rotation, and scaling vectors.
76///
77/// # Arguments
78/// * `translation`: An array representing the translation.
79/// * `rotation`: An array representing the rotation angles.
80/// * `scaling`: An array representing the scaling factors.
81///
82/// # Returns
83/// * Returns a 4x4 array representing the transformation matrix.
84pub fn build_transformation_matrix(
85    translation: [f64; 3],
86    rotation: [f64; 3],
87    scaling: [f64; 3],
88) -> [[f64; 4]; 4] {
89    let mut tmat: [[f64; 4]; 4] = [
90        [1.0, 0.0, 0.0, 0.0],
91        [0.0, 1.0, 0.0, 0.0],
92        [0.0, 0.0, 1.0, 0.0],
93        [0.0, 0.0, 0.0, 1.0],
94    ];
95
96    // Apply translation
97    tmat[0][3] = translation[0];
98    tmat[1][3] = translation[1];
99    tmat[2][3] = translation[2];
100
101    // Apply rotation
102    let rot_mat = build_rotation_matrix(&rotation);
103
104    for i in 0..3 {
105        for j in 0..3 {
106            tmat[i][j] = rot_mat[i][j];
107        }
108    }
109
110    // Apply scaling
111    for row in tmat.iter_mut().take(3) {
112        row[0] *= scaling[0];
113        row[1] *= scaling[1];
114        row[2] *= scaling[2];
115    }
116
117    tmat
118}
119
120/// Translates a list of nodes by the given position.
121///
122/// # Arguments
123/// * `nodes`: A mutable vector of 3D nodes represented as arrays of f64.
124/// * `translation`: An array of f64 that represents the translation.
125pub fn translate_nodes(nodes: &mut [[f64; 3]], translation: &[f64; 3]) {
126    for node in nodes.iter_mut() {
127        for j in 0..3 {
128            node[j] += translation[j];
129        }
130    }
131}
132
133/// Rotate a set of nodes around an origin by given angles.
134///
135/// # Arguments
136/// * `nodes`: A mutable reference to a `Vec<[f64; 3]>` containing nodes to rotate.
137/// * `theta`: A reference to an `[f64; 3]` specifying rotation angles for each axis in radians.
138/// * `origin`: A reference to an `[f64; 3]` specifying the origin of rotation.
139pub fn rotate_nodes(nodes: &mut [[f64; 3]], theta: &[f64; 3], origin: &[f64; 3]) {
140    let rotation_matrix = build_rotation_matrix(theta);
141
142    nodes
143        .iter_mut()
144        .for_each(|node| rotate_node(node, &rotation_matrix, origin));
145}
146
147/// Rotate a node in 3D space using a given rotation matrix and an origin point.
148///
149/// # Arguments
150/// * `node`: Mutable reference to the node to be rotated.
151///             This node will be modified in-place.
152/// * `rotation_matrix`: Reference to a 3x3 rotation matrix.
153/// * `origin`: Reference to the origin point around which the node will be rotated.
154///
155/// # Examples
156/// ```
157/// let mut node = [1.0, 0.0, 0.0];
158/// let rotation_matrix = [[0.0, -1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]];
159/// let origin = [0.0, 0.0, 0.0];
160///
161/// rotate_node(&mut node, &rotation_matrix, &origin);
162/// ```
163pub fn rotate_node(
164    node: &mut [f64; 3],
165    rotation_matrix: &[[f64; 3]; 3],
166    origin: &[f64; 3],
167) {
168    node[0] -= origin[0];
169    node[1] -= origin[1];
170    node[2] -= origin[2];
171
172    let rotated_node = mat3_vec3_mul(rotation_matrix, node);
173
174    node[0] = rotated_node[0] + origin[0];
175    node[1] = rotated_node[1] + origin[1];
176    node[2] = rotated_node[2] + origin[2];
177}
178
179/// Computes the rotation matrix from Euler angles.
180///
181/// # Arguments
182/// * `theta`: A list of Euler angles [alpha, beta, gamma] in degrees.
183///
184/// # Returns
185/// * A 3x3 rotation matrix represented as [[f64; 3]; 3].
186///
187/// # Example
188/// ```
189/// let angles = [45.0, 30.0, 60.0];
190/// let r_matrix = build_rotation_matrix(angles);
191/// ```
192pub fn build_rotation_matrix(theta: &[f64; 3]) -> [[f64; 3]; 3] {
193    let (alpha, beta, gamma) = (
194        theta[0] * PI / 180.0,
195        theta[1] * PI / 180.0,
196        theta[2] * PI / 180.0,
197    );
198
199    let mut rotation_matrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
200
201    if gamma != 0.0 {
202        let (c, s) = (gamma.cos(), gamma.sin());
203        let r = [[c, -s, 0.0], [s, c, 0.0], [0.0, 0.0, 1.0]];
204        rotation_matrix = mat3_mat3_mul(&r, &rotation_matrix);
205    }
206    if beta != 0.0 {
207        let (c, s) = (beta.cos(), beta.sin());
208        let r = [[c, 0.0, s], [0.0, 1.0, 0.0], [-s, 0.0, c]];
209        rotation_matrix = mat3_mat3_mul(&r, &rotation_matrix);
210    }
211    if alpha != 0.0 {
212        let (c, s) = (alpha.cos(), alpha.sin());
213        let r = [[1.0, 0.0, 0.0], [0.0, c, -s], [0.0, s, c]];
214        rotation_matrix = mat3_mat3_mul(&r, &rotation_matrix);
215    }
216    rotation_matrix
217}
218
219/// Scales a list of nodes by the given scaling factor, relative to a given origin.
220///
221/// # Arguments
222/// * `nodes`: A mutable vector of 3D nodes represented as arrays of f64.
223/// * `scaling`: An array of f64 that represents the scaling factors for each dimension.
224/// * `origin`: The point relative to which the scaling should be performed.
225pub fn scale_nodes(nodes: &mut [[f64; 3]], scaling: &[f64; 3], origin: &[f64; 3]) {
226    for node in nodes.iter_mut() {
227        scale_node(node, scaling, origin);
228    }
229}
230
231/// Scales a node by the given scaling factor, relative to a given origin.
232///
233/// # Arguments
234/// * `node`: A mutable 3D node.
235/// * `scaling`: An array of f64 that represents the scaling factors for each dimension.
236/// * `origin`: The point relative to which the scaling should be performed.
237pub fn scale_node(node: &mut [f64; 3], scaling: &[f64; 3], origin: &[f64; 3]) {
238    for j in 0..3 {
239        node[j] -= origin[j];
240        node[j] *= scaling[j];
241        node[j] += origin[j];
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_transform_nodes() {
251        let transformation_matrix =
252            build_transformation_matrix([1.3, 15., 2.5], [25., 25., 25.], [2., 5., 2.]);
253
254        let mut nodes = vec![[1.4, 1.2, 3.5], [34., 23., 53.]];
255
256        transform_nodes(&mut nodes, &transformation_matrix, &[0., 0., 0.]);
257
258        let expected_nodes = vec![
259            [4.260097156389118, 18.32001817946411, 11.047239560979023],
260            [57.90475899451524, 97.23229418127102, 140.77057189748564],
261        ];
262
263        let mut equal = true;
264        'outer: for i in 0..2 {
265            for j in 0..3 {
266                if (nodes[i][j] - expected_nodes[i][j]).abs() > 1e-5 {
267                    equal = false;
268                    break 'outer;
269                }
270            }
271        }
272
273        assert!(equal);
274    }
275
276    #[test]
277    fn test_rotation_matrix() {
278        let theta: [f64; 3] = [45.0, 45.0, 45.0];
279        let rotation_matrix = build_rotation_matrix(&theta);
280
281        let expected_matrix = [
282            [0.5, -0.5, 0.70710678],
283            [0.85355339, 0.14644661, -0.5],
284            [0.14644661, 0.85355339, 0.5],
285        ];
286
287        let mut equal = true;
288        'outer: for i in 0..3 {
289            for j in 0..3 {
290                if (rotation_matrix[i][j] - expected_matrix[i][j]).abs() > 1e-5 {
291                    equal = false;
292                    break 'outer;
293                }
294            }
295        }
296
297        assert!(equal);
298    }
299
300    #[test]
301    fn test_rotate_node() {
302        let mut node: [f64; 3] = [1.0, 0.0, 0.0];
303        let origin: [f64; 3] = [0.0, 0.0, 0.0];
304        let rotation_matrix = [
305            [0.5, -0.5, 0.70710678],
306            [0.85355339, 0.14644661, -0.5],
307            [0.14644661, 0.85355339, 0.5],
308        ];
309
310        rotate_node(&mut node, &rotation_matrix, &origin);
311        let expected_node: [f64; 3] = [0.5, 0.85355339, 0.14644661];
312
313        assert!((node[0] - expected_node[0]).abs() < 1e-5);
314        assert!((node[1] - expected_node[1]).abs() < 1e-5);
315        assert!((node[2] - expected_node[2]).abs() < 1e-5);
316    }
317
318    #[test]
319    fn test_rotate_nodes() {
320        let mut nodes = vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
321        let theta = [0.0, 0.0, 90.0];
322        let origin = [0.0, 0.0, 0.0];
323
324        rotate_nodes(&mut nodes, &theta, &origin);
325
326        let expected_nodes = vec![[0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]];
327
328        let mut equal = true;
329        'outer: for i in 0..nodes.len() {
330            for j in 0..3 {
331                if (nodes[i][j] - expected_nodes[i][j]).abs() > 1e-5 {
332                    equal = false;
333                    break 'outer;
334                }
335            }
336        }
337
338        assert!(equal);
339    }
340
341    #[test]
342    fn test_translate_nodes() {
343        let mut nodes = vec![[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]];
344        let translation = [1.0, 1.0, 1.0];
345        translate_nodes(&mut nodes, &translation);
346        assert_eq!(nodes, vec![[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]]);
347    }
348
349    #[test]
350    fn test_scale_nodes() {
351        let mut nodes = vec![[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]];
352        let origin = [1.0, 1.0, 1.0];
353        let scaling = [2.0, 2.0, 2.0];
354        scale_nodes(&mut nodes, &scaling, &origin);
355        assert_eq!(nodes, vec![[1.0, 1.0, 1.0], [3.0, 3.0, 3.0]]);
356    }
357}