use hyperreal::{Real, ZeroKnowledge as ZeroStatus};
use crate::classify::is_zero;
use crate::{Aabb2, Classification, CurvePolicy, Point2};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BezierEndpoint {
Start,
End,
}
#[derive(Clone, Debug, PartialEq)]
pub struct EndpointTangent2 {
dx: Real,
dy: Real,
zero_status: ZeroStatus,
}
impl EndpointTangent2 {
pub fn new(dx: Real, dy: Real) -> Self {
let length_squared = &dx * &dx + &dy * &dy;
let zero_status = length_squared.zero_status();
Self {
dx,
dy,
zero_status,
}
}
pub const fn dx(&self) -> &Real {
&self.dx
}
pub const fn dy(&self) -> &Real {
&self.dy
}
pub const fn zero_status(&self) -> ZeroStatus {
self.zero_status
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct QuadraticBezier2 {
start: Point2,
control: Point2,
end: Point2,
}
impl QuadraticBezier2 {
pub const fn new(start: Point2, control: Point2, end: Point2) -> Self {
Self {
start,
control,
end,
}
}
pub const fn start(&self) -> &Point2 {
&self.start
}
pub const fn control(&self) -> &Point2 {
&self.control
}
pub const fn end(&self) -> &Point2 {
&self.end
}
pub fn control_points(&self) -> [&Point2; 3] {
[&self.start, &self.control, &self.end]
}
pub fn point_at(&self, t: Real) -> Point2 {
let left = self.start.lerp(&self.control, t.clone());
let right = self.control.lerp(&self.end, t.clone());
left.lerp(&right, t)
}
pub fn contains_point_at_parameter(
&self,
point: &Point2,
t: Real,
policy: &CurvePolicy,
) -> Classification<bool> {
point_equals_at_parameter(self.point_at(t), point, policy)
}
pub fn control_hull_box(&self, policy: &CurvePolicy) -> Classification<Aabb2> {
Aabb2::from_points(self.control_points(), policy)
}
pub fn endpoints_coincident_status(&self) -> ZeroStatus {
self.start.distance_squared(&self.end).zero_status()
}
pub fn endpoint_tangent(&self, endpoint: BezierEndpoint) -> EndpointTangent2 {
let two = Real::from(2_i8);
let (dx, dy) = match endpoint {
BezierEndpoint::Start => self.control.delta_from(&self.start),
BezierEndpoint::End => self.end.delta_from(&self.control),
};
EndpointTangent2::new(&two * dx, &two * dy)
}
pub fn structural_facts(&self) -> crate::Bezier2Facts {
crate::facts::quadratic_bezier_facts(self)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CubicBezier2 {
start: Point2,
control1: Point2,
control2: Point2,
end: Point2,
}
impl CubicBezier2 {
pub const fn new(start: Point2, control1: Point2, control2: Point2, end: Point2) -> Self {
Self {
start,
control1,
control2,
end,
}
}
pub const fn start(&self) -> &Point2 {
&self.start
}
pub const fn control1(&self) -> &Point2 {
&self.control1
}
pub const fn control2(&self) -> &Point2 {
&self.control2
}
pub const fn end(&self) -> &Point2 {
&self.end
}
pub fn control_points(&self) -> [&Point2; 4] {
[&self.start, &self.control1, &self.control2, &self.end]
}
pub fn point_at(&self, t: Real) -> Point2 {
let p01 = self.start.lerp(&self.control1, t.clone());
let p12 = self.control1.lerp(&self.control2, t.clone());
let p23 = self.control2.lerp(&self.end, t.clone());
let p012 = p01.lerp(&p12, t.clone());
let p123 = p12.lerp(&p23, t.clone());
p012.lerp(&p123, t)
}
pub fn contains_point_at_parameter(
&self,
point: &Point2,
t: Real,
policy: &CurvePolicy,
) -> Classification<bool> {
point_equals_at_parameter(self.point_at(t), point, policy)
}
pub fn control_hull_box(&self, policy: &CurvePolicy) -> Classification<Aabb2> {
Aabb2::from_points(self.control_points(), policy)
}
pub fn endpoints_coincident_status(&self) -> ZeroStatus {
self.start.distance_squared(&self.end).zero_status()
}
pub fn endpoint_tangent(&self, endpoint: BezierEndpoint) -> EndpointTangent2 {
let three = Real::from(3_i8);
let (dx, dy) = match endpoint {
BezierEndpoint::Start => self.control1.delta_from(&self.start),
BezierEndpoint::End => self.end.delta_from(&self.control2),
};
EndpointTangent2::new(&three * dx, &three * dy)
}
pub fn structural_facts(&self) -> crate::Bezier2Facts {
crate::facts::cubic_bezier_facts(self)
}
}
fn point_equals_at_parameter(
curve_point: Point2,
point: &Point2,
policy: &CurvePolicy,
) -> Classification<bool> {
let distance_squared = curve_point.distance_squared(point);
is_zero(&distance_squared, policy)
.map(Classification::Decided)
.unwrap_or(Classification::Uncertain(
crate::UncertaintyReason::Ordering,
))
}