better lighting

This commit is contained in:
asrael 2026-01-31 09:31:17 -06:00
parent 805a2713a3
commit 6ed4e17065
75 changed files with 6417 additions and 3667 deletions

View file

@ -1,21 +1,22 @@
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use libm::floorf;
use core::ptr;
use crate::math::Vec3;
use crate::pxl8::*;
pub const CHUNK_SIZE: usize = 32;
pub const CHUNK_VOLUME: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
const CHUNK_SIZE: i32 = PXL8_VXL_CHUNK_SIZE as i32;
const CHUNK_VOLUME: usize = PXL8_VXL_CHUNK_VOLUME as usize;
const WORLD_CHUNK_SIZE: f32 = PXL8_VXL_WORLD_CHUNK_SIZE as f32;
const AIR: u8 = PXL8_VXL_BLOCK_AIR as u8;
const GRASS: u8 = 3;
const DIRT: u8 = 2;
const STONE: u8 = 1;
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 chunk: *mut pxl8_vxl_chunk,
pub cx: i32,
pub cy: i32,
pub cz: i32,
@ -23,79 +24,57 @@ pub struct VoxelChunk {
impl VoxelChunk {
pub fn new(cx: i32, cy: i32, cz: i32) -> Self {
Self {
blocks: [AIR; CHUNK_VOLUME],
cx,
cy,
cz,
}
let chunk = unsafe { pxl8_vxl_chunk_create() };
Self { chunk, 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 {
pub fn get(&self, x: i32, y: i32, z: i32) -> u8 {
if self.chunk.is_null() {
return AIR;
}
self.blocks[Self::index(x, y, z)]
unsafe { pxl8_vxl_block_get(self.chunk, 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 set(&mut self, x: i32, y: i32, z: i32, block: u8) {
if self.chunk.is_null() {
return;
}
unsafe { pxl8_vxl_block_set(self.chunk, x, y, z, block) }
}
pub fn fill(&mut self, block: u8) {
if self.chunk.is_null() {
return;
}
unsafe { pxl8_vxl_block_fill(self.chunk, block) }
}
pub fn is_uniform(&self) -> bool {
if self.chunk.is_null() {
return true;
}
unsafe { pxl8_vxl_chunk_is_uniform(self.chunk) }
}
pub fn rle_encode(&self) -> Vec<u8> {
if self.chunk.is_null() {
return Vec::new();
}
let mut linear = vec![0u8; CHUNK_VOLUME];
unsafe {
pxl8_vxl_chunk_linearize(self.chunk, linear.as_mut_ptr());
}
let mut result = Vec::new();
let mut i = 0;
while i < CHUNK_VOLUME {
let block = self.blocks[i];
let block = linear[i];
let mut run_len = 1usize;
while i + run_len < CHUNK_VOLUME
&& self.blocks[i + run_len] == block
&& linear[i + run_len] == block
&& run_len < 256
{
run_len += 1;
@ -110,6 +89,34 @@ impl VoxelChunk {
}
}
impl Drop for VoxelChunk {
fn drop(&mut self) {
if !self.chunk.is_null() {
unsafe { pxl8_vxl_chunk_destroy(self.chunk) };
self.chunk = ptr::null_mut();
}
}
}
impl Clone for VoxelChunk {
fn clone(&self) -> Self {
let new_chunk = Self::new(self.cx, self.cy, self.cz);
for z in 0..CHUNK_SIZE {
for y in 0..CHUNK_SIZE {
for x in 0..CHUNK_SIZE {
let block = self.get(x, y, z);
if block != AIR {
unsafe {
pxl8_vxl_block_set(new_chunk.chunk, x, y, z, block);
}
}
}
}
}
new_chunk
}
}
pub struct VoxelWorld {
pub chunks: Vec<VoxelChunk>,
pub seed: u64,
@ -141,61 +148,7 @@ impl VoxelWorld {
}
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
libm::floorf(x / WORLD_CHUNK_SIZE) as i32
}
pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, i32)> {
@ -220,6 +173,37 @@ impl VoxelWorld {
self.ensure_chunk(cx, cy, cz);
}
}
pub fn find_surface_y(&mut self, world_x: f32, world_z: f32) -> f32 {
let scale = PXL8_VXL_SCALE as f32;
let block_x = libm::floorf(world_x / scale) as i32;
let block_z = libm::floorf(world_z / scale) as i32;
let cx = libm::floorf(block_x as f32 / CHUNK_SIZE as f32) as i32;
let cz = libm::floorf(block_z as f32 / CHUNK_SIZE as f32) as i32;
let lx = ((block_x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
let lz = ((block_z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
for cy in (-2..=2).rev() {
self.ensure_chunk(cx, cy, cz);
if let Some(chunk) = self.get_chunk(cx, cy, cz) {
for ly in (0..CHUNK_SIZE).rev() {
let block = chunk.get(lx, ly, lz);
if block == GRASS {
let world_y = (cy * CHUNK_SIZE + ly + 1) as f32 * scale;
return world_y;
}
}
}
}
0.0
}
}
fn noise3d(x: i32, y: i32, z: i32, seed: u64) -> f32 {
let h = hash(seed ^ (x as u64) ^ ((y as u64) << 21) ^ ((z as u64) << 42));
(h & 0xFFFF) as f32 / 65535.0
}
fn hash(mut x: u64) -> u64 {
@ -231,11 +215,6 @@ fn hash(mut x: u64) -> u64 {
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)
}
@ -244,33 +223,84 @@ 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;
fn value_noise_3d(x: f32, y: f32, z: f32, seed: u64) -> f32 {
let x0 = libm::floorf(x) as i32;
let y0 = libm::floorf(y) as i32;
let z0 = libm::floorf(z) as i32;
let x1 = x0 + 1;
let y1 = y0 + 1;
let z1 = z0 + 1;
let tx = smoothstep(x - x0 as f32);
let ty = smoothstep(y - y0 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 c000 = noise3d(x0, y0, z0, seed);
let c100 = noise3d(x1, y0, z0, seed);
let c010 = noise3d(x0, y1, z0, seed);
let c110 = noise3d(x1, y1, z0, seed);
let c001 = noise3d(x0, y0, z1, seed);
let c101 = noise3d(x1, y0, z1, seed);
let c011 = noise3d(x0, y1, z1, seed);
let c111 = noise3d(x1, y1, z1, seed);
let a = lerp(c00, c10, tx);
let b = lerp(c01, c11, tx);
lerp(a, b, tz)
let a00 = lerp(c000, c100, tx);
let a10 = lerp(c010, c110, tx);
let a01 = lerp(c001, c101, tx);
let a11 = lerp(c011, c111, tx);
let b0 = lerp(a00, a10, ty);
let b1 = lerp(a01, a11, ty);
lerp(b0, b1, tz)
}
fn fbm(x: f32, z: f32, seed: u64, octaves: u32) -> f32 {
fn fbm_3d(x: f32, y: 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));
value += amplitude * value_noise_3d(
x * frequency,
y * frequency,
z * frequency,
seed.wrapping_add(i as u64 * 1000)
);
max_value += amplitude;
amplitude *= 0.5;
frequency *= 2.0;
}
value / max_value
}
fn fbm_2d(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 {
let x0 = libm::floorf(x * frequency) as i32;
let z0 = libm::floorf(z * frequency) as i32;
let x1 = x0 + 1;
let z1 = z0 + 1;
let tx = smoothstep(x * frequency - x0 as f32);
let tz = smoothstep(z * frequency - z0 as f32);
let offset_seed = seed.wrapping_add(i as u64 * 1000);
let c00 = (hash(offset_seed ^ (x0 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
let c10 = (hash(offset_seed ^ (x1 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
let c01 = (hash(offset_seed ^ (x0 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
let c11 = (hash(offset_seed ^ (x1 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
let a = lerp(c00, c10, tx);
let b = lerp(c01, c11, tx);
value += amplitude * lerp(a, b, tz);
max_value += amplitude;
amplitude *= 0.5;
frequency *= 2.0;
@ -280,29 +310,60 @@ fn fbm(x: f32, z: f32, seed: u64, octaves: u32) -> f32 {
}
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;
let world_x = chunk.cx * CHUNK_SIZE;
let world_y = chunk.cy * CHUNK_SIZE;
let world_z = chunk.cz * CHUNK_SIZE;
for lz in 0..CHUNK_SIZE {
for lx in 0..CHUNK_SIZE {
let mut height_cache = [[0i32; 32]; 32];
for lz in 0..32 {
for lx in 0..32 {
let wx = (world_x + lx as i32) as f32;
let wz = (world_z + lz as i32) as f32;
height_cache[lz][lx] = (fbm_2d(wx * 0.02, wz * 0.02, seed, 4) * 32.0) as i32;
}
}
let height = fbm(wx * 0.02, wz * 0.02, seed, 4) * 32.0 + 16.0;
let height = height as i32;
let mut density = [[[0.0f32; 33]; 33]; 33];
for lz in 0..33 {
for ly in 0..33 {
for lx in 0..33 {
let wx = (world_x + lx as i32) as f32;
let wy = (world_y + ly as i32) as f32;
let wz = (world_z + lz as i32) as f32;
for ly in 0..CHUNK_SIZE {
let wy = world_y + ly as i32;
let hx = lx.min(31);
let hz = lz.min(31);
let base_height = height_cache[hz][hx] as f32;
let height_bias = (base_height - wy) * 0.1;
let block = if wy > height {
AIR
} else if wy == height {
let cave_noise = fbm_3d(wx * 0.05, wy * 0.05, wz * 0.05, seed.wrapping_add(1000), 2);
density[lz][ly][lx] = height_bias + (cave_noise - 0.55);
}
}
}
for lz in 0..CHUNK_SIZE {
for ly in 0..CHUNK_SIZE {
for lx in 0..CHUNK_SIZE {
let d = density[lz as usize][ly as usize][lx as usize];
if d <= 0.0 {
continue;
}
let d_above = density[lz as usize][(ly + 1) as usize][lx as usize];
let is_surface = d_above <= 0.0;
let block = if is_surface {
GRASS
} else if wy > height - 4 {
DIRT
} else {
STONE
let wy = world_y + ly;
let base_height = height_cache[lz as usize][lx as usize];
let depth = base_height - wy;
if depth < 4 {
DIRT
} else {
STONE
}
};
chunk.set(lx, ly, lz, block);