Compare commits
8 commits
main
...
rtree-thef
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36ffce22f9 | ||
|
|
0a54cad11e | ||
|
|
c9bab10f6b | ||
|
|
24b7709b4c | ||
|
|
ccb598ac1f | ||
|
|
969c84d9cc | ||
|
|
d8f1ffd801 | ||
|
|
228c6ff976 |
14 changed files with 347 additions and 233 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
|
@ -357,15 +357,13 @@ dependencies = [
|
|||
"bevy",
|
||||
"criterion",
|
||||
"dirs",
|
||||
"glam 0.31.0",
|
||||
"include_dir",
|
||||
"ordered-float",
|
||||
"rand",
|
||||
"rand_hc",
|
||||
"rstar",
|
||||
"rusqlite",
|
||||
"rusqlite_migration",
|
||||
"steel-core",
|
||||
"steel-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -989,7 +987,7 @@ dependencies = [
|
|||
"arrayvec 0.7.6",
|
||||
"bevy_reflect",
|
||||
"derive_more",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"itertools 0.14.0",
|
||||
"libm",
|
||||
"rand",
|
||||
|
|
@ -1163,7 +1161,7 @@ dependencies = [
|
|||
"downcast-rs 2.0.2",
|
||||
"erased-serde",
|
||||
"foldhash 0.2.0",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"indexmap 2.13.0",
|
||||
"inventory",
|
||||
"petgraph",
|
||||
|
|
@ -1223,7 +1221,7 @@ dependencies = [
|
|||
"downcast-rs 2.0.2",
|
||||
"encase",
|
||||
"fixedbitset",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"image",
|
||||
"indexmap 2.13.0",
|
||||
"js-sys",
|
||||
|
|
@ -1390,7 +1388,7 @@ dependencies = [
|
|||
"crossbeam-queue",
|
||||
"derive_more",
|
||||
"futures-lite",
|
||||
"heapless 0.9.2",
|
||||
"heapless",
|
||||
"pin-project",
|
||||
]
|
||||
|
||||
|
|
@ -2878,6 +2876,15 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.31.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74a4d85559e2637d3d839438b5b3d75c31e655276f9544d72475c36b92fabbed"
|
||||
dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
|
|
@ -3053,16 +3060,6 @@ dependencies = [
|
|||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
|
||||
dependencies = [
|
||||
"hash32",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapless"
|
||||
version = "0.9.2"
|
||||
|
|
@ -3087,7 +3084,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "29a164ceff4500f2a72b1d21beaa8aa8ad83aec2b641844c659b190cb3ea2e0b"
|
||||
dependencies = [
|
||||
"constgebra",
|
||||
"glam",
|
||||
"glam 0.30.10",
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
|
|
@ -4505,15 +4502,6 @@ dependencies = [
|
|||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54fc7b35e3026136eaf1decdc66ecde3efadfd663cc0d71115ad40da7ebcff63"
|
||||
dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_xoshiro"
|
||||
version = "0.6.0"
|
||||
|
|
@ -4691,17 +4679,6 @@ version = "0.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
|
||||
|
||||
[[package]]
|
||||
name = "rstar"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "421400d13ccfd26dfa5858199c30a5d76f9c54e0dba7575273025b43c5175dbb"
|
||||
dependencies = [
|
||||
"heapless 0.8.0",
|
||||
"num-traits",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.37.0"
|
||||
|
|
|
|||
15
Cargo.toml
15
Cargo.toml
|
|
@ -5,14 +5,14 @@ edition = "2024"
|
|||
|
||||
[dependencies]
|
||||
bevy = { version = "0.18", default-features = false, features = ["2d"] }
|
||||
criterion = { version = "0.8.1", default-features = false, features = ["cargo_bench_support", "rayon"] }
|
||||
dirs = "6.0.0"
|
||||
glam = { version = "0.31.0", default-features = false, features = ["libm"] }
|
||||
include_dir = "0.7.4"
|
||||
ordered-float = "5.1.0"
|
||||
rstar = "0.12.2"
|
||||
rusqlite = { version = "0.37", default-features = false, features = ["bundled", "blob", "functions", "jiff"] }
|
||||
rusqlite_migration = { version = "2.3.0", features = ["from-directory"] }
|
||||
steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" }
|
||||
steel-derive = { git="https://github.com/mattwparas/steel.git", branch = "master" }
|
||||
|
||||
# Enable a small amount of optimization in the dev profile.
|
||||
[profile.dev]
|
||||
|
|
@ -22,11 +22,12 @@ opt-level = 1
|
|||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.9.2"
|
||||
rand_hc = "0.4.0"
|
||||
criterion = { version = "0.8.1", default-features = false, features = ["cargo_bench_support", "rayon"] }
|
||||
[profile.bench]
|
||||
inherits = "release"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
name = "main"
|
||||
harness = false
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.9.2"
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
(struct point (x))
|
||||
|
||||
(define (dist p1 p2)
|
||||
)
|
||||
26
benches/bench_insert_bulk.rs
Normal file
26
benches/bench_insert_bulk.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#[path = "shared.rs"]
|
||||
mod shared;
|
||||
use std::hint::black_box;
|
||||
|
||||
use autobarts::spindex::RStarTree;
|
||||
use criterion::{Criterion, criterion_group};
|
||||
use shared::*;
|
||||
|
||||
fn bench_insert_bulk_rstartree_2d(_c: &mut Criterion) {
|
||||
let points = generate_2d_data();
|
||||
let mut cc = configure_criterion();
|
||||
cc.bench_function("insert_bulk_2d_rstartree", |b| {
|
||||
b.iter_with_setup(
|
||||
|| {
|
||||
let tree = RStarTree::new(BENCH_NODE_CAPACITY);
|
||||
(tree, points.clone())
|
||||
},
|
||||
|(mut tree, points)| {
|
||||
tree.insert_bulk(points);
|
||||
black_box(());
|
||||
},
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_insert_bulk_rstartree_2d,);
|
||||
92
benches/bench_range_search.rs
Normal file
92
benches/bench_range_search.rs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
use std::hint::black_box;
|
||||
|
||||
use autobarts::{geom::Point, spindex::RStarTree};
|
||||
use bevy::math::Vec2;
|
||||
use criterion::{Criterion, criterion_group};
|
||||
use rand::{Rng, SeedableRng, seq::SliceRandom};
|
||||
|
||||
//
|
||||
// Benchmark Parameters
|
||||
//
|
||||
pub const BENCH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
pub const BENCH_NUM_INSERT: i32 = 50_000;
|
||||
pub const BENCH_NODE_CAPACITY: usize = 5;
|
||||
|
||||
const SEED: u64 = 8;
|
||||
|
||||
//
|
||||
// Data Generation Functions (Raw Data)
|
||||
//
|
||||
pub fn generate_2d_data() -> Vec<Point> {
|
||||
let data: Vec<Point> = (0..BENCH_NUM_INSERT)
|
||||
.map(|i| {
|
||||
let point = bevy::prelude::Vec2::new(i as f32, i as f32);
|
||||
|
||||
Point {
|
||||
point,
|
||||
entity: bevy::prelude::Entity::PLACEHOLDER,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
// Configure Criterion with a timeout for benchmarks
|
||||
pub fn configure_criterion() -> Criterion {
|
||||
Criterion::default()
|
||||
.measurement_time(BENCH_TIMEOUT)
|
||||
.sample_size(10)
|
||||
}
|
||||
|
||||
const BENCH_RANGE_RADIUS: f32 = 30.0;
|
||||
|
||||
fn benchmark_range_rstartree_2d(_c: &mut Criterion) {
|
||||
let mut points = generate_2d_data();
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(SEED);
|
||||
let len = points.len();
|
||||
for i in 0..len {
|
||||
let j = rng.random_range(0..len);
|
||||
points.swap(i, j);
|
||||
}
|
||||
|
||||
let mut tree = RStarTree::new(BENCH_NODE_CAPACITY);
|
||||
|
||||
tree.insert_bulk(points.clone());
|
||||
|
||||
let mut cc = configure_criterion();
|
||||
let mut idx = 0;
|
||||
let len = points.len();
|
||||
cc.bench_function("range_rstartree_2d", |b| {
|
||||
b.iter(|| {
|
||||
let res = tree.range_search(&points[idx], BENCH_RANGE_RADIUS);
|
||||
idx = (idx + 1) % len;
|
||||
black_box(res)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn benchmark_range_bbox_rstartree_2d(_c: &mut Criterion) {
|
||||
let points = generate_2d_data();
|
||||
let mut tree = RStarTree::new(BENCH_NODE_CAPACITY);
|
||||
|
||||
tree.insert_bulk(points);
|
||||
|
||||
let query_rect = bevy::math::bounding::Aabb2d::new(
|
||||
Vec2::new(35.0 - BENCH_RANGE_RADIUS, 45.0 - BENCH_RANGE_RADIUS),
|
||||
4.0 * Vec2::splat(BENCH_RANGE_RADIUS),
|
||||
);
|
||||
let mut cc = configure_criterion();
|
||||
cc.bench_function("range_rstartree_2d", |b| {
|
||||
b.iter(|| {
|
||||
let res = tree.range_search_bbox(&query_rect);
|
||||
black_box(res)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
benchmark_range_rstartree_2d,
|
||||
//benchmark_range_bbox_rstartree_2d,
|
||||
);
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
use rand::{Rng, SeedableRng};
|
||||
use rand_hc::Hc128Rng;
|
||||
|
||||
use autobats::geom::Point;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use rstar::{RStarInsertionStrategy, RTree, RTreeParams};
|
||||
|
||||
const SEED_1: &[u8; 32] = b"Gv0aHMtHkBGsUXNspGU9fLRuCWkZWHZx";
|
||||
const SEED_2: &[u8; 32] = b"km7DO4GeaFZfTcDXVpnO7ZJlgUY7hZiS";
|
||||
|
||||
struct Params;
|
||||
|
||||
impl RTreeParams for Params {
|
||||
const MIN_SIZE: usize = 2;
|
||||
const MAX_SIZE: usize = 30;
|
||||
const REINSERTION_COUNT: usize = 1;
|
||||
type DefaultInsertionStrategy = RStarInsertionStrategy;
|
||||
}
|
||||
|
||||
const DEFAULT_BENCHMARK_TREE_SIZE: usize = 50_000;
|
||||
|
||||
fn bulk_load_baseline(c: &mut Criterion) {
|
||||
c.bench_function("bulk load baseline", move |b| {
|
||||
let points: Vec<_> = create_random_points(DEFAULT_BENCHMARK_TREE_SIZE, SEED_1);
|
||||
|
||||
b.iter(|| {
|
||||
RTree::<_, Params>::bulk_load_with_params(points.clone());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn bulk_load_comparison(c: &mut Criterion) {
|
||||
c.bench_function("insert sequential", |b| {
|
||||
let points: Vec<_> = create_random_points(DEFAULT_BENCHMARK_TREE_SIZE, SEED_1);
|
||||
b.iter(move || {
|
||||
let mut rtree = rstar::RTree::new();
|
||||
for point in &points {
|
||||
rtree.insert(*point);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn tree_creation_quality(c: &mut Criterion) {
|
||||
const SIZE: usize = 100_000;
|
||||
let points: Vec<_> = create_random_points(SIZE, SEED_1);
|
||||
let tree_bulk_loaded = RTree::<_, Params>::bulk_load_with_params(points.clone());
|
||||
let mut tree_sequential = RTree::new();
|
||||
for point in &points {
|
||||
tree_sequential.insert(*point);
|
||||
}
|
||||
|
||||
let query_points = create_random_points(100_000, SEED_2);
|
||||
let query_points_cloned_1 = query_points.clone();
|
||||
c.bench_function("bulk load quality", move |b| {
|
||||
b.iter(|| {
|
||||
for query_point in &query_points {
|
||||
tree_bulk_loaded.nearest_neighbor(query_point).unwrap();
|
||||
}
|
||||
})
|
||||
})
|
||||
.bench_function("sequential load quality", move |b| {
|
||||
b.iter(|| {
|
||||
for query_point in &query_points_cloned_1 {
|
||||
tree_sequential.nearest_neighbor(query_point).unwrap();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn range_query(c: &mut Criterion) {
|
||||
const SIZE: usize = 50_000;
|
||||
let points: Vec<_> = create_random_points(SIZE, SEED_1);
|
||||
let tree = RTree::<_, Params>::bulk_load_with_params(points.clone());
|
||||
|
||||
c.bench_function("range query", move |b| {
|
||||
let d = 30.0f32.powi(2);
|
||||
let mut i = 0;
|
||||
b.iter(|| {
|
||||
let q = &points[i];
|
||||
i = (i + 1) % SIZE;
|
||||
tree.locate_within_distance(*q, d)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bulk_load_baseline,
|
||||
// bulk_load_comparison,
|
||||
// tree_creation_quality,
|
||||
range_query
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
fn create_random_points(num_points: usize, seed: &[u8; 32]) -> Vec<Point> {
|
||||
let mut rng = Hc128Rng::from_seed(*seed);
|
||||
let r = (-1_000.0)..=1_000.0;
|
||||
(0..num_points)
|
||||
.map(|_| Point::new(rng.random_range(r.clone()), rng.random_range(r.clone())))
|
||||
.collect()
|
||||
}
|
||||
6
benches/main.rs
Normal file
6
benches/main.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
use criterion::criterion_main;
|
||||
|
||||
//mod bench_insert_bulk;
|
||||
mod bench_range_search;
|
||||
|
||||
criterion_main!(bench_range_search::benches,);
|
||||
34
benches/shared.rs
Normal file
34
benches/shared.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use autobarts::geom::Point;
|
||||
use criterion::Criterion;
|
||||
|
||||
//
|
||||
// Benchmark Parameters
|
||||
//
|
||||
pub const BENCH_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
|
||||
pub const BENCH_NUM_INSERT: i32 = 50_000;
|
||||
pub const BENCH_NODE_CAPACITY: usize = 5;
|
||||
|
||||
//
|
||||
// Data Generation Functions (Raw Data)
|
||||
//
|
||||
pub fn generate_2d_data() -> Vec<Point> {
|
||||
let data: Vec<Point> = (0..BENCH_NUM_INSERT)
|
||||
.map(|i| {
|
||||
let point = bevy::prelude::Vec2::new(i as f32, i as f32);
|
||||
|
||||
Point {
|
||||
point,
|
||||
entity: bevy::prelude::Entity::PLACEHOLDER,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
// Configure Criterion with a timeout for benchmarks
|
||||
pub fn configure_criterion() -> Criterion {
|
||||
Criterion::default()
|
||||
.measurement_time(BENCH_TIMEOUT)
|
||||
.sample_size(10)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{path::PathBuf, sync::LazyLock};
|
||||
|
||||
use include_dir::{include_dir, Dir};
|
||||
use rusqlite::{config::DbConfig, Connection};
|
||||
use include_dir::{Dir, include_dir};
|
||||
use rusqlite::{Connection, config::DbConfig};
|
||||
use rusqlite_migration::Migrations;
|
||||
|
||||
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/migrations");
|
||||
|
|
|
|||
19
src/ffi.rs
19
src/ffi.rs
|
|
@ -1,19 +0,0 @@
|
|||
use steel::steel_vm::{engine::Engine, register_fn::RegisterFn};
|
||||
|
||||
use crate::{geom::*, units::*};
|
||||
|
||||
pub fn get_vm() -> Engine {
|
||||
let mut vm = Engine::new();
|
||||
vm.register_type::<Point>("point?");
|
||||
vm.register_fn("dist2", dist2);
|
||||
vm.register_fn("dist", dist);
|
||||
vm
|
||||
}
|
||||
|
||||
fn dist2(p1: &Point, p2: &Point) -> f32 {
|
||||
p1.point.distance_squared(p2.point)
|
||||
}
|
||||
|
||||
fn dist(p1: &Point, p2: &Point) -> f32 {
|
||||
p1.point.distance(p2.point)
|
||||
}
|
||||
54
src/geom.rs
54
src/geom.rs
|
|
@ -1,56 +1,16 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::{math::bounding::Aabb2d, prelude::*};
|
||||
use ordered_float::OrderedFloat;
|
||||
use rstar::Point as PointTrait;
|
||||
use steel_derive::Steel;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Steel)]
|
||||
const POINT_RADIUS: f32 = f32::EPSILON * 16.0;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Point {
|
||||
pub point: Vec2,
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn new(x: f32, y: f32) -> Self {
|
||||
Self {
|
||||
point: Vec2::new(x, y),
|
||||
entity: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[f32; 2]> for Point {
|
||||
fn from(value: [f32; 2]) -> Self {
|
||||
Self {
|
||||
point: value.into(),
|
||||
entity: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointTrait for Point {
|
||||
type Scalar = f32;
|
||||
|
||||
const DIMENSIONS: usize = 2;
|
||||
|
||||
fn generate(mut generator: impl FnMut(usize) -> Self::Scalar) -> Self {
|
||||
let point = Vec2::new(generator(0), generator(1));
|
||||
Point {
|
||||
point,
|
||||
entity: Entity::PLACEHOLDER,
|
||||
}
|
||||
}
|
||||
|
||||
fn nth(&self, index: usize) -> Self::Scalar {
|
||||
self.point[index]
|
||||
}
|
||||
|
||||
fn nth_mut(&mut self, index: usize) -> &mut Self::Scalar {
|
||||
&mut self.point[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Point {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
OrderedFloat(self.point.x) == OrderedFloat(other.point.x)
|
||||
|
|
@ -69,3 +29,9 @@ impl PartialOrd for Point {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn mbr(&self) -> Aabb2d {
|
||||
Aabb2d::new(self.point, Vec2::splat(POINT_RADIUS))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
pub mod db;
|
||||
pub mod ffi;
|
||||
pub mod geom;
|
||||
pub mod units;
|
||||
pub mod spindex;
|
||||
|
|
|
|||
153
src/spindex.rs
Normal file
153
src/spindex.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
// based on code stolen under the terms of the MIT license from
|
||||
// https://github.com/habedi/spart/blob/0f0e92c556b8906801d3f8b9c2a8a6491f493d9c/src/rstar_tree.rs
|
||||
|
||||
use bevy::{
|
||||
math::bounding::{Aabb2d, BoundingVolume, IntersectsVolume},
|
||||
prelude::Vec2,
|
||||
};
|
||||
|
||||
use crate::geom::Point;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Entry {
|
||||
Leaf { mbr: Aabb2d, object: Point },
|
||||
Node { mbr: Aabb2d, child: TreeNode },
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
/// Returns a reference to the minimum bounding volume for this entry.
|
||||
pub fn mbr(&self) -> &Aabb2d {
|
||||
match self {
|
||||
Entry::Leaf { mbr, .. } | Entry::Node { mbr, .. } => mbr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TreeNode {
|
||||
pub entries: Vec<Entry>,
|
||||
pub is_leaf: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RStarTree {
|
||||
root: TreeNode,
|
||||
max_entries: usize,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn as_leaf_obj(&self) -> Option<&Point> {
|
||||
match self {
|
||||
Entry::Leaf { object, .. } => Some(object),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn child(&self) -> Option<&TreeNode> {
|
||||
match self {
|
||||
Entry::Node { child, .. } => Some(child),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeNode {
|
||||
fn is_leaf(&self) -> bool {
|
||||
self.is_leaf
|
||||
}
|
||||
|
||||
fn entries(&self) -> &[Entry] {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
fn range_search_bbox<'s>(&'s self, bbox: &Aabb2d, out: &mut Vec<&'s Point>) {
|
||||
if self.is_leaf() {
|
||||
for entry in self.entries() {
|
||||
if let Some(obj) = entry.as_leaf_obj()
|
||||
&& entry.mbr().intersects(bbox)
|
||||
{
|
||||
out.push(obj);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for entry in self.entries() {
|
||||
if let Some(child) = entry.child()
|
||||
&& entry.mbr().intersects(bbox)
|
||||
{
|
||||
child.range_search_bbox(bbox, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mbr(&self) -> Option<Aabb2d> {
|
||||
entries_mbr(self.entries())
|
||||
}
|
||||
}
|
||||
|
||||
impl RStarTree {
|
||||
pub fn new(max_entries: usize) -> Self {
|
||||
let max_entries = max_entries.max(4);
|
||||
RStarTree {
|
||||
root: TreeNode {
|
||||
entries: Vec::new(),
|
||||
is_leaf: true,
|
||||
},
|
||||
max_entries,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn range_search(&self, query_point: &Point, radius: f32) -> Vec<&Point> {
|
||||
let query_bbox = Aabb2d::new(query_point.point, Vec2::splat(radius));
|
||||
let r2 = radius.powi(2);
|
||||
let candidates = self.range_search_bbox(&query_bbox);
|
||||
candidates
|
||||
.into_iter()
|
||||
.filter(|&other| query_point.point.distance_squared(other.point) <= r2)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn range_search_bbox(&self, query_bbox: &Aabb2d) -> Vec<&Point> {
|
||||
let mut res = Vec::with_capacity(self.root.entries.len() / 10);
|
||||
self.root.range_search_bbox(query_bbox, &mut res);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn insert_bulk(&mut self, objects: Vec<Point>) {
|
||||
if objects.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut entries: Vec<Entry> = objects
|
||||
.into_iter()
|
||||
.map(|point| Entry::Leaf {
|
||||
mbr: point.mbr(),
|
||||
object: point,
|
||||
})
|
||||
.collect();
|
||||
|
||||
while entries.len() > self.max_entries {
|
||||
let mut new_level_entries = Vec::new();
|
||||
let chunks = entries.chunks(self.max_entries);
|
||||
|
||||
for chunk in chunks {
|
||||
let child = TreeNode {
|
||||
entries: chunk.to_vec(),
|
||||
is_leaf: self.root.is_leaf,
|
||||
};
|
||||
if let Some(mbr) = child.mbr() {
|
||||
new_level_entries.push(Entry::Node { mbr, child });
|
||||
}
|
||||
}
|
||||
entries = new_level_entries;
|
||||
self.root.is_leaf = false;
|
||||
}
|
||||
|
||||
self.root.entries.extend(entries);
|
||||
}
|
||||
}
|
||||
|
||||
fn entries_mbr(entries: &[Entry]) -> Option<Aabb2d> {
|
||||
let mut iter = entries.iter();
|
||||
let first = *iter.next()?.mbr();
|
||||
Some(iter.fold(first, |acc, entry| acc.merge(entry.mbr())))
|
||||
}
|
||||
15
src/units.rs
15
src/units.rs
|
|
@ -1,15 +0,0 @@
|
|||
use steel_derive::Steel;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Steel)]
|
||||
pub enum Weapons {
|
||||
Bullet { damage: u32 },
|
||||
Missile { speed: f32, damage: u32 },
|
||||
Bomb { damage: u32 },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Steel)]
|
||||
pub enum Machines {
|
||||
Tank,
|
||||
Factory,
|
||||
Plane,
|
||||
}
|
||||
Loading…
Reference in a new issue