#![allow(mixed_script_confusables)]
use core::{fmt, ops};
#[cfg(feature = "plotters")]
pub mod plotters;
#[cfg(any(feature = "svg", feature = "svg_fmt"))]
pub mod svg_common;
#[cfg(feature = "svg")]
pub mod svg;
#[cfg(feature = "svg_fmt")]
pub mod svg_fmt;
pub trait Scalar: Sized + Copy + fmt::Debug + PartialOrd
+ ops::Neg<Output = Self>
+ ops::Add<Output = Self> + ops::Sub<Output = Self>
+ ops::Mul<Output = Self> + ops::Div<Output = Self>
+ ops::AddAssign + ops::SubAssign
+ ops::MulAssign + ops::DivAssign
{
const ZERO: Self;
const ONE: Self;
const NEG_ONE: Self;
const HALF: Self;
const EPSILON: Self;
const FEW_EPSILON: Self;
const VERY_LARGE: Self;
fn abs(self) -> Self;
fn max(self, other: Self) -> Self;
fn recip(self) -> Self;
fn sqrt(self) -> Self;
fn exp(self) -> Self;
fn ln(self) -> Self;
fn sin_cos(self) -> (Self, Self);
fn atan(self) -> Self;
fn atan2(self, other: Self) -> Self;
fn sinh_cosh(self) -> (Self, Self);
fn asinh(self) -> Self;
fn acosh(self) -> Self;
fn is_normal(self) -> bool;
fn is_sign_positive(self) -> bool;
}
macro_rules! scalar_impl {
($f:ident) => { impl Scalar for $f {
const ZERO: Self = 0.0;
const ONE: Self = 1.0;
const NEG_ONE: Self = -1.0;
const HALF: Self = 0.5;
const EPSILON: Self = Self::EPSILON;
const FEW_EPSILON: Self = 2.0 * Self::EPSILON;
const VERY_LARGE: Self = 1e6;
fn abs(self) -> Self { self.abs() }
fn max(self, other: Self) -> Self { self.max(other) }
fn recip(self) -> Self { self.recip() }
fn sqrt(self) -> Self { self.sqrt() }
fn exp(self) -> Self { self.exp() }
fn ln(self) -> Self { self.ln() }
fn sin_cos(self) -> (Self, Self) { self.sin_cos() }
fn atan(self) -> Self { self.atan() }
fn atan2(self, other: Self) -> Self { self.atan2(other) }
fn sinh_cosh(self) -> (Self, Self) { (self.sinh(), self.cosh()) }
fn asinh(self) -> Self { self.asinh() }
fn acosh(self) -> Self { self.acosh() }
fn is_normal(self) -> bool { self.is_normal() }
fn is_sign_positive(self) -> bool { self.is_sign_positive() }
}};
}
scalar_impl!(f32);
scalar_impl!(f64);
pub trait Multivector: Sized + Copy {
type K: Scalar;
const HAS_K: bool = false;
const HAS_T: bool = false;
const HAS_X: bool = false;
const HAS_Y: bool = false;
const HAS_TX: bool = false;
const HAS_TY: bool = false;
const HAS_XY: bool = false;
const HAS_TXY: bool = false;
const UNITIZED: bool;
#[inline(always)]
fn k(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn t(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn x(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn y(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn tx(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn ty(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn xy(self) -> Self::K { Self::K::ZERO }
#[inline(always)]
fn txy(self) -> Self::K { Self::K::ZERO }
fn from_all_components(components: [Self::K; 8]) -> Self;
fn dot<M: Multivector<K = Self::K>>(self, rhs: M) -> Self::K {
let mut res = Self::K::ZERO;
if Self::HAS_K && M::HAS_K { res += self.k() * rhs.k() }
if Self::HAS_T && M::HAS_T { res -= self.t() * rhs.t() }
if Self::HAS_X && M::HAS_X { res += self.x() * rhs.x() }
if Self::HAS_Y && M::HAS_Y { res += self.y() * rhs.y() }
if Self::HAS_TX && M::HAS_TX { res += self.tx() * rhs.tx() }
if Self::HAS_TY && M::HAS_TY { res += self.ty() * rhs.ty() }
if Self::HAS_XY && M::HAS_XY { res -= self.xy() * rhs.xy() }
if Self::HAS_TXY && M::HAS_TXY { res += self.txy() * rhs.txy() }
res
}
fn antidot<M: Multivector<K = Self::K>>(self, rhs: M) -> Self::K {
let mut res = Self::K::ZERO;
if Self::HAS_K && M::HAS_K { res -= self.k() * rhs.k() }
if Self::HAS_T && M::HAS_T { res -= self.t() * rhs.t() }
if Self::HAS_X && M::HAS_X { res += self.x() * rhs.x() }
if Self::HAS_Y && M::HAS_Y { res += self.y() * rhs.y() }
if Self::HAS_TX && M::HAS_TX { res -= self.tx() * rhs.tx() }
if Self::HAS_TY && M::HAS_TY { res -= self.ty() * rhs.ty() }
if Self::HAS_XY && M::HAS_XY { res += self.xy() * rhs.xy() }
if Self::HAS_TXY && M::HAS_TXY { res += self.txy() * rhs.txy() }
res
}
}
fn all_components<M: Multivector>(m: M) -> [M::K; 8] {
[
m.k(), m.t(), m.x(), m.y(),
m.tx(), m.ty(), m.xy(), m.txy(),
]
}
fn project_all_components<M1, M2>(m: M1) -> M2
where M1: Multivector, M2: Multivector<K = M1::K> {
M2::from_all_components(all_components(m))
}
macro_rules! multivector_impl {
(comp => $T:ident,) => {};
(comp => $T:ident, k $($p:ident)*) => {
const HAS_K: bool = true;
#[inline(always)]
fn k(self) -> K { self.k }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, t $($p:ident)*) => {
const HAS_T: bool = true;
#[inline(always)]
fn t(self) -> K { self.t }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, x $($p:ident)*) => {
const HAS_X: bool = true;
#[inline(always)]
fn x(self) -> K { self.x }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, y $($p:ident)*) => {
const HAS_Y: bool = true;
#[inline(always)]
fn y(self) -> K { self.y }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, tx $($p:ident)*) => {
const HAS_TX: bool = true;
#[inline(always)]
fn tx(self) -> K { self.tx }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, ty $($p:ident)*) => {
const HAS_TY: bool = true;
#[inline(always)]
fn ty(self) -> K { self.ty }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, xy $($p:ident)*) => {
const HAS_XY: bool = true;
#[inline(always)]
fn xy(self) -> K { self.xy }
multivector_impl!(comp => $T, $($p)*);
};
(comp => $T:ident, txy $($p:ident)*) => {
const HAS_TXY: bool = true;
#[inline(always)]
fn txy(self) -> K { self.txy }
multivector_impl!(comp => $T, $($p)*);
};
(unit => weighted) => {
const UNITIZED: bool = false;
};
(unit => unitized) => {
const UNITIZED: bool = true;
};
($T:ident, $($p:ident)*, $comp:expr, $u:ident $(,)?) => {
impl<K: Scalar> Multivector for $T<K> {
type K = K;
multivector_impl!(comp => $T, $($p)*);
multivector_impl!(unit => $u);
fn from_all_components(components: [K; 8]) -> Self {
#[inline(always)]
fn typeassist<S, K, F: FnOnce([K; 8]) -> S>(comp: F) -> F {
comp
}
let comp = typeassist::<Self, K, _>(#[inline] $comp);
comp(components)
}
}
}
}
pub trait Unitize: Multivector {
type Output: Multivector<K = Self::K>;
fn unitize(self) -> Self::Output {
let Some(res) = self.try_unitize() else {
let q = self.antidot(self).abs().sqrt();
panic!("failed to unitize multivector with length {q:?}");
};
res
}
fn try_unitize(self) -> Option<Self::Output> {
let q = self.antidot(self).abs().sqrt();
q.is_normal().then(||
Self::Output::from_all_components([
if Self::HAS_K && Self::Output::HAS_K { self.k() / q } else { Self::K::ZERO },
if Self::HAS_T && Self::Output::HAS_T { self.t() / q } else { Self::K::ZERO },
if Self::HAS_X && Self::Output::HAS_X { self.x() / q } else { Self::K::ZERO },
if Self::HAS_Y && Self::Output::HAS_Y { self.y() / q } else { Self::K::ZERO },
if Self::HAS_TX && Self::Output::HAS_TX { self.tx() / q } else { Self::K::ZERO },
if Self::HAS_TY && Self::Output::HAS_TY { self.ty() / q } else { Self::K::ZERO },
if Self::HAS_XY && Self::Output::HAS_XY { self.xy() / q } else { Self::K::ZERO },
if Self::HAS_TXY && Self::Output::HAS_TXY { self.txy() / q } else { Self::K::ZERO },
])
)
}
}
macro_rules! weight_scale_impl {
($T:ident, $U:ident) => {
impl<K: Scalar> ops::Mul<K> for $T<K> {
type Output = $U<K>;
fn mul(self, other: K) -> Self::Output {
Self::Output::from_all_components([
if Self::HAS_K && Self::Output::HAS_K { self.k() * other } else { K::ZERO },
if Self::HAS_T && Self::Output::HAS_T { self.t() * other } else { K::ZERO },
if Self::HAS_X && Self::Output::HAS_X { self.x() * other } else { K::ZERO },
if Self::HAS_Y && Self::Output::HAS_Y { self.y() * other } else { K::ZERO },
if Self::HAS_TX && Self::Output::HAS_TX { self.tx() * other } else { K::ZERO },
if Self::HAS_TY && Self::Output::HAS_TY { self.ty() * other } else { K::ZERO },
if Self::HAS_XY && Self::Output::HAS_XY { self.xy() * other } else { K::ZERO },
if Self::HAS_TXY && Self::Output::HAS_TXY { self.txy() * other } else { K::ZERO },
])
}
}
};
($T:ident) => {
weight_scale_impl!($T, $T);
impl<K: Scalar> ops::MulAssign<K> for $T<K> {
fn mul_assign(&mut self, other: K) {
*self = *self * other;
}
}
};
}
macro_rules! weight_add_impl {
($T:ident + $U:ident -> $V:ident) => {
impl<K: Scalar> ops::Add<$U<K>> for $T<K> {
type Output = $V<K>;
fn add(self, other: $U<K>) -> Self::Output {
Self::Output::from_all_components([
self.k() + other.k(),
self.t() + other.t(),
self.x() + other.x(),
self.y() + other.y(),
self.tx() + other.tx(),
self.ty() + other.ty(),
self.xy() + other.xy(),
self.txy() + other.txy(),
])
}
}
impl<K: Scalar> ops::Sub<$U<K>> for $T<K> {
type Output = $V<K>;
fn sub(self, other: $U<K>) -> Self::Output {
Self::Output::from_all_components([
self.k() - other.k(),
self.t() - other.t(),
self.x() - other.x(),
self.y() - other.y(),
self.tx() - other.tx(),
self.ty() - other.ty(),
self.xy() - other.xy(),
self.txy() - other.txy(),
])
}
}
};
($T:ident -> $V:ident) => {
weight_add_impl!($T + $T -> $V);
};
($T:ident += $U:ident) => {
weight_add_impl!($T + $U -> $T);
impl<K: Scalar> ops::AddAssign<$U<K>> for $T<K> {
fn add_assign(&mut self, other: $U<K>) {
*self = *self + other
}
}
impl<K: Scalar> ops::SubAssign<$U<K>> for $T<K> {
fn sub_assign(&mut self, other: $U<K>) {
*self = *self - other
}
}
};
($T:ident) => {
weight_add_impl!($T += $T);
};
}
pub enum CenterKind<K: Scalar> {
Point(WeightedPoint<K>),
Ideal(IdealPoint<K>),
Line(WeightedLine<K>),
}
#[derive(Clone, Copy, Debug)]
pub struct Center<K: Scalar = f64> {
pub t: K,
pub x: K,
pub y: K,
}
impl<K: Scalar> Center<K> {
pub fn classify(self) -> CenterKind<K> {
let t2 = self.t * self.t;
let q = self.x * self.x + self.y * self.y - t2;
if q.abs() <= t2 * K::FEW_EPSILON {
CenterKind::Ideal(project_all_components(self))
} else if q > K::ZERO {
CenterKind::Line(project_all_components(self))
} else {
CenterKind::Point(project_all_components(self))
}
}
}
impl<K: Scalar> From<WeightedPoint<K>> for Center<K> {
fn from(value: WeightedPoint<K>) -> Self {
value.into_center()
}
}
impl<K: Scalar> From<Point<K>> for Center<K> {
fn from(value: Point<K>) -> Self {
value.into_center()
}
}
impl<K: Scalar> From<IdealPoint<K>> for Center<K> {
fn from(value: IdealPoint<K>) -> Self {
value.into_center()
}
}
impl<K: Scalar> From<WeightedLine<K>> for Center<K> {
fn from(value: WeightedLine<K>) -> Self {
value.into_center()
}
}
impl<K: Scalar> From<Line<K>> for Center<K> {
fn from(value: Line<K>) -> Self {
value.into_center()
}
}
multivector_impl!(Center, t x y, |[_, t, x, y, ..]| Self { t, x, y }, weighted);
weight_scale_impl!(Center);
weight_add_impl!(Center);
weight_add_impl!(Center += WeightedPoint);
weight_add_impl!(Center += Point);
weight_add_impl!(Center += IdealPoint);
weight_add_impl!(Center += WeightedLine);
weight_add_impl!(Center += Line);
#[derive(Clone, Copy, Debug)]
pub struct WeightedPoint<K: Scalar = f64> {
pub t: K,
pub x: K,
pub y: K,
}
impl<K: Scalar> WeightedPoint<K> {
pub const fn into_center(self) -> Center<K> {
let Self { t, x, y } = self;
Center { t, x, y }
}
pub fn invert(self, p: WeightedPoint<K>) -> WeightedPoint<K> {
let k = p.t * self.t - p.x * self.x - p.y * self.y;
let tx = p.x * self.t - p.t * self.x;
let ty = p.y * self.t - p.t * self.y;
let xy = p.y * self.x - p.x * self.y;
WeightedPoint {
t: self.t * k - self.x * tx - self.y * ty,
x: self.x * k - self.t * tx - self.y * xy,
y: self.y * k - self.t * ty + self.x * xy,
}
}
pub fn klein(self) -> (K, K) {
(self.x / self.t, self.y / self.t)
}
pub fn poincaré(self) -> (K, K) {
let (kx, ky) = self.klein();
let h2 = K::ONE - kx * kx - ky * ky;
let h = h2.max(K::ZERO).sqrt();
let ih = K::recip(K::ONE + h);
(kx * ih, ky * ih)
}
}
impl<K: Scalar> ops::Mul for WeightedPoint<K> {
type Output = Motor<K>;
fn mul(self, rhs: Self) -> Self::Output {
Motor {
k: self.dot(rhs),
tx: self.t * rhs.x - self.x * rhs.t,
ty: self.t * rhs.y - self.y * rhs.t,
xy: self.x * rhs.y - self.y * rhs.x,
}
}
}
impl<K: Scalar> From<Point<K>> for WeightedPoint<K> {
fn from(value: Point<K>) -> Self {
value.into_weighted()
}
}
multivector_impl!(WeightedPoint, t x y, |[_, t, x, y, ..]| Self { t, x, y }, weighted);
impl<K: Scalar> Unitize for WeightedPoint<K> { type Output = Point<K>; }
weight_scale_impl!(WeightedPoint);
weight_add_impl!(WeightedPoint);
weight_add_impl!(WeightedPoint + Center -> Center);
weight_add_impl!(WeightedPoint + Point -> Center);
weight_add_impl!(WeightedPoint + IdealPoint -> Center);
weight_add_impl!(WeightedPoint + WeightedLine -> Center);
weight_add_impl!(WeightedPoint + Line -> Center);
#[derive(Clone, Copy, Debug)]
pub struct Point<K: Scalar = f64> {
pub t: K,
pub x: K,
pub y: K,
}
impl<K: Scalar> Point<K> {
pub const ORIGIN: Self = Self { t: K::ONE, x: K::ZERO, y: K::ZERO };
pub const fn into_center(self) -> Center<K> {
let Self { t, x, y } = self;
Center { t, x, y }
}
pub const fn into_weighted(self) -> WeightedPoint<K> {
let Self { t, x, y } = self;
WeightedPoint { t, x, y }
}
pub fn length(self) -> K {
K::acosh(self.t)
}
pub fn dist(self, rhs: Self) -> K {
let d = K::max(K::ONE, -self.dot(rhs));
K::acosh(d)
}
pub fn unitize(mut self) -> Self {
let q = self.t * self.t - self.x * self.x - self.y * self.y;
let q = q.recip().sqrt();
self.t *= q;
self.x *= q;
self.y *= q;
self
}
pub fn on_x(x: K) -> Self {
let (sh, ch) = x.sinh_cosh();
Self {
t: ch,
x: sh,
y: K::ZERO,
}
}
pub fn on_y(y: K) -> Self {
let (sh, ch) = y.sinh_cosh();
Self {
t: ch,
x: K::ZERO,
y: sh,
}
}
pub fn polar(r: K, θ: K) -> Self {
let (sh, ch) = r.sinh_cosh();
let (s, c) = θ.sin_cos();
Self {
t: ch,
x: c * sh,
y: s * sh,
}
}
pub fn midpoint(self, other: Self) -> WeightedPoint<K> {
if self.t.is_sign_positive() == other.t.is_sign_positive() {
project_all_components(self + other)
} else {
project_all_components(self - other)
}
}
pub fn bisector(self, other: Self) -> WeightedLine<K> {
if self.t.is_sign_positive() == other.t.is_sign_positive() {
project_all_components(self - other)
} else {
project_all_components(self + other)
}
}
pub fn invert(self, p: Point<K>) -> Point<K> {
let k = p.t * self.t - p.x * self.x - p.y * self.y;
let tx = p.x * self.t - p.t * self.x;
let ty = p.y * self.t - p.t * self.y;
let xy = p.y * self.x - p.x * self.y;
Point {
t: self.t * k - self.x * tx - self.y * ty,
x: self.x * k - self.t * tx - self.y * xy,
y: self.y * k - self.t * ty + self.x * xy,
}
}
pub fn klein(self) -> (K, K) {
(self.x / self.t, self.y / self.t)
}
pub fn poincaré(self) -> (K, K) {
let t1 = self.t + K::ONE;
(self.x / t1, self.y / t1)
}
}
impl<K: Scalar> ops::Mul for Point<K> {
type Output = Motor<K>;
fn mul(self, rhs: Self) -> Self::Output {
Motor {
k: self.dot(rhs),
tx: self.t * rhs.x - self.x * rhs.t,
ty: self.t * rhs.y - self.y * rhs.t,
xy: self.x * rhs.y - self.y * rhs.x,
}
}
}
multivector_impl!(Point, t x y, |[_, t, x, y, ..]| Self { t, x, y }, unitized);
impl<K: Scalar> Unitize for Point<K> { type Output = Self; }
weight_scale_impl!(Point, WeightedPoint);
weight_add_impl!(Point -> Center);
weight_add_impl!(Point + Center -> Center);
weight_add_impl!(Point + WeightedPoint -> Center);
weight_add_impl!(Point + IdealPoint -> Center);
weight_add_impl!(Point + WeightedLine -> Center);
weight_add_impl!(Point + Line -> Center);
#[derive(Clone, Copy, Debug)]
pub struct IdealPoint<K: Scalar = f64> {
pub t: K,
pub x: K,
pub y: K,
}
impl<K: Scalar> IdealPoint<K> {
pub const NORTH: Self = Self { t: K::ONE, x: K::ZERO, y: K::ONE };
pub const SOUTH: Self = Self { t: K::ONE, x: K::ZERO, y: K::NEG_ONE };
pub const EAST: Self = Self { t: K::ONE, x: K::ONE, y: K::ZERO };
pub const WEST: Self = Self { t: K::ONE, x: K::NEG_ONE, y: K::ZERO };
pub const fn into_center(self) -> Center<K> {
let Self { t, x, y } = self;
Center { t, x, y }
}
pub fn polar(θ: K) -> Self {
let (s, c) = θ.sin_cos();
Self {
t: K::ONE,
x: c,
y: s,
}
}
pub fn angle(self) -> K {
if self.t >= K::ZERO {
K::atan2(self.y, self.x)
} else {
K::atan2(-self.y, -self.x)
}
}
}
multivector_impl!(IdealPoint, t x y, |[_, t, x, y, ..]| Self { t, x, y }, weighted);
weight_scale_impl!(IdealPoint);
weight_add_impl!(IdealPoint -> Center);
weight_add_impl!(IdealPoint + Center -> Center);
weight_add_impl!(IdealPoint + WeightedPoint -> Center);
weight_add_impl!(IdealPoint + Point -> Center);
weight_add_impl!(IdealPoint + WeightedLine -> Center);
weight_add_impl!(IdealPoint + Line -> Center);
#[derive(Clone, Copy, Debug)]
pub struct WeightedLine<K: Scalar = f64> {
pub t: K,
pub x: K,
pub y: K,
}
impl<K: Scalar> WeightedLine<K> {
pub const fn into_center(self) -> Center<K> {
let Self { t, x, y } = self;
Center { t, x, y }
}
pub fn between(a: WeightedPoint<K>, b: WeightedPoint<K>) -> Self {
let xt = a.x * b.t - a.t * b.x;
let ty = a.t * b.y - a.y * b.t;
let xy = a.x * b.y - a.y * b.x;
Self { t: xy, x: ty, y: xt }
}
pub fn reflect(self, p: WeightedPoint<K>) -> WeightedPoint<K> {
let k = p.t * self.t - p.x * self.x - p.y * self.y;
let tx = p.x * self.t - p.t * self.x;
let ty = p.y * self.t - p.t * self.y;
let xy = p.y * self.x - p.x * self.y;
WeightedPoint {
t: self.t * k - self.x * tx - self.y * ty,
x: self.x * k - self.t * tx - self.y * xy,
y: self.y * k - self.t * ty + self.x * xy,
}
}
fn poincaré_radius(self) -> K {
let r2 = self.x * self.x + self.y * self.y;
let r2 = r2 / (self.t * self.t);
(r2 - K::ONE).max(K::ZERO).sqrt()
}
}
impl<K: Scalar> From<Line<K>> for WeightedLine<K> {
fn from(value: Line<K>) -> Self {
let Line { t, x, y } = value;
Self { t, x, y }
}
}
multivector_impl!(WeightedLine, t x y, |[_, t, x, y, ..]| Self { t, x, y }, weighted);
impl<K: Scalar> Unitize for WeightedLine<K> { type Output = Line<K>; }
weight_scale_impl!(WeightedLine);
weight_add_impl!(WeightedLine -> Center);
weight_add_impl!(WeightedLine + Center -> Center);
weight_add_impl!(WeightedLine + WeightedPoint -> Center);
weight_add_impl!(WeightedLine + Point -> Center);
weight_add_impl!(WeightedLine + IdealPoint -> Center);
weight_add_impl!(WeightedLine + Line -> Center);
#[derive(Clone, Copy, Debug)]
pub struct Line<K: Scalar = f64> {
pub t: K,
pub x: K,
pub y: K,
}
impl<K: Scalar> Line<K> {
pub const X: Self = Self { t: K::ZERO, x: K::ZERO, y: K::NEG_ONE };
pub const Y: Self = Self { t: K::ZERO, x: K::ONE, y: K::ZERO };
pub const fn into_center(self) -> Center<K> {
let Self { t, x, y } = self;
Center { t, x, y }
}
pub const fn into_weighted(self) -> WeightedLine<K> {
let Self { t, x, y } = self;
WeightedLine { t, x, y }
}
pub fn on_x(x: K) -> Self {
let (sh, ch) = x.sinh_cosh();
Self {
t: sh,
x: ch,
y: K::ZERO,
}
}
pub fn on_y(y: K) -> Self {
let (sh, ch) = y.sinh_cosh();
Self {
t: sh,
x: K::ZERO,
y: ch,
}
}
pub fn polar(r: K, θ: K) -> Self {
let (sh, ch) = r.sinh_cosh();
let (s, c) = θ.sin_cos();
Self {
t: sh,
x: c * ch,
y: s * ch,
}
}
pub fn reflect(self, p: Point<K>) -> Point<K> {
let k = p.t * self.t - p.x * self.x - p.y * self.y;
let tx = p.x * self.t - p.t * self.x;
let ty = p.y * self.t - p.t * self.y;
let xy = p.y * self.x - p.x * self.y;
Point {
t: self.t * k - self.x * tx - self.y * ty,
x: self.x * k - self.t * tx - self.y * xy,
y: self.y * k - self.t * ty + self.x * xy,
}
}
pub fn midline(self, other: Self) -> WeightedLine<K> {
if self.dot(other).is_sign_positive() {
project_all_components(self + other)
} else {
project_all_components(self - other)
}
}
pub fn bisector(self, other: Self) -> WeightedLine<K> {
if self.dot(other).is_sign_positive() {
project_all_components(self - other)
} else {
project_all_components(self + other)
}
}
}
multivector_impl!(Line, t x y, |[_, t, x, y, ..]| Self { t, x, y }, unitized);
impl<K: Scalar> Unitize for Line<K> { type Output = Self; }
weight_scale_impl!(Line, WeightedLine);
weight_add_impl!(Line -> Center);
weight_add_impl!(Line + Center -> Center);
weight_add_impl!(Line + WeightedPoint -> Center);
weight_add_impl!(Line + Point -> Center);
weight_add_impl!(Line + IdealPoint -> Center);
weight_add_impl!(Line + WeightedLine -> Center);
#[derive(Clone, Copy, Debug)]
struct CenterBivector<K: Scalar = f64> {
pub tx: K,
pub ty: K,
pub xy: K,
}
impl<K: Scalar> CenterBivector<K> {
pub fn at(c: Center<K>) -> Self {
let Center { t, x, y } = c;
Self { tx: -y, ty: x, xy: t }
}
}
#[derive(Clone, Copy, Debug)]
pub struct Motor<K: Scalar = f64> {
pub k: K,
pub tx: K,
pub ty: K,
pub xy: K,
}
impl<K: Scalar> Motor<K> {
pub const IDENTITY: Self = Self { k: K::ONE, tx: K::ZERO, ty: K::ZERO, xy: K::ZERO };
pub fn apply(self, v: WeightedPoint<K>) -> WeightedPoint<K> {
let t = v.t * self.k + v.x * self.tx + v.y * self.ty;
let x = v.x * self.k + v.t * self.tx + v.y * self.xy;
let y = v.y * self.k + v.t * self.ty - v.x * self.xy;
let txy = -v.t * self.xy + v.x * self.ty - v.y * self.tx;
WeightedPoint {
t: self.k * t + self.tx * x + self.ty * y - self.xy * txy,
x: self.tx * t + self.k * x + self.xy * y - self.ty * txy,
y: self.ty * t - self.xy * x + self.k * y + self.tx * txy,
}
}
pub fn horolation2(c: IdealPoint<K>, θ: K) -> Self {
let c = CenterBivector::at(c.into_center());
Self {
k: K::NEG_ONE,
tx: θ * c.tx,
ty: θ * c.ty,
xy: θ * c.xy,
}
}
pub fn horolation(c: IdealPoint<K>, θ: K) -> Self {
Self::horolation2(c, K::HALF * θ)
}
pub fn along2(u: Point<K>, v: Point<K>) -> Self {
u * v
}
pub fn along(u: Point<K>, v: Point<K>) -> Self {
u * u.midpoint(v).unitize()
}
pub fn normalize(mut self) -> Self {
let q = self.k * self.k - self.tx * self.tx - self.ty * self.ty + self.xy * self.xy;
let q = q.recip().sqrt();
self.k *= q;
self.tx *= q;
self.ty *= q;
self.xy *= q;
self
}
}
impl<K: Scalar> ops::Mul for Motor<K> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
k: self.k * rhs.k + self.tx * rhs.tx + self.ty * rhs.ty - self.xy * rhs.xy,
tx: self.k * rhs.tx + self.tx * rhs.k - self.ty * rhs.xy + self.xy * rhs.ty,
ty: self.k * rhs.ty + self.tx * rhs.xy + self.ty * rhs.k - self.xy * rhs.tx,
xy: self.k * rhs.xy + self.tx * rhs.ty - self.ty * rhs.tx + self.xy * rhs.k,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compose_inverses() {
fn dist2ish(m1: Motor, m2: Motor) -> f64 {
let dk = m1.k - m2.k;
let dtx = m1.tx - m2.tx;
let dty = m1.ty - m2.ty;
let dxy = m1.xy - m2.xy;
dk * dk + dtx * dtx + dty * dty + dxy * dxy
}
fn assert_inverses(m1: Motor, m2: Motor) {
let c = (m1 * m2).normalize();
const NEG_IDENTITY: Motor = Motor { k: -1.0, tx: 0.0, ty: 0.0, xy: 0.0 };
let d1 = dist2ish(Motor::IDENTITY, c);
let d2 = dist2ish(NEG_IDENTITY, c);
assert!(d1 <= 1e-5 || d2 <= 1e-5);
}
let p1 = Point::on_x(2.0);
let p2 = Point::on_y(3.0);
assert_inverses(Motor::along2(p1, p2), Motor::along2(p2, p1));
let h = IdealPoint::polar(1.0);
assert_inverses(Motor::horolation2(h, 5.0), Motor::horolation2(h, -5.0));
}
#[test]
fn apply_action_of_compose() {
let m1 = Motor::horolation(IdealPoint::polar(5.0), 6.0);
let m2 = Motor::along2(Point::on_x(2.0), Point::on_y(3.0));
for i in 1..=5 {
for j in 0..5 {
let p = Point::polar(i as f64 / 5.0, j as f64).into_weighted();
let composed = (m1 * m2).apply(p);
let applied = m1.apply(m2.apply(p));
let dist = dbg!(composed.unitize().dist(applied.unitize()));
assert!(dist <= 1e-2);
}
}
}
}