1use egui::Ui;
6use glam::{DMat4, Mat4, Quat, Vec3};
7use transform_gizmo_egui::{
8 Gizmo, GizmoConfig, GizmoExt, GizmoMode, GizmoOrientation, GizmoVisuals,
9 config::TransformPivotPoint, math::Transform, mint,
10};
11
12pub struct TransformGizmo {
14 gizmo: Gizmo,
15}
16
17impl Default for TransformGizmo {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl TransformGizmo {
24 #[must_use]
26 pub fn new() -> Self {
27 Self {
28 gizmo: Gizmo::default(),
29 }
30 }
31
32 pub fn interact(
45 &mut self,
46 ui: &mut Ui,
47 view_matrix: Mat4,
48 projection_matrix: Mat4,
49 model_matrix: Mat4,
50 local_space: bool,
51 viewport: egui::Rect,
52 ) -> Option<Mat4> {
53 let orientation = if local_space {
54 GizmoOrientation::Local
55 } else {
56 GizmoOrientation::Global
57 };
58
59 let view_f64 = mat4_to_dmat4(view_matrix);
61 let proj_f64 = mat4_to_dmat4(projection_matrix);
62
63 let view_mint: mint::RowMatrix4<f64> = dmat4_to_row_mint(view_f64);
65 let proj_mint: mint::RowMatrix4<f64> = dmat4_to_row_mint(proj_f64);
66
67 let (scale, rotation, translation) = model_matrix.to_scale_rotation_translation();
69 let transform = Transform {
70 translation: mint::Vector3 {
71 x: f64::from(translation.x),
72 y: f64::from(translation.y),
73 z: f64::from(translation.z),
74 },
75 rotation: mint::Quaternion {
76 v: mint::Vector3 {
77 x: f64::from(rotation.x),
78 y: f64::from(rotation.y),
79 z: f64::from(rotation.z),
80 },
81 s: f64::from(rotation.w),
82 },
83 scale: mint::Vector3 {
84 x: f64::from(scale.x),
85 y: f64::from(scale.y),
86 z: f64::from(scale.z),
87 },
88 };
89
90 let config = GizmoConfig {
91 view_matrix: view_mint,
92 projection_matrix: proj_mint,
93 viewport,
94 modes: GizmoMode::all(),
95 mode_override: None,
96 orientation,
97 pivot_point: TransformPivotPoint::MedianPoint,
98 snapping: false,
99 snap_angle: 0.0,
100 snap_distance: 0.0,
101 snap_scale: 0.0,
102 visuals: GizmoVisuals::default(),
103 pixels_per_point: ui.ctx().pixels_per_point(),
104 };
105
106 self.gizmo.update_config(config);
108
109 if let Some((_result, new_transforms)) = self.gizmo.interact(ui, &[transform]) {
111 if let Some(new_transform) = new_transforms.first() {
112 let translation = Vec3::new(
114 new_transform.translation.x as f32,
115 new_transform.translation.y as f32,
116 new_transform.translation.z as f32,
117 );
118 let rotation = Quat::from_xyzw(
119 new_transform.rotation.v.x as f32,
120 new_transform.rotation.v.y as f32,
121 new_transform.rotation.v.z as f32,
122 new_transform.rotation.s as f32,
123 );
124 let scale = Vec3::new(
125 new_transform.scale.x as f32,
126 new_transform.scale.y as f32,
127 new_transform.scale.z as f32,
128 );
129
130 return Some(Mat4::from_scale_rotation_translation(
131 scale,
132 rotation,
133 translation,
134 ));
135 }
136 }
137
138 None
139 }
140
141 #[must_use]
143 pub fn decompose_transform(matrix: Mat4) -> (Vec3, Vec3, Vec3) {
144 let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
145 let euler = rotation.to_euler(glam::EulerRot::XYZ);
146 let euler_degrees = Vec3::new(
147 euler.0.to_degrees(),
148 euler.1.to_degrees(),
149 euler.2.to_degrees(),
150 );
151 (translation, euler_degrees, scale)
152 }
153
154 #[must_use]
156 pub fn compose_transform(translation: Vec3, euler_degrees: Vec3, scale: Vec3) -> Mat4 {
157 let rotation = Quat::from_euler(
158 glam::EulerRot::XYZ,
159 euler_degrees.x.to_radians(),
160 euler_degrees.y.to_radians(),
161 euler_degrees.z.to_radians(),
162 );
163 Mat4::from_scale_rotation_translation(scale, rotation, translation)
164 }
165}
166
167fn mat4_to_dmat4(m: Mat4) -> DMat4 {
169 DMat4::from_cols_array(&[
170 f64::from(m.x_axis.x),
171 f64::from(m.x_axis.y),
172 f64::from(m.x_axis.z),
173 f64::from(m.x_axis.w),
174 f64::from(m.y_axis.x),
175 f64::from(m.y_axis.y),
176 f64::from(m.y_axis.z),
177 f64::from(m.y_axis.w),
178 f64::from(m.z_axis.x),
179 f64::from(m.z_axis.y),
180 f64::from(m.z_axis.z),
181 f64::from(m.z_axis.w),
182 f64::from(m.w_axis.x),
183 f64::from(m.w_axis.y),
184 f64::from(m.w_axis.z),
185 f64::from(m.w_axis.w),
186 ])
187}
188
189fn dmat4_to_row_mint(m: DMat4) -> mint::RowMatrix4<f64> {
191 mint::RowMatrix4 {
193 x: mint::Vector4 {
194 x: m.x_axis.x,
195 y: m.y_axis.x,
196 z: m.z_axis.x,
197 w: m.w_axis.x,
198 },
199 y: mint::Vector4 {
200 x: m.x_axis.y,
201 y: m.y_axis.y,
202 z: m.z_axis.y,
203 w: m.w_axis.y,
204 },
205 z: mint::Vector4 {
206 x: m.x_axis.z,
207 y: m.y_axis.z,
208 z: m.z_axis.z,
209 w: m.w_axis.z,
210 },
211 w: mint::Vector4 {
212 x: m.x_axis.w,
213 y: m.y_axis.w,
214 z: m.z_axis.w,
215 w: m.w_axis.w,
216 },
217 }
218}
219
220#[cfg(test)]
221mod tests {
222 use super::*;
223
224 #[test]
225 fn test_gizmo_creation() {
226 let gizmo = TransformGizmo::new();
227 drop(gizmo);
229 }
230
231 #[test]
232 fn test_decompose_compose_roundtrip() {
233 let translation = Vec3::new(1.0, 2.0, 3.0);
234 let euler_degrees = Vec3::new(45.0, 30.0, 15.0);
235 let scale = Vec3::new(1.0, 2.0, 1.5);
236
237 let matrix = TransformGizmo::compose_transform(translation, euler_degrees, scale);
238 let (t, r, s) = TransformGizmo::decompose_transform(matrix);
239
240 assert!((t - translation).length() < 0.001);
241 assert!((r - euler_degrees).length() < 0.1);
242 assert!((s - scale).length() < 0.001);
243 }
244}