use egui::Ui;
use glam::{DMat4, Mat4, Quat, Vec3};
use transform_gizmo_egui::{
Gizmo, GizmoConfig, GizmoExt, GizmoMode, GizmoOrientation, GizmoVisuals,
config::TransformPivotPoint, math::Transform, mint,
};
pub struct TransformGizmo {
gizmo: Gizmo,
}
impl Default for TransformGizmo {
fn default() -> Self {
Self::new()
}
}
impl TransformGizmo {
#[must_use]
pub fn new() -> Self {
Self {
gizmo: Gizmo::default(),
}
}
pub fn interact(
&mut self,
ui: &mut Ui,
view_matrix: Mat4,
projection_matrix: Mat4,
model_matrix: Mat4,
local_space: bool,
viewport: egui::Rect,
) -> Option<Mat4> {
let orientation = if local_space {
GizmoOrientation::Local
} else {
GizmoOrientation::Global
};
let view_f64 = mat4_to_dmat4(view_matrix);
let proj_f64 = mat4_to_dmat4(projection_matrix);
let view_mint: mint::RowMatrix4<f64> = dmat4_to_row_mint(view_f64);
let proj_mint: mint::RowMatrix4<f64> = dmat4_to_row_mint(proj_f64);
let (scale, rotation, translation) = model_matrix.to_scale_rotation_translation();
let transform = Transform {
translation: mint::Vector3 {
x: f64::from(translation.x),
y: f64::from(translation.y),
z: f64::from(translation.z),
},
rotation: mint::Quaternion {
v: mint::Vector3 {
x: f64::from(rotation.x),
y: f64::from(rotation.y),
z: f64::from(rotation.z),
},
s: f64::from(rotation.w),
},
scale: mint::Vector3 {
x: f64::from(scale.x),
y: f64::from(scale.y),
z: f64::from(scale.z),
},
};
let config = GizmoConfig {
view_matrix: view_mint,
projection_matrix: proj_mint,
viewport,
modes: GizmoMode::all(),
mode_override: None,
orientation,
pivot_point: TransformPivotPoint::MedianPoint,
snapping: false,
snap_angle: 0.0,
snap_distance: 0.0,
snap_scale: 0.0,
visuals: GizmoVisuals::default(),
pixels_per_point: ui.ctx().pixels_per_point(),
};
self.gizmo.update_config(config);
if let Some((_result, new_transforms)) = self.gizmo.interact(ui, &[transform]) {
if let Some(new_transform) = new_transforms.first() {
let translation = Vec3::new(
new_transform.translation.x as f32,
new_transform.translation.y as f32,
new_transform.translation.z as f32,
);
let rotation = Quat::from_xyzw(
new_transform.rotation.v.x as f32,
new_transform.rotation.v.y as f32,
new_transform.rotation.v.z as f32,
new_transform.rotation.s as f32,
);
let scale = Vec3::new(
new_transform.scale.x as f32,
new_transform.scale.y as f32,
new_transform.scale.z as f32,
);
return Some(Mat4::from_scale_rotation_translation(
scale,
rotation,
translation,
));
}
}
None
}
#[must_use]
pub fn decompose_transform(matrix: Mat4) -> (Vec3, Vec3, Vec3) {
let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
let euler = rotation.to_euler(glam::EulerRot::XYZ);
let euler_degrees = Vec3::new(
euler.0.to_degrees(),
euler.1.to_degrees(),
euler.2.to_degrees(),
);
(translation, euler_degrees, scale)
}
#[must_use]
pub fn compose_transform(translation: Vec3, euler_degrees: Vec3, scale: Vec3) -> Mat4 {
let rotation = Quat::from_euler(
glam::EulerRot::XYZ,
euler_degrees.x.to_radians(),
euler_degrees.y.to_radians(),
euler_degrees.z.to_radians(),
);
Mat4::from_scale_rotation_translation(scale, rotation, translation)
}
}
fn mat4_to_dmat4(m: Mat4) -> DMat4 {
DMat4::from_cols_array(&[
f64::from(m.x_axis.x),
f64::from(m.x_axis.y),
f64::from(m.x_axis.z),
f64::from(m.x_axis.w),
f64::from(m.y_axis.x),
f64::from(m.y_axis.y),
f64::from(m.y_axis.z),
f64::from(m.y_axis.w),
f64::from(m.z_axis.x),
f64::from(m.z_axis.y),
f64::from(m.z_axis.z),
f64::from(m.z_axis.w),
f64::from(m.w_axis.x),
f64::from(m.w_axis.y),
f64::from(m.w_axis.z),
f64::from(m.w_axis.w),
])
}
fn dmat4_to_row_mint(m: DMat4) -> mint::RowMatrix4<f64> {
mint::RowMatrix4 {
x: mint::Vector4 {
x: m.x_axis.x,
y: m.y_axis.x,
z: m.z_axis.x,
w: m.w_axis.x,
},
y: mint::Vector4 {
x: m.x_axis.y,
y: m.y_axis.y,
z: m.z_axis.y,
w: m.w_axis.y,
},
z: mint::Vector4 {
x: m.x_axis.z,
y: m.y_axis.z,
z: m.z_axis.z,
w: m.w_axis.z,
},
w: mint::Vector4 {
x: m.x_axis.w,
y: m.y_axis.w,
z: m.z_axis.w,
w: m.w_axis.w,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gizmo_creation() {
let gizmo = TransformGizmo::new();
drop(gizmo);
}
#[test]
fn test_decompose_compose_roundtrip() {
let translation = Vec3::new(1.0, 2.0, 3.0);
let euler_degrees = Vec3::new(45.0, 30.0, 15.0);
let scale = Vec3::new(1.0, 2.0, 1.5);
let matrix = TransformGizmo::compose_transform(translation, euler_degrees, scale);
let (t, r, s) = TransformGizmo::decompose_transform(matrix);
assert!((t - translation).length() < 0.001);
assert!((r - euler_degrees).length() < 0.1);
assert!((s - scale).length() < 0.001);
}
}