use crate::{Point, Scalar, WeightedLine, WeightedPoint};
pub trait LinePlotter<K>: Sized {
fn new() -> Self;
fn move_to(self, x: K, y: K) -> Self;
fn line_to(self, x: K, y: K) -> Self;
fn close(self) -> Self;
}
pub trait ArcPlotter<K>: LinePlotter<K> {
fn arc_to(self, rx: K, ry: K, rot: K, large_arc: bool, sweep_clockwise: bool, x: K, y: K) -> Self;
}
#[derive(Clone, Copy, Debug)]
struct PlotterCtx<K: Scalar> {
segment_start: WeightedPoint<K>,
position: WeightedPoint<K>,
}
impl<K: Scalar> PlotterCtx<K> {
const fn new() -> Self {
Self {
segment_start: Point::ORIGIN.into_weighted(),
position: Point::ORIGIN.into_weighted(),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct KleinFrame<K: Scalar = f64> {
pub center_x: K,
pub center_y: K,
pub radius: K,
}
impl<K: Scalar> KleinFrame<K> {
pub fn path<P: LinePlotter<K>>(self) -> KleinPathBuilder<K, P> {
KleinPathBuilder { frame: self, ctx: PlotterCtx::new(), plotter: P::new() }
}
}
#[cfg(feature = "svg")]
impl<K> KleinFrame<K>
where K: Scalar + Into<svg::node::element::path::Parameters> {
pub fn path_svg(self) -> KleinPathBuilder<K, svg::node::element::path::Data> {
self.path()
}
}
#[cfg(feature = "svg")]
impl<K> KleinFrame<K>
where K: Scalar + Into<svg::node::Value> {
pub fn frame_svg(self) -> svg::node::element::Circle {
svg::node::element::Circle::new()
.set("cx", self.center_x)
.set("cy", self.center_y)
.set("r", self.radius)
}
}
#[cfg(feature = "svg_fmt")]
impl<K> KleinFrame<K>
where K: Scalar + Into<f32> {
pub fn path_svg_fmt(self) -> KleinPathBuilder<K, svg_fmt::Path> {
self.path()
}
pub fn frame_svg_fmt(self) -> svg_fmt::Circle {
svg_fmt::Circle {
x: self.center_x.into(),
y: self.center_y.into(),
radius: self.radius.into(),
style: svg_fmt::Style::default(),
comment: None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct KleinPathBuilder<K: Scalar, P: LinePlotter<K>> {
frame: KleinFrame<K>,
ctx: PlotterCtx<K>,
plotter: P,
}
impl<K: Scalar, P: LinePlotter<K>> KleinPathBuilder<K, P> {
pub fn move_to(mut self, p: WeightedPoint<K>) -> Self {
self.ctx.segment_start = p;
self.ctx.position = p;
let (kx, ky) = p.klein();
let kx = self.frame.center_x + self.frame.radius * kx;
let ky = self.frame.center_y - self.frame.radius * ky;
self.plotter = self.plotter.move_to(kx, ky);
self
}
pub fn line_to(mut self, p: WeightedPoint<K>) -> Self {
self.ctx.position = p;
let (kx, ky) = p.klein();
let kx = self.frame.center_x + self.frame.radius * kx;
let ky = self.frame.center_y - self.frame.radius * ky;
self.plotter = self.plotter.line_to(kx, ky);
self
}
pub fn close(mut self) -> Self {
self.ctx.position = self.ctx.segment_start;
self.plotter = self.plotter.close();
self
}
pub fn finish(self) -> P {
self.plotter
}
}
#[derive(Clone, Copy, Debug)]
pub struct PoincaréFrame<K: Scalar = f64> {
pub center_x: K,
pub center_y: K,
pub radius: K,
}
impl<K: Scalar> PoincaréFrame<K> {
pub fn path<P: LinePlotter<K>>(self) -> PoincaréPathBuilder<K, P> {
PoincaréPathBuilder { frame: self, ctx: PlotterCtx::new(), plotter: P::new() }
}
}
#[cfg(feature = "svg")]
impl<K> PoincaréFrame<K>
where K: Scalar + Into<svg::node::element::path::Parameters> {
pub fn path_svg(self) -> PoincaréPathBuilder<K, svg::node::element::path::Data> {
self.path()
}
}
#[cfg(feature = "svg")]
impl<K> PoincaréFrame<K>
where K: Scalar + Into<svg::node::Value> {
pub fn frame_svg(self) -> svg::node::element::Circle {
svg::node::element::Circle::new()
.set("cx", self.center_x)
.set("cy", self.center_y)
.set("r", self.radius)
}
}
#[cfg(feature = "svg_fmt")]
impl<K> PoincaréFrame<K>
where K: Scalar + Into<f32> {
pub fn path_svg_fmt(self) -> PoincaréPathBuilder<K, svg_fmt::Path> {
self.path()
}
pub fn frame_svg_fmt(self) -> svg_fmt::Circle {
svg_fmt::Circle {
x: self.center_x.into(),
y: self.center_y.into(),
radius: self.radius.into(),
style: svg_fmt::Style::default(),
comment: None,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct PoincaréPathBuilder<K: Scalar, P: LinePlotter<K>> {
frame: PoincaréFrame<K>,
ctx: PlotterCtx<K>,
plotter: P,
}
impl<K: Scalar, P: LinePlotter<K>> PoincaréPathBuilder<K, P> {
pub fn move_to(mut self, p: WeightedPoint<K>) -> Self {
self.ctx.segment_start = p;
self.ctx.position = p;
let (kx, ky) = p.poincaré();
let kx = self.frame.center_x + self.frame.radius * kx;
let ky = self.frame.center_y - self.frame.radius * ky;
self.plotter = self.plotter.move_to(kx, ky);
self
}
pub fn finish(self) -> P {
self.plotter
}
}
impl<K: Scalar, P: ArcPlotter<K>> PoincaréPathBuilder<K, P> {
pub fn line_to(mut self, p: WeightedPoint<K>) -> Self {
let (kx, ky) = p.poincaré();
let kx = self.frame.center_x + self.frame.radius * kx;
let ky = self.frame.center_y - self.frame.radius * ky;
let line = WeightedLine::between(self.ctx.position, p);
let r = line.poincaré_radius();
if r >= K::VERY_LARGE {
self.plotter = self.plotter.line_to(kx, ky);
} else {
let r = self.frame.radius * r;
self.plotter = self.plotter.arc_to(r, r, K::ZERO, false, line.t.is_sign_positive(), kx, ky);
}
self.ctx.position = p;
self
}
pub fn close(self) -> Self {
let start = self.ctx.segment_start;
self.line_to(start)
}
}
pub use PoincaréFrame as PoincareFrame;
pub use PoincaréPathBuilder as PoincarePathBuilder;