use crate::core::scene3d::mat4::{Mat4, Vec3, mat4_rotate, mat4_scale, mat4_translate};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RotationCenter {
Absolute(f32),
Lower,
Center,
Upper,
}
impl Default for RotationCenter {
fn default() -> Self {
RotationCenter::Absolute(0.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Item3DTransform {
translation: Vec3,
rotation_angle_deg: f32,
rotation_axis: Vec3,
rotation_center: [RotationCenter; 3],
matrix3: [[f32; 3]; 3],
scale: Vec3,
}
const MATRIX3_IDENTITY: [[f32; 3]; 3] = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]];
impl Default for Item3DTransform {
fn default() -> Self {
Item3DTransform {
translation: Vec3::ZERO,
rotation_angle_deg: 0.0,
rotation_axis: Vec3::new(0.0, 0.0, 1.0),
rotation_center: [RotationCenter::default(); 3],
matrix3: MATRIX3_IDENTITY,
scale: Vec3::new(1.0, 1.0, 1.0),
}
}
}
impl Item3DTransform {
pub fn new() -> Self {
Self::default()
}
pub fn is_identity(&self) -> bool {
*self == Self::default()
}
pub fn set_scale(&mut self, sx: f32, sy: f32, sz: f32) {
self.scale = Vec3::new(sx, sy, sz);
}
pub fn scale(&self) -> Vec3 {
self.scale
}
pub fn set_translation(&mut self, x: f32, y: f32, z: f32) {
self.translation = Vec3::new(x, y, z);
}
pub fn translation(&self) -> Vec3 {
self.translation
}
pub fn set_rotation_center(&mut self, x: RotationCenter, y: RotationCenter, z: RotationCenter) {
self.rotation_center = [x, y, z];
}
pub fn rotation_center(&self) -> [RotationCenter; 3] {
self.rotation_center
}
pub fn set_rotation(&mut self, angle_deg: f32, axis: Vec3) {
if axis.length() == 0.0 {
self.rotation_angle_deg = 0.0;
self.rotation_axis = Vec3::new(0.0, 0.0, 1.0);
} else {
self.rotation_angle_deg = angle_deg;
self.rotation_axis = axis.normalized();
}
}
pub fn rotation(&self) -> (f32, Vec3) {
(self.rotation_angle_deg, self.rotation_axis)
}
pub fn set_matrix(&mut self, matrix: Option<[[f32; 3]; 3]>) {
self.matrix3 = matrix.unwrap_or(MATRIX3_IDENTITY);
}
pub fn matrix(&self) -> [[f32; 3]; 3] {
self.matrix3
}
fn object_to_rotate_matrix(&self) -> Mat4 {
let m = &self.matrix3;
let matrix4 = Mat4::from_rows([
[m[0][0], m[0][1], m[0][2], 0.0],
[m[1][0], m[1][1], m[1][2], 0.0],
[m[2][0], m[2][1], m[2][2], 0.0],
[0.0, 0.0, 0.0, 1.0],
]);
matrix4 * mat4_scale(self.scale.x, self.scale.y, self.scale.z)
}
pub fn resolve_rotation_center(&self, data_bounds: Option<(Vec3, Vec3)>) -> Vec3 {
let bounds = data_bounds.map(|b| transform_aabb(&self.object_to_rotate_matrix(), b));
let mut out = [0.0f32; 3];
for (index, (slot, center)) in out.iter_mut().zip(self.rotation_center).enumerate() {
*slot = match (center, bounds) {
(RotationCenter::Absolute(v), _) => v,
(_, None) => 0.0,
(RotationCenter::Lower, Some((lo, _))) => lo.to_array()[index],
(RotationCenter::Center, Some((lo, hi))) => {
0.5 * (lo.to_array()[index] + hi.to_array()[index])
}
(RotationCenter::Upper, Some((_, hi))) => hi.to_array()[index],
};
}
Vec3::from_array(out)
}
pub fn composed_matrix(&self, data_bounds: Option<(Vec3, Vec3)>) -> Mat4 {
let c = self.resolve_rotation_center(data_bounds);
let t = self.translation;
let a = self.rotation_axis;
mat4_translate(t.x, t.y, t.z)
* mat4_translate(c.x, c.y, c.z)
* mat4_rotate(self.rotation_angle_deg.to_radians(), a.x, a.y, a.z)
* mat4_translate(-c.x, -c.y, -c.z)
* self.object_to_rotate_matrix()
}
pub fn transform_bounds(&self, raw: Option<(Vec3, Vec3)>) -> Option<(Vec3, Vec3)> {
let raw = raw?;
if self.is_identity() {
return Some(raw);
}
Some(transform_aabb(&self.composed_matrix(Some(raw)), raw))
}
}
pub fn transform_aabb(m: &Mat4, (mn, mx): (Vec3, Vec3)) -> (Vec3, Vec3) {
let mut lo = Vec3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
let mut hi = Vec3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY);
for corner in [
Vec3::new(mn.x, mn.y, mn.z),
Vec3::new(mx.x, mn.y, mn.z),
Vec3::new(mn.x, mx.y, mn.z),
Vec3::new(mx.x, mx.y, mn.z),
Vec3::new(mn.x, mn.y, mx.z),
Vec3::new(mx.x, mn.y, mx.z),
Vec3::new(mn.x, mx.y, mx.z),
Vec3::new(mx.x, mx.y, mx.z),
] {
let p = m.transform_point(corner, false);
lo = Vec3::new(lo.x.min(p.x), lo.y.min(p.y), lo.z.min(p.z));
hi = Vec3::new(hi.x.max(p.x), hi.y.max(p.y), hi.z.max(p.z));
}
(lo, hi)
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: Vec3, b: Vec3) -> bool {
(a.x - b.x).abs() < 1e-4 && (a.y - b.y).abs() < 1e-4 && (a.z - b.z).abs() < 1e-4
}
#[test]
fn default_is_identity() {
let t = Item3DTransform::new();
assert!(t.is_identity());
let p = Vec3::new(1.5, -2.0, 3.0);
assert!(close(t.composed_matrix(None).transform_point(p, false), p));
}
#[test]
fn compose_order_matches_silx_stack() {
let mut t = Item3DTransform::new();
t.set_scale(2.0, 3.0, 4.0);
t.set_rotation(90.0, Vec3::new(0.0, 0.0, 1.0));
t.set_translation(10.0, 20.0, 30.0);
let m = t.composed_matrix(None);
assert!(close(
m.transform_point(Vec3::new(1.0, 0.0, 0.0), false),
Vec3::new(10.0, 22.0, 30.0)
));
let mut t = Item3DTransform::new();
t.set_scale(2.0, 3.0, 4.0);
t.set_matrix(Some([[0.0, 1.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0]]));
let m = t.composed_matrix(None);
assert!(close(
m.transform_point(Vec3::new(1.0, 1.0, 0.0), false),
Vec3::new(3.0, 2.0, 0.0)
));
}
#[test]
fn rotation_center_absolute_offsets_the_pivot() {
let mut t = Item3DTransform::new();
t.set_rotation(180.0, Vec3::new(0.0, 0.0, 1.0));
t.set_rotation_center(
RotationCenter::Absolute(1.0),
RotationCenter::Absolute(0.0),
RotationCenter::Absolute(0.0),
);
let m = t.composed_matrix(None);
assert!(close(
m.transform_point(Vec3::new(2.0, 0.0, 0.0), false),
Vec3::new(0.0, 0.0, 0.0)
));
}
#[test]
fn rotation_center_tags_resolve_against_scaled_bounds() {
let mut t = Item3DTransform::new();
t.set_scale(2.0, 1.0, 1.0);
t.set_rotation_center(
RotationCenter::Center,
RotationCenter::Lower,
RotationCenter::Upper,
);
let bounds = Some((Vec3::ZERO, Vec3::new(2.0, 4.0, 6.0)));
assert!(close(
t.resolve_rotation_center(bounds),
Vec3::new(2.0, 0.0, 6.0)
));
assert!(close(t.resolve_rotation_center(None), Vec3::ZERO));
t.set_rotation_center(
RotationCenter::Center,
RotationCenter::Center,
RotationCenter::Absolute(0.0),
);
t.set_rotation(180.0, Vec3::new(0.0, 0.0, 1.0));
let m = t.composed_matrix(bounds);
assert!(close(
m.transform_point(Vec3::ZERO, false),
Vec3::new(4.0, 4.0, 0.0)
));
}
#[test]
fn zero_rotation_axis_resets_like_silx() {
let mut t = Item3DTransform::new();
t.set_rotation(45.0, Vec3::ZERO);
assert_eq!(t.rotation(), (0.0, Vec3::new(0.0, 0.0, 1.0)));
assert!(t.is_identity());
}
#[test]
fn transform_bounds_is_the_transformed_corner_aabb() {
let mut t = Item3DTransform::new();
t.set_rotation(90.0, Vec3::new(0.0, 0.0, 1.0));
t.set_translation(10.0, 0.0, 0.0);
let out = t
.transform_bounds(Some((Vec3::ZERO, Vec3::new(2.0, 1.0, 3.0))))
.unwrap();
assert!(close(out.0, Vec3::new(9.0, 0.0, 0.0)));
assert!(close(out.1, Vec3::new(10.0, 2.0, 3.0)));
let id = Item3DTransform::new();
let raw = (Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0));
assert_eq!(id.transform_bounds(Some(raw)), Some(raw));
assert_eq!(id.transform_bounds(None), None);
}
}