#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct FiniteBinary32(f32);
impl FiniteBinary32 {
pub const ZERO: Self = Self(0.0);
pub const ONE: Self = Self(1.0);
#[must_use]
pub fn new(value: f32) -> Option<Self> {
value.is_finite().then_some(Self(value))
}
#[must_use]
pub const fn get(self) -> f32 {
self.0
}
}
impl<'de> Deserialize<'de> for FiniteBinary32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(f32::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("FiniteBinary32 must be finite"))
}
}
impl TryFrom<f32> for FiniteBinary32 {
type Error = &'static str;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::new(value).ok_or("FiniteBinary32 must be finite")
}
}
impl From<FiniteBinary32> for f32 {
fn from(value: FiniteBinary32) -> Self {
value.get()
}
}
impl From<FiniteBinary32> for FiniteReal {
fn from(value: FiniteBinary32) -> Self {
Self(f64::from(value.get()))
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct UnitBinary32(f32);
impl UnitBinary32 {
#[must_use]
pub fn new(value: f32) -> Option<Self> {
(0.0..=1.0).contains(&value).then_some(Self(value))
}
#[must_use]
pub const fn get(self) -> f32 {
self.0
}
}
impl<'de> Deserialize<'de> for UnitBinary32 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(f32::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("UnitBinary32 must be in [0, 1]"))
}
}
impl TryFrom<f32> for UnitBinary32 {
type Error = &'static str;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Self::new(value).ok_or("UnitBinary32 must be in [0, 1]")
}
}
impl From<UnitBinary32> for f32 {
fn from(value: UnitBinary32) -> Self {
value.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(try_from = "i64", into = "i64")]
pub struct PositiveI64(i64);
impl PositiveI64 {
pub const fn new(value: i64) -> Option<Self> {
if value > 0 {
Some(Self(value))
} else {
None
}
}
pub const fn get(&self) -> i64 {
self.0
}
}
impl TryFrom<i64> for PositiveI64 {
type Error = String;
fn try_from(value: i64) -> Result<Self, Self::Error> {
Self::new(value).ok_or_else(|| format!("PositiveI64 must be positive, got {value}"))
}
}
impl From<PositiveI64> for i64 {
fn from(value: PositiveI64) -> Self {
value.get()
}
}
macro_rules! checked_scalar {
($(#[$attribute:meta])* $name:ident, $value:ident, $condition:expr, $error:literal) => {
$(#[$attribute])*
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct $name(f64);
rewrite_scalar!($name);
impl $name {
pub const fn new($value: f64) -> Option<Self> {
if $value.is_finite() && $condition { Some(Self($value)) } else { None }
}
pub const fn get(self) -> f64 {
self.0
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: serde::Deserializer<'de>,
{
Self::new(f64::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom($error))
}
}
impl TryFrom<f64> for $name {
type Error = &'static str;
fn try_from(value: f64) -> Result<Self, Self::Error> {
Self::new(value).ok_or($error)
}
}
impl From<$name> for f64 {
fn from(value: $name) -> Self { value.get() }
}
};
}
checked_scalar!(
Length, value, true, "Length must be finite"
);
checked_scalar!(
#[derive(Default)]
Angle, value, true, "Angle must be finite"
);
checked_scalar!(
PositiveLength, value, value > 0.0, "PositiveLength must be positive and finite"
);
checked_scalar!(
NonZeroLength, value, value != 0.0, "NonZeroLength must be finite and nonzero"
);
checked_scalar!(
NonNegativeLength, value, value >= 0.0, "NonNegativeLength must be nonnegative and finite"
);
checked_scalar!(
SlopeAngle, value, value.abs() < std::f64::consts::FRAC_PI_2,
"SlopeAngle must be finite and strictly between -pi/2 and pi/2"
);
checked_scalar!(
InteriorAngle, value, value > 0.0 && value < std::f64::consts::PI,
"InteriorAngle must be finite and strictly between zero and pi"
);
checked_scalar!(
PositiveAngle, value, value > 0.0, "PositiveAngle must be positive and finite"
);
checked_scalar!(
NonZeroAngle, value, value != 0.0, "NonZeroAngle must be finite and nonzero"
);
checked_scalar!(
FiniteReal, value, true, "FiniteReal must be finite"
);
impl FiniteReal {
pub const NEG_ONE: Self = Self(-1.0);
}
checked_scalar!(
PositiveReal, value, value > 0.0, "PositiveReal must be positive and finite"
);
checked_scalar!(
NonNegativeReal, value, value >= 0.0, "NonNegativeReal must be nonnegative and finite"
);
checked_scalar!(
NonZeroReal, value, value != 0.0, "NonZeroReal must be finite and nonzero"
);
checked_scalar!(
Fraction, value, value >= 0.0 && value <= 1.0, "Fraction must be between zero and one"
);
checked_scalar!(
Magnification, value, value >= 1.0, "Magnification must be finite and at least one"
);
impl Length {
pub const ZERO: Self = Self(0.0);
}
impl Angle {
pub const FULL_TURN: Self = Self(std::f64::consts::TAU);
pub const QUARTER_TURN: Self = Self(std::f64::consts::FRAC_PI_2);
pub const HALF_TURN: Self = Self(std::f64::consts::PI);
pub const THREE_QUARTER_TURN: Self = Self(3.0 * std::f64::consts::FRAC_PI_2);
pub const ZERO: Self = Self(0.0);
}
impl NonNegativeLength {
pub const ZERO: Self = Self(0.0);
#[must_use]
pub const fn from_finite_assigned_real(value: FiniteReal) -> Option<Self> {
if value.get() >= 0.0 {
Some(Self(value.get()))
} else {
None
}
}
#[must_use]
pub fn scaled_by_sine(self, angle: Angle) -> Self {
Self(self.0 * angle.0.sin().abs())
}
#[must_use]
pub const fn from_assigned_real(value: NonNegativeReal) -> Self {
Self(value.0)
}
#[must_use]
pub fn scaled(self, scale: PositiveReal) -> Option<Self> {
let value = self.0 * scale.0;
value.is_finite().then_some(Self(value))
}
}
impl PositiveReal {
pub const ONE: Self = Self(1.0);
#[must_use]
pub const fn from_assigned_length(value: PositiveLength) -> Self {
Self(value.0)
}
#[must_use]
pub const fn from_finite(value: FiniteReal) -> Option<Self> {
if value.get() > 0.0 {
Some(Self(value.get()))
} else {
None
}
}
}
impl NonNegativeReal {
pub const ZERO: Self = Self(0.0);
#[must_use]
pub const fn positive(self) -> Option<PositiveReal> {
if self.0 > 0.0 {
Some(PositiveReal(self.0))
} else {
None
}
}
#[must_use]
pub const fn from_finite(value: FiniteReal) -> Option<Self> {
if value.get() >= 0.0 {
Some(Self(value.get()))
} else {
None
}
}
#[must_use]
pub fn scaled(self, scale: PositiveReal) -> Option<Self> {
let value = self.0 * scale.0;
value.is_finite().then_some(Self(value))
}
}
impl Magnification {
pub const MILLIMETERS_PER_METER: Self = Self(1000.0);
}
impl SlopeAngle {
pub const ZERO: Self = Self(0.0);
}
impl PositiveAngle {
pub const FULL_TURN: Self = Self(std::f64::consts::TAU);
pub const QUARTER_TURN: Self = Self(std::f64::consts::FRAC_PI_2);
pub const HALF_TURN: Self = Self(std::f64::consts::PI);
pub const THREE_QUARTER_TURN: Self = Self(3.0 * std::f64::consts::FRAC_PI_2);
#[must_use]
pub const fn from_assigned_real(value: FiniteReal) -> Option<Self> {
if value.get() > 0.0 {
Some(Self(value.get()))
} else {
None
}
}
}
impl NonZeroAngle {
#[must_use]
pub const fn abs(self) -> PositiveAngle {
PositiveAngle(self.0.abs())
}
}
macro_rules! scalar_subset {
($from:ident => $to:ident, $error:literal) => {
impl From<$from> for $to {
fn from(value: $from) -> Self {
Self(value.get())
}
}
impl TryFrom<$to> for $from {
type Error = &'static str;
fn try_from(value: $to) -> Result<Self, Self::Error> {
Self::new(value.get()).ok_or($error)
}
}
};
}
scalar_subset!(PositiveLength => NonZeroLength, "length must be positive");
scalar_subset!(PositiveLength => NonNegativeLength, "length must be positive");
scalar_subset!(PositiveLength => Length, "length must be positive");
scalar_subset!(NonZeroLength => Length, "length must be nonzero");
scalar_subset!(NonNegativeLength => Length, "length must be nonnegative");
scalar_subset!(InteriorAngle => PositiveAngle, "angle must be strictly less than pi");
scalar_subset!(InteriorAngle => NonZeroAngle, "angle must be strictly between zero and pi");
scalar_subset!(InteriorAngle => Angle, "angle must be strictly between zero and pi");
scalar_subset!(PositiveAngle => NonZeroAngle, "angle must be positive");
scalar_subset!(PositiveAngle => Angle, "angle must be positive");
scalar_subset!(NonZeroAngle => Angle, "angle must be nonzero");
scalar_subset!(SlopeAngle => Angle, "angle must be strictly between -pi/2 and pi/2");
scalar_subset!(PositiveReal => NonZeroReal, "value must be positive");
scalar_subset!(PositiveReal => NonNegativeReal, "value must be positive");
scalar_subset!(PositiveReal => FiniteReal, "value must be positive");
scalar_subset!(NonZeroReal => FiniteReal, "value must be nonzero");
scalar_subset!(NonNegativeReal => FiniteReal, "value must be nonnegative");
scalar_subset!(Fraction => NonNegativeReal, "value must be between zero and one");
scalar_subset!(Fraction => FiniteReal, "value must be between zero and one");
scalar_subset!(Magnification => PositiveReal, "value must be at least one");
impl Length {
#[must_use]
pub const fn negated(self) -> Self {
Self(-self.0)
}
#[must_use]
pub const fn magnitude(self) -> FiniteReal {
FiniteReal(self.0)
}
#[must_use]
pub const fn abs(self) -> Self {
Self(self.0.abs())
}
#[must_use]
pub const fn from_assigned_real(value: FiniteReal) -> Self {
Self(value.0)
}
}
impl PositiveLength {
#[must_use]
pub const fn from_assigned_positive_real(value: PositiveReal) -> Self {
Self(value.get())
}
#[must_use]
pub const fn from_assigned_real(value: FiniteReal) -> Option<Self> {
if value.get() > 0.0 {
Some(Self(value.get()))
} else {
None
}
}
#[must_use]
pub const fn magnitude(self) -> FiniteReal {
FiniteReal(self.0)
}
pub(crate) fn scale_ordered_pair(
values: [f64; 2],
scale: PositiveReal,
) -> Result<[Self; 2], usize> {
let [first, second] = values.map(|value| value * scale.0);
if !first.is_finite() || first <= 0.0 {
return Err(0);
}
if second <= 0.0 {
return Err(1);
}
Ok([Self(first), Self(second)])
}
}
impl NonZeroLength {
#[must_use]
pub const fn from_assigned_real(value: FiniteReal) -> Option<Self> {
if value.get() == 0.0 {
None
} else {
Some(Self(value.get()))
}
}
#[must_use]
pub const fn abs(self) -> PositiveLength {
PositiveLength(self.0.abs())
}
#[must_use]
pub fn magnified(self, factor: Magnification) -> Option<Self> {
let value = self.0 * factor.0;
value.is_finite().then_some(Self(value))
}
}
impl Angle {
#[must_use]
pub const fn negated(self) -> Self {
Self(-self.0)
}
#[must_use]
pub const fn abs(self) -> Self {
Self(self.0.abs())
}
#[must_use]
pub const fn from_assigned_real(value: FiniteReal) -> Self {
Self(value.0)
}
}
impl FiniteReal {
pub const ONE: Self = Self(1.0);
pub const ZERO: Self = Self(0.0);
pub const HALF: Self = Self(0.5);
pub(crate) const TWO: Self = Self(2.0);
#[must_use]
pub const fn negated(self) -> Self {
Self(-self.0)
}
#[must_use]
pub const fn abs(self) -> Self {
Self(self.0.abs())
}
#[must_use]
pub(crate) fn subnormal_units(negative: bool, units: u64) -> Option<Self> {
let magnitude = cadmpeg_core::convert::f64_from_u64(units)? * f64::from_bits(1);
Some(Self(if negative { -magnitude } else { magnitude }))
}
#[must_use]
pub fn atan2(self, x: Self) -> Self {
Self(self.0.atan2(x.0))
}
#[must_use]
pub(crate) fn from_index(index: usize) -> Option<Self> {
cadmpeg_core::convert::f64_from_index(index).map(Self)
}
#[must_use]
pub(crate) fn from_integer(value: i64) -> Option<Self> {
cadmpeg_core::convert::f64_from_i64(value).map(Self)
}
#[must_use]
pub(crate) fn turns_from(self, start: Self) -> Self {
use crate::math::sum::{ExactSignedSum, ScaledValue};
let mut difference = ExactSignedSum::default();
difference.add_product(self.0, 1.0);
difference.add_product(start.0, -1.0);
let turn = ScaledValue::of_nonzero(NonZeroReal(std::f64::consts::TAU));
Self(difference.finish().map_or(0.0, |difference| {
difference.quotient_shifted_product(turn, 0)
}))
}
pub(crate) fn segment_position(self, start: Self, end: Self) -> SegmentPosition {
use crate::math::sum::ExactSignedSum;
if !((self >= start && self <= end) || (self <= start && self >= end)) {
return SegmentPosition::Outside;
}
let mut width = ExactSignedSum::default();
width.add_product(end.0, 1.0);
width.add_product(start.0, -1.0);
let Some(width) = width.finish() else {
return SegmentPosition::Degenerate;
};
let mut distance = ExactSignedSum::default();
distance.add_product(self.0, 1.0);
distance.add_product(start.0, -1.0);
SegmentPosition::Within(Self(
distance
.finish()
.map_or(0.0, |distance| distance.quotient_shifted_product(width, 0)),
))
}
pub(crate) fn hyperbolic_tail(self) -> (f64, NonZeroReal) {
let tail = (-self.0.abs()).exp();
(tail, NonZeroReal((1.0 + tail * tail).powi(2)))
}
pub(crate) fn hypot_one_nonzero(self) -> NonZeroReal {
NonZeroReal(self.0.hypot(1.0))
}
pub(crate) fn hyperbolic_tail_unit_sum(self) -> (f64, NonZeroReal) {
let tail = (-self.0.abs()).exp();
(tail, NonZeroReal(1.0 + tail * tail))
}
pub(crate) fn beyond_unit(self) -> Option<BeyondUnit> {
(self.0.abs() > 1.0).then_some(BeyondUnit(self.0))
}
#[must_use]
pub(crate) fn halved(self) -> Self {
Self(self.0 * 0.5)
}
#[must_use]
pub(crate) fn midpoint(self, other: Self) -> Self {
Self(self.0.midpoint(other.0))
}
pub(crate) fn normal_or_zero(value: f64) -> Option<Self> {
(value.is_normal() || value == 0.0).then_some(Self(value))
}
pub(crate) fn lanes<const N: usize>(lanes: [Vec<f64>; N]) -> Option<[Vec<Self>; N]> {
lanes
.iter()
.flatten()
.all(|value| value.is_finite())
.then(|| lanes.map(|lane| lane.into_iter().map(Self).collect()))
}
pub(crate) fn optional<const N: usize>(values: [Option<f64>; N]) -> Option<[Option<Self>; N]> {
values
.iter()
.flatten()
.all(|value| value.is_finite())
.then(|| values.map(|value| value.map(Self)))
}
pub(crate) fn array<const N: usize>(values: [f64; N]) -> Option<[Self; N]> {
values
.iter()
.all(|value| value.is_finite())
.then(|| values.map(Self))
}
pub(crate) fn lane(values: Vec<f64>) -> Option<Vec<Self>> {
values
.iter()
.all(|value| value.is_finite())
.then(|| values.into_iter().map(Self).collect())
}
pub(crate) fn rows<const N: usize>(rows: Vec<[f64; N]>) -> Option<Vec<[Self; N]>> {
rows.iter()
.flatten()
.all(|value| value.is_finite())
.then(|| rows.into_iter().map(|row| row.map(Self)).collect())
}
pub(crate) fn grid<const N: usize, const M: usize>(
grid: [[f64; M]; N],
) -> Option<[[Self; M]; N]> {
grid.iter()
.flatten()
.all(|value| value.is_finite())
.then(|| grid.map(|row| row.map(Self)))
}
pub(crate) fn optional_grid<const N: usize, const M: usize>(
grid: [[Option<f64>; M]; N],
) -> Option<[[Option<Self>; M]; N]> {
grid.iter()
.flatten()
.flatten()
.all(|value| value.is_finite())
.then(|| grid.map(|row| row.map(|value| value.map(Self))))
}
#[must_use]
pub fn raw_array<const N: usize>(values: [Self; N]) -> [f64; N] {
values.map(Self::get)
}
#[must_use]
pub fn raw_optional<const N: usize>(values: [Option<Self>; N]) -> [Option<f64>; N] {
values.map(|value| value.map(Self::get))
}
#[must_use]
pub fn raw_lane(values: &[Self]) -> Vec<f64> {
values.iter().map(|value| value.0).collect()
}
#[must_use]
pub fn raw_rows<const N: usize>(rows: &[[Self; N]]) -> Vec<[f64; N]> {
rows.iter().map(|row| row.map(Self::get)).collect()
}
#[must_use]
pub fn raw_grid<const N: usize, const M: usize>(grid: [[Self; M]; N]) -> [[f64; M]; N] {
grid.map(|row| row.map(Self::get))
}
#[must_use]
pub fn raw_optional_grid<const N: usize, const M: usize>(
grid: [[Option<Self>; M]; N],
) -> [[Option<f64>; M]; N] {
grid.map(Self::raw_optional)
}
#[must_use]
pub fn raw_lanes<const N: usize>(lanes: &[Vec<Self>; N]) -> [Vec<f64>; N] {
std::array::from_fn(|index| lanes[index].iter().map(|value| value.0).collect())
}
}
impl crate::topology::IncreasingParameterInterval {
pub fn affine_coefficients_to(self, target: Self) -> Option<(FiniteReal, FiniteReal)> {
use crate::math::sum::ExactSignedSum;
let scale = target.scaled_span().quotient(self.scaled_span()).ok()?;
if scale == FiniteReal::ZERO {
return None;
}
let mut offset = ExactSignedSum::default();
offset.add_product(target.lower(), 1.0);
offset.add_product(self.lower(), -scale.get());
Some((scale, offset.finite_sum()?))
}
pub(crate) fn span_factors(self) -> (f64, bool) {
let [lower, upper] = self.endpoints();
let span = upper - lower;
if span.is_finite() {
(span, false)
} else {
(upper * 0.5 - lower * 0.5, true)
}
}
pub(crate) fn scaled_span(self) -> crate::math::sum::ScaledValue {
use crate::math::sum::ScaledValue;
let (factor, doubled) = self.span_factors();
let span = ScaledValue::of_nonzero(NonZeroReal(factor));
if doubled {
span.doubled()
} else {
span
}
}
pub fn map_from(
self,
source: Self,
parameter: FiniteReal,
reversed: bool,
) -> Result<FiniteReal, f64> {
use crate::math::sum::ExactSignedSum;
let [source_start, source_end] = source.endpoints();
let [lower, upper] = self.endpoints();
let (target_start, target_end) = if reversed {
(upper, lower)
} else {
(lower, upper)
};
let parameter = parameter.get();
let mut numerator = ExactSignedSum::default();
numerator.add_product(parameter, target_end);
numerator.add_product(source_start, -target_end);
numerator.add_product(source_end, target_start);
numerator.add_product(parameter, -target_start);
match numerator.finish() {
Some(numerator) => numerator.quotient(source.scaled_span()),
None => Ok(FiniteReal::ZERO),
}
}
#[must_use]
pub const fn finite_endpoints(self) -> [FiniteReal; 2] {
let [lower, upper] = self.endpoints();
[FiniteReal(lower), FiniteReal(upper)]
}
}
impl crate::topology::ParameterInterval {
#[must_use]
pub const fn finite_endpoints(self) -> [FiniteReal; 2] {
let [start, end] = self.endpoints();
[FiniteReal(start), FiniteReal(end)]
}
#[must_use]
pub(crate) fn project(self, value: ExtendedReal) -> FiniteReal {
let [start, end] = self.endpoints();
FiniteReal(value.0.clamp(start, end))
}
}
impl<const N: usize> crate::units::FiniteVector<N> {
#[must_use]
pub fn finite_components(self) -> [FiniteReal; N] {
self.get().map(FiniteReal)
}
}
impl crate::geometry::nurbs::KnotVector {
#[must_use]
pub fn finite_knot(&self, index: usize) -> Option<FiniteReal> {
self.as_slice().get(index).map(|knot| FiniteReal(*knot))
}
pub fn finite_knots(
&self,
) -> impl DoubleEndedIterator<Item = FiniteReal> + ExactSizeIterator + '_ {
self.as_slice().iter().map(|knot| FiniteReal(*knot))
}
}
impl crate::geometry::DirectedParameterRange {
#[must_use]
pub const fn finite_endpoints(self) -> [FiniteReal; 2] {
let [start, end] = self.endpoints();
[FiniteReal(start), FiniteReal(end)]
}
}
impl crate::geometry::RecordBounds {
#[must_use]
pub(crate) fn finite_values(self) -> [Option<FiniteReal>; 4] {
self.get().map(|value| value.map(FiniteReal))
}
}
impl crate::features::FinitePoint3 {
#[must_use]
pub const fn coordinates(self) -> [FiniteReal; 3] {
let point = self.get();
[
FiniteReal(point.x),
FiniteReal(point.y),
FiniteReal(point.z),
]
}
}
impl crate::features::FiniteVector3 {
#[must_use]
pub const fn components(self) -> [FiniteReal; 3] {
let vector = self.get();
[
FiniteReal(vector.x),
FiniteReal(vector.y),
FiniteReal(vector.z),
]
}
#[must_use]
pub(crate) fn binade_chart(self) -> Option<([FiniteReal; 3], i32)> {
let vector = self.get();
let largest = vector.x.abs().max(vector.y.abs()).max(vector.z.abs());
let exponent = crate::math::power_of_two_bound(largest)?;
let chart = |value: f64| FiniteReal(crate::math::power_of_two_product(value, -exponent));
Some((
[chart(vector.x), chart(vector.y), chart(vector.z)],
exponent,
))
}
#[must_use]
pub fn unit_nonzero(self) -> Option<crate::math::Vector3> {
use crate::math::sum::ScaledValue;
let ([x, y, z], exponent) = self.binade_chart()?;
let length = crate::math::Vector3::new(x.0, y.0, z.0).norm();
let scaled_length = ScaledValue::of_nonzero(NonZeroReal(length));
let component = |value: f64, chart: FiniteReal| match NonZeroReal::new(value) {
Some(value) if chart.0.abs() < f64::MIN_POSITIVE => {
ScaledValue::of_nonzero(value).quotient_shifted_product(scaled_length, -exponent)
}
_ => chart.0 / length,
};
let vector = self.get();
Some(crate::math::Vector3::new(
component(vector.x, x),
component(vector.y, y),
component(vector.z, z),
))
}
#[must_use]
pub(crate) fn unit_with_cubed_length(self) -> Option<(crate::math::Vector3, [NonZeroReal; 4])> {
let unit = self.unit_nonzero()?;
let vector = self.get();
let scale = vector.x.abs().max(vector.y.abs()).max(vector.z.abs());
let length = (vector.x / scale)
.hypot(vector.y / scale)
.hypot(vector.z / scale);
let scale = NonZeroReal(scale);
Some((
unit,
[scale, scale, scale, NonZeroReal(length * length * length)],
))
}
}
impl crate::units::FinitePoint2 {
#[must_use]
pub const fn coordinates(self) -> [FiniteReal; 2] {
let point = self.get();
[FiniteReal(point.u), FiniteReal(point.v)]
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct ExtendedReal(f64);
impl ExtendedReal {
pub(crate) const fn from_finite(value: FiniteReal) -> Self {
Self(value.0)
}
pub(crate) fn new(value: f64) -> Option<Self> {
(!value.is_nan()).then_some(Self(value))
}
pub(crate) fn stepped(origin: FiniteReal, scale: FiniteReal, step: FiniteReal) -> Self {
Self(origin.0 - scale.0 * step.0)
}
pub(crate) fn hypot(x: FiniteReal, y: FiniteReal) -> Self {
Self(x.0.hypot(y.0))
}
#[must_use]
pub(crate) fn minus(self, value: Length) -> Self {
Self(self.0 - value.0)
}
#[must_use]
pub(crate) fn over(self, divisor: NonZeroLength) -> Self {
Self(self.0 / divisor.0)
}
pub(crate) fn atan2(self, x: Self) -> FiniteReal {
FiniteReal(self.0.atan2(x.0))
}
}
pub(crate) enum SegmentPosition {
Outside,
Degenerate,
Within(FiniteReal),
}
#[derive(Clone, Copy)]
pub(crate) struct UnitCosine(f64);
impl UnitCosine {
pub(crate) fn between(
first: crate::units::UnitVector3,
second: crate::units::UnitVector3,
) -> Self {
let dot = first.as_raw().dot(*second.as_raw());
Self(if dot.abs() > 1.0 { dot.signum() } else { dot })
}
pub(crate) const fn get(self) -> f64 {
self.0
}
}
#[derive(Clone, Copy)]
pub(crate) struct BeyondUnit(f64);
impl BeyondUnit {
pub(crate) fn magnitude(self) -> NonZeroReal {
NonZeroReal(self.0.abs())
}
pub(crate) fn arcsec_factor(self) -> NonZeroReal {
let magnitude = self.0.abs();
NonZeroReal((((magnitude - 1.0) / magnitude) * (1.0 + 1.0 / magnitude)).sqrt())
}
pub(crate) fn quotient(self, dividend: FiniteReal) -> FiniteReal {
FiniteReal(dividend.0 / self.0)
}
pub(crate) fn square_complement(self) -> NonZeroReal {
let x = self.0;
NonZeroReal(((x - 1.0) / x) * ((x + 1.0) / x))
}
pub(crate) fn arcoth(self) -> FiniteReal {
FiniteReal((1.0 / self.0).atanh())
}
}
impl PositiveReal {
pub(crate) fn unit_complement_root(self) -> Option<Self> {
(self.0 < 1.0).then(|| Self(((1.0 - self.0) * (1.0 + self.0)).sqrt()))
}
}
impl NonZeroReal {
#[must_use]
pub const fn from_finite(value: FiniteReal) -> Option<Self> {
if value.get() == 0.0 {
None
} else {
Some(Self(value.get()))
}
}
pub const ONE: Self = Self(1.0);
pub const FRAC_1_SQRT_2: Self = Self(std::f64::consts::FRAC_1_SQRT_2);
pub(crate) fn magnitude(self) -> Self {
Self(self.0.abs())
}
pub(crate) fn hyperbolic_sinh_denominator(self) -> Self {
Self(-(-2.0 * self.0.abs()).exp_m1())
}
}
#[cfg(test)]
mod tests;
mod identity_rewrite;