import more fidget
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
use glam::Vec4;
|
||||
|
||||
/// A point in space with associated partial derivatives.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Grad {
|
||||
/// Value of the distance field at this point
|
||||
pub v: f32,
|
||||
/// Partial derivative with respect to `x`
|
||||
pub dx: f32,
|
||||
/// Partial derivative with respect to `y`
|
||||
pub dy: f32,
|
||||
/// Partial derivative with respect to `z`
|
||||
pub dz: f32,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Grad {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({}, {}, {}, {})", self.v, self.dx, self.dy, self.dz)
|
||||
}
|
||||
}
|
||||
|
||||
impl Grad {
|
||||
/// Constructs a new gradient
|
||||
#[inline]
|
||||
pub fn new(v: f32, dx: f32, dy: f32, dz: f32) -> Self {
|
||||
Self { v, dx, dy, dz }
|
||||
}
|
||||
|
||||
/// Looks up a gradient by index (0 = x, 1 = y, 2 = z)
|
||||
///
|
||||
/// # Panics
|
||||
/// If the index is not in the 0-2 range
|
||||
#[inline]
|
||||
pub fn d(&self, i: usize) -> f32 {
|
||||
match i {
|
||||
0 => self.dx,
|
||||
1 => self.dy,
|
||||
2 => self.dz,
|
||||
_ => panic!("invalid index {i}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Absolute value
|
||||
#[inline]
|
||||
pub fn abs(self) -> Self {
|
||||
if self.v < 0.0 {
|
||||
Grad {
|
||||
v: -self.v,
|
||||
dx: -self.dx,
|
||||
dy: -self.dy,
|
||||
dz: -self.dz,
|
||||
}
|
||||
} else {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Square root
|
||||
#[inline]
|
||||
pub fn sqrt(self) -> Self {
|
||||
let v = self.v.sqrt();
|
||||
Grad {
|
||||
v,
|
||||
dx: self.dx / (2.0 * v),
|
||||
dy: self.dy / (2.0 * v),
|
||||
dz: self.dz / (2.0 * v),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sine
|
||||
#[inline]
|
||||
pub fn sin(self) -> Self {
|
||||
let c = self.v.cos();
|
||||
Grad {
|
||||
v: self.v.sin(),
|
||||
dx: self.dx * c,
|
||||
dy: self.dy * c,
|
||||
dz: self.dz * c,
|
||||
}
|
||||
}
|
||||
|
||||
/// Cosine
|
||||
#[inline]
|
||||
pub fn cos(self) -> Self {
|
||||
let s = -self.v.sin();
|
||||
Grad {
|
||||
v: self.v.cos(),
|
||||
dx: self.dx * s,
|
||||
dy: self.dy * s,
|
||||
dz: self.dz * s,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tangent
|
||||
#[inline]
|
||||
pub fn tan(self) -> Self {
|
||||
let c = self.v.cos().powi(2);
|
||||
Grad {
|
||||
v: self.v.tan(),
|
||||
dx: self.dx / c,
|
||||
dy: self.dy / c,
|
||||
dz: self.dz / c,
|
||||
}
|
||||
}
|
||||
|
||||
/// Arcsin
|
||||
#[inline]
|
||||
pub fn asin(self) -> Self {
|
||||
let r = (1.0 - self.v.powi(2)).sqrt();
|
||||
Grad {
|
||||
v: self.v.asin(),
|
||||
dx: self.dx / r,
|
||||
dy: self.dy / r,
|
||||
dz: self.dz / r,
|
||||
}
|
||||
}
|
||||
|
||||
/// Arccos
|
||||
#[inline]
|
||||
pub fn acos(self) -> Self {
|
||||
let r = (1.0 - self.v.powi(2)).sqrt();
|
||||
Grad {
|
||||
v: self.v.acos(),
|
||||
dx: -self.dx / r,
|
||||
dy: -self.dy / r,
|
||||
dz: -self.dz / r,
|
||||
}
|
||||
}
|
||||
|
||||
/// Arctangent
|
||||
#[inline]
|
||||
pub fn atan(self) -> Self {
|
||||
let r = self.v.powi(2) + 1.0;
|
||||
Grad {
|
||||
v: self.v.atan(),
|
||||
dx: self.dx / r,
|
||||
dy: self.dy / r,
|
||||
dz: self.dz / r,
|
||||
}
|
||||
}
|
||||
|
||||
/// Exponential function
|
||||
#[inline]
|
||||
pub fn exp(self) -> Self {
|
||||
let v = self.v.exp();
|
||||
Grad {
|
||||
v,
|
||||
dx: v * self.dx,
|
||||
dy: v * self.dy,
|
||||
dz: v * self.dz,
|
||||
}
|
||||
}
|
||||
|
||||
/// Natural log
|
||||
#[inline]
|
||||
pub fn ln(self) -> Self {
|
||||
Grad {
|
||||
v: self.v.ln(),
|
||||
dx: self.dx / self.v,
|
||||
dy: self.dy / self.v,
|
||||
dz: self.dz / self.v,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reciprocal
|
||||
#[inline]
|
||||
pub fn recip(self) -> Self {
|
||||
let v2 = -self.v.powi(2);
|
||||
Grad {
|
||||
v: 1.0 / self.v,
|
||||
dx: self.dx / v2,
|
||||
dy: self.dy / v2,
|
||||
dz: self.dz / v2,
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimum of two values
|
||||
#[inline]
|
||||
pub fn min(self, rhs: Self) -> Self {
|
||||
if self.v < rhs.v { self } else { rhs }
|
||||
}
|
||||
|
||||
/// Maximum of two values
|
||||
#[inline]
|
||||
pub fn max(self, rhs: Self) -> Self {
|
||||
if self.v > rhs.v { self } else { rhs }
|
||||
}
|
||||
|
||||
/// Least non-negative remainder
|
||||
#[inline]
|
||||
pub fn rem_euclid(&self, rhs: Grad) -> Self {
|
||||
let e = self.v.div_euclid(rhs.v);
|
||||
Grad {
|
||||
v: self.v.rem_euclid(rhs.v),
|
||||
dx: self.dx - rhs.dx * e,
|
||||
dy: self.dy - rhs.dy * e,
|
||||
dz: self.dz - rhs.dz * e,
|
||||
}
|
||||
}
|
||||
|
||||
/// Snap to the largest less-than-or-equal value
|
||||
#[inline]
|
||||
pub fn floor(&self) -> Self {
|
||||
Grad {
|
||||
v: self.v.floor(),
|
||||
dx: 0.0,
|
||||
dy: 0.0,
|
||||
dz: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Snap to the smallest greater-than-or-equal value
|
||||
#[inline]
|
||||
pub fn ceil(&self) -> Self {
|
||||
Grad {
|
||||
v: self.v.ceil(),
|
||||
dx: 0.0,
|
||||
dy: 0.0,
|
||||
dz: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Rounds to the nearest integer
|
||||
#[inline]
|
||||
pub fn round(&self) -> Self {
|
||||
Grad {
|
||||
v: self.v.round(),
|
||||
dx: 0.0,
|
||||
dy: 0.0,
|
||||
dz: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Four-quadrant arctangent
|
||||
#[inline]
|
||||
pub fn atan2(self, x: Self) -> Self {
|
||||
let y = self;
|
||||
|
||||
let d = x.v.powi(2) + y.v.powi(2);
|
||||
Grad {
|
||||
v: y.v.atan2(x.v),
|
||||
dx: (x.v * y.dx - y.v * x.dx) / d,
|
||||
dy: (x.v * y.dy - y.v * x.dy) / d,
|
||||
dz: (x.v * y.dz - y.v * x.dz) / d,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the two values are roughly equal, panicking otherwise
|
||||
#[cfg(test)]
|
||||
pub(crate) fn compare_eq(&self, other: Self) {
|
||||
let d = (self.v - other.v)
|
||||
.abs()
|
||||
.max((self.dx - other.dx).abs())
|
||||
.max((self.dy - other.dy).abs())
|
||||
.max((self.dz - other.dz).abs());
|
||||
if d >= 1e-6 {
|
||||
panic!("lhs != rhs ({self:?} != {other:?})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Grad {
|
||||
#[inline]
|
||||
fn from(v: f32) -> Self {
|
||||
Grad {
|
||||
v,
|
||||
dx: 0.0,
|
||||
dy: 0.0,
|
||||
dz: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Grad> for Vec4 {
|
||||
#[inline]
|
||||
fn from(g: Grad) -> Self {
|
||||
Vec4::new(g.dx, g.dy, g.dz, g.v)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Grad> for Grad {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Grad {
|
||||
v: self.v + rhs.v,
|
||||
dx: self.dx + rhs.dx,
|
||||
dy: self.dy + rhs.dy,
|
||||
dz: self.dz + rhs.dz,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Grad> for Grad {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
v: self.v * rhs.v,
|
||||
dx: self.v * rhs.dx + rhs.v * self.dx,
|
||||
dy: self.v * rhs.dy + rhs.v * self.dy,
|
||||
dz: self.v * rhs.dz + rhs.v * self.dz,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<f32> for Grad {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
Self {
|
||||
v: self.v * rhs,
|
||||
dx: self.dx * rhs,
|
||||
dy: self.dy * rhs,
|
||||
dz: self.dz * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<Grad> for Grad {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn div(self, rhs: Self) -> Self {
|
||||
let d = rhs.v.powi(2);
|
||||
Self {
|
||||
v: self.v / rhs.v,
|
||||
dx: (rhs.v * self.dx - self.v * rhs.dx) / d,
|
||||
dy: (rhs.v * self.dy - self.v * rhs.dy) / d,
|
||||
dz: (rhs.v * self.dz - self.v * rhs.dz) / d,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Grad> for Grad {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
v: self.v - rhs.v,
|
||||
dx: self.dx - rhs.dx,
|
||||
dy: self.dy - rhs.dy,
|
||||
dz: self.dz - rhs.dz,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Grad {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self {
|
||||
Self {
|
||||
v: -self.v,
|
||||
dx: -self.dx,
|
||||
dy: -self.dy,
|
||||
dz: -self.dz,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
use std::simd::{
|
||||
StdFloat,
|
||||
cmp::{SimdPartialEq, SimdPartialOrd},
|
||||
num::SimdFloat,
|
||||
u32x8,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
interpreter::{
|
||||
Mask, VALUE_0, VALUE_1, VALUE_2, VALUE_05, VALUE_M1, VALUE_NAN, VALUE_PI, Value, glfract,
|
||||
glsign,
|
||||
},
|
||||
vm::choice::{Choice, VChoice},
|
||||
};
|
||||
|
||||
/// Stores a range, with conservative calculations to guarantee that it always
|
||||
/// contains the actual value.
|
||||
///
|
||||
/// # Warning
|
||||
/// This implementation does not set rounding modes, so it may not be _perfect_.
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
#[repr(C)]
|
||||
pub struct Interval {
|
||||
lower: Value,
|
||||
upper: Value,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Interval {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.debug_tuple("")
|
||||
.field(&self.lower)
|
||||
.field(&self.upper)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
pub const HALF: Self = Self::const_splat(0.5);
|
||||
pub const MONE: Self = Self::const_splat(-1.0);
|
||||
pub const NAN: Self = Self::const_splat(core::f32::NAN);
|
||||
pub const ONE: Self = Self::const_splat(1.0);
|
||||
pub const PI: Self = Self::const_splat(core::f32::consts::PI);
|
||||
pub const ZERO: Self = Self::const_splat(0.0);
|
||||
|
||||
/// Builds a new interval
|
||||
///
|
||||
/// There are two kinds of valid interval:
|
||||
/// - `[lower, upper]` where `lower <= upper`
|
||||
/// - `[NaN, NaN]`
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if the resulting interval would be invalid
|
||||
#[inline]
|
||||
pub fn new(lower: Value, upper: Value) -> Self {
|
||||
assert!(
|
||||
(upper.simd_ge(lower) | (lower.is_nan() & upper.is_nan())).all(),
|
||||
"invalid interval [{lower:?}, {upper:?}]"
|
||||
);
|
||||
Self { lower, upper }
|
||||
}
|
||||
|
||||
pub fn splat(value: f32) -> Interval {
|
||||
Interval::new(Value::splat(value), Value::splat(value))
|
||||
}
|
||||
|
||||
pub fn splat2(lower: f32, upper: f32) -> Interval {
|
||||
Interval::new(Value::splat(lower), Value::splat(upper))
|
||||
}
|
||||
|
||||
const fn const_splat(value: f32) -> Interval {
|
||||
Interval {
|
||||
lower: Value::splat(value),
|
||||
upper: Value::splat(value),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the lower bound of the interval
|
||||
#[inline]
|
||||
pub fn lower(&self) -> Value {
|
||||
self.lower
|
||||
}
|
||||
|
||||
/// Returns the upper bound of the interval
|
||||
#[inline]
|
||||
pub fn upper(&self) -> Value {
|
||||
self.upper
|
||||
}
|
||||
|
||||
/// Checks whether the given value is (strictly) contained in the interval
|
||||
#[inline]
|
||||
pub fn contains(&self, v: Value) -> Mask {
|
||||
v.simd_ge(self.lower) & v.simd_le(self.upper)
|
||||
}
|
||||
|
||||
/// Returns `true` if either bound of the interval is `NaN`
|
||||
#[inline]
|
||||
pub fn has_nan(&self) -> Mask {
|
||||
self.lower.is_nan() | self.upper.is_nan()
|
||||
}
|
||||
|
||||
/// Calculates the absolute value of the interval
|
||||
#[inline]
|
||||
pub fn abs(self) -> Self {
|
||||
let llt = self.lower.simd_lt(VALUE_0);
|
||||
let ugt = self.upper.simd_gt(VALUE_0);
|
||||
let lower = llt.select(ugt.select(VALUE_0, -self.upper), self.lower);
|
||||
let upper = llt.select(
|
||||
ugt.select(self.upper.simd_max(-self.lower), -self.lower),
|
||||
self.upper,
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Squares the interval
|
||||
///
|
||||
/// Note that this has tighter bounds than multiplication, because we know
|
||||
/// that both sides of the multiplication are the same value.
|
||||
#[inline]
|
||||
pub fn square(self) -> Self {
|
||||
let ult = self.upper.simd_lt(VALUE_0);
|
||||
let lgt = self.lower.simd_gt(VALUE_0);
|
||||
let has_nan = self.has_nan();
|
||||
let lower = ult.select(
|
||||
self.upper * self.upper,
|
||||
lgt.select(self.lower * self.lower, has_nan.select(VALUE_NAN, VALUE_0)),
|
||||
);
|
||||
let upper = ult.select(
|
||||
self.lower * self.lower,
|
||||
lgt.select(
|
||||
self.upper * self.upper,
|
||||
has_nan.select(VALUE_NAN, {
|
||||
let k = self.lower.abs().simd_max(self.upper.abs());
|
||||
k * k
|
||||
}),
|
||||
),
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Cubes the interval
|
||||
///
|
||||
/// Note that this has tighter bounds than multiplication, because we know
|
||||
/// that both sides of the multiplication are the same value.
|
||||
#[inline]
|
||||
pub fn cube(self) -> Self {
|
||||
let has_nan = self.has_nan();
|
||||
let lower = has_nan.select(VALUE_NAN, self.lower * self.lower * self.lower);
|
||||
let upper = has_nan.select(VALUE_NAN, self.upper * self.upper * self.upper);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the sine of the interval
|
||||
#[inline]
|
||||
pub fn sin(self) -> Self {
|
||||
let same_cycle = ((self.lower / VALUE_PI) + VALUE_05)
|
||||
.floor()
|
||||
.simd_eq(((self.upper / VALUE_PI) + VALUE_05).floor());
|
||||
let up = (((self.upper / VALUE_PI) + VALUE_05).floor()) % (VALUE_2);
|
||||
let whole_cycle = (((self.upper / VALUE_PI) + VALUE_05).floor()
|
||||
- ((self.lower / VALUE_PI) + VALUE_05).floor())
|
||||
.simd_gt(VALUE_1);
|
||||
let temp0 = self.lower.sin();
|
||||
let temp1 = self.upper.sin();
|
||||
let lower = self.has_nan().select(
|
||||
VALUE_NAN,
|
||||
((!whole_cycle & (up.simd_eq(VALUE_1))) | same_cycle)
|
||||
.select(temp0.simd_min(temp1), VALUE_M1),
|
||||
);
|
||||
let upper = self.has_nan().select(
|
||||
VALUE_NAN,
|
||||
((!whole_cycle & (up.simd_eq(VALUE_0))) | same_cycle)
|
||||
.select(temp0.simd_max(temp1), VALUE_1),
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the cosine of the interval
|
||||
#[inline]
|
||||
pub fn cos(self) -> Self {
|
||||
let same_cycle = (self.lower / VALUE_PI)
|
||||
.floor()
|
||||
.simd_eq((self.upper / VALUE_PI).floor());
|
||||
let up = ((self.upper / VALUE_PI).floor()) % (VALUE_2);
|
||||
let whole_cycle =
|
||||
((self.upper / VALUE_PI).floor() - (self.lower / VALUE_PI).floor()).simd_gt(VALUE_1);
|
||||
let temp0 = self.lower.cos();
|
||||
let temp1 = self.upper.cos();
|
||||
let lower = self.has_nan().select(
|
||||
VALUE_NAN,
|
||||
((!whole_cycle & (up.simd_eq(VALUE_0))) | same_cycle)
|
||||
.select(temp0.simd_min(temp1), VALUE_M1),
|
||||
);
|
||||
let upper = self.has_nan().select(
|
||||
VALUE_NAN,
|
||||
((!whole_cycle & (up.simd_eq(VALUE_1))) | same_cycle)
|
||||
.select(temp0.simd_max(temp1), VALUE_1),
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the tangent of the interval
|
||||
///
|
||||
/// Returns the `NAN` interval if the result contains a undefined point
|
||||
#[inline]
|
||||
pub fn tan(self) -> Self {
|
||||
let size = self.upper - self.lower;
|
||||
let lower_tmp = Value::from_array(self.lower.to_array().map(|f| f.tan()));
|
||||
let upper_tmp = Value::from_array(self.upper.to_array().map(|f| f.tan()));
|
||||
let lower =
|
||||
(size.simd_lt(VALUE_PI) & upper_tmp.simd_ge(lower_tmp)).select(lower_tmp, VALUE_NAN);
|
||||
let upper =
|
||||
(size.simd_lt(VALUE_PI) & upper_tmp.simd_ge(lower_tmp)).select(upper_tmp, VALUE_NAN);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the arcsine of the interval
|
||||
///
|
||||
/// Returns the `NAN` interval if the input is invalid
|
||||
#[inline]
|
||||
pub fn asin(self) -> Self {
|
||||
let lower = (self.lower.simd_lt(VALUE_M1) | self.upper.simd_gt(VALUE_1)).select(
|
||||
VALUE_NAN,
|
||||
Value::from_array(self.lower.to_array().map(|f| f.asin())),
|
||||
);
|
||||
let upper = (self.lower.simd_lt(VALUE_M1) | self.upper.simd_gt(VALUE_1)).select(
|
||||
VALUE_NAN,
|
||||
Value::from_array(self.upper.to_array().map(|f| f.asin())),
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the arccosine of the interval
|
||||
///
|
||||
/// Returns the `NAN` interval if the input is invalid
|
||||
#[inline]
|
||||
pub fn acos(self) -> Self {
|
||||
let lower = (self.lower.simd_lt(VALUE_M1) | self.upper.simd_gt(VALUE_1)).select(
|
||||
VALUE_NAN,
|
||||
Value::from_array(self.upper.to_array().map(|f| f.asin())),
|
||||
);
|
||||
let upper = (self.lower.simd_lt(VALUE_M1) | self.upper.simd_gt(VALUE_1)).select(
|
||||
VALUE_NAN,
|
||||
Value::from_array(self.lower.to_array().map(|f| f.asin())),
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the arctangent of the interval
|
||||
#[inline]
|
||||
pub fn atan(self) -> Self {
|
||||
let lower = Value::from_array(self.lower.to_array().map(|f| f.asin()));
|
||||
let upper = Value::from_array(self.upper.to_array().map(|f| f.asin()));
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Computes the exponent function applied to the interval
|
||||
#[inline]
|
||||
pub fn exp(self) -> Self {
|
||||
Interval::new(self.lower.exp(), self.upper.exp())
|
||||
}
|
||||
|
||||
/// Computes the natural log of the input interval
|
||||
///
|
||||
/// Returns the `NAN` interval if the input contains zero
|
||||
#[inline]
|
||||
pub fn ln(self) -> Self {
|
||||
let lower = (self.has_nan()).select(VALUE_NAN, self.lower.ln());
|
||||
let upper = (self.has_nan()).select(VALUE_NAN, self.upper.ln());
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Calculates the square root of the interval
|
||||
///
|
||||
/// If the interval contains values below 0, returns a `NAN` interval.
|
||||
#[inline]
|
||||
pub fn sqrt(self) -> Self {
|
||||
let lower = (self.lower.simd_lt(VALUE_0)).select(VALUE_NAN, self.lower.sqrt());
|
||||
let upper = (self.lower.simd_lt(VALUE_0)).select(VALUE_NAN, self.upper.sqrt());
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Calculates the reciprocal of the interval
|
||||
///
|
||||
/// If the interval includes 0, returns the `NAN` interval
|
||||
#[inline]
|
||||
pub fn recip(self) -> Self {
|
||||
let lower = (self.lower.simd_le(VALUE_0) & self.upper.simd_ge(VALUE_0))
|
||||
.select(VALUE_NAN, self.upper.recip());
|
||||
let upper = (self.lower.simd_le(VALUE_0) & self.upper.simd_ge(VALUE_0))
|
||||
.select(VALUE_NAN, self.lower.recip());
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
/// Calculates the minimum of two intervals
|
||||
///
|
||||
/// Returns both the result and a [`VChoice`] indicating whether one side is
|
||||
/// always less than the other.
|
||||
///
|
||||
/// If either side is `NAN`, returns the `NAN` interval and `VChoice::Both`.
|
||||
#[inline]
|
||||
pub fn min_choice(self, rhs: Self) -> (Self, VChoice) {
|
||||
let has_nan = self.has_nan() | rhs.has_nan();
|
||||
let choice = has_nan.select(
|
||||
VChoice::BOTH.0,
|
||||
self.upper.simd_lt(rhs.lower).select(
|
||||
VChoice::LEFT.0,
|
||||
rhs.upper
|
||||
.simd_lt(self.lower)
|
||||
.select(VChoice::RIGHT.0, VChoice::BOTH.0),
|
||||
),
|
||||
);
|
||||
(
|
||||
Interval::new(
|
||||
has_nan.select(VALUE_NAN, self.lower.simd_min(rhs.lower)),
|
||||
has_nan.select(VALUE_NAN, self.upper.simd_min(rhs.upper)),
|
||||
),
|
||||
VChoice(choice),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates the maximum of two intervals
|
||||
///
|
||||
/// Returns both the result and a [`VChoice`] indicating whether one side is
|
||||
/// always greater than the other.
|
||||
///
|
||||
/// If either side is `NAN`, returns the `NAN` interval and `VChoice::Both`.
|
||||
#[inline]
|
||||
pub fn max_choice(self, rhs: Self) -> (Self, VChoice) {
|
||||
let has_nan = self.has_nan() | rhs.has_nan();
|
||||
let choice = has_nan.select(
|
||||
VChoice::BOTH.0,
|
||||
self.lower.simd_gt(rhs.upper).select(
|
||||
VChoice::LEFT.0,
|
||||
rhs.lower
|
||||
.simd_gt(self.upper)
|
||||
.select(VChoice::RIGHT.0, VChoice::BOTH.0),
|
||||
),
|
||||
);
|
||||
(
|
||||
Interval::new(
|
||||
has_nan.select(VALUE_NAN, self.lower.simd_min(rhs.lower)),
|
||||
has_nan.select(VALUE_NAN, self.upper.simd_min(rhs.upper)),
|
||||
),
|
||||
VChoice(choice),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates the short-circuiting `AND` of two intervals
|
||||
///
|
||||
/// Returns both the result and a [`VChoice`] indicating whether one side is
|
||||
/// always selected. An unambiguous 0 in `self` selects itself; an
|
||||
/// unambiguous 1 selects the opposite branch.
|
||||
#[inline]
|
||||
pub fn and_choice(self, rhs: Self) -> (Self, VChoice) {
|
||||
let has_nan = self.has_nan() | rhs.has_nan();
|
||||
let choice = has_nan.select(
|
||||
VChoice::BOTH.0,
|
||||
(self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0)).select(
|
||||
VChoice::LEFT.0,
|
||||
self.contains(VALUE_0)
|
||||
.select(VChoice::BOTH.0, VChoice::RIGHT.0),
|
||||
),
|
||||
);
|
||||
(
|
||||
Interval::new(
|
||||
has_nan.select(
|
||||
VALUE_NAN,
|
||||
(self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0)).select(
|
||||
VALUE_0,
|
||||
self.contains(VALUE_0)
|
||||
.select(rhs.lower.simd_min(VALUE_0), rhs.lower),
|
||||
),
|
||||
),
|
||||
has_nan.select(
|
||||
VALUE_NAN,
|
||||
(self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0)).select(
|
||||
VALUE_0,
|
||||
self.contains(VALUE_0)
|
||||
.select(rhs.upper.simd_max(VALUE_0), rhs.upper),
|
||||
),
|
||||
),
|
||||
),
|
||||
VChoice(choice),
|
||||
)
|
||||
}
|
||||
|
||||
/// Calculates the short-circuiting `OR` of two intervals
|
||||
///
|
||||
/// Returns both the result and a [`VChoice`] indicating whether one side is
|
||||
/// always selected. An unambiguous 0 in `self` selects the opposite
|
||||
/// branch; an unambiguous 1 selects itself.
|
||||
#[inline]
|
||||
pub fn or_choice(self, rhs: Self) -> (Self, VChoice) {
|
||||
let has_nan = self.has_nan() | rhs.has_nan();
|
||||
let choice = has_nan.select(
|
||||
VChoice::BOTH.0,
|
||||
self.contains(VALUE_0).select(
|
||||
(self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0))
|
||||
.select(VChoice::RIGHT.0, VChoice::BOTH.0),
|
||||
VChoice::LEFT.0,
|
||||
),
|
||||
);
|
||||
(
|
||||
Interval::new(
|
||||
has_nan.select(
|
||||
VALUE_NAN,
|
||||
self.contains(VALUE_0).select(
|
||||
(self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0))
|
||||
.select(rhs.lower, rhs.lower.simd_min(self.lower)),
|
||||
self.lower,
|
||||
),
|
||||
),
|
||||
has_nan.select(
|
||||
VALUE_NAN,
|
||||
self.contains(VALUE_0).select(
|
||||
(self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0))
|
||||
.select(rhs.upper, rhs.upper.simd_max(self.upper)),
|
||||
self.upper,
|
||||
),
|
||||
),
|
||||
),
|
||||
VChoice(choice),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the midpoint of the interval
|
||||
#[inline]
|
||||
pub fn midpoint(self) -> Value {
|
||||
(self.lower + self.upper) / VALUE_2
|
||||
}
|
||||
|
||||
/// Splits the interval at the midpoint
|
||||
///
|
||||
/// ```
|
||||
/// # use fidget::types::Interval;
|
||||
/// let a = Interval::new(0.0, 1.0);
|
||||
/// let (lo, hi) = a.split();
|
||||
/// assert_eq!(lo, Interval::new(0.0, 0.5));
|
||||
/// assert_eq!(hi, Interval::new(0.5, 1.0));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn split(self) -> (Self, Self) {
|
||||
let mid = self.midpoint();
|
||||
(
|
||||
Interval::new(self.lower, mid),
|
||||
Interval::new(mid, self.upper),
|
||||
)
|
||||
}
|
||||
|
||||
/// Linear interpolation from `lower` to `upper`
|
||||
///
|
||||
/// ```
|
||||
/// # use fidget::types::Interval;
|
||||
/// let a = Interval::new(0.0, 2.0);
|
||||
/// assert_eq!(a.lerp(0.5), 1.0);
|
||||
/// assert_eq!(a.lerp(0.75), 1.5);
|
||||
/// assert_eq!(a.lerp(2.0), 4.0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn lerp(self, frac: Value) -> Value {
|
||||
self.lower * (VALUE_1 - frac) + self.upper * frac
|
||||
}
|
||||
|
||||
/// Calculates the width of the interval
|
||||
///
|
||||
/// ```
|
||||
/// # use fidget::types::Interval;
|
||||
/// let a = Interval::new(2.0, 3.0);
|
||||
/// assert_eq!(a.width(), 1.0);
|
||||
/// let b = Interval::new(2.0, 5.0);
|
||||
/// assert_eq!(b.width(), 3.0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn width(self) -> Value {
|
||||
self.upper - self.lower
|
||||
}
|
||||
|
||||
/// Checks that the two values are roughly equal, panicking otherwise
|
||||
#[cfg(test)]
|
||||
pub(crate) fn compare_eq(&self, other: Self) {
|
||||
let d = (self.lower - other.lower)
|
||||
.abs()
|
||||
.simd_max((self.upper - other.upper).abs());
|
||||
if d.simd_ge(Value::splat(1e-6)).any() {
|
||||
panic!("lhs != rhs ({self:?} != {other:?})");
|
||||
}
|
||||
}
|
||||
|
||||
/// Largest value that is less-than-or-equal to this value
|
||||
#[inline]
|
||||
pub fn floor(&self) -> Self {
|
||||
Interval::new(self.lower.floor(), self.upper.floor())
|
||||
}
|
||||
|
||||
/// Smallest value that is greater-than-or-equal to this value
|
||||
#[inline]
|
||||
pub fn ceil(&self) -> Self {
|
||||
Interval::new(self.lower.ceil(), self.upper.ceil())
|
||||
}
|
||||
|
||||
/// Rounded value
|
||||
#[inline]
|
||||
pub fn round(&self) -> Self {
|
||||
Interval::new(self.lower.round(), self.upper.round())
|
||||
}
|
||||
|
||||
/// Four-quadrant arctangent
|
||||
#[inline]
|
||||
pub fn atan2(self, x: Self) -> Self {
|
||||
let has_nan = self.has_nan() | x.has_nan();
|
||||
// TODO optimize this further
|
||||
Interval::new(
|
||||
has_nan.select(VALUE_NAN, -VALUE_PI),
|
||||
has_nan.select(VALUE_NAN, VALUE_PI),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sign(self) -> Interval {
|
||||
Interval::new(glsign(self.lower), glsign(self.upper))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn fract(self) -> Interval {
|
||||
let ge1 = (self.upper - self.lower).simd_ge(VALUE_1);
|
||||
Interval::new(
|
||||
ge1.select(VALUE_0, glfract(self.lower)),
|
||||
ge1.select(VALUE_1, glfract(self.upper)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn compare(self, other: Interval) -> Interval {
|
||||
let has_nan = self.has_nan() | other.has_nan();
|
||||
let check1 = self.upper.simd_lt(other.lower);
|
||||
let check2 = other.upper.simd_lt(self.lower);
|
||||
let check3 = other.upper.simd_eq(self.lower);
|
||||
let check4 = self.upper.simd_eq(other.lower);
|
||||
let lower = has_nan.select(
|
||||
VALUE_NAN,
|
||||
check1.select(
|
||||
VALUE_M1,
|
||||
check2.select(
|
||||
VALUE_1,
|
||||
check3.select(VALUE_0, check4.select(VALUE_M1, VALUE_M1)),
|
||||
),
|
||||
),
|
||||
);
|
||||
let upper = has_nan.select(
|
||||
VALUE_NAN,
|
||||
check1.select(
|
||||
VALUE_M1,
|
||||
check2.select(
|
||||
VALUE_1,
|
||||
check3.select(VALUE_1, check4.select(VALUE_0, VALUE_1)),
|
||||
),
|
||||
),
|
||||
);
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
|
||||
pub fn clamp(self, min: Interval, max: Interval) -> Interval {
|
||||
Interval::new(
|
||||
self.lower.simd_clamp(min.lower, max.lower),
|
||||
self.upper.simd_clamp(min.upper, max.upper),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Interval {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "({:?}, {:?})", self.lower, self.upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[Value; 2]> for Interval {
|
||||
#[inline]
|
||||
fn from(i: [Value; 2]) -> Interval {
|
||||
Interval::new(i[0], i[1])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Value> for Interval {
|
||||
#[inline]
|
||||
fn from(f: Value) -> Self {
|
||||
Interval::new(f, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Not for Interval {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
let has_nan = self.has_nan();
|
||||
let is_zero = self.lower.simd_eq(VALUE_0) & self.upper.simd_eq(VALUE_0);
|
||||
let crosses_zero = self.lower.simd_le(VALUE_0) & self.upper.simd_ge(VALUE_0);
|
||||
let lower = has_nan.select(VALUE_NAN, is_zero.select(VALUE_1, VALUE_0));
|
||||
let upper = has_nan.select(VALUE_NAN, crosses_zero.select(VALUE_1, VALUE_0));
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Rem<Interval> for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn rem(self, rhs: Interval) -> Self::Output {
|
||||
// TODO optimize this more?
|
||||
let has_nan = self.has_nan() | rhs.has_nan() | rhs.contains(VALUE_0);
|
||||
let other_constant = rhs.lower.simd_eq(rhs.upper) & rhs.lower.simd_gt(VALUE_0);
|
||||
let a = self.lower / rhs.lower;
|
||||
let b = self.upper / rhs.lower;
|
||||
let floors = a.simd_ne(a.floor()) & a.floor().simd_eq(b.floor());
|
||||
|
||||
let lower = has_nan.select(
|
||||
VALUE_NAN,
|
||||
(other_constant & floors).select(self.lower % rhs.lower, VALUE_0),
|
||||
);
|
||||
let upper = has_nan.select(
|
||||
VALUE_NAN,
|
||||
(other_constant & floors).select(self.upper % rhs.lower, rhs.upper.abs()),
|
||||
);
|
||||
|
||||
Interval::new(lower, upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<Interval> for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Interval::new(self.lower + rhs.lower, self.upper + rhs.upper)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Interval> for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
let has_nan = self.has_nan() | rhs.has_nan();
|
||||
let mut out = [VALUE_0; 4];
|
||||
let mut k = 0;
|
||||
for i in [self.lower, self.upper] {
|
||||
for j in [rhs.lower, rhs.upper] {
|
||||
out[k] = i * j;
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
let mut lower = out[0];
|
||||
let mut upper = out[0];
|
||||
for &v in &out[1..] {
|
||||
lower = lower.simd_min(v);
|
||||
upper = upper.simd_max(v);
|
||||
}
|
||||
Interval::new(
|
||||
has_nan.select(VALUE_NAN, lower),
|
||||
has_nan.select(VALUE_NAN, upper),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<Value> for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn mul(self, rhs: Value) -> Self {
|
||||
let has_nan = self.has_nan() | rhs.is_nan();
|
||||
let rlt = rhs.simd_lt(VALUE_0);
|
||||
Interval::new(
|
||||
has_nan.select(VALUE_NAN, rlt.select(self.upper * rhs, self.lower * rhs)),
|
||||
has_nan.select(VALUE_NAN, rlt.select(self.lower * rhs, self.upper * rhs)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<Interval> for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn div(self, rhs: Self) -> Self {
|
||||
let has_nan = self.has_nan() | (rhs.lower.simd_lt(VALUE_0) & rhs.upper.simd_gt(VALUE_0));
|
||||
let mut out = [VALUE_0; 4];
|
||||
let mut k = 0;
|
||||
for i in [self.lower, self.upper] {
|
||||
for j in [rhs.lower, rhs.upper] {
|
||||
out[k] = i / j;
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
let mut lower = out[0];
|
||||
let mut upper = out[0];
|
||||
for &v in &out[1..] {
|
||||
lower = lower.simd_min(v);
|
||||
upper = upper.simd_max(v);
|
||||
}
|
||||
Interval::new(
|
||||
has_nan.select(VALUE_NAN, lower),
|
||||
has_nan.select(VALUE_NAN, upper),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<Interval> for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Interval::new(self.lower - rhs.upper, self.upper - rhs.lower)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Neg for Interval {
|
||||
type Output = Self;
|
||||
|
||||
#[inline]
|
||||
fn neg(self) -> Self {
|
||||
Interval::new(-self.upper, -self.lower)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
//! Custom types used during evaluation
|
||||
|
||||
mod grad;
|
||||
mod interval;
|
||||
pub use grad::Grad;
|
||||
pub use interval::Interval;
|
||||
Reference in New Issue
Block a user