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, 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 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(&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() } }