use glam::Mat3;
pub use glam::{Quat, Vec3};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Transform {
pub translation: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Angle {
radians: f32,
}
impl Transform {
pub const IDENTITY: Self = Self {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
pub const fn at(translation: Vec3) -> Self {
Self {
translation,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
}
}
pub const fn with_translation(mut self, translation: Vec3) -> Self {
self.translation = translation;
self
}
pub const fn scale_by(mut self, scale: f32) -> Self {
self.scale = Vec3::new(scale, scale, scale);
self
}
pub fn rotate_x_deg(mut self, degrees: f32) -> Self {
let added = Quat::from_axis_angle(
Vec3::new(1.0, 0.0, 0.0),
Angle::from_degrees(degrees).radians(),
);
self.rotation = compose_rotations(self.rotation, added);
self
}
pub fn rotate_y_deg(mut self, degrees: f32) -> Self {
let added = Quat::from_axis_angle(
Vec3::new(0.0, 1.0, 0.0),
Angle::from_degrees(degrees).radians(),
);
self.rotation = compose_rotations(self.rotation, added);
self
}
pub fn rotate_z_deg(mut self, degrees: f32) -> Self {
let added = Quat::from_axis_angle(
Vec3::new(0.0, 0.0, 1.0),
Angle::from_degrees(degrees).radians(),
);
self.rotation = compose_rotations(self.rotation, added);
self
}
pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self {
let Some(forward) = normalize_vec3(target - self.translation) else {
return self;
};
let up = normalize_vec3(up).unwrap_or(Vec3::Y);
let right = normalize_vec3(forward.cross(up)).or_else(|| {
let fallback_up = if forward.y.abs() < 0.99 {
Vec3::Y
} else {
Vec3::X
};
normalize_vec3(forward.cross(fallback_up))
});
let Some(right) = right else {
return self;
};
let true_up = right.cross(forward);
let rotation = Quat::from_mat3(&Mat3::from_cols(right, true_up, -forward));
if rotation.is_finite() {
self.rotation = rotation.normalize();
}
self
}
}
fn compose_rotations(base: Quat, added: Quat) -> Quat {
let product = base * added;
let length_sq = product.length_squared();
if length_sq <= f32::EPSILON || !length_sq.is_finite() {
return Quat::IDENTITY;
}
product.normalize()
}
fn normalize_vec3(vector: Vec3) -> Option<Vec3> {
let length_sq = vector.length_squared();
if length_sq <= f32::EPSILON || !length_sq.is_finite() {
return None;
}
Some(vector / length_sq.sqrt())
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
impl Angle {
pub fn from_degrees(degrees: f32) -> Self {
Self::from_radians(degrees.to_radians())
}
pub const fn from_radians(radians: f32) -> Self {
Self { radians }
}
pub const fn radians(self) -> f32 {
self.radians
}
}