1use glam::{Mat4, Quat, Vec3};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum GizmoMode {
10 #[default]
12 Translate,
13 Rotate,
15 Scale,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum GizmoSpace {
22 #[default]
24 World,
25 Local,
27}
28
29#[derive(Debug, Clone)]
31pub struct GizmoConfig {
32 pub mode: GizmoMode,
34 pub space: GizmoSpace,
36 pub visible: bool,
38 pub size: f32,
40 pub snap_translate: f32,
42 pub snap_rotate: f32,
44 pub snap_scale: f32,
46}
47
48impl Default for GizmoConfig {
49 fn default() -> Self {
50 Self {
51 mode: GizmoMode::Translate,
52 space: GizmoSpace::World,
53 visible: true,
54 size: 100.0,
55 snap_translate: 0.0,
56 snap_rotate: 0.0,
57 snap_scale: 0.0,
58 }
59 }
60}
61
62impl GizmoConfig {
63 #[must_use]
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 #[must_use]
71 pub fn with_mode(mut self, mode: GizmoMode) -> Self {
72 self.mode = mode;
73 self
74 }
75
76 #[must_use]
78 pub fn with_space(mut self, space: GizmoSpace) -> Self {
79 self.space = space;
80 self
81 }
82
83 #[must_use]
85 pub fn with_size(mut self, size: f32) -> Self {
86 self.size = size;
87 self
88 }
89
90 #[must_use]
92 pub fn with_snap_translate(mut self, snap: f32) -> Self {
93 self.snap_translate = snap;
94 self
95 }
96
97 #[must_use]
99 pub fn with_snap_rotate(mut self, snap: f32) -> Self {
100 self.snap_rotate = snap;
101 self
102 }
103
104 #[must_use]
106 pub fn with_snap_scale(mut self, snap: f32) -> Self {
107 self.snap_scale = snap;
108 self
109 }
110}
111
112#[derive(Debug, Clone, Copy)]
116pub struct Transform {
117 pub translation: Vec3,
119 pub rotation: Quat,
121 pub scale: Vec3,
123}
124
125impl Default for Transform {
126 fn default() -> Self {
127 Self {
128 translation: Vec3::ZERO,
129 rotation: Quat::IDENTITY,
130 scale: Vec3::ONE,
131 }
132 }
133}
134
135impl Transform {
136 #[must_use]
138 pub fn identity() -> Self {
139 Self::default()
140 }
141
142 #[must_use]
144 pub fn from_translation(translation: Vec3) -> Self {
145 Self {
146 translation,
147 ..Default::default()
148 }
149 }
150
151 #[must_use]
153 pub fn from_rotation(rotation: Quat) -> Self {
154 Self {
155 rotation,
156 ..Default::default()
157 }
158 }
159
160 #[must_use]
162 pub fn from_scale(scale: Vec3) -> Self {
163 Self {
164 scale,
165 ..Default::default()
166 }
167 }
168
169 #[must_use]
173 pub fn from_matrix(matrix: Mat4) -> Self {
174 let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
175 Self {
176 translation,
177 rotation,
178 scale,
179 }
180 }
181
182 #[must_use]
184 pub fn to_matrix(&self) -> Mat4 {
185 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
186 }
187
188 #[must_use]
190 pub fn euler_angles(&self) -> Vec3 {
191 let (x, y, z) = self.rotation.to_euler(glam::EulerRot::XYZ);
192 Vec3::new(x, y, z)
193 }
194
195 pub fn set_euler_angles(&mut self, angles: Vec3) {
197 self.rotation = Quat::from_euler(glam::EulerRot::XYZ, angles.x, angles.y, angles.z);
198 }
199
200 #[must_use]
202 pub fn euler_angles_degrees(&self) -> Vec3 {
203 self.euler_angles() * (180.0 / std::f32::consts::PI)
204 }
205
206 pub fn set_euler_angles_degrees(&mut self, degrees: Vec3) {
208 self.set_euler_angles(degrees * (std::f32::consts::PI / 180.0));
209 }
210
211 pub fn translate(&mut self, delta: Vec3) {
213 self.translation += delta;
214 }
215
216 pub fn rotate(&mut self, delta: Quat) {
218 self.rotation = delta * self.rotation;
219 }
220
221 pub fn scale_by(&mut self, factor: Vec3) {
223 self.scale *= factor;
224 }
225
226 pub fn snap_translation(&mut self, snap: f32) {
228 if snap > 0.0 {
229 self.translation = (self.translation / snap).round() * snap;
230 }
231 }
232
233 pub fn snap_rotation(&mut self, snap_degrees: f32) {
235 if snap_degrees > 0.0 {
236 let mut euler = self.euler_angles_degrees();
237 euler = (euler / snap_degrees).round() * snap_degrees;
238 self.set_euler_angles_degrees(euler);
239 }
240 }
241
242 pub fn snap_scale(&mut self, snap: f32) {
244 if snap > 0.0 {
245 self.scale = (self.scale / snap).round() * snap;
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum GizmoAxis {
253 X,
255 Y,
257 Z,
259 XY,
261 XZ,
263 YZ,
265 All,
267 None,
269}
270
271impl GizmoAxis {
272 #[must_use]
274 pub fn direction(&self) -> Option<Vec3> {
275 match self {
276 GizmoAxis::X => Some(Vec3::X),
277 GizmoAxis::Y => Some(Vec3::Y),
278 GizmoAxis::Z => Some(Vec3::Z),
279 _ => None,
280 }
281 }
282
283 #[must_use]
285 pub fn color(&self) -> Vec3 {
286 match self {
287 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), }
293 }
294}
295
296#[repr(C)]
298#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
299pub struct GizmoUniforms {
300 pub model: [[f32; 4]; 4],
302 pub color: [f32; 3],
304 pub highlighted: f32,
306}
307
308impl Default for GizmoUniforms {
309 fn default() -> Self {
310 Self {
311 model: Mat4::IDENTITY.to_cols_array_2d(),
312 color: [1.0, 1.0, 1.0],
313 highlighted: 0.0,
314 }
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_transform_matrix_roundtrip() {
324 let t = Transform {
325 translation: Vec3::new(1.0, 2.0, 3.0),
326 rotation: Quat::IDENTITY,
327 scale: Vec3::ONE,
328 };
329 let matrix = t.to_matrix();
330 let back = Transform::from_matrix(matrix);
331 assert!((back.translation - t.translation).length() < 1e-6);
332 }
333
334 #[test]
335 fn test_transform_euler_angles() {
336 let mut t = Transform::identity();
337 t.set_euler_angles_degrees(Vec3::new(0.0, 90.0, 0.0));
338 let angles = t.euler_angles_degrees();
339 assert!((angles.y - 90.0).abs() < 0.1);
340 }
341
342 #[test]
343 fn test_snap_translation() {
344 let mut t = Transform::from_translation(Vec3::new(1.2, 2.7, 3.1));
345 t.snap_translation(0.5);
346 assert_eq!(t.translation, Vec3::new(1.0, 2.5, 3.0));
347 }
348}