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
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()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue