mgecumshooter/esotera
added chunk generating/saving system
Commit d3e02c9e4e27 · mgecumshooter · 2026-10-18 11:58 +0300
Comments
No comments yet.
Diff
diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,8 @@
neorun.json
-*~
\ No newline at end of file
+*~
+
+saves
+./saves
+/saves
\ No newline at end of file
diff --git a/src/chunk/mod.rs b/src/chunk/mod.rs
--- a/src/chunk/mod.rs
+++ b/src/chunk/mod.rs
@@ -5,9 +5,11 @@
// core::*,
// consts::*,
};
+use std::fs;
// internal deps
-use crate::voxel::{ self, Voxel, };
+use crate::voxel::{ self, Voxel, VOXEL_SIZE };
+use crate::util::*;
// constants
@@ -68,7 +70,35 @@
// returns global voxel position by its chunk index
pub fn global_voxel_pos(&self, idx: usize) -> Vector3 {
- self.chunk_pos + self.voxel_pos(idx)
+ self.chunk_pos * CHUNK_SIZE as f32 + self.voxel_pos(idx)
+ }
+
+ pub fn get_chunk_aabb(&self) -> Aabb {
+ Aabb {
+ min: self.chunk_pos * CHUNK_SIZE as f32,
+ max: self.chunk_pos * CHUNK_SIZE as f32 + Vector3::new
+ (CHUNK_SIZE as f32, CHUNK_SIZE as f32, CHUNK_SIZE as f32),
+ }
+ }
+
+ pub fn get_voxel_global_aabb(&self, idx: usize) -> Aabb {
+ Aabb {
+ min: self.global_voxel_pos(idx),
+ max: self.global_voxel_pos(idx) + Vector3::new
+ (VOXEL_SIZE as f32, VOXEL_SIZE as f32, VOXEL_SIZE as f32),
+ }
+ }
+
+ pub fn get_voxel_pos_glb(&self, x: i32, y: i32, z: i32) -> (i32, i32, i32) {
+ let chunk_x: i32;
+ let chunk_y: i32;
+ let chunk_z: i32;
+
+ chunk_x = (x as f32 - self.chunk_pos.x * CHUNK_SIZE as f32) as i32;
+ chunk_y = (y as f32 - self.chunk_pos.y * CHUNK_SIZE as f32) as i32;
+ chunk_z = (z as f32 - self.chunk_pos.z * CHUNK_SIZE as f32) as i32;
+
+ (chunk_x, chunk_y, chunk_z)
}
// detects, whether voxel is exoped or not
@@ -84,7 +114,8 @@
let ny = y as isize + dy;
let nz = z as isize + dz;
- if nx < 0 || ny < 0 || nz < 0 || nx >= CHUNK_SIZE as isize || ny >= CHUNK_SIZE as isize || nz >= CHUNK_SIZE as isize {
+ if nx < 0 || ny < 0 || nz < 0 || nx >= CHUNK_SIZE as isize ||
+ ny >= CHUNK_SIZE as isize || nz >= CHUNK_SIZE as isize {
return true;
}
@@ -107,7 +138,7 @@
if self.is_exposed(x, y, z) {
ctx.draw_cube(
- self.chunk_pos + Vector3::new(x as f32, y as f32, z as f32),
+ self.chunk_pos * CHUNK_SIZE as f32 + Vector3::new(x as f32, y as f32, z as f32),
voxel::VOXEL_SIZE as f32,
voxel::VOXEL_SIZE as f32,
voxel::VOXEL_SIZE as f32,
@@ -119,4 +150,48 @@
}
self
}
+
+ pub fn save(&self) {
+ let dir = "saves";
+ let _ = fs::create_dir_all(dir);
+ let path = format!("{}/chunk_{}_{}_{}.bin", dir,
+ self.chunk_pos.x as i32,
+ self.chunk_pos.y as i32,
+ self.chunk_pos.z as i32,
+ );
+
+ let mut bytes = Vec::with_capacity(self.voxels.len());
+ for v in &self.voxels {
+ bytes.push(match v {
+ Voxel::Air => 0u8,
+ Voxel::Stone => 1u8,
+ });
+ }
+
+ let _ = fs::write(path, bytes);
+ }
+
+ pub fn load_from_disk(pos: Vector3) -> Option<Self> {
+ let path = format!("saves/chunk_{}_{}_{}.bin",
+ pos.x as i32, pos.y as i32, pos.z as i32,
+ );
+ if let Ok(bytes) = fs::read(path) {
+ if bytes.len() == CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE {
+ let mut voxels = [Voxel::Air; CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE];
+ for (i, &b) in bytes.iter().enumerate() {
+ voxels[i] = match b {
+ 1u8 => Voxel::Stone,
+ _ => Voxel::Air,
+ };
+ }
+ return Some(Self{
+ chunk_pos: pos,
+ voxels,
+ dirty: false,
+ mesh: None,
+ });
+ }
+ }
+ None
+ }
}
diff --git a/src/main.rs b/src/main.rs
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,20 +1,19 @@
// external deps
use sola_raylib as rlb;
-use rlb::{
- prelude::*,
- // core::*,
- // consts::*,
-};
-
+use rlb::prelude::*;
// modules declarations
mod chunk;
mod voxel;
+mod player;
+mod util;
+mod world;
// internal modules
use chunk::*;
-
-
+use player::Player;
+use util::Cube;
+use world::*;
fn main() {
@@ -35,31 +34,52 @@
rl.disable_cursor();
-
// shader stuff
let mut shader = rl.load_shader(&rl_thd, Some("shaders/vert.glsl"), Some("shaders/frag.glsl")).unwrap();
let mut post_shader = rl.load_shader(&rl_thd, None, Some("shaders/post.glsl")).unwrap();
- let mut rend_tex = rl.load_render_texture(&rl_thd, rl.get_render_width() as u32, rl.get_render_height() as u32).unwrap();
+ let mut rend_tex = rl.load_render_texture
+ (&rl_thd, rl.get_render_width() as u32, rl.get_render_height() as u32).unwrap();
// chunks
- let test_chunk = Chunk::new(Vector3::zero());
+ // let test_chunk = Chunk::new(Vector3::zero());
+
+ // let test2 = [&test_chunk];
+
+ let mut world = World::new(2);
+
+ let mut plr = Player::new(
+ Vector3::new(0f32, 10f32, 0f32),
+ Cube::new(
+ Vector3::new(5f32, 10f32, 5f32),
+ ),
+ );
// main game loop
while !rl.window_should_close() {
+ world.update_around_player(plr.get_eye_pos());
+
+ let active_chunks: Vec<&Chunk> = world.chunks.values().collect();
+ plr.update(&rl, &active_chunks);
+ plr.apply_to_camera(&mut cam);
+
+
if rend_tex.width() != rl.get_render_width() || rend_tex.height() != rl.get_render_height() {
- rend_tex = rl.load_render_texture(&rl_thd, rl.get_render_width() as u32, rl.get_render_height() as u32).unwrap();
+ rend_tex = rl.load_render_texture
+ (&rl_thd, rl.get_render_width() as u32, rl.get_render_height() as u32).unwrap();
}
- rl.update_camera(&mut cam, CameraMode::CAMERA_FIRST_PERSON);
- rl.draw(&rl_thd, |mut d| {
+ rl.draw(&rl_thd, |mut d| {
d.draw_texture_mode(&rl_thd, &mut rend_tex, |mut tx| {
tx.clear_background(Color::new(30, 30, 30, 255));
tx.draw_mode3D(cam, |mut d3, _| {
d3.draw_grid(100, 1f32);
d3.draw_shader_mode(&mut shader, |mut shd| {
- test_chunk.draw(&mut shd);
+ // test_chunk.draw(&mut shd);
+ for chunk in world.chunks.values() {
+ chunk.draw(&mut shd);
+ }
});
});
});
@@ -71,7 +91,17 @@
height: -rend_tex.texture().height as f32
}, Vector2::zero(), Color::WHITE);
});
+ d.draw_text(
+ &format!("x: {:.2}, y: {:.2}, z: {:.2}",
+ plr.get_eye_pos().x,
+ plr.get_eye_pos().y,
+ plr.get_eye_pos().z,
+ ),
+ 0,
+ 0,
+ 20,
+ Color::WHITE
+ );
});
}
}
-
diff --git a/src/player/mod.rs b/src/player/mod.rs
new file mode 100644
--- /dev/null
+++ b/src/player/mod.rs
@@ -0,0 +1,169 @@
+use super::rlb;
+use rlb::prelude::*;
+
+use crate::util::*;
+use crate::voxel::*;
+use crate::chunk::Chunk;
+
+const MOVE_SPEED: f32 = 5f32;
+const MOUSE_SENS: f32 = 0.05f32;
+const PITCH_LIMIT: f32 = 89f32;
+
+pub enum Axis {
+ X,
+ Y,
+ Z,
+}
+
+pub struct Player {
+ body: Cube,
+ pos: Vector3,
+ yaw: f32,
+ pitch: f32,
+}
+
+impl Player {
+ pub fn new(pos: Vector3, body: Cube) -> Self {
+ Self {
+ pos: pos,
+ body: body,
+ yaw: 0f32,
+ pitch: 0f32,
+ }
+ }
+
+ pub fn get_aabb(&self) -> Aabb {
+ Aabb {
+ min: self.pos,
+ max: Vector3::new(self.body.width, self.body.height, self.body.length) + self.pos,
+ }
+ }
+
+ pub fn get_feet_pos(&self) -> Vector3 {
+ self.get_aabb().min + Vector3::new(self.body.width * 0.5f32, 0f32, self.body.length * 0.5f32)
+ }
+
+ pub fn get_eye_pos(&self) -> Vector3 {
+ self.get_feet_pos() + Vector3::new(0f32, self.body.height * 0.9, 0f32)
+ }
+
+ pub fn get_look_dir(&self) -> Vector3 {
+ let y = self.yaw.to_radians();
+ let p = self.pitch.to_radians();
+
+ Vector3::new(
+ p.cos() * y.sin(),
+ p.sin(),
+ -p.cos() * y.cos(),
+ )
+ }
+
+ pub fn move_axis (&mut self, velocity: f32, axis: Axis, world: &Vec<Aabb>) {
+ if velocity == 0f32 {
+ return;
+ }
+
+ match axis {
+ Axis::X => self.pos.x += velocity,
+ Axis::Y => self.pos.y += velocity,
+ Axis::Z => self.pos.z += velocity,
+ }
+
+ let plr = self.get_aabb();
+
+ for voxel in world {
+ if plr.overlaps(&voxel) {
+ match axis {
+ Axis::X => {
+ if velocity > 0f32 {
+ self.pos.x = voxel.min.x - self.body.width;
+ } else {
+ self.pos.x = voxel.max.x;
+ }
+ }
+ Axis::Y => {
+ if velocity > 0f32 {
+ self.pos.y = voxel.min.y - self.body.width;
+ } else {
+ self.pos.y = voxel.max.y;
+ }
+ }
+ Axis::Z => {
+ if velocity > 0f32 {
+ self.pos.z = voxel.min.z - self.body.width;
+ } else {
+ self.pos.z = voxel.max.z;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ pub fn get_surroundings(&self, world: &[&Chunk]) -> Vec<Aabb> {
+ let plr_aabb = self.get_aabb();
+ let mut obstacles = Vec::new();
+
+ let min_x = (plr_aabb.min.x.floor() as isize) - 1;
+ let max_x = (plr_aabb.max.x.ceil() as isize) + 1;
+
+ let min_y = (plr_aabb.min.y.floor() as isize) - 1;
+ let max_y = (plr_aabb.max.y.ceil() as isize) + 1;
+
+ let min_z = (plr_aabb.min.z.floor() as isize) - 1;
+ let max_z = (plr_aabb.max.z.ceil() as isize) + 1;
+
+ for chunk in world {
+ for x in min_x..=max_x {
+ for y in min_y..=max_y {
+ for z in min_z..=max_z {
+ let (chk_x, chk_y, chk_z) = chunk.get_voxel_pos_glb(x as i32, y as i32, z as i32);
+ if let Some(Voxel::Stone) = chunk.get_voxel
+ (chk_x as usize, chk_y as usize, chk_z as usize) {
+ let idx = chunk.voxel_idx(chk_x as usize, chk_y as usize, chk_z as usize);
+ obstacles.push(chunk.get_voxel_global_aabb(idx));
+ }
+ }
+ }
+ }
+ }
+ obstacles
+ }
+
+ pub fn update(&mut self, rl: &RaylibHandle, world: &[&Chunk]) {
+ let mouse_delta = rl.get_mouse_delta();
+
+ self.yaw += mouse_delta.x * MOUSE_SENS;
+ self.pitch -= mouse_delta.y * MOUSE_SENS;
+ self.pitch = self.pitch.clamp(-PITCH_LIMIT, PITCH_LIMIT);
+
+ let forward =
+ (rl.is_key_down(KeyboardKey::KEY_W) as i8 - rl.is_key_down(KeyboardKey::KEY_S) as i8) as f32;
+ let sideways =
+ (rl.is_key_down(KeyboardKey::KEY_D) as i8 - rl.is_key_down(KeyboardKey::KEY_A) as i8) as f32;
+
+ let y = self.yaw.to_radians();
+ let fwd = Vector3::new(y.sin(), 0f32, -y.cos());
+ let sdw = Vector3::new(y.cos(), 0f32, y.sin());
+
+ let mut wish = fwd * forward + sdw * sideways;
+
+ let len = (wish.x * wish.x + wish.z * wish.z).sqrt();
+ if len > 0f32 {
+ wish.x /= len;
+ wish.z /= len;
+ }
+
+ let obstacles = self.get_surroundings(world);
+
+ let dt = rl.get_frame_time();
+ self.move_axis(wish.x * MOVE_SPEED * dt, Axis::X, &obstacles);
+ self.move_axis(wish.z * MOVE_SPEED * dt, Axis::Z, &obstacles);
+ }
+
+ pub fn apply_to_camera (&self, cam: &mut Camera3D) {
+ let eye = self.get_eye_pos();
+ cam.position = eye;
+ cam.target = eye + self.get_look_dir();
+ }
+}
diff --git a/src/util/mod.rs b/src/util/mod.rs
new file mode 100644
--- /dev/null
+++ b/src/util/mod.rs
@@ -0,0 +1,32 @@
+use super::rlb;
+use rlb::prelude::*;
+
+pub struct Cube {
+ pub width: f32,
+ pub length: f32,
+ pub height: f32,
+}
+
+impl Cube {
+ pub fn new(params: Vector3) -> Self {
+ Self {
+ width: params.x,
+ height: params.y,
+ length: params.z,
+ }
+ }
+}
+
+#[derive(Clone, Copy)]
+pub struct Aabb {
+ pub min: Vector3,
+ pub max: Vector3,
+}
+
+impl Aabb {
+ pub fn overlaps (&self, other: &Aabb) -> bool {
+ self.min.x < other.max.x && self.max.x > other.min.x &&
+ self.min.y < other.max.y && self.max.y > other.min.y &&
+ self.min.z < other.max.z && self.max.z > other.min.z
+ }
+}
diff --git a/src/world/mod.rs b/src/world/mod.rs
new file mode 100644
--- /dev/null
+++ b/src/world/mod.rs
@@ -0,0 +1,64 @@
+use std::collections::HashMap;
+use crate::chunk::{Chunk, CHUNK_SIZE};
+use super::rlb::prelude::*;
+
+pub struct World {
+ pub chunks: HashMap<(i32, i32, i32), Chunk>,
+ pub render_distance: i32,
+}
+
+impl World {
+ pub fn new(render: i32) -> Self {
+ Self {
+ chunks: HashMap::new(),
+ render_distance: render,
+ }
+ }
+
+ pub fn update_around_player(&mut self, player_pos: Vector3) {
+ let p_chunk_x = (player_pos.x / CHUNK_SIZE as f32).floor() as i32;
+ let p_chunk_y = (player_pos.y / CHUNK_SIZE as f32).floor() as i32;
+ let p_chunk_z = (player_pos.z / CHUNK_SIZE as f32).floor() as i32;
+
+ let mut needed_chunks = Vec::new();
+
+ let r = self.render_distance;
+ for x in -r..=r {
+ for y in -r..=r {
+ for z in -r..=r {
+ if (x*x + y*y + z*z) <= (r*r) {
+ let chunk_coords = (p_chunk_x + x,
+ p_chunk_y + y,
+ p_chunk_z + z
+ );
+ needed_chunks.push(chunk_coords);
+
+ if !self.chunks.contains_key(&chunk_coords) {
+ let pos_v3 = Vector3::new(chunk_coords.0 as f32,
+ chunk_coords.1 as f32,
+ chunk_coords.2 as f32,
+ );
+ let chunk = Chunk::load_from_disk(pos_v3).unwrap_or_else(|| {
+ Chunk::new(pos_v3)
+ });
+
+ self.chunks.insert(chunk_coords, chunk);
+ }
+ }
+ }
+ }
+ }
+
+ let mut to_unload = Vec::new();
+ for &coords in self.chunks.keys() {
+ if !needed_chunks.contains(&coords) {
+ to_unload.push(coords);
+ }
+ }
+ for coords in to_unload {
+ if let Some(chunk) = self.chunks.remove(&coords) {
+ chunk.save();
+ }
+ }
+ }
+}