diff --git a/studio/src/interaction/gizmos.rs b/studio/src/interaction/gizmos.rs index d3c57f0..8579b54 100644 --- a/studio/src/interaction/gizmos.rs +++ b/studio/src/interaction/gizmos.rs @@ -6,11 +6,17 @@ use crate::design_system; use super::{InteractionMode, InteractionTransform, Selected, TransformSpace}; +#[derive(Clone, Copy)] +enum GizmoVariant { + Full, + Partial, +} + pub(super) fn draw( q_selection: Query<&GlobalTransform, With>, q_camera_xform: Query<&Transform, With>, q_window: Query<&Window, With>, - interaction_mode: Res, + mode: Res, mut gizmos: Gizmos, mut config: ResMut, ) { @@ -21,7 +27,6 @@ pub(super) fn draw( config.line_perspective = false; config.depth_bias = -1.; - let InteractionMode { transform, space } = *interaction_mode; let camera_xform = q_camera_xform.single(); // TODO: We only want to render one gizmo -- this is just for testing @@ -33,40 +38,83 @@ pub(super) fn draw( // you dial the camera's focal length way down, so I'm gonna call it // good enough. let dist = Vec3::distance(camera_xform.translation, xform.translation); + let size = dist / 20.; - if transform.contains(InteractionTransform::TRANSLATE) { - draw_translate_gizmo(&xform, &mut gizmos, space, dist / 12.5); + let variant = if mode + .transform + .contains(InteractionTransform::TRANSLATE | InteractionTransform::ROTATE) + { + GizmoVariant::Partial + } else { + GizmoVariant::Full + }; + + if mode.transform.contains(InteractionTransform::TRANSLATE) { + draw_translate_gizmo(&mut gizmos, variant, &xform, mode.space, size); } - if transform.contains(InteractionTransform::ROTATE) { - draw_rotate_gizmo(&xform, camera_xform, &mut gizmos, space, dist / 20.); + if mode.transform.contains(InteractionTransform::ROTATE) { + draw_rotate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size); } } } } +// FIXME: We might need more control over the Z-ordering / depth bias than the +// Bevy gizmos give us. :/ + #[inline] -fn draw_translate_gizmo(xform: &Transform, gizmos: &mut Gizmos, space: TransformSpace, size: f32) { +fn draw_translate_gizmo( + gizmos: &mut Gizmos, + variant: GizmoVariant, + xform: &Transform, + space: TransformSpace, + size: f32, +) { let origin = xform.translation; let basis = match space { TransformSpace::Local => xform, TransformSpace::World => &Transform::IDENTITY, }; - let mut draw_axis = |direction: Vec3, color: Color| { - let end = origin + direction * size; - gizmos.line(origin, end, color); - }; + match variant { + GizmoVariant::Full => { + gizmos.sphere(origin, Quat::IDENTITY, size * 0.125, Color::WHITE); - draw_axis(basis.local_x(), design_system::Color::RED); - draw_axis(basis.local_y(), design_system::Color::GREEN); - draw_axis(basis.local_z(), design_system::Color::BLUE); + let mut draw_axis = |direction: Vec3, color: Color| { + let end = origin + direction * size * 1.4; + gizmos.line(origin, end, color); + + let cone_base = origin + direction * size; + let radius = size * 0.1; + let length = size * 0.4; + gizmos.cone(cone_base, direction, radius, length, color); + }; + + draw_axis(basis.local_x(), design_system::Color::RED); + draw_axis(basis.local_y(), design_system::Color::GREEN); + draw_axis(basis.local_z(), design_system::Color::BLUE); + } + GizmoVariant::Partial => { + let mut draw_axis = |direction: Vec3, color: Color| { + let cone_base = origin + direction * size * 1.4; + let radius = size * 0.1; + let length = size * 0.4; + gizmos.cone(cone_base, direction, radius, length, color); + }; + + draw_axis(basis.local_x(), design_system::Color::RED); + draw_axis(basis.local_y(), design_system::Color::GREEN); + draw_axis(basis.local_z(), design_system::Color::BLUE); + } + } } #[inline] fn draw_rotate_gizmo( + gizmos: &mut Gizmos, + variant: GizmoVariant, xform: &Transform, camera_xform: &Transform, - gizmos: &mut Gizmos, space: TransformSpace, radius: f32, ) { @@ -75,32 +123,66 @@ fn draw_rotate_gizmo( TransformSpace::Local => xform, TransformSpace::World => &Transform::IDENTITY, }; + let camera_fwd = camera_xform.forward(); // Draw a screen-space rotator ring gizmos.circle(origin, camera_xform.forward(), radius * 1.2, Color::WHITE); - // Draw a semicircle for each of the X, Y and Z axes + draw_rotator_axis_rings( + gizmos, + RotatorRings::Near, + origin, + camera_fwd, + basis, + radius, + ); + + if matches!(variant, GizmoVariant::Full) { + draw_rotator_axis_rings(gizmos, RotatorRings::Far, origin, camera_fwd, basis, radius); + } +} + +enum RotatorRings { + Near, + Far, +} + +#[inline] +fn draw_rotator_axis_rings( + gizmos: &mut Gizmos, + which: RotatorRings, + origin: Vec3, + camera_fwd: Vec3, + basis: &Transform, + radius: f32, +) { for (normal, color) in [ (basis.local_x(), design_system::Color::RED), (basis.local_y(), design_system::Color::GREEN), (basis.local_z(), design_system::Color::BLUE), ] { - let perp = Vec3::cross(normal, camera_xform.forward()).normalize(); - let start = origin + perp * radius; + let (color, perp) = match which { + RotatorRings::Near => (color, Vec3::cross(normal, camera_fwd)), + RotatorRings::Far => (color.with_a(0.2), Vec3::cross(camera_fwd, normal)), + }; + + let start = origin + perp.normalize() * radius; gizmos.arc(origin, normal, start, consts::PI, color); } } -trait Arc { +trait GizmoExtras { /// Draw an arc around `center` with angular `length` (in radians), /// beginning at `point` and progressing counter-clockwise around `axis` /// (if axis were pointed toward the viewer). The radius is derived from the /// distance between `center` and `start`. fn arc(&mut self, center: Vec3, axis: Vec3, start: Vec3, length: f32, color: Color); + + fn cone(&mut self, base: Vec3, normal: Vec3, radius: f32, length: f32, color: Color); } -impl<'a> Arc for Gizmos<'a> { +impl<'a> GizmoExtras for Gizmos<'a> { fn arc(&mut self, center: Vec3, normal: Vec3, start: Vec3, length: f32, color: Color) { let two_pi = consts::PI * 2.; let segments = f32::round(32. * (length / two_pi)); @@ -114,4 +196,21 @@ impl<'a> Arc for Gizmos<'a> { vector = next; } } + + fn cone(&mut self, base: Vec3, normal: Vec3, radius: f32, length: f32, color: Color) { + self.circle(base, normal, radius, color); + + let angle_inc = (consts::PI * 2.) / 32.; + let rotator = Mat3::from_axis_angle(normal, angle_inc); + let perp = normal.any_orthonormal_pair().0; + let first = base + perp * radius; + let end = base + normal * length; + + let mut vector = first - base; + for _ in 0..32 { + let start = base + vector; + self.line(start, end, color); + vector = rotator * vector; + } + } }