feat(gui): add toolbar widget

feat(gui): add grid_select, toggle, panel, status_bar, image widgets
fix(bsp): fill in exterior cells
This commit is contained in:
asrael 2026-02-27 06:50:49 -06:00
parent 5a565844dd
commit 8d491612ab
63 changed files with 3150 additions and 1686 deletions

View file

@ -1,5 +1,3 @@
extern crate alloc;
use alloc::boxed::Box;
use alloc::vec::Vec;
@ -146,6 +144,7 @@ pub struct Bsp {
pub vertex_lights: Box<[u32]>,
pub vertices: Box<[Vertex]>,
pub visdata: Box<[u8]>,
pub heightfield: Box<[f32]>,
}
#[derive(Default)]
@ -161,6 +160,16 @@ pub struct BspBuilder {
pub vertex_lights: Vec<u32>,
pub vertices: Vec<Vertex>,
pub visdata: Vec<u8>,
pub heightfield: Vec<f32>,
pub heightfield_w: u16,
pub heightfield_h: u16,
pub heightfield_ox: f32,
pub heightfield_oz: f32,
pub heightfield_cell_size: f32,
pub bounds_min_x: f32,
pub bounds_min_z: f32,
pub bounds_max_x: f32,
pub bounds_max_z: f32,
}
impl BspBuilder {
@ -182,6 +191,7 @@ impl From<BspBuilder> for Bsp {
let vertex_lights = b.vertex_lights.into_boxed_slice();
let vertices = b.vertices.into_boxed_slice();
let visdata = b.visdata.into_boxed_slice();
let heightfield = b.heightfield.into_boxed_slice();
let inner = pxl8_bsp {
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
@ -198,6 +208,7 @@ impl From<BspBuilder> for Bsp {
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
heightfield: if heightfield.is_empty() { core::ptr::null_mut() } else { heightfield.as_ptr() as *mut _ },
lightdata_size: 0,
num_cell_portals: cell_portals.len() as u32,
num_edges: edges.len() as u32,
@ -211,7 +222,17 @@ impl From<BspBuilder> for Bsp {
num_surfedges: surfedges.len() as u32,
num_vertex_lights: vertex_lights.len() as u32,
num_vertices: vertices.len() as u32,
num_heightfield: heightfield.len() as u32,
heightfield_ox: b.heightfield_ox,
heightfield_oz: b.heightfield_oz,
heightfield_cell_size: b.heightfield_cell_size,
heightfield_w: b.heightfield_w,
heightfield_h: b.heightfield_h,
visdata_size: visdata.len() as u32,
bounds_min_x: b.bounds_min_x,
bounds_min_z: b.bounds_min_z,
bounds_max_x: b.bounds_max_x,
bounds_max_z: b.bounds_max_z,
};
Self {
@ -227,6 +248,7 @@ impl From<BspBuilder> for Bsp {
vertex_lights,
vertices,
visdata,
heightfield,
}
}
}

View file

@ -1,38 +1,23 @@
extern crate alloc;
pub mod stream;
use crate::bsp::Bsp;
use crate::math::Vec3;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum ChunkId {
Bsp(u32),
Bsp { cx: i32, cz: i32 },
}
pub enum Chunk {
Bsp { id: u32, bsp: Bsp, version: u32 },
Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 },
}
impl Chunk {
pub fn id(&self) -> ChunkId {
match self {
Chunk::Bsp { id, .. } => ChunkId::Bsp(*id),
}
}
pub fn version(&self) -> u32 {
match self {
Chunk::Bsp { version, .. } => *version,
}
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
match self {
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius),
}
}
pub fn as_bsp(&self) -> Option<&Bsp> {
match self {
Chunk::Bsp { bsp, .. } => Some(bsp),

View file

@ -1,5 +1,3 @@
extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
@ -28,11 +26,11 @@ impl ClientChunkState {
}
pub fn next_pending(&mut self) -> Option<ChunkId> {
self.pending.pop()
}
pub fn queue_message(&mut self, msg: ChunkMessage) {
self.pending_messages.push(msg);
if self.pending.is_empty() {
None
} else {
Some(self.pending.remove(0))
}
}
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
@ -50,23 +48,6 @@ impl ClientChunkState {
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();
}
pub fn clear_known_bsp(&mut self) {
self.known.retain(|id, _| !matches!(id, ChunkId::Bsp(_)));
}
pub fn forget(&mut self, id: &ChunkId) {
self.known.remove(id);
}
}
impl Default for ClientChunkState {

View file

@ -31,7 +31,7 @@ pub use bsp::*;
pub use chunk::*;
pub use chunk::stream::*;
pub use math::*;
pub use procgen::{ProcgenParams, generate, generate_rooms};
pub use procgen::generate_chunk;
pub use pxl8::*;
pub use sim::*;
pub use transport::*;

View file

@ -3,9 +3,12 @@
extern crate alloc;
use alloc::vec::Vec;
use pxl8d::*;
use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState;
use pxl8d::pxl8::pxl8_cmd_type::*;
use pxl8d::pxl8::pxl8_msg_type::*;
const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
@ -73,6 +76,9 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
let mut player_id: Option<u64> = None;
let mut last_client_tick: u64 = 0;
let mut client_chunks = ClientChunkState::new();
let mut player_cx: i32 = 0;
let mut player_cz: i32 = 0;
let mut has_sent_initial_enter = false;
let mut sequence: u32 = 0;
let mut last_tick = get_time_ns();
@ -93,52 +99,14 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
let mut latest_input: Option<pxl8d::pxl8_input_msg> = None;
while let Some(msg_type) = transport.recv() {
match msg_type {
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_INPUT as u8 => {
x if x == PXL8_MSG_INPUT as u8 => {
latest_input = Some(transport.get_input());
}
x if x == pxl8d::pxl8_msg_type::PXL8_MSG_COMMAND as u8 => {
x if x == PXL8_MSG_COMMAND as u8 => {
let cmd = transport.get_command();
if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SPAWN_ENTITY as u16 {
if cmd.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);
sim.world.generate_bsp(1, None);
sim.world.set_active(ChunkId::Bsp(1));
client_chunks.request(ChunkId::Bsp(1));
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1);
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_SCENE as u16 {
let chunk_id = u32::from_be_bytes([
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
]);
let pos_x = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]);
let pos_y = f32::from_be_bytes([cmd.payload[12], cmd.payload[13], cmd.payload[14], cmd.payload[15]]);
let pos_z = f32::from_be_bytes([cmd.payload[16], cmd.payload[17], cmd.payload[18], cmd.payload[19]]);
sim.world.clear_active();
client_chunks.clear_pending();
client_chunks.clear_known_bsp();
transport.send_chunk_exit(sequence);
sequence = sequence.wrapping_add(1);
let params = match chunk_id {
2 => Some(ProcgenParams {
width: 20,
height: 20,
seed: 12345u32.wrapping_add(2),
min_room_size: 14,
max_room_size: 16,
num_rooms: 1,
}),
_ => None,
};
sim.world.generate_bsp(chunk_id, params.as_ref());
sim.world.set_active(ChunkId::Bsp(chunk_id));
client_chunks.request(ChunkId::Bsp(chunk_id));
transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence);
sequence = sequence.wrapping_add(1);
sim.teleport_player(pos_x, pos_y, pos_z);
}
}
_ => {}
@ -154,7 +122,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
}
let mut count = 0;
sim.generate_snapshot(player_id.unwrap_or(u64::MAX), |state| {
sim.generate_snapshot(|state| {
if count < entities_buf.len() {
entities_buf[count] = *state;
count += 1;
@ -173,22 +141,46 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
sequence = sequence.wrapping_add(1);
if let Some(pid) = player_id {
if let Some(_player) = sim.get_player_position(pid) {
if let Some((px, _py, pz)) = sim.get_player_position(pid) {
let new_cx = libm::floorf(px / 1024.0) as i32;
let new_cz = libm::floorf(pz / 1024.0) as i32;
for dz in -2..=2_i32 {
for dx in -2..=2_i32 {
let cx = new_cx + dx;
let cz = new_cz + dz;
sim.world.generate_bsp(cx, cz);
client_chunks.request(ChunkId::Bsp { cx, cz });
}
}
if !has_sent_initial_enter || player_cx != new_cx || player_cz != new_cz {
player_cx = new_cx;
player_cz = new_cz;
sim.world.set_active(ChunkId::Bsp { cx: new_cx, cz: new_cz });
transport.send_chunk_enter(new_cx, new_cz, sequence);
sequence = sequence.wrapping_add(1);
has_sent_initial_enter = true;
}
let mut burst = false;
while let Some(chunk_id) = client_chunks.next_pending() {
match chunk_id {
ChunkId::Bsp(id) => {
ChunkId::Bsp { cx, cz } => {
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());
let msgs = bsp_to_messages(bsp, cx, cz, chunk.version());
client_chunks.queue_messages(msgs);
client_chunks.mark_sent(chunk_id, chunk.version());
burst = true;
}
}
}
}
}
for _ in 0..8 {
let send_limit = if burst { 64 } else { 8 };
for _ in 0..send_limit {
if let Some(msg) = client_chunks.next_message() {
transport.send_chunk(&msg, sequence);
sequence = sequence.wrapping_add(1);
@ -202,30 +194,21 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
}
}
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
use alloc::vec::Vec;
fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec<transport::ChunkMessage> {
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.vertices.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.edges.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.faces.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.planes.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.nodes.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.leafs.len() as u32).to_be_bytes());
data.extend_from_slice(&(bsp.surfedges.len() 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());
data.extend_from_slice(&(bsp.heightfield.len() as u32).to_be_bytes());
for v in &bsp.vertices {
data.extend_from_slice(&v.position.x.to_be_bytes());
@ -318,5 +301,23 @@ fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<tra
data.extend_from_slice(&vl.to_be_bytes());
}
transport::ChunkMessage::from_bsp(data, id, version)
if !bsp.heightfield.is_empty() {
for h in &bsp.heightfield {
data.extend_from_slice(&h.to_be_bytes());
}
let c_bsp = bsp.as_c_bsp();
data.extend_from_slice(&c_bsp.heightfield_w.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_h.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_ox.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_oz.to_be_bytes());
data.extend_from_slice(&c_bsp.heightfield_cell_size.to_be_bytes());
}
let c_bsp = bsp.as_c_bsp();
data.extend_from_slice(&c_bsp.bounds_min_x.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_min_z.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_max_x.to_be_bytes());
data.extend_from_slice(&c_bsp.bounds_max_z.to_be_bytes());
transport::ChunkMessage::from_bsp(data, cx, cz, version)
}

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,6 @@
extern crate alloc;
use alloc::vec::Vec;
use crate::chunk::ChunkId;
use crate::math::Vec3;
use crate::pxl8::*;
use crate::world::World;
@ -96,13 +95,6 @@ impl Simulation {
id
}
pub fn teleport_player(&mut self, x: f32, y: f32, z: f32) {
if let Some(id) = self.player {
let ent = &mut self.entities[id as usize];
ent.pos = Vec3::new(x, y, z);
}
}
pub fn step(&mut self, inputs: &[pxl8_input_msg], dt: f32) {
self.tick += 1;
self.time += dt;
@ -114,22 +106,8 @@ impl Simulation {
self.integrate(dt);
}
fn make_sim_world(&self, _pos: Vec3) -> pxl8_sim_world {
if let Some(chunk) = self.world.active() {
if let Some(bsp) = chunk.as_bsp() {
return pxl8_sim_world {
bsp: bsp.as_c_bsp(),
};
}
}
pxl8_sim_world {
bsp: core::ptr::null(),
}
}
fn integrate(&mut self, dt: f32) {
let cfg = pxl8_sim_config {
fn sim_config() -> pxl8_sim_config {
pxl8_sim_config {
move_speed: 180.0,
ground_accel: 10.0,
air_accel: 1.0,
@ -140,7 +118,39 @@ impl Simulation {
player_radius: 16.0,
player_height: 72.0,
max_pitch: 1.5,
}
}
fn make_sim_world(&self, player_pos: Vec3) -> pxl8_sim_world {
let chunk_size: f32 = 16.0 * 64.0;
let center_cx = libm::floorf(player_pos.x / chunk_size) as i32;
let center_cz = libm::floorf(player_pos.z / chunk_size) as i32;
let mut sim = pxl8_sim_world {
chunks: [core::ptr::null(); 9],
center_cx,
center_cz,
chunk_size,
};
for dz in -1..=1i32 {
for dx in -1..=1i32 {
let cx = center_cx + dx;
let cz = center_cz + dz;
let id = ChunkId::Bsp { cx, cz };
if let Some(chunk) = self.world.get(&id) {
if let Some(bsp) = chunk.as_bsp() {
let idx = ((dz + 1) * 3 + (dx + 1)) as usize;
sim.chunks[idx] = bsp.as_c_bsp();
}
}
}
}
sim
}
fn integrate(&mut self, dt: f32) {
let cfg = Self::sim_config();
for i in 0..self.entities.len() {
let ent = &self.entities[i];
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
@ -156,18 +166,7 @@ impl Simulation {
}
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
let cfg = pxl8_sim_config {
move_speed: 180.0,
ground_accel: 10.0,
air_accel: 1.0,
stop_speed: 100.0,
friction: 6.0,
gravity: 800.0,
jump_velocity: 200.0,
player_radius: 16.0,
player_height: 72.0,
max_pitch: 1.5,
};
let cfg = Self::sim_config();
let Some(id) = self.player else { return };
let ent = &self.entities[id as usize];
if ent.flags & ALIVE == 0 {
@ -181,7 +180,7 @@ impl Simulation {
}
}
pub fn generate_snapshot<F>(&self, _player_id: u64, mut writer: F)
pub fn generate_snapshot<F>(&self, mut writer: F)
where
F: FnMut(&pxl8_entity_state),
{
@ -211,10 +210,6 @@ impl Simulation {
}
}
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() {

View file

@ -1,16 +1,18 @@
extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use crate::pxl8::*;
use crate::pxl8::pxl8_msg_type::*;
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_BSP: u8 = 1;
pub fn chunk_hash(cx: i32, cz: i32) -> u32 {
let h = (cx as u32).wrapping_mul(374761393).wrapping_add((cz as u32).wrapping_mul(668265263));
h ^ (h >> 16)
}
pub struct ChunkMessage {
pub chunk_type: u8,
pub id: u32,
@ -25,16 +27,17 @@ pub struct ChunkMessage {
}
impl ChunkMessage {
pub fn from_bsp(data: Vec<u8>, chunk_id: u32, version: u32) -> Vec<ChunkMessage> {
pub fn from_bsp(data: Vec<u8>, cx: i32, cz: i32, version: u32) -> Vec<ChunkMessage> {
let total_size = data.len();
let id = chunk_hash(cx, cz);
if total_size <= CHUNK_MAX_PAYLOAD {
return vec![ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id: chunk_id,
cx: 0,
id,
cx,
cy: 0,
cz: 0,
cz,
version,
flags: CHUNK_FLAG_FINAL,
fragment_idx: 0,
@ -53,10 +56,10 @@ impl ChunkMessage {
messages.push(ChunkMessage {
chunk_type: CHUNK_TYPE_BSP,
id: chunk_id,
cx: 0,
id,
cx,
cy: 0,
cz: 0,
cz,
version,
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
fragment_idx: i as u8,
@ -362,7 +365,7 @@ impl Transport {
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) {
pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) {
if !self.has_client {
return;
}
@ -378,34 +381,13 @@ impl Transport {
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;
buf[0..4].copy_from_slice(&cx.to_be_bytes());
buf[4..8].copy_from_slice(&cz.to_be_bytes());
offset += 8;
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
pub fn send_chunk_exit(&mut self, sequence: u32) {
if !self.has_client {
return;
}
let mut offset = 0;
let msg_header = pxl8_msg_header {
sequence,
size: 0,
type_: PXL8_MSG_CHUNK_EXIT as u8,
version: PXL8_PROTOCOL_VERSION as u8,
};
offset += self.serialize_header(&msg_header, offset);
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
}
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
let buf = &mut self.send_buf[offset..];
buf[0] = msg.chunk_type;

View file

@ -1,15 +1,12 @@
extern crate alloc;
use alloc::collections::BTreeMap;
use crate::chunk::{Chunk, ChunkId};
use crate::math::Vec3;
use crate::procgen::{ProcgenParams, generate_rooms};
use crate::procgen::generate_chunk;
pub struct World {
active: Option<ChunkId>,
chunks: BTreeMap<ChunkId, Chunk>,
seed: u64,
pub seed: u64,
}
impl World {
@ -21,67 +18,26 @@ impl World {
}
}
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 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 set_active(&mut self, id: ChunkId) {
self.active = Some(id);
}
pub fn clear_active(&mut self) {
self.active = None;
}
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
if let Some(chunk) = self.active() {
return chunk.trace(from, to, radius);
}
to
}
pub fn generate_bsp(&mut self, id: u32, params: Option<&ProcgenParams>) -> &Chunk {
let chunk_id = ChunkId::Bsp(id);
pub fn generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk {
let chunk_id = ChunkId::Bsp { cx, cz };
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 });
let bsp = generate_chunk(cx, cz, self.seed);
self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, 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 {