278 lines
7.8 KiB
Rust
278 lines
7.8 KiB
Rust
|
|
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()
|
||
|
|
}
|
||
|
|
}
|