1use std::cmp::Ordering;
26use std::f32::consts::PI;
27use std::hash::Hash;
28use std::ops::Sub;
29
30use crate::math::{screen_to_world, world_to_screen};
31use egui::{Color32, Context, Id, PointerButton, Pos2, Rect, Sense, Ui};
32use glam::{DMat4, DQuat, DVec3, Mat4, Quat, Vec3, Vec4Swizzles};
33
34use crate::subgizmo::rotation::RotationParams;
35use crate::subgizmo::scale::ScaleParams;
36use crate::subgizmo::translation::TranslationParams;
37use crate::subgizmo::{
38 ArcballSubGizmo, RotationSubGizmo, ScaleSubGizmo, SubGizmo, TransformKind, TranslationSubGizmo,
39};
40
41mod math;
42mod painter;
43mod subgizmo;
44
45pub const DEFAULT_SNAP_ANGLE: f32 = PI / 32.0;
47pub const DEFAULT_SNAP_DISTANCE: f32 = 0.1;
49pub const DEFAULT_SNAP_SCALE: f32 = 0.1;
51
52pub struct Gizmo {
53 id: Id,
54 config: GizmoConfig,
55 subgizmos: Vec<Box<dyn SubGizmo>>,
56}
57
58impl Gizmo {
59 pub fn new(id_source: impl Hash) -> Self {
60 Self {
61 id: Id::new(id_source),
62 config: GizmoConfig::default(),
63 subgizmos: Default::default(),
64 }
65 }
66
67 pub fn model_matrix(mut self, model_matrix: mint::ColumnMatrix4<f32>) -> Self {
69 self.config.model_matrix = Mat4::from(model_matrix).as_dmat4();
70 self
71 }
72
73 pub fn view_matrix(mut self, view_matrix: mint::ColumnMatrix4<f32>) -> Self {
75 self.config.view_matrix = Mat4::from(view_matrix).as_dmat4();
76 self
77 }
78
79 pub fn projection_matrix(mut self, projection_matrix: mint::ColumnMatrix4<f32>) -> Self {
81 self.config.projection_matrix = Mat4::from(projection_matrix).as_dmat4();
82 self
83 }
84
85 pub const fn viewport(mut self, viewport: Rect) -> Self {
87 self.config.viewport = viewport;
88 self
89 }
90
91 pub const fn mode(mut self, mode: GizmoMode) -> Self {
93 self.config.mode = mode;
94 self
95 }
96
97 pub const fn orientation(mut self, orientation: GizmoOrientation) -> Self {
99 self.config.orientation = orientation;
100 self
101 }
102
103 pub const fn snapping(mut self, snapping: bool) -> Self {
105 self.config.snapping = snapping;
106 self
107 }
108
109 pub const fn snap_angle(mut self, snap_angle: f32) -> Self {
111 self.config.snap_angle = snap_angle;
112 self
113 }
114
115 pub const fn snap_distance(mut self, snap_distance: f32) -> Self {
117 self.config.snap_distance = snap_distance;
118 self
119 }
120
121 pub const fn snap_scale(mut self, snap_scale: f32) -> Self {
123 self.config.snap_scale = snap_scale;
124 self
125 }
126
127 pub const fn visuals(mut self, visuals: GizmoVisuals) -> Self {
129 self.config.visuals = visuals;
130 self
131 }
132
133 pub fn interact(mut self, ui: &mut Ui) -> Option<GizmoResult> {
138 self.config.prepare(ui);
139
140 match self.config.mode {
142 GizmoMode::Rotate => {
143 self.add_subgizmos(self.new_rotation());
144 self.add_subgizmos(self.new_arcball());
145 }
146 GizmoMode::Translate => self.add_subgizmos(self.new_translation()),
147 GizmoMode::Scale => self.add_subgizmos(self.new_scale()),
148 };
149
150 let mut result = None;
151 let mut active_subgizmo = None;
152 let mut state = GizmoState::load(ui.ctx(), self.id);
153
154 if let Some(pointer_ray) = self.pointer_ray(ui) {
155 let viewport = self.config.viewport;
156 let id = self.id;
157
158 if state.active_subgizmo_id.is_none() {
161 if let Some(subgizmo) = self.pick_subgizmo(ui, pointer_ray) {
162 subgizmo.set_focused(true);
163
164 let interaction = ui.interact(viewport, id, Sense::click_and_drag());
165 let dragging = interaction.dragged_by(PointerButton::Primary);
166 if interaction.drag_started() && dragging {
167 state.active_subgizmo_id = Some(subgizmo.id());
168 }
169 }
170 }
171
172 active_subgizmo = state.active_subgizmo_id.and_then(|id| {
173 self.subgizmos
174 .iter_mut()
175 .find(|subgizmo| subgizmo.id() == id)
176 });
177
178 if let Some(subgizmo) = active_subgizmo.as_mut() {
179 if ui.input(|i| i.pointer.primary_down()) {
180 subgizmo.set_active(true);
181 subgizmo.set_focused(true);
182 result = subgizmo.update(ui, pointer_ray);
183 } else {
184 state.active_subgizmo_id = None;
185 }
186 }
187 }
188
189 if let Some((_, result)) = active_subgizmo.zip(result) {
190 self.config.translation = Vec3::from(result.translation).as_dvec3();
191 self.config.rotation = Quat::from(result.rotation).as_dquat();
192 self.config.scale = Vec3::from(result.scale).as_dvec3();
193 }
194
195 state.save(ui.ctx(), self.id);
196
197 self.draw_subgizmos(ui, &mut state);
198
199 result
200 }
201
202 fn draw_subgizmos(&mut self, ui: &mut Ui, state: &mut GizmoState) {
203 for subgizmo in &mut self.subgizmos {
204 if state.active_subgizmo_id.is_none() || subgizmo.is_active() {
205 subgizmo.draw(ui);
206 }
207 }
208 }
209
210 fn pick_subgizmo(&mut self, ui: &Ui, ray: Ray) -> Option<&mut Box<dyn SubGizmo>> {
212 self.subgizmos
213 .iter_mut()
214 .filter_map(|subgizmo| subgizmo.pick(ui, ray).map(|t| (t, subgizmo)))
215 .min_by(|(first, _), (second, _)| first.partial_cmp(second).unwrap_or(Ordering::Equal))
216 .map(|(_, subgizmo)| subgizmo)
217 }
218
219 fn new_arcball(&self) -> [ArcballSubGizmo; 1] {
221 [ArcballSubGizmo::new(self.id.with("arc"), self.config, ())]
222 }
223
224 fn new_rotation(&self) -> [RotationSubGizmo; 4] {
226 [
227 RotationSubGizmo::new(
228 self.id.with("rx"),
229 self.config,
230 RotationParams {
231 direction: GizmoDirection::X,
232 },
233 ),
234 RotationSubGizmo::new(
235 self.id.with("ry"),
236 self.config,
237 RotationParams {
238 direction: GizmoDirection::Y,
239 },
240 ),
241 RotationSubGizmo::new(
242 self.id.with("rz"),
243 self.config,
244 RotationParams {
245 direction: GizmoDirection::Z,
246 },
247 ),
248 RotationSubGizmo::new(
249 self.id.with("rs"),
250 self.config,
251 RotationParams {
252 direction: GizmoDirection::View,
253 },
254 ),
255 ]
256 }
257
258 fn new_translation(&self) -> [TranslationSubGizmo; 7] {
260 [
261 TranslationSubGizmo::new(
262 self.id.with("txs"),
263 self.config,
264 TranslationParams {
265 direction: GizmoDirection::View,
266 transform_kind: TransformKind::Plane,
267 },
268 ),
269 TranslationSubGizmo::new(
270 self.id.with("tx"),
271 self.config,
272 TranslationParams {
273 direction: GizmoDirection::X,
274 transform_kind: TransformKind::Axis,
275 },
276 ),
277 TranslationSubGizmo::new(
278 self.id.with("ty"),
279 self.config,
280 TranslationParams {
281 direction: GizmoDirection::Y,
282 transform_kind: TransformKind::Axis,
283 },
284 ),
285 TranslationSubGizmo::new(
286 self.id.with("tz"),
287 self.config,
288 TranslationParams {
289 direction: GizmoDirection::Z,
290 transform_kind: TransformKind::Axis,
291 },
292 ),
293 TranslationSubGizmo::new(
294 self.id.with("tyz"),
295 self.config,
296 TranslationParams {
297 direction: GizmoDirection::X,
298 transform_kind: TransformKind::Plane,
299 },
300 ),
301 TranslationSubGizmo::new(
302 self.id.with("txz"),
303 self.config,
304 TranslationParams {
305 direction: GizmoDirection::Y,
306 transform_kind: TransformKind::Plane,
307 },
308 ),
309 TranslationSubGizmo::new(
310 self.id.with("txy"),
311 self.config,
312 TranslationParams {
313 direction: GizmoDirection::Z,
314 transform_kind: TransformKind::Plane,
315 },
316 ),
317 ]
318 }
319
320 fn new_scale(&self) -> [ScaleSubGizmo; 7] {
322 [
323 ScaleSubGizmo::new(
324 self.id.with("txs"),
325 self.config,
326 ScaleParams {
327 direction: GizmoDirection::View,
328 transform_kind: TransformKind::Plane,
329 },
330 ),
331 ScaleSubGizmo::new(
332 self.id.with("sx"),
333 self.config,
334 ScaleParams {
335 direction: GizmoDirection::X,
336 transform_kind: TransformKind::Axis,
337 },
338 ),
339 ScaleSubGizmo::new(
340 self.id.with("sy"),
341 self.config,
342 ScaleParams {
343 direction: GizmoDirection::Y,
344 transform_kind: TransformKind::Axis,
345 },
346 ),
347 ScaleSubGizmo::new(
348 self.id.with("sz"),
349 self.config,
350 ScaleParams {
351 direction: GizmoDirection::Z,
352 transform_kind: TransformKind::Axis,
353 },
354 ),
355 ScaleSubGizmo::new(
356 self.id.with("syz"),
357 self.config,
358 ScaleParams {
359 direction: GizmoDirection::X,
360 transform_kind: TransformKind::Plane,
361 },
362 ),
363 ScaleSubGizmo::new(
364 self.id.with("sxz"),
365 self.config,
366 ScaleParams {
367 direction: GizmoDirection::Y,
368 transform_kind: TransformKind::Plane,
369 },
370 ),
371 ScaleSubGizmo::new(
372 self.id.with("sxy"),
373 self.config,
374 ScaleParams {
375 direction: GizmoDirection::Z,
376 transform_kind: TransformKind::Plane,
377 },
378 ),
379 ]
380 }
381
382 fn add_subgizmos<T: SubGizmo, const N: usize>(&mut self, subgizmos: [T; N]) {
384 for subgizmo in subgizmos {
385 self.subgizmos.push(Box::new(subgizmo));
386 }
387 }
388
389 fn pointer_ray(&self, ui: &Ui) -> Option<Ray> {
391 let screen_pos = ui.input(|i| i.pointer.hover_pos())?;
392
393 let mat = self.config.view_projection.inverse();
394 let origin = screen_to_world(self.config.viewport, mat, screen_pos, -1.0);
395 let target = screen_to_world(self.config.viewport, mat, screen_pos, 1.0);
396
397 let direction = target.sub(origin).normalize();
398
399 Some(Ray {
400 screen_pos,
401 origin,
402 direction,
403 })
404 }
405}
406
407#[derive(Debug, Copy, Clone)]
409pub struct GizmoResult {
410 pub scale: mint::Vector3<f32>,
412 pub rotation: mint::Quaternion<f32>,
414 pub translation: mint::Vector3<f32>,
416 pub mode: GizmoMode,
418 pub value: Option<[f32; 3]>,
420}
421
422impl GizmoResult {
423 pub fn transform(&self) -> mint::ColumnMatrix4<f32> {
425 Mat4::from_scale_rotation_translation(
426 self.scale.into(),
427 self.rotation.into(),
428 self.translation.into(),
429 )
430 .into()
431 }
432}
433
434#[derive(Debug, Copy, Clone, Eq, PartialEq)]
435pub enum GizmoMode {
436 Rotate,
438 Translate,
440 Scale,
442}
443
444#[derive(Debug, Copy, Clone, Eq, PartialEq)]
445pub enum GizmoOrientation {
446 Global,
449 Local,
452}
453
454#[derive(Debug, Copy, Clone, Eq, PartialEq)]
455pub enum GizmoDirection {
456 X,
458 Y,
460 Z,
462 View,
464}
465
466#[derive(Debug, Copy, Clone)]
468pub struct GizmoVisuals {
469 pub x_color: Color32,
471 pub y_color: Color32,
473 pub z_color: Color32,
475 pub s_color: Color32,
477 pub inactive_alpha: f32,
479 pub highlight_alpha: f32,
481 pub highlight_color: Option<Color32>,
483 pub stroke_width: f32,
485 pub gizmo_size: f32,
487}
488
489impl Default for GizmoVisuals {
490 fn default() -> Self {
491 Self {
492 x_color: Color32::from_rgb(255, 50, 0),
493 y_color: Color32::from_rgb(50, 255, 0),
494 z_color: Color32::from_rgb(0, 50, 255),
495 s_color: Color32::from_rgb(255, 255, 255),
496 inactive_alpha: 0.5,
497 highlight_alpha: 0.9,
498 highlight_color: None,
499 stroke_width: 4.0,
500 gizmo_size: 75.0,
501 }
502 }
503}
504
505#[derive(Debug, Copy, Clone)]
506pub(crate) struct GizmoConfig {
507 pub view_matrix: DMat4,
508 pub projection_matrix: DMat4,
509 pub model_matrix: DMat4,
510 pub viewport: Rect,
511 pub mode: GizmoMode,
512 pub orientation: GizmoOrientation,
513 pub snapping: bool,
514 pub snap_angle: f32,
515 pub snap_distance: f32,
516 pub snap_scale: f32,
517 pub visuals: GizmoVisuals,
518 pub rotation: DQuat,
520 pub translation: DVec3,
521 pub scale: DVec3,
522 pub view_projection: DMat4,
523 pub mvp: DMat4,
524 pub gizmo_view_forward: DVec3,
525 pub scale_factor: f32,
526 pub focus_distance: f32,
528 pub left_handed: bool,
529}
530
531impl Default for GizmoConfig {
532 fn default() -> Self {
533 Self {
534 view_matrix: DMat4::IDENTITY,
535 projection_matrix: DMat4::IDENTITY,
536 model_matrix: DMat4::IDENTITY,
537 viewport: Rect::NOTHING,
538 mode: GizmoMode::Rotate,
539 orientation: GizmoOrientation::Global,
540 snapping: false,
541 snap_angle: DEFAULT_SNAP_ANGLE,
542 snap_distance: DEFAULT_SNAP_DISTANCE,
543 snap_scale: DEFAULT_SNAP_SCALE,
544 visuals: GizmoVisuals::default(),
545 rotation: DQuat::IDENTITY,
547 translation: DVec3::ZERO,
548 scale: DVec3::ONE,
549 view_projection: DMat4::IDENTITY,
550 mvp: DMat4::IDENTITY,
551 gizmo_view_forward: DVec3::ONE,
552 scale_factor: 0.0,
553 focus_distance: 0.0,
554 left_handed: false,
555 }
556 }
557}
558
559impl GizmoConfig {
560 fn prepare(&mut self, ui: &Ui) {
563 if self.viewport.is_negative() {
565 self.viewport = ui.clip_rect();
566 }
567
568 let (scale, rotation, translation) = self.model_matrix.to_scale_rotation_translation();
569 self.rotation = rotation;
570 self.translation = translation;
571 self.scale = scale;
572 self.view_projection = self.projection_matrix * self.view_matrix;
573 self.mvp = self.projection_matrix * self.view_matrix * self.model_matrix;
574
575 self.scale_factor = self.mvp.as_ref()[15] as f32
576 / self.projection_matrix.as_ref()[0] as f32
577 / self.viewport.width()
578 * 2.0;
579
580 self.focus_distance = self.scale_factor * (self.visuals.stroke_width / 2.0 + 5.0);
581
582 self.left_handed = if self.projection_matrix.z_axis.w == 0.0 {
583 self.projection_matrix.z_axis.z > 0.0
584 } else {
585 self.projection_matrix.z_axis.w > 0.0
586 };
587
588 let gizmo_screen_pos =
589 world_to_screen(self.viewport, self.mvp, self.translation).unwrap_or_default();
590
591 let gizmo_view_near = screen_to_world(
592 self.viewport,
593 self.view_projection.inverse(),
594 gizmo_screen_pos,
595 -1.0,
596 );
597
598 self.gizmo_view_forward = (gizmo_view_near - self.translation).normalize_or_zero();
599 }
600
601 pub(crate) fn view_forward(&self) -> DVec3 {
603 self.view_matrix.row(2).xyz()
604 }
605
606 pub(crate) fn view_up(&self) -> DVec3 {
608 self.view_matrix.row(1).xyz()
609 }
610
611 pub(crate) fn view_right(&self) -> DVec3 {
613 self.view_matrix.row(0).xyz()
614 }
615
616 pub(crate) fn local_space(&self) -> bool {
618 self.orientation == GizmoOrientation::Local || self.mode == GizmoMode::Scale
620 }
621}
622
623#[derive(Debug, Copy, Clone)]
624pub(crate) struct Ray {
625 screen_pos: Pos2,
626 origin: DVec3,
627 direction: DVec3,
628}
629
630#[derive(Default, Debug, Copy, Clone)]
632struct GizmoState {
633 active_subgizmo_id: Option<Id>,
634}
635
636pub(crate) trait WidgetData: Sized + Default + Copy + Clone + Send + Sync + 'static {
637 fn load(ctx: &Context, gizmo_id: Id) -> Self {
638 ctx.memory_mut(|mem| *mem.data.get_temp_mut_or_default::<Self>(gizmo_id))
639 }
640
641 fn save(self, ctx: &Context, gizmo_id: Id) {
642 ctx.memory_mut(|mem| mem.data.insert_temp(gizmo_id, self));
643 }
644}
645
646impl WidgetData for GizmoState {}