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
6 changes: 5 additions & 1 deletion examples/perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,9 @@ Opt 9
Querying: 6987.64ms
Total: 7067.82ms
_______________________________________________________________________

After Opt12
Building: 75.20ms
Querying: 6976.01ms
Total: 7051.21ms
_______________________________________________________________________
*/
9 changes: 8 additions & 1 deletion examples/perf_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,12 @@ OPT 9 - Calculate exact total nodes needed
Total Build time: 7442.48ms
Average Build: 74.42ms
Overall time: 7965.43ms

___________________________________________________
Removed sorted_order
Total Add time: 390.01ms
Average Add: 3.90ms
Total Build time: 7471.13ms
Average Build: 74.71ms
Overall time: 7986.26ms
___________________________________________________
*/
108 changes: 74 additions & 34 deletions src/hilbert_rtree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ pub struct HilbertRTree {
total_nodes: usize,
/// Pre-allocated capacity in bytes (0 if not pre-allocated)
allocated_capacity: usize,
/// Inverse mapping: original item ID -> position in sorted tree
/// After build(), sorted_order[original_id] = position in memory
/// This allows get(item_id) to find the correct box despite Hilbert sorting
pub(crate) sorted_order: Vec<usize>,
}

const MAX_HILBERT: u32 = u16::MAX as u32;
Expand Down Expand Up @@ -123,7 +119,6 @@ impl HilbertRTree {
position: 0,
bounds: Box::new(f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
total_nodes: 0,
sorted_order: Vec::new(),
}
}

Expand Down Expand Up @@ -209,8 +204,34 @@ impl HilbertRTree {
/// assert_eq!(tree.get_point(1), Some((3.0, 4.0)));
/// assert_eq!(tree.get_point(2), None);
/// ```
/// Gets the center point for a given item ID (for points added via add_point)
///
/// Returns the center coordinates (x, y) for the item with the given ID,
/// or None if the ID is out of bounds. This method is intended for items
/// that were added as points using add_point().
///
/// # Arguments
/// * `item_id` - The index of the point (0-based, in order added)
///
/// # Example
/// ```
/// use aabb::prelude::*;
/// let mut tree = AABB::with_capacity(2);
/// tree.add_point(1.5, 2.5);
/// tree.add_point(3.0, 4.0);
/// tree.build();
///
/// assert_eq!(tree.get_point(0), Some((1.5, 2.5)));
/// assert_eq!(tree.get_point(1), Some((3.0, 4.0)));
/// assert_eq!(tree.get_point(2), None);
/// ```
pub fn get_point(&self, item_id: usize) -> Option<(f64, f64)> {
self.get(item_id).map(|(min_x, min_y, _max_x, _max_y)| (min_x, min_y))
if let Some((min_x, min_y, _max_x, _max_y)) = self.get(item_id) {
// For points, min_x == max_x and min_y == max_y, so we can return either
Some((min_x, min_y))
} else {
None
}
}

/// Builds the Hilbert R-tree index
Expand Down Expand Up @@ -290,8 +311,8 @@ impl HilbertRTree {
std::ptr::write_unaligned(root_idx_ptr, 0_u32 << 2_u32); // First child at position 0
}

// For single-node case, no sorting happens, so sorted_order is identity mapping
self.sorted_order = (0..num_items).collect();
// For single-node case, no sorting happens
// No need to populate sorted_order since we use lazy lookup
return;
}

Expand Down Expand Up @@ -348,14 +369,8 @@ impl HilbertRTree {
// Initialize leaf indices AFTER sorting - map new position to original box ID
let indices_start = HEADER_SIZE + total_nodes * size_of::<Box>();

// Build inverse mapping: original_id -> sorted_position
// This allows get(item_id) to find the correct box despite Hilbert sorting
let mut sorted_order = vec![0usize; num_items];
for sorted_pos in 0..num_items {
let original_id = sort_indices[sorted_pos];
sorted_order[original_id] = sorted_pos;
}
self.sorted_order = sorted_order;
// Note: We removed the sorted_order vector to save memory
// get() method now uses lazy search through indices when needed

for i in 0..num_items {
let idx_ptr = &mut self.data[indices_start + i * size_of::<u32>()] as *mut u8 as *mut u32;
Expand Down Expand Up @@ -417,6 +432,47 @@ impl HilbertRTree {
self.num_items == 0
}

/// Gets the bounding box for a given item ID (0-based insertion order)
///
/// Returns the bounding box (min_x, min_y, max_x, max_y) for the item with the given ID,
/// or None if the ID is out of bounds.
///
/// # Arguments
/// * `item_id` - The index of the item (0-based, in order added)
///
/// # Example
/// ```
/// use aabb::prelude::*;
/// let mut tree = AABB::with_capacity(2);
/// tree.add(1.0, 2.0, 3.0, 4.0);
/// tree.add(5.0, 6.0, 7.0, 8.0);
/// tree.build();
///
/// assert_eq!(tree.get(0), Some((1.0, 2.0, 3.0, 4.0)));
/// assert_eq!(tree.get(1), Some((5.0, 6.0, 7.0, 8.0)));
/// assert_eq!(tree.get(2), None);
/// ```
pub fn get(&self, item_id: usize) -> Option<(f64, f64, f64, f64)> {
if item_id >= self.num_items {
return None;
}

// If tree hasn't been built yet, items are in insertion order
if self.level_bounds.is_empty() {
let bbox = self.get_box(item_id);
return Some((bbox.min_x, bbox.min_y, bbox.max_x, bbox.max_y));
}

// After build(): search through leaf nodes to find which position has this item_id
for pos in 0..self.num_items {
if self.get_index(pos) as usize == item_id {
let bbox = self.get_box(pos);
return Some((bbox.min_x, bbox.min_y, bbox.max_x, bbox.max_y));
}
}
None
}


/// Finds all boxes that intersect with a given rectangular region.
///
Expand Down Expand Up @@ -1776,23 +1832,8 @@ impl HilbertRTree {
/// let bbox = tree.get(0).unwrap();
/// assert_eq!(bbox, (0.0, 0.0, 2.0, 2.0));
/// ```
pub fn get(&self, item_id: usize) -> Option<(f64, f64, f64, f64)> {
if item_id >= self.num_items {
return None;
}

// After build(), boxes are sorted by Hilbert curve order
// sorted_order[item_id] gives us the position of this item in the sorted tree
let pos = if self.sorted_order.is_empty() {
// If tree hasn't been built yet, items are in original order
item_id
} else {
self.sorted_order[item_id]
};

let box_data = self.get_box(pos);
Some((box_data.min_x, box_data.min_y, box_data.max_x, box_data.max_y))
}
// Note: get() method removed to eliminate sorted_order dependency
// Use spatial query methods instead for accessing data

/// Get box at position using read_unaligned
#[inline(always)]
Expand Down Expand Up @@ -1999,7 +2040,6 @@ impl HilbertRTree {
bounds,
total_nodes,
allocated_capacity: data_len,
sorted_order: Vec::new(),
})
}
}
Expand Down