#![no_std]
extern crate alloc;
mod bezier;
mod error;
mod linear;
#[cfg(feature = "nurbs")]
mod nurbs;
mod orthogonal;
#[cfg(feature = "spline")]
mod spline;
use alloc::vec::Vec;
pub use error::RoutingError;
const EPSILON: f64 = 1e-9;
#[cfg(any(feature = "spline", feature = "nurbs"))]
const FEATURE_HANDLE_RATIO: f64 = 0.25;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RouteStyle {
Linear,
Bezier {
curvature: f64,
},
Orthogonal {
corner_radius: f64,
},
Spline,
Nurbs {
degree: u32,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct RouteRequest {
pub from: (f64, f64),
pub to: (f64, f64),
pub from_tangent: (f64, f64),
pub to_tangent: (f64, f64),
pub style: RouteStyle,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PathData {
pub points: Vec<(f64, f64)>,
pub kind: PathKind,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PathKind {
Polyline,
Cubic,
Quadratic,
Nurbs {
knots: Vec<f64>,
weights: Vec<f64>,
},
}
pub fn route(req: &RouteRequest) -> Result<PathData, RoutingError> {
validate_request(req)?;
match req.style {
RouteStyle::Linear => Ok(linear::route(req)),
RouteStyle::Bezier { curvature } => Ok(bezier::route(req, curvature)),
RouteStyle::Orthogonal { corner_radius } => Ok(orthogonal::route(req, corner_radius)),
RouteStyle::Spline => {
#[cfg(feature = "spline")]
{
spline::route(req)
}
#[cfg(not(feature = "spline"))]
{
Err(RoutingError::FeatureDisabled { style: "Spline" })
}
}
RouteStyle::Nurbs { degree } => {
#[cfg(feature = "nurbs")]
{
nurbs::route(req, degree)
}
#[cfg(not(feature = "nurbs"))]
{
let _ = degree;
Err(RoutingError::FeatureDisabled { style: "Nurbs" })
}
}
}
}
fn validate_request(req: &RouteRequest) -> Result<(), RoutingError> {
for value in [
req.from.0,
req.from.1,
req.to.0,
req.to.1,
req.from_tangent.0,
req.from_tangent.1,
req.to_tangent.0,
req.to_tangent.1,
] {
if !value.is_finite() {
return Err(RoutingError::InvalidInput {
reason: "route request contains non-finite coordinates or tangents",
});
}
}
match req.style {
RouteStyle::Bezier { curvature } if !curvature.is_finite() => {
Err(RoutingError::InvalidInput {
reason: "bezier curvature must be finite",
})
}
RouteStyle::Orthogonal { corner_radius } if !corner_radius.is_finite() => {
Err(RoutingError::InvalidInput {
reason: "orthogonal corner radius must be finite",
})
}
_ => Ok(()),
}
}
pub(crate) fn add(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
(a.0 + b.0, a.1 + b.1)
}
pub(crate) fn sub(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
(a.0 - b.0, a.1 - b.1)
}
pub(crate) fn scale(v: (f64, f64), factor: f64) -> (f64, f64) {
(v.0 * factor, v.1 * factor)
}
pub(crate) fn distance(a: (f64, f64), b: (f64, f64)) -> f64 {
let d = sub(b, a);
sqrt(d.0 * d.0 + d.1 * d.1)
}
pub(crate) fn length(v: (f64, f64)) -> f64 {
sqrt(v.0 * v.0 + v.1 * v.1)
}
fn sqrt(value: f64) -> f64 {
if value <= 0.0 {
return 0.0;
}
let mut x = if value >= 1.0 { value } else { 1.0 };
for _ in 0..16 {
x = 0.5 * (x + value / x);
}
x
}
pub(crate) fn is_degenerate_segment(a: (f64, f64), b: (f64, f64)) -> bool {
distance(a, b) <= EPSILON
}
pub(crate) fn is_zero_tangent(v: (f64, f64)) -> bool {
length(v) <= EPSILON
}
pub(crate) fn cubic_control_points(req: &RouteRequest, handle_scale: f64) -> [(f64, f64); 4] {
[
req.from,
add(req.from, scale(req.from_tangent, handle_scale)),
add(req.to, scale(req.to_tangent, handle_scale)),
req.to,
]
}
#[cfg(any(feature = "spline", feature = "nurbs"))]
pub(crate) fn feature_control_points(req: &RouteRequest) -> [(f64, f64); 4] {
let handle_scale = distance(req.from, req.to) * FEATURE_HANDLE_RATIO;
cubic_control_points(req, handle_scale)
}