use geometry_coords::CoordinateScalar;
use geometry_cs::{CartesianFamily, CoordinateSystem};
use geometry_tag::SameAs;
use geometry_trait::{Point, PointMut, Segment};
use crate::cartesian::Pythagoras;
use crate::distance::DistanceStrategy;
#[derive(Debug, Default, Clone, Copy)]
pub struct PointToSegment<PP = Pythagoras>(pub PP);
impl<P, S, PP> DistanceStrategy<P, S> for PointToSegment<PP>
where
P: PointMut + Default,
S: Segment<Point = P>,
<P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
PP: DistanceStrategy<P, P, Out = P::Scalar>,
PP::Comparable: DistanceStrategy<P, P, Out = P::Scalar>,
{
type Out = P::Scalar;
type Comparable = PointToSegment<PP::Comparable>;
#[inline]
fn distance(&self, p: &P, s: &S) -> Self::Out {
let foot = closest_point_on_segment::<P, S>(p, s);
self.0.distance(p, &foot)
}
#[inline]
fn comparable(&self) -> Self::Comparable {
PointToSegment(self.0.comparable())
}
}
fn closest_point_on_segment<P, S>(p: &P, seg: &S) -> P
where
P: PointMut + Default,
S: Segment<Point = P>,
{
let start = endpoint::<P, S, 0>(seg);
let end = endpoint::<P, S, 1>(seg);
let (numerator, denominator) = dots::<P>(p, &start, &end);
if denominator <= P::Scalar::ZERO {
return start;
}
let t = numerator / denominator;
if t <= P::Scalar::ZERO {
start
} else if t >= P::Scalar::ONE {
end
} else {
assemble_foot::<P>(&start, &end, t)
}
}
#[inline]
fn endpoint<P, S, const I: usize>(s: &S) -> P
where
P: PointMut + Default,
S: Segment<Point = P>,
{
let mut out: P = Default::default();
match P::DIM {
1 => <Walk<0, 1> as WriteEndpoint<0, 1>>::run::<P, S, I>(s, &mut out),
2 => <Walk<0, 2> as WriteEndpoint<0, 2>>::run::<P, S, I>(s, &mut out),
3 => <Walk<0, 3> as WriteEndpoint<0, 3>>::run::<P, S, I>(s, &mut out),
4 => <Walk<0, 4> as WriteEndpoint<0, 4>>::run::<P, S, I>(s, &mut out),
_ => panic!("PointToSegment: P::DIM exceeds MAX_DIM (4)"),
}
out
}
#[inline]
fn dots<P: Point>(p: &P, a: &P, b: &P) -> (P::Scalar, P::Scalar) {
let init = (P::Scalar::ZERO, P::Scalar::ZERO);
match P::DIM {
1 => <Walk<0, 1> as Dots<0, 1>>::run::<P>(init, p, a, b),
2 => <Walk<0, 2> as Dots<0, 2>>::run::<P>(init, p, a, b),
3 => <Walk<0, 3> as Dots<0, 3>>::run::<P>(init, p, a, b),
4 => <Walk<0, 4> as Dots<0, 4>>::run::<P>(init, p, a, b),
_ => panic!("PointToSegment: P::DIM exceeds MAX_DIM (4)"),
}
}
#[inline]
fn assemble_foot<P: PointMut + Default>(a: &P, b: &P, t: P::Scalar) -> P {
let mut out: P = Default::default();
match P::DIM {
1 => <Walk<0, 1> as AssembleFoot<0, 1>>::run::<P>(a, b, t, &mut out),
2 => <Walk<0, 2> as AssembleFoot<0, 2>>::run::<P>(a, b, t, &mut out),
3 => <Walk<0, 3> as AssembleFoot<0, 3>>::run::<P>(a, b, t, &mut out),
4 => <Walk<0, 4> as AssembleFoot<0, 4>>::run::<P>(a, b, t, &mut out),
_ => panic!("PointToSegment: P::DIM exceeds MAX_DIM (4)"),
}
out
}
struct Walk<const I: usize, const N: usize>;
mod sealed {
pub trait Sealed<const I: usize, const N: usize> {}
}
trait WriteEndpoint<const I: usize, const N: usize>: sealed::Sealed<I, N> {
fn run<P, S, const I_SEG: usize>(s: &S, out: &mut P)
where
P: PointMut,
S: Segment<Point = P>;
}
trait Dots<const I: usize, const N: usize>: sealed::Sealed<I, N> {
fn run<P: Point>(acc: (P::Scalar, P::Scalar), p: &P, a: &P, b: &P) -> (P::Scalar, P::Scalar);
}
trait AssembleFoot<const I: usize, const N: usize>: sealed::Sealed<I, N> {
fn run<P: PointMut>(a: &P, b: &P, t: P::Scalar, out: &mut P);
}
impl<const N: usize> sealed::Sealed<N, N> for Walk<N, N> {}
impl<const N: usize> WriteEndpoint<N, N> for Walk<N, N> {
#[inline]
fn run<P, S, const I_SEG: usize>(_s: &S, _out: &mut P)
where
P: PointMut,
S: Segment<Point = P>,
{
}
}
impl<const N: usize> Dots<N, N> for Walk<N, N> {
#[inline]
fn run<P: Point>(
acc: (P::Scalar, P::Scalar),
_p: &P,
_a: &P,
_b: &P,
) -> (P::Scalar, P::Scalar) {
acc
}
}
impl<const N: usize> AssembleFoot<N, N> for Walk<N, N> {
#[inline]
fn run<P: PointMut>(_a: &P, _b: &P, _t: P::Scalar, _out: &mut P) {}
}
macro_rules! impl_walk {
($i:expr, $n:expr) => {
impl sealed::Sealed<$i, $n> for Walk<$i, $n> {}
impl WriteEndpoint<$i, $n> for Walk<$i, $n> {
#[inline]
fn run<P, S, const I_SEG: usize>(s: &S, out: &mut P)
where
P: PointMut,
S: Segment<Point = P>,
{
let v = s.get_indexed::<I_SEG, $i>();
<P as PointMut>::set::<$i>(out, v);
<Walk<{ $i + 1 }, $n> as WriteEndpoint<{ $i + 1 }, $n>>::run::<P, S, I_SEG>(s, out);
}
}
impl Dots<$i, $n> for Walk<$i, $n> {
#[inline]
fn run<P: Point>(
acc: (P::Scalar, P::Scalar),
p: &P,
a: &P,
b: &P,
) -> (P::Scalar, P::Scalar) {
let ap = p.get::<$i>() - a.get::<$i>();
let ab = b.get::<$i>() - a.get::<$i>();
let acc = (acc.0 + ap * ab, acc.1 + ab * ab);
<Walk<{ $i + 1 }, $n> as Dots<{ $i + 1 }, $n>>::run::<P>(acc, p, a, b)
}
}
impl AssembleFoot<$i, $n> for Walk<$i, $n> {
#[inline]
fn run<P: PointMut>(a: &P, b: &P, t: P::Scalar, out: &mut P) {
let ad = a.get::<$i>();
let bd = b.get::<$i>();
<P as PointMut>::set::<$i>(out, ad + t * (bd - ad));
<Walk<{ $i + 1 }, $n> as AssembleFoot<{ $i + 1 }, $n>>::run::<P>(a, b, t, out);
}
}
};
}
impl_walk!(0, 1);
impl_walk!(0, 2);
impl_walk!(1, 2);
impl_walk!(0, 3);
impl_walk!(1, 3);
impl_walk!(2, 3);
impl_walk!(0, 4);
impl_walk!(1, 4);
impl_walk!(2, 4);
impl_walk!(3, 4);
#[cfg(test)]
mod tests {
use super::PointToSegment;
use crate::cartesian::{ComparablePythagoras, Pythagoras};
use crate::distance::DistanceStrategy;
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Segment};
fn pt(x: f64, y: f64) -> Point2D<f64, Cartesian> {
Point2D::<f64, Cartesian>::new(x, y)
}
fn seg(ax: f64, ay: f64, bx: f64, by: f64) -> Segment<Point2D<f64, Cartesian>> {
Segment::new(pt(ax, ay), pt(bx, by))
}
#[test]
fn proj_case_1() {
let d = PointToSegment::<Pythagoras>::default()
.distance(&pt(1.0, 1.0), &seg(0.0, 0.0, 2.0, 3.0));
let expected = 1.0_f64 / 13.0_f64.sqrt();
assert!((d - expected).abs() < 1e-12);
assert!((d - 0.277_352_039_583_27).abs() < 1e-5);
}
#[test]
fn proj_case_2() {
let d = PointToSegment::<Pythagoras>::default()
.distance(&pt(2.0, 2.0), &seg(1.0, 4.0, 4.0, 1.0));
assert!((d - 0.5 * 2.0_f64.sqrt()).abs() < 1e-12);
}
#[test]
fn proj_clamps_to_end() {
let d = PointToSegment::<Pythagoras>::default()
.distance(&pt(6.0, 1.0), &seg(1.0, 4.0, 4.0, 1.0));
assert!((d - 2.0).abs() < 1e-12);
}
#[test]
fn proj_clamps_to_start() {
let d = PointToSegment::<Pythagoras>::default()
.distance(&pt(1.0, 6.0), &seg(1.0, 4.0, 4.0, 1.0));
assert!((d - 2.0).abs() < 1e-12);
}
#[test]
fn perpendicular_distance() {
let d = PointToSegment::<Pythagoras>::default()
.distance(&pt(5.0, 7.0), &seg(0.0, 0.0, 10.0, 0.0));
assert!((d - 7.0).abs() < 1e-12);
}
#[test]
fn comparable_form_returns_squared_distance() {
let cmp = PointToSegment::<ComparablePythagoras>::default()
.distance(&pt(5.0, 7.0), &seg(0.0, 0.0, 10.0, 0.0));
assert!((cmp - 49.0).abs() < 1e-12);
}
#[test]
fn comparable_method_swaps_inner_strategy() {
type P = Point2D<f64, Cartesian>;
type S = Segment<P>;
let real = PointToSegment::<Pythagoras>::default();
let cmp = <PointToSegment<Pythagoras> as DistanceStrategy<P, S>>::comparable(&real);
let d = cmp.distance(&pt(5.0, 7.0), &seg(0.0, 0.0, 10.0, 0.0));
assert!((d - 49.0).abs() < 1e-12);
}
#[test]
fn degenerate_segment_falls_back_to_endpoint_distance() {
let d = PointToSegment::<Pythagoras>::default()
.distance(&pt(3.0, 4.0), &seg(0.0, 0.0, 0.0, 0.0));
assert!((d - 5.0).abs() < 1e-12);
}
}