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

@ -5,21 +5,51 @@ fn main() {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let pxl8_src = PathBuf::from(&manifest_dir).join("../src");
println!("cargo:rerun-if-changed=../src/bsp/pxl8_bsp.h");
println!("cargo:rerun-if-changed=../src/core/pxl8_log.c");
println!("cargo:rerun-if-changed=../src/core/pxl8_log.h");
println!("cargo:rerun-if-changed=../src/core/pxl8_types.h");
println!("cargo:rerun-if-changed=../src/math/pxl8_math.c");
println!("cargo:rerun-if-changed=../src/math/pxl8_math.h");
println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.c");
println!("cargo:rerun-if-changed=../src/net/pxl8_protocol.h");
println!("cargo:rerun-if-changed=../src/sim/pxl8_sim.c");
println!("cargo:rerun-if-changed=../src/sim/pxl8_sim.h");
println!("cargo:rerun-if-changed=../src/vxl/pxl8_vxl.c");
println!("cargo:rerun-if-changed=../src/vxl/pxl8_vxl.h");
cc::Build::new()
.file(pxl8_src.join("core/pxl8_log.c"))
.file(pxl8_src.join("hal/pxl8_mem.c"))
.file(pxl8_src.join("math/pxl8_math.c"))
.file(pxl8_src.join("math/pxl8_noise.c"))
.file(pxl8_src.join("sim/pxl8_sim.c"))
.file(pxl8_src.join("vxl/pxl8_vxl.c"))
.include(pxl8_src.join("bsp"))
.include(pxl8_src.join("core"))
.define("PXL8_SERVER", None)
.compile("pxl8_log");
.include(pxl8_src.join("hal"))
.include(pxl8_src.join("math"))
.include(pxl8_src.join("net"))
.include(pxl8_src.join("sim"))
.include(pxl8_src.join("vxl"))
.compile("pxl8");
let bindings = bindgen::Builder::default()
.header(pxl8_src.join("core/pxl8_log.h").to_str().unwrap())
.header(pxl8_src.join("net/pxl8_protocol.h").to_str().unwrap())
.header(pxl8_src.join("sim/pxl8_sim.h").to_str().unwrap())
.header(pxl8_src.join("vxl/pxl8_vxl.h").to_str().unwrap())
.header(pxl8_src.join("math/pxl8_noise.h").to_str().unwrap())
.clang_arg(format!("-I{}", pxl8_src.join("bsp").display()))
.clang_arg(format!("-I{}", pxl8_src.join("core").display()))
.clang_arg(format!("-I{}", pxl8_src.join("math").display()))
.clang_arg(format!("-I{}", pxl8_src.join("net").display()))
.clang_arg(format!("-I{}", pxl8_src.join("sim").display()))
.clang_arg(format!("-I{}", pxl8_src.join("vxl").display()))
.blocklist_item("FP_NAN")
.blocklist_item("FP_INFINITE")
.blocklist_item("FP_ZERO")
.blocklist_item("FP_SUBNORMAL")
.blocklist_item("FP_NORMAL")
.use_core()
.rustified_enum(".*")
.generate()
@ -27,6 +57,6 @@ fn main() {
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("protocol.rs"))
.write_to_file(out_path.join("pxl8.rs"))
.expect("Couldn't write bindings");
}

View file

@ -1,173 +1,216 @@
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec;
use crate::math::Vec3;
use crate::math::{Vec3, VEC3_ZERO};
use crate::pxl8::*;
#[derive(Clone, Copy, Default)]
pub struct BspVertex {
pub position: Vec3,
pub type Vertex = pxl8_bsp_vertex;
pub type Edge = pxl8_bsp_edge;
pub type Face = pxl8_bsp_face;
pub type Plane = pxl8_bsp_plane;
pub type Node = pxl8_bsp_node;
pub type Leaf = pxl8_bsp_leaf;
pub type Portal = pxl8_bsp_portal;
pub type CellPortals = pxl8_bsp_cell_portals;
impl Default for Edge {
fn default() -> Self {
Self { vertex: [0, 0] }
}
}
#[derive(Clone, Copy, Default)]
pub struct BspEdge {
pub vertex: [u16; 2],
impl Default for Face {
fn default() -> Self {
Self {
first_edge: 0,
lightmap_offset: 0,
num_edges: 0,
plane_id: 0,
side: 0,
styles: [0; 4],
material_id: 0,
aabb_min: VEC3_ZERO,
aabb_max: VEC3_ZERO,
}
}
}
#[derive(Clone, Copy, Default)]
pub struct BspFace {
pub first_edge: u32,
pub lightmap_offset: u32,
pub num_edges: u16,
pub plane_id: u16,
pub side: u16,
pub styles: [u8; 4],
pub material_id: u16,
pub aabb_min: Vec3,
pub aabb_max: Vec3,
impl Default for Leaf {
fn default() -> Self {
Self {
ambient_level: [0; 4],
contents: 0,
first_marksurface: 0,
maxs: [0; 3],
mins: [0; 3],
num_marksurfaces: 0,
visofs: -1,
}
}
}
#[derive(Clone, Copy, Default)]
pub struct BspPlane {
pub normal: Vec3,
pub dist: f32,
pub plane_type: i32,
impl Default for Node {
fn default() -> Self {
Self {
children: [0, 0],
first_face: 0,
maxs: [0; 3],
mins: [0; 3],
num_faces: 0,
plane_id: 0,
}
}
}
#[derive(Clone, Copy, Default)]
pub struct BspNode {
pub children: [i32; 2],
pub first_face: u16,
pub maxs: [i16; 3],
pub mins: [i16; 3],
pub num_faces: u16,
pub plane_id: u32,
impl Default for Plane {
fn default() -> Self {
Self {
dist: 0.0,
normal: VEC3_ZERO,
type_: 0,
}
}
}
#[derive(Clone, Copy, Default)]
pub struct BspLeaf {
pub ambient_level: [u8; 4],
pub contents: i32,
pub first_marksurface: u16,
pub maxs: [i16; 3],
pub mins: [i16; 3],
pub num_marksurfaces: u16,
pub visofs: i32,
impl Default for Vertex {
fn default() -> Self {
Self { position: VEC3_ZERO }
}
}
#[derive(Clone, Copy, Default)]
pub struct BspPortal {
pub x0: f32,
pub z0: f32,
pub x1: f32,
pub z1: f32,
pub target_leaf: u32,
impl Default for Portal {
fn default() -> Self {
Self {
x0: 0.0,
z0: 0.0,
x1: 0.0,
z1: 0.0,
target_leaf: 0,
}
}
}
#[derive(Clone, Default)]
pub struct BspCellPortals {
pub portals: [BspPortal; 4],
pub num_portals: u8,
impl Default for CellPortals {
fn default() -> Self {
Self {
portals: [Portal::default(); 4],
num_portals: 0,
}
}
}
pub struct Bsp {
pub vertices: Vec<BspVertex>,
pub edges: Vec<BspEdge>,
pub surfedges: Vec<i32>,
pub planes: Vec<BspPlane>,
pub faces: Vec<BspFace>,
pub nodes: Vec<BspNode>,
pub leafs: Vec<BspLeaf>,
inner: pxl8_bsp,
pub cell_portals: Box<[CellPortals]>,
pub edges: Box<[Edge]>,
pub faces: Box<[Face]>,
pub leafs: Box<[Leaf]>,
pub marksurfaces: Box<[u16]>,
pub nodes: Box<[Node]>,
pub planes: Box<[Plane]>,
pub surfedges: Box<[i32]>,
pub vertex_lights: Box<[u32]>,
pub vertices: Box<[Vertex]>,
pub visdata: Box<[u8]>,
}
#[derive(Default)]
pub struct BspBuilder {
pub cell_portals: Vec<CellPortals>,
pub edges: Vec<Edge>,
pub faces: Vec<Face>,
pub leafs: Vec<Leaf>,
pub marksurfaces: Vec<u16>,
pub cell_portals: Vec<BspCellPortals>,
pub visdata: Vec<u8>,
pub nodes: Vec<Node>,
pub planes: Vec<Plane>,
pub surfedges: Vec<i32>,
pub vertex_lights: Vec<u32>,
pub vertices: Vec<Vertex>,
pub visdata: Vec<u8>,
}
impl BspBuilder {
pub fn new() -> Self {
Self::default()
}
}
impl From<BspBuilder> for Bsp {
fn from(b: BspBuilder) -> Self {
let cell_portals = b.cell_portals.into_boxed_slice();
let edges = b.edges.into_boxed_slice();
let faces = b.faces.into_boxed_slice();
let leafs = b.leafs.into_boxed_slice();
let marksurfaces = b.marksurfaces.into_boxed_slice();
let nodes = b.nodes.into_boxed_slice();
let planes = b.planes.into_boxed_slice();
let surfedges = b.surfedges.into_boxed_slice();
let vertex_lights = b.vertex_lights.into_boxed_slice();
let vertices = b.vertices.into_boxed_slice();
let visdata = b.visdata.into_boxed_slice();
let inner = pxl8_bsp {
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
edges: if edges.is_empty() { core::ptr::null_mut() } else { edges.as_ptr() as *mut _ },
faces: if faces.is_empty() { core::ptr::null_mut() } else { faces.as_ptr() as *mut _ },
leafs: if leafs.is_empty() { core::ptr::null_mut() } else { leafs.as_ptr() as *mut _ },
lightdata: core::ptr::null_mut(),
lightmaps: core::ptr::null_mut(),
marksurfaces: if marksurfaces.is_empty() { core::ptr::null_mut() } else { marksurfaces.as_ptr() as *mut _ },
models: core::ptr::null_mut(),
nodes: if nodes.is_empty() { core::ptr::null_mut() } else { nodes.as_ptr() as *mut _ },
planes: if planes.is_empty() { core::ptr::null_mut() } else { planes.as_ptr() as *mut _ },
surfedges: if surfedges.is_empty() { core::ptr::null_mut() } else { surfedges.as_ptr() as *mut _ },
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
lightdata_size: 0,
num_cell_portals: cell_portals.len() as u32,
num_edges: edges.len() as u32,
num_faces: faces.len() as u32,
num_leafs: leafs.len() as u32,
num_lightmaps: 0,
num_marksurfaces: marksurfaces.len() as u32,
num_models: 0,
num_nodes: nodes.len() as u32,
num_planes: planes.len() as u32,
num_surfedges: surfedges.len() as u32,
num_vertex_lights: vertex_lights.len() as u32,
num_vertices: vertices.len() as u32,
visdata_size: visdata.len() as u32,
};
Self {
inner,
cell_portals,
edges,
faces,
leafs,
marksurfaces,
nodes,
planes,
surfedges,
vertex_lights,
vertices,
visdata,
}
}
}
impl Bsp {
pub fn new() -> Self {
Self {
vertices: Vec::new(),
edges: Vec::new(),
surfedges: Vec::new(),
planes: Vec::new(),
faces: Vec::new(),
nodes: Vec::new(),
leafs: Vec::new(),
marksurfaces: Vec::new(),
cell_portals: Vec::new(),
visdata: Vec::new(),
vertex_lights: Vec::new(),
}
}
pub fn find_leaf(&self, pos: Vec3) -> i32 {
if self.nodes.is_empty() {
return -1;
}
let mut node_id: i32 = 0;
while node_id >= 0 {
let node = &self.nodes[node_id as usize];
let plane = &self.planes[node.plane_id as usize];
let dist = pos.dot(plane.normal) - plane.dist;
node_id = node.children[if dist < 0.0 { 1 } else { 0 }];
}
-(node_id + 1)
}
pub fn point_solid(&self, pos: Vec3) -> bool {
let leaf = self.find_leaf(pos);
if leaf < 0 || leaf as usize >= self.leafs.len() {
return true;
}
self.leafs[leaf as usize].contents == -1
}
fn point_clear(&self, x: f32, y: f32, z: f32, radius: f32) -> bool {
if self.point_solid(Vec3::new(x, y, z)) { return false; }
if self.point_solid(Vec3::new(x + radius, y, z)) { return false; }
if self.point_solid(Vec3::new(x - radius, y, z)) { return false; }
if self.point_solid(Vec3::new(x, y, z + radius)) { return false; }
if self.point_solid(Vec3::new(x, y, z - radius)) { return false; }
true
pub fn as_c_bsp(&self) -> &pxl8_bsp {
&self.inner
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if self.nodes.is_empty() {
return to;
}
if self.point_clear(to.x, to.y, to.z, radius) {
return to;
}
let x_ok = self.point_clear(to.x, from.y, from.z, radius);
let z_ok = self.point_clear(from.x, from.y, to.z, radius);
if x_ok && z_ok {
let dx = to.x - from.x;
let dz = to.z - from.z;
if dx * dx > dz * dz {
Vec3::new(to.x, from.y, from.z)
} else {
Vec3::new(from.x, from.y, to.z)
}
} else if x_ok {
Vec3::new(to.x, from.y, from.z)
} else if z_ok {
Vec3::new(from.x, from.y, to.z)
} else {
from
}
unsafe { pxl8_bsp_trace(&self.inner, from, to, radius) }
}
}
impl Default for Bsp {
fn default() -> Self {
Self::new()
BspBuilder::new().into()
}
}

View file

@ -4,6 +4,7 @@ pub mod stream;
use crate::bsp::Bsp;
use crate::math::Vec3;
use crate::pxl8::pxl8_vxl_trace;
use crate::voxel::VoxelChunk;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
@ -35,7 +36,9 @@ impl Chunk {
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
match self {
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius),
Chunk::Vxl { data, .. } => data.trace(from, to, radius),
Chunk::Vxl { cx, cy, cz, data, .. } => unsafe {
pxl8_vxl_trace(data.chunk, *cx, *cy, *cz, from, to, radius)
},
}
}

View file

@ -77,6 +77,18 @@ impl ClientChunkState {
self.pending.clear();
self.pending_messages.clear();
}
pub fn clear_known_vxl(&mut self) {
self.known.retain(|id, _| !matches!(id, ChunkId::Vxl(_, _, _)));
}
pub fn clear_known_bsp(&mut self) {
self.known.retain(|id, _| !matches!(id, ChunkId::Bsp(_)));
}
pub fn forget(&mut self, id: &ChunkId) {
self.known.remove(id);
}
}
impl Default for ClientChunkState {

View file

@ -24,8 +24,8 @@ fn panic(_info: &PanicInfo) -> ! {
}
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
pub mod protocol {
include!(concat!(env!("OUT_DIR"), "/protocol.rs"));
pub mod pxl8 {
include!(concat!(env!("OUT_DIR"), "/pxl8.rs"));
}
pub use bsp::*;
@ -33,7 +33,7 @@ pub use chunk::*;
pub use chunk::stream::*;
pub use math::*;
pub use procgen::{ProcgenParams, generate, generate_rooms};
pub use protocol::*;
pub use pxl8::*;
pub use sim::*;
pub use transport::*;
pub use voxel::*;

View file

@ -1,9 +1,9 @@
use crate::protocol::{pxl8_log, pxl8_log_init};
use crate::pxl8::{pxl8_log, pxl8_log_init};
use core::ffi::c_char;
static mut G_LOG: pxl8_log = pxl8_log {
handler: None,
level: crate::protocol::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG,
level: crate::pxl8::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG,
};
pub fn init() {
@ -20,7 +20,7 @@ macro_rules! pxl8_debug {
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::pxl8_log_write_debug(
$crate::pxl8::pxl8_log_write_debug(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
@ -38,7 +38,7 @@ macro_rules! pxl8_error {
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::pxl8_log_write_error(
$crate::pxl8::pxl8_log_write_error(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
@ -56,7 +56,7 @@ macro_rules! pxl8_info {
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::pxl8_log_write_info(
$crate::pxl8::pxl8_log_write_info(
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
@ -72,7 +72,7 @@ macro_rules! pxl8_trace {
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::pxl8_log_write_trace(
$crate::pxl8::pxl8_log_write_trace(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,
@ -90,7 +90,7 @@ macro_rules! pxl8_warn {
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::pxl8_log_write_warn(
$crate::pxl8::pxl8_log_write_warn(
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
line!() as ::core::ffi::c_int,
c"%s".as_ptr() as *const ::core::ffi::c_char,

View file

@ -6,6 +6,7 @@ extern crate alloc;
use pxl8d::*;
use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState;
use pxl8d::math::Vec3;
const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
@ -73,14 +74,15 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
let mut player_id: Option<u64> = None;
let mut last_client_tick: u64 = 0;
let mut client_chunks = ClientChunkState::new();
let mut client_stream_radius: i32 = 3;
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
let mut entities_buf = [protocol::pxl8_entity_state {
let mut entities_buf = [pxl8d::pxl8_entity_state {
entity_id: 0,
userdata: [0u8; 56],
}; 64];
let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
let mut inputs_buf: [pxl8d::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
pxl8_debug!("[SERVER] Entering main loop");
loop {
@ -91,15 +93,15 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
last_tick = now;
let dt = (elapsed as f32) / 1_000_000_000.0;
let mut latest_input: Option<protocol::pxl8_input_msg> = None;
let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
while let Some(msg_type) = transport.recv() {
match msg_type {
x if x == protocol::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
latest_input = Some(transport.get_input());
}
x if x == protocol::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
let cmd = transport.get_command();
if cmd.cmd_type == protocol::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
player_id = Some(sim.spawn_player(x, y, z) as u64);
@ -110,6 +112,46 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
pxl8_debug!("[SERVER] Sending CHUNK_ENTER for BSP id=1");
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1);
pxl8_debug!("[SERVER] Preloading voxel chunks around spawn and door");
let spawn_pos = Vec3 { x, y, z };
let door_y = sim.voxels.find_surface_y(942.0, 416.0);
let door_pos = Vec3 { x: 942.0, y: door_y, z: 416.0 };
pxl8_debug!("[SERVER] Door placed at surface y={}", door_y);
sim.voxels.load_chunks_around(spawn_pos, client_stream_radius);
sim.voxels.load_chunks_around(door_pos, client_stream_radius);
client_chunks.request_vxl_radius(spawn_pos, client_stream_radius, &sim.voxels);
client_chunks.request_vxl_radius(door_pos, client_stream_radius, &sim.voxels);
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_EXIT_CHUNK as u16 {
sim.world.clear_active();
client_chunks.clear_pending();
client_chunks.clear_known_vxl();
client_chunks.clear_known_bsp();
transport.send_chunk_exit(sequence);
sequence = sequence.wrapping_add(1);
let exit_x = f32::from_be_bytes([cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]]);
let exit_y = f32::from_be_bytes([cmd.payload[4], cmd.payload[5], cmd.payload[6], cmd.payload[7]]);
let exit_z = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]);
let exit_pos = Vec3 { x: exit_x, y: exit_y, z: exit_z };
sim.teleport_player(exit_x, exit_y, exit_z);
sim.voxels.load_chunks_around(exit_pos, client_stream_radius);
client_chunks.request_vxl_radius(exit_pos, client_stream_radius, &sim.voxels);
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_CHUNK as u16 {
let chunk_id = u32::from_be_bytes([
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
]);
pxl8_debug!("[SERVER] Enter chunk command - entering BSP {}", chunk_id);
if sim.world.contains(&ChunkId::Bsp(chunk_id)) {
sim.world.set_active(ChunkId::Bsp(chunk_id));
client_chunks.request(ChunkId::Bsp(chunk_id));
transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1);
}
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SET_CHUNK_SETTINGS as u16 {
let render_dist = i32::from_be_bytes([
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
]);
client_stream_radius = render_dist.clamp(1, 8);
}
}
_ => {}
@ -132,7 +174,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
}
});
let header = protocol::pxl8_snapshot_header {
let header = pxl8d::pxl8_snapshot_header {
entity_count: count as u16,
event_count: 0,
player_id: player_id.unwrap_or(0),
@ -147,54 +189,36 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
if let Some(player) = sim.get_player_position(pid) {
let pos = Vec3 { x: player.0, y: player.1, z: player.2 };
if sim.world.active().is_some() {
while let Some(chunk_id) = client_chunks.next_pending() {
match chunk_id {
ChunkId::Bsp(id) => {
pxl8_debug!("[SERVER] Processing pending BSP chunk");
if let Some(chunk) = sim.world.get(&chunk_id) {
if let Some(bsp) = chunk.as_bsp() {
let msgs = bsp_to_messages(bsp, id, chunk.version());
pxl8_debug!("[SERVER] BSP serialized, queueing messages");
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, chunk.version());
}
}
}
ChunkId::Vxl(cx, cy, cz) => {
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
sim.voxels.load_chunks_around(pos, client_stream_radius);
client_chunks.request_vxl_radius(pos, client_stream_radius, &sim.voxels);
while let Some(chunk_id) = client_chunks.next_pending() {
match chunk_id {
ChunkId::Bsp(id) => {
if let Some(chunk) = sim.world.get(&chunk_id) {
if let Some(bsp) = chunk.as_bsp() {
let msgs = bsp_to_messages(bsp, id, chunk.version());
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, 1);
client_chunks.mark_sent(chunk_id, chunk.version());
}
}
}
}
for _ in 0..4 {
if let Some(msg) = client_chunks.next_message() {
transport.send_chunk(&msg, sequence);
sequence = sequence.wrapping_add(1);
}
}
} else {
client_chunks.request_vxl_radius(pos, 2, &sim.voxels);
for _ in 0..2 {
if let Some(chunk_id) = client_chunks.next_pending() {
if let ChunkId::Vxl(cx, cy, cz) = chunk_id {
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
for msg in &msgs {
transport.send_chunk(msg, sequence);
sequence = sequence.wrapping_add(1);
}
client_chunks.mark_sent(chunk_id, 1);
}
ChunkId::Vxl(cx, cy, cz) => {
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, 1);
}
}
}
}
for _ in 0..8 {
if let Some(msg) = client_chunks.next_message() {
transport.send_chunk(&msg, sequence);
sequence = sequence.wrapping_add(1);
}
}
}
}
}
@ -250,7 +274,7 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<tra
data.extend_from_slice(&p.normal.y.to_be_bytes());
data.extend_from_slice(&p.normal.z.to_be_bytes());
data.extend_from_slice(&p.dist.to_be_bytes());
data.extend_from_slice(&p.plane_type.to_be_bytes());
data.extend_from_slice(&p.type_.to_be_bytes());
}
for f in &bsp.faces {

View file

@ -1,42 +1,62 @@
use core::ops::{Add, Mul, Sub};
#[derive(Clone, Copy, Default)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: f32,
use crate::pxl8::pxl8_vec3;
pub type Vec3 = pxl8_vec3;
pub const VEC3_ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
pub const VEC3_Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 };
pub trait Vec3Ext {
fn new(x: f32, y: f32, z: f32) -> Self;
fn dot(self, rhs: Self) -> f32;
}
impl Vec3 {
pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
pub const Y: Self = Self { x: 0.0, y: 1.0, z: 0.0 };
pub const fn new(x: f32, y: f32, z: f32) -> Self {
impl Vec3Ext for pxl8_vec3 {
fn new(x: f32, y: f32, z: f32) -> Self {
Self { x, y, z }
}
pub fn dot(self, rhs: Self) -> f32 {
fn dot(self, rhs: Self) -> f32 {
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
}
}
impl Add for Vec3 {
impl Default for pxl8_vec3 {
fn default() -> Self {
VEC3_ZERO
}
}
impl Add for pxl8_vec3 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Sub for Vec3 {
impl Sub for pxl8_vec3 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
z: self.z - rhs.z,
}
}
}
impl Mul<f32> for Vec3 {
impl Mul<f32> for pxl8_vec3 {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self::new(self.x * rhs, self.y * rhs, self.z * rhs)
Self {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}

View file

@ -4,11 +4,12 @@ use alloc::vec;
use alloc::vec::Vec;
use libm::sqrtf;
use crate::bsp::{Bsp, BspVertex, BspEdge, BspFace, BspPlane, BspNode, BspLeaf, BspPortal, BspCellPortals};
use crate::math::Vec3;
use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex};
use crate::math::{Vec3, Vec3Ext};
pub const CELL_SIZE: f32 = 64.0;
pub const WALL_HEIGHT: f32 = 128.0;
pub const TRIM_HEIGHT: f32 = 12.0;
pub const PVS_MAX_DEPTH: u32 = 64;
#[derive(Clone, Copy)]
@ -107,7 +108,7 @@ impl Bounds {
}
struct BspBuildContext<'a> {
bsp: &'a mut Bsp,
bsp: &'a mut BspBuilder,
grid: &'a RoomGrid,
node_count: u32,
plane_offset: u32,
@ -133,7 +134,7 @@ fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) {
}
}
fn compute_face_aabb(face: &mut BspFace, verts: &[BspVertex], vert_idx: usize) {
fn compute_face_aabb(face: &mut Face, verts: &[Vertex], vert_idx: usize) {
face.aabb_min = Vec3::new(1e30, 1e30, 1e30);
face.aabb_max = Vec3::new(-1e30, -1e30, -1e30);
@ -148,7 +149,7 @@ fn compute_face_aabb(face: &mut BspFace, verts: &[BspVertex], vert_idx: usize) {
}
}
fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32) -> i32 {
fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32) -> i32 {
if x1 - x0 == 1 && y1 - y0 == 1 {
let leaf_idx = y0 * ctx.grid.width + x0;
return -(leaf_idx + 1);
@ -164,16 +165,16 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32,
let mid_x = (x0 + x1) / 2;
let split_pos = mid_x as f32 * CELL_SIZE;
ctx.bsp.planes[plane_idx as usize] = BspPlane {
ctx.bsp.planes[plane_idx as usize] = Plane {
normal: Vec3::new(1.0, 0.0, 0.0),
dist: split_pos,
plane_type: 0,
type_: 0,
};
let child0 = build_bsp_node(ctx, mid_x, y0, x1, y1, depth + 1);
let child1 = build_bsp_node(ctx, x0, y0, mid_x, y1, depth + 1);
let child0 = build_bsp_node_grid(ctx, mid_x, y0, x1, y1, depth + 1);
let child1 = build_bsp_node_grid(ctx, x0, y0, mid_x, y1, depth + 1);
ctx.bsp.nodes[node_idx as usize] = BspNode {
ctx.bsp.nodes[node_idx as usize] = Node {
plane_id: plane_idx,
children: [child0, child1],
..Default::default()
@ -182,16 +183,16 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32,
let mid_y = (y0 + y1) / 2;
let split_pos = mid_y as f32 * CELL_SIZE;
ctx.bsp.planes[plane_idx as usize] = BspPlane {
ctx.bsp.planes[plane_idx as usize] = Plane {
normal: Vec3::new(0.0, 0.0, 1.0),
dist: split_pos,
plane_type: 0,
type_: 0,
};
let child0 = build_bsp_node(ctx, x0, mid_y, x1, y1, depth + 1);
let child1 = build_bsp_node(ctx, x0, y0, x1, mid_y, depth + 1);
let child0 = build_bsp_node_grid(ctx, x0, mid_y, x1, y1, depth + 1);
let child1 = build_bsp_node_grid(ctx, x0, y0, x1, mid_y, depth + 1);
ctx.bsp.nodes[node_idx as usize] = BspNode {
ctx.bsp.nodes[node_idx as usize] = Node {
plane_id: plane_idx,
children: [child0, child1],
..Default::default()
@ -201,9 +202,33 @@ fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32,
node_idx as i32
}
fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32, floor_leaf_idx: i32) -> i32 {
let node_idx = ctx.node_count;
ctx.node_count += 1;
let plane_idx = ctx.plane_offset;
ctx.plane_offset += 1;
ctx.bsp.planes[plane_idx as usize] = Plane {
normal: Vec3::new(0.0, 1.0, 0.0),
dist: 0.0,
type_: 1,
};
let above_floor = build_bsp_node_grid(ctx, x0, y0, x1, y1, depth);
ctx.bsp.nodes[node_idx as usize] = Node {
plane_id: plane_idx,
children: [above_floor, -(floor_leaf_idx + 1)],
..Default::default()
};
node_idx as i32
}
fn build_cell_portals(grid: &RoomGrid) -> Vec<CellPortals> {
let total_cells = (grid.width * grid.height) as usize;
let mut portals = vec![BspCellPortals::default(); total_cells];
let mut portals = vec![CellPortals::default(); total_cells];
for y in 0..grid.height {
for x in 0..grid.width {
@ -218,7 +243,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if x > 0 && grid.get(x - 1, y) == 0 {
let p = &mut portals[c];
let idx = p.num_portals as usize;
p.portals[idx] = BspPortal {
p.portals[idx] = Portal {
x0: cx,
z0: cz,
x1: cx,
@ -230,7 +255,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if x < grid.width - 1 && grid.get(x + 1, y) == 0 {
let p = &mut portals[c];
let idx = p.num_portals as usize;
p.portals[idx] = BspPortal {
p.portals[idx] = Portal {
x0: cx + CELL_SIZE,
z0: cz + CELL_SIZE,
x1: cx + CELL_SIZE,
@ -242,7 +267,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if y > 0 && grid.get(x, y - 1) == 0 {
let p = &mut portals[c];
let idx = p.num_portals as usize;
p.portals[idx] = BspPortal {
p.portals[idx] = Portal {
x0: cx + CELL_SIZE,
z0: cz,
x1: cx,
@ -254,7 +279,7 @@ fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
if y < grid.height - 1 && grid.get(x, y + 1) == 0 {
let p = &mut portals[c];
let idx = p.num_portals as usize;
p.portals[idx] = BspPortal {
p.portals[idx] = Portal {
x0: cx,
z0: cz + CELL_SIZE,
x1: cx + CELL_SIZE,
@ -277,8 +302,8 @@ struct FloodEntry {
fn portal_flood_bfs(
start_leaf: u32,
portals: &[BspCellPortals],
leafs: &[BspLeaf],
portals: &[CellPortals],
leafs: &[Leaf],
pvs: &mut [u8],
num_leafs: u32,
) {
@ -325,8 +350,8 @@ fn portal_flood_bfs(
fn compute_leaf_pvs(
start_leaf: u32,
portals: &[BspCellPortals],
leafs: &[BspLeaf],
portals: &[CellPortals],
leafs: &[Leaf],
num_leafs: u32,
) -> Vec<u8> {
let pvs_bytes = ((num_leafs + 7) / 8) as usize;
@ -357,7 +382,7 @@ fn rle_compress_pvs(pvs: &[u8]) -> Vec<u8> {
out
}
fn build_pvs_data(bsp: &mut Bsp, portals: &[BspCellPortals]) {
fn build_pvs_data(bsp: &mut BspBuilder, portals: &[CellPortals]) {
let num_leafs = bsp.leafs.len() as u32;
let mut visdata = Vec::new();
@ -417,7 +442,7 @@ fn compute_vertex_light(
total.min(1.0)
}
fn compute_bsp_vertex_lighting(bsp: &mut Bsp, lights: &[LightSource], ambient: f32) {
fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) {
if bsp.vertices.is_empty() {
return;
}
@ -462,35 +487,35 @@ fn compute_bsp_vertex_lighting(bsp: &mut Bsp, lights: &[LightSource], ambient: f
}
}
fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
let mut face_count = 0;
fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) {
let mut wall_count = 0;
let mut floor_ceiling_count = 0;
for y in 0..grid.height {
for x in 0..grid.width {
if grid.get(x, y) == 0 {
if grid.get(x - 1, y) == 1 { face_count += 1; }
if grid.get(x + 1, y) == 1 { face_count += 1; }
if grid.get(x, y - 1) == 1 { face_count += 1; }
if grid.get(x, y + 1) == 1 { face_count += 1; }
if grid.get(x - 1, y) == 1 { wall_count += 1; }
if grid.get(x + 1, y) == 1 { wall_count += 1; }
if grid.get(x, y - 1) == 1 { wall_count += 1; }
if grid.get(x, y + 1) == 1 { wall_count += 1; }
floor_ceiling_count += 1;
}
}
}
face_count += floor_ceiling_count;
let face_count = wall_count * 2 + floor_ceiling_count;
let vertex_count = face_count * 4;
let total_cells = (grid.width * grid.height) as usize;
let max_nodes = 2 * total_cells;
let total_planes = face_count + max_nodes;
let max_nodes = 2 * total_cells + 1;
let total_planes = face_count + max_nodes + 1;
bsp.vertices = vec![BspVertex::default(); vertex_count];
bsp.faces = vec![BspFace::default(); face_count];
bsp.planes = vec![BspPlane::default(); total_planes];
bsp.edges = vec![BspEdge::default(); vertex_count];
bsp.vertices = vec![Vertex::default(); vertex_count];
bsp.faces = vec![Face::default(); face_count];
bsp.planes = vec![Plane::default(); total_planes];
bsp.edges = vec![Edge::default(); vertex_count];
bsp.surfedges = vec![0i32; vertex_count];
bsp.nodes = vec![BspNode::default(); max_nodes];
bsp.nodes = vec![Node::default(); max_nodes];
let mut face_cell = vec![0u32; face_count];
@ -506,9 +531,35 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
let cell_idx = (y * grid.width + x) as u32;
if grid.get(x - 1, y) == 1 {
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0);
bsp.planes[face_idx].dist = fx;
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 1;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
}
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx += 1;
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0);
@ -517,7 +568,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 0;
bsp.faces[face_idx].material_id = 3;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
@ -534,8 +585,8 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
}
if grid.get(x + 1, y) == 1 {
bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy);
@ -545,7 +596,33 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 0;
bsp.faces[face_idx].material_id = 1;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
}
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx += 1;
bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy);
bsp.planes[face_idx].normal = Vec3::new(-1.0, 0.0, 0.0);
bsp.planes[face_idx].dist = -(fx + CELL_SIZE);
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 3;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
@ -562,8 +639,8 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
}
if grid.get(x, y - 1) == 1 {
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, WALL_HEIGHT, fy);
@ -573,7 +650,33 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 0;
bsp.faces[face_idx].material_id = 1;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
}
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx += 1;
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, TRIM_HEIGHT, fy);
bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, 1.0);
bsp.planes[face_idx].dist = fy;
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 3;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
@ -590,9 +693,35 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
}
if grid.get(x, y + 1) == 1 {
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0);
bsp.planes[face_idx].dist = -(fy + CELL_SIZE);
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 1;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16;
bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32;
}
compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx);
face_cell[face_idx] = cell_idx;
vert_idx += 4;
edge_idx += 4;
face_idx += 1;
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0);
@ -601,7 +730,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
bsp.faces[face_idx].plane_id = face_idx as u16;
bsp.faces[face_idx].num_edges = 4;
bsp.faces[face_idx].first_edge = edge_idx as u32;
bsp.faces[face_idx].material_id = 0;
bsp.faces[face_idx].material_id = 3;
for i in 0..4 {
bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16;
@ -661,7 +790,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
bsp.edges.truncate(edge_idx);
bsp.surfedges.truncate(edge_idx);
bsp.leafs = vec![BspLeaf::default(); total_cells];
bsp.leafs = vec![Leaf::default(); total_cells + 1];
bsp.marksurfaces = vec![0u16; face_idx];
let mut faces_per_cell = vec![0u32; total_cells];
@ -712,6 +841,17 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
}
}
let floor_leaf_idx = total_cells as i32;
let floor_leaf = &mut bsp.leafs[total_cells];
floor_leaf.contents = -1;
floor_leaf.mins = [0, i16::MIN, 0];
floor_leaf.maxs = [
(grid.width as f32 * CELL_SIZE) as i16,
0,
(grid.height as f32 * CELL_SIZE) as i16,
];
floor_leaf.visofs = -1;
let face_count_final = face_idx;
let mut ctx = BspBuildContext {
bsp,
@ -720,7 +860,7 @@ fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
plane_offset: face_count_final as u32,
};
build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0);
build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0, floor_leaf_idx);
let node_count = ctx.node_count as usize;
let plane_count = ctx.plane_offset as usize;
@ -782,7 +922,7 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp {
}
}
let mut bsp = Bsp::new();
let mut bsp = BspBuilder::new();
grid_to_bsp(&mut bsp, &grid);
let light_height = 80.0;
@ -798,7 +938,7 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp {
compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1);
bsp
bsp.into()
}
pub fn generate(params: &ProcgenParams) -> Bsp {

View file

@ -1,38 +1,29 @@
extern crate alloc;
use alloc::vec::Vec;
use libm::{cosf, sinf, sqrtf};
use crate::math::Vec3;
use crate::protocol::*;
use crate::math::{Vec3, Vec3Ext, VEC3_ZERO};
use crate::pxl8::*;
use crate::voxel::VoxelWorld;
use crate::world::World;
const ALIVE: u32 = 1 << 0;
const PLAYER: u32 = 1 << 1;
const GROUNDED: u32 = 1 << 2;
pub type Entity = pxl8_sim_entity;
const ALIVE: u32 = PXL8_SIM_FLAG_ALIVE;
const PLAYER: u32 = PXL8_SIM_FLAG_PLAYER;
const MAX_ENTITIES: usize = 1024;
#[derive(Clone, Copy)]
pub struct Entity {
pub flags: u32,
pub kind: u16,
pub pos: Vec3,
pub vel: Vec3,
pub yaw: f32,
pub pitch: f32,
}
impl Default for Entity {
fn default() -> Self {
Self {
flags: 0,
kind: 0,
pos: Vec3::ZERO,
vel: Vec3::ZERO,
pos: VEC3_ZERO,
vel: VEC3_ZERO,
yaw: 0.0,
pitch: 0.0,
flags: 0,
kind: 0,
_pad: 0,
}
}
}
@ -96,6 +87,13 @@ impl Simulation {
id
}
pub fn teleport_player(&mut self, x: f32, y: f32, z: f32) {
if let Some(id) = self.player {
let ent = &mut self.entities[id as usize];
ent.pos = Vec3::new(x, y, z);
}
}
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
self.tick += 1;
self.time += dt;
@ -107,120 +105,68 @@ impl Simulation {
self.integrate(dt);
}
fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if self.world.active().is_some() {
return self.world.trace(from, to, radius);
fn make_sim_world(&self, pos: Vec3) -> pxl8_sim_world {
if let Some(chunk) = self.world.active() {
if let Some(bsp) = chunk.as_bsp() {
return pxl8_sim_world {
bsp: bsp.as_c_bsp(),
vxl: core::ptr::null(),
vxl_cx: 0,
vxl_cy: 0,
vxl_cz: 0,
};
}
}
let cx = VoxelWorld::world_to_chunk(pos.x);
let cy = VoxelWorld::world_to_chunk(pos.y);
let cz = VoxelWorld::world_to_chunk(pos.z);
if let Some(chunk) = self.voxels.get_chunk(cx, cy, cz) {
pxl8_sim_world {
bsp: core::ptr::null(),
vxl: chunk.chunk as *const _,
vxl_cx: cx,
vxl_cy: cy,
vxl_cz: cz,
}
} else {
pxl8_sim_world {
bsp: core::ptr::null(),
vxl: core::ptr::null(),
vxl_cx: 0,
vxl_cy: 0,
vxl_cz: 0,
}
}
self.voxels.trace(from, to, radius)
}
fn integrate(&mut self, dt: f32) {
const GRAVITY: f32 = 800.0;
const FRICTION: f32 = 6.0;
const RADIUS: f32 = 16.0;
for i in 0..self.entities.len() {
let ent = &self.entities[i];
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
continue;
}
let mut vel = ent.vel;
let pos = ent.pos;
let flags = ent.flags;
vel.y = vel.y - GRAVITY * dt;
if flags & GROUNDED != 0 {
let speed = sqrtf(vel.x * vel.x + vel.z * vel.z);
if speed > 0.0 {
let drop = speed * FRICTION * dt;
let scale = (speed - drop).max(0.0) / speed;
vel.x = vel.x * scale;
vel.z = vel.z * scale;
}
}
let target = pos + vel * dt;
let new_pos = self.trace(pos, target, RADIUS);
let world = self.make_sim_world(ent.pos);
let ent = &mut self.entities[i];
ent.vel = vel;
ent.pos = new_pos;
unsafe {
pxl8_sim_integrate(ent, &world, dt);
}
}
}
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
let Some(id) = self.player else { return };
let ent = &mut self.entities[id as usize];
let ent = &self.entities[id as usize];
if ent.flags & ALIVE == 0 {
return;
}
const MOVE_SPEED: f32 = 320.0;
const GROUND_ACCEL: f32 = 10.0;
const AIR_ACCEL: f32 = 1.0;
const STOP_SPEED: f32 = 100.0;
const FRICTION: f32 = 6.0;
const RADIUS: f32 = 16.0;
let sin_yaw = sinf(input.yaw);
let cos_yaw = cosf(input.yaw);
let input_len = sqrtf(input.move_x * input.move_x + input.move_y * input.move_y);
let (move_dir, target_speed) = if input_len > 0.0 {
let nx = input.move_x / input_len;
let ny = input.move_y / input_len;
let dir = Vec3::new(
cos_yaw * nx - sin_yaw * ny,
0.0,
-sin_yaw * nx - cos_yaw * ny,
);
(dir, MOVE_SPEED)
} else {
(Vec3::ZERO, 0.0)
};
let grounded = ent.flags & GROUNDED != 0;
if grounded {
let speed = sqrtf(ent.vel.x * ent.vel.x + ent.vel.z * ent.vel.z);
if speed > 0.0 {
let control = speed.max(STOP_SPEED);
let drop = control * FRICTION * dt;
let scale = (speed - drop).max(0.0) / speed;
ent.vel.x *= scale;
ent.vel.z *= scale;
}
}
if target_speed > 0.0 {
let accel = if grounded { GROUND_ACCEL } else { AIR_ACCEL };
let current = ent.vel.x * move_dir.x + ent.vel.z * move_dir.z;
let add = target_speed - current;
if add > 0.0 {
let amount = (accel * target_speed * dt).min(add);
ent.vel.x += move_dir.x * amount;
ent.vel.z += move_dir.z * amount;
}
}
ent.yaw = input.yaw;
ent.pitch = (ent.pitch - input.look_dy * 0.008).clamp(-1.5, 1.5);
let pos = ent.pos;
let vel = ent.vel;
let target = pos + vel * dt;
let new_pos = self.trace(pos, target, RADIUS);
let ground_check = self.trace(new_pos, new_pos - Vec3::Y * 2.0, RADIUS);
let world = self.make_sim_world(ent.pos);
let ent = &mut self.entities[id as usize];
ent.pos = new_pos;
if ground_check.y < new_pos.y - 1.0 {
ent.flags &= !GROUNDED;
} else {
ent.flags |= GROUNDED;
unsafe {
pxl8_sim_move_player(ent, input, &world, dt);
}
}

View file

@ -2,8 +2,8 @@ extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::protocol::*;
use crate::protocol::pxl8_msg_type::*;
use crate::pxl8::*;
use crate::pxl8::pxl8_msg_type::*;
use crate::voxel::VoxelChunk;
pub const DEFAULT_PORT: u16 = 7777;
@ -374,6 +374,24 @@ impl Transport {
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk_exit(&mut self, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: PXL8_MSG_CHUNK_EXIT as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0] = msg.chunk_type;

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);

View file

@ -38,10 +38,6 @@ impl World {
self.chunks.remove(id)
}
pub fn set_active(&mut self, id: ChunkId) {
self.active = Some(id);
}
pub fn active(&self) -> Option<&Chunk> {
self.active.as_ref().and_then(|id| self.chunks.get(id))
}
@ -50,6 +46,14 @@ impl World {
self.active
}
pub fn set_active(&mut self, id: ChunkId) {
self.active = Some(id);
}
pub fn clear_active(&mut self) {
self.active = None;
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if let Some(chunk) = self.active() {
return chunk.trace(from, to, radius);