use crate::{
error::ERROR_MARGIN,
safe_float::SafeFloat,
utils::{motor2d_to_mat3, point_to_vec, vec_to_point, weighted_vec_to_point},
};
use geometric_algebra::{ppga2d, InnerProduct, RegressiveProduct, Signum, SquaredMagnitude, Zero};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LineSegment {
pub control_points: [SafeFloat<f32, 2>; 1],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IntegralQuadraticCurveSegment {
pub control_points: [SafeFloat<f32, 2>; 2],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct IntegralCubicCurveSegment {
pub control_points: [SafeFloat<f32, 2>; 3],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RationalQuadraticCurveSegment {
pub weight: SafeFloat<f32, 1>,
pub control_points: [SafeFloat<f32, 2>; 2],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RationalCubicCurveSegment {
pub weights: SafeFloat<f32, 4>,
pub control_points: [SafeFloat<f32, 2>; 3],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SegmentType {
Line,
IntegralQuadraticCurve,
IntegralCubicCurve,
RationalQuadraticCurve,
RationalCubicCurve,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Join {
Miter,
Bevel,
Round,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Cap {
Square,
Round,
Out,
In,
Right,
Left,
Butt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DashInterval {
pub gap_start: SafeFloat<f32, 1>,
pub gap_end: SafeFloat<f32, 1>,
pub dash_start: Cap,
pub dash_end: Cap,
}
pub const MAX_DASH_INTERVALS: usize = 4;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DynamicStrokeOptions {
Dashed {
join: Join,
pattern: Vec<DashInterval>,
phase: SafeFloat<f32, 1>,
},
Solid {
join: Join,
start: Cap,
end: Cap,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CurveApproximation {
UniformlySpacedParameters(usize),
UniformTangentAngle(SafeFloat<f32, 1>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct StrokeOptions {
pub width: SafeFloat<f32, 1>,
pub offset: SafeFloat<f32, 1>,
pub miter_clip: SafeFloat<f32, 1>,
pub closed: bool,
pub dynamic_stroke_options_group: usize,
pub curve_approximation: CurveApproximation,
}
impl StrokeOptions {
pub fn legalize(&mut self) {
self.width = self.width.unwrap().abs().into();
self.offset = self.offset.unwrap().clamp(-0.5, 0.5).into();
self.miter_clip = self.miter_clip.unwrap().abs().into();
}
}
fn tangent_from_points(a: [f32; 2], b: [f32; 2]) -> ppga2d::Plane {
vec_to_point(a).regressive_product(vec_to_point(b))
}
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct Path {
pub stroke_options: Option<StrokeOptions>,
pub start: SafeFloat<f32, 2>,
pub line_segments: Vec<LineSegment>,
pub integral_quadratic_curve_segments: Vec<IntegralQuadraticCurveSegment>,
pub integral_cubic_curve_segments: Vec<IntegralCubicCurveSegment>,
pub rational_quadratic_curve_segments: Vec<RationalQuadraticCurveSegment>,
pub rational_cubic_curve_segments: Vec<RationalCubicCurveSegment>,
pub segment_types: Vec<SegmentType>,
}
impl Path {
pub fn push_line(&mut self, segment: LineSegment) {
self.line_segments.push(segment);
self.segment_types.push(SegmentType::Line);
}
pub fn push_integral_quadratic_curve(&mut self, segment: IntegralQuadraticCurveSegment) {
self.integral_quadratic_curve_segments.push(segment);
self.segment_types.push(SegmentType::IntegralQuadraticCurve);
}
pub fn push_integral_cubic_curve(&mut self, segment: IntegralCubicCurveSegment) {
self.integral_cubic_curve_segments.push(segment);
self.segment_types.push(SegmentType::IntegralCubicCurve);
}
pub fn push_rational_quadratic_curve(&mut self, segment: RationalQuadraticCurveSegment) {
self.rational_quadratic_curve_segments.push(segment);
self.segment_types.push(SegmentType::RationalQuadraticCurve);
}
pub fn push_rational_cubic_curve(&mut self, segment: RationalCubicCurveSegment) {
self.rational_cubic_curve_segments.push(segment);
self.segment_types.push(SegmentType::RationalCubicCurve);
}
pub fn get_end(&self) -> [f32; 2] {
match self.segment_types.last() {
Some(SegmentType::Line) => {
let segment = self.line_segments.last().unwrap();
segment.control_points[0].unwrap()
}
Some(SegmentType::IntegralQuadraticCurve) => {
let segment = self.integral_quadratic_curve_segments.last().unwrap();
segment.control_points[1].unwrap()
}
Some(SegmentType::IntegralCubicCurve) => {
let segment = self.integral_cubic_curve_segments.last().unwrap();
segment.control_points[2].unwrap()
}
Some(SegmentType::RationalQuadraticCurve) => {
let segment = self.rational_quadratic_curve_segments.last().unwrap();
segment.control_points[1].unwrap()
}
Some(SegmentType::RationalCubicCurve) => {
let segment = self.rational_cubic_curve_segments.last().unwrap();
segment.control_points[2].unwrap()
}
None => self.start.unwrap(),
}
}
pub fn get_start_tangent(&self) -> ppga2d::Plane {
match self.segment_types.last() {
Some(SegmentType::Line) => {
let segment = self.line_segments.last().unwrap();
tangent_from_points(self.start.unwrap(), segment.control_points[0].unwrap()).signum()
}
Some(SegmentType::IntegralQuadraticCurve) => {
let segment = self.integral_quadratic_curve_segments.last().unwrap();
tangent_from_points(self.start.unwrap(), segment.control_points[0].unwrap()).signum()
}
Some(SegmentType::IntegralCubicCurve) => {
let segment = self.integral_cubic_curve_segments.last().unwrap();
tangent_from_points(self.start.unwrap(), segment.control_points[0].unwrap()).signum()
}
Some(SegmentType::RationalQuadraticCurve) => {
let segment = self.rational_quadratic_curve_segments.last().unwrap();
tangent_from_points(self.start.unwrap(), segment.control_points[0].unwrap()).signum()
}
Some(SegmentType::RationalCubicCurve) => {
let segment = self.rational_cubic_curve_segments.last().unwrap();
tangent_from_points(self.start.unwrap(), segment.control_points[0].unwrap()).signum()
}
None => ppga2d::Plane::zero(),
}
}
pub fn get_end_tangent(&self) -> ppga2d::Plane {
match self.segment_types.last() {
Some(SegmentType::Line) => {
let previous_point = match self.segment_types.iter().rev().nth(1) {
Some(SegmentType::Line) => {
let segment = self.line_segments.iter().rev().nth(1).unwrap();
segment.control_points[0].unwrap()
}
Some(SegmentType::IntegralQuadraticCurve) => {
let segment = self.integral_quadratic_curve_segments.last().unwrap();
segment.control_points[1].unwrap()
}
Some(SegmentType::IntegralCubicCurve) => {
let segment = self.integral_cubic_curve_segments.last().unwrap();
segment.control_points[2].unwrap()
}
Some(SegmentType::RationalQuadraticCurve) => {
let segment = self.rational_quadratic_curve_segments.last().unwrap();
segment.control_points[1].unwrap()
}
Some(SegmentType::RationalCubicCurve) => {
let segment = self.rational_cubic_curve_segments.last().unwrap();
segment.control_points[2].unwrap()
}
None => self.start.unwrap(),
};
let segment = self.line_segments.last().unwrap();
tangent_from_points(previous_point, segment.control_points[0].unwrap()).signum()
}
Some(SegmentType::IntegralQuadraticCurve) => {
let segment = self.integral_quadratic_curve_segments.last().unwrap();
tangent_from_points(segment.control_points[0].unwrap(), segment.control_points[1].unwrap()).signum()
}
Some(SegmentType::IntegralCubicCurve) => {
let segment = self.integral_cubic_curve_segments.last().unwrap();
tangent_from_points(segment.control_points[1].unwrap(), segment.control_points[2].unwrap()).signum()
}
Some(SegmentType::RationalQuadraticCurve) => {
let segment = self.rational_quadratic_curve_segments.last().unwrap();
tangent_from_points(segment.control_points[0].unwrap(), segment.control_points[1].unwrap()).signum()
}
Some(SegmentType::RationalCubicCurve) => {
let segment = self.rational_cubic_curve_segments.last().unwrap();
tangent_from_points(segment.control_points[1].unwrap(), segment.control_points[2].unwrap()).signum()
}
None => ppga2d::Plane::zero(),
}
}
pub fn append(&mut self, other: &mut Self) {
self.line_segments.append(&mut other.line_segments);
self.integral_quadratic_curve_segments
.append(&mut other.integral_quadratic_curve_segments);
self.integral_cubic_curve_segments.append(&mut other.integral_cubic_curve_segments);
self.rational_quadratic_curve_segments
.append(&mut other.rational_quadratic_curve_segments);
self.rational_cubic_curve_segments.append(&mut other.rational_cubic_curve_segments);
}
pub fn transform(&mut self, scalator: &ppga2d::Scalar, motor: &ppga2d::Motor) {
let mut transform = motor2d_to_mat3(motor);
transform[0].g0[0] *= scalator.g0;
transform[1].g0[1] *= scalator.g0;
fn transform_point(transform: &[ppga2d::Point; 3], p: SafeFloat<f32, 2>) -> SafeFloat<f32, 2> {
let p = p.unwrap();
[
transform[2].g0[0] + p[0] * transform[0].g0[0] + p[1] * transform[1].g0[0],
transform[2].g0[1] + p[0] * transform[0].g0[1] + p[1] * transform[1].g0[1],
]
.into()
}
self.start = transform_point(&transform, self.start);
let mut line_segment_iter = self.line_segments.iter_mut();
let mut integral_quadratic_curve_segment_iter = self.integral_quadratic_curve_segments.iter_mut();
let mut integral_cubic_curve_segment_iter = self.integral_cubic_curve_segments.iter_mut();
let mut rational_quadratic_curve_segment_iter = self.rational_quadratic_curve_segments.iter_mut();
let mut rational_cubic_curve_segment_iter = self.rational_cubic_curve_segments.iter_mut();
for segment_type in &mut self.segment_types {
match *segment_type {
SegmentType::Line => {
let segment = line_segment_iter.next().unwrap();
for control_point in &mut segment.control_points {
*control_point = transform_point(&transform, *control_point);
}
}
SegmentType::IntegralQuadraticCurve => {
let segment = integral_quadratic_curve_segment_iter.next().unwrap();
for control_point in &mut segment.control_points {
*control_point = transform_point(&transform, *control_point);
}
}
SegmentType::IntegralCubicCurve => {
let segment = integral_cubic_curve_segment_iter.next().unwrap();
for control_point in &mut segment.control_points {
*control_point = transform_point(&transform, *control_point);
}
}
SegmentType::RationalQuadraticCurve => {
let segment = rational_quadratic_curve_segment_iter.next().unwrap();
for control_point in &mut segment.control_points {
*control_point = transform_point(&transform, *control_point);
}
}
SegmentType::RationalCubicCurve => {
let segment = rational_cubic_curve_segment_iter.next().unwrap();
for control_point in &mut segment.control_points {
*control_point = transform_point(&transform, *control_point);
}
}
}
}
}
pub fn reverse(&mut self) {
let mut previous_control_point = self.start;
let mut line_segment_iter = self.line_segments.iter_mut();
let mut integral_quadratic_curve_segment_iter = self.integral_quadratic_curve_segments.iter_mut();
let mut integral_cubic_curve_segment_iter = self.integral_cubic_curve_segments.iter_mut();
let mut rational_quadratic_curve_segment_iter = self.rational_quadratic_curve_segments.iter_mut();
let mut rational_cubic_curve_segment_iter = self.rational_cubic_curve_segments.iter_mut();
for segment_type in &mut self.segment_types {
match *segment_type {
SegmentType::Line => {
let segment = line_segment_iter.next().unwrap();
std::mem::swap(&mut previous_control_point, &mut segment.control_points[0]);
}
SegmentType::IntegralQuadraticCurve => {
let segment = integral_quadratic_curve_segment_iter.next().unwrap();
std::mem::swap(&mut previous_control_point, &mut segment.control_points[1]);
}
SegmentType::IntegralCubicCurve => {
let segment = integral_cubic_curve_segment_iter.next().unwrap();
segment.control_points.swap(0, 1);
std::mem::swap(&mut previous_control_point, &mut segment.control_points[2]);
}
SegmentType::RationalQuadraticCurve => {
let segment = rational_quadratic_curve_segment_iter.next().unwrap();
std::mem::swap(&mut previous_control_point, &mut segment.control_points[1]);
}
SegmentType::RationalCubicCurve => {
let segment = rational_cubic_curve_segment_iter.next().unwrap();
let mut weights = segment.weights.unwrap();
weights.reverse();
segment.weights = weights.into();
segment.control_points.swap(0, 1);
std::mem::swap(&mut previous_control_point, &mut segment.control_points[2]);
}
}
}
self.start = previous_control_point;
self.segment_types.reverse();
self.line_segments.reverse();
self.integral_quadratic_curve_segments.reverse();
self.integral_cubic_curve_segments.reverse();
self.rational_quadratic_curve_segments.reverse();
self.rational_cubic_curve_segments.reverse();
}
pub fn convert_integral_curves_to_rational_curves(&mut self) {
let mut integral_quadratic_curve_segment_iter = self.integral_quadratic_curve_segments.iter();
let mut integral_cubic_curve_segment_iter = self.integral_cubic_curve_segments.iter();
let mut rational_quadratic_curve_segment_index = 0;
let mut rational_cubic_curve_segment_index = 0;
for segment_type in &mut self.segment_types {
match *segment_type {
SegmentType::Line => {}
SegmentType::IntegralQuadraticCurve => {
let segment = integral_quadratic_curve_segment_iter.next().unwrap();
self.rational_quadratic_curve_segments.insert(
rational_quadratic_curve_segment_index,
RationalQuadraticCurveSegment {
weight: 1.0.into(),
control_points: segment.control_points,
},
);
rational_quadratic_curve_segment_index += 1;
*segment_type = SegmentType::RationalQuadraticCurve;
}
SegmentType::IntegralCubicCurve => {
let segment = integral_cubic_curve_segment_iter.next().unwrap();
self.rational_cubic_curve_segments.insert(
rational_cubic_curve_segment_index,
RationalCubicCurveSegment {
weights: [1.0, 1.0, 1.0, 1.0].into(),
control_points: segment.control_points,
},
);
rational_cubic_curve_segment_index += 1;
*segment_type = SegmentType::RationalCubicCurve;
}
SegmentType::RationalQuadraticCurve => {
rational_quadratic_curve_segment_index += 1;
}
SegmentType::RationalCubicCurve => {
rational_cubic_curve_segment_index += 1;
}
}
}
self.integral_quadratic_curve_segments.clear();
self.integral_cubic_curve_segments.clear();
}
pub fn convert_quadratic_curves_to_cubic_curves(&mut self) {
let mut line_segment_iter = self.line_segments.iter();
let mut integral_quadratic_curve_segment_iter = self.integral_quadratic_curve_segments.iter();
let mut integral_cubic_curve_segment_index = 0;
let mut rational_quadratic_curve_segment_iter = self.rational_quadratic_curve_segments.iter();
let mut rational_cubic_curve_segment_index = 0;
let mut previous_control_point = self.start.unwrap();
for segment_type in &mut self.segment_types {
match *segment_type {
SegmentType::Line => {
let segment = line_segment_iter.next().unwrap();
previous_control_point = segment.control_points[0].unwrap();
}
SegmentType::IntegralQuadraticCurve => {
let segment = integral_quadratic_curve_segment_iter.next().unwrap();
let control_point_a = segment.control_points[0].unwrap();
let control_point_b = segment.control_points[1].unwrap();
self.integral_cubic_curve_segments.insert(
integral_cubic_curve_segment_index,
IntegralCubicCurveSegment {
control_points: [
[
previous_control_point[0] + (control_point_a[0] - previous_control_point[0]) * 2.0 / 3.0,
previous_control_point[1] + (control_point_a[1] - previous_control_point[1]) * 2.0 / 3.0,
]
.into(),
[
control_point_b[0] + (control_point_a[0] - control_point_b[0]) * 2.0 / 3.0,
control_point_b[1] + (control_point_a[1] - control_point_b[1]) * 2.0 / 3.0,
]
.into(),
segment.control_points[1],
],
},
);
integral_cubic_curve_segment_index += 1;
*segment_type = SegmentType::IntegralCubicCurve;
previous_control_point = segment.control_points[1].unwrap();
}
SegmentType::IntegralCubicCurve => {
previous_control_point = self.integral_cubic_curve_segments[integral_cubic_curve_segment_index].control_points[2].unwrap();
integral_cubic_curve_segment_index += 1;
}
SegmentType::RationalQuadraticCurve => {
let segment = rational_quadratic_curve_segment_iter.next().unwrap();
let control_points = [
vec_to_point(previous_control_point),
weighted_vec_to_point(segment.weight.unwrap(), segment.control_points[0].unwrap()),
vec_to_point(segment.control_points[1].unwrap()),
];
let new_control_points = [
control_points[0] + (control_points[1] - control_points[0]) * ppga2d::Scalar { g0: 2.0 / 3.0 },
control_points[2] + (control_points[1] - control_points[2]) * ppga2d::Scalar { g0: 2.0 / 3.0 },
];
self.rational_cubic_curve_segments.insert(
rational_cubic_curve_segment_index,
RationalCubicCurveSegment {
weights: [1.0, new_control_points[0].g0[0], new_control_points[1].g0[0], 1.0].into(),
control_points: [
point_to_vec(new_control_points[0]).into(),
point_to_vec(new_control_points[1]).into(),
segment.control_points[1],
],
},
);
rational_cubic_curve_segment_index += 1;
*segment_type = SegmentType::RationalCubicCurve;
previous_control_point = segment.control_points[1].unwrap();
}
SegmentType::RationalCubicCurve => {
previous_control_point = self.rational_cubic_curve_segments[rational_cubic_curve_segment_index].control_points[2].unwrap();
rational_cubic_curve_segment_index += 1;
}
}
}
self.integral_quadratic_curve_segments.clear();
self.rational_quadratic_curve_segments.clear();
}
pub fn close(&mut self) {
if tangent_from_points(self.start.unwrap(), self.get_end()).squared_magnitude().g0 <= ERROR_MARGIN {
return;
}
self.push_line(LineSegment {
control_points: [self.start],
});
}
pub fn push_arc(&mut self, tangent_crossing: [f32; 2], to: [f32; 2]) {
let inner_product = tangent_from_points(tangent_crossing, self.get_end()).inner_product(tangent_from_points(tangent_crossing, to));
self.push_rational_quadratic_curve(RationalQuadraticCurveSegment {
weight: (inner_product.g0.acos() * 0.5).sin().into(),
control_points: [tangent_crossing.into(), to.into()],
});
}
pub fn push_quarter_ellipse(&mut self, tangent_crossing: [f32; 2], to: [f32; 2]) {
self.push_rational_quadratic_curve(RationalQuadraticCurveSegment {
weight: std::f32::consts::FRAC_1_SQRT_2.into(),
control_points: [tangent_crossing.into(), to.into()],
});
}
pub fn from_polygon(vertices: &[[f32; 2]]) -> Self {
let mut vertices = vertices.iter();
let mut result = Path {
start: vertices.next().unwrap().into(),
..Path::default()
};
for control_point in vertices {
result.push_line(LineSegment {
control_points: [control_point.into()],
});
}
result
}
pub fn from_regular_polygon(center: [f32; 2], radius: f32, rotation: f32, vertex_count: usize) -> Self {
let mut vertices = Vec::with_capacity(vertex_count);
for i in 0..vertex_count {
let angle = rotation + i as f32 / vertex_count as f32 * std::f32::consts::PI * 2.0;
vertices.push([center[0] + radius * angle.cos(), center[1] + radius * angle.sin()]);
}
Self::from_polygon(&vertices)
}
pub fn from_rect(center: [f32; 2], half_extent: [f32; 2]) -> Self {
Self::from_polygon(&[
[center[0] - half_extent[0], center[1] - half_extent[1]],
[center[0] - half_extent[0], center[1] + half_extent[1]],
[center[0] + half_extent[0], center[1] + half_extent[1]],
[center[0] + half_extent[0], center[1] - half_extent[1]],
])
}
pub fn from_rounded_rect(center: [f32; 2], half_extent: [f32; 2], radius: f32) -> Self {
let vertices = [
(
[center[0] - half_extent[0] + radius, center[1] - half_extent[1]],
[center[0] - half_extent[0], center[1] - half_extent[1]],
[center[0] - half_extent[0], center[1] - half_extent[1] + radius],
),
(
[center[0] - half_extent[0], center[1] + half_extent[1] - radius],
[center[0] - half_extent[0], center[1] + half_extent[1]],
[center[0] - half_extent[0] + radius, center[1] + half_extent[1]],
),
(
[center[0] + half_extent[0] - radius, center[1] + half_extent[1]],
[center[0] + half_extent[0], center[1] + half_extent[1]],
[center[0] + half_extent[0], center[1] + half_extent[1] - radius],
),
(
[center[0] + half_extent[0], center[1] - half_extent[1] + radius],
[center[0] + half_extent[0], center[1] - half_extent[1]],
[center[0] + half_extent[0] - radius, center[1] - half_extent[1]],
),
];
let mut result = Path {
start: vertices[3].2.into(),
..Path::default()
};
for (from, corner, to) in &vertices {
result.push_line(LineSegment {
control_points: [from.into()],
});
result.push_quarter_ellipse(*corner, *to);
}
result
}
pub fn from_ellipse(center: [f32; 2], half_extent: [f32; 2]) -> Self {
let vertices = [
(
[center[0] - half_extent[0], center[1] - half_extent[1]],
[center[0] - half_extent[0], center[1]],
),
(
[center[0] - half_extent[0], center[1] + half_extent[1]],
[center[0], center[1] + half_extent[1]],
),
(
[center[0] + half_extent[0], center[1] + half_extent[1]],
[center[0] + half_extent[0], center[1]],
),
(
[center[0] + half_extent[0], center[1] - half_extent[1]],
[center[0], center[1] - half_extent[1]],
),
];
let mut result = Path {
start: vertices[3].1.into(),
..Path::default()
};
for (corner, to) in &vertices {
result.push_quarter_ellipse(*corner, *to);
}
result
}
pub fn from_circle(center: [f32; 2], radius: f32) -> Self {
Self::from_ellipse(center, [radius, radius])
}
}