mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 23:48:11 +08:00
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:
co-authored by
Keavon Chambers
parent
2febbfd698
commit
3eb98c6d6d
@@ -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)))
|
||||
}
|
||||
Reference in New Issue
Block a user