@ -0,0 +1,2 @@ | 
				
			|||||||
 | 
					/target/ | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -0,0 +1,48 @@ | 
				
			|||||||
 | 
					{ | 
				
			||||||
 | 
					    "version": "0.2.0", | 
				
			||||||
 | 
					    "configurations": [ | 
				
			||||||
 | 
					        { | 
				
			||||||
 | 
					            "type": "lldb", | 
				
			||||||
 | 
					            "request": "launch", | 
				
			||||||
 | 
					            "name": "Debug", | 
				
			||||||
 | 
					            "cargo": { | 
				
			||||||
 | 
					                "args": [ | 
				
			||||||
 | 
					                    "build", | 
				
			||||||
 | 
					                    "--bin=bevy-bloxel-test", | 
				
			||||||
 | 
					                    "--package=bevy-bloxel-test" | 
				
			||||||
 | 
					                ], | 
				
			||||||
 | 
					                "filter": { | 
				
			||||||
 | 
					                    "name": "bevy-bloxel-test", | 
				
			||||||
 | 
					                    "kind": "bin" | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            }, | 
				
			||||||
 | 
					            "args": [], | 
				
			||||||
 | 
					            "cwd": "${workspaceFolder}", | 
				
			||||||
 | 
					            "env": { | 
				
			||||||
 | 
					                // When Bevy looks for assets, it checks BEVY_ASSET_ROOT, CARGO_MANIFEST_DIR, and | 
				
			||||||
 | 
					                // then falls back to the executable directory. When debugging, the cargo manifest | 
				
			||||||
 | 
					                // directory is not set, so we need to specify this environment variable. | 
				
			||||||
 | 
					                "BEVY_ASSET_ROOT": "${workspaceFolder}", | 
				
			||||||
 | 
					            }, | 
				
			||||||
 | 
					        }, | 
				
			||||||
 | 
					        { | 
				
			||||||
 | 
					            "type": "lldb", | 
				
			||||||
 | 
					            "request": "launch", | 
				
			||||||
 | 
					            "name": "Debug unit tests", | 
				
			||||||
 | 
					            "cargo": { | 
				
			||||||
 | 
					                "args": [ | 
				
			||||||
 | 
					                    "test", | 
				
			||||||
 | 
					                    "--no-run", | 
				
			||||||
 | 
					                    "--bin=bevy-bloxel-test", | 
				
			||||||
 | 
					                    "--package=bevy-bloxel-test" | 
				
			||||||
 | 
					                ], | 
				
			||||||
 | 
					                "filter": { | 
				
			||||||
 | 
					                    "name": "bevy-bloxel-test", | 
				
			||||||
 | 
					                    "kind": "bin" | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            }, | 
				
			||||||
 | 
					            "args": [], | 
				
			||||||
 | 
					            "cwd": "${workspaceFolder}", | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    ] | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,19 @@ | 
				
			|||||||
 | 
					[package] | 
				
			||||||
 | 
					name = "bevy-bloxel-test" | 
				
			||||||
 | 
					version = "0.1.0" | 
				
			||||||
 | 
					edition = "2021" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Enable a small amount of optimization in the dev profile. | 
				
			||||||
 | 
					[profile.dev] | 
				
			||||||
 | 
					opt-level = 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Enable a large amount of optimization in the dev profile for dependencies. | 
				
			||||||
 | 
					[profile.dev.package."*"] | 
				
			||||||
 | 
					opt-level = 3 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies] | 
				
			||||||
 | 
					bevy = { version = "0.15.3", features = [ "file_watcher", "embedded_watcher" ] } | 
				
			||||||
 | 
					bitvec = "1.0.1" | 
				
			||||||
 | 
					overload = "0.1.1" | 
				
			||||||
 | 
					rand = "0.9.0" | 
				
			||||||
 | 
					zorder = "0.2.2" | 
				
			||||||
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.3 KiB  | 
| 
		 After Width: | Height: | Size: 3.0 KiB  | 
| 
		 After Width: | Height: | Size: 3.2 KiB  | 
| 
		 After Width: | Height: | Size: 3.5 KiB  | 
| 
		 After Width: | Height: | Size: 3.3 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
| 
		 After Width: | Height: | Size: 3.3 KiB  | 
@ -0,0 +1,7 @@ | 
				
			|||||||
 | 
					use bevy::prelude::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component)] | 
				
			||||||
 | 
					pub struct Block; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Deref)] | 
				
			||||||
 | 
					pub struct BlockTexture(pub Handle<Image>); | 
				
			||||||
@ -0,0 +1,26 @@ | 
				
			|||||||
 | 
					use bevy::{prelude::*, utils::HashMap}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{ | 
				
			||||||
 | 
					    math::{ChunkPos, CHUNK_LENGTH}, | 
				
			||||||
 | 
					    storage::PaletteBloxelStorage, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Default)] | 
				
			||||||
 | 
					pub struct Chunk; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Deref, DerefMut)] | 
				
			||||||
 | 
					#[require(Chunk)] | 
				
			||||||
 | 
					pub struct ChunkData(PaletteBloxelStorage<Entity>); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ChunkData { | 
				
			||||||
 | 
					    pub fn new(default: Entity) -> Self { | 
				
			||||||
 | 
					        Self(PaletteBloxelStorage::new( | 
				
			||||||
 | 
					            UVec3::splat(CHUNK_LENGTH as u32), | 
				
			||||||
 | 
					            default, | 
				
			||||||
 | 
					        )) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Default, Deref, DerefMut)] | 
				
			||||||
 | 
					#[require(Transform, Visibility)] | 
				
			||||||
 | 
					pub struct ChunkMap(HashMap<ChunkPos, Entity>); | 
				
			||||||
@ -0,0 +1,5 @@ | 
				
			|||||||
 | 
					mod pos; | 
				
			||||||
 | 
					mod region; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use pos::Pos; | 
				
			||||||
 | 
					pub use region::Region; | 
				
			||||||
@ -0,0 +1,212 @@ | 
				
			|||||||
 | 
					use std::{ | 
				
			||||||
 | 
					    marker::PhantomData, | 
				
			||||||
 | 
					    ops::{Add, AddAssign, Index, IndexMut, Sub, SubAssign}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    ecs::component::Component, | 
				
			||||||
 | 
					    math::{Dir3, IVec3, InvalidDirectionError}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::Region; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Pos<T> { | 
				
			||||||
 | 
					    pub x: i32, | 
				
			||||||
 | 
					    pub y: i32, | 
				
			||||||
 | 
					    pub z: i32, | 
				
			||||||
 | 
					    _marker: PhantomData<T>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Pos<T> { | 
				
			||||||
 | 
					    pub const ORIGIN: Self = Self::new(0, 0, 0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const fn new(x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            x, | 
				
			||||||
 | 
					            y, | 
				
			||||||
 | 
					            z, | 
				
			||||||
 | 
					            _marker: PhantomData, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn region(self) -> Region<T> { | 
				
			||||||
 | 
					        Region::new_unchecked(self.into(), self.into()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to(self, other: Self) -> f32 { | 
				
			||||||
 | 
					        (self.distance_to_squared(other) as f32).sqrt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to_squared(self, other: Self) -> i32 { | 
				
			||||||
 | 
					        IVec3::distance_squared(self.into(), other.into()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn direction_to(self, other: Self) -> Result<Dir3, InvalidDirectionError> { | 
				
			||||||
 | 
					        Dir3::new((other - self).as_vec3()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn min(self, rhs: Self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x.min(rhs.x), self.y.min(rhs.y), self.z.min(rhs.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn max(self, rhs: Self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x.max(rhs.x), self.y.max(rhs.y), self.z.max(rhs.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::fmt::Display for Pos<T> { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        write!(f, "[{}, {}, {}]", self.x, self.y, self.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::fmt::Debug for Pos<T> { | 
				
			||||||
 | 
					    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        fmt.debug_tuple(stringify!(Pos<T>)) | 
				
			||||||
 | 
					            .field(&self.x) | 
				
			||||||
 | 
					            .field(&self.y) | 
				
			||||||
 | 
					            .field(&self.z) | 
				
			||||||
 | 
					            .finish() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Trait implementations that SHOULD be handled by #[derive]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::ecs::component::StorageType; | 
				
			||||||
 | 
					impl<T: Send + Sync + 'static> Component for Pos<T> { | 
				
			||||||
 | 
					    const STORAGE_TYPE: StorageType = StorageType::Table; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Default for Pos<T> { | 
				
			||||||
 | 
					    fn default() -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            x: 0, | 
				
			||||||
 | 
					            y: 0, | 
				
			||||||
 | 
					            z: 0, | 
				
			||||||
 | 
					            _marker: PhantomData, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Clone for Pos<T> { | 
				
			||||||
 | 
					    fn clone(&self) -> Self { | 
				
			||||||
 | 
					        *self | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Copy for Pos<T> {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> PartialEq for Pos<T> { | 
				
			||||||
 | 
					    fn eq(&self, other: &Self) -> bool { | 
				
			||||||
 | 
					        (self.x, self.y, self.z).eq(&(other.x, other.y, other.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Eq for Pos<T> {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::hash::Hash for Pos<T> { | 
				
			||||||
 | 
					    fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | 
				
			||||||
 | 
					        (self.x, self.y, self.z).hash(state) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Conversion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> From<(i32, i32, i32)> for Pos<T> { | 
				
			||||||
 | 
					    fn from((x, y, z): (i32, i32, i32)) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl<T> From<Pos<T>> for (i32, i32, i32) { | 
				
			||||||
 | 
					    fn from(Pos { x, y, z, .. }: Pos<T>) -> Self { | 
				
			||||||
 | 
					        (x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> From<[i32; 3]> for Pos<T> { | 
				
			||||||
 | 
					    fn from([x, y, z]: [i32; 3]) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl<T> From<Pos<T>> for [i32; 3] { | 
				
			||||||
 | 
					    fn from(Pos { x, y, z, .. }: Pos<T>) -> Self { | 
				
			||||||
 | 
					        [x, y, z] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> From<IVec3> for Pos<T> { | 
				
			||||||
 | 
					    fn from(IVec3 { x, y, z }: IVec3) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl<T> From<Pos<T>> for IVec3 { | 
				
			||||||
 | 
					    fn from(Pos { x, y, z, .. }: Pos<T>) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Offsetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Add<IVec3> for Pos<T> { | 
				
			||||||
 | 
					    type Output = Pos<T>; | 
				
			||||||
 | 
					    fn add(self, rhs: IVec3) -> Self::Output { | 
				
			||||||
 | 
					        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Sub<IVec3> for Pos<T> { | 
				
			||||||
 | 
					    type Output = Pos<T>; | 
				
			||||||
 | 
					    fn sub(self, rhs: IVec3) -> Self::Output { | 
				
			||||||
 | 
					        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> AddAssign<IVec3> for Pos<T> { | 
				
			||||||
 | 
					    fn add_assign(&mut self, rhs: IVec3) { | 
				
			||||||
 | 
					        self.x += rhs.x; | 
				
			||||||
 | 
					        self.y += rhs.y; | 
				
			||||||
 | 
					        self.z += rhs.z; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> SubAssign<IVec3> for Pos<T> { | 
				
			||||||
 | 
					    fn sub_assign(&mut self, rhs: IVec3) { | 
				
			||||||
 | 
					        self.x -= rhs.x; | 
				
			||||||
 | 
					        self.y -= rhs.y; | 
				
			||||||
 | 
					        self.z -= rhs.z; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Difference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Sub<Pos<T>> for Pos<T> { | 
				
			||||||
 | 
					    type Output = IVec3; | 
				
			||||||
 | 
					    fn sub(self, rhs: Self) -> IVec3 { | 
				
			||||||
 | 
					        IVec3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Indexing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Index<usize> for Pos<T> { | 
				
			||||||
 | 
					    type Output = i32; | 
				
			||||||
 | 
					    fn index(&self, index: usize) -> &Self::Output { | 
				
			||||||
 | 
					        match index { | 
				
			||||||
 | 
					            0 => &self.x, | 
				
			||||||
 | 
					            1 => &self.y, | 
				
			||||||
 | 
					            2 => &self.z, | 
				
			||||||
 | 
					            _ => panic!("index out of bounds"), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> IndexMut<usize> for Pos<T> { | 
				
			||||||
 | 
					    fn index_mut(&mut self, index: usize) -> &mut Self::Output { | 
				
			||||||
 | 
					        match index { | 
				
			||||||
 | 
					            0 => &mut self.x, | 
				
			||||||
 | 
					            1 => &mut self.y, | 
				
			||||||
 | 
					            2 => &mut self.z, | 
				
			||||||
 | 
					            _ => panic!("index out of bounds"), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,175 @@ | 
				
			|||||||
 | 
					use std::{ | 
				
			||||||
 | 
					    marker::PhantomData, | 
				
			||||||
 | 
					    ops::{Add, AddAssign, Sub, SubAssign}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    ecs::component::Component, | 
				
			||||||
 | 
					    math::{IVec3, UVec3}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::Pos; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Region<T> { | 
				
			||||||
 | 
					    min: IVec3, | 
				
			||||||
 | 
					    max: IVec3, | 
				
			||||||
 | 
					    _marker: PhantomData<T>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Region<T> { | 
				
			||||||
 | 
					    pub fn new_unchecked(min: IVec3, max: IVec3) -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            min, | 
				
			||||||
 | 
					            max, | 
				
			||||||
 | 
					            _marker: PhantomData, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new_checked(min: Pos<T>, max: Pos<T>) -> Result<Self, ()> { | 
				
			||||||
 | 
					        let min: IVec3 = min.into(); | 
				
			||||||
 | 
					        let max: IVec3 = max.into(); | 
				
			||||||
 | 
					        if min.x <= max.x && min.y <= max.y && min.z <= max.z { | 
				
			||||||
 | 
					            Ok(Self::new_unchecked(min, max)) | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            Err(()) | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new(a: Pos<T>, b: Pos<T>) -> Self { | 
				
			||||||
 | 
					        let a: IVec3 = a.into(); | 
				
			||||||
 | 
					        let b: IVec3 = b.into(); | 
				
			||||||
 | 
					        let min = IVec3::new(a.x.min(b.x), a.y.min(b.y), a.z.min(b.z)); | 
				
			||||||
 | 
					        let max = IVec3::new(a.x.max(b.x), a.y.max(b.y), a.z.max(b.z)); | 
				
			||||||
 | 
					        Self::new_unchecked(min, max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn size(&self) -> UVec3 { | 
				
			||||||
 | 
					        (self.max - self.min + IVec3::ONE).as_uvec3() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn contains(&self, pos: Pos<T>) -> bool { | 
				
			||||||
 | 
					        let pos: IVec3 = pos.into(); | 
				
			||||||
 | 
					        (pos.x >= self.min.x && pos.x <= self.max.x) | 
				
			||||||
 | 
					            && (pos.y >= self.min.y && pos.y <= self.max.y) | 
				
			||||||
 | 
					            && (pos.z >= self.min.z && pos.z <= self.max.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn intersects(&self, other: Self) -> bool { | 
				
			||||||
 | 
					        (other.max.x > self.min.x && other.min.x < self.max.x) | 
				
			||||||
 | 
					            && (other.max.y > self.min.y && other.min.y < self.max.y) | 
				
			||||||
 | 
					            && (other.max.z > self.min.z && other.min.z < self.max.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to(&self, pos: Pos<T>) -> f32 { | 
				
			||||||
 | 
					        (self.distance_to_squared(pos) as f32).sqrt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to_squared(&self, pos: Pos<T>) -> i32 { | 
				
			||||||
 | 
					        let pos: IVec3 = pos.into(); | 
				
			||||||
 | 
					        let clamped = pos.max(self.min).min(self.max); | 
				
			||||||
 | 
					        pos.distance_squared(clamped) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn expand(&self, amount: i32) -> Result<Self, ()> { | 
				
			||||||
 | 
					        Self::new_checked( | 
				
			||||||
 | 
					            (self.min - IVec3::splat(amount)).into(), | 
				
			||||||
 | 
					            (self.max + IVec3::splat(amount)).into(), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::fmt::Display for Region<T> { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        write!(f, "{} to {}", self.min, self.max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::fmt::Debug for Region<T> { | 
				
			||||||
 | 
					    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        fmt.debug_struct(stringify!(Region<T>)) | 
				
			||||||
 | 
					            .field("min", &self.min) | 
				
			||||||
 | 
					            .field("max", &self.max) | 
				
			||||||
 | 
					            .finish() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Trait implementations that SHOULD be handled by #[derive]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::ecs::component::StorageType; | 
				
			||||||
 | 
					impl<T: Send + Sync + 'static> Component for Region<T> { | 
				
			||||||
 | 
					    const STORAGE_TYPE: StorageType = StorageType::Table; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Default for Region<T> { | 
				
			||||||
 | 
					    fn default() -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            min: IVec3::ZERO, | 
				
			||||||
 | 
					            max: IVec3::ZERO, | 
				
			||||||
 | 
					            _marker: PhantomData, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Clone for Region<T> { | 
				
			||||||
 | 
					    fn clone(&self) -> Self { | 
				
			||||||
 | 
					        *self | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Copy for Region<T> {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> PartialEq for Region<T> { | 
				
			||||||
 | 
					    fn eq(&self, other: &Self) -> bool { | 
				
			||||||
 | 
					        (self.min, self.max).eq(&(other.min, other.max)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Eq for Region<T> {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::hash::Hash for Region<T> { | 
				
			||||||
 | 
					    fn hash<H: std::hash::Hasher>(&self, state: &mut H) { | 
				
			||||||
 | 
					        (self.min, self.max).hash(state) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Offsetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Add<IVec3> for Region<T> { | 
				
			||||||
 | 
					    type Output = Region<T>; | 
				
			||||||
 | 
					    fn add(self, rhs: IVec3) -> Self::Output { | 
				
			||||||
 | 
					        Self::new_unchecked(self.min + rhs, self.max + rhs) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl<T> Add<IVec3> for &Region<T> { | 
				
			||||||
 | 
					    type Output = Region<T>; | 
				
			||||||
 | 
					    fn add(self, rhs: IVec3) -> Self::Output { | 
				
			||||||
 | 
					        Region::new_unchecked(self.min + rhs, self.max + rhs) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> Sub<IVec3> for Region<T> { | 
				
			||||||
 | 
					    type Output = Region<T>; | 
				
			||||||
 | 
					    fn sub(self, rhs: IVec3) -> Self::Output { | 
				
			||||||
 | 
					        Self::new_unchecked(self.min - rhs, self.max - rhs) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl<T> Sub<IVec3> for &Region<T> { | 
				
			||||||
 | 
					    type Output = Region<T>; | 
				
			||||||
 | 
					    fn sub(self, rhs: IVec3) -> Self::Output { | 
				
			||||||
 | 
					        Region::new_unchecked(self.min - rhs, self.max - rhs) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> AddAssign<IVec3> for Region<T> { | 
				
			||||||
 | 
					    fn add_assign(&mut self, rhs: IVec3) { | 
				
			||||||
 | 
					        self.min += rhs; | 
				
			||||||
 | 
					        self.max += rhs; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> SubAssign<IVec3> for Region<T> { | 
				
			||||||
 | 
					    fn sub_assign(&mut self, rhs: IVec3) { | 
				
			||||||
 | 
					        self.min -= rhs; | 
				
			||||||
 | 
					        self.max -= rhs; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,129 @@ | 
				
			|||||||
 | 
					use std::ops::{self, Index, IndexMut}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    ecs::component::Component, | 
				
			||||||
 | 
					    math::{Dir3, IVec3, InvalidDirectionError}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use overload::overload; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::BlockRegion; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] | 
				
			||||||
 | 
					pub struct BlockPos { | 
				
			||||||
 | 
					    pub x: i32, | 
				
			||||||
 | 
					    pub y: i32, | 
				
			||||||
 | 
					    pub z: i32, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl BlockPos { | 
				
			||||||
 | 
					    pub const ORIGIN: BlockPos = Self::new(0, 0, 0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const fn new(x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self { x, y, z } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn region(self) -> BlockRegion { | 
				
			||||||
 | 
					        BlockRegion::new_unchecked(self, self) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to(self, other: Self) -> f32 { | 
				
			||||||
 | 
					        (self.distance_to_squared(other) as f32).sqrt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to_squared(self, other: Self) -> i32 { | 
				
			||||||
 | 
					        IVec3::distance_squared(self.into(), other.into()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn direction_to(self, other: Self) -> Result<Dir3, InvalidDirectionError> { | 
				
			||||||
 | 
					        Dir3::new((other - self).as_vec3()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn min(self, rhs: Self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x.min(rhs.x), self.y.min(rhs.y), self.z.min(rhs.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn max(self, rhs: Self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x.max(rhs.x), self.y.max(rhs.y), self.z.max(rhs.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn offset(self, x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x + x, self.y + y, self.z + z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for BlockPos { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        write!(f, "[{}, {}, {}]", self.x, self.y, self.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Offsetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: BlockPos) + (r: IVec3) -> BlockPos { BlockPos::new(l.x + r.x, l.y + r.y, l.z + r.z) }); | 
				
			||||||
 | 
					overload!((l: BlockPos) - (r: IVec3) -> BlockPos { BlockPos::new(l.x - r.x, l.y - r.y, l.z - r.z) }); | 
				
			||||||
 | 
					overload!((s: &mut BlockPos) += (v: IVec3) { s.x += v.x; s.y += v.y; s.z += v.z; }); | 
				
			||||||
 | 
					overload!((s: &mut BlockPos) -= (v: IVec3) { s.x -= v.x; s.y -= v.y; s.z -= v.z; }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Difference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: BlockPos) - (r: BlockPos) -> IVec3 { IVec3::new(l.x - r.x, l.y - r.y, l.z - r.z) }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Indexing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Index<usize> for BlockPos { | 
				
			||||||
 | 
					    type Output = i32; | 
				
			||||||
 | 
					    fn index(&self, index: usize) -> &Self::Output { | 
				
			||||||
 | 
					        match index { | 
				
			||||||
 | 
					            0 => &self.x, | 
				
			||||||
 | 
					            1 => &self.y, | 
				
			||||||
 | 
					            2 => &self.z, | 
				
			||||||
 | 
					            _ => panic!("index out of bounds"), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl IndexMut<usize> for BlockPos { | 
				
			||||||
 | 
					    fn index_mut(&mut self, index: usize) -> &mut Self::Output { | 
				
			||||||
 | 
					        match index { | 
				
			||||||
 | 
					            0 => &mut self.x, | 
				
			||||||
 | 
					            1 => &mut self.y, | 
				
			||||||
 | 
					            2 => &mut self.z, | 
				
			||||||
 | 
					            _ => panic!("index out of bounds"), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Conversion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<(i32, i32, i32)> for BlockPos { | 
				
			||||||
 | 
					    fn from((x, y, z): (i32, i32, i32)) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<BlockPos> for (i32, i32, i32) { | 
				
			||||||
 | 
					    fn from(BlockPos { x, y, z }: BlockPos) -> Self { | 
				
			||||||
 | 
					        (x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<[i32; 3]> for BlockPos { | 
				
			||||||
 | 
					    fn from([x, y, z]: [i32; 3]) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<BlockPos> for [i32; 3] { | 
				
			||||||
 | 
					    fn from(BlockPos { x, y, z }: BlockPos) -> Self { | 
				
			||||||
 | 
					        [x, y, z] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<IVec3> for BlockPos { | 
				
			||||||
 | 
					    fn from(IVec3 { x, y, z }: IVec3) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<BlockPos> for IVec3 { | 
				
			||||||
 | 
					    fn from(BlockPos { x, y, z }: BlockPos) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,87 @@ | 
				
			|||||||
 | 
					use std::ops; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    ecs::component::Component, | 
				
			||||||
 | 
					    math::{IVec3, UVec3}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use overload::overload; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::BlockPos; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] | 
				
			||||||
 | 
					pub struct BlockRegion { | 
				
			||||||
 | 
					    min: BlockPos, | 
				
			||||||
 | 
					    max: BlockPos, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl BlockRegion { | 
				
			||||||
 | 
					    pub fn new_unchecked(min: BlockPos, max: BlockPos) -> Self { | 
				
			||||||
 | 
					        Self { min, max } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new_checked(min: BlockPos, max: BlockPos) -> Result<Self, ()> { | 
				
			||||||
 | 
					        if min.x <= max.x && min.y <= max.y && min.z <= max.z { | 
				
			||||||
 | 
					            Ok(Self::new_unchecked(min, max)) | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            Err(()) | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new(a: BlockPos, b: BlockPos) -> Self { | 
				
			||||||
 | 
					        let min = BlockPos::new(a.x.min(b.x), a.y.min(b.y), a.z.min(b.z)); | 
				
			||||||
 | 
					        let max = BlockPos::new(a.x.max(b.x), a.y.max(b.y), a.z.max(b.z)); | 
				
			||||||
 | 
					        Self::new_unchecked(min, max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn size(&self) -> UVec3 { | 
				
			||||||
 | 
					        (self.max - self.min + IVec3::ONE).as_uvec3() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn contains(&self, pos: BlockPos) -> bool { | 
				
			||||||
 | 
					        (pos.x >= self.min.x && pos.x <= self.max.x) | 
				
			||||||
 | 
					            && (pos.y >= self.min.y && pos.y <= self.max.y) | 
				
			||||||
 | 
					            && (pos.z >= self.min.z && pos.z <= self.max.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn intersects(&self, other: Self) -> bool { | 
				
			||||||
 | 
					        (other.max.x > self.min.x && other.min.x < self.max.x) | 
				
			||||||
 | 
					            && (other.max.y > self.min.y && other.min.y < self.max.y) | 
				
			||||||
 | 
					            && (other.max.z > self.min.z && other.min.z < self.max.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to(&self, pos: BlockPos) -> f32 { | 
				
			||||||
 | 
					        (self.distance_to_squared(pos) as f32).sqrt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to_squared(&self, pos: BlockPos) -> i32 { | 
				
			||||||
 | 
					        let clamped = pos.max(self.min).min(self.max); | 
				
			||||||
 | 
					        pos.distance_to_squared(clamped) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn offset(&self, x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            min: self.min.offset(x, y, z), | 
				
			||||||
 | 
					            max: self.max.offset(x, y, z), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn expand(&self, amount: i32) -> Result<Self, ()> { | 
				
			||||||
 | 
					        Self::new_checked( | 
				
			||||||
 | 
					            self.min - IVec3::splat(amount), | 
				
			||||||
 | 
					            self.max + IVec3::splat(amount), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for BlockRegion { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        write!(f, "{} to {}", self.min, self.max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Offsetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ?BlockRegion) + (r: IVec3) -> BlockRegion { BlockRegion::new_unchecked(l.min + r, l.max + r) }); | 
				
			||||||
 | 
					overload!((l: ?BlockRegion) - (r: IVec3) -> BlockRegion { BlockRegion::new_unchecked(l.min - r, l.max - r) }); | 
				
			||||||
 | 
					overload!((s: &mut BlockRegion) += (v: IVec3) { s.min += v; s.max += v; }); | 
				
			||||||
 | 
					overload!((s: &mut BlockRegion) -= (v: IVec3) { s.min -= v; s.max -= v; }); | 
				
			||||||
@ -0,0 +1,158 @@ | 
				
			|||||||
 | 
					use std::ops::{self, Index, IndexMut}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    ecs::component::Component, | 
				
			||||||
 | 
					    math::{Dir3, IVec3, InvalidDirectionError}, | 
				
			||||||
 | 
					    transform::components::Transform, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use overload::overload; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{BlockPos, BlockRegion, ChunkRegion}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const CHUNK_SHIFT: i32 = 4; | 
				
			||||||
 | 
					pub const CHUNK_MASK: i32 = !(!0 << CHUNK_SHIFT); // = 0b1111
 | 
				
			||||||
 | 
					pub const CHUNK_LENGTH: usize = 1 << CHUNK_SHIFT; // = 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const CHUNK_MAX: IVec3 = IVec3::splat((CHUNK_LENGTH - 1) as i32); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] | 
				
			||||||
 | 
					pub struct ChunkPos { | 
				
			||||||
 | 
					    pub x: i32, | 
				
			||||||
 | 
					    pub y: i32, | 
				
			||||||
 | 
					    pub z: i32, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ChunkPos { | 
				
			||||||
 | 
					    pub const ORIGIN: ChunkPos = Self::new(0, 0, 0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const fn new(x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self { x, y, z } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn region(self) -> ChunkRegion { | 
				
			||||||
 | 
					        ChunkRegion::new_unchecked(self, self) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to(self, other: Self) -> f32 { | 
				
			||||||
 | 
					        (self.distance_to_squared(other) as f32).sqrt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to_squared(self, other: Self) -> i32 { | 
				
			||||||
 | 
					        IVec3::distance_squared(self.into(), other.into()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn direction_to(self, other: Self) -> Result<Dir3, InvalidDirectionError> { | 
				
			||||||
 | 
					        Dir3::new((other - self).as_vec3()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn transform(self) -> Transform { | 
				
			||||||
 | 
					        let pos: IVec3 = self.into(); | 
				
			||||||
 | 
					        Transform::from_translation((pos << CHUNK_SHIFT).as_vec3()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn min(self, rhs: Self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x.min(rhs.x), self.y.min(rhs.y), self.z.min(rhs.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn max(self, rhs: Self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x.max(rhs.x), self.y.max(rhs.y), self.z.max(rhs.z)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn offset(self, x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self::new(self.x + x, self.y + y, self.z + z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn from_block_pos(pos: BlockPos) -> (Self, IVec3) { | 
				
			||||||
 | 
					        let pos: IVec3 = pos.into(); | 
				
			||||||
 | 
					        ((pos >> CHUNK_SHIFT).into(), pos & CHUNK_MASK) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn to_block_pos(self, relative: IVec3) -> BlockPos { | 
				
			||||||
 | 
					        let pos: IVec3 = self.into(); | 
				
			||||||
 | 
					        ((pos << CHUNK_SHIFT) + relative).into() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn to_block_region(self) -> BlockRegion { | 
				
			||||||
 | 
					        BlockRegion::new_unchecked( | 
				
			||||||
 | 
					            self.to_block_pos(IVec3::ZERO), | 
				
			||||||
 | 
					            self.to_block_pos(IVec3::splat(CHUNK_LENGTH as i32 - 1)), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for ChunkPos { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        write!(f, "[{}, {}, {}]", self.x, self.y, self.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Offsetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ChunkPos) + (r: IVec3) -> ChunkPos { ChunkPos::new(l.x + r.x, l.y + r.y, l.z + r.z) }); | 
				
			||||||
 | 
					overload!((l: ChunkPos) - (r: IVec3) -> ChunkPos { ChunkPos::new(l.x - r.x, l.y - r.y, l.z - r.z) }); | 
				
			||||||
 | 
					overload!((s: &mut ChunkPos) += (v: IVec3) { s.x += v.x; s.y += v.y; s.z += v.z; }); | 
				
			||||||
 | 
					overload!((s: &mut ChunkPos) -= (v: IVec3) { s.x -= v.x; s.y -= v.y; s.z -= v.z; }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Difference
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ChunkPos) - (r: ChunkPos) -> IVec3 { IVec3::new(l.x - r.x, l.y - r.y, l.z - r.z) }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Indexing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Index<usize> for ChunkPos { | 
				
			||||||
 | 
					    type Output = i32; | 
				
			||||||
 | 
					    fn index(&self, index: usize) -> &Self::Output { | 
				
			||||||
 | 
					        match index { | 
				
			||||||
 | 
					            0 => &self.x, | 
				
			||||||
 | 
					            1 => &self.y, | 
				
			||||||
 | 
					            2 => &self.z, | 
				
			||||||
 | 
					            _ => panic!("index out of bounds"), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl IndexMut<usize> for ChunkPos { | 
				
			||||||
 | 
					    fn index_mut(&mut self, index: usize) -> &mut Self::Output { | 
				
			||||||
 | 
					        match index { | 
				
			||||||
 | 
					            0 => &mut self.x, | 
				
			||||||
 | 
					            1 => &mut self.y, | 
				
			||||||
 | 
					            2 => &mut self.z, | 
				
			||||||
 | 
					            _ => panic!("index out of bounds"), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Conversion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<(i32, i32, i32)> for ChunkPos { | 
				
			||||||
 | 
					    fn from((x, y, z): (i32, i32, i32)) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ChunkPos> for (i32, i32, i32) { | 
				
			||||||
 | 
					    fn from(ChunkPos { x, y, z }: ChunkPos) -> Self { | 
				
			||||||
 | 
					        (x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<[i32; 3]> for ChunkPos { | 
				
			||||||
 | 
					    fn from([x, y, z]: [i32; 3]) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ChunkPos> for [i32; 3] { | 
				
			||||||
 | 
					    fn from(ChunkPos { x, y, z }: ChunkPos) -> Self { | 
				
			||||||
 | 
					        [x, y, z] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<IVec3> for ChunkPos { | 
				
			||||||
 | 
					    fn from(IVec3 { x, y, z }: IVec3) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ChunkPos> for IVec3 { | 
				
			||||||
 | 
					    fn from(ChunkPos { x, y, z }: ChunkPos) -> Self { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,94 @@ | 
				
			|||||||
 | 
					use std::ops; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    ecs::component::Component, | 
				
			||||||
 | 
					    math::{IVec3, UVec3}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					use overload::overload; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{BlockRegion, ChunkPos, CHUNK_MAX}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] | 
				
			||||||
 | 
					pub struct ChunkRegion { | 
				
			||||||
 | 
					    min: ChunkPos, | 
				
			||||||
 | 
					    max: ChunkPos, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ChunkRegion { | 
				
			||||||
 | 
					    pub fn new_unchecked(min: ChunkPos, max: ChunkPos) -> Self { | 
				
			||||||
 | 
					        Self { min, max } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new_checked(min: ChunkPos, max: ChunkPos) -> Result<Self, ()> { | 
				
			||||||
 | 
					        if min.x <= max.x && min.y <= max.y && min.z <= max.z { | 
				
			||||||
 | 
					            Ok(Self::new_unchecked(min, max)) | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            Err(()) | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new(a: ChunkPos, b: ChunkPos) -> Self { | 
				
			||||||
 | 
					        let min = ChunkPos::new(a.x.min(b.x), a.y.min(b.y), a.z.min(b.z)); | 
				
			||||||
 | 
					        let max = ChunkPos::new(a.x.max(b.x), a.y.max(b.y), a.z.max(b.z)); | 
				
			||||||
 | 
					        Self::new_unchecked(min, max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn size(&self) -> UVec3 { | 
				
			||||||
 | 
					        (self.max - self.min + IVec3::ONE).as_uvec3() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn contains(&self, pos: ChunkPos) -> bool { | 
				
			||||||
 | 
					        (pos.x >= self.min.x && pos.x <= self.max.x) | 
				
			||||||
 | 
					            && (pos.y >= self.min.y && pos.y <= self.max.y) | 
				
			||||||
 | 
					            && (pos.z >= self.min.z && pos.z <= self.max.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn intersects(&self, other: Self) -> bool { | 
				
			||||||
 | 
					        (other.max.x > self.min.x && other.min.x < self.max.x) | 
				
			||||||
 | 
					            && (other.max.y > self.min.y && other.min.y < self.max.y) | 
				
			||||||
 | 
					            && (other.max.z > self.min.z && other.min.z < self.max.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to(&self, pos: ChunkPos) -> f32 { | 
				
			||||||
 | 
					        (self.distance_to_squared(pos) as f32).sqrt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn distance_to_squared(&self, pos: ChunkPos) -> i32 { | 
				
			||||||
 | 
					        let clamped = pos.max(self.min).min(self.max); | 
				
			||||||
 | 
					        pos.distance_to_squared(clamped) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn offset(&self, x: i32, y: i32, z: i32) -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            min: self.min.offset(x, y, z), | 
				
			||||||
 | 
					            max: self.max.offset(x, y, z), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn expand(&self, amount: i32) -> Result<Self, ()> { | 
				
			||||||
 | 
					        Self::new_checked( | 
				
			||||||
 | 
					            self.min - IVec3::splat(amount), | 
				
			||||||
 | 
					            self.max + IVec3::splat(amount), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn to_block_region(&self) -> BlockRegion { | 
				
			||||||
 | 
					        BlockRegion::new_unchecked( | 
				
			||||||
 | 
					            self.min.to_block_pos(IVec3::ZERO), | 
				
			||||||
 | 
					            self.min.to_block_pos(CHUNK_MAX), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for ChunkRegion { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        write!(f, "{} to {}", self.min, self.max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Offsetting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ?ChunkRegion) + (r: IVec3) -> ChunkRegion { ChunkRegion::new_unchecked(l.min + r, l.max + r) }); | 
				
			||||||
 | 
					overload!((l: ?ChunkRegion) - (r: IVec3) -> ChunkRegion { ChunkRegion::new_unchecked(l.min - r, l.max - r) }); | 
				
			||||||
 | 
					overload!((s: &mut ChunkRegion) += (v: IVec3) { s.min += v; s.max += v; }); | 
				
			||||||
 | 
					overload!((s: &mut ChunkRegion) -= (v: IVec3) { s.min -= v; s.max -= v; }); | 
				
			||||||
@ -0,0 +1,11 @@ | 
				
			|||||||
 | 
					mod block_pos; | 
				
			||||||
 | 
					mod block_region; | 
				
			||||||
 | 
					mod chunk_pos; | 
				
			||||||
 | 
					mod chunk_region; | 
				
			||||||
 | 
					mod z_order_index; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use block_pos::*; | 
				
			||||||
 | 
					pub use block_region::*; | 
				
			||||||
 | 
					pub use chunk_pos::*; | 
				
			||||||
 | 
					pub use chunk_region::*; | 
				
			||||||
 | 
					pub use z_order_index::*; | 
				
			||||||
@ -0,0 +1,211 @@ | 
				
			|||||||
 | 
					use std::ops; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::math::IVec3; | 
				
			||||||
 | 
					use overload::overload; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::ChunkPos; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Helper function that creates a [`ZOrderIndex`], panicking if the supplied values
 | 
				
			||||||
 | 
					/// are not between [`ZOrderIndex::ELEMENT_MIN`] and [`ZOrderIndex::ELEMENT_MAX`].
 | 
				
			||||||
 | 
					pub fn zorder(x: i32, y: i32, z: i32) -> ZOrderIndex { | 
				
			||||||
 | 
					    ZOrderIndex::new(x, y, z).unwrap() | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Encodes 3 signed 21-bit integers into a 64-bit integer, their bits interleaved.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This struct wraps an integer which represents an index into a space-filling curve called
 | 
				
			||||||
 | 
					/// [Z-Order Curve](https://en.wikipedia.org/wiki/Z-order_curve). This is also referred to as
 | 
				
			||||||
 | 
					/// Morton order, code, or encoding.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// By interleaving the 3 sub-elements into a single integer, some amount of packing can be
 | 
				
			||||||
 | 
					/// achieved, at the loss of some bits per elements. For example, with this 64-bit integer,
 | 
				
			||||||
 | 
					/// 21 bits per elements are available (`2_097_152` distinct values), which may be enough to
 | 
				
			||||||
 | 
					/// represent block coordinates in a bloxel game world.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// One upside of encoding separate coordinates into a single Z-Order index is that it can then
 | 
				
			||||||
 | 
					/// be effectively used to index into octrees, and certain operations such as bitwise shifting
 | 
				
			||||||
 | 
					/// are quite useful.
 | 
				
			||||||
 | 
					#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | 
				
			||||||
 | 
					pub struct ZOrderIndex(u64); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ZOrderIndex { | 
				
			||||||
 | 
					    pub const ZERO: Self = Self::from_raw(0); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const BITS_PER_ELEMENT: usize = (size_of::<u64>() * 8) / 3; | 
				
			||||||
 | 
					    pub const ELEMENT_MIN: i32 = !0 << (Self::BITS_PER_ELEMENT - 1); | 
				
			||||||
 | 
					    pub const ELEMENT_MAX: i32 = !Self::ELEMENT_MIN; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const TOTAL_USABLE_BITS: usize = Self::BITS_PER_ELEMENT * 3; | 
				
			||||||
 | 
					    const USABLE_BITS_MASK: u64 = !(!0 << Self::TOTAL_USABLE_BITS); | 
				
			||||||
 | 
					    const SIGN_BITS_MASK: u64 = 0b111 << (Self::TOTAL_USABLE_BITS - 3); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub const fn from_raw(value: u64) -> Self { | 
				
			||||||
 | 
					        Self(value & Self::USABLE_BITS_MASK) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new(x: i32, y: i32, z: i32) -> Result<Self, ()> { | 
				
			||||||
 | 
					        if (x >= Self::ELEMENT_MIN && x <= Self::ELEMENT_MAX) | 
				
			||||||
 | 
					            && (y >= Self::ELEMENT_MIN && y <= Self::ELEMENT_MAX) | 
				
			||||||
 | 
					            && (z >= Self::ELEMENT_MIN && z <= Self::ELEMENT_MAX) | 
				
			||||||
 | 
					        { | 
				
			||||||
 | 
					            let raw = zorder::index_of([x as u32, y as u32, z as u32]); | 
				
			||||||
 | 
					            Ok(Self::from_raw(raw as u64)) | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            Err(()) | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn raw(self) -> u64 { | 
				
			||||||
 | 
					        self.0 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn offset(self, x: i32, y: i32, z: i32) -> Result<Self, ()> { | 
				
			||||||
 | 
					        let (self_x, self_y, self_z) = self.into(); | 
				
			||||||
 | 
					        Self::new(self_x + x, self_y + y, self_z + z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Display for ZOrderIndex { | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        let (x, y, z) = (*self).into(); | 
				
			||||||
 | 
					        write!(f, "[{}, {}, {}]", x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl std::fmt::Debug for ZOrderIndex { | 
				
			||||||
 | 
					    fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 
				
			||||||
 | 
					        let (x, y, z) = (*self).into(); | 
				
			||||||
 | 
					        fmt.debug_tuple(stringify!(ZOrderIndex)) | 
				
			||||||
 | 
					            .field(&x) | 
				
			||||||
 | 
					            .field(&y) | 
				
			||||||
 | 
					            .field(&z) | 
				
			||||||
 | 
					            .finish() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bitwise AND, OR, XOR
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) & (r: u64) -> ZOrderIndex { ZOrderIndex::from_raw(l.0 & r) }); | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) | (r: u64) -> ZOrderIndex { ZOrderIndex::from_raw(l.0 | r) }); | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) ^ (r: u64) -> ZOrderIndex { ZOrderIndex::from_raw(l.0 ^ r) }); | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) & (r: ZOrderIndex) -> ZOrderIndex { l & r.0 }); | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) | (r: ZOrderIndex) -> ZOrderIndex { l | r.0 }); | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) ^ (r: ZOrderIndex) -> ZOrderIndex { l ^ r.0 }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Bitshifting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) << (r: usize) -> ZOrderIndex { | 
				
			||||||
 | 
					    ZOrderIndex::from_raw(l.0 << (r * 3)) | 
				
			||||||
 | 
					}); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					overload!((l: ZOrderIndex) >> (r: usize) -> ZOrderIndex { | 
				
			||||||
 | 
					    let mut result = l.0; | 
				
			||||||
 | 
					    let sign_bits = result & ZOrderIndex::SIGN_BITS_MASK; | 
				
			||||||
 | 
					    for _ in 0..r { result = (result >> 3) | sign_bits } | 
				
			||||||
 | 
					    ZOrderIndex::from_raw(result) | 
				
			||||||
 | 
					}); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Conversion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryFrom<(i32, i32, i32)> for ZOrderIndex { | 
				
			||||||
 | 
					    type Error = (); | 
				
			||||||
 | 
					    fn try_from((x, y, z): (i32, i32, i32)) -> Result<Self, Self::Error> { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ZOrderIndex> for (i32, i32, i32) { | 
				
			||||||
 | 
					    fn from(value: ZOrderIndex) -> Self { | 
				
			||||||
 | 
					        const SHIFT: usize = (size_of::<i32>() * 8) - ZOrderIndex::BITS_PER_ELEMENT; | 
				
			||||||
 | 
					        zorder::coord_of(value.0 as u128) | 
				
			||||||
 | 
					            .map(|i| ((i as i32) << SHIFT) >> SHIFT) | 
				
			||||||
 | 
					            .into() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryFrom<[i32; 3]> for ZOrderIndex { | 
				
			||||||
 | 
					    type Error = (); | 
				
			||||||
 | 
					    fn try_from([x, y, z]: [i32; 3]) -> Result<Self, Self::Error> { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ZOrderIndex> for [i32; 3] { | 
				
			||||||
 | 
					    fn from(value: ZOrderIndex) -> Self { | 
				
			||||||
 | 
					        Into::<(i32, i32, i32)>::into(value).into() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryFrom<IVec3> for ZOrderIndex { | 
				
			||||||
 | 
					    type Error = (); | 
				
			||||||
 | 
					    fn try_from(IVec3 { x, y, z }: IVec3) -> Result<Self, Self::Error> { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ZOrderIndex> for IVec3 { | 
				
			||||||
 | 
					    fn from(value: ZOrderIndex) -> Self { | 
				
			||||||
 | 
					        Into::<(i32, i32, i32)>::into(value).into() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryFrom<ChunkPos> for ZOrderIndex { | 
				
			||||||
 | 
					    type Error = (); | 
				
			||||||
 | 
					    fn try_from(ChunkPos { x, y, z }: ChunkPos) -> Result<Self, Self::Error> { | 
				
			||||||
 | 
					        Self::new(x, y, z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					impl From<ZOrderIndex> for ChunkPos { | 
				
			||||||
 | 
					    fn from(value: ZOrderIndex) -> Self { | 
				
			||||||
 | 
					        Into::<(i32, i32, i32)>::into(value).into() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)] | 
				
			||||||
 | 
					mod tests { | 
				
			||||||
 | 
					    use super::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    fn encode_decode_into() { | 
				
			||||||
 | 
					        assert_eq!((6, -16, 15), zorder(6, -16, 15).into()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let min = ZOrderIndex::ELEMENT_MIN; | 
				
			||||||
 | 
					        let max = ZOrderIndex::ELEMENT_MAX; | 
				
			||||||
 | 
					        assert_eq!((min, 0, max), zorder(min, 0, max).into()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    fn bounds_checking() { | 
				
			||||||
 | 
					        let min = 0b11111111111_1_00000000000000000000u32 as i32; | 
				
			||||||
 | 
					        let max = 0b00000000000_0_11111111111111111111u32 as i32; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!( | 
				
			||||||
 | 
					            Ok((ZOrderIndex::ELEMENT_MIN, ZOrderIndex::ELEMENT_MAX, 0)), | 
				
			||||||
 | 
					            ZOrderIndex::new(min, max, 0).map(Into::into) | 
				
			||||||
 | 
					        ); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert!(ZOrderIndex::new(min - 1, max, 0).is_err()); | 
				
			||||||
 | 
					        assert!(ZOrderIndex::new(min, max + 1, 0).is_err()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    #[rustfmt::skip] | 
				
			||||||
 | 
					    fn raw() { | 
				
			||||||
 | 
					        let x_mask: u64 = 0b0_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_001_001_000; | 
				
			||||||
 | 
					        let y_mask: u64 = 0b0_010_010_010_010_010_010_010_010_010_010_010_010_010_010_010_010_010_000_000_000_000; | 
				
			||||||
 | 
					        let z_mask: u64 = 0b0_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_000_100_100_100_100; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let raw = ZOrderIndex::from_raw(x_mask | y_mask | z_mask); | 
				
			||||||
 | 
					        assert_eq!(raw, zorder(6, -16, 15)); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    fn bitshift() { | 
				
			||||||
 | 
					        let zero = ZOrderIndex::ZERO; | 
				
			||||||
 | 
					        assert_eq!(zero, zero >> 2); | 
				
			||||||
 | 
					        assert_eq!(zero, zero << 2); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(zorder(4, 8, 12), zorder(1, 2, 3) << 2); | 
				
			||||||
 | 
					        assert_eq!(zorder(1, 2, 3), zorder(4, 8, 12) >> 2); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(zorder(-4, -8, -12), zorder(-1, -2, -3) << 2); | 
				
			||||||
 | 
					        assert_eq!(zorder(-1, -2, -3), zorder(-4, -8, -12) >> 2); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,127 @@ | 
				
			|||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    asset::RenderAssetUsages, prelude::*, render::mesh::Indices, render::mesh::PrimitiveTopology, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::bloxel::{block::BlockTexture, prelude::*, storage::BloxelArray}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create_bloxel_mesh( | 
				
			||||||
 | 
					    bloxels: &impl BloxelView<Entity>, | 
				
			||||||
 | 
					    layout: &TextureAtlasLayout, | 
				
			||||||
 | 
					    sources: &TextureAtlasSources, | 
				
			||||||
 | 
					    block_lookup: &Query<&BlockTexture, With<Block>>, | 
				
			||||||
 | 
					) -> Mesh { | 
				
			||||||
 | 
					    let offset = -(bloxels.size().as_vec3() / 2.); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut positions: Vec<Vec3> = vec![]; | 
				
			||||||
 | 
					    let mut normals: Vec<Vec3> = vec![]; | 
				
			||||||
 | 
					    let mut uvs: Vec<Vec2> = vec![]; | 
				
			||||||
 | 
					    let mut indices = vec![]; | 
				
			||||||
 | 
					    let mut num_vertices = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut append_vertex = |pos: Vec3, normal: Dir3, uv: Vec2| { | 
				
			||||||
 | 
					        positions.push(pos); | 
				
			||||||
 | 
					        normals.push(*normal); | 
				
			||||||
 | 
					        uvs.push(uv); | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut append_quad = |show: bool, points: [Vec3; 4], normal: Dir3, uvs: Rect| { | 
				
			||||||
 | 
					        if show { | 
				
			||||||
 | 
					            append_vertex(points[0], normal, (uvs.min.x, uvs.min.y).into()); | 
				
			||||||
 | 
					            append_vertex(points[1], normal, (uvs.max.x, uvs.min.y).into()); | 
				
			||||||
 | 
					            append_vertex(points[2], normal, (uvs.max.x, uvs.max.y).into()); | 
				
			||||||
 | 
					            append_vertex(points[3], normal, (uvs.min.x, uvs.max.y).into()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for i in [0, 1, 2, 2, 3, 0] { | 
				
			||||||
 | 
					                indices.push(num_vertices + i); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            num_vertices += 4; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut append_cube = |pos: IVec3, uvs: Rect, sides: [bool; 6]| { | 
				
			||||||
 | 
					        let min = offset + pos.as_vec3(); | 
				
			||||||
 | 
					        let max = offset + (pos + IVec3::ONE).as_vec3(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // left/right, bottom/top, back/front
 | 
				
			||||||
 | 
					        let lbb = Vec3::new(min.x, min.y, min.z); | 
				
			||||||
 | 
					        let lbf = Vec3::new(min.x, max.y, max.z); | 
				
			||||||
 | 
					        let ltb = Vec3::new(min.x, max.y, min.z); | 
				
			||||||
 | 
					        let ltf = Vec3::new(min.x, min.y, max.z); | 
				
			||||||
 | 
					        let rbb = Vec3::new(max.x, min.y, min.z); | 
				
			||||||
 | 
					        let rbf = Vec3::new(max.x, max.y, max.z); | 
				
			||||||
 | 
					        let rtb = Vec3::new(max.x, max.y, min.z); | 
				
			||||||
 | 
					        let rtf = Vec3::new(max.x, min.y, max.z); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        append_quad(sides[0], [rbb, rtb, rbf, rtf], Dir3::X, uvs); // Right
 | 
				
			||||||
 | 
					        append_quad(sides[1], [ltf, lbf, ltb, lbb], Dir3::NEG_X, uvs); // Left
 | 
				
			||||||
 | 
					        append_quad(sides[2], [rtb, ltb, lbf, rbf], Dir3::Y, uvs); // Top
 | 
				
			||||||
 | 
					        append_quad(sides[3], [rtf, ltf, lbb, rbb], Dir3::NEG_Y, uvs); // Bottom
 | 
				
			||||||
 | 
					        append_quad(sides[4], [ltf, rtf, rbf, lbf], Dir3::Z, uvs); // Front
 | 
				
			||||||
 | 
					        append_quad(sides[5], [ltb, rtb, rbb, lbb], Dir3::NEG_Z, uvs); // Back
 | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Shrinks the UV rectangle by a tiny amount to get rid of ugly seams at the edges of blocks,
 | 
				
			||||||
 | 
					    // due to floating point inaccuracy when the color is looked up in the shader or something.
 | 
				
			||||||
 | 
					    // Temporary fix until a more sophisticated graphics overhaul using texture arrays or so.
 | 
				
			||||||
 | 
					    let shrink_amount = -0.1 / layout.size.x as f32; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let size = bloxels.size().as_ivec3(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Capture all textures, which is currently all we need to know about a block to render it,
 | 
				
			||||||
 | 
					    // into a 3D array for us to look up, with a 1 block buffer so we can safely look up neighbors.
 | 
				
			||||||
 | 
					    let mut textures = BloxelArray::new_with_default(bloxels.size() + UVec3::new(2, 2, 2)); | 
				
			||||||
 | 
					    for z in 0..size.z { | 
				
			||||||
 | 
					        for y in 0..size.y { | 
				
			||||||
 | 
					            for x in 0..size.x { | 
				
			||||||
 | 
					                let pos = IVec3::new(x, y, z); | 
				
			||||||
 | 
					                let block = bloxels.get(pos); | 
				
			||||||
 | 
					                textures[pos + IVec3::ONE] = block_lookup.get(block).ok().map(|t| &t.0); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for z in 0..size.z { | 
				
			||||||
 | 
					        for y in 0..size.y { | 
				
			||||||
 | 
					            for x in 0..size.x { | 
				
			||||||
 | 
					                let pos = IVec3::new(x, y, z); | 
				
			||||||
 | 
					                if let Some(texture) = textures[pos + IVec3::ONE] { | 
				
			||||||
 | 
					                    if let Some(uvs) = uv_rect(sources, layout, texture) { | 
				
			||||||
 | 
					                        append_cube( | 
				
			||||||
 | 
					                            pos, | 
				
			||||||
 | 
					                            uvs.inflate(shrink_amount), | 
				
			||||||
 | 
					                            [ | 
				
			||||||
 | 
					                                textures[pos + IVec3::new(2, 1, 1)].is_none(), | 
				
			||||||
 | 
					                                textures[pos + IVec3::new(0, 1, 1)].is_none(), | 
				
			||||||
 | 
					                                textures[pos + IVec3::new(1, 2, 1)].is_none(), | 
				
			||||||
 | 
					                                textures[pos + IVec3::new(1, 0, 1)].is_none(), | 
				
			||||||
 | 
					                                textures[pos + IVec3::new(1, 1, 2)].is_none(), | 
				
			||||||
 | 
					                                textures[pos + IVec3::new(1, 1, 0)].is_none(), | 
				
			||||||
 | 
					                            ], | 
				
			||||||
 | 
					                        ); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Mesh::new( | 
				
			||||||
 | 
					        PrimitiveTopology::TriangleList, | 
				
			||||||
 | 
					        RenderAssetUsages::default(), | 
				
			||||||
 | 
					    ) | 
				
			||||||
 | 
					    .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions) | 
				
			||||||
 | 
					    .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) | 
				
			||||||
 | 
					    .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) | 
				
			||||||
 | 
					    .with_inserted_indices(Indices::U32(indices)) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn uv_rect( | 
				
			||||||
 | 
					    sources: &TextureAtlasSources, | 
				
			||||||
 | 
					    layout: &TextureAtlasLayout, | 
				
			||||||
 | 
					    texture: impl Into<AssetId<Image>>, | 
				
			||||||
 | 
					) -> Option<Rect> { | 
				
			||||||
 | 
					    sources.texture_rect(layout, texture).map(|rect| { | 
				
			||||||
 | 
					        let rect = rect.as_rect(); | 
				
			||||||
 | 
					        let size = layout.size.as_vec2(); | 
				
			||||||
 | 
					        Rect::from_corners(rect.min / size, rect.max / size) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,46 @@ | 
				
			|||||||
 | 
					use std::ops::Deref; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::prelude::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{ | 
				
			||||||
 | 
					    bloxel::{block::BlockTexture, prelude::*}, | 
				
			||||||
 | 
					    TerrainAtlas, TerrainMaterial, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod mesh_generator; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use mesh_generator::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Generates meshes for chunks that have `ChunkStorage` but no `Mesh3d` yet.
 | 
				
			||||||
 | 
					pub fn generate_chunk_mesh( | 
				
			||||||
 | 
					    mut commands: Commands, | 
				
			||||||
 | 
					    mut meshes: ResMut<Assets<Mesh>>, | 
				
			||||||
 | 
					    terrain_material: Option<Res<TerrainMaterial>>, | 
				
			||||||
 | 
					    terrain_atlas: Option<Res<TerrainAtlas>>, | 
				
			||||||
 | 
					    atlas_layouts: Res<Assets<TextureAtlasLayout>>, | 
				
			||||||
 | 
					    chunks_without_meshes: Query<(Entity, &ChunkData), (With<Chunk>, Without<Mesh3d>)>, | 
				
			||||||
 | 
					    block_lookup: Query<&BlockTexture, With<Block>>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    // This system will run before `TerrainMaterial` is available, so to avoid
 | 
				
			||||||
 | 
					    // the system failing, we'll make the material and atlas an `Option`.
 | 
				
			||||||
 | 
					    let Some(terrain_material) = terrain_material else { | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					    // Atlas and layout should exist at this point, if material does.
 | 
				
			||||||
 | 
					    let terrain_atlas = terrain_atlas.unwrap(); | 
				
			||||||
 | 
					    let terrain_atlas_layout = atlas_layouts.get(&terrain_atlas.layout).unwrap(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (entity, storage) in chunks_without_meshes.iter() { | 
				
			||||||
 | 
					        let mesh = create_bloxel_mesh( | 
				
			||||||
 | 
					            storage.deref(), | 
				
			||||||
 | 
					            terrain_atlas_layout, | 
				
			||||||
 | 
					            &terrain_atlas.sources, | 
				
			||||||
 | 
					            &block_lookup, | 
				
			||||||
 | 
					        ); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        commands.entity(entity).insert(( | 
				
			||||||
 | 
					            Mesh3d(meshes.add(mesh)), | 
				
			||||||
 | 
					            MeshMaterial3d(terrain_material.0.clone()), | 
				
			||||||
 | 
					        )); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,78 @@ | 
				
			|||||||
 | 
					use bevy::prelude::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod block; | 
				
			||||||
 | 
					pub mod chunk; | 
				
			||||||
 | 
					pub mod generic_math; | 
				
			||||||
 | 
					pub mod math; | 
				
			||||||
 | 
					pub mod mesh; | 
				
			||||||
 | 
					pub mod storage; | 
				
			||||||
 | 
					pub mod worldgen; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod prelude { | 
				
			||||||
 | 
					    pub use super::{ | 
				
			||||||
 | 
					        block::*, | 
				
			||||||
 | 
					        chunk::*, | 
				
			||||||
 | 
					        math::*, | 
				
			||||||
 | 
					        storage::{BloxelView, BloxelViewMut}, | 
				
			||||||
 | 
					        BloxelPlugin, | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use prelude::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct BloxelPlugin; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Plugin for BloxelPlugin { | 
				
			||||||
 | 
					    fn build(&self, app: &mut App) { | 
				
			||||||
 | 
					        app.add_systems( | 
				
			||||||
 | 
					            Update, | 
				
			||||||
 | 
					            ( | 
				
			||||||
 | 
					                set_transform_on_chunkpos_changed, | 
				
			||||||
 | 
					                worldgen::create_chunks_around_camera, | 
				
			||||||
 | 
					                worldgen::generate_terrain, | 
				
			||||||
 | 
					                mesh::generate_chunk_mesh, | 
				
			||||||
 | 
					            ), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					        .add_observer(add_chunks_to_chunkmap) | 
				
			||||||
 | 
					        .add_observer(remove_chunks_from_chunkmap); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn set_transform_on_chunkpos_changed( | 
				
			||||||
 | 
					    mut commands: Commands, | 
				
			||||||
 | 
					    chunk_query: Query<(Entity, &ChunkPos), Changed<ChunkPos>>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    for (chunk, pos) in chunk_query.iter() { | 
				
			||||||
 | 
					        commands.entity(chunk).insert(pos.transform()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn add_chunks_to_chunkmap( | 
				
			||||||
 | 
					    trigger: Trigger<OnAdd, (Parent, ChunkPos)>, | 
				
			||||||
 | 
					    chunk_query: Query<(&Parent, &ChunkPos)>, | 
				
			||||||
 | 
					    mut map_query: Query<&mut ChunkMap>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    let chunk = trigger.entity(); | 
				
			||||||
 | 
					    let Ok((parent, pos)) = chunk_query.get(chunk) else { | 
				
			||||||
 | 
					        // Trigger with a bundle doesn't work as expected. It triggers
 | 
				
			||||||
 | 
					        // when ANY component is added, rather than when ALL are present.
 | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					    let mut map = map_query.get_mut(parent.get()).unwrap(); | 
				
			||||||
 | 
					    map.try_insert(*pos, chunk).expect("chunk already present"); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn remove_chunks_from_chunkmap( | 
				
			||||||
 | 
					    trigger: Trigger<OnRemove, (Parent, ChunkPos)>, | 
				
			||||||
 | 
					    chunk_query: Query<(&Parent, &ChunkPos)>, | 
				
			||||||
 | 
					    mut map_query: Query<&mut ChunkMap>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    let chunk = trigger.entity(); | 
				
			||||||
 | 
					    let Ok((parent, pos)) = chunk_query.get(chunk) else { | 
				
			||||||
 | 
					        // See above.
 | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					    let mut map = map_query.get_mut(parent.get()).unwrap(); | 
				
			||||||
 | 
					    map.remove(pos).expect("chunk not found"); | 
				
			||||||
 | 
					    println!("Chunk {chunk} @ {pos:?} removed from {}", parent.get()); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,74 @@ | 
				
			|||||||
 | 
					use std::ops::{Index, IndexMut}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::math::{IVec3, UVec3}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::bloxel_view::{ivec3_to_index, BloxelView, BloxelViewMut}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct BloxelArray<T: Copy> { | 
				
			||||||
 | 
					    size: UVec3, | 
				
			||||||
 | 
					    data: Vec<T>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy> BloxelArray<T> { | 
				
			||||||
 | 
					    pub fn new(size: UVec3, fill: T) -> Self { | 
				
			||||||
 | 
					        assert!(size.x > 0 && size.y > 0 && size.z > 0); | 
				
			||||||
 | 
					        let len = (size.x * size.y * size.z) as usize; | 
				
			||||||
 | 
					        let data = vec![fill; len]; | 
				
			||||||
 | 
					        Self { size, data } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new_with_default(size: UVec3) -> Self | 
				
			||||||
 | 
					    where | 
				
			||||||
 | 
					        T: Default, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					        Self::new(size, Default::default()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_index(&self, pos: impl Into<IVec3>) -> usize { | 
				
			||||||
 | 
					        let pos = pos.into(); | 
				
			||||||
 | 
					        assert!(self.contains(pos)); | 
				
			||||||
 | 
					        ivec3_to_index(pos, self.size) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy> Index<IVec3> for BloxelArray<T> { | 
				
			||||||
 | 
					    type Output = T; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn index(&self, pos: IVec3) -> &Self::Output { | 
				
			||||||
 | 
					        let index = self.get_index(pos); | 
				
			||||||
 | 
					        &self.data[index] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy> IndexMut<IVec3> for BloxelArray<T> { | 
				
			||||||
 | 
					    fn index_mut(&mut self, pos: IVec3) -> &mut Self::Output { | 
				
			||||||
 | 
					        let index = self.get_index(pos); | 
				
			||||||
 | 
					        &mut self.data[index] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy> BloxelView<T> for BloxelArray<T> { | 
				
			||||||
 | 
					    fn size(&self) -> UVec3 { | 
				
			||||||
 | 
					        self.size | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn contains(&self, pos: impl Into<IVec3>) -> bool { | 
				
			||||||
 | 
					        let pos = pos.into(); | 
				
			||||||
 | 
					        let size = self.size.as_ivec3(); | 
				
			||||||
 | 
					        (pos.x >= 0 && pos.x < size.x) | 
				
			||||||
 | 
					            && (pos.y >= 0 && pos.y < size.y) | 
				
			||||||
 | 
					            && (pos.z >= 0 && pos.z < size.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get(&self, pos: impl Into<IVec3>) -> T { | 
				
			||||||
 | 
					        let index = self.get_index(pos); | 
				
			||||||
 | 
					        self.data[index] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy> BloxelViewMut<T> for BloxelArray<T> { | 
				
			||||||
 | 
					    fn set(&mut self, pos: impl Into<IVec3>, value: T) { | 
				
			||||||
 | 
					        let index = self.get_index(pos); | 
				
			||||||
 | 
					        self.data[index] = value; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,28 @@ | 
				
			|||||||
 | 
					use bevy::math::{IVec3, UVec3}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub trait BloxelView<T> { | 
				
			||||||
 | 
					    fn size(&self) -> UVec3; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn contains(&self, pos: impl Into<IVec3>) -> bool; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get(&self, pos: impl Into<IVec3>) -> T; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub trait BloxelViewMut<T>: BloxelView<T> { | 
				
			||||||
 | 
					    fn set(&mut self, pos: impl Into<IVec3>, value: T); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn ivec3_to_index(pos: impl Into<IVec3>, size: UVec3) -> usize { | 
				
			||||||
 | 
					    let pos = pos.into(); | 
				
			||||||
 | 
					    let size = size.as_ivec3(); | 
				
			||||||
 | 
					    (pos.x + (pos.y * size.x) + (pos.z * size.x * size.y)) as usize | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn index_to_ivec3(index: usize, size: UVec3) -> IVec3 { | 
				
			||||||
 | 
					    let size = size.as_ivec3(); | 
				
			||||||
 | 
					    let i = index as i32; | 
				
			||||||
 | 
					    let x = i % size.x; | 
				
			||||||
 | 
					    let y = i / size.x % size.y; | 
				
			||||||
 | 
					    let z = i / size.x / size.y; | 
				
			||||||
 | 
					    IVec3::new(x, y, z) | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,375 @@ | 
				
			|||||||
 | 
					use std::{array::from_fn, collections::BinaryHeap}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bevy::{ | 
				
			||||||
 | 
					    math::IVec3, | 
				
			||||||
 | 
					    utils::{HashMap, HashSet}, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::bloxel::math::{zorder, ChunkPos, ChunkRegion, ZOrderIndex}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const START_INDEX_LOOKUP: [usize; 11] = [ | 
				
			||||||
 | 
					    0, 1, 9, 73, 585, 4681, 37449, 299593, 2396745, 19173961, 153391689, | 
				
			||||||
 | 
					]; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct ChunkedOctree<T: Default + Copy + Eq> { | 
				
			||||||
 | 
					    depth: usize, | 
				
			||||||
 | 
					    regions: HashMap<ZOrderIndex, Vec<T>>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Default + Copy + Eq> ChunkedOctree<T> { | 
				
			||||||
 | 
					    pub fn new(depth: usize) -> Self { | 
				
			||||||
 | 
					        assert!(depth > 0 && depth < START_INDEX_LOOKUP.len()); | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            depth, | 
				
			||||||
 | 
					            regions: Default::default(), | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(&self, node: impl TryInto<OctreeNode>) -> T { | 
				
			||||||
 | 
					        if let Ok(node) = node.try_into() { | 
				
			||||||
 | 
					            if node.level <= self.depth { | 
				
			||||||
 | 
					                return self | 
				
			||||||
 | 
					                    .regions | 
				
			||||||
 | 
					                    .get(&(node.pos >> (self.depth - node.level))) | 
				
			||||||
 | 
					                    .map(|region| region[node.index(self.depth)]) | 
				
			||||||
 | 
					                    .unwrap_or_default(); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        Default::default() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn update<F>(&mut self, chunk_pos: ChunkPos, update_fn: F) | 
				
			||||||
 | 
					    where | 
				
			||||||
 | 
					        F: Fn(OctreeNode, Option<&[T; 8]>, &mut T), | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					        let pos: ZOrderIndex = chunk_pos.try_into().unwrap(); | 
				
			||||||
 | 
					        let region = self.regions.entry(pos >> self.depth).or_insert_with(|| { | 
				
			||||||
 | 
					            let size = START_INDEX_LOOKUP[self.depth + 1] + 1; | 
				
			||||||
 | 
					            vec![Default::default(); size] | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut node = OctreeNode::new(0, pos); | 
				
			||||||
 | 
					        while node.level <= self.depth { | 
				
			||||||
 | 
					            let parent_index = node.index(self.depth); | 
				
			||||||
 | 
					            let (parent, children) = if let Some(children_index) = node.children_index(self.depth) { | 
				
			||||||
 | 
					                let (left, right) = region.split_at_mut(children_index); | 
				
			||||||
 | 
					                let parent = &mut left[parent_index]; | 
				
			||||||
 | 
					                let children = right[0..8].try_into().unwrap(); | 
				
			||||||
 | 
					                (parent, Some(children)) | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                let parent = &mut region[parent_index]; | 
				
			||||||
 | 
					                (parent, None) | 
				
			||||||
 | 
					            }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let previous = *parent; | 
				
			||||||
 | 
					            update_fn(node, children, parent); | 
				
			||||||
 | 
					            if *parent == previous { | 
				
			||||||
 | 
					                break; // If no change was made, we don't have anything to propagate.
 | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            node = node.parent(); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn find<'a, W, F>( | 
				
			||||||
 | 
					        &'a self, | 
				
			||||||
 | 
					        from: impl IntoIterator<Item = &'a ChunkPos>, | 
				
			||||||
 | 
					        priority_fn: F, | 
				
			||||||
 | 
					    ) -> OctreeIterator<'a, T, W, F> | 
				
			||||||
 | 
					    where | 
				
			||||||
 | 
					        W: Ord, | 
				
			||||||
 | 
					        F: Fn(OctreeNode, T) -> Option<W>, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					        OctreeIterator::new(self, from, priority_fn) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct OctreeIterator<'a, T, P, F> | 
				
			||||||
 | 
					where | 
				
			||||||
 | 
					    T: Default + Copy + Eq, | 
				
			||||||
 | 
					    P: Ord, | 
				
			||||||
 | 
					    F: Fn(OctreeNode, T) -> Option<P>, | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    octree: &'a ChunkedOctree<T>, | 
				
			||||||
 | 
					    checked: HashSet<ZOrderIndex>, | 
				
			||||||
 | 
					    queue: BinaryHeap<PriorityItem<T, P>>, | 
				
			||||||
 | 
					    priority_fn: F, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a, T, P, F> OctreeIterator<'a, T, P, F> | 
				
			||||||
 | 
					where | 
				
			||||||
 | 
					    T: Default + Copy + Eq, | 
				
			||||||
 | 
					    P: Ord, | 
				
			||||||
 | 
					    F: Fn(OctreeNode, T) -> Option<P>, | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    fn new( | 
				
			||||||
 | 
					        octree: &'a ChunkedOctree<T>, | 
				
			||||||
 | 
					        from: impl IntoIterator<Item = &'a ChunkPos>, | 
				
			||||||
 | 
					        priority_fn: F, | 
				
			||||||
 | 
					    ) -> Self { | 
				
			||||||
 | 
					        let mut result = Self { | 
				
			||||||
 | 
					            octree, | 
				
			||||||
 | 
					            checked: Default::default(), | 
				
			||||||
 | 
					            queue: Default::default(), | 
				
			||||||
 | 
					            priority_fn, | 
				
			||||||
 | 
					        }; | 
				
			||||||
 | 
					        for pos in from { | 
				
			||||||
 | 
					            let node_pos: ZOrderIndex = (*pos).try_into().unwrap(); | 
				
			||||||
 | 
					            result.search_region(node_pos >> octree.depth); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        result | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn search_region(&mut self, region_pos: ZOrderIndex) { | 
				
			||||||
 | 
					        if self.checked.insert(region_pos) { | 
				
			||||||
 | 
					            self.push_item((self.octree.depth, region_pos).into()); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn push_item(&mut self, node: OctreeNode) { | 
				
			||||||
 | 
					        let value = self.octree.get(node); | 
				
			||||||
 | 
					        if let Some(priority) = (self.priority_fn)(node, value) { | 
				
			||||||
 | 
					            self.queue.push(PriorityItem { | 
				
			||||||
 | 
					                priority, | 
				
			||||||
 | 
					                node, | 
				
			||||||
 | 
					                value, | 
				
			||||||
 | 
					            }); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<'a, T, P, F> Iterator for OctreeIterator<'a, T, P, F> | 
				
			||||||
 | 
					where | 
				
			||||||
 | 
					    T: Default + Copy + Eq, | 
				
			||||||
 | 
					    P: Ord, | 
				
			||||||
 | 
					    F: Fn(OctreeNode, T) -> Option<P>, | 
				
			||||||
 | 
					{ | 
				
			||||||
 | 
					    type Item = (ChunkPos, T, P); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn next(&mut self) -> Option<Self::Item> { | 
				
			||||||
 | 
					        loop { | 
				
			||||||
 | 
					            let PriorityItem { | 
				
			||||||
 | 
					                priority, | 
				
			||||||
 | 
					                node, | 
				
			||||||
 | 
					                value, | 
				
			||||||
 | 
					            } = self.queue.pop()?; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if node.level == self.octree.depth { | 
				
			||||||
 | 
					                // If the current highest priority item is a region, see if its
 | 
				
			||||||
 | 
					                // neighboring regions are candidates to consider as well.
 | 
				
			||||||
 | 
					                // NOTE: Faulty `priority_fn` could cause this to loop forever.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Add all the region's neighbors to the priority queue.
 | 
				
			||||||
 | 
					                // NOTE: `search_region` skips any already added regions.
 | 
				
			||||||
 | 
					                for neighbor in node.neighbors() { | 
				
			||||||
 | 
					                    self.search_region(neighbor.pos); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Some(children) = node.children() { | 
				
			||||||
 | 
					                // If current highest priority node has children,
 | 
				
			||||||
 | 
					                // add them to the priority queue and continue.
 | 
				
			||||||
 | 
					                for child in children { | 
				
			||||||
 | 
					                    self.push_item(child); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                // If there are no children, then we're at `ChunkPos` level.
 | 
				
			||||||
 | 
					                // Return this chunk as the highest priority item found.
 | 
				
			||||||
 | 
					                let chunk_pos = node.pos.into(); | 
				
			||||||
 | 
					                return Some((chunk_pos, value, priority)); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] | 
				
			||||||
 | 
					pub struct OctreeNode { | 
				
			||||||
 | 
					    pub level: usize, | 
				
			||||||
 | 
					    pub pos: ZOrderIndex, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl OctreeNode { | 
				
			||||||
 | 
					    pub fn new(level: usize, pos: ZOrderIndex) -> Self { | 
				
			||||||
 | 
					        Self { level, pos } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn parent(&self) -> Self { | 
				
			||||||
 | 
					        Self::new(self.level + 1, self.pos >> 1) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn children(&self) -> Option<[Self; 8]> { | 
				
			||||||
 | 
					        (self.level > 0).then(|| from_fn(|i| self.child_unchecked(i as u64))) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn neighbors(&self) -> [Self; 26] { | 
				
			||||||
 | 
					        let (pos_x, pos_y, pos_z) = self.pos.into(); | 
				
			||||||
 | 
					        let mut result = [OctreeNode::default(); 26]; | 
				
			||||||
 | 
					        let mut index = 0; | 
				
			||||||
 | 
					        for x in -1..1 { | 
				
			||||||
 | 
					            for y in -1..1 { | 
				
			||||||
 | 
					                for z in -1..1 { | 
				
			||||||
 | 
					                    if x != 0 && y != 0 && z != 0 { | 
				
			||||||
 | 
					                        let pos = zorder(pos_x + x, pos_y + y, pos_z + z); | 
				
			||||||
 | 
					                        result[index] = OctreeNode::new(self.level, pos); | 
				
			||||||
 | 
					                        index += 1; | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        result | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn region(&self) -> ChunkRegion { | 
				
			||||||
 | 
					        let min = (self.pos << self.level).into(); | 
				
			||||||
 | 
					        let max = min + IVec3::splat((1 << self.level) - 1); | 
				
			||||||
 | 
					        ChunkRegion::new_unchecked(min, max) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn index(&self, depth: usize) -> usize { | 
				
			||||||
 | 
					        let local_pos = self.pos & !(!0 << ((depth - self.level) * 3)); | 
				
			||||||
 | 
					        START_INDEX_LOOKUP[depth - self.level] + local_pos.raw() as usize | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn children_index(&self, depth: usize) -> Option<usize> { | 
				
			||||||
 | 
					        (self.level > 0).then(|| self.child_unchecked(0).index(depth)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns the child of this node with the specified index (within `0..8`).
 | 
				
			||||||
 | 
					    /// No safety checks are done to ensure that `level` or `index` are valid.
 | 
				
			||||||
 | 
					    fn child_unchecked(&self, index: u64) -> Self { | 
				
			||||||
 | 
					        Self::new(self.level - 1, (self.pos << 1) | index) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Into<OctreeNode> for (usize, ZOrderIndex) { | 
				
			||||||
 | 
					    fn into(self) -> OctreeNode { | 
				
			||||||
 | 
					        OctreeNode::new(self.0, self.1) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryInto<OctreeNode> for ChunkPos { | 
				
			||||||
 | 
					    type Error = (); | 
				
			||||||
 | 
					    fn try_into(self) -> Result<OctreeNode, ()> { | 
				
			||||||
 | 
					        Ok(OctreeNode::new(0, self.try_into()?)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TryInto<OctreeNode> for (i32, i32, i32) { | 
				
			||||||
 | 
					    type Error = (); | 
				
			||||||
 | 
					    fn try_into(self) -> Result<OctreeNode, ()> { | 
				
			||||||
 | 
					        Into::<ChunkPos>::into(self).try_into() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct PriorityItem<T: Default + Copy + Eq, P: Ord> { | 
				
			||||||
 | 
					    priority: P, | 
				
			||||||
 | 
					    node: OctreeNode, | 
				
			||||||
 | 
					    value: T, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Default + Copy + Eq, P: Ord> Eq for PriorityItem<T, P> {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Default + Copy + Eq, P: Ord> PartialEq for PriorityItem<T, P> { | 
				
			||||||
 | 
					    fn eq(&self, other: &Self) -> bool { | 
				
			||||||
 | 
					        self.priority.eq(&other.priority) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Default + Copy + Eq, P: Ord> Ord for PriorityItem<T, P> { | 
				
			||||||
 | 
					    fn cmp(&self, other: &Self) -> std::cmp::Ordering { | 
				
			||||||
 | 
					        self.priority.cmp(&other.priority) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Default + Copy + Eq, P: Ord> PartialOrd for PriorityItem<T, P> { | 
				
			||||||
 | 
					    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | 
				
			||||||
 | 
					        self.priority.partial_cmp(&other.priority) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[cfg(test)] | 
				
			||||||
 | 
					mod tests { | 
				
			||||||
 | 
					    use super::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    fn update() { | 
				
			||||||
 | 
					        let mut octree = ChunkedOctree::<bool>::new(3); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((0, 0, 0))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((1, 1, 1))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((-1, -1, -1))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        octree.update((0, 0, 0).into(), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((0, 0, 0))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((0, zorder(0, 0, 0)))); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((1, zorder(0, 0, 0)))); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((2, zorder(0, 0, 0)))); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((3, zorder(0, 0, 0)))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((0, zorder(1, 1, 1)))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((1, zorder(2, 2, 2)))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((2, zorder(4, 4, 4)))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((3, zorder(8, 8, 8)))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((0, zorder(-1, -1, -1)))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((1, zorder(-1, -1, -1)))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((2, zorder(-1, -1, -1)))); | 
				
			||||||
 | 
					        assert_eq!(false, octree.get((3, zorder(-1, -1, -1)))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        octree.update(ChunkPos::new(-12, -17, -42), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((-12, -17, -42))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((0, zorder(-12, -17, -42)))); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((1, zorder(-6, -9, -21)))); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((2, zorder(-3, -5, -11)))); | 
				
			||||||
 | 
					        assert_eq!(true, octree.get((3, zorder(-2, -3, -6)))); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    #[rustfmt::skip] | 
				
			||||||
 | 
					    fn find() { | 
				
			||||||
 | 
					        let mut octree = ChunkedOctree::<bool>::new(3); | 
				
			||||||
 | 
					        octree.update(( 2,  3,  4).into(), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					        octree.update((10, 10, 10).into(), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					        octree.update(( 8,  8,  8).into(), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					        octree.update(( 0, 16, -1).into(), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					        octree.update(( 9,  9,  9).into(), |_, _, parent| *parent = true); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let from = ChunkPos::new(8, 8, 8); | 
				
			||||||
 | 
					        let mut iterator = octree.find([&from], |node, value| { | 
				
			||||||
 | 
					            value.then(|| -node.region().distance_to_squared(from)) | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(Some((( 8,  8,  8).into(), true, -(0*0 + 0*0 + 0*0))), iterator.next()); | 
				
			||||||
 | 
					        assert_eq!(Some((( 9,  9,  9).into(), true, -(1*1 + 1*1 + 1*1))), iterator.next()); | 
				
			||||||
 | 
					        assert_eq!(Some(((10, 10, 10).into(), true, -(2*2 + 2*2 + 2*2))), iterator.next()); | 
				
			||||||
 | 
					        assert_eq!(Some((( 2,  3,  4).into(), true, -(6*6 + 5*5 + 4*4))), iterator.next()); | 
				
			||||||
 | 
					        assert_eq!(Some((( 0, 16, -1).into(), true, -(8*8 + 8*8 + 9*9))), iterator.next()); | 
				
			||||||
 | 
					        assert_eq!(None, iterator.next()); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[test] | 
				
			||||||
 | 
					    fn node_region() { | 
				
			||||||
 | 
					        let chunk = |a: (i32, i32, i32), b: (i32, i32, i32)| -> ChunkRegion { | 
				
			||||||
 | 
					            ChunkRegion::new(a.into(), b.into()) | 
				
			||||||
 | 
					        }; | 
				
			||||||
 | 
					        let node = |level: usize, pos: (i32, i32, i32)| -> ChunkRegion { | 
				
			||||||
 | 
					            OctreeNode::new(level, pos.try_into().unwrap()).region() | 
				
			||||||
 | 
					        }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(chunk((0, 0, 0), (0, 0, 0)), node(0, (0, 0, 0))); | 
				
			||||||
 | 
					        assert_eq!(chunk((0, 0, 0), (1, 1, 1)), node(1, (0, 0, 0))); | 
				
			||||||
 | 
					        assert_eq!(chunk((0, 0, 0), (3, 3, 3)), node(2, (0, 0, 0))); | 
				
			||||||
 | 
					        assert_eq!(chunk((0, 0, 0), (7, 7, 7)), node(3, (0, 0, 0))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(chunk((-1, -1, -1), (-1, -1, -1)), node(0, (-1, -1, -1))); | 
				
			||||||
 | 
					        assert_eq!(chunk((-2, -2, -2), (-1, -1, -1)), node(1, (-1, -1, -1))); | 
				
			||||||
 | 
					        assert_eq!(chunk((-4, -4, -4), (-1, -1, -1)), node(2, (-1, -1, -1))); | 
				
			||||||
 | 
					        assert_eq!(chunk((-8, -8, -8), (-1, -1, -1)), node(3, (-1, -1, -1))); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        assert_eq!(chunk((2, -3, -4), (2, -3, -4)), node(0, (2, -3, -4))); | 
				
			||||||
 | 
					        assert_eq!(chunk((4, -6, -8), (5, -5, -7)), node(1, (2, -3, -4))); | 
				
			||||||
 | 
					        assert_eq!(chunk((8, -12, -16), (11, -9, -13)), node(2, (2, -3, -4))); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,10 @@ | 
				
			|||||||
 | 
					mod bloxel_array; | 
				
			||||||
 | 
					mod bloxel_view; | 
				
			||||||
 | 
					mod chunked_octree; | 
				
			||||||
 | 
					mod palette_bloxel_storage; | 
				
			||||||
 | 
					mod palette_storage; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub use bloxel_array::*; | 
				
			||||||
 | 
					pub use bloxel_view::*; | 
				
			||||||
 | 
					pub use chunked_octree::*; | 
				
			||||||
 | 
					pub use palette_bloxel_storage::*; | 
				
			||||||
@ -0,0 +1,63 @@ | 
				
			|||||||
 | 
					use bevy::math::{IVec3, UVec3}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use super::{ | 
				
			||||||
 | 
					    bloxel_view::{ivec3_to_index, BloxelView, BloxelViewMut}, | 
				
			||||||
 | 
					    palette_storage::PaletteStorage, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct PaletteBloxelStorage<T: Copy + Eq> { | 
				
			||||||
 | 
					    size: UVec3, | 
				
			||||||
 | 
					    data: PaletteStorage<T>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy + Eq> PaletteBloxelStorage<T> { | 
				
			||||||
 | 
					    pub fn new(size: UVec3, fill: T) -> Self { | 
				
			||||||
 | 
					        assert!(size.x > 0 && size.y > 0 && size.z > 0); | 
				
			||||||
 | 
					        let len = (size.x * size.y * size.z).try_into().unwrap(); | 
				
			||||||
 | 
					        let data = PaletteStorage::new(len, fill); | 
				
			||||||
 | 
					        Self { size, data } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new_with_default(size: UVec3) -> Self | 
				
			||||||
 | 
					    where | 
				
			||||||
 | 
					        T: Default, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					        Self::new(size, Default::default()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get_used_count(&self, value: T) -> usize { | 
				
			||||||
 | 
					        self.data.get_used_count(value) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_index(&self, pos: impl Into<IVec3>) -> usize { | 
				
			||||||
 | 
					        let pos = pos.into(); | 
				
			||||||
 | 
					        assert!(self.contains(pos)); | 
				
			||||||
 | 
					        ivec3_to_index(pos, self.size) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy + Eq> BloxelView<T> for PaletteBloxelStorage<T> { | 
				
			||||||
 | 
					    fn size(&self) -> UVec3 { | 
				
			||||||
 | 
					        self.size | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn contains(&self, pos: impl Into<IVec3>) -> bool { | 
				
			||||||
 | 
					        let pos = pos.into(); | 
				
			||||||
 | 
					        let size = self.size.as_ivec3(); | 
				
			||||||
 | 
					        (pos.x >= 0 && pos.x < size.x) | 
				
			||||||
 | 
					            && (pos.y >= 0 && pos.y < size.y) | 
				
			||||||
 | 
					            && (pos.z >= 0 && pos.z < size.z) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get(&self, pos: impl Into<IVec3>) -> T { | 
				
			||||||
 | 
					        let index = self.get_index(pos); | 
				
			||||||
 | 
					        self.data.get(index) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy + Eq> BloxelViewMut<T> for PaletteBloxelStorage<T> { | 
				
			||||||
 | 
					    fn set(&mut self, pos: impl Into<IVec3>, value: T) { | 
				
			||||||
 | 
					        let index = self.get_index(pos); | 
				
			||||||
 | 
					        self.data.set(index, value); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,213 @@ | 
				
			|||||||
 | 
					use bitvec::prelude::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct PaletteStorage<T: Copy + Eq> { | 
				
			||||||
 | 
					    len: usize, | 
				
			||||||
 | 
					    bits: usize, | 
				
			||||||
 | 
					    data: BitVec, | 
				
			||||||
 | 
					    entries: Vec<PaletteEntry<T>>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T: Copy + Eq> PaletteStorage<T> { | 
				
			||||||
 | 
					    pub fn new(len: usize, fill: T) -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            len, | 
				
			||||||
 | 
					            bits: 0, | 
				
			||||||
 | 
					            data: bitvec![], | 
				
			||||||
 | 
					            entries: vec![PaletteEntry { | 
				
			||||||
 | 
					                value: Some(fill), | 
				
			||||||
 | 
					                used: len, | 
				
			||||||
 | 
					            }], | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn new_with_default(len: usize) -> Self | 
				
			||||||
 | 
					    where | 
				
			||||||
 | 
					        T: Default, | 
				
			||||||
 | 
					    { | 
				
			||||||
 | 
					        Self::new(len, Default::default()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Gets the length of this `PaletteStorage`. That is, how many
 | 
				
			||||||
 | 
					    /// elements can be accessed via the `get` and `set` functions.
 | 
				
			||||||
 | 
					    pub fn len(&self) -> usize { | 
				
			||||||
 | 
					        self.len | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Gets the number of bits each element takes up when stored.
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// More bits means more palette entries can be stored, so more unique
 | 
				
			||||||
 | 
					    /// values can be stored in this `PaletteStorage`, at the cost of more
 | 
				
			||||||
 | 
					    /// memory. This will automatically increase as necessary.
 | 
				
			||||||
 | 
					    pub fn bits(&self) -> usize { | 
				
			||||||
 | 
					        self.bits | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Gets the number of times the specific value occurs in this `PaletteStorage`.
 | 
				
			||||||
 | 
					    pub fn get_used_count(&self, value: T) -> usize { | 
				
			||||||
 | 
					        self.find_existing_entry(value).map(|e| e.used).unwrap_or(0) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn get(&self, index: usize) -> T { | 
				
			||||||
 | 
					        let palette_index = self.get_palette_index(index); | 
				
			||||||
 | 
					        let entry = &self.entries[palette_index]; | 
				
			||||||
 | 
					        entry.value.expect("invalid palette entry") | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn set(&mut self, index: usize, value: T) -> T { | 
				
			||||||
 | 
					        let prev_palette_index = self.get_palette_index(index); | 
				
			||||||
 | 
					        let prev_entry = &mut self.entries[prev_palette_index]; | 
				
			||||||
 | 
					        let prev_value = prev_entry.value.expect("invalid palette entry"); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // If entry found at `index` already has this value, return early.
 | 
				
			||||||
 | 
					        if prev_value == value { | 
				
			||||||
 | 
					            return value; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Reduce the number of times the previously used palette for this
 | 
				
			||||||
 | 
					        // `index` is in use. This potentially allows this entry to be reused.
 | 
				
			||||||
 | 
					        prev_entry.used -= 1; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Find a palette entry for this value. Resizes palette if necessary.
 | 
				
			||||||
 | 
					        let (new_palette_index, new_entry) = self.find_or_create_entry(value); | 
				
			||||||
 | 
					        // increase the number of times it's in use
 | 
				
			||||||
 | 
					        new_entry.used += 1; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Update the palette index in the actual `data` BitVec.
 | 
				
			||||||
 | 
					        self.set_palette_index(index, new_palette_index); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        prev_value | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Gets the bit range into `data` for the specified index.
 | 
				
			||||||
 | 
					    fn bit_range(&self, index: usize) -> std::ops::Range<usize> { | 
				
			||||||
 | 
					        (index * self.bits)..((index + 1) * self.bits) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Looks up the palette index (into `entries`) at the specified index.
 | 
				
			||||||
 | 
					    fn get_palette_index(&self, index: usize) -> usize { | 
				
			||||||
 | 
					        if self.bits > 0 { | 
				
			||||||
 | 
					            let bit_range = self.bit_range(index); | 
				
			||||||
 | 
					            self.data[bit_range].load() | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            0 | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn set_palette_index(&mut self, index: usize, value: usize) { | 
				
			||||||
 | 
					        let bit_range = self.bit_range(index); | 
				
			||||||
 | 
					        self.data[bit_range].store(value) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn find_existing_entry(&self, value: T) -> Option<&PaletteEntry<T>> { | 
				
			||||||
 | 
					        self.entries.iter().find(|e| e.value == Some(value)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn find_or_create_entry(&mut self, value: T) -> (usize, &mut PaletteEntry<T>) { | 
				
			||||||
 | 
					        match self.find_entry_index(value) { | 
				
			||||||
 | 
					            // The palette entry already exists, so just return it.
 | 
				
			||||||
 | 
					            FindResult::Existing(index) => { | 
				
			||||||
 | 
					                let entry = &mut self.entries[index]; | 
				
			||||||
 | 
					                (index, entry) | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            // The entry didn't exist, but we found one we can reuse.
 | 
				
			||||||
 | 
					            FindResult::Uninitialized(index) | FindResult::Unused(index) => { | 
				
			||||||
 | 
					                let entry = &mut self.entries[index]; | 
				
			||||||
 | 
					                entry.value = Some(value); | 
				
			||||||
 | 
					                (index, entry) | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					            // Everything in the palette is already in use. RESIZE!
 | 
				
			||||||
 | 
					            FindResult::None => { | 
				
			||||||
 | 
					                let index = self.entries.len(); | 
				
			||||||
 | 
					                self.resize_palette(self.bits + 1); | 
				
			||||||
 | 
					                let entry = &mut self.entries[index]; | 
				
			||||||
 | 
					                entry.value = Some(value); | 
				
			||||||
 | 
					                (index, entry) | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn find_entry_index(&self, value: T) -> FindResult { | 
				
			||||||
 | 
					        let mut result = FindResult::None; | 
				
			||||||
 | 
					        for (index, entry) in self.entries.iter().enumerate() { | 
				
			||||||
 | 
					            match entry { | 
				
			||||||
 | 
					                // If the specified value is found in the palette, return it immediately.
 | 
				
			||||||
 | 
					                PaletteEntry { value: v, .. } if *v == Some(value) => { | 
				
			||||||
 | 
					                    return FindResult::Existing(index) | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					                // Store the first uninitialized entry in case we don't find the specified value.
 | 
				
			||||||
 | 
					                PaletteEntry { value: None, .. } => match result { | 
				
			||||||
 | 
					                    FindResult::Existing(_) => unreachable!(), | 
				
			||||||
 | 
					                    FindResult::Uninitialized(_) => {} | 
				
			||||||
 | 
					                    _ => result = FindResult::Uninitialized(index), | 
				
			||||||
 | 
					                }, | 
				
			||||||
 | 
					                // Otherwise, pick the first initialized entry that's currently used.
 | 
				
			||||||
 | 
					                PaletteEntry { used: 0, .. } => match result { | 
				
			||||||
 | 
					                    FindResult::Existing(_) => unreachable!(), | 
				
			||||||
 | 
					                    FindResult::Uninitialized(_) | FindResult::Unused(_) => {} | 
				
			||||||
 | 
					                    FindResult::None => result = FindResult::Unused(index), | 
				
			||||||
 | 
					                }, | 
				
			||||||
 | 
					                _ => {} | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        result | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn resize_palette(&mut self, new_bits: usize) { | 
				
			||||||
 | 
					        if new_bits == self.bits { | 
				
			||||||
 | 
					            return; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Revisit this to see if we can optimize it.
 | 
				
			||||||
 | 
					        // Create a new data BitVec and copy the bits from the old one over.
 | 
				
			||||||
 | 
					        let mut new_data = BitVec::with_capacity(self.len * new_bits); | 
				
			||||||
 | 
					        if new_bits == 0 { | 
				
			||||||
 | 
					            // Nothing to do if we have nothing to copy to.
 | 
				
			||||||
 | 
					        } else if self.bits == 0 { | 
				
			||||||
 | 
					            // No data to copy from, so just fill with zeroes.
 | 
				
			||||||
 | 
					            new_data.resize(self.len * new_bits, false); | 
				
			||||||
 | 
					        } else if new_bits > self.bits { | 
				
			||||||
 | 
					            let additional_bits = new_bits - self.bits; | 
				
			||||||
 | 
					            for chunk in self.data.chunks_exact(self.bits) { | 
				
			||||||
 | 
					                new_data.extend(chunk); | 
				
			||||||
 | 
					                for _ in 0..additional_bits { | 
				
			||||||
 | 
					                    new_data.push(false); | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					            for chunk in self.data.chunks_exact(self.bits) { | 
				
			||||||
 | 
					                new_data.extend(&chunk[0..new_bits]); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        self.data = new_data; | 
				
			||||||
 | 
					        self.bits = new_bits; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Resize the palette itself.
 | 
				
			||||||
 | 
					        let num_entries = 2usize.pow(new_bits as u32); | 
				
			||||||
 | 
					        self.entries.resize_with(num_entries, Default::default); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum FindResult { | 
				
			||||||
 | 
					    Existing(usize), | 
				
			||||||
 | 
					    Uninitialized(usize), | 
				
			||||||
 | 
					    Unused(usize), | 
				
			||||||
 | 
					    None, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// #[derive(Default)]
 | 
				
			||||||
 | 
					struct PaletteEntry<T> { | 
				
			||||||
 | 
					    value: Option<T>, | 
				
			||||||
 | 
					    used: usize, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOTE: For some weird reason, deriving `Default` doesn't quite do what we want
 | 
				
			||||||
 | 
					//       when later calling `Vec::resize_with`, so we're manually implementing
 | 
				
			||||||
 | 
					//       the trait instead. [insert confused noises here]
 | 
				
			||||||
 | 
					impl<T> Default for PaletteEntry<T> { | 
				
			||||||
 | 
					    fn default() -> Self { | 
				
			||||||
 | 
					        Self { | 
				
			||||||
 | 
					            value: None, | 
				
			||||||
 | 
					            used: 0, | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,91 @@ | 
				
			|||||||
 | 
					use bevy::prelude::*; | 
				
			||||||
 | 
					use rand::prelude::*; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{ | 
				
			||||||
 | 
					    bloxel::{prelude::*, storage::ChunkedOctree}, | 
				
			||||||
 | 
					    camera_controller::ControlledCamera, | 
				
			||||||
 | 
					    TerrainBlocks, | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create_chunks_around_camera( | 
				
			||||||
 | 
					    mut commands: Commands, | 
				
			||||||
 | 
					    mut octree: Local<GeneratedChunks>, | 
				
			||||||
 | 
					    camera: Single<&Transform, With<ControlledCamera>>, | 
				
			||||||
 | 
					    chunk_map: Single<Entity, With<ChunkMap>>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    let block_pos = camera.translation.as_ivec3().into(); | 
				
			||||||
 | 
					    let (from, _) = ChunkPos::from_block_pos(block_pos); | 
				
			||||||
 | 
					    let mut iterator = octree.find([&from], |node, generated| { | 
				
			||||||
 | 
					        if generated != Generated::All { | 
				
			||||||
 | 
					            let distance = node.region().distance_to_squared(from); | 
				
			||||||
 | 
					            if distance <= 6 * 6 { | 
				
			||||||
 | 
					                return Some(-distance); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        None | 
				
			||||||
 | 
					    }); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some((chunk_pos, _, _)) = iterator.next() { | 
				
			||||||
 | 
					        commands.entity(*chunk_map).with_child((Chunk, chunk_pos)); | 
				
			||||||
 | 
					        octree.update(chunk_pos, |_node, children, parent| { | 
				
			||||||
 | 
					            let children = children.map(|a| a.as_slice()).unwrap_or_default(); | 
				
			||||||
 | 
					            *parent = if children.iter().all(|c| *c == Generated::All) { | 
				
			||||||
 | 
					                Generated::All | 
				
			||||||
 | 
					            } else { | 
				
			||||||
 | 
					                Generated::Some | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        }); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn generate_terrain( | 
				
			||||||
 | 
					    mut commands: Commands, | 
				
			||||||
 | 
					    terrain_blocks: Option<Res<TerrainBlocks>>, | 
				
			||||||
 | 
					    chunks_without_data: Query<(Entity, &ChunkPos), (With<Chunk>, Without<ChunkData>)>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    let Some(terrain) = terrain_blocks else { | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut rng = rand::rng(); | 
				
			||||||
 | 
					    let random_blocks = [terrain.grass, terrain.dirt, terrain.rock, terrain.sand]; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (entity, chunk_pos) in chunks_without_data.iter() { | 
				
			||||||
 | 
					        let mut data = ChunkData::new(terrain.air); | 
				
			||||||
 | 
					        let size = data.size().as_ivec3(); | 
				
			||||||
 | 
					        for z in 0..size.z { | 
				
			||||||
 | 
					            for y in 0..size.y { | 
				
			||||||
 | 
					                for x in 0..size.x { | 
				
			||||||
 | 
					                    let relative = IVec3::new(x, y, z); | 
				
			||||||
 | 
					                    let block_pos = chunk_pos.to_block_pos(relative); | 
				
			||||||
 | 
					                    let chance = (-block_pos.y as f64 / 32.).clamp(0.001, 1.); | 
				
			||||||
 | 
					                    if rng.random_bool(chance) { | 
				
			||||||
 | 
					                        let block = *random_blocks.choose(&mut rng).unwrap(); | 
				
			||||||
 | 
					                        data.set(relative, block); | 
				
			||||||
 | 
					                    } | 
				
			||||||
 | 
					                } | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        commands.entity(entity).insert(data); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Deref, DerefMut)] | 
				
			||||||
 | 
					pub struct GeneratedChunks { | 
				
			||||||
 | 
					    octree: ChunkedOctree<Generated>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for GeneratedChunks { | 
				
			||||||
 | 
					    fn default() -> Self { | 
				
			||||||
 | 
					        let octree = ChunkedOctree::new(5); | 
				
			||||||
 | 
					        Self { octree } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] | 
				
			||||||
 | 
					pub enum Generated { | 
				
			||||||
 | 
					    #[default] | 
				
			||||||
 | 
					    None, | 
				
			||||||
 | 
					    Some, | 
				
			||||||
 | 
					    All, | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,110 @@ | 
				
			|||||||
 | 
					use bevy::{input::mouse::AccumulatedMouseMotion, prelude::*, window::CursorGrabMode}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MOVEMENT_SPEED: f32 = 0.15; | 
				
			||||||
 | 
					const MOUSE_SENSITIVITY: Vec2 = Vec2::new(0.002, 0.002); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct CameraControllerPlugin; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Marks an entity as being the camera controlled by this plugin.
 | 
				
			||||||
 | 
					#[derive(Component)] | 
				
			||||||
 | 
					pub struct ControlledCamera; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Plugin for CameraControllerPlugin { | 
				
			||||||
 | 
					    fn build(&self, app: &mut App) { | 
				
			||||||
 | 
					        app.add_systems( | 
				
			||||||
 | 
					            Update, | 
				
			||||||
 | 
					            ( | 
				
			||||||
 | 
					                capture_mouse, | 
				
			||||||
 | 
					                camera_mouse_rotation, | 
				
			||||||
 | 
					                camera_keyboard_translation, | 
				
			||||||
 | 
					            ) | 
				
			||||||
 | 
					                .chain(), | 
				
			||||||
 | 
					        ); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn capture_mouse( | 
				
			||||||
 | 
					    mut window: Single<&mut Window>, | 
				
			||||||
 | 
					    mouse: Res<ButtonInput<MouseButton>>, | 
				
			||||||
 | 
					    key: Res<ButtonInput<KeyCode>>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    if mouse.just_pressed(MouseButton::Left) { | 
				
			||||||
 | 
					        window.cursor_options.visible = false; | 
				
			||||||
 | 
					        window.cursor_options.grab_mode = CursorGrabMode::Locked; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if key.just_pressed(KeyCode::Escape) { | 
				
			||||||
 | 
					        window.cursor_options.visible = true; | 
				
			||||||
 | 
					        window.cursor_options.grab_mode = CursorGrabMode::None; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn camera_mouse_rotation( | 
				
			||||||
 | 
					    window: Single<&Window>, | 
				
			||||||
 | 
					    mut camera: Query<&mut Transform, With<ControlledCamera>>, | 
				
			||||||
 | 
					    mouse_motion: Res<AccumulatedMouseMotion>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    // Mouse must be grabbed by the window for this system to run.
 | 
				
			||||||
 | 
					    if window.cursor_options.grab_mode != CursorGrabMode::Locked { | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We also need to have exactly one camera with the `ControlledCamera` component.
 | 
				
			||||||
 | 
					    let Ok(mut transform) = camera.get_single_mut() else { | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let delta = mouse_motion.delta; | 
				
			||||||
 | 
					    if delta != Vec2::ZERO { | 
				
			||||||
 | 
					        let delta_yaw = -delta.x * MOUSE_SENSITIVITY.x; | 
				
			||||||
 | 
					        let delta_pitch = -delta.y * MOUSE_SENSITIVITY.y; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let rot_yaw = Quat::from_axis_angle(Vec3::Y, delta_yaw); | 
				
			||||||
 | 
					        let rot_pitch = Quat::from_axis_angle(Vec3::X, delta_pitch); | 
				
			||||||
 | 
					        transform.rotation = rot_yaw * transform.rotation * rot_pitch; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn camera_keyboard_translation( | 
				
			||||||
 | 
					    window: Single<&Window>, | 
				
			||||||
 | 
					    mut camera: Query<&mut Transform, With<ControlledCamera>>, | 
				
			||||||
 | 
					    key: Res<ButtonInput<KeyCode>>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    // Mouse must be grabbed by the window for this system to run.
 | 
				
			||||||
 | 
					    if window.cursor_options.grab_mode != CursorGrabMode::Locked { | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // We also need to have exactly one camera with the `ControlledCamera` component.
 | 
				
			||||||
 | 
					    let Ok(mut transform) = camera.get_single_mut() else { | 
				
			||||||
 | 
					        return; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut input = Vec2::ZERO; | 
				
			||||||
 | 
					    if key.pressed(KeyCode::KeyD) { | 
				
			||||||
 | 
					        input.x += 1.; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if key.pressed(KeyCode::KeyA) { | 
				
			||||||
 | 
					        input.x -= 1.; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if key.pressed(KeyCode::KeyW) { | 
				
			||||||
 | 
					        input.y += 1.; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if key.pressed(KeyCode::KeyS) { | 
				
			||||||
 | 
					        input.y -= 1.; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut movement = input * MOVEMENT_SPEED; | 
				
			||||||
 | 
					    if key.pressed(KeyCode::ShiftLeft) { | 
				
			||||||
 | 
					        movement *= 4.; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if movement.x != 0. { | 
				
			||||||
 | 
					        let translation = transform.right() * movement.x; | 
				
			||||||
 | 
					        transform.translation += translation; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if movement.y != 0. { | 
				
			||||||
 | 
					        let translation = transform.forward() * movement.y; | 
				
			||||||
 | 
					        transform.translation += translation; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,145 @@ | 
				
			|||||||
 | 
					use bevy::{asset::LoadedFolder, prelude::*}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod bloxel; | 
				
			||||||
 | 
					mod camera_controller; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use bloxel::block::BlockTexture; | 
				
			||||||
 | 
					use bloxel::prelude::*; | 
				
			||||||
 | 
					use camera_controller::{CameraControllerPlugin, ControlledCamera}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Resource)] | 
				
			||||||
 | 
					pub struct TerrainTextures(Handle<LoadedFolder>); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Resource)] | 
				
			||||||
 | 
					pub struct TerrainAtlas { | 
				
			||||||
 | 
					    pub layout: Handle<TextureAtlasLayout>, | 
				
			||||||
 | 
					    pub sources: TextureAtlasSources, | 
				
			||||||
 | 
					    pub texture: Handle<Image>, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Resource)] | 
				
			||||||
 | 
					pub struct TerrainMaterial(Handle<StandardMaterial>); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Resource)] | 
				
			||||||
 | 
					pub struct TerrainBlocks { | 
				
			||||||
 | 
					    air: Entity, | 
				
			||||||
 | 
					    grass: Entity, | 
				
			||||||
 | 
					    dirt: Entity, | 
				
			||||||
 | 
					    rock: Entity, | 
				
			||||||
 | 
					    sand: Entity, | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() { | 
				
			||||||
 | 
					    App::new() | 
				
			||||||
 | 
					        .add_plugins(( | 
				
			||||||
 | 
					            DefaultPlugins.set(ImagePlugin::default_nearest()), | 
				
			||||||
 | 
					            CameraControllerPlugin, | 
				
			||||||
 | 
					            BloxelPlugin, | 
				
			||||||
 | 
					        )) | 
				
			||||||
 | 
					        .add_systems( | 
				
			||||||
 | 
					            Startup, | 
				
			||||||
 | 
					            ( | 
				
			||||||
 | 
					                setup_sunlight, | 
				
			||||||
 | 
					                setup_camera, | 
				
			||||||
 | 
					                setup_terrain_blocks, | 
				
			||||||
 | 
					                load_terrain_textures, | 
				
			||||||
 | 
					                generate_sample_chunks, | 
				
			||||||
 | 
					            ), | 
				
			||||||
 | 
					        ) | 
				
			||||||
 | 
					        .add_systems(Update, check_terrain_textures) | 
				
			||||||
 | 
					        .run(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn setup_terrain_blocks(mut commands: Commands, asset_server: Res<AssetServer>) { | 
				
			||||||
 | 
					    let air = commands.spawn((Block, Name::new("air"))).id(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut block = |name: &'static str, texture: &str| -> Entity { | 
				
			||||||
 | 
					        let path = format!("terrain/{texture}.png"); | 
				
			||||||
 | 
					        let texture = BlockTexture(asset_server.load(path)); | 
				
			||||||
 | 
					        commands.spawn((Block, Name::new(name), texture)).id() | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let blocks = TerrainBlocks { | 
				
			||||||
 | 
					        air, | 
				
			||||||
 | 
					        grass: block("grass", "grass_top"), | 
				
			||||||
 | 
					        dirt: block("dirt", "dirt"), | 
				
			||||||
 | 
					        rock: block("rock", "rock"), | 
				
			||||||
 | 
					        sand: block("sand", "sand"), | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    commands.insert_resource(blocks); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn load_terrain_textures(mut commands: Commands, asset_server: Res<AssetServer>) { | 
				
			||||||
 | 
					    commands.insert_resource(TerrainTextures(asset_server.load_folder("terrain"))); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Waits for all textures in `assets/terrain/` to be loaded, then builds an atlas
 | 
				
			||||||
 | 
					/// from those along with the [`TerrainMaterial`] and [`TerrainAtlas`] resources.
 | 
				
			||||||
 | 
					fn check_terrain_textures( | 
				
			||||||
 | 
					    mut commands: Commands, | 
				
			||||||
 | 
					    mut events: EventReader<AssetEvent<LoadedFolder>>, | 
				
			||||||
 | 
					    terrain_textures: Res<TerrainTextures>, | 
				
			||||||
 | 
					    loaded_folders: Res<Assets<LoadedFolder>>, | 
				
			||||||
 | 
					    mut textures: ResMut<Assets<Image>>, | 
				
			||||||
 | 
					    mut materials: ResMut<Assets<StandardMaterial>>, | 
				
			||||||
 | 
					    mut atlas_layouts: ResMut<Assets<TextureAtlasLayout>>, | 
				
			||||||
 | 
					) { | 
				
			||||||
 | 
					    for event in events.read() { | 
				
			||||||
 | 
					        if event.is_loaded_with_dependencies(&terrain_textures.0) { | 
				
			||||||
 | 
					            let mut atlas_builder = TextureAtlasBuilder::default(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let folder = loaded_folders.get(&terrain_textures.0).unwrap(); | 
				
			||||||
 | 
					            for handle in folder.handles.iter() { | 
				
			||||||
 | 
					                let id = handle.id().typed_unchecked::<Image>(); | 
				
			||||||
 | 
					                let Some(texture) = textures.get(id) else { | 
				
			||||||
 | 
					                    warn!( | 
				
			||||||
 | 
					                        "{:?} did not resolve to an `Image` asset.", | 
				
			||||||
 | 
					                        handle.path().unwrap() | 
				
			||||||
 | 
					                    ); | 
				
			||||||
 | 
					                    continue; | 
				
			||||||
 | 
					                }; | 
				
			||||||
 | 
					                atlas_builder.add_texture(Some(id), texture); | 
				
			||||||
 | 
					            } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let (layout, sources, texture) = atlas_builder.build().unwrap(); | 
				
			||||||
 | 
					            let layout = atlas_layouts.add(layout); | 
				
			||||||
 | 
					            let texture = textures.add(texture); | 
				
			||||||
 | 
					            let material = materials.add(texture.clone()); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            commands.insert_resource(TerrainMaterial(material)); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            commands.insert_resource(TerrainAtlas { | 
				
			||||||
 | 
					                layout, | 
				
			||||||
 | 
					                sources, | 
				
			||||||
 | 
					                texture, | 
				
			||||||
 | 
					            }); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn generate_sample_chunks(mut commands: Commands) { | 
				
			||||||
 | 
					    commands.spawn(ChunkMap::default()).with_children(|map| { | 
				
			||||||
 | 
					        // map.spawn((Chunk, ChunkPos::new(0, 0, 0)));
 | 
				
			||||||
 | 
					        // map.spawn((Chunk, ChunkPos::new(1, 0, 0)));
 | 
				
			||||||
 | 
					        // map.spawn((Chunk, ChunkPos::new(-1, 0, 0)));
 | 
				
			||||||
 | 
					    }); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn setup_sunlight(mut commands: Commands) { | 
				
			||||||
 | 
					    commands.spawn(( | 
				
			||||||
 | 
					        DirectionalLight { | 
				
			||||||
 | 
					            shadows_enabled: true, | 
				
			||||||
 | 
					            ..default() | 
				
			||||||
 | 
					        }, | 
				
			||||||
 | 
					        Transform::from_xyz(4., 8., 4.).looking_at(Vec3::ZERO, Vec3::Y), | 
				
			||||||
 | 
					    )); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn setup_camera(mut commands: Commands) { | 
				
			||||||
 | 
					    commands.spawn(( | 
				
			||||||
 | 
					        ControlledCamera, | 
				
			||||||
 | 
					        Camera3d::default(), | 
				
			||||||
 | 
					        Transform::from_xyz(-24., 16., 16.).looking_at((0., 0., 0.).into(), Vec3::Y), | 
				
			||||||
 | 
					    )); | 
				
			||||||
 | 
					} | 
				
			||||||