extern crate alloc; use alloc::vec::Vec; use crate::math::{Vec3, Vec3Ext, VEC3_ZERO}; use crate::pxl8::*; use crate::voxel::VoxelWorld; use crate::world::World; pub type Entity = pxl8_sim_entity; const ALIVE: u32 = PXL8_SIM_FLAG_ALIVE; const PLAYER: u32 = PXL8_SIM_FLAG_PLAYER; const MAX_ENTITIES: usize = 1024; impl Default for Entity { fn default() -> Self { Self { pos: VEC3_ZERO, vel: VEC3_ZERO, yaw: 0.0, pitch: 0.0, flags: 0, kind: 0, _pad: 0, } } } pub struct Simulation { pub entities: Vec, pub free_list: Vec, pub player: Option, 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 { 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 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; for input in inputs { self.move_player(input, dt); } 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(), vxl: core::ptr::null(), vxl_cx: 0, vxl_cy: 0, vxl_cz: 0, }; } } let cx = VoxelWorld::world_to_chunk(pos.x); let cy = VoxelWorld::world_to_chunk(pos.y); let cz = VoxelWorld::world_to_chunk(pos.z); if let Some(chunk) = self.voxels.get_chunk(cx, cy, cz) { pxl8_sim_world { bsp: core::ptr::null(), vxl: chunk.chunk as *const _, vxl_cx: cx, vxl_cy: cy, vxl_cz: cz, } } else { pxl8_sim_world { bsp: core::ptr::null(), vxl: core::ptr::null(), vxl_cx: 0, vxl_cy: 0, vxl_cz: 0, } } } fn integrate(&mut self, dt: f32) { for i in 0..self.entities.len() { let ent = &self.entities[i]; if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 { continue; } let world = self.make_sim_world(ent.pos); let ent = &mut self.entities[i]; unsafe { pxl8_sim_integrate(ent, &world, dt); } } } fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) { let Some(id) = self.player else { return }; let ent = &self.entities[id as usize]; if ent.flags & ALIVE == 0 { return; } let world = self.make_sim_world(ent.pos); let ent = &mut self.entities[id as usize]; unsafe { pxl8_sim_move_player(ent, input, &world, dt); } } pub fn generate_snapshot(&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() } }