use crate::vector::*;
use crate::rotator::*;
use crate::BinarySerializable;
use glam::Mat4;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Transform {
pub location: Vector,
pub rotation: Quaternion,
pub scale: Vector,
}
impl fmt::Display for Transform {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Location: ({:.2}, {:.2}, {:.2}), Rotation: {}, Scale: ({:.2}, {:.2}, {:.2})",
self.location.x, self.location.y, self.location.z,
self.get_rotator(),
self.scale.x, self.scale.y, self.scale.z
)
}
}
impl BinarySerializable for Transform {}
impl Transform {
pub const IDENTITY: Self = Self {
location: Vector::ZERO,
rotation: Quaternion::IDENTITY,
scale: Vector::ONE,
};
pub fn new(location: Vector, rotation: Quaternion, scale: Vector) -> Self {
Self { location, rotation, scale }
}
pub fn from_location(location: Vector) -> Self {
Self {
location,
..Self::IDENTITY
}
}
pub fn from_rotation(rotation: Quaternion) -> Self {
Self {
rotation,
..Self::IDENTITY
}
}
pub fn from_scale(scale: Vector) -> Self {
Self {
scale,
..Self::IDENTITY
}
}
pub fn from_uniform_scale(scale: f32) -> Self {
Self {
scale: Vector::splat(scale),
..Self::IDENTITY
}
}
pub fn from_location_rotator(location: Vector, rotator: Rotator) -> Self {
Self {
location,
rotation: rotator.to_quaternion(),
scale: Vector::ONE,
}
}
pub fn from_location_rotator_scale(location: Vector, rotator: Rotator, scale: Vector) -> Self {
Self {
location,
rotation: rotator.to_quaternion(),
scale,
}
}
pub fn to_matrix(self) -> Matrix4 {
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.location)
}
pub fn from_matrix(matrix: Matrix4) -> Self {
let (scale, rotation, location) = matrix.to_scale_rotation_translation();
Self { location, rotation, scale }
}
pub fn get_rotator(self) -> Rotator {
Rotator::from_quaternion(self.rotation)
}
pub fn set_rotator(&mut self, rotator: Rotator) {
self.rotation = rotator.to_quaternion();
}
pub fn transform_point(self, point: Vector) -> Vector {
self.to_matrix().transform_point3(point)
}
pub fn transform_vector(self, vector: Vector) -> Vector {
self.rotation * (vector * self.scale)
}
pub fn transform_direction(self, direction: Vector) -> Vector {
self.rotation * direction
}
pub fn inverse(self) -> Self {
let inv_matrix = self.to_matrix().inverse();
Self::from_matrix(inv_matrix)
}
pub fn combine(self, other: Transform) -> Self {
let combined_matrix = other.to_matrix() * self.to_matrix();
Self::from_matrix(combined_matrix)
}
pub fn get_forward_vector(self) -> Vector {
self.transform_direction(VectorConstants::FORWARD)
}
pub fn get_right_vector(self) -> Vector {
self.transform_direction(VectorConstants::RIGHT)
}
pub fn get_up_vector(self) -> Vector {
self.transform_direction(VectorConstants::UP)
}
pub fn is_nearly_equal(self, other: Transform, tolerance: f32) -> bool {
(self.location - other.location).length() <= tolerance
&& self.rotation.abs_diff_eq(other.rotation, tolerance)
&& (self.scale - other.scale).length() <= tolerance
}
pub fn is_nearly_identity(self, tolerance: f32) -> bool {
self.is_nearly_equal(Self::IDENTITY, tolerance)
}
pub fn lerp(self, other: Transform, alpha: f32) -> Self {
Self {
location: self.location.lerp(other.location, alpha),
rotation: self.rotation.slerp(other.rotation, alpha),
scale: self.scale.lerp(other.scale, alpha),
}
}
pub fn add_location(mut self, delta: Vector) -> Self {
self.location += delta;
self
}
pub fn add_rotation(mut self, delta_rotation: Quaternion) -> Self {
self.rotation = delta_rotation * self.rotation;
self
}
pub fn add_uniform_scale(mut self, delta_scale: f32) -> Self {
self.scale *= delta_scale;
self
}
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transform_identity() {
let transform = Transform::IDENTITY;
let point = Vector::new(1.0, 2.0, 3.0);
let transformed = transform.transform_point(point);
assert!((transformed - point).length() < 0.001);
}
#[test]
fn test_transform_location() {
let transform = Transform::from_location(Vector::new(10.0, 20.0, 30.0));
let point = Vector::ZERO;
let transformed = transform.transform_point(point);
assert_eq!(transformed, Vector::new(10.0, 20.0, 30.0));
}
#[test]
fn test_transform_matrix_roundtrip() {
let original = Transform::new(
Vector::new(1.0, 2.0, 3.0),
Quaternion::from_rotation_y(45.0_f32.to_radians()),
Vector::new(2.0, 2.0, 2.0),
);
let matrix = original.to_matrix();
let reconstructed = Transform::from_matrix(matrix);
assert!(original.is_nearly_equal(reconstructed, 0.001));
}
#[test]
fn test_transform_combine() {
let t1 = Transform::from_location(Vector::new(10.0, 0.0, 0.0));
let t2 = Transform::from_location(Vector::new(0.0, 20.0, 0.0));
let combined = t1.combine(t2);
let point = Vector::ZERO;
let result = combined.transform_point(point);
assert_eq!(result, Vector::new(10.0, 20.0, 0.0));
}
#[test]
fn test_transform_inverse() {
let transform = Transform::new(
Vector::new(10.0, 20.0, 30.0),
Quaternion::from_rotation_z(90.0_f32.to_radians()),
Vector::splat(2.0),
);
let inverse = transform.inverse();
let combined = transform.combine(inverse);
assert!(combined.is_nearly_identity(0.001));
}
#[test]
fn test_transform_display() {
let transform = Transform::new(
Vector::new(10.0, 20.0, 30.0),
Quaternion::from_rotation_y(45.0_f32.to_radians()),
Vector::new(2.0, 2.0, 2.0),
);
let display_str = format!("{}", transform);
assert!(display_str.contains("Location: (10.00, 20.00, 30.00)"));
assert!(display_str.contains("Scale: (2.00, 2.00, 2.00)"));
}
#[test]
fn test_transform_json_serialization() {
let transform = Transform::from_location(Vector::new(1.0, 2.0, 3.0));
let json = serde_json::to_string(&transform).unwrap();
let deserialized: Transform = serde_json::from_str(&json).unwrap();
assert!(transform.is_nearly_equal(deserialized, 0.001));
}
#[test]
fn test_transform_binary_serialization() {
let transform = Transform::new(
Vector::new(10.0, 20.0, 30.0),
Quaternion::from_rotation_z(90.0_f32.to_radians()),
Vector::splat(2.0),
);
let binary = transform.to_binary().unwrap();
let deserialized = Transform::from_binary(&binary).unwrap();
assert!(transform.is_nearly_equal(deserialized, 0.001));
}
}