From d709eb5ec859d0df047cb31a77c647bb4a6ac0e4 Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 19 Jun 2022 00:25:25 -0700 Subject: [PATCH] Add spatial index --- examples/chunk.rs | 2 +- src/bvh.rs | 108 ++++++++++------------ src/client.rs | 2 +- src/entity.rs | 49 +++++++--- src/lib.rs | 2 + src/packets.rs | 22 +++-- src/server.rs | 2 + src/slotmap.rs | 60 ++++++------- src/spatial_index.rs | 209 +++++++++++++++++++++++++++++++++++++++++++ src/util.rs | 2 +- src/world.rs | 14 ++- 11 files changed, 356 insertions(+), 116 deletions(-) create mode 100644 src/spatial_index.rs diff --git a/examples/chunk.rs b/examples/chunk.rs index dccdb4f..781339d 100644 --- a/examples/chunk.rs +++ b/examples/chunk.rs @@ -7,7 +7,7 @@ use valence::client::GameMode; use valence::config::{Config, ServerListPing}; use valence::text::Color; use valence::{ - async_trait, ChunkPos, ClientMut, DimensionId, EntityType, Server, ShutdownResult, Text, + async_trait, ChunkPos, ClientMut, DimensionId, Server, ShutdownResult, Text, TextFormat, WorldId, WorldsMut, }; diff --git a/src/bvh.rs b/src/bvh.rs index fb0089b..e72ba85 100644 --- a/src/bvh.rs +++ b/src/bvh.rs @@ -2,7 +2,7 @@ use std::mem; use approx::relative_eq; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use vek::Aabr; +use vek::Aabb; #[derive(Clone)] pub struct Bvh { @@ -11,19 +11,27 @@ pub struct Bvh { root: NodeIdx, } +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum TraverseStep { + Miss, + Hit, + Return(T), +} + #[derive(Clone)] struct InternalNode { - bb: Aabr, + bb: Aabb, left: NodeIdx, right: NodeIdx, } #[derive(Clone)] struct LeafNode { - bb: Aabr, + bb: Aabb, id: T, } +// TODO: we could use usize here to store more elements. type NodeIdx = u32; impl Bvh { @@ -35,7 +43,7 @@ impl Bvh { } } - pub fn build(&mut self, leaves: impl IntoIterator)>) { + pub fn build(&mut self, leaves: impl IntoIterator)>) { self.leaf_nodes.clear(); self.internal_nodes.clear(); @@ -52,7 +60,7 @@ impl Bvh { self.internal_nodes.resize( leaf_count - 1, InternalNode { - bb: Aabr::default(), + bb: Aabb::default(), left: NodeIdx::MAX, right: NodeIdx::MAX, }, @@ -71,7 +79,7 @@ impl Bvh { .leaf_nodes .par_iter() .map(|l| l.bb) - .reduce(|| id, Aabr::union); + .reduce(|| id, Aabb::union); self.root = build_rec( 0, @@ -85,74 +93,49 @@ impl Bvh { debug_assert_eq!(self.internal_nodes.len(), self.leaf_nodes.len() - 1); } - pub fn find(&self, mut collides: C, mut find: F) -> Option + pub fn traverse(&self, mut f: F) -> Option where - C: FnMut(Aabr) -> bool, - F: FnMut(&T, Aabr) -> Option, + F: FnMut(Option<&T>, Aabb) -> TraverseStep, { if !self.leaf_nodes.is_empty() { - self.find_rec(self.root, &mut collides, &mut find) + self.traverse_rec(self.root, &mut f) } else { None } } - fn find_rec(&self, idx: NodeIdx, collides: &mut C, find: &mut F) -> Option + fn traverse_rec(&self, idx: NodeIdx, f: &mut F) -> Option where - C: FnMut(Aabr) -> bool, - F: FnMut(&T, Aabr) -> Option, + F: FnMut(Option<&T>, Aabb) -> TraverseStep, { if idx < self.internal_nodes.len() as NodeIdx { let internal = &self.internal_nodes[idx as usize]; - if collides(internal.bb) { - if let Some(found) = self.find_rec(internal.left, collides, find) { - return Some(found); - } - - if let Some(found) = self.find_rec(internal.right, collides, find) { - return Some(found); - } + match f(None, internal.bb) { + TraverseStep::Miss => None, + TraverseStep::Hit => self + .traverse_rec(internal.left, f) + .or_else(|| self.traverse_rec(internal.right, f)), + TraverseStep::Return(u) => Some(u), } } else { let leaf = &self.leaf_nodes[(idx - self.internal_nodes.len() as NodeIdx) as usize]; - if collides(leaf.bb) { - return find(&leaf.id, leaf.bb); + match f(Some(&leaf.id), leaf.bb) { + TraverseStep::Miss | TraverseStep::Hit => None, + TraverseStep::Return(u) => Some(u), } } - - None - } - - pub fn visit(&self, mut f: impl FnMut(Aabr, usize)) { - if !self.leaf_nodes.is_empty() { - self.visit_rec(self.root, 0, &mut f); - } - } - - pub fn visit_rec(&self, idx: NodeIdx, depth: usize, f: &mut impl FnMut(Aabr, usize)) { - if idx >= self.internal_nodes.len() as NodeIdx { - let leaf = &self.leaf_nodes[(idx - self.internal_nodes.len() as NodeIdx) as usize]; - f(leaf.bb, depth); - } else { - let internal = &self.internal_nodes[idx as usize]; - - self.visit_rec(internal.left, depth + 1, f); - self.visit_rec(internal.right, depth + 1, f); - - f(internal.bb, depth); - } } } fn build_rec( idx: NodeIdx, - bounds: Aabr, + bounds: Aabb, internal_nodes: &mut [InternalNode], leaf_nodes: &mut [LeafNode], total_leaf_count: NodeIdx, -) -> (NodeIdx, Aabr) { +) -> (NodeIdx, Aabb) { debug_assert_eq!(leaf_nodes.len() - 1, internal_nodes.len()); if leaf_nodes.len() == 1 { @@ -163,19 +146,26 @@ fn build_rec( debug_assert!(bounds.is_valid()); let dims = bounds.max - bounds.min; - let (mut split, bounds_left, bounds_right) = if dims.x >= dims.y { + let (mut split, bounds_left, bounds_right) = if dims.x >= dims.y && dims.x >= dims.z { let mid = middle(bounds.min.x, bounds.max.x); let [bounds_left, bounds_right] = bounds.split_at_x(mid); let p = partition(leaf_nodes, |l| middle(l.bb.min.x, l.bb.max.x) <= mid); (p, bounds_left, bounds_right) - } else { + } else if dims.y >= dims.x && dims.y >= dims.z { let mid = middle(bounds.min.y, bounds.max.y); let [bounds_left, bounds_right] = bounds.split_at_y(mid); let p = partition(leaf_nodes, |l| middle(l.bb.min.y, l.bb.max.y) <= mid); + (p, bounds_left, bounds_right) + } else { + let mid = middle(bounds.min.z, bounds.max.z); + let [bounds_left, bounds_right] = bounds.split_at_z(mid); + + let p = partition(leaf_nodes, |l| middle(l.bb.min.z, l.bb.max.z) <= mid); + (p, bounds_left, bounds_right) }; @@ -262,7 +252,7 @@ fn partition(s: &mut [T], mut pred: impl FnMut(&T) -> bool) -> usize { true_count } -fn middle(a: f32, b: f32) -> f32 { +fn middle(a: f64, b: f64) -> f64 { (a + b) / 2.0 } @@ -280,11 +270,11 @@ mod tests { fn empty() { let mut bvh = Bvh::new(); - bvh.find(|_| false, |_, _| Some(())); + bvh.traverse(|_, _| TraverseStep::Return(())); bvh.build([]); - bvh.build([(5, Aabr::default())]); - bvh.find(|_| false, |_, _| Some(())); + bvh.build([(5, Aabb::default())]); + bvh.traverse(|_, _| TraverseStep::Return(())); } #[test] @@ -292,13 +282,13 @@ mod tests { let mut bvh = Bvh::new(); bvh.build([ - ((), Aabr::default()), - ((), Aabr::default()), - ((), Aabr::default()), - ((), Aabr::default()), - ((), Aabr::new_empty(5.0.into())), + ((), Aabb::default()), + ((), Aabb::default()), + ((), Aabb::default()), + ((), Aabb::default()), + ((), Aabb::new_empty(5.0.into())), ]); - bvh.find(|_| false, |_, _| Some(())); + bvh.traverse(|_, _| TraverseStep::Return(())); } } diff --git a/src/client.rs b/src/client.rs index 8c3078a..7f718e6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -52,7 +52,7 @@ impl Clients { } pub fn count(&self) -> usize { - self.sm.count() + self.sm.len() } pub fn get(&self, client: ClientId) -> Option<&Client> { diff --git a/src/entity.rs b/src/entity.rs index c593195..e5debd1 100644 --- a/src/entity.rs +++ b/src/entity.rs @@ -4,6 +4,7 @@ pub mod types; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::iter::FusedIterator; +use std::num::NonZeroU32; use std::ops::Deref; use bitfield_struct::bitfield; @@ -24,6 +25,7 @@ use crate::var_int::VarInt; pub struct Entities { sm: SlotMap, uuid_to_entity: HashMap, + network_id_to_entity: HashMap, } pub struct EntitiesMut<'a>(&'a mut Entities); @@ -41,12 +43,13 @@ impl Entities { Self { sm: SlotMap::new(), uuid_to_entity: HashMap::new(), + network_id_to_entity: HashMap::new(), } } /// Returns the number of live entities. - pub fn count(&self) -> usize { - self.sm.count() + pub fn len(&self) -> usize { + self.sm.len() } /// Gets the [`EntityId`] of the entity with the given UUID in an efficient @@ -76,6 +79,10 @@ impl<'a> EntitiesMut<'a> { Self(entities) } + pub fn reborrow(&mut self) -> EntitiesMut { + EntitiesMut(self.0) + } + /// Spawns a new entity with the default data. The new entity's [`EntityId`] /// is returned. /// @@ -87,7 +94,7 @@ impl<'a> EntitiesMut<'a> { } /// Like [`create`](Entities::create), but requires specifying the new - /// entity's UUID. This is useful for deserialization. + /// entity's UUID. /// /// The provided UUID must not conflict with an existing entity UUID in this /// world. If it does, `None` is returned and the entity is not spawned. @@ -95,7 +102,7 @@ impl<'a> EntitiesMut<'a> { match self.0.uuid_to_entity.entry(uuid) { Entry::Occupied(_) => None, Entry::Vacant(ve) => { - let (id, entity) = self.0.sm.insert(Entity { + let (k, e) = self.0.sm.insert(Entity { flags: EntityFlags(0), meta: EntityMeta::new(EntityType::Marker), new_position: Vec3::default(), @@ -107,11 +114,9 @@ impl<'a> EntitiesMut<'a> { uuid, }); - ve.insert(EntityId(id)); + ve.insert(EntityId(k)); - // TODO: insert into partition. - - Some((EntityId(id), EntityMut(entity))) + Some((EntityId(k), EntityMut(e))) } } } @@ -123,7 +128,11 @@ impl<'a> EntitiesMut<'a> { .remove(&e.uuid) .expect("UUID should have been in UUID map"); - // TODO: remove entity from partition. + self.0 + .network_id_to_entity + .remove(&entity.0.version()) + .expect("network ID should have been in the network ID map"); + true } else { false @@ -131,8 +140,23 @@ impl<'a> EntitiesMut<'a> { } pub fn retain(&mut self, mut f: impl FnMut(EntityId, EntityMut) -> bool) { - // TODO - self.0.sm.retain(|k, v| f(EntityId(k), EntityMut(v))) + self.0.sm.retain(|k, v| { + if f(EntityId(k), EntityMut(v)) { + true + } else { + self.0 + .uuid_to_entity + .remove(&v.uuid) + .expect("UUID should have been in UUID map"); + + self.0 + .network_id_to_entity + .remove(&k.version()) + .expect("network ID should have been in the network ID map"); + + false + } + }); } pub fn get_mut(&mut self, entity: EntityId) -> Option { @@ -170,8 +194,7 @@ pub struct EntityId(Key); impl EntityId { pub(crate) fn to_network_id(self) -> i32 { - // ID 0 is reserved for clients. - self.0.index() as i32 + 1 + self.0.version().get() as i32 } } diff --git a/src/lib.rs b/src/lib.rs index 0988e12..dd1110a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ mod packets; mod protocol; pub mod server; mod slotmap; +pub mod spatial_index; pub mod text; pub mod util; mod var_int; @@ -39,6 +40,7 @@ pub use dimension::{Dimension, DimensionId}; pub use entity::{Entities, EntitiesMut, Entity, EntityId, EntityType}; pub use ident::Ident; pub use server::{start_server, NewClientData, Server, ShutdownResult}; +pub use spatial_index::{SpatialIndex, SpatialIndexMut}; pub use text::{Text, TextFormat}; pub use uuid::Uuid; pub use world::{WorldId, WorldMeta, WorldMetaMut, WorldMut, WorldRef, Worlds, WorldsMut}; diff --git a/src/packets.rs b/src/packets.rs index 96c4509..9484f4a 100644 --- a/src/packets.rs +++ b/src/packets.rs @@ -1133,7 +1133,7 @@ pub mod play { #[cfg(test)] #[test] - fn test_s2c_play_packet_order() { + fn s2c_play_packet_order() { let ids = [ $( (stringify!($packet), $packet::PACKET_ID), @@ -1141,7 +1141,13 @@ pub mod play { ]; if let Some(w) = ids.windows(2).find(|w| w[0].1 >= w[1].1) { - panic!("the {} and {} variants of the s2c play packet enum are not properly sorted by their packet ID", w[0].0, w[1].0); + panic!( + "the {} (ID {:#x}) and {} (ID {:#x}) variants of the s2c play packet enum are not properly sorted by their packet ID", + w[0].0, + w[0].1, + w[1].0, + w[1].1 + ); } } } @@ -1181,8 +1187,8 @@ pub mod play { SpawnPosition, EntityMetadata, EntityVelocity, - EntityTeleport, TimeUpdate, + EntityTeleport, } } @@ -1771,7 +1777,7 @@ pub mod play { #[cfg(test)] #[test] - fn test_c2s_play_packet_order() { + fn c2s_play_packet_order() { let ids = [ $( (stringify!($packet), $packet::PACKET_ID), @@ -1779,7 +1785,13 @@ pub mod play { ]; if let Some(w) = ids.windows(2).find(|w| w[0].1 >= w[1].1) { - panic!("the {} and {} variants of the c2s play packet enum are not properly sorted by their packet ID", w[0].0, w[1].0); + panic!( + "the {} (ID {:#x}) and {} (ID {:#x}) variants of the c2s play packet enum are not properly sorted by their packet ID", + w[0].0, + w[0].1, + w[1].0, + w[1].1 + ); } } } diff --git a/src/server.rs b/src/server.rs index 52c75be..fb1d5e6 100644 --- a/src/server.rs +++ b/src/server.rs @@ -372,6 +372,8 @@ fn do_update_loop(server: Server, mut worlds: WorldsMut) -> ShutdownResult { } }); + world.spatial_index.update(world.entities.reborrow()); + world.clients.par_iter_mut().for_each(|(_, mut client)| { client.update(&server, &world.entities, &world.chunks, &world.meta); }); diff --git a/src/slotmap.rs b/src/slotmap.rs index 0b22be1..13088ca 100644 --- a/src/slotmap.rs +++ b/src/slotmap.rs @@ -1,9 +1,6 @@ -//! Like the `slotmap` crate, but uses no unsafe code and has rayon support. - use std::iter::FusedIterator; use std::mem; -use std::num::{NonZeroU32, NonZeroU64}; -use std::sync::atomic::{AtomicU64, Ordering}; +use std::num::NonZeroU32; use rayon::iter::{ IndexedParallelIterator, IntoParallelRefIterator, IntoParallelRefMutIterator, ParallelIterator, @@ -16,34 +13,30 @@ pub struct SlotMap { next_free_head: u32, /// The number of occupied slots. count: u32, + /// Version counter. + version: NonZeroU32, } #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct Key { index: u32, - // Split the u64 version into two u32 fields so that the key is 12 bytes on 64 bit systems. - version_high: NonZeroU32, - version_low: u32, + version: NonZeroU32, } impl Key { - fn new(index: u32, version: NonZeroU64) -> Self { - Self { - index, - version_high: NonZeroU32::new((version.get() >> 32) as u32) - .expect("versions <= 0x00000000ffffffff are illegal"), - version_low: version.get() as u32, - } + pub fn new(index: u32, version: NonZeroU32) -> Self { + Self { index, version } } - fn new_unique(index: u32) -> Self { - static NEXT: AtomicU64 = AtomicU64::new(u64::MAX); + fn new_unique(index: u32, version: &mut NonZeroU32) -> Self { + *version = NonZeroU32::new(version.get().wrapping_add(1)).unwrap_or_else(|| { + log::warn!("slotmap version overflow"); + ONE + }); - let version = NEXT.fetch_sub(1, Ordering::SeqCst); Self { index, - version_high: ((version >> 32) as u32).try_into().unwrap(), - version_low: version as u32, + version: *version, } } @@ -51,15 +44,19 @@ impl Key { self.index } - pub fn version(self) -> NonZeroU64 { - let n = (self.version_high.get() as u64) << 32 | self.version_low as u64; - NonZeroU64::new(n).expect("version should be nonzero") + pub fn version(self) -> NonZeroU32 { + self.version } } +const ONE: NonZeroU32 = match NonZeroU32::new(1) { + Some(n) => n, + None => unreachable!(), +}; + #[derive(Clone, Debug)] enum Slot { - Occupied { value: T, version: NonZeroU64 }, + Occupied { value: T, version: NonZeroU32 }, Free { next_free: u32 }, } @@ -69,10 +66,11 @@ impl SlotMap { slots: Vec::new(), next_free_head: 0, count: 0, + version: ONE, } } - pub fn count(&self) -> usize { + pub fn len(&self) -> usize { self.count as usize } @@ -87,7 +85,7 @@ impl SlotMap { self.count += 1; self.next_free_head += 1; - let key = Key::new_unique(self.next_free_head - 1); + let key = Key::new_unique(self.next_free_head - 1, &mut self.version); self.slots.push(Slot::Occupied { value: f(key), @@ -107,7 +105,7 @@ impl SlotMap { Slot::Free { next_free } => *next_free, }; - let key = Key::new_unique(self.next_free_head); + let key = Key::new_unique(self.next_free_head, &mut self.version); *slot = Slot::Occupied { value: f(key), @@ -258,7 +256,7 @@ mod tests { assert_eq!(sm.remove(k0), Some(10)); sm.clear(); - assert_eq!(sm.count(), 0); + assert_eq!(sm.len(), 0); } #[test] @@ -272,15 +270,9 @@ mod tests { sm.retain(|k, _| k == k1); assert_eq!(sm.get(k1), Some(&20)); - assert_eq!(sm.count(), 1); + assert_eq!(sm.len(), 1); assert_eq!(sm.get(k0), None); assert_eq!(sm.get(k2), None); } - - #[test] - #[should_panic] - fn bad_key() { - let _ = Key::new(0, NonZeroU64::new(0x00000000ffffffff).unwrap()); - } } diff --git a/src/spatial_index.rs b/src/spatial_index.rs new file mode 100644 index 0000000..d2cc568 --- /dev/null +++ b/src/spatial_index.rs @@ -0,0 +1,209 @@ +use std::ops::Deref; + +use vek::{Aabb, Vec3}; + +use crate::bvh::Bvh; +pub use crate::bvh::TraverseStep; +use crate::{EntitiesMut, EntityId}; + +pub struct SpatialIndex { + bvh: Bvh, +} + +pub struct SpatialIndexMut<'a>(&'a mut SpatialIndex); + +impl<'a> Deref for SpatialIndexMut<'a> { + type Target = SpatialIndex; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl SpatialIndex { + pub(crate) fn new() -> Self { + Self { bvh: Bvh::new() } + } + + pub fn traverse(&self, mut f: F) -> Option + where + F: FnMut(Option, Aabb) -> TraverseStep, + { + self.bvh.traverse(|e, bb| f(e.cloned(), bb)) + } + + pub fn query(&self, mut collides: C, mut f: F) -> Option + where + C: FnMut(Aabb) -> bool, + F: FnMut(EntityId, Aabb) -> Option, + { + self.traverse(|e, bb| { + if collides(bb) { + e.and_then(|id| f(id, bb)) + .map_or(TraverseStep::Hit, TraverseStep::Return) + } else { + TraverseStep::Miss + } + }) + } + + pub fn raycast(&self, origin: Vec3, direction: Vec3) -> Option { + debug_assert!( + direction.is_normalized(), + "the ray direction must be normalized" + ); + + let mut hit: Option = None; + + self.traverse::<_, ()>(|entity, bb| { + if let Some((near, far)) = ray_box_intersection(origin, direction, bb) { + if hit.as_ref().map_or(true, |hit| near < hit.near) { + if let Some(entity) = entity { + hit = Some(RaycastHit { + entity, + bb, + near, + far, + }); + } + TraverseStep::Hit + } else { + // Do not explore subtrees that cannot produce an intersection closer than the + // closest we've seen so far. + TraverseStep::Miss + } + } else { + TraverseStep::Miss + } + }); + + hit + } + + pub fn raycast_all(&self, origin: Vec3, direction: Vec3, mut f: F) -> Option + where + F: FnMut(RaycastHit) -> Option, + { + debug_assert!( + direction.is_normalized(), + "the ray direction must be normalized" + ); + + self.traverse( + |entity, bb| match (ray_box_intersection(origin, direction, bb), entity) { + (Some((near, far)), Some(entity)) => { + let hit = RaycastHit { + entity, + bb, + near, + far, + }; + f(hit).map_or(TraverseStep::Hit, TraverseStep::Return) + } + (Some(_), None) => TraverseStep::Hit, + (None, _) => TraverseStep::Miss, + }, + ) + } +} + +impl<'a> SpatialIndexMut<'a> { + pub(crate) fn new(si: &'a mut SpatialIndex) -> Self { + Self(si) + } + + pub(crate) fn update(&mut self, entities: EntitiesMut) { + self.0 + .bvh + .build(entities.iter().map(|(id, e)| (id, e.hitbox()))) + } +} + +/// Represents an intersection between a ray and an entity's axis-aligned +/// bounding box. +#[derive(Clone, Copy, PartialEq)] +pub struct RaycastHit { + /// The [`EntityId`] of the entity that was hit by the ray. + pub entity: EntityId, + /// The bounding box of the entity that was hit. + pub bb: Aabb, + /// The distance from the ray origin to the closest intersection point. + /// If the origin of the ray is inside the bounding box, then this will be + /// zero. + pub near: f64, + /// The distance from the ray origin to the second intersection point. This + /// represents the point at which the ray exits the bounding box. + pub far: f64, +} + +fn ray_box_intersection(ro: Vec3, rd: Vec3, bb: Aabb) -> Option<(f64, f64)> { + let mut near = -f64::INFINITY; + let mut far = f64::INFINITY; + + for i in 0..3 { + // Rust's definition of min and max properly handle the NaNs that these + // computations might produce. + let t0 = (bb.min[i] - ro[i]) / rd[i]; + let t1 = (bb.max[i] - ro[i]) / rd[i]; + + near = near.max(t0.min(t1)); + far = far.min(t0.max(t1)); + } + + if near <= far && far >= 0.0 { + Some((near.max(0.0), far)) + } else { + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ray_box_edge_cases() { + let bb = Aabb { + min: Vec3::new(0.0, 0.0, 0.0), + max: Vec3::new(1.0, 1.0, 1.0), + }; + + let ros = [ + // On a corner + Vec3::new(0.0, 0.0, 0.0), + // Outside + Vec3::new(-0.5, 0.5, -0.5), + // In the center + Vec3::new(0.5, 0.5, 0.5), + // On an edge + Vec3::new(0.0, 0.5, 0.0), + // On a face + Vec3::new(0.0, 0.5, 0.5), + // Outside slabs + Vec3::new(-2.0, -2.0, -2.0), + ]; + + let rds = [ + Vec3::new(1.0, 0.0, 0.0), + Vec3::new(-1.0, 0.0, 0.0), + Vec3::new(0.0, 1.0, 0.0), + Vec3::new(0.0, -1.0, 0.0), + Vec3::new(0.0, 0.0, 1.0), + Vec3::new(0.0, 0.0, -1.0), + ]; + + assert!(rds.iter().all(|d| d.is_normalized())); + + for ro in ros { + for rd in rds { + if let Some((near, far)) = ray_box_intersection(ro, rd, bb) { + assert!(near.is_finite()); + assert!(far.is_finite()); + assert!(near <= far); + assert!(near >= 0.0); + assert!(far >= 0.0); + } + } + } + } +} diff --git a/src/util.rs b/src/util.rs index e837027..e213f22 100644 --- a/src/util.rs +++ b/src/util.rs @@ -2,7 +2,7 @@ use std::iter::FusedIterator; use num::cast::AsPrimitive; use num::Float; -use vek::{Aabb, Extent3, Vec3}; +use vek::{Aabb, Vec3}; use crate::ChunkPos; diff --git a/src/world.rs b/src/world.rs index cb207b9..3744ab8 100644 --- a/src/world.rs +++ b/src/world.rs @@ -4,7 +4,10 @@ use std::ops::Deref; use rayon::iter::ParallelIterator; use crate::slotmap::{Key, SlotMap}; -use crate::{Chunks, ChunksMut, Clients, ClientsMut, DimensionId, Entities, EntitiesMut, Server}; +use crate::{ + Chunks, ChunksMut, Clients, ClientsMut, DimensionId, Entities, EntitiesMut, Server, + SpatialIndex, SpatialIndexMut, +}; pub struct Worlds { sm: SlotMap, @@ -33,7 +36,7 @@ impl Worlds { } pub fn count(&self) -> usize { - self.sm.count() + self.sm.len() } pub fn get(&self, world: WorldId) -> Option { @@ -58,6 +61,7 @@ impl<'a> WorldsMut<'a> { let (id, world) = self.0.sm.insert(World { clients: Clients::new(), entities: Entities::new(), + spatial_index: SpatialIndex::new(), chunks: Chunks::new( self.server.clone(), (self.server.dimension(dim).height / 16) as u32, @@ -122,6 +126,7 @@ impl<'a> WorldsMut<'a> { pub(crate) struct World { clients: Clients, entities: Entities, + spatial_index: SpatialIndex, chunks: Chunks, meta: WorldMeta, } @@ -130,6 +135,7 @@ pub(crate) struct World { pub struct WorldRef<'a> { pub clients: &'a Clients, pub entities: &'a Entities, + pub spatial_index: &'a SpatialIndex, pub chunks: &'a Chunks, pub meta: &'a WorldMeta, } @@ -139,6 +145,7 @@ impl<'a> WorldRef<'a> { Self { clients: &w.clients, entities: &w.entities, + spatial_index: &w.spatial_index, chunks: &w.chunks, meta: &w.meta, } @@ -149,6 +156,7 @@ impl<'a> WorldRef<'a> { pub struct WorldMut<'a> { pub clients: ClientsMut<'a>, pub entities: EntitiesMut<'a>, + pub spatial_index: SpatialIndexMut<'a>, pub chunks: ChunksMut<'a>, pub meta: WorldMetaMut<'a>, } @@ -158,6 +166,7 @@ impl<'a> WorldMut<'a> { WorldMut { clients: ClientsMut::new(&mut w.clients), entities: EntitiesMut::new(&mut w.entities), + spatial_index: SpatialIndexMut::new(&mut w.spatial_index), chunks: ChunksMut::new(&mut w.chunks), meta: WorldMetaMut(&mut w.meta), } @@ -167,6 +176,7 @@ impl<'a> WorldMut<'a> { WorldRef { clients: &self.clients, entities: &self.entities, + spatial_index: &self.spatial_index, chunks: &self.chunks, meta: &self.meta, }