1#![no_std]
2
3extern crate alloc;
6
7mod bezier;
8mod error;
9mod linear;
10#[cfg(feature = "nurbs")]
11mod nurbs;
12mod orthogonal;
13#[cfg(feature = "spline")]
14mod spline;
15
16use alloc::vec::Vec;
17
18pub use error::RoutingError;
19
20const EPSILON: f64 = 1e-9;
21#[cfg(any(feature = "spline", feature = "nurbs"))]
22const FEATURE_HANDLE_RATIO: f64 = 0.25;
23
24#[derive(Debug, Clone, Copy, PartialEq)]
26pub enum RouteStyle {
27 Linear,
29 Bezier {
31 curvature: f64,
33 },
34 Orthogonal {
36 corner_radius: f64,
38 },
39 Spline,
41 Nurbs {
43 degree: u32,
45 },
46}
47
48#[derive(Debug, Clone, PartialEq)]
50pub struct RouteRequest {
51 pub from: (f64, f64),
53 pub to: (f64, f64),
55 pub from_tangent: (f64, f64),
57 pub to_tangent: (f64, f64),
59 pub style: RouteStyle,
61}
62
63#[derive(Debug, Clone, PartialEq)]
65pub struct PathData {
66 pub points: Vec<(f64, f64)>,
68 pub kind: PathKind,
70}
71
72#[derive(Debug, Clone, PartialEq)]
74pub enum PathKind {
75 Polyline,
77 Cubic,
79 Quadratic,
81 Nurbs {
83 knots: Vec<f64>,
85 weights: Vec<f64>,
87 },
88}
89
90pub fn route(req: &RouteRequest) -> Result<PathData, RoutingError> {
92 validate_request(req)?;
93 match req.style {
94 RouteStyle::Linear => Ok(linear::route(req)),
95 RouteStyle::Bezier { curvature } => Ok(bezier::route(req, curvature)),
96 RouteStyle::Orthogonal { corner_radius } => Ok(orthogonal::route(req, corner_radius)),
97 RouteStyle::Spline => {
98 #[cfg(feature = "spline")]
99 {
100 spline::route(req)
101 }
102 #[cfg(not(feature = "spline"))]
103 {
104 Err(RoutingError::FeatureDisabled { style: "Spline" })
105 }
106 }
107 RouteStyle::Nurbs { degree } => {
108 #[cfg(feature = "nurbs")]
109 {
110 nurbs::route(req, degree)
111 }
112 #[cfg(not(feature = "nurbs"))]
113 {
114 let _ = degree;
115 Err(RoutingError::FeatureDisabled { style: "Nurbs" })
116 }
117 }
118 }
119}
120
121fn validate_request(req: &RouteRequest) -> Result<(), RoutingError> {
122 for value in [
123 req.from.0,
124 req.from.1,
125 req.to.0,
126 req.to.1,
127 req.from_tangent.0,
128 req.from_tangent.1,
129 req.to_tangent.0,
130 req.to_tangent.1,
131 ] {
132 if !value.is_finite() {
133 return Err(RoutingError::InvalidInput {
134 reason: "route request contains non-finite coordinates or tangents",
135 });
136 }
137 }
138
139 match req.style {
140 RouteStyle::Bezier { curvature } if !curvature.is_finite() => {
141 Err(RoutingError::InvalidInput {
142 reason: "bezier curvature must be finite",
143 })
144 }
145 RouteStyle::Orthogonal { corner_radius } if !corner_radius.is_finite() => {
146 Err(RoutingError::InvalidInput {
147 reason: "orthogonal corner radius must be finite",
148 })
149 }
150 _ => Ok(()),
151 }
152}
153
154pub(crate) fn add(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
155 (a.0 + b.0, a.1 + b.1)
156}
157
158pub(crate) fn sub(a: (f64, f64), b: (f64, f64)) -> (f64, f64) {
159 (a.0 - b.0, a.1 - b.1)
160}
161
162pub(crate) fn scale(v: (f64, f64), factor: f64) -> (f64, f64) {
163 (v.0 * factor, v.1 * factor)
164}
165
166pub(crate) fn distance(a: (f64, f64), b: (f64, f64)) -> f64 {
167 let d = sub(b, a);
168 sqrt(d.0 * d.0 + d.1 * d.1)
169}
170
171pub(crate) fn length(v: (f64, f64)) -> f64 {
172 sqrt(v.0 * v.0 + v.1 * v.1)
173}
174
175fn sqrt(value: f64) -> f64 {
176 if value <= 0.0 {
177 return 0.0;
178 }
179
180 let mut x = if value >= 1.0 { value } else { 1.0 };
181 for _ in 0..16 {
182 x = 0.5 * (x + value / x);
183 }
184 x
185}
186
187pub(crate) fn is_degenerate_segment(a: (f64, f64), b: (f64, f64)) -> bool {
188 distance(a, b) <= EPSILON
189}
190
191pub(crate) fn is_zero_tangent(v: (f64, f64)) -> bool {
192 length(v) <= EPSILON
193}
194
195pub(crate) fn cubic_control_points(req: &RouteRequest, handle_scale: f64) -> [(f64, f64); 4] {
196 [
197 req.from,
198 add(req.from, scale(req.from_tangent, handle_scale)),
199 add(req.to, scale(req.to_tangent, handle_scale)),
200 req.to,
201 ]
202}
203
204#[cfg(any(feature = "spline", feature = "nurbs"))]
205pub(crate) fn feature_control_points(req: &RouteRequest) -> [(f64, f64); 4] {
206 let handle_scale = distance(req.from, req.to) * FEATURE_HANDLE_RATIO;
207 cubic_control_points(req, handle_scale)
208}