use glam::{Mat4, Quat, Vec3};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GizmoMode {
#[default]
Translate,
Rotate,
Scale,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GizmoSpace {
#[default]
World,
Local,
}
#[derive(Debug, Clone)]
pub struct GizmoConfig {
pub mode: GizmoMode,
pub space: GizmoSpace,
pub visible: bool,
pub size: f32,
pub snap_translate: f32,
pub snap_rotate: f32,
pub snap_scale: f32,
}
impl Default for GizmoConfig {
fn default() -> Self {
Self {
mode: GizmoMode::Translate,
space: GizmoSpace::World,
visible: true,
size: 100.0,
snap_translate: 0.0,
snap_rotate: 0.0,
snap_scale: 0.0,
}
}
}
impl GizmoConfig {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_mode(mut self, mode: GizmoMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub fn with_space(mut self, space: GizmoSpace) -> Self {
self.space = space;
self
}
#[must_use]
pub fn with_size(mut self, size: f32) -> Self {
self.size = size;
self
}
#[must_use]
pub fn with_snap_translate(mut self, snap: f32) -> Self {
self.snap_translate = snap;
self
}
#[must_use]
pub fn with_snap_rotate(mut self, snap: f32) -> Self {
self.snap_rotate = snap;
self
}
#[must_use]
pub fn with_snap_scale(mut self, snap: f32) -> Self {
self.snap_scale = snap;
self
}
}
#[derive(Debug, Clone, Copy)]
pub struct Transform {
pub translation: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
impl Default for Transform {
fn default() -> Self {
Self {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
}
}
}
impl Transform {
#[must_use]
pub fn identity() -> Self {
Self::default()
}
#[must_use]
pub fn from_translation(translation: Vec3) -> Self {
Self {
translation,
..Default::default()
}
}
#[must_use]
pub fn from_rotation(rotation: Quat) -> Self {
Self {
rotation,
..Default::default()
}
}
#[must_use]
pub fn from_scale(scale: Vec3) -> Self {
Self {
scale,
..Default::default()
}
}
#[must_use]
pub fn from_matrix(matrix: Mat4) -> Self {
let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
Self {
translation,
rotation,
scale,
}
}
#[must_use]
pub fn to_matrix(&self) -> Mat4 {
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
#[must_use]
pub fn euler_angles(&self) -> Vec3 {
let (x, y, z) = self.rotation.to_euler(glam::EulerRot::XYZ);
Vec3::new(x, y, z)
}
pub fn set_euler_angles(&mut self, angles: Vec3) {
self.rotation = Quat::from_euler(glam::EulerRot::XYZ, angles.x, angles.y, angles.z);
}
#[must_use]
pub fn euler_angles_degrees(&self) -> Vec3 {
self.euler_angles() * (180.0 / std::f32::consts::PI)
}
pub fn set_euler_angles_degrees(&mut self, degrees: Vec3) {
self.set_euler_angles(degrees * (std::f32::consts::PI / 180.0));
}
pub fn translate(&mut self, delta: Vec3) {
self.translation += delta;
}
pub fn rotate(&mut self, delta: Quat) {
self.rotation = delta * self.rotation;
}
pub fn scale_by(&mut self, factor: Vec3) {
self.scale *= factor;
}
pub fn snap_translation(&mut self, snap: f32) {
if snap > 0.0 {
self.translation = (self.translation / snap).round() * snap;
}
}
pub fn snap_rotation(&mut self, snap_degrees: f32) {
if snap_degrees > 0.0 {
let mut euler = self.euler_angles_degrees();
euler = (euler / snap_degrees).round() * snap_degrees;
self.set_euler_angles_degrees(euler);
}
}
pub fn snap_scale(&mut self, snap: f32) {
if snap > 0.0 {
self.scale = (self.scale / snap).round() * snap;
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GizmoAxis {
X,
Y,
Z,
XY,
XZ,
YZ,
All,
None,
}
impl GizmoAxis {
#[must_use]
pub fn direction(&self) -> Option<Vec3> {
match self {
GizmoAxis::X => Some(Vec3::X),
GizmoAxis::Y => Some(Vec3::Y),
GizmoAxis::Z => Some(Vec3::Z),
_ => None,
}
}
#[must_use]
pub fn color(&self) -> Vec3 {
match self {
GizmoAxis::X | GizmoAxis::YZ => Vec3::new(1.0, 0.2, 0.2), GizmoAxis::Y | GizmoAxis::XZ => Vec3::new(0.2, 1.0, 0.2), GizmoAxis::Z | GizmoAxis::XY => Vec3::new(0.2, 0.2, 1.0), GizmoAxis::All => Vec3::new(1.0, 1.0, 1.0), GizmoAxis::None => Vec3::new(0.5, 0.5, 0.5), }
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GizmoUniforms {
pub model: [[f32; 4]; 4],
pub color: [f32; 3],
pub highlighted: f32,
}
impl Default for GizmoUniforms {
fn default() -> Self {
Self {
model: Mat4::IDENTITY.to_cols_array_2d(),
color: [1.0, 1.0, 1.0],
highlighted: 0.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transform_matrix_roundtrip() {
let t = Transform {
translation: Vec3::new(1.0, 2.0, 3.0),
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
let matrix = t.to_matrix();
let back = Transform::from_matrix(matrix);
assert!((back.translation - t.translation).length() < 1e-6);
}
#[test]
fn test_transform_euler_angles() {
let mut t = Transform::identity();
t.set_euler_angles_degrees(Vec3::new(0.0, 90.0, 0.0));
let angles = t.euler_angles_degrees();
assert!((angles.y - 90.0).abs() < 0.1);
}
#[test]
fn test_snap_translation() {
let mut t = Transform::from_translation(Vec3::new(1.2, 2.7, 3.1));
t.snap_translation(0.5);
assert_eq!(t.translation, Vec3::new(1.0, 2.5, 3.0));
}
}