From 8ce5513e77537da1c50c40984102c81469148590 Mon Sep 17 00:00:00 2001 From: copygirl Date: Mon, 2 Jun 2025 22:51:19 +0200 Subject: [PATCH] Fix chunk culling, general cleanup - Add `Neighbor` enum - Add `USize3` struct - Remove `BloxelView` trait - Remove `generic_math` module - Add `ChunkedBloxelView` helper struct which contains neighboring chunk data to pass to mesh generator - Add and use a whole bunch of helper functions, like `BloxelStoreMut::update` and `BloxelArray::from_fn` - Use `ndarray` and `cart_prod` crates to simplify code --- Cargo.lock | 57 +++++ Cargo.toml | 2 + src/bloxel/chunk.rs | 8 +- src/bloxel/generic_math/mod.rs | 5 - src/bloxel/generic_math/pos.rs | 213 ------------------- src/bloxel/generic_math/region.rs | 184 ---------------- src/bloxel/math/block_pos.rs | 14 +- src/bloxel/math/block_region.rs | 47 ++-- src/bloxel/math/chunk_pos.rs | 14 +- src/bloxel/math/chunk_region.rs | 21 +- src/bloxel/math/mod.rs | 4 + src/bloxel/math/neighbor.rs | 195 +++++++++++++++++ src/bloxel/math/size.rs | 80 +++++++ src/bloxel/math/z_order_index.rs | 6 +- src/bloxel/mesh/mesh_generator.rs | 58 ++--- src/bloxel/mesh/mod.rs | 57 ++++- src/bloxel/mod.rs | 3 +- src/bloxel/storage/bloxel_array.rs | 76 +++---- src/bloxel/storage/bloxel_store.rs | 24 +++ src/bloxel/storage/bloxel_view.rs | 28 --- src/bloxel/storage/chunked_octree.rs | 2 +- src/bloxel/storage/mod.rs | 4 +- src/bloxel/storage/palette_bloxel_storage.rs | 54 ++--- src/bloxel/worldgen/mod.rs | 36 ++-- 24 files changed, 561 insertions(+), 631 deletions(-) delete mode 100644 src/bloxel/generic_math/mod.rs delete mode 100644 src/bloxel/generic_math/pos.rs delete mode 100644 src/bloxel/generic_math/region.rs create mode 100644 src/bloxel/math/neighbor.rs create mode 100644 src/bloxel/math/size.rs create mode 100644 src/bloxel/storage/bloxel_store.rs delete mode 100644 src/bloxel/storage/bloxel_view.rs diff --git a/Cargo.lock b/Cargo.lock index 8650c7e..333e448 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -308,6 +308,8 @@ version = "0.1.0" dependencies = [ "bevy", "bitvec", + "cart_prod", + "ndarray", "noise-functions", "overload", "rand 0.9.1", @@ -1485,6 +1487,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "cart_prod" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97a605730917e77d9505da266dfcf2d0b04307e9785e1456d8ef3d4a2b95f32" + [[package]] name = "cc" version = "1.2.25" @@ -2718,6 +2726,16 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matrixmultiply" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.4" @@ -2819,6 +2837,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ndarray" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + [[package]] name = "ndk" version = "0.8.0" @@ -2975,6 +3008,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -2986,6 +3028,15 @@ dependencies = [ "syn", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -3621,6 +3672,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + [[package]] name = "read-fonts" version = "0.29.2" diff --git a/Cargo.toml b/Cargo.toml index da947de..60ddf71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ opt-level = 3 [dependencies] bevy = { version = "0.16.0", features = [ "file_watcher", "embedded_watcher" ] } bitvec = "1.0.1" +cart_prod = "0.1.0" +ndarray = "0.16.1" noise-functions = "0.8.1" overload = "0.1.1" rand = "0.9.1" diff --git a/src/bloxel/chunk.rs b/src/bloxel/chunk.rs index 7cdb998..a1848bf 100644 --- a/src/bloxel/chunk.rs +++ b/src/bloxel/chunk.rs @@ -1,7 +1,7 @@ use bevy::{platform::collections::HashMap, prelude::*}; use super::{ - math::{ChunkPos, CHUNK_LENGTH}, + math::{ChunkPos, USize3, CHUNK_LENGTH}, storage::PaletteBloxelStorage, }; @@ -14,10 +14,8 @@ pub struct ChunkData(PaletteBloxelStorage); impl ChunkData { pub fn new(default: Entity) -> Self { - Self(PaletteBloxelStorage::new( - UVec3::splat(CHUNK_LENGTH as u32), - default, - )) + let len = (CHUNK_LENGTH as u32).try_into().unwrap(); + Self(PaletteBloxelStorage::new(USize3::splat(len), default)) } } diff --git a/src/bloxel/generic_math/mod.rs b/src/bloxel/generic_math/mod.rs deleted file mode 100644 index c5f5eff..0000000 --- a/src/bloxel/generic_math/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod pos; -mod region; - -pub use pos::Pos; -pub use region::Region; diff --git a/src/bloxel/generic_math/pos.rs b/src/bloxel/generic_math/pos.rs deleted file mode 100644 index 232d4f5..0000000 --- a/src/bloxel/generic_math/pos.rs +++ /dev/null @@ -1,213 +0,0 @@ -use std::{ - marker::PhantomData, - ops::{Add, AddAssign, Index, IndexMut, Sub, SubAssign}, -}; - -use bevy::{ - ecs::component::{Component, Immutable}, - math::{Dir3, IVec3, InvalidDirectionError}, -}; - -use super::Region; - -pub struct Pos { - pub x: i32, - pub y: i32, - pub z: i32, - _marker: PhantomData, -} - -impl Pos { - 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 { - 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::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 std::fmt::Display for Pos { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "[{}, {}, {}]", self.x, self.y, self.z) - } -} - -impl std::fmt::Debug for Pos { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.debug_tuple(stringify!(Pos)) - .field(&self.x) - .field(&self.y) - .field(&self.z) - .finish() - } -} - -// Trait implementations that SHOULD be handled by #[derive] - -use bevy::ecs::component::StorageType; -impl Component for Pos { - const STORAGE_TYPE: StorageType = StorageType::Table; - type Mutability = Immutable; -} - -impl Default for Pos { - fn default() -> Self { - Self { - x: 0, - y: 0, - z: 0, - _marker: PhantomData, - } - } -} - -impl Clone for Pos { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Pos {} - -impl PartialEq for Pos { - fn eq(&self, other: &Self) -> bool { - (self.x, self.y, self.z).eq(&(other.x, other.y, other.z)) - } -} - -impl Eq for Pos {} - -impl std::hash::Hash for Pos { - fn hash(&self, state: &mut H) { - (self.x, self.y, self.z).hash(state) - } -} - -// Conversion - -impl From<(i32, i32, i32)> for Pos { - fn from((x, y, z): (i32, i32, i32)) -> Self { - Self::new(x, y, z) - } -} -impl From> for (i32, i32, i32) { - fn from(Pos { x, y, z, .. }: Pos) -> Self { - (x, y, z) - } -} - -impl From<[i32; 3]> for Pos { - fn from([x, y, z]: [i32; 3]) -> Self { - Self::new(x, y, z) - } -} -impl From> for [i32; 3] { - fn from(Pos { x, y, z, .. }: Pos) -> Self { - [x, y, z] - } -} - -impl From for Pos { - fn from(IVec3 { x, y, z }: IVec3) -> Self { - Self::new(x, y, z) - } -} -impl From> for IVec3 { - fn from(Pos { x, y, z, .. }: Pos) -> Self { - Self::new(x, y, z) - } -} - -// Offsetting - -impl Add for Pos { - type Output = Pos; - fn add(self, rhs: IVec3) -> Self::Output { - Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z) - } -} - -impl Sub for Pos { - type Output = Pos; - fn sub(self, rhs: IVec3) -> Self::Output { - Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z) - } -} - -impl AddAssign for Pos { - fn add_assign(&mut self, rhs: IVec3) { - self.x += rhs.x; - self.y += rhs.y; - self.z += rhs.z; - } -} - -impl SubAssign for Pos { - fn sub_assign(&mut self, rhs: IVec3) { - self.x -= rhs.x; - self.y -= rhs.y; - self.z -= rhs.z; - } -} - -// Difference - -impl Sub> for Pos { - 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 Index for Pos { - 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 for Pos { - 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"), - } - } -} diff --git a/src/bloxel/generic_math/region.rs b/src/bloxel/generic_math/region.rs deleted file mode 100644 index 587da24..0000000 --- a/src/bloxel/generic_math/region.rs +++ /dev/null @@ -1,184 +0,0 @@ -use std::{ - marker::PhantomData, - ops::{Add, AddAssign, Sub, SubAssign}, -}; - -use bevy::{ - ecs::component::{Component, Immutable}, - math::{IVec3, UVec3}, -}; - -use super::Pos; - -pub struct Region { - min: IVec3, - max: IVec3, - _marker: PhantomData, -} - -impl Region { - pub fn new_unchecked(min: IVec3, max: IVec3) -> Self { - Self { - min, - max, - _marker: PhantomData, - } - } - - pub fn new_checked(min: Pos, max: Pos) -> Result { - 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, b: Pos) -> 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) -> 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) -> f32 { - (self.distance_to_squared(pos) as f32).sqrt() - } - - pub fn distance_to_squared(&self, pos: Pos) -> 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::new_checked( - (self.min - IVec3::splat(amount)).into(), - (self.max + IVec3::splat(amount)).into(), - ) - } -} - -impl std::fmt::Display for Region { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} to {}", self.min, self.max) - } -} - -impl std::fmt::Debug for Region { - fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt.debug_struct(stringify!(Region)) - .field("min", &self.min) - .field("max", &self.max) - .finish() - } -} - -// Trait implementations that SHOULD be handled by #[derive] - -use bevy::ecs::component::StorageType; -impl Component for Region { - const STORAGE_TYPE: StorageType = StorageType::Table; - type Mutability = Immutable; -} - -impl Default for Region { - fn default() -> Self { - Self { - min: IVec3::ZERO, - max: IVec3::ZERO, - _marker: PhantomData, - } - } -} - -impl Clone for Region { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for Region {} - -impl PartialEq for Region { - fn eq(&self, other: &Self) -> bool { - (self.min, self.max).eq(&(other.min, other.max)) - } -} - -impl Eq for Region {} - -impl std::hash::Hash for Region { - fn hash(&self, state: &mut H) { - (self.min, self.max).hash(state) - } -} - -// Conversion - -impl From> for (Pos, Pos) { - fn from(Region { min, max, .. }: Region) -> Self { - (min.into(), max.into()) - } -} - -// Offsetting - -impl Add for Region { - type Output = Region; - fn add(self, rhs: IVec3) -> Self::Output { - Self::new_unchecked(self.min + rhs, self.max + rhs) - } -} -impl Add for &Region { - type Output = Region; - fn add(self, rhs: IVec3) -> Self::Output { - Region::new_unchecked(self.min + rhs, self.max + rhs) - } -} - -impl Sub for Region { - type Output = Region; - fn sub(self, rhs: IVec3) -> Self::Output { - Self::new_unchecked(self.min - rhs, self.max - rhs) - } -} -impl Sub for &Region { - type Output = Region; - fn sub(self, rhs: IVec3) -> Self::Output { - Region::new_unchecked(self.min - rhs, self.max - rhs) - } -} - -impl AddAssign for Region { - fn add_assign(&mut self, rhs: IVec3) { - self.min += rhs; - self.max += rhs; - } -} - -impl SubAssign for Region { - fn sub_assign(&mut self, rhs: IVec3) { - self.min -= rhs; - self.max -= rhs; - } -} diff --git a/src/bloxel/math/block_pos.rs b/src/bloxel/math/block_pos.rs index 83d46d5..befae3d 100644 --- a/src/bloxel/math/block_pos.rs +++ b/src/bloxel/math/block_pos.rs @@ -45,10 +45,6 @@ impl BlockPos { 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 { @@ -59,14 +55,14 @@ impl std::fmt::Display for BlockPos { // 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; }); +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) }); +overload!((l: ?BlockPos) - (r: ?BlockPos) -> IVec3 { IVec3::new(l.x - r.x, l.y - r.y, l.z - r.z) }); // Indexing diff --git a/src/bloxel/math/block_region.rs b/src/bloxel/math/block_region.rs index 91eba0d..94d43fb 100644 --- a/src/bloxel/math/block_region.rs +++ b/src/bloxel/math/block_region.rs @@ -4,9 +4,10 @@ use bevy::{ ecs::component::Component, math::{IVec3, UVec3}, }; +use cart_prod::specs::Hom3FCartProd; use overload::overload; -use super::BlockPos; +use super::{BlockPos, USize3}; #[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct BlockRegion { @@ -33,8 +34,24 @@ impl BlockRegion { Self::new_unchecked(min, max) } - pub fn size(&self) -> UVec3 { - (self.max - self.min + IVec3::ONE).as_uvec3() + pub fn from_min_and_size(min: BlockPos, size: USize3) -> Self { + let max = min + UVec3::from(size).as_ivec3(); + Self::new_unchecked(min, max) + } + + pub fn min(&self) -> BlockPos { + self.min + } + + pub fn max(&self) -> BlockPos { + self.max + } + + pub fn size(&self) -> USize3 { + (self.max - self.min + IVec3::ONE) + .as_uvec3() + .try_into() + .unwrap() } pub fn contains(&self, pos: BlockPos) -> bool { @@ -58,19 +75,21 @@ impl BlockRegion { 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::new_checked( self.min - IVec3::splat(amount), self.max + IVec3::splat(amount), ) } + + pub fn iter(&self) -> impl Iterator { + let xs = self.min.x..=self.max.x; + let ys = self.min.y..=self.max.y; + let zs = self.min.z..=self.max.z; + Hom3FCartProd::new(xs, ys, zs).map(|a| a.into()) + } + + //pub fn chunks(&self) -> impl Iterator {} } impl std::fmt::Display for BlockRegion { @@ -81,10 +100,10 @@ impl std::fmt::Display for BlockRegion { // 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; }); +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; }); // Conversion diff --git a/src/bloxel/math/chunk_pos.rs b/src/bloxel/math/chunk_pos.rs index cfd13d0..f1cfd45 100644 --- a/src/bloxel/math/chunk_pos.rs +++ b/src/bloxel/math/chunk_pos.rs @@ -58,10 +58,6 @@ impl ChunkPos { 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) @@ -88,14 +84,14 @@ impl std::fmt::Display for ChunkPos { // 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; }); +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) }); +overload!((l: ?ChunkPos) - (r: ?ChunkPos) -> IVec3 { IVec3::new(l.x - r.x, l.y - r.y, l.z - r.z) }); // Indexing diff --git a/src/bloxel/math/chunk_region.rs b/src/bloxel/math/chunk_region.rs index 39ae0f2..c2beef1 100644 --- a/src/bloxel/math/chunk_region.rs +++ b/src/bloxel/math/chunk_region.rs @@ -1,12 +1,9 @@ use std::ops; -use bevy::{ - ecs::component::Component, - math::{IVec3, UVec3}, -}; +use bevy::{ecs::component::Component, math::IVec3}; use overload::overload; -use super::{BlockRegion, ChunkPos, CHUNK_MAX}; +use super::{BlockRegion, ChunkPos, USize3, CHUNK_MAX}; #[derive(Component, Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct ChunkRegion { @@ -33,8 +30,11 @@ impl ChunkRegion { Self::new_unchecked(min, max) } - pub fn size(&self) -> UVec3 { - (self.max - self.min + IVec3::ONE).as_uvec3() + pub fn size(&self) -> USize3 { + (self.max - self.min + IVec3::ONE) + .as_uvec3() + .try_into() + .unwrap() } pub fn contains(&self, pos: ChunkPos) -> bool { @@ -58,13 +58,6 @@ impl ChunkRegion { 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::new_checked( self.min - IVec3::splat(amount), diff --git a/src/bloxel/math/mod.rs b/src/bloxel/math/mod.rs index aa072c5..b8bf4b8 100644 --- a/src/bloxel/math/mod.rs +++ b/src/bloxel/math/mod.rs @@ -2,10 +2,14 @@ mod block_pos; mod block_region; mod chunk_pos; mod chunk_region; +mod neighbor; +mod size; mod z_order_index; pub use block_pos::*; pub use block_region::*; pub use chunk_pos::*; pub use chunk_region::*; +pub use neighbor::*; +pub use size::*; pub use z_order_index::*; diff --git a/src/bloxel/math/neighbor.rs b/src/bloxel/math/neighbor.rs new file mode 100644 index 0000000..e64f84e --- /dev/null +++ b/src/bloxel/math/neighbor.rs @@ -0,0 +1,195 @@ +use std::ops::{self, Neg}; + +use bevy::math::{Dir3, IVec3}; +use overload::overload; + +use super::{BlockPos, ChunkPos}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum Neighbor { + Right, // +X + Left, // -X + Top, // +Y + Bottom, // -Y + Front, // +Z + Back, // +Z + + RightTop, + LeftTop, + RightBottom, + LeftBottom, + RightFront, + LeftFront, + RightBack, + LeftBack, + TopFront, + BottomFront, + TopBack, + BottomBack, + + RightTopFront, + LeftTopFront, + RightBottomFront, + LeftBottomFront, + RightTopBack, + LeftTopBack, + RightBottomBack, + LeftBottomBack, +} + +#[rustfmt::skip] +impl Neighbor { + /// All neighbors that share a face. Also known as "orthogonal" or "direct" neighbors. + pub const FACE: [Self; 6] = [ Self::Right, Self::Left, Self::Top, Self::Bottom, Self::Front, Self::Back ]; + + /// All neighbors that share an edge and only an edge. + pub const EDGE: [Self; 12] = [ Self::RightTop, Self::LeftTop, Self::RightBottom, Self::LeftBottom, Self::RightFront, Self::LeftFront, + Self::RightBack, Self::LeftBack, Self::TopFront, Self::BottomFront, Self::TopBack, Self::BottomBack ]; + + /// All neighbors that share a corner and only a corner. + pub const CORNER: [Self; 8] = [ Self::RightTopFront, Self::LeftTopFront, Self::RightBottomFront, Self::LeftBottomFront, + Self::RightTopBack, Self::LeftTopBack, Self::RightBottomBack, Self::LeftBottomBack ]; + + pub const ALL: [Self; 26] = [ + Self::Right, Self::Left, Self::Top, Self::Bottom, Self::Front, Self::Back, + Self::RightTop, Self::LeftTop, Self::RightBottom, Self::LeftBottom, Self::RightFront, Self::LeftFront, + Self::RightBack, Self::LeftBack, Self::TopFront, Self::BottomFront, Self::TopBack, Self::BottomBack, + Self::RightTopFront, Self::LeftTopFront, Self::RightBottomFront, Self::LeftBottomFront, + Self::RightTopBack, Self::LeftTopBack, Self::RightBottomBack, Self::LeftBottomBack, + ]; +} + +impl From for IVec3 { + #[rustfmt::skip] + fn from(n: Neighbor) -> Self { + match n { + Neighbor::Right => Self::new( 1, 0, 0), + Neighbor::Left => Self::new(-1, 0, 0), + Neighbor::Top => Self::new(0, 1, 0), + Neighbor::Bottom => Self::new(0, -1, 0), + Neighbor::Front => Self::new(0, 0, 1), + Neighbor::Back => Self::new(0, 0, -1), + + Neighbor::RightTop => Self::new( 1, 1, 0), + Neighbor::LeftTop => Self::new(-1, 1, 0), + Neighbor::RightBottom => Self::new( 1, -1, 0), + Neighbor::LeftBottom => Self::new(-1, -1, 0), + Neighbor::RightFront => Self::new( 1, 0, 1), + Neighbor::LeftFront => Self::new(-1, 0, 1), + Neighbor::RightBack => Self::new( 1, 0, -1), + Neighbor::LeftBack => Self::new(-1, 0, -1), + Neighbor::TopFront => Self::new( 0, 1, 1), + Neighbor::BottomFront => Self::new( 0, -1, 1), + Neighbor::TopBack => Self::new( 0, 1, -1), + Neighbor::BottomBack => Self::new( 0, -1, -1), + + Neighbor::RightTopFront => Self::new( 1, 1, 1), + Neighbor::LeftTopFront => Self::new(-1, 1, 1), + Neighbor::RightBottomFront => Self::new( 1, -1, 1), + Neighbor::LeftBottomFront => Self::new(-1, -1, 1), + Neighbor::RightTopBack => Self::new( 1, 1, -1), + Neighbor::LeftTopBack => Self::new(-1, 1, -1), + Neighbor::RightBottomBack => Self::new( 1, -1, -1), + Neighbor::LeftBottomBack => Self::new(-1, -1, -1), + } + } +} + +impl TryFrom for Neighbor { + type Error = (); + #[rustfmt::skip] + fn try_from(v: IVec3) -> Result { + match v { + IVec3 { x: 1, y: 0, z: 0 } => Ok(Self::Right), + IVec3 { x: -1, y: 0, z: 0 } => Ok(Self::Left), + IVec3 { x: 0, y: 1, z: 0 } => Ok(Self::Top), + IVec3 { x: 0, y: -1, z: 0 } => Ok(Self::Bottom), + IVec3 { x: 0, y: 0, z: 1 } => Ok(Self::Front), + IVec3 { x: 0, y: 0, z: -1 } => Ok(Self::Back), + + IVec3 { x: 1, y: 1, z: 0 } => Ok(Self::RightTop), + IVec3 { x: -1, y: 1, z: 0 } => Ok(Self::LeftTop), + IVec3 { x: 1, y: -1, z: 0 } => Ok(Self::RightBottom), + IVec3 { x: -1, y: -1, z: 0 } => Ok(Self::LeftBottom), + IVec3 { x: 1, y: 0, z: 1 } => Ok(Self::RightFront), + IVec3 { x: -1, y: 0, z: 1 } => Ok(Self::LeftFront), + IVec3 { x: 1, y: 0, z: -1 } => Ok(Self::RightBack), + IVec3 { x: -1, y: 0, z: -1 } => Ok(Self::LeftBack), + IVec3 { x: 0, y: 1, z: 1 } => Ok(Self::TopFront), + IVec3 { x: 0, y: -1, z: 1 } => Ok(Self::BottomFront), + IVec3 { x: 0, y: 1, z: -1 } => Ok(Self::TopBack), + IVec3 { x: 0, y: -1, z: -1 } => Ok(Self::BottomBack), + + IVec3 { x: 1, y: 1, z: 1 } => Ok(Self::RightTopFront), + IVec3 { x: -1, y: 1, z: 1 } => Ok(Self::LeftTopFront), + IVec3 { x: 1, y: -1, z: 1 } => Ok(Self::RightBottomFront), + IVec3 { x: -1, y: -1, z: 1 } => Ok(Self::LeftBottomFront), + IVec3 { x: 1, y: 1, z: -1 } => Ok(Self::RightTopBack), + IVec3 { x: -1, y: 1, z: -1 } => Ok(Self::LeftTopBack), + IVec3 { x: 1, y: -1, z: -1 } => Ok(Self::RightBottomBack), + IVec3 { x: -1, y: -1, z: -1 } => Ok(Self::LeftBottomBack), + + _ => Err(()), + } + } +} + +impl From for Dir3 { + fn from(n: Neighbor) -> Dir3 { + Self::new(IVec3::from(n).as_vec3()).unwrap() + } +} + +impl Neg for Neighbor { + type Output = Neighbor; + #[rustfmt::skip] + fn neg(self) -> Self::Output { + match self { + Self::Right => Self::Left, + Self::Left => Self::Right, + Self::Top => Self::Bottom, + Self::Bottom => Self::Top, + Self::Front => Self::Back, + Self::Back => Self::Front, + + Self::RightTop => Self::LeftBottom, + Self::LeftTop => Self::RightBottom, + Self::RightBottom => Self::LeftTop, + Self::LeftBottom => Self::RightTop, + Self::RightFront => Self::LeftBack, + Self::LeftFront => Self::RightBack, + Self::RightBack => Self::LeftFront, + Self::LeftBack => Self::RightFront, + Self::TopFront => Self::BottomBack, + Self::BottomFront => Self::TopBack, + Self::TopBack => Self::BottomFront, + Self::BottomBack => Self::TopFront, + + Self::RightTopFront => Self::LeftBottomBack, + Self::LeftTopFront => Self::RightBottomBack, + Self::RightBottomFront => Self::LeftTopBack, + Self::LeftBottomFront => Self::RightTopBack, + Self::RightTopBack => Self::LeftBottomFront, + Self::LeftTopBack => Self::RightBottomFront, + Self::RightBottomBack => Self::LeftTopFront, + Self::LeftBottomBack => Self::RightTopFront, + } + } +} + +// Offsetting + +overload!((l: ?IVec3) + (r: Neighbor) -> IVec3 { l + IVec3::from(r) }); +overload!((l: ?IVec3) - (r: Neighbor) -> IVec3 { l - IVec3::from(r) }); +overload!((s: &mut IVec3) += (v: Neighbor) { s.add_assign(IVec3::from(v)) }); +overload!((s: &mut IVec3) -= (v: Neighbor) { s.sub_assign(IVec3::from(v)) }); + +overload!((l: ?BlockPos) + (r: Neighbor) -> BlockPos { l + IVec3::from(r) }); +overload!((l: ?BlockPos) - (r: Neighbor) -> BlockPos { l - IVec3::from(r) }); +overload!((s: &mut BlockPos) += (v: Neighbor) { s.add_assign(IVec3::from(v)) }); +overload!((s: &mut BlockPos) -= (v: Neighbor) { s.sub_assign(IVec3::from(v)) }); + +overload!((l: ?ChunkPos) + (r: Neighbor) -> ChunkPos { l + IVec3::from(r) }); +overload!((l: ?ChunkPos) - (r: Neighbor) -> ChunkPos { l - IVec3::from(r) }); +overload!((s: &mut ChunkPos) += (v: Neighbor) { s.add_assign(IVec3::from(v)) }); +overload!((s: &mut ChunkPos) -= (v: Neighbor) { s.sub_assign(IVec3::from(v)) }); diff --git a/src/bloxel/math/size.rs b/src/bloxel/math/size.rs new file mode 100644 index 0000000..d5cd748 --- /dev/null +++ b/src/bloxel/math/size.rs @@ -0,0 +1,80 @@ +use std::num::{NonZeroU32, TryFromIntError}; + +use bevy::math::{IVec3, UVec3}; + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub struct USize3 { + pub width: NonZeroU32, + pub height: NonZeroU32, + pub depth: NonZeroU32, +} + +impl USize3 { + pub fn new(width: NonZeroU32, height: NonZeroU32, depth: NonZeroU32) -> Self { + Self { + width, + height, + depth, + } + } + + pub fn splat(length: NonZeroU32) -> Self { + Self::new(length, length, length) + } + + pub fn contains(&self, pos: IVec3) -> bool { + (0..self.width.get() as i32).contains(&pos.x) + && (0..self.height.get() as i32).contains(&pos.y) + && (0..self.depth.get() as i32).contains(&pos.z) + } + + pub fn ivec3_to_index(&self, pos: IVec3) -> Option { + self.contains(pos).then(|| { + let w = self.width.get() as i32; + let h = self.height.get() as i32; + (pos.x + (pos.y * w) + (pos.z * w * h)) as usize + }) + } + + pub fn index_to_ivec3(&self, index: usize) -> IVec3 { + let i = index as i32; + let w = self.width.get() as i32; + let h = self.height.get() as i32; + IVec3::new(i % w, i / w % h, i / w / h) + } +} + +impl From for (NonZeroU32, NonZeroU32, NonZeroU32) { + fn from(size: USize3) -> Self { + (size.width, size.depth, size.height) + } +} + +impl From for (u32, u32, u32) { + fn from(size: USize3) -> Self { + (size.width.get(), size.depth.get(), size.height.get()) + } +} + +impl TryFrom for USize3 { + type Error = TryFromIntError; + fn try_from(UVec3 { x, y, z }: UVec3) -> Result { + Ok(Self::new(x.try_into()?, y.try_into()?, z.try_into()?)) + } +} + +impl From for UVec3 { + fn from(size: USize3) -> Self { + Self::new(size.width.get(), size.height.get(), size.depth.get()) + } +} + +impl ndarray::IntoDimension for USize3 { + type Dim = ndarray::Ix3; + fn into_dimension(self) -> Self::Dim { + let w = self.width.get() as usize; + let h = self.height.get() as usize; + let d = self.depth.get() as usize; + ndarray::Ix3(w, h, d) + } +} diff --git a/src/bloxel/math/z_order_index.rs b/src/bloxel/math/z_order_index.rs index a313308..26273ca 100644 --- a/src/bloxel/math/z_order_index.rs +++ b/src/bloxel/math/z_order_index.rs @@ -130,7 +130,7 @@ impl TryFrom<[i32; 3]> for ZOrderIndex { } impl From for [i32; 3] { fn from(value: ZOrderIndex) -> Self { - Into::<(i32, i32, i32)>::into(value).into() + <(i32, i32, i32)>::from(value).into() } } @@ -142,7 +142,7 @@ impl TryFrom for ZOrderIndex { } impl From for IVec3 { fn from(value: ZOrderIndex) -> Self { - Into::<(i32, i32, i32)>::into(value).into() + <(i32, i32, i32)>::from(value).into() } } @@ -154,7 +154,7 @@ impl TryFrom for ZOrderIndex { } impl From for ChunkPos { fn from(value: ZOrderIndex) -> Self { - Into::<(i32, i32, i32)>::into(value).into() + <(i32, i32, i32)>::from(value).into() } } diff --git a/src/bloxel/mesh/mesh_generator.rs b/src/bloxel/mesh/mesh_generator.rs index ad9405c..491e8f2 100644 --- a/src/bloxel/mesh/mesh_generator.rs +++ b/src/bloxel/mesh/mesh_generator.rs @@ -1,16 +1,21 @@ use bevy::{ - asset::RenderAssetUsages, prelude::*, render::mesh::Indices, render::mesh::PrimitiveTopology, + asset::RenderAssetUsages, + prelude::*, + render::mesh::{Indices, PrimitiveTopology}, }; +use cart_prod::specs::Hom3FCartProd; use crate::bloxel::{block::BlockTexture, prelude::*, storage::BloxelArray}; pub fn create_bloxel_mesh( - bloxels: &impl BloxelView, + bloxels: &impl BloxelStore, layout: &TextureAtlasLayout, sources: &TextureAtlasSources, block_lookup: &Query<&BlockTexture, With>, ) -> Mesh { - let offset = -(bloxels.size().as_vec3() / 2.); + let size = bloxels.size(); + let isize = UVec3::from(size).as_ivec3(); + let offset = -(isize.as_vec3() / 2.); let mut positions: Vec = vec![]; let mut normals: Vec = vec![]; @@ -65,41 +70,24 @@ pub fn create_bloxel_mesh( // 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); - } - } - } + let textures = BloxelArray::from_fn(size, |pos| { + bloxels + .get(pos) + .and_then(|e| block_lookup.get(e).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(), - ], - ); - } - } + for [x, y, z] in Hom3FCartProd::new(1..isize.z - 1, 1..isize.y - 1, 1..isize.x - 1) { + let pos = IVec3::new(x, y, z); + if let Some(Some(texture)) = textures.get(pos) { + if let Some(uvs) = uv_rect(sources, layout, texture) { + append_cube( + pos - IVec3::ONE, + uvs.inflate(shrink_amount), + Neighbor::FACE.map(|n| textures.get(pos + n).flatten().is_none()), + ); } } } diff --git a/src/bloxel/mesh/mod.rs b/src/bloxel/mesh/mod.rs index c2d4658..58b0aea 100644 --- a/src/bloxel/mesh/mod.rs +++ b/src/bloxel/mesh/mod.rs @@ -1,6 +1,4 @@ -use std::ops::Deref; - -use bevy::prelude::*; +use bevy::{platform::collections::HashMap, prelude::*}; use crate::{ bloxel::{block::BlockTexture, prelude::*}, @@ -15,10 +13,19 @@ pub use mesh_generator::*; pub fn generate_chunk_mesh( mut commands: Commands, mut meshes: ResMut>, + + // Allows us to find chunks with have their data filled in by + // the world generator, but don't yet have a mesh generated for it. + chunks_without_meshes: Query<(Entity, &ChunkPos, &ChunkData), (With, Without)>, + // The `chunk_map` and `chunks_with_data` will allow us to get neighboring + // chunks' data so we can determine whether blocks at the border should + // have their sides be culled. + chunk_map: Single<&ChunkMap>, + chunks_with_data: Query<&ChunkData>, + terrain_material: Option>, terrain_atlas: Option>, atlas_layouts: Res>, - chunks_without_meshes: Query<(Entity, &ChunkData), (With, Without)>, block_lookup: Query<&BlockTexture, With>, ) { // This system will run before `TerrainMaterial` is available, so to avoid @@ -32,9 +39,25 @@ pub fn generate_chunk_mesh( 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() { + 'chunks: for (entity, pos, storage) in chunks_without_meshes.iter() { + let mut chunks = ChunkedBloxelView { + region: pos.to_block_region().expand(1).unwrap(), + map: HashMap::with_capacity(9), + }; + chunks.map.insert(*pos, storage); + + for pos in Neighbor::FACE.map(|n| pos + n) { + let Some(neighbor_chunk) = chunk_map.get(&pos) else { + continue 'chunks; + }; + let Some(neighbor_data) = chunks_with_data.get(*neighbor_chunk).ok() else { + continue 'chunks; + }; + chunks.map.insert(pos, neighbor_data); + } + let mesh = create_bloxel_mesh( - storage.deref(), + &chunks, terrain_atlas_layout, &terrain_atlas.sources, &block_lookup, @@ -46,3 +69,25 @@ pub fn generate_chunk_mesh( )); } } + +struct ChunkedBloxelView<'a> { + region: BlockRegion, + map: HashMap, +} + +impl ChunkedBloxelView<'_> { + fn get(&self, pos: BlockPos) -> Option { + let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos); + self.map.get(&chunk_pos).and_then(|d| d.get(rel_pos)) + } +} + +impl BloxelStore for ChunkedBloxelView<'_> { + fn size(&self) -> USize3 { + self.region.size() + } + + fn get(&self, pos: IVec3) -> Option { + ChunkedBloxelView::get(self, self.region.min() + pos) + } +} diff --git a/src/bloxel/mod.rs b/src/bloxel/mod.rs index cb76980..bb9da92 100644 --- a/src/bloxel/mod.rs +++ b/src/bloxel/mod.rs @@ -2,7 +2,6 @@ use bevy::prelude::*; pub mod block; pub mod chunk; -pub mod generic_math; pub mod math; pub mod mesh; pub mod storage; @@ -13,7 +12,7 @@ pub mod prelude { block::*, chunk::*, math::*, - storage::{BloxelView, BloxelViewMut}, + storage::{BloxelStore, BloxelStoreMut}, worldgen::WorldGenPlugin, BloxelPlugin, }; diff --git a/src/bloxel/storage/bloxel_array.rs b/src/bloxel/storage/bloxel_array.rs index e76700c..7484068 100644 --- a/src/bloxel/storage/bloxel_array.rs +++ b/src/bloxel/storage/bloxel_array.rs @@ -1,74 +1,56 @@ -use std::ops::{Index, IndexMut}; +use bevy::math::IVec3; +use ndarray::Array3; -use bevy::math::{IVec3, UVec3}; - -use super::bloxel_view::{ivec3_to_index, BloxelView, BloxelViewMut}; +use super::{BloxelStore, BloxelStoreMut}; +use crate::bloxel::math::USize3; pub struct BloxelArray { - size: UVec3, - data: Vec, + size: USize3, + data: Array3, } impl BloxelArray { - 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]; + pub fn new(size: USize3, fill: T) -> Self { + let data = Array3::from_elem(size, fill); Self { size, data } } - pub fn new_with_default(size: UVec3) -> Self + pub fn default(size: USize3) -> Self where T: Default, { - Self::new(size, Default::default()) - } - - fn get_index(&self, pos: impl Into) -> usize { - let pos = pos.into(); - assert!(self.contains(pos)); - ivec3_to_index(pos, self.size) + let data = Array3::default(size); + Self { size, data } } -} - -impl Index for BloxelArray { - type Output = T; - fn index(&self, pos: IVec3) -> &Self::Output { - let index = self.get_index(pos); - &self.data[index] + pub fn from_fn(size: USize3, f: impl Fn(IVec3) -> T) -> Self { + let f = |(x, y, z)| f(IVec3::new(x as i32, y as i32, z as i32)); + let data = Array3::from_shape_fn(size, f); + Self { size, data } } -} -impl IndexMut for BloxelArray { - fn index_mut(&mut self, pos: IVec3) -> &mut Self::Output { - let index = self.get_index(pos); - &mut self.data[index] + /// Helper function to construct a `[usize; 3]` to index into `data`, if valid, or `None` otherwise. + fn to_index(&self, pos: IVec3) -> Option<[usize; 3]> { + self.size + .contains(pos) + .then(|| [pos.x as usize, pos.y as usize, pos.z as usize]) } } -impl BloxelView for BloxelArray { - fn size(&self) -> UVec3 { +impl BloxelStore for BloxelArray { + fn size(&self) -> USize3 { self.size } - fn contains(&self, pos: impl Into) -> 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) -> T { - let index = self.get_index(pos); - self.data[index] + fn get(&self, pos: IVec3) -> Option { + self.to_index(pos).map(|index| self.data[index]) } } -impl BloxelViewMut for BloxelArray { - fn set(&mut self, pos: impl Into, value: T) { - let index = self.get_index(pos); - self.data[index] = value; +impl BloxelStoreMut for BloxelArray { + fn set(&mut self, pos: IVec3, value: T) -> Result { + self.to_index(pos) + .map(|index| std::mem::replace(&mut self.data[index], value)) + .ok_or(()) } } diff --git a/src/bloxel/storage/bloxel_store.rs b/src/bloxel/storage/bloxel_store.rs new file mode 100644 index 0000000..abe0a42 --- /dev/null +++ b/src/bloxel/storage/bloxel_store.rs @@ -0,0 +1,24 @@ +use bevy::math::{IVec3, UVec3}; +use cart_prod::specs::Hom3FCartProd; + +use crate::bloxel::math::USize3; + +pub trait BloxelStore { + fn size(&self) -> USize3; + + fn get(&self, pos: IVec3) -> Option; +} + +pub trait BloxelStoreMut: BloxelStore { + fn set(&mut self, pos: IVec3, value: T) -> Result; + + fn update(&mut self, f: impl Fn(IVec3, T) -> T) { + let (w, h, d) = self.size().into(); + for prod in Hom3FCartProd::new(0..w, 0..h, 0..d) { + let pos = UVec3::from(prod).as_ivec3(); + let old = self.get(pos).unwrap(); + let new = f(pos, old); + self.set(pos, new).unwrap(); + } + } +} diff --git a/src/bloxel/storage/bloxel_view.rs b/src/bloxel/storage/bloxel_view.rs deleted file mode 100644 index b0a8e71..0000000 --- a/src/bloxel/storage/bloxel_view.rs +++ /dev/null @@ -1,28 +0,0 @@ -use bevy::math::{IVec3, UVec3}; - -pub trait BloxelView { - fn size(&self) -> UVec3; - - fn contains(&self, pos: impl Into) -> bool; - - fn get(&self, pos: impl Into) -> T; -} - -pub trait BloxelViewMut: BloxelView { - fn set(&mut self, pos: impl Into, value: T); -} - -pub fn ivec3_to_index(pos: impl Into, 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) -} diff --git a/src/bloxel/storage/chunked_octree.rs b/src/bloxel/storage/chunked_octree.rs index a35e1fa..a4a6aad 100644 --- a/src/bloxel/storage/chunked_octree.rs +++ b/src/bloxel/storage/chunked_octree.rs @@ -244,7 +244,7 @@ impl TryInto for ChunkPos { impl TryInto for (i32, i32, i32) { type Error = (); fn try_into(self) -> Result { - Into::::into(self).try_into() + ChunkPos::from(self).try_into() } } diff --git a/src/bloxel/storage/mod.rs b/src/bloxel/storage/mod.rs index d561cc6..8ec49ea 100644 --- a/src/bloxel/storage/mod.rs +++ b/src/bloxel/storage/mod.rs @@ -1,10 +1,10 @@ mod bloxel_array; -mod bloxel_view; +mod bloxel_store; mod chunked_octree; mod palette_bloxel_storage; mod palette_storage; pub use bloxel_array::*; -pub use bloxel_view::*; +pub use bloxel_store::*; pub use chunked_octree::*; pub use palette_bloxel_storage::*; diff --git a/src/bloxel/storage/palette_bloxel_storage.rs b/src/bloxel/storage/palette_bloxel_storage.rs index 8ffc2e1..6db73da 100644 --- a/src/bloxel/storage/palette_bloxel_storage.rs +++ b/src/bloxel/storage/palette_bloxel_storage.rs @@ -1,24 +1,21 @@ -use bevy::math::{IVec3, UVec3}; +use bevy::math::IVec3; -use super::{ - bloxel_view::{ivec3_to_index, BloxelView, BloxelViewMut}, - palette_storage::PaletteStorage, -}; +use super::{palette_storage::PaletteStorage, BloxelStore, BloxelStoreMut}; +use crate::bloxel::math::USize3; pub struct PaletteBloxelStorage { - size: UVec3, + size: USize3, data: PaletteStorage, } impl PaletteBloxelStorage { - 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); + pub fn new(size: USize3, fill: T) -> Self { + let len = size.width.get() * size.height.get() * size.depth.get(); + let data = PaletteStorage::new(len as usize, fill); Self { size, data } } - pub fn new_with_default(size: UVec3) -> Self + pub fn new_with_default(size: USize3) -> Self where T: Default, { @@ -28,36 +25,25 @@ impl PaletteBloxelStorage { pub fn get_used_count(&self, value: T) -> usize { self.data.get_used_count(value) } - - fn get_index(&self, pos: impl Into) -> usize { - let pos = pos.into(); - assert!(self.contains(pos)); - ivec3_to_index(pos, self.size) - } } -impl BloxelView for PaletteBloxelStorage { - fn size(&self) -> UVec3 { +impl BloxelStore for PaletteBloxelStorage { + fn size(&self) -> USize3 { self.size } - fn contains(&self, pos: impl Into) -> 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) -> T { - let index = self.get_index(pos); - self.data.get(index) + fn get(&self, pos: IVec3) -> Option { + self.size + .ivec3_to_index(pos) + .map(|index| self.data.get(index)) } } -impl BloxelViewMut for PaletteBloxelStorage { - fn set(&mut self, pos: impl Into, value: T) { - let index = self.get_index(pos); - self.data.set(index, value); +impl BloxelStoreMut for PaletteBloxelStorage { + fn set(&mut self, pos: IVec3, value: T) -> Result { + self.size + .ivec3_to_index(pos) + .map(|index| self.data.set(index, value)) + .ok_or(()) } } diff --git a/src/bloxel/worldgen/mod.rs b/src/bloxel/worldgen/mod.rs index 79fd838..9e84fdb 100644 --- a/src/bloxel/worldgen/mod.rs +++ b/src/bloxel/worldgen/mod.rs @@ -113,27 +113,23 @@ fn generate_terrain( 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 float_pos = Into::::into(block_pos).as_vec3(); - - let bias = ((float_pos.y + 32.) / 64.).clamp(-0.25, 1.); - let sample = Simplex - .fbm(3, 0.65, 2.0) - .weighted(0.4) - .frequency(0.01) - .sample3(float_pos); - - if sample > bias { - data.set(relative, terrain.rock); - } - } + data.update(|relative, _| { + let block_pos = chunk_pos.to_block_pos(relative); + let float_pos = IVec3::from(block_pos).as_vec3(); + + let bias = ((float_pos.y + 32.) / 64.).clamp(-0.25, 1.); + let sample = Simplex + .fbm(3, 0.65, 2.0) + .weighted(0.4) + .frequency(0.01) + .sample3(float_pos); + + if sample > bias { + terrain.rock + } else { + terrain.air } - } + }); commands.entity(entity).insert(data); } }