clawft_kernel/
spatial_backend.rs1use clawft_bvh::{
8 Aabb, BranchId, BranchMeta, DiffEntry, Frustum, Leaf, LeafId, Ray, RayHit, Vec3,
9};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13#[derive(Debug, Error)]
17pub enum SpatialError {
18 #[error("capacity exceeded: max {max} leaves (current {current})")]
20 CapacityExceeded {
21 max: usize,
23 current: usize,
25 },
26
27 #[error("epoch conflict: expected {expected}, actual {actual}")]
29 EpochConflict {
30 expected: u64,
32 actual: u64,
34 },
35
36 #[error("unknown branch {0}")]
38 UnknownBranch(u64),
39
40 #[error("io error: {0}")]
42 Io(#[from] std::io::Error),
43
44 #[error("{0}")]
46 Other(String),
47}
48
49pub type SpatialResult<T> = Result<T, SpatialError>;
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
56#[serde(rename_all = "lowercase")]
57pub enum SpatialBackendKind {
58 #[default]
60 Bvh,
61}
62
63pub trait SpatialBackend: Send + Sync {
70 fn insert(&self, leaf: Leaf) -> SpatialResult<LeafId>;
72
73 fn remove(&self, id: LeafId) -> bool;
75
76 fn get(&self, id: LeafId) -> Option<Leaf>;
78
79 fn query_point(&self, p: Vec3) -> Vec<LeafId>;
81
82 fn query_aabb(&self, bb: Aabb) -> Vec<LeafId>;
84
85 fn query_sphere(&self, center: Vec3, radius: f32) -> Vec<LeafId>;
87
88 fn query_ray(&self, ray: Ray, max_t: f32) -> Vec<RayHit>;
90
91 fn query_frustum(&self, frustum: &Frustum) -> Vec<LeafId>;
93
94 fn query_knn(&self, p: Vec3, k: usize) -> Vec<LeafId>;
96
97 fn derive_branch(&self, meta: BranchMeta) -> SpatialResult<BranchId>;
99
100 fn branch_diff(&self, a: BranchId, b: BranchId, region: Aabb) -> Vec<DiffEntry>;
102
103 fn epoch(&self) -> u64;
105
106 fn len(&self) -> usize;
108
109 fn is_empty(&self) -> bool {
111 self.len() == 0
112 }
113
114 fn backend_name(&self) -> &str;
116
117 fn max_leaves(&self) -> Option<usize> {
119 None
120 }
121
122 fn insert_with_epoch(&self, leaf: Leaf, parent_epoch: u64) -> SpatialResult<LeafId> {
124 let current = self.epoch();
125 if parent_epoch < current {
126 return Err(SpatialError::EpochConflict {
127 expected: parent_epoch,
128 actual: current,
129 });
130 }
131 self.insert(leaf)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn backend_kind_default_is_bvh() {
141 assert_eq!(SpatialBackendKind::default(), SpatialBackendKind::Bvh);
142 }
143
144 #[test]
145 fn error_display() {
146 let err = SpatialError::CapacityExceeded {
147 max: 10,
148 current: 10,
149 };
150 assert!(format!("{err}").contains("10"));
151 let err = SpatialError::EpochConflict {
152 expected: 1,
153 actual: 2,
154 };
155 assert!(format!("{err}").contains('1'));
156 }
157}