Fixes, cleanup

* Fix z-ordering issues with the gizmo elements
* Restore the translucent far-side rotator ring arcs
* Restore the translation arrow fading
* Remove dead and/or commented-out code
This commit is contained in:
Danny McGee
2023-10-13 17:15:09 -04:00
parent afb1d309af
commit f11b89fb48
2 changed files with 151 additions and 286 deletions
+26 -8
View File
@@ -186,14 +186,7 @@ impl GizmoElement {
let start = xform.translation + a * base_size * 0.925;
let end = xform.translation + b * base_size * 0.925;
let vector = end - start;
let length = vector.length();
let direction = vector / length;
line.origin = start;
line.direction = direction;
line.length = length;
*line = Line::from((start, end));
}
#[inline]
@@ -313,3 +306,28 @@ bitflags! {
const SCREEN_Z = 0b1100;
}
}
impl From<(Vec3, Vec3)> for Line {
#[inline]
fn from(value: (Vec3, Vec3)) -> Self {
let (start, end) = value;
let vector = end - start;
let length = vector.length();
let direction = vector / length;
Self {
origin: start,
direction,
length,
}
}
}
impl From<Line> for (Vec3, Vec3) {
#[inline]
fn from(value: Line) -> Self {
let start = value.origin;
let end = start + value.direction * value.length;
(start, end)
}
}
+125 -278
View File
@@ -2,20 +2,13 @@ use std::f32::consts;
use bevy::{prelude::*, window::PrimaryWindow};
// use crate::{design_system, math::Lerp};
use crate::math::Lerp;
#[rustfmt::skip]
use super::{
model::{Arc, Axes, Control, Gizmo, GizmoColor, Ring, Shape},
// InteractionMode, Selected, TransformSpace, TransformType,
InteractionMode, TransformSpace, TransformType,
};
// #[derive(Clone, Copy)]
// enum GizmoVariant {
// Full,
// Partial,
// }
pub(super) fn configure(
q_window: Query<&Window, With<PrimaryWindow>>,
mut config: ResMut<GizmoConfig>,
@@ -23,75 +16,112 @@ pub(super) fn configure(
let scale_factor = q_window.single().scale_factor() as f32;
config.line_width = 4. * scale_factor;
config.line_perspective = false;
config.depth_bias = -1.;
config.depth_bias = -0.1;
}
pub(super) fn draw(
q_gizmo: Query<&Gizmo>,
q_gizmo: Query<(&Gizmo, &GlobalTransform)>,
q_camera_xform: Query<&Transform, With<Camera>>,
mut gizmos: Gizmos,
mode: Res<InteractionMode>,
) {
let cam_xform = q_camera_xform.single();
// TODO (maybe):
// 1. Map GizmoElements to a new Vec<(Shape, Color)>
// 2. Bisect the rotator rings and push the back sides into the vec
// 3. Sort by camera depth
// 4. Draw
//
// (Starting to feel like a really gross hack -- wish I had a better
// grasp of Bevy's render pipeline so we didn't have to lean so hard on
// the Gizmos API)
let Ok((gizmo, world_xform)) = q_gizmo.get_single() else {
return;
};
for gizmo in q_gizmo.iter() {
for el in gizmo.0.iter() {
match el.shape {
Shape::Ring(ring) => {
let GizmoColor::Single(color) = el.color else {
unreachable!();
};
let mut cached_xform: Option<Transform> = None;
if let Control::Rotation(Axes::X | Axes::Y | Axes::Z) = el.control {
let (front, _back) = bisect_rotator_ring(&ring, cam_xform);
gizmos.arc(front.center, front.axis, front.start, front.length, color);
// TODO
// gizmos.arc(
// back.center,
// back.axis,
// back.start,
// back.length,
// color.with_a(0.2),
// );
} else {
gizmos.circle(ring.origin, ring.normal, ring.radius, color);
for el in gizmo.0.iter() {
match el.shape {
Shape::Ring(ring) => {
let GizmoColor::Single(color) = el.color else {
warn!("Unexpected gradient color specified for gizmo ring!");
continue;
};
if let Control::Rotation(Axes::X | Axes::Y | Axes::Z) = el.control {
let (front, back) = bisect_rotator_ring(&ring, cam_xform);
gizmos.arc(front.center, front.axis, front.start, front.length, color);
if !mode.transform.intersects(TransformType::TRANSLATE) {
gizmos.arc(
back.center,
back.axis,
back.start,
back.length,
color.with_a(0.2),
);
}
} else {
gizmos.circle(ring.origin, ring.normal, ring.radius, color);
}
Shape::Line(line) => {
// TODO: Camera-direction fade
let start = line.origin;
let end = line.origin + line.direction * line.length;
}
shape @ (Shape::Line(_) | Shape::Cone(_)) => {
let cam_pos = cam_xform.translation;
let xform = match cached_xform {
Some(xform) => xform,
None => {
let xform = world_xform.compute_transform();
cached_xform = Some(xform);
xform
}
};
match el.color {
GizmoColor::Single(color) => {
gizmos.line(start, end, color);
}
GizmoColor::Gradient(color_a, color_b) => {
gizmos.line_gradient(start, end, color_a, color_b);
let basis = match mode.space {
TransformSpace::Local => xform,
TransformSpace::World => Transform::from_translation(xform.translation),
};
let Control::Translation(axes) = el.control else {
unreachable!();
};
let alpha = fade_aligned_control(axes, &basis, cam_pos);
match shape {
Shape::Line(line) => {
let (start, end) = line.into();
match el.color {
GizmoColor::Single(color) => {
gizmos.line(start, end, color.with_a(alpha));
}
GizmoColor::Gradient(start_color, end_color) => {
gizmos.line_gradient(
start,
end,
start_color.with_a(alpha),
end_color.with_a(alpha),
);
}
}
}
}
Shape::Cone(cone) => {
// TODO: Camera-direction fade
let GizmoColor::Single(color) = el.color else {
unreachable!()
};
gizmos.cone(cone.base, cone.direction, cone.radius, cone.length, color);
Shape::Cone(cone) => {
let GizmoColor::Single(color) = el.color else {
warn!("Unexpected gradient color specified for gizmo cone!");
continue;
};
gizmos.cone(
cone.base,
cone.direction,
cone.radius,
cone.length,
color.with_a(alpha),
);
}
_ => unreachable!(),
}
}
}
}
}
/// Slice a non-screen-space rotator ring in half by the camera's XY plane,
/// returning the semicircle closer to the camera as the first tuple element,
/// and the one further away as the second.
#[inline]
fn bisect_rotator_ring(ring: &Ring, cam_xform: &Transform) -> (Arc, Arc) {
let plane_intersection = Vec3::cross(ring.normal, cam_xform.forward()).normalize();
let start_a = ring.origin + plane_intersection * ring.radius;
@@ -110,230 +140,47 @@ fn bisect_rotator_ring(ring: &Ring, cam_xform: &Transform) -> (Arc, Arc) {
(arc_a, arc_b)
}
// pub(super) fn draw_old(
// q_selection: Query<&GlobalTransform, With<Selected>>,
// q_camera_xform: Query<&Transform, With<Camera>>,
// q_window: Query<&Window, With<PrimaryWindow>>,
// mode: Res<InteractionMode>,
// mut gizmos: Gizmos,
// mut config: ResMut<GizmoConfig>,
// ) {
// if !q_selection.is_empty() {
// // TODO: Use an app state or something so we only have to set this up once
// let scale_factor = q_window.single().scale_factor() as f32;
// config.line_width = 4. * scale_factor;
// config.line_perspective = false;
// config.depth_bias = -1.;
/// Compute an alpha value to fade the translation arrows out when they're
/// pointed directly at or away from the camera.
///
/// This prevents overlapping/obscuring the center screen-space translation
/// ring, or just generally getting in the way when the control is not very
/// useful to grab onto.
#[inline]
fn fade_aligned_control(axes: Axes, xform: &Transform, cam_pos: Vec3) -> f32 {
let to_camera = (cam_pos - xform.translation).normalize_or_zero();
let base_factor = match axes {
Axes::X | Axes::Y | Axes::Z => {
let direction = match axes {
Axes::X => xform.local_x(),
Axes::Y => xform.local_y(),
Axes::Z => xform.local_z(),
_ => unreachable!(),
};
// let camera_xform = q_camera_xform.single();
Vec3::dot(to_camera, direction).abs()
}
Axes::XY | Axes::XZ | Axes::YZ => {
let (a, b) = match axes {
Axes::XY => (xform.local_x(), xform.local_y()),
Axes::YZ => (xform.local_y(), xform.local_z()),
Axes::XZ => (xform.local_x(), xform.local_z()),
_ => unreachable!(),
};
// // TODO: We only want to render one gizmo -- this is just for testing
// for world_xform in q_selection.iter() {
// let xform = world_xform.compute_transform();
// // Note: Scaling the gizmo size like this isn't perfect due to
// // distortion from the perspective projection. It bugged me at
// // first, but even Blender exhibits this type of gizmo distortion if
// // 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.;
f32::max(Vec3::dot(to_camera, a).abs(), Vec3::dot(to_camera, b).abs())
}
_ => panic!(
"Unexpected `axes` value provided to `fade_aligned_control`: {:?}",
axes
),
};
// let variant = if mode
// .transform
// .contains(TransformType::TRANSLATE | TransformType::ROTATE)
// {
// GizmoVariant::Partial
// } else {
// GizmoVariant::Full
// };
// if mode.transform.contains(TransformType::TRANSLATE) {
// draw_translate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
// }
// if mode.transform.contains(TransformType::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. As best as I can tell, the elements are drawn in
// // reverse invocation order (the methods called last are rendered first,
// // like the commands are being popped from a LIFO stack), but it's not
// // 100% consistent and there seems to be lots of flickering / Z-fighting.
// // Might not be a big deal, so I'm inclined to roll with it for now and
// // see how users respond.
// #[inline]
// fn draw_translate_gizmo(
// gizmos: &mut Gizmos,
// variant: GizmoVariant,
// xform: &Transform,
// camera_xform: &Transform,
// space: TransformSpace,
// size: f32,
// ) {
// let origin = xform.translation;
// let basis = match space {
// TransformSpace::Local => xform,
// TransformSpace::World => &Transform::IDENTITY,
// };
// for (direction, mut color) in [
// (basis.local_x(), design_system::Color::RED),
// (basis.local_y(), design_system::Color::GREEN),
// (basis.local_z(), design_system::Color::BLUE),
// ] {
// // Fade the arrow out when it's pointed directly at the camera.
// // (Prevents overlapping/obscuring the center screen-space ring, or just
// // generally getting in the way when it's not very useful to grab onto.)
// let alpha = (1. - Vec3::dot(camera_xform.forward(), direction).abs())
// .remap((0., 0.1), (0., 1.))
// .clamp(0., 1.);
// color = color.with_a(alpha);
// // In "Partial" mode, we only draw the center ring and the arrow tips to
// // leave room for the rotator rings.
// if matches!(variant, GizmoVariant::Full) {
// // Draw a line from just outside the center ring to the tip of the arrow
// let start = origin + direction * size * 0.25;
// let end = origin + direction * size * 1.6;
// gizmos.line(start, end, color);
// // Draw a line from the midpoint of this arrow to the midpoint of
// // the next one. This "crossbar" can be used to translate the object
// // on the plane defined by the corresponding axes.
// let midpoint_self = origin + ((end - origin) / 2.);
// let (ortho, mut color_ortho) = get_ortho(basis, direction);
// // Fade the crossbar when its plane is edge-on with the camera, just
// // like we did for the arrow pointing toward/away from the camera.
// let ortho_alpha = (1. - Vec3::dot(camera_xform.forward(), ortho).abs())
// .remap((0., 0.1), (0., 1.))
// .clamp(0., 1.);
// color_ortho = color_ortho.with_a(f32::min(ortho_alpha, alpha));
// let ortho_end = origin + ortho * size * 1.6;
// let midpoint_ortho = origin + ((ortho_end - origin) / 2.);
// gizmos.line_gradient(midpoint_self, midpoint_ortho, color, color_ortho);
// }
// // Draw the arrow tip
// let factor = match variant {
// GizmoVariant::Full => 1.2,
// GizmoVariant::Partial => 1.4,
// };
// let cone_base = origin + direction * size * factor;
// let radius = size * 0.1;
// let length = size * 0.4;
// gizmos.cone(cone_base, direction, radius, length, color);
// }
// // Draw the center ring. This can be dragged around to translate the object
// // in screen-space (i.e. on the camera's XY plane).
// gizmos.circle(origin, camera_xform.forward(), size * 0.2, Color::WHITE);
// }
// // Dumb helper function for grabbing the normal vector/color for the translation
// // gizmo's next arrow. We use this to determine where and how to render the
// // planar translation "crossbars".
// #[inline]
// fn get_ortho(xform: &Transform, compare: Vec3) -> (Vec3, Color) {
// let x = xform.local_x();
// let y = xform.local_y();
// let z = xform.local_z();
// let diff_x = (compare - x).length_squared();
// let diff_y = (compare - y).length_squared();
// let diff_z = (compare - z).length_squared();
// if diff_x < diff_y && diff_x < diff_z {
// (y, design_system::Color::GREEN)
// } else if diff_y < diff_x && diff_y < diff_z {
// (z, design_system::Color::BLUE)
// } else {
// (x, design_system::Color::RED)
// }
// }
// #[inline]
// fn draw_rotate_gizmo(
// gizmos: &mut Gizmos,
// variant: GizmoVariant,
// xform: &Transform,
// camera_xform: &Transform,
// space: TransformSpace,
// radius: f32,
// ) {
// let origin = xform.translation;
// let basis = match space {
// TransformSpace::Local => xform,
// TransformSpace::World => &Transform::IDENTITY,
// };
// let camera_fwd = camera_xform.forward();
// // Draw a ring around the whole gizmo for rotating the object in screen
// // space (i.e. around the camera's forward vector).
// gizmos.circle(origin, camera_fwd, radius * 1.2, Color::WHITE);
// // Draw semicircles for the main basis vector axes, bisected by the camera's
// // XY plane. The `RotatorRings` argument determines which side of the plane
// // to draw on.
// draw_rotator_axis_rings(
// gizmos,
// RotatorRings::Near,
// origin,
// camera_fwd,
// basis,
// radius,
// );
// // We skip drawing the far-side of the rings in "Partial" mode to cut down
// // on visual noise and leave less room for mis-clicks.
// 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),
// ] {
// // The line of intersection between two planes will be mutually
// // orthogonal to the normal vectors of those planes, so we can find the
// // points of intersection between the ring and the camera plane by
// // taking the cross product of the camera direction and the rotation
// // axis, and scaling it by the ring's radius. The order of the operands
// // determines which of the intersection points we get.
// 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);
// }
// }
#[rustfmt::skip] // This is harder to read without the line breaks
return (1. - base_factor)
.remap((0., 0.1), (0., 1.))
.clamp(0., 1.);
}
trait GizmoExtras {
/// Draw an arc around `center` with angular `length` (in radians),