Add `emath::Rangef` and helpers for splitting a `Rect` (#2978)

* emath: add Rangef helper

* emath: Add Rect splitting helper

* Add helper for interpolating Pos2
This commit is contained in:
Emil Ernerfeldt 2023-05-08 12:20:26 +02:00 committed by GitHub
parent 3d6a15f442
commit faf31365d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 2 deletions

View File

@ -30,6 +30,7 @@ pub mod align;
mod history;
mod numeric;
mod pos2;
mod range;
mod rect;
mod rect_transform;
mod rot2;
@ -41,6 +42,7 @@ pub use {
history::History,
numeric::*,
pos2::*,
range::Rangef,
rect::*,
rect_transform::*,
rot2::*,

View File

@ -188,6 +188,14 @@ impl Pos2 {
y: self.y.clamp(min.y, max.y),
}
}
/// Linearly interpolate towards another point, so that `0.0 => self, 1.0 => other`.
pub fn lerp(&self, other: Pos2, t: f32) -> Pos2 {
Pos2 {
x: lerp(self.x..=other.x, t),
y: lerp(self.y..=other.y, t),
}
}
}
impl std::ops::Index<usize> for Pos2 {

110
crates/emath/src/range.rs Normal file
View File

@ -0,0 +1,110 @@
use std::ops::{RangeFrom, RangeFull, RangeInclusive, RangeToInclusive};
/// Includive range of floats, i.e. `min..=max`, but more ergonomic than [`RangeInclusive`].
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Rangef {
pub min: f32,
pub max: f32,
}
impl Rangef {
/// Infinite range that contains everything, from -∞ to +∞, inclusive.
pub const EVERYTHING: Self = Self {
min: f32::NEG_INFINITY,
max: f32::INFINITY,
};
/// The inverse of [`Self::EVERYTHING`]: stretches from positive infinity to negative infinity.
/// Contains nothing.
pub const NOTHING: Self = Self {
min: f32::INFINITY,
max: f32::NEG_INFINITY,
};
/// An invalid [`Rangef`] filled with [`f32::NAN`].
pub const NAN: Self = Self {
min: f32::NAN,
max: f32::NAN,
};
#[inline]
pub fn new(min: f32, max: f32) -> Self {
Self { min, max }
}
#[inline]
pub fn span(&self) -> f32 {
self.max - self.min
}
#[inline]
pub fn contains(&self, x: f32) -> bool {
self.min <= x && x <= self.max
}
}
impl From<Rangef> for RangeInclusive<f32> {
#[inline]
fn from(Rangef { min, max }: Rangef) -> Self {
min..=max
}
}
impl From<&Rangef> for RangeInclusive<f32> {
#[inline]
fn from(&Rangef { min, max }: &Rangef) -> Self {
min..=max
}
}
impl From<RangeInclusive<f32>> for Rangef {
#[inline]
fn from(range: RangeInclusive<f32>) -> Self {
Self::new(*range.start(), *range.end())
}
}
impl From<&RangeInclusive<f32>> for Rangef {
#[inline]
fn from(range: &RangeInclusive<f32>) -> Self {
Self::new(*range.start(), *range.end())
}
}
impl From<RangeFrom<f32>> for Rangef {
#[inline]
fn from(range: RangeFrom<f32>) -> Self {
Self::new(range.start, f32::INFINITY)
}
}
impl From<&RangeFrom<f32>> for Rangef {
#[inline]
fn from(range: &RangeFrom<f32>) -> Self {
Self::new(range.start, f32::INFINITY)
}
}
impl From<RangeFull> for Rangef {
#[inline]
fn from(_: RangeFull) -> Self {
Self::new(f32::NEG_INFINITY, f32::INFINITY)
}
}
impl From<&RangeFull> for Rangef {
#[inline]
fn from(_: &RangeFull) -> Self {
Self::new(f32::NEG_INFINITY, f32::INFINITY)
}
}
impl From<RangeToInclusive<f32>> for Rangef {
#[inline]
fn from(range: RangeToInclusive<f32>) -> Self {
Self::new(f32::NEG_INFINITY, range.end)
}
}

View File

@ -53,7 +53,7 @@ impl Rect {
max: pos2(-INFINITY, -INFINITY),
};
/// An invalid [`Rect`] filled with [`f32::NAN`];
/// An invalid [`Rect`] filled with [`f32::NAN`].
pub const NAN: Self = Self {
min: pos2(f32::NAN, f32::NAN),
max: pos2(f32::NAN, f32::NAN),
@ -82,7 +82,12 @@ impl Rect {
}
#[inline(always)]
pub fn from_x_y_ranges(x_range: RangeInclusive<f32>, y_range: RangeInclusive<f32>) -> Self {
pub fn from_x_y_ranges(
x_range: impl Into<RangeInclusive<f32>>,
y_range: impl Into<RangeInclusive<f32>>,
) -> Self {
let x_range = x_range.into();
let y_range = y_range.into();
Rect {
min: pos2(*x_range.start(), *y_range.start()),
max: pos2(*x_range.end(), *y_range.end()),
@ -361,13 +366,28 @@ impl Rect {
/// Linearly interpolate so that `[0, 0]` is [`Self::min`] and
/// `[1, 1]` is [`Self::max`].
#[deprecated = "Use `lerp_inside` instead"]
pub fn lerp(&self, t: Vec2) -> Pos2 {
self.lerp_inside(t)
}
/// Linearly interpolate so that `[0, 0]` is [`Self::min`] and
/// `[1, 1]` is [`Self::max`].
pub fn lerp_inside(&self, t: Vec2) -> Pos2 {
Pos2 {
x: lerp(self.min.x..=self.max.x, t.x),
y: lerp(self.min.y..=self.max.y, t.y),
}
}
/// Linearly self towards other rect.
pub fn lerp_towards(&self, other: &Rect, t: f32) -> Self {
Self {
min: self.min.lerp(other.min, t),
max: self.max.lerp(other.max, t),
}
}
#[inline(always)]
pub fn x_range(&self) -> RangeInclusive<f32> {
self.min.x..=self.max.x
@ -521,6 +541,30 @@ impl Rect {
pub fn right_bottom(&self) -> Pos2 {
pos2(self.right(), self.bottom())
}
/// Split rectangle in left and right halves. `t` is expected to be in the (0,1) range.
pub fn split_left_right_at_fraction(&self, t: f32) -> (Rect, Rect) {
self.split_left_right_at_x(lerp(self.min.x..=self.max.x, t))
}
/// Split rectangle in left and right halves at the given `x` coordinate.
pub fn split_left_right_at_x(&self, split_x: f32) -> (Rect, Rect) {
let left = Rect::from_min_max(self.min, Pos2::new(split_x, self.max.y));
let right = Rect::from_min_max(Pos2::new(split_x, self.min.y), self.max);
(left, right)
}
/// Split rectangle in top and bottom halves. `t` is expected to be in the (0,1) range.
pub fn split_top_bottom_at_fraction(&self, t: f32) -> (Rect, Rect) {
self.split_top_bottom_at_y(lerp(self.min.y..=self.max.y, t))
}
/// Split rectangle in top and bottom halves at the given `y` coordinate.
pub fn split_top_bottom_at_y(&self, split_y: f32) -> (Rect, Rect) {
let top = Rect::from_min_max(self.min, Pos2::new(self.max.x, split_y));
let bottom = Rect::from_min_max(Pos2::new(self.min.x, split_y), self.max);
(top, bottom)
}
}
impl std::fmt::Debug for Rect {