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