Skip to content

Commit 7d7bbc3

Browse files
author
Alex Pyattaev
committed
updates to deps, improved readme, clippy comments
1 parent bd6b41a commit 7d7bbc3

File tree

9 files changed

+76
-70
lines changed

9 files changed

+76
-70
lines changed

Cargo.toml

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,33 @@
11
[package]
22
name = "spatialtree"
33
description = "A fast and flexible generic spatial tree collection (Octree, Quadtree, etc)"
4-
version = "0.1.1"
4+
version = "0.1.2"
55
edition = "2021"
66
license = "GPL-3.0"
77
repository = "https://github.com/alexpyattaev/spatialtree"
88
documentation = "https://docs.rs/spatialtree"
9-
keywords = ["octree", "quadtree", "tree","lod", "generic"]
9+
keywords = ["octree", "quadtree", "tree", "lod", "generic"]
1010
categories = ["data-structures"]
11-
exclude = [
12-
".github/",
13-
]
11+
exclude = [".github/"]
1412

1513

1614
[dependencies]
17-
arrayvec = "0.7.2"
18-
duplicate = "1.0.0"
19-
slab = "0.4.8"
20-
rand ={version="0.8.5", features=['small_rng'], optional = true }
15+
arrayvec = "0.7"
16+
duplicate = "1.0"
17+
slab = "0.4"
18+
rand = { version = "0.8", features = ['small_rng'], optional = true }
2119

2220
[features]
2321
default = ["rand"]
24-
rand=["dep:rand"]
22+
rand = ["dep:rand"]
2523

2624
[dev-dependencies]
27-
freelist = "0.1.0"
25+
freelist = "0.1"
2826
lru = "0.10.0"
2927
rayon = "1.5"
3028
glium = "0.30"
3129
rand_derive = "0.5.0"
32-
criterion = {version= "0.4.0", features=['html_reports']}
30+
criterion = { version = "0.4.0", features = ['html_reports'] }
3331

3432
[[bench]]
3533
name = "iterators"
@@ -44,16 +42,15 @@ name = "coordinates"
4442
harness = false
4543

4644
[profile.release]
47-
opt-level=3
45+
opt-level = 3
4846
overflow-checks = false
49-
debug=0
47+
debug = 0
5048
strip = "symbols"
51-
debug-assertions=false
52-
lto="fat"
49+
debug-assertions = false
50+
lto = "fat"
5351

5452

5553
[profile.bench]
56-
debug=0
57-
lto="fat"
54+
debug = 0
55+
lto = "fat"
5856
strip = "symbols"
59-

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Internals are partially based on:
1414
The aim of this crate is to provide a generic, easy to use tree data structure that can be used to make Quadtrees, Octrees for various realtime applications (e.g. games or GIS software).
1515

1616
Internally, the tree tries to keep all needed memory allocated in slab arenas to avoid the memory fragmentation and allocator pressure. All operations, where possible, use either stack allocations or allocate at most once.
17-
17+
1818

1919
## Features
2020
- Highly tunable for different scales (from 8 bit to 64 bit coordinates), 2D, 3D, N-D if desired.
@@ -97,6 +97,24 @@ let idx = tree.insert(QuadVec::new([1u8,2], 3), |p| {p.pos.iter().sum::<u8>() as
9797
assert_eq!(tree.get_chunk(idx).chunk, 3);
9898
```
9999

100+
Lookups in the tree can be efficiently done over a wide area using an axis-aligned bounding box (AABB) to select the desired region as follows:
101+
```rust
102+
# use spatialtree::*;
103+
let mut tree = OctTree::<usize, OctVec>::new();
104+
let min = OctVec::new([0u8, 0, 0], 3);
105+
let max = OctVec::new([7u8, 7, 7], 3);
106+
107+
// create iterator over all possible positions in the tree
108+
let pos_iter = iter_all_positions_in_bounds(min, max);
109+
// fill tree with important data
110+
tree.insert_many(pos_iter, |_| 42 );
111+
// iterateing over data in AABB yields both positions and associated data chunks
112+
// only populated positions are yielded, empty ones are quietly skipped.
113+
for (pos, data) in tree.iter_chunks_in_aabb(min, max) {
114+
dbg!(pos, data);
115+
}
116+
```
117+
Selecting chunks this way will never traverse deeper than the deepest chunk in the AABB limits provided. Both limits should have the same depth.
100118

101119
## Advanced usage
102120
This structure can be used for purposes such as progressive LOD.

benches/coordinates.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ fn run_eval<const N: usize, FL: LodVec<N>>(c: &mut Criterion, title: &str) {
2626
let mut rng = SmallRng::seed_from_u64(42);
2727
let samples_num = 10;
2828

29-
//for depth in [1, 4, 16].iter() {
30-
for depth in [1].iter() {
29+
for depth in [1, 4].iter() {
3130
group.significance_level(0.1).sample_size(samples_num);
3231
//TODO: more sensible stuff here
3332
group.bench_with_input(BenchmarkId::from_parameter(depth), depth, |b, &_depth| {

benches/freelist.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
/* Generic tree structures for storage of spatial data.
2-
Copyright (C) 2023 Alexander Pyattaev
2+
Copyright (C) 2023 Alexander Pyattaev
33
4-
This program is free software: you can redistribute it and/or modify
5-
it under the terms of the GNU General Public License as published by
6-
the Free Software Foundation, either version 3 of the License, or
7-
(at your option) any later version.
4+
This program is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
88
9-
This program is distributed in the hope that it will be useful,
10-
but WITHOUT ANY WARRANTY; without even the implied warranty of
11-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12-
GNU General Public License for more details.
9+
This program is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
1313
14-
You should have received a copy of the GNU General Public License
15-
along with this program. If not, see <https://www.gnu.org/licenses/>.
16-
*/
14+
You should have received a copy of the GNU General Public License
15+
along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
*/
1717

1818
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
1919
use freelist as libfreelist;
@@ -83,7 +83,7 @@ where
8383
let mut rng = SmallRng::seed_from_u64(42);
8484
let samples_num = 10;
8585

86-
for depth in [50 /* 1000, 100000*/].iter() {
86+
for depth in [25, 50].iter() {
8787
group.significance_level(0.1).sample_size(samples_num);
8888
group.bench_with_input(BenchmarkId::from_parameter(depth), depth, |b, &depth| {
8989
b.iter(|| {

benches/iterators.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ use spatialtree::*;
2424

2525
const N_LOOKUPS: usize = 40;
2626

27-
type DTYPE = u8;
27+
type DataType = u8;
2828

29-
fn generate_area_bounds(rng: &mut SmallRng, depth: u8) -> (OctVec<DTYPE>, OctVec<DTYPE>) {
30-
let cmax = ((1usize << depth as usize) - 1) as DTYPE;
29+
fn generate_area_bounds(rng: &mut SmallRng, depth: u8) -> (OctVec<DataType>, OctVec<DataType>) {
30+
let cmax = ((1usize << depth as usize) - 1) as DataType;
3131

3232
let min = rand_cv(
3333
rng,
@@ -40,7 +40,7 @@ fn generate_area_bounds(rng: &mut SmallRng, depth: u8) -> (OctVec<DTYPE>, OctVec
4040
OctVec::new([cmax, cmax, cmax], depth),
4141
);
4242

43-
return (min, max);
43+
(min, max)
4444
}
4545

4646
struct ChuChunk {

examples/glium.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct Chunk {
3535
}
3636

3737
fn make_shaders(display: &Display) -> Program {
38-
let program = program!(display,
38+
program!(display,
3939
140 => {
4040
vertex: "
4141
#version 140
@@ -76,8 +76,7 @@ fn make_shaders(display: &Display) -> Program {
7676
"
7777
}
7878
)
79-
.unwrap();
80-
return program;
79+
.unwrap()
8180
}
8281

8382
#[derive(Copy, Clone)]
@@ -98,7 +97,7 @@ impl RenderContext {
9897
pub fn new(event_loop: &EventLoop<()>) -> Self {
9998
let wb = glutin::window::WindowBuilder::new().with_title("Quadtree demo");
10099
let cb = glutin::ContextBuilder::new().with_vsync(true);
101-
let display = Display::new(wb, cb, &event_loop).unwrap();
100+
let display = Display::new(wb, cb, event_loop).unwrap();
102101
// make a vertex buffer
103102
// we'll reuse it as we only need to draw one quad multiple times anyway
104103
let vertex_buffer = {
@@ -125,7 +124,7 @@ impl RenderContext {
125124
let index_buffer = IndexBuffer::new(
126125
&display,
127126
PrimitiveType::TrianglesList,
128-
&[0 as u16, 1, 2, 1, 2, 3],
127+
&[0_u16, 1, 2, 1, 2, 3],
129128
)
130129
.unwrap();
131130

@@ -173,7 +172,7 @@ fn draw(mouse_pos: (f32, f32), tree: &mut QuadTree<Chunk, QuadVec>, ctx: &Render
173172
// here we get the chunk position and size
174173
let uniforms = uniform! {
175174
offset: position.float_coords(),
176-
scale: position.float_size() as f32,
175+
scale: position.float_size(),
177176
state: chunk.cache_state,
178177
selected: chunk.selected as i32,
179178
};

src/iter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ mod tests {
568568
);
569569

570570
for (l, c) in tree.iter_chunks_in_aabb_mut(min, max) {
571-
assert_eq!(c.visible, false, "no way any voxel is still visible");
571+
assert!(!c.visible, "no way any voxel is still visible");
572572
assert_eq!(
573573
l.pos.depth, D,
574574
"All chunks must be at max depth (as we did not insert any others)"

src/tree.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,11 @@
1818
//! Contains the tree struct, which is used to hold all chunks
1919
2020
use crate::coords::*;
21+
use crate::util_funcs::*;
2122
use slab::Slab;
2223
use std::fmt::Debug;
2324
use std::num::NonZeroU32;
2425
use std::ops::ControlFlow;
25-
use crate::util_funcs::*;
26-
27-
2826

2927
// type aliases to make iterators more readable
3028
pub(crate) type ChunkStorage<const N: usize, C, L> = Slab<ChunkContainer<N, C, L>>;
@@ -37,6 +35,7 @@ pub(crate) type NodeStorage<const B: usize> = Slab<TreeNode<B>>;
3735
/// Template parameters are:
3836
/// * N is the number bits needed to encode B, i.e. 3 for octrees and 4 for quadtrees.
3937
/// * B is the branch count per node, i.e. 8 for octrees and 4 for quadtrees. Has to be power of two.
38+
///
4039
/// An invariant between the two has to be ensured where 1<<N == B, else tree will fail to construct.
4140
/// This will look nicer once const generics fully stabilize.
4241
#[derive(Clone, Debug)]
@@ -451,7 +450,7 @@ where
451450
for (i, old_idx) in iter_treenode_children(&children) {
452451
let old_node = self.nodes.remove(old_idx);
453452
//eliminate empty nodes
454-
if old_node.is_empty(){
453+
if old_node.is_empty() {
455454
self.new_nodes[n].children[i] = None;
456455
continue;
457456
}
@@ -751,7 +750,6 @@ mod tests {
751750
}
752751
}
753752

754-
755753
#[test]
756754
pub fn defragment() {
757755
let mut tree = QuadTree::<TestChunk, QuadVec>::new();
@@ -763,21 +761,20 @@ mod tests {
763761
];
764762
tree.insert_many(targets.iter().copied(), |_| TestChunk {});
765763
// slab is a Vec, so it should have binary exponential growth. With 4 entires it should have capacity = len.
766-
assert_eq!(tree.chunks.capacity(),tree.chunks.len());
764+
assert_eq!(tree.chunks.capacity(), tree.chunks.len());
767765
tree.pop_chunk_by_position(targets[1]);
768-
assert_eq!(tree.chunks.capacity(),4);
769-
assert_eq!(tree.chunks.len(),3);
766+
assert_eq!(tree.chunks.capacity(), 4);
767+
assert_eq!(tree.chunks.len(), 3);
770768
tree.defragment_chunks();
771769
tree.shrink_to_fit();
772-
assert_eq!(tree.chunks.capacity(),tree.chunks.len());
770+
assert_eq!(tree.chunks.capacity(), tree.chunks.len());
773771
dbg!(tree.nodes.len());
774772
tree.pop_chunk_by_position(targets[0]);
775773
//TODO!
776774
//tree.prune();
777775
dbg!(tree.nodes.len());
778776
}
779777

780-
781778
#[test]
782779
pub fn alignment() {
783780
assert_eq!(

src/util_funcs.rs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1616
*/
1717

18-
use std::num::NonZeroU32;
1918
use crate::coords::*;
19+
use std::num::NonZeroU32;
2020

2121
/// Utility function to cast random structures into arrays of bytes
2222
/// This is mostly for debugging purposes
@@ -26,8 +26,6 @@ pub unsafe fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
2626
::core::slice::from_raw_parts((p as *const T) as *const u8, ::core::mem::size_of::<T>())
2727
}
2828

29-
30-
3129
/// Type for relative pointers to nodes in the tree. Kept 32bit for cache locality during lookups.
3230
/// Should you need > 4 billion nodes in the tree do let me know who sells you the RAM.
3331
pub type NodePtr = Option<NonZeroU32>;
@@ -67,13 +65,13 @@ impl ChunkPtr {
6765
#[inline]
6866
pub(crate) fn from(x: Option<usize>) -> Self {
6967
match x {
70-
Some(v) => Self ( v as i32 ),
68+
Some(v) => Self(v as i32),
7169
None => Self::None,
7270
}
7371
}
7472
// Cheat for "compatibility" with option.
7573
#[allow(non_upper_case_globals)]
76-
pub const None: Self = ChunkPtr( -1 );
74+
pub const None: Self = ChunkPtr(-1);
7775
}
7876

7977
//TODO: impl Try once stable
@@ -91,12 +89,12 @@ impl ChunkPtr {
9189
* }
9290
* }*/
9391

94-
9592
//TODO - use struct of arrays?
9693
/// Tree node that encompasses multiple children at once. This just barely fits into one cache line for octree.
9794
/// For each possible child, the node has two relative pointers:
9895
/// * children will point to the TreeNode in a given branch direction
9996
/// * chunk will point to the data chunk in a given branch direction
97+
///
10098
/// both pointers may be "None", indicating either no children, or no data
10199
#[derive(Clone, Debug)]
102100
pub struct TreeNode<const B: usize> {
@@ -117,26 +115,24 @@ impl<const B: usize> TreeNode<B> {
117115
}
118116

119117
#[inline]
120-
pub fn iter_existing_chunks(
121-
&self,
122-
) -> impl Iterator<Item = (usize, usize)> +'_ {
118+
pub fn iter_existing_chunks(&self) -> impl Iterator<Item = (usize, usize)> + '_ {
123119
self.chunk.iter().filter_map(|c| c.get()).enumerate()
124120
}
125121

126122
#[inline]
127123
pub fn is_empty(&self) -> bool {
128-
self.children.iter().all(|c| c.is_none()) && self.chunk.iter().all(|c| *c== ChunkPtr::None)
124+
self.children.iter().all(|c| c.is_none()) && self.chunk.iter().all(|c| *c == ChunkPtr::None)
129125
}
130126
}
131127

132128
#[inline]
133-
pub fn iter_treenode_children< const N: usize>(
129+
pub fn iter_treenode_children<const N: usize>(
134130
children: &[NodePtr; N],
135131
) -> impl Iterator<Item = (usize, usize)> + '_ {
136132
children
137-
.iter()
138-
.filter_map(|c| Some((*c)?.get() as usize))
139-
.enumerate()
133+
.iter()
134+
.filter_map(|c| Some((*c)?.get() as usize))
135+
.enumerate()
140136
}
141137

142138
// utility struct for holding actual chunks and the node that owns them

0 commit comments

Comments
 (0)