1use std::ops::{Deref, DerefMut};
2
3pub use ecolor::Color32;
4
5use emath::Rect;
6use enumset::{EnumSet, EnumSetType, enum_set};
7
8use crate::math::{
9 DMat4, DQuat, DVec3, DVec4, Transform, Vec4Swizzles, screen_to_world, world_to_screen,
10};
11
12pub const DEFAULT_SNAP_ANGLE: f32 = std::f32::consts::PI / 32.0;
14pub const DEFAULT_SNAP_DISTANCE: f32 = 0.1;
16pub const DEFAULT_SNAP_SCALE: f32 = 0.1;
18
19#[derive(Debug, Copy, Clone)]
24pub struct GizmoConfig {
25 pub view_matrix: mint::RowMatrix4<f64>,
27 pub projection_matrix: mint::RowMatrix4<f64>,
29 pub viewport: Rect,
31 pub modes: EnumSet<GizmoMode>,
33 pub mode_override: Option<GizmoMode>,
35 pub orientation: GizmoOrientation,
37 pub pivot_point: TransformPivotPoint,
39 pub snapping: bool,
41 pub snap_angle: f32,
43 pub snap_distance: f32,
45 pub snap_scale: f32,
47 pub visuals: GizmoVisuals,
49 pub pixels_per_point: f32,
51}
52
53impl Default for GizmoConfig {
54 fn default() -> Self {
55 Self {
56 view_matrix: DMat4::IDENTITY.into(),
57 projection_matrix: DMat4::IDENTITY.into(),
58 viewport: Rect::NOTHING,
59 modes: GizmoMode::all(),
60 mode_override: None,
61 orientation: GizmoOrientation::default(),
62 pivot_point: TransformPivotPoint::default(),
63 snapping: false,
64 snap_angle: DEFAULT_SNAP_ANGLE,
65 snap_distance: DEFAULT_SNAP_DISTANCE,
66 snap_scale: DEFAULT_SNAP_SCALE,
67 visuals: GizmoVisuals::default(),
68 pixels_per_point: 1.0,
69 }
70 }
71}
72
73impl GizmoConfig {
74 pub(crate) fn view_forward(&self) -> DVec3 {
76 DVec4::from(self.view_matrix.z).xyz()
77 }
78
79 pub(crate) fn view_up(&self) -> DVec3 {
81 DVec4::from(self.view_matrix.y).xyz()
82 }
83
84 pub(crate) fn view_right(&self) -> DVec3 {
86 DVec4::from(self.view_matrix.x).xyz()
87 }
88
89 pub(crate) fn local_space(&self) -> bool {
91 self.orientation() == GizmoOrientation::Local
92 }
93
94 pub(crate) fn orientation(&self) -> GizmoOrientation {
96 self.orientation
97 }
98
99 pub(crate) fn modes_changed(&self, other: &Self) -> bool {
101 (self.modes != other.modes && self.mode_override.is_none())
102 || (self.mode_override != other.mode_override)
103 }
104}
105
106#[derive(Debug, Copy, Clone, Default)]
107pub(crate) struct PreparedGizmoConfig {
108 config: GizmoConfig,
109 pub(crate) rotation: DQuat,
111 pub(crate) translation: DVec3,
113 pub(crate) scale: DVec3,
115 pub(crate) view_projection: DMat4,
117 pub(crate) model_matrix: DMat4,
119 pub(crate) mvp: DMat4,
121 pub(crate) scale_factor: f32,
123 pub(crate) focus_distance: f32,
125 pub(crate) left_handed: bool,
127 pub(crate) eye_to_model_dir: DVec3,
129}
130
131impl Deref for PreparedGizmoConfig {
132 type Target = GizmoConfig;
133
134 fn deref(&self) -> &Self::Target {
135 &self.config
136 }
137}
138
139impl DerefMut for PreparedGizmoConfig {
140 fn deref_mut(&mut self) -> &mut Self::Target {
141 &mut self.config
142 }
143}
144
145impl PreparedGizmoConfig {
146 pub(crate) fn update_for_config(&mut self, config: GizmoConfig) {
147 let projection_matrix = DMat4::from(config.projection_matrix);
148 let view_matrix = DMat4::from(config.view_matrix);
149
150 let view_projection = projection_matrix * view_matrix;
151
152 let left_handed = if projection_matrix.z_axis.w == 0.0 {
153 if projection_matrix.z_axis.z > 0.0 {
158 let vx = view_matrix.x_axis.xyz();
159 let vy = view_matrix.y_axis.xyz();
160 let vz = view_matrix.z_axis.xyz();
161 vx.cross(vy).dot(vz) < 0.0
162 } else {
163 false
164 }
165 } else {
166 projection_matrix.z_axis.w > 0.0
167 };
168
169 self.config = config;
170 self.view_projection = view_projection;
171 self.left_handed = left_handed;
172
173 self.update_transform(Transform {
174 scale: self.scale.into(),
175 rotation: self.rotation.into(),
176 translation: self.translation.into(),
177 });
178 }
179
180 pub(crate) fn update_for_targets(&mut self, targets: &[Transform]) {
181 let mut scale = DVec3::ZERO;
182 let mut translation = DVec3::ZERO;
183 let mut rotation = DQuat::IDENTITY;
184
185 let mut target_count = 0;
186 for target in targets {
187 scale += DVec3::from(target.scale);
188 translation += DVec3::from(target.translation);
189 rotation = DQuat::from(target.rotation);
190
191 target_count += 1;
192 }
193
194 if target_count == 0 {
195 scale = DVec3::ONE;
196 } else {
197 translation /= target_count as f64;
198 scale /= target_count as f64;
199 }
200
201 self.update_transform(Transform {
202 scale: scale.into(),
203 rotation: rotation.into(),
204 translation: translation.into(),
205 });
206 }
207
208 pub(crate) fn update_transform(&mut self, transform: Transform) {
209 self.translation = transform.translation.into();
210 self.rotation = transform.rotation.into();
211 self.scale = transform.scale.into();
212 self.model_matrix =
213 DMat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation);
214 self.mvp = self.view_projection * self.model_matrix;
215
216 self.scale_factor = self.mvp.as_ref()[15] as f32
217 / self.projection_matrix.x.x as f32
218 / self.config.viewport.width()
219 * 2.0;
220
221 let gizmo_screen_pos =
222 world_to_screen(self.config.viewport, self.mvp, self.translation).unwrap_or_default();
223
224 let gizmo_view_near = screen_to_world(
225 self.config.viewport,
226 self.view_projection.inverse(),
227 gizmo_screen_pos,
228 -1.0,
229 );
230
231 self.focus_distance = self.scale_factor * (self.config.visuals.stroke_width / 2.0 + 5.0);
232
233 self.eye_to_model_dir = (gizmo_view_near - self.translation).normalize_or_zero();
234 }
235
236 pub(crate) fn as_transform(&self) -> Transform {
237 Transform {
238 scale: self.scale.into(),
239 rotation: self.rotation.into(),
240 translation: self.translation.into(),
241 }
242 }
243}
244
245#[derive(Debug, EnumSetType, Hash)]
247pub enum GizmoMode {
248 RotateView,
250 RotateX,
252 RotateY,
254 RotateZ,
256 TranslateView,
258 TranslateX,
260 TranslateY,
262 TranslateZ,
264 TranslateXY,
266 TranslateXZ,
268 TranslateYZ,
270 ScaleUniform,
272 ScaleX,
274 ScaleY,
276 ScaleZ,
278 ScaleXY,
280 ScaleXZ,
282 ScaleYZ,
284 Arcball,
286}
287
288impl GizmoMode {
289 pub fn all() -> EnumSet<Self> {
291 EnumSet::all()
292 }
293
294 pub const fn all_rotate() -> EnumSet<Self> {
296 enum_set!(Self::RotateX | Self::RotateY | Self::RotateZ | Self::RotateView)
297 }
298
299 pub const fn all_translate() -> EnumSet<Self> {
301 enum_set!(
302 Self::TranslateX
303 | Self::TranslateY
304 | Self::TranslateZ
305 | Self::TranslateXY
306 | Self::TranslateXZ
307 | Self::TranslateYZ
308 | Self::TranslateView
309 )
310 }
311
312 pub const fn all_scale() -> EnumSet<Self> {
314 enum_set!(
315 Self::ScaleX
316 | Self::ScaleY
317 | Self::ScaleZ
318 | Self::ScaleXY
319 | Self::ScaleXZ
320 | Self::ScaleYZ
321 | Self::ScaleUniform
322 )
323 }
324
325 pub fn is_rotate(&self) -> bool {
327 self.kind() == GizmoModeKind::Rotate
328 }
329
330 pub fn is_translate(&self) -> bool {
332 self.kind() == GizmoModeKind::Translate
333 }
334
335 pub fn is_scale(&self) -> bool {
337 self.kind() == GizmoModeKind::Scale
338 }
339
340 pub fn axes(&self) -> EnumSet<GizmoDirection> {
342 match self {
343 Self::RotateX | Self::TranslateX | Self::ScaleX => {
344 enum_set!(GizmoDirection::X)
345 }
346 Self::RotateY | Self::TranslateY | Self::ScaleY => {
347 enum_set!(GizmoDirection::Y)
348 }
349 Self::RotateZ | Self::TranslateZ | Self::ScaleZ => {
350 enum_set!(GizmoDirection::Z)
351 }
352 Self::RotateView | Self::TranslateView => {
353 enum_set!(GizmoDirection::View)
354 }
355 Self::ScaleUniform | Self::Arcball => {
356 enum_set!(GizmoDirection::X | GizmoDirection::Y | GizmoDirection::Z)
357 }
358 Self::TranslateXY | Self::ScaleXY => {
359 enum_set!(GizmoDirection::X | GizmoDirection::Y)
360 }
361 Self::TranslateXZ | Self::ScaleXZ => {
362 enum_set!(GizmoDirection::X | GizmoDirection::Z)
363 }
364 Self::TranslateYZ | Self::ScaleYZ => {
365 enum_set!(GizmoDirection::Y | GizmoDirection::Z)
366 }
367 }
368 }
369
370 pub fn all_from_axes(axes: EnumSet<GizmoDirection>) -> EnumSet<Self> {
372 EnumSet::<Self>::all()
373 .iter()
374 .filter(|mode| mode.axes() == axes)
375 .collect()
376 }
377
378 pub fn kind(&self) -> GizmoModeKind {
379 match self {
380 Self::RotateX | Self::RotateY | Self::RotateZ | Self::RotateView => {
381 GizmoModeKind::Rotate
382 }
383 Self::TranslateX
384 | Self::TranslateY
385 | Self::TranslateZ
386 | Self::TranslateXY
387 | Self::TranslateXZ
388 | Self::TranslateYZ
389 | Self::TranslateView => GizmoModeKind::Translate,
390 Self::ScaleX
391 | Self::ScaleY
392 | Self::ScaleZ
393 | Self::ScaleXY
394 | Self::ScaleXZ
395 | Self::ScaleYZ
396 | Self::ScaleUniform => GizmoModeKind::Scale,
397 Self::Arcball => GizmoModeKind::Arcball,
398 }
399 }
400
401 pub fn all_from_kind(kind: GizmoModeKind) -> EnumSet<Self> {
402 EnumSet::<Self>::all()
403 .iter()
404 .filter(|mode| mode.kind() == kind)
405 .collect()
406 }
407
408 pub fn from_kind_and_axes(kind: GizmoModeKind, axes: EnumSet<GizmoDirection>) -> Option<Self> {
409 EnumSet::<Self>::all()
410 .iter()
411 .find(|mode| mode.kind() == kind && mode.axes() == axes)
412 }
413}
414
415#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)]
416pub enum GizmoModeKind {
417 Rotate,
418 Translate,
419 Scale,
420 Arcball,
421}
422
423#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
425pub enum TransformPivotPoint {
426 #[default]
428 MedianPoint,
429 IndividualOrigins,
431}
432
433#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
435pub enum GizmoOrientation {
436 #[default]
438 Global,
439 Local,
441}
442
443#[derive(Debug, EnumSetType, Hash)]
444pub enum GizmoDirection {
445 X,
447 Y,
449 Z,
451 View,
453}
454
455#[derive(Debug, Copy, Clone)]
457pub struct GizmoVisuals {
458 pub x_color: Color32,
460 pub y_color: Color32,
462 pub z_color: Color32,
464 pub s_color: Color32,
466 pub inactive_alpha: f32,
468 pub highlight_alpha: f32,
470 pub highlight_color: Option<Color32>,
472 pub stroke_width: f32,
474 pub gizmo_size: f32,
476}
477
478impl Default for GizmoVisuals {
479 fn default() -> Self {
480 Self {
481 x_color: Color32::from_rgb(255, 0, 125),
482 y_color: Color32::from_rgb(0, 255, 125),
483 z_color: Color32::from_rgb(0, 125, 255),
484 s_color: Color32::from_rgb(255, 255, 255),
485 inactive_alpha: 0.7,
486 highlight_alpha: 1.0,
487 highlight_color: None,
488 stroke_width: 4.0,
489 gizmo_size: 75.0,
490 }
491 }
492}