emath-0.33.0/.cargo_vcs_info.json0000644000000001520000000000100122130ustar { "git": { "sha1": "430a3fbc7872a64ad607e95fa87a566c8e4f1e87" }, "path_in_vcs": "crates/emath" }emath-0.33.0/Cargo.lock0000644000000056060000000000100101770ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "bytemuck" version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "document-features" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" dependencies = [ "litrs", ] [[package]] name = "emath" version = "0.33.0" dependencies = [ "bytemuck", "document-features", "mint", "serde", ] [[package]] name = "litrs" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "mint" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff" [[package]] name = "proc-macro2" version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", "serde_derive", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "syn" version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "unicode-ident" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" emath-0.33.0/Cargo.toml0000644000000165640000000000100102270ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2024" rust-version = "1.88" name = "emath" version = "0.33.0" authors = ["Emil Ernerfeldt "] build = false include = [ "../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Minimal 2D math library for GUI work" homepage = "https://github.com/emilk/egui/tree/main/crates/emath" readme = "README.md" keywords = [ "math", "gui", ] categories = [ "mathematics", "gui", ] license = "MIT OR Apache-2.0" repository = "https://github.com/emilk/egui/tree/main/crates/emath" resolver = "2" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] [features] default = [] [lib] name = "emath" path = "src/lib.rs" [dependencies.bytemuck] version = "1.24.0" features = ["derive"] optional = true [dependencies.document-features] version = "0.2.11" optional = true [dependencies.mint] version = "0.5.9" optional = true [dependencies.serde] version = "1.0.228" features = ["derive"] optional = true [lints.clippy] allow_attributes = "warn" as_ptr_cast_mut = "warn" assigning_clones = "allow" await_holding_lock = "warn" bool_to_int_with_if = "warn" branches_sharing_code = "warn" char_lit_as_u8 = "warn" checked_conversions = "warn" clear_with_drain = "warn" cloned_instead_of_copied = "warn" comparison_chain = "allow" dbg_macro = "warn" debug_assert_with_mut_call = "warn" default_union_representation = "warn" derive_partial_eq_without_eq = "warn" disallowed_macros = "warn" disallowed_methods = "warn" disallowed_names = "warn" disallowed_script_idents = "warn" disallowed_types = "warn" doc_comment_double_space_linebreaks = "warn" doc_link_with_quotes = "warn" doc_markdown = "warn" elidable_lifetime_names = "warn" empty_enum = "warn" empty_enum_variants_with_brackets = "warn" empty_line_after_outer_attr = "warn" enum_glob_use = "warn" equatable_if_let = "warn" exit = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" explicit_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" fn_to_numeric_cast_any = "warn" from_iter_instead_of_collect = "warn" get_unwrap = "warn" if_let_mutex = "warn" ignore_without_reason = "warn" implicit_clone = "warn" implied_bounds_in_impls = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" index_refutable_slice = "warn" inefficient_to_string = "warn" infinite_loop = "warn" into_iter_without_iter = "warn" invalid_upcast_comparisons = "warn" iter_filter_is_ok = "warn" iter_filter_is_some = "warn" iter_not_returning_iterator = "warn" iter_on_empty_collections = "warn" iter_on_single_items = "warn" iter_over_hash_type = "warn" iter_without_into_iter = "warn" large_digit_groups = "warn" large_include_file = "warn" large_stack_arrays = "warn" large_stack_frames = "warn" large_types_passed_by_value = "warn" let_underscore_must_use = "allow" let_underscore_untyped = "allow" let_unit_value = "warn" linkedlist = "warn" literal_string_with_formatting_args = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" manual_assert = "warn" manual_clamp = "warn" manual_instant_elapsed = "warn" manual_is_power_of_two = "warn" manual_is_variant_and = "warn" manual_let_else = "warn" manual_midpoint = "warn" manual_ok_or = "warn" manual_range_contains = "allow" manual_string_new = "warn" map_err_ignore = "warn" map_flatten = "warn" map_unwrap_or = "allow" match_bool = "warn" match_same_arms = "warn" match_wild_err_arm = "warn" match_wildcard_for_single_variants = "warn" mem_forget = "warn" mismatching_type_param_order = "warn" missing_assert_message = "warn" missing_enforced_import_renames = "warn" missing_errors_doc = "warn" missing_safety_doc = "warn" mixed_attributes_style = "warn" mut_mut = "warn" mutex_integer = "warn" needless_borrow = "warn" needless_continue = "warn" needless_for_each = "warn" needless_pass_by_ref_mut = "warn" needless_pass_by_value = "warn" negative_feature_names = "warn" non_std_lazy_statics = "warn" non_zero_suggestions = "warn" nonstandard_macro_braces = "warn" option_as_ref_cloned = "warn" option_option = "warn" path_buf_push_overwrite = "warn" pathbuf_init_then_push = "warn" precedence_bits = "warn" print_stderr = "warn" print_stdout = "warn" ptr_as_ptr = "warn" ptr_cast_constness = "warn" pub_underscore_fields = "warn" pub_without_shorthand = "warn" rc_mutex = "warn" readonly_write_lock = "warn" redundant_type_annotations = "warn" ref_as_ptr = "warn" ref_option_ref = "warn" ref_patterns = "warn" rest_pat_in_fully_bound_structs = "warn" return_and_then = "warn" same_functions_in_if_condition = "warn" self_named_module_files = "allow" semicolon_if_nothing_returned = "warn" set_contains_or_insert = "warn" should_panic_without_expect = "allow" significant_drop_tightening = "allow" single_char_pattern = "warn" single_match_else = "warn" single_option_map = "warn" str_split_at_newline = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_lit_chars_any = "warn" string_to_string = "warn" suspicious_command_arg_space = "warn" suspicious_xor_used_as_pow = "warn" todo = "warn" too_long_first_doc_paragraph = "warn" too_many_lines = "allow" trailing_empty_array = "warn" trait_duplication_in_bounds = "warn" transmute_ptr_to_ptr = "warn" tuple_array_conversions = "warn" unchecked_duration_subtraction = "warn" undocumented_unsafe_blocks = "warn" unimplemented = "warn" uninhabited_references = "warn" uninlined_format_args = "warn" unnecessary_box_returns = "warn" unnecessary_debug_formatting = "warn" unnecessary_literal_bound = "warn" unnecessary_safety_comment = "warn" unnecessary_safety_doc = "warn" unnecessary_self_imports = "warn" unnecessary_semicolon = "warn" unnecessary_struct_initialization = "warn" unnecessary_wraps = "warn" unnested_or_patterns = "warn" unused_peekable = "warn" unused_rounding = "warn" unused_self = "warn" unused_trait_names = "warn" unwrap_used = "allow" use_self = "warn" useless_let_if_seq = "warn" useless_transmute = "warn" verbose_file_reads = "warn" wildcard_dependencies = "warn" wildcard_imports = "allow" zero_sized_map_values = "warn" [lints.clippy.all] level = "warn" priority = -1 [lints.rust] elided_lifetimes_in_paths = "warn" rust_2021_prelude_collisions = "warn" semicolon_in_expressions_from_macros = "warn" trivial_casts = "allow" trivial_numeric_casts = "warn" unexpected_cfgs = "warn" unsafe_code = "deny" unsafe_op_in_unsafe_fn = "warn" unused_extern_crates = "warn" unused_import_braces = "warn" unused_lifetimes = "warn" unused_qualifications = "allow" [lints.rust.future_incompatible] level = "warn" priority = -1 [lints.rust.nonstandard_style] level = "warn" priority = -1 [lints.rust.rust_2018_idioms] level = "warn" priority = -1 [lints.rustdoc] all = "warn" broken_intra_doc_links = "warn" missing_crate_level_docs = "warn" emath-0.33.0/Cargo.toml.orig000064400000000000000000000024451046102023000137010ustar 00000000000000[package] name = "emath" version.workspace = true authors = ["Emil Ernerfeldt "] description = "Minimal 2D math library for GUI work" edition.workspace = true rust-version.workspace = true homepage = "https://github.com/emilk/egui/tree/main/crates/emath" license.workspace = true readme = "README.md" repository = "https://github.com/emilk/egui/tree/main/crates/emath" categories = ["mathematics", "gui"] keywords = ["math", "gui"] include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"] [lints] workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--generate-link-to-definition"] [lib] [features] default = [] [dependencies] #! ### Optional dependencies ## [`bytemuck`](https://docs.rs/bytemuck) enables you to cast `emath` types to `&[u8]`. bytemuck = { workspace = true, optional = true, features = ["derive"] } ## Enable this when generating docs. document-features = { workspace = true, 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 = { workspace = true, optional = true } ## Allow serialization using [`serde`](https://docs.rs/serde). serde = { workspace = true, optional = true } emath-0.33.0/README.md000064400000000000000000000011111046102023000122560ustar 00000000000000# emath - egui math library [![Latest version](https://img.shields.io/crates/v/emath.svg)](https://crates.io/crates/emath) [![Documentation](https://docs.rs/emath/badge.svg)](https://docs.rs/emath) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) ![MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![Apache](https://img.shields.io/badge/license-Apache-blue.svg) A bare-bones 2D math library with types and functions useful for GUI building. Made for [`egui`](https://github.com/emilk/egui/). emath-0.33.0/src/align.rs000064400000000000000000000256351046102023000132470ustar 00000000000000//! One- and two-dimensional alignment ([`Align::Center`], [`Align2::LEFT_TOP`] etc). use crate::{Pos2, Rangef, Rect, Vec2, pos2, vec2}; /// left/center/right or top/center/bottom alignment for e.g. anchors and layouts. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub enum Align { /// Left or top. #[default] 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 the inverse alignment. /// `Min` becomes `Max`, `Center` stays the same, `Max` becomes `Min`. pub fn flip(self) -> Self { match self { Self::Min => Self::Max, Self::Center => Self::Center, Self::Max => Self::Min, } } /// 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: impl Into) -> Rangef { let range = range.into(); let Rangef { min, max } = range; if max - min == f32::INFINITY && size == f32::INFINITY { return range; } match self { Self::Min => Rangef::new(min, min + size), Self::Center => { if size == f32::INFINITY { Rangef::new(f32::NEG_INFINITY, f32::INFINITY) } else { let left = crate::fast_midpoint(min, max) - size / 2.0; Rangef::new(left, left + size) } } Self::Max => Rangef::new(max - size, max), } } } // ---------------------------------------------------------------------------- /// Two-dimension alignment, e.g. [`Align2::LEFT_TOP`]. #[derive(Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Align2(pub [Align; 2]); impl Align2 { pub const LEFT_BOTTOM: Self = Self([Align::Min, Align::Max]); pub const LEFT_CENTER: Self = Self([Align::Min, Align::Center]); pub const LEFT_TOP: Self = Self([Align::Min, Align::Min]); pub const CENTER_BOTTOM: Self = Self([Align::Center, Align::Max]); pub const CENTER_CENTER: Self = Self([Align::Center, Align::Center]); pub const CENTER_TOP: Self = Self([Align::Center, Align::Min]); pub const RIGHT_BOTTOM: Self = Self([Align::Max, Align::Max]); pub const RIGHT_CENTER: Self = Self([Align::Max, Align::Center]); pub const RIGHT_TOP: Self = Self([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()) } /// Flip on the x-axis /// e.g. `TOP_LEFT` -> `TOP_RIGHT` pub fn flip_x(self) -> Self { Self([self.x().flip(), self.y()]) } /// Flip on the y-axis /// e.g. `TOP_LEFT` -> `BOTTOM_LEFT` pub fn flip_y(self) -> Self { Self([self.x(), self.y().flip()]) } /// Flip on both axes /// e.g. `TOP_LEFT` -> `BOTTOM_RIGHT` pub fn flip(self) -> Self { Self([self.x().flip(), self.y().flip()]) } /// 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()) } /// Use this anchor to position something around `pos`, /// e.g. [`Self::RIGHT_TOP`] means the right-top of the rect /// will end up at `pos`. pub fn anchor_size(self, pos: Pos2, size: Vec2) -> Rect { let x = match self.x() { Align::Min => pos.x, Align::Center => pos.x - 0.5 * size.x, Align::Max => pos.x - size.x, }; let y = match self.y() { Align::Min => pos.y, Align::Center => pos.y - 0.5 * size.y, Align::Max => pos.y - size.y, }; Rect::from_min_size(pos2(x, y), 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 for Align2 { type Output = Align; #[inline(always)] fn index(&self, index: usize) -> &Align { &self.0[index] } } impl std::ops::IndexMut 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) } impl std::fmt::Debug for Align2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Align2({:?}, {:?})", self.x(), self.y()) } } emath-0.33.0/src/easing.rs000064400000000000000000000114561046102023000134170ustar 00000000000000//! Easing functions for animations. //! //! Contains most easing functions from . //! //! All functions take a value in `[0, 1]` and return a value in `[0, 1]`. //! //! Derived from . use std::f32::consts::PI; #[inline] fn powf(base: f32, exp: f32) -> f32 { base.powf(exp) } /// No easing, just `y = x` #[inline] pub fn linear(t: f32) -> f32 { t } /// /// /// Modeled after the parabola `y = x^2` #[inline] pub fn quadratic_in(t: f32) -> f32 { t * t } /// /// /// Same as `1.0 - quadratic_in(1.0 - t)`. #[inline] pub fn quadratic_out(t: f32) -> f32 { -(t * (t - 2.)) } /// #[inline] pub fn quadratic_in_out(t: f32) -> f32 { if t < 0.5 { 2. * t * t } else { (-2. * t * t) + (4. * t) - 1. } } /// /// /// Modeled after the parabola `y = x^3` #[inline] pub fn cubic_in(t: f32) -> f32 { t * t * t } /// #[inline] pub fn cubic_out(t: f32) -> f32 { let f = t - 1.; f * f * f + 1. } /// #[inline] pub fn cubic_in_out(t: f32) -> f32 { if t < 0.5 { 4. * t * t * t } else { let f = (2. * t) - 2.; 0.5 * f * f * f + 1. } } /// /// /// Modeled after quarter-cycle of sine wave #[inline] pub fn sin_in(t: f32) -> f32 { ((t - 1.) * 2. * PI).sin() + 1. } /// /// /// Modeled after quarter-cycle of sine wave (different phase) #[inline] pub fn sin_out(t: f32) -> f32 { (t * 2. * PI).sin() } /// /// /// Modeled after half sine wave #[inline] pub fn sin_in_out(t: f32) -> f32 { 0.5 * (1. - (t * PI).cos()) } /// /// /// Modeled after shifted quadrant IV of unit circle #[inline] pub fn circular_in(t: f32) -> f32 { 1. - (1. - t * t).sqrt() } /// /// /// Modeled after shifted quadrant II of unit circle #[inline] pub fn circular_out(t: f32) -> f32 { (2. - t).sqrt() * t } /// #[inline] pub fn circular_in_out(t: f32) -> f32 { if t < 0.5 { 0.5 * (1. - (1. - 4. * t * t).sqrt()) } else { 0.5 * ((-(2. * t - 3.) * (2. * t - 1.)).sqrt() + 1.) } } /// /// /// There is a small discontinuity at 0. #[inline] pub fn exponential_in(t: f32) -> f32 { if t == 0. { t } else { powf(2.0, 10. * (t - 1.)) } } /// /// /// There is a small discontinuity at 1. #[inline] pub fn exponential_out(t: f32) -> f32 { if t == 1. { t } else { 1. - powf(2.0, -10. * t) } } /// /// /// There is a small discontinuity at 0 and 1. #[inline] pub fn exponential_in_out(t: f32) -> f32 { if t == 0. || t == 1. { t } else if t < 0.5 { 0.5 * powf(2.0, 20. * t - 10.) } else { 0.5 * powf(2.0, -20. * t + 10.) + 1. } } /// #[inline] pub fn back_in(t: f32) -> f32 { t * t * t - t * (t * PI).sin() } /// #[inline] pub fn back_out(t: f32) -> f32 { let f = 1. - t; 1. - (f * f * f - f * (f * PI).sin()) } /// #[inline] pub fn back_in_out(t: f32) -> f32 { if t < 0.5 { let f = 2. * t; 0.5 * (f * f * f - f * (f * PI).sin()) } else { let f = 1. - (2. * t - 1.); 0.5 * (1. - (f * f * f - f * (f * PI).sin())) + 0.5 } } /// /// /// Each bounce is modelled as a parabola. #[inline] pub fn bounce_in(t: f32) -> f32 { 1. - bounce_out(1. - t) } /// /// /// Each bounce is modelled as a parabola. #[inline] pub fn bounce_out(t: f32) -> f32 { if t < 4. / 11. { const T2: f32 = 121. / 16.; T2 * t * t } else if t < 8. / 11. { const T2: f32 = 363. / 40.; const T1: f32 = -99. / 10.; const T0: f32 = 17. / 5.; T2 * t * t + T1 * t + T0 } else if t < 9. / 10. { const T2: f32 = 4356. / 361.; const T1: f32 = -35442. / 1805.; const T0: f32 = 16061. / 1805.; T2 * t * t + T1 * t + T0 } else { const T2: f32 = 54. / 5.; const T1: f32 = -513. / 25.; const T0: f32 = 268. / 25.; T2 * t * t + T1 * t + T0 } } /// /// /// Each bounce is modelled as a parabola. #[inline] pub fn bounce_in_out(t: f32) -> f32 { if t < 0.5 { 0.5 * bounce_in(t * 2.) } else { 0.5 * bounce_out(t * 2. - 1.) + 0.5 } } emath-0.33.0/src/gui_rounding.rs000064400000000000000000000153331046102023000146400ustar 00000000000000/// We (sometimes) round sizes and coordinates to an even multiple of this value. /// /// This is only used for rounding _logical UI points_, used for widget coordinates and sizes. /// When rendering, you may want to round to an integer multiple of the physical _pixels_ instead, /// using [`GuiRounding::round_to_pixels`]. /// /// See [`GuiRounding::round_ui`] for more information. /// /// This constant has to be a (negative) power of two so that it can be represented exactly /// by a floating point number. /// /// If we pick too large a value (e.g. 1 or 1/2), then we get judder during scrolling and animations. /// If we pick too small a value (e.g. 1/4096), we run the risk of rounding errors again. /// /// `f32` has 23 bits of mantissa, so if we use e.g. 1/8 as the rounding factor, /// we can represent all numbers up to 2^20 exactly, which is plenty /// (to my knowledge there are no displays that are a million pixels wide). pub const GUI_ROUNDING: f32 = 1.0 / 32.0; /// Trait for rounding coordinates and sizes to align with either . /// /// See [`GuiRounding::round_ui`] for more information. pub trait GuiRounding { /// Rounds floating point numbers to an even multiple of the GUI rounding factor, [`crate::GUI_ROUNDING`]. /// /// Use this for widget coordinates and sizes. /// /// Rounding sizes and positions prevent rounding errors when doing sizing calculations. /// We don't round to integers, because that would be too coarse (causing visible juddering when scrolling, for instance). /// Instead we round to an even multiple of [`GUI_ROUNDING`]. fn round_ui(self) -> Self; /// Like [`Self::round_ui`], but always rounds towards negative infinity. fn floor_ui(self) -> Self; /// Round a size or position to an even multiple of the physical pixel size. /// /// This can be useful for crisp rendering. /// /// The `self` should be in coordinates of _logical UI points_. /// The argument `pixels_per_point` is the number of _physical pixels_ per logical UI point. /// For instance, on a high-DPI screen, `pixels_per_point` could be `2.0`. fn round_to_pixels(self, pixels_per_point: f32) -> Self; /// Will round the position to be in the center of a pixel. /// /// The pixel size is `1.0 / pixels_per_point`. /// /// So if `pixels_per_point = 2` (i.e. `pixel size = 0.5`), /// then the position will be rounded to the closest of `…, 0.25, 0.75, 1.25, …`. /// /// This is useful, for instance, when picking the center of a line that is one pixel wide. fn round_to_pixel_center(self, pixels_per_point: f32) -> Self; } impl GuiRounding for f32 { #[inline] fn round_ui(self) -> Self { (self / GUI_ROUNDING).round() * GUI_ROUNDING } #[inline] fn floor_ui(self) -> Self { (self / GUI_ROUNDING).floor() * GUI_ROUNDING } #[inline] fn round_to_pixels(self, pixels_per_point: f32) -> Self { (self * pixels_per_point).round() / pixels_per_point } #[inline] fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { ((self * pixels_per_point - 0.5).round() + 0.5) / pixels_per_point } } impl GuiRounding for f64 { #[inline] fn round_ui(self) -> Self { (self / GUI_ROUNDING as Self).round() * GUI_ROUNDING as Self } #[inline] fn floor_ui(self) -> Self { (self / GUI_ROUNDING as Self).floor() * GUI_ROUNDING as Self } #[inline] fn round_to_pixels(self, pixels_per_point: f32) -> Self { (self * pixels_per_point as Self).round() / pixels_per_point as Self } #[inline] fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { ((self * pixels_per_point as Self - 0.5).round() + 0.5) / pixels_per_point as Self } } impl GuiRounding for crate::Vec2 { #[inline] fn round_ui(self) -> Self { Self::new(self.x.round_ui(), self.y.round_ui()) } #[inline] fn floor_ui(self) -> Self { Self::new(self.x.floor_ui(), self.y.floor_ui()) } #[inline] fn round_to_pixels(self, pixels_per_point: f32) -> Self { Self::new( self.x.round_to_pixels(pixels_per_point), self.y.round_to_pixels(pixels_per_point), ) } // This doesn't really make sense for a Vec2, but πŸ€·β€β™‚οΈ #[inline] fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { Self::new( self.x.round_to_pixel_center(pixels_per_point), self.y.round_to_pixel_center(pixels_per_point), ) } } impl GuiRounding for crate::Pos2 { #[inline] fn round_ui(self) -> Self { Self::new(self.x.round_ui(), self.y.round_ui()) } #[inline] fn floor_ui(self) -> Self { Self::new(self.x.floor_ui(), self.y.floor_ui()) } #[inline] fn round_to_pixels(self, pixels_per_point: f32) -> Self { Self::new( self.x.round_to_pixels(pixels_per_point), self.y.round_to_pixels(pixels_per_point), ) } #[inline] fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { Self::new( self.x.round_to_pixel_center(pixels_per_point), self.y.round_to_pixel_center(pixels_per_point), ) } } impl GuiRounding for crate::Rect { /// Rounded so that two adjacent rects that tile perfectly /// will continue to tile perfectly. #[inline] fn round_ui(self) -> Self { Self::from_min_max(self.min.round_ui(), self.max.round_ui()) } /// Rounded so that two adjacent rects that tile perfectly /// will continue to tile perfectly. #[inline] fn floor_ui(self) -> Self { Self::from_min_max(self.min.floor_ui(), self.max.floor_ui()) } /// Rounded so that two adjacent rects that tile perfectly /// will continue to tile perfectly. #[inline] fn round_to_pixels(self, pixels_per_point: f32) -> Self { Self::from_min_max( self.min.round_to_pixels(pixels_per_point), self.max.round_to_pixels(pixels_per_point), ) } /// Rounded so that two adjacent rects that tile perfectly /// will continue to tile perfectly. #[inline] fn round_to_pixel_center(self, pixels_per_point: f32) -> Self { Self::from_min_max( self.min.round_to_pixel_center(pixels_per_point), self.max.round_to_pixel_center(pixels_per_point), ) } } #[test] fn test_gui_rounding() { assert_eq!(0.0_f32.round_ui(), 0.0); assert_eq!((GUI_ROUNDING * 1.11).round_ui(), GUI_ROUNDING); assert_eq!((-GUI_ROUNDING * 1.11).round_ui(), -GUI_ROUNDING); assert_eq!(f32::NEG_INFINITY.round_ui(), f32::NEG_INFINITY); assert_eq!(f32::INFINITY.round_ui(), f32::INFINITY); assert_eq!(0.17_f32.round_to_pixel_center(2.0), 0.25); } emath-0.33.0/src/history.rs000064400000000000000000000150171046102023000136470ustar 00000000000000use 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)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct History { /// 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 History 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, 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 { 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 + '_ { self.values.iter().map(|(time, value)| (*time, *value)) } pub fn values(&self) -> impl ExactSizeIterator + '_ { 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() { debug_assert!(*last_time <= now, "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 { 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 { 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 History where T: Copy, T: std::iter::Sum, T: std::ops::Div, { #[inline] pub fn sum(&self) -> T { self.values().sum() } pub fn average(&self) -> Option { let num = self.len(); if num > 0 { Some(self.sum() / (num as f32)) } else { None } } } impl History where T: Copy, T: std::iter::Sum, T: std::ops::Div, T: std::ops::Mul, { /// 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 { Some(self.average()? * self.rate()?) } } impl History where T: Copy, T: std::ops::Sub, Vel: std::ops::Div, { /// 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 { 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 } } } emath-0.33.0/src/lib.rs000064400000000000000000000320251046102023000127120ustar 00000000000000//! 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)] use std::ops::{Add, Div, Mul, RangeInclusive, Sub}; // ---------------------------------------------------------------------------- pub mod align; pub mod easing; mod gui_rounding; mod history; mod numeric; mod ordered_float; mod pos2; mod range; mod rect; mod rect_align; mod rect_transform; mod rot2; pub mod smart_aim; mod ts_transform; mod vec2; mod vec2b; pub use self::{ align::{Align, Align2}, gui_rounding::{GUI_ROUNDING, GuiRounding}, history::History, numeric::*, ordered_float::*, pos2::*, range::Rangef, rect::*, rect_align::RectAlign, rect_transform::*, rot2::*, ts_transform::*, vec2::*, vec2b::*, }; // ---------------------------------------------------------------------------- /// Helper trait to implement [`lerp`] and [`remap`]. pub trait One { const ONE: Self; } impl One for f32 { const ONE: Self = 1.0; } impl One for f64 { const ONE: Self = 1.0; } /// Helper trait to implement [`lerp`] and [`remap`]. pub trait Real: Copy + PartialEq + PartialOrd + One + Add + Sub + Mul + Div { } 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(range: impl Into>, t: T) -> R where T: Real + Mul, R: Copy + Add, { let range = range.into(); (T::ONE - t) * *range.start() + t * *range.end() } /// This is a faster version of [`f32::midpoint`] which doesn't handle overflow. /// /// ``` /// # use emath::fast_midpoint; /// assert_eq!(fast_midpoint(1.0, 5.0), 3.0); /// ``` #[inline(always)] pub fn fast_midpoint(a: R, b: R) -> R where R: Copy + Add + Div + One, { let two = R::ONE + R::ONE; (a + b) / two } /// 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(range: RangeInclusive, value: R) -> Option where R: Copy + PartialEq + Sub + Div, { 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(x: T, from: impl Into>, to: impl Into>) -> T where T: Real, { let from = from.into(); let to = to.into(); debug_assert!( from.start() != from.end(), "from.start() and from.end() should not be equal" ); 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( x: T, from: impl Into>, to: impl Into>, ) -> T where T: Real, { let from = from.into(); let to = to.into(); 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(), "from.start() and from.end() should not be equal" ); 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!("{value:.decimal_places$}").parse().unwrap_or(value) } pub fn format_with_minimum_decimals(value: f64, decimals: usize) -> String { format_with_decimals_in_range(value, decimals..=6) } /// Use as few decimals as possible to show the value accurately, but within the given range. /// /// Decimals are counted after the decimal point. pub fn format_with_decimals_in_range(value: f64, decimal_range: RangeInclusive) -> String { let min_decimals = *decimal_range.start(); let max_decimals = *decimal_range.end(); debug_assert!( min_decimals <= max_decimals, "min_decimals should be <= max_decimals, but got min_decimals: {min_decimals}, max_decimals: {max_decimals}" ); debug_assert!( max_decimals < 100, "max_decimals should be < 100, but got {max_decimals}" ); 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!("{value:.decimals$}"); let epsilon = 16.0 * f32::EPSILON; // margin large enough to handle most peoples round-tripping needs if almost_equal(text.parse::().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!("{value:.max_decimals$}") } /// 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 } } #[expect(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) } /// If you have a value animating over time, /// how much towards its target do you need to move it this frame? /// /// You only need to store the start time and target value in order to animate using this function. /// /// ``` rs /// struct Animation { /// current_value: f32, /// /// animation_time_span: (f64, f64), /// target_value: f32, /// } /// /// impl Animation { /// fn update(&mut self, now: f64, dt: f32) { /// let t = interpolation_factor(self.animation_time_span, now, dt, ease_in_ease_out); /// self.current_value = emath::lerp(self.current_value..=self.target_value, t); /// } /// } /// ``` pub fn interpolation_factor( (start_time, end_time): (f64, f64), current_time: f64, dt: f32, easing: impl Fn(f32) -> f32, ) -> f32 { let animation_duration = (end_time - start_time) as f32; let prev_time = current_time - dt as f64; let prev_t = easing((prev_time - start_time) as f32 / animation_duration); let end_t = easing((current_time - start_time) as f32 / animation_duration); if end_t < 1.0 { (end_t - prev_t) / (1.0 - prev_t) } else { 1.0 } } /// Ease in, ease out. /// /// `f(0) = 0, f'(0) = 0, f(1) = 1, f'(1) = 0`. #[inline] pub fn ease_in_ease_out(t: f32) -> f32 { let t = t.clamp(0.0, 1.0); (3.0 * t * t - 2.0 * t * t * t).clamp(0.0, 1.0) } emath-0.33.0/src/numeric.rs000064400000000000000000000051461046102023000136120ustar 00000000000000/// 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 = $t::MIN; const MAX: Self = $t::MAX; #[inline(always)] fn to_f64(self) -> f64 { #[allow(trivial_numeric_casts, clippy::allow_attributes)] { self as f64 } } #[inline(always)] fn from_f64(num: f64) -> Self { #[allow(trivial_numeric_casts, clippy::allow_attributes)] { num as Self } } } }; } macro_rules! impl_numeric_integer { ($t: ident) => { impl Numeric for $t { const INTEGRAL: bool = true; const MIN: Self = $t::MIN; const MAX: Self = $t::MAX; #[inline(always)] fn to_f64(self) -> f64 { self as f64 } #[inline(always)] fn from_f64(num: f64) -> Self { num as Self } } }; } macro_rules! impl_numeric_non_zero_unsigned { ($t: path) => { impl Numeric for $t { const INTEGRAL: bool = true; const MIN: Self = Self::MIN; const MAX: Self = Self::MAX; #[inline(always)] fn to_f64(self) -> f64 { self.get() as f64 } #[inline(always)] fn from_f64(num: f64) -> Self { Self::new(num.round().max(1.0) as _).unwrap_or(Self::MIN) } } }; } 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); impl_numeric_non_zero_unsigned!(std::num::NonZeroU8); impl_numeric_non_zero_unsigned!(std::num::NonZeroU16); impl_numeric_non_zero_unsigned!(std::num::NonZeroU32); impl_numeric_non_zero_unsigned!(std::num::NonZeroU64); impl_numeric_non_zero_unsigned!(std::num::NonZeroU128); impl_numeric_non_zero_unsigned!(std::num::NonZeroUsize); emath-0.33.0/src/ordered_float.rs000064400000000000000000000074221046102023000147600ustar 00000000000000//! Total order on floating point types. //! Can be used for sorting, min/max computation, and other collection algorithms. use std::cmp::Ordering; use std::hash::{Hash, Hasher}; /// Wraps a floating-point value to add total order and hash. /// Possible types for `T` are `f32` and `f64`. /// /// All NaNs are considered equal to each other. /// The size of zero is ignored. /// /// See also [`Float`]. #[derive(Clone, Copy)] pub struct OrderedFloat(pub T); impl OrderedFloat { #[inline] pub fn into_inner(self) -> T { self.0 } } impl std::fmt::Debug for OrderedFloat { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl Eq for OrderedFloat {} impl PartialEq for OrderedFloat { #[inline] fn eq(&self, other: &Self) -> bool { // NaNs are considered equal (equivalent) when it comes to ordering if self.0.is_nan() { other.0.is_nan() } else { self.0 == other.0 } } } impl PartialOrd for OrderedFloat { #[inline] fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for OrderedFloat { #[inline] fn cmp(&self, other: &Self) -> Ordering { match self.0.partial_cmp(&other.0) { Some(ord) => ord, None => self.0.is_nan().cmp(&other.0.is_nan()), } } } impl Hash for OrderedFloat { fn hash(&self, state: &mut H) { self.0.hash(state); } } impl From for OrderedFloat { #[inline] fn from(val: T) -> Self { Self(val) } } // ---------------------------------------------------------------------------- /// Extension trait to provide `ord()` method. /// /// Example with `f64`: /// ``` /// use emath::Float as _; /// /// let array = [1.0, 2.5, 2.0]; /// let max = array.iter().max_by_key(|val| val.ord()); /// /// assert_eq!(max, Some(&2.5)); /// ``` pub trait Float: PartialOrd + PartialEq + private::FloatImpl { /// Type to provide total order, useful as key in sorted contexts. fn ord(self) -> OrderedFloat where Self: Sized; } impl Float for f32 { #[inline] fn ord(self) -> OrderedFloat { OrderedFloat(self) } } impl Float for f64 { #[inline] fn ord(self) -> OrderedFloat { OrderedFloat(self) } } // Keep this trait in private module, to avoid exposing its methods as extensions in user code mod private { use super::{Hash as _, Hasher}; pub trait FloatImpl { fn is_nan(&self) -> bool; fn hash(&self, state: &mut H); } impl FloatImpl for f32 { #[inline] fn is_nan(&self) -> bool { Self::is_nan(*self) } #[inline] fn hash(&self, state: &mut H) { let bits = if self.is_nan() { // "Canonical" NaN. 0x7fc00000 } else { // A trick taken from the `ordered-float` crate: -0.0 + 0.0 == +0.0. // https://github.com/reem/rust-ordered-float/blob/1841f0541ea0e56779cbac03de2705149e020675/src/lib.rs#L2178-L2181 (self + 0.0).to_bits() }; bits.hash(state); } } impl FloatImpl for f64 { #[inline] fn is_nan(&self) -> bool { Self::is_nan(*self) } #[inline] fn hash(&self, state: &mut H) { let bits = if self.is_nan() { // "Canonical" NaN. 0x7ff8000000000000 } else { (self + 0.0).to_bits() }; bits.hash(state); } } } emath-0.33.0/src/pos2.rs000064400000000000000000000167161046102023000130400ustar 00000000000000use std::{ fmt, ops::{Add, AddAssign, MulAssign, Sub, SubAssign}, }; use crate::{Div, Mul, Vec2, lerp}; /// 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 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 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> for Pos2 { #[inline(always)] fn from(v: mint::Point2) -> Self { Self::new(v.x, v.y) } } #[cfg(feature = "mint")] impl From for mint::Point2 { #[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), } } /// Linearly interpolate towards another point, so that `0.0 => self, 1.0 => other`. pub fn lerp(&self, other: Self, t: f32) -> Self { Self { x: lerp(self.x..=other.x, t), y: lerp(self.y..=other.y, t), } } } impl std::ops::Index 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 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 for Pos2 { #[inline(always)] fn add_assign(&mut self, rhs: Vec2) { *self = Self { x: self.x + rhs.x, y: self.y + rhs.y, }; } } impl SubAssign for Pos2 { #[inline(always)] fn sub_assign(&mut self, rhs: Vec2) { *self = Self { x: self.x - rhs.x, y: self.y - rhs.y, }; } } impl Add for Pos2 { type Output = Self; #[inline(always)] fn add(self, rhs: Vec2) -> Self { Self { x: self.x + rhs.x, y: self.y + rhs.y, } } } impl Sub for Pos2 { type Output = Vec2; #[inline(always)] fn sub(self, rhs: Self) -> Vec2 { Vec2 { x: self.x - rhs.x, y: self.y - rhs.y, } } } impl Sub for Pos2 { type Output = Self; #[inline(always)] fn sub(self, rhs: Vec2) -> Self { Self { x: self.x - rhs.x, y: self.y - rhs.y, } } } impl Mul for Pos2 { type Output = Self; #[inline(always)] fn mul(self, factor: f32) -> Self { Self { x: self.x * factor, y: self.y * factor, } } } impl Mul for f32 { type Output = Pos2; #[inline(always)] fn mul(self, vec: Pos2) -> Pos2 { Pos2 { x: self * vec.x, y: self * vec.y, } } } impl MulAssign for Pos2 { #[inline(always)] fn mul_assign(&mut self, rhs: f32) { self.x *= rhs; self.y *= rhs; } } impl Div for Pos2 { type Output = Self; #[inline(always)] fn div(self, factor: f32) -> Self { Self { x: self.x / factor, y: self.y / factor, } } } impl fmt::Debug for Pos2 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(precision) = f.precision() { write!(f, "[{1:.0$} {2:.0$}]", precision, self.x, self.y) } else { write!(f, "[{:.1} {:.1}]", self.x, self.y) } } } impl fmt::Display for Pos2 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("[")?; self.x.fmt(f)?; f.write_str(" ")?; self.y.fmt(f)?; f.write_str("]")?; Ok(()) } } emath-0.33.0/src/range.rs000064400000000000000000000130621046102023000132400ustar 00000000000000use std::ops::{RangeFrom, RangeFull, RangeInclusive, RangeToInclusive}; /// Inclusive range of floats, i.e. `min..=max`, but more ergonomic than [`RangeInclusive`]. #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct Rangef { pub min: f32, pub max: f32, } impl Rangef { /// Infinite range that contains everything, from -∞ to +∞, inclusive. pub const EVERYTHING: Self = Self { min: f32::NEG_INFINITY, max: f32::INFINITY, }; /// The inverse of [`Self::EVERYTHING`]: stretches from positive infinity to negative infinity. /// Contains nothing. pub const NOTHING: Self = Self { min: f32::INFINITY, max: f32::NEG_INFINITY, }; /// An invalid [`Rangef`] filled with [`f32::NAN`]. pub const NAN: Self = Self { min: f32::NAN, max: f32::NAN, }; #[inline] pub fn new(min: f32, max: f32) -> Self { Self { min, max } } #[inline] pub fn point(min_and_max: f32) -> Self { Self { min: min_and_max, max: min_and_max, } } /// The length of the range, i.e. `max - min`. #[inline] pub fn span(self) -> f32 { self.max - self.min } /// The center of the range #[inline] pub fn center(self) -> f32 { 0.5 * (self.min + self.max) } #[inline] #[must_use] pub fn contains(self, x: f32) -> bool { self.min <= x && x <= self.max } /// Equivalent to `x.clamp(min, max)` #[inline] #[must_use] pub fn clamp(self, x: f32) -> f32 { x.clamp(self.min, self.max) } /// Flip `min` and `max` if needed, so that `min <= max` after. #[inline] pub fn as_positive(self) -> Self { Self { min: self.min.min(self.max), max: self.min.max(self.max), } } /// Shrink by this much on each side, keeping the center #[inline] #[must_use] pub fn shrink(self, amnt: f32) -> Self { Self { min: self.min + amnt, max: self.max - amnt, } } /// Expand by this much on each side, keeping the center #[inline] #[must_use] pub fn expand(self, amnt: f32) -> Self { Self { min: self.min - amnt, max: self.max + amnt, } } /// Flip the min and the max #[inline] #[must_use] pub fn flip(self) -> Self { Self { min: self.max, max: self.min, } } /// The overlap of two ranges, i.e. the range that is contained by both. /// /// If the ranges do not overlap, returns a range with `span() < 0.0`. /// /// ``` /// # use emath::Rangef; /// assert_eq!(Rangef::new(0.0, 10.0).intersection(Rangef::new(5.0, 15.0)), Rangef::new(5.0, 10.0)); /// assert_eq!(Rangef::new(0.0, 10.0).intersection(Rangef::new(10.0, 20.0)), Rangef::new(10.0, 10.0)); /// assert!(Rangef::new(0.0, 10.0).intersection(Rangef::new(20.0, 30.0)).span() < 0.0); /// ``` #[inline] #[must_use] pub fn intersection(self, other: Self) -> Self { Self { min: self.min.max(other.min), max: self.max.min(other.max), } } /// Do the two ranges intersect? /// /// ``` /// # use emath::Rangef; /// assert!(Rangef::new(0.0, 10.0).intersects(Rangef::new(5.0, 15.0))); /// assert!(Rangef::new(0.0, 10.0).intersects(Rangef::new(5.0, 6.0))); /// assert!(Rangef::new(0.0, 10.0).intersects(Rangef::new(10.0, 20.0))); /// assert!(!Rangef::new(0.0, 10.0).intersects(Rangef::new(20.0, 30.0))); /// ``` #[inline] #[must_use] pub fn intersects(self, other: Self) -> bool { other.min <= self.max && self.min <= other.max } } impl From for RangeInclusive { #[inline] fn from(Rangef { min, max }: Rangef) -> Self { min..=max } } impl From<&Rangef> for RangeInclusive { #[inline] fn from(&Rangef { min, max }: &Rangef) -> Self { min..=max } } impl From> for Rangef { #[inline] fn from(range: RangeInclusive) -> Self { Self::new(*range.start(), *range.end()) } } impl From<&RangeInclusive> for Rangef { #[inline] fn from(range: &RangeInclusive) -> Self { Self::new(*range.start(), *range.end()) } } impl From> for Rangef { #[inline] fn from(range: RangeFrom) -> Self { Self::new(range.start, f32::INFINITY) } } impl From<&RangeFrom> for Rangef { #[inline] fn from(range: &RangeFrom) -> Self { Self::new(range.start, f32::INFINITY) } } impl From for Rangef { #[inline] fn from(_: RangeFull) -> Self { Self::new(f32::NEG_INFINITY, f32::INFINITY) } } impl From<&RangeFull> for Rangef { #[inline] fn from(_: &RangeFull) -> Self { Self::new(f32::NEG_INFINITY, f32::INFINITY) } } impl From> for Rangef { #[inline] fn from(range: RangeToInclusive) -> Self { Self::new(f32::NEG_INFINITY, range.end) } } impl PartialEq> for Rangef { #[inline] fn eq(&self, other: &RangeInclusive) -> bool { self.min == *other.start() && self.max == *other.end() } } impl PartialEq for RangeInclusive { #[inline] fn eq(&self, other: &Rangef) -> bool { *self.start() == other.min && *self.end() == other.max } } emath-0.33.0/src/rect.rs000064400000000000000000000611631046102023000131060ustar 00000000000000use std::fmt; use crate::{Div, Mul, NumExt as _, Pos2, Rangef, Rot2, Vec2, fast_midpoint, lerp, pos2, vec2}; use std::ops::{BitOr, BitOrAssign}; /// 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. /// /// `Rect` does NOT implement `Default`, because there is no obvious default value. /// [`Rect::ZERO`] may seem reasonable, but when used as a bounding box, [`Rect::NOTHING`] /// is a better default - so be explicit instead! #[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(-f32::INFINITY, -f32::INFINITY), max: pos2(f32::INFINITY, f32::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(f32::INFINITY, f32::INFINITY), max: pos2(-f32::INFINITY, -f32::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), }; /// A [`Rect`] filled with zeroes. pub const ZERO: Self = Self { min: Pos2::ZERO, max: Pos2::ZERO, }; #[inline(always)] pub const fn from_min_max(min: Pos2, max: Pos2) -> Self { Self { min, max } } /// left-top corner plus a size (stretching right-down). #[inline(always)] pub fn from_min_size(min: Pos2, size: Vec2) -> Self { Self { min, max: min + size, } } #[inline(always)] pub fn from_center_size(center: Pos2, size: Vec2) -> Self { Self { min: center - size * 0.5, max: center + size * 0.5, } } #[inline(always)] pub fn from_x_y_ranges(x_range: impl Into, y_range: impl Into) -> Self { let x_range = x_range.into(); let y_range = y_range.into(); Self { min: pos2(x_range.min, y_range.min), max: pos2(x_range.max, y_range.max), } } /// Returns the bounding rectangle of the two points. #[inline] pub fn from_two_pos(a: Pos2, b: Pos2) -> Self { Self { min: pos2(a.x.min(b.x), a.y.min(b.y)), max: pos2(a.x.max(b.x), a.y.max(b.y)), } } /// A zero-sized rect at a specific point. #[inline] pub fn from_pos(point: Pos2) -> Self { Self { min: point, max: point, } } /// Bounding-box around the points. pub fn from_points(points: &[Pos2]) -> Self { let mut rect = Self::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 } #[must_use] #[inline] pub fn with_min_x(mut self, min_x: f32) -> Self { self.min.x = min_x; self } #[must_use] #[inline] pub fn with_min_y(mut self, min_y: f32) -> Self { self.min.y = min_y; self } #[must_use] #[inline] pub fn with_max_x(mut self, max_x: f32) -> Self { self.max.x = max_x; self } #[must_use] #[inline] pub fn with_max_y(mut self, max_y: f32) -> Self { self.max.y = max_y; self } /// 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 { Self::from_min_max(self.min - amnt, self.max + amnt) } /// Scale up by this factor in each direction, keeping the center #[must_use] pub fn scale_from_center(self, scale_factor: f32) -> Self { self.scale_from_center2(Vec2::splat(scale_factor)) } /// Scale up by this factor in each direction, keeping the center #[must_use] pub fn scale_from_center2(self, scale_factor: Vec2) -> Self { Self::from_center_size(self.center(), self.size() * scale_factor) } /// 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 { Self::from_min_max(self.min + amnt, self.max - amnt) } #[must_use] #[inline] pub fn translate(self, amnt: Vec2) -> Self { Self::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: Self) -> 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: Self) -> 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: Self) -> Self { Self { 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: Self) -> Self { Self { min: self.min.max(other.min), max: self.max.min(other.max), } } #[inline(always)] pub fn center(&self) -> Pos2 { Pos2 { x: fast_midpoint(self.min.x, self.max.x), y: fast_midpoint(self.min.y, self.max.y), } } /// `rect.size() == Vec2 { x: rect.width(), y: rect.height() }` #[inline(always)] pub fn size(&self) -> Vec2 { self.max - self.min } /// Note: this can be negative. #[inline(always)] pub fn width(&self) -> f32 { self.max.x - self.min.x } /// Note: this can be negative. #[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) } } /// This is never negative, and instead returns zero for negative rectangles. #[inline(always)] pub fn area(&self) -> f32 { self.width().at_least(0.0) * self.height().at_least(0.0) } /// The distance from the rect to the position. /// /// The distance is zero when the position is in the interior of the rectangle. /// /// [Negative rectangles](Self::is_negative) always return [`f32::INFINITY`]. #[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. /// /// [Negative rectangles](Self::is_negative) always return [`f32::INFINITY`]. #[inline] pub fn distance_sq_to_pos(&self, pos: Pos2) -> f32 { if self.is_negative() { return f32::INFINITY; } 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. /// /// [Negative rectangles](Self::is_negative) always return [`f32::INFINITY`]. /// /// ``` /// # use emath::{pos2, Rect}; /// let rect = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)); /// assert_eq!(rect.signed_distance_to_pos(pos2(0.50, 0.50)), -0.50); /// assert_eq!(rect.signed_distance_to_pos(pos2(0.75, 0.50)), -0.25); /// assert_eq!(rect.signed_distance_to_pos(pos2(1.50, 0.50)), 0.50); /// ``` pub fn signed_distance_to_pos(&self, pos: Pos2) -> f32 { if self.is_negative() { return f32::INFINITY; } let edge_distances = (pos - self.center()).abs() - self.size() * 0.5; let inside_dist = edge_distances.max_elem().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`]. #[inline] pub fn lerp_inside(&self, t: Vec2) -> Pos2 { Pos2 { x: lerp(self.min.x..=self.max.x, t.x), y: lerp(self.min.y..=self.max.y, t.y), } } /// Linearly self towards other rect. #[inline] pub fn lerp_towards(&self, other: &Self, t: f32) -> Self { Self { min: self.min.lerp(other.min, t), max: self.max.lerp(other.max, t), } } #[inline(always)] pub fn x_range(&self) -> Rangef { Rangef::new(self.min.x, self.max.x) } #[inline(always)] pub fn y_range(&self) -> Rangef { Rangef::new(self.min.y, self.max.y) } #[inline(always)] pub fn bottom_up_range(&self) -> Rangef { Rangef::new(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)] #[doc(alias = "top_left")] 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)] #[doc(alias = "top_right")] 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)] #[doc(alias = "bottom_left")] 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)] #[doc(alias = "bottom_right")] pub fn right_bottom(&self) -> Pos2 { pos2(self.right(), self.bottom()) } /// Split rectangle in left and right halves. `t` is expected to be in the (0,1) range. pub fn split_left_right_at_fraction(&self, t: f32) -> (Self, Self) { self.split_left_right_at_x(lerp(self.min.x..=self.max.x, t)) } /// Split rectangle in left and right halves at the given `x` coordinate. pub fn split_left_right_at_x(&self, split_x: f32) -> (Self, Self) { let left = Self::from_min_max(self.min, Pos2::new(split_x, self.max.y)); let right = Self::from_min_max(Pos2::new(split_x, self.min.y), self.max); (left, right) } /// Split rectangle in top and bottom halves. `t` is expected to be in the (0,1) range. pub fn split_top_bottom_at_fraction(&self, t: f32) -> (Self, Self) { self.split_top_bottom_at_y(lerp(self.min.y..=self.max.y, t)) } /// Split rectangle in top and bottom halves at the given `y` coordinate. pub fn split_top_bottom_at_y(&self, split_y: f32) -> (Self, Self) { let top = Self::from_min_max(self.min, Pos2::new(self.max.x, split_y)); let bottom = Self::from_min_max(Pos2::new(self.min.x, split_y), self.max); (top, bottom) } } impl Rect { /// Does this Rect intersect the given ray (where `d` is normalized)? /// /// A ray that starts inside the rect will return `true`. pub fn intersects_ray(&self, o: Pos2, d: Vec2) -> bool { debug_assert!( d.is_normalized(), "Debug assert: expected normalized direction, but `d` has length {}", d.length() ); let mut tmin = -f32::INFINITY; let mut tmax = f32::INFINITY; if d.x != 0.0 { let tx1 = (self.min.x - o.x) / d.x; let tx2 = (self.max.x - o.x) / d.x; tmin = tmin.max(tx1.min(tx2)); tmax = tmax.min(tx1.max(tx2)); } if d.y != 0.0 { let ty1 = (self.min.y - o.y) / d.y; let ty2 = (self.max.y - o.y) / d.y; tmin = tmin.max(ty1.min(ty2)); tmax = tmax.min(ty1.max(ty2)); } 0.0 <= tmax && tmin <= tmax } /// Where does a ray from the center intersect the rectangle? /// /// `d` is the direction of the ray and assumed to be normalized. pub fn intersects_ray_from_center(&self, d: Vec2) -> Pos2 { debug_assert!( d.is_normalized(), "expected normalized direction, but `d` has length {}", d.length() ); let mut tmin = f32::NEG_INFINITY; let mut tmax = f32::INFINITY; for i in 0..2 { let inv_d = 1.0 / -d[i]; let mut t0 = (self.min[i] - self.center()[i]) * inv_d; let mut t1 = (self.max[i] - self.center()[i]) * inv_d; if inv_d < 0.0 { std::mem::swap(&mut t0, &mut t1); } tmin = tmin.max(t0); tmax = tmax.min(t1); } let t = tmax.min(tmin); self.center() + t * -d } } impl fmt::Debug for Rect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(precision) = f.precision() { write!(f, "[{1:.0$?} - {2:.0$?}]", precision, self.min, self.max) } else { write!(f, "[{:?} - {:?}]", self.min, self.max) } } } impl fmt::Display for Rect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("[")?; self.min.fmt(f)?; f.write_str(" - ")?; self.max.fmt(f)?; f.write_str("]")?; Ok(()) } } /// from (min, max) or (left top, right bottom) impl From<[Pos2; 2]> for Rect { #[inline] fn from([min, max]: [Pos2; 2]) -> Self { Self { min, max } } } impl Mul for Rect { type Output = Self; #[inline] fn mul(self, factor: f32) -> Self { Self { min: self.min * factor, max: self.max * factor, } } } impl Mul for f32 { type Output = Rect; #[inline] fn mul(self, vec: Rect) -> Rect { Rect { min: self * vec.min, max: self * vec.max, } } } impl Div for Rect { type Output = Self; #[inline] fn div(self, factor: f32) -> Self { Self { min: self.min / factor, max: self.max / factor, } } } impl BitOr for Rect { type Output = Self; #[inline] fn bitor(self, other: Self) -> Self { self.union(other) } } impl BitOrAssign for Rect { #[inline] fn bitor_assign(&mut self, other: Self) { *self = self.union(other); } } #[cfg(test)] mod tests { use super::*; #[test] fn test_rect() { let r = Rect::from_min_max(pos2(10.0, 10.0), pos2(20.0, 20.0)); assert_eq!(r.distance_sq_to_pos(pos2(15.0, 15.0)), 0.0); assert_eq!(r.distance_sq_to_pos(pos2(10.0, 15.0)), 0.0); assert_eq!(r.distance_sq_to_pos(pos2(10.0, 10.0)), 0.0); assert_eq!(r.distance_sq_to_pos(pos2(5.0, 15.0)), 25.0); // left of assert_eq!(r.distance_sq_to_pos(pos2(25.0, 15.0)), 25.0); // right of assert_eq!(r.distance_sq_to_pos(pos2(15.0, 5.0)), 25.0); // above assert_eq!(r.distance_sq_to_pos(pos2(15.0, 25.0)), 25.0); // below assert_eq!(r.distance_sq_to_pos(pos2(25.0, 5.0)), 50.0); // right and above } #[test] fn scale_rect() { let c = pos2(100.0, 50.0); let r = Rect::from_center_size(c, vec2(30.0, 60.0)); assert_eq!( r.scale_from_center(2.0), Rect::from_center_size(c, vec2(60.0, 120.0)) ); assert_eq!( r.scale_from_center(0.5), Rect::from_center_size(c, vec2(15.0, 30.0)) ); assert_eq!( r.scale_from_center2(vec2(2.0, 3.0)), Rect::from_center_size(c, vec2(60.0, 180.0)) ); } #[expect(clippy::print_stdout)] #[test] fn test_ray_intersection() { let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0)); println!("Righward ray from left:"); assert!(rect.intersects_ray(pos2(0.0, 2.0), Vec2::RIGHT)); println!("Righward ray from center:"); assert!(rect.intersects_ray(pos2(2.0, 2.0), Vec2::RIGHT)); println!("Righward ray from right:"); assert!(!rect.intersects_ray(pos2(4.0, 2.0), Vec2::RIGHT)); println!("Leftward ray from left:"); assert!(!rect.intersects_ray(pos2(0.0, 2.0), Vec2::LEFT)); println!("Leftward ray from center:"); assert!(rect.intersects_ray(pos2(2.0, 2.0), Vec2::LEFT)); println!("Leftward ray from right:"); assert!(rect.intersects_ray(pos2(4.0, 2.0), Vec2::LEFT)); } #[test] fn test_ray_from_center_intersection() { let rect = Rect::from_min_max(pos2(1.0, 1.0), pos2(3.0, 3.0)); assert_eq!( rect.intersects_ray_from_center(Vec2::RIGHT), pos2(3.0, 2.0), "rightward ray" ); assert_eq!( rect.intersects_ray_from_center(Vec2::UP), pos2(2.0, 1.0), "upward ray" ); assert_eq!( rect.intersects_ray_from_center(Vec2::LEFT), pos2(1.0, 2.0), "leftward ray" ); assert_eq!( rect.intersects_ray_from_center(Vec2::DOWN), pos2(2.0, 3.0), "downward ray" ); assert_eq!( rect.intersects_ray_from_center((Vec2::LEFT + Vec2::DOWN).normalized()), pos2(1.0, 3.0), "bottom-left corner ray" ); assert_eq!( rect.intersects_ray_from_center((Vec2::LEFT + Vec2::UP).normalized()), pos2(1.0, 1.0), "top-left corner ray" ); assert_eq!( rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::DOWN).normalized()), pos2(3.0, 3.0), "bottom-right corner ray" ); assert_eq!( rect.intersects_ray_from_center((Vec2::RIGHT + Vec2::UP).normalized()), pos2(3.0, 1.0), "top-right corner ray" ); } } emath-0.33.0/src/rect_align.rs000064400000000000000000000217311046102023000142550ustar 00000000000000use crate::{Align2, Pos2, Rect, Vec2}; /// Position a child [`Rect`] relative to a parent [`Rect`]. /// /// The corner from [`RectAlign::child`] on the new rect will be aligned to /// the corner from [`RectAlign::parent`] on the original rect. /// /// There are helper constants for the 12 common menu positions: /// ```text /// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” /// β”‚ TOP_START β”‚ β”‚ TOP β”‚ β”‚ TOP_END β”‚ /// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ /// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” /// β”‚LEFT_STARTβ”‚ β”‚ β”‚ β”‚RIGHT_STARTβ”‚ /// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ /// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” /// β”‚ LEFT β”‚ β”‚ some_rect β”‚ β”‚ RIGHT β”‚ /// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ /// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” /// β”‚ LEFT_END β”‚ β”‚ β”‚ β”‚ RIGHT_END β”‚ /// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ /// β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” /// β”‚BOTTOM_STARTβ”‚ β”‚BOTTOMβ”‚ β”‚BOTTOM_ENDβ”‚ /// β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ /// ``` // There is no `new` function on purpose, since writing out `parent` and `child` is more // reasonable. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct RectAlign { /// The alignment in the parent (original) rect. pub parent: Align2, /// The alignment in the child (new) rect. pub child: Align2, } impl Default for RectAlign { fn default() -> Self { Self::BOTTOM_START } } impl RectAlign { /// Along the top edge, leftmost. pub const TOP_START: Self = Self { parent: Align2::LEFT_TOP, child: Align2::LEFT_BOTTOM, }; /// Along the top edge, centered. pub const TOP: Self = Self { parent: Align2::CENTER_TOP, child: Align2::CENTER_BOTTOM, }; /// Along the top edge, rightmost. pub const TOP_END: Self = Self { parent: Align2::RIGHT_TOP, child: Align2::RIGHT_BOTTOM, }; /// Along the right edge, topmost. pub const RIGHT_START: Self = Self { parent: Align2::RIGHT_TOP, child: Align2::LEFT_TOP, }; /// Along the right edge, centered. pub const RIGHT: Self = Self { parent: Align2::RIGHT_CENTER, child: Align2::LEFT_CENTER, }; /// Along the right edge, bottommost. pub const RIGHT_END: Self = Self { parent: Align2::RIGHT_BOTTOM, child: Align2::LEFT_BOTTOM, }; /// Along the bottom edge, rightmost. pub const BOTTOM_END: Self = Self { parent: Align2::RIGHT_BOTTOM, child: Align2::RIGHT_TOP, }; /// Along the bottom edge, centered. pub const BOTTOM: Self = Self { parent: Align2::CENTER_BOTTOM, child: Align2::CENTER_TOP, }; /// Along the bottom edge, leftmost. pub const BOTTOM_START: Self = Self { parent: Align2::LEFT_BOTTOM, child: Align2::LEFT_TOP, }; /// Along the left edge, bottommost. pub const LEFT_END: Self = Self { parent: Align2::LEFT_BOTTOM, child: Align2::RIGHT_BOTTOM, }; /// Along the left edge, centered. pub const LEFT: Self = Self { parent: Align2::LEFT_CENTER, child: Align2::RIGHT_CENTER, }; /// Along the left edge, topmost. pub const LEFT_START: Self = Self { parent: Align2::LEFT_TOP, child: Align2::RIGHT_TOP, }; /// The 12 most common menu positions as an array, for use with [`RectAlign::find_best_align`]. pub const MENU_ALIGNS: [Self; 12] = [ Self::BOTTOM_START, Self::BOTTOM_END, Self::TOP_START, Self::TOP_END, Self::RIGHT_END, Self::RIGHT_START, Self::LEFT_END, Self::LEFT_START, // These come last on purpose, we prefer the corner ones Self::TOP, Self::RIGHT, Self::BOTTOM, Self::LEFT, ]; /// Align in the parent rect. pub fn parent(&self) -> Align2 { self.parent } /// Align in the child rect. pub fn child(&self) -> Align2 { self.child } /// Convert an [`Align2`] to an [`RectAlign`], positioning the child rect inside the parent. pub fn from_align2(align: Align2) -> Self { Self { parent: align, child: align, } } /// The center of the child rect will be aligned to a corner of the parent rect. pub fn over_corner(align: Align2) -> Self { Self { parent: align, child: Align2::CENTER_CENTER, } } /// Position the child rect outside the parent rect. pub fn outside(align: Align2) -> Self { Self { parent: align, child: align.flip(), } } /// Calculate the child rect based on a size and some optional gap. pub fn align_rect(&self, parent_rect: &Rect, size: Vec2, gap: f32) -> Rect { let (pivot, anchor) = self.pivot_pos(parent_rect, gap); pivot.anchor_size(anchor, size) } /// Returns a [`Align2`] and a [`Pos2`] that you can e.g. use with `Area::fixed_pos` /// and `Area::pivot` to align an `Area` to some rect. pub fn pivot_pos(&self, parent_rect: &Rect, gap: f32) -> (Align2, Pos2) { (self.child(), self.anchor(parent_rect, gap)) } /// Returns a sign vector (-1, 0 or 1 in each direction) that can be used as an offset to the /// child rect, creating a gap between the rects while keeping the edges aligned. pub fn gap_vector(&self) -> Vec2 { let mut gap = -self.child.to_sign(); // Align the edges in these cases match *self { Self::TOP_START | Self::TOP_END | Self::BOTTOM_START | Self::BOTTOM_END => { gap.x = 0.0; } Self::LEFT_START | Self::LEFT_END | Self::RIGHT_START | Self::RIGHT_END => { gap.y = 0.0; } _ => {} } gap } /// Calculator the anchor point for the child rect, based on the parent rect and an optional gap. pub fn anchor(&self, parent_rect: &Rect, gap: f32) -> Pos2 { let pos = self.parent.pos_in_rect(parent_rect); let offset = self.gap_vector() * gap; pos + offset } /// Flip the alignment on the x-axis. pub fn flip_x(self) -> Self { Self { parent: self.parent.flip_x(), child: self.child.flip_x(), } } /// Flip the alignment on the y-axis. pub fn flip_y(self) -> Self { Self { parent: self.parent.flip_y(), child: self.child.flip_y(), } } /// Flip the alignment on both axes. pub fn flip(self) -> Self { Self { parent: self.parent.flip(), child: self.child.flip(), } } /// Returns the 3 alternative [`RectAlign`]s that are flipped in various ways, for use /// with [`RectAlign::find_best_align`]. pub fn symmetries(self) -> [Self; 3] { [self.flip_x(), self.flip_y(), self.flip()] } /// Look for the first alternative [`RectAlign`] that allows the child rect to fit /// inside the `content_rect`. /// /// If no alternative fits, the first is returned. /// If no alternatives are given, `None` is returned. /// /// See also: /// - [`RectAlign::symmetries`] to calculate alternatives /// - [`RectAlign::MENU_ALIGNS`] for the 12 common menu positions pub fn find_best_align( values_to_try: impl Iterator, content_rect: Rect, parent_rect: Rect, gap: f32, expected_size: Vec2, ) -> Option { let mut first_choice = None; for align in values_to_try { first_choice = first_choice.or(Some(align)); // Remember the first alternative let suggested_popup_rect = align.align_rect(&parent_rect, expected_size, gap); if content_rect.contains_rect(suggested_popup_rect) { return Some(align); } } first_choice } } emath-0.33.0/src/rect_transform.rs000064400000000000000000000043461046102023000152010ustar 00000000000000use crate::{Pos2, Rect, Vec2, pos2, remap, remap_clamp}; /// 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) -> Self { 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 for RectTransform { type Output = Pos2; fn mul(self, pos: Pos2) -> Pos2 { self.transform_pos(pos) } } /// Transforms the position. impl std::ops::Mul for &RectTransform { type Output = Pos2; fn mul(self, pos: Pos2) -> Pos2 { self.transform_pos(pos) } } emath-0.33.0/src/rot2.rs000064400000000000000000000120121046102023000130240ustar 00000000000000use 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 #[inline] 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. #[inline] pub fn from_angle(angle: f32) -> Self { let (s, c) = angle.sin_cos(); Self { s, c } } #[inline] pub fn angle(self) -> f32 { self.s.atan2(self.c) } /// The factor by which vectors will be scaled. #[inline] pub fn length(self) -> f32 { self.c.hypot(self.s) } #[inline] pub fn length_squared(self) -> f32 { self.c.powi(2) + self.s.powi(2) } #[inline] pub fn is_finite(self) -> bool { self.c.is_finite() && self.s.is_finite() } #[must_use] #[inline] pub fn inverse(self) -> Self { Self { s: -self.s, c: self.c, } / self.length_squared() } #[must_use] #[inline] 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(), "Rot2::normalized produced a non-finite result" ); ret } } impl std::fmt::Debug for Rot2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if let Some(precision) = f.precision() { write!( f, "Rot2 {{ angle: {:.2$}Β°, length: {} }}", self.angle().to_degrees(), self.length(), precision ) } else { write!( f, "Rot2 {{ angle: {:.1}Β°, length: {} }}", self.angle().to_degrees(), self.length(), ) } } } impl std::ops::Mul for Rot2 { type Output = Self; #[inline] fn mul(self, r: Self) -> Self { /* |lc -ls| * |rc -rs| |ls lc| |rs rc| */ Self { 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 for Rot2 { type Output = Vec2; #[inline] 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 for f32 { type Output = Rot2; #[inline] fn mul(self, r: Rot2) -> Rot2 { Rot2 { c: self * r.c, s: self * r.s, } } } /// Scales the rotor. impl std::ops::Mul for Rot2 { type Output = Self; #[inline] fn mul(self, r: f32) -> Self { Self { c: self.c * r, s: self.s * r, } } } /// Scales the rotor. impl std::ops::Div for Rot2 { type Output = Self; #[inline] fn div(self, r: f32) -> Self { Self { 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 {rotated:?} to equal {expected:?}. rot: {rot:?}", ); let undone = rot.inverse() * rot; assert!(undone.angle().abs() < 1e-5); assert!((undone.length() - 1.0).abs() < 1e-5,); } } } emath-0.33.0/src/smart_aim.rs000064400000000000000000000130301046102023000141130ustar 00000000000000//! Find "simple" numbers is some range. Used by sliders. use crate::fast_midpoint; 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(), "min: {min:?}, max: {max:?}" ); 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 = fast_midpoint(min_exponent, max_exponent); 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); 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 &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 { debug_assert!( 1 <= min && min <= max && max <= 9, "min should be in [1, 9], but was {min:?} and max should be in [min, 9], but was {max:?}" ); if min <= 5 && 5 <= max { 5 } else { min.midpoint(max) } } #[expect(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); const NAN: f64 = f64::NAN; const INFINITY: f64 = f64::INFINITY; const NEG_INFINITY: f64 = f64::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); } emath-0.33.0/src/ts_transform.rs000064400000000000000000000101411046102023000146600ustar 00000000000000use crate::{Pos2, Rect, Vec2}; /// Linearly transforms positions via a translation, then a scaling. /// /// [`TSTransform`] first scales points with the scaling origin at `0, 0` /// (the top left corner), then translates them. #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] #[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))] pub struct TSTransform { /// Scaling applied first, scaled around (0, 0). pub scaling: f32, /// Translation amount, applied after scaling. pub translation: Vec2, } impl Eq for TSTransform {} impl Default for TSTransform { #[inline] fn default() -> Self { Self::IDENTITY } } impl TSTransform { pub const IDENTITY: Self = Self { translation: Vec2::ZERO, scaling: 1.0, }; #[inline] /// Creates a new translation that first scales points around /// `(0, 0)`, then translates them. pub fn new(translation: Vec2, scaling: f32) -> Self { Self { scaling, translation, } } #[inline] pub fn from_translation(translation: Vec2) -> Self { Self::new(translation, 1.0) } #[inline] pub fn from_scaling(scaling: f32) -> Self { Self::new(Vec2::ZERO, scaling) } /// Is this a valid, invertible transform? pub fn is_valid(&self) -> bool { self.scaling.is_finite() && self.translation.x.is_finite() && self.scaling != 0.0 } /// Inverts the transform. /// /// ``` /// # use emath::{pos2, vec2, TSTransform}; /// let p1 = pos2(2.0, 3.0); /// let p2 = pos2(12.0, 5.0); /// let ts = TSTransform::new(vec2(2.0, 3.0), 2.0); /// let inv = ts.inverse(); /// assert_eq!(inv.mul_pos(p1), pos2(0.0, 0.0)); /// assert_eq!(inv.mul_pos(p2), pos2(5.0, 1.0)); /// /// assert_eq!(ts.inverse().inverse(), ts); /// ``` #[inline] pub fn inverse(&self) -> Self { Self::new(-self.translation / self.scaling, 1.0 / self.scaling) } /// Transforms the given coordinate. /// /// ``` /// # use emath::{pos2, vec2, TSTransform}; /// let p1 = pos2(0.0, 0.0); /// let p2 = pos2(5.0, 1.0); /// let ts = TSTransform::new(vec2(2.0, 3.0), 2.0); /// assert_eq!(ts.mul_pos(p1), pos2(2.0, 3.0)); /// assert_eq!(ts.mul_pos(p2), pos2(12.0, 5.0)); /// ``` #[inline] pub fn mul_pos(&self, pos: Pos2) -> Pos2 { self.scaling * pos + self.translation } /// Transforms the given rectangle. /// /// ``` /// # use emath::{pos2, vec2, Rect, TSTransform}; /// let rect = Rect::from_min_max(pos2(5.0, 5.0), pos2(15.0, 10.0)); /// let ts = TSTransform::new(vec2(1.0, 0.0), 3.0); /// let transformed = ts.mul_rect(rect); /// assert_eq!(transformed.min, pos2(16.0, 15.0)); /// assert_eq!(transformed.max, pos2(46.0, 30.0)); /// ``` #[inline] pub fn mul_rect(&self, rect: Rect) -> Rect { Rect { min: self.mul_pos(rect.min), max: self.mul_pos(rect.max), } } } /// Transforms the position. impl std::ops::Mul for TSTransform { type Output = Pos2; #[inline] fn mul(self, pos: Pos2) -> Pos2 { self.mul_pos(pos) } } /// Transforms the rectangle. impl std::ops::Mul for TSTransform { type Output = Rect; #[inline] fn mul(self, rect: Rect) -> Rect { self.mul_rect(rect) } } impl std::ops::Mul for TSTransform { type Output = Self; #[inline] /// Applies the right hand side transform, then the left hand side. /// /// ``` /// # use emath::{TSTransform, vec2}; /// let ts1 = TSTransform::new(vec2(1.0, 0.0), 2.0); /// let ts2 = TSTransform::new(vec2(-1.0, -1.0), 3.0); /// let ts_combined = TSTransform::new(vec2(2.0, -1.0), 6.0); /// assert_eq!(ts_combined, ts2 * ts1); /// ``` fn mul(self, rhs: Self) -> Self::Output { // Apply rhs first. Self { scaling: self.scaling * rhs.scaling, translation: self.translation + self.scaling * rhs.translation, } } } emath-0.33.0/src/vec2.rs000064400000000000000000000317021046102023000130040ustar 00000000000000use std::fmt; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use crate::Vec2b; /// 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 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 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) } } impl From for Vec2 { #[inline(always)] fn from(v: Vec2b) -> Self { Self { x: v.x as i32 as f32, y: v.y as i32 as f32, } } } // ---------------------------------------------------------------------------- // Mint compatibility and convenience conversions #[cfg(feature = "mint")] impl From> for Vec2 { #[inline] fn from(v: mint::Vector2) -> Self { Self::new(v.x, v.y) } } #[cfg(feature = "mint")] impl From for mint::Vector2 { #[inline] fn from(v: Vec2) -> Self { Self { x: v.x, y: v.y } } } // ---------------------------------------------------------------------------- impl Vec2 { /// Right pub const X: Self = Self { x: 1.0, y: 0.0 }; /// Down pub const Y: Self = Self { x: 0.0, y: 1.0 }; /// +X pub const RIGHT: Self = Self { x: 1.0, y: 0.0 }; /// -X pub const LEFT: Self = Self { x: -1.0, y: 0.0 }; /// -Y pub const UP: Self = Self { x: 0.0, y: -1.0 }; /// +Y pub const DOWN: Self = Self { x: 0.0, y: 1.0 }; pub const ZERO: Self = Self { x: 0.0, y: 0.0 }; pub const ONE: Self = Self { x: 1.0, y: 1.0 }; pub const INFINITY: Self = Self::splat(f32::INFINITY); pub const NAN: Self = Self::splat(f32::NAN); #[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 } } /// Checks if `self` has length `1.0` up to a precision of `1e-6`. #[inline(always)] pub fn is_normalized(self) -> bool { (self.length_sq() - 1.0).abs() < 2e-6 } /// 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 { let (sin, cos) = angle.sin_cos(); vec2(cos, 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) } /// Swizzle the axes. #[inline] #[must_use] pub fn yx(self) -> Self { Self { x: self.y, y: self.x, } } #[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 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 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 = Self; #[inline(always)] fn neg(self) -> Self { vec2(-self.x, -self.y) } } impl AddAssign for Vec2 { #[inline(always)] fn add_assign(&mut self, rhs: Self) { *self = Self { x: self.x + rhs.x, y: self.y + rhs.y, }; } } impl SubAssign for Vec2 { #[inline(always)] fn sub_assign(&mut self, rhs: Self) { *self = Self { x: self.x - rhs.x, y: self.y - rhs.y, }; } } impl Add for Vec2 { type Output = Self; #[inline(always)] fn add(self, rhs: Self) -> Self { Self { x: self.x + rhs.x, y: self.y + rhs.y, } } } impl Sub for Vec2 { type Output = Self; #[inline(always)] fn sub(self, rhs: Self) -> Self { Self { x: self.x - rhs.x, y: self.y - rhs.y, } } } /// Element-wise multiplication impl Mul for Vec2 { type Output = Self; #[inline(always)] fn mul(self, vec: Self) -> Self { Self { x: self.x * vec.x, y: self.y * vec.y, } } } /// Element-wise division impl Div for Vec2 { type Output = Self; #[inline(always)] fn div(self, rhs: Self) -> Self { Self { x: self.x / rhs.x, y: self.y / rhs.y, } } } impl MulAssign for Vec2 { #[inline(always)] fn mul_assign(&mut self, rhs: f32) { self.x *= rhs; self.y *= rhs; } } impl DivAssign for Vec2 { #[inline(always)] fn div_assign(&mut self, rhs: f32) { self.x /= rhs; self.y /= rhs; } } impl Mul for Vec2 { type Output = Self; #[inline(always)] fn mul(self, factor: f32) -> Self { Self { x: self.x * factor, y: self.y * factor, } } } impl Mul for f32 { type Output = Vec2; #[inline(always)] fn mul(self, vec: Vec2) -> Vec2 { Vec2 { x: self * vec.x, y: self * vec.y, } } } impl Div for Vec2 { type Output = Self; #[inline(always)] fn div(self, factor: f32) -> Self { Self { x: self.x / factor, y: self.y / factor, } } } impl fmt::Debug for Vec2 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(precision) = f.precision() { write!(f, "[{1:.0$} {2:.0$}]", precision, self.x, self.y) } else { write!(f, "[{:.1} {:.1}]", self.x, self.y) } } } impl fmt::Display for Vec2 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("[")?; self.x.fmt(f)?; f.write_str(" ")?; self.y.fmt(f)?; f.write_str("]")?; Ok(()) } } #[cfg(test)] mod test { use super::*; macro_rules! almost_eq { ($left: expr, $right: expr) => { let left = $left; let right = $right; assert!((left - right).abs() < 1e-6, "{} != {}", left, right); }; } #[test] fn test_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); almost_eq!(Vec2::LEFT.angle(), 0.50 * TAU); assert_eq!(Vec2::UP.angle(), -0.25 * TAU); let mut assignment = vec2(1.0, 2.0); assignment += vec2(3.0, 4.0); assert_eq!(assignment, vec2(4.0, 6.0)); let mut assignment = vec2(4.0, 6.0); assignment -= vec2(1.0, 2.0); assert_eq!(assignment, vec2(3.0, 4.0)); let mut assignment = vec2(1.0, 2.0); assignment *= 2.0; assert_eq!(assignment, vec2(2.0, 4.0)); let mut assignment = vec2(2.0, 4.0); assignment /= 2.0; assert_eq!(assignment, vec2(1.0, 2.0)); } #[test] fn test_vec2_normalized() { fn generate_spiral(n: usize, start: Vec2, end: Vec2) -> impl Iterator { let angle_step = 2.0 * std::f32::consts::PI / n as f32; let radius_step = (end.length() - start.length()) / n as f32; (0..n).map(move |i| { let angle = i as f32 * angle_step; let radius = start.length() + i as f32 * radius_step; let x = radius * angle.cos(); let y = radius * angle.sin(); vec2(x, y) }) } for v in generate_spiral(40, Vec2::splat(0.1), Vec2::splat(2.0)) { let vn = v.normalized(); almost_eq!(vn.length(), 1.0); assert!(vn.is_normalized()); } } } emath-0.33.0/src/vec2b.rs000064400000000000000000000043161046102023000131470ustar 00000000000000use crate::Vec2; /// Two bools, one for each axis (X and Y). #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub struct Vec2b { pub x: bool, pub y: bool, } impl Vec2b { pub const FALSE: Self = Self { x: false, y: false }; pub const TRUE: Self = Self { x: true, y: true }; #[inline] pub fn new(x: bool, y: bool) -> Self { Self { x, y } } #[inline] pub fn any(&self) -> bool { self.x || self.y } /// Are both `x` and `y` true? #[inline] pub fn all(&self) -> bool { self.x && self.y } #[inline] pub fn and(&self, other: impl Into) -> Self { let other = other.into(); Self { x: self.x && other.x, y: self.y && other.y, } } #[inline] pub fn or(&self, other: impl Into) -> Self { let other = other.into(); Self { x: self.x || other.x, y: self.y || other.y, } } /// Convert to a float `Vec2` where the components are 1.0 for `true` and 0.0 for `false`. #[inline] pub fn to_vec2(self) -> Vec2 { Vec2::new(self.x.into(), self.y.into()) } } impl From for Vec2b { #[inline] fn from(val: bool) -> Self { Self { x: val, y: val } } } impl From<[bool; 2]> for Vec2b { #[inline] fn from([x, y]: [bool; 2]) -> Self { Self { x, y } } } impl std::ops::Index for Vec2b { type Output = bool; #[inline(always)] fn index(&self, index: usize) -> &bool { match index { 0 => &self.x, 1 => &self.y, _ => panic!("Vec2b index out of bounds: {index}"), } } } impl std::ops::IndexMut for Vec2b { #[inline(always)] fn index_mut(&mut self, index: usize) -> &mut bool { match index { 0 => &mut self.x, 1 => &mut self.y, _ => panic!("Vec2b index out of bounds: {index}"), } } } impl std::ops::Not for Vec2b { type Output = Self; #[inline] fn not(self) -> Self::Output { Self { x: !self.x, y: !self.y, } } }