stream world data from pxl8d to pxl8
This commit is contained in:
parent
39ee0fefb7
commit
a71a9840b2
55 changed files with 5290 additions and 2131 deletions
318
pxl8d/src/voxel.rs
Normal file
318
pxl8d/src/voxel.rs
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use libm::floorf;
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
pub const CHUNK_SIZE: usize = 32;
|
||||
pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
|
||||
|
||||
pub const AIR: u8 = 0;
|
||||
pub const STONE: u8 = 1;
|
||||
pub const DIRT: u8 = 2;
|
||||
pub const GRASS: u8 = 3;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct VoxelChunk {
|
||||
pub blocks: [u8; CHUNK_VOLUME],
|
||||
pub cx: i32,
|
||||
pub cy: i32,
|
||||
pub cz: i32,
|
||||
}
|
||||
|
||||
impl VoxelChunk {
|
||||
pub fn new(cx: i32, cy: i32, cz: i32) -> Self {
|
||||
Self {
|
||||
blocks: [AIR; CHUNK_VOLUME],
|
||||
cx,
|
||||
cy,
|
||||
cz,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
if !self.is_solid_radius(to.x, to.y, to.z, radius) {
|
||||
return to;
|
||||
}
|
||||
|
||||
let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius);
|
||||
let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius);
|
||||
let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius);
|
||||
|
||||
let mut result = from;
|
||||
if x_ok { result.x = to.x; }
|
||||
if y_ok { result.y = to.y; }
|
||||
if z_ok { result.z = to.z; }
|
||||
result
|
||||
}
|
||||
|
||||
fn world_to_local(x: f32, chunk_coord: i32) -> usize {
|
||||
let chunk_base = chunk_coord as f32 * CHUNK_SIZE as f32;
|
||||
let local = (x - chunk_base) as usize;
|
||||
local.min(CHUNK_SIZE - 1)
|
||||
}
|
||||
|
||||
fn is_solid_at(&self, x: f32, y: f32, z: f32) -> bool {
|
||||
let lx = Self::world_to_local(x, self.cx);
|
||||
let ly = Self::world_to_local(y, self.cy);
|
||||
let lz = Self::world_to_local(z, self.cz);
|
||||
self.get(lx, ly, lz) != AIR
|
||||
}
|
||||
|
||||
fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool {
|
||||
self.is_solid_at(x - radius, y, z) ||
|
||||
self.is_solid_at(x + radius, y, z) ||
|
||||
self.is_solid_at(x, y - radius, z) ||
|
||||
self.is_solid_at(x, y + radius, z) ||
|
||||
self.is_solid_at(x, y, z - radius) ||
|
||||
self.is_solid_at(x, y, z + radius)
|
||||
}
|
||||
|
||||
pub fn index(x: usize, y: usize, z: usize) -> usize {
|
||||
x + y * CHUNK_SIZE + z * CHUNK_SIZE * CHUNK_SIZE
|
||||
}
|
||||
|
||||
pub fn get(&self, x: usize, y: usize, z: usize) -> u8 {
|
||||
if x >= CHUNK_SIZE || y >= CHUNK_SIZE || z >= CHUNK_SIZE {
|
||||
return AIR;
|
||||
}
|
||||
self.blocks[Self::index(x, y, z)]
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: usize, y: usize, z: usize, block: u8) {
|
||||
if x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE {
|
||||
self.blocks[Self::index(x, y, z)] = block;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rle_encode(&self) -> Vec<u8> {
|
||||
let mut result = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < CHUNK_VOLUME {
|
||||
let block = self.blocks[i];
|
||||
let mut run_len = 1usize;
|
||||
|
||||
while i + run_len < CHUNK_VOLUME
|
||||
&& self.blocks[i + run_len] == block
|
||||
&& run_len < 256
|
||||
{
|
||||
run_len += 1;
|
||||
}
|
||||
|
||||
result.push(block);
|
||||
result.push((run_len - 1) as u8);
|
||||
i += run_len;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VoxelWorld {
|
||||
pub chunks: Vec<VoxelChunk>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
impl VoxelWorld {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self {
|
||||
chunks: Vec::new(),
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_chunk(&self, cx: i32, cy: i32, cz: i32) -> Option<&VoxelChunk> {
|
||||
self.chunks.iter().find(|c| c.cx == cx && c.cy == cy && c.cz == cz)
|
||||
}
|
||||
|
||||
pub fn get_chunk_mut(&mut self, cx: i32, cy: i32, cz: i32) -> Option<&mut VoxelChunk> {
|
||||
self.chunks.iter_mut().find(|c| c.cx == cx && c.cy == cy && c.cz == cz)
|
||||
}
|
||||
|
||||
pub fn ensure_chunk(&mut self, cx: i32, cy: i32, cz: i32) -> &mut VoxelChunk {
|
||||
if self.get_chunk(cx, cy, cz).is_none() {
|
||||
let mut chunk = VoxelChunk::new(cx, cy, cz);
|
||||
generate_chunk(&mut chunk, self.seed);
|
||||
self.chunks.push(chunk);
|
||||
}
|
||||
self.get_chunk_mut(cx, cy, cz).unwrap()
|
||||
}
|
||||
|
||||
pub fn world_to_chunk(x: f32) -> i32 {
|
||||
floorf(x / CHUNK_SIZE as f32) as i32
|
||||
}
|
||||
|
||||
pub fn world_to_local(x: f32) -> usize {
|
||||
let chunk = floorf(x / CHUNK_SIZE as f32);
|
||||
let local = (x - chunk * CHUNK_SIZE as f32) as usize;
|
||||
local.min(CHUNK_SIZE - 1)
|
||||
}
|
||||
|
||||
pub fn is_solid(&self, x: f32, y: f32, z: f32) -> bool {
|
||||
let cx = Self::world_to_chunk(x);
|
||||
let cy = Self::world_to_chunk(y);
|
||||
let cz = Self::world_to_chunk(z);
|
||||
|
||||
let lx = Self::world_to_local(x);
|
||||
let ly = Self::world_to_local(y);
|
||||
let lz = Self::world_to_local(z);
|
||||
|
||||
match self.get_chunk(cx, cy, cz) {
|
||||
Some(chunk) => chunk.get(lx, ly, lz) != AIR,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_solid_radius(&self, x: f32, y: f32, z: f32, radius: f32) -> bool {
|
||||
self.is_solid(x - radius, y, z) ||
|
||||
self.is_solid(x + radius, y, z) ||
|
||||
self.is_solid(x, y - radius, z) ||
|
||||
self.is_solid(x, y + radius, z) ||
|
||||
self.is_solid(x, y, z - radius) ||
|
||||
self.is_solid(x, y, z + radius)
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
if !self.is_solid_radius(to.x, to.y, to.z, radius) {
|
||||
return to;
|
||||
}
|
||||
|
||||
let x_ok = !self.is_solid_radius(to.x, from.y, from.z, radius);
|
||||
let y_ok = !self.is_solid_radius(from.x, to.y, from.z, radius);
|
||||
let z_ok = !self.is_solid_radius(from.x, from.y, to.z, radius);
|
||||
|
||||
let mut result = from;
|
||||
|
||||
if x_ok {
|
||||
result.x = to.x;
|
||||
}
|
||||
if y_ok {
|
||||
result.y = to.y;
|
||||
}
|
||||
if z_ok {
|
||||
result.z = to.z;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, i32)> {
|
||||
let cx = Self::world_to_chunk(pos.x);
|
||||
let cy = Self::world_to_chunk(pos.y);
|
||||
let cz = Self::world_to_chunk(pos.z);
|
||||
|
||||
let mut result = Vec::new();
|
||||
for dz in -radius..=radius {
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
result.push((cx + dx, cy + dy, cz + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn load_chunks_around(&mut self, pos: Vec3, radius: i32) {
|
||||
let coords = self.chunks_near(pos, radius);
|
||||
for (cx, cy, cz) in coords {
|
||||
self.ensure_chunk(cx, cy, cz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(mut x: u64) -> u64 {
|
||||
x ^= x >> 33;
|
||||
x = x.wrapping_mul(0xff51afd7ed558ccd);
|
||||
x ^= x >> 33;
|
||||
x = x.wrapping_mul(0xc4ceb9fe1a85ec53);
|
||||
x ^= x >> 33;
|
||||
x
|
||||
}
|
||||
|
||||
fn noise2d(x: i32, z: i32, seed: u64) -> f32 {
|
||||
let h = hash(seed ^ (x as u64) ^ ((z as u64) << 32));
|
||||
(h & 0xFFFF) as f32 / 65535.0
|
||||
}
|
||||
|
||||
fn smoothstep(t: f32) -> f32 {
|
||||
t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a + (b - a) * t
|
||||
}
|
||||
|
||||
fn value_noise(x: f32, z: f32, seed: u64) -> f32 {
|
||||
let x0 = floorf(x) as i32;
|
||||
let z0 = floorf(z) as i32;
|
||||
let x1 = x0 + 1;
|
||||
let z1 = z0 + 1;
|
||||
|
||||
let tx = smoothstep(x - x0 as f32);
|
||||
let tz = smoothstep(z - z0 as f32);
|
||||
|
||||
let c00 = noise2d(x0, z0, seed);
|
||||
let c10 = noise2d(x1, z0, seed);
|
||||
let c01 = noise2d(x0, z1, seed);
|
||||
let c11 = noise2d(x1, z1, seed);
|
||||
|
||||
let a = lerp(c00, c10, tx);
|
||||
let b = lerp(c01, c11, tx);
|
||||
lerp(a, b, tz)
|
||||
}
|
||||
|
||||
fn fbm(x: f32, z: f32, seed: u64, octaves: u32) -> f32 {
|
||||
let mut value = 0.0;
|
||||
let mut amplitude = 1.0;
|
||||
let mut frequency = 1.0;
|
||||
let mut max_value = 0.0;
|
||||
|
||||
for i in 0..octaves {
|
||||
value += amplitude * value_noise(x * frequency, z * frequency, seed.wrapping_add(i as u64 * 1000));
|
||||
max_value += amplitude;
|
||||
amplitude *= 0.5;
|
||||
frequency *= 2.0;
|
||||
}
|
||||
|
||||
value / max_value
|
||||
}
|
||||
|
||||
fn generate_chunk(chunk: &mut VoxelChunk, seed: u64) {
|
||||
let world_x = chunk.cx * CHUNK_SIZE as i32;
|
||||
let world_y = chunk.cy * CHUNK_SIZE as i32;
|
||||
let world_z = chunk.cz * CHUNK_SIZE as i32;
|
||||
|
||||
for lz in 0..CHUNK_SIZE {
|
||||
for lx in 0..CHUNK_SIZE {
|
||||
let wx = (world_x + lx as i32) as f32;
|
||||
let wz = (world_z + lz as i32) as f32;
|
||||
|
||||
let height = fbm(wx * 0.02, wz * 0.02, seed, 4) * 32.0 + 16.0;
|
||||
let height = height as i32;
|
||||
|
||||
for ly in 0..CHUNK_SIZE {
|
||||
let wy = world_y + ly as i32;
|
||||
|
||||
let block = if wy > height {
|
||||
AIR
|
||||
} else if wy == height {
|
||||
GRASS
|
||||
} else if wy > height - 4 {
|
||||
DIRT
|
||||
} else {
|
||||
STONE
|
||||
};
|
||||
|
||||
chunk.set(lx, ly, lz, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VoxelWorld {
|
||||
fn default() -> Self {
|
||||
Self::new(12345)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue