Files
Graphite/libraries/path-bool/src/util/grid.rs
Dennis Kobert a4ec50d8ba Improve robustness and performance of the boolean operation algorithm (#2191)
* Improve perf of path bool lib

* Use swap remove

* Use outer/inner bounding box for inclusion testing

* Reuse allocations for hit testing

* Use direct root finding for inclusion testing

* Reuse bounding box

* Use faster hash and specify capacities

* Use hashmap based approach for find vertices

* Unroll find_vertecies loop and use 32 bit positions

* Tune initial vec capacities

* Remove unused bounding boxes

* Use smallvec for storing outgoing edges

* Improve allocations for compute_minor

* Use approximate bounding box for edge finding

* Transition aabb to use glam vecs

* Make find vertecies use 64 bit again this is slower but less likely to cause issues

* Improve intersection candidate finding

* Remove loop check in bit vec iter

* Special case cubic line intersections

* Optimize grid rounding and add debug output

* Remove file write

* Remove faulty line intersection

* Fix grid rounding

* Improve robustness and cleanaup code

* Make elided lifetime explicit

* Fix tests

* Fix a boolean ops crash

* Add comment

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
2025-08-21 23:15:36 +00:00

129 lines
2.7 KiB
Rust

use crate::aabb::Aabb;
use glam::{DVec2, IVec2};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
pub(crate) struct Grid {
cell_factor: f64,
cells: FxHashMap<IVec2, SmallVec<[usize; 6]>>,
}
impl Grid {
pub(crate) fn new(cell_size: f64, edges: usize) -> Self {
Grid {
cell_factor: cell_size.recip(),
cells: FxHashMap::with_capacity_and_hasher(edges, Default::default()),
}
}
pub(crate) fn insert(&mut self, bbox: &Aabb, index: usize) {
let min_cell = self.point_to_cell_floor(bbox.min());
let max_cell = self.point_to_cell_ceil(bbox.max());
for i in min_cell.x..=max_cell.x {
for j in min_cell.y..=max_cell.y {
self.cells.entry((i, j).into()).or_default().push(index);
}
}
}
pub(crate) fn query(&self, bbox: &Aabb, result: &mut BitVec) {
let min_cell = self.point_to_cell_floor(bbox.min());
let max_cell = self.point_to_cell_ceil(bbox.max());
for i in min_cell.x..=max_cell.x {
for j in min_cell.y..=max_cell.y {
if let Some(indices) = self.cells.get(&(i, j).into()) {
for &index in indices {
result.set(index);
}
}
}
}
// result.sort_unstable();
// result.dedup();
}
fn point_to_cell_ceil(&self, point: DVec2) -> IVec2 {
(point * self.cell_factor).ceil().as_ivec2()
}
fn point_to_cell_floor(&self, point: DVec2) -> IVec2 {
(point * self.cell_factor).floor().as_ivec2()
}
}
pub struct BitVec {
data: Vec<u64>,
}
impl BitVec {
pub fn new(capacity: usize) -> Self {
let num_words = capacity.div_ceil(64);
BitVec { data: vec![0; num_words] }
}
pub fn set(&mut self, index: usize) {
let word_index = index / 64;
let bit_index = index % 64;
self.data[word_index] |= 1u64 << bit_index;
}
pub fn clear(&mut self) {
self.data.fill(0);
}
pub fn iter_set_bits(&self) -> BitVecIterator<'_> {
BitVecIterator {
bit_vec: self,
current_word: self.data[0],
word_index: 0,
}
}
}
pub struct BitVecIterator<'a> {
bit_vec: &'a BitVec,
current_word: u64,
word_index: usize,
}
impl<'a> Iterator for BitVecIterator<'a> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.current_word == 0 {
self.word_index += 1;
if self.word_index == self.bit_vec.data.len() {
return None;
}
self.current_word = self.bit_vec.data[self.word_index];
continue;
}
let tz = self.current_word.trailing_zeros() as usize;
self.current_word ^= 1 << tz;
let result = self.word_index * 64 + tz;
return Some(result);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bitvec() {
let mut bv = BitVec::new(200);
bv.set(5);
bv.set(64);
bv.set(128);
bv.set(199);
let set_bits: Vec<usize> = bv.iter_set_bits().collect();
assert_eq!(set_bits, vec![5, 64, 128, 199]);
}
}