pub(crate) fn centroid(points: &[[f32; 3]]) -> [f32; 3] {
let mut sum = [0.0f32; 3];
for p in points {
for i in 0..3 {
sum[i] += p[i];
}
}
let n = points.len().max(1) as f32;
sum.map(|v| v / n)
}
pub(crate) fn translate(start: [f32; 3], axis: [f32; 3], delta: f32) -> [f32; 3] {
[
start[0] + axis[0] * delta,
start[1] + axis[1] * delta,
start[2] + axis[2] * delta,
]
}
pub(crate) fn orbit(start: [f32; 3], pivot: [f32; 3], axis: usize, angle_deg: f32) -> [f32; 3] {
let (b, c) = match axis {
0 => (1, 2),
1 => (2, 0),
_ => (0, 1),
};
let (sin, cos) = angle_deg.to_radians().sin_cos();
let db = start[b] - pivot[b];
let dc = start[c] - pivot[c];
let mut out = start;
out[b] = pivot[b] + cos * db - sin * dc;
out[c] = pivot[c] + sin * db + cos * dc;
out
}
pub(crate) fn scale_from(start: [f32; 3], pivot: [f32; 3], axis: usize, factor: f32) -> [f32; 3] {
let mut out = start;
out[axis] = pivot[axis] + (start[axis] - pivot[axis]) * factor;
out
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: [f32; 3], b: [f32; 3]) -> bool {
a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-4)
}
#[test]
fn centroid_is_the_member_mean() {
assert_eq!(centroid(&[[2.0, 4.0, -6.0]]), [2.0, 4.0, -6.0]);
assert_eq!(
centroid(&[[0.0, 0.0, 0.0], [2.0, 4.0, -6.0]]),
[1.0, 2.0, -3.0]
);
assert_eq!(centroid(&[]), [0.0, 0.0, 0.0]);
}
#[test]
fn translate_moves_along_the_axis() {
assert!(close(
translate([1.0, 2.0, 3.0], [0.0, 1.0, 0.0], -2.5),
[1.0, -0.5, 3.0]
));
}
#[test]
fn orbit_turns_right_handed_about_each_axis() {
assert!(close(
orbit([1.0, 0.0, 0.0], [0.0; 3], 1, 90.0),
[0.0, 0.0, -1.0]
));
assert!(close(
orbit([1.0, 0.0, 0.0], [0.0; 3], 2, 90.0),
[0.0, 1.0, 0.0]
));
assert!(close(
orbit([0.0, 1.0, 0.0], [0.0; 3], 0, 90.0),
[0.0, 0.0, 1.0]
));
}
#[test]
fn orbit_about_an_offset_pivot_keeps_the_radius() {
let p = orbit([3.0, 5.0, 0.0], [1.0, 5.0, 0.0], 1, 180.0);
assert!(close(p, [-1.0, 5.0, 0.0]), "{p:?}");
assert!(close(
orbit([1.0, 5.0, 0.0], [1.0, 5.0, 0.0], 1, 77.0),
[1.0, 5.0, 0.0]
));
}
#[test]
fn scale_from_stretches_only_the_dragged_axis() {
assert!(close(
scale_from([3.0, 2.0, 1.0], [1.0, 0.0, 0.0], 0, 2.0),
[5.0, 2.0, 1.0]
));
assert!(close(
scale_from([1.0, 2.0, 1.0], [1.0, 0.0, 0.0], 0, 4.0),
[1.0, 2.0, 1.0]
));
}
}