1use ecolor::Rgba;
2use emath::Pos2;
3use enumset::EnumSet;
4use std::ops::{Add, AddAssign, Sub};
5
6use crate::GizmoOrientation;
7use crate::config::{
8 GizmoConfig, GizmoDirection, GizmoMode, PreparedGizmoConfig, TransformPivotPoint,
9};
10use crate::math::{Transform, screen_to_world};
11use epaint::Mesh;
12use glam::{DMat4, DQuat, DVec3};
13
14use crate::subgizmo::rotation::RotationParams;
15use crate::subgizmo::scale::ScaleParams;
16use crate::subgizmo::translation::TranslationParams;
17use crate::subgizmo::{
18 ArcballSubGizmo, RotationSubGizmo, ScaleSubGizmo, SubGizmo, SubGizmoControl,
19 TranslationSubGizmo, common::TransformKind,
20};
21
22#[derive(Clone, Debug, Default)]
24pub struct Gizmo {
25 config: PreparedGizmoConfig,
30 subgizmos: Vec<SubGizmo>,
32 active_subgizmo_id: Option<u64>,
33
34 target_start_transforms: Vec<Transform>,
35
36 gizmo_start_transform: Transform,
37}
38
39impl Gizmo {
40 pub fn new(config: GizmoConfig) -> Self {
42 let mut gizmo = Self::default();
43 gizmo.update_config(config);
44 gizmo
45 }
46
47 pub fn config(&self) -> &GizmoConfig {
49 &self.config
50 }
51
52 pub fn update_config(&mut self, config: GizmoConfig) {
54 if config.modes_changed(&self.config) {
55 self.subgizmos.clear();
56 self.active_subgizmo_id = None;
57 }
58
59 self.config.update_for_config(config);
60
61 if self.subgizmos.is_empty() {
62 self.add_rotation();
63 self.add_translation();
64 self.add_scale();
65 }
66 }
67
68 pub fn is_focused(&self) -> bool {
70 self.subgizmos.iter().any(|subgizmo| subgizmo.is_focused())
71 }
72
73 pub fn update(
108 &mut self,
109 interaction: GizmoInteraction,
110 targets: &[Transform],
111 ) -> Option<(GizmoResult, Vec<Transform>)> {
112 if !self.config.viewport.is_finite() {
113 return None;
114 }
115
116 if self.active_subgizmo_id.is_none() {
119 self.config.update_for_targets(targets);
120 }
121
122 for subgizmo in &mut self.subgizmos {
123 subgizmo.update_config(self.config);
125 subgizmo.set_focused(false);
127 }
128
129 let force_active = self.config.mode_override.is_some();
130
131 let pointer_ray = self.pointer_ray(Pos2::from(interaction.cursor_pos));
132
133 if self.active_subgizmo_id.is_none() && interaction.hovered {
136 if let Some(subgizmo) = self.pick_subgizmo(pointer_ray) {
137 subgizmo.set_focused(true);
138
139 if interaction.drag_started || force_active {
141 self.active_subgizmo_id = Some(subgizmo.id());
142 self.target_start_transforms = targets.to_vec();
143 self.gizmo_start_transform = self.config.as_transform();
144 }
145 }
146 }
147
148 let mut result = None;
149
150 if let Some(subgizmo) = self.active_subgizmo_mut() {
151 if interaction.dragging || force_active {
152 subgizmo.set_active(true);
153 subgizmo.set_focused(true);
154 result = subgizmo.update(pointer_ray);
155 } else {
156 subgizmo.set_active(false);
157 subgizmo.set_focused(false);
158 self.active_subgizmo_id = None;
159 }
160 }
161
162 let Some(result) = result else {
163 self.config.update_for_targets(targets);
166
167 for subgizmo in &mut self.subgizmos {
168 subgizmo.update_config(self.config);
169 }
170
171 return None;
172 };
173
174 self.update_config_with_result(result);
175
176 let updated_targets =
177 self.update_transforms_with_result(result, targets, &self.target_start_transforms);
178
179 Some((result, updated_targets))
180 }
181
182 pub fn draw(&self) -> GizmoDrawData {
186 if !self.config.viewport.is_finite() {
187 return GizmoDrawData::default();
188 }
189
190 let mut draw_data = GizmoDrawData::default();
191 for subgizmo in &self.subgizmos {
192 if self.active_subgizmo_id.is_none() || subgizmo.is_active() {
193 draw_data += subgizmo.draw();
194 }
195 }
196
197 draw_data
198 }
199
200 pub fn pick_preview(&self, cursor_pos: (f32, f32)) -> bool {
202 let pointer_ray = self.pointer_ray(Pos2::from(cursor_pos));
203 self.subgizmos.iter().any(|x| x.pick_preview(pointer_ray))
204 }
205
206 fn active_subgizmo_mut(&mut self) -> Option<&mut SubGizmo> {
207 self.active_subgizmo_id.and_then(|id| {
208 self.subgizmos
209 .iter_mut()
210 .find(|subgizmo| subgizmo.id() == id)
211 })
212 }
213
214 fn update_transforms_with_result(
215 &self,
216 result: GizmoResult,
217 transforms: &[Transform],
218 start_transforms: &[Transform],
219 ) -> Vec<Transform> {
220 transforms
221 .iter()
222 .zip(start_transforms)
223 .map(|(transform, start_transform)| match result {
224 GizmoResult::Rotation {
225 axis,
226 delta,
227 total: _,
228 is_view_axis,
229 } => self.update_rotation(transform, axis, delta, is_view_axis),
230 GizmoResult::Translation { delta, total: _ } => {
231 self.update_translation(delta, transform, start_transform)
232 }
233 GizmoResult::Scale { total } => {
234 self.update_scale(transform, start_transform, total)
235 }
236 GizmoResult::Arcball { delta, total: _ } => {
237 self.update_rotation_quat(transform, delta.into())
238 }
239 })
240 .collect()
241 }
242
243 fn update_rotation(
244 &self,
245 transform: &Transform,
246 axis: mint::Vector3<f64>,
247 delta: f64,
248 is_view_axis: bool,
249 ) -> Transform {
250 let axis = match self.config.orientation() {
251 GizmoOrientation::Local if !is_view_axis => {
252 (DQuat::from(transform.rotation) * DVec3::from(axis)).normalize()
253 }
254 _ => DVec3::from(axis),
255 };
256
257 let delta = DQuat::from_axis_angle(axis, delta);
258
259 self.update_rotation_quat(transform, delta)
260 }
261
262 fn update_rotation_quat(&self, transform: &Transform, delta: DQuat) -> Transform {
263 let translation = match self.config.pivot_point {
264 TransformPivotPoint::MedianPoint => (self.config.translation
265 + delta * (DVec3::from(transform.translation) - self.config.translation))
266 .into(),
267 TransformPivotPoint::IndividualOrigins => transform.translation,
268 };
269
270 let new_rotation = (delta * DQuat::from(transform.rotation)).normalize();
271
272 Transform {
273 scale: transform.scale,
274 rotation: new_rotation.into(),
275 translation,
276 }
277 }
278
279 fn update_translation(
280 &self,
281 delta: mint::Vector3<f64>,
282 transform: &Transform,
283 start_transform: &Transform,
284 ) -> Transform {
285 let delta = match self.config.orientation() {
286 GizmoOrientation::Global => DVec3::from(delta),
287 GizmoOrientation::Local => DQuat::from(start_transform.rotation) * DVec3::from(delta),
288 };
289
290 Transform {
291 scale: start_transform.scale,
292 rotation: start_transform.rotation,
293 translation: (delta + DVec3::from(transform.translation)).into(),
294 }
295 }
296
297 fn update_scale(
298 &self,
299 transform: &Transform,
300 start_transform: &Transform,
301 scale: mint::Vector3<f64>,
302 ) -> Transform {
303 let new_scale = match self.config.orientation() {
304 GizmoOrientation::Global => {
305 let scaled_transform_mat = DMat4::from_scale(scale.into())
306 * DMat4::from_scale_rotation_translation(
307 DVec3::from(start_transform.scale),
308 DQuat::from(start_transform.rotation),
309 DVec3::from(start_transform.translation),
310 );
311 let (scale, _, _) = scaled_transform_mat.to_scale_rotation_translation();
312 scale
313 }
314 GizmoOrientation::Local => DVec3::from(start_transform.scale) * DVec3::from(scale),
315 };
316
317 Transform {
318 scale: new_scale.into(),
319 ..*transform
320 }
321 }
322
323 fn update_config_with_result(&mut self, result: GizmoResult) {
324 let new_config_transform = self.update_transforms_with_result(
325 result,
326 &[self.config.as_transform()],
327 &[self.gizmo_start_transform],
328 )[0];
329
330 self.config.update_transform(new_config_transform);
331 }
332
333 #[allow(clippy::manual_inspect)]
335 fn pick_subgizmo(&mut self, ray: Ray) -> Option<&mut SubGizmo> {
336 if self.config.mode_override.is_some() {
338 return self.subgizmos.first_mut().map(|subgizmo| {
339 subgizmo.pick(ray);
340
341 subgizmo
342 });
343 }
344
345 self.subgizmos
346 .iter_mut()
347 .filter_map(|subgizmo| subgizmo.pick(ray).map(|t| (t, subgizmo)))
348 .min_by(|(first, _), (second, _)| {
349 first
350 .partial_cmp(second)
351 .unwrap_or(std::cmp::Ordering::Equal)
352 })
353 .map(|(_, subgizmo)| subgizmo)
354 }
355
356 fn enabled_modes(&self) -> EnumSet<GizmoMode> {
358 self.config
359 .mode_override
360 .map_or(self.config.modes, EnumSet::only)
361 }
362
363 fn add_rotation(&mut self) {
365 let modes = self.enabled_modes();
366
367 if modes.contains(GizmoMode::RotateX) {
368 self.subgizmos.push(
369 RotationSubGizmo::new(
370 self.config,
371 RotationParams {
372 direction: GizmoDirection::X,
373 },
374 )
375 .into(),
376 );
377 }
378
379 if modes.contains(GizmoMode::RotateY) {
380 self.subgizmos.push(
381 RotationSubGizmo::new(
382 self.config,
383 RotationParams {
384 direction: GizmoDirection::Y,
385 },
386 )
387 .into(),
388 );
389 }
390
391 if modes.contains(GizmoMode::RotateZ) {
392 self.subgizmos.push(
393 RotationSubGizmo::new(
394 self.config,
395 RotationParams {
396 direction: GizmoDirection::Z,
397 },
398 )
399 .into(),
400 );
401 }
402
403 if modes.contains(GizmoMode::RotateView) {
404 self.subgizmos.push(
405 RotationSubGizmo::new(
406 self.config,
407 RotationParams {
408 direction: GizmoDirection::View,
409 },
410 )
411 .into(),
412 );
413 }
414
415 if modes.contains(GizmoMode::Arcball) {
416 self.subgizmos
417 .push(ArcballSubGizmo::new(self.config, ()).into());
418 }
419 }
420
421 fn add_translation(&mut self) {
423 let modes = self.enabled_modes();
424
425 if modes.contains(GizmoMode::TranslateX) {
426 self.subgizmos.push(
427 TranslationSubGizmo::new(
428 self.config,
429 TranslationParams {
430 mode: GizmoMode::TranslateX,
431 direction: GizmoDirection::X,
432 transform_kind: TransformKind::Axis,
433 },
434 )
435 .into(),
436 );
437 }
438
439 if modes.contains(GizmoMode::TranslateY) {
440 self.subgizmos.push(
441 TranslationSubGizmo::new(
442 self.config,
443 TranslationParams {
444 mode: GizmoMode::TranslateY,
445 direction: GizmoDirection::Y,
446 transform_kind: TransformKind::Axis,
447 },
448 )
449 .into(),
450 );
451 }
452
453 if modes.contains(GizmoMode::TranslateZ) {
454 self.subgizmos.push(
455 TranslationSubGizmo::new(
456 self.config,
457 TranslationParams {
458 mode: GizmoMode::TranslateZ,
459 direction: GizmoDirection::Z,
460 transform_kind: TransformKind::Axis,
461 },
462 )
463 .into(),
464 );
465 }
466
467 if modes.contains(GizmoMode::TranslateView) {
468 self.subgizmos.push(
469 TranslationSubGizmo::new(
470 self.config,
471 TranslationParams {
472 mode: GizmoMode::TranslateView,
473 direction: GizmoDirection::View,
474 transform_kind: TransformKind::Plane,
475 },
476 )
477 .into(),
478 );
479 }
480
481 if modes.contains(GizmoMode::TranslateXY) {
482 self.subgizmos.push(
483 TranslationSubGizmo::new(
484 self.config,
485 TranslationParams {
486 mode: GizmoMode::TranslateXY,
487 direction: GizmoDirection::X,
488 transform_kind: TransformKind::Plane,
489 },
490 )
491 .into(),
492 );
493 }
494
495 if modes.contains(GizmoMode::TranslateXZ) {
496 self.subgizmos.push(
497 TranslationSubGizmo::new(
498 self.config,
499 TranslationParams {
500 mode: GizmoMode::TranslateXZ,
501 direction: GizmoDirection::Y,
502 transform_kind: TransformKind::Plane,
503 },
504 )
505 .into(),
506 );
507 }
508
509 if modes.contains(GizmoMode::TranslateYZ) {
510 self.subgizmos.push(
511 TranslationSubGizmo::new(
512 self.config,
513 TranslationParams {
514 mode: GizmoMode::TranslateYZ,
515 direction: GizmoDirection::Z,
516 transform_kind: TransformKind::Plane,
517 },
518 )
519 .into(),
520 );
521 }
522 }
523
524 fn add_scale(&mut self) {
526 let modes = self.enabled_modes();
527
528 if modes.contains(GizmoMode::ScaleX) {
529 self.subgizmos.push(
530 ScaleSubGizmo::new(
531 self.config,
532 ScaleParams {
533 mode: GizmoMode::ScaleX,
534 direction: GizmoDirection::X,
535 transform_kind: TransformKind::Axis,
536 },
537 )
538 .into(),
539 );
540 }
541
542 if modes.contains(GizmoMode::ScaleY) {
543 self.subgizmos.push(
544 ScaleSubGizmo::new(
545 self.config,
546 ScaleParams {
547 mode: GizmoMode::ScaleY,
548 direction: GizmoDirection::Y,
549 transform_kind: TransformKind::Axis,
550 },
551 )
552 .into(),
553 );
554 }
555
556 if modes.contains(GizmoMode::ScaleZ) {
557 self.subgizmos.push(
558 ScaleSubGizmo::new(
559 self.config,
560 ScaleParams {
561 mode: GizmoMode::ScaleZ,
562 direction: GizmoDirection::Z,
563 transform_kind: TransformKind::Axis,
564 },
565 )
566 .into(),
567 );
568 }
569
570 if modes.contains(GizmoMode::ScaleUniform) && !modes.contains(GizmoMode::RotateView) {
571 self.subgizmos.push(
572 ScaleSubGizmo::new(
573 self.config,
574 ScaleParams {
575 mode: GizmoMode::ScaleUniform,
576 direction: GizmoDirection::View,
577 transform_kind: TransformKind::Plane,
578 },
579 )
580 .into(),
581 );
582 }
583
584 if modes.contains(GizmoMode::ScaleXY) && !modes.contains(GizmoMode::TranslateXY) {
585 self.subgizmos.push(
586 ScaleSubGizmo::new(
587 self.config,
588 ScaleParams {
589 mode: GizmoMode::ScaleXY,
590 direction: GizmoDirection::X,
591 transform_kind: TransformKind::Plane,
592 },
593 )
594 .into(),
595 );
596 }
597
598 if modes.contains(GizmoMode::ScaleXZ) && !modes.contains(GizmoMode::TranslateXZ) {
599 self.subgizmos.push(
600 ScaleSubGizmo::new(
601 self.config,
602 ScaleParams {
603 mode: GizmoMode::ScaleXZ,
604 direction: GizmoDirection::Y,
605 transform_kind: TransformKind::Plane,
606 },
607 )
608 .into(),
609 );
610 }
611
612 if modes.contains(GizmoMode::ScaleYZ) && !modes.contains(GizmoMode::TranslateYZ) {
613 self.subgizmos.push(
614 ScaleSubGizmo::new(
615 self.config,
616 ScaleParams {
617 mode: GizmoMode::ScaleYZ,
618 direction: GizmoDirection::Z,
619 transform_kind: TransformKind::Plane,
620 },
621 )
622 .into(),
623 );
624 }
625 }
626
627 fn pointer_ray(&self, screen_pos: Pos2) -> Ray {
629 let mat = self.config.view_projection.inverse();
630 let origin = screen_to_world(self.config.viewport, mat, screen_pos, -1.0);
631 let target = screen_to_world(self.config.viewport, mat, screen_pos, 1.0);
632
633 let direction = target.sub(origin).normalize();
634
635 Ray {
636 screen_pos,
637 origin,
638 direction,
639 }
640 }
641}
642
643#[derive(Default, Clone, Copy, Debug)]
645pub struct GizmoInteraction {
646 pub cursor_pos: (f32, f32),
648 pub hovered: bool,
653 pub drag_started: bool,
657 pub dragging: bool,
661}
662
663#[derive(Debug, Copy, Clone)]
665pub enum GizmoResult {
666 Rotation {
667 axis: mint::Vector3<f64>,
669 delta: f64,
671 total: f64,
673 is_view_axis: bool,
675 },
676 Translation {
677 delta: mint::Vector3<f64>,
679 total: mint::Vector3<f64>,
681 },
682 Scale {
683 total: mint::Vector3<f64>,
685 },
686 Arcball {
687 delta: mint::Quaternion<f64>,
689 total: mint::Quaternion<f64>,
691 },
692}
693
694#[derive(Default, Clone, Debug)]
696pub struct GizmoDrawData {
697 pub vertices: Vec<[f32; 2]>,
699 pub colors: Vec<[f32; 4]>,
701 pub indices: Vec<u32>,
703}
704
705impl From<Mesh> for GizmoDrawData {
706 fn from(mesh: Mesh) -> Self {
707 let (vertices, colors): (Vec<_>, Vec<_>) = mesh
708 .vertices
709 .iter()
710 .map(|vertex| {
711 (
712 [vertex.pos.x, vertex.pos.y],
713 Rgba::from(vertex.color).to_array(),
714 )
715 })
716 .unzip();
717
718 Self {
719 vertices,
720 colors,
721 indices: mesh.indices,
722 }
723 }
724}
725
726impl AddAssign for GizmoDrawData {
727 fn add_assign(&mut self, rhs: Self) {
728 let index_offset = self.vertices.len() as u32;
729 self.vertices.extend(rhs.vertices);
730 self.colors.extend(rhs.colors);
731 self.indices
732 .extend(rhs.indices.into_iter().map(|idx| index_offset + idx));
733 }
734}
735
736impl Add for GizmoDrawData {
737 type Output = Self;
738
739 fn add(mut self, rhs: Self) -> Self::Output {
740 self += rhs;
741 self
742 }
743}
744
745#[derive(Debug, Copy, Clone)]
746pub(crate) struct Ray {
747 pub(crate) screen_pos: Pos2,
748 pub(crate) origin: DVec3,
749 pub(crate) direction: DVec3,
750}