mirror of
https://github.com/emilk/egui.git
synced 2026-08-30 13:20:05 -04:00
Move egui/math into new crate emath
This commit is contained in:
117
emath/src/align.rs
Normal file
117
emath/src/align.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
//! 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 `Layout`s.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
pub enum Align {
|
||||
/// Left or top.
|
||||
Min,
|
||||
|
||||
/// Horizontal or vertical center.
|
||||
Center,
|
||||
|
||||
/// Right or bottom.
|
||||
Max,
|
||||
}
|
||||
|
||||
impl Align {
|
||||
/// Convenience for [`Self::Min`]
|
||||
pub fn left() -> Self {
|
||||
Self::Min
|
||||
}
|
||||
/// Convenience for [`Self::Max`]
|
||||
pub fn right() -> Self {
|
||||
Self::Max
|
||||
}
|
||||
/// Convenience for [`Self::Min`]
|
||||
pub fn top() -> Self {
|
||||
Self::Min
|
||||
}
|
||||
/// Convenience for [`Self::Max`]
|
||||
pub fn bottom() -> Self {
|
||||
Self::Max
|
||||
}
|
||||
|
||||
/// Convert `Min => 0.0`, `Center => 0.5` or `Max => 1.0`.
|
||||
pub fn to_factor(&self) -> f32 {
|
||||
match self {
|
||||
Self::Min => 0.0,
|
||||
Self::Center => 0.5,
|
||||
Self::Max => 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Align {
|
||||
fn default() -> Align {
|
||||
Align::Min
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`].
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
|
||||
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 {
|
||||
pub fn x(self) -> Align {
|
||||
self.0[0]
|
||||
}
|
||||
pub fn y(self) -> Align {
|
||||
self.0[1]
|
||||
}
|
||||
|
||||
/// 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 = match self.x() {
|
||||
Align::Min => frame.left(),
|
||||
Align::Center => frame.center().x - size.x / 2.0,
|
||||
Align::Max => frame.right() - size.x,
|
||||
};
|
||||
let y = match self.y() {
|
||||
Align::Min => frame.top(),
|
||||
Align::Center => frame.center().y - size.y / 2.0,
|
||||
Align::Max => frame.bottom() - size.y,
|
||||
};
|
||||
|
||||
Rect::from_min_size(Pos2::new(x, y), size)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center_size_in_rect(size: Vec2, frame: Rect) -> Rect {
|
||||
Align2::CENTER_CENTER.align_size_within_rect(size, frame)
|
||||
}
|
||||
310
emath/src/lib.rs
Normal file
310
emath/src/lib.rs
Normal file
@@ -0,0 +1,310 @@
|
||||
//! 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`
|
||||
|
||||
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
|
||||
#![forbid(unsafe_code)]
|
||||
#![warn(
|
||||
clippy::all,
|
||||
clippy::await_holding_lock,
|
||||
clippy::dbg_macro,
|
||||
clippy::doc_markdown,
|
||||
clippy::empty_enum,
|
||||
clippy::enum_glob_use,
|
||||
clippy::exit,
|
||||
clippy::filter_map_next,
|
||||
clippy::fn_params_excessive_bools,
|
||||
clippy::if_let_mutex,
|
||||
clippy::imprecise_flops,
|
||||
clippy::inefficient_to_string,
|
||||
clippy::linkedlist,
|
||||
clippy::lossy_float_literal,
|
||||
clippy::macro_use_imports,
|
||||
clippy::match_on_vec_items,
|
||||
clippy::match_wildcard_for_single_variants,
|
||||
clippy::mem_forget,
|
||||
clippy::mismatched_target_os,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_safety_doc,
|
||||
clippy::needless_borrow,
|
||||
clippy::needless_continue,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::option_option,
|
||||
clippy::pub_enum_variant_names,
|
||||
clippy::rest_pat_in_fully_bound_structs,
|
||||
clippy::todo,
|
||||
clippy::unimplemented,
|
||||
clippy::unnested_or_patterns,
|
||||
clippy::verbose_file_reads,
|
||||
future_incompatible,
|
||||
missing_crate_level_docs,
|
||||
missing_doc_code_examples,
|
||||
// missing_docs,
|
||||
nonstandard_style,
|
||||
rust_2018_idioms,
|
||||
unused_doc_comments,
|
||||
)]
|
||||
#![allow(clippy::manual_range_contains)]
|
||||
|
||||
use std::ops::{Add, Div, Mul, RangeInclusive, Sub};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub mod align;
|
||||
mod pos2;
|
||||
mod rect;
|
||||
mod rot2;
|
||||
pub mod smart_aim;
|
||||
mod vec2;
|
||||
|
||||
pub use {
|
||||
align::{Align, Align2},
|
||||
pos2::*,
|
||||
rect::*,
|
||||
rot2::*,
|
||||
vec2::*,
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Helper trait to implement [`lerp`] and [`remap`].
|
||||
pub trait One {
|
||||
fn one() -> Self;
|
||||
}
|
||||
impl One for f32 {
|
||||
fn one() -> Self {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
impl One for f64 {
|
||||
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.
|
||||
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()
|
||||
}
|
||||
|
||||
/// 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,
|
||||
{
|
||||
#![allow(clippy::float_cmp)]
|
||||
debug_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,
|
||||
{
|
||||
#![allow(clippy::float_cmp)]
|
||||
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 {
|
||||
debug_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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `range.start()` if `x <= range.start()`,
|
||||
/// returns `range.end()` if `x >= range.end()`
|
||||
/// and returns `x` elsewhen.
|
||||
pub fn clamp<T>(x: T, range: RangeInclusive<T>) -> T
|
||||
where
|
||||
T: Copy + PartialOrd,
|
||||
{
|
||||
debug_assert!(range.start() <= range.end());
|
||||
if x <= *range.start() {
|
||||
*range.start()
|
||||
} else if *range.end() <= x {
|
||||
*range.end()
|
||||
} else {
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
/// 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();
|
||||
debug_assert!(min_decimals <= max_decimals);
|
||||
debug_assert!(max_decimals < 100);
|
||||
let max_decimals = max_decimals.min(16);
|
||||
let min_decimals = min_decimals.min(max_decimals);
|
||||
|
||||
if min_decimals == max_decimals {
|
||||
format!("{:.*}", max_decimals, value)
|
||||
} else {
|
||||
// Ugly/slow way of doing this. TODO: 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 {
|
||||
#![allow(clippy::float_cmp)]
|
||||
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
#[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)`
|
||||
fn at_least(self, lower_limit: Self) -> Self;
|
||||
|
||||
/// More readable version of `self.min(upper_limit)`
|
||||
fn at_most(self, upper_limit: Self) -> Self;
|
||||
}
|
||||
|
||||
macro_rules! impl_num_ext {
|
||||
($t: ty) => {
|
||||
impl NumExt for $t {
|
||||
fn at_least(self, lower_limit: Self) -> Self {
|
||||
self.max(lower_limit)
|
||||
}
|
||||
fn at_most(self, upper_limit: Self) -> Self {
|
||||
self.min(upper_limit)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_num_ext!(f32);
|
||||
impl_num_ext!(f64);
|
||||
impl_num_ext!(usize);
|
||||
impl_num_ext!(Vec2);
|
||||
impl_num_ext!(Pos2);
|
||||
153
emath/src/pos2.rs
Normal file
153
emath/src/pos2.rs
Normal file
@@ -0,0 +1,153 @@
|
||||
use std::ops::{Add, AddAssign, RangeInclusive, 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).
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Pos2 {
|
||||
pub x: f32,
|
||||
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 }
|
||||
}
|
||||
|
||||
impl From<[f32; 2]> for Pos2 {
|
||||
fn from(v: [f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[f32; 2]> for Pos2 {
|
||||
fn from(v: &[f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Pos2 {
|
||||
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()`.
|
||||
pub fn to_vec2(self) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x,
|
||||
y: self.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn distance(self, other: Self) -> f32 {
|
||||
(self - other).length()
|
||||
}
|
||||
|
||||
pub fn distance_sq(self, other: Self) -> f32 {
|
||||
(self - other).length_sq()
|
||||
}
|
||||
|
||||
pub fn floor(self) -> Self {
|
||||
pos2(self.x.floor(), self.y.floor())
|
||||
}
|
||||
|
||||
pub fn round(self) -> Self {
|
||||
pos2(self.x.round(), self.y.round())
|
||||
}
|
||||
|
||||
pub fn ceil(self) -> Self {
|
||||
pos2(self.x.ceil(), self.y.ceil())
|
||||
}
|
||||
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
pos2(self.x.min(other.x), self.y.min(other.y))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
pos2(self.x.max(other.x), self.y.max(other.y))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn clamp(self, range: RangeInclusive<Self>) -> Self {
|
||||
Self {
|
||||
x: clamp(self.x, range.start().x..=range.end().x),
|
||||
y: clamp(self.y, range.start().y..=range.end().y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Pos2 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.x == other.x && self.y == other.y
|
||||
}
|
||||
}
|
||||
impl Eq for Pos2 {}
|
||||
|
||||
impl AddAssign<Vec2> for Pos2 {
|
||||
fn add_assign(&mut self, rhs: Vec2) {
|
||||
*self = Pos2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign<Vec2> for Pos2 {
|
||||
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;
|
||||
fn add(self, rhs: Vec2) -> Pos2 {
|
||||
Pos2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Pos2 {
|
||||
type Output = Vec2;
|
||||
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;
|
||||
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)
|
||||
}
|
||||
}
|
||||
233
emath/src/rect.rs
Normal file
233
emath/src/rect.rs
Normal file
@@ -0,0 +1,233 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A rectangular region of space.
|
||||
///
|
||||
/// Normally given in points, e.g. logical pixels.
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Rect {
|
||||
pub min: Pos2,
|
||||
pub max: Pos2,
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
/// Infinite rectangle that contains everything
|
||||
pub fn everything() -> Self {
|
||||
let inf = f32::INFINITY;
|
||||
Self {
|
||||
min: pos2(-inf, -inf),
|
||||
max: pos2(inf, inf),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nothing() -> Self {
|
||||
let inf = f32::INFINITY;
|
||||
Self {
|
||||
min: pos2(inf, inf),
|
||||
max: pos2(-inf, -inf),
|
||||
}
|
||||
}
|
||||
|
||||
/// invalid, NAN filled Rect.
|
||||
pub fn invalid() -> Self {
|
||||
Self {
|
||||
min: pos2(f32::NAN, f32::NAN),
|
||||
max: pos2(f32::NAN, f32::NAN),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_min_max(min: Pos2, max: Pos2) -> Self {
|
||||
Rect { min, max }
|
||||
}
|
||||
|
||||
pub fn from_min_size(min: Pos2, size: Vec2) -> Self {
|
||||
Rect {
|
||||
min,
|
||||
max: min + size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_center_size(center: Pos2, size: Vec2) -> Self {
|
||||
Rect {
|
||||
min: center - size * 0.5,
|
||||
max: center + size * 0.5,
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 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]
|
||||
pub fn translate(self, amnt: Vec2) -> Self {
|
||||
Rect::from_min_size(self.min + amnt, self.size())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn intersect(self, other: Rect) -> Self {
|
||||
Self {
|
||||
min: self.min.max(other.min),
|
||||
max: self.max.min(other.max),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
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]
|
||||
pub fn contains(&self, p: Pos2) -> bool {
|
||||
self.min.x <= p.x
|
||||
&& p.x <= self.min.x + self.size().x
|
||||
&& self.min.y <= p.y
|
||||
&& p.y <= self.min.y + self.size().y
|
||||
}
|
||||
|
||||
pub fn extend_with(&mut self, p: Pos2) {
|
||||
self.min = self.min.min(p);
|
||||
self.max = self.max.max(p);
|
||||
}
|
||||
|
||||
pub fn union(self, other: Rect) -> Rect {
|
||||
Rect {
|
||||
min: self.min.min(other.min),
|
||||
max: self.max.max(other.max),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn center(&self) -> Pos2 {
|
||||
Pos2 {
|
||||
x: self.min.x + self.size().x / 2.0,
|
||||
y: self.min.y + self.size().y / 2.0,
|
||||
}
|
||||
}
|
||||
pub fn size(&self) -> Vec2 {
|
||||
self.max - self.min
|
||||
}
|
||||
pub fn width(&self) -> f32 {
|
||||
self.max.x - self.min.x
|
||||
}
|
||||
pub fn height(&self) -> f32 {
|
||||
self.max.y - self.min.y
|
||||
}
|
||||
pub fn area(&self) -> f32 {
|
||||
self.width() * self.height()
|
||||
}
|
||||
|
||||
pub fn x_range(&self) -> RangeInclusive<f32> {
|
||||
self.min.x..=self.max.x
|
||||
}
|
||||
pub fn y_range(&self) -> RangeInclusive<f32> {
|
||||
self.min.y..=self.max.y
|
||||
}
|
||||
pub fn bottom_up_range(&self) -> RangeInclusive<f32> {
|
||||
self.max.y..=self.min.y
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.max.x < self.min.x || self.max.y < self.min.y
|
||||
}
|
||||
|
||||
pub fn is_finite(&self) -> bool {
|
||||
self.min.is_finite() && self.max.is_finite()
|
||||
}
|
||||
|
||||
// Convenience functions (assumes origin is towards left top):
|
||||
pub fn left(&self) -> f32 {
|
||||
self.min.x
|
||||
}
|
||||
pub fn right(&self) -> f32 {
|
||||
self.max.x
|
||||
}
|
||||
pub fn top(&self) -> f32 {
|
||||
self.min.y
|
||||
}
|
||||
pub fn bottom(&self) -> f32 {
|
||||
self.max.y
|
||||
}
|
||||
pub fn left_top(&self) -> Pos2 {
|
||||
pos2(self.left(), self.top())
|
||||
}
|
||||
pub fn center_top(&self) -> Pos2 {
|
||||
pos2(self.center().x, self.top())
|
||||
}
|
||||
pub fn right_top(&self) -> Pos2 {
|
||||
pos2(self.right(), self.top())
|
||||
}
|
||||
pub fn left_center(&self) -> Pos2 {
|
||||
pos2(self.left(), self.center().y)
|
||||
}
|
||||
pub fn right_center(&self) -> Pos2 {
|
||||
pos2(self.right(), self.center().y)
|
||||
}
|
||||
pub fn left_bottom(&self) -> Pos2 {
|
||||
pos2(self.left(), self.bottom())
|
||||
}
|
||||
pub fn center_bottom(&self) -> Pos2 {
|
||||
pos2(self.center().x, self.bottom())
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
183
emath/src/rot2.rs
Normal file
183
emath/src/rot2.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
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.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
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 {
|
||||
pub fn identity() -> Self {
|
||||
Self { s: 0.0, c: 1.0 }
|
||||
}
|
||||
|
||||
/// 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,
|
||||
};
|
||||
debug_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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,);
|
||||
}
|
||||
}
|
||||
}
|
||||
159
emath/src/smart_aim.rs
Normal file
159
emath/src/smart_aim.rs
Normal file
@@ -0,0 +1,159 @@
|
||||
//! Find "simple" numbers is some range. Used by sliders.
|
||||
|
||||
#![allow(clippy::float_cmp)] // I know what I'm doing
|
||||
|
||||
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;
|
||||
}
|
||||
debug_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] {
|
||||
debug_assert!(v < 10.0, "{:?}", v);
|
||||
let mut digits = [0; NUM_DECIMALS];
|
||||
let mut v = v.abs();
|
||||
for r in digits.iter_mut() {
|
||||
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 {
|
||||
debug_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: 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);
|
||||
}
|
||||
237
emath/src/vec2.rs
Normal file
237
emath/src/vec2.rs
Normal file
@@ -0,0 +1,237 @@
|
||||
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, RangeInclusive, Sub, SubAssign};
|
||||
|
||||
use crate::*;
|
||||
|
||||
/// A vector has a direction and length.
|
||||
/// A [`Vec2`] is often used to represent a size.
|
||||
///
|
||||
/// Egui represents positions using [`Pos2`].
|
||||
///
|
||||
/// Normally the units are points (logical pixels).
|
||||
#[derive(Clone, Copy, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Vec2 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
/// `vec2(x,y) == Vec2::new(x, y)`
|
||||
#[inline(always)]
|
||||
pub const fn vec2(x: f32, y: f32) -> Vec2 {
|
||||
Vec2 { x, y }
|
||||
}
|
||||
|
||||
impl From<[f32; 2]> for Vec2 {
|
||||
fn from(v: [f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[f32; 2]> for Vec2 {
|
||||
fn from(v: &[f32; 2]) -> Self {
|
||||
Self { x: v[0], y: v[1] }
|
||||
}
|
||||
}
|
||||
|
||||
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 fn zero() -> Self {
|
||||
Self { x: 0.0, y: 0.0 }
|
||||
}
|
||||
|
||||
pub fn infinity() -> Self {
|
||||
Self {
|
||||
x: f32::INFINITY,
|
||||
y: f32::INFINITY,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(x: f32, y: f32) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn splat(v: impl Into<f32>) -> Self {
|
||||
let v: f32 = v.into();
|
||||
Self { x: v, y: v }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn length(self) -> f32 {
|
||||
self.x.hypot(self.y)
|
||||
}
|
||||
|
||||
pub fn length_sq(self) -> f32 {
|
||||
self.x * self.x + self.y * self.y
|
||||
}
|
||||
|
||||
/// Create a unit vector with the given angle (in radians).
|
||||
/// * An angle of zero gives the unit X axis.
|
||||
/// * An angle of 𝞃/4 = 90° gives the unit Y axis.
|
||||
pub fn angled(angle: f32) -> Self {
|
||||
vec2(angle.cos(), angle.sin())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn floor(self) -> Self {
|
||||
vec2(self.x.floor(), self.y.floor())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn round(self) -> Self {
|
||||
vec2(self.x.round(), self.y.round())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn ceil(self) -> Self {
|
||||
vec2(self.x.ceil(), self.y.ceil())
|
||||
}
|
||||
|
||||
/// True if all members are also finite.
|
||||
pub fn is_finite(self) -> bool {
|
||||
self.x.is_finite() && self.y.is_finite()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn min(self, other: Self) -> Self {
|
||||
vec2(self.x.min(other.x), self.y.min(other.y))
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn max(self, other: Self) -> Self {
|
||||
vec2(self.x.max(other.x), self.y.max(other.y))
|
||||
}
|
||||
|
||||
/// Returns the minimum of `self.x` and `self.y`.
|
||||
#[must_use]
|
||||
pub fn min_elem(self) -> f32 {
|
||||
self.x.min(self.y)
|
||||
}
|
||||
|
||||
/// Returns the maximum of `self.x` and `self.y`.
|
||||
#[must_use]
|
||||
pub fn max_elem(self) -> f32 {
|
||||
self.x.max(self.y)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn clamp(self, range: RangeInclusive<Self>) -> Self {
|
||||
Self {
|
||||
x: clamp(self.x, range.start().x..=range.end().x),
|
||||
y: clamp(self.y, range.start().y..=range.end().y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Vec2 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.x == other.x && self.y == other.y
|
||||
}
|
||||
}
|
||||
impl Eq for Vec2 {}
|
||||
|
||||
impl Neg for Vec2 {
|
||||
type Output = Vec2;
|
||||
|
||||
fn neg(self) -> Vec2 {
|
||||
vec2(-self.x, -self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec2 {
|
||||
fn add_assign(&mut self, rhs: Vec2) {
|
||||
*self = Vec2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Vec2 {
|
||||
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;
|
||||
fn add(self, rhs: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec2 {
|
||||
type Output = Vec2;
|
||||
fn sub(self, rhs: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f32> for Vec2 {
|
||||
fn mul_assign(&mut self, rhs: f32) {
|
||||
self.x *= rhs;
|
||||
self.y *= rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vec2 {
|
||||
type Output = Vec2;
|
||||
fn mul(self, factor: f32) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self.x * factor,
|
||||
y: self.y * factor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec2> for f32 {
|
||||
type Output = Vec2;
|
||||
fn mul(self, vec: Vec2) -> Vec2 {
|
||||
Vec2 {
|
||||
x: self * vec.x,
|
||||
y: self * vec.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for Vec2 {
|
||||
type Output = Vec2;
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user