1use bevy::{
2 ecs::{entity::Entity, resource::Resource},
3 reflect::prelude::*,
4};
5use obvhs::{
6 aabb::Aabb,
7 bvh2::{Bvh2, reinsertion::ReinsertionOptimizer},
8 ploc::{PlocBuilder, PlocSearchDistance, SortPrecision, rebuild::compute_rebuild_path_flags},
9};
10
11use crate::{
12 collider_tree::ProxyId,
13 data_structures::stable_vec::StableVec,
14 prelude::{ActiveCollisionHooks, CollisionLayers},
15};
16
17#[derive(Clone, Default)]
23pub struct ColliderTree {
24 pub bvh: Bvh2,
26 pub proxies: StableVec<ColliderTreeProxy>,
28 pub moved_proxies: MovedProxyList,
33 pub workspace: ColliderTreeWorkspace,
35}
36
37#[derive(Clone, Default, Debug)]
42pub struct MovedProxyList {
43 proxies: Vec<ProxyId>,
45 indices: Vec<u32>,
47}
48
49const PROXY_NOT_PRESENT: u32 = u32::MAX;
51
52impl MovedProxyList {
53 #[inline]
57 pub fn push(&mut self, proxy_id: ProxyId) -> bool {
58 if self.indices.len() <= proxy_id.index() {
59 self.indices.resize(proxy_id.index() + 1, PROXY_NOT_PRESENT);
60 } else if self.indices[proxy_id.index()] != PROXY_NOT_PRESENT {
61 return false;
62 }
63
64 self.indices[proxy_id.index()] = self.proxies.len() as u32;
65 self.proxies.push(proxy_id);
66
67 true
68 }
69
70 #[inline]
74 pub fn remove(&mut self, proxy_id: ProxyId) {
75 let Some(index) = self.indices.get_mut(proxy_id.index()) else {
76 return;
77 };
78 if *index == PROXY_NOT_PRESENT {
79 return;
80 }
81
82 let index = core::mem::replace(index, PROXY_NOT_PRESENT) as usize;
83 self.proxies.swap_remove(index);
84
85 if let Some(&swapped) = self.proxies.get(index) {
87 self.indices[swapped.index()] = index as u32;
88 }
89 }
90
91 #[inline]
93 pub fn clear(&mut self) {
94 for proxy_id in self.proxies.drain(..) {
95 self.indices[proxy_id.index()] = PROXY_NOT_PRESENT;
96 }
97 }
98}
99
100impl core::ops::Deref for MovedProxyList {
101 type Target = [ProxyId];
102
103 #[inline]
104 fn deref(&self) -> &[ProxyId] {
105 &self.proxies
106 }
107}
108
109#[derive(Clone, Debug)]
111pub struct ColliderTreeProxy {
112 pub collider: Entity,
114 pub body: Option<Entity>,
116 pub layers: CollisionLayers,
118 pub flags: ColliderTreeProxyFlags,
120}
121
122#[repr(transparent)]
124#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
125#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
126#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
127#[reflect(Debug, PartialEq)]
128pub struct ColliderTreeProxyFlags(u32);
129
130bitflags::bitflags! {
131 impl ColliderTreeProxyFlags: u32 {
132 const SENSOR = 1 << 0;
134 const BODY_DISABLED = 1 << 1;
136 const CUSTOM_FILTER = 1 << 2;
138 const MODIFY_CONTACTS = 1 << 3;
140 const CONTACT_EVENTS = 1 << 4;
142 }
143}
144
145impl ColliderTreeProxyFlags {
146 #[inline]
148 pub fn new(
149 is_sensor: bool,
150 is_body_disabled: bool,
151 events_enabled: bool,
152 active_hooks: ActiveCollisionHooks,
153 ) -> Self {
154 let mut flags = ColliderTreeProxyFlags::empty();
155 if is_sensor {
156 flags |= ColliderTreeProxyFlags::SENSOR;
157 }
158 if is_body_disabled {
159 flags |= ColliderTreeProxyFlags::BODY_DISABLED;
160 }
161 if active_hooks.contains(ActiveCollisionHooks::FILTER_PAIRS) {
162 flags |= ColliderTreeProxyFlags::CUSTOM_FILTER;
163 }
164 if active_hooks.contains(ActiveCollisionHooks::MODIFY_CONTACTS) {
165 flags |= ColliderTreeProxyFlags::MODIFY_CONTACTS;
166 }
167 if events_enabled {
168 flags |= ColliderTreeProxyFlags::CONTACT_EVENTS;
169 }
170 flags
171 }
172}
173
174impl ColliderTreeProxy {
175 #[inline]
177 pub fn is_sensor(&self) -> bool {
178 self.flags.contains(ColliderTreeProxyFlags::SENSOR)
179 }
180
181 #[inline]
183 pub fn has_custom_filter(&self) -> bool {
184 self.flags.contains(ColliderTreeProxyFlags::CUSTOM_FILTER)
185 }
186
187 #[inline]
189 pub fn has_contact_modification(&self) -> bool {
190 self.flags.contains(ColliderTreeProxyFlags::MODIFY_CONTACTS)
191 }
192}
193
194#[derive(Resource, Default)]
200pub struct ColliderTreeWorkspace {
201 pub ploc_builder: PlocBuilder,
203 pub reinsertion_optimizer: ReinsertionOptimizer,
205 pub temp_flags: Vec<bool>,
207}
208
209impl Clone for ColliderTreeWorkspace {
210 fn clone(&self) -> Self {
211 Self {
212 ploc_builder: self.ploc_builder.clone(),
213 reinsertion_optimizer: ReinsertionOptimizer::default(),
214 temp_flags: Vec::new(),
215 }
216 }
217}
218
219impl ColliderTree {
220 #[inline]
222 pub fn add_proxy(&mut self, aabb: Aabb, proxy: ColliderTreeProxy) -> ProxyId {
223 let id = self.proxies.push(proxy) as u32;
224
225 self.bvh.insert_primitive(aabb, id);
227
228 self.moved_proxies.push(ProxyId::new(id));
230
231 ProxyId::new(id)
232 }
233
234 #[inline]
238 pub fn remove_proxy(&mut self, proxy_id: ProxyId) -> Option<ColliderTreeProxy> {
239 if let Some(proxy) = self.proxies.try_remove(proxy_id.index()) {
240 self.bvh.remove_primitive(proxy_id.id());
242
243 self.moved_proxies.remove(proxy_id);
245
246 Some(proxy)
247 } else {
248 None
249 }
250 }
251
252 #[inline]
256 pub fn get_proxy(&self, proxy_id: ProxyId) -> Option<&ColliderTreeProxy> {
257 self.proxies.get(proxy_id.index())
258 }
259
260 #[inline]
264 pub fn get_proxy_mut(&mut self, proxy_id: ProxyId) -> Option<&mut ColliderTreeProxy> {
265 self.proxies.get_mut(proxy_id.index())
266 }
267
268 #[inline]
274 pub unsafe fn get_proxy_unchecked(&self, proxy_id: ProxyId) -> &ColliderTreeProxy {
275 unsafe { self.proxies.get_unchecked(proxy_id.index()) }
276 }
277
278 #[inline]
284 pub unsafe fn get_proxy_unchecked_mut(&mut self, proxy_id: ProxyId) -> &mut ColliderTreeProxy {
285 unsafe { self.proxies.get_unchecked_mut(proxy_id.index()) }
286 }
287
288 #[inline]
292 pub fn get_proxy_aabb(&self, proxy_id: ProxyId) -> Option<Aabb> {
293 let node_id = self.bvh.primitives_to_nodes.get(proxy_id.index())?;
294 self.bvh.nodes.get(*node_id as usize).map(|node| node.aabb)
295 }
296
297 #[inline]
303 pub unsafe fn get_proxy_aabb_unchecked(&self, proxy_id: ProxyId) -> Aabb {
304 unsafe {
305 let node_id = *self.bvh.primitives_to_nodes.get_unchecked(proxy_id.index()) as usize;
306 self.bvh.nodes.get_unchecked(node_id).aabb
307 }
308 }
309
310 #[inline]
318 pub fn set_proxy_aabb(&mut self, proxy_id: ProxyId, aabb: Aabb) {
319 let node_index = self.bvh.primitives_to_nodes[proxy_id.index()] as usize;
321
322 self.bvh.nodes[node_index].set_aabb(aabb);
324 }
325
326 #[inline]
334 pub fn resize_proxy_aabb(&mut self, proxy_id: ProxyId, aabb: Aabb) {
335 let node_index = self.bvh.primitives_to_nodes[proxy_id.index()] as usize;
336 self.bvh.resize_node(node_index, aabb);
337 }
338
339 #[inline]
341 pub fn reinsert_proxy(&mut self, proxy_id: ProxyId, aabb: Aabb) {
342 let node_id = self.bvh.primitives_to_nodes[proxy_id.index()];
344 self.bvh.resize_node(node_id as usize, aabb);
345 self.bvh.reinsert_node(node_id as usize);
346 }
347
348 #[inline]
350 pub fn refit_all(&mut self) {
351 self.bvh.refit_all();
352 }
353
354 #[inline]
356 pub fn rebuild_full(&mut self) {
357 self.workspace.ploc_builder.full_rebuild(
358 &mut self.bvh,
359 PlocSearchDistance::Minimum,
360 SortPrecision::U64,
361 0,
362 );
363 }
364
365 #[inline]
367 pub fn rebuild_partial(&mut self, leaves: &[u32]) {
368 self.bvh.init_parents_if_uninit();
369
370 compute_rebuild_path_flags(&self.bvh, leaves, &mut self.workspace.temp_flags);
372
373 self.workspace.ploc_builder.partial_rebuild(
374 &mut self.bvh,
375 |node_id| self.workspace.temp_flags[node_id],
376 PlocSearchDistance::Minimum,
377 SortPrecision::U64,
378 0,
379 );
380 }
381
382 #[inline]
387 pub fn optimize(&mut self, batch_size_ratio: f32) {
388 self.workspace
389 .reinsertion_optimizer
390 .run(&mut self.bvh, batch_size_ratio, None);
391 }
392
393 #[inline]
397 pub fn optimize_candidates(&mut self, candidates: &[u32], iterations: u32) {
398 self.workspace.reinsertion_optimizer.run_with_candidates(
399 &mut self.bvh,
400 candidates,
401 iterations,
402 );
403 }
404}