mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-18 07:35:19 +00:00
Plane-constrained translation handles
This commit is contained in:
@@ -2,7 +2,7 @@ use std::f32::consts;
|
||||
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
|
||||
use crate::design_system;
|
||||
use crate::{design_system, math::Lerp};
|
||||
|
||||
use super::{InteractionMode, InteractionTransform, Selected, TransformSpace};
|
||||
|
||||
@@ -50,7 +50,7 @@ pub(super) fn draw(
|
||||
};
|
||||
|
||||
if mode.transform.contains(InteractionTransform::TRANSLATE) {
|
||||
draw_translate_gizmo(&mut gizmos, variant, &xform, mode.space, size);
|
||||
draw_translate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
|
||||
}
|
||||
if mode.transform.contains(InteractionTransform::ROTATE) {
|
||||
draw_rotate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
|
||||
@@ -67,6 +67,7 @@ fn draw_translate_gizmo(
|
||||
gizmos: &mut Gizmos,
|
||||
variant: GizmoVariant,
|
||||
xform: &Transform,
|
||||
camera_xform: &Transform,
|
||||
space: TransformSpace,
|
||||
size: f32,
|
||||
) {
|
||||
@@ -76,36 +77,65 @@ fn draw_translate_gizmo(
|
||||
TransformSpace::World => &Transform::IDENTITY,
|
||||
};
|
||||
|
||||
match variant {
|
||||
GizmoVariant::Full => {
|
||||
gizmos.sphere(origin, Quat::IDENTITY, size * 0.125, Color::WHITE);
|
||||
gizmos.circle(origin, camera_xform.forward(), size * 0.2, Color::WHITE);
|
||||
|
||||
for (direction, color) in [
|
||||
(basis.local_x(), design_system::Color::RED),
|
||||
(basis.local_y(), design_system::Color::GREEN),
|
||||
(basis.local_z(), design_system::Color::BLUE),
|
||||
] {
|
||||
let end = origin + direction * size * 1.4;
|
||||
gizmos.line(origin, end, color);
|
||||
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),
|
||||
] {
|
||||
let alpha = (1. - Vec3::dot(camera_xform.forward(), direction).abs())
|
||||
.remap((0., 0.1), (0., 1.))
|
||||
.clamp(0., 1.);
|
||||
|
||||
let cone_base = origin + direction * size;
|
||||
let radius = size * 0.1;
|
||||
let length = size * 0.4;
|
||||
gizmos.cone(cone_base, direction, radius, length, color);
|
||||
color = color.with_a(alpha);
|
||||
|
||||
let cone_base = match variant {
|
||||
GizmoVariant::Full => {
|
||||
let start = origin + direction * size * 0.25;
|
||||
let end = origin + direction * size * 1.6;
|
||||
gizmos.line(start, end, color);
|
||||
|
||||
let midpoint_self = origin + ((end - origin) / 2.);
|
||||
let (ortho, mut color_ortho) = get_ortho(basis, direction);
|
||||
|
||||
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);
|
||||
|
||||
origin + direction * size * 1.2
|
||||
}
|
||||
}
|
||||
GizmoVariant::Partial => {
|
||||
for (direction, color) in [
|
||||
(basis.local_x(), design_system::Color::RED),
|
||||
(basis.local_y(), design_system::Color::GREEN),
|
||||
(basis.local_z(), design_system::Color::BLUE),
|
||||
] {
|
||||
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);
|
||||
}
|
||||
}
|
||||
GizmoVariant::Partial => origin + direction * size * 1.4,
|
||||
};
|
||||
|
||||
let radius = size * 0.1;
|
||||
let length = size * 0.4;
|
||||
gizmos.cone(cone_base, direction, radius, length, color);
|
||||
}
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use std::ops;
|
||||
|
||||
use super::NearlyEq;
|
||||
|
||||
pub trait Lerp {
|
||||
fn lerp(self, a: Self, b: Self) -> Self;
|
||||
fn inv_lerp(self, a: Self, b: Self) -> Self;
|
||||
fn remap(self, from: (Self, Self), to: (Self, Self)) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
#[rustfmt::skip] trait One { fn one() -> Self; }
|
||||
#[rustfmt::skip] impl One for f32 { fn one() -> Self { 1. } }
|
||||
#[rustfmt::skip] impl One for f64 { fn one() -> Self { 1_f64 } }
|
||||
|
||||
#[rustfmt::skip] trait Zero { fn zero() -> Self; }
|
||||
#[rustfmt::skip] impl Zero for f32 { fn zero() -> Self { 0. } }
|
||||
#[rustfmt::skip] impl Zero for f64 { fn zero() -> Self { 0_f64 } }
|
||||
|
||||
// lol it would have been faster and less code to just copy/paste. At least I tried.
|
||||
impl<T> Lerp for T
|
||||
where
|
||||
T: ops::Add<Output = T>
|
||||
+ ops::Sub<Output = T>
|
||||
+ ops::Mul<Output = T>
|
||||
+ ops::Div<Output = T>
|
||||
+ NearlyEq<T>
|
||||
+ One
|
||||
+ Zero
|
||||
+ Sized
|
||||
+ Copy,
|
||||
{
|
||||
fn lerp(self, a: Self, b: Self) -> Self {
|
||||
(<Self as One>::one() - self) * a + b * self
|
||||
}
|
||||
|
||||
fn inv_lerp(self, a: Self, b: Self) -> Self {
|
||||
let dividend = b - a;
|
||||
if !dividend.nearly_eq(<Self as Zero>::zero()) {
|
||||
(self - a) / dividend
|
||||
} else {
|
||||
b
|
||||
}
|
||||
}
|
||||
|
||||
fn remap(self, from: (Self, Self), to: (Self, Self)) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let t = self.inv_lerp(from.0, from.1);
|
||||
t.lerp(to.0, to.1)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
mod interpolation;
|
||||
mod util;
|
||||
|
||||
pub use interpolation::*;
|
||||
pub use util::*;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::f32::consts;
|
||||
|
||||
use bevy::prelude::Vec3;
|
||||
|
||||
pub trait NearlyEq<Rhs> {
|
||||
@@ -15,3 +17,11 @@ impl NearlyEq<Vec3> for Vec3 {
|
||||
self.x.nearly_eq(rhs.x) && self.y.nearly_eq(rhs.y) && self.z.nearly_eq(rhs.z)
|
||||
}
|
||||
}
|
||||
|
||||
/// Fit a [0, 1] `t` interpolation parameter to a sine curve
|
||||
///
|
||||
/// https://www.desmos.com/calculator/k9x4c4cr5y
|
||||
#[inline]
|
||||
pub fn curve_sin(t: f32) -> f32 {
|
||||
-1. * (f32::cos(t * consts::PI) / 2.) + 0.5
|
||||
}
|
||||
|
||||
+2
-10
@@ -8,7 +8,7 @@ use bevy::{
|
||||
|
||||
use crate::{
|
||||
input::{Action, CameraMotion, InputAction, Lifecycle},
|
||||
math::NearlyEq,
|
||||
math::{self, NearlyEq},
|
||||
};
|
||||
|
||||
pub struct OrbitCamPlugin;
|
||||
@@ -157,7 +157,7 @@ fn update_velocity(
|
||||
} else if t.0.nearly_eq(1.) {
|
||||
target_vel
|
||||
} else {
|
||||
Vec3::lerp(velocity.0, target_vel, curve_sin(t.0))
|
||||
Vec3::lerp(velocity.0, target_vel, math::curve_sin(t.0))
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -281,11 +281,3 @@ fn do_rotate(win: &Window, delta: Vec2, xform: &mut Transform, orbit_cam: &mut O
|
||||
// Update focus point
|
||||
orbit_cam.focus = xform.translation + xform.forward() * orbit_cam.radius;
|
||||
}
|
||||
|
||||
/// Fit a [0, 1] `t` interpolation parameter to a sine curve
|
||||
///
|
||||
/// https://www.desmos.com/calculator/k9x4c4cr5y
|
||||
#[inline]
|
||||
fn curve_sin(t: f32) -> f32 {
|
||||
-1. * (f32::cos(t * consts::PI) / 2.) + 0.5
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user