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
main
copygirl 4 days ago
parent 877f72a1e0
commit 8ce5513e77
  1. 57
      Cargo.lock
  2. 2
      Cargo.toml
  3. 8
      src/bloxel/chunk.rs
  4. 5
      src/bloxel/generic_math/mod.rs
  5. 213
      src/bloxel/generic_math/pos.rs
  6. 184
      src/bloxel/generic_math/region.rs
  7. 14
      src/bloxel/math/block_pos.rs
  8. 47
      src/bloxel/math/block_region.rs
  9. 14
      src/bloxel/math/chunk_pos.rs
  10. 21
      src/bloxel/math/chunk_region.rs
  11. 4
      src/bloxel/math/mod.rs
  12. 195
      src/bloxel/math/neighbor.rs
  13. 80
      src/bloxel/math/size.rs
  14. 6
      src/bloxel/math/z_order_index.rs
  15. 48
      src/bloxel/mesh/mesh_generator.rs
  16. 57
      src/bloxel/mesh/mod.rs
  17. 3
      src/bloxel/mod.rs
  18. 76
      src/bloxel/storage/bloxel_array.rs
  19. 24
      src/bloxel/storage/bloxel_store.rs
  20. 28
      src/bloxel/storage/bloxel_view.rs
  21. 2
      src/bloxel/storage/chunked_octree.rs
  22. 4
      src/bloxel/storage/mod.rs
  23. 54
      src/bloxel/storage/palette_bloxel_storage.rs
  24. 16
      src/bloxel/worldgen/mod.rs

57
Cargo.lock generated

@ -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"

@ -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"

@ -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<Entity>);
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))
}
}

@ -1,5 +0,0 @@
mod pos;
mod region;
pub use pos::Pos;
pub use region::Region;

@ -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<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;
type Mutability = Immutable;
}
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"),
}
}
}

@ -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<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;
type Mutability = Immutable;
}
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)
}
}
// Conversion
impl<T> From<Region<T>> for (Pos<T>, Pos<T>) {
fn from(Region { min, max, .. }: Region<T>) -> Self {
(min.into(), max.into())
}
}
// 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;
}
}

@ -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

@ -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, ()> {
Self::new_checked(
self.min - IVec3::splat(amount),
self.max + IVec3::splat(amount),
)
}
pub fn iter(&self) -> impl Iterator<Item = BlockPos> {
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<Item = (ChunkPos, BlockRegion)> {}
}
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

@ -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

@ -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, ()> {
Self::new_checked(
self.min - IVec3::splat(amount),

@ -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::*;

@ -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<Neighbor> 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<IVec3> for Neighbor {
type Error = ();
#[rustfmt::skip]
fn try_from(v: IVec3) -> Result<Neighbor, Self::Error> {
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<Neighbor> 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)) });

@ -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<usize> {
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<USize3> for (NonZeroU32, NonZeroU32, NonZeroU32) {
fn from(size: USize3) -> Self {
(size.width, size.depth, size.height)
}
}
impl From<USize3> for (u32, u32, u32) {
fn from(size: USize3) -> Self {
(size.width.get(), size.depth.get(), size.height.get())
}
}
impl TryFrom<UVec3> for USize3 {
type Error = TryFromIntError;
fn try_from(UVec3 { x, y, z }: UVec3) -> Result<Self, Self::Error> {
Ok(Self::new(x.try_into()?, y.try_into()?, z.try_into()?))
}
}
impl From<USize3> 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)
}
}

@ -130,7 +130,7 @@ impl TryFrom<[i32; 3]> for ZOrderIndex {
}
impl From<ZOrderIndex> 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<IVec3> for ZOrderIndex {
}
impl From<ZOrderIndex> 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<ChunkPos> for ZOrderIndex {
}
impl From<ZOrderIndex> for ChunkPos {
fn from(value: ZOrderIndex) -> Self {
Into::<(i32, i32, i32)>::into(value).into()
<(i32, i32, i32)>::from(value).into()
}
}

@ -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<Entity>,
bloxels: &impl BloxelStore<Entity>,
layout: &TextureAtlasLayout,
sources: &TextureAtlasSources,
block_lookup: &Query<&BlockTexture, With<Block>>,
) -> 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<Vec3> = vec![];
let mut normals: Vec<Vec3> = vec![];
@ -65,44 +70,27 @@ 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 {
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(texture) = textures[pos + IVec3::ONE] {
if let Some(Some(texture)) = textures.get(pos) {
if let Some(uvs) = uv_rect(sources, layout, texture) {
append_cube(
pos,
pos - IVec3::ONE,
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(),
],
Neighbor::FACE.map(|n| textures.get(pos + n).flatten().is_none()),
);
}
}
}
}
}
Mesh::new(
PrimitiveTopology::TriangleList,

@ -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<Assets<Mesh>>,
// 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<Chunk>, Without<Mesh3d>)>,
// 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<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
@ -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<ChunkPos, &'a ChunkData>,
}
impl ChunkedBloxelView<'_> {
fn get(&self, pos: BlockPos) -> Option<Entity> {
let (chunk_pos, rel_pos) = ChunkPos::from_block_pos(pos);
self.map.get(&chunk_pos).and_then(|d| d.get(rel_pos))
}
}
impl BloxelStore<Entity> for ChunkedBloxelView<'_> {
fn size(&self) -> USize3 {
self.region.size()
}
fn get(&self, pos: IVec3) -> Option<Entity> {
ChunkedBloxelView::get(self, self.region.min() + pos)
}
}

@ -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,
};

@ -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<T: Copy> {
size: UVec3,
data: Vec<T>,
size: USize3,
data: Array3<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];
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<IVec3>) -> usize {
let pos = pos.into();
assert!(self.contains(pos));
ivec3_to_index(pos, self.size)
let data = Array3::default(size);
Self { size, data }
}
}
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]
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<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]
/// 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<T: Copy> BloxelView<T> for BloxelArray<T> {
fn size(&self) -> UVec3 {
impl<T: Copy> BloxelStore<T> for BloxelArray<T> {
fn size(&self) -> USize3 {
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]
fn get(&self, pos: IVec3) -> Option<T> {
self.to_index(pos).map(|index| 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;
impl<T: Copy> BloxelStoreMut<T> for BloxelArray<T> {
fn set(&mut self, pos: IVec3, value: T) -> Result<T, ()> {
self.to_index(pos)
.map(|index| std::mem::replace(&mut self.data[index], value))
.ok_or(())
}
}

@ -0,0 +1,24 @@
use bevy::math::{IVec3, UVec3};
use cart_prod::specs::Hom3FCartProd;
use crate::bloxel::math::USize3;
pub trait BloxelStore<T: Copy> {
fn size(&self) -> USize3;
fn get(&self, pos: IVec3) -> Option<T>;
}
pub trait BloxelStoreMut<T: Copy>: BloxelStore<T> {
fn set(&mut self, pos: IVec3, value: T) -> Result<T, ()>;
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();
}
}
}

@ -1,28 +0,0 @@
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)
}

@ -244,7 +244,7 @@ impl TryInto<OctreeNode> for ChunkPos {
impl TryInto<OctreeNode> for (i32, i32, i32) {
type Error = ();
fn try_into(self) -> Result<OctreeNode, ()> {
Into::<ChunkPos>::into(self).try_into()
ChunkPos::from(self).try_into()
}
}

@ -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::*;

@ -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<T: Copy + Eq> {
size: UVec3,
size: USize3,
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);
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<T: Copy + Eq> PaletteBloxelStorage<T> {
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 {
impl<T: Copy + Eq> BloxelStore<T> for PaletteBloxelStorage<T> {
fn size(&self) -> USize3 {
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)
fn get(&self, pos: IVec3) -> Option<T> {
self.size
.ivec3_to_index(pos)
.map(|index| 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);
impl<T: Copy + Eq> BloxelStoreMut<T> for PaletteBloxelStorage<T> {
fn set(&mut self, pos: IVec3, value: T) -> Result<T, ()> {
self.size
.ivec3_to_index(pos)
.map(|index| self.data.set(index, value))
.ok_or(())
}
}

@ -113,13 +113,9 @@ 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);
data.update(|relative, _| {
let block_pos = chunk_pos.to_block_pos(relative);
let float_pos = Into::<IVec3>::into(block_pos).as_vec3();
let float_pos = IVec3::from(block_pos).as_vec3();
let bias = ((float_pos.y + 32.) / 64.).clamp(-0.25, 1.);
let sample = Simplex
@ -129,11 +125,11 @@ fn generate_terrain(
.sample3(float_pos);
if sample > bias {
data.set(relative, terrain.rock);
}
}
}
terrain.rock
} else {
terrain.air
}
});
commands.entity(entity).insert(data);
}
}

Loading…
Cancel
Save