2026-01-25 09:26:30 -06:00
|
|
|
extern crate alloc;
|
|
|
|
|
|
|
|
|
|
use alloc::vec::Vec;
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
use crate::math::{Vec3, Vec3Ext, VEC3_ZERO};
|
|
|
|
|
use crate::pxl8::*;
|
2026-01-25 09:26:30 -06:00
|
|
|
use crate::voxel::VoxelWorld;
|
|
|
|
|
use crate::world::World;
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
pub type Entity = pxl8_sim_entity;
|
2026-01-25 09:26:30 -06:00
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
const ALIVE: u32 = PXL8_SIM_FLAG_ALIVE;
|
|
|
|
|
const PLAYER: u32 = PXL8_SIM_FLAG_PLAYER;
|
2026-01-25 09:26:30 -06:00
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
const MAX_ENTITIES: usize = 1024;
|
2026-01-25 09:26:30 -06:00
|
|
|
|
|
|
|
|
impl Default for Entity {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
2026-01-31 09:31:17 -06:00
|
|
|
pos: VEC3_ZERO,
|
|
|
|
|
vel: VEC3_ZERO,
|
2026-01-25 09:26:30 -06:00
|
|
|
yaw: 0.0,
|
|
|
|
|
pitch: 0.0,
|
2026-01-31 09:31:17 -06:00
|
|
|
flags: 0,
|
|
|
|
|
kind: 0,
|
|
|
|
|
_pad: 0,
|
2026-01-25 09:26:30 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 17:48:25 -06:00
|
|
|
impl Clone for Entity {
|
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
pos: self.pos,
|
|
|
|
|
vel: self.vel,
|
|
|
|
|
yaw: self.yaw,
|
|
|
|
|
pitch: self.pitch,
|
|
|
|
|
flags: self.flags,
|
|
|
|
|
kind: self.kind,
|
|
|
|
|
_pad: self._pad,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 09:26:30 -06:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-25 09:26:30 -06:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
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,
|
|
|
|
|
}
|
2026-01-25 09:26:30 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
let world = self.make_sim_world(ent.pos);
|
2026-01-25 09:26:30 -06:00
|
|
|
let ent = &mut self.entities[i];
|
2026-01-31 09:31:17 -06:00
|
|
|
unsafe {
|
|
|
|
|
pxl8_sim_integrate(ent, &world, dt);
|
|
|
|
|
}
|
2026-01-25 09:26:30 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
|
|
|
|
|
let Some(id) = self.player else { return };
|
2026-01-31 09:31:17 -06:00
|
|
|
let ent = &self.entities[id as usize];
|
2026-01-25 09:26:30 -06:00
|
|
|
if ent.flags & ALIVE == 0 {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
let world = self.make_sim_world(ent.pos);
|
2026-01-25 09:26:30 -06:00
|
|
|
let ent = &mut self.entities[id as usize];
|
2026-01-31 09:31:17 -06:00
|
|
|
unsafe {
|
|
|
|
|
pxl8_sim_move_player(ent, input, &world, dt);
|
2026-01-25 09:26:30 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|