stream world data from pxl8d to pxl8

This commit is contained in:
asrael 2026-01-25 09:26:30 -06:00
parent 39ee0fefb7
commit a71a9840b2
55 changed files with 5290 additions and 2131 deletions

2
pxl8d/.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[unstable]
build-std = ["core", "alloc"]

1
pxl8d/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target/

263
pxl8d/Cargo.lock generated Normal file
View file

@ -0,0 +1,263 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "cc"
version = "1.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "find-msvc-tools"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libloading"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [
"cfg-if",
"windows-link",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
dependencies = [
"unicode-ident",
]
[[package]]
name = "pxl8d"
version = "0.1.0"
dependencies = [
"bindgen",
"cc",
"libc",
"libm",
"windows-sys",
]
[[package]]
name = "quote"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

40
pxl8d/Cargo.toml Normal file
View file

@ -0,0 +1,40 @@
[package]
name = "pxl8d"
version = "0.1.0"
edition = "2024"
[lib]
name = "pxl8d"
[build-dependencies]
bindgen = "0.72"
cc = "1.2"
[dependencies]
libc = { version = "0.2", default-features = false }
libm = { version = "0.2", default-features = false }
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", default-features = false, features = [
"Win32_System_Memory",
"Win32_System_Performance",
"Win32_System_Threading",
"Win32_Networking_WinSock",
] }
[[bin]]
name = "pxl8d"
path = "src/main.rs"
[profile.dev]
opt-level = 2
panic = "abort"
[profile.dev.package."*"]
opt-level = 3
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
strip = true

32
pxl8d/build.rs Normal file
View file

@ -0,0 +1,32 @@
use std::env;
use std::path::PathBuf;
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/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/net/pxl8_protocol.h");
cc::Build::new()
.file(pxl8_src.join("core/pxl8_log.c"))
.include(pxl8_src.join("core"))
.define("PXL8_SERVER", None)
.compile("pxl8_log");
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())
.clang_arg(format!("-I{}", pxl8_src.join("core").display()))
.use_core()
.rustified_enum(".*")
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("protocol.rs"))
.expect("Couldn't write bindings");
}

View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

36
pxl8d/src/allocator.rs Normal file
View file

@ -0,0 +1,36 @@
use core::alloc::{GlobalAlloc, Layout};
pub struct Allocator;
#[cfg(unix)]
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
unsafe { libc::memalign(layout.align(), layout.size()) as *mut u8 }
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
unsafe { libc::free(ptr as *mut libc::c_void) }
}
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 }
}
}
#[cfg(windows)]
unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
use windows_sys::Win32::System::Memory::*;
unsafe { HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 }
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
use windows_sys::Win32::System::Memory::*;
unsafe { HeapFree(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void) };
}
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
use windows_sys::Win32::System::Memory::*;
unsafe { HeapReAlloc(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void, new_size) as *mut u8 }
}
}

173
pxl8d/src/bsp.rs Normal file
View file

@ -0,0 +1,173 @@
extern crate alloc;
use alloc::vec::Vec;
use crate::math::Vec3;
#[derive(Clone, Copy, Default)]
pub struct BspVertex {
pub position: Vec3,
}
#[derive(Clone, Copy, Default)]
pub struct BspEdge {
pub vertex: [u16; 2],
}
#[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,
}
#[derive(Clone, Copy, Default)]
pub struct BspPlane {
pub normal: Vec3,
pub dist: f32,
pub plane_type: i32,
}
#[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,
}
#[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,
}
#[derive(Clone, Copy, Default)]
pub struct BspPortal {
pub x0: f32,
pub z0: f32,
pub x1: f32,
pub z1: f32,
pub target_leaf: u32,
}
#[derive(Clone, Default)]
pub struct BspCellPortals {
pub portals: [BspPortal; 4],
pub num_portals: u8,
}
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>,
pub marksurfaces: Vec<u16>,
pub cell_portals: Vec<BspCellPortals>,
pub visdata: Vec<u8>,
pub vertex_lights: Vec<u32>,
}
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 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
}
}
}
impl Default for Bsp {
fn default() -> Self {
Self::new()
}
}

55
pxl8d/src/chunk.rs Normal file
View file

@ -0,0 +1,55 @@
extern crate alloc;
pub mod stream;
use crate::bsp::Bsp;
use crate::math::Vec3;
use crate::voxel::VoxelChunk;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ChunkId {
Bsp(u32),
Vxl(i32, i32, i32),
}
pub enum Chunk {
Bsp { id: u32, bsp: Bsp, version: u32 },
Vxl { cx: i32, cy: i32, cz: i32, data: VoxelChunk, version: u32 },
}
impl Chunk {
pub fn id(&self) -> ChunkId {
match self {
Chunk::Bsp { id, .. } => ChunkId::Bsp(*id),
Chunk::Vxl { cx, cy, cz, .. } => ChunkId::Vxl(*cx, *cy, *cz),
}
}
pub fn version(&self) -> u32 {
match self {
Chunk::Bsp { version, .. } => *version,
Chunk::Vxl { version, .. } => *version,
}
}
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),
}
}
pub fn as_bsp(&self) -> Option<&Bsp> {
match self {
Chunk::Bsp { bsp, .. } => Some(bsp),
_ => None,
}
}
pub fn as_vxl(&self) -> Option<&VoxelChunk> {
match self {
Chunk::Vxl { data, .. } => Some(data),
_ => None,
}
}
}

86
pxl8d/src/chunk/stream.rs Normal file
View file

@ -0,0 +1,86 @@
extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::chunk::ChunkId;
use crate::math::Vec3;
use crate::transport::ChunkMessage;
use crate::voxel::VoxelWorld;
pub struct ClientChunkState {
known: BTreeMap<ChunkId, u32>,
pending: Vec<ChunkId>,
pending_messages: Vec<ChunkMessage>,
}
impl ClientChunkState {
pub fn new() -> Self {
Self {
known: BTreeMap::new(),
pending: Vec::new(),
pending_messages: Vec::new(),
}
}
pub fn request(&mut self, id: ChunkId) {
if !self.known.contains_key(&id) && !self.pending.contains(&id) {
self.pending.push(id);
}
}
pub fn request_vxl_radius(&mut self, pos: Vec3, radius: i32, world: &VoxelWorld) {
let cx = VoxelWorld::world_to_chunk(pos.x);
let cy = VoxelWorld::world_to_chunk(pos.y);
let cz = VoxelWorld::world_to_chunk(pos.z);
for dz in -radius..=radius {
for dy in -radius..=radius {
for dx in -radius..=radius {
if world.get_chunk(cx + dx, cy + dy, cz + dz).is_some() {
self.request(ChunkId::Vxl(cx + dx, cy + dy, cz + dz));
}
}
}
}
}
pub fn next_pending(&mut self) -> Option<ChunkId> {
self.pending.pop()
}
pub fn queue_message(&mut self, msg: ChunkMessage) {
self.pending_messages.push(msg);
}
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
self.pending_messages.extend(msgs);
}
pub fn next_message(&mut self) -> Option<ChunkMessage> {
if self.pending_messages.is_empty() {
None
} else {
Some(self.pending_messages.remove(0))
}
}
pub fn mark_sent(&mut self, id: ChunkId, version: u32) {
self.known.insert(id, version);
}
pub fn has_pending(&self) -> bool {
!self.pending.is_empty() || !self.pending_messages.is_empty()
}
pub fn clear_pending(&mut self) {
self.pending.clear();
self.pending_messages.clear();
}
}
impl Default for ClientChunkState {
fn default() -> Self {
Self::new()
}
}

40
pxl8d/src/lib.rs Normal file
View file

@ -0,0 +1,40 @@
#![no_std]
extern crate alloc;
mod allocator;
pub mod bsp;
pub mod chunk;
pub mod log;
pub mod math;
pub mod procgen;
pub mod sim;
pub mod transport;
pub mod voxel;
pub mod world;
use core::panic::PanicInfo;
#[global_allocator]
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
loop {}
}
#[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 use bsp::*;
pub use chunk::*;
pub use chunk::stream::*;
pub use math::*;
pub use procgen::{ProcgenParams, generate, generate_rooms};
pub use protocol::*;
pub use sim::*;
pub use transport::*;
pub use voxel::*;
pub use world::*;

139
pxl8d/src/log.rs Normal file
View file

@ -0,0 +1,139 @@
use crate::protocol::{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,
};
pub fn init() {
unsafe {
pxl8_log_init(&raw mut G_LOG);
}
}
#[macro_export]
macro_rules! pxl8_debug {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::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,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_error {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::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,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_info {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::pxl8_log_write_info(
c"%s".as_ptr() as *const ::core::ffi::c_char,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_trace {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::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,
buf.as_ptr(),
);
}
}};
}
#[macro_export]
macro_rules! pxl8_warn {
($($arg:tt)*) => {{
use ::core::fmt::Write;
let mut buf = $crate::log::LogBuffer::new();
let _ = ::core::write!(&mut buf, $($arg)*);
buf.push(0);
unsafe {
$crate::protocol::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,
buf.as_ptr(),
);
}
}};
}
pub struct LogBuffer {
buf: [u8; 1024],
pos: usize,
}
impl LogBuffer {
pub const fn new() -> Self {
Self {
buf: [0; 1024],
pos: 0,
}
}
pub fn as_ptr(&self) -> *const c_char {
self.buf.as_ptr() as *const c_char
}
pub fn push(&mut self, b: u8) {
if self.pos < self.buf.len() {
self.buf[self.pos] = b;
self.pos += 1;
}
}
}
impl core::fmt::Write for LogBuffer {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for b in s.bytes() {
if self.pos >= self.buf.len() - 1 {
break;
}
self.buf[self.pos] = b;
self.pos += 1;
}
Ok(())
}
}

325
pxl8d/src/main.rs Normal file
View file

@ -0,0 +1,325 @@
#![no_std]
#![no_main]
extern crate alloc;
use pxl8d::*;
use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState;
const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
#[cfg(unix)]
fn get_time_ns() -> u64 {
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
}
#[cfg(windows)]
fn get_time_ns() -> u64 {
use windows_sys::Win32::System::Performance::*;
static mut FREQ: i64 = 0;
unsafe {
if FREQ == 0 {
QueryPerformanceFrequency(&mut FREQ);
}
let mut count: i64 = 0;
QueryPerformanceCounter(&mut count);
((count as u128 * 1_000_000_000) / FREQ as u128) as u64
}
}
#[cfg(unix)]
fn sleep_ms(ms: u64) {
let ts = libc::timespec {
tv_sec: (ms / 1000) as i64,
tv_nsec: ((ms % 1000) * 1_000_000) as i64,
};
unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
}
#[cfg(windows)]
fn sleep_ms(ms: u64) {
use windows_sys::Win32::System::Threading::Sleep;
unsafe { Sleep(ms as u32) };
}
fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
(x, y, z, yaw, pitch)
}
#[unsafe(no_mangle)]
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
log::init();
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
Some(t) => {
pxl8_info!("[SERVER] Bound to port 7777");
t
}
None => {
pxl8_error!("[SERVER] Failed to bind to port 7777");
return 1;
}
};
let mut sim = Simulation::new();
let mut player_id: Option<u64> = None;
let mut last_client_tick: u64 = 0;
let mut client_chunks = ClientChunkState::new();
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
let mut entities_buf = [protocol::pxl8_entity_state {
entity_id: 0,
userdata: [0u8; 56],
}; 64];
let mut inputs_buf: [protocol::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
pxl8_debug!("[SERVER] Entering main loop");
loop {
let now = get_time_ns();
let elapsed = now.saturating_sub(last_tick);
if elapsed >= TICK_NS {
last_tick = now;
let dt = (elapsed as f32) / 1_000_000_000.0;
let mut latest_input: Option<protocol::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 => {
latest_input = Some(transport.get_input());
}
x if x == protocol::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 {
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
player_id = Some(sim.spawn_player(x, y, z) as u64);
pxl8_debug!("[SERVER] Spawn command received, generating BSP...");
sim.world.generate_bsp(1, None);
sim.world.set_active(ChunkId::Bsp(1));
client_chunks.request(ChunkId::Bsp(1));
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);
}
}
_ => {}
}
}
if let Some(input) = latest_input {
last_client_tick = input.tick;
inputs_buf[0] = input;
sim.step(&inputs_buf[..1], dt);
} else {
sim.step(&[], dt);
}
let mut count = 0;
sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| {
if count < entities_buf.len() {
entities_buf[count] = *state;
count += 1;
}
});
let header = protocol::pxl8_snapshot_header {
entity_count: count as u16,
event_count: 0,
player_id: player_id.unwrap_or(0),
tick: last_client_tick,
time: sim.time,
};
transport.send_snapshot(&header, &entities_buf[..count], sequence);
sequence = sequence.wrapping_add(1);
if let Some(pid) = player_id {
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);
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, 1);
}
}
}
}
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);
}
}
}
}
}
}
}
}
sleep_ms(1);
}
}
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
use alloc::vec::Vec;
pxl8_debug!("[SERVER] bsp_to_messages: serializing BSP");
let mut data = Vec::new();
let num_verts = bsp.vertices.len();
let num_edges = bsp.edges.len();
let num_faces = bsp.faces.len();
let num_planes = bsp.planes.len();
let num_nodes = bsp.nodes.len();
let num_leafs = bsp.leafs.len();
let num_surfedges = bsp.surfedges.len();
data.extend_from_slice(&(num_verts as u32).to_be_bytes());
data.extend_from_slice(&(num_edges as u32).to_be_bytes());
data.extend_from_slice(&(num_faces as u32).to_be_bytes());
data.extend_from_slice(&(num_planes as u32).to_be_bytes());
data.extend_from_slice(&(num_nodes as u32).to_be_bytes());
data.extend_from_slice(&(num_leafs as u32).to_be_bytes());
data.extend_from_slice(&(num_surfedges as u32).to_be_bytes());
data.extend_from_slice(&(bsp.marksurfaces.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.cell_portals.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.visdata.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes());
for v in &bsp.vertices {
data.extend_from_slice(&v.position.x.to_be_bytes());
data.extend_from_slice(&v.position.y.to_be_bytes());
data.extend_from_slice(&v.position.z.to_be_bytes());
}
for e in &bsp.edges {
data.extend_from_slice(&e.vertex[0].to_be_bytes());
data.extend_from_slice(&e.vertex[1].to_be_bytes());
}
for se in &bsp.surfedges {
data.extend_from_slice(&se.to_be_bytes());
}
for p in &bsp.planes {
data.extend_from_slice(&p.normal.x.to_be_bytes());
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());
}
for f in &bsp.faces {
data.extend_from_slice(&f.first_edge.to_be_bytes());
data.extend_from_slice(&f.lightmap_offset.to_be_bytes());
data.extend_from_slice(&f.num_edges.to_be_bytes());
data.extend_from_slice(&f.plane_id.to_be_bytes());
data.extend_from_slice(&f.side.to_be_bytes());
data.extend_from_slice(&f.styles);
data.extend_from_slice(&f.material_id.to_be_bytes());
data.extend_from_slice(&f.aabb_min.x.to_be_bytes());
data.extend_from_slice(&f.aabb_min.y.to_be_bytes());
data.extend_from_slice(&f.aabb_min.z.to_be_bytes());
data.extend_from_slice(&f.aabb_max.x.to_be_bytes());
data.extend_from_slice(&f.aabb_max.y.to_be_bytes());
data.extend_from_slice(&f.aabb_max.z.to_be_bytes());
}
for n in &bsp.nodes {
data.extend_from_slice(&n.children[0].to_be_bytes());
data.extend_from_slice(&n.children[1].to_be_bytes());
data.extend_from_slice(&n.first_face.to_be_bytes());
data.extend_from_slice(&n.maxs[0].to_be_bytes());
data.extend_from_slice(&n.maxs[1].to_be_bytes());
data.extend_from_slice(&n.maxs[2].to_be_bytes());
data.extend_from_slice(&n.mins[0].to_be_bytes());
data.extend_from_slice(&n.mins[1].to_be_bytes());
data.extend_from_slice(&n.mins[2].to_be_bytes());
data.extend_from_slice(&n.num_faces.to_be_bytes());
data.extend_from_slice(&n.plane_id.to_be_bytes());
}
for l in &bsp.leafs {
data.extend_from_slice(&l.ambient_level);
data.extend_from_slice(&l.contents.to_be_bytes());
data.extend_from_slice(&l.first_marksurface.to_be_bytes());
data.extend_from_slice(&l.maxs[0].to_be_bytes());
data.extend_from_slice(&l.maxs[1].to_be_bytes());
data.extend_from_slice(&l.maxs[2].to_be_bytes());
data.extend_from_slice(&l.mins[0].to_be_bytes());
data.extend_from_slice(&l.mins[1].to_be_bytes());
data.extend_from_slice(&l.mins[2].to_be_bytes());
data.extend_from_slice(&l.num_marksurfaces.to_be_bytes());
data.extend_from_slice(&l.visofs.to_be_bytes());
}
for ms in &bsp.marksurfaces {
data.extend_from_slice(&ms.to_be_bytes());
}
for cp in &bsp.cell_portals {
data.push(cp.num_portals);
data.push(0);
data.push(0);
data.push(0);
for i in 0..4 {
data.extend_from_slice(&cp.portals[i].x0.to_be_bytes());
data.extend_from_slice(&cp.portals[i].z0.to_be_bytes());
data.extend_from_slice(&cp.portals[i].x1.to_be_bytes());
data.extend_from_slice(&cp.portals[i].z1.to_be_bytes());
data.extend_from_slice(&cp.portals[i].target_leaf.to_be_bytes());
}
}
data.extend_from_slice(&bsp.visdata);
for vl in &bsp.vertex_lights {
data.extend_from_slice(&vl.to_be_bytes());
}
transport::ChunkMessage::from_bsp(data, id, version)
}

42
pxl8d/src/math.rs Normal file
View file

@ -0,0 +1,42 @@
use core::ops::{Add, Mul, Sub};
#[derive(Clone, Copy, Default)]
pub struct Vec3 {
pub x: f32,
pub y: f32,
pub z: 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 {
Self { x, y, z }
}
pub fn dot(self, rhs: Self) -> f32 {
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
}
}
impl Add for Vec3 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
}
}
impl Sub for Vec3 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
}
}
impl Mul<f32> for Vec3 {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self::new(self.x * rhs, self.y * rhs, self.z * rhs)
}
}

806
pxl8d/src/procgen.rs Normal file
View file

@ -0,0 +1,806 @@
extern crate alloc;
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;
pub const CELL_SIZE: f32 = 64.0;
pub const WALL_HEIGHT: f32 = 128.0;
pub const PVS_MAX_DEPTH: u32 = 64;
#[derive(Clone, Copy)]
pub struct LightSource {
pub position: Vec3,
pub intensity: f32,
pub radius: f32,
}
#[derive(Clone, Copy)]
pub struct ProcgenParams {
pub width: i32,
pub height: i32,
pub seed: u32,
pub min_room_size: i32,
pub max_room_size: i32,
pub num_rooms: i32,
}
impl Default for ProcgenParams {
fn default() -> Self {
Self {
width: 16,
height: 16,
seed: 12345,
min_room_size: 3,
max_room_size: 6,
num_rooms: 8,
}
}
}
struct RoomGrid {
cells: Vec<u8>,
width: i32,
height: i32,
}
impl RoomGrid {
fn new(width: i32, height: i32) -> Self {
Self {
cells: vec![0; (width * height) as usize],
width,
height,
}
}
fn get(&self, x: i32, y: i32) -> u8 {
if x < 0 || x >= self.width || y < 0 || y >= self.height {
return 1;
}
self.cells[(y * self.width + x) as usize]
}
fn set(&mut self, x: i32, y: i32, value: u8) {
if x >= 0 && x < self.width && y >= 0 && y < self.height {
self.cells[(y * self.width + x) as usize] = value;
}
}
fn fill(&mut self, value: u8) {
for cell in &mut self.cells {
*cell = value;
}
}
}
struct Rng {
state: u32,
}
impl Rng {
fn new(seed: u32) -> Self {
Self { state: seed }
}
fn next(&mut self) -> u32 {
self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345);
(self.state >> 16) & 0x7FFF
}
}
#[derive(Clone, Copy)]
struct Bounds {
x: i32,
y: i32,
w: i32,
h: i32,
}
impl Bounds {
fn intersects(&self, other: &Bounds) -> bool {
!(self.x + self.w <= other.x || other.x + other.w <= self.x ||
self.y + self.h <= other.y || other.y + other.h <= self.y)
}
}
struct BspBuildContext<'a> {
bsp: &'a mut Bsp,
grid: &'a RoomGrid,
node_count: u32,
plane_offset: u32,
}
fn carve_corridor_h(grid: &mut RoomGrid, x1: i32, x2: i32, y: i32) {
let start = x1.min(x2);
let end = x1.max(x2);
for x in start..=end {
grid.set(x, y, 0);
grid.set(x, y - 1, 0);
grid.set(x, y + 1, 0);
}
}
fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) {
let start = y1.min(y2);
let end = y1.max(y2);
for y in start..=end {
grid.set(x, y, 0);
grid.set(x - 1, y, 0);
grid.set(x + 1, y, 0);
}
}
fn compute_face_aabb(face: &mut BspFace, verts: &[BspVertex], vert_idx: usize) {
face.aabb_min = Vec3::new(1e30, 1e30, 1e30);
face.aabb_max = Vec3::new(-1e30, -1e30, -1e30);
for i in 0..4 {
let v = verts[vert_idx + i].position;
if v.x < face.aabb_min.x { face.aabb_min.x = v.x; }
if v.x > face.aabb_max.x { face.aabb_max.x = v.x; }
if v.y < face.aabb_min.y { face.aabb_min.y = v.y; }
if v.y > face.aabb_max.y { face.aabb_max.y = v.y; }
if v.z < face.aabb_min.z { face.aabb_min.z = v.z; }
if v.z > face.aabb_max.z { face.aabb_max.z = v.z; }
}
}
fn build_bsp_node(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);
}
let node_idx = ctx.node_count;
ctx.node_count += 1;
let plane_idx = ctx.plane_offset;
ctx.plane_offset += 1;
if depth % 2 == 0 {
let mid_x = (x0 + x1) / 2;
let split_pos = mid_x as f32 * CELL_SIZE;
ctx.bsp.planes[plane_idx as usize] = BspPlane {
normal: Vec3::new(1.0, 0.0, 0.0),
dist: split_pos,
plane_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);
ctx.bsp.nodes[node_idx as usize] = BspNode {
plane_id: plane_idx,
children: [child0, child1],
..Default::default()
};
} else {
let mid_y = (y0 + y1) / 2;
let split_pos = mid_y as f32 * CELL_SIZE;
ctx.bsp.planes[plane_idx as usize] = BspPlane {
normal: Vec3::new(0.0, 0.0, 1.0),
dist: split_pos,
plane_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);
ctx.bsp.nodes[node_idx as usize] = BspNode {
plane_id: plane_idx,
children: [child0, child1],
..Default::default()
};
}
node_idx as i32
}
fn build_cell_portals(grid: &RoomGrid) -> Vec<BspCellPortals> {
let total_cells = (grid.width * grid.height) as usize;
let mut portals = vec![BspCellPortals::default(); total_cells];
for y in 0..grid.height {
for x in 0..grid.width {
if grid.get(x, y) != 0 {
continue;
}
let c = (y * grid.width + x) as usize;
let cx = x as f32 * CELL_SIZE;
let cz = y as f32 * CELL_SIZE;
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 {
x0: cx,
z0: cz,
x1: cx,
z1: cz + CELL_SIZE,
target_leaf: (y * grid.width + (x - 1)) as u32,
};
p.num_portals += 1;
}
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 {
x0: cx + CELL_SIZE,
z0: cz + CELL_SIZE,
x1: cx + CELL_SIZE,
z1: cz,
target_leaf: (y * grid.width + (x + 1)) as u32,
};
p.num_portals += 1;
}
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 {
x0: cx + CELL_SIZE,
z0: cz,
x1: cx,
z1: cz,
target_leaf: ((y - 1) * grid.width + x) as u32,
};
p.num_portals += 1;
}
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 {
x0: cx,
z0: cz + CELL_SIZE,
x1: cx + CELL_SIZE,
z1: cz + CELL_SIZE,
target_leaf: ((y + 1) * grid.width + x) as u32,
};
p.num_portals += 1;
}
}
}
portals
}
#[derive(Clone, Copy)]
struct FloodEntry {
leaf: u32,
depth: u32,
}
fn portal_flood_bfs(
start_leaf: u32,
portals: &[BspCellPortals],
leafs: &[BspLeaf],
pvs: &mut [u8],
num_leafs: u32,
) {
let pvs_bytes = ((num_leafs + 7) / 8) as usize;
let mut visited = vec![0u8; pvs_bytes];
let mut queue = Vec::with_capacity(num_leafs as usize);
pvs[(start_leaf >> 3) as usize] |= 1 << (start_leaf & 7);
visited[(start_leaf >> 3) as usize] |= 1 << (start_leaf & 7);
queue.push(FloodEntry { leaf: start_leaf, depth: 0 });
let mut head = 0;
while head < queue.len() {
let e = queue[head];
head += 1;
if e.depth >= PVS_MAX_DEPTH {
continue;
}
if leafs[e.leaf as usize].contents == -1 {
continue;
}
let cp = &portals[e.leaf as usize];
for i in 0..cp.num_portals {
let target = cp.portals[i as usize].target_leaf;
let byte = (target >> 3) as usize;
let bit = target & 7;
if visited[byte] & (1 << bit) != 0 {
continue;
}
visited[byte] |= 1 << bit;
if leafs[target as usize].contents == -1 {
continue;
}
pvs[byte] |= 1 << bit;
queue.push(FloodEntry { leaf: target, depth: e.depth + 1 });
}
}
}
fn compute_leaf_pvs(
start_leaf: u32,
portals: &[BspCellPortals],
leafs: &[BspLeaf],
num_leafs: u32,
) -> Vec<u8> {
let pvs_bytes = ((num_leafs + 7) / 8) as usize;
let mut pvs = vec![0u8; pvs_bytes];
portal_flood_bfs(start_leaf, portals, leafs, &mut pvs, num_leafs);
pvs
}
fn rle_compress_pvs(pvs: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
let mut i = 0;
while i < pvs.len() {
if pvs[i] != 0 {
out.push(pvs[i]);
i += 1;
} else {
let mut count = 0u8;
while i < pvs.len() && pvs[i] == 0 && count < 255 {
count += 1;
i += 1;
}
out.push(0);
out.push(count);
}
}
out
}
fn build_pvs_data(bsp: &mut Bsp, portals: &[BspCellPortals]) {
let num_leafs = bsp.leafs.len() as u32;
let mut visdata = Vec::new();
for leaf in 0..num_leafs {
if bsp.leafs[leaf as usize].contents == -1 {
bsp.leafs[leaf as usize].visofs = -1;
continue;
}
let pvs = compute_leaf_pvs(leaf, portals, &bsp.leafs, num_leafs);
let compressed = rle_compress_pvs(&pvs);
bsp.leafs[leaf as usize].visofs = visdata.len() as i32;
visdata.extend_from_slice(&compressed);
}
bsp.visdata = visdata;
}
fn compute_vertex_light(
pos: Vec3,
normal: Vec3,
lights: &[LightSource],
ambient: f32,
) -> f32 {
let mut total = ambient;
for light in lights {
let to_light = Vec3::new(
light.position.x - pos.x,
light.position.y - pos.y,
light.position.z - pos.z,
);
let dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z;
let dist = sqrtf(dist_sq).max(1.0);
if dist > light.radius {
continue;
}
let inv_dist = 1.0 / dist;
let light_dir = Vec3::new(
to_light.x * inv_dist,
to_light.y * inv_dist,
to_light.z * inv_dist,
);
let ndotl = (normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z).max(0.0);
let attenuation = (1.0 - dist / light.radius).max(0.0);
let attenuation = attenuation * attenuation;
total += light.intensity * ndotl * attenuation;
}
total.min(1.0)
}
fn compute_bsp_vertex_lighting(bsp: &mut Bsp, lights: &[LightSource], ambient: f32) {
if bsp.vertices.is_empty() {
return;
}
bsp.vertex_lights = vec![0u32; bsp.vertices.len()];
for f in 0..bsp.faces.len() {
let face = &bsp.faces[f];
let normal = if (face.plane_id as usize) < bsp.planes.len() {
bsp.planes[face.plane_id as usize].normal
} else {
Vec3::new(0.0, 1.0, 0.0)
};
for e in 0..face.num_edges {
let surfedge_idx = (face.first_edge + e as u32) as usize;
if surfedge_idx >= bsp.surfedges.len() {
continue;
}
let edge_idx = bsp.surfedges[surfedge_idx];
let vert_idx = if edge_idx >= 0 {
let ei = edge_idx as usize;
if ei >= bsp.edges.len() { continue; }
bsp.edges[ei].vertex[0] as usize
} else {
let ei = (-edge_idx) as usize;
if ei >= bsp.edges.len() { continue; }
bsp.edges[ei].vertex[1] as usize
};
if vert_idx >= bsp.vertices.len() {
continue;
}
let pos = bsp.vertices[vert_idx].position;
let light = compute_vertex_light(pos, normal, lights, ambient);
let light_byte = (light * 255.0) as u8;
bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF;
}
}
}
fn grid_to_bsp(bsp: &mut Bsp, grid: &RoomGrid) {
let mut face_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; }
floor_ceiling_count += 1;
}
}
}
face_count += 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;
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.surfedges = vec![0i32; vertex_count];
bsp.nodes = vec![BspNode::default(); max_nodes];
let mut face_cell = vec![0u32; face_count];
let mut vert_idx = 0usize;
let mut face_idx = 0usize;
let mut edge_idx = 0usize;
for y in 0..grid.height {
for x in 0..grid.width {
if grid.get(x, y) == 0 {
let fx = x as f32 * CELL_SIZE;
let fy = y as f32 * CELL_SIZE;
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 + 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, 0.0, 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 = 0;
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;
}
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 + 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);
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 = 0;
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;
}
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 + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx, WALL_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 = 0;
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;
}
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 + 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, 0.0, 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 = 0;
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;
}
}
}
}
for y in 0..grid.height {
for x in 0..grid.width {
if grid.get(x, y) == 0 {
let fx = x as f32 * CELL_SIZE;
let fy = y as f32 * CELL_SIZE;
let cell_idx = (y * grid.width + x) as u32;
bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy);
bsp.vertices[vert_idx + 1].position = Vec3::new(fx, 0.0, fy + CELL_SIZE);
bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE);
bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, 0.0, fy);
bsp.planes[face_idx].normal = Vec3::new(0.0, 1.0, 0.0);
bsp.planes[face_idx].dist = 0.0;
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;
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.truncate(vert_idx);
bsp.faces.truncate(face_idx);
bsp.edges.truncate(edge_idx);
bsp.surfedges.truncate(edge_idx);
bsp.leafs = vec![BspLeaf::default(); total_cells];
bsp.marksurfaces = vec![0u16; face_idx];
let mut faces_per_cell = vec![0u32; total_cells];
let mut cell_offset = vec![0u32; total_cells];
let mut cell_cursor = vec![0u32; total_cells];
for i in 0..face_idx {
faces_per_cell[face_cell[i] as usize] += 1;
}
let mut offset = 0u32;
for c in 0..total_cells {
cell_offset[c] = offset;
offset += faces_per_cell[c];
}
for i in 0..face_idx {
let c = face_cell[i] as usize;
bsp.marksurfaces[(cell_offset[c] + cell_cursor[c]) as usize] = i as u16;
cell_cursor[c] += 1;
}
for y in 0..grid.height {
for x in 0..grid.width {
let c = (y * grid.width + x) as usize;
let leaf = &mut bsp.leafs[c];
let fx = x as f32 * CELL_SIZE;
let fz = y as f32 * CELL_SIZE;
leaf.mins[0] = fx as i16;
leaf.mins[1] = 0;
leaf.mins[2] = fz as i16;
leaf.maxs[0] = (fx + CELL_SIZE) as i16;
leaf.maxs[1] = WALL_HEIGHT as i16;
leaf.maxs[2] = (fz + CELL_SIZE) as i16;
if grid.get(x, y) == 0 {
leaf.contents = -2;
leaf.first_marksurface = cell_offset[c] as u16;
leaf.num_marksurfaces = faces_per_cell[c] as u16;
} else {
leaf.contents = -1;
leaf.first_marksurface = 0;
leaf.num_marksurfaces = 0;
}
leaf.visofs = -1;
}
}
let face_count_final = face_idx;
let mut ctx = BspBuildContext {
bsp,
grid,
node_count: 0,
plane_offset: face_count_final as u32,
};
build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0);
let node_count = ctx.node_count as usize;
let plane_count = ctx.plane_offset as usize;
ctx.bsp.nodes.truncate(node_count);
ctx.bsp.planes.truncate(plane_count);
let portals = build_cell_portals(grid);
build_pvs_data(ctx.bsp, &portals);
ctx.bsp.cell_portals = portals;
}
pub fn generate_rooms(params: &ProcgenParams) -> Bsp {
let mut rng = Rng::new(params.seed);
let mut grid = RoomGrid::new(params.width, params.height);
grid.fill(1);
let mut rooms: Vec<Bounds> = Vec::new();
let max_attempts = params.num_rooms * 10;
for _ in 0..max_attempts {
if rooms.len() >= params.num_rooms as usize {
break;
}
let w = params.min_room_size + (rng.next() % (params.max_room_size - params.min_room_size + 1) as u32) as i32;
let h = params.min_room_size + (rng.next() % (params.max_room_size - params.min_room_size + 1) as u32) as i32;
let x = 1 + (rng.next() % (params.width - w - 2) as u32) as i32;
let y = 1 + (rng.next() % (params.height - h - 2) as u32) as i32;
let new_room = Bounds { x, y, w, h };
let overlaps = rooms.iter().any(|r| new_room.intersects(r));
if !overlaps {
for ry in y..(y + h) {
for rx in x..(x + w) {
grid.set(rx, ry, 0);
}
}
if !rooms.is_empty() {
let prev = rooms.last().unwrap();
let new_cx = x + w / 2;
let new_cy = y + h / 2;
let prev_cx = prev.x + prev.w / 2;
let prev_cy = prev.y + prev.h / 2;
if rng.next() % 2 == 0 {
carve_corridor_h(&mut grid, prev_cx, new_cx, prev_cy);
carve_corridor_v(&mut grid, prev_cy, new_cy, new_cx);
} else {
carve_corridor_v(&mut grid, prev_cy, new_cy, prev_cx);
carve_corridor_h(&mut grid, prev_cx, new_cx, new_cy);
}
}
rooms.push(new_room);
}
}
let mut bsp = Bsp::new();
grid_to_bsp(&mut bsp, &grid);
let light_height = 80.0;
let lights: Vec<LightSource> = rooms.iter().map(|room| {
let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE;
let cy = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE;
LightSource {
position: Vec3::new(cx, light_height, cy),
intensity: 0.8,
radius: 300.0,
}
}).collect();
compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1);
bsp
}
pub fn generate(params: &ProcgenParams) -> Bsp {
generate_rooms(params)
}

277
pxl8d/src/sim.rs Normal file
View file

@ -0,0 +1,277 @@
extern crate alloc;
use alloc::vec::Vec;
use libm::{cosf, sinf, sqrtf};
use crate::math::Vec3;
use crate::protocol::*;
use crate::voxel::VoxelWorld;
use crate::world::World;
const ALIVE: u32 = 1 << 0;
const PLAYER: u32 = 1 << 1;
const GROUNDED: u32 = 1 << 2;
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,
yaw: 0.0,
pitch: 0.0,
}
}
}
pub struct Simulation {
pub entities: Vec<Entity>,
pub free_list: Vec<u32>,
pub player: Option<u32>,
pub tick: u64,
pub time: f32,
pub voxels: VoxelWorld,
pub world: World,
}
impl Simulation {
pub fn new() -> Self {
let mut entities = Vec::with_capacity(MAX_ENTITIES);
entities.resize(MAX_ENTITIES, Entity::default());
let mut free_list = Vec::with_capacity(MAX_ENTITIES);
for i in (0..MAX_ENTITIES).rev() {
free_list.push(i as u32);
}
Self {
entities,
free_list,
player: None,
tick: 0,
time: 0.0,
voxels: VoxelWorld::new(12345),
world: World::new(12345),
}
}
pub fn spawn(&mut self) -> Option<u32> {
let idx = self.free_list.pop()?;
self.entities[idx as usize] = Entity {
flags: ALIVE,
..Default::default()
};
Some(idx)
}
pub fn despawn(&mut self, id: u32) {
if (id as usize) < self.entities.len() {
self.entities[id as usize].flags = 0;
self.free_list.push(id);
}
}
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> u32 {
let id = self.spawn().unwrap_or(0);
let ent = &mut self.entities[id as usize];
ent.flags = ALIVE | PLAYER;
ent.pos = Vec3::new(x, y, z);
self.player = Some(id);
self.voxels.load_chunks_around(ent.pos, 2);
id
}
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
self.tick += 1;
self.time += dt;
for input in inputs {
self.move_player(input, dt);
}
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);
}
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 ent = &mut self.entities[i];
ent.vel = vel;
ent.pos = new_pos;
}
}
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];
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 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;
}
}
pub fn generate_snapshot<F>(&self, _player_id: u64, mut writer: F)
where
F: FnMut(&pxl8_entity_state),
{
for (i, ent) in self.entities.iter().enumerate() {
if ent.flags & ALIVE == 0 {
continue;
}
let mut state = pxl8_entity_state {
entity_id: i as u64,
userdata: [0u8; 56],
};
let bytes = &mut state.userdata;
bytes[0..4].copy_from_slice(&ent.pos.x.to_be_bytes());
bytes[4..8].copy_from_slice(&ent.pos.y.to_be_bytes());
bytes[8..12].copy_from_slice(&ent.pos.z.to_be_bytes());
bytes[12..16].copy_from_slice(&ent.yaw.to_be_bytes());
bytes[16..20].copy_from_slice(&ent.pitch.to_be_bytes());
bytes[20..24].copy_from_slice(&ent.vel.x.to_be_bytes());
bytes[24..28].copy_from_slice(&ent.vel.y.to_be_bytes());
bytes[28..32].copy_from_slice(&ent.vel.z.to_be_bytes());
bytes[32..36].copy_from_slice(&ent.flags.to_be_bytes());
bytes[36..38].copy_from_slice(&ent.kind.to_be_bytes());
writer(&state);
}
}
pub fn entity_count(&self) -> usize {
self.entities.iter().filter(|e| e.flags & ALIVE != 0).count()
}
pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> {
let idx = player_id as usize;
if idx < self.entities.len() {
let ent = &self.entities[idx];
if ent.flags & ALIVE != 0 && ent.flags & PLAYER != 0 {
return Some((ent.pos.x, ent.pos.y, ent.pos.z));
}
}
None
}
}
impl Default for Simulation {
fn default() -> Self {
Self::new()
}
}

461
pxl8d/src/transport.rs Normal file
View file

@ -0,0 +1,461 @@
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::protocol::*;
use crate::protocol::pxl8_msg_type::*;
use crate::voxel::VoxelChunk;
pub const DEFAULT_PORT: u16 = 7777;
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
pub const CHUNK_FLAG_RLE: u8 = 0x01;
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
pub const CHUNK_TYPE_VXL: u8 = 0;
pub const CHUNK_TYPE_BSP: u8 = 1;
pub struct ChunkMessage {
pub chunk_type: u8,
pub id: u32,
pub cx: i32,
pub cy: i32,
pub cz: i32,
pub version: u32,
pub flags: u8,
pub fragment_idx: u8,
pub fragment_count: u8,
pub payload: Vec<u8>,
}
impl ChunkMessage {
pub fn from_voxel(chunk: &VoxelChunk, version: u32) -> Vec<ChunkMessage> {
let rle_data = chunk.rle_encode();
let total_size = rle_data.len();
if total_size <= CHUNK_MAX_PAYLOAD {
return vec![ChunkMessage {
chunk_type: CHUNK_TYPE_VXL,
id: 0,
cx: chunk.cx,
cy: chunk.cy,
cz: chunk.cz,
version,
flags: CHUNK_FLAG_RLE | CHUNK_FLAG_FINAL,
fragment_idx: 0,
fragment_count: 1,
payload: rle_data,
}];
}
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
let mut messages = Vec::new();
for i in 0..fragment_count {
let start = i * CHUNK_MAX_PAYLOAD;
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
let is_final = i == fragment_count - 1;
messages.push(ChunkMessage {
chunk_type: CHUNK_TYPE_VXL,
id: 0,
cx: chunk.cx,
cy: chunk.cy,
cz: chunk.cz,
version,
flags: CHUNK_FLAG_RLE | if is_final { CHUNK_FLAG_FINAL } else { 0 },
fragment_idx: i as u8,
fragment_count: fragment_count as u8,
payload: rle_data[start..end].to_vec(),
});
}
messages
}
pub fn from_bsp(data: Vec<u8>, chunk_id: u32, version: u32) -> Vec<ChunkMessage> {
let total_size = data.len();
if total_size <= CHUNK_MAX_PAYLOAD {
return vec![ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id: chunk_id,
cx: 0,
cy: 0,
cz: 0,
version,
flags: CHUNK_FLAG_FINAL,
fragment_idx: 0,
fragment_count: 1,
payload: data,
}];
}
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
let mut messages = Vec::new();
for i in 0..fragment_count {
let start = i * CHUNK_MAX_PAYLOAD;
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
let is_final = i == fragment_count - 1;
messages.push(ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id: chunk_id,
cx: 0,
cy: 0,
cz: 0,
version,
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
fragment_idx: i as u8,
fragment_count: fragment_count as u8,
payload: data[start..end].to_vec(),
});
}
messages
}
}
#[cfg(unix)]
mod sys {
use libc::{c_int, c_void, sockaddr, sockaddr_in, socklen_t};
pub type RawSocket = c_int;
pub const INVALID_SOCKET: RawSocket = -1;
pub fn socket() -> RawSocket {
unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }
}
pub fn bind(sock: RawSocket, port: u16) -> c_int {
let addr = sockaddr_in {
sin_family: libc::AF_INET as u16,
sin_port: port.to_be(),
sin_addr: libc::in_addr { s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() },
sin_zero: [0; 8],
};
unsafe { libc::bind(sock, &addr as *const _ as *const sockaddr, core::mem::size_of::<sockaddr_in>() as socklen_t) }
}
pub fn set_nonblocking(sock: RawSocket) -> c_int {
unsafe {
let flags = libc::fcntl(sock, libc::F_GETFL, 0);
libc::fcntl(sock, libc::F_SETFL, flags | libc::O_NONBLOCK)
}
}
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut sockaddr_in) -> isize {
let mut addr_len = core::mem::size_of::<sockaddr_in>() as socklen_t;
unsafe {
libc::recvfrom(
sock,
buf.as_mut_ptr() as *mut c_void,
buf.len(),
0,
addr as *mut _ as *mut sockaddr,
&mut addr_len,
)
}
}
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &sockaddr_in) -> isize {
unsafe {
libc::sendto(
sock,
buf.as_ptr() as *const c_void,
buf.len(),
0,
addr as *const _ as *const sockaddr,
core::mem::size_of::<sockaddr_in>() as socklen_t,
)
}
}
pub fn close(sock: RawSocket) {
unsafe { libc::close(sock) };
}
}
#[cfg(windows)]
mod sys {
use windows_sys::Win32::Networking::WinSock::*;
pub type RawSocket = SOCKET;
pub const INVALID_SOCKET_VAL: RawSocket = INVALID_SOCKET;
pub fn socket() -> RawSocket {
unsafe { socket(AF_INET as i32, SOCK_DGRAM as i32, 0) }
}
pub fn bind(sock: RawSocket, port: u16) -> i32 {
let addr = SOCKADDR_IN {
sin_family: AF_INET,
sin_port: port.to_be(),
sin_addr: IN_ADDR { S_un: IN_ADDR_0 { S_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be() } },
sin_zero: [0; 8],
};
unsafe { bind(sock, &addr as *const _ as *const SOCKADDR, core::mem::size_of::<SOCKADDR_IN>() as i32) }
}
pub fn set_nonblocking(sock: RawSocket) -> i32 {
let mut nonblocking: u32 = 1;
unsafe { ioctlsocket(sock, FIONBIO as i32, &mut nonblocking) }
}
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut SOCKADDR_IN) -> i32 {
let mut addr_len = core::mem::size_of::<SOCKADDR_IN>() as i32;
unsafe {
recvfrom(
sock,
buf.as_mut_ptr(),
buf.len() as i32,
0,
addr as *mut _ as *mut SOCKADDR,
&mut addr_len,
)
}
}
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &SOCKADDR_IN) -> i32 {
unsafe {
sendto(
sock,
buf.as_ptr(),
buf.len() as i32,
0,
addr as *const _ as *const SOCKADDR,
core::mem::size_of::<SOCKADDR_IN>() as i32,
)
}
}
pub fn close(sock: RawSocket) {
unsafe { closesocket(sock) };
}
}
#[cfg(unix)]
type SockAddr = libc::sockaddr_in;
#[cfg(windows)]
type SockAddr = windows_sys::Win32::Networking::WinSock::SOCKADDR_IN;
pub struct Transport {
client_addr: SockAddr,
has_client: bool,
recv_buf: [u8; 512],
send_buf: [u8; 2048],
socket: sys::RawSocket,
}
impl Transport {
pub fn bind(port: u16) -> Option<Self> {
let sock = sys::socket();
if sock == sys::INVALID_SOCKET {
return None;
}
if sys::bind(sock, port) < 0 {
sys::close(sock);
return None;
}
if sys::set_nonblocking(sock) < 0 {
sys::close(sock);
return None;
}
Some(Self {
client_addr: unsafe { core::mem::zeroed() },
has_client: false,
recv_buf: [0u8; 512],
send_buf: [0u8; 2048],
socket: sock,
})
}
pub fn recv(&mut self) -> Option<u8> {
let mut addr: SockAddr = unsafe { core::mem::zeroed() };
let len = sys::recvfrom(self.socket, &mut self.recv_buf, &mut addr);
if len <= 0 || (len as usize) < size_of::<pxl8_msg_header>() {
return None;
}
self.client_addr = addr;
self.has_client = true;
let header = self.deserialize_header();
Some(header.type_)
}
pub fn get_input(&self) -> pxl8_input_msg {
self.deserialize_input()
}
pub fn get_command(&self) -> pxl8_command_msg {
self.deserialize_command()
}
pub fn send_snapshot(
&mut self,
header: &pxl8_snapshot_header,
entities: &[pxl8_entity_state],
sequence: u32,
) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: PXL8_MSG_SNAPSHOT as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
offset += self.serialize_snapshot_header(header, offset);
for entity in entities {
offset += self.serialize_entity_state(entity, offset);
}
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk(&mut self, msg: &ChunkMessage, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: PXL8_MSG_CHUNK as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
offset += self.serialize_chunk_msg_header(msg, offset);
let payload_len = msg.payload.len().min(self.send_buf.len() - offset);
self.send_buf[offset..offset + payload_len].copy_from_slice(&msg.payload[..payload_len]);
offset += payload_len;
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk_enter(&mut self, chunk_id: u32, chunk_type: u8, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: PXL8_MSG_CHUNK_ENTER as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
let buf = &mut self.send_buf[offset..];
buf[0..4].copy_from_slice(&chunk_id.to_be_bytes());
buf[4] = chunk_type;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
offset += 8;
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;
buf[1] = msg.flags;
buf[2] = msg.fragment_idx;
buf[3] = msg.fragment_count;
buf[4..8].copy_from_slice(&msg.id.to_be_bytes());
buf[8..12].copy_from_slice(&(msg.cx as u32).to_be_bytes());
buf[12..16].copy_from_slice(&(msg.cy as u32).to_be_bytes());
buf[16..20].copy_from_slice(&(msg.cz as u32).to_be_bytes());
buf[20..24].copy_from_slice(&msg.version.to_be_bytes());
let payload_size = msg.payload.len() as u16;
buf[24..26].copy_from_slice(&payload_size.to_be_bytes());
buf[26..28].copy_from_slice(&[0u8; 2]);
28
}
fn serialize_header(&mut self, h: &pxl8_msg_header, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0..4].copy_from_slice(&h.sequence.to_be_bytes());
buf[4..6].copy_from_slice(&h.size.to_be_bytes());
buf[6] = h.type_;
buf[7] = h.version;
8
}
fn serialize_snapshot_header(&mut self, h: &pxl8_snapshot_header, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0..2].copy_from_slice(&h.entity_count.to_be_bytes());
buf[2..4].copy_from_slice(&h.event_count.to_be_bytes());
buf[4..12].copy_from_slice(&h.player_id.to_be_bytes());
buf[12..20].copy_from_slice(&h.tick.to_be_bytes());
buf[20..24].copy_from_slice(&h.time.to_be_bytes());
24
}
fn serialize_entity_state(&mut self, e: &pxl8_entity_state, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0..8].copy_from_slice(&e.entity_id.to_be_bytes());
buf[8..64].copy_from_slice(&e.userdata);
64
}
fn deserialize_header(&self) -> pxl8_msg_header {
let buf = &self.recv_buf;
pxl8_msg_header {
sequence: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
size: u16::from_be_bytes([buf[4], buf[5]]),
type_: buf[6],
version: buf[7],
}
}
fn deserialize_input(&self) -> pxl8_input_msg {
let buf = &self.recv_buf[8..];
pxl8_input_msg {
buttons: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
look_dx: f32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]),
look_dy: f32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]),
move_x: f32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]),
move_y: f32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]),
yaw: f32::from_be_bytes([buf[20], buf[21], buf[22], buf[23]]),
tick: u64::from_be_bytes([buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31]]),
timestamp: u64::from_be_bytes([buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39]]),
}
}
fn deserialize_command(&self) -> pxl8_command_msg {
let buf = &self.recv_buf[8..];
let mut cmd = pxl8_command_msg {
cmd_type: u16::from_be_bytes([buf[0], buf[1]]),
payload: [0u8; 64],
payload_size: u16::from_be_bytes([buf[66], buf[67]]),
tick: u64::from_be_bytes([buf[68], buf[69], buf[70], buf[71], buf[72], buf[73], buf[74], buf[75]]),
};
cmd.payload.copy_from_slice(&buf[2..66]);
cmd
}
}
impl Drop for Transport {
fn drop(&mut self) {
sys::close(self.socket);
}
}

318
pxl8d/src/voxel.rs Normal file
View 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)
}
}

87
pxl8d/src/world.rs Normal file
View file

@ -0,0 +1,87 @@
extern crate alloc;
use alloc::collections::BTreeMap;
use crate::chunk::{Chunk, ChunkId};
use crate::math::Vec3;
use crate::procgen::{ProcgenParams, generate_rooms};
pub struct World {
active: Option<ChunkId>,
chunks: BTreeMap<ChunkId, Chunk>,
seed: u64,
}
impl World {
pub fn new(seed: u64) -> Self {
Self {
chunks: BTreeMap::new(),
active: None,
seed,
}
}
pub fn insert(&mut self, chunk: Chunk) {
let id = chunk.id();
self.chunks.insert(id, chunk);
}
pub fn get(&self, id: &ChunkId) -> Option<&Chunk> {
self.chunks.get(id)
}
pub fn get_mut(&mut self, id: &ChunkId) -> Option<&mut Chunk> {
self.chunks.get_mut(id)
}
pub fn remove(&mut self, id: &ChunkId) -> Option<Chunk> {
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))
}
pub fn active_id(&self) -> Option<ChunkId> {
self.active
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if let Some(chunk) = self.active() {
return chunk.trace(from, to, radius);
}
to
}
pub fn generate_bsp(&mut self, id: u32, params: Option<&ProcgenParams>) -> &Chunk {
let chunk_id = ChunkId::Bsp(id);
if !self.chunks.contains_key(&chunk_id) {
let default_params = ProcgenParams {
seed: (self.seed as u32).wrapping_add(id),
..Default::default()
};
let p = params.unwrap_or(&default_params);
let bsp = generate_rooms(p);
self.chunks.insert(chunk_id, Chunk::Bsp { id, bsp, version: 1 });
}
self.chunks.get(&chunk_id).unwrap()
}
pub fn contains(&self, id: &ChunkId) -> bool {
self.chunks.contains_key(id)
}
pub fn iter(&self) -> impl Iterator<Item = (&ChunkId, &Chunk)> {
self.chunks.iter()
}
}
impl Default for World {
fn default() -> Self {
Self::new(12345)
}
}