use crate::{
Error, Result,
vlm::{INVERSE_TOLERANCE, Mat4, Quat, RotationOrder, Vec3},
};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Transform {
translation: Vec3,
rotation: Quat,
scale: Vec3,
shear: Vec3,
}
impl Default for Transform {
fn default() -> Self {
Self {
translation: Vec3::default(),
rotation: Quat::identity(),
scale: Vec3 {
x: 1.0,
y: 1.0,
z: 1.0,
},
shear: Vec3::default(),
}
}
}
impl Transform {
pub fn new(translation: Vec3, rotation: Quat, scale: Vec3, shear: Vec3) -> Self {
Self {
translation,
rotation: rotation.try_normalized().unwrap_or(Quat::identity()),
scale,
shear,
}
}
pub fn set_position(&mut self, position: Vec3) {
self.translation = position;
}
pub fn set_rotation(&mut self, rotation: Quat) {
self.rotation = rotation.try_normalized().unwrap_or(Quat::identity());
}
pub fn set_scale(&mut self, scale: Vec3) {
self.scale = scale;
}
pub fn set_shear(&mut self, shear: Vec3) {
self.shear = shear;
}
pub const fn position(&self) -> Vec3 {
self.translation
}
pub const fn rotation(&self) -> Quat {
self.rotation
}
pub const fn scale(&self) -> Vec3 {
self.scale
}
pub const fn shear(&self) -> Vec3 {
self.shear
}
pub fn forward(&self) -> Vec3 {
let neg_z = Vec3 {
x: 0.0,
y: 0.0,
z: -1.0,
};
self.rotation.try_rotate(neg_z).unwrap_or(neg_z)
}
pub fn right(&self) -> Vec3 {
let pos_x = Vec3 {
x: 1.0,
y: 0.0,
z: 0.0,
};
self.rotation.try_rotate(pos_x).unwrap_or(pos_x)
}
pub fn up(&self) -> Vec3 {
let pos_y = Vec3 {
x: 0.0,
y: 1.0,
z: 0.0,
};
self.rotation.try_rotate(pos_y).unwrap_or(pos_y)
}
pub fn look_at(&mut self, target: Vec3, world_up: Vec3) {
if let Some(q) = quat_look_at(self.translation, target, world_up) {
self.rotation = q;
}
}
pub fn set_rotation_degrees(&mut self, degrees: Vec3, order: RotationOrder) {
if let Some(q) = Quat::try_from_euler_degrees(degrees, order) {
self.rotation = q;
}
}
pub fn rotation_degrees(&self, order: RotationOrder) -> Vec3 {
self
.rotation
.try_to_euler_degrees(order)
.unwrap_or_default()
}
pub fn pan_local(&mut self, right: f32, up: f32, forward: f32) {
let r = self.right();
let u = self.up();
let f = self.forward();
self.translation.x += r.x * right + u.x * up + f.x * forward;
self.translation.y += r.y * right + u.y * up + f.y * forward;
self.translation.z += r.z * right + u.z * up + f.z * forward;
}
pub fn get_matrix(&self) -> Mat4 {
trs_shear_to_mat4(self.translation, self.rotation, self.scale, self.shear)
}
pub fn set_matrix(&mut self, matrix: Mat4) -> Result<()> {
if !matrix.is_finite() {
return Err(Error::invalid_argument(
"core::Transform::set_matrix requires a finite matrix",
));
}
let d = matrix
.try_decompose_affine(INVERSE_TOLERANCE)
.ok_or_else(|| {
Error::invalid_argument("core::Transform::set_matrix requires a non-singular affine matrix")
})?;
let rotation = d.rotation.try_normalized().ok_or_else(|| {
Error::invalid_argument(
"core::Transform::set_matrix produced a degenerate rotation quaternion",
)
})?;
self.translation = d.translation;
self.rotation = rotation;
self.scale = d.scale;
self.shear = d.shear;
Ok(())
}
pub fn is_finite(&self) -> bool {
self.translation.is_finite()
&& self.rotation.is_finite()
&& self.scale.is_finite()
&& self.shear.is_finite()
&& self.rotation.try_normalized().is_some()
}
pub fn validate(&self) -> Result<()> {
if !self.is_finite() {
return Err(Error::invalid_argument(
"core::Transform components must be finite and the rotation must be normalizable",
));
}
Ok(())
}
}
pub fn quaternion_to_six_d(q: Quat) -> [f32; 6] {
let r = Vec3 {
x: 1.0,
y: 0.0,
z: 0.0,
};
let u = Vec3 {
x: 0.0,
y: 1.0,
z: 0.0,
};
let col0 = q.try_rotate(r).unwrap_or(r);
let col1 = q.try_rotate(u).unwrap_or(u);
[col0.x, col0.y, col0.z, col1.x, col1.y, col1.z]
}
pub fn quaternion_from_six_d(d: [f32; 6]) -> Quat {
let a0 = Vec3 {
x: d[0],
y: d[1],
z: d[2],
};
let a1 = Vec3 {
x: d[3],
y: d[4],
z: d[5],
};
let Some(b0) = a0.try_normalized() else {
return Quat::identity();
};
let dot = b0.dot(a1);
let a1_orth = Vec3 {
x: a1.x - b0.x * dot,
y: a1.y - b0.y * dot,
z: a1.z - b0.z * dot,
};
let Some(b1) = a1_orth.try_normalized() else {
return Quat::identity();
};
let b2 = b0.cross(b1);
quat_from_rotation_rows(b0, b1, b2)
}
fn trs_shear_to_mat4(t: Vec3, q: Quat, s: Vec3, shear: Vec3) -> Mat4 {
let unit = q.try_normalized().unwrap_or(Quat::identity());
let r0 = unit
.try_rotate(Vec3 {
x: 1.0,
y: 0.0,
z: 0.0,
})
.unwrap_or(Vec3 {
x: 1.0,
y: 0.0,
z: 0.0,
});
let r1 = unit
.try_rotate(Vec3 {
x: 0.0,
y: 1.0,
z: 0.0,
})
.unwrap_or(Vec3 {
x: 0.0,
y: 1.0,
z: 0.0,
});
let r2 = unit
.try_rotate(Vec3 {
x: 0.0,
y: 0.0,
z: 1.0,
})
.unwrap_or(Vec3 {
x: 0.0,
y: 0.0,
z: 1.0,
});
let sx = s.x;
let sy = s.y;
let sz = s.z;
let sxy = shear.x;
let sxz = shear.y;
let syz = shear.z;
let row0 = Vec3 {
x: r0.x * sx + r1.x * sy * sxy + r2.x * sz * sxz,
y: r0.y * sx + r1.y * sy * sxy + r2.y * sz * sxz,
z: r0.z * sx + r1.z * sy * sxy + r2.z * sz * sxz,
};
let row1 = Vec3 {
x: r1.x * sy + r2.x * sz * syz,
y: r1.y * sy + r2.y * sz * syz,
z: r1.z * sy + r2.z * sz * syz,
};
let row2 = Vec3 {
x: r2.x * sz,
y: r2.y * sz,
z: r2.z * sz,
};
Mat4 {
m: [
[row0.x, row0.y, row0.z, 0.0],
[row1.x, row1.y, row1.z, 0.0],
[row2.x, row2.y, row2.z, 0.0],
[t.x, t.y, t.z, 1.0],
],
}
}
fn quat_look_at(eye: Vec3, target: Vec3, world_up: Vec3) -> Option<Quat> {
let f = (target - eye).try_normalized()?;
let r = f.cross(world_up).try_normalized()?;
let backward = -f;
let u = backward.cross(r);
quat_from_rotation_rows(r, u, backward).try_normalized()
}
fn quat_from_rotation_rows(r: Vec3, u: Vec3, f: Vec3) -> Quat {
let trace = r.x + u.y + f.z;
if trace > 0.0 {
let s = (trace + 1.0).sqrt() * 2.0; Quat {
w: 0.25 * s,
x: (f.y - u.z) / s,
y: (r.z - f.x) / s,
z: (u.x - r.y) / s,
}
} else if r.x > u.y && r.x > f.z {
let s = (1.0 + r.x - u.y - f.z).sqrt() * 2.0; Quat {
w: (f.y - u.z) / s,
x: 0.25 * s,
y: (r.y + u.x) / s,
z: (r.z + f.x) / s,
}
} else if u.y > f.z {
let s = (1.0 + u.y - r.x - f.z).sqrt() * 2.0; Quat {
w: (r.z - f.x) / s,
x: (r.y + u.x) / s,
y: 0.25 * s,
z: (u.z + f.y) / s,
}
} else {
let s = (1.0 + f.z - r.x - u.y).sqrt() * 2.0; Quat {
w: (u.x - r.y) / s,
x: (r.z + f.x) / s,
y: (u.z + f.y) / s,
z: 0.25 * s,
}
}
}