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
4 changes: 4 additions & 0 deletions benchmark/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions benchmark/benches/region_merge_benchmark.rs
Original file line number Diff line number Diff line change
@@ -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);
90 changes: 89 additions & 1 deletion tlib/src/figure/rectangle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,30 @@ impl Rect {
}
}

#[inline]
pub fn union_intersects(&self, rect: &Rect) -> Option<Rect> {
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);
Expand Down Expand Up @@ -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]
Expand All @@ -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<FRect> {
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,
Expand Down Expand Up @@ -1658,6 +1716,36 @@ impl AtomicRect {
}
}


#[inline]
pub fn union_intersects(&self, rect: &AtomicRect) -> Option<AtomicRect> {
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);
Expand Down
124 changes: 87 additions & 37 deletions tlib/src/figure/region.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(&current, &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 {
Expand All @@ -298,17 +283,18 @@ fn merge_if_intersect(a: &CoordRect, b: &CoordRect) -> Option<CoordRect> {
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() {
Expand All @@ -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);
}
}
}