use super::PointId; use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE; use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath}; use dyn_any::DynAny; use glam::DVec2; use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez}; use std::ops::Sub; /// Represents different ways of calculating the centroid. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] pub enum CentroidType { /// The center of mass for the area of a solid shape's interior, as if made out of an infinitely flat material. #[default] Area, /// The center of mass for the arc length of a curved shape's perimeter, as if made out of an infinitely thin wire. Length, } pub trait AsU64 { fn as_u64(&self) -> u64; } impl AsU64 for u32 { fn as_u64(&self) -> u64 { *self as u64 } } impl AsU64 for u64 { fn as_u64(&self) -> u64 { *self } } impl AsU64 for f64 { fn as_u64(&self) -> u64 { *self as u64 } } pub trait AsI64 { fn as_i64(&self) -> i64; } impl AsI64 for u32 { fn as_i64(&self) -> i64 { *self as i64 } } impl AsI64 for u64 { fn as_i64(&self) -> i64 { *self as i64 } } impl AsI64 for f64 { fn as_i64(&self) -> i64 { *self as i64 } } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] pub enum GridType { #[default] Rectangular, Isometric, } #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] pub enum ArcType { #[default] Open = 0, Closed, PieSlice, } #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] pub enum MergeByDistanceAlgorithm { #[default] Spatial, Topological, } #[repr(C)] #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)] #[widget(Radio)] pub enum PointSpacingType { #[default] /// The desired spacing distance between points. Separation, /// The exact number of points to span the path. Quantity, } pub fn point_to_dvec2(point: Point) -> DVec2 { DVec2 { x: point.x, y: point.y } } pub fn dvec2_to_point(value: DVec2) -> Point { Point { x: value.x, y: value.y } } pub fn segment_to_handles(segment: &PathSeg) -> BezierHandles { match *segment { PathSeg::Line(_) => BezierHandles::Linear, PathSeg::Quad(QuadBez { p0: _, p1, p2: _ }) => BezierHandles::Quadratic { handle: point_to_dvec2(p1) }, PathSeg::Cubic(CubicBez { p0: _, p1, p2, p3: _ }) => BezierHandles::Cubic { handle_start: point_to_dvec2(p1), handle_end: point_to_dvec2(p2), }, } } pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> PathSeg { match handles { bezier_rs::BezierHandles::Linear => { let p0 = dvec2_to_point(start); let p1 = dvec2_to_point(end); PathSeg::Line(Line::new(p0, p1)) } bezier_rs::BezierHandles::Quadratic { handle } => { let p0 = dvec2_to_point(start); let p1 = dvec2_to_point(handle); let p2 = dvec2_to_point(end); PathSeg::Quad(QuadBez::new(p0, p1, p2)) } bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => { let p0 = dvec2_to_point(start); let p1 = dvec2_to_point(handle_start); let p2 = dvec2_to_point(handle_end); let p3 = dvec2_to_point(end); PathSeg::Cubic(CubicBez::new(p0, p1, p2, p3)) } } } pub fn subpath_to_kurbo_bezpath(subpath: Subpath) -> BezPath { let maniputor_groups = subpath.manipulator_groups(); let closed = subpath.closed(); bezpath_from_manipulator_groups(maniputor_groups, closed) } pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup], closed: bool) -> BezPath { let mut bezpath = kurbo::BezPath::new(); let mut out_handle; let Some(first) = manipulator_groups.first() else { return bezpath }; bezpath.move_to(dvec2_to_point(first.anchor)); out_handle = first.out_handle; for manipulator in manipulator_groups.iter().skip(1) { match (out_handle, manipulator.in_handle) { (Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)), (None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)), (None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)), (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)), } out_handle = manipulator.out_handle; } if closed { match (out_handle, first.in_handle) { (Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)), (None, None) => bezpath.line_to(dvec2_to_point(first.anchor)), (None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)), (Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)), } bezpath.close_path(); } bezpath } pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec>, bool) { let mut manipulator_groups = Vec::>::new(); let mut is_closed = false; for element in bezpath.elements() { let manipulator_group = match *element { kurbo::PathEl::MoveTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None), kurbo::PathEl::LineTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None), kurbo::PathEl::QuadTo(point, point1) => ManipulatorGroup::new(point_to_dvec2(point1), Some(point_to_dvec2(point)), None), kurbo::PathEl::CurveTo(point, point1, point2) => { if let Some(last_maipulator_group) = manipulator_groups.last_mut() { last_maipulator_group.out_handle = Some(point_to_dvec2(point)); } ManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None) } kurbo::PathEl::ClosePath => { if let Some(last_group) = manipulator_groups.pop() { if let Some(first_group) = manipulator_groups.first_mut() { first_group.out_handle = last_group.in_handle; } } is_closed = true; break; } }; manipulator_groups.push(manipulator_group); } (manipulator_groups, is_closed) } /// Returns true if the [`PathSeg`] is equivalent to a line. /// /// This is different from simply checking if the segment is [`PathSeg::Line`] or [`PathSeg::Quad`] or [`PathSeg::Cubic`]. Bezier curve can also be a line if the control points are colinear to the start and end points. Therefore if the handles exceed the start and end point, it will still be considered as a line. pub fn is_linear(segment: PathSeg) -> bool { let is_colinear = |a: Point, b: Point, c: Point| -> bool { ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)).abs() < MAX_ABSOLUTE_DIFFERENCE }; match segment { PathSeg::Line(_) => true, PathSeg::Quad(QuadBez { p0, p1, p2 }) => is_colinear(p0, p1, p2), PathSeg::Cubic(CubicBez { p0, p1, p2, p3 }) => is_colinear(p0, p1, p3) && is_colinear(p0, p2, p3), } } /// Get an iterator over the coordinates of all points in a path segment. pub fn get_segment_points(segment: PathSeg) -> Vec { match segment { PathSeg::Line(line) => [line.p0, line.p1].to_vec(), PathSeg::Quad(quad_bez) => [quad_bez.p0, quad_bez.p1, quad_bez.p2].to_vec(), PathSeg::Cubic(cubic_bez) => [cubic_bez.p0, cubic_bez.p1, cubic_bez.p2, cubic_bez.p3].to_vec(), } } /// Returns true if the corresponding points of the two [`PathSeg`]s are within the provided absolute value difference from each other. pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> bool { let seg1 = if is_linear(seg1) { PathSeg::Line(Line::new(seg1.start(), seg1.end())) } else { seg1 }; let seg2 = if is_linear(seg2) { PathSeg::Line(Line::new(seg2.start(), seg2.end())) } else { seg2 }; let seg1_points = get_segment_points(seg1); let seg2_points = get_segment_points(seg2); let cmp = |a: f64, b: f64| a.sub(b).abs() < max_abs_diff; seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y)) }