mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Add Vec2 support to the scalar math operator nodes (#4360)
* Add Vec2 support to the scalar math operator nodes * Support the scalar-with-Vec2 operand order in the Exponent, Root, Logarithm, Min, Max, and Clamp nodes * Detect an f32 base of e in the Logarithm node despite inexact widening to f64
This commit is contained in:
committed by
Dennis Kobert
parent
8bb8b7786b
commit
af1da0ebbb
@@ -9,7 +9,6 @@ use log::warn;
|
||||
use math_parser::ast;
|
||||
use math_parser::context::{EvalContext, NothingMap, ValueProvider};
|
||||
use math_parser::value::{Number, Value};
|
||||
use num_traits::Pow;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::ops::{Add, Mul, Rem, Sub};
|
||||
use vector_types::Gradient;
|
||||
@@ -180,17 +179,36 @@ fn divide<A: SafeDivide<B>, B>(
|
||||
numerator.safe_divide(denominator)
|
||||
}
|
||||
|
||||
trait Componentwise {
|
||||
fn componentwise(self, f: impl Fn(f64) -> f64) -> Self;
|
||||
}
|
||||
impl Componentwise for f64 {
|
||||
fn componentwise(self, f: impl Fn(f64) -> f64) -> Self {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
impl Componentwise for f32 {
|
||||
fn componentwise(self, f: impl Fn(f64) -> f64) -> Self {
|
||||
f(self as f64) as f32
|
||||
}
|
||||
}
|
||||
impl Componentwise for DVec2 {
|
||||
fn componentwise(self, f: impl Fn(f64) -> f64) -> Self {
|
||||
DVec2::new(f(self.x), f(self.y))
|
||||
}
|
||||
}
|
||||
|
||||
/// The reciprocal operation (`1/x`) calculates the multiplicative inverse of a number.
|
||||
///
|
||||
/// Produces 0 if the input is 0.
|
||||
/// Produces 0 if the input is 0. With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn reciprocal<T: num_traits::float::Float>(
|
||||
fn reciprocal<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the reciprocal is calculated.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: T,
|
||||
) -> T {
|
||||
if value == T::from(0.).unwrap() { T::from(0.).unwrap() } else { T::from(1.).unwrap() / value }
|
||||
value.componentwise(|value| if value == 0. { 0. } else { 1. / value })
|
||||
}
|
||||
|
||||
/// The modulo operation (`%`) calculates the remainder from the division of two scalar numbers or vectors.
|
||||
@@ -213,108 +231,243 @@ fn modulo<A: Rem<B, Output: Add<B, Output: Rem<B, Output = A::Output>>>, B: Copy
|
||||
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
|
||||
}
|
||||
|
||||
pub trait Exponent<Rhs = Self> {
|
||||
type Output;
|
||||
fn power(self, power: Rhs) -> Self::Output;
|
||||
}
|
||||
impl Exponent for f64 {
|
||||
type Output = f64;
|
||||
fn power(self, power: f64) -> f64 {
|
||||
self.powf(power)
|
||||
}
|
||||
}
|
||||
impl Exponent for f32 {
|
||||
type Output = f32;
|
||||
fn power(self, power: f32) -> f32 {
|
||||
self.powf(power)
|
||||
}
|
||||
}
|
||||
impl Exponent for u32 {
|
||||
type Output = u32;
|
||||
fn power(self, power: u32) -> u32 {
|
||||
self.pow(power)
|
||||
}
|
||||
}
|
||||
impl Exponent for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn power(self, power: DVec2) -> DVec2 {
|
||||
DVec2::new(self.x.powf(power.x), self.y.powf(power.y))
|
||||
}
|
||||
}
|
||||
impl Exponent<f64> for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn power(self, power: f64) -> DVec2 {
|
||||
DVec2::new(self.x.powf(power), self.y.powf(power))
|
||||
}
|
||||
}
|
||||
impl Exponent<DVec2> for f64 {
|
||||
type Output = DVec2;
|
||||
fn power(self, power: DVec2) -> DVec2 {
|
||||
DVec2::new(self.powf(power.x), self.powf(power.y))
|
||||
}
|
||||
}
|
||||
|
||||
/// The exponent operation (`^`) calculates the result of raising a number to a power.
|
||||
///
|
||||
/// With vec2 inputs, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<T: Pow<T>>(
|
||||
fn exponent<A: Exponent<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// The base number that is raised to the power.
|
||||
#[implementations(f64, f32, u32)]
|
||||
base: T,
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
base: A,
|
||||
/// The power to which the base number is raised.
|
||||
#[implementations(f64, f32, u32)]
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
#[default(2.)]
|
||||
power: T,
|
||||
) -> <T as num_traits::Pow<T>>::Output {
|
||||
base.pow(power)
|
||||
power: B,
|
||||
) -> <A as Exponent<B>>::Output {
|
||||
base.power(power)
|
||||
}
|
||||
|
||||
fn scalar_nth_root(radicand: f64, degree: f64) -> f64 {
|
||||
if degree == 2. {
|
||||
radicand.sqrt()
|
||||
} else if degree == 3. {
|
||||
radicand.cbrt()
|
||||
} else if degree <= 0. {
|
||||
0.
|
||||
} else {
|
||||
radicand.powf(1. / degree)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NthRoot<Degree = Self> {
|
||||
type Output;
|
||||
fn nth_root(self, degree: Degree) -> Self::Output;
|
||||
}
|
||||
impl NthRoot for f64 {
|
||||
type Output = f64;
|
||||
fn nth_root(self, degree: f64) -> f64 {
|
||||
scalar_nth_root(self, degree)
|
||||
}
|
||||
}
|
||||
impl NthRoot for f32 {
|
||||
type Output = f32;
|
||||
fn nth_root(self, degree: f32) -> f32 {
|
||||
scalar_nth_root(self as f64, degree as f64) as f32
|
||||
}
|
||||
}
|
||||
impl NthRoot for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn nth_root(self, degree: DVec2) -> DVec2 {
|
||||
DVec2::new(scalar_nth_root(self.x, degree.x), scalar_nth_root(self.y, degree.y))
|
||||
}
|
||||
}
|
||||
impl NthRoot<f64> for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn nth_root(self, degree: f64) -> DVec2 {
|
||||
DVec2::new(scalar_nth_root(self.x, degree), scalar_nth_root(self.y, degree))
|
||||
}
|
||||
}
|
||||
impl NthRoot<DVec2> for f64 {
|
||||
type Output = DVec2;
|
||||
fn nth_root(self, degree: DVec2) -> DVec2 {
|
||||
DVec2::new(scalar_nth_root(self, degree.x), scalar_nth_root(self, degree.y))
|
||||
}
|
||||
}
|
||||
|
||||
/// The `n`th root operation (`√`) calculates the inverse of exponentiation. Square root inverts squaring, cube root inverts cubing, and so on.
|
||||
///
|
||||
/// This is equivalent to raising the number to the power of `1/n`.
|
||||
/// This is equivalent to raising the number to the power of `1/n`. With vec2 inputs, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<T: num_traits::float::Float>(
|
||||
fn root<A: NthRoot<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// The number inside the radical for which the `n`th root is calculated.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
radicand: T,
|
||||
#[implementations(f64, f32, DVec2, DVec2, f64)]
|
||||
radicand: A,
|
||||
/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.
|
||||
/// Degrees 0 or less are invalid and will produce an output of 0.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
degree: T,
|
||||
) -> T {
|
||||
if degree == T::from(2.).unwrap() {
|
||||
radicand.sqrt()
|
||||
} else if degree == T::from(3.).unwrap() {
|
||||
radicand.cbrt()
|
||||
} else if degree <= T::from(0.).unwrap() {
|
||||
T::from(0.).unwrap()
|
||||
} else {
|
||||
radicand.powf(T::from(1.).unwrap() / degree)
|
||||
}
|
||||
#[implementations(f64, f32, f64, DVec2, DVec2)]
|
||||
degree: B,
|
||||
) -> <A as NthRoot<B>>::Output {
|
||||
radicand.nth_root(degree)
|
||||
}
|
||||
|
||||
/// The logarithmic function (`log`) calculates the logarithm of a number with a specified base. If the natural logarithm function (`ln`) is desired, set the base to "e".
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<T: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the logarithm is calculated.
|
||||
#[implementations(f64, f32)]
|
||||
value: T,
|
||||
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
base: T,
|
||||
) -> T {
|
||||
if base == T::from(2.).unwrap() {
|
||||
fn scalar_logarithm(value: f64, base: f64) -> f64 {
|
||||
if base == 2. {
|
||||
value.log2()
|
||||
} else if base == T::from(10.).unwrap() {
|
||||
} else if base == 10. {
|
||||
value.log10()
|
||||
} else if (base - T::from(std::f64::consts::E).unwrap()).abs() < T::epsilon() * T::from(1e6).unwrap() {
|
||||
} else if (base - std::f64::consts::E).abs() < f64::EPSILON * 1e6 {
|
||||
value.ln()
|
||||
} else {
|
||||
value.log(base)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Logarithm<Base = Self> {
|
||||
type Output;
|
||||
fn logarithm(self, base: Base) -> Self::Output;
|
||||
}
|
||||
impl Logarithm for f64 {
|
||||
type Output = f64;
|
||||
fn logarithm(self, base: f64) -> f64 {
|
||||
scalar_logarithm(self, base)
|
||||
}
|
||||
}
|
||||
impl Logarithm for f32 {
|
||||
type Output = f32;
|
||||
fn logarithm(self, base: f32) -> f32 {
|
||||
// The f32 representation of e widens inexactly, so match it against e at f32 precision and substitute the exact f64 e
|
||||
let base = if (base - std::f32::consts::E).abs() < f32::EPSILON * 10. {
|
||||
std::f64::consts::E
|
||||
} else {
|
||||
base as f64
|
||||
};
|
||||
scalar_logarithm(self as f64, base) as f32
|
||||
}
|
||||
}
|
||||
impl Logarithm for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn logarithm(self, base: DVec2) -> DVec2 {
|
||||
DVec2::new(scalar_logarithm(self.x, base.x), scalar_logarithm(self.y, base.y))
|
||||
}
|
||||
}
|
||||
impl Logarithm<f64> for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn logarithm(self, base: f64) -> DVec2 {
|
||||
DVec2::new(scalar_logarithm(self.x, base), scalar_logarithm(self.y, base))
|
||||
}
|
||||
}
|
||||
impl Logarithm<DVec2> for f64 {
|
||||
type Output = DVec2;
|
||||
fn logarithm(self, base: DVec2) -> DVec2 {
|
||||
DVec2::new(scalar_logarithm(self, base.x), scalar_logarithm(self, base.y))
|
||||
}
|
||||
}
|
||||
|
||||
/// The logarithmic function (`log`) calculates the logarithm of a number with a specified base. If the natural logarithm function (`ln`) is desired, set the base to "e".
|
||||
///
|
||||
/// With vec2 inputs, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<A: Logarithm<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the logarithm is calculated.
|
||||
#[implementations(f64, f32, DVec2, DVec2, f64)]
|
||||
value: A,
|
||||
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32, f64, DVec2, DVec2)]
|
||||
base: B,
|
||||
) -> <A as Logarithm<B>>::Output {
|
||||
value.logarithm(base)
|
||||
}
|
||||
|
||||
/// The sine trigonometric function (`sin`) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine<T: num_traits::float::Float>(
|
||||
fn sine<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
theta: T,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> T {
|
||||
if radians { theta.sin() } else { theta.to_radians().sin() }
|
||||
theta.componentwise(|theta| if radians { theta.sin() } else { theta.to_radians().sin() })
|
||||
}
|
||||
|
||||
/// The cosine trigonometric function (`cos`) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine<T: num_traits::float::Float>(
|
||||
fn cosine<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
theta: T,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> T {
|
||||
if radians { theta.cos() } else { theta.to_radians().cos() }
|
||||
theta.componentwise(|theta| if radians { theta.cos() } else { theta.to_radians().cos() })
|
||||
}
|
||||
|
||||
/// The tangent trigonometric function (`tan`) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent<T: num_traits::float::Float>(
|
||||
fn tangent<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
theta: T,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> T {
|
||||
if radians { theta.tan() } else { theta.to_radians().tan() }
|
||||
theta.componentwise(|theta| if radians { theta.tan() } else { theta.to_radians().tan() })
|
||||
}
|
||||
|
||||
/// The inverse sine trigonometric function (`asin`) calculates the angle whose sine is the input value.
|
||||
@@ -476,36 +629,42 @@ fn as_f64(_: impl Ctx, value: f64) -> f64 {
|
||||
}
|
||||
|
||||
/// The rounding function (`round`) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn round<T: num_traits::float::Float>(
|
||||
fn round<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The number to be rounded to the nearest whole number.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.round()
|
||||
value.componentwise(f64::round)
|
||||
}
|
||||
|
||||
/// The floor function (`floor`) rounds down an input value to the nearest whole number, unless the input number is already whole.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn floor<T: num_traits::float::Float>(
|
||||
fn floor<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The number to be rounded down.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.floor()
|
||||
value.componentwise(f64::floor)
|
||||
}
|
||||
|
||||
/// The ceiling function (`ceil`) rounds up an input value to the nearest whole number, unless the input number is already whole.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn ceiling<T: num_traits::float::Float>(
|
||||
fn ceiling<T: Componentwise>(
|
||||
_: impl Ctx,
|
||||
/// The number to be rounded up.
|
||||
#[implementations(f64, f32)]
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: T,
|
||||
) -> T {
|
||||
value.ceil()
|
||||
value.componentwise(f64::ceil)
|
||||
}
|
||||
|
||||
trait AbsoluteValue {
|
||||
@@ -538,6 +697,8 @@ impl AbsoluteValue for i64 {
|
||||
}
|
||||
|
||||
/// The absolute value function (`abs`) removes the negative sign from an input value, if present.
|
||||
///
|
||||
/// With a vec2 input, this applies separately to the X and Y components. For the overall length of a vec2, see the "Magnitude" node instead.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn absolute_value<T: AbsoluteValue>(
|
||||
_: impl Ctx,
|
||||
@@ -548,57 +709,129 @@ fn absolute_value<T: AbsoluteValue>(
|
||||
value.abs()
|
||||
}
|
||||
|
||||
pub trait MinMax<Rhs = Self> {
|
||||
type Output;
|
||||
fn minimum(self, other: Rhs) -> Self::Output;
|
||||
fn maximum(self, other: Rhs) -> Self::Output;
|
||||
}
|
||||
impl MinMax for f64 {
|
||||
type Output = f64;
|
||||
fn minimum(self, other: f64) -> f64 {
|
||||
if self < other { self } else { other }
|
||||
}
|
||||
fn maximum(self, other: f64) -> f64 {
|
||||
if self > other { self } else { other }
|
||||
}
|
||||
}
|
||||
impl MinMax for f32 {
|
||||
type Output = f32;
|
||||
fn minimum(self, other: f32) -> f32 {
|
||||
if self < other { self } else { other }
|
||||
}
|
||||
fn maximum(self, other: f32) -> f32 {
|
||||
if self > other { self } else { other }
|
||||
}
|
||||
}
|
||||
impl MinMax for u32 {
|
||||
type Output = u32;
|
||||
fn minimum(self, other: u32) -> u32 {
|
||||
if self < other { self } else { other }
|
||||
}
|
||||
fn maximum(self, other: u32) -> u32 {
|
||||
if self > other { self } else { other }
|
||||
}
|
||||
}
|
||||
impl MinMax for String {
|
||||
type Output = String;
|
||||
fn minimum(self, other: Self) -> String {
|
||||
if self < other { self } else { other }
|
||||
}
|
||||
fn maximum(self, other: Self) -> String {
|
||||
if self > other { self } else { other }
|
||||
}
|
||||
}
|
||||
impl MinMax for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn minimum(self, other: DVec2) -> DVec2 {
|
||||
self.min(other)
|
||||
}
|
||||
fn maximum(self, other: DVec2) -> DVec2 {
|
||||
self.max(other)
|
||||
}
|
||||
}
|
||||
impl MinMax<f64> for DVec2 {
|
||||
type Output = DVec2;
|
||||
fn minimum(self, other: f64) -> DVec2 {
|
||||
self.min(DVec2::splat(other))
|
||||
}
|
||||
fn maximum(self, other: f64) -> DVec2 {
|
||||
self.max(DVec2::splat(other))
|
||||
}
|
||||
}
|
||||
impl MinMax<DVec2> for f64 {
|
||||
type Output = DVec2;
|
||||
fn minimum(self, other: DVec2) -> DVec2 {
|
||||
DVec2::splat(self).min(other)
|
||||
}
|
||||
fn maximum(self, other: DVec2) -> DVec2 {
|
||||
DVec2::splat(self).max(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// The minimum function (`min`) picks the smaller of two numbers.
|
||||
///
|
||||
/// With vec2 inputs, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn min<T: std::cmp::PartialOrd>(
|
||||
fn min<A: MinMax<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the lesser is returned.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
value: T,
|
||||
#[implementations(f64, f32, u32, String, DVec2, DVec2, f64)]
|
||||
value: A,
|
||||
/// The other of the two numbers, of which the lesser is returned.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value < other_value { value } else { other_value }
|
||||
#[implementations(f64, f32, u32, String, DVec2, f64, DVec2)]
|
||||
other_value: B,
|
||||
) -> <A as MinMax<B>>::Output {
|
||||
value.minimum(other_value)
|
||||
}
|
||||
|
||||
/// The maximum function (`max`) picks the larger of two numbers.
|
||||
///
|
||||
/// With vec2 inputs, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn max<T: std::cmp::PartialOrd>(
|
||||
fn max<A: MinMax<B>, B>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the greater is returned.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
value: T,
|
||||
#[implementations(f64, f32, u32, String, DVec2, DVec2, f64)]
|
||||
value: A,
|
||||
/// The other of the two numbers, of which the greater is returned.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value > other_value { value } else { other_value }
|
||||
#[implementations(f64, f32, u32, String, DVec2, f64, DVec2)]
|
||||
other_value: B,
|
||||
) -> <A as MinMax<B>>::Output {
|
||||
value.maximum(other_value)
|
||||
}
|
||||
|
||||
/// The clamp function (`clamp`) restricts a number to a specified range between a minimum and maximum value. The minimum and maximum values are automatically swapped if they are reversed.
|
||||
///
|
||||
/// With vec2 inputs, this applies separately to the X and Y components.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn clamp<T: std::cmp::PartialOrd>(
|
||||
fn clamp<A: MinMax<B>, B: MinMax<Output = B> + Clone>(
|
||||
_: impl Ctx,
|
||||
/// The number to be clamped, which is restricted to the range between the minimum and maximum values.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
value: T,
|
||||
#[implementations(f64, f32, u32, String, DVec2, DVec2, f64)]
|
||||
value: A,
|
||||
/// The left (smaller) side of the range. The output is never less than this number.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
min: T,
|
||||
#[implementations(f64, f32, u32, String, DVec2, f64, DVec2)]
|
||||
min: B,
|
||||
/// The right (greater) side of the range. The output is never greater than this number.
|
||||
#[implementations(f64, f32, u32, String)]
|
||||
#[implementations(f64, f32, u32, String, DVec2, f64, DVec2)]
|
||||
#[default(1)]
|
||||
max: T,
|
||||
) -> T {
|
||||
let (min, max) = if min < max { (min, max) } else { (max, min) };
|
||||
if value < min {
|
||||
min
|
||||
} else if value > max {
|
||||
max
|
||||
} else {
|
||||
value
|
||||
}
|
||||
max: B,
|
||||
) -> <A as MinMax<B>>::Output
|
||||
where
|
||||
<A as MinMax<B>>::Output: MinMax<B, Output = <A as MinMax<B>>::Output>,
|
||||
{
|
||||
let (min, max) = (min.clone().minimum(max.clone()), min.maximum(max));
|
||||
value.maximum(min).minimum(max)
|
||||
}
|
||||
|
||||
/// The greatest common divisor (GCD) calculates the largest positive integer that divides both of the two input numbers without leaving a remainder.
|
||||
@@ -1048,6 +1281,51 @@ mod test {
|
||||
assert_eq!(magnitude(&(), vector), 5.);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn clamp_vec2_within_swapped_bounds() {
|
||||
let vec2 = |x, y| DVec2::new(x, y);
|
||||
assert_eq!(clamp(&(), vec2(-5., 5.), vec2(1., 1.), vec2(0., 2.)), DVec2::new(0., 2.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn min_max_vec2_with_scalar() {
|
||||
let vec2 = |x, y| DVec2::new(x, y);
|
||||
assert_eq!(super::min(&(), vec2(-5., 5.), 0_f64), DVec2::new(-5., 0.));
|
||||
assert_eq!(super::max(&(), vec2(-5., 5.), 0_f64), DVec2::new(0., 5.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn scalar_with_vec2_operand_orders() {
|
||||
let vec2 = |x, y| DVec2::new(x, y);
|
||||
assert_eq!(super::min(&(), 0_f64, vec2(-5., 5.)), DVec2::new(-5., 0.));
|
||||
assert_eq!(super::max(&(), 0_f64, vec2(-5., 5.)), DVec2::new(0., 5.));
|
||||
assert_eq!(exponent(&(), 2_f64, vec2(2., 3.)), DVec2::new(4., 8.));
|
||||
assert_eq!(root(&(), 64_f64, vec2(2., 3.)), DVec2::new(8., 4.));
|
||||
assert_eq!(logarithm(&(), 8_f64, vec2(2., 10.)), DVec2::new(3., 8_f64.log10()));
|
||||
assert_eq!(clamp(&(), 5_f64, vec2(0., 6.), vec2(1., 10.)), DVec2::new(1., 6.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn vec2_degrees_and_bases() {
|
||||
let vec2 = |x, y| DVec2::new(x, y);
|
||||
assert_eq!(root(&(), vec2(64., 27.), vec2(2., 3.)), DVec2::new(8., 3.));
|
||||
assert_eq!(logarithm(&(), vec2(8., 100.), vec2(2., 10.)), DVec2::new(3., 2.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn logarithm_f32_base_e_and_near_e() {
|
||||
assert_eq!(logarithm(&(), 8_f32, std::f32::consts::E), 8_f64.ln() as f32);
|
||||
assert_eq!(logarithm(&(), 8_f32, 2.7_f32), 8_f64.log(2.7_f32 as f64) as f32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn round_floor_ceiling_vec2() {
|
||||
let vec2 = |x, y| DVec2::new(x, y);
|
||||
assert_eq!(round(&(), vec2(1.5, -1.4)), DVec2::new(2., -1.));
|
||||
assert_eq!(floor(&(), vec2(1.9, -1.1)), DVec2::new(1., -2.));
|
||||
assert_eq!(ceiling(&(), vec2(1.1, -1.9)), DVec2::new(2., -1.));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_expression() {
|
||||
let result = math(&(), 0., "2 + 2".to_string(), 0.);
|
||||
|
||||
Reference in New Issue
Block a user