diff --git a/.gitignore b/.gitignore
index 6985cf1..07db1ba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,6 @@ Cargo.lock
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
+
+#old source
+_src
diff --git a/Cargo.toml b/Cargo.toml
index 20a81de..769fdad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,9 +3,6 @@ name = "kubi"
version = "0.1.0"
edition = "2021"
-[profile.dev.package.noise]
-opt-level = 3
-
[dependencies]
glium = "0.32"
image = { version = "0.24", default_features = false, features = ["png"] }
@@ -14,9 +11,10 @@ env_logger = "0.10"
strum = { version = "0.24", features = ["derive"] }
glam = { version = "0.22", features = ["debug-glam-assert", "mint", "fast-math"] }
hashbrown = "0.13"
-noise = "0.8"
rayon = "1.6"
-#ordered-float = "3.4"
-
-[features]
-default = []
+shipyard = { version = "0.6", features = ["thread_local"] }
+nohash-hasher = "0.2.0"
+anyhow = "1.0"
+flume = "0.10"
+#once_cell = "1.17"
+bracket-noise = "0.8"
diff --git a/README.md b/README.md
index c5b08c0..928969d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,3 @@
-# development moved to
-# https://github.com/griffi-gh/kubi/tree/ecs-rewrite
-this branch contains the latest version before the rewrite
+
Kubi
+work in progress
+~ uwu
\ No newline at end of file
diff --git a/crabs.txt b/crabs.txt
new file mode 100644
index 0000000..aecc062
--- /dev/null
+++ b/crabs.txt
@@ -0,0 +1 @@
+sorry no crabs here
diff --git a/src/game/shaders/glsl/colored2d.frag b/shaders/selection_box.frag
similarity index 55%
rename from src/game/shaders/glsl/colored2d.frag
rename to shaders/selection_box.frag
index 9b2ac94..42b54ed 100644
--- a/src/game/shaders/glsl/colored2d.frag
+++ b/shaders/selection_box.frag
@@ -5,4 +5,5 @@ uniform vec4 u_color;
void main() {
color = u_color;
+ color -= vec4(0, 0, 0, 0.1 * sin(gl_FragCoord.x) * cos(gl_FragCoord.y));
}
diff --git a/shaders/selection_box.vert b/shaders/selection_box.vert
new file mode 100644
index 0000000..7243a1f
--- /dev/null
+++ b/shaders/selection_box.vert
@@ -0,0 +1,10 @@
+#version 150 core
+
+in vec3 position;
+uniform vec3 u_position;
+uniform mat4 perspective;
+uniform mat4 view;
+
+void main() {
+ gl_Position = perspective * view * vec4(position + u_position, 1.);
+}
diff --git a/src/game/shaders/glsl/chunk.frag b/shaders/world.frag
similarity index 100%
rename from src/game/shaders/glsl/chunk.frag
rename to shaders/world.frag
diff --git a/src/game/shaders/glsl/chunk.vert b/shaders/world.vert
similarity index 52%
rename from src/game/shaders/glsl/chunk.vert
rename to shaders/world.vert
index 91e2b39..de3d2e7 100644
--- a/src/game/shaders/glsl/chunk.vert
+++ b/shaders/world.vert
@@ -1,5 +1,11 @@
#version 150 core
+//TODO pack this data:
+// uint position_normal_uv
+// XXYYZZNU
+// wehere Normal and Uv are enums
+// maybe somehow pack in tex index too
+
in vec3 position;
in vec3 normal;
in vec2 uv;
@@ -7,7 +13,7 @@ in uint tex_index;
out vec2 v_uv;
out vec3 v_normal;
flat out uint v_tex_index;
-uniform vec2 position_offset;
+uniform vec3 position_offset;
uniform mat4 perspective;
uniform mat4 view;
@@ -15,5 +21,5 @@ void main() {
v_normal = normal;
v_tex_index = tex_index;
v_uv = uv;
- gl_Position = perspective * view * (vec4(position, 1.0) + vec4(position_offset.x, 0., position_offset.y, 0.));
+ gl_Position = perspective * view * vec4(position + position_offset, 1.);
}
diff --git a/src/block_placement.rs b/src/block_placement.rs
new file mode 100644
index 0000000..89cdf99
--- /dev/null
+++ b/src/block_placement.rs
@@ -0,0 +1,37 @@
+use glam::Vec3;
+use shipyard::{UniqueViewMut, UniqueView, View, IntoIter};
+use crate::{
+ player::MainPlayer,
+ world::{raycast::LookingAtBlock, ChunkStorage, block::Block},
+ input::{Inputs, PrevInputs}
+};
+
+pub fn block_placement_system(
+ main_player: View,
+ raycast: View,
+ input: UniqueView,
+ prev_input: UniqueView,
+ mut world: UniqueViewMut
+) {
+ let action_place = input.action_b && !prev_input.0.action_b;
+ let action_break = input.action_a && !prev_input.0.action_a;
+ if action_place ^ action_break {
+ //get raycast info
+ let Some(ray) = (&main_player, &raycast).iter().next().unwrap().1/**/.0 else { return };
+ //update block
+ let place_position = if action_place {
+ let position = (ray.position - ray.direction * 0.5).floor().as_ivec3();
+ let Some(block) = world.get_block_mut(position) else { return };
+ *block = Block::Dirt;
+ position
+ } else {
+ let Some(block) = world.get_block_mut(ray.block_position) else { return };
+ *block = Block::Air;
+ ray.block_position
+ };
+ //mark chunk as dirty
+ let (chunk_pos, _) = ChunkStorage::to_chunk_coords(place_position);
+ let chunk = world.chunks.get_mut(&chunk_pos).unwrap();
+ chunk.dirty = true;
+ }
+}
diff --git a/src/camera.rs b/src/camera.rs
new file mode 100644
index 0000000..3da67c3
--- /dev/null
+++ b/src/camera.rs
@@ -0,0 +1,43 @@
+use glam::{Mat4, Vec3};
+use shipyard::{Component, Workload, IntoWorkload};
+use std::f32::consts::PI;
+
+mod matrices;
+mod frustum;
+
+use matrices::update_matrices;
+use frustum::{Frustum, update_frustum};
+
+#[derive(Component)]
+pub struct Camera {
+ pub view_matrix: Mat4,
+ pub perspective_matrix: Mat4,
+ pub frustum: Frustum,
+ pub up: Vec3,
+ pub fov: f32,
+ pub z_near: f32,
+ pub z_far: f32,
+}
+impl Camera {
+ pub fn new(fov: f32, z_near: f32, z_far: f32, up: Vec3) -> Self {
+ Self {
+ fov, z_near, z_far, up,
+ //TODO maybe separate this?
+ perspective_matrix: Mat4::default(),
+ view_matrix: Mat4::default(),
+ frustum: Frustum::default(),
+ }
+ }
+}
+impl Default for Camera {
+ fn default() -> Self {
+ Self::new(PI / 3., 0.1, 1024., Vec3::Y)
+ }
+}
+
+pub fn compute_cameras() -> Workload {
+ (
+ update_matrices,
+ update_frustum,
+ ).into_workload()
+}
diff --git a/src/camera/frustum.rs b/src/camera/frustum.rs
new file mode 100644
index 0000000..9042dd4
--- /dev/null
+++ b/src/camera/frustum.rs
@@ -0,0 +1,130 @@
+// basically ported from c++
+// - used as a reference:
+// [ https://github.com/Beastwick18/gltest/blob/main/src/renderer/Frustum.cpp ]
+// - original code:
+// [ https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644 ]
+// - which uses cube vs frustum intersection code from:
+// [ http://iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm ]
+// three layers of stolen code, yay!
+
+use glam::{Vec3A, Vec4, Mat3A, vec3a, Vec3, vec4};
+use shipyard::{ViewMut, IntoIter, View};
+use crate::transform::Transform;
+use super::Camera;
+
+#[repr(usize)]
+enum FrustumPlane {
+ Left,
+ Right,
+ Bottom,
+ Top,
+ Near,
+ Far,
+}
+
+const PLANE_COUNT: usize = 6;
+const PLANE_COMBINATIONS: usize = PLANE_COUNT * (PLANE_COUNT - 1) / 2;
+const POINT_COUNT: usize = 8;
+
+#[derive(Default)]
+pub struct Frustum {
+ planes: [Vec4; PLANE_COUNT],
+ points: [Vec3A; POINT_COUNT]
+}
+impl Frustum {
+ pub fn compute(camera: &Camera) -> Self {
+ //compute transposed view-projection matrix
+ let mat = (camera.perspective_matrix * camera.view_matrix).transpose();
+
+ // compute planes
+ let mut planes = [Vec4::default(); PLANE_COUNT];
+ planes[FrustumPlane::Left as usize] = mat.w_axis + mat.x_axis;
+ planes[FrustumPlane::Right as usize] = mat.w_axis - mat.x_axis;
+ planes[FrustumPlane::Bottom as usize] = mat.w_axis + mat.y_axis;
+ planes[FrustumPlane::Top as usize] = mat.w_axis - mat.y_axis;
+ planes[FrustumPlane::Near as usize] = mat.w_axis + mat.z_axis;
+ planes[FrustumPlane::Far as usize] = mat.w_axis - mat.z_axis;
+
+ //compute crosses
+ let crosses = [
+ Vec3A::from(planes[FrustumPlane::Left as usize]).cross(planes[FrustumPlane::Right as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Left as usize]).cross(planes[FrustumPlane::Bottom as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Left as usize]).cross(planes[FrustumPlane::Top as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Left as usize]).cross(planes[FrustumPlane::Near as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Left as usize]).cross(planes[FrustumPlane::Far as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Right as usize]).cross(planes[FrustumPlane::Bottom as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Right as usize]).cross(planes[FrustumPlane::Top as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Right as usize]).cross(planes[FrustumPlane::Near as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Right as usize]).cross(planes[FrustumPlane::Far as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Bottom as usize]).cross(planes[FrustumPlane::Top as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Bottom as usize]).cross(planes[FrustumPlane::Near as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Bottom as usize]).cross(planes[FrustumPlane::Far as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Top as usize]).cross(planes[FrustumPlane::Near as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Top as usize]).cross(planes[FrustumPlane::Far as usize].into()),
+ Vec3A::from(planes[FrustumPlane::Near as usize]).cross(planes[FrustumPlane::Far as usize].into()),
+ ];
+
+ //compute points
+ let points = [
+ intersection::<{FrustumPlane::Left as usize}, {FrustumPlane::Bottom as usize}, {FrustumPlane::Near as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Left as usize}, {FrustumPlane::Top as usize}, {FrustumPlane::Near as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Right as usize}, {FrustumPlane::Bottom as usize}, {FrustumPlane::Near as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Right as usize}, {FrustumPlane::Top as usize}, {FrustumPlane::Near as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Left as usize}, {FrustumPlane::Bottom as usize}, {FrustumPlane::Far as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Left as usize}, {FrustumPlane::Top as usize}, {FrustumPlane::Far as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Right as usize}, {FrustumPlane::Bottom as usize}, {FrustumPlane::Far as usize}>(&planes, &crosses),
+ intersection::<{FrustumPlane::Right as usize}, {FrustumPlane::Top as usize}, {FrustumPlane::Far as usize}>(&planes, &crosses),
+ ];
+
+ Self { planes, points }
+ }
+
+ pub fn is_box_visible(&self, minp: Vec3, maxp: Vec3) -> bool {
+ // check box outside/inside of frustum
+ for plane in self.planes {
+ if (plane.dot(vec4(minp.x, minp.y, minp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(maxp.x, minp.y, minp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(minp.x, maxp.y, minp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(maxp.x, maxp.y, minp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(minp.x, minp.y, maxp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(maxp.x, minp.y, maxp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(minp.x, maxp.y, maxp.z, 1.)) < 0.) &&
+ (plane.dot(vec4(maxp.x, maxp.y, maxp.z, 1.)) < 0.)
+ {
+ return false
+ }
+ }
+
+ // check frustum outside/inside box
+ if self.points.iter().all(|point| point.x > maxp.x) { return false }
+ if self.points.iter().all(|point| point.x < minp.x) { return false }
+ if self.points.iter().all(|point| point.y > maxp.y) { return false }
+ if self.points.iter().all(|point| point.y < minp.y) { return false }
+ if self.points.iter().all(|point| point.z > maxp.z) { return false }
+ if self.points.iter().all(|point| point.z < minp.z) { return false }
+
+ true
+ }
+}
+
+const fn ij2k() -> usize {
+ I * (9 - I) / 2 + J - 1
+}
+fn intersection(planes: &[Vec4; PLANE_COUNT], crosses: &[Vec3A; PLANE_COMBINATIONS]) -> Vec3A {
+ let d = Vec3A::from(planes[A]).dot(crosses[ij2k::()]);
+ let res = Mat3A::from_cols(
+ crosses[ij2k::()],
+ -crosses[ij2k::()],
+ crosses[ij2k::()],
+ ) * vec3a(planes[A].w, planes[B].w, planes[C].w);
+ res * (-1. / d)
+}
+
+pub fn update_frustum(
+ mut cameras: ViewMut,
+ transforms: View
+) {
+ for (camera, _) in (&mut cameras, transforms.inserted_or_modified()).iter() {
+ camera.frustum = Frustum::compute(camera);
+ }
+}
diff --git a/src/camera/matrices.rs b/src/camera/matrices.rs
new file mode 100644
index 0000000..b2086f6
--- /dev/null
+++ b/src/camera/matrices.rs
@@ -0,0 +1,42 @@
+use glam::{Vec3, Mat4};
+use shipyard::{ViewMut, View, IntoIter, Workload, IntoWorkload};
+use crate::{transform::Transform, events::WindowResizedEvent};
+use super::Camera;
+
+//maybe parallelize these two?
+
+fn update_view_matrix(
+ mut vm_camera: ViewMut,
+ v_transform: View
+) {
+ for (camera, transform) in (&mut vm_camera, v_transform.inserted_or_modified()).iter() {
+ let (_, rotation, translation) = transform.0.to_scale_rotation_translation();
+ let direction = rotation * Vec3::NEG_Z;
+ camera.view_matrix = Mat4::look_to_rh(translation, direction, camera.up);
+ }
+}
+
+fn update_perspective_matrix(
+ mut vm_camera: ViewMut,
+ resize: View,
+) {
+ //TODO update on launch
+ let Some(&size) = resize.iter().next() else {
+ return
+ };
+ for camera in (&mut vm_camera).iter() {
+ camera.perspective_matrix = Mat4::perspective_rh_gl(
+ camera.fov,
+ size.0.x as f32 / size.0.y as f32,
+ camera.z_near,
+ camera.z_far,
+ )
+ }
+}
+
+pub fn update_matrices() -> Workload {
+ (
+ update_view_matrix,
+ update_perspective_matrix,
+ ).into_workload()
+}
diff --git a/src/events.rs b/src/events.rs
new file mode 100644
index 0000000..3a37298
--- /dev/null
+++ b/src/events.rs
@@ -0,0 +1,55 @@
+use glam::UVec2;
+use shipyard::{World, Component, AllStoragesViewMut, SparseSet};
+use glium::glutin::event::{Event, DeviceEvent, DeviceId, WindowEvent};
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+pub struct EventComponent;
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+pub struct OnBeforeExitEvent;
+
+#[derive(Component, Clone, Debug)]
+pub struct InputDeviceEvent{
+ pub device_id: DeviceId,
+ pub event: DeviceEvent
+}
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+pub struct WindowResizedEvent(pub UVec2);
+
+pub fn process_glutin_events(world: &mut World, event: &Event<'_, ()>) {
+ #[allow(clippy::collapsible_match, clippy::single_match)]
+ match event {
+ Event::WindowEvent { window_id: _, event } => match event {
+ WindowEvent::Resized(size) => {
+ world.add_entity((
+ EventComponent,
+ WindowResizedEvent(UVec2::new(size.width as _, size.height as _))
+ ));
+ },
+ _ => ()
+ },
+ Event::DeviceEvent { device_id, event } => {
+ world.add_entity((
+ EventComponent,
+ InputDeviceEvent {
+ device_id: *device_id,
+ event: event.clone()
+ }
+ ));
+ },
+ Event::LoopDestroyed => {
+ world.add_entity((
+ EventComponent,
+ OnBeforeExitEvent
+ ));
+ },
+ _ => (),
+ }
+}
+
+pub fn clear_events(
+ mut all_storages: AllStoragesViewMut,
+) {
+ all_storages.delete_any::>();
+}
diff --git a/src/fly_controller.rs b/src/fly_controller.rs
new file mode 100644
index 0000000..a8972ea
--- /dev/null
+++ b/src/fly_controller.rs
@@ -0,0 +1,52 @@
+use glam::{Vec3, Mat4, Quat, EulerRot, Vec2};
+use shipyard::{Component, View, ViewMut, IntoIter, UniqueView, Workload, IntoWorkload};
+use std::f32::consts::PI;
+use crate::{transform::Transform, input::Inputs, settings::GameSettings, DeltaTime};
+
+#[derive(Component)]
+pub struct FlyController;
+
+pub fn update_controllers() -> Workload {
+ (
+ update_look,
+ update_movement
+ ).into_workload()
+}
+
+const MAX_PITCH: f32 = PI/2. - 0.001;
+
+fn update_look(
+ controllers: View,
+ mut transforms: ViewMut,
+ inputs: UniqueView,
+ settings: UniqueView,
+ dt: UniqueView,
+) {
+ let look = inputs.look * settings.mouse_sensitivity * dt.0.as_secs_f32();
+ if look == Vec2::ZERO { return }
+ for (_, mut transform) in (&controllers, &mut transforms).iter() {
+ let (scale, mut rotation, translation) = transform.0.to_scale_rotation_translation();
+ let (mut yaw, mut pitch, _roll) = rotation.to_euler(EulerRot::YXZ);
+ yaw -= look.x;
+ pitch -= look.y;
+ pitch = pitch.clamp(-MAX_PITCH, MAX_PITCH);
+ rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.).normalize();
+ transform.0 = Mat4::from_scale_rotation_translation(scale, rotation, translation);
+ }
+}
+
+fn update_movement(
+ controllers: View,
+ mut transforms: ViewMut,
+ inputs: UniqueView,
+ dt: UniqueView,
+) {
+ let movement = inputs.movement * 30. * dt.0.as_secs_f32();
+ if movement == Vec2::ZERO { return }
+ for (_, mut transform) in (&controllers, &mut transforms).iter() {
+ let (scale, rotation, mut translation) = transform.0.to_scale_rotation_translation();
+ translation += (rotation * Vec3::NEG_Z) * movement.y;
+ translation += (rotation * Vec3::X) * movement.x;
+ transform.0 = Mat4::from_scale_rotation_translation(scale, rotation, translation);
+ }
+}
diff --git a/src/game.rs b/src/game.rs
deleted file mode 100644
index 3c9d308..0000000
--- a/src/game.rs
+++ /dev/null
@@ -1,162 +0,0 @@
-use glam::Vec2;
-use glium::Surface;
-use glium::glutin::{
- event::{Event, WindowEvent, DeviceEvent},
- event_loop::{EventLoop, ControlFlow},
-};
-use std::time::Instant;
-
-mod assets;
-mod display;
-mod shaders;
-mod camera;
-mod controller;
-mod world;
-mod blocks;
-mod items;
-mod options;
-mod physics;
-mod player;
-
-use assets::Assets;
-use display::init_display;
-use shaders::Programs;
-use camera::Camera;
-use controller::Controls;
-use world::World;
-use options::GameOptions;
-
-struct State {
- pub camera: Camera,
- pub first_draw: bool,
- pub controls: Controls,
- pub world: World
-}
-impl State {
- pub fn init() -> Self {
- Self {
- first_draw: true,
- camera: Camera::default(),
- controls: Controls::default(),
- world: World::new(),
- }
- }
-}
-
-pub fn run() {
- log::info!("starting up");
- let event_loop = EventLoop::new();
- log::info!("initializing display");
- let display = init_display(&event_loop);
- log::info!("compiling shaders");
- let programs = Programs::compile_all(&display);
- log::info!("loading assets");
- let assets = Assets::load_all_sync(&display);
- log::info!("init game options");
- let options = GameOptions::default();
- log::info!("init game state");
- let mut state = State::init();
- state.camera.position = [0., 260., -1.];
- log::info!("game loaded");
-
- //=======================
- // let vertex1 = ChunkVertex { position: [-0.5, -0.5, 0.], uv: [0., 0.], normal: [0., 1., 0.] };
- // let vertex2 = ChunkVertex { position: [ 0.0, 0.5, 0.], uv: [0., 1.], normal: [0., 1., 0.] };
- // let vertex3 = ChunkVertex { position: [ 0.5, -0.5, 0.], uv: [1., 1.], normal: [0., 1., 0.] };
- // let shape = vec![vertex1, vertex2, vertex3];
- // let vertex_buffer = glium::VertexBuffer::new(&display, &shape).unwrap();
- //=======================
-
- let mut last_render = Instant::now();
-
- event_loop.run(move |event, _, control_flow| {
- *control_flow = ControlFlow::Poll;
- match event {
- // Mouse motion
- Event::DeviceEvent {
- event: DeviceEvent::MouseMotion{ delta, }, ..
- } => {
- state.controls.process_mouse_input(delta.0, delta.1);
- return
- }
- // Keyboard input
- Event::DeviceEvent { event: DeviceEvent::Key(input), .. } => {
- if let Some(key) = input.virtual_keycode {
- state.controls.process_keyboard_input(key, input.state);
- }
- return
- }
- // Window events
- Event::WindowEvent { event, .. } => {
- match event {
- WindowEvent::CloseRequested => {
- log::info!("exit requested");
- *control_flow = ControlFlow::Exit;
- return
- },
- WindowEvent::Resized(size) => {
- state.camera.update_perspective_matrix((size.width, size.height));
- },
- _ => return
- }
- },
- Event::MainEventsCleared => (),
- _ => return
- }
-
- //Calculate delta time
- let now = Instant::now();
- let dt = (now - last_render).as_secs_f32();
- last_render = now;
-
- //Update controls
- state.controls.calculate(dt).apply_to_camera(&mut state.camera);
-
- //Load new chunks
-
- state.world.update_loaded_chunks(
- Vec2::new(state.camera.position[0], state.camera.position[2]),
- &options,
- &display
- );
-
- //Start drawing
- let mut target = display.draw();
- target.clear_color_and_depth((0.5, 0.5, 1., 1.), 1.);
-
- //Compute camera
- if state.first_draw {
- let target_dimensions = target.get_dimensions();
- state.camera.update_perspective_matrix(target_dimensions);
- }
- let perspective = state.camera.perspective_matrix;
- let view = state.camera.view_matrix();
-
- //Draw chunks
- state.world.render(&mut target, &programs, &assets, perspective, view, &options);
-
- //Draw example triangle
- // target.draw(
- // &vertex_buffer,
- // glium::index::NoIndices(glium::index::PrimitiveType::TrianglesList),
- // &programs.chunk,
- // &uniform! {
- // model: [
- // [1., 0., 0., 0.],
- // [0., 1., 0., 0.],
- // [0., 0., 1., 0.],
- // [0., 0., 0., 1.0_f32]
- // ],
- // view: view,
- // perspective: perspective,
- // tex: Sampler(&assets.textures.block_atlas, sampler_nearest)
- // },
- // &Default::default()
- // ).unwrap();
-
- //Finish drawing
- target.finish().unwrap();
-
- state.first_draw = false;
- });
-}
diff --git a/src/game/assets.rs b/src/game/assets.rs
deleted file mode 100644
index 27687a9..0000000
--- a/src/game/assets.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-pub mod textures;
-
-use textures::Textures;
-
-pub struct Assets {
- pub textures: Textures
-}
-impl Assets {
- /// Load all assets synchronously
- pub fn load_all_sync(display: &glium::Display) -> Self {
- Self {
- textures: Textures::load_sync(display)
- }
- }
-}
diff --git a/src/game/assets/textures.rs b/src/game/assets/textures.rs
deleted file mode 100644
index c7cf857..0000000
--- a/src/game/assets/textures.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use std::{fs, io, path::PathBuf, sync::atomic::AtomicU16};
-use rayon::prelude::*;
-use glium::texture::{RawImage2d, SrgbTexture2d, SrgbTexture2dArray};
-
-//This code is terrible and has a alot of duplication
-
-fn load_png(file_path: &str, display: &glium::Display) -> SrgbTexture2d {
- log::info!("loading texture {}", file_path);
-
- //Load file
- let data = fs::read(file_path)
- .unwrap_or_else(|_| panic!("Failed to load texture: {}", file_path));
-
- //decode image data
- let image_data = image::load(
- io::Cursor::new(&data),
- image::ImageFormat::Png
- ).unwrap().to_rgba8();
-
- //Create raw glium image
- let image_dimensions = image_data.dimensions();
- let raw_image = RawImage2d::from_raw_rgba_reversed(
- &image_data.into_raw(),
- image_dimensions
- );
-
- //Create texture
- SrgbTexture2d::new(display, raw_image).unwrap()
-}
-
-fn load_png_array(file_paths: &[PathBuf], display: &glium::Display) -> SrgbTexture2dArray {
- let counter = AtomicU16::new(0);
- let raw_images: Vec> = file_paths.par_iter().enumerate().map(|(_, file_path)| {
-
- let fname: &str = file_path.file_name().unwrap_or_default().to_str().unwrap();
-
- //Load file
- let data = fs::read(file_path).expect(&format!("Failed to load texture {}", fname));
-
- //decode image data
- let image_data = image::load(
- io::Cursor::new(&data),
- image::ImageFormat::Png
- ).unwrap().to_rgba8();
-
- //Create raw glium image
- let image_dimensions = image_data.dimensions();
- let raw_image = RawImage2d::from_raw_rgba_reversed(
- &image_data.into_raw(),
- image_dimensions
- );
-
- let counter = counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst) + 1;
- log::info!("loaded texture {}/{}: {}", counter, file_paths.len(), fname);
-
- raw_image
- }).collect();
- SrgbTexture2dArray::new(display, raw_images).unwrap()
-}
-
-pub struct Textures {
- pub blocks: SrgbTexture2dArray
-}
-impl Textures {
- /// Load textures synchronously, one by one and upload them to the GPU
- pub fn load_sync(display: &glium::Display) -> Self {
- Self {
- blocks: load_png_array(&[
- "./assets/blocks/stone.png".into(),
- "./assets/blocks/dirt.png".into(),
- "./assets/blocks/grass_top.png".into(),
- "./assets/blocks/grass_side.png".into(),
- "./assets/blocks/sand.png".into(),
- "./assets/blocks/bedrock.png".into(),
- "./assets/blocks/wood.png".into(),
- "./assets/blocks/wood_top.png".into(),
- "./assets/blocks/leaf.png".into(),
- "./assets/blocks/torch.png".into(),
- "./assets/blocks/tall_grass.png".into(),
- "./assets/blocks/snow.png".into(),
- "./assets/blocks/grass_side_snow.png".into(),
- ], display)
- }
- }
-}
-
-#[derive(Clone, Copy, Debug)]
-#[repr(u8)]
-pub enum BlockTexture {
- Stone = 0,
- Dirt = 1,
- GrassTop = 2,
- GrassSide = 3,
- Sand = 4,
- Bedrock = 5,
- Wood = 6,
- WoodTop = 7,
- Leaf = 8,
- Torch = 9,
- TallGrass = 10,
- Snow = 11,
- GrassSideSnow = 12,
-}
diff --git a/src/game/blocks.rs b/src/game/blocks.rs
deleted file mode 100644
index 9db47b6..0000000
--- a/src/game/blocks.rs
+++ /dev/null
@@ -1,141 +0,0 @@
-use strum::{EnumIter, IntoEnumIterator};
-use crate::game::{
- items::Item,
- assets::textures::BlockTexture,
-};
-
-
-#[derive(Clone, Copy, Debug)]
-pub enum CollisionType {
- Solid,
- Liquid,
- Ladder,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum RenderType {
- OpaqueBlock,
- TranslucentBlock,
- TranslucentLiquid,
- CrossShape
-}
-
-#[derive(Clone, Copy, Debug)]
-pub struct BlockTextures {
- pub top: BlockTexture,
- pub bottom: BlockTexture,
- pub left: BlockTexture,
- pub right: BlockTexture,
- pub back: BlockTexture,
- pub front: BlockTexture,
-}
-impl BlockTextures {
- pub const fn all(tex: BlockTexture) -> Self {
- Self {
- top: tex,
- bottom: tex,
- left: tex,
- right: tex,
- back: tex,
- front: tex,
- }
- }
- pub const fn top_sides_bottom(top: BlockTexture, sides: BlockTexture, bottom: BlockTexture) -> Self {
- Self {
- top,
- bottom,
- left: sides,
- right: sides,
- back: sides,
- front: sides,
- }
- }
-}
-
-#[derive(Clone, Copy, Debug)]
-pub struct BlockDescriptor {
- pub name: &'static str,
- pub id: &'static str,
- pub collision: Option,
- pub raycast_collision: bool,
- pub render: Option<(RenderType, BlockTextures)>,
- pub item: Option- ,
-}
-impl BlockDescriptor {
- //Not using the Default trait because this function has to be const!
- pub const fn default() -> Self {
- Self {
- name: "default",
- id: "default",
- collision: Some(CollisionType::Solid),
- raycast_collision: true,
- render: Some((RenderType::OpaqueBlock, BlockTextures::all(BlockTexture::Stone))),
- item: None
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, EnumIter)]
-pub enum Block {
- Air,
- Stone,
- Dirt,
- Grass,
- Sand,
-}
-impl Block {
- //TODO make this O(1) with compile-time computed maps
- pub fn get_by_id(id: &str) -> Option {
- for block in Self::iter() {
- if block.descriptor().id == id {
- return Some(block)
- }
- }
- None
- }
-
- pub const fn descriptor(self) -> BlockDescriptor {
- match self {
- Self::Air => BlockDescriptor {
- name: "Air",
- id: "air",
- collision: None,
- raycast_collision: false,
- render: None,
- item: None,
- },
- Self::Stone => BlockDescriptor {
- name: "Stone",
- id: "stone",
- collision: Some(CollisionType::Solid),
- raycast_collision: true,
- render: Some((RenderType::OpaqueBlock, BlockTextures::all(BlockTexture::Stone))),
- item: Some(Item::StoneBlock)
- },
- Self::Dirt => BlockDescriptor {
- name: "Dirt",
- id: "dirt",
- collision: Some(CollisionType::Solid),
- raycast_collision: true,
- render: Some((RenderType::OpaqueBlock, BlockTextures::all(BlockTexture::Dirt))),
- item: Some(Item::DirtBlock)
- },
- Self::Grass => BlockDescriptor {
- name: "Grass",
- id: "grass",
- collision: Some(CollisionType::Solid),
- raycast_collision: true,
- render: Some((RenderType::OpaqueBlock, BlockTextures::top_sides_bottom(BlockTexture::GrassTop, BlockTexture::GrassSide, BlockTexture::Dirt))),
- item: Some(Item::DirtBlock)
- },
- Self::Sand => BlockDescriptor {
- name: "Sand",
- id: "sand",
- collision: Some(CollisionType::Solid),
- raycast_collision: true,
- render: Some((RenderType::OpaqueBlock, BlockTextures::all(BlockTexture::Sand))), //this is not a sand tex
- item: Some(Item::StoneBlock)
- }
- }
- }
-}
diff --git a/src/game/camera.rs b/src/game/camera.rs
deleted file mode 100644
index 5dc2ce6..0000000
--- a/src/game/camera.rs
+++ /dev/null
@@ -1,157 +0,0 @@
-// Perspective/View matrix code from:
-// https://glium.github.io/glium/book/tuto-10-perspective.html
-// https://glium.github.io/glium/book/tuto-12-camera.html
-// I don't understand anything but it works
-
-use std::f32::consts::PI;
-
-pub fn calculate_forward_direction(yaw: f32, pitch: f32) -> [f32; 3] {
- [
- yaw.cos() * pitch.cos(),
- pitch.sin(),
- yaw.sin() * pitch.cos(),
- ]
-}
-
-fn normalize_plane(mut plane: [f32; 4]) -> [f32; 4] {
- let mag = (plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]).sqrt();
- plane[0] = plane[0] / mag;
- plane[1] = plane[1] / mag;
- plane[2] = plane[2] / mag;
- plane[3] = plane[3] / mag;
- plane
-}
-
-pub struct Camera {
- pub yaw: f32,
- pub pitch: f32,
- pub position: [f32; 3],
- pub direction: [f32; 3],
- pub up: [f32; 3],
- pub fov: f32,
- pub znear: f32,
- pub zfar: f32,
- pub perspective_matrix: [[f32; 4]; 4],
-}
-impl Camera {
- /// Update camera direction based on yaw/pitch
- pub fn update_direction(&mut self) {
- self.direction = calculate_forward_direction(self.yaw, self.pitch);
- }
- pub fn forward(&mut self, amount: f32) {
- self.position[0] += self.direction[0] * amount;
- self.position[1] += self.direction[1] * amount;
- self.position[2] += self.direction[2] * amount;
- }
-
- pub fn view_matrix(&self) -> [[f32; 4]; 4] {
- let position = self.position;
- let direction = self.direction;
- let up = self.up;
-
- let f = {
- let f = direction;
- let len = f[0] * f[0] + f[1] * f[1] + f[2] * f[2];
- let len = len.sqrt();
- [f[0] / len, f[1] / len, f[2] / len]
- };
- let s = [up[1] * f[2] - up[2] * f[1],
- up[2] * f[0] - up[0] * f[2],
- up[0] * f[1] - up[1] * f[0]];
- let s_norm = {
- let len = s[0] * s[0] + s[1] * s[1] + s[2] * s[2];
- let len = len.sqrt();
- [s[0] / len, s[1] / len, s[2] / len]
- };
- let u = [f[1] * s_norm[2] - f[2] * s_norm[1],
- f[2] * s_norm[0] - f[0] * s_norm[2],
- f[0] * s_norm[1] - f[1] * s_norm[0]];
- let p = [-position[0] * s_norm[0] - position[1] * s_norm[1] - position[2] * s_norm[2],
- -position[0] * u[0] - position[1] * u[1] - position[2] * u[2],
- -position[0] * f[0] - position[1] * f[1] - position[2] * f[2]];
- [
- [s_norm[0], u[0], f[0], 0.0],
- [s_norm[1], u[1], f[1], 0.0],
- [s_norm[2], u[2], f[2], 0.0],
- [p[0], p[1], p[2], 1.0],
- ]
- }
-
- pub fn update_perspective_matrix(&mut self, target_dimensions: (u32, u32)) {
- let znear = self.znear;
- let zfar = self.zfar;
- let fov = self.fov;
- let (width, height) = target_dimensions;
- let aspect_ratio = height as f32 / width as f32;
- let f = 1.0 / (fov / 2.0).tan();
- self.perspective_matrix = [
- [f*aspect_ratio, 0.0, 0.0, 0.0],
- [0.0, f, 0.0, 0.0],
- [0.0, 0.0, (zfar+znear)/(zfar-znear), 1.0],
- [0.0, 0.0, -(2.0*zfar*znear)/(zfar-znear), 0.0],
- ];
- }
-
- // https://www.flipcode.com/archives/Frustum_Culling.shtml
- // https://web.archive.org/web/20070226173353/https://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf
- pub fn frustum_planes(&self, normalized: bool) -> [[f32; 4]; 6] {
- let mut p_planes = [[0.0_f32; 4]; 6];
- let matrix = self.perspective_matrix;
-
- // Left clipping plane
- p_planes[0][0] = matrix[3][0] + matrix[0][0];
- p_planes[0][1] = matrix[3][1] + matrix[0][1];
- p_planes[0][2] = matrix[3][2] + matrix[0][2];
- p_planes[0][3] = matrix[3][3] + matrix[0][3];
- // Right clipping plane
- p_planes[1][0] = matrix[3][0] - matrix[0][0];
- p_planes[1][1] = matrix[3][1] - matrix[0][1];
- p_planes[1][2] = matrix[3][2] - matrix[0][2];
- p_planes[1][3] = matrix[3][3] - matrix[0][3];
- // Top clipping plane
- p_planes[2][0] = matrix[3][0] - matrix[1][0];
- p_planes[2][1] = matrix[3][1] - matrix[1][1];
- p_planes[2][2] = matrix[3][2] - matrix[1][2];
- p_planes[2][3] = matrix[3][3] - matrix[1][3];
- // Bottom clipping plane
- p_planes[3][0] = matrix[3][0] + matrix[1][0];
- p_planes[3][1] = matrix[3][1] + matrix[1][1];
- p_planes[3][2] = matrix[3][2] + matrix[1][2];
- p_planes[3][3] = matrix[3][3] + matrix[1][3];
- // Near clipping plane
- p_planes[4][0] = matrix[3][0] + matrix[3][0];
- p_planes[4][1] = matrix[3][1] + matrix[3][1];
- p_planes[4][2] = matrix[3][2] + matrix[3][2];
- p_planes[4][3] = matrix[3][3] + matrix[3][3];
- // Far clipping plane
- p_planes[5][0] = matrix[3][0] - matrix[3][0];
- p_planes[5][1] = matrix[3][1] - matrix[3][1];
- p_planes[5][2] = matrix[3][2] - matrix[3][2];
- p_planes[5][3] = matrix[3][3] - matrix[3][3];
-
- //Normalize planes
- if normalized {
- for plane in &mut p_planes {
- *plane = normalize_plane(*plane);
- }
- }
-
- p_planes
- }
-
-}
-impl Default for Camera {
- fn default() -> Self {
- Self {
- position: [0., 0., 0.],
- direction: [0., 0., 0.],
- up: [0., 1., 0.],
- fov: PI / 3.,
- zfar: 1024.,
- znear: 0.1,
- yaw: 0.,
- pitch: 0.,
- perspective_matrix: [[0.; 4]; 4]
- }
- }
-}
diff --git a/src/game/controller.rs b/src/game/controller.rs
deleted file mode 100644
index 58b5b29..0000000
--- a/src/game/controller.rs
+++ /dev/null
@@ -1,116 +0,0 @@
-use glium::glutin::event::{VirtualKeyCode, ElementState};
-use std::f32::consts::PI;
-use crate::game::camera::Camera;
-
-#[derive(Default, Clone, Copy)]
-pub struct InputAmounts {
- move_x: (f32, f32),
- move_y: (f32, f32),
- move_z: (f32, f32),
- look_h: f32,
- look_v: f32,
-}
-
-pub struct Actions {
- pub movement: [f32; 3],
- pub rotation: [f32; 2],
-}
-impl Actions {
- pub fn apply_to_camera(&self, camera: &mut Camera) {
- //Apply rotation
- camera.yaw -= self.rotation[0];
- camera.pitch -= self.rotation[1];
- camera.pitch = camera.pitch.clamp(-PI/2. + f32::EPSILON, PI/2. - f32::EPSILON);
- camera.update_direction();
- //Apply movement
- let (yaw_sin, yaw_cos) = camera.yaw.sin_cos();
- //forward movement
- camera.position[0] += yaw_cos * self.movement[2];
- camera.position[2] += yaw_sin * self.movement[2];
- //sideways movement
- camera.position[0] -= -yaw_sin * self.movement[0];
- camera.position[2] -= yaw_cos * self.movement[0];
- //up/down movement
- camera.position[1] += self.movement[1];
- }
-}
-
-pub struct Controls {
- inputs: InputAmounts,
- pub speed: f32,
- pub sensitivity: f32,
-}
-impl Controls {
- //TODO locking controls
- pub fn lock(&mut self) {
- todo!()
- }
- pub fn unlock(&mut self) {
- todo!()
- }
- pub fn process_mouse_input(&mut self, dx: f64, dy: f64) {
- self.inputs.look_h += dx as f32;
- self.inputs.look_v += dy as f32;
- }
- pub fn process_keyboard_input(&mut self, key: VirtualKeyCode, state: ElementState) {
- let value = match state {
- ElementState::Pressed => 1.,
- ElementState::Released => 0.,
- };
- match key {
- VirtualKeyCode::W | VirtualKeyCode::Up => {
- self.inputs.move_z.0 = value;
- }
- VirtualKeyCode::S | VirtualKeyCode::Down => {
- self.inputs.move_z.1 = -value;
- }
- VirtualKeyCode::A | VirtualKeyCode::Left => {
- self.inputs.move_x.0 = -value;
- }
- VirtualKeyCode::D | VirtualKeyCode::Right => {
- self.inputs.move_x.1 = value;
- }
- VirtualKeyCode::Space => {
- self.inputs.move_y.0 = value;
- }
- VirtualKeyCode::LShift => {
- self.inputs.move_y.1 = -value;
- }
- _ => ()
- }
- }
- pub fn calculate(&mut self, dt: f32) -> Actions {
- let movement = {
- let move_x = self.inputs.move_x.0 + self.inputs.move_x.1;
- let move_y = self.inputs.move_y.0 + self.inputs.move_y.1;
- let move_z = self.inputs.move_z.0 + self.inputs.move_z.1;
- let magnitude = (move_x.powi(2) + move_y.powi(2) + move_z.powi(2)).sqrt();
- if magnitude == 0. {
- [0., 0., 0.]
- } else {
- [
- dt * self.speed * (move_x / magnitude),
- dt * self.speed * (move_y / magnitude),
- dt * self.speed * (move_z / magnitude)
- ]
- }
- };
- let rotation = [ //Do mouse inputs need to be multiplied by dt?
- self.inputs.look_h * self.sensitivity * 0.01, //* dt
- self.inputs.look_v * self.sensitivity * 0.01 //* dt
- ];
- //Only mouse related actions need to be reset
- self.inputs.look_h = 0.;
- self.inputs.look_v = 0.;
- Actions { movement, rotation }
- }
-}
-impl Default for Controls {
- fn default() -> Self {
- Self {
- inputs: Default::default(),
- speed: 40.,
- sensitivity: 1.24,
- }
- }
-}
diff --git a/src/game/display.rs b/src/game/display.rs
deleted file mode 100644
index 97d7e02..0000000
--- a/src/game/display.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use glium::Display;
-use glium::glutin::{
- ContextBuilder,
- GlProfile,
- window::WindowBuilder,
- event_loop::EventLoop
-};
-
-pub fn init_display(event_loop: &EventLoop<()>) -> Display {
- let wb = WindowBuilder::new()
- .with_maximized(true);
- let cb = ContextBuilder::new()
- .with_depth_buffer(24)
- .with_gl_profile(GlProfile::Core);
- Display::new(wb, cb, event_loop).unwrap()
-}
diff --git a/src/game/items.rs b/src/game/items.rs
deleted file mode 100644
index 0028d1c..0000000
--- a/src/game/items.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-//TODO items
-
-#[derive(Clone, Copy, Debug)]
-pub enum Item {
- StoneBlock,
- DirtBlock,
- GrassBlock,
- SandBlock,
-}
diff --git a/src/game/options.rs b/src/game/options.rs
deleted file mode 100644
index 7a19998..0000000
--- a/src/game/options.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-#[derive(Clone, Debug)]
-pub struct GameOptions {
- pub render_distance: u8,
- pub debug_wireframe_mode: bool,
-}
-impl Default for GameOptions {
- fn default() -> Self {
- Self {
- render_distance: if cfg!(debug_assertions) { 8 } else { 16 },
- debug_wireframe_mode: false,
- }
- }
-}
diff --git a/src/game/physics.rs b/src/game/physics.rs
deleted file mode 100644
index a0e0d2c..0000000
--- a/src/game/physics.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use glam::{Vec3A, vec3a};
-use crate::game::World;
-
-const GRAVITY: Vec3A = vec3a(0., -1., 0.);
-
-pub struct BasicPhysicsActor {
- pub height: f32,
- pub gravity: Vec3A,
- pub position: Vec3A,
- pub velocity: Vec3A,
-}
-impl BasicPhysicsActor {
- pub fn new(height: f32) -> Self {
- Self {
- height,
- gravity: GRAVITY,
- position: vec3a(0., 0., 0.),
- velocity: vec3a(0., 0., 0.),
- }
- }
- pub fn update(&mut self, world: &World, dt: f32) {
- self.velocity += GRAVITY;
- self.position += self.velocity;
- loop {
- let block_pos = self.position.floor().as_ivec3();
- let block_pos_f = block_pos.as_vec3a();
- if let Some(block) = world.try_get(block_pos) {
- match block.descriptor().collision {
- Some(super::blocks::CollisionType::Solid) => {
- let position_delta = self.position - block_pos_f;
- let distance_to_zero = position_delta.abs();
- let distance_to_one = (vec3a(1., 1., 1.) - position_delta).abs();
-
- // let mut max_distance = 0;
- // let mut max_distance_normal = 0;
- // distance_to_one.x
- //todo compute restitution here
- }
- _ => break
- }
- } else {
- break
- }
- }
- }
-}
diff --git a/src/game/player.rs b/src/game/player.rs
deleted file mode 100644
index 8fde307..0000000
--- a/src/game/player.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use crate::game::camera::Camera;
-use crate::game::physics::BasicPhysicsActor;
-
-pub struct MainPlayer {
- pub camera: Camera,
- pub actor: BasicPhysicsActor,
-}
diff --git a/src/game/shaders.rs b/src/game/shaders.rs
deleted file mode 100644
index d949234..0000000
--- a/src/game/shaders.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-use glium::{Display, Program};
-
-pub mod chunk;
-pub mod colored2d;
-
-pub struct Programs {
- pub colored_2d: Program,
- pub chunk: Program,
-}
-impl Programs {
- pub fn compile_all(display: &Display) -> Self {
- Self {
- colored_2d: Program::from_source(display, colored2d::VERTEX_SHADER, colored2d::FRAGMENT_SHADER, None).unwrap(),
- chunk: Program::from_source(display, chunk::VERTEX_SHADER, chunk::FRAGMENT_SHADER, None).unwrap(),
- }
- }
-}
diff --git a/src/game/shaders/chunk.rs b/src/game/shaders/chunk.rs
deleted file mode 100644
index 03c47e6..0000000
--- a/src/game/shaders/chunk.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use glium::implement_vertex;
-
-#[derive(Clone, Copy)]
-pub struct Vertex {
- pub position: [f32; 3],
- pub normal: [f32; 3],
- pub uv: [f32; 2],
- pub tex_index: u8,
-}
-implement_vertex!(Vertex, position, normal, uv, tex_index);
-
-pub const VERTEX_SHADER: &str = include_str!("./glsl/chunk.vert");
-pub const FRAGMENT_SHADER: &str = include_str!("./glsl/chunk.frag");
-
-// pub const VERTEX_SHADER: &str = r#"
-// #version 150 core
-
-// in vec3 position;
-// in vec3 normal;
-// in vec2 uv;
-// out vec3 v_normal;
-// out vec2 v_uv;
-// uniform mat4 perspective;
-// uniform mat4 view;
-// uniform mat4 model;
-
-// void main() {
-// mat4 modelview = view * model;
-// //v_normal = transpose(inverse(mat3(modelview))) * normal;
-// v_normal = normal;
-// v_uv = uv;
-// gl_Position = perspective * modelview * vec4(position, 1.0);
-// }
-// "#;
diff --git a/src/game/shaders/colored2d.rs b/src/game/shaders/colored2d.rs
deleted file mode 100644
index 336dab8..0000000
--- a/src/game/shaders/colored2d.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use glium::implement_vertex;
-
-#[derive(Clone, Copy)]
-pub struct Vertex {
- pub position: [f32; 2]
-}
-implement_vertex!(Vertex, position);
-
-pub const VERTEX_SHADER: &str = include_str!("./glsl/colored2d.vert");
-pub const FRAGMENT_SHADER: &str = include_str!("./glsl/colored2d.frag");
diff --git a/src/game/shaders/glsl/colored2d.vert b/src/game/shaders/glsl/colored2d.vert
deleted file mode 100644
index af1807e..0000000
--- a/src/game/shaders/glsl/colored2d.vert
+++ /dev/null
@@ -1,7 +0,0 @@
-#version 150 core
-
-in vec2 position;
-
-void main() {
- gl_Position = vec4(position, 0., 1.);
-}
diff --git a/src/game/world.rs b/src/game/world.rs
deleted file mode 100644
index de8ff50..0000000
--- a/src/game/world.rs
+++ /dev/null
@@ -1,201 +0,0 @@
-use glam::{Vec2, IVec2, IVec3, Vec3Swizzles};
-use glium::{
- Display, Frame, Surface,
- DrawParameters, Depth,
- DepthTest, PolygonMode,
- uniform,
- uniforms::{
- Sampler, SamplerBehavior,
- MinifySamplerFilter, MagnifySamplerFilter,
- }
-};
-use hashbrown::HashMap;
-use crate::game::{
- options::GameOptions,
- shaders::Programs,
- assets::Assets,
- blocks::Block,
-};
-
-mod chunk;
-mod thread;
-
-use chunk::{Chunk, ChunkState, CHUNK_SIZE};
-use thread::WorldThreading;
-
-const POSITIVE_X_NEIGHBOR: usize = 0;
-const NEGATIVE_X_NEIGHBOR: usize = 1;
-const POSITIVE_Z_NEIGHBOR: usize = 2;
-const NEGATIVE_Z_NEIGHBOR: usize = 3;
-
-const MAX_TASKS: usize = 6;
-
-pub struct World {
- pub chunks: HashMap,
- pub thread: WorldThreading,
-}
-impl World {
- pub fn chunk_neighbors(&self, position: IVec2) -> [Option<&Chunk>; 4] {
- [
- self.chunks.get(&(position + IVec2::new(1, 0))),
- self.chunks.get(&(position - IVec2::new(1, 0))),
- self.chunks.get(&(position + IVec2::new(0, 1))),
- self.chunks.get(&(position - IVec2::new(0, 1))),
- ]
- }
-
- pub fn try_get(&self, position: IVec3) -> Option {
- let chunk_coord = IVec2::new(position.x, position.z) / CHUNK_SIZE as i32;
- let chunk = self.chunks.get(&chunk_coord)?;
- let block_data = chunk.block_data.as_ref()?;
- let block_position = position - (chunk_coord * CHUNK_SIZE as i32).extend(0).xzy();
- Some(
- *block_data
- .get(block_position.x as usize)?
- .get(block_position.y as usize)?
- .get(block_position.z as usize)?
- )
- }
-
- pub fn new() -> Self {
- Self {
- chunks: HashMap::new(),
- thread: WorldThreading::new(),
- }
- }
-
- pub fn render(
- &self,
- target: &mut Frame,
- programs: &Programs,
- assets: &Assets,
- perspective: [[f32; 4]; 4],
- view: [[f32; 4]; 4],
- options: &GameOptions
- ) {
- let sampler = SamplerBehavior {
- minify_filter: MinifySamplerFilter::Linear,
- magnify_filter: MagnifySamplerFilter::Nearest,
- max_anisotropy: 8,
- ..Default::default()
- };
- let draw_parameters = DrawParameters {
- depth: Depth {
- test: DepthTest::IfLess,
- write: true,
- ..Default::default()
- },
- polygon_mode: if options.debug_wireframe_mode {
- PolygonMode::Line
- } else {
- PolygonMode::Fill
- },
- backface_culling: glium::draw_parameters::BackfaceCullingMode::CullCounterClockwise,
- ..Default::default()
- };
- for (&position, chunk) in &self.chunks {
- if let Some(mesh) = &chunk.mesh {
- target.draw(
- &mesh.vertex_buffer,
- &mesh.index_buffer,
- &programs.chunk,
- &uniform! {
- position_offset: (position.as_vec2() * CHUNK_SIZE as f32).to_array(),
- view: view,
- perspective: perspective,
- tex: Sampler(&assets.textures.blocks, sampler)
- },
- &draw_parameters
- ).unwrap();
- }
- }
- }
-
- pub fn update_loaded_chunks(&mut self, around_position: Vec2, options: &GameOptions, display: &Display) {
- let render_dist = options.render_distance as i32 + 1;
- let inside_chunk = (around_position / CHUNK_SIZE as f32).as_ivec2();
-
- //Mark all chunks for unload
- for (_, chunk) in &mut self.chunks {
- chunk.desired = ChunkState::Unload;
- }
-
- //Load new/update chunks in range
- for x in -render_dist..=render_dist {
- for z in -render_dist..=render_dist {
- let offset = IVec2::new(x, z);
- let position = inside_chunk + offset;
- if !self.chunks.contains_key(&position) {
- self.chunks.insert(position, Chunk::new(position));
- }
- {
- //we only need mutable reference here:
- let chunk = self.chunks.get_mut(&position).unwrap();
- if x == -render_dist || z == -render_dist || x == render_dist || z == render_dist {
- chunk.desired = ChunkState::Loaded;
- } else {
- chunk.desired = ChunkState::Rendered;
- }
- }
- let chunk = self.chunks.get(&position).unwrap();
- if self.thread.task_amount() < MAX_TASKS {
- if matches!(chunk.state, ChunkState::Nothing) && matches!(chunk.desired, ChunkState::Loaded | ChunkState::Rendered) {
- self.thread.queue_load(position);
- self.chunks.get_mut(&position).unwrap().state = ChunkState::Loading;
- } else if matches!(chunk.state, ChunkState::Loaded) && matches!(chunk.desired, ChunkState::Rendered) {
- let mut state_changed = false;
- fn all_some<'a>(x: [Option<&'a Chunk>; 4]) -> Option<[&'a Chunk; 4]> {
- Some([x[0]?, x[1]?, x[2]?, x[3]?])
- }
- if let Some(neighbors) = all_some(self.chunk_neighbors(chunk.position)) {
- if {
- neighbors[0].block_data.is_some() &&
- neighbors[1].block_data.is_some() &&
- neighbors[2].block_data.is_some() &&
- neighbors[3].block_data.is_some()
- } {
- self.thread.queue_mesh(
- position,
- chunk.block_data.clone().unwrap(),
- [
- neighbors[0].block_data.clone().unwrap(),
- neighbors[1].block_data.clone().unwrap(),
- neighbors[2].block_data.clone().unwrap(),
- neighbors[3].block_data.clone().unwrap(),
- ]
- );
- state_changed = true;
- }
- }
- if state_changed {
- self.chunks.get_mut(&position).unwrap().state = ChunkState::Rendering;
- }
- }
- }
- }
- }
- //Unloads and state downgrades
- self.chunks.retain(|_, chunk| {
- match chunk.desired {
- // Chunk unload
- ChunkState::Unload => false,
- // Any => Nothing downgrade
- ChunkState::Nothing => {
- chunk.block_data = None;
- chunk.mesh = None;
- chunk.state = ChunkState::Nothing;
- true
- },
- //Render => Loaded downgrade
- ChunkState::Loaded if matches!(chunk.state, ChunkState::Rendering | ChunkState::Rendered) => {
- chunk.mesh = None;
- chunk.state = ChunkState::Loaded;
- true
- },
- _ => true
- }
- });
- //Apply changes from threads
- self.thread.apply_tasks(&mut self.chunks, display);
- }
-}
diff --git a/src/game/world/chunk.rs b/src/game/world/chunk.rs
deleted file mode 100644
index 481b04b..0000000
--- a/src/game/world/chunk.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use glam::IVec2;
-use glium::{VertexBuffer, IndexBuffer};
-use crate::game::{
- blocks::Block,
- shaders::chunk::Vertex as ChunkVertex
-};
-
-pub const CHUNK_SIZE: usize = 32;
-pub const CHUNK_HEIGHT: usize = 255;
-
-pub enum ChunkState {
- Unload,
- Nothing,
- Loading,
- Loaded,
- Rendering,
- Rendered,
-}
-
-pub type ChunkData = Box<[[[Block; CHUNK_SIZE]; CHUNK_HEIGHT]; CHUNK_SIZE]>;
-
-pub struct ChunkMesh {
- pub is_dirty: bool,
- pub vertex_buffer: VertexBuffer,
- pub index_buffer: IndexBuffer,
-}
-
-pub struct Chunk {
- pub position: IVec2,
- pub block_data: Option,
- pub mesh: Option,
- pub state: ChunkState,
- pub desired: ChunkState,
-}
-impl Chunk {
- pub fn new(position: IVec2) -> Self {
- Self {
- position,
- block_data: None,
- mesh: None,
- state: ChunkState::Nothing,
- desired: ChunkState::Nothing,
- }
- }
-}
diff --git a/src/game/world/thread.rs b/src/game/world/thread.rs
deleted file mode 100644
index b051112..0000000
--- a/src/game/world/thread.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-use glam::IVec2;
-use glium::{Display, VertexBuffer, IndexBuffer, index::PrimitiveType};
-use std::{mem, thread::{self, JoinHandle}};
-use hashbrown::HashMap;
-use super::chunk::{Chunk, ChunkData, ChunkState};
-use crate::game::{shaders::chunk::Vertex as ChunkVertex, world::chunk::ChunkMesh};
-
-mod world_gen;
-mod mesh_gen;
-
-#[derive(Default)]
-pub struct WorldThreading {
- //drain_filter is not stable yet so
- //Options are needed here to take ownership,
- //None values should never appear here!
- pub load_tasks: HashMap>>,
- pub mesh_tasks: HashMap, Vec)>>>,
-}
-impl WorldThreading {
- pub fn new() -> Self {
- Self::default()
- }
- pub fn is_done(&self) -> bool {
- self.load_tasks.is_empty() &&
- self.mesh_tasks.is_empty()
- }
- pub fn task_amount(&self) -> usize {
- self.load_tasks.len() + self.mesh_tasks.len()
- }
- pub fn queue_load(&mut self, position: IVec2) {
- let handle = thread::spawn(move || {
- world_gen::generate_chunk(position, 0xdead_cafe)
- });
- if self.load_tasks.insert(position, Some(handle)).is_some() {
- log::warn!("load: discarded {}, reason: new task started", position);
- }
- }
- pub fn queue_mesh(&mut self, position: IVec2, chunk: ChunkData, neighbor_data: [ChunkData; 4]) {
- let handle = thread::spawn(move || {
- mesh_gen::generate_mesh(position, chunk, neighbor_data)
- });
- if self.mesh_tasks.insert(position, Some(handle)).is_some() {
- log::warn!("mesh: discarded {}, reason: new task started", position);
- }
- }
- pub fn apply_tasks(&mut self, chunks: &mut HashMap, display: &Display) {
- //LOAD TASKS
- self.load_tasks.retain(|position, handle| {
- if !chunks.contains_key(position) {
- log::warn!("load: discarded {}, reason: chunk no longer exists", position);
- return false
- }
- if !matches!(chunks.get(position).unwrap().desired, ChunkState::Loaded | ChunkState::Rendered) {
- log::warn!("load: discarded {}, reason: state undesired", position);
- return false
- }
- if !handle.as_ref().expect("Something went terribly wrong").is_finished() {
- //task not finished yet, keep it and wait
- return true
- }
- log::info!("load: done {}", position);
- let handle = mem::take(handle).unwrap();
- let data = handle.join().unwrap();
- let chunk = chunks.get_mut(position).unwrap();
- chunk.block_data = Some(data);
- chunk.state = ChunkState::Loaded;
- false
- });
- //MESH TASKS
- self.mesh_tasks.retain(|position, handle| {
- if !chunks.contains_key(position) {
- log::warn!("mesh: discarded {}, reason: chunk no longer exists", position);
- return false
- }
- if !matches!(chunks.get(position).unwrap().desired, ChunkState::Rendered) {
- log::warn!("mesh: discarded {}, reason: state undesired", position);
- return false
- }
- if !handle.as_ref().expect("Something went terribly wrong").is_finished() {
- //task not finished yet, keep it and wait
- return true
- }
- log::info!("mesh: done {}", position);
- let handle = mem::take(handle).unwrap();
- let (shape, index) = handle.join().unwrap();
- let chunk = chunks.get_mut(position).unwrap();
- chunk.mesh = Some(ChunkMesh {
- is_dirty: false,
- vertex_buffer: VertexBuffer::new(display, &shape).expect("Failed to build VertexBuffer"),
- index_buffer: IndexBuffer::new(display, PrimitiveType::TrianglesList, &index).expect("Failed to build IndexBuffer")
- });
- chunk.state = ChunkState::Rendered;
- false
- });
-
- }
-}
diff --git a/src/game/world/thread/mesh_gen.rs b/src/game/world/thread/mesh_gen.rs
deleted file mode 100644
index ad32b11..0000000
--- a/src/game/world/thread/mesh_gen.rs
+++ /dev/null
@@ -1,139 +0,0 @@
-use glam::{IVec2, IVec3, Vec2, Vec3A, vec3a, vec2, ivec3};
-use strum::{EnumIter, IntoEnumIterator};
-use crate::game::{
- world::{
- POSITIVE_X_NEIGHBOR,
- NEGATIVE_X_NEIGHBOR,
- POSITIVE_Z_NEIGHBOR,
- NEGATIVE_Z_NEIGHBOR,
- chunk::{ChunkData, CHUNK_SIZE, CHUNK_HEIGHT}
- },
- shaders::chunk::Vertex,
- blocks::Block
-};
-
-#[repr(usize)]
-#[derive(Clone, Copy, Debug, EnumIter)]
-pub enum CubeFace {
- Top = 0,
- Front = 1,
- Left = 2,
- Right = 3,
- Back = 4,
- Bottom = 5,
-}
-const CUBE_FACE_VERTICES: [[Vec3A; 4]; 6] = [
- [vec3a(0., 1., 0.), vec3a(0., 1., 1.), vec3a(1., 1., 0.), vec3a(1., 1., 1.)],
- [vec3a(0., 0., 0.), vec3a(0., 1., 0.), vec3a(1., 0., 0.), vec3a(1., 1., 0.)],
- [vec3a(0., 0., 1.), vec3a(0., 1., 1.), vec3a(0., 0., 0.), vec3a(0., 1., 0.)],
- [vec3a(1., 0., 0.), vec3a(1., 1., 0.), vec3a(1., 0., 1.), vec3a(1., 1., 1.)],
- [vec3a(1., 0., 1.), vec3a(1., 1., 1.), vec3a(0., 0., 1.), vec3a(0., 1., 1.)],
- [vec3a(0., 0., 1.), vec3a(0., 0., 0.), vec3a(1., 0., 1.), vec3a(1., 0., 0.)],
-];
-const CUBE_FACE_NORMALS: [[f32; 3]; 6] = [
- [0., 1., 0.],
- [0., 0., -1.],
- [-1., 0., 0.],
- [1., 0., 0.],
- [0., 0., 1.],
- [0., -1., 0.]
-];
-const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3];
-const UV_COORDS: [[f32; 2]; 4] = [
- [0., 0.],
- [0., 1.],
- [1., 0.],
- [1., 1.],
-];
-
-
-#[derive(Default)]
-struct MeshBuilder {
- vertex_buffer: Vec,
- index_buffer: Vec,
- idx_counter: u32,
-}
-impl MeshBuilder {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn add_face(&mut self, face: CubeFace, coord: IVec3, texture: u8) {
- let coord = coord.as_vec3a();
- let face_index = face as usize;
-
- //Push vertexes
- let norm = CUBE_FACE_NORMALS[face_index];
- let vert = CUBE_FACE_VERTICES[face_index];
- self.vertex_buffer.reserve(4);
- for i in 0..4 {
- self.vertex_buffer.push(Vertex {
- position: (coord + vert[i]).to_array(),
- normal: norm,
- uv: UV_COORDS[i],
- tex_index: texture
- });
- }
-
- //Push indices
- self.index_buffer.extend_from_slice(&CUBE_FACE_INDICES.map(|x| x + self.idx_counter));
- self.idx_counter += 4;
- }
-
- pub fn finish(self) -> (Vec, Vec) {
- (self.vertex_buffer, self.index_buffer)
- }
-}
-
-pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> (Vec, Vec) {
- let get_block = |pos: IVec3| -> Block {
- if pos.x < 0 {
- neighbors[NEGATIVE_X_NEIGHBOR][(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize]
- } else if pos.x >= CHUNK_SIZE as i32 {
- neighbors[POSITIVE_X_NEIGHBOR][pos.x as usize - CHUNK_SIZE as usize][pos.y as usize][pos.z as usize]
- } else if pos.z < 0 {
- neighbors[NEGATIVE_Z_NEIGHBOR][pos.x as usize][pos.y as usize][(CHUNK_SIZE as i32 + pos.z) as usize]
- } else if pos.z >= CHUNK_SIZE as i32 {
- neighbors[POSITIVE_Z_NEIGHBOR][pos.x as usize][pos.y as usize][pos.z as usize - CHUNK_SIZE as usize]
- } else {
- chunk_data[pos.x as usize][pos.y as usize][pos.z as usize]
- }
- };
-
- let mut builer = MeshBuilder::new();
-
- for x in 0..CHUNK_SIZE {
- for y in 0..CHUNK_HEIGHT {
- for z in 0..CHUNK_SIZE {
- let coord = ivec3(x as i32, y as i32, z as i32);
- let descriptor = get_block(coord).descriptor();
- if descriptor.render.is_none() {
- continue
- }
- for face in CubeFace::iter() {
- let facing = Vec3A::from_array(CUBE_FACE_NORMALS[face as usize]).as_ivec3();
- let facing_coord = coord + facing;
- let show = {
- (facing_coord.y < 0) ||
- (facing_coord.y >= CHUNK_HEIGHT as i32) ||
- get_block(facing_coord).descriptor().render.is_none()
- };
- if show {
- let texures = descriptor.render.unwrap().1;
- let block_texture = match face {
- CubeFace::Top => texures.top,
- CubeFace::Front => texures.front,
- CubeFace::Left => texures.left,
- CubeFace::Right => texures.right,
- CubeFace::Back => texures.back,
- CubeFace::Bottom => texures.bottom,
- };
- builer.add_face(face, coord, block_texture as u8);
- }
- }
- }
- }
- }
-
- builer.finish()
-}
diff --git a/src/game/world/thread/world_gen.rs b/src/game/world/thread/world_gen.rs
deleted file mode 100644
index 34b979b..0000000
--- a/src/game/world/thread/world_gen.rs
+++ /dev/null
@@ -1,58 +0,0 @@
-use glam::{Vec2, DVec2, IVec2};
-use noise::{NoiseFn, Perlin, Simplex, Fbm, Seedable};
-use crate::game::{
- world::chunk::{ChunkData, CHUNK_SIZE, CHUNK_HEIGHT},
- blocks::Block
-};
-
-const HEIGHTMAP_SCALE: f64 = 0.004;
-const MOUNTAINESS_SCALE: f64 = 0.0001;
-const MNT_RAMP_1: f64 = 0.5;
-const MNT_RAMP_2: f64 = 0.6;
-const MTN_VAL_SCALE: f64 = 1.233;
-const TERRAIN_HEIGHT_MIN: f64 = 60.;
-const TERRAIN_HEIGHT_MAX: f64 = 80.;
-
-pub fn generate_chunk(position: IVec2, seed: u32) -> ChunkData {
- let world_xz = position.as_vec2() * CHUNK_SIZE as f32;
- let mut chunk = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_HEIGHT]; CHUNK_SIZE]);
-
- //generate noises
- let mut terrain_base_fbm: Fbm = Fbm::new(seed);
- terrain_base_fbm.octaves = 6;
-
- let mut mountainess_base_fbm: Fbm = Fbm::new(seed);
- mountainess_base_fbm.octaves = 4;
-
- //put everything together
- for x in 0..CHUNK_SIZE {
- for z in 0..CHUNK_SIZE {
- let point = world_xz.as_dvec2() + DVec2::from_array([x as f64, z as f64]);
-
- let heightmap = (terrain_base_fbm.get((point * HEIGHTMAP_SCALE).to_array()) + 1.) / 2.;
- let mountainess = MTN_VAL_SCALE * ((mountainess_base_fbm.get((point * MOUNTAINESS_SCALE).to_array()) + 1.) / 2.);
-
- //generate basic terrain
- let terain_height =
- (
- TERRAIN_HEIGHT_MIN +
- (heightmap * TERRAIN_HEIGHT_MAX * (0.1 + 1.5 * if mountainess < MNT_RAMP_1 {
- 0.
- } else {
- if mountainess > MNT_RAMP_2 {
- 1.
- } else {
- (mountainess - MNT_RAMP_1) / (MNT_RAMP_2 - MNT_RAMP_1) * 1.
- }
- }))
- ).floor() as usize;
- for y in 0..terain_height {
- chunk[x][y][z] = Block::Dirt;
- }
- chunk[x][terain_height][z] = Block::Grass;
- }
- }
-
- //return generated world
- chunk
-}
diff --git a/src/input.rs b/src/input.rs
new file mode 100644
index 0000000..7f408e9
--- /dev/null
+++ b/src/input.rs
@@ -0,0 +1,84 @@
+use glam::{Vec2, DVec2};
+use glium::glutin::event::{DeviceEvent, VirtualKeyCode, ElementState};
+use hashbrown::HashSet;
+use nohash_hasher::BuildNoHashHasher;
+use shipyard::{AllStoragesView, Unique, View, IntoIter, UniqueViewMut, Workload, IntoWorkload, UniqueView};
+use crate::events::InputDeviceEvent;
+
+#[derive(Unique, Clone, Copy, Default, Debug)]
+pub struct Inputs {
+ pub movement: Vec2,
+ pub look: Vec2,
+ pub action_a: bool,
+ pub action_b: bool,
+}
+
+#[derive(Unique, Clone, Copy, Default, Debug)]
+pub struct PrevInputs(pub Inputs);
+
+#[derive(Unique, Clone, Default, Debug)]
+pub struct RawInputState {
+ pub keyboard_state: HashSet>,
+ pub button_state: [bool; 32],
+ pub mouse_delta: DVec2
+}
+
+pub fn process_events(
+ device_events: View,
+ mut input_state: UniqueViewMut,
+) {
+ input_state.mouse_delta = DVec2::ZERO;
+ for event in device_events.iter() {
+ match event.event {
+ DeviceEvent::MouseMotion { delta } => {
+ input_state.mouse_delta = DVec2::from(delta);
+ },
+ DeviceEvent::Key(input) => {
+ if let Some(keycode) = input.virtual_keycode {
+ match input.state {
+ ElementState::Pressed => input_state.keyboard_state.insert(keycode),
+ ElementState::Released => input_state.keyboard_state.remove(&keycode),
+ };
+ }
+ },
+ DeviceEvent::Button { button, state } => {
+ if button < 32 {
+ input_state.button_state[button as usize] = matches!(state, ElementState::Pressed);
+ }
+ },
+ _ => ()
+ }
+ }
+}
+
+pub fn update_input_states (
+ raw_inputs: UniqueView,
+ mut inputs: UniqueViewMut,
+ mut prev_inputs: UniqueViewMut,
+) {
+ prev_inputs.0 = *inputs;
+ inputs.movement = Vec2::new(
+ raw_inputs.keyboard_state.contains(&VirtualKeyCode::D) as u32 as f32 -
+ raw_inputs.keyboard_state.contains(&VirtualKeyCode::A) as u32 as f32,
+ raw_inputs.keyboard_state.contains(&VirtualKeyCode::W) as u32 as f32 -
+ raw_inputs.keyboard_state.contains(&VirtualKeyCode::S) as u32 as f32
+ ).normalize_or_zero();
+ inputs.look = raw_inputs.mouse_delta.as_vec2();
+ inputs.action_a = raw_inputs.button_state[1];
+ inputs.action_b = raw_inputs.button_state[3];
+}
+
+pub fn init_input (
+ storages: AllStoragesView
+) {
+ storages.add_unique(Inputs::default());
+ storages.add_unique(PrevInputs::default());
+ storages.add_unique(RawInputState::default());
+}
+
+pub fn process_inputs() -> Workload {
+ (
+ process_events,
+ update_input_states
+ ).into_workload()
+}
diff --git a/src/main.rs b/src/main.rs
index 08de79c..2cda345 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,159 @@
-mod game;
+use shipyard::{
+ World, Workload, IntoWorkload,
+ UniqueView, UniqueViewMut,
+ NonSendSync, Unique
+};
+use glium::{
+ glutin::{
+ event_loop::{EventLoop, ControlFlow},
+ event::{Event, WindowEvent}
+ }
+};
+use glam::vec3;
+use std::time::{Instant, Duration};
+
mod logging;
+pub(crate) mod rendering;
+pub(crate) mod world;
+pub(crate) mod player;
+pub(crate) mod prefabs;
+pub(crate) mod transform;
+pub(crate) mod settings;
+pub(crate) mod camera;
+pub(crate) mod events;
+pub(crate) mod input;
+pub(crate) mod fly_controller;
+pub(crate) mod block_placement;
+
+use rendering::{
+ Renderer,
+ RenderTarget,
+ BackgroundColor,
+ clear_background
+};
+use world::{
+ init_game_world,
+ loading::update_loaded_world_around_player,
+ raycast::update_raycasts
+};
+use player::spawn_player;
+use prefabs::load_prefabs;
+use settings::GameSettings;
+use camera::compute_cameras;
+use events::{clear_events, process_glutin_events};
+use input::{init_input, process_inputs};
+use fly_controller::update_controllers;
+use rendering::{
+ selection_box::render_selection_box,
+ world::draw_world,
+};
+use block_placement::block_placement_system;
+
+#[derive(Unique)]
+pub(crate) struct DeltaTime(Duration);
+
+fn startup() -> Workload {
+ (
+ load_prefabs,
+ init_input,
+ init_game_world,
+ spawn_player,
+ ).into_workload()
+}
+fn update() -> Workload {
+ (
+ process_inputs,
+ update_controllers,
+ update_loaded_world_around_player,
+ update_raycasts,
+ block_placement_system,
+ compute_cameras
+ ).into_workload()
+}
+fn render() -> Workload {
+ (
+ clear_background,
+ draw_world,
+ render_selection_box,
+ ).into_sequential_workload()
+}
+fn after_frame_end() -> Workload {
+ (
+ clear_events,
+ ).into_workload()
+}
+
fn main() {
logging::init();
- game::run();
+
+ //Create event loop
+ let event_loop = EventLoop::new();
+
+ //Create a shipyard world
+ let mut world = World::new();
+
+ //Add systems and uniques, Init and load things
+ world.add_unique_non_send_sync(Renderer::init(&event_loop));
+ world.add_unique(BackgroundColor(vec3(0.5, 0.5, 1.)));
+ world.add_unique(DeltaTime(Duration::default()));
+ world.add_unique(GameSettings::default());
+
+ //Register workloads
+ world.add_workload(startup);
+ world.add_workload(update);
+ world.add_workload(render);
+ world.add_workload(after_frame_end);
+
+ //Run startup systems
+ world.run_workload(startup).unwrap();
+
+ //Run the event loop
+ let mut last_update = Instant::now();
+ event_loop.run(move |event, _, control_flow| {
+ *control_flow = ControlFlow::Poll;
+ process_glutin_events(&mut world, &event);
+ match event {
+ Event::WindowEvent { event, .. } => match event {
+ WindowEvent::Resized(_size) => {
+ // todo ...
+ }
+ WindowEvent::CloseRequested => {
+ log::info!("exit requested");
+ *control_flow = ControlFlow::Exit;
+ },
+ _ => (),
+ },
+ Event::MainEventsCleared => {
+ //Update delta time (maybe move this into a system?)
+ {
+ let mut dt_view = world.borrow::>().unwrap();
+ let now = Instant::now();
+ dt_view.0 = now - last_update;
+ last_update = now;
+ }
+
+ //Run update workflow
+ world.run_workload(update).unwrap();
+
+ //Start rendering (maybe use custom views for this?)
+ let target = {
+ let renderer = world.borrow::>>().unwrap();
+ renderer.display.draw()
+ };
+ world.add_unique_non_send_sync(RenderTarget(target));
+
+ //Run render workflow
+ world.run_workload(render).unwrap();
+
+ //Finish rendering
+ let target = world.remove_unique::().unwrap();
+ target.0.finish().unwrap();
+
+ //FrameEnd
+ world.run_workload(after_frame_end).unwrap();
+ },
+ _ => (),
+ };
+ });
}
diff --git a/src/player.rs b/src/player.rs
new file mode 100644
index 0000000..a6afaed
--- /dev/null
+++ b/src/player.rs
@@ -0,0 +1,27 @@
+use shipyard::{Component, AllStoragesViewMut};
+use crate::{
+ transform::Transform,
+ camera::Camera,
+ fly_controller::FlyController,
+ world::raycast::LookingAtBlock,
+};
+
+#[derive(Component)]
+pub struct Player;
+
+#[derive(Component)]
+pub struct MainPlayer;
+
+pub fn spawn_player (
+ mut storages: AllStoragesViewMut
+) {
+ log::info!("spawning player");
+ storages.add_entity((
+ Player,
+ MainPlayer,
+ Transform::default(),
+ Camera::default(),
+ FlyController,
+ LookingAtBlock::default(),
+ ));
+}
diff --git a/src/prefabs.rs b/src/prefabs.rs
new file mode 100644
index 0000000..793966e
--- /dev/null
+++ b/src/prefabs.rs
@@ -0,0 +1,87 @@
+use shipyard::{NonSendSync, UniqueView, Unique, AllStoragesView};
+use glium::{texture::{SrgbTexture2dArray, MipmapsOption}, Program};
+use strum::EnumIter;
+use crate::rendering::Renderer;
+
+mod texture;
+mod shaders;
+
+use texture::load_texture2darray_prefab;
+use shaders::include_shader_prefab;
+
+pub trait AssetPaths {
+ fn file_name(self) -> &'static str;
+}
+
+#[derive(Clone, Copy, Debug, EnumIter)]
+#[repr(u8)]
+pub enum BlockTexture {
+ Stone = 0,
+ Dirt = 1,
+ GrassTop = 2,
+ GrassSide = 3,
+ Sand = 4,
+ Bedrock = 5,
+ Wood = 6,
+ WoodTop = 7,
+ Leaf = 8,
+ Torch = 9,
+ TallGrass = 10,
+ Snow = 11,
+ GrassSideSnow = 12,
+}
+impl AssetPaths for BlockTexture {
+ fn file_name(self) -> &'static str {
+ match self {
+ Self::Stone => "stone.png",
+ Self::Dirt => "dirt.png",
+ Self::GrassTop => "grass_top.png",
+ Self::GrassSide => "grass_side.png",
+ Self::Sand => "sand.png",
+ Self::Bedrock => "bedrock.png",
+ Self::Wood => "wood.png",
+ Self::WoodTop => "wood_top.png",
+ Self::Leaf => "leaf.png",
+ Self::Torch => "torch.png",
+ Self::TallGrass => "tall_grass.png",
+ Self::Snow => "snow.png",
+ Self::GrassSideSnow => "grass_side_snow.png",
+ }
+ }
+}
+
+#[derive(Unique)]
+pub struct BlockTexturesPrefab(pub SrgbTexture2dArray);
+
+#[derive(Unique)]
+pub struct ChunkShaderPrefab(pub Program);
+
+#[derive(Unique)]
+pub struct SelBoxShaderPrefab(pub Program);
+
+pub fn load_prefabs(
+ storages: AllStoragesView,
+ renderer: NonSendSync>
+) {
+ storages.add_unique_non_send_sync(BlockTexturesPrefab(
+ load_texture2darray_prefab::(
+ "./assets/blocks/".into(),
+ &renderer.display,
+ MipmapsOption::AutoGeneratedMipmaps
+ )
+ ));
+ storages.add_unique_non_send_sync(ChunkShaderPrefab(
+ include_shader_prefab!(
+ "../shaders/world.vert",
+ "../shaders/world.frag",
+ &renderer.display
+ )
+ ));
+ storages.add_unique_non_send_sync(SelBoxShaderPrefab(
+ include_shader_prefab!(
+ "../shaders/selection_box.vert",
+ "../shaders/selection_box.frag",
+ &renderer.display
+ )
+ ));
+}
diff --git a/src/prefabs/shaders.rs b/src/prefabs/shaders.rs
new file mode 100644
index 0000000..97bffa6
--- /dev/null
+++ b/src/prefabs/shaders.rs
@@ -0,0 +1,31 @@
+
+
+macro_rules! include_shader_prefab {
+ ($vert: literal, $frag: literal, $geom: literal, $facade: expr) => {
+ {
+ use ::glium::Program;
+ log::info!("↓↓↓ compiling shader prefab ↓↓↓");
+ log::info!("{} {} {}", $vert, $frag, $geom);
+ Program::from_source(
+ &*$facade,
+ include_str!($vert),
+ include_str!($frag),
+ Some(include_str!($geom)),
+ ).expect("Failed to compile gpu program")
+ }
+ };
+ ($vert: literal, $frag: literal, $facade: expr) => {
+ {
+ use ::glium::Program;
+ log::info!("↓↓↓ compiling shader prefab ↓↓↓");
+ log::info!("{} {}", $vert, $frag);
+ Program::from_source(
+ &*$facade,
+ include_str!($vert),
+ include_str!($frag),
+ None,
+ ).expect("Failed to compile gpu program")
+ }
+ };
+}
+pub(crate) use include_shader_prefab;
diff --git a/src/prefabs/texture.rs b/src/prefabs/texture.rs
new file mode 100644
index 0000000..cf2ae4c
--- /dev/null
+++ b/src/prefabs/texture.rs
@@ -0,0 +1,44 @@
+use strum::IntoEnumIterator;
+use rayon::prelude::*;
+use std::{fs::File, path::PathBuf, io::BufReader};
+use glium::{texture::{SrgbTexture2dArray, RawImage2d, MipmapsOption}, backend::Facade};
+use super::AssetPaths;
+
+pub fn load_texture2darray_prefab<
+ T: AssetPaths + IntoEnumIterator,
+ E: Facade
+>(
+ directory: PathBuf,
+ facade: &E,
+ mipmaps: MipmapsOption,
+) -> SrgbTexture2dArray {
+ log::info!("↓↓↓ loading textures {} ↓↓↓", directory.as_os_str().to_str().unwrap());
+ //Load raw images
+ let tex_files: Vec<&'static str> = T::iter().map(|x| x.file_name()).collect();
+ let raw_images: Vec> = tex_files.par_iter().map(|&file_name| {
+ log::info!("loading texture {}", file_name);
+ //Get path to the image and open the file
+ let reader = {
+ let path = directory.join(file_name);
+ BufReader::new(File::open(path).expect("Failed to open texture file"))
+ };
+ //Parse image data
+ let (image_data, dimensions) = {
+ let image =image::load(
+ reader,
+ image::ImageFormat::Png
+ ).unwrap().to_rgba8();
+ let dimensions = image.dimensions();
+ (image.into_raw(), dimensions)
+ };
+ //Create a glium RawImage
+ RawImage2d::from_raw_rgba_reversed(
+ &image_data,
+ dimensions
+ )
+ }).collect();
+ log::info!("done loading texture files, uploading to the gpu");
+ //Upload images to the GPU
+ SrgbTexture2dArray::with_mipmaps(facade, raw_images, mipmaps)
+ .expect("Failed to upload texture array to GPU")
+}
diff --git a/src/rendering.rs b/src/rendering.rs
new file mode 100644
index 0000000..9389e76
--- /dev/null
+++ b/src/rendering.rs
@@ -0,0 +1,46 @@
+use shipyard::{Unique, NonSendSync, UniqueView, UniqueViewMut};
+use glium::{
+ Display, Surface,
+ glutin::{
+ event_loop::EventLoop,
+ window::WindowBuilder,
+ ContextBuilder, GlProfile
+ },
+};
+use glam::Vec3;
+
+pub mod primitives;
+pub mod world;
+pub mod selection_box;
+
+#[derive(Unique)]
+pub struct RenderTarget(pub glium::Frame);
+
+#[derive(Unique)]
+pub struct BackgroundColor(pub Vec3);
+
+#[derive(Unique)]
+pub struct Renderer {
+ pub display: Display
+}
+impl Renderer {
+ pub fn init(event_loop: &EventLoop<()>) -> Self {
+ log::info!("initializing display");
+ let wb = WindowBuilder::new()
+ .with_title("uwu")
+ .with_maximized(true);
+ let cb = ContextBuilder::new()
+ .with_depth_buffer(24)
+ .with_gl_profile(GlProfile::Core);
+ let display = Display::new(wb, cb, event_loop)
+ .expect("Failed to create a glium Display");
+ Self { display }
+ }
+}
+
+pub fn clear_background(
+ mut target: NonSendSync>,
+ color: UniqueView,
+) {
+ target.0.clear_color_srgb_and_depth((color.0.x, color.0.y, color.0.z, 1.), 1.);
+}
diff --git a/src/rendering/primitives.rs b/src/rendering/primitives.rs
new file mode 100644
index 0000000..7efa2e5
--- /dev/null
+++ b/src/rendering/primitives.rs
@@ -0,0 +1,32 @@
+pub const CUBE_VERTICES: &[f32] = &[
+ // front
+ 0.0, 0.0, 1.0,
+ 1.0, 0.0, 1.0,
+ 1.0, 1.0, 1.0,
+ 0.0, 1.0, 1.0,
+ // back
+ 0.0, 0.0, 0.0,
+ 1.0, 0.0, 0.0,
+ 1.0, 1.0, 0.0,
+ 0.0, 1.0, 0.0
+];
+pub const CUBE_INDICES: &[u16] = &[
+ // front
+ 0, 1, 2,
+ 2, 3, 0,
+ // right
+ 1, 5, 6,
+ 6, 2, 1,
+ // back
+ 7, 6, 5,
+ 5, 4, 7,
+ // left
+ 4, 0, 3,
+ 3, 7, 4,
+ // bottom
+ 4, 5, 1,
+ 1, 0, 4,
+ // top
+ 3, 2, 6,
+ 6, 7, 3
+];
diff --git a/src/rendering/selection_box.rs b/src/rendering/selection_box.rs
new file mode 100644
index 0000000..febfe47
--- /dev/null
+++ b/src/rendering/selection_box.rs
@@ -0,0 +1,91 @@
+use shipyard::{View, IntoIter, NonSendSync, UniqueViewMut, UniqueView};
+use glium::{
+ Surface,
+ implement_vertex,
+ IndexBuffer,
+ index::PrimitiveType,
+ VertexBuffer, uniform,
+ DrawParameters,
+ BackfaceCullingMode,
+ Blend, Depth, DepthTest,
+};
+use crate::{
+ world::raycast::LookingAtBlock,
+ camera::Camera, prefabs::SelBoxShaderPrefab
+};
+use super::{
+ RenderTarget,
+ primitives::{CUBE_INDICES, CUBE_VERTICES}, Renderer
+};
+
+#[derive(Clone, Copy, Default)]
+pub struct SelBoxVertex {
+ pub position: [f32; 3],
+}
+implement_vertex!(SelBoxVertex, position);
+
+const fn box_vertices() -> [SelBoxVertex; CUBE_VERTICES.len() / 3] {
+ let mut arr = [SelBoxVertex { position: [0., 0., 0.] }; CUBE_VERTICES.len() / 3];
+ let mut ptr = 0;
+ loop {
+ arr[ptr] = SelBoxVertex {
+ position: [
+ CUBE_VERTICES[ptr * 3],
+ CUBE_VERTICES[(ptr * 3) + 1],
+ CUBE_VERTICES[(ptr * 3) + 2]
+ ]
+ };
+ ptr += 1;
+ if ptr >= CUBE_VERTICES.len() / 3 {
+ return arr
+ }
+ }
+}
+const BOX_VERTICES: &[SelBoxVertex] = &box_vertices();
+
+//wip
+pub fn render_selection_box(
+ lookat: View,
+ camera: View,
+ mut target: NonSendSync>,
+ display: NonSendSync>,
+ program: NonSendSync>,
+) {
+ let camera = camera.iter().next().unwrap();
+ let Some(lookat) = lookat.iter().next() else { return };
+ let Some(lookat) = lookat.0 else { return };
+
+ //this may be slow but the amount of vertices is very low
+ let vert = VertexBuffer::new(
+ &display.display,
+ BOX_VERTICES
+ ).unwrap();
+
+ let index = IndexBuffer::new(
+ &display.display,
+ PrimitiveType::TrianglesList,
+ CUBE_INDICES
+ ).unwrap();
+
+ //Darken block
+ target.0.draw(
+ &vert,
+ &index,
+ &program.0,
+ &uniform! {
+ u_color: [0., 0., 0., 0.5_f32],
+ u_position: lookat.block_position.as_vec3().to_array(),
+ perspective: camera.perspective_matrix.to_cols_array_2d(),
+ view: camera.view_matrix.to_cols_array_2d(),
+ },
+ &DrawParameters {
+ backface_culling: BackfaceCullingMode::CullClockwise,
+ blend: Blend::alpha_blending(),
+ depth: Depth {
+ test: DepthTest::IfLessOrEqual, //this may be unreliable!
+ ..Default::default()
+ },
+ ..Default::default()
+ }
+ ).unwrap();
+}
diff --git a/src/rendering/world.rs b/src/rendering/world.rs
new file mode 100644
index 0000000..2cd543c
--- /dev/null
+++ b/src/rendering/world.rs
@@ -0,0 +1,107 @@
+use glam::Vec3;
+use shipyard::{NonSendSync, UniqueView, UniqueViewMut, View, IntoIter};
+use glium::{
+ implement_vertex, uniform,
+ Surface, DrawParameters,
+ uniforms::{
+ Sampler,
+ SamplerBehavior,
+ MinifySamplerFilter,
+ MagnifySamplerFilter,
+ SamplerWrapFunction
+ },
+ draw_parameters::{
+ Depth,
+ DepthTest,
+ PolygonMode,
+ BackfaceCullingMode,
+ }
+};
+use crate::{
+ camera::Camera,
+ prefabs::{
+ ChunkShaderPrefab,
+ BlockTexturesPrefab,
+ },
+ world::{
+ ChunkStorage,
+ ChunkMeshStorage,
+ chunk::CHUNK_SIZE,
+ },
+};
+use super::RenderTarget;
+
+#[derive(Clone, Copy)]
+pub struct ChunkVertex {
+ pub position: [f32; 3],
+ pub normal: [f32; 3],
+ pub uv: [f32; 2],
+ pub tex_index: u8,
+}
+implement_vertex!(ChunkVertex, position, normal, uv, tex_index);
+
+
+pub fn draw_world(
+ mut target: NonSendSync>,
+ chunks: UniqueView,
+ meshes: NonSendSync>,
+ program: NonSendSync>,
+ texture: NonSendSync>,
+ camera: View,
+) {
+ let camera = camera.iter().next().expect("No cameras in the scene");
+ let draw_parameters = DrawParameters {
+ depth: Depth {
+ test: DepthTest::IfLess,
+ write: true,
+ ..Default::default()
+ },
+ polygon_mode: PolygonMode::Fill, //Change to Line for wireframe
+ backface_culling: BackfaceCullingMode::CullClockwise,
+ ..Default::default()
+ };
+ let texture_sampler = Sampler(&texture.0, SamplerBehavior {
+ minify_filter: MinifySamplerFilter::LinearMipmapLinear,
+ magnify_filter: MagnifySamplerFilter::Nearest,
+ max_anisotropy: 8,
+ wrap_function: (SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp, SamplerWrapFunction::Clamp),
+ ..Default::default()
+ });
+ let view = camera.view_matrix.to_cols_array_2d();
+ let perspective = camera.perspective_matrix.to_cols_array_2d();
+
+ for (&position, chunk) in &chunks.chunks {
+ if let Some(key) = chunk.mesh_index {
+ let mesh = meshes.get(key).expect("Mesh index pointing to nothing");
+ let world_position = position.as_vec3() * CHUNK_SIZE as f32;
+
+ //Skip mesh if its empty
+ if mesh.index_buffer.len() == 0 {
+ continue
+ }
+
+ //Frustum culling
+ {
+ let minp = world_position;
+ let maxp = world_position + Vec3::splat(CHUNK_SIZE as f32);
+ if !camera.frustum.is_box_visible(minp, maxp) {
+ continue
+ }
+ }
+
+ //Draw chunk mesh
+ target.0.draw(
+ &mesh.vertex_buffer,
+ &mesh.index_buffer,
+ &program.0,
+ &uniform! {
+ position_offset: world_position.to_array(),
+ view: view,
+ perspective: perspective,
+ tex: texture_sampler,
+ },
+ &draw_parameters
+ ).unwrap();
+ }
+ }
+}
diff --git a/src/settings.rs b/src/settings.rs
new file mode 100644
index 0000000..5e80b67
--- /dev/null
+++ b/src/settings.rs
@@ -0,0 +1,16 @@
+use shipyard::Unique;
+
+#[derive(Unique)]
+pub struct GameSettings {
+ //there's a 1 chunk border of loaded but invisible around this
+ pub render_distance: u8,
+ pub mouse_sensitivity: f32,
+}
+impl Default for GameSettings {
+ fn default() -> Self {
+ Self {
+ render_distance: 5,
+ mouse_sensitivity: 1.,
+ }
+ }
+}
diff --git a/src/transform.rs b/src/transform.rs
new file mode 100644
index 0000000..5087b6c
--- /dev/null
+++ b/src/transform.rs
@@ -0,0 +1,6 @@
+use shipyard::Component;
+use glam::Mat4;
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+#[track(All)]
+pub struct Transform(pub Mat4);
diff --git a/src/world.rs b/src/world.rs
new file mode 100644
index 0000000..ff75456
--- /dev/null
+++ b/src/world.rs
@@ -0,0 +1,111 @@
+use nohash_hasher::BuildNoHashHasher;
+use shipyard::{Unique, AllStoragesView};
+use glam::IVec3;
+use hashbrown::HashMap;
+use anyhow::{Result, Context};
+
+pub mod chunk;
+pub mod block;
+pub mod tasks;
+pub mod loading;
+pub mod mesh;
+pub mod neighbors;
+pub mod worldgen;
+pub mod raycast;
+
+use chunk::{Chunk, ChunkMesh};
+use tasks::ChunkTaskManager;
+
+use self::{chunk::CHUNK_SIZE, block::Block};
+
+//TODO separate world struct for render data
+// because this is not send-sync
+
+#[derive(Default, Unique)]
+#[track(Modification)]
+pub struct ChunkStorage {
+ pub chunks: HashMap
+}
+impl ChunkStorage {
+ pub const fn to_chunk_coords(position: IVec3) -> (IVec3, IVec3) {
+ (
+ IVec3::new(
+ position.x.div_euclid(CHUNK_SIZE as i32),
+ position.y.div_euclid(CHUNK_SIZE as i32),
+ position.z.div_euclid(CHUNK_SIZE as i32),
+ ),
+ IVec3::new(
+ position.x.rem_euclid(CHUNK_SIZE as i32),
+ position.y.rem_euclid(CHUNK_SIZE as i32),
+ position.z.rem_euclid(CHUNK_SIZE as i32),
+ )
+ )
+ }
+ pub fn get_block(&self, position: IVec3) -> Option {
+ let (chunk, block) = Self::to_chunk_coords(position);
+ let block = self.chunks
+ .get(&chunk)?
+ .block_data.as_ref()?
+ .blocks.get(block.x as usize)?
+ .get(block.y as usize)?
+ .get(block.z as usize)?;
+ Some(*block)
+ }
+ pub fn get_block_mut(&mut self, position: IVec3) -> Option<&mut Block> {
+ let (chunk, block) = Self::to_chunk_coords(position);
+ let block = self.chunks
+ .get_mut(&chunk)?
+ .block_data.as_mut()?
+ .blocks.get_mut(block.x as usize)?
+ .get_mut(block.y as usize)?
+ .get_mut(block.z as usize)?;
+ Some(block)
+ }
+ pub fn new() -> Self {
+ Self::default()
+ }
+}
+
+#[derive(Unique)]
+pub struct WorldInfo {
+ pub seed: u32,
+}
+
+#[derive(Default, Unique)]
+pub struct ChunkMeshStorage {
+ meshes: HashMap>,
+ index: usize,
+}
+impl ChunkMeshStorage {
+ pub fn new() -> Self {
+ Self {
+ meshes: HashMap::with_capacity_and_hasher(250, BuildNoHashHasher::default()),
+ index: 0,
+ }
+ }
+ pub fn insert(&mut self, mesh: ChunkMesh) -> usize {
+ let index = self.index;
+ self.meshes.insert_unique_unchecked(index, mesh);
+ self.index += 1;
+ index
+ }
+ pub fn update(&mut self, key: usize, mesh: ChunkMesh) -> Result<()> {
+ *self.meshes.get_mut(&key).context("Chunk doesn't exist")? = mesh;
+ Ok(())
+ }
+ pub fn remove(&mut self, key: usize) -> Result<()> {
+ self.meshes.remove(&key).context("Chunk doesn't exist")?;
+ Ok(())
+ }
+ pub fn get(&self, key: usize) -> Option<&ChunkMesh> {
+ self.meshes.get(&key)
+ }
+}
+
+pub fn init_game_world(
+ storages: AllStoragesView,
+) {
+ storages.add_unique_non_send_sync(ChunkMeshStorage::new());
+ storages.add_unique(ChunkStorage::new());
+ storages.add_unique(ChunkTaskManager::new());
+}
diff --git a/src/world/block.rs b/src/world/block.rs
new file mode 100644
index 0000000..92fba77
--- /dev/null
+++ b/src/world/block.rs
@@ -0,0 +1,105 @@
+use strum::EnumIter;
+use crate::prefabs::BlockTexture;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter)]
+#[repr(u8)]
+pub enum Block {
+ Air,
+ Stone,
+ Dirt,
+ Grass,
+ Sand,
+}
+impl Block {
+ pub const fn descriptor(self) -> BlockDescriptor {
+ match self {
+ Self::Air => BlockDescriptor {
+ name: "air",
+ render: RenderType::None,
+ collision: CollisionType::None,
+ raycast_collision: false,
+ },
+ Self::Stone => BlockDescriptor {
+ name: "stone",
+ render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Stone)),
+ collision: CollisionType::Solid,
+ raycast_collision: true,
+ },
+ Self::Dirt => BlockDescriptor {
+ name: "dirt",
+ render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Dirt)),
+ collision: CollisionType::Solid,
+ raycast_collision: true,
+ },
+ Self::Grass => BlockDescriptor {
+ name: "grass",
+ render: RenderType::SolidBlock(CubeTexture::top_sides_bottom(
+ BlockTexture::GrassTop,
+ BlockTexture::GrassSide,
+ BlockTexture::Dirt
+ )),
+ collision: CollisionType::Solid,
+ raycast_collision: true,
+ },
+ Self::Sand => BlockDescriptor {
+ name: "sand",
+ render: RenderType::SolidBlock(CubeTexture::all(BlockTexture::Sand)),
+ collision: CollisionType::Solid,
+ raycast_collision: true,
+ },
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+pub struct BlockDescriptor {
+ pub name: &'static str,
+ pub render: RenderType,
+ pub collision: CollisionType,
+ pub raycast_collision: bool,
+}
+// impl BlockDescriptor {
+// pub fn of(block: Block) -> Self {
+// block.descriptor()
+// }
+// }
+
+#[derive(Clone, Copy, Debug)]
+pub struct CubeTexture {
+ pub top: BlockTexture,
+ pub bottom: BlockTexture,
+ pub left: BlockTexture,
+ pub right: BlockTexture,
+ pub front: BlockTexture,
+ pub back: BlockTexture,
+}
+impl CubeTexture {
+ pub const fn top_sides_bottom(top: BlockTexture, sides: BlockTexture, bottom: BlockTexture) -> Self {
+ Self {
+ top,
+ bottom,
+ left: sides,
+ right: sides,
+ front: sides,
+ back: sides,
+ }
+ }
+ pub const fn horizontal_vertical(horizontal: BlockTexture, vertical: BlockTexture) -> Self {
+ Self::top_sides_bottom(vertical, horizontal, vertical)
+ }
+ pub const fn all(texture: BlockTexture) -> Self {
+ Self::horizontal_vertical(texture, texture)
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum CollisionType {
+ None,
+ Solid,
+}
+
+#[derive(Clone, Copy, Debug)]
+pub enum RenderType {
+ None,
+ SolidBlock(CubeTexture)
+}
diff --git a/src/world/chunk.rs b/src/world/chunk.rs
new file mode 100644
index 0000000..672d767
--- /dev/null
+++ b/src/world/chunk.rs
@@ -0,0 +1,65 @@
+use glam::IVec3;
+use glium::{VertexBuffer, IndexBuffer};
+use super::block::Block;
+use crate::rendering::world::ChunkVertex;
+
+pub const CHUNK_SIZE: usize = 32;
+
+pub type BlockData = Box<[[[Block; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]>;
+
+pub struct ChunkData {
+ pub blocks: BlockData,
+ //pub has_renderable_blocks: bool,
+}
+impl ChunkData {
+ // pub fn update_metadata(&mut self) {
+ // todo!()
+ // }
+}
+
+pub struct ChunkMesh {
+ pub vertex_buffer: VertexBuffer,
+ pub index_buffer: IndexBuffer,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
+pub enum CurrentChunkState {
+ #[default]
+ Nothing,
+ Loading,
+ Loaded,
+ CalculatingMesh,
+ Rendered,
+ RecalculatingMesh,
+ Unloading,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
+pub enum DesiredChunkState {
+ #[default]
+ Nothing,
+ Loaded,
+ Rendered,
+ ToUnload,
+}
+
+pub struct Chunk {
+ pub position: IVec3,
+ pub block_data: Option,
+ pub mesh_index: Option,
+ pub current_state: CurrentChunkState,
+ pub desired_state: DesiredChunkState,
+ pub dirty: bool,
+}
+impl Chunk {
+ pub fn new(position: IVec3) -> Self {
+ Self {
+ position,
+ block_data: None,
+ mesh_index: None,
+ current_state: Default::default(),
+ desired_state: Default::default(),
+ dirty: false,
+ }
+ }
+}
diff --git a/src/world/loading.rs b/src/world/loading.rs
new file mode 100644
index 0000000..a686316
--- /dev/null
+++ b/src/world/loading.rs
@@ -0,0 +1,225 @@
+use glam::{IVec3, ivec3};
+use glium::{VertexBuffer, IndexBuffer, index::PrimitiveType};
+use shipyard::{View, UniqueView, UniqueViewMut, IntoIter, Workload, IntoWorkload, NonSendSync};
+use crate::{
+ player::MainPlayer,
+ transform::Transform,
+ settings::GameSettings,
+ rendering::Renderer
+};
+use super::{
+ ChunkStorage, ChunkMeshStorage,
+ chunk::{Chunk, DesiredChunkState, CHUNK_SIZE, ChunkMesh, CurrentChunkState, ChunkData},
+ tasks::{ChunkTaskManager, ChunkTaskResponse, ChunkTask},
+};
+
+//todo limit task starts insted
+const MAX_CHUNK_OPS: usize = 8;
+
+pub fn update_loaded_world_around_player() -> Workload {
+ (
+ update_chunks_if_player_moved,
+ unload_downgrade_chunks,
+ start_required_tasks,
+ process_completed_tasks,
+ ).into_workload()
+}
+
+pub fn update_chunks_if_player_moved(
+ v_settings: UniqueView,
+ v_local_player: View,
+ v_transform: View,
+ mut vm_world: UniqueViewMut,
+) {
+ //Check if the player actually moved
+ //TODO fix this also triggers on rotation, only activate when the player crosses the chnk border
+ let Some((_, transform)) = (&v_local_player, v_transform.inserted_or_modified()).iter().next() else {
+ return
+ };
+
+ //Read game settings
+ let load_distance = (v_settings.render_distance + 1) as i32;
+
+ //If it did, get it's position and current chunk
+ let player_position = transform.0.to_scale_rotation_translation().2;
+ let player_position_ivec3 = player_position.as_ivec3();
+ let player_at_chunk = ivec3(
+ player_position_ivec3.x.div_euclid(CHUNK_SIZE as i32),
+ player_position_ivec3.y.div_euclid(CHUNK_SIZE as i32),
+ player_position_ivec3.z.div_euclid(CHUNK_SIZE as i32),
+ );
+
+ //Then, mark *ALL* chunks with ToUnload
+ for (_, chunk) in &mut vm_world.chunks {
+ chunk.desired_state = DesiredChunkState::ToUnload;
+ }
+
+ //Then mark chunks that are near to the player
+ for x in -load_distance..=load_distance {
+ for y in -load_distance..=load_distance {
+ for z in -load_distance..=load_distance {
+ let chunk_pos_offset = ivec3(x, y, z);
+ let chunk_pos = player_at_chunk + chunk_pos_offset;
+ let is_border = {
+ chunk_pos_offset.x.abs() == load_distance ||
+ chunk_pos_offset.y.abs() == load_distance ||
+ chunk_pos_offset.z.abs() == load_distance
+ };
+ //If chunk doesn't exist create it
+ let chunk = match vm_world.chunks.get_mut(&chunk_pos) {
+ Some(chunk) => chunk,
+ None => {
+ let chunk = Chunk::new(chunk_pos);
+ vm_world.chunks.insert_unique_unchecked(chunk_pos, chunk);
+ vm_world.chunks.get_mut(&chunk_pos).unwrap()
+ }
+ };
+ let desired = match is_border {
+ true => DesiredChunkState::Loaded,
+ false => DesiredChunkState::Rendered,
+ };
+ chunk.desired_state = desired;
+ }
+ }
+ }
+}
+
+fn unload_downgrade_chunks(
+ mut vm_world: UniqueViewMut,
+ mut vm_meshes: NonSendSync>
+) {
+ if !vm_world.is_modified() {
+ return
+ }
+ //TODO refactor this
+ vm_world.chunks.retain(|_, chunk| {
+ if chunk.desired_state == DesiredChunkState::ToUnload {
+ if let Some(mesh_index) = chunk.mesh_index {
+ vm_meshes.remove(mesh_index).unwrap();
+ }
+ false
+ } else {
+ match chunk.desired_state {
+ DesiredChunkState::Loaded if matches!(chunk.current_state, CurrentChunkState::Rendered | CurrentChunkState::CalculatingMesh | CurrentChunkState::RecalculatingMesh) => {
+ if let Some(mesh_index) = chunk.mesh_index {
+ vm_meshes.remove(mesh_index).unwrap();
+ }
+ chunk.mesh_index = None;
+ chunk.current_state = CurrentChunkState::Loaded;
+ },
+ _ => (),
+ }
+ true
+ }
+ })
+}
+
+fn start_required_tasks(
+ task_manager: UniqueView,
+ mut world: UniqueViewMut,
+) {
+ if !world.is_modified() {
+ return
+ }
+ //HACK: cant iterate over chunks.keys() or chunk directly!
+ let hashmap_keys: Vec = world.chunks.keys().copied().collect();
+ for position in hashmap_keys {
+ let chunk = world.chunks.get(&position).unwrap();
+ match chunk.desired_state {
+ DesiredChunkState::Loaded | DesiredChunkState::Rendered if chunk.current_state == CurrentChunkState::Nothing => {
+ //start load task
+ task_manager.spawn_task(ChunkTask::LoadChunk {
+ seed: 0xbeef_face_dead_cafe,
+ position
+ });
+ //Update chunk state
+ let chunk = world.chunks.get_mut(&position).unwrap();
+ chunk.current_state = CurrentChunkState::Loading;
+ // ===========
+ //log::trace!("Started loading chunk {position}");
+ },
+ DesiredChunkState::Rendered if (chunk.current_state == CurrentChunkState::Loaded || chunk.dirty) => {
+ //get needed data
+ let Some(neighbors) = world.neighbors_all(position) else {
+ continue
+ };
+ let Some(data) = neighbors.mesh_data() else {
+ continue
+ };
+ //spawn task
+ task_manager.spawn_task(ChunkTask::GenerateMesh { data, position });
+ //Update chunk state
+ let chunk = world.chunks.get_mut(&position).unwrap();
+ if chunk.dirty {
+ chunk.current_state = CurrentChunkState::RecalculatingMesh;
+ chunk.dirty = false;
+ } else {
+ chunk.current_state = CurrentChunkState::CalculatingMesh;
+ }
+ // ===========
+ //log::trace!("Started generating mesh for chunk {position}");
+ }
+ _ => ()
+ }
+ }
+}
+
+fn process_completed_tasks(
+ task_manager: UniqueView,
+ mut world: UniqueViewMut,
+ mut meshes: NonSendSync>,
+ renderer: NonSendSync>
+) {
+ for _ in 0..MAX_CHUNK_OPS {
+ if let Some(res) = task_manager.receive() {
+ match res {
+ ChunkTaskResponse::LoadedChunk { position, chunk_data } => {
+ //check if chunk exists
+ let Some(chunk) = world.chunks.get_mut(&position) else {
+ log::warn!("blocks data discarded: chunk doesn't exist");
+ return
+ };
+
+ //check if chunk still wants it
+ if !matches!(chunk.desired_state, DesiredChunkState::Loaded | DesiredChunkState::Rendered) {
+ log::warn!("block data discarded: state undesirable: {:?}", chunk.desired_state);
+ return
+ }
+
+ //set the block data
+ chunk.block_data = Some(ChunkData {
+ blocks: chunk_data
+ });
+
+ //update chunk state
+ chunk.current_state = CurrentChunkState::Loaded;
+ },
+ ChunkTaskResponse::GeneratedMesh { position, vertices, indexes } => {
+ //check if chunk exists
+ let Some(chunk) = world.chunks.get_mut(&position) else {
+ log::warn!("mesh discarded: chunk doesn't exist");
+ return
+ };
+
+ //check if chunk still wants it
+ if chunk.desired_state != DesiredChunkState::Rendered {
+ log::warn!("mesh discarded: state undesirable: {:?}", chunk.desired_state);
+ return
+ }
+
+ //apply the mesh
+ let vertex_buffer = VertexBuffer::new(&renderer.display, &vertices).unwrap();
+ let index_buffer = IndexBuffer::new(&renderer.display, PrimitiveType::TrianglesList, &indexes).unwrap();
+ let mesh_index = meshes.insert(ChunkMesh {
+ vertex_buffer,
+ index_buffer,
+ });
+ chunk.mesh_index = Some(mesh_index);
+
+ //update chunk state
+ chunk.current_state = CurrentChunkState::Rendered;
+ }
+ }
+ }
+ }
+}
diff --git a/src/world/mesh.rs b/src/world/mesh.rs
new file mode 100644
index 0000000..f991594
--- /dev/null
+++ b/src/world/mesh.rs
@@ -0,0 +1,138 @@
+use strum::{EnumIter, IntoEnumIterator};
+use glam::{Vec3A, vec3a, IVec3, ivec3};
+use std::mem::discriminant;
+use super::{chunk::CHUNK_SIZE, block::{Block, RenderType}};
+use crate::rendering::world::ChunkVertex;
+
+pub mod data;
+use data::MeshGenData;
+
+#[repr(usize)]
+#[derive(Clone, Copy, Debug, EnumIter)]
+pub enum CubeFace {
+ Top = 0,
+ Front = 1,
+ Left = 2,
+ Right = 3,
+ Back = 4,
+ Bottom = 5,
+}
+const CUBE_FACE_VERTICES: [[Vec3A; 4]; 6] = [
+ [vec3a(0., 1., 0.), vec3a(0., 1., 1.), vec3a(1., 1., 0.), vec3a(1., 1., 1.)],
+ [vec3a(0., 0., 0.), vec3a(0., 1., 0.), vec3a(1., 0., 0.), vec3a(1., 1., 0.)],
+ [vec3a(0., 0., 1.), vec3a(0., 1., 1.), vec3a(0., 0., 0.), vec3a(0., 1., 0.)],
+ [vec3a(1., 0., 0.), vec3a(1., 1., 0.), vec3a(1., 0., 1.), vec3a(1., 1., 1.)],
+ [vec3a(1., 0., 1.), vec3a(1., 1., 1.), vec3a(0., 0., 1.), vec3a(0., 1., 1.)],
+ [vec3a(0., 0., 1.), vec3a(0., 0., 0.), vec3a(1., 0., 1.), vec3a(1., 0., 0.)],
+];
+const CUBE_FACE_NORMALS: [Vec3A; 6] = [
+ vec3a(0., 1., 0.),
+ vec3a(0., 0., -1.),
+ vec3a(-1.,0., 0.),
+ vec3a(1., 0., 0.),
+ vec3a(0., 0., 1.),
+ vec3a(0., -1.,0.)
+];
+const CUBE_FACE_INDICES: [u32; 6] = [0, 1, 2, 2, 1, 3];
+const UV_COORDS: [[f32; 2]; 4] = [
+ [0., 0.],
+ [0., 1.],
+ [1., 0.],
+ [1., 1.],
+];
+
+#[derive(Default)]
+struct MeshBuilder {
+ vertex_buffer: Vec,
+ index_buffer: Vec,
+ idx_counter: u32,
+}
+impl MeshBuilder {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn add_face(&mut self, face: CubeFace, coord: IVec3, texture: u8) {
+ let coord = coord.as_vec3a();
+ let face_index = face as usize;
+
+ //Push vertexes
+ let norm = CUBE_FACE_NORMALS[face_index];
+ let vert = CUBE_FACE_VERTICES[face_index];
+ self.vertex_buffer.reserve(4);
+ for i in 0..4 {
+ self.vertex_buffer.push(ChunkVertex {
+ position: (coord + vert[i]).to_array(),
+ normal: norm.to_array(),
+ uv: UV_COORDS[i],
+ tex_index: texture
+ });
+ }
+
+ //Push indices
+ self.index_buffer.extend_from_slice(&CUBE_FACE_INDICES.map(|x| x + self.idx_counter));
+ self.idx_counter += 4;
+ }
+
+ pub fn finish(self) -> (Vec, Vec) {
+ (self.vertex_buffer, self.index_buffer)
+ }
+}
+
+pub fn generate_mesh(data: MeshGenData) -> (Vec, Vec) {
+ let get_block = |pos: IVec3| -> Block {
+ if pos.x < 0 {
+ data.block_data_neg_x[(CHUNK_SIZE as i32 + pos.x) as usize][pos.y as usize][pos.z as usize]
+ } else if pos.x >= CHUNK_SIZE as i32 {
+ data.block_data_pos_x[pos.x as usize - CHUNK_SIZE][pos.y as usize][pos.z as usize]
+ } else if pos.y < 0 {
+ data.block_data_neg_y[pos.x as usize][(CHUNK_SIZE as i32 + pos.y) as usize][pos.z as usize]
+ } else if pos.y >= CHUNK_SIZE as i32 {
+ data.block_data_pos_y[pos.x as usize][pos.y as usize - CHUNK_SIZE][pos.z as usize]
+ } else if pos.z < 0 {
+ data.block_data_neg_z[pos.x as usize][pos.y as usize][(CHUNK_SIZE as i32 + pos.z) as usize]
+ } else if pos.z >= CHUNK_SIZE as i32 {
+ data.block_data_pos_z[pos.x as usize][pos.y as usize][pos.z as usize - CHUNK_SIZE]
+ } else {
+ data.block_data[pos.x as usize][pos.y as usize][pos.z as usize]
+ }
+ };
+
+ let mut builder = MeshBuilder::new();
+
+ for x in 0..CHUNK_SIZE {
+ for y in 0..CHUNK_SIZE {
+ for z in 0..CHUNK_SIZE {
+ let coord = ivec3(x as i32, y as i32, z as i32);
+ let block = get_block(coord);
+ let descriptor = block.descriptor();
+ if matches!(descriptor.render, RenderType::None) {
+ continue
+ }
+ for face in CubeFace::iter() {
+ let facing = CUBE_FACE_NORMALS[face as usize].as_ivec3();
+ let facing_coord = coord + facing;
+ let show = discriminant(&get_block(facing_coord).descriptor().render) != discriminant(&descriptor.render);
+ if show {
+ match descriptor.render {
+ RenderType::SolidBlock(textures) => {
+ let face_texture = match face {
+ CubeFace::Top => textures.top,
+ CubeFace::Front => textures.front,
+ CubeFace::Left => textures.left,
+ CubeFace::Right => textures.right,
+ CubeFace::Back => textures.back,
+ CubeFace::Bottom => textures.bottom,
+ };
+ builder.add_face(face, coord, face_texture as u8);
+ },
+ _ => unimplemented!()
+ }
+ }
+ }
+ }
+ }
+ }
+
+ builder.finish()
+}
diff --git a/src/world/mesh/data.rs b/src/world/mesh/data.rs
new file mode 100644
index 0000000..403d1d4
--- /dev/null
+++ b/src/world/mesh/data.rs
@@ -0,0 +1,34 @@
+use crate::world::{
+ neighbors::AllChunkNeighbors,
+ chunk::BlockData
+};
+
+pub struct MeshGenData {
+ pub block_data: BlockData,
+ pub block_data_pos_z: BlockData,
+ pub block_data_neg_z: BlockData,
+ pub block_data_pos_y: BlockData,
+ pub block_data_neg_y: BlockData,
+ pub block_data_pos_x: BlockData,
+ pub block_data_neg_x: BlockData,
+}
+impl<'a> AllChunkNeighbors<'a> {
+ pub fn mesh_data(&self) -> Option {
+ let center_block_data = self.center.block_data.as_ref()?;
+ let front_block_data = self.front.block_data.as_ref()?;
+ let back_block_data = self.back.block_data.as_ref()?;
+ let top_block_data = self.top.block_data.as_ref()?;
+ let bottom_block_data = self.bottom.block_data.as_ref()?;
+ let right_block_data = self.right.block_data.as_ref()?;
+ let left_block_data = self.left.block_data.as_ref()?;
+ Some(MeshGenData {
+ block_data: center_block_data.blocks.clone(),
+ block_data_pos_z: front_block_data.blocks.clone(),
+ block_data_neg_z: back_block_data.blocks.clone(),
+ block_data_pos_y: top_block_data.blocks.clone(),
+ block_data_neg_y: bottom_block_data.blocks.clone(),
+ block_data_pos_x: right_block_data.blocks.clone(),
+ block_data_neg_x: left_block_data.blocks.clone(),
+ })
+ }
+}
diff --git a/src/world/neighbors.rs b/src/world/neighbors.rs
new file mode 100644
index 0000000..5151bce
--- /dev/null
+++ b/src/world/neighbors.rs
@@ -0,0 +1,122 @@
+use glam::{IVec3, ivec3};
+use super::chunk::Chunk;
+
+#[derive(Clone, Copy)]
+pub struct ChunkNeighbors<'a> {
+ pub center: Option<&'a Chunk>,
+ pub top: Option<&'a Chunk>,
+ pub bottom: Option<&'a Chunk>,
+ pub left: Option<&'a Chunk>,
+ pub right: Option<&'a Chunk>,
+ pub front: Option<&'a Chunk>,
+ pub back: Option<&'a Chunk>,
+}
+#[derive(Clone, Copy)]
+pub struct AllChunkNeighbors<'a> {
+ pub center: &'a Chunk,
+ pub top: &'a Chunk,
+ pub bottom: &'a Chunk,
+ pub left: &'a Chunk,
+ pub right: &'a Chunk,
+ pub front: &'a Chunk,
+ pub back: &'a Chunk,
+}
+pub struct AllChunkNeighborsMut<'a> {
+ pub center: &'a mut Chunk,
+ pub top: &'a mut Chunk,
+ pub bottom: &'a mut Chunk,
+ pub left: &'a mut Chunk,
+ pub right: &'a mut Chunk,
+ pub front: &'a mut Chunk,
+ pub back: &'a mut Chunk,
+}
+
+impl<'a> ChunkNeighbors<'a> {
+ pub fn all(&self) -> Option> {
+ Some(AllChunkNeighbors {
+ center: self.center?,
+ top: self.top?,
+ bottom: self.bottom?,
+ left: self.left?,
+ right: self.right?,
+ front: self.front?,
+ back: self.back?,
+ })
+ }
+}
+impl<'a> From> for AllChunkNeighbors<'a> {
+ fn from(neighbors: AllChunkNeighborsMut<'a>) -> Self {
+ AllChunkNeighbors {
+ center: neighbors.center,
+ top: neighbors.top,
+ bottom: neighbors.bottom,
+ left: neighbors.left,
+ right: neighbors.right,
+ front: neighbors.front,
+ back: neighbors.back,
+ }
+ }
+}
+impl<'a> From> for ChunkNeighbors<'a> {
+ fn from(neighbors: AllChunkNeighbors<'a>) -> Self {
+ ChunkNeighbors {
+ center: Some(neighbors.center),
+ top: Some(neighbors.top),
+ bottom: Some(neighbors.bottom),
+ left: Some(neighbors.left),
+ right: Some(neighbors.right),
+ front: Some(neighbors.front),
+ back: Some(neighbors.back),
+ }
+ }
+}
+impl<'a> From> for ChunkNeighbors<'a> {
+ fn from(neighbors: AllChunkNeighborsMut<'a>) -> Self {
+ ChunkNeighbors {
+ center: Some(neighbors.center),
+ top: Some(neighbors.top),
+ bottom: Some(neighbors.bottom),
+ left: Some(neighbors.left),
+ right: Some(neighbors.right),
+ front: Some(neighbors.front),
+ back: Some(neighbors.back),
+ }
+ }
+}
+
+impl super::ChunkStorage {
+ pub fn neighbors(&self, coords: IVec3) -> ChunkNeighbors {
+ ChunkNeighbors {
+ center: self.chunks.get(&coords),
+ top: self.chunks.get(&(coords - ivec3(0, 1, 0))),
+ bottom: self.chunks.get(&(coords + ivec3(0, 1, 0))),
+ left: self.chunks.get(&(coords - ivec3(1, 0, 0))),
+ right: self.chunks.get(&(coords + ivec3(1, 0, 0))),
+ front: self.chunks.get(&(coords + ivec3(0, 0, 1))),
+ back: self.chunks.get(&(coords - ivec3(0, 0, 1))),
+ }
+ }
+ pub fn neighbors_all(&self, coords: IVec3) -> Option {
+ self.neighbors(coords).all()
+ }
+ pub fn neighbors_all_mut(&mut self, coords: IVec3) -> Option {
+ let [
+ center,
+ top,
+ bottom,
+ left,
+ right,
+ front,
+ back
+ ] = self.chunks.get_many_mut([
+ &coords,
+ &(coords - ivec3(0, 1, 0)),
+ &(coords + ivec3(0, 1, 0)),
+ &(coords - ivec3(1, 0, 0)),
+ &(coords + ivec3(1, 0, 0)),
+ &(coords + ivec3(0, 0, 1)),
+ &(coords - ivec3(0, 0, 1)),
+ ])?;
+ Some(AllChunkNeighborsMut { center, top, bottom, left, right, front, back })
+ }
+}
diff --git a/src/world/raycast.rs b/src/world/raycast.rs
new file mode 100644
index 0000000..3f2f407
--- /dev/null
+++ b/src/world/raycast.rs
@@ -0,0 +1,65 @@
+use glam::{Vec3, IVec3};
+use shipyard::{View, Component, ViewMut, IntoIter, UniqueView};
+use crate::transform::Transform;
+
+use super::{ChunkStorage, block::Block};
+
+const RAYCAST_STEP: f32 = 0.25;
+
+#[derive(Clone, Copy, Debug)]
+pub struct RaycastReport {
+ pub length: f32,
+ pub position: Vec3,
+ pub direction: Vec3,
+ pub block_position: IVec3,
+ pub block: Block,
+}
+
+impl ChunkStorage {
+ //this is probably pretty slow...
+ pub fn raycast(&self, origin: Vec3, direction: Vec3, limit: Option) -> Option {
+ debug_assert!(direction.is_normalized(), "Ray direction not normalized");
+ let mut position = origin;
+ let mut length = 0.;
+ loop {
+ let block_position = position.floor().as_ivec3();
+ if let Some(block) = self.get_block(block_position) {
+ if block.descriptor().raycast_collision {
+ return Some(RaycastReport {
+ length,
+ position,
+ direction,
+ block_position,
+ block
+ });
+ }
+ }
+ length += RAYCAST_STEP;
+ position += direction * RAYCAST_STEP;
+ if let Some(limit) = limit {
+ if length > limit {
+ return None;
+ }
+ }
+ }
+ }
+}
+
+#[derive(Component, Clone, Copy, Debug, Default)]
+pub struct LookingAtBlock(pub Option);
+
+pub fn update_raycasts(
+ transform: View,
+ mut raycast: ViewMut,
+ world: UniqueView,
+) {
+ //idk if this check is even needed
+ if !(world.is_inserted_or_modified() || (transform.inserted_or_modified(), &raycast).iter().next().is_some()) {
+ return
+ }
+ for (transform, report) in (&transform, &mut raycast).iter() {
+ let (_, rotation, position) = transform.0.to_scale_rotation_translation();
+ let direction = rotation * Vec3::NEG_Z;
+ *report = LookingAtBlock(world.raycast(position, direction, Some(30.)));
+ }
+}
diff --git a/src/world/tasks.rs b/src/world/tasks.rs
new file mode 100644
index 0000000..2c73c16
--- /dev/null
+++ b/src/world/tasks.rs
@@ -0,0 +1,64 @@
+use flume::{Sender, Receiver};
+use glam::IVec3;
+use shipyard::Unique;
+use rayon::{ThreadPool, ThreadPoolBuilder};
+use super::{
+ chunk::BlockData,
+ mesh::{generate_mesh, data::MeshGenData},
+ worldgen::generate_world,
+};
+use crate::rendering::world::ChunkVertex;
+
+pub enum ChunkTask {
+ LoadChunk {
+ seed: u64,
+ position: IVec3
+ },
+ GenerateMesh {
+ position: IVec3,
+ data: MeshGenData
+ }
+}
+pub enum ChunkTaskResponse {
+ LoadedChunk {
+ position: IVec3,
+ chunk_data: BlockData,
+ },
+ GeneratedMesh {
+ position: IVec3,
+ vertices: Vec,
+ indexes: Vec
+ },
+}
+
+#[derive(Unique)]
+pub struct ChunkTaskManager {
+ channel: (Sender, Receiver),
+ pool: ThreadPool,
+}
+impl ChunkTaskManager {
+ pub fn new() -> Self {
+ Self {
+ channel: flume::unbounded::(), //maybe put a bound or even bound(0)?
+ pool: ThreadPoolBuilder::new().num_threads(4).build().unwrap()
+ }
+ }
+ pub fn spawn_task(&self, task: ChunkTask) {
+ let sender = self.channel.0.clone();
+ self.pool.spawn(move || {
+ let _ = sender.send(match task {
+ ChunkTask::GenerateMesh { position, data } => {
+ let (vertices, indexes) = generate_mesh(data);
+ ChunkTaskResponse::GeneratedMesh { position, vertices, indexes }
+ },
+ ChunkTask::LoadChunk { position, seed } => {
+ let chunk_data = generate_world(position, seed);
+ ChunkTaskResponse::LoadedChunk { position, chunk_data }
+ }
+ });
+ });
+ }
+ pub fn receive(&self) -> Option {
+ self.channel.1.try_recv().ok()
+ }
+}
diff --git a/src/world/worldgen.rs b/src/world/worldgen.rs
new file mode 100644
index 0000000..471055c
--- /dev/null
+++ b/src/world/worldgen.rs
@@ -0,0 +1,49 @@
+use glam::{IVec3, ivec3};
+use bracket_noise::prelude::*;
+use super::{
+ chunk::{BlockData, CHUNK_SIZE},
+ block::Block
+};
+
+pub fn generate_world(chunk_position: IVec3, seed: u64) -> BlockData {
+ let offset = chunk_position * CHUNK_SIZE as i32;
+
+ let mut cave_noise = FastNoise::seeded(seed);
+ cave_noise.set_fractal_type(FractalType::FBM);
+ cave_noise.set_frequency(0.1);
+
+ let mut dirt_noise = FastNoise::seeded(seed.rotate_left(1));
+ dirt_noise.set_fractal_type(FractalType::FBM);
+ dirt_noise.set_frequency(0.1);
+
+ let mut blocks = Box::new([[[Block::Air; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]);
+
+ if chunk_position.y >= 0 {
+ if chunk_position.y == 0 {
+ for x in 0..CHUNK_SIZE {
+ for z in 0..CHUNK_SIZE {
+ blocks[x][0][z] = Block::Dirt;
+ blocks[x][1][z] = Block::Grass;
+ }
+ }
+ }
+ } else {
+ for x in 0..CHUNK_SIZE {
+ for y in 0..CHUNK_SIZE {
+ for z in 0..CHUNK_SIZE {
+ let position = ivec3(x as i32, y as i32, z as i32) + offset;
+ let v_cave_noise = cave_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32 - 10.0).clamp(0., 1.);
+ let v_dirt_noise = dirt_noise.get_noise3d(position.x as f32, position.y as f32, position.z as f32) * (-position.y as f32).clamp(0., 1.);
+ if v_cave_noise > 0.5 {
+ blocks[x][y][z] = Block::Stone;
+ } else if v_dirt_noise > 0.5 {
+ blocks[x][y][z] = Block::Dirt;
+ }
+ }
+ }
+ }
+ }
+
+
+ blocks
+}