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
+64
View File
@@ -0,0 +1,64 @@
use glam::DVec2;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Aabb {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
pub(crate) fn bounding_boxes_overlap(a: &Aabb, b: &Aabb) -> bool {
a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom
}
pub(crate) fn merge_bounding_boxes(a: Option<Aabb>, b: &Aabb) -> Aabb {
match a {
Some(a) => Aabb {
top: a.top.min(b.top),
right: a.right.max(b.right),
bottom: a.bottom.max(b.bottom),
left: a.left.min(b.left),
},
None => *b,
}
}
pub(crate) fn extend_bounding_box(bounding_box: Option<Aabb>, point: DVec2) -> Aabb {
match bounding_box {
Some(bb) => Aabb {
top: bb.top.min(point.y),
right: bb.right.max(point.x),
bottom: bb.bottom.max(point.y),
left: bb.left.min(point.x),
},
None => Aabb {
top: point.y,
right: point.x,
bottom: point.y,
left: point.x,
},
}
}
pub(crate) fn bounding_box_max_extent(bounding_box: &Aabb) -> f64 {
(bounding_box.right - bounding_box.left).max(bounding_box.bottom - bounding_box.top)
}
pub(crate) fn bounding_box_around_point(point: DVec2, padding: f64) -> Aabb {
Aabb {
top: point.y - padding,
right: point.x + padding,
bottom: point.y + padding,
left: point.x - padding,
}
}
pub(crate) fn expand_bounding_box(bounding_box: &Aabb, padding: f64) -> Aabb {
Aabb {
top: bounding_box.top - padding,
right: bounding_box.right + padding,
bottom: bounding_box.bottom + padding,
left: bounding_box.left - padding,
}
}
+6
View File
@@ -0,0 +1,6 @@
#[derive(Clone, Copy, Debug)]
pub struct Epsilons {
pub point: f64,
pub linear: f64,
pub param: f64,
}
+23
View File
@@ -0,0 +1,23 @@
use glam::{DVec2, FloatExt};
pub use std::f64::consts::PI;
pub fn lin_map(value: f64, in_min: f64, in_max: f64, out_min: f64, out_max: f64) -> f64 {
((value - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min
}
pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
a.lerp(b, t)
}
pub fn vector_angle(u: DVec2, v: DVec2) -> f64 {
const EPS: f64 = 1e-12;
let sign = u.x * v.y - u.y * v.x;
if sign.abs() < EPS && (u + v).length_squared() < EPS * EPS {
// TODO: u can be scaled
return PI;
}
sign.signum() * (u.dot(v) / (u.length() * v.length())).acos()
}
+121
View File
@@ -0,0 +1,121 @@
use crate::aabb::Aabb;
use std::collections::HashSet;
pub struct QuadTree<T> {
bounding_box: Aabb,
depth: usize,
inner_node_capacity: usize,
subtrees: Option<Box<[QuadTree<T>; 4]>>,
pairs: Vec<(Aabb, T)>,
}
impl<T: Clone> QuadTree<T> {
pub fn new(bounding_box: Aabb, depth: usize, inner_node_capacity: usize) -> Self {
QuadTree {
bounding_box,
depth,
inner_node_capacity,
subtrees: None,
pairs: Vec::new(),
}
}
pub fn insert(&mut self, bounding_box: Aabb, value: T) -> bool {
if !crate::aabb::bounding_boxes_overlap(&bounding_box, &self.bounding_box) {
return false;
}
if self.depth > 0 && self.pairs.len() >= self.inner_node_capacity {
self.ensure_subtrees();
for tree in self.subtrees.as_mut().unwrap().iter_mut() {
tree.insert(bounding_box, value.clone());
}
} else {
self.pairs.push((bounding_box, value));
}
true
}
pub fn find(&self, bounding_box: &Aabb) -> HashSet<T>
where
T: Eq + std::hash::Hash + Clone,
{
let mut set = HashSet::new();
self.find_internal(bounding_box, &mut set);
set
}
fn find_internal(&self, bounding_box: &Aabb, set: &mut HashSet<T>)
where
T: Eq + std::hash::Hash + Clone,
{
if !crate::aabb::bounding_boxes_overlap(bounding_box, &self.bounding_box) {
return;
}
for (key, value) in &self.pairs {
if crate::aabb::bounding_boxes_overlap(bounding_box, key) {
set.insert(value.clone());
}
}
if let Some(subtrees) = &self.subtrees {
for tree in subtrees.iter() {
tree.find_internal(bounding_box, set);
}
}
}
fn ensure_subtrees(&mut self) {
if self.subtrees.is_some() {
return;
}
let midx = (self.bounding_box.left + self.bounding_box.right) / 2.0;
let midy = (self.bounding_box.top + self.bounding_box.bottom) / 2.0;
self.subtrees = Some(Box::new([
QuadTree::new(
Aabb {
top: self.bounding_box.top,
right: midx,
bottom: midy,
left: self.bounding_box.left,
},
self.depth - 1,
self.inner_node_capacity,
),
QuadTree::new(
Aabb {
top: self.bounding_box.top,
right: self.bounding_box.right,
bottom: midy,
left: midx,
},
self.depth - 1,
self.inner_node_capacity,
),
QuadTree::new(
Aabb {
top: midy,
right: midx,
bottom: self.bounding_box.bottom,
left: self.bounding_box.left,
},
self.depth - 1,
self.inner_node_capacity,
),
QuadTree::new(
Aabb {
top: midy,
right: self.bounding_box.right,
bottom: self.bounding_box.bottom,
left: midx,
},
self.depth - 1,
self.inner_node_capacity,
),
]));
}
}