From faf31365d27420d9d6a6e9a4b6e930d262f39e33 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 8 May 2023 12:20:26 +0200 Subject: [PATCH] Add `emath::Rangef` and helpers for splitting a `Rect` (#2978) * emath: add Rangef helper * emath: Add Rect splitting helper * Add helper for interpolating Pos2 --- crates/emath/src/lib.rs | 2 + crates/emath/src/pos2.rs | 8 +++ crates/emath/src/range.rs | 110 ++++++++++++++++++++++++++++++++++++++ crates/emath/src/rect.rs | 48 ++++++++++++++++- 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 crates/emath/src/range.rs diff --git a/crates/emath/src/lib.rs b/crates/emath/src/lib.rs index 95552afd..89b2db20 100644 --- a/crates/emath/src/lib.rs +++ b/crates/emath/src/lib.rs @@ -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::*, diff --git a/crates/emath/src/pos2.rs b/crates/emath/src/pos2.rs index bafbcf42..16bba20e 100644 --- a/crates/emath/src/pos2.rs +++ b/crates/emath/src/pos2.rs @@ -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 for Pos2 { diff --git a/crates/emath/src/range.rs b/crates/emath/src/range.rs new file mode 100644 index 00000000..ae3e3f0e --- /dev/null +++ b/crates/emath/src/range.rs @@ -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 for RangeInclusive { + #[inline] + fn from(Rangef { min, max }: Rangef) -> Self { + min..=max + } +} + +impl From<&Rangef> for RangeInclusive { + #[inline] + fn from(&Rangef { min, max }: &Rangef) -> Self { + min..=max + } +} + +impl From> for Rangef { + #[inline] + fn from(range: RangeInclusive) -> Self { + Self::new(*range.start(), *range.end()) + } +} + +impl From<&RangeInclusive> for Rangef { + #[inline] + fn from(range: &RangeInclusive) -> Self { + Self::new(*range.start(), *range.end()) + } +} + +impl From> for Rangef { + #[inline] + fn from(range: RangeFrom) -> Self { + Self::new(range.start, f32::INFINITY) + } +} + +impl From<&RangeFrom> for Rangef { + #[inline] + fn from(range: &RangeFrom) -> Self { + Self::new(range.start, f32::INFINITY) + } +} + +impl From 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> for Rangef { + #[inline] + fn from(range: RangeToInclusive) -> Self { + Self::new(f32::NEG_INFINITY, range.end) + } +} diff --git a/crates/emath/src/rect.rs b/crates/emath/src/rect.rs index 6300bb11..b8c6085a 100644 --- a/crates/emath/src/rect.rs +++ b/crates/emath/src/rect.rs @@ -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, y_range: RangeInclusive) -> Self { + pub fn from_x_y_ranges( + x_range: impl Into>, + y_range: impl Into>, + ) -> 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 { 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 {