diff --git a/benchmark/Cargo.toml b/benchmark/Cargo.toml index 1323f43..96748a6 100644 --- a/benchmark/Cargo.toml +++ b/benchmark/Cargo.toml @@ -56,6 +56,10 @@ harness = false name = "regex_benchmark" harness = false +[[bench]] +name = "region_merge_benchmark" +harness = false + [[bench]] name = "size_probe_benchmark" harness = false diff --git a/benchmark/benches/region_merge_benchmark.rs b/benchmark/benches/region_merge_benchmark.rs new file mode 100644 index 0000000..ae57e0f --- /dev/null +++ b/benchmark/benches/region_merge_benchmark.rs @@ -0,0 +1,34 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use rand::Rng; +use tlib::{ + figure::{CoordRect, CoordRegion, FRect}, + prelude::Coordinate, +}; + +fn generate_random_rects(count: usize) -> CoordRegion { + let mut rng = rand::thread_rng(); + let mut region = CoordRegion::new(); + + for _ in 0..count { + let x = rng.gen_range(0.0..1000.0); + let y = rng.gen_range(0.0..1000.0); + let width = rng.gen_range(20.0..80.0); + let height = rng.gen_range(20.0..80.0); + let rect = CoordRect::new(FRect::new(x, y, width, height), Coordinate::World); + region.add_rect(rect); + } + + region +} + +pub fn benchmark_region_merge(c: &mut Criterion) { + c.bench_function("region_merge_10000", |b| { + b.iter(|| { + let mut region = generate_random_rects(10000); + region.merge_all(); + }) + }); +} + +criterion_group!(benches, benchmark_region_merge); +criterion_main!(benches); diff --git a/tlib/src/figure/rectangle.rs b/tlib/src/figure/rectangle.rs index a95a86a..2d7643e 100644 --- a/tlib/src/figure/rectangle.rs +++ b/tlib/src/figure/rectangle.rs @@ -293,6 +293,30 @@ impl Rect { } } + #[inline] + pub fn union_intersects(&self, rect: &Rect) -> Option { + if !self.is_intersects(rect) { + None + } else { + let left = self.x.min(rect.x); + let top = self.y.min(rect.y); + let right = (self.x + self.width).max(rect.x + rect.width); + let bottom = (self.y + self.height).max(rect.y + rect.height); + + let x = left; + let y = top; + let width = right - left; + let height = bottom - top; + + Some(Rect { + x, + y, + width, + height, + }) + } + } + #[inline] pub fn union(&self, rect: &Rect) -> Rect { let left = self.x.min(rect.x); @@ -918,7 +942,9 @@ impl FRect { let right = (self.x + self.width).min(rect.x + rect.width); let bottom = (self.y + self.height).min(rect.y + rect.height); - left <= right && top <= bottom + const EPSILON: f32 = 0.1; + + left <= right + EPSILON && top <= bottom + EPSILON } #[inline] @@ -936,6 +962,38 @@ impl FRect { let width = right - left; let height = bottom - top; + if width <= 0.0 || height <= 0.0 { + return None; + } + + Some(FRect { + x, + y, + width, + height, + }) + } + } + + #[inline] + pub fn union_intersects(&self, rect: &FRect) -> Option { + if !self.is_intersects(rect) { + None + } else { + let left = self.x.min(rect.x); + let top = self.y.min(rect.y); + let right = (self.x + self.width).max(rect.x + rect.width); + let bottom = (self.y + self.height).max(rect.y + rect.height); + + let x = left; + let y = top; + let width = right - left; + let height = bottom - top; + + if width <= 0.0 || height <= 0.0 { + return None; + } + Some(FRect { x, y, @@ -1658,6 +1716,36 @@ impl AtomicRect { } } + + #[inline] + pub fn union_intersects(&self, rect: &AtomicRect) -> Option { + if !self.is_intersects(rect) { + None + } else { + let x = self.x.load(Ordering::SeqCst); + let y = self.y.load(Ordering::SeqCst); + let width = self.width.load(Ordering::SeqCst); + let height = self.height.load(Ordering::SeqCst); + + let rx = rect.x.load(Ordering::SeqCst); + let ry = rect.y.load(Ordering::SeqCst); + let rwidth = rect.width.load(Ordering::SeqCst); + let rheight = rect.height.load(Ordering::SeqCst); + + let left = x.min(rx); + let top = y.min(ry); + let right = (x + width).max(rx + rwidth); + let bottom = (y + height).max(ry + rheight); + + let x = left; + let y = top; + let width = right - left; + let height = bottom - top; + + Some(AtomicRect::new(x, y, width, height)) + } + } + #[inline] pub fn union(&self, rect: &AtomicRect) -> AtomicRect { let x = self.x.load(Ordering::SeqCst); diff --git a/tlib/src/figure/region.rs b/tlib/src/figure/region.rs index b5dff5d..de30455 100644 --- a/tlib/src/figure/region.rs +++ b/tlib/src/figure/region.rs @@ -248,39 +248,24 @@ impl CoordRegion { return; } - loop { - let mut merged = Vec::new(); - let mut used = vec![false; self.regions.len()]; - let mut changed = false; - - for i in 0..self.regions.len() { - if used[i] { - continue; + let mut input = std::mem::take(&mut self.regions); + let mut result = Vec::new(); + + while let Some(mut base) = input.pop() { + let mut i = 0; + while i < input.len() { + if let Some(merged) = merge_if_intersect(&base, &input[i]) { + base = merged; + input.swap_remove(i); + i = 0; + } else { + i += 1; } - let mut current = self.regions[i]; - used[i] = true; - - let mut j = 0; - while j < self.regions.len() { - if i != j && !used[j] { - if let Some(merged_rect) = merge_if_intersect(¤t, &self.regions[j]) { - current = merged_rect; - used[j] = true; - changed = true; - j = 0; - continue; - } - } - j += 1; - } - merged.push(current); - } - - if !changed { - return; } - self.regions = merged; + result.push(base); } + + self.regions = result; } } impl IntoIterator for CoordRegion { @@ -298,17 +283,18 @@ fn merge_if_intersect(a: &CoordRect, b: &CoordRect) -> Option { if a.coord() != b.coord() { return None; } - if let Some(rect) = a.rect().intersects(&b.rect()) { - Some(CoordRect::new(rect, a.coord())) - } else { - None - } + a.rect() + .union_intersects(&b.rect()) + .map(|rect| CoordRect::new(rect, a.coord())) } #[cfg(test)] mod tests { - use super::{FRegion, Region}; - use crate::figure::{FRect, Rect}; + use super::{CoordRegion, FRegion, Region}; + use crate::{ + figure::{CoordRect, FRect, Rect}, + prelude::Coordinate, + }; #[test] fn test_region() { @@ -329,4 +315,68 @@ mod tests { assert_eq!(*rect, origin); } } + + #[test] + fn test_region_merge() { + let mut coord_region = CoordRegion::new(); + + // Merge chain: R0 merges with R1, then with R2 -> final rect (100, 100, 300, 100) + coord_region.add_rect(CoordRect::new( + FRect::new(100.0, 100.0, 100.0, 100.0), + Coordinate::World, + )); // R0 + coord_region.add_rect(CoordRect::new( + FRect::new(200.0, 100.0, 100.0, 100.0), + Coordinate::World, + )); // R1 + coord_region.add_rect(CoordRect::new( + FRect::new(299.9, 100.0, 100.0, 100.0), + Coordinate::World, + )); // R2 (touching by EPSILON) + + // Merge chain: R3 and R4 overlap vertically -> (500, 200, 100, 300) + coord_region.add_rect(CoordRect::new( + FRect::new(500.0, 200.0, 100.0, 100.0), + Coordinate::World, + )); // R3 + coord_region.add_rect(CoordRect::new( + FRect::new(500.0, 300.0, 100.0, 200.0), + Coordinate::World, + )); // R4 + + // Non-overlapping rectangles + coord_region.add_rect(CoordRect::new( + FRect::new(50.0, 50.0, 40.0, 40.0), + Coordinate::World, + )); // R5 + coord_region.add_rect(CoordRect::new( + FRect::new(1000.0, 1000.0, 10.0, 10.0), + Coordinate::World, + )); // R6 + + // Tiny overlap (barely touching) + coord_region.add_rect(CoordRect::new( + FRect::new(800.0, 800.0, 50.0, 50.0), + Coordinate::World, + )); // R7 + coord_region.add_rect(CoordRect::new( + FRect::new(849.95, 800.0, 50.0, 50.0), + Coordinate::World, + )); // R8 (barely touches R7) + + // Trigger merging + coord_region.merge_all(); + + // Expected results: + // - Merged: R0+R1+R2 -> (100, 100, 300, 100) + // - Merged: R3+R4 -> (500, 200, 100, 300) + // - Merged: R7+R8 -> (800, 800, 99.95, 50) + // - Unmerged: R5, R6 + + assert_eq!(coord_region.len(), 5); + + for (i, r) in coord_region.regions.iter().enumerate() { + println!("Region {}: {:?}", i, r); + } + } }