refactor: decouple sim from framework, remove voxel geometry
This commit is contained in:
parent
c538641ec8
commit
5a565844dd
41 changed files with 477 additions and 2407 deletions
|
|
@ -4,55 +4,38 @@ pub mod stream;
|
|||
|
||||
use crate::bsp::Bsp;
|
||||
use crate::math::Vec3;
|
||||
use crate::pxl8::pxl8_vxl_trace;
|
||||
use crate::voxel::VoxelChunk;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub enum ChunkId {
|
||||
Bsp(u32),
|
||||
Vxl(i32, i32, i32),
|
||||
}
|
||||
|
||||
pub enum Chunk {
|
||||
Bsp { id: u32, bsp: Bsp, version: u32 },
|
||||
Vxl { cx: i32, cy: i32, cz: i32, data: VoxelChunk, version: u32 },
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn id(&self) -> ChunkId {
|
||||
match self {
|
||||
Chunk::Bsp { id, .. } => ChunkId::Bsp(*id),
|
||||
Chunk::Vxl { cx, cy, cz, .. } => ChunkId::Vxl(*cx, *cy, *cz),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(&self) -> u32 {
|
||||
match self {
|
||||
Chunk::Bsp { version, .. } => *version,
|
||||
Chunk::Vxl { version, .. } => *version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
match self {
|
||||
Chunk::Bsp { bsp, .. } => bsp.trace(from, to, radius),
|
||||
Chunk::Vxl { cx, cy, cz, data, .. } => unsafe {
|
||||
pxl8_vxl_trace(data.chunk, *cx, *cy, *cz, from, to, radius)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bsp(&self) -> Option<&Bsp> {
|
||||
match self {
|
||||
Chunk::Bsp { bsp, .. } => Some(bsp),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_vxl(&self) -> Option<&VoxelChunk> {
|
||||
match self {
|
||||
Chunk::Vxl { data, .. } => Some(data),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ use alloc::collections::BTreeMap;
|
|||
use alloc::vec::Vec;
|
||||
|
||||
use crate::chunk::ChunkId;
|
||||
use crate::math::Vec3;
|
||||
use crate::transport::ChunkMessage;
|
||||
use crate::voxel::VoxelWorld;
|
||||
|
||||
pub struct ClientChunkState {
|
||||
known: BTreeMap<ChunkId, u32>,
|
||||
|
|
@ -29,22 +27,6 @@ impl ClientChunkState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn request_vxl_radius(&mut self, pos: Vec3, radius: i32, world: &VoxelWorld) {
|
||||
let cx = VoxelWorld::world_to_chunk(pos.x);
|
||||
let cy = VoxelWorld::world_to_chunk(pos.y);
|
||||
let cz = VoxelWorld::world_to_chunk(pos.z);
|
||||
|
||||
for dz in -radius..=radius {
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
if world.get_chunk(cx + dx, cy + dy, cz + dz).is_some() {
|
||||
self.request(ChunkId::Vxl(cx + dx, cy + dy, cz + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_pending(&mut self) -> Option<ChunkId> {
|
||||
self.pending.pop()
|
||||
}
|
||||
|
|
@ -78,10 +60,6 @@ impl ClientChunkState {
|
|||
self.pending_messages.clear();
|
||||
}
|
||||
|
||||
pub fn clear_known_vxl(&mut self) {
|
||||
self.known.retain(|id, _| !matches!(id, ChunkId::Vxl(_, _, _)));
|
||||
}
|
||||
|
||||
pub fn clear_known_bsp(&mut self) {
|
||||
self.known.retain(|id, _| !matches!(id, ChunkId::Bsp(_)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ pub mod math;
|
|||
pub mod procgen;
|
||||
pub mod sim;
|
||||
pub mod transport;
|
||||
pub mod voxel;
|
||||
pub mod world;
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
|
@ -36,5 +35,4 @@ pub use procgen::{ProcgenParams, generate, generate_rooms};
|
|||
pub use pxl8::*;
|
||||
pub use sim::*;
|
||||
pub use transport::*;
|
||||
pub use voxel::*;
|
||||
pub use world::*;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ extern crate alloc;
|
|||
use pxl8d::*;
|
||||
use pxl8d::chunk::ChunkId;
|
||||
use pxl8d::chunk::stream::ClientChunkState;
|
||||
use pxl8d::math::Vec3;
|
||||
|
||||
const TICK_RATE: u64 = 30;
|
||||
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
||||
|
|
@ -74,7 +73,6 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
|||
let mut player_id: Option<u64> = None;
|
||||
let mut last_client_tick: u64 = 0;
|
||||
let mut client_chunks = ClientChunkState::new();
|
||||
let mut client_stream_radius: i32 = 3;
|
||||
|
||||
let mut sequence: u32 = 0;
|
||||
let mut last_tick = get_time_ns();
|
||||
|
|
@ -84,7 +82,6 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
|||
}; 64];
|
||||
let mut inputs_buf: [pxl8d::pxl8_input_msg; 16] = unsafe { core::mem::zeroed() };
|
||||
|
||||
pxl8_debug!("[SERVER] Entering main loop");
|
||||
loop {
|
||||
let now = get_time_ns();
|
||||
let elapsed = now.saturating_sub(last_tick);
|
||||
|
|
@ -105,53 +102,43 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
|||
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
|
||||
player_id = Some(sim.spawn_player(x, y, z) as u64);
|
||||
|
||||
pxl8_debug!("[SERVER] Spawn command received, generating BSP...");
|
||||
sim.world.generate_bsp(1, None);
|
||||
sim.world.set_active(ChunkId::Bsp(1));
|
||||
client_chunks.request(ChunkId::Bsp(1));
|
||||
pxl8_debug!("[SERVER] Sending CHUNK_ENTER for BSP id=1");
|
||||
transport.send_chunk_enter(1, transport::CHUNK_TYPE_BSP, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
|
||||
pxl8_debug!("[SERVER] Preloading voxel chunks around spawn and door");
|
||||
let spawn_pos = Vec3 { x, y, z };
|
||||
let door_y = sim.voxels.find_surface_y(942.0, 416.0);
|
||||
let door_pos = Vec3 { x: 942.0, y: door_y, z: 416.0 };
|
||||
pxl8_debug!("[SERVER] Door placed at surface y={}", door_y);
|
||||
sim.voxels.load_chunks_around(spawn_pos, client_stream_radius);
|
||||
sim.voxels.load_chunks_around(door_pos, client_stream_radius);
|
||||
client_chunks.request_vxl_radius(spawn_pos, client_stream_radius, &sim.voxels);
|
||||
client_chunks.request_vxl_radius(door_pos, client_stream_radius, &sim.voxels);
|
||||
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_EXIT_CHUNK as u16 {
|
||||
sim.world.clear_active();
|
||||
client_chunks.clear_pending();
|
||||
client_chunks.clear_known_vxl();
|
||||
client_chunks.clear_known_bsp();
|
||||
transport.send_chunk_exit(sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
let exit_x = f32::from_be_bytes([cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]]);
|
||||
let exit_y = f32::from_be_bytes([cmd.payload[4], cmd.payload[5], cmd.payload[6], cmd.payload[7]]);
|
||||
let exit_z = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]);
|
||||
let exit_pos = Vec3 { x: exit_x, y: exit_y, z: exit_z };
|
||||
sim.teleport_player(exit_x, exit_y, exit_z);
|
||||
sim.voxels.load_chunks_around(exit_pos, client_stream_radius);
|
||||
client_chunks.request_vxl_radius(exit_pos, client_stream_radius, &sim.voxels);
|
||||
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_CHUNK as u16 {
|
||||
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_ENTER_SCENE as u16 {
|
||||
let chunk_id = u32::from_be_bytes([
|
||||
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
|
||||
]);
|
||||
pxl8_debug!("[SERVER] Enter chunk command - entering BSP {}", chunk_id);
|
||||
if sim.world.contains(&ChunkId::Bsp(chunk_id)) {
|
||||
sim.world.set_active(ChunkId::Bsp(chunk_id));
|
||||
client_chunks.request(ChunkId::Bsp(chunk_id));
|
||||
transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
} else if cmd.cmd_type == pxl8d::pxl8_cmd_type::PXL8_CMD_SET_CHUNK_SETTINGS as u16 {
|
||||
let render_dist = i32::from_be_bytes([
|
||||
cmd.payload[0], cmd.payload[1], cmd.payload[2], cmd.payload[3]
|
||||
]);
|
||||
client_stream_radius = render_dist.clamp(1, 8);
|
||||
let pos_x = f32::from_be_bytes([cmd.payload[8], cmd.payload[9], cmd.payload[10], cmd.payload[11]]);
|
||||
let pos_y = f32::from_be_bytes([cmd.payload[12], cmd.payload[13], cmd.payload[14], cmd.payload[15]]);
|
||||
let pos_z = f32::from_be_bytes([cmd.payload[16], cmd.payload[17], cmd.payload[18], cmd.payload[19]]);
|
||||
|
||||
sim.world.clear_active();
|
||||
client_chunks.clear_pending();
|
||||
client_chunks.clear_known_bsp();
|
||||
transport.send_chunk_exit(sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
|
||||
let params = match chunk_id {
|
||||
2 => Some(ProcgenParams {
|
||||
width: 20,
|
||||
height: 20,
|
||||
seed: 12345u32.wrapping_add(2),
|
||||
min_room_size: 14,
|
||||
max_room_size: 16,
|
||||
num_rooms: 1,
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
sim.world.generate_bsp(chunk_id, params.as_ref());
|
||||
sim.world.set_active(ChunkId::Bsp(chunk_id));
|
||||
client_chunks.request(ChunkId::Bsp(chunk_id));
|
||||
transport.send_chunk_enter(chunk_id, transport::CHUNK_TYPE_BSP, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
|
||||
sim.teleport_player(pos_x, pos_y, pos_z);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
|
@ -186,12 +173,7 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
|||
sequence = sequence.wrapping_add(1);
|
||||
|
||||
if let Some(pid) = player_id {
|
||||
if let Some(player) = sim.get_player_position(pid) {
|
||||
let pos = Vec3 { x: player.0, y: player.1, z: player.2 };
|
||||
|
||||
sim.voxels.load_chunks_around(pos, client_stream_radius);
|
||||
client_chunks.request_vxl_radius(pos, client_stream_radius, &sim.voxels);
|
||||
|
||||
if let Some(_player) = sim.get_player_position(pid) {
|
||||
while let Some(chunk_id) = client_chunks.next_pending() {
|
||||
match chunk_id {
|
||||
ChunkId::Bsp(id) => {
|
||||
|
|
@ -203,13 +185,6 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
|||
}
|
||||
}
|
||||
}
|
||||
ChunkId::Vxl(cx, cy, cz) => {
|
||||
if let Some(chunk) = sim.voxels.get_chunk(cx, cy, cz) {
|
||||
let msgs = transport::ChunkMessage::from_voxel(chunk, 1);
|
||||
client_chunks.queue_messages(msgs);
|
||||
client_chunks.mark_sent(chunk_id, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,8 +205,6 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
|||
fn bsp_to_messages(bsp: &bsp::Bsp, id: u32, version: u32) -> alloc::vec::Vec<transport::ChunkMessage> {
|
||||
use alloc::vec::Vec;
|
||||
|
||||
pxl8_debug!("[SERVER] bsp_to_messages: serializing BSP");
|
||||
|
||||
let mut data = Vec::new();
|
||||
|
||||
let num_verts = bsp.vertices.len();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ use alloc::vec::Vec;
|
|||
|
||||
use crate::math::Vec3;
|
||||
use crate::pxl8::*;
|
||||
use crate::voxel::VoxelWorld;
|
||||
use crate::world::World;
|
||||
|
||||
pub type Entity = pxl8_sim_entity;
|
||||
|
|
@ -48,7 +47,6 @@ pub struct Simulation {
|
|||
pub player: Option<u32>,
|
||||
pub tick: u64,
|
||||
pub time: f32,
|
||||
pub voxels: VoxelWorld,
|
||||
pub world: World,
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +66,6 @@ impl Simulation {
|
|||
player: None,
|
||||
tick: 0,
|
||||
time: 0.0,
|
||||
voxels: VoxelWorld::new(12345),
|
||||
world: World::new(12345),
|
||||
}
|
||||
}
|
||||
|
|
@ -96,8 +93,6 @@ impl Simulation {
|
|||
ent.pos = Vec3::new(x, y, z);
|
||||
self.player = Some(id);
|
||||
|
||||
self.voxels.load_chunks_around(ent.pos, 2);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
|
|
@ -119,43 +114,33 @@ impl Simulation {
|
|||
self.integrate(dt);
|
||||
}
|
||||
|
||||
fn make_sim_world(&self, pos: Vec3) -> pxl8_sim_world {
|
||||
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,
|
||||
}
|
||||
pxl8_sim_world {
|
||||
bsp: core::ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
fn integrate(&mut self, dt: f32) {
|
||||
let cfg = pxl8_sim_config {
|
||||
move_speed: 180.0,
|
||||
ground_accel: 10.0,
|
||||
air_accel: 1.0,
|
||||
stop_speed: 100.0,
|
||||
friction: 6.0,
|
||||
gravity: 800.0,
|
||||
jump_velocity: 200.0,
|
||||
player_radius: 16.0,
|
||||
player_height: 72.0,
|
||||
max_pitch: 1.5,
|
||||
};
|
||||
for i in 0..self.entities.len() {
|
||||
let ent = &self.entities[i];
|
||||
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
|
||||
|
|
@ -165,12 +150,24 @@ impl Simulation {
|
|||
let world = self.make_sim_world(ent.pos);
|
||||
let ent = &mut self.entities[i];
|
||||
unsafe {
|
||||
pxl8_sim_integrate(ent, &world, dt);
|
||||
pxl8_sim_integrate(ent, &world, &cfg, dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_player(&mut self, input: &pxl8_input_msg, dt: f32) {
|
||||
let cfg = pxl8_sim_config {
|
||||
move_speed: 180.0,
|
||||
ground_accel: 10.0,
|
||||
air_accel: 1.0,
|
||||
stop_speed: 100.0,
|
||||
friction: 6.0,
|
||||
gravity: 800.0,
|
||||
jump_velocity: 200.0,
|
||||
player_radius: 16.0,
|
||||
player_height: 72.0,
|
||||
max_pitch: 1.5,
|
||||
};
|
||||
let Some(id) = self.player else { return };
|
||||
let ent = &self.entities[id as usize];
|
||||
if ent.flags & ALIVE == 0 {
|
||||
|
|
@ -180,7 +177,7 @@ impl Simulation {
|
|||
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);
|
||||
pxl8_sim_move_player(ent, input, &world, &cfg, dt);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,11 @@ use alloc::vec;
|
|||
use alloc::vec::Vec;
|
||||
use crate::pxl8::*;
|
||||
use crate::pxl8::pxl8_msg_type::*;
|
||||
use crate::voxel::VoxelChunk;
|
||||
|
||||
pub const DEFAULT_PORT: u16 = 7777;
|
||||
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
|
||||
pub const CHUNK_FLAG_RLE: u8 = 0x01;
|
||||
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
|
||||
|
||||
pub const CHUNK_TYPE_VXL: u8 = 0;
|
||||
pub const CHUNK_TYPE_BSP: u8 = 1;
|
||||
|
||||
pub struct ChunkMessage {
|
||||
|
|
@ -28,50 +25,6 @@ pub struct ChunkMessage {
|
|||
}
|
||||
|
||||
impl ChunkMessage {
|
||||
pub fn from_voxel(chunk: &VoxelChunk, version: u32) -> Vec<ChunkMessage> {
|
||||
let rle_data = chunk.rle_encode();
|
||||
let total_size = rle_data.len();
|
||||
|
||||
if total_size <= CHUNK_MAX_PAYLOAD {
|
||||
return vec![ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_VXL,
|
||||
id: 0,
|
||||
cx: chunk.cx,
|
||||
cy: chunk.cy,
|
||||
cz: chunk.cz,
|
||||
version,
|
||||
flags: CHUNK_FLAG_RLE | CHUNK_FLAG_FINAL,
|
||||
fragment_idx: 0,
|
||||
fragment_count: 1,
|
||||
payload: rle_data,
|
||||
}];
|
||||
}
|
||||
|
||||
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
|
||||
let mut messages = Vec::new();
|
||||
|
||||
for i in 0..fragment_count {
|
||||
let start = i * CHUNK_MAX_PAYLOAD;
|
||||
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
|
||||
let is_final = i == fragment_count - 1;
|
||||
|
||||
messages.push(ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_VXL,
|
||||
id: 0,
|
||||
cx: chunk.cx,
|
||||
cy: chunk.cy,
|
||||
cz: chunk.cz,
|
||||
version,
|
||||
flags: CHUNK_FLAG_RLE | if is_final { CHUNK_FLAG_FINAL } else { 0 },
|
||||
fragment_idx: i as u8,
|
||||
fragment_count: fragment_count as u8,
|
||||
payload: rle_data[start..end].to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
|
||||
pub fn from_bsp(data: Vec<u8>, chunk_id: u32, version: u32) -> Vec<ChunkMessage> {
|
||||
let total_size = data.len();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,379 +0,0 @@
|
|||
extern crate alloc;
|
||||
|
||||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
|
||||
use crate::math::Vec3;
|
||||
use crate::pxl8::*;
|
||||
|
||||
const CHUNK_SIZE: i32 = PXL8_VXL_CHUNK_SIZE as i32;
|
||||
const CHUNK_VOLUME: usize = PXL8_VXL_CHUNK_VOLUME as usize;
|
||||
const WORLD_CHUNK_SIZE: f32 = PXL8_VXL_WORLD_CHUNK_SIZE as f32;
|
||||
const AIR: u8 = PXL8_VXL_BLOCK_AIR as u8;
|
||||
const GRASS: u8 = 3;
|
||||
const DIRT: u8 = 2;
|
||||
const STONE: u8 = 1;
|
||||
|
||||
pub struct VoxelChunk {
|
||||
pub chunk: *mut pxl8_vxl_chunk,
|
||||
pub cx: i32,
|
||||
pub cy: i32,
|
||||
pub cz: i32,
|
||||
}
|
||||
|
||||
impl VoxelChunk {
|
||||
pub fn new(cx: i32, cy: i32, cz: i32) -> Self {
|
||||
let chunk = unsafe { pxl8_vxl_chunk_create() };
|
||||
Self { chunk, cx, cy, cz }
|
||||
}
|
||||
|
||||
pub fn get(&self, x: i32, y: i32, z: i32) -> u8 {
|
||||
if self.chunk.is_null() {
|
||||
return AIR;
|
||||
}
|
||||
unsafe { pxl8_vxl_block_get(self.chunk, x, y, z) }
|
||||
}
|
||||
|
||||
pub fn set(&mut self, x: i32, y: i32, z: i32, block: u8) {
|
||||
if self.chunk.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe { pxl8_vxl_block_set(self.chunk, x, y, z, block) }
|
||||
}
|
||||
|
||||
pub fn fill(&mut self, block: u8) {
|
||||
if self.chunk.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe { pxl8_vxl_block_fill(self.chunk, block) }
|
||||
}
|
||||
|
||||
pub fn is_uniform(&self) -> bool {
|
||||
if self.chunk.is_null() {
|
||||
return true;
|
||||
}
|
||||
unsafe { pxl8_vxl_chunk_is_uniform(self.chunk) }
|
||||
}
|
||||
|
||||
pub fn rle_encode(&self) -> Vec<u8> {
|
||||
if self.chunk.is_null() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut linear = vec![0u8; CHUNK_VOLUME];
|
||||
unsafe {
|
||||
pxl8_vxl_chunk_linearize(self.chunk, linear.as_mut_ptr());
|
||||
}
|
||||
|
||||
let mut result = Vec::new();
|
||||
let mut i = 0;
|
||||
|
||||
while i < CHUNK_VOLUME {
|
||||
let block = linear[i];
|
||||
let mut run_len = 1usize;
|
||||
|
||||
while i + run_len < CHUNK_VOLUME
|
||||
&& linear[i + run_len] == block
|
||||
&& run_len < 256
|
||||
{
|
||||
run_len += 1;
|
||||
}
|
||||
|
||||
result.push(block);
|
||||
result.push((run_len - 1) as u8);
|
||||
i += run_len;
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VoxelChunk {
|
||||
fn drop(&mut self) {
|
||||
if !self.chunk.is_null() {
|
||||
unsafe { pxl8_vxl_chunk_destroy(self.chunk) };
|
||||
self.chunk = ptr::null_mut();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for VoxelChunk {
|
||||
fn clone(&self) -> Self {
|
||||
let new_chunk = Self::new(self.cx, self.cy, self.cz);
|
||||
for z in 0..CHUNK_SIZE {
|
||||
for y in 0..CHUNK_SIZE {
|
||||
for x in 0..CHUNK_SIZE {
|
||||
let block = self.get(x, y, z);
|
||||
if block != AIR {
|
||||
unsafe {
|
||||
pxl8_vxl_block_set(new_chunk.chunk, x, y, z, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
new_chunk
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VoxelWorld {
|
||||
pub chunks: Vec<VoxelChunk>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
impl VoxelWorld {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self {
|
||||
chunks: Vec::new(),
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_chunk(&self, cx: i32, cy: i32, cz: i32) -> Option<&VoxelChunk> {
|
||||
self.chunks.iter().find(|c| c.cx == cx && c.cy == cy && c.cz == cz)
|
||||
}
|
||||
|
||||
pub fn get_chunk_mut(&mut self, cx: i32, cy: i32, cz: i32) -> Option<&mut VoxelChunk> {
|
||||
self.chunks.iter_mut().find(|c| c.cx == cx && c.cy == cy && c.cz == cz)
|
||||
}
|
||||
|
||||
pub fn ensure_chunk(&mut self, cx: i32, cy: i32, cz: i32) -> &mut VoxelChunk {
|
||||
if self.get_chunk(cx, cy, cz).is_none() {
|
||||
let mut chunk = VoxelChunk::new(cx, cy, cz);
|
||||
generate_chunk(&mut chunk, self.seed);
|
||||
self.chunks.push(chunk);
|
||||
}
|
||||
self.get_chunk_mut(cx, cy, cz).unwrap()
|
||||
}
|
||||
|
||||
pub fn world_to_chunk(x: f32) -> i32 {
|
||||
libm::floorf(x / WORLD_CHUNK_SIZE) as i32
|
||||
}
|
||||
|
||||
pub fn chunks_near(&self, pos: Vec3, radius: i32) -> Vec<(i32, i32, i32)> {
|
||||
let cx = Self::world_to_chunk(pos.x);
|
||||
let cy = Self::world_to_chunk(pos.y);
|
||||
let cz = Self::world_to_chunk(pos.z);
|
||||
|
||||
let mut result = Vec::new();
|
||||
for dz in -radius..=radius {
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
result.push((cx + dx, cy + dy, cz + dz));
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn load_chunks_around(&mut self, pos: Vec3, radius: i32) {
|
||||
let coords = self.chunks_near(pos, radius);
|
||||
for (cx, cy, cz) in coords {
|
||||
self.ensure_chunk(cx, cy, cz);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_surface_y(&mut self, world_x: f32, world_z: f32) -> f32 {
|
||||
let scale = PXL8_VXL_SCALE as f32;
|
||||
let block_x = libm::floorf(world_x / scale) as i32;
|
||||
let block_z = libm::floorf(world_z / scale) as i32;
|
||||
|
||||
let cx = libm::floorf(block_x as f32 / CHUNK_SIZE as f32) as i32;
|
||||
let cz = libm::floorf(block_z as f32 / CHUNK_SIZE as f32) as i32;
|
||||
let lx = ((block_x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
|
||||
let lz = ((block_z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
|
||||
|
||||
for cy in (-2..=2).rev() {
|
||||
self.ensure_chunk(cx, cy, cz);
|
||||
if let Some(chunk) = self.get_chunk(cx, cy, cz) {
|
||||
for ly in (0..CHUNK_SIZE).rev() {
|
||||
let block = chunk.get(lx, ly, lz);
|
||||
if block == GRASS {
|
||||
let world_y = (cy * CHUNK_SIZE + ly + 1) as f32 * scale;
|
||||
return world_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn noise3d(x: i32, y: i32, z: i32, seed: u64) -> f32 {
|
||||
let h = hash(seed ^ (x as u64) ^ ((y as u64) << 21) ^ ((z as u64) << 42));
|
||||
(h & 0xFFFF) as f32 / 65535.0
|
||||
}
|
||||
|
||||
fn hash(mut x: u64) -> u64 {
|
||||
x ^= x >> 33;
|
||||
x = x.wrapping_mul(0xff51afd7ed558ccd);
|
||||
x ^= x >> 33;
|
||||
x = x.wrapping_mul(0xc4ceb9fe1a85ec53);
|
||||
x ^= x >> 33;
|
||||
x
|
||||
}
|
||||
|
||||
fn smoothstep(t: f32) -> f32 {
|
||||
t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a + (b - a) * t
|
||||
}
|
||||
|
||||
fn value_noise_3d(x: f32, y: f32, z: f32, seed: u64) -> f32 {
|
||||
let x0 = libm::floorf(x) as i32;
|
||||
let y0 = libm::floorf(y) as i32;
|
||||
let z0 = libm::floorf(z) as i32;
|
||||
let x1 = x0 + 1;
|
||||
let y1 = y0 + 1;
|
||||
let z1 = z0 + 1;
|
||||
|
||||
let tx = smoothstep(x - x0 as f32);
|
||||
let ty = smoothstep(y - y0 as f32);
|
||||
let tz = smoothstep(z - z0 as f32);
|
||||
|
||||
let c000 = noise3d(x0, y0, z0, seed);
|
||||
let c100 = noise3d(x1, y0, z0, seed);
|
||||
let c010 = noise3d(x0, y1, z0, seed);
|
||||
let c110 = noise3d(x1, y1, z0, seed);
|
||||
let c001 = noise3d(x0, y0, z1, seed);
|
||||
let c101 = noise3d(x1, y0, z1, seed);
|
||||
let c011 = noise3d(x0, y1, z1, seed);
|
||||
let c111 = noise3d(x1, y1, z1, seed);
|
||||
|
||||
let a00 = lerp(c000, c100, tx);
|
||||
let a10 = lerp(c010, c110, tx);
|
||||
let a01 = lerp(c001, c101, tx);
|
||||
let a11 = lerp(c011, c111, tx);
|
||||
|
||||
let b0 = lerp(a00, a10, ty);
|
||||
let b1 = lerp(a01, a11, ty);
|
||||
|
||||
lerp(b0, b1, tz)
|
||||
}
|
||||
|
||||
fn fbm_3d(x: f32, y: f32, z: f32, seed: u64, octaves: u32) -> f32 {
|
||||
let mut value = 0.0;
|
||||
let mut amplitude = 1.0;
|
||||
let mut frequency = 1.0;
|
||||
let mut max_value = 0.0;
|
||||
|
||||
for i in 0..octaves {
|
||||
value += amplitude * value_noise_3d(
|
||||
x * frequency,
|
||||
y * frequency,
|
||||
z * frequency,
|
||||
seed.wrapping_add(i as u64 * 1000)
|
||||
);
|
||||
max_value += amplitude;
|
||||
amplitude *= 0.5;
|
||||
frequency *= 2.0;
|
||||
}
|
||||
|
||||
value / max_value
|
||||
}
|
||||
|
||||
fn fbm_2d(x: f32, z: f32, seed: u64, octaves: u32) -> f32 {
|
||||
let mut value = 0.0;
|
||||
let mut amplitude = 1.0;
|
||||
let mut frequency = 1.0;
|
||||
let mut max_value = 0.0;
|
||||
|
||||
for i in 0..octaves {
|
||||
let x0 = libm::floorf(x * frequency) as i32;
|
||||
let z0 = libm::floorf(z * frequency) as i32;
|
||||
let x1 = x0 + 1;
|
||||
let z1 = z0 + 1;
|
||||
|
||||
let tx = smoothstep(x * frequency - x0 as f32);
|
||||
let tz = smoothstep(z * frequency - z0 as f32);
|
||||
|
||||
let offset_seed = seed.wrapping_add(i as u64 * 1000);
|
||||
let c00 = (hash(offset_seed ^ (x0 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
|
||||
let c10 = (hash(offset_seed ^ (x1 as u64) ^ ((z0 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
|
||||
let c01 = (hash(offset_seed ^ (x0 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
|
||||
let c11 = (hash(offset_seed ^ (x1 as u64) ^ ((z1 as u64) << 32)) & 0xFFFF) as f32 / 65535.0;
|
||||
|
||||
let a = lerp(c00, c10, tx);
|
||||
let b = lerp(c01, c11, tx);
|
||||
value += amplitude * lerp(a, b, tz);
|
||||
|
||||
max_value += amplitude;
|
||||
amplitude *= 0.5;
|
||||
frequency *= 2.0;
|
||||
}
|
||||
|
||||
value / max_value
|
||||
}
|
||||
|
||||
fn generate_chunk(chunk: &mut VoxelChunk, seed: u64) {
|
||||
let world_x = chunk.cx * CHUNK_SIZE;
|
||||
let world_y = chunk.cy * CHUNK_SIZE;
|
||||
let world_z = chunk.cz * CHUNK_SIZE;
|
||||
|
||||
let mut height_cache = [[0i32; 32]; 32];
|
||||
for lz in 0..32 {
|
||||
for lx in 0..32 {
|
||||
let wx = (world_x + lx as i32) as f32;
|
||||
let wz = (world_z + lz as i32) as f32;
|
||||
height_cache[lz][lx] = (fbm_2d(wx * 0.02, wz * 0.02, seed, 4) * 32.0) as i32;
|
||||
}
|
||||
}
|
||||
|
||||
let mut density = [[[0.0f32; 33]; 33]; 33];
|
||||
for lz in 0..33 {
|
||||
for ly in 0..33 {
|
||||
for lx in 0..33 {
|
||||
let wx = (world_x + lx as i32) as f32;
|
||||
let wy = (world_y + ly as i32) as f32;
|
||||
let wz = (world_z + lz as i32) as f32;
|
||||
|
||||
let hx = lx.min(31);
|
||||
let hz = lz.min(31);
|
||||
let base_height = height_cache[hz][hx] as f32;
|
||||
let height_bias = (base_height - wy) * 0.1;
|
||||
|
||||
let cave_noise = fbm_3d(wx * 0.05, wy * 0.05, wz * 0.05, seed.wrapping_add(1000), 2);
|
||||
density[lz][ly][lx] = height_bias + (cave_noise - 0.55);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for lz in 0..CHUNK_SIZE {
|
||||
for ly in 0..CHUNK_SIZE {
|
||||
for lx in 0..CHUNK_SIZE {
|
||||
let d = density[lz as usize][ly as usize][lx as usize];
|
||||
if d <= 0.0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let d_above = density[lz as usize][(ly + 1) as usize][lx as usize];
|
||||
let is_surface = d_above <= 0.0;
|
||||
|
||||
let block = if is_surface {
|
||||
GRASS
|
||||
} else {
|
||||
let wy = world_y + ly;
|
||||
let base_height = height_cache[lz as usize][lx as usize];
|
||||
let depth = base_height - wy;
|
||||
if depth < 4 {
|
||||
DIRT
|
||||
} else {
|
||||
STONE
|
||||
}
|
||||
};
|
||||
|
||||
chunk.set(lx, ly, lz, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VoxelWorld {
|
||||
fn default() -> Self {
|
||||
Self::new(12345)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue