use crate::{
core::transformation::rotate_nodes,
morph::{
linear_algebra::least_squares_solver,
transforms::{apply_affine_transform, apply_bernstein_transform},
design_block::DesignBlock,
},
error::{Error, Result},
};
const REFERENCE_COORDINATE_SYSTEM: [[f64; 3]; 4] = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
pub struct FreeFormDeformer {
pub original_design_block: DesignBlock,
pub affine_weights_pre: [[f64; 4]; 4],
pub affine_weights_post: [[f64; 4]; 4],
}
impl FreeFormDeformer {
pub fn new(original_design_block: DesignBlock) -> Result<Self> {
let affine_weights_pre = evaluate_affine_weights(
&original_design_block.local_coordinate_system,
&REFERENCE_COORDINATE_SYSTEM,
)?;
let affine_weights_post = evaluate_affine_weights(
&REFERENCE_COORDINATE_SYSTEM,
&original_design_block.local_coordinate_system,
)?;
Ok(FreeFormDeformer {
original_design_block,
affine_weights_pre,
affine_weights_post,
})
}
pub fn deform(
&self,
points: &[[f64; 3]],
deformed_design_nodes: &[[f64; 3]],
) -> Result<Vec<[f64; 3]>> {
if self.original_design_block.nodes.len() != deformed_design_nodes.len() {
return Err(Error::Deformation(
"The number of original and deformed design points must match!"
.to_string(),
));
}
let corner_point = self.original_design_block.corner_nodes[0];
let scaled_points = apply_affine_transform(
&points
.iter()
.map(|pt| {
[
pt[0] - corner_point[0],
pt[1] - corner_point[1],
pt[2] - corner_point[2],
]
})
.collect::<Vec<[f64; 3]>>(),
&self.affine_weights_pre,
)?;
let point_ids_in_unit_cube = indices_inside_unit_cube(&scaled_points);
let scaled_points_in_cube = point_ids_in_unit_cube
.iter()
.map(|&index| scaled_points[index])
.collect::<Vec<[f64; 3]>>();
let deltas = evaluate_deltas(
&self.original_design_block.nodes,
deformed_design_nodes,
self.original_design_block.theta,
self.original_design_block.centre,
self.original_design_block.scaling_factors,
);
let shifted_points = apply_bernstein_transform(
&scaled_points_in_cube,
&deltas,
&self.original_design_block.resolution,
)?;
let reverse_scaled_points =
apply_affine_transform(&shifted_points, &self.affine_weights_post)?;
let mut new_points = points.to_vec();
point_ids_in_unit_cube
.iter()
.zip(reverse_scaled_points)
.for_each(|(&id, pt)| {
new_points[id] = [
pt[0] + corner_point[0],
pt[1] + corner_point[1],
pt[2] + corner_point[2],
]
});
Ok(new_points)
}
}
fn evaluate_affine_weights(
source_coordinate_system: &[[f64; 3]; 4],
target_coordinate_system: &[[f64; 3]; 4],
) -> Result<[[f64; 4]; 4]> {
if source_coordinate_system.len() != target_coordinate_system.len() {
return Err(Error::Deformation(
"source_coordinate_system and target_coordinate_system must be of the same size!".to_string(),
));
}
let padded_source_coordinate_system = source_coordinate_system
.iter()
.map(|&[x1, x2, x3]| vec![x1, x2, x3, 1.0])
.collect::<Vec<Vec<f64>>>();
let padded_target_coordinate_system = target_coordinate_system
.iter()
.map(|&[x1, x2, x3]| vec![x1, x2, x3, 1.0])
.collect::<Vec<Vec<f64>>>();
let a = least_squares_solver(
&padded_source_coordinate_system,
&padded_target_coordinate_system,
)?;
let mut arr = [[0.0; 4]; 4];
for (i, row) in a.iter().enumerate() {
for (j, &value) in row.iter().enumerate() {
arr[i][j] = value;
}
}
Ok(arr)
}
fn indices_inside_unit_cube(points: &[[f64; 3]]) -> Vec<usize> {
let mut indx_points_inside = Vec::new();
for (index, &point) in points.iter().enumerate() {
if point.iter().all(|&coord| (0.0..=1.0).contains(&coord)) {
indx_points_inside.push(index);
}
}
indx_points_inside
}
pub fn evaluate_deltas(
original_nodes: &[[f64; 3]],
deformed_nodes: &[[f64; 3]],
theta: [f64; 3],
centre: [f64; 3],
scaling_factors: [f64; 3],
) -> Vec<[f64; 3]> {
let mut original = original_nodes.to_vec();
let mut deformed = deformed_nodes.to_vec();
let neg_theta = [-theta[0], -theta[1], -theta[2]];
rotate_nodes(&mut original, &neg_theta, ¢re);
rotate_nodes(&mut deformed, &neg_theta, ¢re);
original
.iter()
.zip(deformed.iter())
.map(|(&ori, &def)| {
[
scaling_factors[0] * (def[0] - ori[0]),
scaling_factors[1] * (def[1] - ori[1]),
scaling_factors[2] * (def[2] - ori[2]),
]
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_evaluate_deltas() {
let original_nodes = &[[1.0, 1.0, 1.0], [2.0, 2.0, 2.0]];
let deformed_nodes = &[[2.0, 2.0, 2.0], [3.0, 3.0, 3.0]];
let theta = [0.0, 0.0, 0.0];
let centre = [0.0, 0.0, 0.0];
let scaling_factors = [1.0, 1.0, 1.0];
let deltas = evaluate_deltas(
original_nodes,
deformed_nodes,
theta,
centre,
scaling_factors,
);
assert_eq!(deltas, vec![[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]);
}
}