hyperbolic_drawing 1.0.0

Manipulate and display elements of 2D hyperbolic space (ℍ²)
Documentation
//! Common elements for interfacing with either the [`svg`] or [`svg_fmt`] crates.

use crate::{Point, Scalar, WeightedLine, WeightedPoint};

/// A plotter which supports points and lines.
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;
}
/// A plotter which supports points, lines, and arcs.
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(),
		}
	}
}


/// A frame for plotting hyperbolic elements according to the Klein projection.
#[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> {
	/// Start a new path.
	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> {
	/// Start a new [`svg::node::element::path::Data`] path.
	/// 
	/// Provided only for convenience of type resolution.
	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> {
	/// The circular frame surrounding this projection, as a [`svg::node::element::Circle`].
	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> {
	/// Start a new [`svg_fmt::Path`] path.
	/// 
	/// Provided only for convenience of type resolution.
	pub fn path_svg_fmt(self) -> KleinPathBuilder<K, svg_fmt::Path> {
		self.path()
	}
	/// The circular frame surrounding this projection, as an [`svg_fmt::Circle`].
	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,
		}
	}
}
/// In-progress path on a [`KleinFrame`].
#[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> {
	/// Lift the pen and move to the given point.
	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
	}
	/// Draw a line to the given point.
	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
	}
	/// Close the current shape (draw a line to the start).
	pub fn close(mut self) -> Self {
		self.ctx.position = self.ctx.segment_start;
		self.plotter = self.plotter.close();
		self
	}
	/// Return the accumulated path.
	pub fn finish(self) -> P {
		self.plotter
	}
}
/*
impl<K: Scalar, P: ArcPlotter<K>> KleinPathBuilder<K, P> {
	pub fn arc_to(self) -> Self {
		todo!()
	}
}
*/

/// A frame for plotting hyperbolic elements according to the Klein projection.
#[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> {
	/// Start a new path.
	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> {
	/// Start a new [`svg::node::element::path::Data`] path.
	/// 
	/// Provided only for convenience of type resolution.
	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> {
	/// The circular frame surrounding this projection, as a [`svg::node::element::Circle`].
	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> {
	/// Start a new [`svg_fmt::Path`] path.
	/// 
	/// Provided only for convenience of type resolution.
	pub fn path_svg_fmt(self) -> PoincaréPathBuilder<K, svg_fmt::Path> {
		self.path()
	}
	/// The circular frame surrounding this projection, as an [`svg_fmt::Circle`].
	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,
		}
	}
}
/// In-progress path on a [`PoincaréFrame`].
#[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> {
	/// Lift the pen and move to the given point.
	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
	}
	/// Return the accumulated path.
	pub fn finish(self) -> P {
		self.plotter
	}
}
impl<K: Scalar, P: ArcPlotter<K>> PoincaréPathBuilder<K, P> {
	/// Draw a line to the given point.
	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
	}
	/// Close the current shape (draw a line to the start).
	pub fn close(self) -> Self {
		let start = self.ctx.segment_start;
		self.line_to(start)
	}
	/*
	pub fn arc_to(self) -> Self {
		todo!()
	}
	*/
}

pub use PoincaréFrame as PoincareFrame;
pub use PoincaréPathBuilder as PoincarePathBuilder;