Something went wrong. Try again.
Homebrew 4D Game Engine In Rust, inspired by Engine4D
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244//! 4D Transform (position, rotation, scale)//!//! A Transform4D represents the position, rotation, and scale of an entity in 4D space.
use rust4d_math::{Rotor4, Vec4};use serde::{Deserialize, Serialize};
/// A 4D transform with position, rotation, and uniform scale#[derive(Clone, Copy, Debug, Serialize, Deserialize)]pub struct Transform4D { /// Position in 4D space pub position: Vec4, /// Rotation as a 4D rotor pub rotation: Rotor4, /// Uniform scale factor pub scale: f32,}
impl Default for Transform4D { fn default() -> Self { Self::identity() }}
impl Transform4D { /// Create an identity transform (no translation, rotation, or scale change) pub fn identity() -> Self { Self { position: Vec4::ZERO, rotation: Rotor4::IDENTITY, scale: 1.0, } }
/// Create a transform with just a position pub fn from_position(position: Vec4) -> Self { Self { position, rotation: Rotor4::IDENTITY, scale: 1.0, } }
/// Create a transform with position and rotation pub fn from_position_rotation(position: Vec4, rotation: Rotor4) -> Self { Self { position, rotation, scale: 1.0, } }
/// Get the rotation matrix as a 4x4 array /// /// This only includes rotation, not position or scale. #[inline] pub fn rotation_matrix(&self) -> [[f32; 4]; 4] { self.rotation.to_matrix() }
/// Transform a point from local space to world space /// /// Applies scale, then rotation, then translation. pub fn transform_point(&self, p: Vec4) -> Vec4 { // Scale let scaled = p * self.scale; // Rotate let rotated = self.rotation.rotate(scaled); // Translate rotated + self.position }
/// Transform a direction from local space to world space /// /// Applies scale and rotation, but not translation. pub fn transform_direction(&self, d: Vec4) -> Vec4 { let scaled = d * self.scale; self.rotation.rotate(scaled) }
/// Compute the inverse transform /// /// The inverse transform undoes this transform: /// `transform.inverse().transform_point(transform.transform_point(p)) == p` pub fn inverse(&self) -> Self { let inv_scale = if self.scale.abs() > 1e-10 { 1.0 / self.scale } else { 1.0 }; let inv_rotation = self.rotation.reverse(); let inv_position = inv_rotation.rotate(-self.position) * inv_scale;
Self { position: inv_position, rotation: inv_rotation, scale: inv_scale, } }
/// Compose two transforms: result = self * other /// /// The composed transform applies `other` first, then `self`. pub fn compose(&self, other: &Self) -> Self { Self { position: self.transform_point(other.position), rotation: self.rotation.compose(&other.rotation), scale: self.scale * other.scale, } }
/// Translate the transform by an offset pub fn translate(&mut self, offset: Vec4) { self.position += offset; }
/// Rotate the transform by a rotor pub fn rotate(&mut self, rotor: Rotor4) { self.rotation = rotor.compose(&self.rotation).normalize(); }
/// Set uniform scale pub fn set_scale(&mut self, scale: f32) { self.scale = scale; }}
#[cfg(test)]mod tests { use super::*; use rust4d_math::RotationPlane; use std::f32::consts::PI;
const EPSILON: f32 = 0.0001;
fn approx_eq(a: f32, b: f32) -> bool { (a - b).abs() < EPSILON }
fn vec_approx_eq(a: Vec4, b: Vec4) -> bool { approx_eq(a.x, b.x) && approx_eq(a.y, b.y) && approx_eq(a.z, b.z) && approx_eq(a.w, b.w) }
#[test] fn test_identity_transform() { let t = Transform4D::identity(); let p = Vec4::new(1.0, 2.0, 3.0, 4.0); let transformed = t.transform_point(p); assert!(vec_approx_eq(p, transformed)); }
#[test] fn test_translation() { let t = Transform4D::from_position(Vec4::new(1.0, 2.0, 3.0, 4.0)); let p = Vec4::ZERO; let transformed = t.transform_point(p); assert!(vec_approx_eq(transformed, Vec4::new(1.0, 2.0, 3.0, 4.0))); }
#[test] fn test_scale() { let mut t = Transform4D::identity(); t.scale = 2.0; let p = Vec4::new(1.0, 1.0, 1.0, 1.0); let transformed = t.transform_point(p); assert!(vec_approx_eq(transformed, Vec4::new(2.0, 2.0, 2.0, 2.0))); }
#[test] fn test_rotation() { let rotor = Rotor4::from_plane_angle(RotationPlane::XY, PI / 2.0); let t = Transform4D::from_position_rotation(Vec4::ZERO, rotor); let p = Vec4::X; let transformed = t.transform_point(p); assert!( vec_approx_eq(transformed, Vec4::Y), "Expected Y, got {:?}", transformed ); }
#[test] fn test_transform_order() { // Transform applies: scale, then rotate, then translate let rotor = Rotor4::from_plane_angle(RotationPlane::XY, PI / 2.0); let mut t = Transform4D::identity(); t.scale = 2.0; t.rotation = rotor; t.position = Vec4::new(10.0, 0.0, 0.0, 0.0);
// X * 2 = (2, 0, 0, 0), rotated 90° in XY = (0, 2, 0, 0), + (10, 0, 0, 0) = (10, 2, 0, 0) let p = Vec4::X; let transformed = t.transform_point(p); assert!( vec_approx_eq(transformed, Vec4::new(10.0, 2.0, 0.0, 0.0)), "Expected (10, 2, 0, 0), got {:?}", transformed ); }
#[test] fn test_inverse() { let rotor = Rotor4::from_plane_angle(RotationPlane::XZ, 0.5); let mut t = Transform4D::from_position_rotation(Vec4::new(1.0, 2.0, 3.0, 4.0), rotor); t.scale = 2.0;
let p = Vec4::new(1.0, 2.0, 3.0, 4.0); let transformed = t.transform_point(p); let back = t.inverse().transform_point(transformed);
assert!(vec_approx_eq(p, back), "Expected {:?}, got {:?}", p, back); }
#[test] fn test_transform_direction() { let t = Transform4D::from_position(Vec4::new(100.0, 100.0, 100.0, 100.0)); let d = Vec4::X; let transformed = t.transform_direction(d); // Direction should not be affected by position assert!(vec_approx_eq(transformed, Vec4::X)); }
#[test] fn test_compose() { let t1 = Transform4D::from_position(Vec4::new(1.0, 0.0, 0.0, 0.0)); let t2 = Transform4D::from_position(Vec4::new(0.0, 2.0, 0.0, 0.0));
// t1.compose(t2) applies t2 first, then t1 let composed = t1.compose(&t2);
let p = Vec4::ZERO; let result = composed.transform_point(p); // Origin -> +y2 (t2) -> +x1 (t1) = (1, 2, 0, 0) assert!(vec_approx_eq(result, Vec4::new(1.0, 2.0, 0.0, 0.0))); }
#[test] fn test_default() { let t = Transform4D::default(); assert!(vec_approx_eq(t.position, Vec4::ZERO)); assert_eq!(t.scale, 1.0); }}