Files
storyteller-bevy/studio/src/math/interp/traits.rs
T
Danny McGee ce6db7fc3c Refactoring, reorganization and cleanup (#7)
### New `math` module
 * Traits for error-tolerant floating-point comparisons
 * Traits for interpolation and easing
 * Bevy plugin to manage 3D interpolations with ECS components

### `skybox` module refactor
 * ECS resource to enable external skybox selection/updates
 * System run conditions for more idiomatic/ECS-friendly control flow

### Camera controller refactor
 * Renamed from `orbit_cam` to `camera_controller`
 * Now delegates to the new interpolation plugin for WASD motion smoothing
 * Simple API for attaching the `CameraController` component to the camera from
   another module -- this resolves an awkward separation-of-concerns situation
	where we had to configure a bunch of render-quality settings in the camera
	controller module

### Scene module refactor
 * Renamed from `test_scene` to `scene`
 * This module is now responsible for configuring the skybox
 * This module is now responsible for spawning the camera and configuring the
   render settings
2023-10-31 16:39:10 -04:00

153 lines
3.8 KiB
Rust

use std::ops;
use bevy::prelude::Vec3;
use crate::math::{
internal::{Clamp, One, Zero},
NearlyZero,
};
pub trait Lerp<Operand> {
/// Interpolate linearly between `a` and `b`, using `self` as the `t` parameter.
fn lerp(self, a: Operand, b: Operand) -> Operand;
}
pub trait InvLerp<T: Sized> {
/// Derive a `t` interpolation parameter for `self`, where `a` is the
/// "origin" and `b` is the "destination".
///
/// # Examples
///
/// ```
/// use math::NearlyEq;
///
/// let t = 48_f32.inv_lerp(32., 64.);
/// // The expected result is 0.5, because 48 is halfway between 32 and 64.
/// assert!(t.nearly_eq(0.5));
/// ```
fn inv_lerp(self, a: Self, b: Self) -> T;
}
pub trait Remap<T: Sized>
where
Self: Sized,
{
/// Re-map a value from its interpolated position in the range `from`, to
/// the equivalent position in the range `to`.
///
/// # Examples
///
/// ```
/// use math::NearlyEq;
///
/// let x = 48_f32.remap((32., 64.), (32., 128.));
/// // The expected result is 80, because 48 is halfway between 32 and 64,
/// // and 80 is halfway between 32 and 128.
/// assert!(x.nearly_eq_within(80., 1e-3));
/// ```
fn remap(self, from: (Self, Self), to: (Self, Self)) -> Self;
}
pub trait CurveSin {
/// Fit a `t` interpolation parameter to a sine curve. Mostly useful for
/// "easing" between steps in an animation.
///
/// https://www.desmos.com/calculator/k9x4c4cr5y
///
/// # Example
///
/// ```
/// // This is fine, but it will feel "jerky" because the velocity is
/// // constant from start to end.
/// fn update_position(dt: f32, start: Vec3, end: Vec3, t: f32) -> Vec3 {
/// (t + dt).clamp(0., 1.).lerp(start, end)
/// }
///
/// // This will smoothly accelerate from a standstill at t=0, and
/// // smoothly decelerate back to a stop at t=1.
/// fn update_position_eased(dt: f32, start: Vec3, end: Vec3, t: f32) -> Vec3 {
/// (t + dt)
/// .clamp(0., 1.)
/// .curve_sin()
/// .lerp(start, end)
/// }
/// ```
fn curve_sin(self) -> Self;
}
impl<T, Operand> Lerp<Operand> for T
where
T: Copy,
T: One,
T: ops::Sub<Output = T>,
Operand: ops::Mul<T, Output = Operand>,
Operand: ops::Add<Output = Operand>,
{
#[inline]
fn lerp(self, a: Operand, b: Operand) -> Operand {
a * (Self::one() - self) + b * self
}
}
impl<T> InvLerp<T> for T
where
T: One + Zero,
T: Clamp,
T: NearlyZero,
T: Copy,
T: ops::Sub<Output = T>,
T: ops::Div<Output = T>,
{
#[inline]
fn inv_lerp(self, a: T, b: T) -> T {
let dividend = b - a;
if !dividend.nearly_zero() {
((self - a) / dividend).clamp(T::zero(), T::one())
} else {
T::one()
}
}
}
// Vector inverse lerp requires a special implementation, yoinked from here:
// https://discussions.unity.com/t/inverselerp-for-vector3/177038
impl InvLerp<f32> for Vec3 {
#[inline]
fn inv_lerp(self, a: Vec3, b: Vec3) -> f32 {
let ab = b - a;
let av = self - a;
if !ab.nearly_zero() {
av.dot(ab) / ab.length_squared()
} else {
1.
}
}
}
impl<T, Operand> Remap<T> for Operand
where
Operand: InvLerp<T>,
T: Lerp<Operand>,
{
#[inline]
fn remap(self, from: (Self, Self), to: (Self, Self)) -> Self {
self.inv_lerp(from.0, from.1).lerp(to.0, to.1)
}
}
impl CurveSin for f32 {
fn curve_sin(self) -> Self {
use std::f32::consts::PI;
-1. * ((self * PI).cos() / 2.) + 0.5
}
}
impl CurveSin for f64 {
fn curve_sin(self) -> Self {
use std::f64::consts::PI;
-1. * ((self * PI).cos() / 2.) + 0.5
}
}