Add path-bool library (#1952)

* Add path-bool library

* Cleanup code

* Cargo format

* Integrate boolean ops into graphite

* Add test for editor crash

* Fix edge sort floating point instability

* Add unit test for red-dress failure

* Backport tests and aux functions

* Use curvature based sorting

* Convert linear cubic splines to line segments

* Deduplicate reversed path segments

* Fix epsilon for empty segments

* Remove parameter based intersection pruning

* Add support for reversed paths

* Add benchmark infrastructure

* Add intersection benchmark

* Add recursion bound

* Implement support for overlapping path segments

* Remove rouge prinln

* Fix sorting for bezier segments with one control point at the start of the segment

* Cleanup log statements

* Directly translate graphite paths to Path segments

* Round data before passing it to path_bool

* Fix flag_faces traversal order

* Add test for white dots in bottom right of painted dreams

* Make rounding configurable

* Update demo artwork to remove manual path modifications

* Convert from path segments to manipulator groups directly

* Remove dead code

* Fix clippy lints

* Replace functions in path segment with methods and add documentation

* Add more documentation

* Close subpaths

* Reorganize files and add README.md

* Add license information

* Code review

* Fix license info

* Adopt new node macro and fix demo artwork

* Close subpaths with Z

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Dennis Kobert
2024-09-21 02:06:43 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 2febbfd698
commit 3eb98c6d6d
165 changed files with 5990 additions and 78 deletions
+138
View File
@@ -0,0 +1,138 @@
pub(crate) mod intersection_path_segment;
pub(crate) mod line_segment;
pub(crate) mod line_segment_aabb;
pub(crate) mod path_cubic_segment_self_intersection;
pub(crate) mod path_segment;
use glam::DVec2;
#[cfg(feature = "parsing")]
use crate::path_command::{to_absolute_commands, AbsolutePathCommand, PathCommand};
use crate::path_segment::PathSegment;
pub type Path = Vec<PathSegment>;
fn reflect_control_point(point: DVec2, control_point: DVec2) -> DVec2 {
point * 2.0 - control_point
}
pub fn path_from_commands<I>(commands: I) -> impl Iterator<Item = PathSegment>
where
I: IntoIterator<Item = PathCommand>,
{
let mut first_point: Option<DVec2> = None;
let mut last_point: Option<DVec2> = None;
let mut last_control_point: Option<DVec2> = None;
to_absolute_commands(commands).filter_map(move |cmd| match cmd {
AbsolutePathCommand::M(point) => {
last_point = Some(point);
first_point = Some(point);
last_control_point = None;
None
}
AbsolutePathCommand::L(point) => {
let start = last_point.unwrap();
last_point = Some(point);
last_control_point = None;
Some(PathSegment::Line(start, point))
}
AbsolutePathCommand::H(x) => {
let start = last_point.unwrap();
let point = DVec2::new(x, start.y);
last_point = Some(point);
last_control_point = None;
Some(PathSegment::Line(start, point))
}
AbsolutePathCommand::V(y) => {
let start = last_point.unwrap();
let point = DVec2::new(start.x, y);
last_point = Some(point);
last_control_point = None;
Some(PathSegment::Line(start, point))
}
AbsolutePathCommand::C(c1, c2, end) => {
let start = last_point.unwrap();
last_point = Some(end);
last_control_point = Some(c2);
Some(PathSegment::Cubic(start, c1, c2, end))
}
AbsolutePathCommand::S(c2, end) => {
let start = last_point.unwrap();
let c1 = reflect_control_point(start, last_control_point.unwrap_or(start));
last_point = Some(end);
last_control_point = Some(c2);
Some(PathSegment::Cubic(start, c1, c2, end))
}
AbsolutePathCommand::Q(c, end) => {
let start = last_point.unwrap();
last_point = Some(end);
last_control_point = Some(c);
Some(PathSegment::Quadratic(start, c, end))
}
AbsolutePathCommand::T(end) => {
let start = last_point.unwrap();
let c = reflect_control_point(start, last_control_point.unwrap_or(start));
last_point = Some(end);
last_control_point = Some(c);
Some(PathSegment::Quadratic(start, c, end))
}
AbsolutePathCommand::A(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, end) => {
let start = last_point.unwrap();
last_point = Some(end);
last_control_point = None;
Some(PathSegment::Arc(start, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, end))
}
AbsolutePathCommand::Z => {
let start = last_point.unwrap();
let end = first_point.unwrap();
last_point = Some(end);
last_control_point = None;
Some(PathSegment::Line(start, end))
}
})
}
pub fn path_to_commands<'a, I>(segments: I, eps: f64) -> impl Iterator<Item = PathCommand> + 'a
where
I: IntoIterator<Item = &'a PathSegment> + 'a,
{
let mut last_point: Option<DVec2> = None;
segments
.into_iter()
.flat_map(move |seg| {
let start = seg.start();
let mut commands = Vec::new();
if last_point.map_or(true, |lp| !start.abs_diff_eq(lp, eps)) {
if last_point.is_some() {
commands.push(PathCommand::Absolute(AbsolutePathCommand::Z));
}
commands.push(PathCommand::Absolute(AbsolutePathCommand::M(start)));
}
match seg {
PathSegment::Line(_, end) => {
commands.push(PathCommand::Absolute(AbsolutePathCommand::L(*end)));
last_point = Some(*end);
}
PathSegment::Cubic(_, c1, c2, end) => {
commands.push(PathCommand::Absolute(AbsolutePathCommand::C(*c1, *c2, *end)));
last_point = Some(*end);
}
PathSegment::Quadratic(_, c, end) => {
commands.push(PathCommand::Absolute(AbsolutePathCommand::Q(*c, *end)));
last_point = Some(*end);
}
PathSegment::Arc(_, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, end) => {
commands.push(PathCommand::Absolute(AbsolutePathCommand::A(*rx, *ry, *x_axis_rotation, *large_arc_flag, *sweep_flag, *end)));
last_point = Some(*end);
}
}
commands
})
.chain(std::iter::once(PathCommand::Absolute(AbsolutePathCommand::Z)))
}