mirror of
https://github.com/Noratrieb/game-wip-dontplay.git
synced 2026-01-17 04:45:02 +01:00
vendor
This commit is contained in:
parent
12163d1338
commit
550b1644cb
363 changed files with 84081 additions and 16 deletions
44
egui/crates/emath/Cargo.toml
Normal file
44
egui/crates/emath/Cargo.toml
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
[package]
|
||||
name = "emath"
|
||||
version = "0.21.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
description = "Minimal 2D math library for GUI work"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
homepage = "https://github.com/emilk/egui/tree/master/crates/emath"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/emilk/egui/tree/master/crates/emath"
|
||||
categories = ["mathematics", "gui"]
|
||||
keywords = ["math", "gui"]
|
||||
include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
||||
[lib]
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
## Enable additional checks if debug assertions are enabled (debug builds).
|
||||
extra_debug_asserts = []
|
||||
## Always enable additional checks.
|
||||
extra_asserts = []
|
||||
|
||||
|
||||
[dependencies]
|
||||
#! ### Optional dependencies
|
||||
|
||||
## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `emath` types to `&[u8]`.
|
||||
bytemuck = { version = "1.7.2", optional = true, features = ["derive"] }
|
||||
|
||||
## Enable this when generating docs.
|
||||
document-features = { version = "0.2", optional = true }
|
||||
|
||||
## [`mint`](https://docs.rs/mint) enables interoperability with other math libraries such as [`glam`](https://docs.rs/glam) and [`nalgebra`](https://docs.rs/nalgebra).
|
||||
mint = { version = "0.5.6", optional = true }
|
||||
|
||||
## Allow serialization using [`serde`](https://docs.rs/serde).
|
||||
serde = { version = "1", optional = true, features = ["derive"] }
|
||||
11
egui/crates/emath/README.md
Normal file
11
egui/crates/emath/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# emath - egui math library
|
||||
|
||||
[](https://crates.io/crates/emath)
|
||||
[](https://docs.rs/emath)
|
||||
[](https://github.com/rust-secure-code/safety-dance/)
|
||||

|
||||

|
||||
|
||||
A bare-bones 2D math library with types and functions useful for GUI building.
|
||||
|
||||
Made for [`egui`](https://github.com/emilk/egui/).
|
||||
265
egui/crates/emath/src/align.rs
Normal file
265
egui/crates/emath/src/align.rs
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
//! One- and two-dimensional alignment ([`Align::Center`], [`Align2::LEFT_TOP`] etc).
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// left/center/right or top/center/bottom alignment for e.g. anchors and layouts.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub enum Align {
|
||||
/// Left or top.
|
||||
Min,
|
||||
|
||||
/// Horizontal or vertical center.
|
||||
Center,
|
||||
|
||||
/// Right or bottom.
|
||||
Max,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
/// Convenience for [`Self::Min`]
|
||||
pub const LEFT: Self = Self::Min;
|
||||
|
||||
/// Convenience for [`Self::Max`]
|
||||
pub const RIGHT: Self = Self::Max;
|
||||
|
||||
/// Convenience for [`Self::Min`]
|
||||
pub const TOP: Self = Self::Min;
|
||||
|
||||
/// Convenience for [`Self::Max`]
|
||||
pub const BOTTOM: Self = Self::Max;
|
||||
|
||||
/// Convert `Min => 0.0`, `Center => 0.5` or `Max => 1.0`.
|
||||
#[inline(always)]
|
||||
pub fn to_factor(self) -> f32 {
|
||||
match self {
|
||||
Self::Min => 0.0,
|
||||
Self::Center => 0.5,
|
||||
Self::Max => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Min => -1.0`, `Center => 0.0` or `Max => 1.0`.
|
||||
#[inline(always)]
|
||||
pub fn to_sign(self) -> f32 {
|
||||
match self {
|
||||
Self::Min => -1.0,
|
||||
Self::Center => 0.0,
|
||||
Self::Max => 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a range of given size within a specified range.
|
||||
///
|
||||
/// If the requested `size` is bigger than the size of `range`, then the returned
|
||||
/// range will not fit into the available `range`. The extra space will be allocated
|
||||
/// from:
|
||||
///
|
||||
/// |Align |Side |
|
||||
/// |------|------------|
|
||||
/// |Min |right (end) |
|
||||
/// |Center|both |
|
||||
/// |Max |left (start)|
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::f32::{INFINITY, NEG_INFINITY};
|
||||
/// use emath::Align::*;
|
||||
///
|
||||
/// // The size is smaller than a range
|
||||
/// assert_eq!(Min .align_size_within_range(2.0, 10.0..=20.0), 10.0..=12.0);
|
||||
/// assert_eq!(Center.align_size_within_range(2.0, 10.0..=20.0), 14.0..=16.0);
|
||||
/// assert_eq!(Max .align_size_within_range(2.0, 10.0..=20.0), 18.0..=20.0);
|
||||
///
|
||||
/// // The size is bigger than a range
|
||||
/// assert_eq!(Min .align_size_within_range(20.0, 10.0..=20.0), 10.0..=30.0);
|
||||
/// assert_eq!(Center.align_size_within_range(20.0, 10.0..=20.0), 5.0..=25.0);
|
||||
/// assert_eq!(Max .align_size_within_range(20.0, 10.0..=20.0), 0.0..=20.0);
|
||||
///
|
||||
/// // The size is infinity, but range is finite - a special case of a previous example
|
||||
/// assert_eq!(Min .align_size_within_range(INFINITY, 10.0..=20.0), 10.0..=INFINITY);
|
||||
/// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=INFINITY);
|
||||
/// assert_eq!(Max .align_size_within_range(INFINITY, 10.0..=20.0), NEG_INFINITY..=20.0);
|
||||
/// ```
|
||||
///
|
||||
/// The infinity-sized ranges can produce a surprising results, if the size is also infinity,
|
||||
/// use such ranges with carefully!
|
||||
///
|
||||
/// ```
|
||||
/// use std::f32::{INFINITY, NEG_INFINITY};
|
||||
/// use emath::Align::*;
|
||||
///
|
||||
/// // Allocating a size aligned for infinity bound will lead to empty ranges!
|
||||
/// assert_eq!(Min .align_size_within_range(2.0, 10.0..=INFINITY), 10.0..=12.0);
|
||||
/// assert_eq!(Center.align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
|
||||
/// assert_eq!(Max .align_size_within_range(2.0, 10.0..=INFINITY), INFINITY..=INFINITY);// (!)
|
||||
///
|
||||
/// assert_eq!(Min .align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
|
||||
/// assert_eq!(Center.align_size_within_range(2.0, NEG_INFINITY..=20.0), NEG_INFINITY..=NEG_INFINITY);// (!)
|
||||
/// assert_eq!(Max .align_size_within_range(2.0, NEG_INFINITY..=20.0), 18.0..=20.0);
|
||||
///
|
||||
///
|
||||
/// // The infinity size will always return the given range if it has at least one infinity bound
|
||||
/// assert_eq!(Min .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
|
||||
/// assert_eq!(Center.align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
|
||||
/// assert_eq!(Max .align_size_within_range(INFINITY, 10.0..=INFINITY), 10.0..=INFINITY);
|
||||
///
|
||||
/// assert_eq!(Min .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
|
||||
/// assert_eq!(Center.align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
|
||||
/// assert_eq!(Max .align_size_within_range(INFINITY, NEG_INFINITY..=20.0), NEG_INFINITY..=20.0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn align_size_within_range(
|
||||
self,
|
||||
size: f32,
|
||||
range: RangeInclusive<f32>,
|
||||
) -> RangeInclusive<f32> {
|
||||
let min = *range.start();
|
||||
let max = *range.end();
|
||||
|
||||
if max - min == f32::INFINITY && size == f32::INFINITY {
|
||||
return range;
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Min => min..=min + size,
|
||||
Self::Center => {
|
||||
if size == f32::INFINITY {
|
||||
f32::NEG_INFINITY..=f32::INFINITY
|
||||
} else {
|
||||
let left = (min + max) / 2.0 - size / 2.0;
|
||||
left..=left + size
|
||||
}
|
||||
}
|
||||
Self::Max => max - size..=max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Align {
|
||||
#[inline(always)]
|
||||
fn default() -> Align {
|
||||
Align::Min
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Align2(pub [Align; 2]);
|
||||
|
||||
impl Align2 {
|
||||
pub const LEFT_BOTTOM: Align2 = Align2([Align::Min, Align::Max]);
|
||||
pub const LEFT_CENTER: Align2 = Align2([Align::Min, Align::Center]);
|
||||
pub const LEFT_TOP: Align2 = Align2([Align::Min, Align::Min]);
|
||||
pub const CENTER_BOTTOM: Align2 = Align2([Align::Center, Align::Max]);
|
||||
pub const CENTER_CENTER: Align2 = Align2([Align::Center, Align::Center]);
|
||||
pub const CENTER_TOP: Align2 = Align2([Align::Center, Align::Min]);
|
||||
pub const RIGHT_BOTTOM: Align2 = Align2([Align::Max, Align::Max]);
|
||||
pub const RIGHT_CENTER: Align2 = Align2([Align::Max, Align::Center]);
|
||||
pub const RIGHT_TOP: Align2 = Align2([Align::Max, Align::Min]);
|
||||
}
|
||||
|
||||
impl Align2 {
|
||||
/// Returns an alignment by the X (horizontal) axis
|
||||
#[inline(always)]
|
||||
pub fn x(self) -> Align {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
/// Returns an alignment by the Y (vertical) axis
|
||||
#[inline(always)]
|
||||
pub fn y(self) -> Align {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
/// -1, 0, or +1 for each axis
|
||||
pub fn to_sign(self) -> Vec2 {
|
||||
vec2(self.x().to_sign(), self.y().to_sign())
|
||||
}
|
||||
|
||||
/// Used e.g. to anchor a piece of text to a part of the rectangle.
|
||||
/// Give a position within the rect, specified by the aligns
|
||||
pub fn anchor_rect(self, rect: Rect) -> Rect {
|
||||
let x = match self.x() {
|
||||
Align::Min => rect.left(),
|
||||
Align::Center => rect.left() - 0.5 * rect.width(),
|
||||
Align::Max => rect.left() - rect.width(),
|
||||
};
|
||||
let y = match self.y() {
|
||||
Align::Min => rect.top(),
|
||||
Align::Center => rect.top() - 0.5 * rect.height(),
|
||||
Align::Max => rect.top() - rect.height(),
|
||||
};
|
||||
Rect::from_min_size(pos2(x, y), rect.size())
|
||||
}
|
||||
|
||||
/// e.g. center a size within a given frame
|
||||
pub fn align_size_within_rect(self, size: Vec2, frame: Rect) -> Rect {
|
||||
let x_range = self.x().align_size_within_range(size.x, frame.x_range());
|
||||
let y_range = self.y().align_size_within_range(size.y, frame.y_range());
|
||||
Rect::from_x_y_ranges(x_range, y_range)
|
||||
}
|
||||
|
||||
/// Returns the point on the rect's frame or in the center of a rect according
|
||||
/// to the alignments of this object.
|
||||
///
|
||||
/// ```text
|
||||
/// (*)-----------+------(*)------+-----------(*)--> X
|
||||
/// | | | |
|
||||
/// | Min, Min | Center, Min | Max, Min |
|
||||
/// | | | |
|
||||
/// +------------+---------------+------------+
|
||||
/// | | | |
|
||||
/// (*)Min, Center|Center(*)Center|Max, Center(*)
|
||||
/// | | | |
|
||||
/// +------------+---------------+------------+
|
||||
/// | | | |
|
||||
/// | Min, Max | Center, Max | Max, Max |
|
||||
/// | | | |
|
||||
/// (*)-----------+------(*)------+-----------(*)
|
||||
/// |
|
||||
/// Y
|
||||
/// ```
|
||||
pub fn pos_in_rect(self, frame: &Rect) -> Pos2 {
|
||||
let x = match self.x() {
|
||||
Align::Min => frame.left(),
|
||||
Align::Center => frame.center().x,
|
||||
Align::Max => frame.right(),
|
||||
};
|
||||
let y = match self.y() {
|
||||
Align::Min => frame.top(),
|
||||
Align::Center => frame.center().y,
|
||||
Align::Max => frame.bottom(),
|
||||
};
|
||||
|
||||
pos2(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Align2 {
|
||||
type Output = Align;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, index: usize) -> &Align {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Align2 {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, index: usize) -> &mut Align {
|
||||
&mut self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates a rectangle of the specified `size` inside the `frame` rectangle
|
||||
/// around of its center.
|
||||
///
|
||||
/// If `size` is bigger than the `frame`s size the returned rect will bounce out
|
||||
/// of the `frame`.
|
||||
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
|
||||
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
|
||||
}
|
||||
229
egui/crates/emath/src/history.rs
Normal file
229
egui/crates/emath/src/history.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
use std::collections::VecDeque;
|
||||
|
||||
/// This struct tracks recent values of some time series.
|
||||
///
|
||||
/// It can be used as a smoothing filter for e.g. latency, fps etc,
|
||||
/// or to show a log or graph of recent events.
|
||||
///
|
||||
/// It has a minimum and maximum length, as well as a maximum storage time.
|
||||
/// * The minimum length is to ensure you have enough data for an estimate.
|
||||
/// * The maximum length is to make sure the history doesn't take up too much space.
|
||||
/// * The maximum age is to make sure the estimate isn't outdated.
|
||||
///
|
||||
/// Time difference between values can be zero, but never negative.
|
||||
///
|
||||
/// This can be used for things like smoothed averages (for e.g. FPS)
|
||||
/// or for smoothed velocity (e.g. mouse pointer speed).
|
||||
/// All times are in seconds.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct History<T> {
|
||||
/// In elements, i.e. of `values.len()`.
|
||||
/// The length is initially zero, but once past `min_len` will not shrink below it.
|
||||
min_len: usize,
|
||||
|
||||
/// In elements, i.e. of `values.len()`.
|
||||
max_len: usize,
|
||||
|
||||
/// In seconds.
|
||||
max_age: f32,
|
||||
|
||||
/// Total number of elements seen ever
|
||||
total_count: u64,
|
||||
|
||||
/// (time, value) pairs, oldest front, newest back.
|
||||
/// Time difference between values can be zero, but never negative.
|
||||
values: VecDeque<(f64, T)>,
|
||||
}
|
||||
|
||||
impl<T> History<T>
|
||||
where
|
||||
T: Copy,
|
||||
{
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # use emath::History;
|
||||
/// # fn now() -> f64 { 0.0 }
|
||||
/// // Drop events that are older than one second,
|
||||
/// // as long we keep at least two events. Never keep more than a hundred events.
|
||||
/// let mut history = History::new(2..100, 1.0);
|
||||
/// assert_eq!(history.average(), None);
|
||||
/// history.add(now(), 40.0_f32);
|
||||
/// history.add(now(), 44.0_f32);
|
||||
/// assert_eq!(history.average(), Some(42.0));
|
||||
/// ```
|
||||
pub fn new(length_range: std::ops::Range<usize>, max_age: f32) -> Self {
|
||||
Self {
|
||||
min_len: length_range.start,
|
||||
max_len: length_range.end,
|
||||
max_age,
|
||||
total_count: 0,
|
||||
values: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_len(&self) -> usize {
|
||||
self.max_len
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_age(&self) -> f32 {
|
||||
self.max_age
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.values.is_empty()
|
||||
}
|
||||
|
||||
/// Current number of values kept in history
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
/// Total number of values seen.
|
||||
/// Includes those that have been discarded due to `max_len` or `max_age`.
|
||||
#[inline]
|
||||
pub fn total_count(&self) -> u64 {
|
||||
self.total_count
|
||||
}
|
||||
|
||||
pub fn latest(&self) -> Option<T> {
|
||||
self.values.back().map(|(_, value)| *value)
|
||||
}
|
||||
|
||||
pub fn latest_mut(&mut self) -> Option<&mut T> {
|
||||
self.values.back_mut().map(|(_, value)| value)
|
||||
}
|
||||
|
||||
/// Amount of time contained from start to end in this [`History`].
|
||||
pub fn duration(&self) -> f32 {
|
||||
if let (Some(front), Some(back)) = (self.values.front(), self.values.back()) {
|
||||
(back.0 - front.0) as f32
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// `(time, value)` pairs
|
||||
/// Time difference between values can be zero, but never negative.
|
||||
// TODO(emilk): impl IntoIter
|
||||
pub fn iter(&'_ self) -> impl ExactSizeIterator<Item = (f64, T)> + '_ {
|
||||
self.values.iter().map(|(time, value)| (*time, *value))
|
||||
}
|
||||
|
||||
pub fn values(&'_ self) -> impl ExactSizeIterator<Item = T> + '_ {
|
||||
self.values.iter().map(|(_time, value)| *value)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn clear(&mut self) {
|
||||
self.values.clear();
|
||||
}
|
||||
|
||||
/// Values must be added with a monotonically increasing time, or at least not decreasing.
|
||||
pub fn add(&mut self, now: f64, value: T) {
|
||||
if let Some((last_time, _)) = self.values.back() {
|
||||
crate::emath_assert!(now >= *last_time, "Time shouldn't move backwards");
|
||||
}
|
||||
self.total_count += 1;
|
||||
self.values.push_back((now, value));
|
||||
self.flush(now);
|
||||
}
|
||||
|
||||
/// Mean time difference between values in this [`History`].
|
||||
pub fn mean_time_interval(&self) -> Option<f32> {
|
||||
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
|
||||
let n = self.len();
|
||||
if n >= 2 {
|
||||
Some((last.0 - first.0) as f32 / ((n - 1) as f32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// Mean number of events per second.
|
||||
pub fn rate(&self) -> Option<f32> {
|
||||
self.mean_time_interval().map(|time| 1.0 / time)
|
||||
}
|
||||
|
||||
/// Remove samples that are too old.
|
||||
pub fn flush(&mut self, now: f64) {
|
||||
while self.values.len() > self.max_len {
|
||||
self.values.pop_front();
|
||||
}
|
||||
while self.values.len() > self.min_len {
|
||||
if let Some((front_time, _)) = self.values.front() {
|
||||
if *front_time < now - (self.max_age as f64) {
|
||||
self.values.pop_front();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> History<T>
|
||||
where
|
||||
T: Copy,
|
||||
T: std::iter::Sum,
|
||||
T: std::ops::Div<f32, Output = T>,
|
||||
{
|
||||
#[inline]
|
||||
pub fn sum(&self) -> T {
|
||||
self.values().sum()
|
||||
}
|
||||
|
||||
pub fn average(&self) -> Option<T> {
|
||||
let num = self.len();
|
||||
if num > 0 {
|
||||
Some(self.sum() / (num as f32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> History<T>
|
||||
where
|
||||
T: Copy,
|
||||
T: std::iter::Sum,
|
||||
T: std::ops::Div<f32, Output = T>,
|
||||
T: std::ops::Mul<f32, Output = T>,
|
||||
{
|
||||
/// Average times rate.
|
||||
/// If you are keeping track of individual sizes of things (e.g. bytes),
|
||||
/// this will estimate the bandwidth (bytes per second).
|
||||
pub fn bandwidth(&self) -> Option<T> {
|
||||
Some(self.average()? * self.rate()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Vel> History<T>
|
||||
where
|
||||
T: Copy,
|
||||
T: std::ops::Sub<Output = Vel>,
|
||||
Vel: std::ops::Div<f32, Output = Vel>,
|
||||
{
|
||||
/// Calculate a smooth velocity (per second) over the entire time span.
|
||||
/// Calculated as the last value minus the first value over the elapsed time between them.
|
||||
pub fn velocity(&self) -> Option<Vel> {
|
||||
if let (Some(first), Some(last)) = (self.values.front(), self.values.back()) {
|
||||
let dt = (last.0 - first.0) as f32;
|
||||
if dt > 0.0 {
|
||||
Some((last.1 - first.1) / dt)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
392
egui/crates/emath/src/lib.rs
Normal file
392
egui/crates/emath/src/lib.rs
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
//! Opinionated 2D math library for building GUIs.
|
||||
//!
|
||||
//! Includes vectors, positions, rectangles etc.
|
||||
//!
|
||||
//! Conventions (unless otherwise specified):
|
||||
//!
|
||||
//! * All angles are in radians
|
||||
//! * X+ is right and Y+ is down.
|
||||
//! * (0,0) is left top.
|
||||
//! * Dimension order is always `x y`
|
||||
//!
|
||||
//! ## Integrating with other math libraries.
|
||||
//! `emath` does not strive to become a general purpose or all-powerful math library.
|
||||
//!
|
||||
//! For that, use something else ([`glam`](https://docs.rs/glam), [`nalgebra`](https://docs.rs/nalgebra), …)
|
||||
//! and enable the `mint` feature flag in `emath` to enable implicit conversion to/from `emath`.
|
||||
//!
|
||||
//! ## Feature flags
|
||||
#![cfg_attr(feature = "document-features", doc = document_features::document_features!())]
|
||||
//!
|
||||
|
||||
#![allow(clippy::float_cmp)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub mod align;
|
||||
mod history;
|
||||
mod numeric;
|
||||
mod pos2;
|
||||
mod rect;
|
||||
mod rect_transform;
|
||||
mod rot2;
|
||||
pub mod smart_aim;
|
||||
mod vec2;
|
||||
|
||||
pub use {
|
||||
align::{Align, Align2},
|
||||
history::History,
|
||||
numeric::*,
|
||||
pos2::*,
|
||||
rect::*,
|
||||
rect_transform::*,
|
||||
rot2::*,
|
||||
vec2::*,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Helper trait to implement [`lerp`] and [`remap`].
|
||||
pub trait One {
|
||||
fn one() -> Self;
|
||||
}
|
||||
|
||||
impl One for f32 {
|
||||
#[inline(always)]
|
||||
fn one() -> Self {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
impl One for f64 {
|
||||
#[inline(always)]
|
||||
fn one() -> Self {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait to implement [`lerp`] and [`remap`].
|
||||
pub trait Real:
|
||||
Copy
|
||||
+ PartialEq
|
||||
+ PartialOrd
|
||||
+ One
|
||||
+ Add<Self, Output = Self>
|
||||
+ Sub<Self, Output = Self>
|
||||
+ Mul<Self, Output = Self>
|
||||
+ Div<Self, Output = Self>
|
||||
{
|
||||
}
|
||||
|
||||
impl Real for f32 {}
|
||||
|
||||
impl Real for f64 {}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Linear interpolation.
|
||||
///
|
||||
/// ```
|
||||
/// # use emath::lerp;
|
||||
/// assert_eq!(lerp(1.0..=5.0, 0.0), 1.0);
|
||||
/// assert_eq!(lerp(1.0..=5.0, 0.5), 3.0);
|
||||
/// assert_eq!(lerp(1.0..=5.0, 1.0), 5.0);
|
||||
/// assert_eq!(lerp(1.0..=5.0, 2.0), 9.0);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn lerp<R, T>(range: RangeInclusive<R>, t: T) -> R
|
||||
where
|
||||
T: Real + Mul<R, Output = R>,
|
||||
R: Copy + Add<R, Output = R>,
|
||||
{
|
||||
(T::one() - t) * *range.start() + t * *range.end()
|
||||
}
|
||||
|
||||
/// Where in the range is this value? Returns 0-1 if within the range.
|
||||
///
|
||||
/// Returns <0 if before and >1 if after.
|
||||
///
|
||||
/// Returns `None` if the input range is zero-width.
|
||||
///
|
||||
/// ```
|
||||
/// # use emath::inverse_lerp;
|
||||
/// assert_eq!(inverse_lerp(1.0..=5.0, 1.0), Some(0.0));
|
||||
/// assert_eq!(inverse_lerp(1.0..=5.0, 3.0), Some(0.5));
|
||||
/// assert_eq!(inverse_lerp(1.0..=5.0, 5.0), Some(1.0));
|
||||
/// assert_eq!(inverse_lerp(1.0..=5.0, 9.0), Some(2.0));
|
||||
/// assert_eq!(inverse_lerp(1.0..=1.0, 3.0), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn inverse_lerp<R>(range: RangeInclusive<R>, value: R) -> Option<R>
|
||||
where
|
||||
R: Copy + PartialEq + Sub<R, Output = R> + Div<R, Output = R>,
|
||||
{
|
||||
let min = *range.start();
|
||||
let max = *range.end();
|
||||
if min == max {
|
||||
None
|
||||
} else {
|
||||
Some((value - min) / (max - min))
|
||||
}
|
||||
}
|
||||
|
||||
/// Linearly remap a value from one range to another,
|
||||
/// so that when `x == from.start()` returns `to.start()`
|
||||
/// and when `x == from.end()` returns `to.end()`.
|
||||
pub fn remap<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
|
||||
where
|
||||
T: Real,
|
||||
{
|
||||
crate::emath_assert!(from.start() != from.end());
|
||||
let t = (x - *from.start()) / (*from.end() - *from.start());
|
||||
lerp(to, t)
|
||||
}
|
||||
|
||||
/// Like [`remap`], but also clamps the value so that the returned value is always in the `to` range.
|
||||
pub fn remap_clamp<T>(x: T, from: RangeInclusive<T>, to: RangeInclusive<T>) -> T
|
||||
where
|
||||
T: Real,
|
||||
{
|
||||
if from.end() < from.start() {
|
||||
return remap_clamp(x, *from.end()..=*from.start(), *to.end()..=*to.start());
|
||||
}
|
||||
if x <= *from.start() {
|
||||
*to.start()
|
||||
} else if *from.end() <= x {
|
||||
*to.end()
|
||||
} else {
|
||||
crate::emath_assert!(from.start() != from.end());
|
||||
let t = (x - *from.start()) / (*from.end() - *from.start());
|
||||
// Ensure no numerical inaccuracies sneak in:
|
||||
if T::one() <= t {
|
||||
*to.end()
|
||||
} else {
|
||||
lerp(to, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Round a value to the given number of decimal places.
|
||||
pub fn round_to_decimals(value: f64, decimal_places: usize) -> f64 {
|
||||
// This is a stupid way of doing this, but stupid works.
|
||||
format!("{:.*}", decimal_places, value)
|
||||
.parse()
|
||||
.unwrap_or(value)
|
||||
}
|
||||
|
||||
pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String {
|
||||
format_with_decimals_in_range(value, decimals..=6)
|
||||
}
|
||||
|
||||
pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive<usize>) -> String {
|
||||
let min_decimals = *decimal_range.start();
|
||||
let max_decimals = *decimal_range.end();
|
||||
crate::emath_assert!(min_decimals <= max_decimals);
|
||||
crate::emath_assert!(max_decimals < 100);
|
||||
let max_decimals = max_decimals.min(16);
|
||||
let min_decimals = min_decimals.min(max_decimals);
|
||||
|
||||
if min_decimals != max_decimals {
|
||||
// Ugly/slow way of doing this. TODO(emilk): clean up precision.
|
||||
for decimals in min_decimals..max_decimals {
|
||||
let text = format!("{:.*}", decimals, value);
|
||||
let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs
|
||||
if almost_equal(text.parse::<f32>().unwrap(), value as f32, epsilon) {
|
||||
// Enough precision to show the value accurately - good!
|
||||
return text;
|
||||
}
|
||||
}
|
||||
// The value has more precision than we expected.
|
||||
// Probably the value was set not by the slider, but from outside.
|
||||
// In any case: show the full value
|
||||
}
|
||||
format!("{:.*}", max_decimals, value)
|
||||
}
|
||||
|
||||
/// Return true when arguments are the same within some rounding error.
|
||||
///
|
||||
/// For instance `almost_equal(x, x.to_degrees().to_radians(), f32::EPSILON)` should hold true for all x.
|
||||
/// The `epsilon` can be `f32::EPSILON` to handle simple transforms (like degrees -> radians)
|
||||
/// but should be higher to handle more complex transformations.
|
||||
pub fn almost_equal(a: f32, b: f32, epsilon: f32) -> bool {
|
||||
if a == b {
|
||||
true // handle infinites
|
||||
} else {
|
||||
let abs_max = a.abs().max(b.abs());
|
||||
abs_max <= epsilon || ((a - b).abs() / abs_max) <= epsilon
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::approx_constant)]
|
||||
#[test]
|
||||
fn test_format() {
|
||||
assert_eq!(format_with_minimum_decimals(1_234_567.0, 0), "1234567");
|
||||
assert_eq!(format_with_minimum_decimals(1_234_567.0, 1), "1234567.0");
|
||||
assert_eq!(format_with_minimum_decimals(3.14, 2), "3.14");
|
||||
assert_eq!(format_with_minimum_decimals(3.14, 3), "3.140");
|
||||
assert_eq!(
|
||||
format_with_minimum_decimals(std::f64::consts::PI, 2),
|
||||
"3.14159"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_almost_equal() {
|
||||
for &x in &[
|
||||
0.0_f32,
|
||||
f32::MIN_POSITIVE,
|
||||
1e-20,
|
||||
1e-10,
|
||||
f32::EPSILON,
|
||||
0.1,
|
||||
0.99,
|
||||
1.0,
|
||||
1.001,
|
||||
1e10,
|
||||
f32::MAX / 100.0,
|
||||
// f32::MAX, // overflows in rad<->deg test
|
||||
f32::INFINITY,
|
||||
] {
|
||||
for &x in &[-x, x] {
|
||||
for roundtrip in &[
|
||||
|x: f32| x.to_degrees().to_radians(),
|
||||
|x: f32| x.to_radians().to_degrees(),
|
||||
] {
|
||||
let epsilon = f32::EPSILON;
|
||||
assert!(
|
||||
almost_equal(x, roundtrip(x), epsilon),
|
||||
"{} vs {}",
|
||||
x,
|
||||
roundtrip(x)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remap() {
|
||||
assert_eq!(remap_clamp(1.0, 0.0..=1.0, 0.0..=16.0), 16.0);
|
||||
assert_eq!(remap_clamp(1.0, 1.0..=0.0, 16.0..=0.0), 16.0);
|
||||
assert_eq!(remap_clamp(0.5, 1.0..=0.0, 16.0..=0.0), 8.0);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Extends `f32`, [`Vec2`] etc with `at_least` and `at_most` as aliases for `max` and `min`.
|
||||
pub trait NumExt {
|
||||
/// More readable version of `self.max(lower_limit)`
|
||||
#[must_use]
|
||||
fn at_least(self, lower_limit: Self) -> Self;
|
||||
|
||||
/// More readable version of `self.min(upper_limit)`
|
||||
#[must_use]
|
||||
fn at_most(self, upper_limit: Self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_num_ext {
|
||||
($t: ty) => {
|
||||
impl NumExt for $t {
|
||||
#[inline(always)]
|
||||
fn at_least(self, lower_limit: Self) -> Self {
|
||||
self.max(lower_limit)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn at_most(self, upper_limit: Self) -> Self {
|
||||
self.min(upper_limit)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_num_ext!(u8);
|
||||
impl_num_ext!(u16);
|
||||
impl_num_ext!(u32);
|
||||
impl_num_ext!(u64);
|
||||
impl_num_ext!(u128);
|
||||
impl_num_ext!(usize);
|
||||
impl_num_ext!(i8);
|
||||
impl_num_ext!(i16);
|
||||
impl_num_ext!(i32);
|
||||
impl_num_ext!(i64);
|
||||
impl_num_ext!(i128);
|
||||
impl_num_ext!(isize);
|
||||
impl_num_ext!(f32);
|
||||
impl_num_ext!(f64);
|
||||
impl_num_ext!(Vec2);
|
||||
impl_num_ext!(Pos2);
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Wrap angle to `[-PI, PI]` range.
|
||||
pub fn normalized_angle(mut angle: f32) -> f32 {
|
||||
use std::f32::consts::{PI, TAU};
|
||||
angle %= TAU;
|
||||
if angle > PI {
|
||||
angle -= TAU;
|
||||
} else if angle < -PI {
|
||||
angle += TAU;
|
||||
}
|
||||
angle
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalized_angle() {
|
||||
macro_rules! almost_eq {
|
||||
($left: expr, $right: expr) => {
|
||||
let left = $left;
|
||||
let right = $right;
|
||||
assert!((left - right).abs() < 1e-6, "{} != {}", left, right);
|
||||
};
|
||||
}
|
||||
|
||||
use std::f32::consts::TAU;
|
||||
almost_eq!(normalized_angle(-3.0 * TAU), 0.0);
|
||||
almost_eq!(normalized_angle(-2.3 * TAU), -0.3 * TAU);
|
||||
almost_eq!(normalized_angle(-TAU), 0.0);
|
||||
almost_eq!(normalized_angle(0.0), 0.0);
|
||||
almost_eq!(normalized_angle(TAU), 0.0);
|
||||
almost_eq!(normalized_angle(2.7 * TAU), -0.3 * TAU);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Calculate a lerp-factor for exponential smoothing using a time step.
|
||||
///
|
||||
/// * `exponential_smooth_factor(0.90, 1.0, dt)`: reach 90% in 1.0 seconds
|
||||
/// * `exponential_smooth_factor(0.50, 0.2, dt)`: reach 50% in 0.2 seconds
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// # use emath::{lerp, exponential_smooth_factor};
|
||||
/// # let (mut smoothed_value, target_value, dt) = (0.0_f32, 1.0_f32, 0.01_f32);
|
||||
/// let t = exponential_smooth_factor(0.90, 0.2, dt); // reach 90% in 0.2 seconds
|
||||
/// smoothed_value = lerp(smoothed_value..=target_value, t);
|
||||
/// ```
|
||||
pub fn exponential_smooth_factor(
|
||||
reach_this_fraction: f32,
|
||||
in_this_many_seconds: f32,
|
||||
dt: f32,
|
||||
) -> f32 {
|
||||
1.0 - (1.0 - reach_this_fraction).powf(dt / in_this_many_seconds)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// An assert that is only active when `emath` is compiled with the `extra_asserts` feature
|
||||
/// or with the `extra_debug_asserts` feature in debug builds.
|
||||
#[macro_export]
|
||||
macro_rules! emath_assert {
|
||||
($($arg: tt)*) => {
|
||||
if cfg!(any(
|
||||
feature = "extra_asserts",
|
||||
all(feature = "extra_debug_asserts", debug_assertions),
|
||||
)) {
|
||||
assert!($($arg)*);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
egui/crates/emath/src/numeric.rs
Normal file
74
egui/crates/emath/src/numeric.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/// Implemented for all builtin numeric types
|
||||
pub trait Numeric: Clone + Copy + PartialEq + PartialOrd + 'static {
|
||||
/// Is this an integer type?
|
||||
const INTEGRAL: bool;
|
||||
|
||||
/// Smallest finite value
|
||||
const MIN: Self;
|
||||
|
||||
/// Largest finite value
|
||||
const MAX: Self;
|
||||
|
||||
fn to_f64(self) -> f64;
|
||||
|
||||
fn from_f64(num: f64) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_numeric_float {
|
||||
($t: ident) => {
|
||||
impl Numeric for $t {
|
||||
const INTEGRAL: bool = false;
|
||||
const MIN: Self = std::$t::MIN;
|
||||
const MAX: Self = std::$t::MAX;
|
||||
|
||||
#[inline(always)]
|
||||
fn to_f64(self) -> f64 {
|
||||
#[allow(trivial_numeric_casts)]
|
||||
{
|
||||
self as f64
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_f64(num: f64) -> Self {
|
||||
#[allow(trivial_numeric_casts)]
|
||||
{
|
||||
num as Self
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! impl_numeric_integer {
|
||||
($t: ident) => {
|
||||
impl Numeric for $t {
|
||||
const INTEGRAL: bool = true;
|
||||
const MIN: Self = std::$t::MIN;
|
||||
const MAX: Self = std::$t::MAX;
|
||||
|
||||
#[inline(always)]
|
||||
fn to_f64(self) -> f64 {
|
||||
self as f64
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_f64(num: f64) -> Self {
|
||||
num as Self
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_numeric_float!(f32);
|
||||
impl_numeric_float!(f64);
|
||||
impl_numeric_integer!(i8);
|
||||
impl_numeric_integer!(u8);
|
||||
impl_numeric_integer!(i16);
|
||||
impl_numeric_integer!(u16);
|
||||
impl_numeric_integer!(i32);
|
||||
impl_numeric_integer!(u32);
|
||||
impl_numeric_integer!(i64);
|
||||
impl_numeric_integer!(u64);
|
||||
impl_numeric_integer!(isize);
|
||||
impl_numeric_integer!(usize);
|
||||
279
egui/crates/emath/src/pos2.rs
Normal file
279
egui/crates/emath/src/pos2.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
use std::ops::{Add, AddAssign, Sub, SubAssign};
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A position on screen.
|
||||
///
|
||||
/// Normally given in points (logical pixels).
|
||||
///
|
||||
/// Mathematically this is known as a "point", but the term position was chosen so not to
|
||||
/// conflict with the unit (one point = X physical pixels).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Pos2 {
|
||||
/// How far to the right.
|
||||
pub x: f32,
|
||||
|
||||
/// How far down.
|
||||
pub y: f32,
|
||||
// implicit w = 1
|
||||
}
|
||||
|
||||
/// `pos2(x, y) == Pos2::new(x, y)`
|
||||
#[inline(always)]
|
||||
pub const fn pos2(x: f32, y: f32) -> Pos2 {
|
||||
Pos2 { x, y }
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Compatibility and convenience conversions to and from [f32; 2]:
|
||||
|
||||
impl From<[f32; 2]> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn from(v: [f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[f32; 2]> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn from(v: &[f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pos2> for [f32; 2] {
|
||||
#[inline(always)]
|
||||
fn from(v: Pos2) -> Self {
|
||||
[v.x, v.y]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Pos2> for [f32; 2] {
|
||||
#[inline(always)]
|
||||
fn from(v: &Pos2) -> Self {
|
||||
[v.x, v.y]
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Compatibility and convenience conversions to and from (f32, f32):
|
||||
|
||||
impl From<(f32, f32)> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn from(v: (f32, f32)) -> Self {
|
||||
Self { x: v.0, y: v.1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&(f32, f32)> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn from(v: &(f32, f32)) -> Self {
|
||||
Self { x: v.0, y: v.1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Pos2> for (f32, f32) {
|
||||
#[inline(always)]
|
||||
fn from(v: Pos2) -> Self {
|
||||
(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Pos2> for (f32, f32) {
|
||||
#[inline(always)]
|
||||
fn from(v: &Pos2) -> Self {
|
||||
(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mint compatibility and convenience conversions
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl From<mint::Point2<f32>> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn from(v: mint::Point2<f32>) -> Self {
|
||||
Self::new(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl From<Pos2> for mint::Point2<f32> {
|
||||
#[inline(always)]
|
||||
fn from(v: Pos2) -> Self {
|
||||
Self { x: v.x, y: v.y }
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl Pos2 {
|
||||
/// The zero position, the origin.
|
||||
/// The top left corner in a GUI.
|
||||
/// Same as `Pos2::default()`.
|
||||
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// The vector from origin to this position.
|
||||
/// `p.to_vec2()` is equivalent to `p - Pos2::default()`.
|
||||
#[inline(always)]
|
||||
pub fn to_vec2(self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn distance(self, other: Self) -> f32 {
|
||||
(self - other).length()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn distance_sq(self, other: Self) -> f32 {
|
||||
(self - other).length_sq()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn floor(self) -> Self {
|
||||
pos2(self.x.floor(), self.y.floor())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn round(self) -> Self {
|
||||
pos2(self.x.round(), self.y.round())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn ceil(self) -> Self {
|
||||
pos2(self.x.ceil(), self.y.ceil())
|
||||
}
|
||||
|
||||
/// True if all members are also finite.
|
||||
#[inline(always)]
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
|
||||
/// True if any member is NaN.
|
||||
#[inline(always)]
|
||||
pub fn any_nan(self) -> bool {
|
||||
self.x.is_nan() || self.y.is_nan()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
pos2(self.x.min(other.x), self.y.min(other.y))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
pos2(self.x.max(other.x), self.y.max(other.y))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn clamp(self, min: Self, max: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.clamp(min.x, max.x),
|
||||
y: self.y.clamp(min.y, max.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Pos2 {
|
||||
type Output = f32;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, index: usize) -> &f32 {
|
||||
match index {
|
||||
0 => &self.x,
|
||||
1 => &self.y,
|
||||
_ => panic!("Pos2 index out of bounds: {}", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, index: usize) -> &mut f32 {
|
||||
match index {
|
||||
0 => &mut self.x,
|
||||
1 => &mut self.y,
|
||||
_ => panic!("Pos2 index out of bounds: {}", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Pos2 {}
|
||||
|
||||
impl AddAssign<Vec2> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, rhs: Vec2) {
|
||||
*self = Pos2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Vec2> for Pos2 {
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Vec2) {
|
||||
*self = Pos2 {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<Vec2> for Pos2 {
|
||||
type Output = Pos2;
|
||||
|
||||
#[inline(always)]
|
||||
fn add(self, rhs: Vec2) -> Pos2 {
|
||||
Pos2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Pos2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn sub(self, rhs: Pos2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Vec2> for Pos2 {
|
||||
type Output = Pos2;
|
||||
|
||||
#[inline(always)]
|
||||
fn sub(self, rhs: Vec2) -> Pos2 {
|
||||
Pos2 {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Pos2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[{:.1} {:.1}]", self.x, self.y)
|
||||
}
|
||||
}
|
||||
537
egui/crates/emath/src/rect.rs
Normal file
537
egui/crates/emath/src/rect.rs
Normal file
|
|
@ -0,0 +1,537 @@
|
|||
use std::f32::INFINITY;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A rectangular region of space.
|
||||
///
|
||||
/// Usually a [`Rect`] has a positive (or zero) size,
|
||||
/// and then [`Self::min`] `<=` [`Self::max`].
|
||||
/// In these cases [`Self::min`] is the left-top corner
|
||||
/// and [`Self::max`] is the right-bottom corner.
|
||||
///
|
||||
/// A rectangle is allowed to have a negative size, which happens when the order
|
||||
/// of `min` and `max` are swapped. These are usually a sign of an error.
|
||||
///
|
||||
/// Normally the unit is points (logical pixels) in screen space coordinates.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Rect {
|
||||
/// One of the corners of the rectangle, usually the left top one.
|
||||
pub min: Pos2,
|
||||
|
||||
/// The other corner, opposing [`Self::min`]. Usually the right bottom one.
|
||||
pub max: Pos2,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Infinite rectangle that contains every point.
|
||||
pub const EVERYTHING: Self = Self {
|
||||
min: pos2(-INFINITY, -INFINITY),
|
||||
max: pos2(INFINITY, INFINITY),
|
||||
};
|
||||
|
||||
/// The inverse of [`Self::EVERYTHING`]: stretches from positive infinity to negative infinity.
|
||||
/// Contains no points.
|
||||
///
|
||||
/// This is useful as the seed for bounding boxes.
|
||||
///
|
||||
/// # Example:
|
||||
/// ```
|
||||
/// # use emath::*;
|
||||
/// let mut rect = Rect::NOTHING;
|
||||
/// assert!(rect.size() == Vec2::splat(-f32::INFINITY));
|
||||
/// assert!(rect.contains(pos2(0.0, 0.0)) == false);
|
||||
/// rect.extend_with(pos2(2.0, 1.0));
|
||||
/// rect.extend_with(pos2(0.0, 3.0));
|
||||
/// assert_eq!(rect, Rect::from_min_max(pos2(0.0, 1.0), pos2(2.0, 3.0)))
|
||||
/// ```
|
||||
pub const NOTHING: Self = Self {
|
||||
min: pos2(INFINITY, INFINITY),
|
||||
max: pos2(-INFINITY, -INFINITY),
|
||||
};
|
||||
|
||||
/// An invalid [`Rect`] filled with [`f32::NAN`];
|
||||
pub const NAN: Self = Self {
|
||||
min: pos2(f32::NAN, f32::NAN),
|
||||
max: pos2(f32::NAN, f32::NAN),
|
||||
};
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn from_min_max(min: Pos2, max: Pos2) -> Self {
|
||||
Rect { min, max }
|
||||
}
|
||||
|
||||
/// left-top corner plus a size (stretching right-down).
|
||||
#[inline(always)]
|
||||
pub fn from_min_size(min: Pos2, size: Vec2) -> Self {
|
||||
Rect {
|
||||
min,
|
||||
max: min + size,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_center_size(center: Pos2, size: Vec2) -> Self {
|
||||
Rect {
|
||||
min: center - size * 0.5,
|
||||
max: center + size * 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn from_x_y_ranges(x_range: RangeInclusive<f32>, y_range: RangeInclusive<f32>) -> Self {
|
||||
Rect {
|
||||
min: pos2(*x_range.start(), *y_range.start()),
|
||||
max: pos2(*x_range.end(), *y_range.end()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bounding rectangle of the two points.
|
||||
#[inline]
|
||||
pub fn from_two_pos(a: Pos2, b: Pos2) -> Self {
|
||||
Rect {
|
||||
min: pos2(a.x.min(b.x), a.y.min(b.y)),
|
||||
max: pos2(a.x.max(b.x), a.y.max(b.y)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Bounding-box around the points.
|
||||
pub fn from_points(points: &[Pos2]) -> Self {
|
||||
let mut rect = Rect::NOTHING;
|
||||
for &p in points {
|
||||
rect.extend_with(p);
|
||||
}
|
||||
rect
|
||||
}
|
||||
|
||||
/// A [`Rect`] that contains every point to the right of the given X coordinate.
|
||||
#[inline]
|
||||
pub fn everything_right_of(left_x: f32) -> Self {
|
||||
let mut rect = Self::EVERYTHING;
|
||||
rect.set_left(left_x);
|
||||
rect
|
||||
}
|
||||
|
||||
/// A [`Rect`] that contains every point to the left of the given X coordinate.
|
||||
#[inline]
|
||||
pub fn everything_left_of(right_x: f32) -> Self {
|
||||
let mut rect = Self::EVERYTHING;
|
||||
rect.set_right(right_x);
|
||||
rect
|
||||
}
|
||||
|
||||
/// A [`Rect`] that contains every point below a certain y coordinate
|
||||
#[inline]
|
||||
pub fn everything_below(top_y: f32) -> Self {
|
||||
let mut rect = Self::EVERYTHING;
|
||||
rect.set_top(top_y);
|
||||
rect
|
||||
}
|
||||
|
||||
/// A [`Rect`] that contains every point above a certain y coordinate
|
||||
#[inline]
|
||||
pub fn everything_above(bottom_y: f32) -> Self {
|
||||
let mut rect = Self::EVERYTHING;
|
||||
rect.set_bottom(bottom_y);
|
||||
rect
|
||||
}
|
||||
|
||||
/// Expand by this much in each direction, keeping the center
|
||||
#[must_use]
|
||||
pub fn expand(self, amnt: f32) -> Self {
|
||||
self.expand2(Vec2::splat(amnt))
|
||||
}
|
||||
|
||||
/// Expand by this much in each direction, keeping the center
|
||||
#[must_use]
|
||||
pub fn expand2(self, amnt: Vec2) -> Self {
|
||||
Rect::from_min_max(self.min - amnt, self.max + amnt)
|
||||
}
|
||||
|
||||
/// Shrink by this much in each direction, keeping the center
|
||||
#[must_use]
|
||||
pub fn shrink(self, amnt: f32) -> Self {
|
||||
self.shrink2(Vec2::splat(amnt))
|
||||
}
|
||||
|
||||
/// Shrink by this much in each direction, keeping the center
|
||||
#[must_use]
|
||||
pub fn shrink2(self, amnt: Vec2) -> Self {
|
||||
Rect::from_min_max(self.min + amnt, self.max - amnt)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn translate(self, amnt: Vec2) -> Self {
|
||||
Rect::from_min_size(self.min + amnt, self.size())
|
||||
}
|
||||
|
||||
/// Rotate the bounds (will expand the [`Rect`])
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn rotate_bb(self, rot: Rot2) -> Self {
|
||||
let a = rot * self.left_top().to_vec2();
|
||||
let b = rot * self.right_top().to_vec2();
|
||||
let c = rot * self.left_bottom().to_vec2();
|
||||
let d = rot * self.right_bottom().to_vec2();
|
||||
|
||||
Self::from_min_max(
|
||||
a.min(b).min(c).min(d).to_pos2(),
|
||||
a.max(b).max(c).max(d).to_pos2(),
|
||||
)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn intersects(self, other: Rect) -> bool {
|
||||
self.min.x <= other.max.x
|
||||
&& other.min.x <= self.max.x
|
||||
&& self.min.y <= other.max.y
|
||||
&& other.min.y <= self.max.y
|
||||
}
|
||||
|
||||
/// keep min
|
||||
pub fn set_width(&mut self, w: f32) {
|
||||
self.max.x = self.min.x + w;
|
||||
}
|
||||
|
||||
/// keep min
|
||||
pub fn set_height(&mut self, h: f32) {
|
||||
self.max.y = self.min.y + h;
|
||||
}
|
||||
|
||||
/// Keep size
|
||||
pub fn set_center(&mut self, center: Pos2) {
|
||||
*self = self.translate(center - self.center());
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn contains(&self, p: Pos2) -> bool {
|
||||
self.min.x <= p.x && p.x <= self.max.x && self.min.y <= p.y && p.y <= self.max.y
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn contains_rect(&self, other: Rect) -> bool {
|
||||
self.contains(other.min) && self.contains(other.max)
|
||||
}
|
||||
|
||||
/// Return the given points clamped to be inside the rectangle
|
||||
/// Panics if [`Self::is_negative`].
|
||||
#[must_use]
|
||||
pub fn clamp(&self, p: Pos2) -> Pos2 {
|
||||
p.clamp(self.min, self.max)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn extend_with(&mut self, p: Pos2) {
|
||||
self.min = self.min.min(p);
|
||||
self.max = self.max.max(p);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
/// Expand to include the given x coordinate
|
||||
pub fn extend_with_x(&mut self, x: f32) {
|
||||
self.min.x = self.min.x.min(x);
|
||||
self.max.x = self.max.x.max(x);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
/// Expand to include the given y coordinate
|
||||
pub fn extend_with_y(&mut self, y: f32) {
|
||||
self.min.y = self.min.y.min(y);
|
||||
self.max.y = self.max.y.max(y);
|
||||
}
|
||||
|
||||
/// The union of two bounding rectangle, i.e. the minimum [`Rect`]
|
||||
/// that contains both input rectangles.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn union(self, other: Rect) -> Rect {
|
||||
Rect {
|
||||
min: self.min.min(other.min),
|
||||
max: self.max.max(other.max),
|
||||
}
|
||||
}
|
||||
|
||||
/// The intersection of two [`Rect`], i.e. the area covered by both.
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn intersect(self, other: Rect) -> Self {
|
||||
Self {
|
||||
min: self.min.max(other.min),
|
||||
max: self.max.min(other.max),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn center(&self) -> Pos2 {
|
||||
Pos2 {
|
||||
x: (self.min.x + self.max.x) / 2.0,
|
||||
y: (self.min.y + self.max.y) / 2.0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.max - self.min
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn width(&self) -> f32 {
|
||||
self.max.x - self.min.x
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn height(&self) -> f32 {
|
||||
self.max.y - self.min.y
|
||||
}
|
||||
|
||||
/// Width / height
|
||||
///
|
||||
/// * `aspect_ratio < 1`: portrait / high
|
||||
/// * `aspect_ratio = 1`: square
|
||||
/// * `aspect_ratio > 1`: landscape / wide
|
||||
pub fn aspect_ratio(&self) -> f32 {
|
||||
self.width() / self.height()
|
||||
}
|
||||
|
||||
/// `[2, 1]` for wide screen, and `[1, 2]` for portrait, etc.
|
||||
/// At least one dimension = 1, the other >= 1
|
||||
/// Returns the proportions required to letter-box a square view area.
|
||||
pub fn square_proportions(&self) -> Vec2 {
|
||||
let w = self.width();
|
||||
let h = self.height();
|
||||
if w > h {
|
||||
vec2(w / h, 1.0)
|
||||
} else {
|
||||
vec2(1.0, h / w)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn area(&self) -> f32 {
|
||||
self.width() * self.height()
|
||||
}
|
||||
|
||||
/// The distance from the rect to the position.
|
||||
///
|
||||
/// The distance is zero when the position is in the interior of the rectangle.
|
||||
#[inline]
|
||||
pub fn distance_to_pos(&self, pos: Pos2) -> f32 {
|
||||
self.distance_sq_to_pos(pos).sqrt()
|
||||
}
|
||||
|
||||
/// The distance from the rect to the position, squared.
|
||||
///
|
||||
/// The distance is zero when the position is in the interior of the rectangle.
|
||||
#[inline]
|
||||
pub fn distance_sq_to_pos(&self, pos: Pos2) -> f32 {
|
||||
let dx = if self.min.x > pos.x {
|
||||
self.min.x - pos.x
|
||||
} else if pos.x > self.max.x {
|
||||
pos.x - self.max.x
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
let dy = if self.min.y > pos.y {
|
||||
self.min.y - pos.y
|
||||
} else if pos.y > self.max.y {
|
||||
pos.y - self.max.y
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
dx * dx + dy * dy
|
||||
}
|
||||
|
||||
/// Signed distance to the edge of the box.
|
||||
///
|
||||
/// Negative inside the box.
|
||||
pub fn signed_distance_to_pos(&self, pos: Pos2) -> f32 {
|
||||
let edge_distances = (pos - self.center()).abs() - self.size() * 0.5;
|
||||
let inside_dist = edge_distances.x.max(edge_distances.y).min(0.0);
|
||||
let outside_dist = edge_distances.max(Vec2::ZERO).length();
|
||||
inside_dist + outside_dist
|
||||
}
|
||||
|
||||
/// Linearly interpolate so that `[0, 0]` is [`Self::min`] and
|
||||
/// `[1, 1]` is [`Self::max`].
|
||||
pub fn lerp(&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),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn x_range(&self) -> RangeInclusive<f32> {
|
||||
self.min.x..=self.max.x
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn y_range(&self) -> RangeInclusive<f32> {
|
||||
self.min.y..=self.max.y
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn bottom_up_range(&self) -> RangeInclusive<f32> {
|
||||
self.max.y..=self.min.y
|
||||
}
|
||||
|
||||
/// `width < 0 || height < 0`
|
||||
#[inline(always)]
|
||||
pub fn is_negative(&self) -> bool {
|
||||
self.max.x < self.min.x || self.max.y < self.min.y
|
||||
}
|
||||
|
||||
/// `width > 0 && height > 0`
|
||||
#[inline(always)]
|
||||
pub fn is_positive(&self) -> bool {
|
||||
self.min.x < self.max.x && self.min.y < self.max.y
|
||||
}
|
||||
|
||||
/// True if all members are also finite.
|
||||
#[inline(always)]
|
||||
pub fn is_finite(&self) -> bool {
|
||||
self.min.is_finite() && self.max.is_finite()
|
||||
}
|
||||
|
||||
/// True if any member is NaN.
|
||||
#[inline(always)]
|
||||
pub fn any_nan(self) -> bool {
|
||||
self.min.any_nan() || self.max.any_nan()
|
||||
}
|
||||
}
|
||||
|
||||
/// ## Convenience functions (assumes origin is towards left top):
|
||||
impl Rect {
|
||||
/// `min.x`
|
||||
#[inline(always)]
|
||||
pub fn left(&self) -> f32 {
|
||||
self.min.x
|
||||
}
|
||||
|
||||
/// `min.x`
|
||||
#[inline(always)]
|
||||
pub fn left_mut(&mut self) -> &mut f32 {
|
||||
&mut self.min.x
|
||||
}
|
||||
|
||||
/// `min.x`
|
||||
#[inline(always)]
|
||||
pub fn set_left(&mut self, x: f32) {
|
||||
self.min.x = x;
|
||||
}
|
||||
|
||||
/// `max.x`
|
||||
#[inline(always)]
|
||||
pub fn right(&self) -> f32 {
|
||||
self.max.x
|
||||
}
|
||||
|
||||
/// `max.x`
|
||||
#[inline(always)]
|
||||
pub fn right_mut(&mut self) -> &mut f32 {
|
||||
&mut self.max.x
|
||||
}
|
||||
|
||||
/// `max.x`
|
||||
#[inline(always)]
|
||||
pub fn set_right(&mut self, x: f32) {
|
||||
self.max.x = x;
|
||||
}
|
||||
|
||||
/// `min.y`
|
||||
#[inline(always)]
|
||||
pub fn top(&self) -> f32 {
|
||||
self.min.y
|
||||
}
|
||||
|
||||
/// `min.y`
|
||||
#[inline(always)]
|
||||
pub fn top_mut(&mut self) -> &mut f32 {
|
||||
&mut self.min.y
|
||||
}
|
||||
|
||||
/// `min.y`
|
||||
#[inline(always)]
|
||||
pub fn set_top(&mut self, y: f32) {
|
||||
self.min.y = y;
|
||||
}
|
||||
|
||||
/// `max.y`
|
||||
#[inline(always)]
|
||||
pub fn bottom(&self) -> f32 {
|
||||
self.max.y
|
||||
}
|
||||
|
||||
/// `max.y`
|
||||
#[inline(always)]
|
||||
pub fn bottom_mut(&mut self) -> &mut f32 {
|
||||
&mut self.max.y
|
||||
}
|
||||
|
||||
/// `max.y`
|
||||
#[inline(always)]
|
||||
pub fn set_bottom(&mut self, y: f32) {
|
||||
self.max.y = y;
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn left_top(&self) -> Pos2 {
|
||||
pos2(self.left(), self.top())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn center_top(&self) -> Pos2 {
|
||||
pos2(self.center().x, self.top())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn right_top(&self) -> Pos2 {
|
||||
pos2(self.right(), self.top())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn left_center(&self) -> Pos2 {
|
||||
pos2(self.left(), self.center().y)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn right_center(&self) -> Pos2 {
|
||||
pos2(self.right(), self.center().y)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn left_bottom(&self) -> Pos2 {
|
||||
pos2(self.left(), self.bottom())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn center_bottom(&self) -> Pos2 {
|
||||
pos2(self.center().x, self.bottom())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn right_bottom(&self) -> Pos2 {
|
||||
pos2(self.right(), self.bottom())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Rect {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[{:?} - {:?}]", self.min, self.max)
|
||||
}
|
||||
}
|
||||
|
||||
/// from (min, max) or (left top, right bottom)
|
||||
impl From<[Pos2; 2]> for Rect {
|
||||
fn from([min, max]: [Pos2; 2]) -> Self {
|
||||
Self { min, max }
|
||||
}
|
||||
}
|
||||
83
egui/crates/emath/src/rect_transform.rs
Normal file
83
egui/crates/emath/src/rect_transform.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
use crate::{pos2, remap, remap_clamp, Pos2, Rect, Vec2};
|
||||
|
||||
/// Linearly transforms positions from one [`Rect`] to another.
|
||||
///
|
||||
/// [`RectTransform`] stores the rectangles, and therefore supports clamping and culling.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct RectTransform {
|
||||
from: Rect,
|
||||
to: Rect,
|
||||
}
|
||||
|
||||
impl RectTransform {
|
||||
pub fn identity(from_and_to: Rect) -> Self {
|
||||
Self::from_to(from_and_to, from_and_to)
|
||||
}
|
||||
|
||||
pub fn from_to(from: Rect, to: Rect) -> Self {
|
||||
Self { from, to }
|
||||
}
|
||||
|
||||
pub fn from(&self) -> &Rect {
|
||||
&self.from
|
||||
}
|
||||
|
||||
pub fn to(&self) -> &Rect {
|
||||
&self.to
|
||||
}
|
||||
|
||||
/// The scale factors.
|
||||
pub fn scale(&self) -> Vec2 {
|
||||
self.to.size() / self.from.size()
|
||||
}
|
||||
|
||||
pub fn inverse(&self) -> RectTransform {
|
||||
Self::from_to(self.to, self.from)
|
||||
}
|
||||
|
||||
/// Transforms the given coordinate in the `from` space to the `to` space.
|
||||
pub fn transform_pos(&self, pos: Pos2) -> Pos2 {
|
||||
pos2(
|
||||
remap(pos.x, self.from.x_range(), self.to.x_range()),
|
||||
remap(pos.y, self.from.y_range(), self.to.y_range()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Transforms the given rectangle in the `in`-space to a rectangle in the `out`-space.
|
||||
pub fn transform_rect(&self, rect: Rect) -> Rect {
|
||||
Rect {
|
||||
min: self.transform_pos(rect.min),
|
||||
max: self.transform_pos(rect.max),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms the given coordinate in the `from` space to the `to` space,
|
||||
/// clamping if necessary.
|
||||
pub fn transform_pos_clamped(&self, pos: Pos2) -> Pos2 {
|
||||
pos2(
|
||||
remap_clamp(pos.x, self.from.x_range(), self.to.x_range()),
|
||||
remap_clamp(pos.y, self.from.y_range(), self.to.y_range()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms the position.
|
||||
impl std::ops::Mul<Pos2> for RectTransform {
|
||||
type Output = Pos2;
|
||||
|
||||
fn mul(self, pos: Pos2) -> Pos2 {
|
||||
self.transform_pos(pos)
|
||||
}
|
||||
}
|
||||
|
||||
/// Transforms the position.
|
||||
impl std::ops::Mul<Pos2> for &RectTransform {
|
||||
type Output = Pos2;
|
||||
|
||||
fn mul(self, pos: Pos2) -> Pos2 {
|
||||
self.transform_pos(pos)
|
||||
}
|
||||
}
|
||||
198
egui/crates/emath/src/rot2.rs
Normal file
198
egui/crates/emath/src/rot2.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
use super::Vec2;
|
||||
|
||||
// {s,c} represents the rotation matrix:
|
||||
//
|
||||
// | c -s |
|
||||
// | s c |
|
||||
//
|
||||
// `vec2(c,s)` represents where the X axis will end up after rotation.
|
||||
//
|
||||
/// Represents a rotation in the 2D plane.
|
||||
//
|
||||
/// A rotation of 𝞃/4 = 90° rotates the X axis to the Y axis.
|
||||
//
|
||||
/// Normally a [`Rot2`] is normalized (unit-length).
|
||||
/// If not, it will also scale vectors.
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Rot2 {
|
||||
/// angle.sin()
|
||||
s: f32,
|
||||
|
||||
/// angle.cos()
|
||||
c: f32,
|
||||
}
|
||||
|
||||
/// Identity rotation
|
||||
impl Default for Rot2 {
|
||||
/// Identity rotation
|
||||
fn default() -> Self {
|
||||
Self { s: 0.0, c: 1.0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Rot2 {
|
||||
/// The identity rotation: nothing rotates
|
||||
pub const IDENTITY: Self = Self { s: 0.0, c: 1.0 };
|
||||
|
||||
/// Angle is clockwise in radians.
|
||||
/// A 𝞃/4 = 90° rotation means rotating the X axis to the Y axis.
|
||||
pub fn from_angle(angle: f32) -> Self {
|
||||
let (s, c) = angle.sin_cos();
|
||||
Self { s, c }
|
||||
}
|
||||
|
||||
pub fn angle(self) -> f32 {
|
||||
self.s.atan2(self.c)
|
||||
}
|
||||
|
||||
/// The factor by which vectors will be scaled.
|
||||
pub fn length(self) -> f32 {
|
||||
self.c.hypot(self.s)
|
||||
}
|
||||
|
||||
pub fn length_squared(self) -> f32 {
|
||||
self.c.powi(2) + self.s.powi(2)
|
||||
}
|
||||
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.c.is_finite() && self.s.is_finite()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn inverse(self) -> Rot2 {
|
||||
Self {
|
||||
s: -self.s,
|
||||
c: self.c,
|
||||
} / self.length_squared()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn normalized(self) -> Self {
|
||||
let l = self.length();
|
||||
let ret = Self {
|
||||
c: self.c / l,
|
||||
s: self.s / l,
|
||||
};
|
||||
crate::emath_assert!(ret.is_finite());
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Rot2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Rot2 {{ angle: {:.1}°, length: {} }}",
|
||||
self.angle().to_degrees(),
|
||||
self.length()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Rot2> for Rot2 {
|
||||
type Output = Rot2;
|
||||
|
||||
fn mul(self, r: Rot2) -> Rot2 {
|
||||
/*
|
||||
|lc -ls| * |rc -rs|
|
||||
|ls lc| |rs rc|
|
||||
*/
|
||||
Rot2 {
|
||||
c: self.c * r.c - self.s * r.s,
|
||||
s: self.s * r.c + self.c * r.s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotates (and maybe scales) the vector.
|
||||
impl std::ops::Mul<Vec2> for Rot2 {
|
||||
type Output = Vec2;
|
||||
|
||||
fn mul(self, v: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.c * v.x - self.s * v.y,
|
||||
y: self.s * v.x + self.c * v.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales the rotor.
|
||||
impl std::ops::Mul<Rot2> for f32 {
|
||||
type Output = Rot2;
|
||||
|
||||
fn mul(self, r: Rot2) -> Rot2 {
|
||||
Rot2 {
|
||||
c: self * r.c,
|
||||
s: self * r.s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales the rotor.
|
||||
impl std::ops::Mul<f32> for Rot2 {
|
||||
type Output = Rot2;
|
||||
|
||||
fn mul(self, r: f32) -> Rot2 {
|
||||
Rot2 {
|
||||
c: self.c * r,
|
||||
s: self.s * r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scales the rotor.
|
||||
impl std::ops::Div<f32> for Rot2 {
|
||||
type Output = Rot2;
|
||||
|
||||
fn div(self, r: f32) -> Rot2 {
|
||||
Rot2 {
|
||||
c: self.c / r,
|
||||
s: self.s / r,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::Rot2;
|
||||
use crate::vec2;
|
||||
|
||||
#[test]
|
||||
fn test_rotation2() {
|
||||
{
|
||||
let angle = std::f32::consts::TAU / 6.0;
|
||||
let rot = Rot2::from_angle(angle);
|
||||
assert!((rot.angle() - angle).abs() < 1e-5);
|
||||
assert!((rot * rot.inverse()).angle().abs() < 1e-5);
|
||||
assert!((rot.inverse() * rot).angle().abs() < 1e-5);
|
||||
}
|
||||
|
||||
{
|
||||
let angle = std::f32::consts::TAU / 4.0;
|
||||
let rot = Rot2::from_angle(angle);
|
||||
assert!(((rot * vec2(1.0, 0.0)) - vec2(0.0, 1.0)).length() < 1e-5);
|
||||
}
|
||||
|
||||
{
|
||||
// Test rotation and scaling
|
||||
let angle = std::f32::consts::TAU / 4.0;
|
||||
let rot = 3.0 * Rot2::from_angle(angle);
|
||||
let rotated = rot * vec2(1.0, 0.0);
|
||||
let expected = vec2(0.0, 3.0);
|
||||
assert!(
|
||||
(rotated - expected).length() < 1e-5,
|
||||
"Expected {:?} to equal {:?}. rot: {:?}",
|
||||
rotated,
|
||||
expected,
|
||||
rot,
|
||||
);
|
||||
|
||||
let undone = rot.inverse() * rot;
|
||||
assert!(undone.angle().abs() < 1e-5);
|
||||
assert!((undone.length() - 1.0).abs() < 1e-5,);
|
||||
}
|
||||
}
|
||||
}
|
||||
157
egui/crates/emath/src/smart_aim.rs
Normal file
157
egui/crates/emath/src/smart_aim.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
//! Find "simple" numbers is some range. Used by sliders.
|
||||
|
||||
const NUM_DECIMALS: usize = 15;
|
||||
|
||||
/// Find the "simplest" number in a closed range [min, max], i.e. the one with the fewest decimal digits.
|
||||
///
|
||||
/// So in the range `[0.83, 1.354]` you will get `1.0`, and for `[0.37, 0.48]` you will get `0.4`.
|
||||
/// This is used when dragging sliders etc to get the values that users are most likely to desire.
|
||||
/// This assumes a decimal centric user.
|
||||
pub fn best_in_range_f64(min: f64, max: f64) -> f64 {
|
||||
// Avoid NaN if we can:
|
||||
if min.is_nan() {
|
||||
return max;
|
||||
}
|
||||
if max.is_nan() {
|
||||
return min;
|
||||
}
|
||||
|
||||
if max < min {
|
||||
return best_in_range_f64(max, min);
|
||||
}
|
||||
if min == max {
|
||||
return min;
|
||||
}
|
||||
if min <= 0.0 && 0.0 <= max {
|
||||
return 0.0; // always prefer zero
|
||||
}
|
||||
if min < 0.0 {
|
||||
return -best_in_range_f64(-max, -min);
|
||||
}
|
||||
|
||||
// Prefer finite numbers:
|
||||
if !max.is_finite() {
|
||||
return min;
|
||||
}
|
||||
crate::emath_assert!(min.is_finite() && max.is_finite());
|
||||
|
||||
let min_exponent = min.log10();
|
||||
let max_exponent = max.log10();
|
||||
|
||||
if min_exponent.floor() != max_exponent.floor() {
|
||||
// pick the geometric center of the two:
|
||||
let exponent = (min_exponent + max_exponent) / 2.0;
|
||||
return 10.0_f64.powi(exponent.round() as i32);
|
||||
}
|
||||
|
||||
if is_integer(min_exponent) {
|
||||
return 10.0_f64.powf(min_exponent);
|
||||
}
|
||||
if is_integer(max_exponent) {
|
||||
return 10.0_f64.powf(max_exponent);
|
||||
}
|
||||
|
||||
let exp_factor = 10.0_f64.powi(max_exponent.floor() as i32);
|
||||
|
||||
let min_str = to_decimal_string(min / exp_factor);
|
||||
let max_str = to_decimal_string(max / exp_factor);
|
||||
|
||||
// eprintln!("min_str: {:?}", min_str);
|
||||
// eprintln!("max_str: {:?}", max_str);
|
||||
|
||||
let mut ret_str = [0; NUM_DECIMALS];
|
||||
|
||||
// Select the common prefix:
|
||||
let mut i = 0;
|
||||
while i < NUM_DECIMALS && max_str[i] == min_str[i] {
|
||||
ret_str[i] = max_str[i];
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if i < NUM_DECIMALS {
|
||||
// Pick the deciding digit.
|
||||
// Note that "to_decimal_string" rounds down, so we that's why we add 1 here
|
||||
ret_str[i] = simplest_digit_closed_range(min_str[i] + 1, max_str[i]);
|
||||
}
|
||||
|
||||
from_decimal_string(&ret_str) * exp_factor
|
||||
}
|
||||
|
||||
fn is_integer(f: f64) -> bool {
|
||||
f.round() == f
|
||||
}
|
||||
|
||||
fn to_decimal_string(v: f64) -> [i32; NUM_DECIMALS] {
|
||||
crate::emath_assert!(v < 10.0, "{:?}", v);
|
||||
let mut digits = [0; NUM_DECIMALS];
|
||||
let mut v = v.abs();
|
||||
for r in &mut digits {
|
||||
let digit = v.floor();
|
||||
*r = digit as i32;
|
||||
v -= digit;
|
||||
v *= 10.0;
|
||||
}
|
||||
digits
|
||||
}
|
||||
|
||||
fn from_decimal_string(s: &[i32]) -> f64 {
|
||||
let mut ret: f64 = 0.0;
|
||||
for (i, &digit) in s.iter().enumerate() {
|
||||
ret += (digit as f64) * 10.0_f64.powi(-(i as i32));
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
/// Find the simplest integer in the range [min, max]
|
||||
fn simplest_digit_closed_range(min: i32, max: i32) -> i32 {
|
||||
crate::emath_assert!(1 <= min && min <= max && max <= 9);
|
||||
if min <= 5 && 5 <= max {
|
||||
5
|
||||
} else {
|
||||
(min + max) / 2
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::approx_constant)]
|
||||
#[test]
|
||||
fn test_aim() {
|
||||
assert_eq!(best_in_range_f64(-0.2, 0.0), 0.0, "Prefer zero");
|
||||
assert_eq!(best_in_range_f64(-10_004.23, 3.14), 0.0, "Prefer zero");
|
||||
assert_eq!(best_in_range_f64(-0.2, 100.0), 0.0, "Prefer zero");
|
||||
assert_eq!(best_in_range_f64(0.2, 0.0), 0.0, "Prefer zero");
|
||||
assert_eq!(best_in_range_f64(7.8, 17.8), 10.0);
|
||||
assert_eq!(best_in_range_f64(99.0, 300.0), 100.0);
|
||||
assert_eq!(best_in_range_f64(-99.0, -300.0), -100.0);
|
||||
assert_eq!(best_in_range_f64(0.4, 0.9), 0.5, "Prefer ending on 5");
|
||||
assert_eq!(best_in_range_f64(14.1, 19.99), 15.0, "Prefer ending on 5");
|
||||
assert_eq!(best_in_range_f64(12.3, 65.9), 50.0, "Prefer leading 5");
|
||||
assert_eq!(best_in_range_f64(493.0, 879.0), 500.0, "Prefer leading 5");
|
||||
assert_eq!(best_in_range_f64(0.37, 0.48), 0.40);
|
||||
// assert_eq!(best_in_range_f64(123.71, 123.76), 123.75); // TODO(emilk): we get 123.74999999999999 here
|
||||
// assert_eq!(best_in_range_f32(123.71, 123.76), 123.75);
|
||||
assert_eq!(best_in_range_f64(7.5, 16.3), 10.0);
|
||||
assert_eq!(best_in_range_f64(7.5, 76.3), 10.0);
|
||||
assert_eq!(best_in_range_f64(7.5, 763.3), 100.0);
|
||||
assert_eq!(best_in_range_f64(7.5, 1_345.0), 100.0);
|
||||
assert_eq!(best_in_range_f64(7.5, 123_456.0), 1000.0, "Geometric mean");
|
||||
assert_eq!(best_in_range_f64(9.9999, 99.999), 10.0);
|
||||
assert_eq!(best_in_range_f64(10.000, 99.999), 10.0);
|
||||
assert_eq!(best_in_range_f64(10.001, 99.999), 50.0);
|
||||
assert_eq!(best_in_range_f64(10.001, 100.000), 100.0);
|
||||
assert_eq!(best_in_range_f64(99.999, 100.000), 100.0);
|
||||
assert_eq!(best_in_range_f64(10.001, 100.001), 100.0);
|
||||
|
||||
use std::f64::{INFINITY, NAN, NEG_INFINITY};
|
||||
assert!(best_in_range_f64(NAN, NAN).is_nan());
|
||||
assert_eq!(best_in_range_f64(NAN, 1.2), 1.2);
|
||||
assert_eq!(best_in_range_f64(NAN, INFINITY), INFINITY);
|
||||
assert_eq!(best_in_range_f64(1.2, NAN), 1.2);
|
||||
assert_eq!(best_in_range_f64(1.2, INFINITY), 1.2);
|
||||
assert_eq!(best_in_range_f64(INFINITY, 1.2), 1.2);
|
||||
assert_eq!(best_in_range_f64(NEG_INFINITY, 1.2), 0.0);
|
||||
assert_eq!(best_in_range_f64(NEG_INFINITY, -2.7), -2.7);
|
||||
assert_eq!(best_in_range_f64(INFINITY, INFINITY), INFINITY);
|
||||
assert_eq!(best_in_range_f64(NEG_INFINITY, NEG_INFINITY), NEG_INFINITY);
|
||||
assert_eq!(best_in_range_f64(NEG_INFINITY, INFINITY), 0.0);
|
||||
assert_eq!(best_in_range_f64(INFINITY, NEG_INFINITY), 0.0);
|
||||
}
|
||||
463
egui/crates/emath/src/vec2.rs
Normal file
463
egui/crates/emath/src/vec2.rs
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
||||
/// A vector has a direction and length.
|
||||
/// A [`Vec2`] is often used to represent a size.
|
||||
///
|
||||
/// emath represents positions using [`crate::Pos2`].
|
||||
///
|
||||
/// Normally the units are points (logical pixels).
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Default, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
|
||||
pub struct Vec2 {
|
||||
/// Rightwards. Width.
|
||||
pub x: f32,
|
||||
|
||||
/// Downwards. Height.
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
/// `vec2(x, y) == Vec2::new(x, y)`
|
||||
#[inline(always)]
|
||||
pub const fn vec2(x: f32, y: f32) -> Vec2 {
|
||||
Vec2 { x, y }
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Compatibility and convenience conversions to and from [f32; 2]:
|
||||
|
||||
impl From<[f32; 2]> for Vec2 {
|
||||
#[inline(always)]
|
||||
fn from(v: [f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[f32; 2]> for Vec2 {
|
||||
#[inline(always)]
|
||||
fn from(v: &[f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec2> for [f32; 2] {
|
||||
#[inline(always)]
|
||||
fn from(v: Vec2) -> Self {
|
||||
[v.x, v.y]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec2> for [f32; 2] {
|
||||
#[inline(always)]
|
||||
fn from(v: &Vec2) -> Self {
|
||||
[v.x, v.y]
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Compatibility and convenience conversions to and from (f32, f32):
|
||||
|
||||
impl From<(f32, f32)> for Vec2 {
|
||||
#[inline(always)]
|
||||
fn from(v: (f32, f32)) -> Self {
|
||||
Self { x: v.0, y: v.1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&(f32, f32)> for Vec2 {
|
||||
#[inline(always)]
|
||||
fn from(v: &(f32, f32)) -> Self {
|
||||
Self { x: v.0, y: v.1 }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec2> for (f32, f32) {
|
||||
#[inline(always)]
|
||||
fn from(v: Vec2) -> Self {
|
||||
(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Vec2> for (f32, f32) {
|
||||
#[inline(always)]
|
||||
fn from(v: &Vec2) -> Self {
|
||||
(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mint compatibility and convenience conversions
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl From<mint::Vector2<f32>> for Vec2 {
|
||||
#[inline]
|
||||
fn from(v: mint::Vector2<f32>) -> Self {
|
||||
Self::new(v.x, v.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "mint")]
|
||||
impl From<Vec2> for mint::Vector2<f32> {
|
||||
#[inline]
|
||||
fn from(v: Vec2) -> Self {
|
||||
Self { x: v.x, y: v.y }
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
impl Vec2 {
|
||||
pub const X: Vec2 = Vec2 { x: 1.0, y: 0.0 };
|
||||
pub const Y: Vec2 = Vec2 { x: 0.0, y: 1.0 };
|
||||
|
||||
pub const RIGHT: Vec2 = Vec2 { x: 1.0, y: 0.0 };
|
||||
pub const LEFT: Vec2 = Vec2 { x: -1.0, y: 0.0 };
|
||||
pub const UP: Vec2 = Vec2 { x: 0.0, y: -1.0 };
|
||||
pub const DOWN: Vec2 = Vec2 { x: 0.0, y: 1.0 };
|
||||
|
||||
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
||||
pub const INFINITY: Self = Self::splat(f32::INFINITY);
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
/// Set both `x` and `y` to the same value.
|
||||
#[inline(always)]
|
||||
pub const fn splat(v: f32) -> Self {
|
||||
Self { x: v, y: v }
|
||||
}
|
||||
|
||||
/// Treat this vector as a position.
|
||||
/// `v.to_pos2()` is equivalent to `Pos2::default() + v`.
|
||||
#[inline(always)]
|
||||
pub fn to_pos2(self) -> crate::Pos2 {
|
||||
crate::Pos2 {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe normalize: returns zero if input is zero.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn normalized(self) -> Self {
|
||||
let len = self.length();
|
||||
if len <= 0.0 {
|
||||
self
|
||||
} else {
|
||||
self / len
|
||||
}
|
||||
}
|
||||
|
||||
/// Rotates the vector by 90°, i.e positive X to positive Y
|
||||
/// (clockwise in egui coordinates).
|
||||
#[inline(always)]
|
||||
pub fn rot90(self) -> Self {
|
||||
vec2(self.y, -self.x)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn length(self) -> f32 {
|
||||
self.x.hypot(self.y)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn length_sq(self) -> f32 {
|
||||
self.x * self.x + self.y * self.y
|
||||
}
|
||||
|
||||
/// Measures the angle of the vector.
|
||||
///
|
||||
/// ```
|
||||
/// # use emath::Vec2;
|
||||
/// use std::f32::consts::TAU;
|
||||
///
|
||||
/// assert_eq!(Vec2::ZERO.angle(), 0.0);
|
||||
/// assert_eq!(Vec2::angled(0.0).angle(), 0.0);
|
||||
/// assert_eq!(Vec2::angled(1.0).angle(), 1.0);
|
||||
/// assert_eq!(Vec2::X.angle(), 0.0);
|
||||
/// assert_eq!(Vec2::Y.angle(), 0.25 * TAU);
|
||||
///
|
||||
/// assert_eq!(Vec2::RIGHT.angle(), 0.0);
|
||||
/// assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU);
|
||||
/// assert_eq!(Vec2::UP.angle(), -0.25 * TAU);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn angle(self) -> f32 {
|
||||
self.y.atan2(self.x)
|
||||
}
|
||||
|
||||
/// Create a unit vector with the given CW angle (in radians).
|
||||
/// * An angle of zero gives the unit X axis.
|
||||
/// * An angle of 𝞃/4 = 90° gives the unit Y axis.
|
||||
///
|
||||
/// ```
|
||||
/// # use emath::Vec2;
|
||||
/// use std::f32::consts::TAU;
|
||||
///
|
||||
/// assert_eq!(Vec2::angled(0.0), Vec2::X);
|
||||
/// assert!((Vec2::angled(0.25 * TAU) - Vec2::Y).length() < 1e-5);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn angled(angle: f32) -> Self {
|
||||
vec2(angle.cos(), angle.sin())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn floor(self) -> Self {
|
||||
vec2(self.x.floor(), self.y.floor())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn round(self) -> Self {
|
||||
vec2(self.x.round(), self.y.round())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn ceil(self) -> Self {
|
||||
vec2(self.x.ceil(), self.y.ceil())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn abs(self) -> Self {
|
||||
vec2(self.x.abs(), self.y.abs())
|
||||
}
|
||||
|
||||
/// True if all members are also finite.
|
||||
#[inline(always)]
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
|
||||
/// True if any member is NaN.
|
||||
#[inline(always)]
|
||||
pub fn any_nan(self) -> bool {
|
||||
self.x.is_nan() || self.y.is_nan()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
vec2(self.x.min(other.x), self.y.min(other.y))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
vec2(self.x.max(other.x), self.y.max(other.y))
|
||||
}
|
||||
|
||||
/// The dot-product of two vectors.
|
||||
#[inline]
|
||||
pub fn dot(self, other: Self) -> f32 {
|
||||
self.x * other.x + self.y * other.y
|
||||
}
|
||||
|
||||
/// Returns the minimum of `self.x` and `self.y`.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn min_elem(self) -> f32 {
|
||||
self.x.min(self.y)
|
||||
}
|
||||
|
||||
/// Returns the maximum of `self.x` and `self.y`.
|
||||
#[inline(always)]
|
||||
#[must_use]
|
||||
pub fn max_elem(self) -> f32 {
|
||||
self.x.max(self.y)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn clamp(self, min: Self, max: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.clamp(min.x, max.x),
|
||||
y: self.y.clamp(min.y, max.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Index<usize> for Vec2 {
|
||||
type Output = f32;
|
||||
|
||||
#[inline(always)]
|
||||
fn index(&self, index: usize) -> &f32 {
|
||||
match index {
|
||||
0 => &self.x,
|
||||
1 => &self.y,
|
||||
_ => panic!("Vec2 index out of bounds: {}", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::IndexMut<usize> for Vec2 {
|
||||
#[inline(always)]
|
||||
fn index_mut(&mut self, index: usize) -> &mut f32 {
|
||||
match index {
|
||||
0 => &mut self.x,
|
||||
1 => &mut self.y,
|
||||
_ => panic!("Vec2 index out of bounds: {}", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Vec2 {}
|
||||
|
||||
impl Neg for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn neg(self) -> Vec2 {
|
||||
vec2(-self.x, -self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec2 {
|
||||
#[inline(always)]
|
||||
fn add_assign(&mut self, rhs: Vec2) {
|
||||
*self = Vec2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Vec2 {
|
||||
#[inline(always)]
|
||||
fn sub_assign(&mut self, rhs: Vec2) {
|
||||
*self = Vec2 {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn add(self, rhs: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn sub(self, rhs: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Element-wise multiplication
|
||||
impl Mul<Vec2> for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, vec: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x * vec.x,
|
||||
y: self.y * vec.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Element-wise division
|
||||
impl Div<Vec2> for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn div(self, rhs: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x / rhs.x,
|
||||
y: self.y / rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f32> for Vec2 {
|
||||
#[inline(always)]
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
self.x *= rhs;
|
||||
self.y *= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, factor: f32) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x * factor,
|
||||
y: self.y * factor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec2> for f32 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn mul(self, vec: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self * vec.x,
|
||||
y: self * vec.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
#[inline(always)]
|
||||
fn div(self, factor: f32) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x / factor,
|
||||
y: self.y / factor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Vec2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "[{:.1} {:.1}]", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vec2() {
|
||||
macro_rules! almost_eq {
|
||||
($left: expr, $right: expr) => {
|
||||
let left = $left;
|
||||
let right = $right;
|
||||
assert!((left - right).abs() < 1e-6, "{} != {}", left, right);
|
||||
};
|
||||
}
|
||||
use std::f32::consts::TAU;
|
||||
|
||||
assert_eq!(Vec2::ZERO.angle(), 0.0);
|
||||
assert_eq!(Vec2::angled(0.0).angle(), 0.0);
|
||||
assert_eq!(Vec2::angled(1.0).angle(), 1.0);
|
||||
assert_eq!(Vec2::X.angle(), 0.0);
|
||||
assert_eq!(Vec2::Y.angle(), 0.25 * TAU);
|
||||
|
||||
assert_eq!(Vec2::RIGHT.angle(), 0.0);
|
||||
assert_eq!(Vec2::DOWN.angle(), 0.25 * TAU);
|
||||
almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU);
|
||||
assert_eq!(Vec2::UP.angle(), -0.25 * TAU);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue