use euclid::{approxeq::ApproxEq, Point2D, Rotation2D, UnknownUnit};
use thiserror::Error;
pub type Angle = euclid::Angle<f64>;
pub type Point = Point2D<f64, UnknownUnit>;
pub type Vector = euclid::Vector2D<f64, UnknownUnit>;
type Rotation = Rotation2D<f64, UnknownUnit, UnknownUnit>;
#[derive(Debug, Error)]
pub enum Error {
#[error("inside tangent cannot be constructed (circles too close together)")]
CirclesTooClose,
#[error("ccc path cannot be constructed (circles too far apart)")]
CirclesTooFarApart,
}
#[derive(Debug, Copy, Clone)]
pub struct StraightPath {
pub origin: Point,
pub vector: Vector,
}
impl StraightPath {
pub fn approx_eq(&self, other: Self) -> bool {
ApproxEq::approx_eq(&self.vector, &other.vector)
&& ApproxEq::approx_eq(&self.origin, &other.origin)
}
}
#[derive(Debug, Copy, Clone)]
pub struct CirclePath {
pub center: Point,
pub radius: f64,
pub angle: Angle,
}
impl CirclePath {
pub fn get_length(&self) -> f64 {
self.angle.radians * self.radius
}
pub fn approx_eq(&self, other: Self) -> bool {
if !ApproxEq::approx_eq(&self.center, &other.center) {
false
} else if !ApproxEq::approx_eq(&self.radius, &other.radius) {
false
} else if !(ApproxEq::approx_eq(&self.angle, &other.angle)
|| ApproxEq::approx_eq(&self.angle.signed(), &other.angle.signed()))
{
false
} else {
true
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct RouteCSC {
pub start: CirclePath,
pub tangent: StraightPath,
pub end: CirclePath,
}
#[derive(Debug, Copy, Clone)]
pub struct RouteCCC {
pub start: CirclePath,
pub middle: CirclePath,
pub end: CirclePath,
}
#[derive(Debug, Copy, Clone)]
pub enum Path {
CSC(RouteCSC),
CCC(RouteCCC),
}
impl RouteCSC {
pub fn rsr(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let start_center = Point::new(radius, 0.0);
let end_center = end_point
+ Rotation::new(end_angle)
.inverse()
.transform_vector(Vector::new(radius, 0.0));
let mut tangent_angle = Angle::radians(
((end_center.y - start_center.y) / (end_center.x - start_center.x)).atan(),
);
if end_center.x < start_center.x {
tangent_angle += Angle::pi();
}
let tangent_magnitude = ((end_center.x - start_center.x).powi(2)
+ (end_center.y - start_center.y).powi(2))
.sqrt();
let start_angle = (Angle::frac_pi_2() - tangent_angle).positive();
let tangent_origin = start_center
+ Rotation::new(Angle::pi() - end_angle).transform_vector(Vector::new(radius, 0.0));
let end_angle = (end_angle - start_angle).positive();
Ok(Self {
start: CirclePath {
center: start_center,
radius: radius,
angle: start_angle,
},
tangent: StraightPath {
origin: tangent_origin,
vector: Vector::from_angle_and_length(tangent_angle, tangent_magnitude),
},
end: CirclePath {
center: end_center,
radius: radius,
angle: end_angle,
},
})
}
pub fn lsl(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let start_center = Point::new(-radius, 0.0);
let end_center = end_point
+ Rotation::new(Angle::pi() - end_angle).transform_vector(Vector::new(radius, 0.0));
let mut tangent_angle = Angle::radians(
((end_center.y - start_center.y) / (end_center.x - start_center.x)).atan(),
)
.positive();
if end_center.x < start_center.x {
tangent_angle = (tangent_angle + Angle::pi()).positive();
}
let tangent_magnitude = ((end_center.x - start_center.x).abs().powi(2)
+ (end_center.y - start_center.y).abs().powi(2))
.sqrt();
let start_angle = (tangent_angle - Angle::frac_pi_2()).positive();
let tangent_origin =
start_center + Rotation::new(start_angle).transform_vector(Vector::new(radius, 0.0));
let end_angle = (end_angle - start_angle).positive();
Ok(Self {
start: CirclePath {
center: start_center,
radius: radius,
angle: start_angle,
},
tangent: StraightPath {
origin: tangent_origin,
vector: Vector::from_angle_and_length(tangent_angle, tangent_magnitude),
},
end: CirclePath {
center: end_center,
radius: radius,
angle: end_angle,
},
})
}
pub fn rsl(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let start_center = Point::new(radius, 0.0);
let end_center = end_point
+ Rotation::new(Angle::pi() - end_angle).transform_vector(Vector::new(radius, 0.0));
if ((end_center.x - start_center.x).powi(2) + (end_center.y - start_center.y).powi(2))
.sqrt()
< 2.0 * radius
{
return Err(Error::CirclesTooClose);
}
let tangent_magnitude = ((end_center.x - start_center.x).powi(2)
+ (end_center.y - start_center.y).powi(2)
- (2.0 * radius).powi(2))
.sqrt();
let tangent_middle = end_center.lerp(start_center, 0.5);
let mut tangent_angle = Angle::radians(
((end_center.y - tangent_middle.y) / (end_center.x - tangent_middle.x)).atan()
- (2.0 * radius / tangent_magnitude).atan(),
);
if end_center.x < start_center.x {
tangent_angle += Angle::pi();
}
let start_angle = (Angle::frac_pi_2() - tangent_angle).positive();
let tangent_origin = start_center
+ Rotation::new(Angle::pi() - start_angle).transform_vector(Vector::new(radius, 0.0));
let end_angle = ((Angle::frac_pi_2() - end_angle) - tangent_angle).positive();
Ok(Self {
start: CirclePath {
center: start_center,
radius: radius,
angle: start_angle,
},
tangent: StraightPath {
origin: tangent_origin,
vector: Vector::from_angle_and_length(tangent_angle, tangent_magnitude),
},
end: CirclePath {
center: end_center,
radius: radius,
angle: end_angle,
},
})
}
pub fn lsr(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let start_center = Point::new(-radius, 0.0);
let end_center = end_point
+ Rotation::new(end_angle)
.inverse()
.transform_vector(Vector::new(radius, 0.0));
if ((end_center.x - start_center.x).powi(2) + (end_center.y - start_center.y).powi(2))
.sqrt()
< 2.0 * radius
{
return Err(Error::CirclesTooClose);
}
let tangent_magnitude = ((end_center.x - start_center.x).powi(2)
+ (end_center.y - start_center.y).powi(2)
- (2.0 * radius).powi(2))
.sqrt();
let tangent_middle = end_center.lerp(start_center, 0.5);
let mut tangent_angle = Angle::radians(
((end_center.y - tangent_middle.y) / (end_center.x - tangent_middle.x)).atan()
+ (2.0 * radius / tangent_magnitude).atan(),
);
if end_center.x < start_center.x {
tangent_angle += Angle::pi();
}
let start_angle = (tangent_angle - Angle::frac_pi_2()).positive();
let tangent_origin =
start_center + Rotation::new(start_angle).transform_vector(Vector::new(radius, 0.0));
let end_angle = ((Angle::frac_pi_2() - end_angle) - tangent_angle).positive();
Ok(Self {
start: CirclePath {
center: start_center,
radius: radius,
angle: start_angle,
},
tangent: StraightPath {
origin: tangent_origin,
vector: Vector::from_angle_and_length(tangent_angle, tangent_magnitude),
},
end: CirclePath {
center: end_center,
radius: radius,
angle: end_angle,
},
})
}
pub fn get_length(&self) -> f64 {
self.start.get_length() + self.tangent.vector.length() + self.end.get_length()
}
pub fn get_shortest(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let mut route_csc;
let route_rsr = Self::rsr(radius, end_point, end_angle).unwrap();
let route_lsl = Self::rsr(radius, end_point, end_angle).unwrap();
let route_lsr = Self::rsr(radius, end_point, end_angle);
let route_rsl = Self::rsr(radius, end_point, end_angle);
route_csc = route_rsr;
if route_lsl.get_length() < route_csc.get_length() {
route_csc = route_lsl;
}
if let Ok(route_lsr) = route_lsr {
if route_lsr.get_length() < route_csc.get_length() {
route_csc = route_lsr;
}
}
if let Ok(route_rsl) = route_rsl {
if route_rsl.get_length() < route_csc.get_length() {
route_csc = route_rsl;
}
}
Ok(route_csc)
}
}
impl RouteCCC {
pub fn rlr(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let start_center = Point::new(radius, 0.0);
let end_center = end_point
+ Rotation::new(end_angle)
.inverse()
.transform_vector(Vector::new(radius, 0.0));
if ((end_center.x - start_center.x).powi(2) + (end_center.y - start_center.y).powi(2))
.sqrt()
> (4.0 * radius)
{
return Err(Error::CirclesTooFarApart);
}
let vector_start_center_middle_center: Vector;
let middle_center = {
let vector_start_center_end_center =
Vector::new(end_center.x - start_center.x, end_center.y - start_center.y);
let vector_start_center_middle_center_angle =
vector_start_center_end_center.angle_from_x_axis().radians
+ (vector_start_center_end_center.length() / (4.0 * radius)).acos();
vector_start_center_middle_center = Vector::new(
(2.0 * radius) * vector_start_center_middle_center_angle.cos(),
(2.0 * radius) * vector_start_center_middle_center_angle.sin(),
);
Point::new(
start_center.x + vector_start_center_middle_center.x,
start_center.y + vector_start_center_middle_center.y,
)
};
let vector_middle_center_end_center = Vector::new(
end_center.x - middle_center.x,
end_center.y - middle_center.y,
);
let start_angle =
(Angle::pi() - vector_start_center_middle_center.angle_from_x_axis()).positive();
let middle_angle = Rotation::new(Angle::pi())
.transform_vector(vector_start_center_middle_center)
.angle_to(vector_middle_center_end_center)
.positive();
let end_angle = Rotation::new(Angle::pi())
.transform_vector(vector_middle_center_end_center)
.angle_to(Vector::new(
end_point.x - end_center.x,
end_point.y - end_center.y,
))
.positive();
Ok(Self {
start: CirclePath {
center: start_center,
radius: radius,
angle: start_angle,
},
middle: CirclePath {
center: middle_center,
radius: radius,
angle: middle_angle,
},
end: CirclePath {
center: end_center,
radius: radius,
angle: end_angle,
},
})
}
pub fn lrl(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let start_center = Point::new(-radius, 0.0);
let end_center = end_point
+ Rotation::new(Angle::pi() - end_angle).transform_vector(Vector::new(radius, 0.0));
if ((end_center.x - start_center.x).powi(2) + (end_center.y - start_center.y).powi(2))
.sqrt()
> (4.0 * radius)
{
return Err(Error::CirclesTooFarApart);
}
let vector_start_center_middle_center: Vector;
let middle_center = {
let vector_start_center_end_center =
Vector::new(end_center.x - start_center.x, end_center.y - start_center.y);
let vector_start_center_middle_center_angle =
vector_start_center_end_center.angle_from_x_axis().radians
- (vector_start_center_end_center.length() / (4.0 * radius)).acos();
vector_start_center_middle_center = Vector::new(
(2.0 * radius) * vector_start_center_middle_center_angle.cos(),
(2.0 * radius) * vector_start_center_middle_center_angle.sin(),
);
Point::new(
start_center.x + vector_start_center_middle_center.x,
start_center.y + vector_start_center_middle_center.y,
)
};
let vector_middle_center_end_center = Vector::new(
end_center.x - middle_center.x,
end_center.y - middle_center.y,
);
let start_angle = (vector_start_center_middle_center.angle_from_x_axis()).positive();
let middle_angle = vector_middle_center_end_center
.angle_to(
Rotation::new(Angle::pi()).transform_vector(vector_start_center_middle_center),
)
.positive();
let end_angle = Vector::new(end_point.x - end_center.x, end_point.y - end_center.y)
.angle_to(Rotation::new(Angle::pi()).transform_vector(vector_middle_center_end_center))
.positive();
Ok(Self {
start: CirclePath {
center: start_center,
radius: radius,
angle: start_angle,
},
middle: CirclePath {
center: middle_center,
radius: radius,
angle: middle_angle,
},
end: CirclePath {
center: end_center,
radius: radius,
angle: end_angle,
},
})
}
pub fn get_length(&self) -> f64 {
self.start.get_length() + self.middle.get_length() + self.end.get_length()
}
pub fn get_shortest(radius: f64, end_point: Point, end_angle: Angle) -> Result<Self, Error> {
let route_rlr = Self::rlr(radius, end_point, end_angle);
let route_lrl = Self::lrl(radius, end_point, end_angle);
if let Ok(route_rlr) = route_rlr {
if let Ok(route_lrl) = route_lrl {
if route_rlr.get_length() < route_lrl.get_length() {
Ok(route_rlr)
} else {
Ok(route_lrl)
}
} else {
Ok(route_rlr)
}
} else if let Ok(route_lrl) = route_lrl {
Ok(route_lrl)
} else {
Err(Error::CirclesTooFarApart)
}
}
}
pub fn get_shortest(radius: f64, end_point: Point, end_angle: Angle) -> Path {
let route_csc = RouteCSC::get_shortest(radius, end_point, end_angle).unwrap();
let route_ccc = RouteCCC::get_shortest(radius, end_point, end_angle);
if let Ok(route_ccc) = route_ccc {
if route_ccc.get_length() < route_csc.get_length() {
Path::CCC(route_ccc)
} else {
Path::CSC(route_csc)
}
} else {
Path::CSC(route_csc)
}
}