Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
debug/
target/

/AGENTS.md

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
Expand Down
5 changes: 2 additions & 3 deletions iOverlay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ readme = "README.md"
categories = ["algorithms", "graphics", "science::geo", "mathematics", "no-std"]

[dependencies]
i_tree = { version = "^0.19.0" }
i_key_sort = { version = "^0.11.0" }
i_tree = { version = "^0.19" }
i_key_sort = { version = "^0.11" }

i_float = { version = "^5.0.0"}
i_shape = { version = "^5.0.0"}

#i_float = { path = "../../iFloat"}
#i_shape = { path = "../../iShape"}
#i_tree = { path = "../../iTree" }
#i_key_sort = { path = "../../iKeySort" }

rayon = { optional = true, version = "^1.10" }
Expand Down
59 changes: 58 additions & 1 deletion iOverlay/src/split/grid_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl<I: IntNumber> FragmentBuffer<I> {
let i0 = self.layout.index(s.a.x);
if s.a.x < s.b.x {
let i1 = self.layout.index(s.b.x - I::ONE);
for count in counts.iter_mut().take(i1).skip(i0) {
for count in counts.iter_mut().take(i1 + 1).skip(i0) {
*count += 1;
}
} else {
Expand Down Expand Up @@ -304,11 +304,68 @@ mod tests {
use crate::geom::x_segment::XSegment;
use crate::split::grid_layout::vec;
use crate::split::grid_layout::{FragmentBuffer, GridLayout};
use alloc::vec::Vec;
use i_float::int::point::IntPoint;
use i_float::int::rect::IntRect;
use i_float::triangle::Triangle;
use rand::RngExt;

fn check_fragment_reservation<I: crate::core::integer::OverlayInt>() {
// Column width is two. Include one column, several columns, exact
// endpoints, both slopes, and verticals on and off a border.
for (edge, bins, border) in [
([0, 1, 1, 1], vec![0], false),
([1, 1, 2, 1], vec![0], false),
([1, 0, 2, 3], vec![0], false),
([0, 1, 8, 1], vec![0, 1, 2, 3], false),
([1, 0, 9, 5], vec![0, 1, 2, 3, 4], false),
([1, 5, 9, 0], vec![0, 1, 2, 3, 4], false),
([0, 0, 0, 3], vec![0], false),
([3, 0, 3, 3], vec![1], false),
([4, 0, 4, 3], vec![2], true),
([12, 0, 12, 3], vec![6], true),
] {
let [ax, ay, bx, by] = edge.map(I::from_usize);
let segment = XSegment {
a: IntPoint::new(ax, ay),
b: IntPoint::new(bx, by),
};
let mut buffer = FragmentBuffer::new(GridLayout {
min_x: I::ZERO,
max_x: I::from_usize(12),
power: 1,
});
for count in [37, 113] {
buffer.init_fragment_buffer(core::iter::repeat_n(segment, count));
let capacities: Vec<_> = buffer.groups.iter().map(Vec::capacity).collect();
for &bin in &bins {
assert!(capacities[bin] >= count);
}
for index in 0..count {
buffer.add_segment(index, segment);
}
for (bin, group) in buffer.groups.iter().enumerate() {
assert_eq!(group.len(), if bins.contains(&bin) { count } else { 0 });
assert_eq!(group.capacity(), capacities[bin]);
}
assert_eq!(buffer.on_border.len(), if border { count } else { 0 });
buffer.clear();
assert!(buffer.on_border.is_empty());
assert!(buffer.groups.iter().all(Vec::is_empty));
}
}
}

#[test]
fn fragment_reservation_i32() {
check_fragment_reservation::<i32>();
}

#[test]
fn fragment_reservation_i64() {
check_fragment_reservation::<i64>();
}

#[test]
fn i64_column_positions_with_large_shift() {
let layout = GridLayout {
Expand Down
72 changes: 68 additions & 4 deletions iOverlay/src/split/solver_fragment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ where
return false;
}

if self.marks.capacity() < size {
let additional = size - self.marks.capacity();
self.marks.reserve(additional);
}
self.marks.reserve(size);

#[cfg(test)]
assert!(self.marks.capacity() - self.marks.len() >= size);

for mut result in results.into_iter() {
self.marks.append(&mut result.marks);
Expand Down Expand Up @@ -259,6 +259,70 @@ mod tests {
use alloc::vec::Vec;
use i_float::int::point::IntPoint;

#[cfg(feature = "allow_multithreading")]
fn check_parallel_mark_merge<I: crate::core::integer::OverlayInt + core::fmt::Debug>() {
use crate::split::grid_layout::FragmentBuffer;
use i_float::int::number::uint::UIntNumber;

let mut splitter = SplitSolver::<I>::new();
splitter.marks.reserve(64);
// Derive the input from the actual capacity, without assuming the
// allocator gives exactly the requested amount. The merge must reserve
// all marks before append, also when a reused empty Vec is too small.
let count = splitter.marks.capacity() * 3 / 4;
for count in [count, count * 3] {
splitter.marks.clear();
let mut edges = Vec::new();
for y in 1..=count {
edges.push(XSegment {
a: IntPoint::new(I::ZERO, I::from_usize(y)),
b: IntPoint::new(I::from_usize(100), I::from_usize(y)),
});
}
edges.push(XSegment {
a: IntPoint::new(I::from_usize(25), I::ZERO),
b: IntPoint::new(I::from_usize(25), I::from_usize(count + 1)),
});
let mut buffer =
FragmentBuffer::new(GridLayout::new(edges.iter().copied(), edges.len()).unwrap());
buffer.init_fragment_buffer(edges.iter().copied());
for (index, edge) in edges.into_iter().enumerate() {
buffer.add_segment(index, edge);
}
splitter.parallel_split(I::WideUInt::ONE, &mut buffer);
assert_eq!(splitter.marks.len(), 2 * count);
splitter
.marks
.sort_unstable_by_key(|mark| (mark.index, mark.point));
for (index, mark) in splitter.marks[..count].iter().enumerate() {
assert_eq!(mark.index, index);
assert_eq!(
mark.point,
IntPoint::new(I::from_usize(25), I::from_usize(index + 1))
);
}
for (index, mark) in splitter.marks[count..].iter().enumerate() {
assert_eq!(mark.index, count);
assert_eq!(
mark.point,
IntPoint::new(I::from_usize(25), I::from_usize(index + 1))
);
}
}
}

#[cfg(feature = "allow_multithreading")]
#[test]
fn parallel_mark_merge_i32() {
check_parallel_mark_merge::<i32>();
}

#[cfg(feature = "allow_multithreading")]
#[test]
fn parallel_mark_merge_i64() {
check_parallel_mark_merge::<i64>();
}

#[test]
fn collinear_overlap_starts_in_a_later_column() {
for (start, end, far_end) in [(80, 100, 150), (180, 300, 400)] {
Expand Down
7 changes: 6 additions & 1 deletion iOverlay/src/split/solver_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ where

for (i, si) in segments.iter().enumerate() {
let time = si.x_segment.a.x;
let si_range = si.x_segment.y_range().into();
let si_y_range = si.x_segment.y_range();
let si_range = si_y_range.clone().into();
for sj in tree.iter_by_range(si_range, time) {
if sj.x_segment.is_not_intersect_y_range(&si_y_range) {
continue;
}

let (this_index, scan_index, this, scan) = if si.x_segment < sj.x_segment {
(i, sj.id, &si.x_segment, &sj.x_segment)
} else {
Expand Down
45 changes: 45 additions & 0 deletions iOverlay/tests/tree_y_range_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
use i_float::int::point::IntPoint;
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::integer::OverlayInt;
use i_overlay::core::overlay::Overlay;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::core::solver::Solver;

fn check_tree_y_ranges<I: OverlayInt + From<i32> + std::fmt::Debug>() {
let point = |x: i32, y: i32| IntPoint::<I>::new(x.into(), y.into());
let rect = |x0, y0, x1, y1| vec![point(x0, y0), point(x0, y1), point(x1, y1), point(x1, y0)];
// The distant contour puts all nearby, disjoint Y bands in one
// coarse tree cell, while their X ranges overlap.
let mut subj = vec![rect(0, 0, 40, 10), rect(0, 30, 40, 40)];
let mut clip = vec![rect(10, 10, 30, 20), rect(40, 2, 50, 8)];
for y in (60..300).step_by(20) {
subj.push(rect(0, y, 40, y + 4));
clip.push(rect(10, y + 8, 30, y + 12));
}
subj.push(rect(0, 1_000_000, 40, 1_000_010));
// Partial horizontal and vertical shared edges above require
// splitting at boundary Y values. These oblique crossings also
// exercise rounding and subsequent split passes.
clip.push(vec![point(-3, 29), point(43, 37), point(17, 43)]);

for fill in [FillRule::EvenOdd, FillRule::NonZero] {
for rule in [OverlayRule::Union, OverlayRule::Intersect, OverlayRule::Xor] {
let list = Overlay::from_subj_and_clip_custom(&subj, &clip, Default::default(), Solver::LIST)
.overlay(rule, fill);
let tree = Overlay::from_subj_and_clip_custom(&subj, &clip, Default::default(), Solver::TREE)
.overlay(rule, fill);
assert!(!list.is_empty());
assert_eq!(tree, list, "{rule:?}, {fill:?}");
}
}
}

#[test]
fn tree_y_ranges_i32() {
check_tree_y_ranges::<i32>();
}

#[test]
fn tree_y_ranges_i64() {
check_tree_y_ranges::<i64>();
}
3 changes: 3 additions & 0 deletions performance/rust_app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This executable pins its dependency resolution independently of the library.
!Cargo.lock
results/
111 changes: 111 additions & 0 deletions performance/rust_app/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions performance/rust_app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
[package]
name = "rust_app"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
rust-version = "1.88"
default-run = "rust_app"

[profile.release]
opt-level = 3
lto = false
codegen-units = 1

[dependencies]
[features]
default = []
multithreading = ["i_overlay/allow_multithreading"]

#i_overlay = { path = "../../iOverlay", default-features = true }
#i_overlay = { path = "../../iOverlay", features = ["allow_multithreading"] }
#i_tree = { path = "../../../iTree" }
i_overlay = "7.0.0"
i_key_sort = "^0.10.1"
i_tree = "^0.19.0"
[dependencies]
i_overlay = { path = "../../iOverlay", version = "^9" }
Loading
Loading