use crate::error::MeshError;
pub const CHUNK_SIZE: usize = 32;
pub const MAX_CHUNK_SIZE: usize = 64;
const NUM_FACES: usize = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Face {
PosX = 0, NegX = 1, PosY = 2, NegY = 3, PosZ = 4, NegZ = 5, }
impl Face {
pub const ALL: [Face; NUM_FACES] = [
Face::PosX,
Face::NegX,
Face::PosY,
Face::NegY,
Face::PosZ,
Face::NegZ,
];
#[inline]
pub fn normal(self) -> [i32; 3] {
match self {
Face::PosX => [1, 0, 0],
Face::NegX => [-1, 0, 0],
Face::PosY => [0, 1, 0],
Face::NegY => [0, -1, 0],
Face::PosZ => [0, 0, 1],
Face::NegZ => [0, 0, -1],
}
}
#[inline]
pub fn normal_f32(self) -> [f32; 3] {
let n = self.normal();
[n[0] as f32, n[1] as f32, n[2] as f32]
}
#[inline]
pub fn normal_axis(self) -> usize {
match self {
Face::PosX | Face::NegX => 0,
Face::PosY | Face::NegY => 1,
Face::PosZ | Face::NegZ => 2,
}
}
#[inline]
pub fn tangent_axes(self) -> (usize, usize) {
match self {
Face::PosX => (1, 2), Face::NegX => (2, 1), Face::PosY => (2, 0), Face::NegY => (0, 2), Face::PosZ => (0, 1), Face::NegZ => (1, 0), }
}
#[inline]
pub fn is_positive(self) -> bool {
matches!(self, Face::PosX | Face::PosY | Face::PosZ)
}
#[inline]
pub fn opposite(self) -> Face {
match self {
Face::PosX => Face::NegX,
Face::NegX => Face::PosX,
Face::PosY => Face::NegY,
Face::NegY => Face::PosY,
Face::PosZ => Face::NegZ,
Face::NegZ => Face::PosZ,
}
}
}
pub struct Chunk {
blocks: Vec<u16>,
size: usize,
}
impl Chunk {
pub fn new(size: usize) -> Result<Self, MeshError> {
if size > MAX_CHUNK_SIZE {
return Err(MeshError::ChunkTooLarge(size, MAX_CHUNK_SIZE));
}
Ok(Self {
blocks: vec![0u16; size * size * size],
size,
})
}
pub fn new_default() -> Self {
Self {
blocks: vec![0u16; CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE],
size: CHUNK_SIZE,
}
}
#[inline]
pub fn size(&self) -> usize {
self.size
}
#[inline]
fn index(&self, x: usize, y: usize, z: usize) -> usize {
x + y * self.size + z * self.size * self.size
}
#[inline]
pub fn get(&self, x: usize, y: usize, z: usize) -> u16 {
debug_assert!(x < self.size && y < self.size && z < self.size);
self.blocks[self.index(x, y, z)]
}
#[inline]
pub fn set(&mut self, x: usize, y: usize, z: usize, block_id: u16) {
debug_assert!(x < self.size && y < self.size && z < self.size);
let idx = self.index(x, y, z);
self.blocks[idx] = block_id;
}
pub fn set_blocks(&mut self, edits: &[u32]) -> u32 {
let s = self.size;
let mut count = 0u32;
let mut i = 0;
while i + 3 < edits.len() {
let x = edits[i] as usize;
let y = edits[i + 1] as usize;
let z = edits[i + 2] as usize;
let block_id = edits[i + 3] as u16;
i += 4;
if x < s && y < s && z < s {
let idx = self.index(x, y, z);
self.blocks[idx] = block_id;
count += 1;
}
}
count
}
#[inline]
pub fn is_air(&self, x: usize, y: usize, z: usize) -> bool {
self.get(x, y, z) == 0
}
#[inline]
pub fn blocks(&self) -> &[u16] {
&self.blocks
}
pub fn extract_border(&self, face: Face) -> Vec<u16> {
let s = self.size;
let mut border = vec![0u16; s * s];
match face {
Face::PosX => {
for y in 0..s {
for z in 0..s {
border[z + y * s] = self.get(s - 1, y, z);
}
}
}
Face::NegX => {
for y in 0..s {
for z in 0..s {
border[z + y * s] = self.get(0, y, z);
}
}
}
Face::PosY => {
for z in 0..s {
for x in 0..s {
border[x + z * s] = self.get(x, s - 1, z);
}
}
}
Face::NegY => {
for z in 0..s {
for x in 0..s {
border[x + z * s] = self.get(x, 0, z);
}
}
}
Face::PosZ => {
for y in 0..s {
for x in 0..s {
border[x + y * s] = self.get(x, y, s - 1);
}
}
}
Face::NegZ => {
for y in 0..s {
for x in 0..s {
border[x + y * s] = self.get(x, y, 0);
}
}
}
}
border
}
}
pub struct ChunkNeighbors {
faces: [Option<Vec<u16>>; NUM_FACES],
size: usize,
}
impl ChunkNeighbors {
pub fn empty(size: usize) -> Self {
Self {
faces: [const { None }; NUM_FACES],
size,
}
}
pub fn set_face(&mut self, face: Face, data: Vec<u16>) {
self.faces[face as usize] = Some(data);
}
#[inline]
pub fn get_border_block(&self, face: Face, u: usize, v: usize) -> u16 {
match &self.faces[face as usize] {
Some(data) => data[u + v * self.size],
None => 0,
}
}
#[inline]
pub fn has_face(&self, face: Face) -> bool {
self.faces[face as usize].is_some()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunk_new_default() {
let chunk = Chunk::new_default();
assert_eq!(chunk.size(), CHUNK_SIZE);
assert!(chunk.is_air(0, 0, 0));
assert!(chunk.is_air(31, 31, 31));
}
#[test]
fn chunk_set_get() {
let mut chunk = Chunk::new_default();
chunk.set(5, 10, 15, 42);
assert_eq!(chunk.get(5, 10, 15), 42);
assert!(!chunk.is_air(5, 10, 15));
assert!(chunk.is_air(0, 0, 0));
}
#[test]
fn set_blocks_batch() {
let mut chunk = Chunk::new_default();
let edits: Vec<u32> = vec![
0, 0, 0, 1,
5, 10, 15, 42,
31, 31, 31, 7,
32, 0, 0, 99,
];
let written = chunk.set_blocks(&edits);
assert_eq!(written, 3);
assert_eq!(chunk.get(0, 0, 0), 1);
assert_eq!(chunk.get(5, 10, 15), 42);
assert_eq!(chunk.get(31, 31, 31), 7);
}
#[test]
fn chunk_too_large() {
let result = Chunk::new(65);
assert!(result.is_err());
}
#[test]
fn chunk_custom_size() {
let chunk = Chunk::new(16).unwrap();
assert_eq!(chunk.size(), 16);
}
#[test]
fn face_normals() {
assert_eq!(Face::PosX.normal(), [1, 0, 0]);
assert_eq!(Face::NegY.normal(), [0, -1, 0]);
assert_eq!(Face::PosZ.normal(), [0, 0, 1]);
}
#[test]
fn face_axes() {
assert_eq!(Face::PosX.normal_axis(), 0);
assert_eq!(Face::PosY.normal_axis(), 1);
assert_eq!(Face::PosZ.normal_axis(), 2);
}
#[test]
fn chunk_neighbors_empty() {
let neighbors = ChunkNeighbors::empty(32);
assert_eq!(neighbors.get_border_block(Face::PosX, 0, 0), 0);
assert!(!neighbors.has_face(Face::PosX));
}
#[test]
fn chunk_neighbors_with_data() {
let mut neighbors = ChunkNeighbors::empty(32);
let mut data = vec![0u16; 32 * 32];
data[5 + 10 * 32] = 7;
neighbors.set_face(Face::PosX, data);
assert!(neighbors.has_face(Face::PosX));
assert_eq!(neighbors.get_border_block(Face::PosX, 5, 10), 7);
assert_eq!(neighbors.get_border_block(Face::PosX, 0, 0), 0);
}
}